#include #include #include #include #include #include #include #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(); } }