Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
mkgeiger authored Jan 16, 2021
1 parent 3421c83 commit 5be3181
Show file tree
Hide file tree
Showing 3 changed files with 347 additions and 2 deletions.
303 changes: 303 additions & 0 deletions MqttWWledController/MqttWWledController.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <Ticker.h>
#include <AsyncMqttClient.h>
#include <EEPROM.h>

// eeprom
#define MQTT_IP_OFFSET 0
#define MQTT_IP_LENGTH 16
#define MQTT_USER_OFFSET 16
#define MQTT_USER_LENGTH 32
#define MQTT_PASSWORD_OFFSET 48
#define MQTT_PASSWORD_LENGTH 32
#define WHITEC_OFFSET 80
#define WHITEC_LENGTH 1
#define WHITEW_OFFSET 81
#define WHITEW_LENGTH 1

// pins
#define WHITEC_GPIO 12
#define WHITEW_GPIO 0
#define BUTTON_GPIO 13

// access point
#define AP_NAME "ZX-2842"
#define AP_TIMEOUT 300
#define MQTT_PORT 1883

// topics
char topic_whtc[30] = "/";
char topic_whtw[30] = "/";
char topic_whtc_fb[30] = "/";
char topic_whtw_fb[30] = "/";

// channel values
uint8_t whitec = 0x00;
uint8_t whitew = 0x00;
uint8_t whitec_old = 0x00;
uint8_t whitew_old = 0x00;
char whtc_str[4];
char whtw_str[4];

// mqtt
IPAddress mqtt_server;
AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;

char mqtt_ip_pre[MQTT_IP_LENGTH] = "";
char mqtt_user_pre[MQTT_USER_LENGTH] = "";
char mqtt_password_pre[MQTT_PASSWORD_LENGTH] = "";

char mqtt_ip[MQTT_IP_LENGTH] = "";
char mqtt_user[MQTT_USER_LENGTH] = "";
char mqtt_password[MQTT_PASSWORD_LENGTH] = "";

// wifi
WiFiManager wifiManager;
WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
Ticker wifiReconnectTimer;
char mac_str[13];

String readEEPROM(int offset, int len)
{
String res = "";
for (int i = 0; i < len; ++i)
{
res += char(EEPROM.read(i + offset));
}
return res;
}

void writeEEPROM(int offset, int len, String value)
{
for (int i = 0; i < len; ++i)
{
if (i < value.length()) {
EEPROM.write(i + offset, value[i]);
} else {
EEPROM.write(i + offset, 0x00);
}
}
}

void connectToWifi()
{
Serial.println("Re-Connecting to Wi-Fi...");
WiFi.setSleepMode(WIFI_NONE_SLEEP);
WiFi.mode(WIFI_STA);
WiFi.begin();
}

void onWifiConnect(const WiFiEventStationModeGotIP& event)
{
Serial.println("Connected to Wi-Fi.");
connectToMqtt();
}

void onWifiDisconnect(const WiFiEventStationModeDisconnected& event)
{
Serial.println("Disconnected from Wi-Fi.");
mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
wifiReconnectTimer.once(2, connectToWifi);
}

void connectToMqtt()
{
Serial.println("Connecting to MQTT...");
mqttClient.connect();
}

void onMqttConnect(bool sessionPresent)
{
Serial.println("Connected to MQTT.");
Serial.print("Session present: ");
Serial.println(sessionPresent);

mqttClient.subscribe(topic_whtc, 2);
mqttClient.subscribe(topic_whtw, 2);

mqttClient.publish(topic_whtc, 0, true, whtc_str);
mqttClient.publish(topic_whtw, 0, true, whtw_str);
mqttClient.publish(topic_whtc_fb, 0, false, whtc_str);
mqttClient.publish(topic_whtw_fb, 0, false, whtw_str);
}

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
Serial.println("Disconnected from MQTT.");

if (WiFi.isConnected())
{
mqttReconnectTimer.once(2, connectToMqtt);
}
}

void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
char pl[7];

strncpy(pl, payload, 6);
pl[len] = 0;
Serial.printf("Publish received. Topic: %s Payload: %s\n", topic, pl);

if (0 == strcmp(topic, topic_whtc))
{
char val[4];

if (len <= 3)
{
strncpy(val, (char*)payload, 3);
val[len] = 0;
whitec = (uint8_t)strtol(val, NULL, 10);
if (whitec != whitec_old)
{
analogWrite(WHITEC_GPIO, ((int)whitec) * 4);
EEPROM.write(WHITEC_OFFSET, whitec);
EEPROM.commit();
}

whitec_old = whitec;
}
}

