SonOff S20 - Jusaba/Domo-Serverpic GitHub Wiki
El dispositivo SonOffS20 es otro dispositivo que se puede conectar a Serverpic y que tiene como objetivo poder conectar/desconectar cargas conectadas a la red eléctrica.
Es otro de los dispositivos de la familia Outlet y el código está almacenado en el repositorio
Este módulo es un módulo comercial aunque, en el catalogo del fabricante ya no aparece como dispositivo comercializado, existen nuevas versiones que todavía no hemos tenido ocasión de analizar.
En la siguiente imagen se puede ver el dispositivo
Por ser un producto comercial, para conocer el hardware es aconsejable acceder a los datos del fabricante no obstante, se puede ver el esquema en este pdf almacenado en el repositorio
Los detalles del hardware que más nos interesan son:
El boton On/Off está conectado a GPIO 0
El relé esta conectado a GPIO 12
El led está conectadpo a GPIO 13
Por utilizar las librerías de Serverpcic el módulo tiene habilitadas todas las ordenes de servicio y, además, tiene las siguientes ordenes específicas de dispositivo
Comando | Descripcion |
---|---|
On | Conecta el dispositivo |
Off | Desconecta el dispositivo |
Change | Provoca un cambio de estado en el dispositivo |
ChangeGet | Provoca un cambio de estado en el dispositivo y manda mensaje a remitente con el nuevo estado |
Get | Manda mensaje a remitente con el estado actual del dispositivo |
Para elaborar el software de este módulo, como siempre, partimos de la estructura del Módulo Básico,
En IO.h se definen los pines de trabajo
#ifdef SONOFF_S20
int PinReset = 0; //Pin empleado para pulsador reset
int PinSalida = 12; //Pin salida para control dispositivo
int PinPulsador = 0; //Pulsador
int PinLed = 13;
#endif
En Serverpic.h, para este dispositivo se introducen los siguientes parámetros
#ifdef SONOFF_S20
#define Placa "SONOFF S20"
#define Modelo "ESP12"
#define Ino "OutletSonOffS20"
//------------
//Pulsador-LED
//------------
#define PulsadorLed
//------------
//Logica
//------------
#define Logica 1
//-----------------
//TiEmpo de rebotes
//-----------------
#define TempoRebotes 150
#endif
Se define PulsadorLed pora indicar al software que este dispositivo, a diferencia de otros de la familia Outlet, tiene pulsador para operación local y led indicador de estado. También se define un tiempo de rebotes para absorver los posibles rebotes del pulsador
En el mismo Serverpic.h se declaran las funciones Globales de la familia y los específicos para los dispositivos que, como este, disponen de pulsador y led
boolean GetDispositivo (void);
void DispositivoOn (void);
void DispositivoOff (void);
#ifdef PulsadorLed
void ICACHE_RAM_ATTR ISRAccionSC (void);
void ICACHE_RAM_ATTR ISRAccionNC (void);
void EnciendeLed (void);
void ApagaLed (void);
void FlashLed (void);
#endif
Las funciones Globales detalladas
/**
******************************************************
* @brief Devielve el estado del dispositivo
*
* @return devuelve <b>1</b> si el dispositivo esta conectado o <b>0<\b> en caso de que no este conectado
*/
boolean GetDispositivo (void)
{
boolean lLogica = Logica; //En función de si es lógica positiva o negativa determinamos el estado
boolean lEstadoDispositivo;
if ( digitalRead(PinSalida) == lLogica )
{
lEstadoDispositivo = 1;
}else{
lEstadoDispositivo = 0;
}
return ( lEstadoDispositivo );
}
/**
******************************************************
* @brief Pone el dispositivo en On
*
*/
void DispositivoOn (void)
{
if ( Logica )
{
digitalWrite(PinSalida, HIGH);
}else{
digitalWrite(PinSalida, LOW);
}
}
/**
******************************************************
* @brief Pone el dispositivo en OPff
*
*/
void DispositivoOff (void)
{
if ( Logica )
{
digitalWrite(PinSalida, LOW);
}else{
digitalWrite(PinSalida, HIGH);
}
}
El detalle de las funciones particulares para dispositivos con pulsador y led
#if defined PulsadorLed || defined PulsadorReset
/**
******************************************************
* @brief Rutina de interrupcion para el pulsador cuando hay conexion con el servidor
*
* Pone el flag de pulsacion a <b>1</b> ( lFlagInterrupcion ) eliminando los rebotes
*/
void ICACHE_RAM_ATTR ISRAccionSC (void)
{
if (millis() > nMilisegundosRebotes + TempoRebotes)
{
lFlagInterrupcion = 1;
nMilisegundosRebotes = millis();
}
}
#endif
#ifdef PulsadorLed
/**
******************************************************
* @brief Rutina de interrupcion para el pulsador cuando no hay conexion con el servidor
*
* Cambia el estado del dispositivo eliminando los rebotes del pulsador
*/
void ICACHE_RAM_ATTR ISRAccionNC (void)
{
if (millis() > nMilisegundosRebotes + TempoRebotes)
{
if ( GetDispositivo() )
{
DispositivoOff();
}else{
DispositivoOn();
}
nMilisegundosRebotes = millis();
}
}
/**
******************************************************
* @brief Enciende el led
*
*/
void EnciendeLed (void)
{
digitalWrite(PinLed, LOW);
}
/**
******************************************************
* @brief Apaga el led
*
*/
void ApagaLed (void)
{
digitalWrite(PinLed, HIGH);
}
/**
******************************************************
* @brief Hace un flash en el led
*
*/
void FlashLed (void)
{
EnciendeLed();
delay(50);
ApagaLed();
delay(50);
EnciendeLed();
delay(50);
ApagaLed();
delay(50);
EnciendeLed();
delay(50);
ApagaLed();
}
#endif
Outlet.ino
El programa principal, a diferencia del Módulo Básico particulariza el comportamiento del dispositivo. Como la familia Outlet es bastante amplia es necesario utilizar condicionales para particularizar cada uno de los sispositivos de la familia.
Eempezamos por el bloque setup()
void setup() {
#ifdef Debug //Usamos el puereto serie solo para debugar
Serial.begin(9600); //Si no debugamos quedan libres los pines Tx, Rx para set urilizados
Serial.println("Iniciando........");
#endif
EEPROM.begin(128); //Reservamos zona de EEPROM
//BorraDatosEprom ( 0, 128 ); //Borramos 64 bytes empezando en la posicion 0
pinMode(PinReset, INPUT_PULLUP); //Configuramos el pin de reset como entrada
pinMode(PinSalida, OUTPUT); //Configuramos el pin del rele com salida
DispositivoOff(); //Ponemos el dispositivo en Off
#ifdef Interruptor //Si el dispositivo tiene definido Interruptor ( Shelly )
pinMode(PinPulsador, INPUT); //Configuramos el pin del pulsador como entrada sin resistencias de pull-up
#endif
#ifdef PulsadorLed //Si el dispositivo tiene definido PulsadorLed ( SonOffS20, SonOff, .. )
pinMode(PinPulsador, INPUT_PULLUP); //Configuramos el pin del pulsador como entrada con resistencias de pull-up
pinMode(PinLed, OUTPUT); //configuramos el pin del led como salida
attachInterrupt(digitalPinToInterrupt(PinPulsador), ISRAccionNC, FALLING); //Habilitamos el pulsador para operar aunque no se conecte al
servidor
#endif
#ifdef PulsadorReset
attachInterrupt(digitalPinToInterrupt(PinReset), ISRAccionSC, FALLING); //Habilitamos pulsador como si estuviera conectado para que
haya flag de interrupcion en loop y poder resetear
#endif
#ifdef PulsadorLed //Si el dispositivo tiene definido PulsadorLed ( SonOffS20, SonOff, .. )
EnciendeLed(); //Encendemos el led
#endif
if ( LeeByteEprom ( FlagConfiguracion ) == 0 ) //Comprobamos si el Flag de configuracion esta a 0
{
ModoAP(); //Si está, Lo ponemos en modo AP
}else{ //Si no esta
if ( ClienteSTA() ) //Lo poenmos en modo STA y nos conectamos a la SSID
{ //Si ha conseguido conectarse a ls SSID en modo STA
if ( ClienteServerPic () ) //Intentamos conectar a ServerPic
{ //Si se consigue
CheckFirmware(); //Comprobamos si el firmware esta actualizado a la ultima version
#ifdef Debug
Serial.println(" ");
Serial.println("Conectado al servidor-------------");
#endif
#ifdef PulsadorLed //Si se ha definido PulsadorLed, apagamos el Led para indicar que se ha conectado
ApagaLed();
#endif
DataConfig aCfg = EpromToConfiguracion (); //Leemos la configuracin de la EEprom
char USUARIO[1+aCfg.Usuario.length()];
(aCfg.Usuario).toCharArray(USUARIO, 1+1+aCfg.Usuario.length()); //Almacenamos en el array USUARIO el nombre de usuario
cDispositivo = USUARIO;
lHomeKit = aCfg.lHomeKit;
lWebSocket = aCfg.lWebSocket;
lEstadisticas = aCfg.lEstadisticas;
if ( lEstadisticas ) //Si están habilitadas las estadisticas, actualizamos el numero de inicios
{
GrabaVariable ("inicios", 1 + LeeVariable("inicios") );
}
}
}
}
#ifdef PulsadorLed //Si el dispositivo tiene definido PulsadorLed ( SonOffS20, SonOff, .. )
attachInterrupt(digitalPinToInterrupt(PinPulsador), ISRAccionSC, FALLING); //Habilitamos pulsador con dsipositivo conectado a servidor
#endif
#ifdef PulsadorReset
attachInterrupt(digitalPinToInterrupt(PinReset), ISRAccionSC, FALLING); //Habilitamos pulsador con dsipositivo conectado a servidor para
poder resetear el dispositivo
#endif
cSalida = LeeValor(); //Arrancamos con el ultimo valor
if ( cSalida == "ERROR") //Si no habia ultimo valor, arrancamos con Off
{
DispositivoOff();
}else{ //Si existia ultimo valor, arrancamos con el valor recuperado
if (cSalida == "On")
{
DispositivoOn();
}
if (cSalida == "Off")
{
DispositivoOff();
}
}
}
En el código se puede ver como, si se está definido Pulsadorled, los condicionales se encargan de configurar los pines afectaos como entradas/salidas y para el pin que tiene asignado el pulsador se definen funciones de interrupción para detectar la pulsación.
Se han comentado los bloques que se han añadido respecto al Módulo Básico
loop()
void loop() {
/*----------------
Comprobacion Reset
------------------*/
TestBtnReset (PinReset);
/*----------------
Comprobacion Conexion
------------------*/
if ( TiempoTest > 0 )
{
if ( millis() > ( nMiliSegundosTest + TiempoTest ) ) //Comprobamos si existe conexion
{
nMiliSegundosTest = millis();
if ( !TestConexion(lEstadisticas) ) //Si se ha perdido la conexion
{
lConexionPerdida = 1; //Flag de perdida conexion a 1
if ( GetDispositivo() ) //Si el dispositivo estaba a On
{
lEstado = 1; //Guardamos ese estado para
//recuperarlo en la reconexion
DispositivoOff(); //Ponemos a Off el dispositivo
}
}else{
if ( lConexionPerdida ) //Comprobamos si una reconexion
//( por perdida anterior )
{ //Si lo es
lConexionPerdida = 0; //Reseteamos flag de reconexion
digitalWrite(PinSalida, !lEstado); //Ponemos el dispositivo en el
//estado anterior a la perdida
//de conexion
lEstado = 0; //Reseteamos el Flag lEstado
}
}
}
}
/*----------------
Analisis comandos
------------------*/
oMensaje = Mensaje ();
if ( oMensaje.lRxMensaje) //Si se ha recibido ( oMensaje.lRsMensaje = 1)
{
#ifdef Debug
Serial.println(oMensaje.Remitente);
Serial.println(oMensaje.Mensaje);
#endif
//En este punto empieza el bloque de programa particular del dispositivo
if (oMensaje.Mensaje == "On") //Si se recibe "On", se pone PinSalida a '1'
{
DispositivoOn();
cSalida = "On"; //Preparamos el mensaje para enviar a HomeKit
//Y/o WebSocket
}
if (oMensaje.Mensaje == "Off") //Si se recibe "Off", se pone PinSalida a '0'
{
DispositivoOff();
cSalida = "Off"; //Preparamos el mensaje para enviar a HomeKit
//Y/o WebSocket
}
if (oMensaje.Mensaje == "Change") //Si se recibe 'Change',
{ //cambia el estado de PinSalida
if ( GetDispositivo() ) //Para ello, miramos el estado
{ //actual
DispositivoOff(); //y lo cambiamos
cSalida = "Off"; //Preparamos cSalida para
}else{ //para informar a HomeKit
DispositivoOn(); //y/o WebSocket
cSalida = "On";
}
}
if (oMensaje.Mensaje == "ChangeGet") //Si se recibe 'ChangeGet', cambia
//el estado de PinSalida y devuelve
//el nuevo estado al remitente
{
if ( GetDispositivo() )
{
DispositivoOff();
cSalida = "Off";
}else{
DispositivoOn();
cSalida = "On";
}
oMensaje.Mensaje = cSalida; //Confeccionamos el mensaje para el servidor
oMensaje.Destinatario = oMensaje.Remitente;
EnviaMensaje(oMensaje); //Y lo enviamos
}
if (oMensaje.Mensaje == "Get") //Si se recibe 'Get',
{ //se devuelve el estado de PinSalida al remitente
if ( GetDispositivo() )
{
cSalida = "On";
}else{
cSalida = "Off";
}
oMensaje.Mensaje = cSalida; //Confeccionamos el mensaje a para el servidor
oMensaje.Destinatario = oMensaje.Remitente;
EnviaMensaje(oMensaje);
cSalida = String(' '); //No ha habido cambio de estado,
//Vaciamos cSalida para que no se envie
//a WebSocket y a HomeKit
}
//--------------------------------------------------------------------
// Actualizacion ultimo valor
//--------------------------------------------------------------------
//Si hay cambio de estado, se lo indicamos al servidor para actualizar
//ultimo valor, daro que se usara para restablecer el dispositivo
//tras una desconexion no deseada ( fallo red electrica )
if ( cSalida != String(' ') )
{
EnviaValor (cSalida);
}
//--------------------------------------------------------------------
// Actualizacion WebSocket
//--------------------------------------------------------------------
//Si hay cambio de estado y está habilitado WebSocket se envia el
//nuevo estado a WebSocket
#ifdef WebSocket
if ( cSalida != String(' ') && lWebSocket )
{
EnviaMensajeWebSocket(cSalida);
}
#endif
//--------------------------------------------------------------------
// Actualizacion HomeKit
//--------------------------------------------------------------------
//Si hay cambio de estado y está habilitado HomeKit se envia el
//nuevo estado a HpmeKit
#ifdef HomeKit
if ( cSalida != String(' ') && oMensaje.Remitente != ( cDispositivo + String("_") ) && lHomeKit )
{
oMensaje.Destinatario = cDispositivo + String("_");
oMensaje.Mensaje = cSalida;
EnviaMensaje(oMensaje);
}
#endif
cSalida = String(' '); //Limpiamos cSalida para iniciar
//un nuevo analisis
if ( lEstadisticas ) //Si están habilitadas estadísticas
{ //Actualizamos numero comandos ejecutados
GrabaVariable ("comandos", 1 + LeeVariable("comandos") );
}
nMiliSegundosTest = millis(); //Refrescamos nMiliSegundosTest para no
//hacer test de conexión por que si se
//ha recibido mensaje es que hay conexion
}
wdt_reset(); //Refrescamos WDT
}
Para compilar, se debe compilar Outlet.ino como de costumbre, en nuestro caso se ha utilizado Stino pero puede utilizarse el IDE de arduino. Para poder cambiarle el firmware es necesario abrir el dispositivo y acceder a los Pins de grabación
Las parametros para la compilación son los siguientes
Placa: Generic ESP8266 Module
CPU Frecuency: 80 MHZ
CRYSTAL Frecuency: 26 MHZ
Debug Level: none
Debug Port: Disabled
Esase Flash: Only sketch
Expressid FW: nonos-sdk 2.2.1 (legacy)
Exceptiosn: Disabled
Flash Frecuency: 40 MHZ
Flash Mode: DOUT ( compatible )
Flash Size: 1M(no SPIFFS)
IwIP Variant: v2 Lower Memory
Reset Method: ck
SSL SUport: ALL SSL chiphers (most compatible)
Upload Speed: 115200
VTables: Flash
Una vez compilado y cargado, la primera vez que se enciende, se crea la red Wifi Jusaba con password 24081960, nos conectamos a esa red y accedemos a la pagina de configuración 192.168.4.1, rellenamos los datos de configuración y enviamos el formulario, desconectamos y conectamos el dispositivo y si todo se ha realizado correctamente el dispositivo se conectará a Serverpic.
Lo primero que hará será chaquear si hay una versión de firmware publicada distinta a la que se le ha grabado y si es así, el dispositivo cargará la nueva versión.