Implementazione di un interruttore remoto con il LoRa E220 attivato da server web


Guest post created with ♥ by William 🙂|

L’obiettivo principale qui è risparmiare energia della batteria per una videocamera Wyse Cam3, che utilizza 180mA a 5volt. Poiché la videocamera è basata sul web e viene visualizzata solo poche volte al giorno, vogliamo evitare che consumi energia continuamente. Al momento, una batteria da 10.000mA dura solo un giorno, quindi abbiamo bisogno di un modo migliore per gestire la sua alimentazione. Utilizzando un interruttore remoto controllato da una richiesta del server web asincrono, possiamo garantire che la videocamera non sprechi batteria quando non viene utilizzata.

ESP32 E220 and remote switch
ESP32 E220 e interruttore remoto

Panoramica del Progetto:

Il progetto dell’interruttore remoto E220 ridurrà il consumo della batteria utilizzando un timer di conto alla rovescia; avviato su richiesta web e spento alla scadenza del timer di conto alla rovescia. Quando viene ricevuta una richiesta web, il modulo trasmettitore E220-900T30D invierà un messaggio wake-on-radio (WOR). Il modulo ricevente si risveglierà dalla modalità Sleep. La modalità Sleep è uno stato di corrente ultra-bassa nel range dei microampere. L’ESP32 si risveglierà dal Deep Sleep e accenderà l’alimentazione della batteria; contemporaneamente, il modulo E220 riceverà il messaggio WOR. Quando il timer di conto alla rovescia scadrà, il modulo E220 invierà un messaggio, spegnendo l’alimentazione della batteria e mettendo l’ESP32 in deep sleep e il modulo ricevente E220 in modalità sleep. Il ciclo si ripete per nuove richieste web.

Breve descrizione del progetto E220-Remote-Switch:

Il progetto E220-Remote-Switch utilizza due moduli RF Ebyte E220-900T30D e due microcontrollori ESP32. Lo stato attuale del progetto è che il monitor della batteria INA226 e l’interruttore MOSFET KY002S non sono stati implementati in questo aggiornamento.

Modalità dimostrazione:

  1. ESP32 Ricevitore: premere il pulsante di reset del ricevitore; questo mette l’ESP32 in deep sleep.
  2. Aprire il browser su http://10.0.0.27/relay; questo creerà una richiesta web per accendere l’alimentazione della batteria e avviare un timer di conto alla rovescia per spegnere l’alimentazione della batteria, quindi mettere il ricevitore ESP32 in deep sleep.
  3. Il primo messaggio è il messaggio WOR per svegliare il modulo ricevente E220 e l’ESP32 in deep sleep. Il secondo messaggio dal trasmettitore E220 è necessario per accendere l’alimentazione della batteria e avviare il timer di conto alla rovescia. Entrambi i messaggi vengono inviati quando arriva una richiesta del server da un clic su un link web per visualizzare la videocamera.  
Demo E220-Remote-Switch con monitoraggio della corrente in sleep

Tre vantaggi dell’utilizzo del modulo Ebyte E220-900T30D sono una maggiore distanza stimata di 10 km, una potenza di 30 dbm e una corrente in modalità sleep di 5 uA. Il terzo vantaggio è la capacità di inviare un messaggio WOR per svegliare il ricetrasmettitore ricevente, consentendo un secondo messaggio per accendere l’alimentazione della batteria.

Una corrente di trasmissione di 620 mA è quasi istantanea a 30 dBm e invia fino a 200 byte prima di abbassare la corrente. Corrente di ricezione; per un messaggio, 17,2 mA. I valori di corrente sono tratti dal manuale utente Ebytes E220-900T30D. Corrente di standby misurata 11,8 mA. Il modulo E220 consuma sempre corrente; utilizzando la modalità sleep E220, la corrente in sleep varia da 0,54 uA a 97,33 uA e l’intero ciclo dei messaggi è misurato con un multimetro digitale impostato per microampere. Occasionalmente si verificano sovraccarichi del misuratore molto brevi, dell’ordine di pochi millisecondi, a causa del messaggio trasmesso o ricevuto.  

Componenti utilizzati

Modulo E220 LoRa; due radio E220-900T30D, ricetrasmettitori, utilizzati come trasmettitore e ricevitore. Si consiglia di etichettare il trasmettitore e il ricevitore.  Modulo Ebyte E220-900T30D

Modulo EByte E220-900T30D
Modulo EByte E220-900T30D

Puoi trovare i moduli su E220-400T22D 433MHz 5Km - E220-400T30D 433MHz 10Km - E220-900T22D 868MHz 915MHz 5Km - E220-900T30D 868MHz 915MHz 10Km

Microcontrollore ESP32: due microcontrollori ESP32 Devkit v1: uno per il server web, uno per ricevere messaggi e uno per accendere l’alimentazione della batteria. 

Qui una selezione di ESP32 ESP32 Dev Kit v1 - TTGO T-Display 1.14 ESP32 - NodeMCU V3 V2 ESP8266 Lolin32 - NodeMCU ESP-32S - WeMos Lolin32 - WeMos Lolin32 mini - ESP32-CAM programmer - ESP32-CAM bundle - ESP32-WROOM-32 - ESP32-S

Interruttore MOSFET bistabile KY002 per interruttore della batteria; l’interruttore può gestire 3-27 Volt a 1,5 Ampere. 

Modulo interruttore bistabile a pulsante singolo 3-27V 1.5A Flip Flop Latch Trigger su bordo interruttore KY002

Monitor batteria INA226  36 Volt, 20 Amp, I²C 

Monitor batteria INA226  

