esp8266 / esp8266/Arduino

Version 3.0.0 up break my92xx library

Open
#8,400 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
16.7k
Forks
13.1k
PR merge metrics
No merged PRs in 30d

Description

### Basic Infos

- [x] This issue complies with the [issue POLICY doc](https://github.com/esp8266/Arduino/blob/master/POLICY.md).
- [x] I have read the documentation at [readthedocs](https://arduino-esp8266.readthedocs.io/en/latest) and the issue is not addressed there.
- [x] I have tested that the issue is present in current master branch (aka latest git).
- [x] I have searched the issue tracker for a similar issue.
- [x] If there is a stack dump, I have decoded it.
- [x] I have filled out all fields below.

#### Platform

- Hardware: ESP8266
- Core Version: 3.0.2
- Development Env: Arduino IDE
- Operating System: MacOS

### Settings in IDE

- Module: Generic ESP8266 Module
- Flash Mode: dout
- Flash Size: 1MB
- lwip Variant: v2 Lower Memory
- Reset Method: nodemcu
- Flash Frequency: 40Mhz
- CPU Frequency: 80Mhz
- Upload Using: OTA or serial
- Upload Speed: 115200 when serial

### Problem Description

Sending code to an Itead SONOFF B1 light bulb, which uses a my9231 chip to control the 5 channels of LED. When I use ESP8266 board definitions 2.7.4, everything works as planned. If I update to 3.0.0 or above, the bulb flashes more or less at random as I adjust values. The brightnesses no longer scale smoothly with the commands, nor go to the proper channels.
Seems funny, since the my92xx library isn't using much - only digitalWrites and the c code for os_delay_us. I tried replacing the os_delay_us definition with delayMicroseconds, without any better luck.

### [MCVE](https://stackoverflow.com/help/mcve) Sketch

```cpp
#define PROGNAME "ESP8266_B1_bulb"
#define VERSION "1.1.0"
#define VERDATE "2021-12-06"

/*
ESP8266_B1_bulb.ino
2021-02-15
Charles B. Malloch, PhD

Program to control a Sonoff B1 Bulb
Target hardware: ESP8266 on a Sonoff B1 RGB WW CW LED light bulb on Edison base

v0.0.1 2021-02-13 cloned from ESP8266_Sonoff v1.1.1
v1.2.1 2021-02-16 cbm repaired timer
v0.2.4 2021-04-06 cbm ready for production test
v0.2.15 2021-04-27 cbm added veil of indication
v0.3.0 2021-11-29 cbm modified to alternately start up with red or white
v0.3.7 2021-12-02 cbm removed timed auto-reboot, 'cause we don't retain color over
a soft restart. Could, with work, retrieve it from MQTT "status"
v0.4.0 2021-12-02 cbm now fixed; writing and reading soft-restart file
v1.0.0 2021-12-02 cbm calling it production!
v1.1.0 2021-12-06 cbm substituting on-time for reset reason to keep old color

Sonoff B1 Bulb Pinout, CW from CCW, for wiring to an ESP-01 programming adapter:
1 Vcc (3.3) red
2 RX yellow NOTE this is the RX of the ESP, so RX header hole ( next to 3V3 )
3 TX green
4 GND black
5 GPIO0 purple

Use adapter from Sonoff box

Schematic:
https://user-images.githubusercontent.com/17343162/29202638-6b2c39ee-7e6a-11e7-93eb-727d7efaa24c.png
3-color LEDs are controlled by my9231 chip

http://www.bitfracture.com/pages/techarticles/squirrel-esp8266-esp8285-wifi-lightbulb-control-project
has picture of my9231 setup (colors not right, though)

MQTT: can send messages like home/bulb/b01/command -> {"color":[32,0,0,0,0]}
[ R, G, B, WW, CW ]

The current two bulbs are designated b01 (hall), b02 (office)

Plans:
Turn on full warm white, alternately non-max red
thus acting like a little-bit-smart bulb

MQTT changes: send timeout_ms only if testing
send reset, on, off messages

Appears to work using upstairs computer and a05 with VERBOSE set to 12
and looking at MQTT telemetry.

--BUT--

When compiled and sent from pro2, IT DOESN'T WORK RIGHT - the colors are screwed
up. When compiled and sent from Hack, it work(ed) OK.
Checked the my92xx library revisions - identical Hack and pro2.
Testing with board revisions; broken ones are at 3.0.2; office OK at 2.7.x
broken at 3.0.1, trying 3.0.0; still NG
tried using delayMicroseconds instead of os_delay_us; still NG
trying 2.7.4, just prior to 3.0.0: WORKS!!!

*/

#include
#include //https://github.com/bblanchon/ArduinoJson

#include
#include
#include
#include
// mDNS is now furnished by ArduinoOTA
// see https://tttapa.github.io/ESP8266/Chap08%20-%20mDNS.html
// ESP8266mDNS is multicast DNS - responds to .local
// #include
// ESP8266WiFiMulti looks at multiple access points and chooses the strongest
// #include
#include
#include

#include

#define ALLOW_OTA
#ifdef ALLOW_OTA
#include
#endif

#include

#include

#define SECOND_ms ( 1000UL )
#define MINUTE_ms ( 60UL * SECOND_ms )
#define HOUR_ms ( 60UL * MINUTE_ms )

// R, G, B, WW, CW
#define RED_color 0x80, 0x00, 0x00, 0x00, 0x00
#define WHITE_color 0x00, 0x00, 0x00, 0x80, 0x80

// ***************************************
// ***************************************
#pragma mark -> vars MAIN PARAMETERS

#define mqtt_baseTopic "home/bulb"

#define BAUDRATE 115200
#define VERBOSE 2

// auto reboot will have side effect of turning bulb off! <- now fixed
const unsigned long rebootInterval_ms = 24UL * HOUR_ms;

#define TESTING 0

// ***************************************
// ***************************************

/********************************** GPIO **************************************/
#pragma mark -> vars GPIO

/*
I2C cbm standard colors: yellow for SDA, blue for SCL
Blue onboard LED is inverse logic, connected as:
ESP-01:
GPIO0 for SDA
GPIO1 ( TX ) is blue, inverse logic
GPIO2 (next to GND) for SCL; 1K pullup on my boards
GPIO3 ( RX ) is red
Adafruit Huzzah:
GPIO0 is red
GPIO2 is blue, inverse logic
GPIO4 is SDA; GPIO5 is SCL
Amica NodeMCU
GPIO4 (D2) is SDA; GPIO5 (D1) is SCL
GPIO16 (D0) is red but this is usually used to drive reset
LOW to pin RST (CH_PD / chip_enable)
blue is GPIO1 TX conflicts with the use of Serial
WeMos-WROOM-02: D16
Sonoff
pagoda:
GPIO4 is red (after board modification) (inverse logic, hardware PWM)
GPIO13 is green (inverse logic, no PWM)
switch block:
red LED associated with relay
GPIO13 is blue (inverse logic, PWM)
GPIO0 is the pushbutton (has pullup resistor)
GPIO12 is the relay output
GPIO14 is J1 Pin 5 (not used in this program)
ITEAD Sonoff S31:
GPIO0 is the pushbutton (has pullup resistor)
GPIO12 is the relay output
GPIO13 is green
Sonoff Bulb
GPIO12 (SDA) and GPIO14 (SCL) to two cascaded MY9231
controlling Blue Red Green | Warm NC Cool
*/

#define INVERSE_LOGIC_ON 0
#define INVERSE_LOGIC_OFF 1

const int pd_LED_white = 4; // hardware PWM

/********************************* Network ************************************/
#pragma mark -> vars Network

// locales include M5, CBMIoT, CBMDATACOL, CBMDDWRT, CBMDDWRT3, CBMDDWRT3GUEST,
// CBMBELKIN, CBM_RASPI_MOSQUITTO
// need to use CBMDDWRT3 for my own network access
// can use CBMIoT or CBMDDWRT3GUEST for Sparkfun etc.

#define WIFI_LOCALE CBMDATACOL
const unsigned int port_UDP = 9250;

/*
The cbmNetworkInfo object has (at least) the following instance variables:
.ip
.gateway
.mask
.ssid
.password
.chipID ( GUID assigned by manufacturer, often the last three octets
of the MAC address, e.g. 5c:cf:7f:xx:xx:xx )
.chipName ( globally-unique name assigned by cbm and marked on PCB
*/

cbmNetworkInfo Network;

// Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient conn_TCP;
WiFiUDP conn_UDP;
PubSubClient conn_MQTT ( conn_TCP );
ESP8266WebServer htmlServer ( 80 );
WebSocketsServer webSocket = WebSocketsServer(81);

/************************************ mDNS ************************************/
#pragma mark -> vars mDNS

const int mdnsOtaIdLen = 40;
char mdnsOtaId [ mdnsOtaIdLen ];

/************************************ MQTT ************************************/
#pragma mark -> vars MQTT

const int mqttClientIDLen = 50;
char mqtt_clientID [ mqttClientIDLen ] = "whatever";

const int mqttTopicLen = 50;
char mqttCmdTopic [ mqttTopicLen ];
char mqttStatusTopic [ mqttTopicLen ];
char mqttTimeoutTopic [ mqttTopicLen ];
char mqttResetReasonTopic [ mqttTopicLen ];

/********************************** NTP ***************************************/
#pragma mark -> vars NTP

IPAddress timeServer ( 17, 253, 14, 253 );
const char* NTPServerName = "time.apple.com";

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte NTPBuffer [NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets

const int timeZone = 0; // GMT
// const int timeZone = -5; // Eastern Standard Time (USA)
// const int timeZone = -4; // Eastern Daylight Time (USA)
unsigned long lastNTPResponseAt_ms = millis();
// uint32_t time_unix_s = 0;
bool ntpTimeGoodP = false;

/********************************** bulb **************************************/
#pragma mark -> vars bulb

#define MY92XX_MODEL MY92XX_MODEL_MY9231
#define MY92XX_CHIPS 2
#define MY92XX_DI_PIN 12
#define MY92XX_DCKI_PIN 14

#define MY92XX_COLD 0
#define MY92XX_WARM 1
#define MY92XX_RED 4
#define MY92XX_GREEN 3
#define MY92XX_BLUE 5

my92xx * _my92xx;

byte currentR, currentG, currentB, currentW, currentC;
// output states: 0 -> off; 1 -> on; 2 -> on timer;
int outputState = 0;

const char * outputStateString [3] = { "OFF", "ON", "TIMER" };
#if TESTING
const unsigned long turnOffTimerDuration_ms = 6UL * SECOND_ms;
#else
const unsigned long turnOffTimerDuration_ms = 30UL * MINUTE_ms;
#endif

unsigned long timerStartedAt_ms = 0UL;
unsigned long timeoutValue_ms = 0UL;
unsigned long settingsDirtyAt_ms = 0UL;

/******************************* Global Vars **********************************/
#pragma mark -> vars global

char uniqueToken [ 9 ];
unsigned long lastRebootAt_nts = 0UL;

const size_t pBufLen = 128;
char pBuf [ pBufLen ];
const int htmlMessageLen = 3072;
char htmlMessage [htmlMessageLen];
const int timeStringLen = 32;
char timeString [ timeStringLen ] = "";
char bootTimeString [ timeStringLen ] = "";

const size_t jsonStrSize = 1000;
char jsonString [ jsonStrSize ]; // needs to be global to be on heap!

bool forceWSUpdate = false;

/**************************** Function Prototypes *****************************/
#pragma mark -> function prototypes

void POST ();

void initializeGPIO ();
void initializeBulb ();
void initializeLittleFS ();
void initializeLampColor ();
void lampColorInitialize_powercycle ();
void lampColorInitialize_restorePrevious ();
void initializeWebServer ();

bool connect_WiFi ();
bool connect_MQTT ();
void handleReceivedMQTTMessage ( char * topic, byte * payload, unsigned int length );
void sendSettingsToMQTT ( bool force = false );
int sendValueToMQTT ( const char * topic, int value, const char * name, bool retainP = false );
int sendValueToMQTT ( const char * topic, const char * value, const char * name, bool retainP = false );

void interpretNewCommandString ( char * theTopic, char * thePayload );

void htmlResponse_root ();
void webSocketEvent ( uint8_t num, WStype_t type, uint8_t * payload, size_t length );
void update_WebSocket ();

time_t getUnixTime();
void sendNTPpacket ( IPAddress& address );
void formatTimeString ( char * result, int resultLen, unsigned long time );
void formatIntervalString ( char * result, int resultLen, unsigned long time );

void saveSettingsToFile ();

int setOutputState ( int state, bool force = false );
void sendOutput ();
void setOutput ( byte R, byte G, byte B, byte W, byte C );
void toggleOutput ();
void setTimer ( unsigned long p_timeoutValue_ms = turnOffTimerDuration_ms );
unsigned long timeRemaining_ms ();

// magic juju to return array size
// see http://forum.arduino.cc/index.php?topic=157398.0
template< typename T, size_t N > size_t ArraySize (T (&) [N]){ return N; }

/******************************************************************************/

void setup ( void ) {

if ( VERBOSE >= 12 ) delay ( 10000 );

initializeGPIO ();
initializeBulb ();

#if TESTING
Serial.printf ( "\n\nTESTING mode %d\n", TESTING );
POST();
#endif

/*********************** LittleFS File System Setup *************************/

initializeLittleFS ();

lampColorInitialize_restorePrevious ();

/****************************** WiFi Setup **********************************/

// for security reasons, the network settings are stored in a private library
Network.init ( WIFI_LOCALE );

if ( ! strncmp ( Network.chipName, "unknown", 12 ) ) {
Network.describeESP ( Network.chipName );
}

// Connect to WiFi access point.
Serial.printf ( "\nESP8266 device '%s' connecting to %s\n", Network.chipName, Network.ssid );
yield ();

WiFi.config ( Network.ip, Network.gw, Network.mask, Network.dns );
// for exception 3 problem, use erase wifi parameters once
WiFi.begin ( Network.ssid, Network.password );

Serial.println ( F ( "about to connect_WiFi" ) );
connect_WiFi ();

// while ( WiFi.status() != WL_CONNECTED ) {
// Serial.print ( F ( "v" ) );
// delay ( 500 ); // implicitly yields but may not pet the nice doggy
// }
// Serial.println ();

if ( VERBOSE >= 15 ) WiFi.printDiag ( Serial );

if ( VERBOSE >= 4 ) {
Serial.println ( F ( "WiFi connected with IP address: " ) );
Serial.println ( WiFi.localIP() );
}

/************************** Random Token Setup ******************************/

// create a hopefully-unique string to identify this program instance
// REQUIREMENT: must have initialized the network for chipName

#ifdef cbmnetworkinfo_h
// Chuck-only
strncpy ( uniqueToken, Network.chipName, 9 );
#else
snprintf ( uniqueToken, 9, "%08x", ESP.getChipId() );
#endif

snprintf ( mdnsOtaId, mdnsOtaIdLen, "esp8266-%s", uniqueToken );
Serial.printf ( "mDNS host name: %s.local\n", mdnsOtaId );

/******************************* UDP Setup **********************************/

conn_UDP.begin ( port_UDP );

if ( VERBOSE >= 4 ) Serial.println ( F ( "UDP connected" ) );
delay ( 50 );

/****************************** MQTT Setup **********************************/

snprintf ( mqtt_clientID, mqttClientIDLen, "%s_%s", PROGNAME, uniqueToken );

snprintf ( mqttCmdTopic, mqttTopicLen, "%s/%s/%s", mqtt_baseTopic, uniqueToken, "command" );
snprintf ( mqttStatusTopic, mqttTopicLen, "%s/%s/%s", mqtt_baseTopic, uniqueToken, "status" );
snprintf ( mqttTimeoutTopic, mqttTopicLen, "%s/%s/%s", mqtt_baseTopic, uniqueToken, "timeout_ms" );
snprintf ( mqttResetReasonTopic, mqttTopicLen, "%s/%s/%s", mqtt_baseTopic, uniqueToken, "reset_reason" );

conn_MQTT.setCallback ( handleReceivedMQTTMessage );

/*

// testing because it wouldn't find the DNS -- it needs the Network.dns arg
// when we connect!!! See above:
// WiFi.config ( Network.ip, Network.gw, Network.mask, Network.dns );

IPAddress mosq;
WiFi.hostByName( CBM_MQTT_SERVER, mosq );
Serial.print ( F ( "Here's what we found for the IP of '" ) );
Serial.print ( CBM_MQTT_SERVER );
Serial.print ( F ( "': " ) );
Serial.println ( mosq );

*/

conn_MQTT.setServer ( CBM_MQTT_SERVER, CBM_MQTT_SERVERPORT );
if ( VERBOSE >= 5 ) {
Serial.print ( F ( "Connecting to MQTT at " ) );
Serial.println ( CBM_MQTT_SERVER );
}
connect_MQTT ();

yield ();

/******************************* NTP Setup **********************************/

setSyncProvider ( getUnixTime );
setSyncInterval ( 300 ); // seconds
unsigned long beganWaitingAt_ms = millis();

while ( ( timeStatus() != timeSet ) && ( ( millis () - beganWaitingAt_ms ) < 10000UL ) ) {
now ();
Serial.print ( F ( "t" ) );
delay ( 100 );
yield();
}

snprintf ( pBuf, pBufLen, "%s/%s/time/startup", PROGNAME, uniqueToken );
if ( timeStatus() == timeSet ) {
lastRebootAt_nts = now();
formatTimeString ( bootTimeString, timeStringLen, now () );
sendValueToMQTT ( pBuf, bootTimeString, "Startup Time" );
Serial.printf ( "Starting at %s\n", bootTimeString );
} else {
Serial.println ( F ( "WARNING: No NTP response within 10 seconds..." ) );
sendValueToMQTT ( pBuf, "WARNING no NTP within 10 seconds", "Startup Time" );
}
yield();

/***************************** OTA Update Setup *****************************/

#ifdef ALLOW_OTA

// Port defaults to 8266
// ArduinoOTA.setPort(8266);

// Hostname defaults to esp8266-[ChipID]
ArduinoOTA.setHostname ( (const char *) mdnsOtaId );

// No authentication by default
ArduinoOTA.setPassword ( (const char *) CBM_OTA_KEY );

ArduinoOTA.onStart([]() {
Serial.println ( F ( "Start" ) );
});

ArduinoOTA.onEnd([]() {
Serial.println ( F ( "\nEnd" ) );
});

ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf ( "Progress: %u%%\r", ( progress / ( total / 100 ) ) );
});

ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println ( F ( "Auth Failed" ) );
else if (error == OTA_BEGIN_ERROR) Serial.println ( F ( "Begin Failed" ) );
else if (error == OTA_CONNECT_ERROR) Serial.println ( F ( "Connect Failed" ) );
else if (error == OTA_RECEIVE_ERROR) Serial.println ( F ( "Receive Failed" ) );
else if (error == OTA_END_ERROR) Serial.println ( F ( "End Failed" ) );
});

ArduinoOTA.begin();
Serial.println ( F ( "ArduinoOTA running" ) );

yield();

#endif

/**************************** HTML Server Setup *****************************/

initializeWebServer ();

// /**************************** mDNS Server Setup *****************************/
//
// snprintf ( mdnsOtaId, mdnsOtaIdLen, "%s_%s", PROGNAME, uniqueToken );
// if (!MDNS.begin ( mdnsOtaId ) ) { // Start the mDNS responder for .local
// Serial.println ( F ( "Error setting up MDNS responder!" ) );
// }
// Serial.printf ( "mDNS responder '%s.local' started", mdnsOtaId );


/************************* Power-on Color Setting ***************************/

initializeLampColor ();

/************************** Report successful init **************************/

Serial.printf ( "\n%s v%s %s cbm", PROGNAME, VERSION, VERDATE );
#ifdef TESTING
Serial.printf ( " TESTING level %d\n", TESTING );
#else
Serial.println ();
#endif

}

void loop ( void ) {

const unsigned long wsSendInterval_ms = 5UL * SECOND_ms;
static unsigned long lastWSSendAt_ms = 0UL;

/****************************************************************************/

if ( ! connect_WiFi () ) return;
yield();

#ifdef ALLOW_OTA
ArduinoOTA.handle();
yield ();
#endif

/****************************************************************************/

// Ensure the connection to the MQTT server is alive (this will make the first
// connection and automatically reconnect when disconnected). See the MQTT_connect
// function definition below.

if ( ! connect_MQTT () ) return;

conn_MQTT.loop();
delay ( 10 ); // fix some issues with WiFi stability
yield();

/****************************************************************************/

htmlServer.handleClient();
webSocket.loop();
yield ();

/****************************************************************************/

if ( ( forceWSUpdate )
|| ( lastWSSendAt_ms == 0UL ) // initial update
|| ( ( millis() - lastWSSendAt_ms ) > wsSendInterval_ms ) // regular update
|| ( timerStartedAt_ms && ( ( millis() - lastWSSendAt_ms ) > 1000UL ) ) // update faster while counting down
) {

if ( ( VERBOSE >= 15 ) && forceWSUpdate ) Serial.println ( F ( "forced WS update" ) );

// dtostrf ( temperature_degC, 0, 1, fBuf );
// snprintf ( pBuf, pBufLen, "%s/%s", "raw/temperature_degC/air", sensorId );
// sendValueToMQTT ( pBuf, fBuf, "Temperature" );

if ( timerStartedAt_ms ) {
// update if clock is ticking
sendValueToMQTT ( mqttTimeoutTopic, timeRemaining_ms(), "time remaining" );
}

update_WebSocket ();
lastWSSendAt_ms = millis();

}

yield ();

// if settings have changed, send them to MQTT?
// if changed by web page, yes
// if changed by power-on, yes
// if soft reset, it screws up the settings?
sendSettingsToMQTT ();

/****************************************************************************/

if ( rebootInterval_ms && ( millis() > rebootInterval_ms ) ) {
snprintf ( pBuf, pBufLen, "%s/%s/time/restart", PROGNAME, uniqueToken );
sendValueToMQTT ( pBuf, bootTimeString, "Restart Time" );
Serial.printf ( "Rebooting at %s\n", bootTimeString );
delay ( 1000 );

ESP.restart(); // soft reset; ESP.reset() is a hard reset leaving regs unknown...

}


if ( outputState ) {
// on
if ( timerStartedAt_ms != 0UL ) {
// timer is running
if ( timeRemaining_ms() == 0UL ) {
// timed out
Serial.printf ( "Timeout!\n" );
setOutputState ( 0 );
timerStartedAt_ms = 0UL;
}
}
}


/****************************************************************************/

yield ();

}

// *****************************************************************************
// ***************************** Initializations *******************************
// *****************************************************************************

void POST () {

Serial.print ( F ( "\n\nPOST\n\n" ) );

int oldVal = setOutputState ( 1 );

sendOutput ( 0x00, 0x00, 0x00, 0x00, 0x00 );
delay ( 100 );

sendOutput ( 0x80, 0x00, 0x00, 0x00, 0x00 );
delay ( 100 );
sendOutput ( 0xff, 0x00, 0x00, 0x00, 0x00 );
delay ( 250 );
sendOutput ( 0x00, 0x00, 0x00, 0x00, 0x00 );
delay ( 10 ); yield();

sendOutput ( 0x00, 0x80, 0x00, 0x00, 0x00 );
delay ( 100 );
sendOutput ( 0x00, 0xff, 0x00, 0x00, 0x00 );
delay ( 250 );
sendOutput ( 0x00, 0x00, 0x00, 0x00, 0x00 );
delay ( 10 ); yield();

sendOutput ( 0x00, 0x00, 0x80, 0x00, 0x00 );
delay ( 100 );
sendOutput ( 0x00, 0x00, 0xff, 0x00, 0x00 );
delay ( 250 );
sendOutput ( 0x00, 0x00, 0x00, 0x00, 0x00 );
delay ( 10 ); yield();

sendOutput ( 0x00, 0x00, 0x00, 0x80, 0x00 );
delay ( 100 );
sendOutput ( 0x00, 0x00, 0x00, 0xff, 0x00 );
delay ( 250 );
sendOutput ( 0x00, 0x00, 0x00, 0x00, 0x00 );
delay ( 10 ); yield();

sendOutput ( 0x00, 0x00, 0x00, 0x00, 0x80 );
delay ( 100 );
sendOutput ( 0x00, 0x00, 0x00, 0x00, 0xff );
delay ( 250 );
sendOutput ( 0x00, 0x00, 0x00, 0x00, 0x00 );

for ( int i = 0; i < 2 * 5; i++ ) {
for ( int j = 0; j < 256; j += 10 ) {
int b = ( i % 2 ) ? 255 - j : j;
sendOutput ( i * 20, 0x00, 0x00, j, j );
delay ( 20 );
}
}

sendOutput ( 0x00, 0x00, 0x00, 0x80, 0x80 );
for ( int i = 0; i < 5; i++ ) {
setOutputState ( 1 );
delay ( 100 );
setOutputState ( 0 );
delay ( 100 );
}

// restore bulb settings
setOutput ( currentR, currentG, currentB, currentW, currentC );
setOutputState ( oldVal );

yield();

}

void initializeGPIO () {

/*
pinMode ( pd_LED_green, OUTPUT ); digitalWrite ( pd_LED_green, INVERSE_LOGIC_OFF );
pinMode ( pd_LED_red, OUTPUT ); digitalWrite ( pd_LED_red, INVERSE_LOGIC_OFF );
pinMode ( pdOutput, OUTPUT ); digitalWrite ( pdOutput, LOW );
pinMode ( pdC5, OUTPUT ); digitalWrite ( pdC5, LOW );
pinMode ( pdButton, INPUT ); // has built-in pullup

*/

Serial.begin ( BAUDRATE );
while ( ! Serial && ( millis() < 2000 ) ) {
// int newStatus = 1 - digitalRead ( pd_LED_red );
// digitalWrite ( pd_LED_green, newStatus );
// digitalWrite ( pd_LED_red, newStatus );
// #ifdef TESTING
// digitalWrite ( pdOutput, newStatus );
// #endif
delay ( 200 ); // wait a little more, if necessary, for serial to come up
yield ();
}
if ( VERBOSE >= 10 ) while ( ! Serial && ( millis() < 20000 ) );
Serial.print ( F ( "\n\n" ) );
// digitalWrite ( pd_LED_green, INVERSE_LOGIC_OFF );
// digitalWrite ( pd_LED_red, INVERSE_LOGIC_OFF );

}

void initializeBulb () {

// _my92xx = new my92xx ( MY92XX_MODEL, MY92XX_CHIPS,
// MY92XX_DI_PIN, MY92XX_DCKI_PIN,
// MY92XX_COMMAND_DEFAULT );

/*
freq divide 4 breaks
one shot enable no diff
bit width 16 breaks
cmd reaction slow breaks
*/

_my92xx = new my92xx ( MY92XX_MODEL, MY92XX_CHIPS,
MY92XX_DI_PIN, MY92XX_DCKI_PIN,
{ \
.scatter = MY92XX_CMD_SCATTER_APDM, \
.frequency = MY92XX_CMD_FREQUENCY_DIVIDE_1, \
.bit_width = MY92XX_CMD_BIT_WIDTH_8, \
.reaction = MY92XX_CMD_REACTION_FAST, \
.one_shot = MY92XX_CMD_ONE_SHOT_DISABLE, \
.resv = 0 \
}
);

_my92xx->setState(true);

currentR = 0;
currentG = 0;
currentB = 0;
currentW = 0;
currentC = 0;

}

void initializeLittleFS () {

if ( ! LittleFS.begin() ) {
Serial.println ( F ( "INFO: LittleFS.begin() failed" ) );
Serial.print ( F (" formatting...") );
if ( LittleFS.format() ) {
Serial.println ( F (" ok") );
} else {
Serial.println ( F (" FAILED") );
}
}

}

void initializeLampColor () {

// enum rst_reason {
// REASON_DEFAULT_RST = 0, // normal startup by power on
// REASON_WDT_RST = 1, // hardware watch dog reset
// REASON_EXCEPTION_RST = 2, // exception reset, GPIO status won’t change
// REASON_SOFT_WDT_RST = 3, // software watch dog reset, GPIO status won’t change
// REASON_SOFT_RESTART = 4, // software restart ,system_restart , GPIO status won’t change
// REASON_DEEP_SLEEP_AWAKE = 5, // wake up from deep-sleep
// REASON_EXT_SYS_RST = 6 // external system reset
// };
//
// char * reasons [ ] = { "REASON_DEFAULT",
// "REASON_WDT",
// "REASON_EXCEPTION",
// "REASON_SOFT_WDT",
// "REASON_SOFT_RESTART",
// "REASON_DEEP_SLEEP_AWAKE",
// "REASON_EXT_SYS_RST "
// };
//
//
// // byte reset_reason_0 = rtc_get_reset_reason ( 0 );
// // Serial.print ( F ( "Reset reason 0: " ) ); Serial.println ( reset_reason_0, HEX );
// // byte reset_reason_1 = rtc_get_reset_reason ( 1 );
// // Serial.print ( F ( "Reset reason 1: " ) ); Serial.println ( reset_reason_1, HEX );
// rst_info *resetInfo;
// resetInfo = ESP.getResetInfoPtr();
// byte reset_reason = resetInfo->reason;
//
// Serial.print ( F ( "Reset reason: " ) ); Serial.println ( reasons [ reset_reason ] );
// sendValueToMQTT ( mqttResetReasonTopic, reasons [ reset_reason ], "Reset reason", true );

// calculate time since previous power-on

// read previous power-on time
// in UNIX time, seconds since 12:00 AM Jan 1, 1970 as time_t
// note: time_t is defined as unsigned long

while ( ! ntpTimeGoodP && ( millis() < 30000UL ) ) {
Serial.println ( F ( "Trying to get a goot time from NTP" ) );
getUnixTime ();
delay ( 5000 );
}

time_t interval_secs = 1000000UL;

if ( ntpTimeGoodP ) {

File f;

f = LittleFS.open ( "/start_UNIX_time.txt", "r" );
if ( VERBOSE >= 4 ) Serial.printf ( "INFO: LittleFS file%s opened\n", f ? "" : " not" );

unsigned long lastStartAt_unixtime;
if ( f ) {
// file will contain one line, with either "white" or "red"
// to identify the color it will next start with
lastStartAt_unixtime = f.parseInt ();
f.close();

if ( VERBOSE >= 5 ) {
Serial.print ( F ( " ... file contained: " ) );
Serial.println ( lastStartAt_unixtime );
}
} else {
Serial.println ( F ( "WARNING: start_UNIX_time.txt file not available" ) );
}

// now rewrite that file with the current time

f = LittleFS.open ( "/start_UNIX_time.txt", "w" );
f.print ( now() );
f.close();

interval_secs = now() - lastStartAt_unixtime;
if ( VERBOSE >= 3 ) {
Serial.print ( F ( "Seconds since last startup: " ) );
Serial.println ( interval_secs );
}

} else {
// don't have a good time from NTP
}

// if ( true || reset_reason == ESP_RST_POWERON ) {
// if ( ( reset_reason == REASON_DEFAULT_RST )
// || ( reset_reason == REASON_EXT_SYS_RST ) ) {

if ( interval_secs < 60UL ) {
lampColorInitialize_powercycle ();
} else {
// reboot interval restart -- probably leave settings to what's in MQTT
// but MQTT doesn't provide a "color" command - colors individually stored, under "status"
// so no "command"
lampColorInitialize_restorePrevious ();
}
sendSettingsToMQTT ( 1 );
}

void lampColorInitialize_powercycle () {

File f;

// power on, but not soft reset
f = LittleFS.open ( "/power_on_color.txt", "r" );
Serial.printf ( "INFO: LittleFS file%s opened\n", f ? "" : " not" );

#define colorStringLen 10
char colorString [ colorStringLen ];
if ( f && f.available() ) {
// file will contain one line, with either "white" or "red"
// to identify the color it will next start with
f.readString().toCharArray( colorString, colorStringLen );
f.close();

if ( VERBOSE >= 5 ) {
Serial.print ( F ( " ... file contained: " ) );
Serial.println ( colorString );
}
} else {
Serial.println ( F ( "WARNING: power_on_color.txt file not available" ) );
}

// regardless of read status, try to write a file

f = LittleFS.open ( "/power_on_color.txt", "w" );
if ( ! strncmp ( colorString, "red", 6 ) ) {
if ( VERBOSE >= 5 ) Serial.println ( F ( "was red; make white" ) );
f.print ( F ( "white" ) );
setOutput ( WHITE_color ); // white
} else {
if ( VERBOSE >= 5 ) Serial.println ( F ( "was white; make red" ) );
f.print ( F ( "red" ) );
setOutput ( RED_color ); // red
}
f.close();
setOutputState ( 1 );
}

void lampColorInitialize_restorePrevious () {

File f;

// soft reset
f = LittleFS.open ( "/soft_reset_color.txt", "r" );
Serial.printf ( "INFO: LittleFS file%s opened\n", f ? "" : " not" );

int stateVector [ 6 ] = { -1, -1, -1, -1, -1, -1 } ;
if ( f && f.available() ) {
/*
file will contain a comma-separated list of 6 items:
5 color values
1 0-or-1 "on" value
*/

Serial.print ( F ( " ... file contained: " ) );
for ( int i = 0; i < 6; i++ ) {
stateVector [ i ] = f.parseInt();
Serial.print ( stateVector [ i ] );
Serial.print ( ( i < 5 ) ? ", " : "\n" );
}
f.close();

// set stateVector if we got a valid line
if ( stateVector [ 5 ] >= 0 ) {
// these values should never all be zero
setOutput ( stateVector [ 0 ],
stateVector [ 1 ],
stateVector [ 2 ],
stateVector [ 3 ],
stateVector [ 4 ] );
// this value will be zero of lamp was off
setOutputState ( stateVector [ 5 ], 1 );
} else {
Serial.println ( F ( "Incomplete data from soft-restart file" ) );
}

} else {
Serial.println ( F ( "WARNING: soft_reset_color.txt file not available" ) );
}

}

void initializeWebServer () {

/**************************** HTML Server Setup *****************************/

// for html, do the on's before server begin

htmlServer.on ( "/", htmlResponse_root );
htmlServer.onNotFound([](){
htmlServer.send(404, "text/plain", "404: Not found");
});
htmlServer.begin();

webSocket.begin();
webSocket.onEvent ( webSocketEvent );
// the below are applicable to a WebSocket CLIENT...
// webSocket.setReconnectInterval ( 15000 ); // ms to reconnect after failure
// ms;
// webSocket.enableHeartbeat ( 30000, 3000, 2 );

}

// *****************************************************************************
// ********************************** WiFi *************************************
// *****************************************************************************

bool connect_WiFi () {

if ( WiFi.status() == WL_CONNECTED ) return true;

const unsigned long oldConnectionTimeout_ms = 10UL * SECOND_ms;

unsigned long startTime_ms = millis();
unsigned long lastDotAt_ms = millis();
unsigned long lastBlinkAt_ms = millis();
static bool newConnection = true;
const int bufLen = 25;
char strBuf [ bufLen ];
bool printed = false;

sendOutput ( 0x00, 0x00, 0x40, 0x00, 0x00 ); // blue while connecting

while ( WiFi.status() != WL_CONNECTED ) {

if ( VERBOSE > 4 && ! printed ) {
Serial.print ( F ( "\nChecking wifi..." ) );
printed = true;
newConnection = true;
}

if ( ( millis() - lastDotAt_ms ) > 1000 ) {
Serial.print ( F ( "." ) );
lastDotAt_ms = millis();
}

if ( ( millis() - startTime_ms ) > oldConnectionTimeout_ms ) {

Serial.printf ( "About to smart config...\n" );

WiFi.beginSmartConfig();

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print ( WiFi.smartConfigDone() );
}

Serial.printf ( "Done smart config.\n" );

}

delay ( 1000 );
}

if ( newConnection && VERBOSE > 4 ) {
Serial.print ( F ( "\n WiFi connected as " ) );
Serial.print ( WiFi.localIP() );
Serial.print ( F ( " in " ) );
Serial.print ( millis() - startTime_ms );
Serial.println ( F ( " ms" ) );
}

startTime_ms = millis();
printed = false;

newConnection = false;

sendOutput ( currentR, currentG, currentB, currentW, currentC ); // restore

return true;
}

bool connect_MQTT () {

if ( conn_MQTT.connected() ) return true;

unsigned long startTime_ms = millis();
unsigned long lastDotAt_ms = millis();
unsigned long lastBlinkAt_ms = millis();
static bool newConnection = true;
const int bufLen = 25;
char strBuf [ bufLen ];
bool printed = false;

sendOutput ( 0x40, 0x00, 0x40, 0x00, 0x00 ); // violet while connecting

while ( ! conn_MQTT.connected () ) {
conn_MQTT.connect ( mqtt_clientID );
if ( VERBOSE > 4 && ! printed ) {
Serial.print ( F ( " Connecting to MQTT as '" ) ); Serial.print ( mqtt_clientID );
Serial.print ( F ( "'; status: " ) );
Serial.print ( conn_MQTT.state () );
Serial.print ( F ( "..." ) );
/*
-4 : MQTT_CONNECTION_TIMEOUT - the server didn't respond within the keepalive time
-3 : MQTT_CONNECTION_LOST - the network connection was broken
-2 : MQTT_CONNECT_FAILED - the network connection failed - perhaps the IP is bad!!!
-1 : MQTT_DISCONNECTED - the client is disconnected cleanly
0 : MQTT_CONNECTED - the cient is connected
1 : MQTT_CONNECT_BAD_PROTOCOL - the server doesn't support the requested version of MQTT
2 : MQTT_CONNECT_BAD_CLIENT_ID - the server rejected the client identifier
3 : MQTT_CONNECT_UNAVAILABLE - the server was unable to accept the connection
4 : MQTT_CONNECT_BAD_CREDENTIALS - the username/password were rejected
5 : MQTT_CONNECT_UNAUTHORIZED - the client was not authorized to connect_WiFi
*/
printed = true;
newConnection = true;
}
if ( ( millis() - lastDotAt_ms ) > 1000 ) {
Serial.print ( F ( "." ) );
lastDotAt_ms = millis();
}
delay ( 50 );
}

if ( newConnection ) {
if ( VERBOSE > 4 ) {
Serial.print ( F ( "\n MQTT connected in " ) );
Serial.print ( millis() - startTime_ms );
Serial.println ( F ( " ms" ) );
}
conn_MQTT.subscribe ( mqttCmdTopic, 1 ); // topic[, QoS]
// it looks like trying to subscribe with QoS 2 silently fails!
if ( VERBOSE > 4 ) {
Serial.print ( F ( " Subscribed to MQTT feed '" ) );
Serial.print ( mqttCmdTopic );
Serial.println ( F ( "'" ) );
}
}
// client.unsubscribe("/example");
newConnection = false;

sendOutput ( currentR, currentG, currentB, currentW, currentC ); // restore

return true;

}

// *****************************************************************************
// ********************************** MQTT *************************************
// *****************************************************************************

void sendSettingsToMQTT ( bool force ) {

const unsigned long sendSettingsDelay_ms = 2UL * SECOND_ms;
static byte oldR = 0x00;
static byte oldG = 0x00;
static byte oldB = 0x00;
static byte oldW = 0x00;
static byte oldC = 0x00;
static int oldState = -99;

if ( force
|| ( settingsDirtyAt_ms
&& ( millis() - settingsDirtyAt_ms ) > sendSettingsDelay_ms
)
) {

if ( currentR != oldR ) {
snprintf ( pBuf, pBufLen, "%s/%s", mqttStatusTopic, "red" );
sendValueToMQTT ( pBuf, currentR, "red", true );
oldR = currentR;
}
if ( currentG != oldG ) {
snprintf ( pBuf, pBufLen, "%s/%s", mqttStatusTopic, "green" );
sendValueToMQTT ( pBuf, currentG, "green", true );
oldG = currentG;
}
if ( currentB != oldB ) {
snprintf ( pBuf, pBufLen, "%s/%s", mqttStatusTopic, "blue" );
sendValueToMQTT ( pBuf, currentB, "blue", true );
oldB = currentB;
}
if ( currentW != oldW ) {
snprintf ( pBuf, pBufLen, "%s/%s", mqttStatusTopic, "warm" );
sendValueToMQTT ( pBuf, currentW, "warm", true );
oldW = currentW;
}
if ( currentC != oldC ) {
snprintf ( pBuf, pBufLen, "%s/%s", mqttStatusTopic, "cool" );
sendValueToMQTT ( pBuf, currentC, "cool", true );
oldC = currentC;
}

if ( outputState != oldState ) {
snprintf ( pBuf, pBufLen, "%s/%s", mqttStatusTopic, "state" );
sendValueToMQTT ( pBuf, outputState, "Output state", true );
oldState = outputState;
}

saveSettingsToFile ();

settingsDirtyAt_ms = 0UL;
}
}

int sendValueToMQTT ( const char * topic, int value, const char * name, bool retainP ) {
const int valLen = 10;
char val [ valLen ];
snprintf ( val, valLen, "%d", value );
return sendValueToMQTT ( topic, val, name, retainP );
}

int sendValueToMQTT ( const char * topic, const char * value, const char * name, bool retainP ) {

/*

Send data via MQTT to Mosquitto

*/

int tLen = strlen ( topic );
int vLen = strlen ( value );
if ( vLen < 1 ) {
if ( VERBOSE >= 10 ) {
Serial.print ( F ( "\nNot sending null payload " ) );
Serial.println ( name );
}
return 0;
}

if ( ( tLen + vLen ) > MQTT_MAX_PACKET_SIZE ) {
if ( VERBOSE >= 10 ) {
Serial.print ( F ( "\nNot sending oversized ( " ) );
Serial.print ( tLen + vLen );
Serial.print ( F ( " > 128 ) packet " ) );
Serial.println ( name );
}
return 0;
}

if ( VERBOSE >= 10 ) {
Serial.print ( F ( "Sending " ) );
Serial.print ( topic );
Serial.print ( F ( " ( name = " ) );
Serial.print ( name );
Serial.print ( F ( ", len = " ) );
Serial.print ( tLen + vLen );
Serial.print ( F ( " ): " ) );
Serial.print ( value );
Serial.print ( F ( " ... " ) );
}

/*

Note: each publish call seems to take about a second
Even though connection remains good
But only when the MQTT server is overtaxed - probably in need of restart!

*/

bool success = conn_MQTT.publish ( topic, value, retainP );
if ( VERBOSE >= 10 ) Serial.println ( success ? "OK!" : "Failed" );

return success ? ( tLen + vLen ) : -1;
};

void handleReceivedMQTTMessage ( char * topic, byte * payload, unsigned int length ) {

const size_t pBufLen = 512;
char pBuf [ pBufLen ];
memcpy ( pBuf, payload, length );
pBuf [ length ] = '\0';

if ( VERBOSE >= 8 ) {
Serial.print ( F ( "Got: " ) );
Serial.print ( topic );
Serial.print ( F ( ": " ) );
Serial.print ( pBuf );
Serial.println ();
}

interpretNewCommandString ( topic, pBuf );
}

void interpretNewCommandString ( char * theTopic, char * thePayload ) {

DynamicJsonDocument doc ( 400 );

if ( VERBOSE >= 4 ) {
Serial.print ( F ( "thePayload: '" ) );
Serial.print ( thePayload );
Serial.println ( F ( "'" ) );
}

DeserializationError error = deserializeJson ( doc, thePayload );

// Test if parsing succeeds.
if ( error ) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}

// Most of the time, you can rely on the implicit casts.
// In other case, you can do doc["time"].as();

if ( doc.containsKey ( "color" ) ) {
byte R, G, B, W, C;
R = doc["color"][0]; // || 0x00;
G = doc["color"][1]; // || 0x00;
B = doc["color"][2]; // || 0x00;
W = doc["color"][3]; // || 0x00;
C = doc["color"][4]; // || 0x00;

setOutput ( R, G, B, W, C );
setOutputState ( 1 );
}

if ( doc.containsKey ( "state" ) ) {
setOutputState ( doc["state"].as() );
}

if ( doc.containsKey ( "timer" ) ) {
setTimer ();
}

}

// *****************************************************************************
// ********************************* output *************************************
// *****************************************************************************

void toggleOutput () {
setOutputState ( ! outputState );
}

int setOutputState ( int newSetting, bool force ) {

// turn on/off without losing settings...
// force resets the output state and resends the retained brightness values

if ( VERBOSE >= 4 ) {
Serial.printf ( "State: %s\n", newSetting ? "ON" : "OFF" );
}

int oldVal = outputState;

if ( ( newSetting != outputState ) || force ) {

// if not force, then newSetting must be unequal to outputState
// if force, can't assume anything about the equality
if ( newSetting >= 0 ) outputState = newSetting;
if ( outputState ) {
// turn on; could be timer or just "on"
if ( ! currentR && ! currentG && ! currentB && ! currentW && ! currentC ) {
// everything's set to zero brightness
if ( VERBOSE >= 12 ) Serial.println ( F ( "Setting default nonzero values" ) );
currentW = 0x80;
currentC = 0x80;
}
sendOutput ( currentR, currentG, currentB, currentW, currentC );
} else {
// turn off, leaving settings alone
if ( VERBOSE >= 12 ) Serial.println ( F ( "Turning off, leaving settings" ) );
sendOutput ( 0x00, 0x00, 0x00, 0x00, 0x00 );
}

if ( VERBOSE > 10 ) Serial.printf ( " -> output is %s\n", outputStateString [ outputState ] );
settingsDirtyAt_ms = millis();
timerStartedAt_ms = 0UL; // cancel timer on any interaction
}

forceWSUpdate = true;
return ( oldVal );

}

void setOutput ( byte R, byte G, byte B, byte W, byte C ) {

// turning off doesn't call this routine, so current values should never be 0

currentR = R;
currentG = G;
currentB = B;
currentW = W;
currentC = C;

sendOutput ( currentR, currentG, currentB, currentW, currentC );
settingsDirtyAt_ms = millis();

forceWSUpdate = true;

}

void sendOutput ( byte R, byte G, byte B, byte W, byte C ) {

// lowest level send - just send these values, without side effects
if ( VERBOSE >= 4 ) {
Serial.printf ( "Color settings: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n",
R, G, B, W, C );
}

_my92xx->setChannel ( MY92XX_RED, (unsigned int) R );
_my92xx->setChannel ( MY92XX_GREEN, (unsigned int) G );
_my92xx->setChannel ( MY92XX_BLUE , (unsigned int) B );
_my92xx->setChannel ( MY92XX_WARM, (unsigned int) W );
_my92xx->setChannel ( MY92XX_COLD, (unsigned int) C );
_my92xx->update();

}

void setTimer ( unsigned long p_timeoutValue_ms ) {

if ( VERBOSE >= 4 ) {
Serial.printf ( "Timer enabled\n" );
}

setOutputState ( 2 );
timerStartedAt_ms = millis();
timeoutValue_ms = p_timeoutValue_ms;
// Serial.printf ( "setTimer: setting %ul ms; turnoff at: %ul ms\n", timeoutValue_ms, turnOffAt_ms );
sendValueToMQTT ( mqttTimeoutTopic, timeoutValue_ms, "Timeout value" );
}

unsigned long timeRemaining_ms () {
if ( timerStartedAt_ms == 0 ) return 0UL;
bool timedOut = ( millis() - timerStartedAt_ms ) > timeoutValue_ms;
return ( timedOut ? 0UL : ( timerStartedAt_ms + timeoutValue_ms ) - millis() );
}

// *****************************************************************************
// ********************************** HTML *************************************
// *****************************************************************************

// { "color": [ 0, 0, 0, 128, 255 ] }
// { "timer": 1 }
// { "state": 1 }

void htmlResponse_root () {

sendOutput ( 0x40, 0x40, 0x00, 0x00, 0x00 );

const char * htmlMessage = R"HTML(







p{} .data { font-weight: bold; }
.right { text-align: right; }

.myButton {
-moz-box-shadow: 3px 4px 0px 0px #899599;
-webkit-box-shadow: 3px 4px 0px 0px #899599;
box-shadow: 3px 4px 0px 0px #899599;
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #ededed), color-stop(1, #bab1ba));
background:-moz-linear-gradient(top, #ededed 5%, #bab1ba 100%);
background:-webkit-linear-gradient(top, #ededed 5%, #bab1ba 100%);
background:-o-linear-gradient(top, #ededed 5%, #bab1ba 100%);
background:-ms-linear-gradient(top, #ededed 5%, #bab1ba 100%);
background:linear-gradient(to bottom, #ededed 5%, #bab1ba 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed', endColorstr='#bab1ba',GradientType=0);
background-color:#ededed;
-moz-border-radius:15px;
-webkit-border-radius:15px;
border-radius:15px;
border:1px solid #d6bcd6;
display:inline-block;
cursor:pointer;
color:#3a8a9e;
font-family:Arial;
font-size:17px;
padding:7px 25px;
text-decoration:none;
text-shadow:0px 1px 0px #e1e2ed;
}
.myButton .willBe { display:none; }
.myButton:hover {
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #bab1ba), color-stop(1, #ededed));
background:-moz-linear-gradient(top, #bab1ba 5%, #ededed 100%);
background:-webkit-linear-gradient(top, #bab1ba 5%, #ededed 100%);
background:-o-linear-gradient(top, #bab1ba 5%, #ededed 100%);
background:-ms-linear-gradient(top, #bab1ba 5%, #ededed 100%);
background:linear-gradient(to bottom, #bab1ba 5%, #ededed 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#bab1ba', endColorstr='#ededed',GradientType=0);
background-color:#bab1ba;
}
.myButton:hover .is { display:none; }
.myButton:hover .willBe { display:inline-block; }
.myButton:active {
position:relative;
top:1px;
}


.timerSliderContainer {
position: relative;
top: 20px;
width: 50%;
}

.timerSlider,.rSlider,.gSlider,.bSlider,.wSlider,.cSlider {
-webkit-appearance: none;
width: 300px;
height: 25px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
}

.timerSlider {
width: 500px;
}

.timerSlider:hover,
.rSlider:hover,
.gSlider:hover,
.bSlider:hover,
.wSlider:hover,
.cSlider:hover {
opacity: 1;
}

.timerSlider::-moz-range-thumb,
.rSlider::-moz-range-thumb,
.gSlider::-moz-range-thumb,
.bSlider::-moz-range-thumb,
.wSlider::-moz-range-thumb,
.cSlider::-moz-range-thumb {
width: 25px;
height: 25px;
cursor: pointer;
}

.timerSlider::-webkit-slider-thumb,
.rSlider::-webkit-slider-thumb,
.gSlider::-webkit-slider-thumb,
.bSlider::-webkit-slider-thumb,
.wSlider::-webkit-slider-thumb,
.cSlider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
cursor: pointer;
}

.timerSlider::-moz-range-thumb,
.timerSlider::-webkit-slider-thumb {
background: #4CAF50;
}

.rSlider::-moz-range-thumb,
.rSlider::-webkit-slider-thumb {
background: #FF0000;
}

.gSlider::-moz-range-thumb,
.gSlider::-webkit-slider-thumb {
background: #00FF00;
}

.bSlider::-moz-range-thumb,
.bSlider::-webkit-slider-thumb {
background: #0000FF;
}

.wSlider::-moz-range-thumb,
.wSlider::-webkit-slider-thumb {
background: #ffc0c0;
}

.cSlider::-moz-range-thumb,
.cSlider::-webkit-slider-thumb {
background: #c0c0ff;
}

.timerSliderinfo {
position: absolute;
top: -45px;
left: 150px;
background: #80d880;
}





var ws;
var wsUri = "ws:";
var loc = window.location;
if (loc.protocol === "https:") { wsUri = "wss:"; }
// wsUri += "//" + loc.host + loc.pathname.replace("simple","ws/simple");
wsUri += "//" + loc.host + ":81/";
ws = new WebSocket ( wsUri );

window.addEventListener('load', onLoad);

function wsConnect() {

ws.onerror = function (error) {
document.getElementById ( 'STATUS' ).innerHTML = "ERROR!!";
document.getElementById ( 'STATUS' ).style = "background:pink; color:red";
document.getElementById ("overlay").style.display = "block";
};
ws.onclose = function() {
document.getElementById ( 'STATUS' ).innerHTML = "<b>not</b> connected";
document.getElementById ( 'STATUS' ).style = "background:none; color:red";
document.getElementById ("overlay").style.display = "block";
// in case of lost connection tries to reconnect every 3 secs
setTimeout ( wsConnect, 3000 );
}
ws.onopen = function () {
document.getElementById ( 'STATUS' ).innerHTML = "connected";
document.getElementById ( 'STATUS' ).style = "background:none; color:black";
ws.send ( "Open for data" );
document.getElementById ( 'OUTPUT_CURRENT' ).innerHTML = "..yet unknown..";
document.getElementById ( 'OUTPUT_ONCHANGE' ).innerHTML = "..yet unknown tobe..";
}
ws.onmessage = function ( msg ) {
var data = msg.data;

document.getElementById ("overlay").style.display = "none";

var jsonObj = JSON.parse(data);
document.getElementById ( 'PROGNAME' ).innerHTML = jsonObj.PROGNAME;
document.getElementById ( 'VERSION' ).innerHTML = jsonObj.VERSION;
document.getElementById ( 'VERDATE' ).innerHTML = jsonObj.VERDATE;
document.getElementById ( 'UNIQUE_TOKEN' ).innerHTML = jsonObj.UNIQUE_TOKEN;
document.getElementById ( 'IP_STRING' ).innerHTML = jsonObj.IP_STRING;
var temp = jsonObj.MDNS_ID;
document.getElementById("MDNS_ID").innerHTML =
( temp == "<failure>" ) ? "<em>mDns failed</em>" : temp.concat ( ".local" );
document.getElementById ( 'TIME' ).innerHTML = jsonObj.TIME;
document.getElementById ( 'MQTT_CMD_TOPIC' ).innerHTML = jsonObj.MQTT_CMD_TOPIC;
document.getElementById ( 'MQTT_STATUS_TOPIC' ).innerHTML = jsonObj.MQTT_STATUS_TOPIC;
document.getElementById ( 'MQTT_TIMEOUT_TOPIC' ).innerHTML = jsonObj.MQTT_TIMEOUT_TOPIC;

document.getElementById ( 'R_VALUE' ).innerHTML = jsonObj.R_VALUE;
document.getElementById ( 'G_VALUE' ).innerHTML = jsonObj.G_VALUE;
document.getElementById ( 'B_VALUE' ).innerHTML = jsonObj.B_VALUE;
document.getElementById ( 'W_VALUE' ).innerHTML = jsonObj.W_VALUE;
document.getElementById ( 'C_VALUE' ).innerHTML = jsonObj.C_VALUE;
document.getElementById ( 'R_SLIDER' ).value = jsonObj.R_VALUE;
document.getElementById ( 'G_SLIDER' ).value = jsonObj.G_VALUE;
document.getElementById ( 'B_SLIDER' ).value = jsonObj.B_VALUE;
document.getElementById ( 'W_SLIDER' ).value = jsonObj.W_VALUE;
document.getElementById ( 'C_SLIDER' ).value = jsonObj.C_VALUE;

document.getElementById ( 'OUTPUT_CURRENT' ).innerHTML = jsonObj.OUTPUT_STATUS;
document.getElementById ( 'OUTPUT_ONCHANGE' ).innerHTML = jsonObj.OUTPUT_CHANGE_STATUS;
var secs = jsonObj.TIMER_SECONDS_REMAINING;
document.getElementById ( "TIMER_SLIDER" ).value = secs;
// var mins = Math.round ( secs / 6 ) / 10;
// document.getElementById ( "TIMER_SLIDER_VALUE" ).innerHTML = ""; // jsonObj.TIMER_SECONDS_REMAINING ? ( mins + " minutes" ) : "";
document.getElementById ( "TIMER_REMAINING" ).innerHTML = jsonObj.TIMER_REMAINING_STRING;

document.getElementById ( 'LAST_BOOT_AT' ).innerHTML = jsonObj.LAST_BOOT_AT;

}
}

function onLoad(event) {
initButtons();
initSliders();
}
function initButtons() {
document.getElementById('MAIN_BUTTON').addEventListener('click', toggle);
}
function toggle(){
ws.send('toggle');
}

function pageUnload () {
document.getElementById ( 'STATUS' ).innerHTML = "page unloaded";
document.getElementById ( 'STATUS' ).style = "background:none; color:red";
document.getElementById ("overlay").style.display = "block";
}

function initSliders() {
var timerSlider = document.getElementById("TIMER_SLIDER");
// document.getElementById("TIMER_SLIDER_VALUE").innerHTML = timerSlider.value;

timerSlider.oninput = function() {
// document.getElementById("TIMER_SLIDER_VALUE").innerHTML = this.value;
ws.send ( 'timer_value:' + this.value );
}

var rSlider = document.getElementById("R_SLIDER");
rSlider.oninput = function() {
ws.send ( 'red_value:' + this.value );
}

var gSlider = document.getElementById("G_SLIDER");
gSlider.oninput = function() {
ws.send ( 'green_value:' + this.value );
}

var bSlider = document.getElementById("B_SLIDER");
bSlider.oninput = function() {
ws.send ( 'blue_value:' + this.value );
}

var wSlider = document.getElementById("W_SLIDER");
wSlider.oninput = function() {
ws.send ( 'warm_value:' + this.value );
}

var cSlider = document.getElementById("C_SLIDER");
cSlider.oninput = function() {
ws.send ( 'cool_value:' + this.value );
}

}

Sonoff LED Bulb







uninitialized

..progname..
v..version..
..verdate.. cbm



Running on
..unique_token.. at
..ipstring..
..mdns..



MQTT command topic:
..mqtt_cmd_topic..

MQTT status topic:
..mqtt_status_topic..

MQTT timeout topic:
..mqtt_timeout_topic..






As of






Color Values




Red
..R..




Green
..G..




Blue
..B..




Warm White
..W..




Cool White
..C..








..currently..
..willbe..









Last boot at ..last_boot..



)HTML";

yield ();

htmlServer.send ( 200, "text/html", htmlMessage );

if ( VERBOSE >= 20 ) {
Serial.print ( F ( "done\n" ) );
}

setOutputState ( -1, true ); // revert output, canceling indicator
forceWSUpdate = true;

}

void webSocketEvent ( uint8_t num, WStype_t type, uint8_t * payload, size_t length ) {

// Serial.printf ( "Event %d received\n", type );

switch ( type ) {
case WStype_ERROR: // 0
if ( VERBOSE >= 10 ) Serial.printf("[WSc] Error!\n");
break;
case WStype_DISCONNECTED: // 1
if ( VERBOSE >= 10 ) Serial.printf("[WSc] Disconnected!\n");
break;
case WStype_CONNECTED: // 2
if ( length > 1 ) {
if ( VERBOSE >= 10 ) Serial.printf ( "[WSc] %s: Connected to url: %s\n", num, payload );
// bool sendTXT ( uint8_t num, uint8_t * payload, size_t length = 0, bool headerToPayload = false );
webSocket.sendTXT ( num, "Connected", 9 );
} else {
if ( VERBOSE >= 10 ) Serial.printf ( "[WSc] CONNECTED message length 1 (empty!)\n" );
}
break;
case WStype_TEXT: // 3
if ( length > 1 ) {
if ( VERBOSE >= 10 ) Serial.printf ( "[WSc] get text: %s\n", payload );
if ( ! strcmp ( (const char *) payload, "toggle" ) ) {
toggleOutput();
}
// if ( ! strcmp ( (const char *) payload, "initiate_timer" ) ) {
// setTimer();
// }
if ( strstr ( (const char *) payload, "timer_value:" ) ) {
if ( VERBOSE >= 10 ) Serial.println ( (const char *) payload );
// strstr returns NULL if string not found
char * pch;
pch = strtok ( (char *) payload, ":" );
pch = strtok ( NULL, ":"); // skip the first token "timer_value"
int seconds = atoi ( pch );
setTimer ( seconds * 1000UL );
} else {
bool change = false;
if ( strstr ( (const char *) payload, "red_value:" ) ) {
if ( VERBOSE >= 10 ) Serial.println ( (const char *) payload );
// strstr returns NULL if string not found
char * pch;
pch = strtok ( (char *) payload, ":" );
pch = strtok ( NULL, ":"); // skip the first token
currentR = atoi ( pch );
change = true;
}
if ( strstr ( (const char *) payload, "green_value:" ) ) {
if ( VERBOSE >= 10 ) Serial.println ( (const char *) payload );
// strstr returns NULL if string not found
char * pch;
pch = strtok ( (char *) payload, ":" );
pch = strtok ( NULL, ":"); // skip the first token
currentG = atoi ( pch );
change = true;
}
if ( strstr ( (const char *) payload, "blue_value:" ) ) {
if ( VERBOSE >= 10 ) Serial.println ( (const char *) payload );
// strstr returns NULL if string not found
char * pch;
pch = strtok ( (char *) payload, ":" );
pch = strtok ( NULL, ":"); // skip the first token
currentB = atoi ( pch );
change = true;
}
if ( strstr ( (const char *) payload, "warm_value:" ) ) {
if ( VERBOSE >= 10 ) Serial.println ( (const char *) payload );
// strstr returns NULL if string not found
char * pch;
pch = strtok ( (char *) payload, ":" );
pch = strtok ( NULL, ":"); // skip the first token
currentW = atoi ( pch );
change = true;
}
if ( strstr ( (const char *) payload, "cool_value:" ) ) {
if ( VERBOSE >= 10 ) Serial.println ( (const char *) payload );
// strstr returns NULL if string not found
char * pch;
pch = strtok ( (char *) payload, ":" );
pch = strtok ( NULL, ":"); // skip the first token
currentC = atoi ( pch );
change = true;
}
if ( change && outputState ) {
setOutput ( currentR, currentG, currentB, currentW, currentC );
forceWSUpdate = true;
}
}
// send message to server
// webSocket.sendTXT ( num, "message here", 12 );
} else {
if ( VERBOSE >= 10 ) Serial.printf ( "[WSc] text message length 1 (empty!)\n" );
}
break;
case WStype_BIN: // 4
if ( VERBOSE >= 10 ) Serial.printf("[WSc] get binary length: %u\n", length);
// hexdump(payload, length);
// send data to server
// webSocket.sendBIN(payload, length);
break;
case WStype_PING: // 9?
// pong will be send automatically
if ( VERBOSE >= 10 ) Serial.printf("[WSc] get ping\n");
break;
case WStype_PONG: // 10
// answer to a ping we send
if ( VERBOSE >= 10 ) Serial.printf("[WSc] get pong\n");
break;
default:
Serial.printf ( "[WSc] got unexpected event type %d\n", type );
break;
}

// Serial.println ( F ( "Switch exited" ) );

}

void update_WebSocket () {

// ArduinoJSON v.6.x

DynamicJsonDocument doc ( 600 );

#if TESTING
doc["PROGNAME"] = PROGNAME " TESTING";
#else
doc["PROGNAME"] = PROGNAME;
#endif
doc["VERSION"] = VERSION;
doc["VERDATE"] = VERDATE;
doc["UNIQUE_TOKEN"] = uniqueToken;

char ipString[25];
snprintf ( ipString, 25, "%u.%u.%u.%u", Network.ip[0], Network.ip[1], Network.ip[2], Network.ip[3] );
doc["IP_STRING"] = ipString;
doc["MDNS_ID"] = mdnsOtaId;

if ( timeStatus() == timeSet ) {
formatTimeString ( timeString, timeStringLen, now() );
} else {
snprintf ( timeString, timeStringLen, "

doc["TIME"] = timeString;

doc["MQTT_CMD_TOPIC"] = mqttCmdTopic;
doc["MQTT_STATUS_TOPIC"] = mqttStatusTopic;
doc["MQTT_TIMEOUT_TOPIC"] = mqttTimeoutTopic;
doc["LAST_BOOT_AT"] = bootTimeString;

// JsonArray dataArray = doc.createNestedArray("data");
// const size_t pBufLen = 32;
// char pBuf [ pBufLen ];

doc["R_VALUE"] = currentR;
doc["G_VALUE"] = currentG;
doc["B_VALUE"] = currentB;
doc["W_VALUE"] = currentW;
doc["C_VALUE"] = currentC;

doc["OUTPUT_STATUS"] = outputStateString [ outputState ];
doc["OUTPUT_CHANGE_STATUS"] = outputState ? "turn OFF" : "turn ON";
if ( timerStartedAt_ms == 0UL ) {
doc["TIMER_SECONDS_REMAINING"] = 0;
doc["TIMER_REMAINING_STRING"] = "";
} else {
unsigned long timeRemaining_s = timeRemaining_ms() / SECOND_ms;
formatIntervalString ( timeString, timeStringLen, timeRemaining_s );
snprintf ( pBuf, pBufLen, "Time remaining %s\n", timeString );
doc["TIMER_SECONDS_REMAINING"] = timeRemaining_s;
doc["TIMER_REMAINING_STRING"] = pBuf;
// Serial.printf ( "Timer pBuf: %s\n", pBuf );
}

serializeJson ( doc, jsonString );


#if 0 && defined ( TELEMETRY_ON )
{
snprintf ( pBuf, pBufLen, "%s/%s/debug/update_Websocket_JSON_string_len", PROGNAME, uniqueToken );
sendValueToMQTT ( pBuf, strlen ( jsonString ), "update_Websocket JSON string len", MQTT_RETAIN );
}
#endif

if ( VERBOSE >= 20 )
Serial.printf ( "doc size %d and JSON string length %d\n", doc.size(), measureJson ( doc ) );

if ( VERBOSE >= 15 ) Serial.println ( jsonString );

// webSocket.sendTXT ( 0, jsonString, strlen ( jsonString ) ); // both work
webSocket.broadcastTXT ( jsonString, strlen ( jsonString ) ); // both work


if ( VERBOSE >= 15 ) Serial.print ( F (" ... sent\n" ) );

if ( ( VERBOSE >= 15 ) && forceWSUpdate ) Serial.println ( F ( "forced WS update completing" ) );

forceWSUpdate = false;

}

// *****************************************************************************
// ********************************** time *************************************
// *****************************************************************************

time_t getUnixTime() {
while (conn_UDP.parsePacket() > 0) ; // discard any previously received packets
if ( VERBOSE >= 16 ) Serial.println ( F ( "Transmit NTP Request" ) );
sendNTPpacket(timeServer);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = conn_UDP.parsePacket();
if (size >= NTP_PACKET_SIZE) {
if ( VERBOSE >= 16 ) Serial.println ( F ( "Receive NTP Response" ) );
ntpTimeGoodP = true;
conn_UDP.read(NTPBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)NTPBuffer[40] << 24;
secsSince1900 |= (unsigned long)NTPBuffer[41] << 16;
secsSince1900 |= (unsigned long)NTPBuffer[42] << 8;
secsSince1900 |= (unsigned long)NTPBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * 1UL * 60UL * 60UL;
}
}
Serial.println ( F ( "No NTP Response :-(" ) );
return 0; // return 0 if unable to get the time
}

void sendNTPpacket ( IPAddress& address ) {
if ( VERBOSE >= 16 ) Serial.printf ( "Sending NTP request to %2u.%2u.%2u.%2u\n",
address[0], address[1], address[2], address[3] );
memset(NTPBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
// Initialize values needed to form NTP request
NTPBuffer[0] = 0b11100011; // LI, Version, Mode
NTPBuffer[1] = 0; // Stratum, or type of clock
NTPBuffer[2] = 6; // Polling Interval
NTPBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
NTPBuffer[12] = 49;
NTPBuffer[13] = 0x4E;
NTPBuffer[14] = 49;
NTPBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
// send a packet requesting a timestamp:
conn_UDP.beginPacket ( address, 123 ); // NTP requests are to port 123
conn_UDP.write ( NTPBuffer, NTP_PACKET_SIZE );
conn_UDP.endPacket();
}

void formatTimeString ( char * result, int resultLen, unsigned long time ) {
snprintf ( result, resultLen, "%04d-%02d-%02d %02d:%02d:%02dZ",
year ( time ), month ( time ), day ( time ), hour ( time ), minute ( time ), second ( time ) );
}

void formatIntervalString ( char * result, int resultLen, unsigned long time ) {
snprintf ( result, resultLen, "%02d:%02d:%02d",
hour ( time ), minute ( time ), second ( time ) );
}

// *****************************************************************************
// ********************************** util *************************************
// *****************************************************************************

void saveSettingsToFile () {

// try to write status to LittleFS

// unsigned long s = millis();

File f;
f = LittleFS.open ( "/soft_reset_color.txt", "w" );
f.printf ( "%d,%d,%d, %d,%d, %d", currentR, currentG, currentB,
currentW, currentC,
outputState );
f.close();

// unsigned long interval = millis() - s;
// Serial.printf ( "LittleFS file write took %lu ms\n", interval );
}

// *****************************************************************************
// *****************************************************************************
// *****************************************************************************
```

### Debug Messages

```
Debug messages go here
```

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.