I have created a phone app for controlling my model train layout…
It handles train control, special functions, real-time track views, signals, events, troubleshooting and dispatching trains… plus some admin type things too…
https://cabin-layout.mixmox.com/2019/03/mobile-phone-layout-control-app.html
#include "Cab.h" // auth token SSID, private urls etc.
// Blynk
#include <BlynkSimpleEsp8266.h>
#include "C:\ard\common\Blynkhelper.h"
// common files, do not add to sketch, they get duplicated if you do
#include "C:\ard\common\utils.h"
#include "C:\ard\common\RemoteSign.h"
#include "C:\ard\common\RemoteSign.cpp"
// hardware setup -----------------------------------------------------
// none
//--------------------------------------------------------------------------
// general constants
const int MAXSPEED = 200; //max of slider
RemoteSign rs(D4, LOW, HIGH); //create an instance of RemoteSign, using D4 as the connection status light
bool forward = true;
int speed = 0; // current speed in km/h
int dspeed = 0; // desired speed in km/h
int limit = 0; // speedlimit in km/h
int train=-1; // current train
int k83adr = 1; // k83 address
int s88 = 0; //s88 to be monitored
const String BLYNK_GREEN = "#23C48E";
const String BLYNK_RED = "#D3435C";
const String BLYNK_YELLOW = "#ED9D00";
const String BLYNK_BLUE = "#04C0F8";
const String BACKGROUND = "#212226";
// log screen
WidgetTerminal terminal(V22);
// event screen
int currentevent = -1;
// dispatch screen
int currentstation = -1;
int currentstationtrack = -1;
int currentdestination = -1;
void shows88(bool onoff, String Descrip){ // display the state of the LED
Blynk.virtualWrite(V26,onoff ? 255:25); // show the led or not
Blynk.virtualWrite(V19,Descrip); // put description into display widget
}
void ShowDisconnect() {
String SpaceString=" ";
Blynk.setProperty(V1, "labels", SpaceString); // clear trains
Blynk.virtualWrite(V1,0);
train=0;
mywriteV1(); //, sets limit, clears track image, signal, etc.
Blynk.setProperty(V30, "labels", SpaceString); // clear stations
Blynk.virtualWrite(V30,0);
ResetStation(); // resets tracks and destinations
Blynk.setProperty(V27, "labels", SpaceString); // clear events
Blynk.virtualWrite(V27,0);
setBothLabels(V40, SpaceString); // clear red button
setBothLabels(V41, SpaceString); // clear green button
s88=0;
Blynk.virtualWrite(V39,s88); // s88 address
shows88(0,SpaceString);
}
void showLimit() {
setBothLabels(V6, String(limit / 10)); // speed limit
}
void showSpeed() {
Blynk.virtualWrite(V5,String(speed) + "/" + String(dspeed));
Blynk.setProperty(V5,"color",speed > limit ? BLYNK_RED:BLYNK_GREEN);
Blynk.virtualWrite(V7,speed);
Blynk.virtualWrite(V2,MAXSPEED - dspeed);
}
void setBothLabels(int pin,String label){
Blynk.setProperty(pin,"offLabel",label);
Blynk.setProperty(pin,"onLabel",label);
}
void mywriteV1() {
if (train < 2) {
// to clear an image, set property opacity 0
Blynk.setProperty(V0,"opacity",0); //clear the track image
Blynk.setProperty(V4,"opacity",0); //clear the signal image
limit=0;
showLimit();
for (byte i=V10; i < V18;i++) { // hide functions
setBothLabels(i," ");
SetBackground(i);
}
Blynk.virtualWrite(V5," "); // speed indicator
Blynk.virtualWrite(V2,MAXSPEED); // MAXSPEED is inverted
Blynk.virtualWrite(V7,0); // current speed
}
}
BLYNK_WRITE(V1) { // Train selection
train = param.asInt() ; // note: there is a <none>=1
mywriteV1();
rs.sendCabData('T',train);
} // V1
BLYNK_WRITE(V2) { // speed control
dspeed = MAXSPEED - param.asInt() ;
rs.sendCabData('G',dspeed);
showSpeed();
} // V2
BLYNK_WRITE(V3) { // direction control
int dir = param.asInt();
forward = 1 == dir ;
speed = 0;
rs.sendCabData('D',dir);
showSpeed();
} // V3
void showPower(byte p) {
switch (p) {
case 1: {Blynk.setProperty(V21,"color", BLYNK_RED );Blynk.setProperty(V38,"offBackColor", BLYNK_RED );Blynk.virtualWrite(V38,0 ); break;} // off
case 2: {Blynk.setProperty(V21,"color", BLYNK_GREEN );Blynk.setProperty(V38,"onBackColor", BLYNK_GREEN );Blynk.virtualWrite(V38,1 ); break;} // on
case 3: {Blynk.setProperty(V21,"color", BLYNK_YELLOW );Blynk.setProperty(V38,"offBackColor", BLYNK_YELLOW );Blynk.virtualWrite(V38,0 ); break;} // halt
} // power mode
Blynk.virtualWrite(V21,p ); // ensure segment switch is aso set
}
BLYNK_WRITE(V8) { // acceleration
int x = param[0].asFloat();
int y = param[1].asFloat();
// int z = param[2].asFloat(); // z always > 9 on my phone
const byte alimit = 15;
if (x > alimit || y > alimit) { // violent shake, shut off power!
rs.sendCabData('P',0);
Blynk.virtualWrite(V21,1); // update the segmented switch
showPower(1); // set the colors of the switch
//Serial.println(F("STOP"));
}
}
BLYNK_WRITE(V9) { // request log data
int mode = param.asInt() ;
if (mode == 1) { // when pressed only
rs.RScommand("{LOG}25");
}
}
BLYNK_WRITE(V18) { // refresh track list
int mode = param.asInt() ;
if (mode == 1) { // when pressed only
rs.RScommand("{LST?}" + String(currentstation));
}
}
BLYNK_WRITE(V20) { // update button
}
BLYNK_WRITE(V21) { // power mode
int mode = param.asInt() ;
rs.sendCabData('P',mode - 1);
showPower(mode);
} // V21
/*
BLYNK_WRITE(V22) { // log
} //22
*/
void mywriteV23(){
// clear existing labels
showK83(1,"red","green"); // default value before getting data from Bw
rs.RScommand("{K83?}" + String(k83adr)); // ask for the data
}
BLYNK_WRITE(V23) { // k83 address
k83adr = param.asInt() ;
mywriteV23();
} // 23
void showK83(byte rg, String Red, String Green) { // show the colors according to the rg (redgreen) value
// dont change labels if red/green are empty
const int rpin = V40;
const int gpin = V41;
static String lastRed;
static String lastGreen;
if ( Red != "") {lastRed = Red; Blynk.setProperty(rpin,"onLabel",lastRed);Blynk.setProperty(rpin,"offLabel",lastRed);} ;
if (Green != "") {lastGreen = Green; Blynk.setProperty(gpin,"onLabel",lastGreen);Blynk.setProperty(gpin,"offLabel",lastGreen);} ;
if (rg ==1) {
Blynk.virtualWrite(rpin,rg );Blynk.virtualWrite(gpin,0 ); // red
}else{
Blynk.virtualWrite(rpin,0 );Blynk.virtualWrite(gpin,1 ); // green
} // red/green mode
} // showK83
BLYNK_WRITE(V24) { // fine adjustment to k83 address
k83adr = k83adr + param.asInt() ;
Blynk.virtualWrite(V23,k83adr);
mywriteV23();
} // 24
BLYNK_WRITE(V25) { // s88 fine adjustment
s88 = s88 + param.asInt() ;
Blynk.virtualWrite(V39,s88);
mywriteV39();
} // 25
BLYNK_WRITE(V27) { // event list
currentevent = param.asInt() ;
}
BLYNK_WRITE(V28) { // execute event
int press = param.asInt() ; // assigning incoming value
if (press == 1 && currentevent != -1) {
rs.RScommand("{CAB}E\21" + String(currentevent));
}
}
BLYNK_WRITE(V29) { // Network status
int ask = param.asInt() ; // assigning incoming value
if (ask == 1) {
showNetworkStatus(V29);
}
} // V29
void ResetStation() { // reset the track and destination controls, also called when we disconnect
String SpaceString = " ";
Blynk.setProperty(V31,"labels",SpaceString); // clear track list
Blynk.virtualWrite(V31,0);
Blynk.setProperty(V32, "labels", SpaceString); // clear destination list
Blynk.virtualWrite(V32,0);
Blynk.setProperty(V33, "offLabel", SpaceString);
Blynk.setProperty(V42, "offLabel", SpaceString); // train to cab button
Blynk.setProperty(V18, "offLabel", SpaceString); // refresh button
}
BLYNK_WRITE(V30) { // station
currentstation = param.asInt() ;
currentstationtrack = -1;
currentdestination = -1;
ResetStation();
rs.RScommand("{LST?}" + String(currentstation)); // get list of tracks and trains on them
}
BLYNK_WRITE(V31) { // station track
currentstationtrack = param.asInt() ;
currentdestination = -1;
Blynk.setProperty(V32, "labels", ""); // clear destination list
Blynk.virtualWrite(V32,0);
Blynk.setProperty(V33, "offLabel", " ");
Blynk.setProperty(V18, "offLabel", "♻️"); // ♻️ refresh button
Blynk.setProperty(V42, "offLabel", "🚇"); //🚇 put train into cab button
rs.RScommand("{LSTD?}" + String(currentstation) + "\21" + String(currentstationtrack)); // get list of destinations
}
BLYNK_WRITE(V32) { // station track destination
currentdestination = param.asInt() ;
Blynk.setProperty(V33, "offLabel", "➡️");
}
BLYNK_WRITE(V33) { // dispatch
int press = param.asInt() ;
if (press == 1 && currentdestination > 0 ) {
rs.RScommand("{CAB}d\21" + String(currentstation) + "\21" + String(currentstationtrack) + "\21" + String(currentdestination));
}
}
BLYNK_WRITE(V34) { // Ignore s88s
int mode = param.asInt() ;
rs.sendCabData('8',mode);
} // V34
BLYNK_WRITE(V35) { // manual control
int mode = param.asInt() ;
rs.sendCabData('M',mode);
} // V35
BLYNK_WRITE(V37) { // stop train
int mode = param.asInt() ;
if (mode == 1) { // only do anything on the initial press
rs.sendCabData('S',0);
} // 1
}
BLYNK_WRITE(V38) { // cab tab power button
int mode = param.asInt() ;
rs.sendCabData('P',mode);
showPower(mode+ 1);
}
void mywriteV39(){
rs.RScommand("{S88?}" + String(s88)); // ask for the data
}
BLYNK_WRITE(V39) { // s88 slider
s88 = param.asInt() ;
mywriteV39();
}
BLYNK_WRITE(V40) { // k83 - red
int press = param.asInt() ;
if (press == 1) {
rs.RScommand("{CAB}k" + sDC1 + String(k83adr) + sDC1 + "R");
mywriteV23();
}
}
BLYNK_WRITE(V41) { // k83 - green
int press = param.asInt() ;
if (press == 1) {
rs.RScommand("{CAB}k" + sDC1 + String(k83adr) + sDC1 + "G");
mywriteV23();
}
}
BLYNK_WRITE(V42) { // get track train into cab
int press = param.asInt() ;
if (press == 1 && currentstationtrack > 0 ) {
rs.RScommand("{CAB}d\21" + String(currentstation) + "\21" + String(currentstationtrack));
}
}
BLYNK_WRITE_DEFAULT() { // function keys
int pin = request.pin - 10;
if (pin > 7 || pin < 0) return; // we only support f0 - f7
int mode = param.asInt() ;
rs.sendCabData('n',pin, mode);
} // fn buttons
void SetBackground(int pin) {
Blynk.setProperty(pin, "onBackColor", BACKGROUND);
Blynk.setProperty(pin, "offBackColor", BACKGROUND);
}
void setup() {
Serial.begin(115200);
Blynk.begin(auth, ssid, password); // get onto wifi network with Blynk
printWifiStatus();
Blynk.virtualWrite(V36, " "); // clear connection info
ShowDisconnect();
// set default k83 slider etc
Blynk.virtualWrite(V23,k83adr);
mywriteV23();
// set default s88
Blynk.virtualWrite(V39,s88);
mywriteV39();
/*
// set up button colors - only need to be run once
SetBackground(V3);
SetBackground(V37);
SetBackground(V38);
SetBackground(V29);
SetBackground(V28);
SetBackground(V18);
SetBackground(V33);
SetBackground(V42);
*/
// show version of firmware
showversion(V20);
showNetworkStatus(V29); // and network status
// set up RemoteSign
rs.setFirmwareVersion(String(FW_VERSION));
// define channel data
rs.setChannelData(1, "CAB", "Phone Cab"); // cab control
// rs.setBlynkPin(1, V1) ; // Train picker widget
rs.begin(); // start server listening
} // setup
void loop() {
rs.run();
Blynk.run();
} // loop()
void BlynkVWriteInt_helper(uint8_t pin, uint8_t value) {
Blynk.virtualWrite(pin, value);
if (pin = 1) {
train = value;
mywriteV1();
rs.sendCabData('T',train);
};
}
void BlynkVWriteStr_helper(uint8_t pin, String text) { // char const *pchar) {
if (pin == V36) { // disconnect notice
train= -1;
Blynk.virtualWrite(pin, 1); // select the <none> train
}
Blynk.virtualWrite(pin, text);
}
void BlynkCabData_helper(uint8_t type, String thedata) {
//Serial.println(thedata);
switch (type) {
case 1:
thedata = "<none>" + sDC2 + thedata;
// train list fallthrough
case 17:
case 18:
case 19:
case 2: { // event list
int r=0;
int i;
BlynkParamAllocated items(1023); // list length, in bytes
for (i=0; i < thedata.length(); i++) {
if(thedata.charAt(i) == '\x12') { // x12 is hex 18 DC2
items.add(thedata.substring(r, i));
r= i + 1;
} // DC1
} // for
items.add(thedata.substring(r, i)); // get the last item also
// put the array into the virtual pin
switch (type) {
case 1 : { Blynk.setProperty(V1, "labels", items); break; } // train list
case 2 : { Blynk.setProperty(V27, "labels", items); break; } // event data
case 17: { Blynk.setProperty(V30, "labels", items); break; } // station list
case 18: { Blynk.setProperty(V31, "labels", items); break; } // stationtrack list
case 19: { Blynk.setProperty(V32, "labels", items); break; } // destination list
} // switch train/events
break;
} // 1 & 2
case 3 : { // function list
// function names must come in F0 through f7
String label;
byte vpin = V10;
int r=0;
int i;
for (i=0; i < thedata.length(); i++) {
if(thedata.charAt(i) == '\x12') { // x12 is hex 18 DC2
label = thedata.substring(r, i);
if (label =="") label = " "; // treat nuls as blank
if (label == " ") { // no function
SetBackground(vpin); // blank it
}else{
Blynk.setProperty(vpin,"onBackColor",BLYNK_BLUE);
Blynk.setProperty(vpin,"offBackColor",BLYNK_GREEN);
}
setBothLabels(vpin++,label);
r= i + 1;
} // DC2
} // for
label = thedata.substring(r, i); // and the last one
if (label =="") label = " "; // treat nuls as blank
if (label == " ") { // no function
SetBackground(vpin); // blank it
}else{
Blynk.setProperty(vpin,"onBackColor",BLYNK_BLUE);
Blynk.setProperty(vpin,"offBackColor",BLYNK_GREEN);
}
setBothLabels(vpin++ , label);
// fill unspecified functions empty
label = " ";
for (i=vpin; i < 18; i++) {
SetBackground(vpin);
setBothLabels(vpin++, label);
}
break;
} // function names
case 4 : { // function states
// function states must come in F0 through f7
String label;
byte vpin = V10;
int r=0;
int i;
byte state;
for (i=0; i < thedata.length(); i++) {
if(thedata.charAt(i) == '\x12') { // x12 is hex 18 DC2
label = thedata.substring(r, i);
state = label.toInt();
Blynk.virtualWrite(vpin++, state);
r= i + 1;
} // DC2
} // for
label = thedata.substring(r, i); // and the last one
state = label.toInt();
Blynk.virtualWrite(vpin, state);
break;
} // function
case 5 : { // image file
if (thedata == "" ) {
Blynk.setProperty(V0,"opacity",0); //clear the track image
}else{
Blynk.setProperty(V0, "urls",imagebase + thedata );
Blynk.setProperty(V0,"opacity",100);
}
break;
}
case 6 : { // signal aspect
if (thedata == "" ) {
Blynk.setProperty(V4,"opacity",0); //clear the track image
}else{
Blynk.setProperty(V4, "urls",signalbase + thedata );
Blynk.setProperty(V4,"opacity",100);
}
break;
}
case 7 : { // speed limit
limit = thedata.toInt();
showLimit();
break;
}
case 8 : { // speed and desired speed
String s = rs.getValue(thedata, DC1, 0) ;
speed= s.toInt();
s = rs.getValue(thedata, DC2, 1) ;
dspeed= s.toInt();
showSpeed();
break;
}
case 9 : { // direction
uint8_t dir = thedata.toInt();
Blynk.virtualWrite(V3, dir);
break;
}
case 10 : { // Power settings
uint8_t p = thedata.toInt() ; // 0 = off 1 = on 2 = halt
Blynk.virtualWrite(V21, ++p ); // 1 = off 2 = on 3 = halt
showPower(p);
break;
}
case 11 : { // Ignore s88s
uint8_t p = thedata.toInt() ; // 0 = off 1 = on
Blynk.virtualWrite(V34, p );
break;
}
case 12 : { // Manual control
// uint8_t p = thedata.toInt() ; // 0 = off 1 = on
Blynk.virtualWrite(V35, 0 ); //always turning off
break;
}
case 13 : { // update one function
String s = rs.getValue(thedata, DC1, 0) ;
int f = s.toInt(); // funtion number
s = rs.getValue(thedata, DC1, 1) ;
int v = s.toInt(); // new value
Blynk.virtualWrite(f + 10, v); // f0 uses V10 so we add 10
break;
}
case 14 : { // shows RemoteSign version
Blynk.virtualWrite(V36,rs.getValue(thedata, DC1, 0));
if (thedata == "Disconnected") {
// reset things to indicate no connection
ShowDisconnect();
}else{
if (train > 0) {
rs.sendCabData('T',train); // refresh current train
}
}
break;
}
case 15 : { // LOG data
terminal.println(thedata);
//terminal.flush(); // do this later perhaps using a timer?
break;
}
case 16 : { // flush LOG data. Called when Bw has finished sending log data
terminal.flush();
break;
}
// 17 is station list (handled above)
// 18 is stationtrack list (handled above)
// 19 is destination list (handled above)
case 20 : { // K83 address state and description thedata 0 = R/G 1 = red name 2 = green name
byte rg;
if (rs.getValue(thedata, DC1, 0) == "R") {
rg=1; // red
}else{
rg=2; // green
}
showK83(rg,rs.getValue(thedata, DC1, 1),rs.getValue(thedata, DC1, 2));
break;
}
case 21 : { // s88 monitor status - we get state and description
String param = rs.getValue(thedata, DC1, 0);
shows88(param.toInt()==1,rs.getValue(thedata, DC1, 1));
break;
}
default :
Serial.print(F("Unknown type for BlynkCabData_helper "));Serial.println(type);
} // switch type
} //BlynkCabData_helper
void executeEvent(int channel,String param) {
} // executeEvent
void showversion(int pin){
Blynk.setProperty(pin,"label","Ver " + String(FW_VERSION));
}
void showNetworkStatus(int pin){
long rssi = WiFi.RSSI();
String IP = WiFi.localIP().toString();
Blynk.setProperty(pin,"label",IP); // put IP address onto Network button
Blynk.setProperty(pin,"offLabel","📶 " + String(rssi) + "dBm");
}