Wifi enabled outdoor train signal

Now that the weather has warmed up enough I have installed the ESP8266 upgrade to my outdoor German train signal.

It uses some buttons and the image widget. Sadly the segmented buttons upper case all the captions, making the aspect names incorrect.

https://cabin-layout.mixmox.com/2019/03/Outdoor-signal-with-wi-fi-control.html

#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include "Signal.h" // defines auth, ssid, password, version
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#include "ota.h"



// =========================================================


//output pins
// currently set for NodeMCU    dont use   D3-0  D4-2 

const byte PHPR = 16; // D0
const byte PHPG = 5; //D1
const byte PHPY = 4; // D2
const byte PVRO2 = 14; // D5
const byte PVRG2 = 12; // D6
const byte PVRG1 = 13; // D7
const byte PVRO1 = 0; // D3   was  2; // D4  


// colors
#define BLYNK_GREEN     "#23C48E"
#define BLYNK_YELLOW    "#ED9D00"
#define BLYNK_RED       "#D3435C"

SimpleTimer timer;

// Signal modes
int Hp = 0;
int Vr = 0;
bool autoA = false; // auto aspect changes
int duration = 0 ; // duration of auto aspect in min


void alloff(){
  digitalWrite(PHPR,RELAY_LOW);
  digitalWrite(PHPG,RELAY_LOW);
  digitalWrite(PHPY,RELAY_LOW);
  digitalWrite(PVRO1,RELAY_LOW);
  digitalWrite(PVRG1,RELAY_LOW);
  digitalWrite(PVRO2,RELAY_LOW);
  digitalWrite(PVRG2,RELAY_LOW);
}


void animate() {
  static byte step = 0;
  Serial.println(step);
  static byte s[8];
  switch (step) {
    case 0 : {  s[1] = RELAY_HIGH; s[2] = RELAY_HIGH; s[3] = RELAY_HIGH; s[4] = RELAY_HIGH; s[5] = RELAY_HIGH; s[6] = RELAY_HIGH; s[7] = RELAY_HIGH;  break;}; //all on
    case 1 : {  s[1] = RELAY_LOW ; s[2] = RELAY_LOW ; s[3] = RELAY_LOW ; s[4] = RELAY_LOW ; s[5] = RELAY_LOW ; s[6] = RELAY_LOW ; s[7] = RELAY_LOW ;  break;}; //all off
    case 2 : {  s[1] = RELAY_HIGH;  break;}; //
    case 3 : {  s[1] = RELAY_LOW; s[2] = RELAY_HIGH ;  break;}; //
    case 4 : {                    s[2] = RELAY_LOW ; s[3] = RELAY_HIGH ;  break;}; //
    case 5 : {                                       s[3] = RELAY_LOW ; s[4] = RELAY_HIGH ;  break;}; //
    case 6 : {                                                          s[4] = RELAY_LOW ; s[5] = RELAY_HIGH ;   break;}; //
    case 7 : {                                                                             s[5] = RELAY_LOW ; s[6] = RELAY_HIGH ;   break;}; //
    case 8 : {                                                                                                s[6] = RELAY_LOW ;  s[7] = RELAY_HIGH ;  break;}; //
    case 9 : {                                                                                                                    s[7] = RELAY_LOW ;  break;}; //
    case 10 :{                    s[2] = RELAY_HIGH;                                       s[5] = RELAY_HIGH;                     s[7] = RELAY_HIGH;  break;}; // greens on
    case 11 :{                    s[2] = RELAY_LOW;  s[3] = RELAY_HIGH; s[4] = RELAY_HIGH; s[5] = RELAY_LOW ; s[6] = RELAY_HIGH;  s[7] = RELAY_LOW;  break;}; // oranges on
    case 16 :
    case 20:
    case 12 :{                                       s[3] = RELAY_LOW;  s[4] = RELAY_HIGH; s[5] = RELAY_LOW ; s[6] = RELAY_LOW ;    break;}; // 4 on
    case 17 :
    case 13 :{                                                          s[4] = RELAY_LOW ; s[5] = RELAY_HIGH ;   break;}; // 5 ON
    case 18 :
    case 14 :{                                                                             s[5] = RELAY_LOW ;                     s[7] = RELAY_HIGH ;  break;}; // 7 ON
    case 19 :
    case 15 :{                                                                                                s[6] = RELAY_HIGH ; s[7] = RELAY_LOW;  break;}; // 6 ON
    case 21 : {                                      s[3] = RELAY_HIGH; s[4] = RELAY_LOW; break;}; // 3 ON
    case 22 : {                   s[2] = RELAY_HIGH; s[3] = RELAY_LOW;  break;}; // 2 ON
    case 23 :{ s[1] = RELAY_HIGH; s[2] = RELAY_LOW;   break;}; // 1 on
    
    default : {step = 0; displayHp(); return;};
  } // switch
  // set the outputs
  digitalWrite(PHPR,s[1]);
  digitalWrite(PHPG,s[2]);
  digitalWrite(PHPY,s[3]);
  digitalWrite(PVRO1,s[4]);
  digitalWrite(PVRG1,s[5]);
  digitalWrite(PVRO2,s[6]);
  digitalWrite(PVRG2,s[7]);
  
  step++;
  timer.setTimeout(600,animate); // call ourselves later
  
}