if (0 == strcmp(topic, topic_whtw))
{
char val[4];

if (len <= 3)
{
strncpy(val, (char*)payload, 3);
val[len] = 0;
whitew = (uint8_t)strtol(val, NULL, 10);
if (whitew != whitew_old)
{
analogWrite(WHITEW_GPIO, ((int)whitew) * 4);
EEPROM.write(WHITEW_OFFSET, whitew);
EEPROM.commit();
}

whitew_old = whitew;
}
}
}

void setup(void)
{
uint8_t mac[6];

// init UART
Serial.begin(115200);
Serial.println();
Serial.println();

// init EEPROM
EEPROM.begin(128);

// init button
pinMode(BUTTON_GPIO, INPUT);

// check if button is pressed
if (LOW == digitalRead(BUTTON_GPIO))
{
Serial.println("reset wifi settings and restart.");
wifiManager.resetSettings();
delay(1000);
ESP.restart();
}

// init WIFI
readEEPROM(MQTT_IP_OFFSET, MQTT_IP_LENGTH).toCharArray(mqtt_ip_pre, MQTT_IP_LENGTH);
readEEPROM(MQTT_USER_OFFSET, MQTT_USER_LENGTH).toCharArray(mqtt_user_pre, MQTT_USER_LENGTH);
readEEPROM(MQTT_PASSWORD_OFFSET, MQTT_PASSWORD_LENGTH).toCharArray(mqtt_password_pre, MQTT_PASSWORD_LENGTH);

WiFiManagerParameter custom_mqtt_ip("ip", "MQTT ip", mqtt_ip_pre, MQTT_IP_LENGTH);
WiFiManagerParameter custom_mqtt_user("user", "MQTT user", mqtt_user_pre, MQTT_USER_LENGTH);
WiFiManagerParameter custom_mqtt_password("passord", "MQTT password", mqtt_password_pre, MQTT_PASSWORD_LENGTH, "type=\"password\"");

wifiManager.addParameter(&custom_mqtt_ip);
wifiManager.addParameter(&custom_mqtt_user);
wifiManager.addParameter(&custom_mqtt_password);

WiFi.setSleepMode(WIFI_NONE_SLEEP);
wifiManager.setConfigPortalTimeout(AP_TIMEOUT);
wifiManager.setAPStaticIPConfig(IPAddress(192,168,1,1), IPAddress(192,168,1,1), IPAddress(255,255,255,0));
if (!wifiManager.autoConnect(AP_NAME))
{
Serial.println("failed to connect and restart.");
delay(1000);
// restart and try again
ESP.restart();
}

strcpy(mqtt_ip, custom_mqtt_ip.getValue());
strcpy(mqtt_user, custom_mqtt_user.getValue());
strcpy(mqtt_password, custom_mqtt_password.getValue());

if ((0 != strcmp(mqtt_ip, mqtt_ip_pre)) ||
(0 != strcmp(mqtt_user, mqtt_user_pre)) ||
(0 != strcmp(mqtt_password, mqtt_password_pre)))
{
Serial.println("Parameters changed, need to update EEPROM.");
writeEEPROM(MQTT_IP_OFFSET, MQTT_IP_LENGTH, mqtt_ip);
writeEEPROM(MQTT_USER_OFFSET, MQTT_USER_LENGTH, mqtt_user);
writeEEPROM(MQTT_PASSWORD_OFFSET, MQTT_PASSWORD_LENGTH, mqtt_password);

EEPROM.commit();
}

wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);

// construct MQTT topics with MAC
WiFi.macAddress(mac);
sprintf(mac_str, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

strcat(topic_whtc, mac_str);
strcat(topic_whtc, "/whtc");
strcat(topic_whtw, mac_str);
strcat(topic_whtw, "/whtw");
strcat(topic_whtc_fb, mac_str);
strcat(topic_whtc_fb, "/whtc_fb");
strcat(topic_whtw_fb, mac_str);
strcat(topic_whtw_fb, "/whtw_fb");

// read stored channels
whitec = EEPROM.read(WHITEC_OFFSET);
whitew = EEPROM.read(WHITEW_OFFSET);
whitec_old = whitec;
whitew_old = whitew;

// set PWM channels
sprintf(whtc_str, "%d", whitec);
sprintf(whtw_str, "%d", whitew);
analogWrite(WHITEC_GPIO, ((int)whitec) * 4);
analogWrite(WHITEW_GPIO, ((int)whitew) * 4);

if (mqtt_server.fromString(mqtt_ip))
{
char mqtt_id[30] = AP_NAME;

strcat(mqtt_id, "-");
strcat(mqtt_id, mac_str);
mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect);
mqttClient.onMessage(onMqttMessage);
mqttClient.setServer(mqtt_server, MQTT_PORT);
mqttClient.setCredentials(mqtt_user, mqtt_password);
mqttClient.setClientId(mqtt_id);

connectToMqtt();
}
else
{
Serial.println("invalid MQTT Broker IP.");
}
}