Non è stata tentata alcuna ottimizzazione a basso consumo poiché ESP32 Devkit v1 è una scheda di sviluppo. Tuttavia, il consumo di corrente del modulo E220 è stato ridotto utilizzando la modalità sleep.

Connessioni del modulo trasmettitore e ricevitore E220

Pinout ESP32 DOIT DEV KIT v1
Pinout ESP32 DOIT DEV KIT v1

Le connessioni di configurazione per il trasmettitore e il ricevitore non richiedono modifiche al cablaggio dopo le connessioni iniziali. La modalità di funzionamento del trasmettitore e del ricevitore utilizzerà i comandi della libreria E220 codificati negli sketch del trasmettitore e del ricevitore ESP32.

esp32Ebyte E220
GPIO21M0
GPIO19M1
TXD2RX
RXD2TX
GPIO15 (pin RTC GPIO importante)AUX
5 VoltsVCC
GNDGND

L’interruttore MOSFET bistabile KY002S sarà incluso nei futuri aggiornamenti; KY002S è emulato in questo sketch con dichiarazioni di stampa seriale.

Il monitor batteria INA226 sarà un futuro aggiornamento; monitorerà tensione, corrente e allarme a una tensione impostata e registrerà su un file.

Schema dettagliato che mostra le connessioni tra ESP32, moduli LoRa E220

Nota: il pin AUX del modulo E220 è collegato a GPIO15, che è un pin RTC GPIO necessario per svegliare ESP32 

Nello schema sopra, D18 è usato come AUX; è sufficiente usare D15 per il progetto.

Trasmettitore: librerie necessarie per ESP32 e modulo LoRa E220

#include <Arduino.h> // *
#include "WiFi.h" // *
#include <WiFiUdp.h> // *
#include <HTTPClient.h> // *
#include <time.h> // *
#include "LoRa_E220.h" // Libreria Ebyte LoRa E220
#include <AsyncTCP.h> // Libreria AsyncTCP
#include "ESPAsyncWebServer.h" // Libreria ESPAsyncWebServer
#include <Ticker.h> // *
#import "index7.h" // Pagina web HTML memorizzata in memoria - Non rimuovere; tranne per sostituire con il nuovo "index7.h".

Le librerie con “*” sono incluse in Arduino IDE, Board Manager 2.0.17. Board Manager 2.0.17 è stato utilizzato per compilare gli sketch per questo progetto.

Ricevitore: librerie necessarie per ESP32 e modulo LoRa 220:

#include "Arduino.h" // *
#include "LoRa_E220.h"
#include <WiFi.h> // *
#include <time.h> // *
#include <FS.h> // *
#include <LittleFS.h> // *
#include "esp_sleep.h" // *
#include "driver/gpio.h" // *
#include "esp_system.h" // *
#include <INA226_WE.h> // Libreria INA226_WE
#include <Wire.h> // *

Le librerie con “*” incluse in Arduino IDE, Board Manager 2.0.17. Board Manager 2.0.17 è stato utilizzato per compilare gli sketch in questo progetto.

Struttura di invio con E220

Il progetto utilizza i contenitori della libreria E220 per inviare messaggi strutturati. Ci sono due strutture: una per il tempo, utilizzando l’ora NTP dal trasmettitore poiché il ricevitore non avrà accesso a Internet; l’altra, Messaggio, utilizza dati di variabili intere per commutare la batteria e il timestamp formattato effettivo; il timestamp verrà utilizzato nel file di registro del monitor della batteria. Il valore dei dati della variabile determina se la posizione dell’interruttore della batteria è acceso o spento.  

Codice del contenitore per la ricezione structure Message message

ResponseStructContainer rsc = e220ttl.receiveMessage(sizeof(Message));

if (rsc.status.code == 1) { // Verifica se lo stato è SUCCESS
 Message message = *(Message*)rsc.data;
 //Serial.println(message.switchState); // Questo stampa sul monitor
 //Serial.println(message.dateTime); // Questo stampa sul monitor
 rsc.close();

Il messaggio WOR viene inviato quando arriva la richiesta del server.  WOR è stato spostato in una funzione per evitare che invii il messaggio WOR all’avvio dello sketch e per poter inviare due messaggi con un solo clic dell’utente web per visualizzare la videocamera.

Funzione WOR:

void sendWOR(){
      e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
      delay(delayTime);
      // Invia messaggio
      ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, "Hello, world? WOR!");
      e220ttl.setMode(MODE_0_NORMAL);
      delay(delayTime);
}

Problemi e loro soluzioni

Il problema più grande era la connessione intermittente tra la breadboard e il modulo E220. Questo è stato risolto utilizzando cavi Dupont femmina-maschio con connessioni modulo-breadboard.  

Un altro problema era inviare un messaggio WOR e poi dover inviare un secondo messaggio utilizzando la stessa richiesta web. La soluzione includeva l’invio del secondo messaggio alla richiesta del server web asincrono.

Codice della soluzione della richiesta del server:

server.on("/relay", HTTP_GET, [](AsyncWebServerRequest *request) {
     request->send_P(200, PSTR("text/html"), HTML7, processor7);
     sendWOR();  //Invia messaggio WOR; ha spostato la posizione per l'invio del messaggio WOR alla funzione sendWOR.
     data = 1;  //Imposta i dati a 1 utilizzati per accendere l'interruttore della batteria
     needAnotherCountdown = 1;  //Limita il ticker una volta al timer "reset" per più usi; invece di un singolo uso.
     countdownTrigger();  //Avvia il timer di conto alla rovescia.
});

Funzione sendWOR():

void sendWOR(){
    e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
    delay(delayTime);
    // Invia messaggio
    ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, "Hello, world? WOR!");
    e220ttl.setMode(MODE_0_NORMAL);
    delay(delayTime);
}

