Raspberry Pi C-Server Sensor Application with websocket

Raspberry Pi C-Server Sensor Application with websocket

Getting pressure and temperature measurements from the Raspberry PI Sense Hat

code_ws_sensor.zip

ws_sensor.svg

REM : sensor access functions ( c language) can be found at : https://github.com/davebm1/c-sense-hat

you’ll have to install :

sudo apt-get install libi2c-dev

After websocket handshaking, the RPI gets measurements with I2C bus and sends them to the client through the websocket.

httpDynServerWs_sensor.c
// CREDITS : F. Harrouet, www.enib.fr/~harrouet
//======================================================================
// REM : all static pages have to be readable only !!
//======================================================================
#include "main.h"
#include "httpServerLib.h"
#include "libWs.h"
#include "senseHat.h"

#include <time.h> 
#include <sqlite3.h> 

#define BUFFER_SIZE 0x1000


//======================================================================
void * dialogThread(void *arg)
{
	char header[0x100];
	char handshake[29];
	float pressure, temperature;
    int values[2];
	
	  
	pthread_detach(pthread_self());
	char buffer[BUFFER_SIZE];
	Request *req=(Request *)arg;
	//---- receive and analyse HTTP request line by line ----
	bool first=true;
	for(;;)
	  {
				if(recvLine(req->sock,buffer,BUFFER_SIZE)<=0) 
				{ break; }
				
				printf("%s",buffer);
			  
				if(first)
				{
						first=false;
						sscanf(buffer,"%s %s",req->requestMethod,req->requestUri);
				}
				else if(sscanf(buffer,"Content-Length : %s",req->contentLength))
				{
					printf("CONTENTLENGTH\n");
						// nothing more to be done
				}
				else if(!strcmp(buffer,"\n")||!strcmp(buffer,"\r\n"))
				{
						break; // end of header
				}
				else if(sscanf(buffer, "Upgrade : %31s", req->upgrade)==1)
				{
				  // nothing more to be done
				}
				else if(sscanf(buffer, "Sec-WebSocket-Key : %31s", req->wsKey)==1)
				{
				  // nothing more to be done
				}								
	  }
	//------------------------------------------------------------------------------------------
	//---- prepare and send HTTP reply ----
	sprintf(req->fileName,"./www%s",req->requestUri);
	char *params=strchr(req->fileName,'?');
	if(params) { *params='\0'; }
	struct stat st;
	int r=stat(req->fileName,&st);
	if((r!=-1)&&S_ISDIR(st.st_mode))
	  {	
			  strcat(req->fileName,"/index.html");
			  r=stat(req->fileName,&st);
	  }
	//------------------------------------------------------------------------------------------  
	//---- HANDLE UPGRADE TO WEBSOCKET REQUEST ----	  
	if(!strcmp(req->requestMethod, "GET")&& !strcmp(req->requestUri, "/")&& !strcmp(req->upgrade, "websocket"))
	{
		printf("upgrading to websocket\n");
		//---- prepare and send reply header ----
		wsHandshake(handshake, req->wsKey);
		
		 const int hLen=snprintf(header, sizeof(header),
                            "HTTP/1.1 101 Switching Protocols\r\n"
                            "Connection: Upgrade\r\n"
                            "Upgrade: websocket\r\n"
                            "Sec-WebSocket-Accept: %s\r\n"
                            "\r\n",
                            handshake);
		  sendAll(req->sock, header, hLen);
		  
		      //---- receive some parameters from client ----
			uint32_t params[3];
			WsResult r=wsRecv(req->sock, params, sizeof(params));
			if((r.opcode==WS_BIN)&&(r.length!=sizeof(params)))
			{
			  fprintf(stderr, "cannot receive parameters\n");
			  goto threadEnd;
			}
		

			const int repeatCount=(int)ntohl(params[2]);
			

			 //---- produce and send values to client ----
			for(int repeat=0; repeat<repeatCount; ++repeat)
			{
				senseHat_getPressureTemperature(&pressure, &temperature);

				values[0] = (int)(1000.0*pressure);
				values[1] = (int)(1000.0*temperature);
							
				sleep(2);

				for(int i=0; i<2; ++i) { values[i]=htonl(values[i]); }

			  wsSend(req->sock, values, sizeof(values), WS_BIN);	//>>>>>>>>>>>>>>>>>>>> WS SEND
			}						
			
			//---- signal end of websocket dialog ----
			wsSend(req->sock, NULL, 0, WS_CLOSE);

		  //---- websocket dialog stops here ----
    goto threadEnd;
	}	
		  
	//------------------------------------------------------------------------------------------	 
	// IF WE HAVE TO SERVE AN EXECUTABLE SCRIPT FILE ( CGI SCRIPT ) : 
	// rem : CGI scripts have to be placed in www/cgi/ folder
	if((access(req->fileName,R_OK|X_OK)==0) && !strncmp(req->requestUri, "/cgi",4))
	  {
	  
			  if(strcmp(req->requestMethod,"GET")&&strcmp(req->requestMethod,"POST"))
				{
						r=sprintf(buffer,"HTTP/1.0 400 Bad Request\n"
										 "Connection: close\n"
										 "Content-Type: text/html\n"
										 "\n"
										 "<html><body>\n"
										 "<b>400 - Bad Request</b><br>\n"
										 "method: %s<br>\n"
										 "uri: %s<br>\n"
										 "fileName: %s<br>\n"
										 "</body></html>\n",
										 req->requestMethod,
										 req->requestUri,
										 req->fileName);
						sendAll(req->sock,buffer,r);
				}
			  else
				{
							// HTTP RESPONSE HEADER
							r=sprintf(buffer,"HTTP/1.0 200 OK\n"
											 "Connection: close\n");
							sendAll(req->sock,buffer,r);
							pid_t child=fork();	// execvp needs a new process, otherwhise the executed process will destroy all current threads.
							switch(child)
							  {
									  case -1:
										{
											perror("fork");
											break;
										}
									  //############ CHILD CODE ######################################
									  case 0:		
										{
											setenv("REQUEST_METHOD",req->requestMethod,1); 	// needed for queryProperties script
											setenv("REQUEST_URI",req->requestUri,1);	 	// needed for queryProperties script	
											setenv("CONTENT_LENGTH",req->contentLength,1); 	// needed for queryProperties script
											createQueryString(req->requestUri,sizeof(req->requestUri),req->queryString);
											setenv("QUERY_STRING",req->queryString,1);
																														
											printf("DEBUG REQ=%s, FILENAME=%s QUERYSTRING=%s \n",req -> requestUri, req -> fileName, req -> queryString);	
											
											if(dup2(req->sock,0)==-1)
																					{ perror("dup2"); exit(1); }
											if(dup2(req->sock,1)==-1)
																					{ perror("dup2"); exit(1); }
											if(dup2(req->sock,2)==-1)
																					{ perror("dup2"); exit(1); }
											close(req->sock);
											char *args[2];
											args[0]=req->fileName;
											args[1]=(char *)0;
											execvp(args[0],args);
											perror("execvp");
											exit(1);
										}
									  //############ PARENT CODE #####################################
									  default:		
										{
											if(waitpid(child,(int *)0,0)==-1)
																					{ perror("waitpid"); }
										}
									  }
							}
			  }
			//------------------------------------------------------------------------------------------  
			else   // STATIC HTTP FILE SERVER
			  {
			  int input=open(req->fileName,O_RDONLY);
			  if(input==-1)
				{
							r=sprintf(buffer,"HTTP/1.0 404 Not Found\n"
											 "Connection: close\n"
											 "Content-Type: text/html\n"
											 "\n"
											 "<html><body>\n"
											 "<b>404 - Not Found</b><br>\n"
											 "method: %s<br>\n"
											 "uri: %s<br>\n"
											 "fileName: %s<br>\n"
											 "</body></html>\n",
											 req->requestMethod,
											 req->requestUri,
											 req->fileName);
							sendAll(req->sock,buffer,r);
				}
			  else
				{
							const char *contentType="unknown/unknown";
							const char *ext=strrchr(req->fileName,'.');
							if(ext)
							  {
									  if(!strcmp(ext,".html"))     contentType="text/html";
									  else if(!strcmp(ext,".png")) contentType="image/png";
									  else if(!strcmp(ext,".ico")) contentType="image/vnd.microsoft.icon";
							  }
							r=sprintf(buffer,"HTTP/1.0 200 OK\n"
											 "Connection: close\n"
											 "Content-Type: %s\n"
											 "Content-Length: %ld\n\n",
											 contentType,
											 st.st_size);
							sendAll(req->sock,buffer,r);
							for(;;)
							  {
									  r=read(input,buffer,BUFFER_SIZE);
									  if(r<=0) break;
									  sendAll(req->sock,buffer,r);
							  }
							close(input);
				}
	  }
	  
threadEnd:
	destroyRequest(req);
	return (void *)0;
	}

