从设备到平台定制测温系统(二)

4. 平台部分

  • 前后端一体, 小巧灵活, 512M内存的Linux系统也可稳定运行
  • 灵活扩展支持不同类型的设备 如下截图都是专用RTU的. 在这里插入图片描述

4.1 独立入库服务, 为方便适配不同协议的数据, 组包规则: 4字节的长度 + json

function tcp_server_run() {
	net.createServer( (sock) => {
		log.log1('new tcp_connect: ' + sock.remoteAddress + ':' + sock.remotePort);

		let g_buf = Buffer.alloc(0);
		let l_all = 0;
		let offset = 0;
		const l_head = 4;

		function ttlv_delimit(chunk, delimiter) {
			let data = Buffer.concat([g_buf, chunk]);
			offset += chunk.length;

		    let ret = [];
	        if (l_all == 0 && offset >= l_head) {
	            l_all = data.readUInt32BE(0) + l_head;    // head 4
	            log.log(sprintf('offset = %d, l_all(%d)', offset, l_all));
	        }
	        while (l_all > 5 && offset >= l_all) {
	            ret.push(data.slice(0, l_all));
	            data = data.slice(l_all);	// left data
	            // --- reset, for next data
	            l_all = 0;  // must reset
	            offset = 0;
	            // parse left data
	            if (data.length > 5) {
	            	l_all = data.readUInt32BE(0) + l_head;    // head 4
	            	offset = data.length;
	            }
	        }

		    g_buf = data;
		    return ret;
		}

	    sock.on('data', async(data) => {
	    	const ret = ttlv_delimit(data);

	    	for (let i=0, l=ret.length; i<l; i++) {
		    	n2++;
	    		const buf = ret[i];
		    	let len = buf.length;
		        log.log1(`--- rx ${n2}(${len}), ${sock.remoteAddress}: ${sock.remotePort}`);
		        const s_valid = buf.slice(l_head).toString('utf8');
		        if (len < 1000) {
			    	log.log1(s_valid);
		        }
		        // --- answer
		        const res = await GranaryLog_handle(s_valid);
		    	sock.write(res);
	    	}
	    });

	    if (timeout_ms > 0) {
		    sock.setTimeout(timeout_ms);	// ms

			sock.on('timeout', () => {
				log.log2('socket timeout');
				sock.end();
			});
	    }

	    sock.on('close', (data) => {
	        log.log2('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
	    });
	    sock.on('end', () => {
	        log.log2('tcp end: ' + sock.remoteAddress + ' ' + sock.remotePort);
	    });
	    sock.on('error', err => {
	        log.log2(err);
	    });

	}).listen(PORT, HOST);

	log.log2('Server listening on ' + HOST +':'+ PORT);
}

4.3 数据解析部分

  • 上报数据一般会超过4G模块的组包长度, 必须组包, 利用libhv实现的单线程Demo
// 内置拆包协议
#define TEST_UNPACK     1
#if TEST_UNPACK
    static unpack_setting_t unpack_setting;
#endif

hloop_t* loop = NULL;
int g_port = 1508;
int g_timeout_sec = 10;    // 10 sec

void end_handle(void) {
    hloop_free(&loop);
    LOG_E("end_handle");
}
static void my_handler(int sig) {
    LOG_E("sig = %d", sig);  // sig = 2(ctrl+c)
    end_handle();
    exit(1);
}
static void signal_handle(void) {
    signal(SIGPIPE, my_handler);  //pipe broken
    signal(SIGINT,  my_handler);  //ctrl+c
    signal(SIGTERM, my_handler);  //kill
    signal(SIGSEGV, my_handler);  //segmentfault
    signal(SIGBUS,  my_handler);  //bus error/**/
}
// --- libhv start
// 客户端主动断开(error=0), 超时服务器端主动断开(error=60/110)
static void on_close(hio_t* io) {
    LOG_W("on_close fd=%d error=%d", hio_fd(io), hio_error(io));
}
static void on_recv(hio_t* io, void* buf, int len_rx) {
    n_rx++;
    char peeraddrstr[SOCKADDR_STRLEN] = {0};
    printf("\n[%s] on_recv(%d) fd=%d [%s]\n",
            time_now_str(), len_rx, hio_fd(io), SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));

    // --- data_handle
    unsigned char tx[1024] = {0};
    memset(tx, 0, 1024);

    int len_tx = data_handle(tx, (unsigned char *)buf, len_rx);
    if (len_tx > 0) {
        hio_write(io, tx, len_tx);
        // printf("[%s] tx(%d) %s\n", time_now_str(), len_tx, array_dbg((unsigned char *)tx, len_tx) );
        printf("[%s] tx(%d) %s\n", time_now_str(), len_tx, array_hex((unsigned char *)tx, len_tx) );
    }
}
static void on_accept(hio_t* io) {
    char peeraddrstr[SOCKADDR_STRLEN] = {0};
    LOG_I("on_accept connfd=%d, [%s]", hio_fd(io), SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));

    hio_setcb_close(io, on_close);
    hio_setcb_read(io, on_recv);