Funzione countdownTrigger():

void countdownTrigger() {
    // Esegui azioni di conto alla rovescia qui
    Serial.println("\nCountdown timer triggered!\n");
    //getDateTime();
    // Pianifica il prossimo conto alla rovescia se necessario
    if (needAnotherCountdown == 1) {
        onceTick.once(60, ISRcamera);
        data = 1;
        switchOne(data);
        needAnotherCountdown = 0;
    }
}

La funzione switchOne() invia un structure Message message, include il valore dei dati e il timestamp formattato.

void switchOne(int data) {
	if (data == 1) {
		Serial.println("\nSveglia da GPIO esterno!");
		Serial.println("Sveglia e inizia a ascoltare!\n");
		delay(500);
		Serial.println("\nESP32 che si sveglia dal Deep Sleep");
		Serial.println("Interruttore della batteria è ACCESO\n");
	}
	if (data == 2) {
		Serial.println("\nAlimentazione batteria spenta");
		Serial.println("ESP32 va in Deep Sleep\n");
	}

	Serial.println("Ciao, sto per inviare il messaggio!");
	e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
	delay (delayTime);
	get_time();
	Message message;
	
	//inizializzare i membri della struttura
	message.switchState = data;

	// Inizializza la data e ora
	String dateTimeStr = get_time();

	if (!dateTimeStr.isEmpty()) {
		strncpy(message.dateTime, dateTimeStr.c_str(), MAX_dateTime_LENGTH - 1);
		message.dateTime[MAX_dateTime_LENGTH - 1] = '\0'; // Assicurati la terminazione con null
	}

	Serial.print("switchState: ");
	Serial.println(message.switchState);

	Serial.print("dateTime: ");
	Serial.println(message.dateTime);

	// Invia messaggio
	ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, &message, sizeof(Message));
	// Controlla se c'è qualche problema di invio riuscito
	Serial.println(rs.getResponseDescription());
}

int sendMessage(int data) {
	Serial.println("Ciao, sto per inviare il messaggio!");
	e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
	delay (delayTime);
	get_time();

	Message message;

	//Inizializza i membri della struttura
	message.switchState = data;

	// Inizializza la data e ora
	String dateTimeStr = get_time();
	if (!dateTimeStr.isEmpty()) {
		strncpy(message.dateTime, dateTimeStr.c_str(), MAX_dateTime_LENGTH - 1);
		message.dateTime[MAX_dateTime_LENGTH - 1] = '\0'; // Assicurati la terminazione con null
	}

	//Serial.print("switchState: "); Serial.println(message.switchState);
	//Serial.print("dateTime: "); Serial.println(message.dateTime);
	// Invia messaggio
	ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, &message, sizeof(Message));
	// Controlla se c'è qualche problema di invio riuscito
	Serial.println(rs.getResponseDescription());
}

Applicazioni per i makers fai-da-te con E220-Remote-Switch

  1. Progetti di automazione domestica
    • Illuminazione intelligente: Controlla l’illuminazione domestica da remoto tramite smartphone o interfaccia web.
    • Tende/tapparelle automatizzate: Apri e chiudi tende o tapparelle in base all’ora o ai livelli di luce.
    • Apriporta del garage: Costruisci un apriporta del garage controllato da remoto.
  2. Progetti di giardino e all’aperto
    • Sistema di irrigazione automatizzato: Irrigazione controllata da remoto in base al programma o all’umidità del suolo.
    • Illuminazione del giardino: Controlla l’illuminazione esterna per estetica e sicurezza.
  3. Sistemi di sicurezza
    • Serratura della porta a distanza: Blocca/sblocca le porte da remoto per sicurezza o controllo accessi.
    • Telecamere di sorveglianza: Controlla l’attivazione e l’angolazione delle telecamere.
  4. Monitoraggio ambientale
    • Stazione metereologica: Monitora temperatura, umidità e pressione da remoto.
    • Monitor della qualità dell’aria: Misura e segnala la qualità dell’aria con avvisi remoti.
  5. Cura degli animali domestici
    • Distributore automatico di cibo: Distribuisci cibo da remoto a orari specifici.
    • Porta per animali: Controlla l’accesso degli animali domestici in base al programma o al comando.
  6. Robotica fai-da-te
    • Controllo del robot: Naviga e opera i robot da remoto.
    • Progetti di droni: Controllo remoto per i modelli di volo e le azioni dei droni fai-da-te.
  7. Gestione energetica
    • Presa intelligente: Gestisci il consumo energetico dei dispositivi da remoto.
    • Controllo del termostato: Regola i sistemi di riscaldamento e raffreddamento domestici da remoto.
  8. Progetti automobilistici
    • Sistema di allarme per auto: Allarme per auto e tracciamento controllato da remoto.
    • Avviamento a distanza: Avvia e ferma il motore dell’auto da remoto.
  9. Intrattenimento e media
    • Sistema audio controllato da remoto: Controlla le configurazioni audio domestiche da remoto.
    • Controllo dello schermo del proiettore: Alza e abbassa gli schermi del proiettore da remoto.
  10. Progetti IoT personalizzati
    • Reti multi-sensore: Monitoraggio e controllo remoto delle reti di sensori.
    • Raccolta dati: Raccogli e analizza i dati dei sensori da remoto.
    • Allerta cassetta postale: Ricevi avvisi per l’arrivo della posta.

Queste applicazioni dimostrano la versatilità e il potenziale dell’E220-Remote-Switch per i makers fai-da-te, permettendo progetti innovativi e funzionali che migliorano la vita quotidiana.

Sketch completi

Ecco gli sketch completi; puoi anche scaricarli dal mio repository GitHub.