//======================================================================
//						MAIN
//======================================================================

int main(int argc, char **argv)
{

	  senseHat_init();

	//---- avoid exiting on broken client connection (ignore SIGPIPE) ----
	struct sigaction sa;
	memset(&sa,0,sizeof(struct sigaction));
	sa.sa_handler=SIG_IGN;
	if(sigaction(SIGPIPE,&sa,(struct sigaction *)0)==-1)
											{ perror("sigaction"); exit(1); }

	//---- check command line arguments ----
	if(argc!=2)
											{ fprintf(stderr,"usage: %s http_port\n",argv[0]); exit(1); }

	//---- extract local port number ----
	int httpPortNumber;
	if(sscanf(argv[1],"%d",&httpPortNumber)!=1)
											{ fprintf(stderr,"invalid port %s\n",argv[1]); exit(1); }

	//---- multithreaded TCP server launching dialogThread() ----

	//---- create HTTP listen socket ----
	int httpSocket=socket(PF_INET,SOCK_STREAM,0);
	if(httpSocket==-1)
											{ perror("socket"); exit(1); }
	// ... avoiding timewait problems (optional)
	int on=1;
	if(setsockopt(httpSocket,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int))==-1)
											{ perror("setsockopt"); exit(1); }
	// ... bound to any local address on the specified port
	struct sockaddr_in myAddr;
	myAddr.sin_family=AF_INET;
	myAddr.sin_port=htons(httpPortNumber);
	myAddr.sin_addr.s_addr=htonl(INADDR_ANY);
	if(bind(httpSocket,(struct sockaddr *)&myAddr,sizeof(myAddr))==-1)
											{ perror("bind"); exit(1); }
	// ... listening connections
	if(listen(httpSocket,10)==-1)
											{ perror("listen"); exit(1); }

	for(;;)
	  {
		  //---- accept new HTTP connection ----
		  struct sockaddr_in fromAddr;
		  socklen_t len=sizeof(fromAddr);
		  int dialogSocket=accept(httpSocket,(struct sockaddr *)&fromAddr,&len);
		  if(dialogSocket==-1)
												{ perror("accept"); exit(1); }
		  printf("new HTTP connection from %s:%d\n",
							inet_ntoa(fromAddr.sin_addr),ntohs(fromAddr.sin_port));

		  //---- start a new dialog thread ----
		  pthread_t th;
		  if(pthread_create(&th,(pthread_attr_t *)0, dialogThread,createRequest(dialogSocket)))
												{ fprintf(stderr,"cannot create thread\n"); exit(1); }
	  }

	close(httpSocket);
	return 0;
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^ EOF ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
www/wsAppli.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>RPI C Server Sensor App </title>
</head>
<body>
<!--=================================================================-->
<!--				DISPLAYS										 
<!--=================================================================-->
<script>
	display_log=function(msg)
	{
		  var logArea = 0;		
		  if(!logArea)
		  { logArea=document.getElementById("log"); }
		  if(msg==null)
		  { logArea.textContent='';}
		  else { logArea.textContent+=msg+'\n'; }
	};
