获取/上报类指令

获取服务器地址 (请求/响应)

1
2
3
4
{
cmd: '获取服务器地址', // 指令 (String)
data: {}, // 数据
}
1
2
3
4
5
6
7
{
cmd: '获取服务器地址', // 指令 (String)
data: {
host: 'localhost', // 服务器域名或IP
port: 23333, // 服务器端口
},
}

获取设备信息 (请求/响应)

1
2
3
4
{
cmd: '获取设备信息', // 指令 (String)
data: {}, // 数据
}
1
2
3
4
5
6
7
8
{
cmd: '获取设备信息', // 指令 (String)
data: {
// 指令数据
id: '1aa169628add4c1e965f5bc69e2e9dbb', // 设备唯一ID (String)
version: 1, // 固件版本 (Number)
},
}

获取设备上报间隔 (请求/响应)

1
2
3
4
{
cmd: '获取设备上报间隔',
data: {},
}
1
2
3
4
5
6
7
8
9
10
{
cmd: '获取设备上报间隔',
data: {
// 上报间隔 ({})
heartbeatInterval: 5000, // 心跳上报间隔 (Number)
dataInterval: 10000, // 数据上报间隔 (Number)
uartInterval: 1000, // 串口发送指令间隔 (Number)
alarmInterval: 0, // 告警/火警上报间隔 (Number)
},
}

获取设备协议配置 (请求/响应)

1
2
3
4
{
cmd: '获取设备协议配置',
data: {},
}
1
2
3
4
5
6
7
{
cmd: '获取设备协议配置',
data: {
type: 'modbus', // 协议类型 ('modbus' || 'gb26875')
subtype: '源诚消防水源水压监测设备', // 协议子类型 (String)
},
}

获取设备告警阈值 (请求/响应)

1
2
3
4
{
cmd: '获取设备告警阈值',
data: {},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
cmd: '获取设备告警阈值',
data: {
alarmValues: [
// 告警阈值, 为空数组时代表不会产生告警 ({}[])
{
key: '压力', // 字段名称 (String)
alarmType: 'alarm', // 告警类型 ('fireAlarm' | 'alarm' | 'operate')
alarmRawRanges: [
// 触发告警范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[0, 5],
[20],
],
filters: [
// 条件过滤器 ({}[])
{
key: '单位', // 条件字段名称 (String)
rawRanges: [
// 达成条件范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[4, 4],
],
},
],
},
],
},
}

获取设备数据 (请求/响应)

1
2
3
4
{
cmd: '获取设备数据',
data: {},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
cmd: '获取设备数据',
data: {
raw: [0, 1, 2, 3], // 原始数据 (Number[])
parsed: {
// 解析数据 ({})
数据单元类型: '上传建筑消防设施系统配置情况',
系统类型: '火灾报警系统',
系统地址: 0,
系统说明长度: 0,
系统配置说明: '系统配置说明',
时间: '2021-10-12 15:43:00',
}
},
}

上报设备数据 (响应)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
cmd: '上报设备数据',
data: {
raw: [0, 1, 2, 3], // 原始数据 (Number[])
parsed: {
// 解析数据 ({})
数据单元类型: '上传建筑消防设施系统配置情况',
系统类型: '火灾报警系统',
系统地址: 0,
系统说明长度: 0,
系统配置说明: '系统配置说明',
时间: '2021-10-12 15:43:00',
}
},
}

上报告警/火警数据 (响应)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
cmd: '上报告警/火警数据',
data: {
raw: [0, 1, 2, 3], // 原始数据
parsed: {
// 解析数据 ({})
数据单元类型: '上传建筑消防设施系统配置情况',
系统类型: '火灾报警系统',
系统地址: 0,
系统说明长度: 0,
系统配置说明: '系统配置说明',
时间: '2021-10-12 15:43:00',
},
alarms: [
// 告警数组 ({}[])
{
key: '压力', // 字段名称 (String)
alarmType: 'alarm', // 告警类型 ('fireAlarm' | 'alarm' | 'operate')
alarmRawRanges: [
// 触发告警范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[0, 5],
[20],
],
filters: [
// 条件过滤器 ({}[])
{
key: '单位', // 条件字段名称 (String)
rawRanges: [
// 达成条件范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[4, 4],
],
},
],
},
]
},
}