Pagina Index HTML

//index7.h
const char HTML7[] PROGMEM = R"====(
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
<style>
  html, body {
  margin: 0; /* Set margin to 0 to remove any unwanted spacing */
  padding: 0;
  height: 100%;
}

.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  height: 100vh; /* Use viewport height for container */
}

  @media (max-width: 768px) {
  .container {
    flex: 1 1 auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
  }
}
    
    header {
      margin: 5vh auto 0;
      width: 100%;
      text-align: center;
    }
    
    main {
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    iframe {
      aspect-ratio: 16 / 9;
      width: 1300px; /* Set the width to 100% to occupy the entire container */
      flex: 1; /* Use flex: 1 to make the iframe expand and fill the available space */
    }

    footer {
      width: 100%;
      text-align: center;
      padding: 10px; /* Add some padding to the footer for spacing */
    }
</style>
</head>
<body>
  <div class="container">
    <header>
      <br><br>This is original H264 video encoded by IP camera; server doesn't do any transcoding.  Wyze Cam v3 video feeds
      <br>Wyze-Bridge Docker, container; which provides webRTC video URL.  Camera maybe offline; depending on battery discharge state.
      <br><br>
    </header>
    <main>
      <iframe class="iframe" width="1300" height="731"src="http://c600.duckdns.org:8889/wyze-cam-v3/" frameborder="0"></iframe> 
    </main>
    <footer>
      <h2><a href="http://%LINK%/Weather" >ESP32 Server</a></h2>  
    </footer>
  </div>
</body>
</html>
)====";

Sketch invio

//E220_Remote_Switch_Sender.ino
//William Lucid 07/19/2024 @ 19:41 EST

//E220 Module is set to ADDL 3

//Fully connectd schema  AUX connected to ESP32, GPIO15
//Ardino IDE:  ESP32 Board Manager, Version 2.0.17

//  See library downloads for each library license.

// With FIXED SENDER configuration

#include <Arduino.h>
#include "WiFi.h"
#include <WiFiUdp.h> 
#include <HTTPClient.h>
#include <time.h>
#include "LoRa_E220.h"
#include <AsyncTCP.h>
#include "ESPAsyncWebServer.h"
#include <Ticker.h>

#import "index7.h"  //Video feed HTML; do not remove

#define DESTINATION_ADDL 2
#define FREQENCY_915
#define CHANNEL 66

#define RXD2 16
#define TXD2 17

#define AUX_PIN GPIO_NUM_15

int delayTime = 100;  //setmode delay duration

WiFiClient client;

///Are we currently connected?
boolean connected = false;

WiFiUDP udp;
// local port to listen for UDP packets
const int udpPort = 1337;
char incomingPacket[255];
char replyPacket[] = "Hi there! Got the message :-)";
//NTP Time Servers
const char * udpAddress1 = "pool.ntp.org";
const char * udpAddress2 = "time.nist.gov";

#define TZ "EST+5EDT,M3.2.0/2,M11.1.0/2"

int DOW, MONTH, DATE, YEAR, HOUR, MINUTE, SECOND;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

char strftime_buf[64];

// ---------- esp32 pins --------------
 LoRa_E220 e220ttl(&Serial2, 15, 21, 19); //  RX AUX M0 M1

//LoRa_E220 e220ttl(&Serial2, 22, 4, 33, 21, 19, UART_BPS_RATE_9600); //  esp32 RX <-- e220 TX, esp32 TX --> e220 RX AUX_PIN M0 M1
// -------------------------------------

// Replace with your network details
const char *ssid = "R2D2";
const char *password = "sissy4357";

AsyncWebServer server(80);

int data = 0;

// Struct to hold date and time components
struct DateTime {
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
};

// Define the maximum length for the dateTime
const int MAX_dateTime_LENGTH = 30;

int switchState;

struct Message {
  int switchState;
  char dateTime[MAX_dateTime_LENGTH];  // Array to hold date/time string
};

Message message;

Ticker oneTick;
Ticker onceTick;

String linkAddress = "xxx.xxx.xxx.xxx:80";

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

volatile int watchdogCounter;
int totalwatchdogCounter;
int cameraPowerOff = 0;
int watchDog;

void ISRwatchdog() {

  portENTER_CRITICAL_ISR(&mux);

  watchdogCounter++;

  if (watchdogCounter >= 75) {

    watchDog = 1;
  }

  portEXIT_CRITICAL_ISR(&mux);
}

int cameraFlag;
int needAnotherCountdown = 0;

void ISRcamera() {
  batteryOff();
}

bool got_interrupt = false;
 
void interruptHandler() {
  got_interrupt = true;
}  

void sendWOR(){
  e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
  delay(delayTime);
  // Send message
  ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, "Hello, world? WOR!");
  e220ttl.setMode(MODE_0_NORMAL);
  delay(delayTime);
}

void setup() {
  Serial.begin(9600);
  delay(500);

  Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
  
  Message message;
  message.switchState = data;
  String dateTimeStr = get_time();
  if (!dateTimeStr.isEmpty()) {
    strncpy(message.dateTime, dateTimeStr.c_str(), MAX_dateTime_LENGTH - 1);
    message.dateTime[MAX_dateTime_LENGTH - 1] = '\0'; // Ensure null-termination
  }

  wifi_Start(); 
  
  pinMode(AUX_PIN, INPUT);

  attachInterrupt(GPIO_NUM_15, interruptHandler, FALLING);
  
  // Startup all pins and UART
  e220ttl.begin();
  delay(delayTime);
  e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
  delay(delayTime);

  //sendWOR();
 
  e220ttl.setMode(MODE_0_NORMAL);
  delay(delayTime);

  Serial.println("\n\n\nWebserver and");
  Serial.println("E220-900T30D Remote Switch\n");

  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  // See https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv for Timezone codes for your region
  setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 3);   // this sets TZ to Indianapolis, Indiana


  server.on("/relay", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, PSTR("text/html"), HTML7, processor7);
    sendWOR();
    data = 1;
    needAnotherCountdown = 1;
    countdownTrigger();
  });

  server.begin();

  oneTick.attach(1.0, ISRwatchdog);  //watchdog  ISR triggers every 1 second
} 