#if TEST_UNPACK
    hio_set_unpack(io, &unpack_setting);
#endif
    /*
    // read timeout => hclose_cb, 设置读超时,一段时间没有数据接收,自动断开连接
    HV_EXPORT void hio_set_read_timeout(hio_t* io, int timeout_ms);
    // write timeout => hclose_cb, 设置写超时,一段时间没有数据发送,自动断开连接
    HV_EXPORT void hio_set_write_timeout(hio_t* io, int timeout_ms);
    // keepalive timeout => hclose_cb, 设置keepalive超时,一段时间没有数据读写,自动断开连接
    HV_EXPORT void hio_set_keepalive_timeout(hio_t* io, int timeout_ms DEFAULT(HIO_DEFAULT_KEEPALIVE_TIMEOUT));
    */
    hio_set_read_timeout(io, g_timeout_sec * 1000);    // start from accept, 一段时间没有数据接收,自动断开连接, on_close fd=7 error=60

    hio_read_start(io);
}
// --------------------------------------------------------
int main(int argc, char *argv[]) {
    if (argc < 0 && argv == NULL) return 0;
    const char* host = "0.0.0.0";

    switch (argc) {
	    case 2:
	        g_port = atoi(argv[1]);
	        break;
	    case 3:
	        g_port = atoi(argv[1]);
	        g_timeout_sec = atoi(argv[2]);
	        break;
    }
    r_printf("port = %d, g_timeout_sec = %d\n", g_port, g_timeout_sec);

    signal_handle();

#if TEST_UNPACK
    memset(&unpack_setting, 0, sizeof(unpack_setting_t));
    // #define DEFAULT_PACKAGE_MAX_LENGTH  (1 << 21)   // 2M
    unpack_setting.package_max_length = DEFAULT_PACKAGE_MAX_LENGTH;
    unpack_setting.mode = UNPACK_BY_DELIMITER;
    unpack_setting.delimiter[0] = '}';	// 仅适应无嵌套json
    unpack_setting.delimiter_bytes = 1;
#endif

    HV_MEMCHECK;
    hlog_disable();

    loop = hloop_new(0);
    hio_t* listenio = hloop_create_tcp_server(loop, host, g_port, on_accept);
    if (listenio == NULL) {
        return -20;
    }
    LOG_I("listenfd=%d, V%s", hio_fd(listenio), hv_version());

    hloop_run(loop);
    hloop_free(&loop);
    end_handle();
    return 0;
}
  • 上报数据一般会超过4G模块的组包长度, 必须组包, node.js实现
