# Integrazione SONOFF con MQTT: esempi di programmazione <abstract> Appunti di uso sulla programmazione dei dispositivi Itead SONOFF </abstract> Queste note hanno fine esemplificativo. Esiste un software ominicomprensivo per l'uso dei dispositivi SONOFF con MTT al link seguente: * [https://github.com/arendst/Sonoff-Tasmota](https://github.com/arendst/Sonoff-Tasmota) L'uso di questo software e' descritto qui: * [http://tanzolab.it/openhab_sonoff](http://tanzolab.it/openhab_sonoff) ## Configurazione ambiente di programmazione Per utilizzare il programma proposto qui bisogna configurare l'ambiente di programmazione Arduino nel modo seguente. In preferences aggiungere http://arduino.esp8266.com/stable/package_esp8266com_index.json Selezionare nelle board: ESP8266 Module Generic ESP8285 Module ## MQTT server setup GLi esempi proposti presuppongono che un server MQTT sia raggiungibile nel broadcast domain associato allo SSID su cui i dispositivi SONOFF sono collegati. Su Linux ed altre piattaforme Unix, e' disponibile una implementazione MQTT chiamata mosquitto che puo' essere installata su Debian e derivate con il comando: sudo apt-get install mosquitto Per i test conviene fermare temporaneamente il demone e lanciare il server in modalita' debug: mosquitto -v Inoltre il servizio mDNS deve essere annunciato su mDNS in modo che tutti i nodi SONOFF possano conoscere in modo trasparente l'indirizzo del server MQTT. avahi-publish -s "MTT server for SONOFF" _mqtt._tcp 1883 "" Mediante il comando avahi-browse puo' essere verificato l'annuncio: $ avahi-browse -D -a + wlan0 IPv4 3.11.0.73-EDO-PC.0fc7956a-5bc9-4501-b416-4b7e85e45693 _nvstream_dbd._tcp local + wlan0 IPv4 DIR3500MCBT-88 _spotify-connect._tcp local + wlan0 IPv4 Chromecast-4fdfe085fbdbce0ec719382e49bdc7f8 _googlecast._tcp local + wlan0 IPv4 netusg20 [00:04:25:aa:03:17] Workstation local + wlan0 IPv4 andrewp [5c:c5:d4:6b:66:74] Workstation local + wlan0 IPv4 MQTT server for SONOFF _mqtt._tcp local Per comandare i rele occore effettuare un publish: mosquitto_pub -h localhost -t 'sonoff/60:01:94:89:08:F0/out' -m 'K3 ON' Idem per disattivarlo: mosquitto_pub -h localhost -t 'sonoff/60:01:94:89:08:F0/out' -m 'K3 OFF' La radice per inviare comandi di output ai dispositivi SONOFF e' sonoff/<mac address>/out Il MAC della scheda, da usare nei comandi publish, viene visualizzato nella console. ## Miglioramenti Miglioramenti possibili: 1. pubblicare in sonoff/devices il MAC address in modo da rendere autoconfigurante il sistema per il supervisore locale 2. pubblicare in sonoff/devices/<MAC>/tipo la tipologia del dispositivo (CH4, TH10/16 etc) 3. usare mDNS per risolvere il nome del del server MQTT presente in rete (per es: mqtt.local) in modo da rendere autoconfigurante il sistema per i clients. (fatto vedi secondo esempio). 4. usare il modulo WifiManager per rendere autoconfigurante il dispositivo nei riguardi della infrastruttura 802.11 (da verificare se il modulo PSF-A85 ha una interfaccia wireless capace della modalita' AP) secondo il data sheet (https://www.itead.cc/wiki/Sonoff_4CH) ne e' capace: network mode station/softAP/SoftAP+station per esempio OpenHAB puo' ricevere tutti i messaggi sotto devices per comprendere quali dispositivi sono attivi in rete. ## Esempio di programma per dispositivo CH4 /* * SONOFF CH4 2.0 relay module * * Schematic: * https://www.itead.cc/wiki/images/d/d3/Sonoff_4CH.SCHMATIC.pdf * * Info on PSF-A85 Wifi module (as used in ITEAD SONOFF CH4): * https://www.itead.cc/wiki/PSF-A85 */ #include <ESP8266WiFi.h> // https://github.com/esp8266/Arduino #include <DNSServer.h> // not used here, try to remove #include <ESP8266WebServer.h> // not used here, try to remove #include <WiFiManager.h> // https://github.com/tzapu/WiFiManager #include <PubSubClient.h> // MQTT implementation, see https://github.com/Imroy/pubsubclient // GPIO for CH4 2.0 relay const int K1 = 12; const int K2 = 5; const int K3 = 4; const int K4 = 15; char ssid[] = "TanzoLab"; char password[] = "tanzolab"; IPAddress mqtt_server(192, 168, 1, 140); // test //IPAddress mqtt_server(192, 168, 1, 2); // CM£3-HOME address // mosquitto_pub -h localhost -t 'sonoff/60:01:94:89:08:F0/out' -m 'K3 ON' WiFiClient wifi_client; String mac = WiFi.macAddress(); // MAC of our interface String publish_path = "sonoff/"+mac+"/in"; String subscribe_path = "sonoff/"+mac+"/out"; PubSubClient client(wifi_client, mqtt_server); //gets called when WiFiManager enters configuration mode void configModeCallback (WiFiManager *myWiFiManager) { Serial.println("Entered config mode"); Serial.println(WiFi.softAPIP()); //if you used auto generated SSID, print it Serial.println(myWiFiManager->getConfigPortalSSID()); //entered config mode, make led toggle faster ? } void callback(const MQTT::Publish& pub) { int n = 0; // handle message arrived Serial.print(pub.topic()); Serial.print(" => ["); Serial.print(pub.payload_string()); Serial.println("]"); char xmac [32]; int nk; char op[32]; //if (sscanf (pub.topic().c_str(), "esp8266/%s/out/K%d %s", xmac, &nk, op ) == 3) { { const char *p; char buf [32]; strcpy (buf, pub.payload_string().c_str()); if (p = strstr(buf, "K")) { Serial.print("XXXXX: "); Serial.print(p); Serial.println(""); nk = p[1] - '0'; Serial.print("NNNNN: "); Serial.print(nk); Serial.println(""); p += 3; Serial.println("COMMAND: "); Serial.print(p); Serial.println("."); if (!strcmp(p, "ON")) { Serial.print("COMMAND: ON on K"); Serial.print(nk); Serial.println("."); switch (nk) { case 1: digitalWrite(K1, HIGH); break; case 2: digitalWrite(K2, HIGH); break; case 3: digitalWrite(K3, HIGH); break; case 4: digitalWrite(K4, HIGH); break; } } if (!strcmp(p, "OFF")) { Serial.print("COMMAND: OFF on K"); Serial.print(nk); Serial.println("."); switch (nk) { case 1: digitalWrite(K1, LOW); break; case 2: digitalWrite(K2, LOW); break; case 3: digitalWrite(K3, LOW); break; case 4: digitalWrite(K4, LOW); break; } } } } #if 0 //if (sscanf (pub.topic().c_str(), "esp8266/%s/out", xmac) == 1 && (!strcmp(xmac,mac.c_str()))) { if (!strcmp (pub.payload_string().c_str(), "ledon")) { //digitalWrite(pinLed, LOW); // turn the LED on (LOW is the voltage level) //ledStatus = true; } if (!strcmp (pub.payload_string().c_str(), "ledoff")) { //ledStatus = false; //digitalWrite(pinLed, HIGH); // turn the LED off (LOW is the voltage level) } } #endif } void setup() { static int i = 0; Serial.begin(115200); delay(10); // We start by connecting to a WiFi network // station mode WiFi.mode(WIFI_STA); if (strlen(password)) WiFi.begin(ssid,password); else WiFi.begin(ssid); Serial.println(); Serial.println("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay (1000); Serial.print(i); Serial.print(": connecting to "); Serial.println(ssid); Serial.printf("Connection status: %d\n", WiFi.status()); Serial.println("--------"); WiFi.printDiag(Serial); Serial.printf("\n........\n\n"); ++i; } client.set_callback(callback); Serial.println(); Serial.print("WiFi connected with ip "); Serial.println(WiFi.localIP()); Serial.print("MAC:"); Serial.println(mac); pinMode(K1, OUTPUT); pinMode(K2, OUTPUT); pinMode(K3, OUTPUT); pinMode(K4, OUTPUT); } // the loop function runs over and over again forever void loop() { static int i = 0; if (WiFi.status() == WL_CONNECTED) { if (!client.connected()) { Serial.println("connecting to MQTT...."); if (client.connect("arduinoClient")) { Serial.println("MQTT connected"); client.publish(publish_path,"START"); client.subscribe(subscribe_path); } } if (client.connected()) client.loop(); } delay(1000); Serial.println("Loop"); char msg[128]; sprintf (msg, "Loop: %d", i++); client.publish("esp8266", msg); } ## Esempio per dispositivo TH10/16 (relay singolo 16 A) /* * SONOFF TH10/16 MQTT management * * Andrea Montefusco */ #include <ESP8266WiFi.h> // https://github.com/esp8266/Arduino #include <ESP8266mDNS.h> #include <WiFiManager.h> // https://github.com/tzapu/WiFiManager #include <PubSubClient.h> static const uint8_t RELAY = 12; // MTDI static const uint8_t SWITCH = 0; // GPIO0 static const uint8_t LED = 13; // MTCK class BlinkingStatusLed { private: unsigned long time_to_blink; uint8_t pin; long period; // in ms float duty_cycle; static const uint8_t LED_STATE_OFF = 0; static const uint8_t LED_STATE_BLINKING_ON = 10; static const uint8_t LED_STATE_BLINKING_OFF = 20; static const uint8_t LED_STATE_ON = 30; uint8_t state = LED_STATE_OFF; public: BlinkingStatusLed (uint8_t pin_ = 0, long period_ = 1000, float duty_cycle_ = 0.3): pin(pin_), period(period_), duty_cycle(duty_cycle_) { pinMode(pin, OUTPUT); digitalWrite(pin, HIGH); // turn the LED off (LOW is the voltage level) state = LED_STATE_OFF; } void run () { if (millis() < time_to_blink) return; switch (state) { case LED_STATE_BLINKING_ON: digitalWrite(pin, LOW); // change status to On time_to_blink = millis() + (long)((float)period*duty_cycle); state = LED_STATE_BLINKING_OFF; //Serial.print("ON "); //Serial.println((float)period*duty_cycle); break; case LED_STATE_BLINKING_OFF: digitalWrite(pin, HIGH); // change status to OFF time_to_blink = millis() + (long)((float)period*(1-duty_cycle)); state = LED_STATE_BLINKING_ON; //Serial.print("OFF "); //Serial.println(((float)period*(1-duty_cycle))); } } void on() { state = LED_STATE_ON; digitalWrite(pin, LOW); } void off() { state = LED_STATE_ON; digitalWrite(pin, HIGH); } void blink (long period_, float duty_cycle_) { period = period_; duty_cycle = duty_cycle_; blink(); } void blink () { time_to_blink = millis(); state = LED_STATE_BLINKING_ON; } }; BlinkingStatusLed sys_led(LED); /* * Relay class * * from https://github.com/ImUrlaub/Sonoff * * manages the single relay in TH10/16 device * the relay is drive through the ESP8265 PWM_0 output. * the idea is to provide the full output available when the relay * is turned on, keeping the max power just for the amount of time * needed to move the contact in close position. * Afterwards the power is decreased (changing the PWM duty cycle) * in order to save power. * */ class Relay { private: unsigned long time_to_reduce_current; unsigned long millis_till_reduction; uint16_t reduced_current; uint8_t pin; static const uint8_t RELAY_STATE_OFF = 0; static const uint8_t RELAY_STATE_ON_WAIT_TO_REDUCE = 1; static const uint8_t RELAY_STATE_ON_REDUCED = 1; uint8_t state = RELAY_STATE_OFF; public: Relay(uint8_t pin = 0, float reduction = 0.4, unsigned long millis_till_reduction = 500, unsigned long analog_write_frequency = 10000) { setReduction(reduction); setMillisTillReduction(millis_till_reduction); analogWriteFreq(analog_write_frequency); this->pin = pin; pinMode ( pin, OUTPUT ); } void on() { switch (state) { case RELAY_STATE_OFF: analogWrite(pin, PWMRANGE); time_to_reduce_current = millis() + millis_till_reduction; state = RELAY_STATE_ON_WAIT_TO_REDUCE; break; } } void off() { analogWrite(pin, 0); state = RELAY_STATE_OFF; } bool isOn() { return state != RELAY_STATE_OFF; } void setReduction(float reduction) { if ( reduction > 1.0 ) reduction = 1.0; else if ( reduction < 0.0 ) reduction = 0.0; reduced_current = (PWMRANGE * 1.0) * reduction; } void setMillisTillReduction(unsigned long millis_till_reduction) { this->millis_till_reduction = millis_till_reduction; } void loop() { switch (state) { case RELAY_STATE_ON_WAIT_TO_REDUCE: if (millis() > time_to_reduce_current) { analogWrite(pin, reduced_current); state = RELAY_STATE_ON_REDUCED; } break; } } }; Relay relay (RELAY); //char ssid[] = "TanzoLab"; //char password[] = "tanzolab"; const char ssid[] = "Telecom-30203937"; const char password[] = "dSHxm8uVVPhCnSlyjV6Db5Vs"; IPAddress mqtt_server(192, 168, 0, 30); //IPAddress mqtt_server(192, 168, 1, 140); // test //IPAddress mqtt_server(192, 168, 1, 2); // CM£3-HOME address // mosquitto_pub -h localhost -t 'sonoff/60:01:94:89:08:F0/out' -m 'K3 ON' WiFiClient wifi_client; String mac = WiFi.macAddress(); // MAC of our interface String publish_path = "sonoff/"+mac+"/in"; String subscribe_path = "sonoff/"+mac+"/out"; PubSubClient mqtt_client(wifi_client, mqtt_server); // // called when something is published // void callback(const MQTT::Publish& pub) { int n = 0; // handle message arrived Serial.print(pub.topic()); Serial.print(" => ["); Serial.print(pub.payload_string()); Serial.println("]"); const char *p; char buf [32]; int nk; strcpy (buf, pub.payload_string().c_str()); if (p = strstr(buf, "K")) { nk = p[1] - '0'; p += 3; // skip to command if (!strcmp(p, "ON")) { if (nk == 1) relay.on(); } if (!strcmp(p, "OFF")) { if (nk == 1) relay.off(); } if (strstr(p, "BLINK")) { p += 6; if (!strcmp(p, "ON")) sys_led.blink(); if (!strcmp(p, "OFF")) sys_led.off(); } } } void setup() { static int i = 0; Serial.begin(115200); delay(10); // We start by connecting to a WiFi network // station mode WiFi.mode(WIFI_STA); if (strlen(password)) WiFi.begin(ssid,password); else WiFi.begin(ssid); Serial.println(); Serial.println("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay (1000); Serial.print(i); Serial.print(": connecting to "); Serial.println(ssid); Serial.printf("Connection status: %d\n", WiFi.status()); Serial.println("--------"); WiFi.printDiag(Serial); Serial.printf("\n........\n\n"); ++i; } mqtt_client.set_callback(callback); Serial.println(); Serial.print("WiFi connected with ip "); Serial.println(WiFi.localIP()); Serial.print("MAC:"); Serial.println(mac); /////////////////////////////////////////////////////////// // // mDNS client and responder // /////////////////////////////////////////////////////////// if (!MDNS.begin(("sonoff-"+mac).c_str())) { Serial.println("Error setting up MDNS responder!"); } Serial.println("mDNS responder started"); MDNS.addService("sonoff-"+mac, "tcp", 8080); // Announce esp tcp service on port 8080 // activate led sys_led.blink(500, 0.1); } //gets called when WiFiManager enters configuration mode void configModeCallback (WiFiManager *myWiFiManager) { Serial.println("Entered config mode"); Serial.println(WiFi.softAPIP()); //if you used auto generated SSID, print it Serial.println(myWiFiManager->getConfigPortalSSID()); //entered config mode, make led toggle faster ? } bool mqtt_server_found = false; // the loop function runs over and over again forever void loop() { // resolve the MQTT server name static int mdns_f = 0; if (mdns_f == 0) { Serial.println("Sending mDNS query"); // _googlecast._tcp int n = MDNS.queryService("mqtt", "tcp"); // Send out query for esp tcp services Serial.println("mDNS query done"); if (n == 0) { Serial.println("no services found"); sys_led.blink(1000, 0.2); } else { Serial.print(n); Serial.println(" service(s) found"); for (int i = 0; i < n; ++i) { // Print details for each service found Serial.print(i + 1); Serial.print(": "); Serial.print(MDNS.hostname(i)); Serial.print(" ("); Serial.print(MDNS.IP(i)); Serial.print(":"); Serial.print(MDNS.port(i)); Serial.println(")"); mqtt_server = MDNS.IP(i); mqtt_server_found = true; mdns_f = 1; } sys_led.blink(1000, 0.15); } Serial.println(); } // // MQTT processing // static int i = 0; if (WiFi.status() == WL_CONNECTED && mqtt_server_found == true) { if (!mqtt_client.connected()) { Serial.println("connecting to MQTT local server...."); if (mqtt_client.connect("SONOFF")) { Serial.println("MQTT connected"); mqtt_client.publish(publish_path,"START"); mqtt_client.subscribe(subscribe_path); } } if (mqtt_client.connected()) mqtt_client.loop(); } if (0) { delay(1000); char msg[128]; sprintf (msg, "Loop: %d", i++); mqtt_client.publish("esp8266", msg); Serial.println(msg); } // run relay class instance relay.loop(); // led sys_led.run(); } ## output del programma 4: connecting to Telecom-xxxx Connection status: 3 Mode: STA PHY mode: N Channel: 11 AP id: 0 Status: 5 Auto connect: 1 SSID (16): Telecom-xxxx Passphrase (24): xxxxxxxxxxx BSSID set: 0 WiFi connected with ip 192.168.0.39 MAC:5C:CF:7F:41:E2:5A mDNS responder started Sending mDNS query mDNS query done 1 service(s) found 1: andrewp (192.168.0.30:5900) connecting to MQTT local server.... MQTT connected Loop: 0 Loop: 1 ## Link al sito ITEAD * [CH4 2.0](https://www.itead.cc/wiki/Sonoff_4CH) * [TH10 16](https://www.itead.cc/wiki/Sonoff_TH_10/16) @include='bio_sergio_tanzilli' @include='bio_andrea_montefusco'
2018 Ⓒ TanzoLab