void loop() {

  DateTime currentDateTime = getCurrentDateTime();	
  
  if((currentDateTime.minute % 15 == 0) && (currentDateTime.second == 0)){
    //webInterface();  //Sends URL Get request to wake up Radio and ESP32 at 1 minute interval
                      // URL = http://10.0.0.27/relay	 
    //delay(1000);
  }
  
  //udp only send data when connected
  if (connected)
  {

    //Send a packet
    udp.beginPacket(udpAddress1, udpPort);
    udp.printf("Seconds since boot: %u", millis() / 1000);
    udp.endPacket();
  }

  // If something available
  if (e220ttl.available() > 1) {
    // read the String message
    ResponseContainer rc = e220ttl.receiveMessage();
    // Is something goes wrong print error
    if (rc.status.code != 1) {
      Serial.println(rc.status.getResponseDescription());
    } else {
      // Print the data received
      Serial.println(rc.status.getResponseDescription());
      Serial.println(rc.data);
    }
  }
}
      
String processor7(const String &var) {

  //index7.h

  if (var == F("LINK"))
    return linkAddress;

  return String();
}

void batteryOff() {
  int data = 2;
  switchOne(data);
  oneTick.detach();
}

void configTime()
{

  configTime(0, 0, udpAddress1, udpAddress2);
  setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 3);   // this sets TZ to Indianapolis, Indiana
  tzset();

  //udp only send data when connected
  if (connected)
  {

    //Send a packet
    udp.beginPacket(udpAddress1, udpPort);
    udp.printf("Seconds since boot: %u", millis() / 1000);
    udp.endPacket();
  }

  Serial.print("wait for first valid dateTime");

  while (time(nullptr) < 100000ul)
  {
    Serial.print(".");
    delay(5000);
  }

  Serial.println("\nSystem Time set\n");

  get_time();

  Serial.println(message.dateTime);

}

void countdownTrigger() {
  // Perform countdown actions here
  Serial.println("\nCountdown timer triggered!\n");
  //getDateTime();
  // Schedule the next countdown if needed
  if (needAnotherCountdown == 1) {
    onceTick.once(60, ISRcamera);
    data = 1;
    switchOne(data);
    needAnotherCountdown = 0;
  }
}

// Function to get current date and time
DateTime getCurrentDateTime() {
    DateTime currentDateTime;
    time_t now = time(nullptr);
    struct tm *ti = localtime(&now);

    // Extract individual components
    currentDateTime.year = ti->tm_year + 1900;
    currentDateTime.month = ti->tm_mon + 1;
    currentDateTime.day = ti->tm_mday;
    currentDateTime.hour = ti->tm_hour;
    currentDateTime.minute = ti->tm_min;
    currentDateTime.second = ti->tm_sec;

    return currentDateTime;
}

// Function to get the dateTime
String get_time() {

    time_t now;
    time(&now);
    char time_output[MAX_dateTime_LENGTH];
    strftime(time_output, MAX_dateTime_LENGTH, "%a  %m/%d/%y   %T", localtime(&now)); 
    return String(time_output); // returns dateTime in the specified format
}

void switchOne(int data) {
  if (data == 1) {
    Serial.println("\nWaked up from external GPIO!");
    Serial.println("Wake and start listening!\n");
    delay(500);
    Serial.println("\nESP32 waking from Deep Sleep");  
    Serial.println("Battery Switch is ON\n"); 
  }

  if (data == 2) {
    Serial.println("\nBattery power switched OFF");
    Serial.println("ESP32 going to Deep Sleep\n");
  }

  Serial.println("Hi, I'm going to send message!");

  e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
  
  delay(delayTime);
  
  get_time();

  Message message; 
  
  //initialize struct members
  message.switchState = data;
  
  // Initialize the dateTime 
  String dateTimeStr = get_time();
  if (!dateTimeStr.isEmpty()) {
    strncpy(message.dateTime, dateTimeStr.c_str(), MAX_dateTime_LENGTH - 1);
    message.dateTime[MAX_dateTime_LENGTH - 1] = '\0'; // Ensure null-termination
  }

  Serial.print("switchState:  "); Serial.println(message.switchState);
  Serial.print("dateTime:  "); Serial.println(message.dateTime);

  // Send message
  ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, &message, sizeof(Message));
  // Check If there is some problem of succesfully send
    Serial.println(rs.getResponseDescription());  
}  


int sendMessage(int data){
  Serial.println("Hi, I'm going to send message!");

  e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
  
  delay(delayTime);
  
  get_time();

  Message message; 

  //Initialize struct members
  message.switchState = data;
 
  // Initialize the dateTime 
  String dateTimeStr = get_time();
  if (!dateTimeStr.isEmpty()) {
    strncpy(message.dateTime, dateTimeStr.c_str(), MAX_dateTime_LENGTH - 1);
    message.dateTime[MAX_dateTime_LENGTH - 1] = '\0'; // Ensure null-termination
  }

  //Serial.print("switchState:  "); Serial.println(message.switchState);
  //Serial.print("dateTime:  "); Serial.println(message.dateTime);

  // Send message
  ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, &message, sizeof(Message));
  // Check If there is some problem of succesfully send
  Serial.println(rs.getResponseDescription());  
}

