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显示