Adafruit MQTT e NodeMCU

Salve, ho creato questo programma per accendere la luce della camera con google assistant o normalmente con un pulsante. Sono arrivato allo step in cui voglio far andare il comando da pulsante anche se non c’è internet. Ho provato ma non riesco a trovare una soluzione, forse qualcuno più esperto potrebbe capire come fare e magari spiegarmi dove sbagliavo.

Grazie per la disponibilità.
Saluti

/***************************************************
  Adafruit MQTT Library ESP8266 Example

  Must use ESP8266 Arduino from:
    https://github.com/esp8266/Arduino

  Works great with Adafruit's Huzzah ESP board & Feather
  ----> https://www.adafruit.com/product/2471
  ----> https://www.adafruit.com/products/2821

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Tony DiCola for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/
#include <ESP8266WiFi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#define relayPin D0
#define buttonPin D1
int relayState = LOW;
int buttonState = LOW;
int WaitDebounce = 50;
unsigned long LastTimeDebounce = 0;
int LastRead = LOW;
/************************* WiFi Access Point *********************************/

#define WLAN_SSID       "your-ssid"
#define WLAN_PASS       "your-password"

/************************* Adafruit.io Setup *********************************/

#define AIO_SERVER      "io.adafruit.com"
#define AIO_SERVERPORT  1883                   // use 8883 for SSL
#define AIO_USERNAME    "your-username"
#define AIO_KEY         "your-key"

/************ Global State (you don't need to change this!) ******************/

// Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient client;
// or... use WiFiFlientSecure for SSL
//WiFiClientSecure client;

// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);

/****************************** Feeds ***************************************/

// Setup a feed called 'photocell' for publishing.
// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>
//Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/photocell");

// Setup a feed called 'onoff' for subscribing to changes.
Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/sample");

/*************************** Sketch Code ************************************/

// Bug workaround for Arduino 1.6.6, it seems to need a function declaration
// for some reason (only affects ESP8266, likely an arduino-builder bug).
void MQTT_connect();

void setup() {
  Serial.begin(115200);
  delay(10);

  pinMode(relayPin, OUTPUT);
  pinMode(buttonPin, INPUT);
  digitalWrite(relayPin, relayState);

  Serial.println(F("Adafruit MQTT demo"));

  // Connect to WiFi access point.
  Serial.println(); Serial.println();
  Serial.print("Connecting to ");
  Serial.println(WLAN_SSID);

  WiFi.begin(WLAN_SSID, WLAN_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.println("WiFi connected");
  Serial.println("IP address: "); Serial.println(WiFi.localIP());

  // Setup MQTT subscription for onoff feed.
  mqtt.subscribe(&onoffbutton);
}

//uint32_t x=0;

void loop() {
  // Ensure the connection to the MQTT server is alive (this will make the first
  // connection and automatically reconnect when disconnected).  See the MQTT_connect
  // function definition further below.
  MQTT_connect();

  // this is our 'wait for incoming subscription packets' busy subloop
  // try to spend your time here

  //google assistant
  Adafruit_MQTT_Subscribe *subscription;
  while ((subscription = mqtt.readSubscription(10))) {
    if (subscription == &onoffbutton) {
      Serial.print(F("Got: "));
      Serial.println((char *)onoffbutton.lastread);
      relayState = atoi((char *)onoffbutton.lastread);
      digitalWrite(relayPin, relayState);
    }
  }

  //button
  int reading = digitalRead(buttonPin);
  if (reading != LastRead) {
    LastTimeDebounce = millis();
  }

  if ((millis() - LastTimeDebounce) > WaitDebounce) {
    int reading = digitalRead(buttonPin);
    if (reading != buttonState and reading == HIGH) {
      relayState = !relayState;
      digitalWrite(relayPin, relayState);
      Serial.print(F("Set: "));
      Serial.println(relayState);
    }
    buttonState = reading;
  }
  LastRead = reading;
  delay(10);

  // Now we can publish stuff!
  /*
    Serial.print(F("\nSending photocell val "));
    Serial.print(x);
    Serial.print("...");
    if (! photocell.publish(x++)) {
    Serial.println(F("Failed"));
    } else {
    Serial.println(F("OK!"));
    }
  */

  // ping the server to keep the mqtt connection alive
  // NOT required if you are publishing once every KEEPALIVE seconds
  /*
    if(! mqtt.ping()) {
    mqtt.disconnect();
    }
  */
}

// Function to connect and reconnect as necessary to the MQTT server.
// Should be called in the loop function and it will take care if connecting.
void MQTT_connect() {
  int8_t ret;

  // Stop if already connected.
  if (mqtt.connected()) {
    return;
  }

  Serial.print("Connecting to MQTT... ");

  uint8_t retries = 3;
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
    Serial.println(mqtt.connectErrorString(ret));
    Serial.println("Retrying MQTT connection in 5 seconds...");
    mqtt.disconnect();
    delay(5000);  // wait 5 seconds
    retries--;
    if (retries == 0) {
      // basically die and wait for WDT to reset me
      while (1);
    }
  }
  Serial.println("MQTT Connected!");
}

