Example program to create IoT devices for a local network, which connect to a local server.

Dependencies:   WebSocketClient WiflyInterface mbed messages

You are viewing an older revision! See the latest version

Tour of the Code

This first part of the tour will focus on the wireless communication. At the end of this section will be a few notes on other interesting aspects of the code: the messaging system, and the manual configuration of the ADC module.

Wireless Communication and the WebSocket Client

Looking at the file structure of the IoT_Ex project, you can easily identify the two libraries necessary for the wireless communication: WiflyInterface and WebSocketClient. These two libraries were developed by other mbed users, and I have modified them slightly to improve their performance. I will not be going into detail on how they work, but on how they are being used for this project.

As shown in Figure 1, below, the Main Program interfaces with the “high level” WebSocket Client. Once a connection is established, the main program simply needs to call either ws.send() or ws.readmsg() to send or receive data from the server. After anyone of those function calls, the data goes through the WiFly Interface, then the UART module, then the physical wires to the RN-171 module. All of these lower level layers of code and hardware ensure everything is sent at the right time, and errors do not occur in the transmission, etc.

Figure 1 - Schematic diagram of the mbed software and hardware.

The next two subsections will parse through some of the important parts of main.cpp and globals.cpp that enable the wireless communication using the WebSocket Client and the WiFly Interface. I will also point out the areas of the code which you can modify to send and receive different types of data.

main.cpp

49   // Main Loop!
50   int main() {

The int main(){…} function is where most of the action takes place, so let’s take some time to parse through it. The first part of the function has some standard declarations, and variable configurations. Lines 74 and 78 is some of the first communications code we find:

72	// Connect to the wifi network. It will basically get stuck here until it
73	// connects to the network.
74	SetupNetwork(5000);
75	
76	// Configure the baud rate of the wifi shield:
77	// This will make our wireless transmissions much faster.
78	ws.setBaud(115200);

The function SetupNetwork() connects to the wireless network. It will try 5000 times before it exits. The function itself is explicity defined in globals.cpp. Once connected to the network, we know the communication between the microcontroller and the RN-171 module is working, so we increase the baud rate of the UART interface between the microcontroller and the RN-171 module to 115200 with a call to ws.setBaud() on line 78.

ws is a global instance of the WebSocketClient class, and is declared in globals.cpp.

Next, the microcontroller will try to connect to the WebSocket server on Line 87:

82	if(IotStatus.CheckFlag(SF_WIRELESSCONNECTED)){
...	    ...
87	    if(ws.connect()){
88	        // Set a status flag:
89	        INFO("Connected.");
90	        IotStatus.SetFlag(SF_SERVERCONNECTED);
91	    }else{
92	        // We could not connect right now..
93	        IotStatus.ClearFlag(SF_SERVERCONNECTED);
94	        INFO("Could not connect to server, will try again later.");
95	        ReconnectAttempts++;
96	    }
97	}

If it cannot connect, it will try again later, during a call to SendNetworkData().

Finally, we get to the infinite loop. This is where the microcontroller spends all of its processing time:

103	// Inifinite main loop:
104	while(1) {
105	    
106	    // Process the wifi command:
107	    if(wifi_cmd > NO_WIFI_CMD){
108	        // Modify the desired variable:
109	        ModifyVariable(wifi_cmd, wifi_data);
110	        // Reset the command:
111	        wifi_cmd = NO_WIFI_CMD;
112	    }
113	    
114	    // Check for new wifi data:
115	    if((wifi_cmd == NO_WIFI_CMD)){
116	        ReceiveNetworkData(&wifi_cmd, &wifi_data);
117	    }
118	    
119	    // Send the network data every 3 seconds:
120	    if(DisplayTimer.read()>(3.0f)){
...	        ...                
133	        // Send data over network:
134	        SendNetworkData();               
...	        ...                
139	        // Reset the timer:
140	        DisplayTimer.reset();
141	        
142	    }
143	} // while(1)

On Line 116, the microcontroller checks for new data from the WebSocket server. The data is parsed in the ReadNetworkData() function into the two variables: wifi_cmd and wifi_data. Within this function, is the function call to ws.readmsg(), mentioned earlier. In effect, the microcontroller is polling the websocket for any new data. You could also implement this as an interrupt, but I leave that as an exercise for the reader. Any received data is handled by the ModifyVariable() function on Line 109.

The if(){…} statement on Line 120 ensures SendNetworkData() is called once every 3 seconds. SendNetworkData() sends a comma separated string to the WebSocket Server of the form of:

IoT_ID, SendCounter, TempSensor

All of the functions listed above are found in globals.cpp.

That’s it!

144	} // main()

globals.cpp

globals.cpp contains global variable and function definitions. We will be looking at a few of the functions that were referenced in the previous subsection. First, SendNetworkData():