获取 modbus 协议子类型 (请求/响应)

1
2
3
4
{
cmd: '获取modbus协议子类型',
data: {},
}
1
2
3
4
5
6
7
8
9
10
11
12
{
cmd: '获取modbus协议子类型',
data: {
modbusSubtypes: [
// modbus 协议的子类型列表 ({}[])
{
name: '源诚消防水源水压监测设备', // 子协议名称
version: 1, // 子协议版本
},
],
},
}

获取 gb26875 协议子类型 (请求/响应)

1
2
3
4
{
cmd: '获取gb26875协议子类型',
data: {},
}
1
2
3
4
5
6
7
8
9
10
11
12
{
cmd: '获取gb26875协议子类型',
data: {
gb26875Subtypes: [
// gb26875 协议的子类型列表 ({}[])
{
name: '默认', // 子协议名称
version: 1, // 子协议版本
},
],
},
}

写入/删除类指令

写入服务器地址 (请求/响应)

1
2
3
4
5
6
7
{
cmd: '写入服务器地址', // 指令 (String)
data: {
host: 'localhost', // 服务器域名或IP
port: 23333, // 服务器端口
},
}
1
2
3
4
5
6
{
cmd: '写入服务器地址', // 指令 (String)
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入设备信息 (请求/响应)

1
2
3
4
5
6
{
cmd: '写入设备信息',
data: {
version: 1, // 固件版本 (Number)
},
}
1
2
3
4
5
6
{
cmd: '写入设备信息',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入设备上报间隔 (请求/响应)

1
2
3
4
5
6
7
8
9
{
cmd: '写入设备上报间隔',
data: {
heartbeatInterval: 5000, // 心跳上报间隔 (Number)
dataInterval: 10000, // 数据上报间隔 (Number)
uartInterval: 1000, // 串口发送指令间隔 (Number)
alarmInterval: 0, // 告警/火警上报间隔 (Number)
},
}
1
2
3
4
5
6
{
cmd: '写入设备上报间隔',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入设备协议设置 (请求/响应)

1
2
3
4
5
6
7
{
cmd: '写入设备协议设置',
data: {
type: 'modbus', // 协议类型 ('modbus' || 'gb26875')
subtype: '源诚消防水源水压监测设备', // 协议子类型 (String)
},
}
1
2
3
4
5
6
{
cmd: '写入设备协议配置',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入设备告警阈值 (请求/响应)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
cmd: '写入设备告警阈值',
data: {
alarmValues: [
// 告警阈值, 为空数组时代表不会产生告警 ({}[])
{
key: '压力', // 字段名称 (String)
alarmType: 'alarm', // 告警类型 ('fireAlarm' | 'alarm' | 'operate')
alarmRawRanges: [
// 触发告警范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[0, 5],
[20],
],
filters: [
// 条件过滤器 ({}[])
{
key: '单位', // 条件字段名称 (String)
rawRanges: [
// 达成条件范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[4, 4],
],
},
],
},
],
},
}
1
2
3
4
5
6
{
cmd: '写入设备告警阈值',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入 modbus 协议子类型 (请求/响应)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
{
cmd: '写入modbus协议子类型',
data: {
subtypes: [
// 需要写入的 modbus 协议子类型 ({}[])
{
name: '源诚消防水源水压监测设备', // 子协议名称
version: 1, // 子协议版本
commands: [
// 子协议指令
{
name: '获取数据', // 指令名称 (String)
data: [1, 3, 0, 0, 0, 5, 133, 201], // 指令内容 (Number[])
},
],
getDataCommand: '获取数据', // 用于获取数据的指令, 空字符串则不启用自动数据上报功能 (String)
keys: [
// 解析 ({}[])
{
key: '单位', // 字段名称 (String)
position: [4, 5], // 解析时, 此字段在 MODBUS 数据中的下标, 从0开始 (Number)
high: true,
switch: [
// 转换器 ({}[])
{
range: [0, 0], // 用数组表示范围 [开始, 结束] ([Number, Number])
value: 'Mpa', // 转换的值 (String || Number)
},
{
range: [1, 1],
value: 'Kpa',
},
{
range: [2, 2],
value: 'Pa',
},
{
range: [3, 3],
value: 'Bar',
},
{
range: [4, 4],
value: 'Mbar',
},
{
range: [5, 5],
value: 'kg/cm²',
},
{
range: [6, 6],
value: 'psi',
},
{
range: [7, 7],
value: 'mh²o',
},
{
range: [8, 8],
value: 'mmh²o',
},
],
},
{
key: '小数点',
position: [6, 7],
high: true,
switch: [
{
range: [1, 1],
value: 0.1,
},
{
range: [2, 2],
value: 0.01,
},
{
range: [3, 3],
value: 0.001,
},
],
},
{
key: '压力',
position: [8, 9],
high: true,
switch: [],
},
],
}
]
}
}
1
2
3
4
5
6
{
cmd: '写入modbus子类型',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入监听设备数据日志状态 (请求/响应)

1
2
3
4
5
6
{
cmd: '写入监听设备数据日志状态',
data: {
listening: true, // 是否监听设备数据日志 (Boolean)
},
}
1
2
3
4
5
6
{
cmd: '写入监听设备数据日志状态',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

系统类指令

心跳 (响应)

1
2
3
4
{
cmd: '心跳',
data: {},
}

答应 (请求)

1
2
3
4
5
6
{
cmd: '答应',
data: {
serialNumber: 0 // 流水编号 (Number)
},
}

发送数据到设备 (请求/响应)

1
2
3
4
5
6
{
cmd: '发送数据到设备',
data: {
bytes: [1, 3, 0, 0, 0, 0, 0, 0], // 发送给设备的数据
},
}
1
2
3
4
5
6
{
cmd: '发送数据到设备',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

设备数据日志 (响应)

1
2
3
4
5
6
7
{
cmd: '设备数据日志',
data: {
time: 233, // 设备本地时间戳
bytes: [1, 3, 0, 0, 0, 0, 0, 0], // 设备实时回传原始数据
},
}

错误类指令

指令错误 (响应)

1
2
3
4
5
6
{
cmd: '指令错误',
data: {
message: '指令错误' // 错误消息
},
}

jpush-expo-config-plugin

一个极光推送的 Expo 自动配置插件, 免去配置原生项目的繁琐步骤

此软件包不能在 “Expo Go” 应用程序中使用

1. 安装

1
2
3
npm install jpush-expo-config-plugin --save
或者
yarn add jpush-expo-config-plugin

注意: 如果项目里没有 jpush-react-native、jcore-react-native, 需要安装

1
2
3
npm install jpush-react-native jcore-react-native --save
或者
yarn add jpush-react-native jcore-react-native

2. 配置

安装此 npm 包后, 请将 配置插件 添加到 app.json 或 app.config.js 的 插件数组 :

app.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"expo": {
"plugins": [
[
"jpush-expo-config-plugin",
{
"appKey": "你的极光推送AppKey",
"channel": "你的极光推送Channel"
}
]
]
}
}

接下来, 按照 “添加自定义 Native 代码” 指南中的描述重新构建应用程序

1
expo prebuild

expo 没办法使用 react-native-amap3d (尝试过, 会报错 module null), 需要 expo eject 变为 react-native 项目, 进行 react-native link (从 v2.0.0 开始支持 RN 的 autolinking,不再需要手动配置。), expo eject 会导致 expo start 等功能不能用.
萌新前端, 请多多包涵!~OWO

初始化 Expo 项目

如果没有安装 expo/cli 先 npm i -g expo/cliyarn global add expo/cli 进行安装

1
2
3
4
5
$ expo init

What would you like to name your app? (你想为你的应用命名什么)

Choose a template (选择模板)

Eject Expo 项目

1
2
3
4
5
6
7
$ npm run eject
or
$ yarn eject

What would you like your Android package name to be? (您希望 Android 包名是什么?)

What would you like your iOS bundle identifier to be? (您希望 iOS bundle identifier 是什么?)

初始化项目环境

如果直接使用 npm run androidyarn android 会报错 error Failed to install the app. Make sure you have the Android development environment set up (错误:无法安装应用程序。确保您已经设置了 Android 开发环境)

需要使用 Android Studio 先打开此项目下的 android 文件夹, 它会自动帮你设置环境!

启动项目

1
2
3
$ npm run android
or
$ yarn android

我所在的 Expo 版本有个坑, 如果直接使用 npm run androidyarn android 会报错 (如果启动成功, 直接略过此处)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xxx/my-app/android/app/src/main/java/com/runomeow/myapp/MainActivity.java:19: 错误: 对于show(MainActivity,SplashScreenImageResizeMode,boolean), 找不到合适的方法
SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, false);
^
方法 SplashScreen.show(Activity,SplashScreenViewProvider,Class<? extends ViewGroup>,boolean,Function0<Unit>,Function1<? super String,Unit>)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenViewProvider,Class<? extends ViewGroup>,boolean,Function0<Unit>)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenViewProvider,Class<? extends ViewGroup>,boolean)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenImageResizeMode,Class<? extends ViewGroup>,boolean,SplashScreenViewProvider,Function0<Unit>,Function1<? super String,Unit>)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenImageResizeMode,Class<? extends ViewGroup>,boolean,SplashScreenViewProvider,Function0<Unit>)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenImageResizeMode,Class<? extends ViewGroup>,boolean,SplashScreenViewProvider)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenImageResizeMode,Class<? extends ViewGroup>,boolean)不适用
(实际参数列表和形式参数列表长度不同)

