Markdown source

# 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'	



Le attività del TanzoLab si svolgono ogni mercoledi sera, salvo casi speciali, dalle ore 18:30 presso i locali della Acme Systems srl e consistono in:

  • Talk monotematici a cura di professionisti in vari settori tecnologici
  • Workshop pratici su elettronica embedded, produzione e informatica
  • Progettazione e realizzazione di nuovi prodotti embedded per l'IT

Le attività vengono coordinate tramite questo sito, in cui vengono pubblicati tutti i lavori svolti o in via di sviluppo, e tramite un gruppo Telegram con cui per interagire direttamente via chat con gli altri membri.