Initial commit

This commit is contained in:
Jan-Henrik Bruhn 2025-04-14 16:34:51 +02:00
commit 4173087c77
11 changed files with 1014 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json
.pio

BIN
backplate.stl Normal file

Binary file not shown.

85
stand.scad Normal file
View file

@ -0,0 +1,85 @@
panel_width = 95;
panel_height = 200;
thickness = 3;
screw_right_right = 17.7;
screw_right_bottom = 8.8;
screw_left_left = 13.8;
screw_left_bottom = 9;
screw_top_right_right = 10.5;
screw_top_right_bottom = 108;
screw_top_left_left = 10.5;
screw_top_left_bottom = 84;
backside_hold_height = 50;
bottom_margin_height = 30;
total_height = backside_hold_height + bottom_margin_height;
backside_standoff_height = 17;
long_thingy_height = 116;
long_thingy_width = 18;
/*
difference() {
union() {
translate([0, -bottom_margin_height, 0]) cube([panel_width, total_height, thickness]);
translate([25, 10, -backside_standoff_height])cylinder(h=backside_standoff_height, d=8, $fn=20);
translate([panel_width - 25, 10, -backside_standoff_height])cylinder(h=backside_standoff_height, d=8, $fn=20);
translate([10, long_thingy_height, -backside_standoff_height])cylinder(h=backside_standoff_height, d=8, $fn=20);
translate([panel_width - 10, long_thingy_height, -backside_standoff_height])cylinder(h=backside_standoff_height, d=8, $fn=20);
cube([long_thingy_width, long_thingy_height, thickness]);
translate([panel_width - long_thingy_width, 0, 0]) cube([long_thingy_width, long_thingy_height, thickness]);
translate([0, long_thingy_height, 0]) cube([panel_width, 8, thickness]);
translate([4, 12.7, -thickness+2])cube([panel_width/8, 2, 1]);
translate([4, 18.3, -thickness+2])cube([panel_width/8, 2, 1]);
}
// knobs
translate([panel_width - bottom_margin_height/2, -bottom_margin_height/2, -1]) cylinder(h = 20, d = 7.2, $fn=20);
translate([panel_width - bottom_margin_height/2 - 25, -bottom_margin_height/2, -1]) cylinder(h = 20, d = 7.2, $fn=20);
// screws
translate([panel_width - screw_right_right, screw_right_bottom, -1]) cylinder(h = 20, d = 3.2, $fn=20);
translate([screw_left_left, screw_left_bottom, -1]) cylinder(h = 20, d = 3.2, $fn=20);
translate([panel_width - screw_top_right_right, screw_top_right_bottom, -1]) cylinder(h = 20, d = 3.2, $fn=20);
translate([screw_top_left_left, screw_top_left_bottom, -1]) cylinder(h = 20, d = 3.2, $fn=20);
translate([10, long_thingy_height, -1-backside_standoff_height]) cylinder(h = 30, d = 3, $fn=20);
translate([panel_width - 10, long_thingy_height, -1-backside_standoff_height]) cylinder(h = 30, d = 3, $fn=20);
translate([25, 10, -1-backside_standoff_height]) cylinder(h = 30, d = 3, $fn=20);
translate([panel_width - 25, 10, -1-backside_standoff_height]) cylinder(h = 30, d = 3, $fn=20);
}*/
color("blue") translate([0, 0, -backside_standoff_height-thickness]) difference() {
union() {
cube([panel_width, long_thingy_height+50, thickness]);
translate([0, 15, 0]) cube([panel_width/4, 3, backside_standoff_height+thickness-.2]);
}
translate([panel_width / 2, long_thingy_height+40, -1]) cylinder(h=20, d=8, $fn=20);
translate([10.5, 20, 12]) rotate([90, 0, 0]) cylinder(h = 20, d = 10, $fn=20);
translate([10, long_thingy_height, -1-backside_standoff_height]) cylinder(h = 30, d = 3.2, $fn=20);
translate([panel_width - 10, long_thingy_height, -1-backside_standoff_height]) cylinder(h = 30, d = 3.2, $fn=20);
translate([25, 10, -1-backside_standoff_height]) cylinder(h = 30, d = 3.2, $fn=20);
translate([panel_width - 25, 10, -1-backside_standoff_height]) cylinder(h = 30, d = 3.2, $fn=20);
}

