SonOff S20 - Jusaba/Domo-Serverpic GitHub Wiki

Introducción 📖

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

Hardware 🔧

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

Comandos 🛠️

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

Software 💾

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

}

Compilación ⚙️

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.

⚠️ **GitHub.com Fallback** ⚠️