Initial commit
This commit is contained in:
commit
4173087c77
11 changed files with 1014 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
.pioenvs
|
||||
.piolibdeps
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
||||
.pio
|
||||
|
BIN
backplate.stl
Normal file
BIN
backplate.stl
Normal file
Binary file not shown.
85
stand.scad
Normal file
85
stand.scad
Normal 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
5
transigione/.gitignore
vendored
Normal 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
10
transigione/.vscode/extensions.json
vendored
Normal 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"
|
||||
]
|
||||
}
|
39
transigione/include/README
Normal file
39
transigione/include/README
Normal 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
46
transigione/lib/README
Normal 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
|
29
transigione/platformio.ini
Normal file
29
transigione/platformio.ini
Normal 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
783
transigione/src/main.cpp
Normal 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
11
transigione/test/README
Normal 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
BIN
wallmount.stl
Normal file
Binary file not shown.
Loading…
Reference in a new issue