void showDuration() { // update the auto button to show minutes left
  static int timerID;
  
  if (autoA) {
    if (duration==0){
      autoAspect(); // start a new one
    }else{  // still time left
      //Serial.println("\r\n"); // force timestamp 
      //Serial.println(duration);
      Blynk.setProperty(V21,"label","≈ " + String(duration--) + " min."); // place the duration on the auto button and decrement it
      timerID = timer.setTimeout(60000L , showDuration);  // call us back in a minute
    }
  }else { // not auto anymore
    timer.deleteTimer(timerID);
    Blynk.setProperty(V21,F("label"),F(" ")); // clear the duration
  } // autoA

} // showDuration

/*
 Auto aspect scheme:
 autoAspect picks a new aspect and duration and calls showDuration.
 showDuration will run every ~minute and update the time left and when it gets to zero, call autoAspect again if auto mode is still on. 
  
 */
void autoAspect() {  // change aspects randomly
  //Serial.println("autoAspect");
  // we may get called an extra time after cancelling auto mode
  if (! autoA) { return;} // get out if not in auto mode

  // if we were not in Hp0 we should switch to Hp0 for a while 
  if (Hp == 0) { // we were in Hp0 so pick another one at random
    Hp = random(1,3); // get a random Hp  but not Hp0
    Vr = random(0,3); // get a random Vr
  } else {
    Hp = 0; // switch to Hp 0 for one cycle
  }
  Blynk.virtualWrite(V1, Hp + 1); //force app to new Hp
  displayHp(); // do the lights
  Blynk.virtualWrite(V2, Vr + 1); //force app to new Vr
  //Serial.print(F("New aspect Hp "));Serial.println(Hp);
  //Serial.print(F("New aspect Vr "));Serial.println(Vr);
  duration = random(3,20); //  3 minutes to 20 minutes 
  //Serial.print(F("For "));Serial.println(String(duration) + F(" minutes"));
  showImage(); // show the aspect in app
  
  showDuration();
}

void showImage(){  // select the appropriate image in image widget
  switch (Hp) {
    case 0 : { Blynk.virtualWrite(V20,1);  Blynk.virtualWrite(V0,F("Hp0"));   return;} // Hp0
    case 1 : { //Hp1
      switch (Vr) {
        case 0 : { Blynk.virtualWrite(V20,2); Blynk.virtualWrite(V0,F("Hp1Vr0"));  return;} // Hp1Vr0 
        case 1 : { Blynk.virtualWrite(V20,4); Blynk.virtualWrite(V0,F("Hp1Vr1"));  return;} // Hp1Vr1 
        case 2 : { Blynk.virtualWrite(V20,6); Blynk.virtualWrite(V0,F("Hp1Vr2"));  return;} // Hp1Vr2
      } // VR
    } // Hp1  
    case 2 : { //Hp2
      switch (Vr) {
        case 0 : { Blynk.virtualWrite(V20,3); Blynk.virtualWrite(V0,F("Hp2Vr0"));  return;} // Hp2Vr0 
        case 1 : { Blynk.virtualWrite(V20,5); Blynk.virtualWrite(V0,F("Hp2Vr1"));   return;} // Hp2Vr1 
        case 2 : { Blynk.virtualWrite(V20,7); Blynk.virtualWrite(V0,F("Hp2Vr2"));  return;} // Hp2Vr2
      } // VR
    } // Hp2
  } // Hp
} // showImage

