| | |
| | | |
| | | |
| | | // ✅ 正确写法(适用于 pkg 打包环境) |
| | | const fs = require('fs'); |
| | | const path = require('path'); |
| | | |
| | | |
| | | // 获取 exe 所在目录(兼容 pkg 打包后的 __dirname) |
| | | const appPath = process.pkg ? path.dirname(process.execPath) : __dirname; |
| | | const mqttConfigPath = path.join(appPath, 'mqtt.json'); |
| | | const aliyunConfigPath = path.join(appPath, 'aliyun.json'); |
| | | const httpConfigPath = path.join(appPath, 'httpConfig.json'); |
| | | const homeConfigPath = path.join(appPath, 'homeConfig.json'); |
| | | |
| | | const mqttConfig = JSON.parse(fs.readFileSync(mqttConfigPath, 'utf8')); |
| | | const aliyunConfig = JSON.parse(fs.readFileSync(aliyunConfigPath, 'utf8')); |
| | | const httpConfig = JSON.parse(fs.readFileSync(httpConfigPath, 'utf8')); |
| | | const homeConfig = JSON.parse(fs.readFileSync(homeConfigPath, 'utf8')); |
| | | |
| | | |
| | | console.log(aliyunConfig) |
| | | |
| | | |
| | | |
| | | const { initMqtt, publishMessage } = require('./mqttClient'); |
| | | |
| | | const net = require('net'); |
| | | const logger = require('./logger'); |
| | | const EventEmitter = require('events'); |
| | | const aliyunIot = require('aliyun-iot-device-sdk'); |
| | | const { getAliyunDeviceSecret } = require('./api'); |
| | | const toModel = require('./Strholp'); |
| | | const { publishMessage } = require('./mqttClient'); |
| | | const dataCache = require('./dataCache'); |
| | | const HttpServer = require('./httpServer'); |
| | | |
| | | // 初始化 MQTT(独立于阿里云) |
| | | initMqtt(mqttConfig); |
| | | |
| | | // ========== 自定义错误类(可选)========== |
| | | class CustomError extends Error { |
| | |
| | | |
| | | // ========== 常量配置 ========== |
| | | const MAX_BUFFER_SIZE = 500; // 缓冲区最大长度 |
| | | const RETRY_INTERVAL_MS = 10000; // 重试间隔 10s |
| | | const RETRY_INTERVAL_MS = 30000; // 重试间隔 30s |
| | | const KEEP_ALIVE_INTERVAL_MS = 60000; // 保活间隔 60s |
| | | const DEVICE_TIMEOUT_MS = 120000; // 设备无响应超时 2分钟 |
| | | |
| | |
| | | } |
| | | |
| | | handleDevice(socket) { |
| | | const deviceId = socket.remoteAddress + ':' + socket.remotePort; |
| | | // 处理 IPv6 映射 IPv4 地址 (::ffff:127.0.0.1 -> 127.0.0.1) |
| | | let remoteAddress = socket.remoteAddress; |
| | | if (remoteAddress.startsWith('::ffff:')) { |
| | | remoteAddress = remoteAddress.slice(7); // 移除 ::ffff: 前缀 |
| | | } |
| | | const deviceId = remoteAddress + ':' + socket.remotePort; |
| | | logger.info(`建立新连接: ${deviceId}`); |
| | | |
| | | const deviceInfo = { |
| | |
| | | const message = buffer.substring(startIdx, endIdx).trim(); |
| | | buffer = buffer.substring(endIdx + 2); // 移除已处理部分(含 \r\n) |
| | | |
| | | logger.info(`${deviceId} 接收到完整消息: ${message}`); |
| | | logger.info(`${deviceId} 接收到完整消息: ${randomLetters(20)}${message}${randomLetters(20)}`); |
| | | this.handleData(deviceId, message); |
| | | } |
| | | }); |
| | |
| | | if (deviceInfo.status === 'pending' || deviceInfo.status === 'invalid') { |
| | | // 握手阶段:在 'K' 和 'K0000' 之间切换(你的原始逻辑) |
| | | deviceInfo.lastSignal = deviceInfo.lastSignal === 'K' ? 'K0000' : 'K'; |
| | | logger.info(`重试发送 '${deviceInfo.lastSignal}' 给设备 ${deviceId}`); |
| | | logger.info(`重试发送 '${randomLetters(10)}${deviceInfo.lastSignal==='K'?'a':'b'}${randomLetters(10)}' 给设备 ${deviceId}`); |
| | | this.sendKeepAliveToDevice(deviceId); |
| | | } |
| | | }, RETRY_INTERVAL_MS); |
| | |
| | | |
| | | try { |
| | | deviceInfo.socket.write(`${deviceInfo.lastSignal}\r\n`); |
| | | logger.info(`发送信号 '${deviceInfo.lastSignal}' 给设备 ${deviceId}`); |
| | | logger.info(`发送信号 '${randomLetters(10)}${deviceInfo.lastSignal==='K'?'a':'b'}${randomLetters(10)}' 给设备 ${deviceId}`); |
| | | } catch (err) { |
| | | logger.error(`发送信号失败 ${deviceId}:`, err.message); |
| | | this.removeDevice(deviceId); |
| | |
| | | async handleData(deviceId, message) { |
| | | const deviceInfo = this.devices.get(deviceId); |
| | | if (!deviceInfo) return; |
| | | |
| | | deviceInfo.lastAck = Date.now(); |
| | | |
| | | try { |
| | | const masData = toModel(message); |
| | | const ipAddress = deviceId |
| | | const masData = toModel(message, ipAddress); |
| | | deviceInfo.iotDeviceNo = masData.n; |
| | | deviceInfo.masData = masData; |
| | | |
| | | |
| | | // ✅【新增】缓存数据到内存(按设备序号) |
| | | dataCache.setDeviceData(masData.n, masData); |
| | | |
| | | // ✅【核心改动】收到数据立即发 MQTT(不管阿里云) |
| | | if (mqttConfig.enabled) { |
| | | const topic = `${mqttConfig.defaultTopicPrefix}/${masData.n}`; |
| | | const payload = JSON.stringify({ |
| | | ...masData, |
| | | deviceId: deviceId, |
| | | timestamp: new Date().toISOString() |
| | | }); |
| | | publishMessage(topic, payload); |
| | | logger.info(`📡 已通过 MQTT 发送数据到 ${topic}`); |
| | | } |
| | | |
| | | if (deviceInfo.status !== 'valid') { |
| | | deviceInfo.status = 'valid'; |
| | | this.emit('validResponse', deviceId); |
| | | logger.info(`${deviceId} 首次有效响应,停止重试`); |
| | | this.stopRetryMechanism(deviceId); |
| | | // 成功后固定使用 'K0000' 保活(建议) |
| | | this.startKeepAlive(deviceId, deviceInfo.lastSignal); |
| | | await this.registerDevice(deviceId); |
| | | // 【可选】仅当阿里云启用时才注册并上报 |
| | | if (aliyunConfig.enabled) { |
| | | if (deviceInfo.status !== 'valid') { |
| | | logger.info(`${deviceId} 阿里云设备状态变为有效`); |
| | | deviceInfo.status = 'valid'; |
| | | this.stopRetryMechanism(deviceId); |
| | | logger.info(`${deviceId} 停止重试机制`); |
| | | this.startKeepAlive(deviceId, deviceInfo.lastSignal); |
| | | logger.info(`${deviceId} 启动保持连接机制`); |
| | | await this.registerAliyunDevice(deviceId); // 改名避免混淆 |
| | | } else { |
| | | this.postPropsToAliyun(deviceId); |
| | | } |
| | | } else { |
| | | logger.info(`${deviceId} 已注册,直接上报数据到阿里云`); |
| | | this.postPropsToDevice(deviceId); |
| | | // this.onDeviceDataReceived(deviceId, masData); |
| | | // 阿里云未启用,但已通过 MQTT 发送,无需其他操作 |
| | | if (deviceInfo.status !== 'valid') { |
| | | deviceInfo.status = 'valid'; |
| | | this.stopRetryMechanism(deviceId); |
| | | this.startKeepAlive(deviceId, deviceInfo.lastSignal); |
| | | logger.info(`${deviceId} 已完成握手(阿里云未启用)`); |
| | | } |
| | | } |
| | | } catch (err) { |
| | | logger.error(`${deviceId} 处理消息出错:`, err.message); |
| | | } |
| | | } |
| | | |
| | | async registerDevice(deviceId) { |
| | | async registerAliyunDevice(deviceId) { |
| | | const deviceInfo = this.devices.get(deviceId); |
| | | if (!deviceInfo || deviceInfo.iotDevice) return; |
| | | |
| | |
| | | logger.info(`${deviceId} 请求三元组,设备号: ${deviceInfo.iotDeviceNo}`); |
| | | const { data } = await getDeviceSYZ(deviceInfo.iotDeviceNo); |
| | | logger.info(`${deviceId} 三元组返回: ${JSON.stringify(data)}`); |
| | | const model=data.data |
| | | const model = data.data |
| | | if (model?.productKey && model?.deviceName && model?.deviceSecret) { |
| | | deviceInfo.iotDevice = aliyunIot.device({ |
| | | ProductKey: model.productKey, |
| | |
| | | } |
| | | } |
| | | |
| | | postPropsToDevice(deviceId) { |
| | | postPropsToAliyun(deviceId) { |
| | | const deviceInfo = this.devices.get(deviceId); |
| | | if (!deviceInfo?.iotDevice || !deviceInfo.masData) return; |
| | | |
| | | logger.info(`${deviceId} 阿里云上报属性: ${JSON.stringify(deviceInfo.masData)}`); |
| | | const props = deviceInfo.masData; |
| | | deviceInfo.iotDevice.postProps(props, (res) => { |
| | | if (res?.message === 'success') { |
| | | logger.info(`${deviceId} 上报属性成功`); |
| | | logger.info(`${deviceId} 阿里云上报属性成功`); |
| | | } else { |
| | | logger.error(`${deviceId} 上报属性失败:`, res?.message || 'unknown'); |
| | | logger.error(`${deviceId} 阿里云上报属性失败:`, res?.message || 'unknown'); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | onDeviceDataReceived(deviceId, data) { |
| | | const topic = `touxiji/${data.n}`; |
| | | const payload = JSON.stringify({ |
| | | ...data, |
| | | timestamp: new Date().toISOString() |
| | | }); |
| | | |
| | | try { |
| | | logger.info(`发布 MQTT 消息到主题 ${topic}: ${payload}`); |
| | | publishMessage(topic, payload); |
| | | } catch (error) { |
| | | logger.error(`MQTT 发布失败 ${topic}:`, error.message); |
| | | } |
| | | } |
| | | |
| | | removeDevice(deviceId) { |
| | |
| | | throw new CustomError("获取三元组失败", err); |
| | | } |
| | | } |
| | | // 生成随机字母字符串 |
| | | function randomLetters(length) { |
| | | const chars = 'abcdefghijklmnopqrstuvwxyz'; |
| | | let result = ''; |
| | | for (let i = 0; i < length; i++) { |
| | | result += chars.charAt(Math.floor(Math.random() * chars.length)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | |
| | | |
| | | // ========== 启动服务器 ========== |
| | | const manager = new DeviceManager(); // ✅ 单例! |
| | |
| | | manager.handleDevice(socket); |
| | | }); |
| | | |
| | | const PORT = process.env.PORT || 10961; |
| | | const PORT = homeConfig.socketPort || 10961; |
| | | server.listen(PORT, () => { |
| | | logger.info(`Socket 服务已启动,监听端口: ${PORT}`); |
| | | }); |
| | | logger.info(`Socket 服务已启动,监听超级端口: ${PORT}`); |
| | | }); |
| | | |
| | | // ========== 启动 HTTP 服务 ========== |
| | | const HTTP_PORT = process.env.HTTP_PORT || httpConfig.port || 8080; |
| | | const httpServer = new HttpServer(HTTP_PORT, httpConfig); |
| | | httpServer.start(); |