树莓派express远程监控实例解析

Express Nodejs socket
xdsnet
发布时间: 2018-06-11
阅读: 897

上文提到可以通过间接控制的方法,利用nodejs+express实现树莓派硬件的监控,本文就此以实例进行详细介绍。

整个项目的代码托管在 https://github.com/xdsnet/rpi-express-demo

---------------------------------------

一、测试环境说明

程序测试硬件为:树莓派3B+ ,以及4根杜邦线,1块面包板,1个蓝色led灯

杜邦线均为公-母线, 其母头分别接树莓派40GPIO口处的 3.3v(pin1),GND(pin39),pin11,pin18

LED灯的 正极连 pin18,负极连pin1 (在面包板上)

接pin11的杜邦线另一端公头悬空,测试时可分别在面包板上与 pin1 或 pin39 连接,模拟一个拨动开关(如果有拨动开关可以直接利用)

基本系统:rasbian

nodejs:10.2.1

npm:6.1.0

gcc: 6.3.0

wiringPi & gpio:2.46

-------------------------------------------

二、关键代码解析

1. 利用wiringPi和socket编写本地tcp服务,具体代码见项目代码 rpi-express-demo/localSvc/server.c


#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>


#include <wiringPi.h>

#define SERVER_PORT 5555

#define LEDpin 5
#define KEYpin 0

#define LINE_MAX 1024


int setLEDon(void){ // 定义亮灯处理
    digitalWrite(LEDpin,HIGH); 
    return HIGH;
}
  
int setLEDoff(void){ // 定义灭灯处理
    digitalWrite(LEDpin,LOW); 
    return LOW;
}
  
int getKEY( ){ // 获取灯状态值
    return digitalRead(KEYpin);
}




