-
Notifications
You must be signed in to change notification settings - Fork 0
[Examples] Development Documentation
In this section, project examples are described and explained. The examples shown here contain larger, more complex files which were developed specifically for the TDC-E device. The following examples are described:
- MQTT Node-RED GPS Application
- MySql Go DIO Application
- MQTT MySql Go GPS Modem Application
- Reboot Node-RED DIO Application
This application uses Node-RED to set up a connection to a MQTT broker and fetches GPS data via a dedicated websocket continuously to write the data to the TDC-E's internal memory.
To run the application successfully, the following interfaces and services need to be set up:
- GPS
- MQTT
If help is needed for setting up GPS, refer to the TDC-E Interface Configuration for GPS. The MQTT broker in the application is not set, as an MQTT server is needed. The server can easily be set up inside the Node-RED application by clicking on the mqtt
node and adding a new broker address.
The application that writes messages into a file, sends the message to an MQTT broker and gets GPS data is made with the following nodes:
websocket in
function
debug
input
write file
mqtt out
A screenshot of the application design is provided below:
The programs listens to two inputs: those are a websocket out
node that continuously fetches GPS data and sets the flow's GPS variable to the current value, and an input
node that listens on port 45000
for buffer input.
The add-gps-timestamp
node then parses the data into a readable and functional format, and the object created by the function is written into the debug screen on the right panel of the screen. The object is optionally sent to the mqtt
broker, whose parameters should be set by the user of the application. The MQTT broker address
and QoS
should be set.
NOTE: Node-RED does not process QoS 2 and QoS 3 and additional functionalities need to be implemented to that effect.
The LED DIO application is an application that can read and write sensor status by checking a websocket that provides necessary information for DIO data. The application uses a TDC-E device, a sensor, and a LED, and utilizes websockets and a MySql database for its running. When the sensor senses an object in its field, the application waits for the duration of which the object was in the field, then turns on the connected LED which needs to glow a total of seconds that the object was in the field. If another object enters its field during the glowing, the application adds the duration of which the another object was in the field to the current duration time, keeping the LED light on for additional X seconds of time. After the LED stops glowing, the object is written into the MySql database.
To run the application successfully, the following interfaces and services need to be set up:
- DIO
- MySql database
- LED
The LED needed for the application is connected with the Digital Input Output (DIO) interface, so DIO setup is required before turning on the application. If help for DIO setup is needed, one can refer to the DIO section of the TDC-E Interface Configuration.
A MySql database and table are used to store all sensor data. The following data is measured and stored into the diobase database and into the dios
table:
id
- identification number of the object
duration
- duration of time the object is in the sensor's field of view
whattime
- timestamp of occurrence
For help on how to set up the MySQL service, please refer to the MySql Environment Setup.
The DIO LED detection program for Go is implemented by creating three separate packages which are interconnected and work together to achieve the desired functionalities. This section describes the program implementation by separating the section into subsections that explain the code logic behind the created packages. Additionally, a bash file called diosend.bat will be described.
The main package imports the following modules:
bytes
database/sql
dioled/webapi
encoding/json
fmt
net/http
net/url
sync
time
The program is started in the main()
function which operates by creating two wait groups, used for waiting for launched goroutines. Goroutines are lightweight threads which are managed by the Go runtime and must be synchronized since they use the same address space.
Two routines are started simultaneously:
- a goroutine running the REST API
- a goroutine running the main program
The main part of the program is located in the main.go file and continuously fetches the current state of the sensor. This is done by sending an authenticated request to a server, using an http client
from net/http
.
func getDio(token, url string) Dio {
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer "+token)
resp, _ := client.Do(req)
var dio Dio
json.NewDecoder(resp.Body).Decode(&dio)
return dio
}
The client is set to create a new default http.Client{}
struct and takes the address of the struct. A new HTTP Request
and uses the GET
method to send a request to the URL of the service, adds the authorization token, then waits for the response using client.Do
. A new Dio variable is created and a JSON decoder is used to decode the response body and set it to the variable before returning it.
If the sensor is on (an object enters the sensor's field), and time has not started counting yet, the program begins counting time. Then, once the sensor is off, time is stopped and the program calculates time elapsed between the sensor registering the object and the object leaving its field. A lock from sync
is used to assure that different routines do not access the total glowing time variable at the same time, the elapsed time is appended to the glowing time, and the resource is then unlocked.
// lock so only one routine may update the value
totalGlowingTimeLock.Lock()
totalGlowingTime += elapsed
totalGlowingTimeLock.Unlock()
Afterwards, the LED starts glowing, which is done by sending a JSON string to the server via POST
method.
Remaining glow time is calculated and the routine sleeps for a millisecond to ensure the process is updates as soon as possible. If the total glowing time is larger than 0, the total duration of LED glowing time is inserted into the database, ensuring the variable is locked once again as it is reset to 0.
To insert into the database, the database is prepared for connections with the following line of code:
db, err := sql.Open("mysql", "root:TDC_arch2023@tcp(192.168.0.100:3306)/diobase")
An SQL query is created and the routine is completed.
The other goroutine that is started is running a REST API Web Service. To run, it imports the following modules:
database/sql
fmt
log
net/http
dioeld/middleware
github.com/gin-gonic/gin
github.com/gin-contrib/cors
The REST API is created using the gin
module, which sets up a default router, sets up a GET
website on which the values will be presented, and fetches all DIO objects from the diobase
database.
Additionally, the service uses cors
which currently enables communication with the "http://localhost:8201"
and "http://192.168.0.100:8201"
origin. If other origins should be able to communicate with the program, add them to the slice.
config := cors.DefaultConfig()
config.AllowOrigins = []string{"http://192.168.0.100:8201", "http://localhost:8201"}
outer.Use(cors.New(config))
As an added security measure, an IP white list was implemented through the middleware package. When any whitelisted page runs on port 6001, the application shows the fetched data in JSON format from the database.
The package is accessed through the InitializeWebAPI()
function which is written with a capital first letter to be accessible by other packages. All other functions are private.
To see the REST Web API data, go to http://192.168.0.100:6001/api/v1/detection when running the application. To see structured data on a graphical user interface (GUI), go to the gui
folder, then click on index.html
to show a simple HTML page that provides data it fetches from the REST API.
The middleware is a small package that uses the following modules:
net/http
github.com/gin-gonic/gin
It consists of a single function called IPWhiteList()
which takes in a map of trusted web addresses, and if the IP address that is trying to access the server isn't one of the whitelisted addresses, the program aborts the process and exits with a forbidden status.
This way, only IP addresses that are written to be whitelisted can view the data. The current implementation lists the following IPs as whitelisted:
var IPWhitelist = map[string]bool{
"127.0.0.1": true,
"192.168.0.100": true,
}
To add another IP address to the list, write it down into the dictionary. The dictionary is located in webapi.go.
This is the documentation section for the MQTT MySql Go GPS Modem Application for the TDC-E device. In it, the usage of the program is described, an environment setup description is provided and implementation is discussed.
The application tracks the current GPS location of the TDC-E, fetching the coordinates of the vehicles every X seconds, then publishing this data in a message to a MQTT broker with Quality of Service (QoS) 2. Since the TDC-E device's connection to remote server can be blocked at any time during travel, the MQTT broker ensures that the data is safely stored for some period of time as it resends the unsuccessfully published data to the broker after getting a connection. The storage size and number of unpublished messages of the MQTT buffer depends on the client device and user's configurations, but any incoming unpublished messages after the buffer is filled will override older unpublished messages. To bypass this problem, a MySql database is added into which all unpublished messages after 5 minutes are written.
When the device gets back online, the broker will resend the data in the buffer automatically, before publishing all data that is stored in the MySql database.
To correctly run the applications described below, an initial setup of the working environment is needed. The following interfaces and application setup are needed to run the programs:
- GPS
- MQTT
- MySql Database
Regarding GPS, a GPS antenna needs to be set up, enabled and connected to the TDC-E. An MQTT broker is needed to be able to store the messages that are published to it. For modem and gps data, websocket and REST API data respectively is needed. The MySql database structure and a link to its setup is described in further detail below. If help is needed for setting up GPS, refer to the TDC-E Interface Configuration for GPS. For help on how to set up the MySQL service, please refer to the MySql Environment Setup.
The MySql database used in the application has the following structure:
The Id
field is the identifier of the MQTT message object and is used to identify a specific database object, as the messages are deleted from the database using this identifier. It is a positive integer
which is auto incremented with each new value.
The Altitude
column is a float
value that signifies the altitude of the object that the gps
fetches. The Course
attribute is a variable string character
size 45 (varchar(45)
). Fix
points to the strength of the GPS object and can be the value od 0
, 1
, or 2
, with 1 being the strongest, two meaning the object has a minimum of 3 satellites, and 0
being no satellites present or picked up. It is an integer
and also part of the GPS object. GpsFixAvailable
is a tinyint(1)
type, which translates to boolean
. It tells whether Fix
is available. Hdop
, Latitude
and Lognitude
are GPS object attributes and are the float
type. The NumberOfSatellites
attribute tells how many satellites are available from the current object position and is the type integer
. SpeedKnots
and SpeedMph
are GPS object attributes of the float
type and signify speed. The TimeSt
is also part of the GPS object and tells the timestamp the object is returned.
Rssi
is an attribute that is fetched from the modem data object and shows the strength of the signal. It is an integer
. DataLinkType
is a varchar(45)
attribute that tells the link type of the modem used. Lastly, a Rfid
attribute of the varchar(45)
type is provided. This column represents the Radio Frequency Identification which the program will be getting from a client device.
To create this database, the following code was used:
CREATE TABLE mqtt (
Id int PRIMARY KEY AUTO_INCREMENT,
Altitude float,
Course varchar(45),
Fix int,
GpsFixAvailable bool,
Hdop float,
Latitude float,
Longitude float,
NumberOfSatellites int,
SpeedKnots float,
SpeedMph float,
TimeSt varchar(45),
Rssi integer,
DataLinkType varchar(45),
Rfid varchar(45)
);
The application encompasses publishing messages continuously to a MQTT broker, getting websocket GPS data, getting REST API modem data, storing unpublished MQTT messages into a database and deleting them from the database once a connection is reestablished. For that end, an application in the programming language Go was created and will be described in detail.
The program is comprised of the following .go
files:
-
main.go
- the main package of the application -
db.go
- a package for connecting, modifying and selecting values from amysql
database; the data table used is of a specific structure so if changes are necessary, this package will have to be modified -
mqttset.go
- a package for creating a client, connecting to a MQTT broker, publishing messages to said broker and subscribing to a topic -
request.go
- a package for working with OAuth2.0 authorization and requests; expanded to fetch modem data -
websockets.go
- a package for creating a connection to, listening on, and writing to a websocket
The Go application also uses a params.json
file which contains OAuth2.0 configuration data used to fetch the authorization token for accessing modem data. This file should not be removed as the program fetches all needed configuration data from it.
In this section, the application logic will be described. We will start with explaining what the main.go
package does, and will be describing other packages as their methods are called in the app.
Firstly, all necessary global parameters are set and a connection to the database is opened. This is done by providing a database management system (in this case mysql
) and a connection string of the following structure:
root:TDC_arch2023@tcp(192.168.0.100:3306)/gpsmqtt
The connection string tells us the following information:
- the user is
root
- the password for the user is
TDC_arch2023
- a
tcp
connection is used - the address
192.168.0.100
is accessed - the port for the service is
3306
(which is the default MySql port) - the database that is used is called
gpsmqtt
The application connects to the database using the following code:
func Connect(dbms string, connectionString string) *sql.DB {
db, err := sql.Open(dbms, connectionString)
if err != nil {
fmt.Println("Couldn't connect to SQL database: ", err)
}
return db
}
Then, defer conn.Close()
is used to close this connection once all operations with the connection are done. The program then creates a client using defined parameters, then connects to the MQTT broker using the following code:
func ConnectClientToBroker(client mqtt.Client) {
if token := client.Connect(); token.Wait() && token.Error() != nil {
fmt.Println("Error connecting to broker: ", token.Error())
os.Exit(1)
}
}
This function checks whether the client connects to the broker. If there is an error with the connection, the function will print out an error and exit the application. The application needs to connect to the broker first to be able to access the broker, as it stays connected even when publishing isn't possible.
It is also important to note that the configuration of the client
needs to be correct so the client can connect to the broker. A correct brokerAddress
thus needs to be provided. The application also sets up a clientId
and username
for the broker to be able to identify which machine is sending the data.
Next up, two timers are created. One is a messageTimer
. This timer is set to time 5 minutes and is tied to message publishing. If no message is published for 5 minutes, this timer will indicate that the client can't connect to the broker at the moment, and will start writing the incoming messages to the database. If a message is published, the timer resets. The other timer is called dbTimer
. This timer is set to time 30 minutes. Each 30 minutes, this service checks whether anything is stored inside the database, then tries to publish the stored messages after converting them into the correct format. After every check, the timer is reset so that the process can begin anew.
Next up, a sync.WaitGroup
is set up, and 4 goroutines or lightweight threads are created to run simultaneously. They are added to the WaitGroup
, and the services wait until each is finished before the program exits. The next sections describe the goroutines in greater detail.
To fetch the modem data of the TDC-E device, a REST API call to the device needs to be made. An OAuth2.0 authorized call needs to be made to get the requested parameters, since it is a call to the TDC-E's Swagger page. The token that is generated with an API call, however, expires every 60 minutes, so a goroutine for periodically fetching the token data was created.
func setToken() {
for {
token = o2.Authorize()
/* Sleep for 59 minutes before reset */
if token != "" {
time.Sleep(59 * time.Minute)
}
}
}
This is the structure of the code for fetching the token. The token is generated with the method Authorize()
which will be described in further detail in a latter section, and if the token isn't an empty string, which happens when the application cannot fetch the token, the goroutine is made to sleep for 59 minutes. This is done so that the program tries to fetch the new token continuously when the website cannot be accessed, as waiting for an hour without access to a token could prove problematic.
The Authorize()
function firstly creates OAuth2.0. configuration data by opening the aforementioned params.json
file. This is done like so:
func createOAuth2Config() (oauth2.Config, url.Values) {
jsonFile, err := os.Open("params.json")
if err != nil {
fmt.Println("Error opening config file: ", err)
}
defer jsonFile.Close()
return setConfValues(jsonFile)
}
The setConfValues()
method reads the jsonFile
and sets the following parameters:
cfg := oauth2.Config{
ClientID: conf.ClientId,
ClientSecret: conf.ClientSecret,
Endpoint: oauth2.Endpoint{
TokenURL: conf.TokenEndpoint,
},
}
username := "XXX"
password := "XXX"
NOTE: The parameters set in params.json
should be changed for access to the values to work. A valid username and password is needed.
After fetching the configuration data, a new httpClient
is made, which makes a POST
request to the TokenURL
defined in params.json
. The needed headers are set, and so is basic authentication. Then the client processes the response, and if the status code is 200
, the application continues by reading the response body and mapping the access_token
string to a local variable which is then returned to the main program. Find the code for the implementation of this functionality below:
func Authorize() string {
cfg, data := createOAuth2Config()
httpClient := &http.Client{}
req, err := http.NewRequest("POST", cfg.Endpoint.TokenURL, strings.NewReader(data.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(cfg.ClientID, cfg.ClientSecret)
resp, err := httpClient.Do(req)
if err != nil {
fmt.Println("Error making request:", err)
return ""
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Println("Request failed with status code:", resp.Status)
return ""
}
response, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
return ""
}
var responseMap map[string]interface{}
if err := json.Unmarshal(response, &responseMap); err != nil {
fmt.Println("Error decoding JSON response: ", err)
return ""
}
accessToken, ok := responseMap["access_token"].(string)
if !ok {
fmt.Println("Access token not found in response")
return ""
}
return accessToken
}
As has already been stated, if the request is made successfully and the token is set, the program sleeps for 59 minutes before generating a token anew, as it expires after 60 minutes. If the access token can't be fetched and the program returns an empty string, the application keeps trying to fetch the token.
To fetch the GPS data needed for generating a publishable message, two GPS variables are created:
-
currentGps
- the currently fetched GPS object -
lastBestGps
- the latest GPS object with a positivefix
value
Both objects are structured in the same way:
type gps struct {
Altitude float32 `json:"Altitude"`
Course *string `json:"Course"`
Fix int `json:"Fix"`
GpsFixAvailable bool `json:"GpsFixAvailable"`
Hdop float32 `json:"Hdop"`
Latitude float32 `json:"Latitude"`
Longitude float32 `json:"Longitude"`
NumberOfSatellites int `json:"NumberOfSatellites"`
SpeedKnots float32 `json:"SpeedKnots"`
SpeedMph float32 `json:"SpeedMph"`
Time string `json:"Time"`
}
The lastBestGps
variable is used when the currentGPS
value has a worse value than the lastBestGps
so the GPS object parameters are as precise as possible.
These objects are fetched through a websocket call. find the implementation of this part of the application below:
func fetchGpsData() {
conn, _ := ws.OpenWebsocket("ws", "192.168.0.100:31768", "/ws/tdce/gps/data")
defer conn.Close()
for {
currentGps = getWsData(conn)
/* If the fix of the currently fetched gps object is equal to or better than the value in lastBestGps, set lastBestGps to this current value */
// 0 - no fix, 1 - best fix, 2 - ok fix
if currentGps.Fix <= lastBestGps.Fix && currentGps.Fix != 0 {
lastBestGps = currentGps
}
}
}
This function opens a websocket on the address ws:192.168.0.100:31768/ws/tdce/gps/data
, then defers its closing. The function getWsData()
calls websockets.go
's ListenOnWs()
, which fetches the current data sent to the websocket. This function looks the following:
func ListenOnWS(conn *websocket.Conn) ([]byte, error) {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("Error reading message: ", err)
return nil, err
}
/* returns bytes from the websocket */
return message, nil
}
The function listens on the websocket connection object, reads the latest message and returns it in []byte
format. It is then mapped to a variable of the previously shown gps
struct type, before it is returned to the fetchGpsData()
function, setting the currentGps
as the one that was fetched, and checking whether the lastBestGps
should be updated. In this case, the value is updated if the following logical expression is true:
currentGps.Fix <= lastBestGps.Fix && currentGps.Fix != 0
This is done so since 0
means no GPS fix is available, and since 2
is the strongest GPS fix.
To simulate a TCP client, a small application for sending messages to a TCP server was created. This application can be found under the name main.go
inside the client-simulation
folder.
This file connects to a TCP
connection, set to localhost:5247
, using the net
package, and sends it data that is set to "Hello, there!"
. This message is then sent to the server by writing data to the set-up connection.
To fetch the RFID
mentioned previously in the document, a TCP server is created. This server listens to any incoming messages, and upon receiving a message, creates a message to be published to the MQTT broker, setting all necessary data.
The goroutine responsible for serving calls the following function:
func serveTcp() {
listener, err := net.Listen("tcp", "localhost:5247")
if err != nil {
fmt.Println("Error:", err)
return
}
defer listener.Close()
fmt.Println("Server is listening on port 5247")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error:", err)
continue
}
go handleClient(conn)
}
}
This function creates a listener
variable which is set to listen on localhost:5247
via TCP protocol. The closing of the connection is deferred so that the application only cleans the listener once all needed operations are done. It implements an infinite loop which accepts incoming messages, printing errors if there are any and handling clients.
func handleClient(conn net.Conn) {
defer conn.Close()
buffer := make([]byte, 1024)
clientDisconnected := false
for !clientDisconnected {
n, err := conn.Read(buffer)
if err != nil {
if err == io.EOF {
clientDisconnected = true
} else {
fmt.Println("Error:", err)
}
}
if n > 0 {
fmt.Printf("Received: %s\n", buffer[:n])
rfid = string(buffer[:n])
fetchModemData()
msge, err := createMessage(rfid)
check(err)
publishToMqtt(msge)
}
}
}
This is the function that is called once a client sends a message to the TCP server. For as long as the client doesn't disconnect, it reads the buffer from the client, and if it isn't empty, it stores the sent message as rfid
. Then, modem data is fetched.
This is done by making a ROPC request to the Swagger URL responsible for returning full modem data. The returned JSON object should be of the following structure:
type modemFull struct {
GsmRegistrationStatus string `json:"gsmRegistrationStatus"`
UtranRegistrationStatus string `json:"utranRegistrationStatus"`
AccessTechnology string `json:"accessTechnology"`
DataLinkType string `json:"dataLinkType"`
OperatorName string `json:"operatorName"`
SimStatus string `json:"simStatus"`
LatestError string `json:"latestError"`
Imei string `json:"imei"`
Ccid string `json:"ccid"`
Imsi string `json:"imsi"`
Rssi int `json:"rssi"`
Ip string `json:"ip"`
Gateway string `json:"gateway"`
Dns1 string `json:"dns1"`
Dns2 string `json:"dns2"`
LocalIp string `json:"localIp"`
RemoteIp string `json:"remoteIp"`
Segment string `json:"segment"`
SegmentType string `json:"segmentType"`
Persist bool `json:"persist"`
Name string `json:"name"`
Type string `json:"type"`
Enabled bool `json:"enabled"`
State string `json:"state"`
}
For now, the modem data that is displayed in the published message is only DataLinkType
and Rssi
, but the program can easily be modified to store additional data about the modem. This request is made by sending the needed access token fetched beforehand and the URL of the service to the getModemData
function. Find the implementation of this function below.
func getModemData(accessToken string, urlConn string) ([]byte, error) {
httpClient := &http.Client{}
req, err := http.NewRequest("GET", urlConn, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+accessToken)
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return responseBody, nil
}
The function first sets up a HTTP Client and makes a GET
request to the provided URL
. Authorization is set by adding a header and setting the value to "Bearer " + accessToken
. The request is read and returned as an array of bytes that is then mapped to a variable of the modemFull
struct type. The needed modem data is set.
Next, the goroutine creates a message by setting all needed data. The following code shows the structure of the message that will be published to the MQTT.
type messageObject struct {
Rfid string `json:"Rfid"`
Gps gps `json:"Gps"`
ModemData modem `json:"ModemData"`
}
Then, the data is published to the MQTT broker. This is the structure of the publishToMqtt
function:
func tryToPublish(msge []byte) {
success := mq.PublishMessage(topic, msge, client, byte(quos))
if success == 1 {
messageTimer.Reset(5 * time.Minute)
}
}
func publishToMqtt(msge []byte) {
select {
case <-messageTimer.C:
modifyDB(conn, "INSERT", msge, 0)
tryToPublish(msge)
default:
tryToPublish(msge)
}
}
The function has two possible cases: the first is that the message timer has run out, and the message is inserted into the mqtt
data table, as in five minutes, the buffer might start overwriting older messages. The first case then tries to publish the message, and if it succeeds, the timer is reset, and the messages aren't sent to the database anymore. By default, the message immediately tries to be published, resetting the timer if the publishing is successful.
The publishing of the message is done in the following way: a topic
, message
, client
and quality of service
is chosen, and a success channel is made to signify whether the message has been published.
An internal function is then created. The token
variable is used for that matter. The github.com/eclipse/paho.mqtt.golang
's built in Publish()
method is used for publishing a message to the set broker. A WaitTimeout()
is set to wait for 5 seconds before cancelling the published message. If the message is published in that interval, the application prints out that the message is published and returns 1
to signify success, and if the timeout runs out, the message isn't published and the successChan
is returned a 0.
The last goroutine that is run by the application is one that checks whether the database contains data, converts the database objects to message objects that can be worked with in Go, then tries to publish the messages that have been queued in the database one by one. If a message is successfully published, it deletes this message from the database, and if not, the object remains stored in it.
After checking all messages, the timer is reset to 30 minutes to schedule the next checkup of whether the database contains new data.
The code for the described scenario is written below:
func checkDatabase() {
for {
select {
case <-dbTimer.C:
queuedMessages := db.SelectValues(conn, "SELECT * FROM mqtt")
messageObjects := convertToMessageObjects(queuedMessages)
for i, msg := range messageObjects {
jsonData, err := json.Marshal(msg)
if err != nil {
fmt.Println("Error marshaling JSON: ", err)
return
}
success := mq.PublishMessage(topic, jsonData, client, byte(quos))
if success == 1 {
modifyDB(conn, "DELETE", nil, queuedMessages[i].Id)
}
}
dbTimer.Reset(30 * time.Minute)
}
}
}
The reboot Node-RED DIO Application is a simple project that demonstrates how to reboot your device. It does so by accessing the device manager and sending a PUT
request to the TDC-E. There are two program files inside the linked folder:
DIO restart.json
api reboot.json
The api reboot.json
file is a Node-RED program that reboots the entire TDC-E device by clicking on a button. The DIO restart.json
file uses the API file, but adds a layer of logic as it automatically reboots the device as soon as a digital output's value is set from 0 to 1. If the program reads a 1 from the DIO, it sends a PUT
request to the server and the device is rebooted as soon as the request is made.
To run the application successfully, the following interfaces and services need to be set up:
- DIO
In this example, DIO A is used, which corresponds to the pin gpio496
. For help with setting up the TDC-E interface configuration, refer to this page.
In this section, application implementation is documented. Firstly, the api reboot.json
is discussed. Then, DIO restart.json
is described as the main focus of this section.
In this flow, rebooting is done through sending a PUT
request to the TDC-E's Device Manager Swagger. To that end, authentication and authorization is needed, and the following nodes should be downloaded via Manage palette or npm
before proceeding with the application:
node-red-contrib-oauth2
The program's structure is the following:
- an
inject
node - an
oauth2
node - a
function
node - a
http request
node - a
debug
node
The image below shows how the application appears in the editor.
The oauth2
node, as previously stated, connects to Swagger and makes an authorization request. The needed parameters are already set. Once the node returns an authorization token, the program uses the function
node to set the headers and payload of the message, as the PUT
request needs to be structured in a specific way to accept the request for rebooting the device. Once all set, the message is sent into the http request
node, and the TDC-E's reboot should soon begin.
To start the reboot process, only a click on the timsestamp
button is needed.
In this flow, rebooting is done after the value of the DIO device changes from 0 to 1. Once this happens, a PUT
request is sent to the TDC-E's Device Manager Swagger. To that end, authentication and authorization is needed, and the following nodes should be downloaded via Manage palette or npm
before proceeding with the application:
node-red-contrib-oauth2
The program's structure is the following:
- an
inject
node - an
exec
node - an
oauth2
node - 4
function
nodes - a
http request
node - 2
debug
nodes
The image below shows how the application appears in the editor.
The program does not need to be run manually, as it starts as soon as the Node-RED container is up. The 7inject` node reads the value of the connected DIO device (set as DIO A) using the command:
cat /sys/class/gpio/gpio496/value
If another GPIO is used as the digital output, the file path the program looks up should change according to your DIO pin.
The value fetched from the exec
node is now parsed, and the state of the DIO is checked. If the payload that we fetched from the terminal command is equal to 1 and the state as of now is "OFF", which is "OFF" by default, the msg.payload
is set to the simple keyword "yes"
. If this condition is not true, the payload is set to a "no"
. Now, the message is forwarded to the next node. The next node checks the restart condition. If the value of the payload is equal to "yes"
, the flow continues, and the last part of the code is the same as the api reboot.json
file.
The oauth2
node connects to Swagger and makes an authorization request. The needed parameters are already set. Once the node returns an authorization token, the program uses the function
node to set the headers and payload of the message, as the PUT
request needs to be structured in a specific way to accept the request for rebooting the device. Once all set, the message is sent into the http request
node, and the TDC-E's reboot should soon begin.