5
transigione/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
transigione/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View file

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
transigione/lib/README Normal file
View file

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View file

@ -0,0 +1,29 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
build_flags =
-DCORE_DEBUG_LEVEL=3
lib_deps =
mrfaptastic/ESP32 HUB75 LED MATRIX PANEL DMA Display@^3.0.12
adafruit/Adafruit GFX Library@^1.12.0
madhephaestus/ESP32Encoder@^0.11.7
arduinogetstarted/ezButton@^1.0.6
johboh/HomeAssistantEntities@^8.0.6
johboh/MQTTRemote@^5.0.2
bblanchon/ArduinoJson@^7.4.1
juerd/ESP-WiFiSettings@^3.9.2
upload_protocol = espota
upload_port = transigione-902e41.local

783
transigione/src/main.cpp Normal file
View file

@ -0,0 +1,783 @@
#include <Arduino.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <ESP32Encoder.h>
#include <ezButton.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <NetworkUdp.h>
#include <ArduinoOTA.h>
#include <ArduinoJson.h>
#include <MQTTRemote.h>
#include <HaBridge.h>
#include <entities/HaEntityLight.h>
#include <SPIFFS.h>
#include <WiFiSettings.h>
// Information about this device.
// All these keys will be added to a "device" key in the Home Assistant configuration for each entity.
// Only a flat layout structure is supported, no nesting.
// We call the setupJsonForThisDevice() from the ardunio setup() function to populate the Json document.
// IJsonDocument can be replaced with nlohmann-json::json or ArduinoJson::JsonDocument
IJsonDocument _json_this_device_doc;
void setupJsonForThisDevice(String name) {
_json_this_device_doc["identifiers"] = name;
_json_this_device_doc["name"] = "Transigione";
_json_this_device_doc["sw_version"] = "1.0.0";
_json_this_device_doc["model"] = "transgione_matrix";
_json_this_device_doc["manufacturer"] = "jhbruhn";
}
MQTTRemote* _mqtt_remote;
// Create the Home Assistant bridge. This is shared across all entities.
// We only have one per device/hardware. In our example, the name of our device is "livingroom".
// See constructor of HaBridge for more documentation.
HaBridge* ha_bridge;
// Create the two lights with the "Human readable" strings. This what will show up in Home Assistant.
// As we have two entities of the same type (light) for the same device, we need to add a child object
// id to separate them.
HaEntityLight* _ha_entity_light;
ESP32Encoder encoder1;
ezButton encoder1Button(21);
ESP32Encoder encoder2;
ezButton encoder2Button(22);
MatrixPanel_I2S_DMA *dma_display = nullptr;
uint16_t myBLACK = dma_display->color565(0, 0, 0);
uint16_t myWHITE = dma_display->color565(255, 255, 255);
uint16_t myRED = dma_display->color565(255, 0, 0);
uint16_t myGREEN = dma_display->color565(0, 255, 0);
uint16_t myBLUE = dma_display->color565(0, 0, 255);
#define XMAX 64
#define YMAX 32
#define BRIGHTNESS 127
void displaySetup() {
HUB75_I2S_CFG mxconfig(
64, // module width
32, // module height
1 // Chain length
);
// If you are using a 64x64 matrix you need to pass a value for the E pin
// The trinity connects GPIO 18 to E.
// This can be commented out for any smaller displays (but should work fine with it)
//mxconfig.gpio.e = 18;
// May or may not be needed depending on your matrix
// Example of what needing it looks like:
// https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/134#issuecomment-866367216
mxconfig.clkphase = false;
// Some matrix panels use different ICs for driving them and some of them have strange quirks.
// If the display is not working right, try this.
//mxconfig.driver = HUB75_I2S_CFG::FM6126A;
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
dma_display->begin();
}
void setup_ota() {
ArduinoOTA.setHostname(WiFiSettings.hostname.c_str());
ArduinoOTA.begin();
}
bool power = true;
uint8_t brightness = BRIGHTNESS;
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println("Moini");
displaySetup();
Serial.println("Moin");
encoder1Button.setDebounceTime(50);
encoder2Button.setDebounceTime(50);
ESP32Encoder::useInternalWeakPullResistors = puType::none;
encoder1.attachSingleEdge(34, 35);
encoder2.attachSingleEdge(32, 33);
encoder1.setFilter(1023);
encoder2.setFilter(1023);
SPIFFS.begin(true); // On first run, will format after failing to mount
WiFiSettings.hostname = "transigione-";
setupJsonForThisDevice(WiFiSettings.hostname);
String mqtt_host = WiFiSettings.string( "mqtt_host", "default.example.org");
int mqtt_port = WiFiSettings.integer("mqtt_port", 0, 65535, 1883);
String mqtt_username = WiFiSettings.string("mqtt_username", "username");
String mqtt_password = WiFiSettings.string("mqtt_password", "password");
// Set callbacks to start OTA when the portal is active
WiFiSettings.onPortal = []() {
setup_ota();
};
WiFiSettings.onPortalWaitLoop = []() {
ArduinoOTA.handle();
};
WiFiSettings.connect();
_mqtt_remote = new MQTTRemote(WiFiSettings.hostname.c_str(), mqtt_host.c_str(), 1883, mqtt_username.c_str(), mqtt_password.c_str());
ha_bridge = new HaBridge(*_mqtt_remote, WiFiSettings.hostname.c_str(), _json_this_device_doc);
_ha_entity_light = new HaEntityLight(*ha_bridge, "Light", "light", {.with_brightness = true});
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_SPIFFS
type = "filesystem";
}
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
})
.onEnd([]() {
Serial.println("\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
}
});
// Can be set between 0 and 255
// WARNING: The birghter it is, the more power it uses
// Could take up to 3A on full brightness
dma_display->setBrightness8(BRIGHTNESS); //0-255
dma_display->clearScreen();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
_mqtt_remote->setOnConnectionChange([](bool connected) {
if (connected) {
Serial.println("We are in");
// Publish Home Assistant Configuration for both lights once connected to MQTT.
_ha_entity_light->publishConfiguration();
_ha_entity_light->publishIsOn(power);
_ha_entity_light->publishBrightness(brightness << 1);
// Subscribe to new light "on" state pushed by Home Assistant.
_ha_entity_light->setOnOn([&](bool on) {
power = on;
_ha_entity_light->publishIsOn(power);
});
_ha_entity_light->setOnBrightness(
[&](uint8_t b) { Serial.println("Got brightness " + String(b) + " for light"); brightness = b >> 1;
_ha_entity_light->publishBrightness(b); });
}
});
setup_ota();
}
void transition1();
void transition2();
void transition3();
void transition4();
void transition5();
void transition6();
uint8_t parameter = 0;
void loop() {
ArduinoOTA.handle();
_mqtt_remote->handle();
encoder1Button.loop();
encoder2Button.loop();
if (encoder1Button.isPressed() || encoder2Button.isPressed()) {
power = !power;
_ha_entity_light->publishIsOn(power);
}
dma_display->setBrightness8(power ? brightness : 0);
uint8_t program = encoder2.getCount();
parameter = encoder1.getCount();
switch (program % 6) {
case 0:
transition6();
break;
case 1:
transition2();
break;
case 2:
transition3();
break;
case 3:
transition4();
break;
case 4:
transition5();
break;
case 5:
transition1();
break;
}
}
uint16_t interpolateRGB565(uint16_t color1, uint16_t color2, uint8_t t) {
// Extract the RGB components from color1
uint8_t r1 = (color1 >> 11) & 0x1F;
uint8_t g1 = (color1 >> 5) & 0x3F;
uint8_t b1 = color1 & 0x1F;
// Extract the RGB components from color2
uint8_t r2 = (color2 >> 11) & 0x1F;
uint8_t g2 = (color2 >> 5) & 0x3F;
uint8_t b2 = color2 & 0x1F;
// Calculate the difference for each component
int16_t rdiff = r2 - r1;
int16_t gdiff = g2 - g1;
int16_t bdiff = b2 - b1;
// Interpolate each component
// Using 32-bit integers for intermediate calculations to avoid overflow
uint8_t r = r1 + ((rdiff * t + 128) >> 8);
uint8_t g = g1 + ((gdiff * t + 128) >> 8);
uint8_t b = b1 + ((bdiff * t + 128) >> 8);
// Combine the components back into a 16-bit RGB565 color
return ((uint16_t)r << 11) | ((uint16_t)g << 5) | b;
}
/**
* Interpolates between two RGB565 colors using HSV color space with fixed-point arithmetic.
* No floating point operations are used. Ensures smooth color transitions.
*
* @param color1 Starting color in RGB565 format (5 bits R, 6 bits G, 5 bits B)
* @param color2 Ending color in RGB565 format (5 bits R, 6 bits G, 5 bits B)
* @param t Interpolation factor from 0 to 255 (0 = color1, 255 = color2)
* @return Interpolated color in RGB565 format
*/
uint16_t interpolateHSV565(uint16_t color1, uint16_t color2, uint8_t t) {
// Extract RGB components from RGB565 colors
uint8_t r1 = (color1 >> 11) & 0x1F;
uint8_t g1 = (color1 >> 5) & 0x3F;
uint8_t b1 = color1 & 0x1F;
uint8_t r2 = (color2 >> 11) & 0x1F;
uint8_t g2 = (color2 >> 5) & 0x3F;
uint8_t b2 = color2 & 0x1F;
// Scale up to 8 bits for better precision
r1 = (r1 << 3) | (r1 >> 2);
g1 = (g1 << 2) | (g1 >> 4);
b1 = (b1 << 3) | (b1 >> 2);
r2 = (r2 << 3) | (r2 >> 2);
g2 = (g2 << 2) | (g2 >> 4);
b2 = (b2 << 3) | (b2 >> 2);
// Convert RGB to HSV for both colors (using fixed-point)
uint16_t h1, s1, v1;
uint16_t h2, s2, v2;
// For color1
uint8_t max1 = r1 > g1 ? (r1 > b1 ? r1 : b1) : (g1 > b1 ? g1 : b1);
uint8_t min1 = r1 < g1 ? (r1 < b1 ? r1 : b1) : (g1 < b1 ? g1 : b1);
uint16_t delta1 = max1 - min1;
// Value is easy
v1 = max1;
// Saturation (scaled to 0-255)
s1 = max1 == 0 ? 0 : ((uint32_t)delta1 * 255) / max1;
// Hue (scaled to 0-1535 for fixed-point representation, 0-360 degrees * 4.26)
if (delta1 == 0) {
h1 = 0; // undefined, default to red
} else if (max1 == r1) {
int16_t diff = g1 - b1;
h1 = 0 + (((int32_t)diff * 256) / delta1);
if (h1 < 0) h1 += 1536;
} else if (max1 == g1) {
int16_t diff = b1 - r1;
h1 = 512 + (((int32_t)diff * 256) / delta1);
} else { // max1 == b1
int16_t diff = r1 - g1;
h1 = 1024 + (((int32_t)diff * 256) / delta1);
}
// For color2 (same calculations)
uint8_t max2 = r2 > g2 ? (r2 > b2 ? r2 : b2) : (g2 > b2 ? g2 : b2);
uint8_t min2 = r2 < g2 ? (r2 < b2 ? r2 : b2) : (g2 < b2 ? g2 : b2);
uint16_t delta2 = max2 - min2;
v2 = max2;
s2 = max2 == 0 ? 0 : ((uint32_t)delta2 * 255) / max2;
if (delta2 == 0) {
h2 = 0;
} else if (max2 == r2) {
int16_t diff = g2 - b2;
h2 = 0 + (((int32_t)diff * 256) / delta2);
if (h2 < 0) h2 += 1536;
} else if (max2 == g2) {
int16_t diff = b2 - r2;
h2 = 512 + (((int32_t)diff * 256) / delta2);
} else { // max2 == b2
int16_t diff = r2 - g2;
h2 = 1024 + (((int32_t)diff * 256) / delta2);
}
// Handle special case for grayscale colors (undefined hue)
if (s1 < 8) h1 = h2;
if (s2 < 8) h2 = h1;
// Interpolate HSV components
uint16_t h_interp, s_interp, v_interp;
// Hue needs special handling for the shortest path around the circle
int16_t h_diff = h2 - h1;
if (h_diff > 768) h_diff -= 1536;
else if (h_diff < -768) h_diff += 1536;
// Use 32-bit arithmetic for the interpolation to avoid overflow issues
h_interp = h1 + (((int32_t)h_diff * t) >> 8);
if (h_interp >= 1536) h_interp -= 1536;
else if (h_interp < 0) h_interp += 1536;
s_interp = s1 + (((int32_t)(s2 - s1) * t) >> 8);
v_interp = v1 + (((int32_t)(v2 - v1) * t) >> 8);
// Convert interpolated HSV back to RGB using more precise fixed-point math
uint8_t r, g, b;
uint8_t region = h_interp / 256;
uint16_t remainder = (h_interp % 256);
// These calculations now use 32-bit fixed-point to avoid rounding errors
uint32_t p = ((uint32_t)v_interp * (255 - s_interp)) / 255;
uint32_t q = ((uint32_t)v_interp * (255 - ((s_interp * remainder) / 256))) / 255;
uint32_t t_val = ((uint32_t)v_interp * (255 - ((s_interp * (255 - remainder)) / 256))) / 255;
switch (region) {
case 0:
r = v_interp;
g = t_val;
b = p;
break;
case 1:
r = q;
g = v_interp;
b = p;
break;
case 2:
r = p;
g = v_interp;
b = t_val;
break;
case 3:
r = p;
g = q;
b = v_interp;
break;
case 4:
r = t_val;
g = p;
b = v_interp;
break;
default: // case 5
r = v_interp;
g = p;
b = q;
break;
}
// Convert back to RGB565 format
r = (r >> 3);
g = (g >> 2);
b = (b >> 3);
return (r << 11) | (g << 5) | b;
}
uint16_t RGB888ToRGB565(uint32_t rgb888) {
// Extract 8-bit color components
uint8_t red = (rgb888 >> 16) & 0xFF;
uint8_t green = (rgb888 >> 8) & 0xFF;
uint8_t blue = rgb888 & 0xFF;
// Convert 8-bit components to 5-6-5 bit format
uint16_t r = (red >> 3) & 0x1F; // 5 bits for red
uint16_t g = (green >> 2) & 0x3F; // 6 bits for green
uint16_t b = (blue >> 3) & 0x1F; // 5 bits for blue
// Combine the components into a 16-bit value
return (r << 11) | (g << 5) | b;
}
void transition1() {
const int color1 = dma_display->color565(0xdf, 0xf5, 0x4f);
const int color2 = dma_display->color565(0x8d, 0x1f, 0xf2);
static uint8_t state[YMAX];
static unsigned long last_move_time = 0;
if (last_move_time == 0) {
for (int y = 0; y < YMAX; y++) {
state[y] = 127;
}
}
if (last_move_time + ((parameter + 50) % 255) < millis()) {
for (int y = 0; y < YMAX; y++) {
int move_length = rand() - RAND_MAX / 2;
int dir = state[y] < 64 || state[y] > 191 ? 4 : 1;
if (move_length > 0) {
dir = -dir;
}
state[y] += dir;
if (state[y] < 0) state[y] = 0;
if (state[y] > 255) state[y] = 255;
}
last_move_time = millis();
}
for (int x = 0; x < XMAX; x++) {
for(int y = 0; y < YMAX; y++) {
int t = x << 2;
t += state[y] - 127;
if (t <= 0) t = 1;
if (t >= 255) t = 254;
int color = interpolateHSV565(color1, color2, t);
dma_display->drawPixel(x, y, color);
}
}
}
void transition2() {
const int color1 = dma_display->color565(0xdf, 0xf5, 0x4f);
const int color2 = dma_display->color565(0x8d, 0x1f, 0xf2);
const int color3 = dma_display->color565(0x6f, 0x55, 0xff);
const int color4 = dma_display->color565(0x1d, 0x9f, 0x23);
static unsigned long last_step = 0;
static uint8_t t1 = 0;
static uint8_t t2 = 254;
static int8_t dir1 = 1;
static int8_t dir2 = 1;
if (last_step + (255-parameter) < millis()) {
t1 += dir1;
t2 += dir2;
if (t1 == 255) {
t1 = 254;
dir1 = -1;
}
if (t1 == 0) {
t1 = 1;
dir1 = 1;
}
if (t2 == 255) {
t2 = 254;
dir2 = -1;
}
if (t2 == 0) {
t2 = 1;
dir2 = 1;
}
last_step = millis();
for (int x = 0; x < XMAX; x++) {
for(int y = 0; y < YMAX; y++) {
int colorA = interpolateHSV565(color1, color2, t1);
int colorB = interpolateHSV565(color3, color4, t2);
int color = interpolateRGB565(colorA, colorB, x << 2);
dma_display->drawPixel(x, y, color);
}
}
}
}
void transition3() {
//0x274001, 0x828a00, 0xf29f05, 0xf25c05, 0xd6568c, 0x4d8584, 0xa62f03, 0x400d01
const int color1 = RGB888ToRGB565(0x274001);
const int color2 = RGB888ToRGB565(0x828a00);
const int color3 = RGB888ToRGB565(0xf29f05);
const int color4 = RGB888ToRGB565(0xf25c05);
const int color5 = RGB888ToRGB565(0xd6568c);
const int color6 = RGB888ToRGB565(0x4d8584);
const int color7 = RGB888ToRGB565(0xa62f03);
const int color8 = RGB888ToRGB565(0x400d01);
static unsigned long last_step = 0;
static uint8_t t1 = 0;
static uint8_t t2 = 0;
static int8_t dir1 = 1;
static int8_t dir2 = 1;
if (last_step + 100 < millis()) {
t1 += dir1;
t2 += dir2;
if (t1 == 255) {
t1 = 254;
dir1 = -1;
}
if (t1 == 0) {
t1 = 1;
dir1 = 1;
}
if (t2 == 255) {
t2 = 254;
dir2 = -1;
}
if (t2 == 0) {
t2 = 1;
dir2 = 1;
}
last_step = millis();
for (int x = 0; x < XMAX; x++) {
for(int y = 0; y < YMAX; y++) {
int colorA = interpolateHSV565(color1, color5, t1);
int colorB = interpolateHSV565(color2, color6, t2);
int colorC = interpolateHSV565(color3, color7, t1);
int colorD = interpolateHSV565(color4, color8, t2);
int colorX = interpolateRGB565(colorA, colorB, x << 2);
int colorY = interpolateRGB565(colorC, colorD, x << 2);
int color = interpolateRGB565(colorX, colorY, y << 3);
dma_display->drawPixel(x, y, color);
}
}
}
}
void transition4() {
const uint8_t NUM_COLORS = 4;
const uint8_t NUM_PALETTES = 8;
// Color palettes (up to 4 colors per palette)
const uint16_t palettes[NUM_PALETTES][NUM_COLORS] = {
// 0: Soft purples and blues
{ 0x780F, 0x401F, 0x001F, 0x4010 },
// 1: Warm tones
{ 0xF800, 0xFA60, 0xFFE0, 0xFD20 },
// 2: Cool tones
{ 0x001F, 0x07FF, 0x05BF, 0x041F },
// 3: Fire
{ 0xF800, 0xFC00, 0xFFE0, 0xFCA0 },
// 4: Ocean
{ 0x001F, 0x021F, 0x03FF, 0x05DF },
// 5: Sunset
{ 0xF800, 0xF81F, 0x780F, 0x001F },
// 6: Neon
{ 0xF81F, 0x07FF, 0xFFE0, 0x07E0 },
// 7: Grayscale
{ 0x0000, 0x528A, 0xAD55, 0xFFFF }
};
// Select the active palette here (index 0-7)
uint8_t currentPaletteIndex = parameter % 8;
const uint32_t TRANSITION_DURATION = 200000UL;
uint32_t t = millis();
const uint16_t* currentPalette = palettes[currentPaletteIndex];
uint32_t totalCycle = NUM_COLORS * TRANSITION_DURATION;
uint32_t localTime = t % totalCycle;
uint8_t index1 = (localTime / TRANSITION_DURATION) % NUM_COLORS;
uint8_t index2 = (index1 + 1) % NUM_COLORS;
uint16_t timeInStep = localTime % TRANSITION_DURATION;
uint8_t blendT = (timeInStep * 255UL) / TRANSITION_DURATION;
for (int x = 0; x < XMAX; x++) {
for (int y = 0; y < YMAX; y++) {
uint8_t offset = ((x + y) * 2 + (t >> 4)) & 0x3F;
uint8_t finalBlend = (blendT + offset) & 0xFF;
uint16_t color = interpolateRGB565(currentPalette[index1], currentPalette[index2], finalBlend);
dma_display->drawPixel(x, y, color);
}
}
}
void transition5() {
static const uint8_t NUM_COLORS = 4;
static const uint8_t NUM_PALETTES = 8;
static const uint32_t COLOR_DURATION = 100000UL; // 100s per color blend
static const uint32_t PALETTE_BLEND_DURATION = 200000UL; // 200s per full palette blend
// More vibrant and distinct palettes
static const uint16_t palettes[NUM_PALETTES][NUM_COLORS] = {
{ 0xF81F, 0x07FF, 0xFFE0, 0x07E0 }, // Neon Glow
{ 0xF800, 0xFC00, 0xFFE0, 0xFFFF }, // Lava
{ 0x001F, 0x07FF, 0x05BF, 0x87FF }, // Ocean
{ 0x07FF, 0x780F, 0xFD20, 0xFFFF }, // Retro
{ 0x001F, 0xF81F, 0x780F, 0x07E0 }, // Cyberpunk
{ 0x07E0, 0xF81F, 0x780F, 0x07FF }, // Aurora
{ 0xF800, 0xFFE0, 0x07E0, 0x001F }, // Rainbow
{ 0x7800, 0x780F, 0xFD20, 0xFBE0 } // Sunset
};
uint32_t t = millis();
// Time into palette blend cycle
uint32_t paletteTime = t % PALETTE_BLEND_DURATION;
uint8_t paletteIndex1 = (t / PALETTE_BLEND_DURATION) % NUM_PALETTES;
uint8_t paletteIndex2 = (paletteIndex1 + 1) % NUM_PALETTES;
// Blend between two palettes
uint8_t paletteBlendT = (paletteTime * 255UL) / PALETTE_BLEND_DURATION;
// Color transition timing
uint32_t localColorTime = t % (NUM_COLORS * COLOR_DURATION);
uint8_t colorIndex1 = (localColorTime / COLOR_DURATION) % NUM_COLORS;
uint8_t colorIndex2 = (colorIndex1 + 1) % NUM_COLORS;
uint16_t colorStepTime = localColorTime % COLOR_DURATION;
uint8_t baseBlend = (colorStepTime * 255UL) / COLOR_DURATION;
// Blend the two palettes into a temporary working palette
uint16_t blendedPalette[NUM_COLORS];
for (uint8_t i = 0; i < NUM_COLORS; i++) {
blendedPalette[i] = interpolateRGB565(
palettes[paletteIndex1][i],
palettes[paletteIndex2][i],
paletteBlendT
);
}
// Center coordinates
int16_t centerX = XMAX / 2;
int16_t centerY = YMAX / 2;
for (int x = 0; x < XMAX; x++) {
for (int y = 0; y < YMAX; y++) {
int16_t dx = x - centerX;
int16_t dy = y - centerY;
uint16_t distance = (uint16_t)sqrt(dx * dx + dy * dy);
uint8_t wave = (distance * 8 + (t >> 5)) & 0xFF;
uint8_t finalBlend = (baseBlend + wave) & 0xFF;
uint16_t color = interpolateRGB565(
blendedPalette[colorIndex1],
blendedPalette[colorIndex2],
finalBlend
);
dma_display->drawPixel(x, y, color);
}
}
}
void transition6() {
static const uint8_t NUM_COLORS = 4;
static const uint8_t NUM_SEGMENTS = NUM_COLORS; // for smooth wrap (4 segments: 0→1→2→3→0)
static const uint8_t NUM_PALETTES = 8;
static const uint32_t COLOR_DURATION = 200000UL; // 200s scroll loop
static const uint32_t PALETTE_BLEND_DURATION = 800000UL; // 800s palette transition
static const uint8_t palettes[NUM_PALETTES][NUM_COLORS][3] = {
{ {255, 0, 0}, {255, 127, 0}, {255, 255, 0}, {0, 255, 0} }, // Rainbow
{ {85, 207, 252}, {247, 168, 184}, {255, 255, 255}, {85, 207, 252} }, // Trans
{ {214, 2, 111}, {155, 79, 150}, {0, 56, 168}, {214, 2, 111} }, // Bi
{ {255, 33, 140}, {255, 181, 0}, {0, 173, 239}, {255, 33, 140} }, // Pan
{ {0, 0, 0}, {164, 164, 164}, {255, 255, 255}, {128, 0, 128} }, // Asexual
{ {244, 200, 0}, {255, 255, 255}, {165, 165, 165}, {155, 89, 182} }, // Non-binary
{ {255, 117, 160}, {243, 60, 140}, {108, 30, 143}, {76, 109, 155} }, // Genderfluid
{ {214, 46, 0}, {255, 90, 54}, {255, 154, 139}, {209, 98, 169} } // Lesbian
};
uint32_t t = millis();
// Palette transition
uint32_t paletteTime = t % PALETTE_BLEND_DURATION;
uint8_t paletteIndex1 = (t / PALETTE_BLEND_DURATION) % NUM_PALETTES;
uint8_t paletteIndex2 = (paletteIndex1 + 1) % NUM_PALETTES;
uint8_t blendT = (paletteTime * 255UL) / PALETTE_BLEND_DURATION;
// Create blended palette with wrap
uint16_t blendedPalette[NUM_COLORS + 1]; // add one for wraparound
for (uint8_t i = 0; i < NUM_COLORS; i++) {
uint32_t rgb1 = (palettes[paletteIndex1][i][0] << 16) |
(palettes[paletteIndex1][i][1] << 8) |
palettes[paletteIndex1][i][2];
uint32_t rgb2 = (palettes[paletteIndex2][i][0] << 16) |
(palettes[paletteIndex2][i][1] << 8) |
palettes[paletteIndex2][i][2];
uint16_t c1 = RGB888ToRGB565(rgb1);
uint16_t c2 = RGB888ToRGB565(rgb2);
blendedPalette[i] = interpolateRGB565(c1, c2, blendT);
}
// Wrap the first color to the end
blendedPalette[NUM_COLORS] = blendedPalette[0];
// Gradient and scroll setup
uint16_t gradientWidth = XMAX;
uint16_t segmentWidth = gradientWidth / NUM_SEGMENTS;
uint16_t scrollOffset = (uint32_t)(t % COLOR_DURATION) * gradientWidth / COLOR_DURATION;
for (int x = 0; x < XMAX; x++) {
uint16_t gradientX = (x + scrollOffset) % gradientWidth;
uint8_t segment = gradientX / segmentWidth;
if (segment >= NUM_SEGMENTS) segment = NUM_SEGMENTS - 1;
uint8_t localX = gradientX - segment * segmentWidth;
uint8_t blendFactor = (localX * 255) / segmentWidth;
uint16_t color = interpolateRGB565(
blendedPalette[segment],
blendedPalette[segment + 1],
blendFactor
);
for (int y = 0; y < YMAX; y++) {
dma_display->drawPixel(x, y, color);
}
}
}

11
transigione/test/README Normal file
View file

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

BIN
wallmount.stl Normal file

Binary file not shown.