TheVezz:
(visto che non mi ricordo come si mette del codice uso github)

CLicca su "More..." vicino a questo tuo post, poi "Modify", quindi ti posizioni in fondo al messaggio e clicchi sul pulsantino in alto a sinistra "[/]". Incolli il testo del codice e salvi.

OK grazie ho modificato il post

TheVezz:
OK grazie ho modificato il post

Ok. non so se ho capito bene cosa intendi, comuque ti direi di provare a modificare la funzione in questo modo:

bool MQTT_connect() {
  int8_t ret;

  // Stop if already connected.
  if (mqtt.connected()) {
    return true;
  }

  Serial.print("Connecting to MQTT... ");

  uint8_t retries = 3;
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
    Serial.println(mqtt.connectErrorString(ret));
    Serial.println("Retrying MQTT connection in 5 seconds...");
    mqtt.disconnect();
    delay(5000);  // wait 5 seconds
    retries--;
    if (retries == 0) {
	  return false;
    }
  }
  Serial.println("MQTT Connected!");
  return true;
}

In pratica restituisce true se si è connesso o è già connesso, false in caso contrario.
Quindi nel loop() fai un "if (MQTT_connect())" per eseguire il codice che ti serve solo quando c'è la connessione:

...
  if (MQTT_connect()) {
    // this is our 'wait for incoming subscription packets' busy subloop
	// try to spend your time here
	//google assistant
	Adafruit_MQTT_Subscribe *subscription;
	while ((subscription = mqtt.readSubscription(10))) {
	  if (subscription == &onoffbutton) {
	    Serial.print(F("Got: "));
	    Serial.println((char *)onoffbutton.lastread);
	    relayState = atoi((char *)onoffbutton.lastread);
	    digitalWrite(relayPin, relayState);
	  }
	}
  }
  //button
  int reading = digitalRead(buttonPin);
  if (reading != LastRead) {
    LastTimeDebounce = millis();
  }
...

Ok, grazie proverò stasera perchè ora vado a lavoro.
Comunque come mai hai cambiato il void in bool?

TheVezz:
Comunque come mai hai cambiato il void in bool?

Perché la funzione, come ho detto, restituisce true o false, che sono valori "booleani" ossia "bool".

Ma se poni questa domanda ho l'impressione che tu non conosca molto il linguaggio (sai la differenza tra una funzione "void" rispetto a "bool"?), perché se è così e quindi stai limitandoti al classico "copia-incolla-smanazza" ti consiglio di approfittare delle difficoltà per imparare almeno certe basi del linguaggio...

Comunque si la tua impressione é giusta non conosco benissimo il linguaggio; solo che non ho fatto copia-incolla-smanazza, ma lo ho fatto da solo uno step alla volta partendo dal file di esempio. Anche prima nel caso si connetteva eseguiva il codice nel void loop dopo mqtt connect, cioè la parte commentata come google assistant e button; se non rusciva a connettersi dop 3 tentativi dovevo resettare col pulsante sul nodemcu. Visto che ora uso valori booleani nel caso true mi esegue google assistant e button, nel caso false andrò a eseguire solo button. Per farlo in questo caso va bene un else? (sono ancora a lavoro quindi testeró il codice dopo verso le 23).

Ho provato a simulare una mancanza di collegamento al server (cambiando la porta) continua provare a riconnettersi al server. Dovrebbe fermarsi dopo 3 tentativi ed eseguire il codice che voglio.

Ho provato a lavorarci su.
Ho modificato la parte di codice nel bool MQTT_connect()

bool MQTT_connect() {
  int8_t ret;

  while (retries > 0) {
    Serial.print("Connecting to MQTT... ");
    ret = mqtt.connect();
    Serial.println(mqtt.connectErrorString(ret));
    if (retries > 1) {
      Serial.println("Retrying MQTT connection in 5 seconds...");
    }
    mqtt.disconnect();
    delay(5000);  // wait 5 seconds
    retries--;
  }
  if (retries == 0) {
    return false;
  }
  if ((ret = mqtt.connect()) = 0) {
    Serial.println("MQTT Connected!");
    retries = 3;
    return true;
  }
}

retries l'ho fatta diventare una variabile globale.
Per il codice da eseguire offline ho usato else.

Ora il relay cambia stato col pulsante anche senza connessione. Ma se rimetto a posta la porta (1883) ottengo l'errore 6. Lista Errori
Progetto su github

TheVezz:
Comunque si la tua impressione é giusta non conosco benissimo il linguaggio; solo che non ho fatto copia-incolla-smanazza, ma lo ho fatto da solo uno step alla volta partendo dal file di esempio.

Hehe beh i file di esempio vanno bene, e li si usa spesso. Ma se non sai le basi del linguaggio (non dico di esserne esperto, ma almeno le basi…), scusami, ma sarebbe come cercare di fare il meccanico senza sapere non solo nulla di come funzioni un motore (limitandosi a guardarne uno dall’esterno) ma neanche a cosa serva una chiave inglese (i tipi di dato, come si definiscono le funzioni, eccetera)…