102	void SendNetworkData(void){
103	    char msg_buffer[CHARMSGBUFF];
...	    ...
106	    if(IotStatus.CheckFlag(SF_SERVERCONNECTED)){
107	        sprintf(msg_buffer, "%d,%d,%.5f", IoT_ID, SendCounter,TempSensor);
108	        INFO("Sending: %s", msg_buffer);    // When this line ...
109	        intresult = ws.send(msg_buffer);
110	    }else{
111	        intresult = -1;
112	    }
113	    DBG("intresult: %d", intresult);
114	        
115	    if(intresult < 0){
...	        ...
154	    }
155	
156	    return;
157	}

During execution, SendNetworkData() first checks to see if the microcontroller is connected to the server – that’s the if(){…} statement on Line 106. If it is connected, data is packed into the message buffer, and then sent using the ws.send() command on Line 109. The result of this function call will be an integer greater than zero if it is a success. If it returns a -1, then the connection to the server is lost. If you want to change what the microcontroller is sending to the server, simply change Line 107 to reflect your changes. Keep in mind that msg_buffer should be large enough to hold everything, which means changing its declaration in Line 103. If it is not, you will get unpredictable results.

The second if(){…} statement in the code block above on Line 115, deals with the case when the connection to the server is lost. There are several ‘troubleshooting’ steps the microcontroller will take to try to re-establish the lost server connection. Eventually if nothing works, it will reset the Wifi Shield and start from scratch.

The next function we will examine is ReceiveNetworkData():

159	void ReceiveNetworkData(unsigned int * wifi_cmd, float * value){
160	    char msg_buffer[CHARMSGBUFF];
161	    char msg_buffer2[CHARMSGBUFF];
162	    int resp;
163	    if(IotStatus.CheckFlag(SF_SERVERCONNECTED)){
164	        // Check for data on the websocket:
165	        resp = ws.readmsg(msg_buffer);
166	        if(resp == 1){
167	            INFO("Received: %s", msg_buffer);
168	            sscanf(msg_buffer, "%d,%s", wifi_cmd, msg_buffer2);
169	            if(*wifi_cmd == CV_LED_WIFI_CMD){
170	                // Get one more value:
171	                sscanf(msg_buffer2, "%f", value);
172	            }
173	        }else if(resp == -1){
174	            // Connection to the server is lost:
175	            IotStatus.ClearFlag(SF_SERVERCONNECTED);
176	        }else{
177	            //DBG("Did not receive anything :(\n\r");
178	            *wifi_cmd = NO_WIFI_CMD;
179	            *value = 0.0f;
180	        }
181	    }
182	    return;
183	}

Just like sending data, the ReceiveNetworkData() function will check for a server connection, Line 163. If it is established, it will attempt to read from the WebSocket, Line 165. If you are going to change the messages that are being sent to the microcontrollers, make sure your message buffer is large enough, changing Line 160 and 161 if necessary. In this implementation, I decided to break up the parsing of the message into two stages. First, I check for a command on Line 169, putting the rest of the message ‘aside’ in msg_buffer2. Once I know what type of data I am dealing with, I look for a float value (Line 171).

If you want to send different types of data to the microcontrollers, you will need to modify Lines 167 to 171.

The last important function to look at is ModifyVariable():

185	void ModifyVariable(unsigned int wifi_var, float wifi_data){
186	    // modifies something in the SCS Controller:
187	    switch(wifi_var){
188	        case CV_LED:
189	            if(wifi_data > 0){
190	                Led = 1;
191	            }else{
192	                Led = 0;
193	            }
194	            break;
195	        
196	        default:
197	            break;
198	    }
199	    return;
200	}

This function simply modifies the variable that is sent from the WebSocket Server. As you can see the only variable that can be modified right now is the Led digital output. Now that you see the code, you can turn on and off the led with numbers other than 0 and 1.

The easiest way to add another variable to modify would be here. Simply define another variable, like #define CV_GAIN 2, and add a case statement at Line 195.

Messaging System

One of the improvements I made to the WebSocketClient and WiflyInterface libraries was to their messaging systems. The messaging system is used to output a string to the serial port for mostly debugging and status purposes. These strings can be read by a terminal program like Tera Term. The same messaging system in those libraries is all over my code at the start of the .cpp files. Here is a snippet from main.cpp:

40	
41	//#define DEBUG
42	#define INFOMESSAGES
43	#define WARNMESSAGES
44	#define ERRMESSAGES
45	#define FUNCNAME "IoT"
46	#include "messages.h"
47	

This system allows me to have 4 levels of messages that can be turned on or off for each file very easily. In the above snippet, any debug messages are suppressed, but Info messages, warning messages, and error messages will be displayed in the terminal. Take a look at messages.h, which can be found in the WiflyInterface library, for how it works.

The declaration of FUNCNAME allows you to easily determine which file generated the message. This little feature was added by Malcolm McCulloch.

Each level of messaging can be used just like a printf() statement. For the four levels of messages we have:

Debug Messages:     DBG(“This is a debug message.”);
Info Messages:     INFO(“This is an info message with the number: %d”, 2);
Warning Messages:   WARN(“This is a warning message with a float: %.3f”, 3.14159f);
Error Messages:     ERR(“Lots: %d, %s, %.5f”, 3, “Hello World”, 4.245423423);

All wikipages