2026-10-12 23:55:15 +02:00
|
|
|
#include "libs.h"
|
|
|
|
|
|
|
|
|
|
void toggleLED(void *parameter);
|
|
|
|
|
|
|
|
|
|
uint32_t power_off_delay;
|
|
|
|
|
int triggered_power_off = false;
|
|
|
|
|
int turn_hassio_power_off = false;
|
2026-10-13 00:12:28 +02:00
|
|
|
|
|
|
|
|
// Wait until the Nano reports READY after booting, or until NANO_BOOT_TIMEOUT
|
|
|
|
|
void waitForNano()
|
|
|
|
|
{
|
|
|
|
|
char line[16];
|
|
|
|
|
uint8_t len = 0;
|
|
|
|
|
uint32_t start = millis();
|
|
|
|
|
while (millis() - start < NANO_BOOT_TIMEOUT)
|
|
|
|
|
{
|
2026-10-13 00:16:24 +02:00
|
|
|
while (nanoSerial.available())
|
2026-10-13 00:12:28 +02:00
|
|
|
{
|
2026-10-13 00:16:24 +02:00
|
|
|
char c = nanoSerial.read();
|
2026-10-13 00:12:28 +02:00
|
|
|
if (c == '\n')
|
|
|
|
|
{
|
|
|
|
|
line[len] = '\0';
|
|
|
|
|
if (strstr(line, "READY"))
|
|
|
|
|
return;
|
|
|
|
|
len = 0;
|
|
|
|
|
}
|
|
|
|
|
else if (len < sizeof(line) - 1)
|
|
|
|
|
line[len++] = c;
|
|
|
|
|
}
|
|
|
|
|
delay(10);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void relayOn()
|
|
|
|
|
{
|
|
|
|
|
// cancel a pending power off countdown
|
|
|
|
|
triggered_power_off = false;
|
|
|
|
|
|
|
|
|
|
if (relay_on)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-10-13 00:35:28 +02:00
|
|
|
nanoSerial.begin(NANO_BAUD, SERIAL_8N1, nano_rx, nano_tx);
|
2026-10-13 00:12:28 +02:00
|
|
|
// drop old data (e.g. ERR replies) so only a new READY counts
|
2026-10-13 00:16:24 +02:00
|
|
|
while (nanoSerial.available())
|
|
|
|
|
nanoSerial.read();
|
2026-10-13 00:12:28 +02:00
|
|
|
|
|
|
|
|
digitalWrite(relay, LOW);
|
|
|
|
|
waitForNano();
|
|
|
|
|
relay_on = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void relayOff()
|
|
|
|
|
{
|
|
|
|
|
digitalWrite(relay, HIGH);
|
|
|
|
|
relay_on = false;
|
|
|
|
|
|
|
|
|
|
// An idle UART TX line is high and would power the Nano through its RX pin
|
2026-10-13 00:16:24 +02:00
|
|
|
nanoSerial.flush();
|
|
|
|
|
nanoSerial.end();
|
2026-10-13 00:12:28 +02:00
|
|
|
pinMode(nano_tx, INPUT);
|
|
|
|
|
}
|
2026-10-13 00:35:28 +02:00
|
|
|
|
2026-10-13 00:44:35 +02:00
|
|
|
// Flash session started: restart the Nano so its bootloader runs, and talk to the
|
2026-10-13 00:35:28 +02:00
|
|
|
// bootloader at its baud rate. The PC uploader keeps sending sync bytes until it answers.
|
2026-10-13 00:44:35 +02:00
|
|
|
// The old bootloader also starts after a power cycle; Optiboot only after a reset pulse.
|
|
|
|
|
void startNanoFlash(uint32_t baud)
|
2026-10-13 00:35:28 +02:00
|
|
|
{
|
|
|
|
|
if (relay_on)
|
|
|
|
|
relayOff();
|
|
|
|
|
delay(NANO_POWER_OFF_TIME);
|
|
|
|
|
|
2026-10-13 00:44:35 +02:00
|
|
|
nanoSerial.begin(baud, SERIAL_8N1, nano_rx, nano_tx);
|
|
|
|
|
digitalWrite(relay, LOW);
|
|
|
|
|
|
|
|
|
|
if (nano_reset >= 0)
|
|
|
|
|
{
|
|
|
|
|
delay(NANO_RESET_DELAY);
|
|
|
|
|
digitalWrite(nano_reset, HIGH);
|
|
|
|
|
delay(NANO_RESET_PULSE);
|
|
|
|
|
digitalWrite(nano_reset, LOW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// drop anything the Nano sent before its bootloader started
|
2026-10-13 00:35:28 +02:00
|
|
|
while (nanoSerial.available())
|
|
|
|
|
nanoSerial.read();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flash session ended: switch off, the clock powers the Nano on again with the new firmware
|
|
|
|
|
void endNanoFlash()
|
|
|
|
|
{
|
|
|
|
|
relayOff();
|
|
|
|
|
// skip the frames that were missed while flashing
|
|
|
|
|
next_display = millis();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NanoFlashBridge nanoFlashBridge(NANO_FLASH_PORT, nanoSerial, startNanoFlash, endNanoFlash);
|
2026-10-12 23:55:15 +02:00
|
|
|
void turnPowerOff(uint32_t delay = 30000, bool _turn_hassio_power_off = false)
|
|
|
|
|
{
|
|
|
|
|
if(triggered_power_off == false)
|
|
|
|
|
{
|
|
|
|
|
// start new countdown
|
|
|
|
|
turn_hassio_power_off = _turn_hassio_power_off;
|
|
|
|
|
power_off_delay = millis() + delay;
|
|
|
|
|
|
|
|
|
|
triggered_power_off = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void updatePower()
|
|
|
|
|
{
|
|
|
|
|
if(millis() > power_off_delay && triggered_power_off == true)
|
|
|
|
|
{
|
|
|
|
|
// finished countdown
|
|
|
|
|
triggered_power_off = false;
|
|
|
|
|
|
|
|
|
|
// OFF
|
2026-10-13 00:12:28 +02:00
|
|
|
relayOff();
|
2026-10-12 23:55:15 +02:00
|
|
|
|
|
|
|
|
if(turn_hassio_power_off)
|
|
|
|
|
{
|
|
|
|
|
power = false;
|
|
|
|
|
powerSwitch.setState(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onSwitchStateChanged(bool state, HASwitch *s)
|
|
|
|
|
{
|
|
|
|
|
hassioDisplayStatus = (state ? HIGH : LOW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onPowerSwitchStateChanged(bool state, HASwitch *s)
|
|
|
|
|
{
|
|
|
|
|
power = (state ? HIGH : LOW);
|
|
|
|
|
|
|
|
|
|
if(power == false)
|
|
|
|
|
{
|
2026-10-13 00:12:28 +02:00
|
|
|
if (relay_on)
|
|
|
|
|
{
|
|
|
|
|
display.fillScreen(0);
|
|
|
|
|
display.display();
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
delay(1000);
|
|
|
|
|
}
|
2026-10-12 23:55:15 +02:00
|
|
|
|
|
|
|
|
turnPowerOff(0);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void print_reset_reason(RESET_REASON reason)
|
|
|
|
|
{
|
|
|
|
|
switch (reason)
|
|
|
|
|
{
|
|
|
|
|
case POWERON_RESET:
|
|
|
|
|
Serial.println("POWERON_RESET");
|
|
|
|
|
break; /**<1, Vbat power on reset*/
|
|
|
|
|
case 3:
|
|
|
|
|
Serial.println("SW_RESET");
|
|
|
|
|
break; /**<3, Software reset digital core*/
|
|
|
|
|
case 4:
|
|
|
|
|
Serial.println("OWDT_RESET");
|
|
|
|
|
break; /**<4, Legacy watch dog reset digital core*/
|
|
|
|
|
case 5:
|
|
|
|
|
Serial.println("DEEPSLEEP_RESET");
|
|
|
|
|
break; /**<5, Deep Sleep reset digital core*/
|
|
|
|
|
case 6:
|
|
|
|
|
Serial.println("SDIO_RESET");
|
|
|
|
|
break; /**<6, Reset by SLC module, reset digital core*/
|
|
|
|
|
case 7:
|
|
|
|
|
Serial.println("TG0WDT_SYS_RESET");
|
|
|
|
|
break; /**<7, Timer Group0 Watch dog reset digital core*/
|
|
|
|
|
case 8:
|
|
|
|
|
Serial.println("TG1WDT_SYS_RESET");
|
|
|
|
|
break; /**<8, Timer Group1 Watch dog reset digital core*/
|
|
|
|
|
case 9:
|
|
|
|
|
Serial.println("RTCWDT_SYS_RESET");
|
|
|
|
|
break; /**<9, RTC Watch dog Reset digital core*/
|
|
|
|
|
case 10:
|
|
|
|
|
Serial.println("INTRUSION_RESET");
|
|
|
|
|
break; /**<10, Instrusion tested to reset CPU*/
|
|
|
|
|
case 11:
|
|
|
|
|
Serial.println("TGWDT_CPU_RESET");
|
|
|
|
|
break; /**<11, Time Group reset CPU*/
|
|
|
|
|
case SW_CPU_RESET:
|
|
|
|
|
Serial.println("SW_CPU_RESET");
|
|
|
|
|
break; /**<12, Software reset CPU*/
|
|
|
|
|
case 13:
|
|
|
|
|
Serial.println("RTCWDT_CPU_RESET");
|
|
|
|
|
break; /**<13, RTC Watch dog Reset CPU*/
|
|
|
|
|
case EXT_CPU_RESET:
|
|
|
|
|
Serial.println("EXT_CPU_RESET");
|
|
|
|
|
break; /**<14, for APP CPU, reseted by PRO CPU*/
|
|
|
|
|
case 15:
|
|
|
|
|
Serial.println("RTCWDT_BROWN_OUT_RESET");
|
|
|
|
|
break; /**<15, Reset when the vdd voltage is not stable*/
|
|
|
|
|
case 16:
|
|
|
|
|
Serial.println("RTCWDT_RTC_RESET");
|
|
|
|
|
break; /**<16, RTC Watch dog reset digital core and rtc module*/
|
|
|
|
|
default:
|
|
|
|
|
Serial.println("NO_MEAN");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkResetStatus()
|
|
|
|
|
{
|
|
|
|
|
Serial.println("CPU0 reset reason: ");
|
|
|
|
|
RESET_REASON reason0 = rtc_get_reset_reason(0);
|
|
|
|
|
print_reset_reason(reason0);
|
|
|
|
|
|
|
|
|
|
// if(reason0 == SW_CPU_RESET)
|
|
|
|
|
// return true;
|
|
|
|
|
|
|
|
|
|
Serial.println("CPU1 reset reason: ");
|
|
|
|
|
RESET_REASON reason1 = rtc_get_reset_reason(1);
|
|
|
|
|
print_reset_reason(reason1);
|
|
|
|
|
|
|
|
|
|
// if(reason1 == SW_CPU_RESET)
|
|
|
|
|
// return true;
|
|
|
|
|
|
|
|
|
|
if (reason0 != POWERON_RESET && reason1 != EXT_CPU_RESET)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setup()
|
|
|
|
|
{
|
|
|
|
|
otaPanicMode = true;
|
|
|
|
|
pinMode(led1, OUTPUT);
|
|
|
|
|
|
|
|
|
|
Serial.begin(115200);
|
|
|
|
|
|
|
|
|
|
WiFi.setHostname("FlipDotDisplay");
|
|
|
|
|
wifiMulti.addAP(ssid, password);
|
|
|
|
|
|
|
|
|
|
Serial.println("Booting");
|
|
|
|
|
WiFi.mode(WIFI_STA);
|
|
|
|
|
WiFi.begin(ssid, password);
|
|
|
|
|
while (WiFi.waitForConnectResult() != WL_CONNECTED)
|
|
|
|
|
{
|
|
|
|
|
Serial.println("Connection Failed! Rebooting...");
|
|
|
|
|
delay(5000);
|
|
|
|
|
ESP.restart();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
// USE_SERIAL.write(0x0C); //CLEAR SCREEN CMD
|
|
|
|
|
// Serial.println("Start updating " + type);
|
|
|
|
|
|
|
|
|
|
// if (!otaPanicMode)
|
|
|
|
|
// {
|
|
|
|
|
// display.begin(&Serial);
|
|
|
|
|
// display.fillScreen(0);
|
|
|
|
|
// display.setTextSize(1);
|
|
|
|
|
// display.setCursor(0, 0);
|
|
|
|
|
// display.print("ota starting!");
|
|
|
|
|
// display.display();
|
|
|
|
|
// }
|
|
|
|
|
})
|
|
|
|
|
.onEnd([]()
|
|
|
|
|
{
|
|
|
|
|
// USE_SERIAL.write(0x0C); //CLEAR SCREEN CMD
|
|
|
|
|
// Serial.println("\nEnd");
|
|
|
|
|
})
|
|
|
|
|
.onProgress([](unsigned int progress, unsigned int total)
|
|
|
|
|
{
|
|
|
|
|
// USE_SERIAL.write(0x0C); //CLEAR SCREEN CMD
|
|
|
|
|
// USE_SERIAL.printf("Progress: %u%%\r", (progress / (total / 100)));
|
|
|
|
|
// if (!otaPanicMode)
|
|
|
|
|
// {
|
|
|
|
|
// display.begin(&Serial);
|
|
|
|
|
// display.fillScreen(0);
|
|
|
|
|
// display.setTextSize(1);
|
|
|
|
|
// display.setCursor(0, 0);
|
|
|
|
|
// display.printf("ota: %u%%\r", (progress / (total / 100)));
|
|
|
|
|
// display.display();
|
|
|
|
|
// }
|
|
|
|
|
})
|
|
|
|
|
.onError([](ota_error_t error)
|
|
|
|
|
{
|
|
|
|
|
// // USE_SERIAL.write(0x0C); //CLEAR SCREEN CMD
|
|
|
|
|
// 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");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ArduinoOTA.begin();
|
2026-10-13 00:35:28 +02:00
|
|
|
nanoFlashBridge.begin();
|
2026-10-12 23:55:15 +02:00
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
ArduinoOTA.handle();
|
|
|
|
|
|
|
|
|
|
digitalWrite(led1, HIGH);
|
|
|
|
|
|
|
|
|
|
vTaskDelay(50 / portTICK_PERIOD_MS);
|
|
|
|
|
|
|
|
|
|
// Turn the LED off
|
|
|
|
|
digitalWrite(led1, LOW);
|
|
|
|
|
|
|
|
|
|
vTaskDelay(50 / portTICK_PERIOD_MS);
|
|
|
|
|
|
|
|
|
|
} while (checkResetStatus() && millis() < 1000 * 15);
|
|
|
|
|
otaPanicMode = false;
|
|
|
|
|
|
|
|
|
|
pinMode(relay, OUTPUT);
|
2026-10-13 00:44:35 +02:00
|
|
|
if (nano_reset >= 0)
|
|
|
|
|
{
|
|
|
|
|
digitalWrite(nano_reset, LOW);
|
|
|
|
|
pinMode(nano_reset, OUTPUT);
|
|
|
|
|
}
|
2026-10-13 00:12:28 +02:00
|
|
|
relayOn();
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:16:24 +02:00
|
|
|
display.begin(&nanoSerial);
|
2026-10-12 23:55:15 +02:00
|
|
|
// display.setTiming(200);
|
|
|
|
|
// display.setTiming(180);
|
|
|
|
|
// display.setTiming(220); // works best so far
|
|
|
|
|
display.setTiming(225);
|
|
|
|
|
|
|
|
|
|
// delay(4000);
|
|
|
|
|
display.fillScreen(0);
|
|
|
|
|
display.setCursor(0, 0);
|
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
display.println("Connecting...");
|
|
|
|
|
display.display();
|
|
|
|
|
|
|
|
|
|
// display.setFont(&FreeSerif12pt7b);
|
|
|
|
|
// display.setFont(&FreeSerifItalic12pt7b);
|
|
|
|
|
|
|
|
|
|
//init and get the time
|
|
|
|
|
// configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
|
|
|
|
|
configTzTime(MYTZ, ntpServer);
|
|
|
|
|
|
|
|
|
|
xTaskCreate(
|
|
|
|
|
toggleLED, // Function that should be called
|
|
|
|
|
"Toggle LED", // Name of the task (for debugging)
|
|
|
|
|
1000, // Stack size (bytes)
|
|
|
|
|
NULL, // Parameter to pass
|
|
|
|
|
1, // Task priority
|
|
|
|
|
NULL // Task handle
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
next_display = millis() / 1000 * 1000;
|
|
|
|
|
|
|
|
|
|
// // TEST CODE
|
|
|
|
|
// do{
|
|
|
|
|
// ArduinoOTA.handle();
|
|
|
|
|
|
|
|
|
|
// digitalWrite(led1, HIGH);
|
|
|
|
|
|
|
|
|
|
// vTaskDelay(50 / portTICK_PERIOD_MS);
|
|
|
|
|
|
|
|
|
|
// // Turn the LED off
|
|
|
|
|
// digitalWrite(led1, LOW);
|
|
|
|
|
|
|
|
|
|
// vTaskDelay(50 / portTICK_PERIOD_MS);
|
|
|
|
|
|
|
|
|
|
// }while(true);
|
|
|
|
|
|
|
|
|
|
// hassio
|
|
|
|
|
// you don't need to verify return status
|
|
|
|
|
// client.begin(mac);
|
|
|
|
|
|
|
|
|
|
// set device's details (optional)
|
|
|
|
|
device.setName("FlipDotDisplay");
|
|
|
|
|
device.setSoftwareVersion("1.0.0");
|
|
|
|
|
|
|
|
|
|
// set icon (optional)
|
|
|
|
|
flipDotDisplay.setIcon("mdi:lightbulb");
|
|
|
|
|
flipDotDisplay.setName("FlipDot Display Clock");
|
|
|
|
|
|
|
|
|
|
// handle switch state
|
|
|
|
|
flipDotDisplay.onCommand(onSwitchStateChanged);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
powerSwitch.setIcon("mdi:lightbulb");
|
|
|
|
|
powerSwitch.setName("FlipDot Display Power");
|
|
|
|
|
powerSwitch.onCommand(onPowerSwitchStateChanged);
|
|
|
|
|
|
|
|
|
|
mqtt.begin(BROKER_ADDR, MQTT_USER, MQTT_PASSWORD);
|
|
|
|
|
|
|
|
|
|
display.fillScreen(0);
|
|
|
|
|
display.display();
|
|
|
|
|
|
|
|
|
|
hour = millis() / 1000 / 60 / 60;
|
|
|
|
|
minute = (millis() / 1000 / 60) % 60;
|
|
|
|
|
second = (millis() / 1000 % 60);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void toggleLED(void *parameter)
|
|
|
|
|
{
|
|
|
|
|
for (;;)
|
|
|
|
|
{ // infinite loop
|
|
|
|
|
if((power == false) || (!(hour >= 7 && hour < 22)))
|
|
|
|
|
{
|
|
|
|
|
// OFF
|
|
|
|
|
digitalWrite(led1, LOW);
|
|
|
|
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Turn the LED on
|
|
|
|
|
digitalWrite(led1, HIGH);
|
|
|
|
|
|
|
|
|
|
if (wifiStatus)
|
|
|
|
|
{
|
|
|
|
|
// Pause the task for 500ms
|
|
|
|
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
|
|
|
|
|
|
|
|
|
// Turn the LED off
|
|
|
|
|
digitalWrite(led1, LOW);
|
|
|
|
|
|
|
|
|
|
if (wifiStatus)
|
|
|
|
|
{
|
|
|
|
|
// Pause the task again for 500ms
|
|
|
|
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define DEG2RAD 0.0174532925
|
|
|
|
|
void drawRotatedBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, bool color, uint16_t angle)
|
|
|
|
|
{
|
|
|
|
|
int16_t newx, newy;
|
|
|
|
|
uint8_t data = 0;
|
|
|
|
|
|
|
|
|
|
float cosa = cos(angle * DEG2RAD), sina = sin(angle * DEG2RAD);
|
|
|
|
|
|
|
|
|
|
x = x - ((w * cosa / 2) - (h * sina / 2));
|
|
|
|
|
y = y - ((h * cosa / 2) + (w * sina / 2));
|
|
|
|
|
|
|
|
|
|
for (int16_t j = 0; j < h; j++)
|
|
|
|
|
{
|
|
|
|
|
for (int16_t i = 0; i < w; i++)
|
|
|
|
|
{
|
|
|
|
|
if ((j * w + i) & 7)
|
|
|
|
|
data <<= 1;
|
|
|
|
|
else
|
|
|
|
|
data = pgm_read_byte(bitmap++);
|
|
|
|
|
|
|
|
|
|
newx = 0.5 + x + ((i * cosa) - (j * sina));
|
|
|
|
|
newy = 0.5 + y + ((j * cosa) + (i * sina));
|
|
|
|
|
|
|
|
|
|
if (data & 0x80)
|
|
|
|
|
display.drawPixel(newx, newy, color);
|
|
|
|
|
//else display.drawPixel(newx, newy, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void turnPowerOn(bool overide = false)
|
|
|
|
|
{
|
|
|
|
|
if(power == true || overide == true)
|
2026-10-13 00:12:28 +02:00
|
|
|
relayOn();
|
2026-10-12 23:55:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void loop()
|
|
|
|
|
{
|
|
|
|
|
ArduinoOTA.handle();
|
|
|
|
|
mqtt.loop();
|
2026-10-13 00:35:28 +02:00
|
|
|
|
|
|
|
|
// While the Nano is being flashed, nothing else may talk to it
|
|
|
|
|
if (nanoFlashBridge.handle())
|
|
|
|
|
return;
|
|
|
|
|
|
2026-10-12 23:55:15 +02:00
|
|
|
updatePower();
|
|
|
|
|
|
|
|
|
|
wifiStatus = WiFi.waitForConnectResult() == WL_CONNECTED;
|
|
|
|
|
|
|
|
|
|
if(next_display < millis())
|
|
|
|
|
{
|
|
|
|
|
if (hassioDisplayStatus)
|
|
|
|
|
// if (false)
|
|
|
|
|
{
|
|
|
|
|
char t[20];
|
|
|
|
|
char timeStringBuff[50]; //50 chars should be enough
|
|
|
|
|
|
|
|
|
|
struct tm timeinfo;
|
|
|
|
|
if (!getLocalTime(&timeinfo))
|
|
|
|
|
{
|
|
|
|
|
// printFromStart("Failed to get time");
|
|
|
|
|
// return;
|
|
|
|
|
|
|
|
|
|
// sprintf(t, "%02d:%02d:%02d\0", millis() / 1000 / 60 / 60, (millis() / 1000 / 60) % 60, (millis() / 1000 % 60));
|
|
|
|
|
// sprintf(t, "%02d%02d%02d\0", millis() / 1000 / 60 / 60, (millis() / 1000 / 60) % 60, (millis() / 1000 % 60));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// const char *format = "%H:%M:%S";
|
|
|
|
|
const char *format = "%H:%M";
|
|
|
|
|
|
|
|
|
|
// strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
|
|
|
|
|
strftime(timeStringBuff, sizeof(timeStringBuff), format, &timeinfo);
|
|
|
|
|
String asString(timeStringBuff);
|
|
|
|
|
|
|
|
|
|
hour = timeinfo.tm_hour;
|
|
|
|
|
minute = timeinfo.tm_min;
|
|
|
|
|
second = timeinfo.tm_sec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// display.setFont(&FreeSerif12pt7b);
|
|
|
|
|
|
|
|
|
|
// if ((millis() / 1000 % 60) <= 30)
|
|
|
|
|
// display.setFont(&FreeSerif12pt7b);
|
|
|
|
|
// else
|
|
|
|
|
// display.setFont(&FreeSerifItalic12pt7b);
|
|
|
|
|
|
|
|
|
|
if(hour <= 7 || (hour >= 23 && minute > 30))
|
|
|
|
|
{
|
2026-10-13 00:12:28 +02:00
|
|
|
if (relay_on)
|
|
|
|
|
{
|
|
|
|
|
display.fillScreen(0);
|
|
|
|
|
display.display();
|
|
|
|
|
}
|
2026-10-12 23:55:15 +02:00
|
|
|
|
|
|
|
|
turnPowerOff();
|
|
|
|
|
}
|
|
|
|
|
// show time between 7 and 23 with updates every minute, and after that; every 15 minutes.
|
|
|
|
|
else if((hour >= 7 && hour < 22) || (minute % 15 == 0))
|
|
|
|
|
// else if((hour >= 7 && hour < 19) || (minute % 5 == 0))
|
|
|
|
|
// else if(true)
|
|
|
|
|
{
|
|
|
|
|
turnPowerOn();
|
|
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
// power can be off (Home Assistant power switch): then nothing is sent
|
|
|
|
|
if (relay_on)
|
|
|
|
|
{
|
|
|
|
|
int xoffset = 3;
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
display.fillScreen(0);
|
|
|
|
|
// display.setTextSize(2);
|
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
// display.setCursor(7, 3);
|
|
|
|
|
display.setCursor(xoffset + 0, 19 - 2);
|
|
|
|
|
display.setTextWrap(false);
|
|
|
|
|
// display.print(t);
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
// if(minute % 2 == 0)
|
|
|
|
|
if(hour >= 17 || hour <= 6)
|
|
|
|
|
display.drawBitmap(xoffset + 0, 0, yawning_pikachu_bitmap_yawning_pikachu_black_border, 35, 19, 1);
|
|
|
|
|
else
|
|
|
|
|
display.drawBitmap(xoffset + 0, 0, pikachu_bitmap_pikachu_black_border, 35, 19, 1);
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
display.drawBitmap(xoffset + 35, 0, clock_thought_bubble_bitmap_clock_thought_bubble, 46, 19, 1);
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
display.setCursor(xoffset + 43, (19/2) - 5);
|
|
|
|
|
display.print(timeStringBuff);
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
display.display();
|
|
|
|
|
}
|
2026-10-12 23:55:15 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
turnPowerOff();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-10-13 00:12:28 +02:00
|
|
|
if (relay_on)
|
|
|
|
|
{
|
|
|
|
|
display.fillScreen(0);
|
|
|
|
|
display.drawBitmap(0, 0, nintendo_bitmap_Nintendo_new, 84, 19, 1);
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
// display.drawBitmap(0, 0, nintendo_bitmap_panda_inverted, 17, 18, 1);
|
|
|
|
|
// display.drawBitmap(18, 0, nintendo_bitmap_panda_inverted, 17, 18, 1);
|
|
|
|
|
// display.drawBitmap(18*2, 0, nintendo_bitmap_panda_inverted, 17, 18, 1);
|
|
|
|
|
// display.drawBitmap(18*3, 0, nintendo_bitmap_panda_inverted, 17, 18, 1);
|
|
|
|
|
// display.drawBitmap(18*4, 0, nintendo_bitmap_panda_inverted, 17, 18, 1);
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
// drawRotatedBitmap(0, 0, nintendo_bitmap_Nintendo_new, 17, 18, 1, 90);
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
// Text inside border.
|
|
|
|
|
// display.drawBitmap(0, 0, nintendo_bitmap_nintendo_border, 84, 19, 1);
|
|
|
|
|
// display.setFont();
|
|
|
|
|
// display.setTextSize(1);
|
|
|
|
|
// display.setTextWrap(false);
|
|
|
|
|
// display.setCursor(5, 4);
|
|
|
|
|
// display.print("Nintendo");
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
// display.setFont();
|
|
|
|
|
// // display.setTextSize(2);
|
|
|
|
|
// display.setTextSize(1);
|
|
|
|
|
// display.setCursor(0, 0);
|
|
|
|
|
// display.setTextWrap(true);
|
|
|
|
|
// // display.print(t);
|
|
|
|
|
// display.print("Carla & Jan Kragt");
|
2026-10-12 23:55:15 +02:00
|
|
|
|
2026-10-13 00:12:28 +02:00
|
|
|
display.display();
|
|
|
|
|
}
|
2026-10-12 23:55:15 +02:00
|
|
|
|
|
|
|
|
turnPowerOff();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next_display += 1000;
|
|
|
|
|
// next_display += 500;
|
|
|
|
|
}
|
|
|
|
|
}
|