void webInterface() {

  //getTimeDate();

  String data = "http://10.0.0.27/relay";

  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;    // Declare object of class HTTPClient

    http.begin(data);  // Specify request destination

    // No need to add content-type header for a simple GET request

    int httpCode = http.GET();   // Send the GET request

    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();  // Get the response payload

      Serial.print("HttpCode: ");
      Serial.print(httpCode);   // Print HTTP return code
      Serial.println("\n");
      //Serial.print("  Data echoed back from Hosted website: ");
      //Serial.println(payload);  // Print payload response      

      http.end();  // Close HTTPClient
    } else {
      Serial.print("HttpCode: ");
      Serial.print(httpCode);   // Print HTTP return code
      Serial.println("  URL Request failed.");

      http.end();   // Close HTTPClient
    }
  } else {
    Serial.println("Error in WiFi connection");
  }
}

void wifi_Start() {

//Server settings
#define ip { 10, 0, 0, 27}
#define subnet \
  { 255, 255, 255, 0 }
#define gateway \
  { 10, 0, 0, 1 }
#define dns \
  { 10, 0, 0, 1 }

  WiFi.mode(WIFI_AP_STA);

  Serial.println();
  Serial.print("MAC: ");
  Serial.println(WiFi.macAddress());

  // We start by connecting to WiFi Station
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  delay(1000);

  //setting the static addresses in function "wifi_Start
  IPAddress ip;
  IPAddress gateway;
  IPAddress subnet;
  IPAddress dns;

  WiFi.config(ip, gateway, subnet, dns);

  Serial.println("Web server running. Waiting for the ESP32 IP...");

  // Printing the ESP IP address
  Serial.print("Server IP:  ");
  Serial.println(WiFi.localIP());
  Serial.print("Port:  ");
  Serial.println("80");
  Serial.print("MAC: ");
  Serial.println(WiFi.macAddress());
  Serial.print("Wi-Fi Channel: ");
  Serial.println(WiFi.channel());
  Serial.println("\n");

  delay(300);

  WiFi.waitForConnectResult();

  Serial.printf("Connection result: %d\n", WiFi.waitForConnectResult());

  server.begin();


  if (WiFi.waitForConnectResult() != 3) {
    delay(3000);
    wifi_Start();
  }
}

Sketch ricezione

//E220_Remote_Switch_Receiver.ino   Added KY002S Bi-Stable MOSFET Switch and INA226 Battery Monitor
//William Lucid 7/28/2024 @ 01:21 EDT

//E220 Module is set to ADDL 2

//Fully connectd schema  AUX connected to ESP32, GPIO15 --Important RTC_GPIO Pin 
//Ardino IDE:  ESP32 Board Manager, Version 2.0.17

//  See library downloads for each library license.

// With FIXED SENDER configuration

#include "Arduino.h"
#include "LoRa_E220.h"
#include <WiFi.h>
#include <time.h>
#include <FS.h>
#include <LittleFS.h>
#include "esp_sleep.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include <INA226_WE.h>
#include <Wire.h>

#define AUX_PIN_BITMASK 0x8000

// Persisted RTC variable
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR int switch_State = 0;

const int pulseDuration = 300;  // 100 milliseconds (adjust as needed)

#define DESTINATION_ADDL 3
#define FREQUENCY_915
#define CHANNEL 66

#define RXD2 16
#define TXD2 17


#define M0_PIN GPIO_NUM_21
#define M1_PIN GPIO_NUM_19

#define AUX_PIN GPIO_NUM_15
#define TRIGGER 32  //KY002S MOSFET Bi-Stable Switch
#define ALERT 4     //INA226 Battery Monitor

#define SDA_PIN 25
#define SCL_PIN 26

int delayTime = 100;  //setmode delay duration

#define I2C_ADDRESS 0x40

INA226_WE ina226 = INA226_WE(I2C_ADDRESS);

volatile bool event = false;

void alert() {
  event = true;
  detachInterrupt(ALERT);
}

volatile bool alertFlag = false;

// Struct to hold date and time components
struct DateTime {
  int year;
  int month;
  int day;
  int hour;
  int minute;
  int second;
};

// Define the maximum length for the timestamp
const int MAX_dateTime_LENGTH = 30;

int data = 0;

int switchState;

struct Message {
  int switchState;
  char dateTime[MAX_dateTime_LENGTH];  // Array to hold date/time string
};

Message message;

#define FPM_SLEEP_MAX_TIME 0xFFFFFFF
void callback() {
  Serial.println("Callback");
  Serial.flush();
}

bool interruptExecuted = false;  // Ensure interruptExecuted is volatile

void IRAM_ATTR wakeUp() {
  // Do not use Serial on interrupt callback
  interruptExecuted = true;
  detachInterrupt(AUX_PIN);
}

void printParameters(struct Configuration configuration);

// ---------- esp32 pins ----------------
LoRa_E220 e220ttl(&Serial2, 15, 21, 19);  //  RX AUX M0 M1

//LoRa_E220 e220ttl(&Serial2, 22, 4, 33, 21, 19, UART_BPS_RATE_9600); //  esp32 RX <-- e220 TX, esp32 TX --> e220 RX AUX_PIN M0 M1
// -------------------------------------

void handleWakeupReason() {
  esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  switch (wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0:
      Serial.println("Wakeup caused by external signal using RTC_IO");
      break;
    case ESP_SLEEP_WAKEUP_EXT1:
      Serial.println("Wakeup caused by external signal using RTC_CNTL");
      break;
    case ESP_SLEEP_WAKEUP_TIMER:
      Serial.println("Wakeup caused by timer");
      break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD:
      Serial.println("Wakeup caused by touchpad");
      break;
    case ESP_SLEEP_WAKEUP_ULP:
      Serial.println("Wakeup caused by ULP program");
      break;
    default:
      Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason);
      break;
  }
}