issues#9047

结合 issues 修改 /android/app/src/main/java/com/runomeow/myapp/MainActivity.java:19 就可以了

1
2
3
4
5
找到
SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, false);

改为
SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, ReactRootView.class, false);

使用 react-native-amap3d

注意, 一定要设置 <MapView /> 宽高才能显示. react-native-amap3d 不支持 x86 平台, 模拟器运行会闪退, 真机运行正常!

1
2
3
$ npm install react-native-amap3d
or
$ yarn add react-native-amap3d

修改 App.tsx

1
2
3
4
5
6
7
8
9
10
11
12
import { MapView } from 'react-native-amap3d';

<MapView
center={{
latitude: 3,
longitude: 106,
}}
style={{
width: 233,
height: 233,
}}
/>;

一直用别人的有点没意思 QAQ
摸鱼自己写一个试试
渣渣代码慎入!!!

演示图片

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const list = document.getElementById('list'); // 获取元素
list.addEventListener('click', () => scrollAnimate(list, 200, 1000, 120)); // 绑定事件

// scrollElement 列表元素
// newScrollTop 移动到什么位置
// direction 动画时间
// fps 帧率
function scrollAnimate(scrollElement, newScrollTop, direction, fps = 60) {
const oldScrollTop = scrollElement.scrollTop; // 原来的位置
const diffScrollTop = newScrollTop - oldScrollTop;// 相差多少
const tickTime = 1000 / fps; // 根据FPS计算每一帧延迟
const num = 1 - 60 / fps / (direction * 0.01); // 根据FPS计算每一帧移动百分之多少
let rate = num; // 当前百分比(默认移动一帧)

const timer = setInterval(() => {
const nowScrollTop = oldScrollTop + diffScrollTop * (1 - rate); // 原来的位置 + 差多少 * 百分比 (0 + 200 * (1 - 0.95))
if (Math.abs(newScrollTop - nowScrollTop) > 1) {
// 如果位置绝对值大于1px, 移动滚动条
scrollElement.scrollTo(0, nowScrollTop);
} else {
// 清除时钟, 直接设为需要移动到的位置
clearInterval(timer);
scrollElement.scrollTo(0, newScrollTop);
}
rate *= num; // 计算百分比(0.95 * 0.95 不断减少)
}, tickTime);
}

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Runo</title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<div id="list">
<div class="list-item">
<div class="list-title">
<div class="list-left">
<span>喵了个咪喵了个咪</span>
</div>
<div class="list-right">
<span>2020-7-24 10:51:49</span>
</div>
</div>
<div class="list-content">
<span>喵呜喵呜喵呜</span>
</div>
</div>
<div class="list-item">
<div class="list-title">
<div class="list-left">
<span>喵了个咪喵了个咪</span>
</div>
<div class="list-right">
<span>2020-7-24 10:51:49</span>
</div>
</div>
<div class="list-content">
<span>喵呜喵呜喵呜</span>
</div>
</div>
<div class="list-item">
<div class="list-title">
<div class="list-left">
<span>喵了个咪喵了个咪</span>
</div>
<div class="list-right">
<span>2020-7-24 10:51:49</span>
</div>
</div>
<div class="list-content">
<span>喵呜喵呜喵呜</span>
</div>
</div>
<div class="list-item">
<div class="list-title">
<div class="list-left">
<span>喵了个咪喵了个咪</span>
</div>
<div class="list-right">
<span>2020-7-24 10:51:49</span>
</div>
</div>
<div class="list-content">
<span>喵呜喵呜喵呜</span>
</div>
</div>
</div>
<script src="./main.js"></script>
</body>
</html>

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
body {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}

#list {
width: 750px;
height: 750px;
overflow-y: auto;

background-color: #f0f0f0;
border-radius: 10px;
}
.list-item {
position: sticky;
top: 10px;
margin: 10px;
padding: 10px;
display: flex;
align-items: center;
flex-direction: column;
height: 100px;
overflow: hidden;
box-sizing: border-box;

background-color: white;
border: 1px solid black;
border-radius: 10px;
}
.list-item:last-of-type {
margin-bottom: 640px;
}
.list-title, .list-content {
width: 100%;
}
.list-title {
display: flex;
align-items: center;
justify-content: space-between;

}
.list-left {

}
.list-right {

}
.list-content {
overflow: hidden;
}

每次更新文章都要生成一次就太麻烦啦!
于是利用 GitHub 的 Webhooks 做一个自动化部署
大概说一下搭建的经过~

Runo+ Blog
GitHub Webhooks

安装以下几个包

  • express 服务端
  • node-cmd 用于执行命令
  • crypto 校验 sha1(可选)
  • dotenv 加载.env(可选)
1
npm install express node-cmd crypto dotenv --save

在 hexo 项目下新建一个 webhooks.js

hmmm 萌新代码写的不好 见谅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const express = require('express');
const app = express();
const nodeCmd = require('node-cmd');
const crypto = require('crypto');
require('dotenv').config();

// JSON支持
app.use(express.json());

// 监听POST请求
app.post('/apis/update', (req, res) => {
// github通过秘钥+请求体算出来的sha1(如果不需要校验的话 下面的校验过程可不要)
const sign = req.headers['x-hub-signature'];
const sha1 =
'sha1=' +
crypto
.createHmac('sha1', process.env.GITHUB_WEBHOOKS_SECRET)
.update(JSON.stringify(req.body))
.digest()
.toString('hex');
if (!sign || sign !== sha1) return res.send('secret error');
// 使用 node-cmd 执行 git pull 拉取仓库
nodeCmd.get('git pull', (err, data) => {
if (!err) {
console.log(data);
// 使用 node-cmd 执行 npx hexo generate 生成页面
nodeCmd.get('npx hexo generate', (err, data) => {
if (err) console.error(err);
else console.log(data);
});
} else console.error(err);
});
res.send('success');
});

// 监听端口
app.listen(process.env.GITHUB_WEBHOOKS_PORT, () =>
console.log('listening ' + process.env.GITHUB_WEBHOOKS_PORT)
);

进行服务器的部署

  1. 将代码提交后, 在服务器 git clone

  2. 执行 npm run build 会在 public 文件夹生成静态网页, 再使用 nginx 等web服务器, 把域名指向 public 文件夹

  3. 使用 pm2 start 启动 webhooks.js, 服务端就算是部署好啦!

为仓库添加 Webhook

  1. 打开 GitHub 仓库

  2. 依次点击 Setting → Webhooks → Add webhook

设置 Webhook

  1. Payload URL: 需要响应的地址 (例如 https://runo.plus/xxx)

  2. Content type: 请求的内容类型 (选择 application/json)

  3. Secret: 秘钥 (需要与服务器上的一样! GITHUB_WEBHOOKS_SECRET, 如果不需要校验则省略)

  4. 选择 Just the push event. (仓库接到推送后触发)

  5. 勾选 Active

  6. 点击 Add webhook 完成添加

提交一次仓库试试效果owo~