void displayHp() {
   displayVr();    
   switch (Hp){
    case 0:
      digitalWrite(PHPR,RELAY_HIGH); digitalWrite(PHPG,RELAY_LOW); digitalWrite(PHPY,RELAY_LOW); 
      break;
    case 1:
      digitalWrite(PHPR,RELAY_LOW); digitalWrite(PHPG,RELAY_HIGH); digitalWrite(PHPY,RELAY_LOW);   
      break;
    case 2:
      digitalWrite(PHPR,RELAY_LOW); digitalWrite(PHPG,RELAY_HIGH); digitalWrite(PHPY,RELAY_HIGH);  
      break;
  } // Switch
} // displayHp



void displayVr() {
 
  if (Hp == 0) {
    digitalWrite(PVRO1,RELAY_LOW); digitalWrite(PVRG1,RELAY_LOW); digitalWrite(PVRO2,RELAY_LOW); digitalWrite(PVRG2,RELAY_LOW); 
  }
  else{
    switch (Vr){
    case 0:
      digitalWrite(PVRO1,RELAY_HIGH); digitalWrite(PVRG1,RELAY_LOW); digitalWrite(PVRO2,RELAY_HIGH); digitalWrite(PVRG2,RELAY_LOW); 
      break;  
    case 1:
      digitalWrite(PVRO1,RELAY_LOW); digitalWrite(PVRG1,RELAY_HIGH); digitalWrite(PVRO2,RELAY_LOW); digitalWrite(PVRG2,RELAY_HIGH); 
      break;    
    case 2:
      digitalWrite(PVRO1,RELAY_LOW); digitalWrite(PVRG1,RELAY_HIGH); digitalWrite(PVRO2,RELAY_HIGH); digitalWrite(PVRG2,RELAY_LOW); 
      break;  
    } // switch Vr
  } // Hp 0
} // displayVr

BLYNK_WRITE(V1) // Hp selector
{
  Hp = param.asInt() - 1 ; // assigning incoming value 
  displayHp(); // forces Vr to update also
  showImage();
}

BLYNK_WRITE(V2) {    // Vr selector
  Vr = param.asInt() -1; // assigning incoming value 
  displayVr();  
  showImage();
  }

BLYNK_WRITE(V3) {    // Network status
  int ask = param.asInt() ; // assigning incoming value 
  if (ask == 1) {
    long rssi = WiFi.RSSI();
   
      Blynk.setProperty(V3,"offLabel","📶 " + String(rssi) + "dBm");
  }
}  // V3

BLYNK_WRITE(V21) { // auto mode
  autoA = (param.asInt() == 1); // keep auto flag
  if (autoA) {
    autoAspect(); // 
  }else{
    duration = 0;
    showDuration();
  }
}

BLYNK_WRITE(V22) { // off button
  
  if (param.asInt() == 1) {
    alloff();
  }
}

BLYNK_WRITE(V23) { // test
  
  if (param.asInt() == 1) {
    animate(); // 
  }
}

BLYNK_WRITE(V30) { // update button pressed
  if (param.asInt() == 1) {
     Blynk.setProperty(V30,"label",F("Updating"));
    checkForUpdates(); // won't return if there is an update attempt
    showversion(V30); 
  }
}
/* not sure I need this, the Blynk server should have all this already and expect it to auto push it to the app
BLYNK_APP_CONNECTED() {
   showImage();
   displayVr();
   showDuration();
}
*/


//============================================================

void setup()
{
  Serial.begin(115200); 
  Blynk.begin(auth, ssid, pass); // get onto wifi network
  printWifiStatus();
 
  // set the relay pins as output
  pinMode(PHPR,OUTPUT);
  pinMode(PHPG,OUTPUT);
  pinMode(PHPY,OUTPUT);
  pinMode(PVRO1,OUTPUT);
  pinMode(PVRO2,OUTPUT);
  pinMode(PVRG1,OUTPUT);
  pinMode(PVRG2,OUTPUT);


  displayHp(); // forces display of initial Hp0 (& Vr0)
  
  Blynk.virtualWrite(V1, 1); //force app to Hp0 also
  Blynk.virtualWrite(V2, 1); //force app to Vr0 also
  Blynk.virtualWrite(V21, 0); //force auto off
  showDuration();

   // show version of firmware
  showversion(V30);
 }
 

void loop()
{
  Blynk.run();
  timer.run();
}

I suspect @Lichtsignaal may enjoy this!

1 Like