Run ESP_UniClock on the NixieIN12v2 clock

Adds CLOCK_90 and the nixie595.ino driver: two 74HC595s on hardware
SPI (GPIO13/14, latch GPIO15), multiplexed from an IRAM-only timer1
ISR. Neon dots on P1; P2 is the decimal-point line on this board.

Also:
- NeoPixel on any ESP8266 pin via bit-bang (GPIO12), and take the pin
  back after SPI.begin() claimed it as MISO
- ArduinoOTA (USE_ARDUINO_OTA), with an OTA upload env in platformio.ini
- DST: EU end date (last Sunday of October) instead of the first Sunday
  of November, and recompute the time from the rules when DST is
  toggled instead of shifting it by 1 h
- forward declarations so PlatformIO's .ino conversion compiles
- FIRMWARE_SERVER can be overridden; empty for CLOCK_90

Verified on the clock: tubes, order, dots, LEDs, LDR, NTP time with DST.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-25 21:23:10 +01:00
1 parent b347f1c63f
commit 0d3e878051
7 files changed
+304 -34

No files matched your search

@@ -44,6 +44,11 @@
#include <stddef.h>
//#define byte uint8_t
#include "clocks.h" //DEFINE YOUR CLOCKS SETUP IN THIS FILE!!! - clocks.h picks the single active CLOCK_NN block, which #defines the driver macro, pins and feature flags read throughout this file
// PlatformIO turns the .ino files into one .cpp and puts prototypes for every function near the top,
// without evaluating #ifdefs. Declare the types of optional libraries here so those prototypes compile
// when the feature (and its #include) is switched off.
class AsyncWiFiManager;
struct RgbColor;
// Detect which generation of the ESP8266 Arduino core is in use: older cores (2.6.x/2.7.x) name the
// RAM-resident-ISR attribute ICACHE_RAM_ATTR, while newer cores renamed it to IRAM_ATTR (shared with
@@ -161,7 +166,9 @@ unsigned long intCounter = 0; //for testing only, interrupt counter
byte c_MinBrightness = RGB_MIN_BRIGHTNESS; //minimum LED brightness
byte c_MaxBrightness = RGB_MAX_BRIGHTNESS; //maximum LED brightness
//--------------------------------------------------------------------------------------------------
#define FIRMWARE_SERVER "http://c.landventure.hu/store" //default OTA update server (see doFirmwareUpdate()); overridable per-clock via prm.firmwareServer / the web admin "firmware" field
#ifndef FIRMWARE_SERVER
#define FIRMWARE_SERVER "http://c.landventure.hu/store" //default OTA update server (see doFirmwareUpdate()); overridable per-clock via prm.firmwareServer / the web admin "firmware" field
#endif
String usedPinsStr; //human-readable list of registered GPIOs, built by listPins(), shown on the web admin "clock details" page
String driverSetupStr; //driver-specific extra setup info string (set by some drivers), also shown on the web admin page
@@ -237,6 +244,9 @@ DNSServer dnsServer;
#include <Wire.h>
#include <EEPROM.h>
#include "ArduinoJson.h"
#ifdef USE_ARDUINO_OTA
#include <ArduinoOTA.h>
#endif
#if defined(USE_NEOPIXEL) || defined(WORDCLOCK)
#include <NeoPixelBrightnessBus.h>
@@ -441,8 +451,10 @@ IPAddress ip;
// switchover DATES are not configurable from the web UI. myTZ is rebuilt with the current offsets
// every time updateTimefromTimeserver() runs (WiFi/NTP path only - the GPS-only fallback in gps.ino
// uses a simpler, always-on +1h DST rule, see CODE_REVIEW.md).
// NOTE: upstream ended DST on the first Sunday of November (the US date). The EU switches at 01:00 UTC:
// last Sunday of March 02:00 standard time, last Sunday of October 03:00 summer time.
TimeChangeRule myDST = {"DST", Last, Sun, Mar, 2, 0};
TimeChangeRule mySTD = {"STD", First, Sun, Nov, 2, 0};
TimeChangeRule mySTD = {"STD", Last, Sun, Oct, 3, 0};
Timezone myTZ(myDST, mySTD);
time_t prevTime = 0; //last second() value that was actually rendered to the display - timeProgram() only redraws when now()!=prevTime, i.e. at most once per real second
time_t protectTimer = 0; //timestamp of the last cathode-protect animation, used by timeProgram()'s prm.interval-minute scheduler
@@ -589,6 +601,9 @@ void Fdelay(unsigned long d) {
#ifdef USE_GPS
smartDelay(1); //feed GPS
#endif
#ifdef USE_ARDUINO_OTA
ArduinoOTA.handle(); //also during long animations/startup waits
#endif
yield();
}
}
@@ -877,6 +892,39 @@ void startMDNS() {
#endif
}
// Optional ArduinoOTA (USE_ARDUINO_OTA in clocks.h): push a new firmware from PlatformIO/Arduino IDE
// over WiFi ("pio run -t upload --upload-port <ip>"). Shows the progress in percent on the two right
// tubes. Password from OTA_PASSWORD (secrets.h) if defined.
void startArduinoOTA() {
#ifdef USE_ARDUINO_OTA
static boolean otaStarted = false;
if (otaStarted) return;
ArduinoOTA.setHostname(OTA_HOSTNAME);
#ifdef OTA_PASSWORD
ArduinoOTA.setPassword(OTA_PASSWORD);
#endif
ArduinoOTA.onStart([]() {
DPRINTLN(F("ArduinoOTA: update started"));
clearDigits();
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
int p = (int)(progress / (total / 100 + 1));
digit[1] = (p / 10) % 10;
digit[0] = p % 10;
});
ArduinoOTA.onEnd([]() {
DPRINTLN(F("ArduinoOTA: done, rebooting"));
});
ArduinoOTA.onError([](ota_error_t error) {
DPRINTF("ArduinoOTA: error %u\n", error);
clearDigits();
});
ArduinoOTA.begin();
otaStarted = true;
DPRINT(F("ArduinoOTA ready: ")); DPRINTLN(OTA_HOSTNAME);
#endif
}
// The Standalone(AP)-mode path (prm.wifiMode==false, or a fallback from a fully-failed STA connect):
// the clock hosts its own WiFi hotspot at the fixed address 192.168.4.1 (prm.ApSsid/prm.ApPsw) and
// runs a DNS server that answers every query with its own IP (dnsServer.start(53,"*",...)) so most
@@ -1242,6 +1290,9 @@ void handleConfigChanged(AsyncWebServerRequest *request) {
if (old_utc_offset != prm.utc_offset) { //Change time zone
setTime(now()+(prm.utc_offset-old_utc_offset)*3600);
updateRTC();
mySTD.offset = prm.utc_offset * 60; //keep myTZ current for a later enableDST change
myDST.offset = mySTD.offset + (prm.enableDST ? 60 : 0);
myTZ = Timezone(myDST, mySTD);
}
}
else if (key == "set12_24") {
@@ -1262,13 +1313,15 @@ void handleConfigChanged(AsyncWebServerRequest *request) {
// running clock by +-1h right away and re-syncs the RTC, instead of only taking effect on the
// next scheduled NTP refresh (myTZ itself is rebuilt from prm.enableDST inside
// updateTimefromTimeserver() on that next refresh).
// FIXED: this used to shift the clock by a blind +-1h, which is wrong outside the summer period
// (switching DST on in winter put the clock 1h ahead until the next NTP sync). Now the local
// time is converted back to UTC with the old rules and forward with the new ones.
prm.enableDST = (value == "true");
if (oldDST && !prm.enableDST) { //Switching off DST
setTime(now()-3600);
updateRTC();
}
if (!oldDST && prm.enableDST) { //Switching on DST
setTime(now()+3600);
if (oldDST != prm.enableDST) {
time_t utc = myTZ.toUTC(now());
myDST.offset = mySTD.offset + (prm.enableDST ? 60 : 0);
myTZ = Timezone(myDST, mySTD);
setTime(myTZ.toLocal(utc));
updateRTC();
}
}
@@ -1889,10 +1942,11 @@ void setup() {
startMDNS();
startServer();
startArduinoOTA();
#ifdef USE_MQTT
setupMqtt();
#endif
showMyIp();
Fdelay(500);
clearDigits();
@@ -3221,6 +3275,9 @@ void loop(void) {
}
}
editor();
#ifdef USE_ARDUINO_OTA
ArduinoOTA.handle();
#endif
if (makeFirmwareUpdate) {
makeFirmwareUpdate = false;
doFirmwareUpdate();