int main(){

    //调用socket函数返回的文件描述符
	int serverSocket;
    //声明两个套接字sockaddr_in结构体变量,分别表示客户端和服务器
	struct sockaddr_in server_addr;
	struct sockaddr_in clientAddr;
	int addr_len = sizeof(clientAddr);
	//用于接收连接,因为只有一个,所以同时只允许一个连接
	int client;
	char buffer[ LINE_MAX ];
	char rtmsg[ LINE_MAX ];
	int iDataNum;
	
	int LEDflag = LOW ;
	int KEYflag ;

	
	char *LED_ON = "on" ;
	char *LED_OFF = "off" ;
	char *KEY_GET = "get" ;
	char *CLOSE_MSG = "quit";

  int Ec=0;
/
    
    //socket函数,失败返回-1
    //int socket(int domain, int type, int protocol);
    //第一个参数表示使用的地址类型,一般都是ipv4,AF_INET
    //第二个参数表示套接字类型:tcp:面向连接的稳定数据传输SOCK_STREAM
    //第三个参数设置为0
	if((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		perror("socket");
		return 1;
	}

	bzero(&server_addr, sizeof(server_addr));
    //初始化服务器端的套接字,并用htons和htonl将端口和地址转成网络字节序
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
    //ip可是是本服务器的ip,也可以用宏INADDR_ANY代替,代表0.0.0.0,表明所有地址
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    //对于bind,accept之类的函数,里面套接字参数都是需要强制转换成(struct sockaddr *)
    //bind三个参数:服务器端的套接字的文件描述符,
    if(bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){
		perror("connect");
		return 1;
	}
    //设置服务器上的socket为监听状态
	if(listen(serverSocket, 15) < 0) {
		perror("listen");
		return 1;
	}

	while(1){
		printf("Listening on port: %d\n", SERVER_PORT);
        //调用accept函数后,会进入阻塞状态
        //accept返回一个套接字的文件描述符,这样服务器端便有两个套接字的文件描述符,
        //serverSocket和client。
        //serverSocket仍然继续在监听状态,client则负责接收和发送数据
        //clientAddr是一个传出参数,accept返回时,传出客户端的地址和端口号
        //addr_len是一个传入-传出参数,传入的是调用者提供的缓冲区的clientAddr的长度,以避免缓冲区溢出。
        //传出的是客户端地址结构体的实际长度。
        //出错返回-1
		client = accept(serverSocket, (struct sockaddr*)&clientAddr, (socklen_t*)&addr_len);
		if(client < 0){
			perror("accept");
			continue;
		}
		printf("\nrecv client data...n");
        //inet_ntoa   ip地址转换函数,将网络字节序IP转换为点分十进制IP
        //表达式:char *inet_ntoa (struct in_addr);
		printf("IP is %s\n", inet_ntoa(clientAddr.sin_addr));
		printf("Port is %d\n", htons(clientAddr.sin_port));
		while(1){
			iDataNum = recv(client, buffer, 1024, 0);
			if(iDataNum <= 0)
			{
        Ec++;
        if(Ec>5) break;
				continue;
			}
			buffer[iDataNum] = '\0';
			if(strcmp(buffer, CLOSE_MSG) == 0){
				break;
			}
		  if(strcmp(buffer, LED_ON) == 0){
        LEDflag=setLEDon();
        sprintf(rtmsg,"cmd=%s&LEDflag=%d",buffer,LEDflag);
      }

		  if(strcmp(buffer, LED_OFF) == 0){
        LEDflag=setLEDoff();
        sprintf(rtmsg,"cmd=%s&LEDflag=%d",buffer,LEDflag);
      }

		  if(strcmp(buffer, KEY_GET) == 0){
        KEYflag=getKEY();
        sprintf(rtmsg,"cmd=%s&KEYflag=%d",buffer,KEYflag);
      }

			printf("server %drecv data is %s\n", iDataNum, buffer);
			send(client, rtmsg,strlen(rtmsg), 0);
		}
	}
	return 0;
}

用gcc -Wall -o rootServer server.c -lwiringPi 编译得到rootSever可执行文件,并用root权限运行即打开了相应本地tcp服务。 


-------------

2. express中路由及模块实现(基本express环境搭建类似http://www.ickey.cc/community/thread-100941-1-1.html),只是具体的路由和模块不同了,主要有3个路由,分别是 "/ledon","/ledoff","/keyget",分别对应于点亮LED,熄灭LED和获取开关值,其具体的模块实现于rpi-express-demo/expressServer/routes/ 下的ledon.js, ledoff.js 和 keyget.js。其中路由的注册是在rpi-express-demo/expressServer/app.js 文件中。下面具体谈谈ledon.js,其他两个路由模块其实差不多,只是改变了发送的指令而已。

ledon.js 具体内容如下:

var express = require('express');
var router = express.Router();
var net = require('net');
var PORT = 5555; // 这里要同本地tcp服务预设一致才能连接上。

router.get('/', function(req, res, next) {
  // 实现逻辑
  var revData='';
  var client = net.connect({port:PORT},function(){
    console.log('连接到服务器');
  });
  client.on('data',function(data){
    revData=data.toString();
    console.log(data.toString());
    res.send(revData);
    client.end();
  });
  client.on('end',function(){
    console.log('断开与服务器的连接');
  });
  client.write("on"); // 具体发送指令,其他两个模块其实就是这里变化了下,分别是'off'和'get'而已。
 // client.write("quit");
  //res.send('respond with a resource');
});

module.exports = router;


在expressServer/ 目录下执行

npm install

以安装相应依赖后即可用npm start来运行express服务了(普通权限即可),应用在3000端口进行监听,用浏览器分别打开

http://<树莓派ip>:3000/ledon  (点亮灯), http://<树莓派ip>:3000/ledoff (熄灭灯),  http://<树莓派ip>:3000/keyget (根据线接到3.3v或者GND 获得不同的返回)。

即可感受远程监控的魅力了。






原创作品,未经权利人授权禁止转载。详情见转载须知 举报文章

点赞 (0)
打赏
当前打赏1人    
xdsnet 擅长:科研教育
评论(0)

登录后可评论,请 登录注册

相关文章推荐
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回

我要举报该内容理由

×
请输入您举报的理由(50字以内)