</script> 
<!--=================================================================-->
<!--				WEBSOCKET							 
<!--=================================================================-->
<script type="text/javascript" charset="utf-8">
	
	hton_i32Array=function(values){
	  var view=new DataView(values.buffer);
	  for(var i=0; i<values.length; ++i)
	  {
		view.setInt32(i*4, values[i], false);
	  }
	};
	<!----------------------------------------------------------------->
	ntoh_i32Array=function(values){
	  var view=new DataView(values.buffer);
	  for(var i=0; i<values.length; ++i)
	  {
		values[i]=view.getInt32(i*4, false);
	  }
	};
	<!----------------------------------------------------------------->
	var host=window.location.hostname;
	var port=window.location.port;

	var ws = new WebSocket('ws://'+host+':'+port+'/');
	ws.binaryType='arraybuffer';
	<!----------------------------------------------------------------->					  
	ws.onopen=function(){
			display_log('CONNECT');
		};
	<!----------------------------------------------------------------->	
	ws.onclose=function(){
		display_log('DISCONNECT');				
	};
	<!----------------------------------------------------------------->
	ws.onmessage=function(evt){
		var values=new Int32Array(evt.data);
		ntoh_i32Array(values);
		var msg='received values :';
		for(var i=0; i<values.length; ++i)
		{
			  msg+=' '+values[i]/1000.0;
		}
		display_log(msg);			
	};	
	<!----------------------------------------------------------------->
    function start() {
      console.log('start...');
      nbr_meas = document.getElementById("nbr_meas");
      var params=new Int32Array(3);
	  params[0]=1000; 			// delayMs
	  params[1]=100;  			// modulo
	  params[2]=nbr_meas.value; // only this parameter is used
	  hton_i32Array(params);
	  ws.send(params);      
    }    

