multi-esp/multi-esp.ino

599 lines
17 KiB
C++

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <RCSwitch.h>
#include <BH1750.h>
#include <Wire.h>
#include <RGBConverter.h>
#include "secrets.h"
#define MQTT_VERSION MQTT_VERSION_3_1_1
// General MQTT settings
const PROGMEM char* MQTT_CLIENT_ID = "multi-esp";
const PROGMEM char* MQTT_SERVER_IP = "10.42.0.3";
const PROGMEM uint16_t MQTT_SERVER_PORT = 1883;
// TODO: check if needed?
//const PROGMEM char* MQTT_USER = "[Redacted]";
//const PROGMEM char* MQTT_PASSWORD = "[Redacted]";
// RFSOCKET subsystem mqtt topic components
const char* MQTT_RFSOCKET_SUBSYSTEM = "rfsockets/";
const char* MQTT_RFSOCKET_STATE_TOPIC = "/status";
const char* MQTT_RFSOCKET_COMMAND_TOPIC = "/switch";
// payloads by default (on/off)
const char* LIGHT_ON = "ON";
const char* LIGHT_OFF = "OFF";
// BH1750 subsystem mqtt topic components
// TODO: implement support for multiple sensors.
// (BH1750 can talk on two different i²c addr)
const char* MQTT_BH1750_SUBSYSTEM = "lightsensor/";
const char* MQTT_BH1750_LOCATION = "livingroom_window";
const char* MQTT_BH1750_STATE_TOPIC = "/status";
unsigned long last_bh1750_publish = millis();
// SimpleRGB subsystem
const PROGMEM uint8_t RGB_LIGHT_RED_PIN = 14; //D5
const PROGMEM uint8_t RGB_LIGHT_GREEN_PIN = 12; //D6
const PROGMEM uint8_t RGB_LIGHT_BLUE_PIN = 15; //D8
const char* MQTT_SIMPLERGB_SUBSYSTEM = "simplergb/";
const char* MQTT_SIMPLERGB_LOCATION = "livingroom_window";
const char* MQTT_SIMPLERGB_STATE_TOPIC = "/light/status";
const char* MQTT_SIMPLERGB_COMMAND_TOPIC = "/light/switch";
const char* MQTT_SIMPLERGB_BRIGHTNESS_STATE_TOPIC = "/brightness/status";
const char* MQTT_SIMPLERGB_BRIGHTNESS_COMMAND_TOPIC = "/brightness/set";
const char* MQTT_SIMPLERGB_RGB_STATE_TOPIC = "/rgb/status";
const char* MQTT_SIMPLERGB_RGB_COMMAND_TOPIC = "/rgb/set";
WiFiClient wifiClient;
PubSubClient client(wifiClient);
RCSwitch mySwitch = RCSwitch();
BH1750 lightMeter;
RGBConverter colorConverter;
typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
bool powered;
} SimpleRGBStrip;
SimpleRGBStrip simpleRGBStrip = (SimpleRGBStrip) {0, 0, 0, false};
typedef struct {
char* channel; // First DIP set
char* id; // Second DIP set
char name[255]; // human readable name (included in mqtt topic)
bool powered; // last known power state of socket
uint8_t type; // 0=DIP, 1=Learned
int on_code; // ON code for self learning sockets (according to trained remote)
int off_code; // Same as above but for OFF
} PowerSocket;
// see struct for description of fields
PowerSocket sockets[] = {
(PowerSocket) {"00000", "10000", "A", false, 0, -1, -1},
(PowerSocket) {"00000", "01000", "B", false, 0, -1, -1},
(PowerSocket) {"00000", "00100", "C", false, 0, -1, -1},
(PowerSocket) {"00000", "00010", "D", false, 0, -1, -1},
(PowerSocket) {"", "", "E", false, 1, 530800, 713872},
(PowerSocket) {"", "", "F", false, 1, 172340, 409380},
(PowerSocket) {"", "", "G", false, 1, 842428, 409388},
(PowerSocket) {"", "", "H", false, 1, 713874, 530802}
};
int numofsockets = sizeof(sockets)/sizeof(sockets[0]);
// function called to adapt the brightness and the color of the led
void setColorHSV(double hue, double saturation, double value) {
hue += 1.0/(360.0*4);
if(hue >= 1.0) {
hue = 0.0;
}
byte rgb[] = {0.0, 0.0, 0.0};
colorConverter.hsvToRgb(hue, saturation, value, rgb);
setColorRGB(rgb[0], rgb[1], rgb[2]);
}
void setColorRGB(uint8_t red, uint8_t green, uint8_t blue) {
analogWrite(RGB_LIGHT_RED_PIN, (1.0/255.0)*red*1023);
analogWrite(RGB_LIGHT_GREEN_PIN, (1.0/255.0)*green*1023);
analogWrite(RGB_LIGHT_BLUE_PIN, (1.0/255.0)*blue*1023);
simpleRGBStrip.red = red;
simpleRGBStrip.green = green;
simpleRGBStrip.blue = blue;
}
//// function called to publish the state of the led (on/off)
void publishRGBState() {
char topic[
strlen(MQTT_SIMPLERGB_SUBSYSTEM) +
strlen(MQTT_SIMPLERGB_LOCATION) +
strlen(MQTT_SIMPLERGB_STATE_TOPIC)
];
strcpy(topic, MQTT_SIMPLERGB_SUBSYSTEM);
strcat(topic, MQTT_SIMPLERGB_LOCATION);
strcat(topic, MQTT_SIMPLERGB_STATE_TOPIC);
if (simpleRGBStrip.powered) {
client.publish(topic, LIGHT_ON, true);
} else {
client.publish(topic, LIGHT_OFF, true);
}
}
// function called to publish the brightness of the led (0-100)
void publishRGBBrightness() {
char topic[
strlen(MQTT_SIMPLERGB_SUBSYSTEM) +
strlen(MQTT_SIMPLERGB_LOCATION) +
strlen(MQTT_SIMPLERGB_STATE_TOPIC)
];
strcpy(topic, MQTT_SIMPLERGB_SUBSYSTEM);
strcat(topic, MQTT_SIMPLERGB_LOCATION);
strcat(topic, MQTT_SIMPLERGB_BRIGHTNESS_STATE_TOPIC);
double hsv[] = {0.0, 0.0, 0.0};
colorConverter.rgbToHsv(simpleRGBStrip.red, simpleRGBStrip.green, simpleRGBStrip.blue, hsv);
char brightness[3];
itoa(hsv[2] * 100, brightness, 10);
client.publish(topic, brightness, true);
}
// function called to publish the colors of the led (xx(x),xx(x),xx(x))
void publishRGBColor() {
char *separator = ",";
char red[8];
char green[8];
char blue[8];
itoa(simpleRGBStrip.red, red, 10);
itoa(simpleRGBStrip.green, green, 10);
itoa(simpleRGBStrip.blue, blue, 10);
char payload[11];
strcpy(payload, red);
strcat(payload, separator);
strcat(payload, green);
strcat(payload, separator);
strcat(payload, blue);
char topic[
strlen(MQTT_SIMPLERGB_SUBSYSTEM) +
strlen(MQTT_SIMPLERGB_LOCATION) +
strlen(MQTT_SIMPLERGB_STATE_TOPIC)
];
strcpy(topic, MQTT_SIMPLERGB_SUBSYSTEM);
strcat(topic, MQTT_SIMPLERGB_LOCATION);
strcat(topic, MQTT_SIMPLERGB_RGB_STATE_TOPIC);
client.publish(topic, payload, true);
}
// function called to measure and publish the state of the BH1750
void publishBH1750State() {
uint16_t lux = lightMeter.readLightLevel();
char lux_string[10];
itoa(lux, lux_string, 10);
char topic[
strlen(MQTT_BH1750_SUBSYSTEM) +
strlen(MQTT_BH1750_LOCATION) +
strlen(MQTT_BH1750_STATE_TOPIC)
];
strcpy(topic, MQTT_BH1750_SUBSYSTEM);
strcat(topic, MQTT_BH1750_LOCATION);
strcat(topic, MQTT_BH1750_STATE_TOPIC);
client.publish(topic, lux_string, true);
}
// function called to publish the state of the rfsockets (on/off)
void publishSocketState(int socketIdx) {
char *socket_name = sockets[socketIdx].name;
char topic[
strlen(MQTT_RFSOCKET_SUBSYSTEM) +
strlen(MQTT_RFSOCKET_STATE_TOPIC) +
strlen(socket_name)
];
strcpy(topic, MQTT_RFSOCKET_SUBSYSTEM);
strcat(topic, socket_name);
strcat(topic, MQTT_RFSOCKET_STATE_TOPIC);
if (sockets[socketIdx].powered) {
client.publish(topic, LIGHT_ON, true);
} else {
client.publish(topic, LIGHT_OFF, true);
}
}
// function called to turn on/off the rfsockets
void setSocketState(byte socketIdx) {
if (sockets[socketIdx].powered) {
setSocket(socketIdx, true);
Serial.println("INFO: Turn light on...");
} else {
setSocket(socketIdx, false);
Serial.println("INFO: Turn light off...");
}
}
// handler for mqtt messages arriving on that subsystems topic
void subsystem_simplergb(char *topic_ptr, String payload) {
const char* delimiter = "/";
topic_ptr = strtok(NULL, delimiter);
if (topic_ptr != NULL) {
char *location = topic_ptr;
if(strcmp(location, "livingroom_window") == 0) {
topic_ptr = strtok(NULL, "");
char *command = topic_ptr;
if(strcmp(command, "light/switch") == 0) {
Serial.println("Command: light");
if (payload.equals(String(LIGHT_ON))) {
if (simpleRGBStrip.powered != true) {
simpleRGBStrip.powered = true;
setColorRGB(simpleRGBStrip.red, simpleRGBStrip.green, simpleRGBStrip.blue);
publishRGBState();
}
} else if (payload.equals(String(LIGHT_OFF))) {
if (simpleRGBStrip.powered != false) {
simpleRGBStrip.powered = false;
setColorRGB(0, 0, 0);
publishRGBState();
}
} else {
Serial.print("Unknown payload received: ");
Serial.println(payload);
Serial.println("Ignoring it…\n");
}
} else if(strcmp(command, "brightness/set") == 0) {
Serial.println("Command: brightness");
uint8_t brightness = payload.toInt();
if (brightness >= 0 && brightness <= 100) {
Serial.print("Parsed brightness value of: ");
Serial.println(brightness);
double hsv[] = {0.0, 0.0, 0.0};
colorConverter.rgbToHsv(simpleRGBStrip.red, simpleRGBStrip.green, simpleRGBStrip.blue, hsv);
setColorHSV(hsv[0], hsv[1], (1.0/100.0) * brightness);
publishRGBBrightness();
} else {
Serial.print("Parsed invalid brightness value of: ");
Serial.println(brightness);
}
} else if(strcmp(command, "rgb/set") == 0) {
Serial.println("Command: rgb");
uint8_t rgb[] = {0, 0, 0};
parseRGBPayload(payload, rgb);
setColorRGB(rgb[0], rgb[1], rgb[2]);
publishRGBColor();
} else {
Serial.print("Unknown command \"");
Serial.print(command);
Serial.println("\" in topic found. Ignoring…");
};
} else {
Serial.print("Unknown location: ");
Serial.println(location);
}
}
}
void parseRGBPayload(String payload, uint8_t rgb[]) {
Serial.print("String payload: ");
Serial.println(payload);
char payload_c[10];
payload.toCharArray(payload_c, 10);
char *payload_ptr;
const char* delimiter = ",";
int red;
int green;
int blue;
payload_ptr = strtok(payload_c, delimiter);
uint8_t color_idx = 0;
while(payload_ptr != NULL) {
int color = atoi(payload_ptr);
payload_ptr = strtok(NULL, delimiter);
if(color_idx < sizeof(rgb)/sizeof(uint8_t)) {
rgb[color_idx] = color;
Serial.print("Setting color idx: ");
Serial.print(color_idx);
Serial.print(" with value ");
Serial.println(color);
}
color_idx++;
}
Serial.print("Parsed colors: ");
Serial.print(" red: ");
Serial.print(rgb[0]);
Serial.print(" green: ");
Serial.print(rgb[1]);
Serial.print(" blue: ");
Serial.println(rgb[2]);
}
// handler for mqtt messages arriving on that subsystems topic
void subsystem_rfsockets(char *topic_ptr, String payload) {
const char* delimiter = "/";
topic_ptr = strtok(NULL, delimiter);
if (topic_ptr != NULL) {
char *name = topic_ptr;
Serial.print("Found socket name: ");
Serial.println(name);
int socketIdx = findSocketIndex(name);
Serial.print("Found socket index ");
Serial.println(socketIdx);
// We only listen on the command channel so no further splitting needed
if (payload.equals(String(LIGHT_ON))) {
if (sockets[socketIdx].powered != true) {
sockets[socketIdx].powered = true;
setSocketState(socketIdx);
publishSocketState(socketIdx);
}
} else if (payload.equals(String(LIGHT_OFF))) {
if (sockets[socketIdx].powered != false) {
sockets[socketIdx].powered = false;
setSocketState(socketIdx);
publishSocketState(socketIdx);
}
} else {
Serial.print("Unknown payload received: ");
Serial.println(payload);
Serial.println("Ignoring it…\n");
}
} else {
Serial.println("No name found in mqtt topic (2nd / missing)");
}
}
// function called when a MQTT message arrived
void callback(char* p_topic, byte* p_payload, unsigned int p_length) {
// concat the payload into a string
String payload;
for (uint8_t i = 0; i < p_length; i++) {
payload.concat((char)p_payload[i]);
}
// handle message topic
Serial.print("Got a message on topic: ");
Serial.println(p_topic);
char *topic_ptr;
const char* delimiter = "/";
topic_ptr = strtok(p_topic, delimiter);
if (topic_ptr != NULL) {
char *subsystem = topic_ptr;
Serial.print("Found subsystem: ");
Serial.println(subsystem);
if(strcmp(subsystem, "rfsockets") == 0) {
Serial.println("Selecting subsystem rfsockets");
subsystem_rfsockets(topic_ptr, payload);
} else if(strcmp(subsystem, "simplergb") == 0) {
Serial.println("Selecting subsystem simplergb");
subsystem_simplergb(topic_ptr, payload);
} else {
Serial.print("Unknown subsystem \"");
Serial.print(subsystem);
Serial.println("\" in topic found. Ignoring…");
}
} else {
Serial.print("Got topic without delimiter (");
Serial.print(delimiter);
Serial.print("), topic was:");
Serial.println(p_topic);
}
}
// handles initial connection and reconnect in case of failures
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.println("INFO: Attempting MQTT connection...");
// Attempt to connect
if (client.connect(MQTT_CLIENT_ID)) {
Serial.println("INFO: connected");
publishRGBState();
publishRGBColor();
publishRGBBrightness();
for (int i = 0; i < numofsockets; i++) {
publishSocketState(i);
}
for (int i = 0; i < numofsockets; i++) {
char *socket_name = sockets[i].name;
char topic[
strlen(MQTT_RFSOCKET_SUBSYSTEM) +
strlen(MQTT_RFSOCKET_COMMAND_TOPIC) +
strlen(socket_name)
];
strcpy(topic, MQTT_RFSOCKET_SUBSYSTEM);
strcat(topic, socket_name);
strcat(topic, MQTT_RFSOCKET_COMMAND_TOPIC);
Serial.print("INFO: subscribing to topic \"");
Serial.print(topic);
Serial.println("\"");
client.subscribe(topic);
}
char simplergb_topic[
strlen(MQTT_SIMPLERGB_SUBSYSTEM) +
strlen(MQTT_SIMPLERGB_LOCATION) +
strlen(MQTT_SIMPLERGB_COMMAND_TOPIC)
];
strcpy(simplergb_topic, MQTT_SIMPLERGB_SUBSYSTEM);
strcat(simplergb_topic, MQTT_SIMPLERGB_LOCATION);
strcat(simplergb_topic, MQTT_SIMPLERGB_COMMAND_TOPIC);
Serial.print("INFO: subscribing to topic \"");
Serial.print(simplergb_topic);
Serial.println("\"");
client.subscribe(simplergb_topic);
char simplergb_b_topic[
strlen(MQTT_SIMPLERGB_SUBSYSTEM) +
strlen(MQTT_SIMPLERGB_LOCATION) +
strlen(MQTT_SIMPLERGB_BRIGHTNESS_COMMAND_TOPIC)
];
strcpy(simplergb_b_topic, MQTT_SIMPLERGB_SUBSYSTEM);
strcat(simplergb_b_topic, MQTT_SIMPLERGB_LOCATION);
strcat(simplergb_b_topic, MQTT_SIMPLERGB_BRIGHTNESS_COMMAND_TOPIC);
Serial.print("INFO: subscribing to topic \"");
Serial.print(simplergb_b_topic);
Serial.println("\"");
client.subscribe(simplergb_b_topic);
char simplergb_rgb_topic[
strlen(MQTT_SIMPLERGB_SUBSYSTEM) +
strlen(MQTT_SIMPLERGB_LOCATION) +
strlen(MQTT_SIMPLERGB_RGB_COMMAND_TOPIC)
];
strcpy(simplergb_rgb_topic, MQTT_SIMPLERGB_SUBSYSTEM);
strcat(simplergb_rgb_topic, MQTT_SIMPLERGB_LOCATION);
strcat(simplergb_rgb_topic, MQTT_SIMPLERGB_RGB_COMMAND_TOPIC);
Serial.print("INFO: subscribing to topic \"");
Serial.print(simplergb_rgb_topic);
Serial.println("\"");
client.subscribe(simplergb_rgb_topic);
} else {
Serial.print("ERROR: failed, rc=");
Serial.print(client.state());
Serial.println("DEBUG: try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
bool setSocket(int socketIdx, bool newStatus) {
PowerSocket socket;
bool success = false;
if (socketIdx >= 0) {
success = true;
socket = sockets[socketIdx];
socket.powered = newStatus;
sockets[socketIdx] = socket; // write back to status array
if (newStatus == true) {
for (char i = 0; i <= 1; i++) {
if (socket.type == 0) {
Serial.println("id socket");
mySwitch.setProtocol(1);
mySwitch.switchOn(socket.channel, socket.id);
} else if (socket.type == 1) {
Serial.println("learning socket");
mySwitch.setProtocol(5);
mySwitch.send(socket.on_code, 24);
}
delay(250);
}
} else {
for (char i = 0; i <= 1; i++) {
if (socket.type == 0) {
Serial.println("id socket");
mySwitch.setProtocol(1);
mySwitch.switchOff(socket.channel, socket.id);
} else if (socket.type == 1) {
Serial.println("learning socket");
char* off_code = socket.id;
mySwitch.setProtocol(5);
mySwitch.send(socket.off_code, 24);
}
delay(250);
}
}
}
return success;
}
int findSocketIndex(char *name) {
for (int socketIdx = 0; socketIdx < numofsockets; socketIdx++) {
PowerSocket canidateSocket = sockets[socketIdx];
if (strcmp(canidateSocket.name, name) == 0) {
Serial.print(canidateSocket.name);
Serial.print(" equals ");
Serial.println(name);
return socketIdx;
}
}
return -1;
}
void setup() {
// setup serial
Serial.begin(115200);
Serial.println("Serial interface initialized");
WiFi.mode(WIFI_STA);
Serial.print("Connecting to wifi ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.println("Enable transmission while waiting on wifi");
mySwitch.enableTransmit(2);
delay(1000);
Serial.print("Transmission enabled, waiting for wifi now.");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// serial output of connection details
Serial.println("");
Serial.print("Wifi connected to ");
Serial.println(WIFI_SSID);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// setup bh1750 sensor
Wire.begin();
lightMeter.begin();
// setup rgb light outputs
pinMode(RGB_LIGHT_BLUE_PIN, OUTPUT);
pinMode(RGB_LIGHT_RED_PIN, OUTPUT);
pinMode(RGB_LIGHT_GREEN_PIN, OUTPUT);
// init the MQTT connection
client.setServer(MQTT_SERVER_IP, MQTT_SERVER_PORT);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
if(abs(millis() - last_bh1750_publish) >= 5000){
Serial.println("Publishing BH1750 state");
publishBH1750State();
last_bh1750_publish = millis();
}
}