void loop(void)
{

}
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,44 @@
# mqtt-ww-led-controller
MQTT contoller for Warm White and Cold White LED stripes, based on a commercial ESP8266 product
# MQTT LED stripe controller
## Overview
Instead of building a LED stripe controller by myself, which is not allways the cheapest and fastest solution, I decided to use a commercial controller. Focus was then more on the software than on the hardware design. The choice fell on the `Luminea ZX-2842` controller, which is cheap, easy to order, easy to open and the contained ESP8266 can easily reflashed with the available test pads on the PCB. Also getting rid of the original firmware, which makes use of a Chinese MQTT cloud, is a good feeling. Instead, my software connects to a local self maintained MQTT broker (see other project from me).

## Hardware
The hardware is the commercial product `Luminea ZX-2842`. It is sold by PEARL (https://www.pearl.de/a-ZX2842-3103.shtml) and Amazon (https://www.amazon.de/dp/B074SKRNLW/ref=cm_sw_em_r_mt_dp_ut0aGb4T5BBRB?_encoding=UTF8&psc=1) .
![ZX2842](/ZX2842.png)

There is an ESP8266 sitting on the backside. 2 PWM outputs are connected to drive the 2 output channels (Warm White + Cold White). Cold White is on GPIO12, Warm White is on GPIO0. The pushbutton is on GPIO13 and is assigned by my software to reset the Wifi settings.

## Serial connection
The serial header (3.3V, RXD, TXD, GND) as well as GPIO0, GPIO2 and RESET (IO0, IO2, RST) are populated as test pads on the frontside of the PCB. You can easily add some solder to fix the wires for the flash process. You need to connect to the serial programming interface of the ESP8266 chip. This is done by connecting any serial-to-USB converter (e.g. the FT232R) TX, RX and GND pins to the ESP8266 RX, TX and GND pins (cross connection!) and powering the ZX-2842. Recheck your serial-to-USB converter so to ensure that it supplies 3.3V voltage and NOT 5V. 5V will damage the ESP chip!

## Flash mode
To place the board into flashing mode, you will need to short IO0 (GPIO0) to GND. This can remain shorted while flashing is in progress, but you will need to remove the short in order to boot afterwards the flashed software.

## Installation
1. install Arduino IDE 1.8.1x
2. download and install the ESP8266 board supporting libraries with this URL: http://arduino.esp8266.com/stable/package_esp8266com_index.json
3. select the `Lolin(Wemos) D1 mini Lite` board
4. install the `Async MQTT client` library: https://github.com/marvinroger/async-mqtt-client/archive/master.zip
5. install the `Async TCP` library: https://github.com/me-no-dev/ESPAsyncTCP/archive/master.zip
6. compile and flash

## SW configuration
The configuration is completely done in the web frontend of the WifiManager. At first startup, the software boots up in access point mode. In this mode you can configure parameters like
* Wifi SSID
* Wifi password
* MQTT broker IP address
* MQTT user
* MQTT password

After these settings were saved, with the next startup, the software boots into normal operating mode and connects to your Wifi and MQTT broker. Entering again into the WifiManager configuration menu can be done be holding the push button pressed during the startup of the software.

## SW normal operation
The software subsribes to MQTT topics, over which the 2 PWM values (Warm White + Cold White) of the ZX-2842 can be changed. Each changed PWM value is stored to EEPROM to be able to restore it after the next startup (e.g. after a power loss). Also the software supports re-connection to Wifi and to the MQTT broker in case of power loss, Wifi loss or MQTT broker unavailability. The MQTT topics begin with the device specific MAC-address string (in the following "A020A600F73A" as an example). This is useful when having multiple controllers in your MQTT cloud to avoid collisions.

Subscribe topics (used to control the PWM channels):
* Topic: "/A020A600F73A/whtw" Payload: "0" - "255"
* Topic: "/A020A600F73A/whtc" Payload: "0" - "255"

Publish topics (used to give feedback to the MQTTbroker about the PWM channels at startup):
* Topic: "/A020A600F73A/whtw_fb" Payload: "0" - "255"
* Topic: "/A020A600F73A/whtc_fb" Payload: "0" - "255"
Binary file added ZX2842.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5be3181

Please sign in to comment.