|
|
@@ -0,0 +1,789 @@
|
|
|
+// modbus.js - 优化版本
|
|
|
+let _globalSlaveAddress = 0x01; //全局初始从机地址
|
|
|
+let connected = false; // 连接状态
|
|
|
+let sharedHeartbeatInterval = null; // 心跳定时器变量
|
|
|
+let agreement = 'DEVICE_A'; //协议类型 默认为 DEVICE_A
|
|
|
+let taskInterval = 500; // 定时任务频率
|
|
|
+let timeStatus = true;
|
|
|
+import store from '@/store';
|
|
|
+// 先声明变量
|
|
|
+let ecUI, ecBLE;
|
|
|
+
|
|
|
+// #ifdef APP
|
|
|
+import _ecUI from '@/utils/ecUI.js'
|
|
|
+import _ecBLE from '@/utils/ecBLE/ecBLE.js'
|
|
|
+ecUI = _ecUI;
|
|
|
+ecBLE = _ecBLE;
|
|
|
+// #endif
|
|
|
+
|
|
|
+// #ifdef MP
|
|
|
+const _ecUI = require('@/utils/ecUI.js')
|
|
|
+const _ecBLE = require('@/utils/ecBLE/ecBLE.js')
|
|
|
+ecUI = _ecUI;
|
|
|
+ecBLE = _ecBLE;
|
|
|
+// #endif
|
|
|
+
|
|
|
+// 将 ecBLE 导出,供其他页面使用
|
|
|
+export { ecBLE, ecUI };
|
|
|
+
|
|
|
+// 获取连接状态
|
|
|
+export function getConnected() {
|
|
|
+ return connected;
|
|
|
+}
|
|
|
+
|
|
|
+//设置协议类型
|
|
|
+export function setAgreement(type) {
|
|
|
+ agreement = type;
|
|
|
+}
|
|
|
+export function setTime(value) {
|
|
|
+ if (value === taskInterval && isHeartbeatRunning()){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ taskInterval = value;
|
|
|
+ stopHeartbeat();
|
|
|
+ startHeartbeat();
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+export function setTimeStatus(value) {
|
|
|
+ timeStatus = value;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+export function initBLE() {
|
|
|
+ // 监听连接状态变化
|
|
|
+ ecBLE.onBLEConnectionStateChange((res) => {
|
|
|
+ console.log(res);
|
|
|
+ if (res.ok && !connected) {
|
|
|
+ connected = true;
|
|
|
+ console.log("连接成功");
|
|
|
+ store.dispatch('ble/updateConnected', res.ok)
|
|
|
+ ecBLE.stopBluetoothDevicesDiscovery();
|
|
|
+ } else {
|
|
|
+ store.dispatch('ble/updateConnected', false)
|
|
|
+ connected = false;
|
|
|
+ ecUI.hideLoading();
|
|
|
+ this.$modal.showToast("请检查是否配置成功");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 接收数据
|
|
|
+ ecBLE.onBLECharacteristicValueChange((str, strHex) => {
|
|
|
+ try {
|
|
|
+ console.log("数据来了");
|
|
|
+ let data = strHex.replace(/[0-9a-fA-F]{2}/g, ' $&') ;
|
|
|
+ const parsedData = readRegister(data)
|
|
|
+ store.dispatch('ble/updateData', parsedData)
|
|
|
+ } catch (error) {
|
|
|
+ store.dispatch('ble/updateError', error)
|
|
|
+ console.error('数据解析失败:', error);
|
|
|
+ this.$modal.showToast("数据解析失败");
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// Getter/Setter 管理全局从机地址
|
|
|
+export function setGlobalSlaveAddress(addr) {
|
|
|
+ if (addr < 0 || addr > 247) {
|
|
|
+ throw new RangeError('slaveAddress 必须在 0~247 之间');
|
|
|
+ }
|
|
|
+ _globalSlaveAddress = addr;
|
|
|
+}
|
|
|
+
|
|
|
+export function getGlobalSlaveAddress() {
|
|
|
+ console.log(_globalSlaveAddress);
|
|
|
+ return _globalSlaveAddress;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 支持的 Modbus 类型
|
|
|
+ */
|
|
|
+export const MODBUS_TYPES = {
|
|
|
+ WRITE_ADDRESS: 'WRITE_ADDRESS',
|
|
|
+ READ_REGISTER: 'READ_REGISTER',
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Modbus 协议帧配置
|
|
|
+ */
|
|
|
+export const MODBUS_FRAME_CONFIG = {
|
|
|
+ DEVICE_A: {
|
|
|
+ WRITE_ADDRESS: { //写地址
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: 0x00,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0017,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ GET_ADDRESS: { //读取地址
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: 0xFF, // 从协议数据看是FF
|
|
|
+ functionCode: 0x03, // 功能码03
|
|
|
+ startAddress: 0x0001, // 起始地址0001
|
|
|
+ value: "0x0046", // 读取0个寄存器(根据实际需求可调整)
|
|
|
+ },
|
|
|
+ TIMED_TASKS:{ //定时任务
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x03,
|
|
|
+ startAddress: 0x0001,
|
|
|
+ value: "0x0046",
|
|
|
+ },
|
|
|
+ RAIN:{ //雨
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0200",
|
|
|
+ },
|
|
|
+ SNOW:{ //雪
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0100",
|
|
|
+ },
|
|
|
+ WIND:{ //风
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0400",
|
|
|
+ },
|
|
|
+ FLATTEN:{ //放平
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0040",
|
|
|
+ },
|
|
|
+ STOP:{ //停止
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0080",
|
|
|
+ },
|
|
|
+ READ_MANUAL:{ //手动
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0010",
|
|
|
+ },
|
|
|
+ READ_AUTO:{ //自动
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0020",
|
|
|
+ },
|
|
|
+ READ_DOWN:{ //向东
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0018",
|
|
|
+ },
|
|
|
+ READ_UP:{ //向西
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0014",
|
|
|
+ },
|
|
|
+ READ_CANCEL:{ //取消
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0029,
|
|
|
+ value: "0x0000",
|
|
|
+ },
|
|
|
+ READ_TIME:{ //校正时间
|
|
|
+ type: MODBUS_TYPES.READ_REGISTER,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x10,
|
|
|
+ startAddress: 0x002C,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+
|
|
|
+ READ_TEMPERATURE:{ //天文写入
|
|
|
+ type: MODBUS_TYPES.READ_REGISTER,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x10,
|
|
|
+ startAddress: 0x0032,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_LIMIT:{ //限位写入
|
|
|
+ type: MODBUS_TYPES.READ_REGISTER,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x10,
|
|
|
+ startAddress: 0x0040,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_INCLINATION:{ //坡度写入
|
|
|
+ type: MODBUS_TYPES.READ_REGISTER,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x10,
|
|
|
+ startAddress: 0x003C,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_FREQUENCY:{ //频点写入
|
|
|
+ type: MODBUS_TYPES.READ_REGISTER,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x10,
|
|
|
+ startAddress: 0x0019,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_DIRECTION:{ //电机方向 正转动
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0025,
|
|
|
+ value: "0x0000",
|
|
|
+ },
|
|
|
+ READ_REVERSE:{ //电机方向 反转
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0025,
|
|
|
+ value: "0x0001",
|
|
|
+ },
|
|
|
+ READ_RETURN:{ //夜返角度
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0042,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_FLAT:{ //放平角度
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0043,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_SPECIFY:{ //指定角度
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0045,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_SNOW:{ //雪天角度
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0044,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_WIND:{ //大风角度
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0046,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_OVERCURRENT:{ //过流写入
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0026,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+ READ_TRACKING:{ //跟踪精度
|
|
|
+ type: MODBUS_TYPES.WRITE_ADDRESS,
|
|
|
+ slaveAddress: _globalSlaveAddress,
|
|
|
+ functionCode: 0x06,
|
|
|
+ startAddress: 0x0027,
|
|
|
+ value: null,
|
|
|
+ },
|
|
|
+
|
|
|
+ },
|
|
|
+ DEVICE_B: {
|
|
|
+
|
|
|
+ },
|
|
|
+ DEVICE_C: {
|
|
|
+
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 写入操作
|
|
|
+ */
|
|
|
+const TS = ['READ_TIME','READ_LIMIT','READ_INCLINATION','READ_FREQUENCY','READ_TEMPERATURE'];
|
|
|
+export function writeRegister(action, valueToWrite) {
|
|
|
+ try{
|
|
|
+ if (action !='TIMED_TASKS'){
|
|
|
+ console.log("停止心跳--------");
|
|
|
+ stopHeartbeat();
|
|
|
+ }
|
|
|
+ let value = valueToWrite;
|
|
|
+ if (!TS.includes(action) && valueToWrite !== null && valueToWrite !== '' && valueToWrite !== undefined) {
|
|
|
+ value = parseInt(valueToWrite, 10);
|
|
|
+ }
|
|
|
+ if (action =='WRITE_ADDRESS'){
|
|
|
+ value = parseInt(valueToWrite, 10);
|
|
|
+ setGlobalSlaveAddress(value);
|
|
|
+ }
|
|
|
+ const buffer = generateModbusFrame(agreement, action, value);
|
|
|
+ const arrayBuffer = arrayBufferToHex(buffer);
|
|
|
+
|
|
|
+ const writeBLECharacteristicValue = ecBLE.writeBLECharacteristicValue(arrayBuffer, true);
|
|
|
+ return writeBLECharacteristicValue;
|
|
|
+ }catch(exception){
|
|
|
+ console.log("写入失败:", exception);
|
|
|
+ }finally {
|
|
|
+ if (action =='WRITE_ADDRESS'){
|
|
|
+ setGlobalSlaveAddress(value);
|
|
|
+ }else if (action !='TIMED_TASKS' && action !='GET_ADDRESS' && timeStatus){
|
|
|
+ setTimeout(() => {
|
|
|
+ if (getConnected() && timeStatus) {
|
|
|
+ startHeartbeat();
|
|
|
+ }
|
|
|
+ }, 50);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 读取寄存器
|
|
|
+ */
|
|
|
+export function readRegister(buffer) {
|
|
|
+ return parseBluetoothData(buffer);
|
|
|
+}
|
|
|
+
|
|
|
+//心跳函数
|
|
|
+export function heartbeat() {
|
|
|
+ try {
|
|
|
+ // 示例:发送一个读取寄存器的请求作为心跳
|
|
|
+ writeRegister('TIMED_TASKS',null);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('心跳请求失败:', error);
|
|
|
+ // 可以在这里添加重连逻辑
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 启动心跳定时器
|
|
|
+export function startHeartbeat() {
|
|
|
+ if (!getConnected()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!isHeartbeatRunning()) {
|
|
|
+ sharedHeartbeatInterval = setInterval(heartbeat, taskInterval);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 停止心跳定时器
|
|
|
+export function stopHeartbeat() {
|
|
|
+ if (isHeartbeatRunning()) {
|
|
|
+ clearInterval(sharedHeartbeatInterval);
|
|
|
+ sharedHeartbeatInterval = null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 检查心跳定时器是否正在运行
|
|
|
+export function isHeartbeatRunning() {
|
|
|
+ return sharedHeartbeatInterval !== null;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 参数校验函数
|
|
|
+ */
|
|
|
+function validateParams({ slaveAddress, functionCode, startAddress, valueToWrite }) {
|
|
|
+ if (slaveAddress < 0 || slaveAddress > 247) {
|
|
|
+ throw new RangeError('slaveAddress 必须在 0~247 之间');
|
|
|
+ }
|
|
|
+ if (functionCode < 1 || functionCode > 255) {
|
|
|
+ throw new RangeError('functionCode 必须在 1~255 之间');
|
|
|
+ }
|
|
|
+ if (startAddress < 0 || startAddress > 0xFFFF) {
|
|
|
+ throw new RangeError('startAddress 必须在 0~65535 之间');
|
|
|
+ }
|
|
|
+ if (valueToWrite < 0 || valueToWrite > 0xFFFF) {
|
|
|
+ throw new RangeError('valueToWrite 必须在 0~65535 之间');
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 创建 Modbus RTU 请求帧(根据配置)
|
|
|
+ * @param {string} protocol 设备协议类型(DEVICE_A / DEVICE_B / DEVICE_C)
|
|
|
+ * @param {string} action 操作
|
|
|
+ * @param {number} [valueToWrite=0] 写入值 (0~65535)
|
|
|
+ * @returns {Buffer} Modbus RTU 请求帧
|
|
|
+ */
|
|
|
+function generateModbusFrame(protocol, action, valueToWrite) {
|
|
|
+ console.log(_globalSlaveAddress);
|
|
|
+ const config = MODBUS_FRAME_CONFIG[protocol]?.[action];
|
|
|
+ if (!config) {
|
|
|
+ throw new Error(`不支持的协议或写入类型: ${protocol} - ${action}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ let { type, slaveAddress, functionCode, startAddress, value } = config;
|
|
|
+ if ((valueToWrite == NaN || valueToWrite == null) && config.value !== undefined && config.value !== null) {
|
|
|
+ valueToWrite = config.value;
|
|
|
+ }
|
|
|
+ if (action !== 'WRITE_ADDRESS' && action !== "GET_ADDRESS"){
|
|
|
+ slaveAddress = _globalSlaveAddress;
|
|
|
+ }
|
|
|
+
|
|
|
+ // validateParams({ slaveAddress, functionCode, startAddress, valueToWrite });
|
|
|
+
|
|
|
+ let buffer;
|
|
|
+ if (type === MODBUS_TYPES.READ_REGISTER) {
|
|
|
+
|
|
|
+ // valueToWrite 应该包含日期数据
|
|
|
+ const dateData = valueToWrite; // 应该是一个数组,包含年、月、日、时、分、秒
|
|
|
+ let buffer1 = new Uint8Array(7 + dateData.length * 2); // 基础7字节 + 数据字节
|
|
|
+ buffer1[0] = slaveAddress;
|
|
|
+ buffer1[1] = functionCode;
|
|
|
+ buffer1[2] = (startAddress >> 8) & 0xFF;
|
|
|
+ buffer1[3] = startAddress & 0xFF;
|
|
|
+ buffer1[4] = (dateData.length >> 8) & 0xFF; // 寄存器数量高字节
|
|
|
+ buffer1[5] = dateData.length & 0xFF; // 寄存器数量低字节
|
|
|
+ buffer1[6] = dateData.length * 2; // 字节数
|
|
|
+
|
|
|
+ // 填充数据
|
|
|
+ for (let i = 0; i < dateData.length; i++) {
|
|
|
+ buffer1[7 + i*2] = (dateData[i] >> 8) & 0xFF; // 高字节
|
|
|
+ buffer1[8 + i*2] = dateData[i] & 0xFF; // 低字节
|
|
|
+ }
|
|
|
+
|
|
|
+ const crc = calculateCRC(buffer1.subarray(0, 7 + dateData.length * 2));
|
|
|
+ const finalBuffer = new Uint8Array(buffer1.length + 2);
|
|
|
+ finalBuffer.set(buffer1);
|
|
|
+ finalBuffer[buffer1.length] = crc[0];
|
|
|
+ finalBuffer[buffer1.length + 1] = crc[1];
|
|
|
+ buffer = finalBuffer;
|
|
|
+ } else {
|
|
|
+ buffer = new Uint8Array(6);
|
|
|
+ buffer[0] = slaveAddress;
|
|
|
+ buffer[1] = functionCode;
|
|
|
+ buffer[2] = (startAddress >> 8) & 0xFF;
|
|
|
+ buffer[3] = startAddress & 0xFF;
|
|
|
+ buffer[4] = (valueToWrite >> 8) & 0xFF;
|
|
|
+ buffer[5] = valueToWrite & 0xFF;
|
|
|
+
|
|
|
+ const crc = calculateCRC(buffer);
|
|
|
+ const finalBuffer = new Uint8Array(buffer.length + crc.length);
|
|
|
+ finalBuffer.set(buffer);
|
|
|
+ finalBuffer.set(crc, buffer.length);
|
|
|
+ buffer = finalBuffer;
|
|
|
+ }
|
|
|
+
|
|
|
+ return buffer;
|
|
|
+}
|
|
|
+function calculateCRC(buffer) {
|
|
|
+ let crc = 0xFFFF;
|
|
|
+ for (let i = 0; i < buffer.length; i++) {
|
|
|
+ const tableIndex = (crc ^ buffer[i]) & 0xFF;
|
|
|
+ crc = (crc >> 8) ^ crcTable[tableIndex];
|
|
|
+ }
|
|
|
+ const crcBuffer = new Uint8Array(2);
|
|
|
+ crcBuffer[0] = crc & 0xFF;
|
|
|
+ crcBuffer[1] = (crc >> 8) & 0xFF;
|
|
|
+ return crcBuffer;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// CRC 表只构建一次
|
|
|
+const crcTable = buildCRCTable();
|
|
|
+
|
|
|
+
|
|
|
+function buildCRCTable() {
|
|
|
+ const table = new Uint16Array(256);
|
|
|
+ for (let i = 0; i < 256; i++) {
|
|
|
+ let crc = i;
|
|
|
+ for (let j = 0; j < 8; j++) {
|
|
|
+ if (crc & 0x0001) {
|
|
|
+ crc = (crc >> 1) ^ 0xA001;
|
|
|
+ } else {
|
|
|
+ crc >>= 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ table[i] = crc;
|
|
|
+ }
|
|
|
+ return table;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将 ArrayBuffer 转为十六进制字符串
|
|
|
+ */
|
|
|
+export function arrayBufferToHex(buffer, withSpaces = false) {
|
|
|
+ const hexArray = [...new Uint8Array(buffer)]
|
|
|
+ .map(b => b.toString(16).padStart(2, '0'));
|
|
|
+ if (withSpaces) {
|
|
|
+ return hexArray.join(' ').toUpperCase();
|
|
|
+ } else {
|
|
|
+ return hexArray.join('').toUpperCase();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析蓝牙数据
|
|
|
+ */
|
|
|
+export function parseBluetoothData(hexString) {
|
|
|
+ ecBLE.saveWriteDataToLocal("TX: " + hexString,"tx");
|
|
|
+ if (!hexString || hexString.length < 6) {
|
|
|
+ const error = new Error('蓝牙数据不完整');
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ // 移除所有空格并重新格式化为标准格式
|
|
|
+ const cleanHexString = hexString.replace(/\s/g, '');
|
|
|
+
|
|
|
+ if (cleanHexString.length < 6) {
|
|
|
+ throw new Error('蓝牙数据不完整');
|
|
|
+ }
|
|
|
+ // 将连续的十六进制字符串转换为带空格的格式
|
|
|
+ const formattedHexString = cleanHexString.match(/.{1,2}/g)?.join(' ') || cleanHexString;
|
|
|
+
|
|
|
+ const byteStrings = formattedHexString.split(' ').filter(s => s.length > 0);
|
|
|
+ console.log("字节字符串数组:", JSON.stringify(byteStrings));
|
|
|
+ console.log("字节数组长度:", byteStrings.length);
|
|
|
+ if (byteStrings.length < 10 ){ //其他操作执行成功
|
|
|
+ // 其他操作执行成功
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!byteStrings || byteStrings.length < 3) {
|
|
|
+ throw new Error('蓝牙数据不完整');
|
|
|
+ }
|
|
|
+
|
|
|
+ const register = {
|
|
|
+ device: '',
|
|
|
+ function: '',
|
|
|
+ registerNumber: '',
|
|
|
+ Addres_23: '',
|
|
|
+ Frequence_25: '',
|
|
|
+ NetworkId_26: '',
|
|
|
+ modAddre_27: '',
|
|
|
+ MotorCurrent_30: '',
|
|
|
+ MotorCurrent_35: '',
|
|
|
+ Battery_32: '',
|
|
|
+ Temperature_33: '',
|
|
|
+ MotDrection_37: '',
|
|
|
+ OverProtection_38: '',
|
|
|
+ TrackingAccuracy_39: '',
|
|
|
+ Message_40: '',
|
|
|
+ WorkModle_41: '',
|
|
|
+ TargetAngle_42: '',
|
|
|
+ RealAngle_43: '',
|
|
|
+ RealAngle_31: '',
|
|
|
+ Year_44: '',
|
|
|
+ Month_45: '',
|
|
|
+ Day_46: '',
|
|
|
+ Hour_47: '',
|
|
|
+ Minute_48: '',
|
|
|
+ Second_49: '',
|
|
|
+ nowtime: '',
|
|
|
+ Longitude_50: '',
|
|
|
+ Latitude_51: '',
|
|
|
+ TimeZone_52: '',
|
|
|
+ EleAngle_53: '',
|
|
|
+ Azimuth_54: '',
|
|
|
+ width_60: '',
|
|
|
+ inter_61: '',
|
|
|
+ UpGrade_62: '',
|
|
|
+ DownGrade_63: '',
|
|
|
+ EasternLimit_64: '',
|
|
|
+ WesternLimit_65: '',
|
|
|
+ NightAngle_66: '',
|
|
|
+ FlatAngle_67: '',
|
|
|
+ SnowAngle_68: '',
|
|
|
+ SpecifiedAngle_69: '',
|
|
|
+ WindAngle_70: '',
|
|
|
+ qAzimuth_54: '',
|
|
|
+ qwidth_60: '',
|
|
|
+ Interval_61: '',
|
|
|
+ qEasternLimit_64: '',
|
|
|
+ qWesternLimit_65: '',
|
|
|
+ qNightAngle_66: '',
|
|
|
+ qFlatAngle_67: '',
|
|
|
+ qSnowAngle_68: '',
|
|
|
+ qSpecifiedAngle_69: '',
|
|
|
+ qWindAngle_70: '',
|
|
|
+ qEleAngle_53: '',
|
|
|
+ qTargetAngle_42: '',
|
|
|
+ qRealAngle_43: '',
|
|
|
+ qRealAngle_31: ''
|
|
|
+ };
|
|
|
+
|
|
|
+ register.device = parseInt(byteStrings[0], 16);
|
|
|
+ register.function = parseInt(byteStrings[1], 16);
|
|
|
+ register.registerNumber = parseInt(byteStrings[2], 16);
|
|
|
+
|
|
|
+ const formattedOutput = [];
|
|
|
+ for (let i = 3; i < byteStrings.length; i += 2) {
|
|
|
+ const byte1 = parseInt(byteStrings[i], 16);
|
|
|
+ const byte2 = i + 1 < byteStrings.length ? parseInt(byteStrings[i + 1], 16) : 0;
|
|
|
+ const combined = ((byte1 << 8) | byte2).toString(16).padStart(4, '0');
|
|
|
+ formattedOutput.push(combined.toUpperCase());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 参照mainwindow.cpp的解析逻辑补全
|
|
|
+ for (let i = 1; i < formattedOutput.length; i++) {
|
|
|
+ const value = parseInt(formattedOutput[i], 16);
|
|
|
+ const valueInt16 = (value > 0x7FFF) ? value - 0x10000 : value; // 转换为有符号16位整数
|
|
|
+
|
|
|
+ if (i === 22) { // 地址写入
|
|
|
+ register.Addres_23 = valueInt16.toString();
|
|
|
+ } else if (i === 24) { // 频点[0-83]
|
|
|
+ register.Frequence_25 = valueInt16.toString();
|
|
|
+ } else if (i === 25) { // 网络ID[0-255]
|
|
|
+ register.NetworkId_26 = valueInt16.toString();
|
|
|
+ } else if (i === 26) { // 模块地址
|
|
|
+ register.modAddre_27 = (parseInt(register.NetworkId_26, 10) * 256) + register.device;
|
|
|
+ } else if (i === 29) { // 电机1电流1位小数(A)
|
|
|
+ register.MotorCurrent_30 = valueInt16.toString();
|
|
|
+ register.MotorCurrent_30 = insertDecimal(register.MotorCurrent_30, 1);
|
|
|
+ } else if (i === 34) { // 电机2电流1位小数(A)
|
|
|
+ register.MotorCurrent_35 = valueInt16.toString();
|
|
|
+ register.MotorCurrent_35 = insertDecimal(register.MotorCurrent_35, 1);
|
|
|
+ } else if (i === 31) { // 电池1位小数(V)
|
|
|
+ register.Battery_32 = valueInt16.toString();
|
|
|
+ register.Battery_32 = insertDecimal(register.Battery_32, 1);
|
|
|
+ } else if (i === 32) { // 温度(度)
|
|
|
+ register.Temperature_33 = valueInt16.toString();
|
|
|
+ } else if (i === 35) { // 标定有效
|
|
|
+ register.Demarcate_36 = valueInt16.toString();
|
|
|
+ } else if (i === 36) { // 电机方向
|
|
|
+ register.MotDrection_37 = valueInt16.toString();
|
|
|
+ } else if (i === 37) { // 过流保护(A)
|
|
|
+ register.OverProtection_38 = valueInt16.toString();
|
|
|
+ } else if (i === 38) { // 跟踪精度
|
|
|
+ register.TrackingAccuracy_39 = valueInt16.toString();
|
|
|
+ } else if (i === 39) { // 十进制转二进制0111倾角+过流+限位
|
|
|
+ register.Message_40 = valueInt16.toString(2); // 转二进制
|
|
|
+ } else if (i === 40) { // 工作模式
|
|
|
+ register.WorkModle_41 = valueInt16.toString(2); // 转二进制
|
|
|
+ } else if (i === 41) { // 目标角度_两位小数
|
|
|
+ register.TargetAngle_42 = valueInt16;
|
|
|
+ register.qTargetAngle_42 = insertDecimal(register.TargetAngle_42.toString(), 2);
|
|
|
+ } else if (i === 42) { // 实际角度1_两位小数
|
|
|
+ register.RealAngle_43 = valueInt16;
|
|
|
+ register.qRealAngle_43 = insertDecimal(register.RealAngle_43.toString(), 2);
|
|
|
+ } else if (i === 30) { // 实际角度2_两位小数
|
|
|
+ register.RealAngle_31 = valueInt16;
|
|
|
+ register.qRealAngle_31 = insertDecimal(register.RealAngle_31.toString(), 2);
|
|
|
+ } else if (i === 43) { // 年
|
|
|
+ register.Year_44 = valueInt16.toString();
|
|
|
+ } else if (i === 44) { // 月
|
|
|
+ register.Month_45 = valueInt16.toString();
|
|
|
+ } else if (i === 45) { // 日
|
|
|
+ register.Day_46 = valueInt16.toString();
|
|
|
+ } else if (i === 46) { // 时
|
|
|
+ register.Hour_47 = valueInt16.toString();
|
|
|
+ } else if (i === 47) { // 分
|
|
|
+ register.Minute_48 = valueInt16.toString();
|
|
|
+ } else if (i === 48) { // 秒
|
|
|
+ register.Second_49 = valueInt16.toString();
|
|
|
+ register.nowtime = `${register.Year_44}-${register.Month_45}-${register.Day_46} ${register.Hour_47.padStart(2, '0')}:${register.Minute_48.padStart(2, '0')}:${register.Second_49.padStart(2, '0')}`;
|
|
|
+ } else if (i === 49) { // 太阳经度_两位小数
|
|
|
+ register.Longitude_50 = valueInt16.toString();
|
|
|
+ register.Longitude_50 = insertDecimal(register.Longitude_50, 2);
|
|
|
+ } else if (i === 50) { // 太阳纬度_两位小数
|
|
|
+ register.Latitude_51 = valueInt16.toString();
|
|
|
+ register.Latitude_51 = insertDecimal(register.Latitude_51, 2);
|
|
|
+ } else if (i === 51) { // 时区_两位小数800
|
|
|
+ register.TimeZone_52 = valueInt16.toString();
|
|
|
+ register.TimeZone_52 = insertDecimal(register.TimeZone_52, 2);
|
|
|
+ } else if (i === 52) { // 太阳高度角_两位小数
|
|
|
+ register.EleAngle_53 = valueInt16;
|
|
|
+ register.qEleAngle_53 = insertDecimal(register.EleAngle_53.toString(), 2);
|
|
|
+ } else if (i === 53) { // 太阳方位角_两位小数180调零
|
|
|
+ register.Azimuth_54 = valueInt16;
|
|
|
+ register.qAzimuth_54 = insertDecimal(register.Azimuth_54.toString(), 2);
|
|
|
+ } else if (i === 59) { // 宽度_两位小数
|
|
|
+ register.width_60 = valueInt16;
|
|
|
+ register.qwidth_60 = insertDecimal(register.width_60.toString(), 2);
|
|
|
+ } else if (i === 60) { // 间距_两位小数
|
|
|
+ register.inter_61 = valueInt16;
|
|
|
+ register.Interval_61 = insertDecimal(register.inter_61.toString(), 2);
|
|
|
+ } else if (i === 61) { // 上坡度_两位小数
|
|
|
+ register.UpGrade_62 = valueInt16.toString();
|
|
|
+ register.UpGrade_62 = insertDecimal(register.UpGrade_62, 2);
|
|
|
+ } else if (i === 62) { // 下坡度_两位小数
|
|
|
+ register.DownGrade_63 = valueInt16.toString();
|
|
|
+ register.DownGrade_63 = insertDecimal(register.DownGrade_63, 2);
|
|
|
+ } else if (i === 63) { // 东限位
|
|
|
+ register.EasternLimit_64 = valueInt16;
|
|
|
+ register.qEasternLimit_64 = register.EasternLimit_64.toString();
|
|
|
+ } else if (i === 64) { // 西限位
|
|
|
+ register.WesternLimit_65 = valueInt16;
|
|
|
+ register.qWesternLimit_65 = register.WesternLimit_65.toString();
|
|
|
+ } else if (i === 65) { // 夜返角
|
|
|
+ register.NightAngle_66 = valueInt16;
|
|
|
+ register.qNightAngle_66 = register.NightAngle_66.toString();
|
|
|
+ } else if (i === 66) { // 放平角度
|
|
|
+ register.FlatAngle_67 = valueInt16;
|
|
|
+ register.qFlatAngle_67 = register.FlatAngle_67.toString();
|
|
|
+ } else if (i === 67) { // 雪天角度
|
|
|
+ register.SnowAngle_68 = valueInt16;
|
|
|
+ register.qSnowAngle_68 = register.SnowAngle_68.toString();
|
|
|
+ } else if (i === 68) { // 指定角度
|
|
|
+ register.SpecifiedAngle_69 = valueInt16;
|
|
|
+ register.qSpecifiedAngle_69 = register.SpecifiedAngle_69.toString();
|
|
|
+ } else if (i === 69) { // 大风角度
|
|
|
+ register.WindAngle_70 = valueInt16;
|
|
|
+ register.qWindAngle_70 = register.WindAngle_70.toString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ console.log(JSON.stringify( register));
|
|
|
+ return register;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 在数字中插入小数点
|
|
|
+ */
|
|
|
+export function insertDecimal(value, digits = 2) {
|
|
|
+ // 处理空值或无效值
|
|
|
+ if (value === null || value === undefined || value === '') {
|
|
|
+ return (0).toFixed(digits);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保是数字类型
|
|
|
+ const numValue = Number(value);
|
|
|
+
|
|
|
+ // 如果不是有效数字,返回默认值
|
|
|
+ if (isNaN(numValue)) {
|
|
|
+ return (0).toFixed(digits);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算除数
|
|
|
+ const divisor = Math.pow(10, digits);
|
|
|
+
|
|
|
+ // 执行除法并格式化为指定小数位数
|
|
|
+ return (numValue / divisor).toFixed(digits);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 显示接收数据(格式化为 16 字节一行)
|
|
|
+ */
|
|
|
+export function displayReceiveData(buffer) {
|
|
|
+ const hexStr = HexToAscii(buffer);
|
|
|
+ const lines = hexStr.split(' ');
|
|
|
+ let str = '';
|
|
|
+
|
|
|
+ for (let i = 0; i < lines.length; i++) {
|
|
|
+ str += lines[i] + ' ';
|
|
|
+ if ((i + 1) % 16 === 0) str += '\n';
|
|
|
+ }
|
|
|
+
|
|
|
+ return str;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将字节数组转为十六进制字符串
|
|
|
+ */
|
|
|
+export function HexToAscii(buffer) {
|
|
|
+ return [...new Uint8Array(buffer)]
|
|
|
+ .map(b => b.toString(16).padStart(2, '0'))
|
|
|
+ .join(' ')
|
|
|
+ .trim();
|
|
|
+}
|
|
|
+
|