Abstract/Summary: This project uses LDRs to monitor the location of a model train and then uses WiFi SSE. (Server Send Events) to update the client when there are any changes. The sensors will also control a set of 3 aspect signals.
Possible Audience: This project will be for model train enthusiasts who are interested interested in WiFi applications with the ESP8266.
Keywords: ESP8266, SSE Server Sent Events, Asynchronous callbacks, Placeholders, Events, Server, Client, Access Point. Inheritance
Components: The design was tested using an ESP8266 Mini and smartphone to observe the response. LDRs (4 off) were used to detect the train position and LEDs simulated the signals. (4 off RED, AMBER, GREEN)
Libraries: ESPAsyncWebServer.h, ESPAsyncTCP.h and LDR_Ser_Sig3_Ser_Class.h
--------------------------------------------
I have a small layout that consists of several blocks of track where I wish to monitor the trains location. One such block is illustrated below:
The track includes 4 LDR sensors that detect the presence of a train. Two previous projects:
(i) LDR_Ser_Sig3_ser_Class that detected the train via the "train_over_sensor( ) method and controlled the corresponding signals through a RED-AMBER-GREEN sequence. This is represented with the solid down or vertical arrow.
(ii) The second project, WiFi_SSE developed a simulator that simulated a train passing over the sensors and generated a HTML WiFi output to a smart device. This is represented by the left to right horizontal arrow.
This project, LDR_ESP_WiFI_SSE will read the sensor status and use the ESP8266 to generate a WiFi signal to send the train location to a smart device.** This project is represented by the right angled arrow.
** The project will retain the ability to control the signals.
-----------------------------------------
The project firmware is illustrated in the above diagram. Previous projects have developed several classes/libraries.
(i) The status of the LDR sensors is read via the method LDR_Serial_Sensor_Class::train_over_sensor(sensor).
(ii) The signals maybe controlled using the method Signals3_Serial_Class::signal_control(signal,status)
These two methods are encapsulated into the class LDR_Ser_Sig3_Ser_Class.
A separate project WiFi_SSE simulated a model train moving across the sensors and continuously updated the WiFi HTML display to a smart device.**
This project will combine WiFi_SSE with LDR_Ser_Sig3_Ser_Class to send the actual or true status to a smart device.
** With Server Send Events the server sends an HTML update without a request from the client.
---------------------------------------------------------
The program will require several libraries. If any are not available these need to be created. For example:
In the project LDR_Ser_Sig3_Ser_Class there are 3 files with titles LDR_Ser_Sig3_Ser_Class and extensions ,cpp, .h and .ino.
1. Combine files into a .zip file.
2. In the Arduino IDE load these file. That is sketch --> include library --> add .zip library and select LDR_Ser_Sig3_Ser_Class.zip
The library may be used in the new project sketch --> include library --> LDR_Ser_Sig3_Ser_Class from pull down menu of contributed libraries
-----------------------------
1. In the Arduino IDE create a new project LDR_ESPP_WiFi_SSE.
2. Create new source code LDR_ESP_WiFi_SSE. The IDE will give it the extension *.ino.
3. Use the inverted triangle to create two more files LDR_ESP_WiFi_SSE with extensions *.cpp (implementation file) and *.h (header file).
4. Populate the starting code as shown.
The project will inherit the LDR_Ser_Sig3_Ser_Class. The header file for the definition of the new class becomes**:
The constructor implementation becomes:
In the client or application code an instance "train" of the class LDR_ESP_WiFi_SSE will be created:
The code should compile.
** This project will use the default values/pins as given in the circuit below. The full circuit ready for generating a PCB is also shown. (The PROG option to allow for two different programs was not implemented)
-------------------
All the initialisation will be performed in the begin( ) method. This must be included as a public method of the LDR_ESP_WiFi_SSE class.
In the implementation file the begin( ) will call LDR_Ser_Sig3_Ser_Class::begin(1,1,4);. In this project the default values will be used. LDR_Ser_Sig3_Ser_Class inherits the Signal3_Serial_Class where the begin parameters 1,1,4 select external NOR gate, positive logic and 4 signals.
The implementation code for LDR_ESP_WiFi_SSE::begin ( ) will be expanded as the project progresses.
The client code will become:
-----------------------------------
To add the WiFi the ssid (serial set identifier) and password^^ must be defined and the library files ESPAsyncTCP and ESPAsyncWebServer included. One objective of Cpp is to hide all the detail from the end user. In this example the end user will define the ssid and password** but how they are used will be part of the implementation file:
**In my system I propose to have 4 such units so a ssid of the form Trains1, Trans2 ... might be more appropriate.
^^ Note the password must either be nothing for an open network or 8 or more characters.
**If there are no parameters when creating an object or instance parenthesis should not be used. The compiler will treat the object as a method and an error will be flagged elsewhere in the program.
--------------------
One of the libraries added is AsyncWebServer. The next step is to create an object/instance of that class. The port is specified as port 80 which is typically used for HTML interface. The additional statements/code in the implementation file will be:
As given the code to this point handles requests from the client only. In the model train example it would be nice if the client display was updated every time the train moved to a new state. This can be achieved using the events source. The additional set code will be:
----------------------------
To connect to the network the following code should be included in the begin function.
This code needs the ssid and password as defined in the client or *.ino code. One option is to pass these parameters in the begin( ) method. That is :
void setup() {train.begin( ssid,password); }The definition of the begin( ) method must be updated/modified in the class definition. That is in the header file:
class LDR_ESP_WiFi_SSE : public LDR_Ser_Sig3_Ser_Class { public : LDR_ESP_WiFi_SSE ( ); //use default values for pins void begin(const char* ssid, const char* password ); //use default valuesUpon a request the "text/html" specified by the message array WiFi_Mess will be sent. Rather than including this as part of the main code an additional file index.h is implemented.
This file is basically HTML code.
Notes:
1. I have chosen to create a file I have called index.h. In the Arduino IDE click on the inverted triangle on the upper right and select New_Tab. Give it the label index.h and copy paste the above code. ( add "#include "index.h" to *.cpp or implementation code)
2. The HTML code is an array of characters known as WiFi_Mess [ ].
3. The directive PROGMEM tells the compiler to generate code that will read Flash ROM rather than dynamic RAM. Further at start up a copy is not placed in RAM.
4. The R" ( )" prefix tells the compiler what you see is what we want. That is there is no need for new line characters for example.
5. The line <meta name="viewport" content="width=device-width, initial-scale=1"> effectively allows the HTML display to adjust for different width devices.(Smartphone vs PC)
6. The line <link rel="icon" href="data:,"> tells the client not to request the favicon a small image that would be displayed in the tool bar.
7. The information between the <style> ...</style> indicates how the text will be displayed.
8. The text in the <BODY> section is what is displayed in the heading 2 format. .
-------------------------------
Use a smart device -
(i) Select WiFi
(ii) as required select identifier "Monitor_Trains"
(iii) give password "12345678"
(iv) The browser should receive the 'message connected to Network WiFi "Monitor Trains"'
(v) To display "Monitor_Trains" in the browser give default address 192.168.4.1.
The smart device should give a once off display similar to that shown below
----------------------------
The code to this point handles requests from the client only. In the model train example it would be nice if the client display was updated every time the train moved to a new state. This can be achieved using the events source. The additional set code will be:
This code will do nothing. The HTML code and the program loop( ) must both be modified to handle the events.
------------------------
To handle the events the following Java script is added to the HTML code between the <BODY>..</BODY> tags
Notes:
1. The (!!window.EventSource) check that the browser supports EventSource. Internet Explorer apparently does not.
2. var source = new EventSource('/events'); instantiates the EventSource object where /events is the url that generates the events.
3. The three handlers open, error and train_state bind those operations to source to deal with those message situations.
4. In the case of the 'train_state' the existing message "Monitoring Trains" will need to be modified to display the new message for example: "No trains present."
5.The script will replace the text in the HTML document enclosed by the "train" tag with the new message. The data portion of e.data where e is the short var reference for event object passed to the event handlers.
The final modification is to add the "train" tag to the default HTML message <span id="train">Monitoring Trains</span>
-------------------------------------
To send messages the folowing code can be included in the implementation file:
The method WiFi_message should be included in the class definition:
class LDR_ESP_WiFi_SSE : public LDR_Ser_Sig3_Ser_Class { public : LDR_ESP_WiFi_SSE ( ); //use default values for pins void begin(const char* ssid, const char* password ); //use default values void WiFi_message(char* mess);To test the Java Script added to the HTML code the following code can be included inside the program loop( ); to generate an event or interrupt
The client display will now change from "Monitoring Trains" to "No trains present".
The next step is to include the full suite of messages.
Explanation
The events.send will find the tag "train_state" in the HTML code and this is associated with the message "No trains present".
The message is then inserted in the placeholder specified by the tag "train"
<span id="train">Monitoring Trains</span>
The result is the smart device display will change from "Monitoring Trains" to "No trains present".
-----------------------------------------------
To allow multiple messages the header file will need to be significantly enhanced:
Notes:
(i) The wiring to the serial input chip is define in the line: #define COMING_SEN 0 etc
(ii) An enum "where_is_train" is defined listing all the possible states. Since the trains may approach for either direction there is coming0, coming1 etc.The initial state will be startup.
(iii) There is a public method monitor_trains( ) that returns where_is_train. Note for convenience this method has been given a parameter. For normal operations it should be 0. However, if set to "1" this has the practical effect that on the bench the LDR pin can be grounded to represent train over sensor.
(iv) There will be a private method train_state that will determine the current state of a train. For example coming0, arriving( ) when train is over these sensors but approaching0 when between them.
(v) There will also be a private variable the_train that gives the train state.
---------------------------------------
Notes.
(i) int so...s3 are just shorthand for sensor results. train_over_sensor(COMING_SEN) will return "1" if train is over sensor etc.
(ii) if (test), when test =1 will invert the results sensor s0 etc. That is shorting the input to ground will simulate a train present.
(iii) signal_control(0,s0) etc will control signals determined by sensor.
(iv) return enum where_is_train monitor_train() ; once train_state( ) is written will return train state. For testing return waiting; may be used.
-----------------------------
At this point train.monitor_train() will return "waiting" that message should be displayed. The "if" statement implies that only new states will be displayed.
----------------------------------------------
The state machine can be programmed into the implementation code.
It will be basically quite simple.
(i) At reset or power up it will move to the sate "waiting"
(ii) If the train can enter from both directions in the state "waiting" the firmware tests both end sensors. If s0 is detected it moves to state coming0 or if s3 is detected it moves to the state coming1
(iii) In coming state the firmware waits until the train has cleared the sensor and moves to the state "approaching"
(iv) etc
That completes the development of the firmware for the project.
------------------------------------------------
The prototype was constructed on two boards. A PCB was designed for the final product.
The first board consisted of a parallel to serial converter 74HC165 that could handle up to 8 inputs (only 4 were used). For testing on the bench LDRs were wired to the screw terminals - only one shown. In the final train layout the LDRs were mounted under the tracks and wired back to the screw terminals**
The wires out to the right go to the second board. This board contains the ESP8266 plus the 74HC02 and 74HC164 to generate the signals. For initial testing LEDs are used.
In the test environment there were changes in the amount of light that caused variable results with the LDRs so a jumper was used across the sensor terminals. This inverted the signal so the LDR_ESP_WiFi_SSE::monitor_train(uint8_t test ) method included a parameter "test" that when set returned the signal to its true value for ease of testing. For normal operation using the LDR in the final product use either monitor_train(0) or no parameter monitor_train( ).
The screen capture shows the result of a train having completely passing over the first sensor but before reaching the second. That is the state "Approach"
Some Detail:
Each loop in the *.ino code calls the method monitor_train( ).
Monitor_train( ) will read the status of the LDR sensors.
Monitor_train( ) uses the result of the sensors and calls the method train_state( ).
train_state will use the sensor information to determine the location of the train (it could be between sensors)
the state of the train is returned via monitor_train ( ) to the main program loop which sends the message to WiFi_message( ) to be transmitted over WiFi.
For the final product a PCB was developed.
The ESP8266 is located to the right of the board with the glue chips to the left and below. The top row of screw terminals are wired to the sensors while the lower row are wired to the signals.
The board is powered from the model railway tracks. On the centre left is bridge rectifier and 5V regulator.
** The design uses 4K7 ohm pull up resistors with the LDRs. This may need to be adjusted for other environments.
----------------------------------------------
For this page/project all the pins and the delay timings were left at the default values. However if desired these may be changed. This will involve selecting different options in the inherited classes. This is illustrated in the diagram below;
In the illustration the constructor LDR_ESP_WiFi_SSE has 5 parameters (int ser_in, int load, int clk_in, int ser_out, int clk_out ) that define the wiring. All 5 may be passed to constructor LDR_Ser_Sig3_Ser_Class that will become the parameters for LDR_Serial_Class and Signals3_Serial_Class. LDR_Serial_Class uses (int ser, int load, int clk) while Signals3_Serial_Class handles (int ser_in, int clk); **
The begin method may have 3 additional parameters (int ext_gate, int polarity, int no_sigs). These are all passed to the Signals3_Serial_Class::begin( ).
The LDR_Serial_Class has a method train_over_sensor( ). The parameter/argument will be the sensor of interest. This method includes a filter fixed at 1 second.
Signals3_Serial_Class will require two to four arguments. The first argument is the signal of interest and the second argument its value. The remaining two arguments are the wait times that default to 5 seconds each.
**In the project code has been presented for using all 5 parameters or none where default values are used. It will be necessary to add additional code to the libraries if 1 to 4 parameters will be used.
-----------------------------------------------------------------