diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 9a2dc969..b813dba5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -63,7 +63,8 @@ "Bash(done)", "Bash(file /tmp/tile_*.png)", "Bash(for REF in \"http://localhost:8080/\" \"https://dev.fastbee.cn/\" \"https://www.tianditu.gov.cn/\")", - "Read(//tmp/**)" + "Read(//tmp/**)", + "Bash(node -e ' *)" ] } } diff --git a/yack-app/apis/modules/mqttTest.js b/yack-app/apis/modules/mqttTest.js new file mode 100644 index 00000000..65a64364 --- /dev/null +++ b/yack-app/apis/modules/mqttTest.js @@ -0,0 +1,42 @@ +const http = uni.$u.http; + +// 指令编码 +export function encode(params) { + return http.get('/iot/message/encode', { + params: params + }); +} + +// 指令解码 +export function decode(params) { + return http.get('/iot/message/decode', { + params: params + }); +} + +// 平台下发指令 +export function messagePost(data) { + return http.post('/iot/message/post', data); +} + +// 查询设备指令偏好设置列表 +export function preferencesList(params) { + return http.get('/iot/preferences/list', { + params: params + }); +} + +// 新增设备指令偏好设置 +export function addPreferences(data) { + return http.post('/iot/preferences', data); +} + +// 修改设备指令偏好设置 +export function editPreferences(data) { + return http.put('/iot/preferences', data); +} + +// 删除设备指令偏好设置 +export function delPreferences(data) { + return http.delete('/iot/preferences/' + data.id); +} diff --git a/yack-app/env.config.js b/yack-app/env.config.js index c6f8a021..1cfea6fa 100644 --- a/yack-app/env.config.js +++ b/yack-app/env.config.js @@ -1,5 +1,5 @@ // H5端开发和生产环境协议 -let protocalDev = "wss://"; +let protocalDev = "ws://"; let protocalProd = "wss://"; // 条件编译,微信端和移动端使用wxs协议 @@ -19,7 +19,7 @@ const CONFIG = { development: { officialWebUrl: 'http://localhost:8080/', baseUrl: 'http://localhost:8080/', - mqttServer: protocalDev + 'localhost/mqtt', + mqttServer: protocalDev + '47.121.180.229:8083/mqtt', decoderUrl: 'http://localhost:8080/', qqmapKey, xinzhiKey, diff --git a/yack-app/locale/en-US.json b/yack-app/locale/en-US.json index a476c7a2..c75992af 100644 --- a/yack-app/locale/en-US.json +++ b/yack-app/locale/en-US.json @@ -49,6 +49,8 @@ "navBar.updateDevice": "Device Update", "navBar.eventLog": "Event Log", "navBar.orderLog": "Instruction log", + "navBar.dataDebug": "Data Debug", + "navBar.commandBuilder": "Command Builder", "navBar.player": "Player", "common.add": "add", "common.delete": "Delete", @@ -240,6 +242,72 @@ "createBtn": "Create Device", "content": "This feature requires login to use. Do you want to log in?" }, + "debug": { + "title": "Data Debug", + "send": "Send", + "reveiceType": "Reply", + "reportType": "Report", + "sendType": "Send", + "hex": "HEX", + "json": "JSON", + "ascii": "ASCII", + "inputPlaceholder": "Please enter the content to send", + "typePlaceholder": "Select format", + "analysis": "Parse", + "analysised": "Parsed", + "name": "Name", + "identifier": "Identifier", + "value": "Value", + "more": "More", + "quickCommand": "Quick Commands", + "addCommand": "Add Command", + "editCommand": "Edit Command", + "slaveAddress": "Slave Address", + "issueType": "Command Type", + "functionCode": "Function Code", + "startAddress": "Start Register Address", + "registerCount": "Register Count", + "coilCount": "Coil Count", + "writeCoilValue": "Coil Value", + "writeRegisterValue": "Register Value", + "registerValue": "Register Value", + "reportValue": "Report Value", + "coilValue": "Coil Value", + "generateCommand": "Generate", + "generatedCommand": "Generated Command", + "saveAsCommand": "Save As Command", + "copy": "Copy", + "cancel": "Cancel", + "confirm": "Confirm", + "commandName": "Command Name", + "inputCommandName": "Please enter command name", + "commandContent": "Command Content", + "commandScope": "Command Scope", + "productCommand": "Product Command (shared by same product)", + "deviceCommand": "Device Command (this device only)", + "deleteCommand": "Delete Command", + "deleteConfirm": "Are you sure to delete this command?", + "reportCommand": "Device Report", + "issueCommand": "Platform Issue", + "code01": "01 Read Coils", + "code02": "02 Read Discrete Inputs", + "code03": "03 Read Holding Registers", + "code04": "04 Read Input Registers", + "code05": "05 Write Single Coil", + "code06": "06 Write Single Register", + "code15": "15 Write Multiple Coils", + "code16": "16 Write Multiple Registers", + "sendEmpty": "Please enter the content to send", + "sendFail": "Send failed", + "analysisEmpty": "No data to parse", + "analysisFail": "Parse failed", + "generateFail": "Generate failed", + "saveSuccess": "Saved successfully", + "saveFail": "Save failed", + "deleteSuccess": "Deleted successfully", + "deleteFail": "Delete failed", + "copySuccess": "Copied successfully" + }, "about": { "about": "about", "authorQQ": "Author QQ : 164770707", diff --git a/yack-app/locale/zh-CN.json b/yack-app/locale/zh-CN.json index 9bcf2f3c..0cf9d83e 100644 --- a/yack-app/locale/zh-CN.json +++ b/yack-app/locale/zh-CN.json @@ -46,6 +46,8 @@ "navBar.updateDevice": "设备修改", "navBar.eventLog": "事件日志", "navBar.orderLog": "指令日志", + "navBar.dataDebug": "数据调试", + "navBar.commandBuilder": "指令生成", "navBar.player": "播放器", "common.fastbee": "岩安测控", "common.add": "添加", @@ -241,6 +243,72 @@ "createBtn": "创建设备", "content": "该功能需要登录才可使用,是否去登录?" }, + "debug": { + "title": "数据调试", + "send": "发送", + "reveiceType": "回复", + "reportType": "上报", + "sendType": "下发", + "hex": "HEX", + "json": "JSON", + "ascii": "ASCII", + "inputPlaceholder": "请输入要发送的内容", + "typePlaceholder": "选择格式", + "analysis": "解析", + "analysised": "已解析", + "name": "参数名", + "identifier": "标识符", + "value": "参数值", + "more": "更多", + "quickCommand": "快捷指令", + "addCommand": "新建指令", + "editCommand": "编辑指令", + "slaveAddress": "从机地址", + "issueType": "指令类型", + "functionCode": "功能码", + "startAddress": "起始寄存器地址", + "registerCount": "寄存器个数", + "coilCount": "线圈个数", + "writeCoilValue": "线圈值", + "writeRegisterValue": "寄存器值", + "registerValue": "寄存器值", + "reportValue": "上报值", + "coilValue": "线圈值", + "generateCommand": "生成指令", + "generatedCommand": "生成的指令", + "saveAsCommand": "存为指令", + "copy": "复制", + "cancel": "取消", + "confirm": "确定", + "commandName": "指令名称", + "inputCommandName": "请输入指令名称", + "commandContent": "指令内容", + "commandScope": "指令范围", + "productCommand": "产品指令(同类产品通用)", + "deviceCommand": "设备指令(仅本设备)", + "deleteCommand": "删除指令", + "deleteConfirm": "确认删除该指令吗?", + "reportCommand": "设备上报", + "issueCommand": "平台下发", + "code01": "01 读线圈", + "code02": "02 读离散输入", + "code03": "03 读保持寄存器", + "code04": "04 读输入寄存器", + "code05": "05 写单个线圈", + "code06": "06 写单个寄存器", + "code15": "15 写多个线圈", + "code16": "16 写多个寄存器", + "sendEmpty": "请输入要发送的内容", + "sendFail": "发送失败", + "analysisEmpty": "暂无可解析的数据", + "analysisFail": "解析失败", + "generateFail": "生成失败", + "saveSuccess": "保存成功", + "saveFail": "保存失败", + "deleteSuccess": "删除成功", + "deleteFail": "删除失败", + "copySuccess": "复制成功" + }, "about": { "about": "关于", "authorQQ": "作者企鹅:164770707", diff --git a/yack-app/pages.json b/yack-app/pages.json index 9cdedb1f..c4c217e0 100644 --- a/yack-app/pages.json +++ b/yack-app/pages.json @@ -533,6 +533,26 @@ "enablePullDownRefresh": true } }, + { + "path": "home/device/debug/index", + "meta": { + "title": "%navBar.dataDebug%" + }, + "style": { + "navigationBarTitleText": "%navBar.dataDebug%", + "enablePullDownRefresh": false + } + }, + { + "path": "home/device/debug/edit", + "meta": { + "title": "%navBar.commandBuilder%" + }, + "style": { + "navigationBarTitleText": "%navBar.commandBuilder%", + "enablePullDownRefresh": false + } + }, { "path": "login/register", "style": { diff --git a/yack-app/pages/tabBar/home/index.vue b/yack-app/pages/tabBar/home/index.vue index ddc65e4f..8f17acea 100644 --- a/yack-app/pages/tabBar/home/index.vue +++ b/yack-app/pages/tabBar/home/index.vue @@ -242,7 +242,8 @@ pageSize: 10, groupId: 0, deviceName: null, - showChild: true + showChild: true, + entrance: 2 }, loadStatus: 'loadmore', // 加载更多 deviceList: [], // 设备列表 diff --git a/yack-app/pagesA/home/device/index.vue b/yack-app/pagesA/home/device/index.vue index 95d4ef01..334b0a60 100644 --- a/yack-app/pagesA/home/device/index.vue +++ b/yack-app/pagesA/home/device/index.vue @@ -331,12 +331,17 @@ this.deviceForm.rssid = message.rssid; } }; - //不是modbus不转发到子页面,其他设备的页面有回调方法 - if (this.isSubDev) { - /*发送设备上报到子模块*/ - if (topic.endsWith('ws/service')) { - console.log('接收到【设备数据上报】主题:', topic) - console.log('接收到【设备数据上报】主题:', message) + /*发送设备上报到数据调试页面*/ + if (topic.endsWith('ws/service')) { + console.log('接收到【设备数据上报】主题:', topic) + console.log('接收到【设备数据上报】主题:', message) + this.$bus.$emit("updateData", { + serialNumber: topics[2], + productId: this.deviceForm.productId, + data: message, + }) + //不是modbus不转发到子页面,其他设备的页面有回调方法 + if (this.isSubDev) { this.$bus.$emit("updateDeviceStatus", { serialNumber: topics[2], productId: this.deviceForm.productId, diff --git a/yack-app/pagesA/home/device/log/index.vue b/yack-app/pagesA/home/device/log/index.vue index 859b80a5..5d0f9c63 100644 --- a/yack-app/pagesA/home/device/log/index.vue +++ b/yack-app/pagesA/home/device/log/index.vue @@ -6,6 +6,8 @@ @click="handleEventLogClick"> + @@ -58,6 +60,17 @@ uni.navigateTo({ url: `/pagesB/home/device/log/order?deviceId=${this.deviceId}&serialNumber=${this.serialNumber}` }); + }, + handleDebugClick () { + const device = { + deviceId: this.deviceInfo.deviceId, + serialNumber: this.deviceInfo.serialNumber, + productId: this.deviceInfo.productId, + protocolCode: this.deviceInfo.protocolCode, + }; + uni.navigateTo({ + url: `/pagesB/home/device/debug/index?device=${encodeURIComponent(JSON.stringify(device))}` + }); } } }; diff --git a/yack-app/pagesB/home/device/debug/edit.vue b/yack-app/pagesB/home/device/debug/edit.vue new file mode 100644 index 00000000..0861df5f --- /dev/null +++ b/yack-app/pagesB/home/device/debug/edit.vue @@ -0,0 +1,615 @@ + + + + + + + \ No newline at end of file diff --git a/yack-app/pagesB/home/device/debug/index.vue b/yack-app/pagesB/home/device/debug/index.vue new file mode 100644 index 00000000..321ab823 --- /dev/null +++ b/yack-app/pagesB/home/device/debug/index.vue @@ -0,0 +1,655 @@ + + + + + + + \ No newline at end of file diff --git a/yack-app/utils/common.js b/yack-app/utils/common.js index f6a27579..88f53543 100644 --- a/yack-app/utils/common.js +++ b/yack-app/utils/common.js @@ -26,7 +26,7 @@ export function tansParams (params) { /** * 参数处理,避免小程序跳转10层后无法跳转 - * @param url + * @param url */ export function navigateBackTo (url) { const backUrl = url.slice(1); @@ -35,4 +35,130 @@ export function navigateBackTo (url) { const index = pages.findIndex(item => item.route === backUrl); const backnum = num - index - 1; uni.navigateBack({ delta: backnum }); +} + +/** + * 深复制 + * @param {any} obj 需要复制的对象 + */ +export function deepClone (obj) { + if (typeof obj == 'object') { + const objClone = Array.isArray(obj) ? [] : {} + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + if (obj[key] && typeof obj[key] == 'object') { + objClone[key] = deepClone(obj[key]) + } else { + objClone[key] = obj[key] + } + } + } + return objClone + } + return obj +} + +/** + * @desc 时间戳转日期毫秒 + * @param {Date} format 时间格式 Y:年,M:月,D:天 h:小时,m:分钟,s:秒 默认:'Y/M/D h:m' + */ +export function formatDate (arg, format) { + let now + if (!arg) { + return null + } else { + now = new Date(arg) + } + format = format || 'Y.M.D h:m' + + const year = now.getFullYear() + let month = now.getMonth() + 1 + month = month >= 10 ? month : '0' + month + let date = now.getDate() + date = date >= 10 ? date : '0' + date + let hour = now.getHours() + hour = hour >= 10 ? hour : '0' + hour + let minute = now.getMinutes() + minute = minute >= 10 ? minute : '0' + minute + let second = now.getSeconds() + second = second >= 10 ? second : '0' + second + return format + .replace('Y', year) + .replace('M', month) + .replace('D', date) + .replace('h', hour) + .replace('m', minute) + .replace('s', second) +} + +/** + * 复制文本(uni-app 剪贴板) + * @param {string} copytext + */ +export function copyText (copytext) { + uni.setClipboardData({ + data: copytext, + success: () => {}, + }) + return { type: 'success', message: '复制成功' } +} + +/** + * hex转10进制 + * @param hexStr + */ +export function hex2int (hexStr) { + let twoStr = parseInt(hexStr, 16).toString(2) // 将十六转十进制,再转2进制 + let bitNum = hexStr.length * 4 // 1个字节 = 8bit ,0xff 一个 "f"就是4位 + if (twoStr.length < bitNum) { + while (twoStr.length < bitNum) { + twoStr = "0" + twoStr + } + } + if (twoStr.substring(0, 1) == "0") { + // 正数 + const two = parseInt(twoStr, 2) // 二进制转十进制 + return two + } else { + // 负数 + let twoStr_unsign = "" + let two = parseInt(twoStr, 2) - 1 // 补码:(负数)反码+1,符号位不变;相对十进制来说也是 +1,但这里是负数,+1就是绝对值数据-1 + twoStr = two.toString(2) + twoStr_unsign = twoStr.substring(1, bitNum) // 舍弃首位(符号位) + // 去除首字符,将0转为1,将1转为0 反码 + twoStr_unsign = twoStr_unsign.replace(/0/g, "z") + twoStr_unsign = twoStr_unsign.replace(/1/g, "0") + twoStr_unsign = twoStr_unsign.replace(/z/g, "1") + two = -parseInt(twoStr_unsign, 2) + return two + } +} + +/** + * 10进制转16进制 + * @param hexStr + */ +export function int2hex (hexStr) { + let hexInt = typeof hexStr == 'string' ? parseInt(hexStr, 10) : hexStr + let hex = '' + const LENGTH_04 = 4 + const LENGTH_15 = 15 + if (hexInt >= 0) { + hex = hexInt.toString(16).toLocaleLowerCase() + } else { + let twoStr = (-hexInt - 1).toString(2) + let sb = '000000000000000' + twoStr = sb.slice(0, LENGTH_15 - twoStr.length) + twoStr + // 将0转为1,将1转为0 反码 + twoStr = twoStr.replace(/0/g, "z") + twoStr = twoStr.replace(/1/g, "0") + twoStr = twoStr.replace(/z/g, "1") + hex = parseInt(('1' + twoStr), 2).toString(16).toLocaleLowerCase() + } + // 长度不等于4 补全 + if (hex.length != LENGTH_04) { + let sb = "0000" + hex = sb.slice(0, LENGTH_04 - hex.length) + hex + } + return hex.toUpperCase() } \ No newline at end of file