</script>
<!--=================================================================-->
<!--				ONLOAD WINDOW INIT					 						 
<!--=================================================================--> 
<script>	
	window.onload = function() {
			if(!host){
				host='localhost';
				port=8080;
			  }	  
	};
</script>

<!--=================================================================-->
<!--				HTML											 -->
<!--=================================================================-->

<p>[<a href="/">home</a>]</p>

<hr> <!---------------------------------------------------------------->

<h2>Getting Sensor Datas with Websocket</h2>  

			<strong> Number of Measurements :  </strong><br/>   
			<input type="text" id="nbr_meas"/>     <br/>   
			<button onclick="start()">Start</button>


<p><b>Log:</b></p>
<p></p><pre id="log"></pre><p></p>	

<hr> <!---------------------------------------------------------------->


  
</body>
</html>
<!--=================================================================-->

Adding LED Control

GPIO CONFIG

$ nano /etc/profile.d/gpio_config.sh

#! /bin/bash
cd /sys/class/gpio/
echo 4 > export
cd gpio4
echo out > direction
echo 1 > value
cd ~

code_ws_sensor_led.zip

for led config, have a look at : dynamic_http_server.html

ws_sensor_led.svg

www/wsAppliLed.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>RPI C Server Sensor App </title>
</head>
<body>
<!--=================================================================-->
<!--				DISPLAYS										 
<!--=================================================================-->
<script>
	display_log=function(msg)
	{
		  var logArea = 0;		
		  if(!logArea)
		  { logArea=document.getElementById("log"); }
		  if(msg==null)
		  { logArea.textContent='';}
		  else { logArea.textContent+=msg+'\n'; }
	};