Anche prima nel caso si connetteva eseguiva il codice nel void loop dopo mqtt connect, cioè la parte commentata come google assistant e button; se non rusciva a connettersi dop 3 tentativi dovevo resettare col pulsante sul nodemcu.

Eh certo, se hai cercato di capire il codice (hai cercato?) nella funzione MQTT_Connect() c’è questa parte qui:

    delay(5000);  // wait 5 seconds
    retries--;
    if (retries == 0) {
      // basically die and wait for WDT to reset me
      while (1);
    }

al termine dei tentativi entra nella “while (1);” che è un loop dal quale non esce (“basically die” = “in sostanza muori”).
Io l’ho cambiato facendo tornare “false” ossia “non sono riuscito a connettermi” e quindi non si blocca.

Ma, ripeto, cerca PRIMA di imparare le basi del linguaggio e quindi sapere intanto “leggere” un listato fatto da altri, e POI poi pensare di fare qualcosa di veramente “tuo”!
Visto che ora uso valori booleani nel caso true mi esegue google assistant e button, nel caso false andrò a eseguire solo button. Per farlo in questo caso va bene un else? (sono ancora a lavoro quindi testeró il codice dopo verso le 23).

TheVezz:
Ho provato a simulare una mancanza di collegamento al server (cambiando la porta) continua provare a riconnettersi al server. Dovrebbe fermarsi dopo 3 tentativi ed eseguire il codice che voglio.

Eh certo, ma io ti ho risolto il primo problema, sperando che trovassi tu la soluzione al resto, altrimenti il codice te lo scrivo io, e non è questo lo scopo del forum... :wink:

Quello che dici lo fa perché, se analizzi il codice appunto, la funzione MQTT_connect() viene richiamata sempre nel loop().

Ma, scusa, non era chiaro nelle tue specifiche cosa volessi fare, dicevi solo:
"Sono arrivato allo step in cui voglio far andare il comando da pulsante anche se non c'è internet."

Se vuoi semplicemente dire che quando non riesce a connettersi non deve più riprovarci, non devi modificare la MQTT_connect() (che non ho capito perché tu lo abbia fatto...) ma cosa deve fare quando non si connette, o quando dopo essersi connesso perde la connessione e non riesce più a riprenderla, ossia la MQTT_connect() restituisce "false".

In questo caso ti basterebbe modificare il loop() (mantenendo la funzione MQTT_connect che ti avevo fatto):

...

bool connLost = false;

void loop() {
  // Verifico se non ho perso la connessione
  if (!connLost) {
    // Ok, verifica la connessione o prova a (ri)connettersi
    if (!MQTT_connect()) {
	  // Connessione/riconnessione fallita! Lascio perdere
	  connLost = true;
	} else {
	    // La connessione è ok
		Adafruit_MQTT_Subscribe *subscription;
		while ((subscription = mqtt.readSubscription(10))) {
		  if (subscription == &onoffbutton) {
			Serial.print(F("Got: "));
			Serial.println((char *)onoffbutton.lastread);
			relayState = atoi((char *)onoffbutton.lastread);
			digitalWrite(relayPin, relayState);
		  }
		}
    }
  }
  //button
  int reading = digitalRead(buttonPin);
  if (reading != LastRead) {
    LastTimeDebounce = millis();
  }
...

PS: come vedi, abbastanza spesso provare prima a descrivere in italiano cosa si vuole fare, sotto forma di commenti, e poi iniziare a scrivere sotto il codice, aiuta a scrivere il programma oltre al fatto di averlo poi ben autodocumentato...

Ok grazie mille per l'aiuto. Ora proseguo con il prossimo step: ogni 5 minuti se non è connesso al server proverà a riconnettersi 3 volte.

Dovrebbe essere semplice da fare, userò millis().
Comunque hai ragione non avevo spiegato bene quello che sto facendo.

Necessario:

  • Google Home o Assistant
  • IFTTT
  • Account Adafruit
  • NodeMCU
  • Relay
  • Lampadina
  • Pulsante
  1. Creo una nuova applet su ifttt.
    trigger service: google assistant.
    trigger: say a simple phrase.
    inserisco 3 comandi vocali e una risposta da google assistant
    action service: adafruit
    action: send data to adafruit IO
    inserire nome feed e dato da salvare

  2. Creo un feed su adafruit IO, tipo di blocco da creare "toggle"

  3. Programmo il NodeMCU in modo che quando il dato sul feed viene cambiato da 1 a 0 o viceversa cambio lo
    stato del relay, o quando premo il pulsante cambio lo stato del relay. Il relay lo uso per accendere e
    spegnere una lampadina. NEl caso seono senza internet voglio comunque poter accendere e spegnere la
    luce (pulsante) e ogni 5 minuti prova a collegarsi al server

Spero che ora sia più chiaro quello che sto facendo