void enterSleepMode() {
  e220ttl.setMode(MODE_3_SLEEP);
  delay(delayTime);
}

void enterDeepSleep() {
  e220ttl.setMode(MODE_2_POWER_SAVING);
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_15, LOW);
  Serial.flush();  // Ensure all Serial data is sent before sleep
  gpio_hold_en(GPIO_NUM_21);
  gpio_hold_en(GPIO_NUM_19);
  gpio_deep_sleep_hold_en();
  Serial.println("Going to deep sleep");
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void setup() {
  Serial.begin(9600);
  delay(1000);

  Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); // TX = 17, RX = 16
  delay(500);

  pinMode(M0_PIN, OUTPUT);
  pinMode(M1_PIN, OUTPUT);

  enterSleepMode();

  Serial.println("\n\nE220 Remote Switch Receiver\n");

  //Set default Trigger normal boot 
  digitalWrite(TRIGGER, LOW);

  // Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  // Configure wake-up interrupt on GPIO15 (AUX pin)
  attachInterrupt(GPIO_NUM_15, wakeUp, FALLING);

  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_ON);

  pinMode(AUX_PIN, INPUT_PULLUP);  // GPIO15 WakeUp
  pinMode(TRIGGER, OUTPUT);        // ESP32, GPIO23
  pinMode(ALERT, INPUT);          // ESP32, GPIO4

  bool fsok = LittleFS.begin(true);
  Serial.printf_P(PSTR("\nFS init: %s\n"), fsok ? PSTR("ok") : PSTR("fail!"));

  Wire.begin(SDA_PIN, SCL_PIN);

  if (!ina226.init()) {
    Serial.println("\nFailed to init INA226. Check your wiring.");
    //while(1){}
  }

  // INA226 configuration
  ina226.enableAlertLatch();
  ina226.setAlertType(BUS_UNDER, 5);
  attachInterrupt(digitalPinToInterrupt(ALERT), alert, FALLING);

  e220ttl.begin();
  delay(delayTime);
  e220ttl.setMode(MODE_2_WOR_RECEIVER);
  delay(delayTime);

  // Check if the wakeup was due to external GPIO
  if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0) {
    //Serial.println("Waked up from external GPIO!");

    gpio_hold_dis(GPIO_NUM_21);
    gpio_hold_dis(GPIO_NUM_19);
    gpio_deep_sleep_hold_dis();

    e220ttl.setMode(MODE_0_NORMAL);
    delay(delayTime);
    e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, "We have waked up from message, but we can't read It!");
  } else {
    e220ttl.setMode(MODE_2_POWER_SAVING);
    delay(delayTime);
    Serial.println("Going to deep sleep!");
    delay(100);

    if (ESP_OK == gpio_hold_en(GPIO_NUM_21)) {
      Serial.println("HOLD 21");
    } else {
      Serial.println("NO HOLD 21");
    }
    if (ESP_OK == gpio_hold_en(GPIO_NUM_19)) {
      Serial.println("HOLD 19");
    } else {
      Serial.println("NO HOLD 19");
    }
    
    gpio_deep_sleep_hold_en();
    delay(1);

    //Set default Trigger going deep sleep
    digitalWrite(TRIGGER, LOW);

    esp_sleep_enable_ext0_wakeup(GPIO_NUM_15, LOW);

    esp_deep_sleep_start();
  }
}

void loop() {

  //Serial.println("Test deep sleep");

  e220ttl.setMode(MODE_2_WOR_RECEIVER);
  
  if (e220ttl.available() > 0) {
    Serial.println("\nMessage arrived!");

    ResponseStructContainer rsc = e220ttl.receiveMessage(sizeof(Message));
    if (rsc.status.code == 1) {  // Check if the status is SUCCESS
      Message message = *(Message*)rsc.data;
      //Serial.println(message.switchState);  // This prints to monitor
      //Serial.println(message.dateTime);     // This prints to monitor
      rsc.close();

      e220ttl.setMode(MODE_0_NORMAL);
      delay(delayTime);

      ResponseStatus rsSend = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, "We have received the message!");
      Serial.println(rsSend.getResponseDescription());
      delay(10);

      e220ttl.setMode(MODE_0_NORMAL);
      delay(delayTime);

      enterSleepMode();
      
      if (message.switchState == 1 ) {
        Serial.println("\nWaked up from external0 RTC GPIO!");
        Serial.println("Wake and start listening!\n");
        digitalWrite(TRIGGER, HIGH);
        Serial.println("\nBattery power switched ON");
        Serial.println("ESP32 wake from Deep Sleep\n");
        getINA226(message.dateTime);
      }     

      if (message.switchState == 2) {
        digitalWrite(TRIGGER, HIGH);
        Serial.println("\nBattery power switched OFF");
        Serial.println("ESP32 going to Deep Sleep\n");
        enterDeepSleep();
      }   

      if (event) {
        Serial.println("Under voltage alert");
        alertFlag = true;
        ina226.readAndClearFlags();
        //attachInterrupt(digitalPinToInterrupt(ALERT), alert, FALLING);
        event = false;
        ina226.readAndClearFlags();
        //enterDeepSleep();
      }   
    }
  }
}

int main() {
  // Create an instance of the Message struct
  Message message;

  // Get the timestamp using the get_time function and assign it to the struct member
  String timestamp = get_time();
  timestamp.toCharArray(message.dateTime, MAX_dateTime_LENGTH);

  // Now you can use message.timestamp as needed...

  return 0;
}

