Queste note hanno fine esemplificativo. Esiste un software ominicomprensivo per l'uso dei dispositivi SONOFF con MTT al link seguente:
L'uso di questo software e' descritto qui:
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
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 possibili:
pubblicare in sonoff/devices il MAC address in modo da rendere autoconfigurante il sistema per il supervisore locale
pubblicare in sonoff/devices/
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).
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.
/*
* 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);
}
/*
* 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();
}
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
2018 Ⓒ TanzoLab