function tcp_server_run() {
	net.createServer( (sock) => {
		log.log1('new tcp_connect: ' + sock.remoteAddress + ':' + sock.remotePort);

		let g_buf = Buffer.alloc(0);	// must first alloc
		let l_all = 0;
		let offset = 0;

		function json_delimit(chunk, delimiter) {
			let data = Buffer.concat([g_buf, chunk]);
		    let position;
		    let ret = [];

		    while ((position = data.indexOf(delimiter)) !== -1) {
				const p1 = data.indexOf('{');	// 0715, test/test_delimit.js
		    	if (p1 !== -1) {
					ret.push(data.slice(p1, position + delimiter.length));
		    	}
				data = data.slice(position + delimiter.length);
		    }

		    g_buf = data;	// must save g_buf
		    return ret;
		}

		// data is buffer
		sock.on('data', async(data) => {
			n2++;
			const len = data.length;
	        let s1 = data.toString('utf8');
	        let s2 = data.toString('hex');
	        let len1 = s1.length;
	        log.log(len1, s1);
	        // log.log(len1, s1, s2);

	        if (len1 > 0) {
	        	const ret = json_delimit(data, '}');

		    	for (let i=0, l=ret.length; i<l; i++) {
		    		const buf = ret[i];
					const len = buf.length;
					let s;
					if (g_data_type == 1) {
						s = buf.toString('utf8');
					}
					else {
						s = buf.toString('hex');
					}
					let s1 = sprintf('valid(%d): %s', len, s);
					log_file(s1);
			        // --- answer
		    		const res = await data_handle(buf);
		    		if (res && res.length > 0) {
		    			// await delay_ms(1000);
				    	sock.write(res);
				    	if (g_data_type == 1) {
			    			s1 = sprintf('tx(%d): %s', res.length, res.toString('utf8'));
				    	}
				    	else {
			    			s1 = sprintf('tx(%d): %s', res.length, res.toString('hex'));
				    	}
		    			log_file(s1);
		    		}
		    	}
	        }
	    });

	    if (timeout_ms > 0) {
		    sock.setTimeout(timeout_ms);	// ms
			sock.on('timeout', () => {
				log.log1('socket timeout! ' + sock.remoteAddress + ':' + sock.remotePort);
				sock.end();
			});
	    }

	    sock.on('close', (data) => {
	        log.log1('tcp_close: ' + sock.remoteAddress + ' ' + sock.remotePort);
	    });
	    sock.on('end', () => {
	        log.log1('tcp_end: ' + sock.remoteAddress + ' ' + sock.remotePort);
	    });
	    sock.on('error', err => {
	        log.log1(err);
	    });

	}).listen(PORT, HOST);

	log.log2('Server listening on ' + HOST +':'+ PORT);
}

4.3 平台后端, 数据库为mongodb, node.js + express, 不班门弄斧了

4.4 平台前端

4.4.1 平房仓显示
  • 设置电缆参数 在这里插入图片描述

  • 数据总览 在这里插入图片描述

  • 数据报表显示 在这里插入图片描述

  • html

<table class="table table-hover table-condensed table-bordered table-header margin-top-0 margin-bottom-0" ng-if="$ctrl.result.granaryId.type===1" ng-repeat="y in $ctrl.granaryCableY">
        <thead>
            <tr>
                <th class="text-center" width="6%">\ {{ 'lab.cable-number' | T }}</th>
                <th class="text-center" width="{{ 94/($ctrl.result.granaryId.cableNumberInLongDirection) }}%" ng-repeat="x in $ctrl.granaryCableX.slice($ctrl.result.granaryId.cableNumberInLongDirection * (y-1), $ctrl.result.granaryId.cableNumberInLongDirection * y)">
                    C{{x}}
                </th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="z in $ctrl.granaryCableZ">
                <th class="text-center">{{ z }}</th>
                <td class="text-center" ng-repeat="x in $ctrl.granaryCableX.slice($ctrl.result.granaryId.cableNumberInLongDirection * (y-1), $ctrl.result.granaryId.cableNumberInLongDirection * y)"
                    ng-class="{'bg-red': $ctrl.granaryPoints['C'+ x +'L' + z].toFixed(1) === $ctrl.granaryStats.max.value.toFixed(1),
                             'bg-green': $ctrl.granaryPoints['C'+ x +'L' + z].toFixed(1) === $ctrl.granaryStats.min.value.toFixed(1)}">
                    <span ng-if="$ctrl.granaryPoints['C'+x+'L'+z] != undefined">{{ $ctrl.granaryPoints['C'+x+'L'+z].toFixed(1)}}</span>
                    <span ng-if="$ctrl.granaryPoints['H'+x+'L'+z] != undefined">(°C)/ {{ $ctrl.granaryPoints['H'+x+'L'+z].toFixed(1)}}(%)</span>
                    <span ng-if="$ctrl.granaryPoints['W'+x+'L'+z] != undefined">/ {{ $ctrl.granaryPoints['W'+x+'L'+z].toFixed(1)}}(%)</span>
                </td>
            </tr>
        </tbody>
    </table>
  • 数据2.5D显示 在这里插入图片描述

  • 数据3D显示 在这里插入图片描述

4.4.2 圆筒仓显示
  • 设置电缆参数 在这里插入图片描述

  • 数据2.5D显示 在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值