// Function to get the timestamp
String get_time() {
  time_t now;
  time(&now);
  char time_output[MAX_dateTime_LENGTH];
  strftime(time_output, MAX_dateTime_LENGTH, "%a  %d-%m-%y %T", localtime(&now));
  return String(time_output);  // returns timestamp in the specified format
}

void getINA226(const char* dtStamp) {
  float shuntVoltage_mV = 0.0;
  float loadVoltage_V = 0.0;
  float busVoltage_V = 0.0;
  float current_mA = 0.0;
  float power_mW = 0.0;
  ina226.startSingleMeasurement();
  ina226.readAndClearFlags();
  shuntVoltage_mV = ina226.getShuntVoltage_mV();
  busVoltage_V = ina226.getBusVoltage_V();
  current_mA = ina226.getCurrent_mA();
  power_mW = ina226.getBusPower();
  loadVoltage_V = busVoltage_V + (shuntVoltage_mV / 1000);
  checkForI2cErrors();

  Serial.println(dtStamp);
  Serial.print("Shunt Voltage [mV]: ");
  Serial.println(shuntVoltage_mV);
  Serial.print("Bus Voltage [V]: ");
  Serial.println(busVoltage_V);
  Serial.print("Load Voltage [V]: ");
  Serial.println(loadVoltage_V);
  Serial.print("Current[mA]: ");
  Serial.println(current_mA);
  Serial.print("Bus Power [mW]: ");
  Serial.println(power_mW);

  if (!ina226.overflow) {
    Serial.println("Values OK - no overflow");
  } else {
    Serial.println("Overflow! Choose higher current range");
  }
  Serial.println();

  // Open a "log.txt" for appended writing
  File log = LittleFS.open("/log.txt", "a");

  if (!log) {
    Serial.println("file 'log.txt' open failed");
  }

  log.print(dtStamp);
  log.print(" , ");
  log.print(shuntVoltage_mV, 3);
  log.print(" , ");
  log.print(busVoltage_V, 3);
  log.print(" , ");
  log.print(loadVoltage_V, 3);
  log.print(" , ");
  log.print(current_mA, 3);
  log.print(" , ");
  log.print(power_mW, 3);
  log.print(" , ");
  if(alertFlag){
    log.print("Under Voltage alert");
    alertFlag = false;
  }
  log.println("");
  log.close();
}

void checkForI2cErrors() {
  byte errorCode = ina226.getI2cErrorCode();
  if (errorCode) {
    Serial.print("I2C error: ");
    Serial.println(errorCode);
    switch (errorCode) {
      case 1:
        Serial.println("Data too long to fit in transmit buffer");
        break;
      case 2:
        Serial.println("Received NACK on transmit of address");
        break;
      case 3:
        Serial.println("Received NACK on transmit of data");
        break;
      case 4:
        Serial.println("Other error");
        break;
      case 5:
        Serial.println("Timeout");
        break;
      default:
        Serial.println("Can't identify the error");
    }
    if (errorCode) {
      while (1) {}
    }
  }
}

Sketch di utilità per caricare il contenuto statico

#include <WiFi.h>
#include <FS.h>
#include <SPIFFS.h>
#include <LittleFS.h>
#include <FTPServer.h>

// Replace "XXXXXXXX" with your network details
const char * ssid = "XXXX";
const char * password = "XXXXXXXX";

WiFiClient client;

FTPServer ftpSrv(LittleFS);

void setup(){

  Serial.begin(9600);
  delay(1000);
  
  wifi_Start();

  bool fsok = LittleFS.begin(true);
  Serial.printf_P(PSTR("FS init: %s\n"), fsok ? PSTR("ok") : PSTR("fail!"));

  Serial.printf_P(PSTR("\nConnected to %s, IP address is %s\n"), ssid, WiFi.localIP().toString().c_str());

  // setup the ftp server with username and password
  // ports are defined in FTPCommon.h, default is
  //   21 for the control connection
  //   50009 for the data connection (passive mode by default)
  ftpSrv.begin(F("admin"), F("password")); //username, password for ftp.  set ports in ESP8266FtpServer.h  (default 21, 50009 for PASV)
}

void loop(){ 
  for(int x = 1; x < 5000; x++){
    ftpSrv.handleFTP();    
  }  
}

void wifi_Start() {

//Server settings
#define ip { 10, 0, 0, 28 }
#define subnet \
  { 255, 255, 255, 0 }
#define gateway \
  { 10, 0, 0, 1 }
#define dns \
  { 10, 0, 0, 1 }

  WiFi.mode(WIFI_AP_STA);

  Serial.println();
  Serial.print("MAC: ");
  Serial.println(WiFi.macAddress());

  // We start by connecting to WiFi Station
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  delay(1000);

  //setting the static addresses in function "wifi_Start
  IPAddress ip;
  IPAddress gateway;
  IPAddress subnet;
  IPAddress dns;

  WiFi.config(ip, gateway, subnet, dns);

  Serial.println("Web server running. Waiting for the ESP32 IP...");

  // Printing the ESP IP address
  Serial.print("Server IP:  ");
  Serial.println(WiFi.localIP());
  Serial.print("Port:  ");
  Serial.println("80");
  Serial.print("MAC: ");
  Serial.println(WiFi.macAddress());
  Serial.print("Wi-Fi Channel: ");
  Serial.println(WiFi.channel());
  Serial.println("\n");

  delay(500);

  WiFi.waitForConnectResult();

  Serial.printf("Connection result: %d\n", WiFi.waitForConnectResult());

  if (WiFi.waitForConnectResult() != 3) {
    delay(3000);
    wifi_Start();
  }
}

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *