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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/**
 * 配置服务模块
 * 负责配置的校验、保存和应用,包括各种配置项的规则验证和回调处理
 */
import utils from '../common/utils/utils.js'
import config from '../../dxmodules/dxConfig.js'
import mqtt from '../../dxmodules/dxMqtt.js'
import std from '../../dxmodules/dxStd.js'
import ntp from '../../dxmodules/dxNtp.js'
import common from '../../dxmodules/dxCommon.js'
import driver from '../driver.js'
import bus from '../../dxmodules/dxEventBus.js'
import mqttService from './mqttService.js'
import logger from '../../dxmodules/dxLogger.js'
const configService = {}
 
/**
 * IP地址校验正则
 * 匹配以点分十进制形式表示的 IP 地址,例如:192.168.0.1
 */
const ipCheck = v => /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(v)
 
/**
 * IP地址或域名校验正则(带端口)
 * 匹配IP地址或域名,可选择带有端口号
 */
const ipOrDomainCheckWithPort = v => /^(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})(:\d{1,5})?$/.test(v);
 
/**
 * 正整数校验正则
 */
const regpCheck = v => /^[1-9]\d*$/.test(v)
 
/**
 * 非负整数校验正则
 */
const regnCheck = v => /^([1-9]\d*|0{1})$/.test(v)
 
/**
 * 所有支持的配置项的检验规则以及设置成功后的回调
 * rule:校验规则,返回true校验成功,false校验失败
 * callback:配置修改触发回调
 */
const supported = {
    // 人脸配置
    face: {
        similarity: { rule: v => typeof v == 'number' && v >= 0 && v <= 1, callback: v => driver.face.faceUpdateConfig({ score: v }) },
        livenessOff: { rule: v => [0, 1].includes(v), callback: v => driver.face.faceUpdateConfig({ livingCheckEnable: v }) },
        livenessVal: { rule: v => typeof v == 'number' && v >= 0 && v <= 1, callback: v => driver.face.faceUpdateConfig({ livingScore: v }) },
        showNir: { rule: v => [0, 1].includes(v), callback: v => driver.capturer.showNir(v) },
        detectMask: { rule: v => [0, 1].includes(v), callback: v => driver.face.faceUpdateConfig({ detectMaskEnable: v }) },
        stranger: { rule: v => [0, 1, 2].includes(v) },
        voiceMode: { rule: v => [0, 1, 2].includes(v) },
        voiceModeDate: { rule: v => typeof v == 'string' },
    },
    // MQTT配置
    mqtt: {
        addr: { rule: ipOrDomainCheckWithPort },
        clientId: { rule: v => typeof v == 'string' },
        username: { rule: v => typeof v == 'string' },
        password: { rule: v => typeof v == 'string' },
        qos: { rule: v => [0, 1, 2].includes(v) },
        prefix: { rule: v => typeof v == 'string' },
        willtopic: { rule: v => typeof v == 'string' },
        onlinecheck: { rule: v => [0, 1, 2].includes(v) },
        timeout: { rule: regpCheck },
    },
    // 网络配置
    net: {
        type: { rule: v => [0, 1, 2, 4].includes(v) }, // 网络类型
        dhcp: { rule: v => [1, 2, 3].includes(v) }, // DHCP设置
        ip: { rule: ipCheck }, // IP地址
        gateway: { rule: ipCheck }, // 网关地址
        dns: { rule: v => !v.split(",").some(ip => !ipCheck(ip)) }, // DNS服务器
        mask: { rule: ipCheck }, // 子网掩码
        mac: { rule: v => typeof v == 'string' }, // MAC地址
        ssid: { rule: v => typeof v == 'string' }, // WiFi SSID
        psk: { rule: v => typeof v == 'string' }, // WiFi密码
    },
    // NTP时间同步配置
    ntp: {
        ntp: { rule: v => [0, 1].includes(v) }, // NTP开关
        server: { rule: ipCheck }, // NTP服务器
        gmt: { rule: v => typeof v == 'number' && v >= 0 && v <= 24, callback: v => ntp.updateGmt(v) }, // 时区设置
    },
    // 系统配置
    sys: {
        uuid: { rule: v => typeof v == 'string' }, // 设备UUID
        model: { rule: v => typeof v == 'string' }, // 设备型号
        mode: { rule: v => typeof v == 'string', callback: v => setMode(v) }, // 设备模式
        sn: { rule: v => typeof v == 'string' }, // 设备序列号
        version: { rule: v => typeof v == 'string' }, // 系统版本
        releaseDate: { rule: v => typeof v == 'string' }, // 发布日期
        nfc: { rule: v => [0, 1].includes(v) }, // NFC开关
        nfcIdentityCardEnable: { rule: v => [1, 3].includes(v) }, // 云证开关
        pwd: { rule: v => [0, 1].includes(v) }, // 密码开门开关
        strangerImage: { rule: v => [0, 1].includes(v) }, // 陌生人保存图片开关
        accessImageType: { rule: v => [0, 1].includes(v) }, // 通行图片类型
        interval: { rule: regnCheck }, // 系统间隔设置
    },
    // 门禁配置
    access: {
        relayTime: { rule: regpCheck }, // 继电器动作时间
        offlineAccessNum: { rule: regpCheck }, // 离线开门次数
        tamperAlarm: { rule: v => [0, 1].includes(v) }, // 防拆报警开关
    },
    // 基础配置
    base: {
        firstLogin: { rule: v => [0, 1].includes(v) }, // 首次登录标志
        brightness: { rule: regnCheck, callback: v => driver.face.setDisplayBacklight(v) }, // 屏幕亮度
        brightnessAuto: { rule: v => [0, 1].includes(v) }, // 自动亮度开关
        showIp: { rule: v => [0, 1].includes(v), callback: v => driver.screen.hideIp(v) }, // 是否显示IP地址
        showSn: { rule: v => [0, 1].includes(v), callback: v => driver.screen.hideSn(v) }, // 是否显示序列号
        appMode: { rule: v => [0, 1].includes(v), callback: v => driver.screen.appMode(v) }, // 应用模式
        screenOff: { rule: regnCheck, callback: v => bus.fire("screenManagerRefresh") }, // 熄屏时间
        screensaver: { rule: regnCheck, callback: v => bus.fire("screenManagerRefresh") }, // 屏幕保护
        volume: { rule: regnCheck, callback: v => driver.alsa.volume(v) }, // 音量设置
        password: { rule: v => typeof v == 'string' && v.length >= 8 }, // 管理员密码
        language: { rule: v => ["EN", "CN"].includes(v), callback: v => driver.screen.changeLanguage() }, // 语言设置
        showProgramCode: { rule: v => [0, 1].includes(v) }, // 是否显示程序代码
        showIdentityCard: { rule: v => [0, 1].includes(v) }, // 是否显示身份证信息
        luminanceWhite: { rule: v => typeof v == 'number' && v >= 0 && v <= 100, callback: v => driver.pwm.luminanceWhite(v) }, // 白光亮度
        luminanceNir: { rule: v => typeof v == 'number' && v >= 0 && v <= 100, callback: v => driver.pwm.luminanceNir(v) }, // 红外光亮度
    },
    // 密码通行配置
    passwordAccess: {
        passwordAccess: { rule: v => [0, 1].includes(v) }, // 密码通行开关
        emergencyPassword: { rule: v => typeof v == 'string' && v.length >= 8 }, // 应急开仓密码
    },
    // HTTP配置
    http: {
        gas: { rule: v => typeof v == 'string' }, // HTTP_气体
        status: { rule: v => typeof v == 'string' }, // HTTP_状态
        safeInputAccess: { rule: v => typeof v == 'string' } // HTTP接口路径
    },
    // 仓廒名称
    houseName: {},
    // 库区名称
    GranaryName: {},
    // 更新配置
    update: {
        gasTime: { rule: regpCheck }, // 气体浓度更新时间(秒)
        statusTime: { rule: regpCheck } // 状态信息更新时间(秒)
    }
}
 
/**
 * 需要重启的配置项
 */
const needReboot = ["sys.nfc", "sys.nfcIdentityCardEnable", "ntp"]
configService.needReboot = needReboot
 
/**
 * 修改设备模式
 * @param {string} params - 模式参数
 */
function setMode(params) {
    common.systemWithRes(`echo 'app' > /etc/.app_v1`, 2)
    common.setMode(params)
}
 
/**
 * 配置json校验并保存
 * @param {object} data - 配置json对象
 * @returns {boolean|string} true表示校验并保存成功,string表示错误信息
 */
configService.configVerifyAndSave = function (data) {
    let netFlag = false // 网络配置标志
    let mqttFlag = false // MQTT配置标志
    let isReboot = false // 是否需要重启
    
    for (const key in data) {
        if (key == 'net') {
            netFlag = true
        }
        if (key == 'mqtt') {
            mqttFlag = true
        }
        
        // 检查配置组是否支持
        if (!supported[key]) {
            return key + " not supported"
        }
        
        const item = data[key];
        if (typeof item != 'object') {
            // 顶层配置项,直接保存
            config.set(key, item)
            continue
        }
        
        // 检查是否需要重启
        if (needReboot.includes(key)) {
            isReboot = true
        }
        
        for (const subKey in item) {
            let option = supported[key][subKey]
            if (isEmpty(option)) {
                return subKey + " not supported"
            }
            
            const value = item[subKey];
            
            // 检查是否需要重启
            if (needReboot.includes(key + "." + subKey)) {
                isReboot = true
            }
            
            // 校验配置值
            if (!option.rule || option.rule(value)) {
                // 没有校验规则默认校验通过
                config.set(key + "." + subKey, value)
                if (option.callback) {
                    // 执行配置设置回调
                    option.callback(value)
                }
            } else {
                return value + " check failure"
            }
        }
    }
    
    // 保存配置
    config.save()
    
    // 检查需要重启的配置,3秒后重启
    if (isReboot) {
        driver.screen.upgrade({ title: "confirm.restartDevice", content: "confirm.restartDeviceDis" })
        common.asyncReboot(3)
    }
    
    // 处理网络配置变更
    if (netFlag) {
        // 等待 1 秒 因为需要返回 mqtt
        std.setTimeout(() => {
            bus.fire("switchNetworkType", config.get("net.type"))
        }, 1000);
    }
    
    // 处理MQTT配置变更
    if (mqttFlag) {
        let option = { 
            mqttAddr: config.get("mqtt.addr"), 
            clientId: config.get('mqtt.clientId') + std.genRandomStr(3), 
            subs: mqttService.getTopics(), 
            username: config.get("mqtt.username"), 
            password: config.get("mqtt.password"), 
            qos: config.get("mqtt.qos"), 
            willTopic: config.get("mqtt.willTopic"), 
            willMessage: JSON.stringify({ "uuid": config.get("sys.uuid") }) 
        }
        logger.info("重启mqtt", JSON.stringify(option))
        // 销毁 mqtt 重新 init
        bus.fire(mqtt.RECONNECT, option)
    }
    
    // 触发配置更新事件,通知其他模块配置已更新
    bus.fire('configUpdated')
    
    return true
}
 
/**
 * 判空函数
 * @param {*} value - 要判断的值
 * @returns {boolean} true表示值为空,false表示值不为空
 */
function isEmpty(value) {
    return value === undefined || value === null
}
 
export default configService