</script> 
<script src="gauge.js"></script>
<!--=================================================================-->
<!--				WEBSOCKET							 
<!--=================================================================-->
<script type="text/javascript" charset="utf-8">
	
	hton_i32Array=function(values){
	  var view=new DataView(values.buffer);
	  for(var i=0; i<values.length; ++i)
	  {
		view.setInt32(i*4, values[i], false);
	  }
	};
	<!----------------------------------------------------------------->
	ntoh_i32Array=function(values){
	  var view=new DataView(values.buffer);
	  for(var i=0; i<values.length; ++i)
	  {
		values[i]=view.getInt32(i*4, false);
	  }
	};
	<!----------------------------------------------------------------->
	var host=window.location.hostname;
	var port=window.location.port;

	var ws = new WebSocket('ws://'+host+':'+port+'/');
	ws.binaryType='arraybuffer';
	<!----------------------------------------------------------------->					  
	ws.onopen=function(){
			display_log('CONNECT');
		};
	<!----------------------------------------------------------------->	
	ws.onclose=function(){
		display_log('DISCONNECT');				
	};
	<!----------------------------------------------------------------->
	ws.onmessage=function(evt){
		var values=new Int32Array(evt.data);
		ntoh_i32Array(values);
		var msg='received values :';
		for(var i=0; i<values.length; ++i)
		{
			  msg+=' '+values[i]/1000.0;
		}
		display_log(msg);
		var gauge=new Gauge('gauge'); 
		gauge.draw(values[0]/1000.0);				
	};	
	<!----------------------------------------------------------------->
    function start() {
      console.log('start...');
      nbr_meas = document.getElementById("nbr_meas");
      var params=new Int32Array(3);
	  params[0]=1000; 			// delayMs
	  params[1]=100;  			// modulo
	  params[2]=nbr_meas.value; // only this parameter is used
	  hton_i32Array(params);
	  ws.send(params);      
    }    

</script>
<!--=================================================================-->
<!--				AJAX REQUEST FOR LED CONTROL					 
<!--=================================================================--> 
<script>
	function do_onclick(led, label){
		  var xmlhttp=new XMLHttpRequest();
		  
		  // register callback : function to be called when we receive the response
		  xmlhttp.onreadystatechange=function() {
			if (xmlhttp.readyState==4 && xmlhttp.status==200) {
					  label.innerHTML=xmlhttp.responseText;
			}
		  }
		  // send request and poll for asynchronous response
		  xmlhttp.open("GET","cgi/ajax_gpio4.sh?gpio4="+led.checked,true);
		  xmlhttp.send();
	}
</script>	
<!--=================================================================-->
<!--				ONLOAD WINDOW INIT					 						 
<!--=================================================================--> 
<script>	
	window.onload = function() {
			var led=document.getElementById("tLed");
			var label=document.getElementById("tLedLabel");
			led.checked=false;
			do_onclick(led, label);
			if(!host){
				host='localhost';
				port=8080;
			  }	  
	};
</script>

<!--=================================================================-->
<!--				HTML											 -->
<!--=================================================================-->

<p>[<a href="/">home</a>]</p>

<hr> <!---------------------------------------------------------------->

<h2>Control GPIO4 LED</h2>

<table>
	<tr>
	<td > 
			<h3>Static Part :</h3>
			Control GPIO4 led <input type="checkbox" id="tLed" onclick="do_onclick(this, document.getElementById('tLedLabel'))">
	</td>
	<td>	
			<span id="tLedLabel"> GPIO4 led turned off</span>		
	</td>
	</tr>
</table>

<hr> <!---------------------------------------------------------------->

<h2>Getting Sensor Datas with Websocket</h2>  
<table>
	<tr>
	<td > 
			<strong> Number of Measurements :  </strong><br/>   
			<input type="text" id="nbr_meas"/>     <br/>   
			<button onclick="start()">Start</button>
	</td>
	<td>	
			<h3> Pressure Measurement </h3>
			<div >
			<canvas id="gauge" width="170" height="170"></canvas>
			</div>			
	</td>
	</tr>
</table>

<p><b>Log:</b></p>
<p></p><pre id="log"></pre><p></p>	

<hr> <!---------------------------------------------------------------->

  
</body>
</html>
<!--=================================================================-->

Adding Database Management

code_ws_sensor_led_db.zip

Let’s use a lightweight database : sqlite3

# RPI TERM
$ sudo apt-get install sqlite3 libsqlite3-dev

Cross Compiling and Debug

please have a look at : https://web.enib.fr/~kerhoas/ide/rpi-cross-development

code_ws_sensor_led_db_cross_dev.zip