Просмотр исходного кода

feat(蓝牙): 重构蓝牙连接逻辑并添加新功能

-重构了蓝牙连接和断开连接的逻辑,优化了服务和特征值的发现过程
- 新增了对通知功能的支持,并改进了写入特征值的处理方式- 添加了日志记录功能,支持保存和管理蓝牙通信数据- 新增了蓝牙状态管理的 Vuex 模块
- 优化了图标样式表的引入路径
mws 5 месяцев назад
Родитель
Сommit
97b0eed9d7

+ 4 - 3
App.vue

@@ -2,7 +2,7 @@
   import config from './config'
   import store from '@/store'
   import { getToken } from '@/utils/auth'
-  import "@/static/iconfont.css";
+
 
   export default {
     onLaunch: function() {
@@ -15,7 +15,7 @@
         this.initConfig()
         // 检查用户登录状态
         //#ifdef H5
-        this.checkLogin()
+        // this.checkLogin()
         //#endif
         this.updateManager();
       },
@@ -67,5 +67,6 @@
 </script>
 
 <style lang="scss">
-  @import '@/static/scss/index.scss'
+	  @import "@/static/iconfont.css";
+	  @import '@/static/scss/index.scss'
 </style>

+ 70 - 0
components/DeviceConfigCard.vue

@@ -0,0 +1,70 @@
+<template>
+	<view class="device-config-card">
+		<view class="card-header">
+			<text class="card-title">{{ title }}</text>
+			<u-button 
+				v-if="showAction" 
+				type="primary" 
+				size="mini" 
+				@click="handleAction"
+			>
+				{{ actionText }}
+			</u-button>
+		</view>
+		
+		<view class="card-content">
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'DeviceConfigCard',
+		props: {
+			title: {
+				type: String,
+				default: '设备配置'
+			},
+			showAction: {
+				type: Boolean,
+				default: false
+			},
+			actionText: {
+				type: String,
+				default: '操作'
+			}
+		},
+		methods: {
+			handleAction() {
+				this.$emit('action')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+@import '@/static/scss/custom-theme.scss';
+
+.device-config-card {
+	@include card-style;
+	margin-bottom: $custom-spacing-md;
+	
+	.card-header {
+		@include flex-between;
+		margin-bottom: $custom-spacing-md;
+		padding-bottom: $custom-spacing-sm;
+		border-bottom: 1px solid $custom-border-primary;
+		
+		.card-title {
+			font-size: 16px;
+			font-weight: bold;
+			color: $custom-text-primary;
+		}
+	}
+	
+	.card-content {
+		// 内容区域样式
+	}
+}
+</style> 

+ 189 - 0
components/ParamRow.vue

@@ -0,0 +1,189 @@
+<template>
+	<view class="param-row">
+		<u-button 
+			:type="labelType" 
+			size="mini" 
+			class="param-label"
+		>
+			{{ label }}
+		</u-button>
+		
+		<!-- 输入框 -->
+		<u-input 
+			v-if="inputType === 'text'"
+			v-model="inputValue" 
+			:placeholder="placeholder"
+			:type="inputType"
+			class="param-input"
+			@input="handleInput"
+		></u-input>
+		
+		<!-- 选择器 -->
+		<u-select 
+			v-else-if="inputType === 'select'"
+			v-model="inputValue" 
+			:list="options"
+			:placeholder="placeholder"
+			class="param-select"
+			@change="handleSelect"
+		></u-select>
+		
+		<!-- 复选框 -->
+		<u-checkbox 
+			v-if="showCheckbox"
+			v-model="checkboxValue" 
+			class="checkbox"
+			@change="handleCheckbox"
+		>
+			{{ checkboxLabel }}
+		</u-checkbox>
+		
+		<!-- 操作按钮 -->
+		<u-button 
+			v-if="actionButton"
+			:type="buttonType" 
+			size="mini" 
+			class="action-btn"
+			:disabled="buttonDisabled"
+			@click="handleAction"
+		>
+			{{ actionButton }}
+		</u-button>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'ParamRow',
+		props: {
+			label: {
+				type: String,
+				required: true
+			},
+			labelType: {
+				type: String,
+				default: 'primary'
+			},
+			inputType: {
+				type: String,
+				default: 'text'
+			},
+			placeholder: {
+				type: String,
+				default: '请输入'
+			},
+			value: {
+				type: [String, Number],
+				default: ''
+			},
+			options: {
+				type: Array,
+				default: () => []
+			},
+			showCheckbox: {
+				type: Boolean,
+				default: false
+			},
+			checkboxLabel: {
+				type: String,
+				default: ''
+			},
+			checkboxValue: {
+				type: Boolean,
+				default: false
+			},
+			actionButton: {
+				type: String,
+				default: ''
+			},
+			buttonType: {
+				type: String,
+				default: 'info'
+			},
+			buttonDisabled: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				inputValue: this.value
+			}
+		},
+		watch: {
+			value(newVal) {
+				this.inputValue = newVal
+			}
+		},
+		methods: {
+			handleInput(value) {
+				this.$emit('input', value)
+			},
+			handleSelect(value) {
+				this.$emit('change', value)
+			},
+			handleCheckbox(value) {
+				this.$emit('checkbox-change', value)
+			},
+			handleAction() {
+				this.$emit('action')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+@import '@/static/scss/custom-theme.scss';
+
+.param-row {
+	@include flex-start;
+	margin-bottom: $custom-spacing-md;
+	gap: $custom-spacing-sm;
+	flex-wrap: wrap;
+	
+	.param-label {
+		min-width: 80px;
+		@include button-style('primary');
+	}
+	
+	.param-input, .param-select {
+		flex: 1;
+		min-width: 120px;
+		@include input-style;
+	}
+	
+	.checkbox {
+		margin-left: $custom-spacing-sm;
+	}
+	
+	.action-btn {
+		@include button-style('info');
+		background-color: $custom-bg-secondary;
+		color: $custom-text-secondary;
+		border: 1px solid $custom-border-secondary;
+		
+		&:disabled {
+			background-color: $custom-bg-disabled;
+			color: $custom-text-disabled;
+			border-color: $custom-border-disabled;
+		}
+	}
+}
+
+// 响应式设计
+@include responsive(md) {
+	.param-row {
+		.param-input, .param-select {
+			min-width: 150px;
+		}
+	}
+}
+
+@include responsive(lg) {
+	.param-row {
+		.param-input, .param-select {
+			min-width: 200px;
+		}
+	}
+}
+</style> 

+ 48 - 0
index.js

@@ -0,0 +1,48 @@
+// store/index.js
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  state: {
+    bleConnected: false,
+    bleLoading: false,
+    bleError: null,
+    bleData: null, // 新增:用于保存接收到的蓝牙数据
+  },
+  mutations: {
+    SET_BLE_CONNECTED(state, status) {
+      state.bleConnected = status
+    },
+    SET_BLE_LOADING(state, status) {
+      state.bleLoading = status
+    },
+    SET_BLE_ERROR(state, error) {
+      state.bleError = error
+    },
+    SET_BLE_DATA(state, data) {
+      state.bleData = data
+    }
+  },
+  actions: {
+    updateBleConnected({ commit }, status) {
+      commit('SET_BLE_CONNECTED', status)
+    },
+    updateBleLoading({ commit }, status) {
+      commit('SET_BLE_LOADING', status)
+    },
+    updateBleError({ commit }, error) {
+      commit('SET_BLE_ERROR', error)
+    },
+    updateBleData({ commit }, data) {
+      commit('SET_BLE_DATA', data)
+    }
+  },
+  getters: {
+    bleConnected: state => state.bleConnected,
+    bleLoading: state => state.bleLoading,
+    bleError: state => state.bleError,
+    bleData: state => state.bleData
+  }
+})

+ 0 - 1
main.js

@@ -2,7 +2,6 @@ import Vue from 'vue'
 import App from './App'
 import store from './store' // store
 import plugins from './plugins' // plugins
-import './permission' // permission
 import uView from '@/uni_modules/uview-ui'
 import mqttTool from '@/utils/mqttTool'
 

+ 9 - 5
manifest.json

@@ -1,6 +1,6 @@
 {
-    "name" : "老药师云平台",
-    "appid" : "__UNI__E4C911E",
+    "name" : "亿旭智能",
+    "appid" : "__UNI__0BF6F83",
     "description" : "",
     "versionName" : "1.1.0",
     "versionCode" : "100",
@@ -14,7 +14,9 @@
             "autoclose" : true,
             "delay" : 0
         },
-        "modules" : {},
+        "modules" : {
+            "Bluetooth" : {}
+        },
         "distribute" : {
             "android" : {
                 "permissions" : [
@@ -35,13 +37,15 @@
                     "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
                 ]
             },
-            "ios" : {},
+            "ios" : {
+                "dSYMs" : false
+            },
             "sdkConfigs" : {}
         }
     },
     "quickapp" : {},
     "mp-weixin" : {
-        "appid" : "wx0ef8e17314eb2764",
+        "appid" : "wx67fbe90ca1db0372",
         "plugins" : {},
         "setting" : {
             "urlCheck" : false,

+ 45 - 199
pages.json

@@ -1,212 +1,40 @@
 {
   "pages": [
 
-    {
-      "path": "pages/login",
-      "style": {
-        "navigationBarTitleText": "登录"
-      }
-    },
     {
       "path": "pages/bluetooth/index/index",
       "style": {
-        "navigationBarTitleText": "蓝牙配网"
-      }
-    },
-   
-    {
-      "path": "pages/user/list",
-      "style": {
-        "navigationBarTitleText": "用户列表"
-      }
-    },
-    {
-      "path": "pages/index",
-      "style": {
-        "navigationBarTitleText": "登录中",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/work/index",
-      "style": {
-        "navigationBarTitleText": "工作台"
-      }
-    },
-    {
-      "path": "pages/mine/index",
-      "style": {
-        "navigationBarTitleText": "我的"
-      }
-    },
-    {
-      "path": "pages/mine/avatar/index",
-      "style": {
-        "navigationBarTitleText": "修改头像"
-      }
-    },
-    {
-      "path": "pages/mine/info/index",
-      "style": {
-        "navigationBarTitleText": "个人信息"
-      }
-    },
-    {
-      "path": "pages/mine/info/edit",
-      "style": {
-        "navigationBarTitleText": "编辑资料"
+        "navigationBarTitleText": "首页"
       }
     },
     {
-      "path": "pages/mine/pwd/index",
+      "path": "pages/bluetooth/set/set",
       "style": {
-        "navigationBarTitleText": "修改密码"
+        "navigationBarTitleText": "操作"
       }
     },
     {
-      "path": "pages/mine/setting/index",
-      "style": {
-        "navigationBarTitleText": "应用设置"
-      }
-    },
-    {
-      "path": "pages/mine/help/index",
-      "style": {
-        "navigationBarTitleText": "常见问题"
-      }
-    },
-    {
-      "path": "pages/mine/about/index",
-      "style": {
-        "navigationBarTitleText": "关于我们"
-      }
-    },
-    {
-      "path": "pages/common/webview/index",
-      "style": {
-        "navigationBarTitleText": "浏览网页"
-      }
-    },
-    {
-      "path": "pages/common/textview/index",
-      "style": {
-        "navigationBarTitleText": "浏览文本"
-      }
+    	"path" : "pages/bluetooth/set/one",
+    	"style" : 
+    	{
+    		"navigationBarTitleText" : "设置一"
+    	}
     },
-    
     {
-      "path": "pages/wifi/index",
-      "style": {
-        "navigationBarTitleText": "配网"
-      }
+    	"path" : "pages/bluetooth/set/two",
+    	"style" : 
+    	{
+    		"navigationBarTitleText" : "设置二"
+    	}
     },
-   
     {
-      "path": "pages/user/index",
-      "style": {
-        "navigationBarTitleText": "客户列表"
-      }
-    },
-
-    
-	{
-		"path": "pages/regist/index",
-		"style": {
-			"navigationBarTitleText": "注册信息"
-		}
-	},
-	{
-		"path" : "pages/reviews/reviews",
-		"style" :
-		{
-			"navigationBarTitleText" : "注册审核",
-			"enablePullDownRefresh" : false
-		}
-	}
+    	"path" : "pages/bluetooth/set/record",
+    	"style" : 
+    	{
+    		"navigationBarTitleText" : ""
+    	}
+    }
   ],
-  "subPackages": [
-      {
-        "root": "subpkg",
-        "pages": [
-			{
-			  "path": "device/index",
-			  "style": {
-			    "navigationBarTitleText": "设备列表"
-			  }
-			},
-         {
-           "path": "device/detail",
-           "style": {
-             "navigationBarTitleText": "详情",
-             "navigationStyle": "custom"
-           }
-         },
-		 {
-		   "path": "device/scan",
-		   "style": {
-		     "navigationBarTitleText": "绑定设备"
-		   }
-		 },
-         {
-           "path": "device/detail_6",
-           "style": {
-             "navigationBarTitleText": "详情",
-             "navigationStyle": "custom"
-           }
-         },
-         {
-           "path": "device/detail_new_5",
-           "style": {
-             "navigationBarTitleText": "详情",
-             "navigationStyle": "custom"
-           }
-         },
-         {
-           "path": "device/detail_new_20250528",
-           "style": {
-             "navigationBarTitleText": "详情",
-             "navigationStyle": "custom"
-           }
-         },
-         {
-           "path": "device/setting",
-           "style": {
-             "navigationBarTitleText": "设置"
-           }
-         },
-		 {
-		   "path": "device/alert",
-		   "style": {
-		     "navigationBarTitleText": "报警列表"
-		   }
-		 },
-		 {
-		   "path": "device/error",
-		   "style": {
-		     "navigationBarTitleText": "异常列表"
-		   }
-		 },
-		 {
-		   "path": "device/reset/reset",
-		   "style": {
-		     "navigationBarTitleText": "设备注销"
-		   }
-		 },
-		 {
-		   "path": "device/reset/resetrecord",
-		   "style": {
-		     "navigationBarTitleText": "注销记录"
-		   }
-		 },
-		 {
-		   "path": "device/rank",
-		   "style": {
-		     "navigationBarTitleText": "排行榜"
-		   }
-		 }
-        ]
-      }
-    ],
   "tabBar": {
     "color": "#000000",
     "selectedColor": "#000000",
@@ -214,16 +42,34 @@
     "backgroundColor": "#ffffff",
     "list": [
       {
-        "pagePath": "pages/work/index",
-        "iconPath": "static/images/tabbar/work.png",
-        "selectedIconPath": "static/images/tabbar/work_.png",
-        "text": "工作台"
+        "pagePath": "pages/bluetooth/index/index",
+        "iconPath": "static/images/tabbar/ly.png",
+        "selectedIconPath": "static/images/tabbar/ly_active.png",
+        "text": "首页"
+      },
+      {
+        "pagePath": "pages/bluetooth/set/set",
+        "iconPath": "static/images/tabbar/cz.png",
+        "selectedIconPath": "static/images/tabbar/cz_active.png",
+        "text": "操作"
+      },
+      {
+        "pagePath": "pages/bluetooth/set/one",
+        "iconPath": "static/images/tabbar/set.png",
+        "selectedIconPath": "static/images/tabbar/set_active.png",
+        "text": "设置一"
+      },
+      {
+        "pagePath": "pages/bluetooth/set/two",
+        "iconPath": "static/images/tabbar/set.png",
+        "selectedIconPath": "static/images/tabbar/set_active.png",
+        "text": "设置二"
       },
       {
-        "pagePath": "pages/mine/index",
-        "iconPath": "static/images/tabbar/mine.png",
-        "selectedIconPath": "static/images/tabbar/mine_.png",
-        "text": "我的"
+        "pagePath": "pages/bluetooth/set/record",
+        "iconPath": "static/images/tabbar/record.png",
+        "selectedIconPath": "static/images/tabbar/record_active.png",
+        "text": "接收"
       }
     ]
   },

Разница между файлами не показана из-за своего большого размера
+ 441 - 746
pages/bluetooth/index/index.vue


+ 780 - 0
pages/bluetooth/indexOne/index.vue

@@ -0,0 +1,780 @@
+<template>
+  <view style="height: 100vh">
+	<view v-if="!connected">
+    <scroll-view class="main-container" scroll-y="true"
+                 :refresher-enabled="false">
+      <view v-for="(item, index) in deviceListDataShow" :key="item.id" class="list-item" hover-class="list-item-hover"
+            hover-start-time="0" hover-stay-time="100" @click="listViewTap(item.id)">
+        <image v-if="item.manufacturer==='eciot'" src="/static/img/ecble.png" class="list-item-img"></image>
+        <image v-else src="/static/img/ble.png" class="list-item-img"></image>
+        <text class="list-item-name">{{item.name}}</text>
+        <image v-if="item.rssi >= -41" src="/static/img/s5.png" mode="aspectFit" class="list-item-rssi-img"></image>
+        <image v-else-if="item.rssi >= -55" src="/static/img/s4.png" mode="aspectFit" class="list-item-rssi-img"></image>
+        <image v-else-if="item.rssi >= -65" src="/static/img/s3.png" mode="aspectFit" class="list-item-rssi-img"></image>
+        <image v-else-if="item.rssi >= -75" src="/static/img/s2.png" mode="aspectFit" class="list-item-rssi-img"></image>
+        <image v-else="item.rssi < -75" src="/static/img/s1.png" mode="aspectFit" class="list-item-rssi-img"></image>
+        <text class="list-item-rssi">{{item.rssi}}</text>
+        <view class="list-item-line"></view>
+      </view>
+      <u-popup :show="showSendData"  >
+        <view class="slot-content">
+          <view>
+            <u--input
+                :placeholder="i18('请输入值')"
+                border="surround"
+                v-model="sendData"
+            ></u--input>
+          </view>
+          <view style="margin:10px;text-decoration: underline;margin-bottom: 0px" @click="sendLanyaData">
+            <u-button>发送</u-button>
+          </view>
+        </view>
+      </u-popup>
+      <view v-if="deviceListDataShow.length==0" class="notice"> - {{ $t('buletooth.nodevice') }} -</view>
+      <view class="gap"></view>
+    </scroll-view>
+  </view>
+
+  <view v-if="connected">
+    <view class="text-area">
+      <u--form  v-if="isCompanyUser()" style="width: 100%;"
+               labelPosition="left"
+               ref="uForm"
+      >
+        <u-form-item
+            label="WIFI名称:"
+            borderBottom
+            labelWidth="auto"
+            @click="openChooseWifi"
+            ref="item1"
+        >
+          <u--input
+              v-model="SSID"
+              disabled
+              disabledColor="#ffffff"
+              placeholder="请选择wifi:"
+              border="none"
+          ></u--input>
+          <u-icon
+              slot="right"
+              name="arrow-right"
+          ></u-icon>
+        </u-form-item>
+        <u-form-item
+            label="密码:"
+            borderBottom
+            ref="item1"
+        >
+          <u--input
+              v-model="password"
+              border="none"
+          ></u--input>
+        </u-form-item>
+
+      </u--form>
+      <u--form  v-else style="width: 100%;"
+                labelPosition="left"
+                ref="uForm"
+      >
+        <u-form-item
+            label="WIFI名称:"
+            borderBottom
+            labelWidth="auto"
+            @click="openChooseWifi"
+            ref="item1"
+        >
+          <u--input
+              v-model="SSID"
+              disabled
+              disabledColor="#ffffff"
+              placeholder="请选择wifi:"
+              border="none"
+          ></u--input>
+          <u-icon
+              slot="right"
+              name="arrow-right"
+          ></u-icon>
+        </u-form-item>
+        <u-form-item
+            label="密码:"
+            borderBottom
+            ref="item1"
+        >
+          <u--input
+              v-model="password"
+              border="none"
+          ></u--input>
+        </u-form-item>
+
+
+        <u-form-item
+            label="二维码ID:"
+            borderBottom
+            ref="item1"
+            labelWidth="auto"
+        >
+          <u--input
+              v-model="qrcodeid"
+              border="none"
+          ></u--input>
+        </u-form-item>
+
+        <u-form-item
+            @click="showProductList = true"
+            label="产品类型:"
+            borderBottom
+            ref="item1"
+            labelWidth="auto"
+        >
+          <u--input disabled
+                    v-model="productName"
+                    border="none"
+          ></u--input>
+        </u-form-item>
+
+      </u--form>
+    </view>
+    <view style="margin:10px">
+      <u-button text="开始配网" v-if="isCompanyUser()" @click="doConnectUser(1)" size="small" type="primary"></u-button>
+
+      <u-button text="开始配网" v-else @click="doConnect(2)" size="small" type="primary"></u-button>
+
+    </view>
+    <u-picker @cancel="showWiftList=false" @confirm="chooseWifi"  :show="showWiftList" :columns="wifiList"></u-picker>
+
+    <u-picker @cancel="showProductList=false" keyName="text" @confirm="chooseProduct"  :show="showProductList" :columns="chooseProductList"></u-picker>
+
+  </view>
+  </view>
+</template>
+
+<script>
+import authObj from '@/plugins/auth.js';
+// #ifdef APP
+import ecUI from '@/utils/ecUI.js'
+import ecBLE from '@/utils/ecBLE/ecBLE.js'
+// #endif
+// #ifdef MP
+const ecUI = require('@/utils/ecUI.js')
+const ecBLE = require('@/utils/ecBLE/ecBLE.js')
+// #endif
+import i18 from '@/utils/i18.js'
+import {getDevcieByQrcodeID} from "@/api/device/device";
+	let ctx
+	let deviceListData = []
+	export default {
+    data() {
+			return {
+        showProductList:false,
+        showPwd:false,
+        rightPwd:"",
+        pwd:"",
+        timer:"",
+        buleid:"",
+				deviceListDataShow: [],
+        showTimer:null,
+        connected:false,
+        uuid:"",
+        showDeviceNo:false,
+        inputDeviceNo:"",
+        commonCode:"",
+        showSendData:false,
+        sendData:"",
+        qrcodeid:"",
+        productName:"",
+        deviceno:"",
+        SSID:"",
+        password:"",
+        showWiftList:false,
+        productId:56,
+        chooseProductList:[[      {
+          id:56,text:"3"
+        },
+          {
+            id:56,text:"4"
+          },
+          {
+            id:57,text:"5"
+          }
+          ]],
+        wifiList: [
+          []
+        ],
+
+			}
+		},
+		onLoad() {
+      uni.setNavigationBarTitle({
+        title: "蓝牙配网"
+      })
+      this.deviceno  = this.generateTimestamp();
+      ecUI.showLoading("正在初始化蓝牙模块")
+			ctx = this
+      clearInterval(this.timer);
+      this.timer = setInterval(() => {
+        ctx.deviceListDataShow = JSON.parse(JSON.stringify(deviceListData))
+      }, 800)
+      console.log(this.commonCode)
+      this.productName = this.chooseProductList[0][0].text;
+		},
+    onUnload(){
+      ecBLE.stopBluetoothDevicesDiscovery();
+      ecBLE.closeBLEConnection()
+    },
+		onShow() {
+      if(this.showTimer!= null){
+        clearTimeout(this.showTimer);
+      }
+			this.showTimer = setTimeout(() => {
+				ctx.openBluetoothAdapter()
+			}, 100)
+		},
+		methods: {
+      isCompanyUser(){
+        return authObj.authRoleAdmin(["companymgr"]) || authObj.authRoleAdmin(["companyuser"]);
+      },
+      chooseProduct(e){
+        this.productId = e.value[0].id;
+        this.productName = e.value[0].text;
+        this.showProductList= false;
+      },
+      generateTimestamp() {
+  const date = new Date();
+  const year = date.getFullYear().toString().substr(2);
+  const month = date.getMonth() + 1;
+  const day = date.getDate();
+  const hours = date.getHours();
+  const minutes = date.getMinutes();
+
+  // 生成6位随机数字
+  const randomNum = Math.floor(Math.random() * 900000) + 100000;
+
+  // 将数字拼接成字符串
+  const timestamp = `${year}${month < 10 ? '0' + month : month}${day < 10 ? '0' + day : day}${hours}${minutes < 10 ? '0' + minutes : minutes}${randomNum}`;
+
+  return timestamp;
+},
+      getAuth(){
+        wx.getSetting({
+          success(res) {
+            if (!res.authSetting['scope.userLocation']) {
+              wx.authorize({
+                scope: 'scope.userLocation',
+                success () {
+                  // 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问
+
+                }
+              })
+            }
+          }
+        })
+      },
+      chooseWifi(e){
+        this.SSID = e.value[0];
+        this.showWiftList= false;
+      },
+      openChooseWifi(){
+        if(this.wifiList[0].length == 0){
+          this.getWifiList();
+        }else{
+          this.showWiftList = true;
+        }
+      },
+      openWifi(){
+        let self = this;
+        // #ifdef MP-WEIXIN
+        wx.startWifi({
+          success(res) {
+            console.log(res);
+            // self.getWifiList();
+          },
+          fail(res) {
+            console.log(res)
+            uni.showToast({
+              title: '请打开WIFI',
+              icon: 'none',
+              duration: 3000
+            });
+
+          },
+        })
+        // #endif
+
+      },
+      getWifi(){
+        // #ifdef MP-WEIXIN
+        var that = this
+        wx.getConnectedWifi({
+          success(res) {
+            console.log(res)
+            that.BSSID = res.wifi.BSSID
+            that.WIFIName = res.wifi.SSID
+          },
+          fail(res) {
+            console.log(res)
+            //报错的相关处理
+          },
+        })
+        // #endif
+
+      },
+      getWifiList(){
+        // #ifdef MP-WEIXIN
+        var that = this
+        uni.showLoading();
+        wx.getWifiList({
+          success(res) {
+            console.log(res)
+            wx.onGetWifiList(function(res) {
+              that.showWiftList = true;
+              uni.hideLoading();
+              console.log("获取wifi列表");
+              that.wifiList = [[]];
+              console.log(res.wifiList); //在这里提取列表数据
+              //通过遍历将WIFI名字存入集合,以便下卡框等组件使用
+              for (var i = 0; i < res.wifiList.length; i++) {
+                that.wifiList[0].push(res.wifiList[i].SSID)
+              }
+            })
+          },
+          fail(res) {
+            console.log(res)
+            uni.showToast({
+              title: '获取wifi失败,请检查wifi',
+              icon: 'none',
+              duration: 2000
+            });
+          },
+        })
+        // #endif
+
+      },
+      doConnectUser(){
+        if(this.SSID == ""){
+          uni.showToast({
+            title: '请选择WIFI',
+            icon: 'none',
+            duration: 3000
+          });
+          return;
+        }
+        if(this.password == ""){
+          uni.showToast({
+            title: '请输入wifi密码',
+            icon: 'none',
+            duration: 3000
+          });
+          return;
+        }
+
+        /**
+         * ID:XXX+回车换行(设备编号)
+         * QR:XXXX+回车换行(二维码ID)
+         * ssid:WiFi名字+回车换行
+         * psd:wifi密码+回车换行
+         * 最后+OK
+         */
+        let self = this;
+        let endStr = "$$";
+        ecUI.showLoading("正在配置网络信息")
+
+        setTimeout(function(){
+          self.sendBlueData("ssid:"+self.SSID+endStr);
+        },400);
+
+        setTimeout(function(){
+          self.sendBlueData("psd:"+self.password+endStr);
+        },600);
+
+
+        setTimeout(function(){
+          self.sendBlueData("OK");
+        },800);
+
+
+
+      },
+      doConnect(){
+        if(this.SSID == ""){
+          uni.showToast({
+            title: '请选择WIFI',
+            icon: 'none',
+            duration: 3000
+          });
+          return;
+        }
+        if(this.password == ""){
+          uni.showToast({
+            title: '请输入wifi密码',
+            icon: 'none',
+            duration: 3000
+          });
+          return;
+        }
+
+        if(this.qrcodeid == ""){
+          uni.showToast({
+            title: '请输入二维码ID',
+            icon: 'none',
+            duration: 3000
+          });
+          return;
+        }
+
+
+        if(this.deviceno == ""){
+          uni.showToast({
+            title: '请输入设备编号',
+            icon: 'none',
+            duration: 3000
+          });
+          return;
+        }
+
+
+        /**
+         * ID:XXX+回车换行(设备编号)
+         * QR:XXXX+回车换行(二维码ID)
+         * ssid:WiFi名字+回车换行
+         * psd:wifi密码+回车换行
+         * 最后+OK
+         */
+        let self = this;
+        getDevcieByQrcodeID(this.qrcodeid).then(res=>{
+          if(res.data != null){
+            self.$modal.showToast("当前二维码已经配置设备");
+          }else{
+            let endStr = "$$";
+            ecUI.showLoading("正在配置网络信息")
+            setTimeout(function(){
+              self.sendBlueData("ID:"+self.deviceno+endStr);
+            },200);
+            setTimeout(function(){
+              self.sendBlueData("QR:"+self.qrcodeid+endStr);
+            },400);
+
+            setTimeout(function(){
+              self.sendBlueData("ssid:"+self.SSID+endStr);
+            },600);
+
+            setTimeout(function(){
+              self.sendBlueData("psd:"+self.password+endStr);
+            },800);
+
+            setTimeout(function(){
+              self.sendBlueData("pid:"+self.productId+endStr);
+            },1000);
+
+            setTimeout(function(){
+              self.sendBlueData("OK");
+            },1200);
+          }
+        })
+
+
+
+
+      },
+      stringToHex(str) {
+          let hex = '';
+          for (let i = 0; i < str.length; i++) {
+            const char = str.charCodeAt(i);
+            const hexChar = char.toString(16).padStart(2, '0');
+            hex += hexChar;
+          }
+          return hex;
+      },
+      doByTime(func,time){
+        setTimeout(function (){
+          func();
+        },time);
+      },
+      sendLanyaData(){
+        this.$modal.showToast("正在发送");
+       this.sendBlueData(this.sendData);
+      },
+      i18(text){
+        return text;
+      },
+      cancel(){
+        this.showPwd = false;
+        uni.navigateBack({
+        });
+      },
+      resetPwd(){
+        let self = this;
+        if(!this.inputDeviceNo){
+          self.$modal.showToast("请输入序列号");
+          return;
+        }
+        let uuidRight = false;
+        if(this.inputDeviceNo == this.commonCode || this.uuid == this.inputDeviceNo){
+          uuidRight = true;
+        }
+        if(!uuidRight){
+          self.$modal.showToast("序列号有误");
+          return;
+        }
+        if(uuidRight){
+          this.$modal.confirm("密码将被重置为123456").then(res=>{
+            setPwd("123456")
+            self.$modal.showToast("密码修改成功");
+            this.rightPwd = "123456";
+            self.loginSuccess();
+          });
+        }
+      },
+       getBeijingTime() {
+         const date = new Date();
+         const utcTime = date.getTime() + (date.getTimezoneOffset() * 60 * 1000);
+         const beijingTime = new Date(utcTime + (8 * 60 * 60 * 1000));
+         return beijingTime;
+      },
+       generateUniqueNumber(date) {
+         let dateString = date.toISOString().slice(0, 10).replace(/-/g, '');
+         console.log(dateString)
+         let hash = w_md5.hex_md5_32(dateString);
+         console.log(hash);//32位小写
+         let str = "";
+         for (let i = 0; i < 6; i++) {
+           const c = hash.charCodeAt(i);
+           str  = str+""+c+""
+         }
+         return str.substr(0,6);
+},
+      goBack(){
+        uni.navigateBack({
+        });
+      },
+      $t(title){
+        return title;
+      },
+      inputPwd(){
+        if(!this.pwd ){
+          this.$modal.showToast(this.$t('buletooth.errpwd'));
+        }else{
+          if(this.rightPwd && this.rightPwd === this.pwd){
+           this.loginSuccess();
+          }else{
+            this.$modal.showToast(this.$t('buletooth.errpwd'));
+          }
+
+        }
+      },
+      loginSuccess(){
+        this.showPwd = false;
+        this.showDeviceNo = false;
+        uni.setStorageSync("pwd",this.rightPwd);
+        uni.setStorageSync('blueid', this.buleid);
+        this.pwd = "";
+        ecBLE.stopBluetoothDevicesDiscovery();
+        uni.navigateTo({
+          url: '/pages/weitiandi/bluetooth/status'
+        });
+      },
+      getLocalPwd(){
+        let pwd = uni.getStorageSync("pwd");
+        return pwd;
+      },
+     sendBlueData(tempSendData){
+       tempSendData = this.stringToHex(tempSendData);
+        let data = tempSendData
+            .replace(/\s*/g, '')
+            .replace(/\n/g, '')
+            .replace(/\r/g, '')
+        console.log("写入数据:"+data);
+        ecBLE.writeBLECharacteristicValue(data, true)
+      },
+			listViewTap(id){
+        let self = this;
+				ecUI.showLoading("正在连接蓝牙")
+				ecBLE.onBLEConnectionStateChange(res => {
+          console.log(res);
+					if (res.ok) {
+            self.connected = true;
+            self.openWifi();
+            ecUI.hideLoading()
+            self.showSendData = true;
+						ecBLE.stopBluetoothDevicesDiscovery();
+            //
+					} else {
+            ecUI.hideLoading()
+            this.$modal.showToast("请检查是否配置成功");
+					}
+				});
+        //receive data
+        ecBLE.onBLECharacteristicValueChange((str, strHex) => {
+			   console.log("数据来了")
+          let isCheckRevHex = true;
+          let data =
+              (isCheckRevHex ? strHex.replace(/[0-9a-fA-F]{2}/g, ' $&') : str)
+          console.log(data)
+          self.$modal.closeLoading();
+          data = parseDataObj(data);
+
+        })
+        self.connected = false;
+				ecBLE.createBLEConnection(id);
+        setTimeout(function (){
+          if(!self.connected){
+            self.$modal.showToast(i18('连接失败'));
+            self.startBluetoothDevicesDiscovery()
+          }
+        },10000);
+			},
+      showInputPwd(){
+        this.showPwd = true;
+      },
+      messageCallback(data){
+        let self = this;
+        console.log(data);
+        let type = data.type;
+        let real_data = data.real_data;
+        if(type == 253){
+          self.$modal.closeLoading();
+          self.uuid = real_data.substr(0,6);
+        }
+        self.$forceUpdate();
+        console.log('收到服务器内容:' + JSON.stringify(data));
+      },
+      forgetPwd(){
+        this.$modal.loading("正在读取设备ID");
+        getUUID()
+        this.showDeviceNo = true;
+      },
+			openBluetoothAdapter() {
+        let self = this;
+				ecBLE.onBluetoothAdapterStateChange(res => {
+          ecUI.hideLoading()
+					if (res.ok) {
+              ctx.startBluetoothDevicesDiscovery()
+					} else {
+						ecUI.showModal(
+                this.$t('buletooth.tip'),
+							`Bluetooth adapter error | ${res.errCode} | ${res.errMsg}`,
+							() => {
+
+							}
+						)
+					}
+				})
+				ecBLE.openBluetoothAdapter()
+			},
+			startBluetoothDevicesDiscovery() {
+        ecBLE.stopBluetoothDevicesDiscovery();
+				console.log('start search')
+				ecBLE.onBluetoothDeviceFound(res => {
+          let isRight = true;
+          // if(res.name.startsWith('BT_') || res.name.startsWith('FC41D')){
+          //   isRight = true;
+          // }
+          if(!isRight){
+            return;
+          }
+					for (const item of deviceListData) {
+						if (item.id === res.id) {
+							item.name = res.name
+							item.rssi = res.rssi
+							return
+						}
+					}
+					let manufacturer = ''
+					if (res.name.length === 11 && res.name.startsWith('@')) {
+						manufacturer = 'eciot'
+					}
+					if (res.name.length === 15 && res.name.startsWith('BT_')) {
+						manufacturer = 'eciot'
+					}
+          manufacturer = 'eciot'
+					deviceListData.push({
+						id: res.id,
+						name: res.name,
+						rssi: res.rssi,
+						manufacturer,
+					})
+				})
+				ecBLE.startBluetoothDevicesDiscovery()
+			},
+		}
+	}
+</script>
+
+<style>
+	.main-container {
+		height: 100vh;
+	}
+
+	.list-item {
+		height: 57px;
+		position: relative;
+	}
+
+	.list-item-hover {
+		background-color: #e5e4e9;
+	}
+
+	.list-item-img {
+		position: absolute;
+		width: 36px;
+		height: 36px;
+		left: 20px;
+		top: 10px;
+	}
+
+	.list-item-name {
+		position: absolute;
+		font-size: 22px;
+		left: 76px;
+		top: 0px;
+		line-height: 56px;
+	}
+
+	.list-item-rssi-img {
+		position: absolute;
+		width: 20px;
+		height: 20px;
+		right: 20px;
+		top: 13px;
+	}
+
+	.list-item-rssi {
+		position: absolute;
+		width: 40px;
+		height: 20px;
+		right: 10px;
+		top: 33px;
+		font-size: 12px;
+		font-weight: bold;
+		display: flex;
+		justify-content: center;
+	}
+
+	.list-item-line {
+		position: absolute;
+		height: 1px;
+		width: 100vw;
+		left: 20px;
+		top: 56px;
+		background-color: #c6c6c8;
+	}
+
+	.notice {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		margin-top: 10px;
+		font-size: 13px;
+		color: #909399;
+	}
+
+	.gap {
+		height: 57px;
+	}
+  .text-area {
+    display: flex;
+    justify-content: center;
+    background: white;
+    width: 100%;
+    height: 100%;
+  }
+</style>

+ 613 - 0
pages/bluetooth/set/one.vue

@@ -0,0 +1,613 @@
+<template>
+  <view class="container">
+
+
+    <!-- 头部区域 -->
+    <view class="header">
+      <u-row>
+        <u-col span="2">
+          <view class="status-btn" :class="{ active_btn: communicationLink_1 }">Link</view>
+        </u-col>
+        <u-col span="2">
+          <view class="status-btn" :class="{ active_btn: bleConnected }">blue</view>
+        </u-col>
+        <u-col span="6" textAlign="left">
+          <text class="title">长沙亿旭智能</text>
+        </u-col>
+        <u-col span="2">
+
+        </u-col>
+      </u-row>
+    </view>
+
+    <!-- 主要内容区域 -->
+    <scroll-view class="content" scroll-y>
+      <view class="param-section">
+        <!-- 经度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">经度</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.longitude"
+                placeholder=""
+                class="param-input"
+            ></u-input>
+          </view>
+        </view>
+
+        <!-- 纬度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">纬度</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.latitude"
+                placeholder=""
+                class="param-input"
+            ></u-input>
+          </view>
+        </view>
+
+        <!-- 时区 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">时区</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.timezone"
+                placeholder=""
+                class="param-input"
+            ></u-input>
+          </view>
+          <u-radio-group v-model="params.timezoneMode" class="radio-group">
+            <u-radio label="读" name="read"></u-radio>
+            <u-radio label="写" name="write"></u-radio>
+          </u-radio-group>
+          <u-button :plain="true" :hairline="true" :disabled="params.timezoneMode == 'read'"  size="mini" class="action-btn" @click="addValue('READ_TEMPERATURE')">天文写入</u-button>
+        </view>
+
+        <!-- 频点 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="warning" size="mini" class="param-label">频点[0-83]</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.frequency"
+                placeholder=""
+                class="param-input"
+            ></u-input>
+          </view>
+        </view>
+
+        <!-- 网络ID -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="warning" size="mini" class="param-label">网络ID</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.networkId"
+                placeholder=""
+                class="param-input"
+            ></u-input>
+          </view>
+        </view>
+
+        <!-- 模块地址 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="warning" size="mini" class="param-label">模块地址</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                v-model="params.moduleAddress"
+                placeholder=""
+                class="param-input"
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+            ></u-input>
+          </view>
+          <u-checkbox-group
+              placement="column">
+            <u-checkbox v-model="params.wWl" class="checkbox" label="W_WL"  @change="handleWwlChange" >W_WL</u-checkbox>
+          </u-checkbox-group>
+          <u-button :plain="true" :hairline="true"  :disabled="params.wWl" size="mini" class="action-btn" @click="addValue('WRITE_ADDRESS')">参数写入</u-button>
+        </view>
+
+        <!-- 东限位 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">东限位(°)</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                v-model="params.eastLimit"
+                placeholder=""
+                class="param-input"
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+            ></u-input>
+          </view>
+        </view>
+
+        <!-- 西限位 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">西限位(°)</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                v-model="params.westLimit"
+                placeholder=""
+                class="param-input"
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+            ></u-input>
+          </view>
+          <u-checkbox-group
+              placement="column">
+            <u-checkbox v-model="params.wLim" class="checkbox" label="W_Lin" @change="limit">W_Lim</u-checkbox>
+          </u-checkbox-group>
+          <u-button :plain="true" :hairline="true" :disabled="params.wLim"  size="mini" class="action-btn" @click="addValue('READ_LIMIT')">限位写入</u-button>
+        </view>
+
+        <!-- 宽度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="warning" size="mini" class="param-label">宽度</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                v-model="params.width"
+                placeholder=""
+                class="param-input"
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+            ></u-input>
+          </view>
+        </view>
+
+        <!-- 间距 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="warning" size="mini" class="param-label">间距</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                v-model="params.spacing"
+                placeholder=""
+                class="param-input"
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+            ></u-input>
+          </view>
+        </view>
+
+        <!-- 上坡度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="warning" size="mini" class="param-label">上坡度</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                v-model="params.uphillSlope"
+                placeholder=""
+                class="param-input"
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+            ></u-input>
+          </view>
+        </view>
+
+        <!-- 下坡度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="warning" size="mini" class="param-label">下坡度</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                v-model="params.downhillSlope"
+                placeholder=""
+                class="param-input"
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+            ></u-input>
+          </view>
+          <u-radio-group v-model="params.downhillMode" class="radio-group">
+            <u-radio label="读" name="read"></u-radio>
+            <u-radio label="写" name="write"></u-radio>
+          </u-radio-group>
+          <u-button :plain="true" :hairline="true" :disabled="params.downhillMode == 'read'"  size="mini" class="action-btn" @click="addValue('READ_INCLINATION')">参数写入</u-button>
+        </view>
+      </view>
+    </scroll-view>
+    <u-modal :show="confirmShow" title="提示" :content='confirmContent' :showCancelButton=true  @cancel="cancel"  @confirm="confirm" ></u-modal>
+
+  </view>
+</template>
+
+<script>
+import {writeRegister} from '@/utils/modbus.js';
+export default {
+  data() {
+    return {
+      editStatus: false,
+      communicationLink_1: false,
+      communicationTimer_1: null, // 添加这一行用于保存定时器 ID
+      selectAction: '',
+      confirmShow: false,
+      confirmContent: '是否确定执行该操作?',
+      checkboxValue1: [],
+      activeTab: 'home',
+      params: {
+        longitude: '',
+        latitude: '',
+        latMode: 'read',
+        timezone: '',
+        timezoneMode: 'read',
+        frequency: '',
+        networkId: '',
+        moduleAddress: '',
+        wWl: true,
+        eastLimit: '',
+        westLimit: '',
+        wLim: true,
+        width: '',
+        spacing: '',
+        uphillSlope: '',
+        uphillMode: 'read',
+        downhillSlope: '',
+        downhillMode: 'read'
+      }
+    }
+  },
+  computed: {
+    bleConnected() {
+      return this.$store.getters['ble/connected']
+    },
+    bleData() {
+      return this.$store.getters['ble/data']
+    }
+  },
+  watch: {
+    bleData: {
+      handler(newData, oldData) {
+        if (newData) {
+          console.log('接收到蓝牙数据:', newData)
+          // 清除之前的定时器
+          if (this.communicationTimer_1) {
+            clearTimeout(this.communicationTimer_1)
+            this.communicationTimer_1 = null
+          }
+          // 设置通信链接状态为 true
+          this.communicationLink_1 = true
+          if (!this.editStatus){
+            this.updateSensorData(newData)
+          }
+
+        }
+      },
+      deep: true, // 深度监听,确保嵌套对象变化时也能触发
+      immediate: true // 立即执行一次
+    }
+  },
+  methods: {
+    limit(value){
+      console.log('限制选择:', value)
+      this.params.wLim = !value;
+    },
+    handleWwlChange(value){
+      this.params.wWl = !value;
+    },
+    handleBlurWithDelay() {
+      setTimeout(() => {
+        this.editStatus = false;
+      }, 1000);
+    },
+    cancel(){
+      this.confirmShow = false
+    },
+    addValue(action) {
+      this.selectAction = action;
+      this.confirmShow = true;
+    },
+    confirm() {
+      const action = this.selectAction;
+      switch (action) {
+        case 'READ_TEMPERATURE': // 天文写入
+          const longitude = parseFloat(this.params.longitude || "0");
+          const latitude = parseFloat(this.params.latitude || "0");
+          const timezone = parseFloat(this.params.timezone || "0");
+
+          // 转换为带两位小数的整数(乘以100)
+          const intValue1 = Math.round(longitude * 100);
+          const intValue2 = Math.round(latitude * 100);
+          const intValue3 = Math.round(timezone * 100);
+          // 创建数组存储值
+          const values = [];
+          values.push(intValue1);
+          values.push(intValue2);
+          values.push(intValue3);
+
+          writeRegister("READ_TEMPERATURE",values);
+          break
+        case 'WRITE_ADDRESS': // 网络地址写入
+          // 获取输入值
+          let frequency = parseInt(this.params.frequency) || 0;     // 频点
+          let networkId = parseInt(this.params.networkId) || 0;     // 网络ID
+          let moduleAddr = parseInt(this.params.moduleAddress) || 0; // 模块地址
+          // 限制频点范围为0-83
+          frequency = Math.max(0, Math.min(83, frequency));
+          // 计算模块地址: 网络ID * 256 + 设备地址
+          let calculatedModuleAddr = (networkId * 256) + moduleAddr;
+          // 创建值数组
+          let addressValues = [frequency, networkId, calculatedModuleAddr];
+          // 调用写入寄存器函数
+          writeRegister("READ_FREQUENCY", addressValues);
+          break
+
+        case 'READ_LIMIT': // 限位写入
+          let eastLimit = parseInt(this.params.eastLimit) || 0;
+          let westLimit = parseInt(this.params.westLimit) || 0;
+          let limit = [eastLimit, westLimit];
+          writeRegister("READ_LIMIT",limit);
+          break
+        case 'READ_INCLINATION': // 坡度写入
+          let width = parseFloat( this.params.width);
+          let interval = parseFloat(this.params.spacing);
+          let upgrade = parseFloat(this.params.uphillSlope);
+          let downgrade = parseFloat(this.params.downhillSlope);
+          // 转换为16位整数并乘以100
+          let number = Math.floor(width * 100);
+          let number1 = Math.floor(interval * 100);
+          let number2 = Math.floor(upgrade * 100);
+          let number3 = Math.floor(downgrade * 100);
+          let number4 = [number, number1, number2, number3];
+          writeRegister("READ_INCLINATION",number4);
+          break
+      }
+      this.confirmShow = false;
+      uni.showToast({
+        title: '操作执行成功!',
+        icon: 'success',
+        duration: 2000
+      });
+    },
+    handleLink() {
+      uni.showToast({
+        title: '连接设备',
+        icon: 'none'
+      })
+    },
+    switchTab(tab) {
+      this.activeTab = tab
+      uni.showToast({
+        title: `切换到${tab}`,
+        icon: 'none'
+      })
+    },
+    updateSensorData(data) {
+      // 根据蓝牙数据更新参数值
+      if (data.Longitude_50 !== undefined && this.params.timezoneMode == 'read') {
+        this.params.longitude = data.Longitude_50
+      }
+
+      if (data.Latitude_51 !== undefined && this.params.timezoneMode == 'read') {
+        this.params.latitude = data.Latitude_51
+      }
+
+      if (data.TimeZone_52 !== undefined && this.params.timezoneMode == 'read') {
+        this.params.timezone = data.TimeZone_52
+      }
+
+      if (data.Frequence_25 !== undefined && this.params.wWl) {
+        this.params.frequency = data.Frequence_25
+      }
+
+      if (data.NetworkId_26 !== undefined && this.params.wWl) {
+        this.params.networkId = data.NetworkId_26
+      }
+
+      if (data.modAddre_27 !== undefined && this.params.wWl) {
+        this.params.moduleAddress = data.modAddre_27
+      }
+
+      if (data.qEasternLimit_64 !== undefined && this.params.wLim) {
+        this.params.eastLimit = data.qEasternLimit_64
+      }
+
+      if (data.qWesternLimit_65 !== undefined && this.params.wLim) {
+        this.params.westLimit = data.qWesternLimit_65
+      }
+
+      if (data.qwidth_60 !== undefined && this.params.downhillMode == 'read') {
+        this.params.width = data.qwidth_60
+      }
+
+      if (data.Interval_61 !== undefined && this.params.downhillMode == 'read') {
+        this.params.spacing = data.Interval_61
+      }
+
+      if (data.UpGrade_62 !== undefined  && this.params.downhillMode == 'read') {
+        this.params.uphillSlope = data.UpGrade_62
+      }
+
+      if (data.DownGrade_63 !== undefined && this.params.downhillMode == 'read') {
+        this.params.downhillSlope = data.DownGrade_63
+      }
+
+      // 设置6秒后将 communicationLink_1 设置为 false 的定时器
+      this.communicationTimer_1 = setTimeout(() => {
+        this.communicationLink_1 = false
+        this.communicationTimer_1 = null
+      }, 5000)
+    }
+  }
+}
+</script>
+
+
+<style lang="scss" >
+@import '@/static/scss/custom-theme.scss';
+
+.container {
+  height: calc(100vh - 140px);
+  display: flex;
+  flex-direction: column;
+  background-color: $custom-bg-tertiary;
+}
+
+.status-bar {
+  height: $custom-status-height;
+  background-color: $custom-bg-primary;
+  @include flex-between;
+  padding: 0 $custom-spacing-md;
+  font-size: 14px;
+
+  .status-left {
+    color: $custom-text-primary;
+  }
+
+  .status-right {
+    @include flex-start;
+    gap: $custom-spacing-xs;
+
+    .data-rate {
+      color: $custom-text-secondary;
+      font-size: 12px;
+    }
+
+    .wifi-icon, .hd-text, .network, .battery {
+      font-size: 12px;
+    }
+  }
+}
+
+.header {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20rpx;
+
+  .link-btn {
+    background-color: #ff4757;
+    border: none;
+    width: 60px;
+    margin-left: 0px;
+  }
+
+  .title {
+    color: #fff;
+    font-size: 36rpx;
+    font-weight: bold;
+  }
+
+  .status-btn {
+    background-color: #ff4757;
+    border: none;
+    margin-left: 0px;
+    padding: 0px;
+    width: 40px;
+    text-align: center;
+    color: white;
+    border-radius: 4px;
+    height: 20px;
+    line-height: 20px;
+  }
+  .active_btn {
+    background-color: yellowgreen !important;
+  }
+
+}
+
+.content {
+  flex: 1;
+  padding: $custom-spacing-md;
+  height: calc(100vh - 60px);
+}
+
+.param-section {
+  @include card-style;
+  overflow: auto;
+}
+
+.param-row {
+  @include flex-start;
+  margin-bottom: $custom-spacing-md;
+  gap: $custom-spacing-sm;
+  height: 40px;
+
+  .param-label {
+    width: 70px;
+    @include button-style('primary');
+  }
+
+  .param-input {
+    flex: 1;
+    width: 60px;
+    @include input-style;
+  }
+
+  .radio-group {
+    @include flex-start;
+    gap: $custom-spacing-md;
+  }
+
+  .checkbox {
+    margin-left: $custom-spacing-sm;
+  }
+
+  .action-btn {
+    border: 1px solid $custom-border-secondary;
+    height: 20px;
+    line-height: 20px;
+    margin-right: 0px;
+  }
+}
+
+
+
+
+// 响应式设计
+@include responsive(md) {
+  .param-row {
+    .param-input {
+      min-width: 150px;
+    }
+  }
+}
+
+@include responsive(lg) {
+  .param-row {
+    .param-input {
+      min-width: 200px;
+    }
+  }
+}
+
+::v-deep .param-section .u-button--mini{
+   width: 80px !important;
+   height: 36px !important;
+   font-size: 10px !important;
+}
+
+::v-deep .param-section .u-input{
+   width: 60px !important;
+}
+::v-deep .content text{
+  font-size: 12px !important;
+}
+</style>

+ 469 - 0
pages/bluetooth/set/record.vue

@@ -0,0 +1,469 @@
+<template>
+	<view class="container">
+
+    <!-- 头部区域 -->
+    <view class="header">
+      <u-row>
+        <u-col span="2">
+          <view class="status-btn" :class="{ active_btn: communicationLink }">Link</view>
+        </u-col>
+        <u-col span="2">
+          <view class="status-btn" :class="{ active_btn: bleConnected }">blue</view>
+        </u-col>
+        <u-col span="6" textAlign="left">
+          <text class="title">长沙亿旭智能</text>
+        </u-col>
+        <u-col span="2">
+
+        </u-col>
+      </u-row>
+    </view>
+	
+	
+		<view class="main-content">
+			<!-- 接收窗口 -->
+			<view class="receive-section">
+				<text class="section-title">接收窗口</text>
+				<view class="receive-window">
+					<scroll-view scroll-y="true" class="receive-textarea">
+            <view
+                v-for="(log, index) in sortedWriteLogs"
+                :key="index"
+                class="log-item"
+            >
+              <view class="log-time">{{log.time}}{{log.data}}</view>
+            </view>
+					</scroll-view>
+				</view>
+			</view>
+
+
+			<!-- 输入控制区域 -->
+			<view class="input-section">
+
+        <u-row customStyle="margin-bottom: 10px" gutter="10">
+          <u-col span="8">
+            <u-input
+                v-model="inputValue"
+                placeholder="输入指令"
+                class="command-input"
+            ></u-input>
+          </u-col>
+          <u-col span="4">
+            <u-checkbox-group
+                placement="column">
+              <u-checkbox v-model="cmdChecked" label="cmd" @change="changeCmd"></u-checkbox>
+            </u-checkbox-group>
+          </u-col>
+        </u-row>
+        <!-- 操作按钮 -->
+        <u-row justify="end" customStyle="margin-bottom: 10px">
+          <u-col span="4">
+            <u-button type="info" size="mini" class="write-btn"  @click="sendMsg">指令发送</u-button>
+          </u-col>
+        </u-row>
+
+
+        <u-row customStyle="margin-bottom: 10px" gutter="10">
+          <u-col span="6">
+            <u-button text="清空发送" @click="removeLogsByType('rx')"></u-button>
+          </u-col>
+          <u-col span="6">
+            <u-button text="清空接受" @click="removeLogsByType('tx')"></u-button>
+          </u-col>
+        </u-row>
+
+			</view>
+		</view>
+
+	</view>
+</template>
+
+<script>
+  import ecBLEApp from '@/utils/ecBLE/ecBLEApp.js'
+	import UniDataPickerView
+    from "../../../uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue";
+
+  export default {
+    components: {UniDataPickerView},
+		data() {
+			return {
+        communicationLink: false,
+        communicationTimer_3: null, // 添加这一行用于保存定时器 ID
+        writeLogs: [],
+        showSex: false,
+        checkboxValue1: [],
+				inputValue: '',
+				cmdChecked: false,
+				carriageReturnChecked: false,
+				wtypeValue: '',
+				wtypeChecked: false,
+				componentType: '单面',
+				activeTab: 'receive',
+				componentList: [
+					{ name: '单面'},
+					{ name: '双面'},
+					{ name: '多面'}
+				]
+			}
+		},
+    onShow() {
+      // 从全局变量加载日志
+      this.writeLogs = ecBLEApp.getGlobalWriteLogs()
+      // 添加监听器以实时更新
+      ecBLEApp.addLogListener(this.onLogUpdate)
+    },
+
+    onHide() {
+      // 移除监听器
+      ecBLEApp.removeLogListener(this.onLogUpdate)
+    },
+    computed: {
+      bleConnected() {
+        return this.$store.getters['ble/connected']
+      },
+      bleData() {
+        return this.$store.getters['ble/data']
+      },
+      // 修改排序计算属性
+      sortedWriteLogs() {
+        return this.writeLogs.slice().sort((a, b) => {
+          return new Date(b.time) - new Date(a.time); // 倒序排列
+        });
+      }
+    },
+    watch: {
+      bleData: {
+        handler(newData, oldData) {
+          if (newData) {
+            console.log('接收到蓝牙数据:', newData)
+            // 清除之前的定时器
+            if (this.communicationTimer_3) {
+              clearTimeout(this.communicationTimer_3)
+              this.communicationTimer_3 = null
+            }
+            // 设置通信链接状态为 true
+            this.communicationLink = true
+            this.updateSensorData(newData)
+
+            // 设置6秒后将 communicationLink 设置为 false 的定时器
+            this.communicationTimer_3 = setTimeout(() => {
+              this.communicationLink = false
+              this.communicationTimer_3 = null
+            }, 5000)
+          }
+        },
+        deep: true, // 深度监听,确保嵌套对象变化时也能触发
+        immediate: true // 立即执行一次
+      }
+    },
+		methods: {
+      updateSensorData(data) {
+
+      },
+      sendMsg(){
+        uni.showModal({
+          title: '提示',
+          content: `是否确定发送消息`+this.inputValue+`?`,
+          success: (res) => {
+            if (res.confirm) {
+              ecBLEApp.writeBLECharacteristicValueTwo(this.inputValue,!this.cmdChecked);
+            }
+          }
+        });
+      },
+      changeCmd(e) {
+        this.cmdChecked = e
+      },
+      changeWtype(e){
+        this.wtypeChecked = e
+      },
+      // 日志更新回调
+      onLogUpdate(logs) {
+        // this.writeLogs = [...logs] // 创建新数组以触发响应式更新
+        this.writeLogs.splice(0, this.writeLogs.length, ...logs)
+      },
+      loadLogs() {
+        // 从全局变量获取日志
+        const newLogs = ecBLEApp.getGlobalWriteLogs()
+        // 使用数组变更方法保持响应式
+        this.writeLogs.splice(0, this.writeLogs.length, ...newLogs)
+      },
+      selectValue(e) {
+        this.componentType = e.name
+      },
+			switchTab(tab) {
+				this.activeTab = tab;
+			},
+			sendCommand() {
+				// 发送指令逻辑
+				console.log('发送指令:', this.inputValue);
+			},
+			writeType() {
+				// 类型写入逻辑
+				console.log('写入类型:', this.wtypeValue);
+			},
+			clearSend() {
+				this.inputValue = '';
+			},
+			clearReceive() {
+				// 清空接收窗口
+				console.log('清空接收窗口');
+			},
+      removeLogsByType(type) {
+        uni.showModal({
+          title: '确认删除',
+          content: `确定要删除所有类型为 '${type}' 的日志吗?`,
+          success: (res) => {
+            if (res.confirm) {
+              const result = ecBLEApp.removeWriteDataByType(type)
+              if (result.success) {
+                uni.showToast({
+                  title: `已删除${result.deletedCount}条日志`,
+                  icon: 'success'
+                })
+                // 重新加载日志
+                this.loadLogs();
+                console.log('删除日志结果:', result)
+                console.log(this.writeLogs)
+              } else {
+                uni.showToast({
+                  title: '删除失败',
+                  icon: 'none'
+                })
+              }
+            }
+          }
+        })
+      },
+		}
+	}
+</script>
+
+<style lang="scss" >
+@import '@/static/scss/custom-theme.scss';
+
+.container {
+	height: calc(100vh - 120px);
+	display: flex;
+	flex-direction: column;
+	background-color: #f5f5f5;
+}
+
+.header {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20rpx;
+
+  .link-btn {
+    background-color: #ff4757;
+    border: none;
+    width: 60px;
+    margin-left: 0px;
+  }
+
+  .title {
+    color: #fff;
+    font-size: 36rpx;
+    font-weight: bold;
+  }
+
+  .status-btn {
+    background-color: #ff4757;
+    border: none;
+    margin-left: 0px;
+    padding: 0px;
+    width: 40px;
+    text-align: center;
+    color: white;
+    border-radius: 4px;
+    height: 20px;
+    line-height: 20px;
+  }
+  .active_btn {
+    background-color: yellowgreen !important;
+  }
+}
+
+.status-bar {
+	height: 60rpx;
+	background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 0 20rpx;
+	color: white;
+	font-size: 24rpx;
+}
+
+.status-left {
+	display: flex;
+	align-items: center;
+	gap: 20rpx;
+}
+
+.status-center {
+	flex: 1;
+	text-align: center;
+}
+
+.app-title {
+	font-size: 32rpx;
+	font-weight: bold;
+}
+
+.status-right {
+	display: flex;
+	align-items: center;
+	gap: 10rpx;
+}
+
+.network-info {
+	font-size: 20rpx;
+}
+
+.signal {
+	font-size: 20rpx;
+}
+
+.signal-bars {
+	display: flex;
+	gap: 2rpx;
+}
+
+.bar {
+	width: 4rpx;
+	height: 12rpx;
+	background-color: white;
+	border-radius: 1rpx;
+}
+
+.battery {
+	font-size: 20rpx;
+}
+
+.main-content {
+	flex: 1;
+	padding: 20rpx;
+	background-color: #f5f5f5;
+}
+
+.receive-section {
+	margin-bottom: 30rpx;
+}
+
+.section-title {
+	font-size: 28rpx;
+	font-weight: bold;
+	color: #333;
+	margin-bottom: 10rpx;
+	display: block;
+}
+
+.receive-window {
+	background-color: white;
+	border-radius: 10rpx;
+	border: 2rpx solid #e0e0e0;
+	height: 40vh;
+  overflow: auto;
+}
+
+.receive-textarea {
+	height: 100%;
+	padding: 20rpx;
+}
+
+.placeholder-text {
+	color: #999;
+	font-size: 24rpx;
+}
+
+.input-section {
+	background-color: white;
+	border-radius: 10rpx;
+	padding: 20rpx;
+}
+
+.input-row {
+	display: flex;
+	align-items: center;
+	margin-bottom: 20rpx;
+	gap: 20rpx;
+}
+
+.input-group {
+	display: flex;
+	align-items: center;
+	flex: 1;
+	gap: 10rpx;
+}
+
+.command-input, .wtype-input {
+	flex: 1;
+}
+
+.scroll-btn {
+	width: 60rpx;
+	height: 60rpx;
+}
+
+.checkbox-group {
+	display: flex;
+	align-items: center;
+}
+
+.send-btn {
+	width: 120rpx;
+}
+
+.component-section {
+	margin-bottom: 20rpx;
+}
+
+.component-select {
+	width: 100%;
+}
+
+.wtype-section {
+	margin-bottom: 20rpx;
+}
+
+.write-btn {
+  padding: 2px;
+  color: #a7a7a7;
+  margin: 5px;
+  height: 30px;
+}
+
+.action-buttons {
+	display: flex;
+	gap: 20rpx;
+	margin-bottom: 20rpx;
+}
+
+.clear-btn {
+	flex: 1;
+}
+
+.device-info {
+	display: flex;
+	align-items: center;
+	gap: 10rpx;
+  margin-bottom: 10px;
+}
+
+.info-label {
+	font-size: 28rpx;
+	color: #333;
+}
+
+.info-value {
+	font-size: 28rpx;
+	color: #666;
+}
+
+::v-deep .input-section .u-button--mini{
+  width: 100px !important;
+  height: 30px !important;
+}
+
+</style>

+ 620 - 0
pages/bluetooth/set/set.vue

@@ -0,0 +1,620 @@
+<template>
+  <view class="container">
+    <!-- 头部区域 -->
+    <view class="header">
+      <u-row>
+        <u-col span="2">
+          <view class="status-btn" :class="{ active_btn: communicationLink_set }">Link</view>
+        </u-col>
+        <u-col span="2">
+          <view class="status-btn" :class="{ active_btn: bleConnected }">blue</view>
+        </u-col>
+        <u-col span="6" textAlign="left">
+          <text class="title">长沙亿旭智能</text>
+        </u-col>
+        <u-col span="2">
+        </u-col>
+      </u-row>
+    </view>
+
+    <!-- 主要内容区域 -->
+    <view class="main-content">
+      <!-- 日期时间显示 -->
+      <view class="datetime-box">
+        <text class="datetime-text">{{ currentDateTime }}</text>
+      </view>
+
+      <view class="status-indicators">
+        <!-- 状态指示器 -->
+        <view class="indicator-row">
+          <view class="indicator-item" v-for="(item, index) in indicators1" :key="index">
+            <view class="status-indicator" :class="{ 'active': item.value }"></view>
+            <text class="indicator-text">{{ item.label }}</text>
+          </view>
+        </view>
+        <view class="indicator-row">
+          <view class="indicator-item" v-for="(item, index) in indicators2" :key="index">
+            <view class="status-indicator" :class="{ 'active': item.value }"></view>
+            <text class="indicator-text">{{ item.label }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 传感器读数和控制 -->
+      <view class="sensor-controls">
+        <view class="control-item" v-for="(item, index) in sensorControls" :key="index">
+          <view class="control-label">{{ item.label }}</view>
+          <view class="control-value">
+            <u--input
+                border="none"
+                v-model="item.value"
+            ></u--input>
+          </view>
+          <view class="control-buttons">
+            <u-button
+                v-for="(btn, btnIndex) in item.buttons"
+                :key="btnIndex"
+                :plain="true" :hairline="true"
+                :class="getButtonClass(btn)"
+                size="mini"
+                @click="handleButtonClick(btn.text,btn.action)"
+            >
+              {{ btn.text }}
+            </u-button>
+          </view>
+        </view>
+      </view>
+    </view>
+    <u-modal :show="confirmShow" title="提示" :content='confirmContent' :showCancelButton=true  @cancel="cancel"  @confirm="confirm" ></u-modal>
+  </view>
+</template>
+
+<script>
+import {writeRegister,heartbeat} from '@/utils/modbus.js';
+export default {
+  data() {
+    return {
+      communicationLink_set: false,
+      communicationTimer_set: null,
+      selectAction: '',
+      currentDateTime: '0000-00-00 00:00:00',
+      currentTab: 0,
+      rxCount: 0,
+      txCount: 0,
+      confirmShow: false,
+      confirmContent: '是否确定执行该操作?',
+      workMode: '', // 添加工作模式变量
+      indicators1: [
+        {name: 'tilt', label: '倾角', value: false},
+        {name: 'limit', label: '限位', value: false},
+        {name: 'overcurrent', label: '过流', value: false}
+      ],
+      indicators2: [
+        {name: 'rtc', label: 'RTC', value: false},
+        {name: 'wind', label: '大风', value: false},
+        {name: 'undervoltage', label: '欠压', value: false}
+      ],
+      sensorControls: [
+        {
+          name: 'sunAltitude',
+          label: '太阳高度角',
+          value: '',
+          buttons: [
+            {text: '自动', action: 'auto', type: 'primary'}
+          ]
+        },
+        {
+          name: 'sunAzimuth',
+          label: '太阳方位角',
+          value: '',
+          buttons: [
+            {text: '手动', action: 'manual', type: 'primary'},
+            {text: '标定', action: 'calibrate', type: 'primary'}
+          ]
+        },
+        {
+          name: 'targetAngle',
+          label: '目标角度',
+          value: '',
+          buttons: [
+            {text: '向东', action: 'east', type: 'primary'},
+            {text: '放平', action: 'level', type: 'primary'}
+          ]
+        },
+        {
+          name: 'angle1',
+          label: '角度1',
+          value: '',
+          buttons: [
+            {text: '向西', action: 'west', type: 'primary'},
+            {text: '雪', action: 'snow', type: 'primary'}
+          ]
+        },
+        {
+          name: 'angle2',
+          label: '角度2',
+          value: '',
+          buttons: [
+            {text: '雨', action: 'rain', type: 'primary'},
+            {text: '风', action: 'wind', type: 'primary'}
+          ]
+        },
+        {
+          name: 'motor1Current',
+          label: '电机1电流(A)',
+          value: '',
+          buttons: []
+        },
+        {
+          name: 'motor2Current',
+          label: '电机2电流(A)',
+          value: '',
+          buttons: []
+        },
+        {
+          name: 'temperature',
+          label: '温度(°)',
+          value: '',
+          buttons: []
+        },
+        {
+          name: 'voltage',
+          label: '电压(V)',
+          value: '',
+          buttons: [
+            {text: '校正时间', action: 'calibrateTime', type: 'primary', number: 1}
+          ]
+        }
+      ]
+    }
+  },
+  computed: {
+    bleConnected() {
+      return this.$store.getters['ble/connected']
+    },
+    bleData() {
+      return this.$store.getters['ble/data']
+    }
+  },
+  watch: {
+    bleData: {
+      handler(newData, oldData) {
+        if (newData) {
+          console.log('newData:', newData)
+          // 清除之前的定时器
+          if (this.communicationTimer_set) {
+            clearTimeout(this.communicationTimer_set)
+            this.communicationTimer_set = null
+          }
+          // 设置通信链接状态为 true
+          this.communicationLink_set = true
+          this.updateSensorData(newData)
+
+
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    cancel(){
+      this.confirmShow = false
+    },
+    confirm() {
+      switch (this.selectAction) {
+        case 'auto': // 自动
+          writeRegister("READ_AUTO",null);
+          break
+        case 'manual': // 手动
+          writeRegister("READ_MANUAL",null)
+          break
+        case 'calibrate':  // 标定
+          // writeRegister("WRITE_REGISTER",null)
+          break
+        case 'east': // 向东
+          writeRegister("READ_DOWN",null)
+          break
+        case 'level': // 放平
+          writeRegister("FLATTEN",null)
+          break
+        case 'west': // 向西
+          writeRegister("READ_UP",null)
+          break
+        case 'snow': // 雪
+          writeRegister("SNOW",null)
+          break
+        case 'rain': // 雨
+          writeRegister("RAIN",null)
+          break
+        case 'wind': // 风
+          writeRegister("WIND",null)
+          break
+        case 'calibrateTime':  // 校正时间
+          writeRegister("READ_TIME",this.getCurrentTimeValues())
+          break
+        case 'cancel': //取消
+          writeRegister("READ_CANCEL",null)
+      }
+      this.confirmShow = false;
+      uni.showToast({
+        title: '操作执行成功!',
+        icon: 'success',
+        duration: 2000
+      });
+    },
+    handleButtonClick(text,action) {
+      console.log(action)
+      this.selectAction = action;
+      this.confirmContent = `是否确定执行`+text+`相关的操作?`;
+      this.confirmShow = true;
+    },
+    // 获取按钮的class
+    getButtonClass(btn) {
+      // 建立action与工作模式的映射关系
+      const actionToModeMap = {
+        'auto': '自动',
+        'manual': '手动',
+        'level': '放平',
+        'snow': '雪',
+        'rain': '雨',
+        'wind': '风',
+        'east':'向东',
+        'west':'向西',
+      };
+
+      let classes = ['btn-broder'];
+
+      // 添加宽度类
+      if (btn.number == 1) {
+        classes.push('one-btn');
+      }
+
+      // 如果是工作模式按钮且与当前工作模式匹配,则添加绿色背景类
+      if (actionToModeMap[btn.action] && this.workMode === actionToModeMap[btn.action]) {
+        classes.push('active-work-mode');
+      }
+
+      return classes;
+    },
+    updateSensorData(data) {
+      // 根据蓝牙数据更新传感器值
+      this.sensorControls.forEach(control => {
+        switch (control.name) {
+          case 'sunAltitude': // 太阳高度角
+            if (data.qEleAngle_53 !== undefined) {
+              control.value = data.qEleAngle_53
+            }
+            break
+          case 'sunAzimuth': // 太阳方位角
+            if (data.qAzimuth_54 !== undefined) {
+              control.value = data.qAzimuth_54
+            }
+            break
+          case 'targetAngle': // 目标角度
+            if (data.qTargetAngle_42 !== undefined) {
+              control.value = data.qTargetAngle_42
+            }
+            break
+          case 'angle1': // 角度1
+            if (data.qRealAngle_43 !== undefined) {
+              control.value = data.qRealAngle_43
+            }
+            break
+          case 'angle2': // 角度2
+            if (data.qRealAngle_31 !== undefined) {
+              control.value = data.qRealAngle_31
+            }
+            break
+          case 'motor1Current': // 电机1电流
+            if (data.MotorCurrent_30 !== undefined) {
+              control.value = data.MotorCurrent_30
+            }
+            break
+          case 'motor2Current': // 电机2电流
+            if (data.MotorCurrent_35 !== undefined) {
+              control.value = data.MotorCurrent_35
+            }
+            break
+          case 'temperature': // 温度
+            if (data.Temperature_33 !== undefined) {
+              control.value = data.Temperature_33
+            }
+            break
+          case 'voltage': // 电压
+            if (data.Battery_32 !== undefined) {
+              control.value = data.Battery_32
+            }
+            break
+        }
+      })
+
+      console.log('WorkModle_41:',data.WorkModle_41);
+      // 根据C++中的工作模式解析逻辑更新workMode
+      if (data.WorkModle_41 !== undefined) {
+        const workModeBits = data.WorkModle_41.toString();
+        switch (workModeBits) {
+          case "100000": // 自动
+            this.workMode = '自动'
+            break
+          case "10000": // 手动
+            this.workMode = '手动'
+            break
+          case "1000000": // 放平
+            this.workMode = '放平'
+            break
+          case "1000000000": // 雨
+            this.workMode = '雨'
+            break
+          case "100000000": // 雪
+            this.workMode = '雪'
+            break
+          case "10000000000": // 风
+            this.workMode = '风'
+            break
+          case "10000000": // 指定
+            this.workMode = '指定'
+            break
+          case "11000": // 向东
+            this.workMode = '向东'
+            break
+          case "10100": // 向西
+            this.workMode = '向西'
+            break
+          default:
+            this.workMode = '未知模式'
+            break
+        }
+      }
+
+      if (data.Message_40 !== undefined) {
+        // 根据二进制字符串更新指示器状态
+        const messageBits = data.Message_40.toString()
+        console.log('messageBits:',messageBits)
+        // 清除所有状态
+        this.indicators1.forEach(item => item.value = false)
+        this.indicators2.forEach(item => item.value = false)
+
+        // 根据实际协议确定位的含义
+        if(messageBits === "100") {
+          this.indicators1[0].value = true // 倾角
+        } else if(messageBits === "10") {
+          this.indicators1[2].value = true // 过流
+        } else if(messageBits === "1") {
+          this.indicators1[1].value = true // 限位
+        } else if(messageBits === "110") {
+          this.indicators1[2].value = true // 过流
+          this.indicators1[1].value = true // 限位
+        } else if(messageBits === "101") {
+          this.indicators1[0].value = true // 倾角
+          this.indicators1[2].value = true // 过流
+        } else if(messageBits === "11") {
+          this.indicators1[1].value = true // 限位
+          this.indicators1[2].value = true // 过流
+        } else if(messageBits === "111") {
+          this.indicators1[0].value = true // 倾角
+          this.indicators1[1].value = true // 限位
+          this.indicators1[2].value = true // 过流
+        }
+      }
+
+      // 更新时间显示
+      if (data.nowtime !== undefined) {
+        this.currentDateTime = data.nowtime
+      }
+
+      // 设置6秒后将 communicationLink_set 设置为 false 的定时器
+      this.communicationTimer_set = setTimeout(() => {
+        this.communicationLink_set = false
+        this.communicationTimer_set = null
+      }, 5000)
+    },
+    getCurrentTimeValues(){
+      const now = new Date();
+      // 获取各个时间组件
+      const year = now.getFullYear();        // 年份 (例如: 2024)
+      const month = now.getMonth() + 1;      // 月份 (0-11, 需要+1变为1-12)
+      const day = now.getDate();             // 日期 (1-31)
+      const hours = now.getHours();          // 小时 (0-23)
+      const minutes = now.getMinutes();      // 分钟 (0-59)
+      const seconds = now.getSeconds();      // 秒 (0-59)
+
+      // 返回时间值数组,每个值都是16位整数
+      return [
+        year,      // 年
+        month,     // 月
+        day,       // 日
+        hours,     // 时
+        minutes,   // 分
+        seconds    // 秒
+      ];
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+  background-color: #f5f5f5;
+  height: calc(100vh - 120px);
+  display: flex;
+  flex-direction: column;
+}
+
+.header {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20rpx;
+
+  .link-btn {
+    background-color: #ff4757;
+    border: none;
+    width: 60px;
+    margin-left: 0px;
+  }
+
+  .title {
+    color: #fff;
+    font-size: 36rpx;
+    font-weight: bold;
+  }
+
+  .status-btn {
+    background-color: #ff4757;
+    border: none;
+    margin-left: 0px;
+    padding: 0px;
+    width: 40px;
+    text-align: center;
+    color: white;
+    border-radius: 4px;
+    height: 20px;
+    line-height: 20px;
+  }
+  .active_btn {
+    background-color: yellowgreen !important;
+  }
+}
+
+.main-content {
+  flex: 1;
+  padding: 20rpx;
+}
+
+.work-mode-display {
+  background-color: #fff;
+  padding: 20rpx;
+  border-radius: 16rpx;
+  margin-bottom: 20rpx;
+  text-align: center;
+
+  .work-mode-text {
+    font-size: 28rpx;
+    font-weight: bold;
+    color: #333;
+  }
+}
+
+.datetime-box {
+  background-color: #fff;
+  padding: 30rpx;
+  border-radius: 16rpx;
+  margin-bottom: 20rpx;
+  text-align: center;
+
+  .datetime-text {
+    font-size: 32rpx;
+    font-weight: bold;
+    color: #333;
+  }
+}
+
+.status-indicators {
+  background-color: #fff;
+  padding: 20rpx;
+  border-radius: 16rpx;
+  margin-bottom: 20rpx;
+
+  .indicator-row {
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+    margin-bottom: 20rpx;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  .indicator-item {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex: 1;
+    text-align: center;
+    gap: 10rpx;
+  }
+
+  .indicator-text {
+    font-size: 24rpx;
+    color: #666;
+  }
+}
+
+.sensor-controls {
+  background-color: #fff;
+  border-radius: 16rpx;
+  overflow: auto;
+  height: calc(100% - 220rpx);
+
+  .control-item {
+    display: flex;
+    align-items: center;
+    padding: 20rpx;
+    border-bottom: 1rpx solid #f0f0f0;
+
+    &:last-child {
+      border-bottom: none;
+    }
+  }
+
+  .control-label {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: #fff;
+    padding: 10rpx 20rpx;
+    border-radius: 8rpx;
+    font-size: 24rpx;
+    width: 240rpx;
+    text-align: center;
+    margin-right: 20rpx;
+  }
+
+  .control-value {
+    background-color: #f8f9fa;
+    padding: 10rpx 20rpx;
+    border-radius: 8rpx;
+    font-size: 28rpx;
+    font-weight: bold;
+    color: #333;
+    width: 140rpx;
+    height: 60rpx;
+    text-align: center;
+    margin-right: 20rpx;
+  }
+
+  .control-buttons {
+    display: flex;
+    gap: 20rpx;
+  }
+
+  .u-button--mini {
+    width: 120rpx;
+  }
+}
+
+.one-btn {
+  width: 240rpx !important;
+}
+
+.btn-broder {
+  border: 1rpx solid #dadbde;
+}
+
+// 活跃的工作模式按钮样式
+.active-work-mode {
+  background-color: #00ff00 !important; // 绿色背景
+  color: #000 !important;
+  border-color: #00ff00 !important;
+}
+
+.status-indicator {
+  width: 20rpx;
+  height: 20rpx;
+  border-radius: 50%;
+  background-color: #ccc;
+  margin-right: 10rpx;
+
+  &.active {
+    background-color: red;
+  }
+}
+</style>

+ 785 - 0
pages/bluetooth/set/two.vue

@@ -0,0 +1,785 @@
+<template>
+  <view class="container">
+
+
+    <!-- 头部区域 -->
+    <view class="header">
+      <u-row>
+        <u-col span="2">
+          <view class="status-btn" :class="{ active_btn: communicationLink_2 }">Link</view>
+        </u-col>
+        <u-col span="2">
+          <view class="status-btn" :class="{ active_btn: bleConnected }">blue</view>
+        </u-col>
+        <u-col span="6" textAlign="left">
+          <text class="title">长沙亿旭智能</text>
+        </u-col>
+        <u-col span="2">
+
+        </u-col>
+      </u-row>
+    </view>
+
+
+    <!-- 主要内容区域 -->
+    <scroll-view class="content" scroll-y>
+      <view class="param-section">
+        <!-- 电机方向 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">电机方向</u-button>
+          </view>
+          <view class="input-class" >
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.motorDirection"
+                disabledColor="#ffffff"
+                placeholder="请选择方向"
+                readonly
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group
+                placement="column">
+              <u-checkbox v-model="params.wDir" @change="handleDirectionChange"  class="checkbox" label="W_Dir">W_Dir</u-checkbox>
+            </u-checkbox-group>
+          </view>
+
+          <u-button :plain="true" :hairline="true"  size="mini" class="action-btn" @click="writeDirection" :disabled="params.wDir">方向设置</u-button>
+        </view>
+
+        <!-- 通讯时间 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">通讯时间</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.communicationTime"
+                disabledColor="#ffffff"
+                placeholder=""
+
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group v-model="checkboxValue_timedSend"
+                placement="column">
+              <u-checkbox  name="timedSend"   v-model="params.timedSend" class="checkbox" label="定时发送" @change="handleCommunicationTimeChange">定时发送</u-checkbox>
+            </u-checkbox-group>
+          </view>
+        </view>
+
+        <!-- 夜返角度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">夜返角度°</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.nightReturnAngle"
+                placeholder="请输入角度"
+                type="text"
+                class="param-input"
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group
+
+                placement="column">
+              <u-checkbox v-model="params.wBackA" class="checkbox" label="W_BackA"  @change="handleNightReturnAngleChange">W_BackA</u-checkbox>
+            </u-checkbox-group>
+          </view>
+          <u-button :plain="true" :hairline="true"  size="mini" class="action-btn" :disabled="params.wBackA"
+                    @click="writeNightReturnAngle">夜返角写入
+          </u-button>
+        </view>
+
+        <!-- 放平角度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">放平角度°</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.flatAngle"
+                placeholder="请输入角度"
+                type="text"
+                class="param-input"
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group
+
+                placement="column">
+              <u-checkbox v-model="params.wFlat" class="checkbox" label="W_Flat" @change="handleFlatAngleChange">W_Flat</u-checkbox>
+            </u-checkbox-group>
+          </view>
+          <u-button :plain="true" :hairline="true"  size="mini" class="action-btn" :disabled="params.wFlat" @click="writeFlatAngle">
+            放平角度写入
+          </u-button>
+        </view>
+
+        <!-- 指定角度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">指定角度°</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.specifiedAngle"
+                placeholder="请输入角度"
+                type="text"
+                class="param-input"
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group
+
+                placement="column">
+              <u-checkbox v-model="params.wSetA" class="checkbox" label="W_SetA" @change="handleSpecifiedAngleChange">W_SetA</u-checkbox>
+            </u-checkbox-group>
+          </view>
+          <u-button :plain="true" :hairline="true"  size="mini" class="action-btn" :disabled="params.wSetA" @click="writeSpecifiedAngle">
+            指定角度写入
+          </u-button>
+        </view>
+
+        <!-- 雪天角度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">雪天角度°</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.snowAngle"
+                placeholder="请输入角度"
+                type="text"
+                class="param-input"
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group
+
+                placement="column">
+              <u-checkbox v-model="params.wSno" class="checkbox" label="W_Sno" @change="handleSnowAngleChange">W_Sno</u-checkbox>
+            </u-checkbox-group>
+          </view>
+          <u-button :plain="true" :hairline="true"  size="mini" class="action-btn" :disabled="params.wSno" @click="writeSnowAngle">
+            雪天角度写入
+          </u-button>
+        </view>
+
+        <!-- 大风角度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">大风角度°</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.windAngle"
+                placeholder="请输入角度"
+                type="text"
+                class="param-input"
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group
+                placement="column">
+              <u-checkbox v-model="params.wWin" class="checkbox" label="W_Win" @change="handleWindAngleChange">W_Win</u-checkbox>
+            </u-checkbox-group>
+          </view>
+          <u-button :plain="true" :hairline="true"  size="mini" class="action-btn" :disabled="params.wWin" @click="writeWindAngle">
+            大风角度写入
+          </u-button>
+        </view>
+
+        <!-- 过流值 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">过流值(A)</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.overcurrentValue"
+                placeholder="请输入过流值"
+                type="text"
+                class="param-input"
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group
+                placement="column">
+              <u-checkbox v-model="params.wCurr" class="checkbox" label="W_Curr" @change="handleOvercurrentValueChange">W_Curr</u-checkbox>
+            </u-checkbox-group>
+          </view>
+          <u-button :plain="true" :hairline="true"  size="mini" class="action-btn" :disabled="params.wCurr"
+                    @click="writeOvercurrentProtection">过流保护写入
+          </u-button>
+        </view>
+
+        <!-- 跟踪精度 -->
+        <view class="param-row">
+          <view class="btn-class">
+            <u-button type="primary" size="mini" class="param-label">跟踪精度</u-button>
+          </view>
+          <view class="input-class">
+            <u-input
+                @focus="editStatus = true"
+                @blur="handleBlurWithDelay"
+                v-model="params.trackingAccuracy"
+                placeholder="请输入精度"
+                type="text"
+                class="param-input"
+            ></u-input>
+          </view>
+          <view class="checkbox-class">
+            <u-checkbox-group
+
+                placement="column">
+              <u-checkbox v-model="params.wAccu" class="checkbox" label="W_Accu" @change="handleAccuracyChange">W_Accu</u-checkbox>
+            </u-checkbox-group>
+          </view>
+          <u-button :plain="true" :hairline="true"  size="mini" class="action-btn" :disabled="params.wAccu" @click="writeAccuracy">
+            精度写入
+          </u-button>
+        </view>
+      </view>
+    </scroll-view>
+    <u-action-sheet
+        :show="showMotorDirection"
+        :actions="directionOptions"
+        title="请选择电机方向"
+        @close="showMotorDirection = false"
+        @select="motorDirectionSelect"
+    >
+    </u-action-sheet>
+
+  </view>
+</template>
+
+<script>
+import {writeRegister,setTime,stopHeartbeat,setTimeStatus} from '@/utils/modbus.js';
+export default {
+  data() {
+    return {
+      checkboxValue_timedSend: ['timedSend'],
+      editStatus: false,
+      showMotorDirection: false,
+      checkboxValue1: [],
+      activeTab: 'settings2',
+      params: {
+        motorDirection: '正转',
+        communicationTime: '0.5',
+        nightReturnAngle: '-30',
+        flatAngle: '-1',
+        specifiedAngle: '20',
+        snowAngle: '30',
+        windAngle: '10',
+        overcurrentValue: '5',
+        trackingAccuracy: '1',
+        wDir:true,
+        timedSend: true,
+        wBackA: true,
+        wFlat: true,
+        wSetA: true,
+        wSno: true,
+        wWin: true,
+        wCurr: true,
+        wAccu: true
+      },
+      directionOptions: [
+        {name: '正转', value: '正转'},
+        {name: '反转', value: '反转'}
+      ],
+      communicationLink_2: false,
+      communicationTimer_2: null, // 添加这一行用于保存定时器 ID
+    }
+  },
+  computed: {
+    bleConnected() {
+      return this.$store.getters['ble/connected']
+    },
+    bleData() {
+      return this.$store.getters['ble/data']
+    }
+  },
+  watch: {
+    bleData: {
+      handler(newData, oldData) {
+        if (newData) {
+          console.log('接收到蓝牙数据:', newData)
+          // 清除之前的定时器
+          if (this.communicationTimer_2) {
+            clearTimeout(this.communicationTimer_2)
+            this.communicationTimer_2 = null
+          }
+          // 设置通信链接状态为 true
+          this.communicationLink_2 = true
+          if (!this.editStatus){
+             this.updateSensorData(newData)
+          }
+
+        }
+      },
+      deep: true, // 深度监听,确保嵌套对象变化时也能触发
+      immediate: true // 立即执行一次
+    }
+  },
+  methods: {
+    handleBlurWithDelay() {
+      setTimeout(() => {
+        this.editStatus = false;
+      }, 1000);
+    },
+    handleNightReturnAngleChange(value){
+      this.params.wBackA = !value;
+    },
+    handleFlatAngleChange(value){
+      this.params.wFlat = !value;
+    },
+    handleSpecifiedAngleChange(value){
+      this.params.wSetA = !value;
+    },
+    handleSnowAngleChange(value){
+      this.params.wSno = !value;
+    },
+    handleWindAngleChange(value){
+      this.params.wWin = !value;
+    },
+    handleOvercurrentValueChange(value){
+      this.params.wCurr = !value;
+    },
+    handleAccuracyChange(value){
+      this.params.wAccu = !value;
+    },
+    handleCommunicationTimeChange(value){
+      this.params.wTim = !value;
+      if (value){
+        let number = parseFloat(this.params.communicationTime || "0");
+        const intValue1 = Math.round(number * 1000);
+        setTime(intValue1);
+      }else{
+        stopHeartbeat();
+      }
+      setTimeStatus(value);
+    },
+    handleDirectionChange(value){
+       this.params.wDir = !value;
+    },
+    motorDirectionSelect(e){
+      this.params.motorDirection = e.name
+      uni.showModal({
+        title: '确认写入',
+        content: `确定执行电机方向设置?`,
+        success: (res) => {
+          if (res.confirm) {
+            if (e.name === '正转'){
+              writeRegister('READ_DIRECTION', null)
+            }else {
+              writeRegister('READ_REVERSE', null)
+            }
+            uni.showToast({
+              title: '操作执行成功!',
+              icon: 'success',
+              duration: 2000
+            });
+          }
+        }
+      });
+
+
+    },
+    writeDirection() {
+      this.showMotorDirection = true;
+    },
+    writeNightReturnAngle() {
+      if (this.params.wBackA) return
+      uni.showModal({
+        title: '确认写入',
+        content: `是否确定执行此操作?`,
+        success: (res) => {
+           if (res.confirm) {
+             writeRegister('READ_RETURN', this.params.nightReturnAngle);
+             uni.showToast({
+               title: '操作执行成功!',
+               icon: 'success',
+               duration: 2000
+             });
+           }
+        }
+      });
+
+
+    },
+    writeFlatAngle() {
+      if (this.params.wFlat) return
+      uni.showModal({
+        title: '确认写入',
+        content: `是否确定执行此操作?`,
+        success: (res) => {
+           if (res.confirm) {
+             writeRegister('READ_FLAT', this.params.flatAngle);
+             uni.showToast({
+               title: '操作执行成功!',
+               icon: 'success',
+               duration: 2000
+             });
+           }
+        }
+      });
+
+
+    },
+    writeSpecifiedAngle() {
+      if (this.params.wSetA) return
+      uni.showModal({
+        title: '确认写入',
+        content: `是否确定执行此操作?`,
+        success: (res) => {
+           if (res.confirm) {
+             writeRegister('READ_SPECIFY', this.params.specifiedAngle);
+             uni.showToast({
+               title: '操作执行成功!',
+               icon: 'success',
+               duration: 2000
+             });
+           }
+        }
+      });
+
+
+    },
+    writeSnowAngle() {
+      if (this.params.wSno) return
+      uni.showModal({
+        title: '确认写入',
+        content: `是否确定执行此操作?`,
+        success: (res) => {
+           if (res.confirm) {
+             writeRegister('READ_SNOW', this.params.snowAngle);
+             uni.showToast({
+               title: '操作执行成功!',
+               icon: 'success',
+               duration: 2000
+             });
+           }
+        }
+      });
+
+
+    },
+    writeWindAngle() {
+      if (this.params.wWin) return
+      uni.showModal({
+        title: '确认写入',
+        content: `是否确定执行此操作?`,
+        success: (res) => {
+           if (res.confirm) {
+             writeRegister('READ_WIND', this.params.windAngle);
+             uni.showToast({
+               title: '操作执行成功!',
+               icon: 'success',
+               duration: 2000
+             });
+           }
+        }
+      });
+
+    },
+    writeOvercurrentProtection() {
+      if (this.params.wCurr) return
+      uni.showModal({
+        title: '确认写入',
+        content: `是否确定执行此操作?`,
+        success: (res) => {
+           if (res.confirm) {
+             writeRegister('READ_OVERCURRENT', this.params.overcurrentValue);
+             uni.showToast({
+               title: '操作执行成功!',
+               icon: 'success',
+               duration: 2000
+             });
+           }
+        }
+      });
+
+
+    },
+    writeAccuracy() {
+      if (this.params.wAccu) return
+      uni.showModal({
+        title: '确认写入',
+        content: `是否确定执行此操作?`,
+        success: (res) => {
+           if (res.confirm) {
+             writeRegister('READ_ACCURACY', this.params.trackingAccuracy);
+             uni.showToast({
+               title: '操作执行成功!',
+               icon: 'success',
+               duration: 2000
+             });
+           }
+        }
+      });
+
+    },
+    updateSensorData(data) {
+      // 根据蓝牙数据更新参数值
+      if (data.MotDrection_37 !== undefined && this.params.wDir) {
+        // 电机方向: 0表示正转,1表示反转
+        this.params.motorDirection = data.MotDrection_37 === '1' ? '反转' : '正转'
+      }
+
+      if (data.qNightAngle_66 !== undefined && this.params.wBackA) {
+        this.params.nightReturnAngle = data.qNightAngle_66
+      }
+
+      if (data.qFlatAngle_67 !== undefined && this.params.wFlat) {
+        this.params.flatAngle = data.qFlatAngle_67
+      }
+
+      if (data.qSpecifiedAngle_69 !== undefined && this.params.wSetA) {
+        this.params.specifiedAngle = data.qSpecifiedAngle_69
+      }
+
+      if (data.qSnowAngle_68 !== undefined && this.params.wSno) {
+        this.params.snowAngle = data.qSnowAngle_68
+      }
+
+      if (data.qWindAngle_70 !== undefined && this.params.wWin ) {
+        this.params.windAngle = data.qWindAngle_70
+      }
+
+      if (data.OverProtection_38 !== undefined && this.params.wCurr) {
+        this.params.overcurrentValue = data.OverProtection_38
+      }
+
+      if (data.TrackingAccuracy_39 !== undefined && this.params.wAccu) {
+        this.params.trackingAccuracy = data.TrackingAccuracy_39
+      }
+
+      // 设置6秒后将 communicationLink_2 设置为 false 的定时器
+      this.communicationTimer_2 = setTimeout(() => {
+        this.communicationLink_2 = false
+        this.communicationTimer_2 = null
+      }, 5000)
+
+    }
+  }
+}
+</script>
+
+<style lang="scss" >
+@import '@/static/scss/custom-theme.scss';
+
+.container {
+  height: calc(100% - 140px);
+  display: flex;
+  flex-direction: column;
+  background-color: $custom-bg-tertiary;
+}
+
+.status-bar {
+  height: $custom-status-height;
+  background-color: $custom-bg-primary;
+  @include flex-between;
+  padding: 0 $custom-spacing-md;
+  font-size: 14px;
+
+  .status-left {
+    color: $custom-text-primary;
+  }
+
+  .status-right {
+    @include flex-start;
+    gap: $custom-spacing-xs;
+
+    .data-rate {
+      color: $custom-text-secondary;
+      font-size: 12px;
+    }
+
+    .wifi-icon, .hd-text, .network, .battery {
+      font-size: 12px;
+    }
+  }
+}
+
+.header {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20rpx;
+
+  .link-btn {
+    background-color: #ff4757;
+    border: none;
+    width: 60px;
+    margin-left: 0px;
+  }
+
+  .title {
+    color: #fff;
+    font-size: 36rpx;
+    font-weight: bold;
+  }
+
+  .status-btn {
+    background-color: #ff4757;
+    border: none;
+    margin-left: 0px;
+    padding: 0px;
+    width: 40px;
+    text-align: center;
+    color: white;
+    border-radius: 4px;
+    height: 20px;
+    line-height: 20px;
+  }
+  .active_btn {
+    background-color: yellowgreen !important;
+  }
+
+}
+
+.content {
+  flex: 1;
+  padding: $custom-spacing-md;
+}
+
+.param-section {
+  @include card-style;
+}
+
+.param-row {
+  @include flex-start;
+  margin-bottom: $custom-spacing-md;
+  gap: 2px;
+
+  .param-label {
+    width: 70px;
+    @include button-style('primary');
+  }
+
+  .param-input {
+    flex: 1;
+    width: 60px;
+    @include input-style;
+  }
+
+  .radio-group {
+    @include flex-start;
+    gap: $custom-spacing-md;
+  }
+
+  .checkbox {
+    margin-left: $custom-spacing-sm;
+  }
+
+  .action-btn {
+    width: 90px;
+    background-color: $custom-bg-secondary;
+    color: $custom-text-secondary;
+    border: 1px solid $custom-border-secondary;
+    height: 30px;
+    line-height: 30px;
+    margin-right: 0px;
+    font-size: 12px;
+  }
+}
+
+.bottom-nav {
+  height: $custom-nav-height;
+  background-color: $custom-nav-bg;
+  display: flex;
+  border-top: 1px solid $custom-border-primary;
+
+  .nav-item {
+    flex: 1;
+    @include flex-center;
+    font-size: 14px;
+    color: $custom-text-secondary;
+    background-color: $custom-bg-secondary;
+    border-right: 1px solid $custom-border-primary;
+    transition: $custom-transition;
+
+    &:last-child {
+      border-right: none;
+    }
+
+    &.active {
+      background-color: $custom-nav-active-bg;
+      color: $custom-text-primary;
+      font-weight: bold;
+    }
+
+    &:hover {
+      background-color: darken($custom-bg-secondary, 5%);
+    }
+  }
+}
+
+.footer-status {
+  height: $custom-footer-height;
+  @include gradient-bg;
+  @include flex-between;
+  padding: 0 $custom-spacing-md;
+
+  .bt-status {
+    color: #007aff;
+    font-size: 14px;
+    font-weight: bold;
+  }
+
+  .rx-tx-status {
+    color: $custom-text-primary;
+    font-size: 12px;
+  }
+}
+
+
+// 响应式设计
+@include responsive(md) {
+  .param-row {
+    .param-input {
+      min-width: 150px;
+    }
+  }
+}
+
+@include responsive(lg) {
+  .param-row {
+    .param-input {
+      min-width: 200px;
+    }
+  }
+}
+
+::v-deep .param-section .u-button--mini{
+  width: 80px !important;
+  height: 36px !important;
+  font-size: 10px !important;
+}
+
+::v-deep .param-section .u-input{
+  width: 60px !important;
+}
+
+::v-deep .content text{
+    font-size: 12px !important;
+}
+
+</style>

+ 2 - 2
permission.js

@@ -1,11 +1,11 @@
 import { getToken } from '@/utils/auth'
 
 // 登录页面
-const loginPage = "/pages/login"
+const loginPage = "/pages/bluetooth/index/index"
   
 // 页面白名单
 const whiteList = [
-  '/pages/login', '/pages/common/webview/index','/pages/index'
+  '/pages/bluetooth/index/index', '/pages/bluetooth/set/set'
 ]
 
 // 检查地址白名单

+ 25 - 0
project.config.json

@@ -0,0 +1,25 @@
+{
+  "setting": {
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "uglifyFileName": false,
+    "enhance": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "useCompilerPlugins": false,
+    "minifyWXML": true
+  },
+  "compileType": "miniprogram",
+  "simulatorPluginLibVersion": {},
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "appid": "wx2f3d21be6a6b7857",
+  "editorSetting": {}
+}

+ 14 - 0
project.private.config.json

@@ -0,0 +1,14 @@
+{
+  "libVersion": "3.8.11",
+  "projectname": "tianmucloudmobile",
+  "setting": {
+    "urlCheck": true,
+    "coverView": true,
+    "lazyloadPlaceholderEnable": false,
+    "skylineRenderEnable": false,
+    "preloadBackgroundData": false,
+    "autoAudits": false,
+    "showShadowRootInWxmlPanel": true,
+    "compileHotReLoad": true
+  }
+}

+ 1 - 1
static/iconfont.css

@@ -1,6 +1,6 @@
 @font-face {
   font-family: "iconfont"; /* Project id 4213972 */
-  src: url('/static/iconfont.ttf') format('truetype');
+  src: url('@/static/iconfont.ttf') format('truetype');
 }
 
 .iconfont {

BIN
static/images/tabbar/cz.png


BIN
static/images/tabbar/cz_active.png


BIN
static/images/tabbar/ly.png


BIN
static/images/tabbar/ly_active.png


BIN
static/images/tabbar/record.png


BIN
static/images/tabbar/record_active.png


BIN
static/images/tabbar/set.png


BIN
static/images/tabbar/set_active.png


+ 160 - 0
static/scss/custom-theme.scss

@@ -0,0 +1,160 @@
+// 自定义主题变量
+$custom-primary: #667eea;
+$custom-secondary: #764ba2;
+$custom-success: #4cd964;
+$custom-warning: #f0ad4e;
+$custom-error: #dd524d;
+$custom-info: #5bc0de;
+
+// 背景色
+$custom-bg-primary: #ffffff;
+$custom-bg-secondary: #f8f8f8;
+$custom-bg-tertiary: #f5f5f5;
+$custom-bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+
+// 文字颜色
+$custom-text-primary: #333333;
+$custom-text-secondary: #666666;
+$custom-text-tertiary: #999999;
+$custom-text-white: #ffffff;
+
+// 边框颜色
+$custom-border-primary: #e5e5e5;
+$custom-border-secondary: #ddd;
+$custom-border-disabled: #ccc;
+
+// 禁用状态
+$custom-bg-disabled: #f5f5f5;
+$custom-text-disabled: #999999;
+
+// 按钮样式
+$custom-btn-height: 32px;
+$custom-btn-radius: 4px;
+$custom-btn-font-size: 12px;
+
+// 输入框样式
+$custom-input-height: 32px;
+$custom-input-radius: 4px;
+$custom-input-border: 1px solid #e5e5e5;
+
+// 导航栏样式
+$custom-nav-height: 50px;
+$custom-nav-bg: #ffffff;
+$custom-nav-active-bg: #e0e0e0;
+
+// 状态栏样式
+$custom-status-height: 44px;
+$custom-footer-height: 40px;
+
+// 间距
+$custom-spacing-xs: 5px;
+$custom-spacing-sm: 10px;
+$custom-spacing-md: 15px;
+$custom-spacing-lg: 20px;
+
+// 圆角
+$custom-radius-sm: 4px;
+$custom-radius-md: 8px;
+$custom-radius-lg: 12px;
+
+// 阴影
+$custom-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1);
+$custom-shadow-medium: 0 4px 8px rgba(0, 0, 0, 0.15);
+$custom-shadow-heavy: 0 8px 16px rgba(0, 0, 0, 0.2);
+
+// 动画
+$custom-transition: all 0.3s ease;
+
+// 响应式断点
+$custom-breakpoint-sm: 576px;
+$custom-breakpoint-md: 768px;
+$custom-breakpoint-lg: 992px;
+$custom-breakpoint-xl: 1200px;
+
+// 混合器
+@mixin flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+@mixin flex-between {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+@mixin flex-start {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+}
+
+@mixin gradient-bg {
+  background: $custom-bg-gradient;
+}
+
+@mixin card-style {
+  background-color: $custom-bg-primary;
+  border-radius: $custom-radius-md;
+  box-shadow: $custom-shadow-light;
+  padding: $custom-spacing-md;
+}
+
+@mixin button-style($type: 'default') {
+  height: $custom-btn-height;
+  border-radius: $custom-btn-radius;
+  font-size: $custom-btn-font-size;
+  transition: $custom-transition;
+  
+  @if $type == 'primary' {
+    background-color: $custom-primary;
+    color: $custom-text-white;
+  } @else if $type == 'secondary' {
+    background-color: $custom-secondary;
+    color: $custom-text-white;
+  } @else if $type == 'success' {
+    background-color: $custom-success;
+    color: $custom-text-white;
+  } @else if $type == 'warning' {
+    background-color: $custom-warning;
+    color: $custom-text-white;
+  } @else if $type == 'error' {
+    background-color: $custom-error;
+    color: $custom-text-white;
+  } @else if $type == 'info' {
+    background-color: $custom-info;
+    color: $custom-text-white;
+  } @else {
+    background-color: $custom-bg-secondary;
+    color: $custom-text-secondary;
+    border: 1px solid $custom-border-secondary;
+  }
+}
+
+@mixin input-style {
+  height: $custom-input-height;
+  border-radius: $custom-input-radius;
+  border: $custom-input-border;
+  padding: 0 $custom-spacing-sm;
+  font-size: 14px;
+  transition: $custom-transition;
+  
+  &:focus {
+    border-color: $custom-primary;
+    box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
+  }
+}
+
+// 响应式工具类
+@mixin responsive($breakpoint) {
+  @if $breakpoint == sm {
+    @media (min-width: $custom-breakpoint-sm) { @content; }
+  } @else if $breakpoint == md {
+    @media (min-width: $custom-breakpoint-md) { @content; }
+  } @else if $breakpoint == lg {
+    @media (min-width: $custom-breakpoint-lg) { @content; }
+  } @else if $breakpoint == xl {
+    @media (min-width: $custom-breakpoint-xl) { @content; }
+  }
+} 

+ 5 - 3
store/index.js

@@ -1,15 +1,17 @@
 import Vue from 'vue'
 import Vuex from 'vuex'
 import user from '@/store/modules/user'
+import ble from '@/store/modules/ble' // 引入蓝牙模块
 import getters from './getters'
 
 Vue.use(Vuex)
 
 const store = new Vuex.Store({
   modules: {
-    user
+    user,
+    ble // 注册蓝牙模块
   },
-  getters
+  getters
 })
 
-export default store
+export default store

+ 48 - 0
store/modules/ble.js

@@ -0,0 +1,48 @@
+// store/modules/ble.js
+export default {
+  namespaced: true,
+
+  state: {
+    connected: false,
+    loading: false,
+    error: null,
+    data: null
+  },
+
+  mutations: {
+    SET_CONNECTED(state, status) {
+      state.connected = status
+    },
+    SET_LOADING(state, status) {
+      state.loading = status
+    },
+    SET_ERROR(state, error) {
+      state.error = error
+    },
+    SET_DATA(state, data) {
+      state.data = data
+    }
+  },
+
+  actions: {
+    updateConnected({ commit }, status) {
+      commit('SET_CONNECTED', status)
+    },
+    updateLoading({ commit }, status) {
+      commit('SET_LOADING', status)
+    },
+    updateError({ commit }, error) {
+      commit('SET_ERROR', error)
+    },
+    updateData({ commit }, data) {
+      commit('SET_DATA', data)
+    }
+  },
+
+  getters: {
+    connected: state => state.connected,
+    loading: state => state.loading,
+    error: state => state.error,
+    data: state => state.data
+  }
+}

+ 466 - 65
utils/ecBLE/ecBLEApp.js

@@ -279,93 +279,215 @@ const setBLEMTU = mtu => {
 
 uni.onBLEConnectionStateChange(async res => {
 	log(res)
-	// {"deviceId":"EC:22:05:13:78:49","connected":true}
 	if (res.connected) {
-		console.log("connected successfully")
+		console.log("=== 设备连接成功 ===")
 		const servicesResult = await getBLEDeviceServices()
-		console.log(servicesResult)
+		console.log("服务发现结果:", servicesResult)
 		if (!servicesResult.ok) {
 			ecBLEConnectionStateChangeCallback(servicesResult)
 			closeBLEConnection()
 			return
 		}
-		console.log(servicesResult)
-		for (const service of servicesResult.services) {
-			var isRightService = false;
-			if ((service.uuid.toUpperCase() === ecGattServerUUIDOption1) ||
-				(service.uuid.toUpperCase() === ecGattServerUUIDOption2)) {
-				ecGattServerUUID = service.uuid
-				isRightService = true;
-			}
-			if ((service.uuid.toUpperCase() === yiyuanUUIDOption1) ||
-				(service.uuid.toUpperCase() === yiyuanUUIDOption2)) {
-				ecGattServerUUID = service.uuid
-				isRightService = true;
-			}
-			if(!isRightService){
-				continue;
-			}
-			console.log("uuid:"+ecGattServerUUID)
-			const characteristicsResult = await getBLEDeviceCharacteristics(
-				service.uuid
-			)
-			if (!characteristicsResult.ok) {
-				ecBLEConnectionStateChangeCallback(characteristicsResult)
-				closeBLEConnection()
-				return
-			}
-			console.log(characteristicsResult)
-			for (const characteristic of characteristicsResult.characteristics) {
-				if (
-					characteristic.properties &&
-					characteristic.properties.notify
-				) {
-					const notifyResult =
-						await notifyBLECharacteristicValueChange(
+		console.log("设备提供的所有服务:", servicesResult.services)
+
+		// 优先查找的服务和特征值UUID(与微信端保持一致)
+		const targetServiceUUIDs = [
+			'0000FFE0-0000-1000-8000-00805F9B34FB'.toUpperCase(),
+			'FFE0'.toUpperCase(),
+			// 保留原来的选项
+			'0000FFF0-0000-1000-8000-00805F9B34FB'.toUpperCase(),
+			'FFF0'.toUpperCase(),
+			'0000F536-0000-1000-8000-00805F9B34FB'.toUpperCase(),
+			'F536'.toUpperCase()
+		];
+
+		const targetCharacteristicUUIDs = [
+			'0000FFE1-0000-1000-8000-00805F9B34FB'.toUpperCase(),
+			'FFE1'.toUpperCase(),
+			// 保留原来的选项
+			'0000FFF2-0000-1000-8000-00805F9B34FB'.toUpperCase(),
+			'FFF2'.toUpperCase(),
+			'0000FF15-0000-1000-8000-00805F9B34FB'.toUpperCase(),
+			'FF15'.toUpperCase()
+		];
+
+		let serviceFound = false;
+		let notifyEnabled = false; // 新增:跟踪是否成功启用通知
+		let writeCharacteristicFound = false; // 跟踪是否找到写入特征值
+
+		// 首先按优先级查找
+		serviceLoop: for (const service of servicesResult.services) {
+			const serviceUUID = service.uuid.toUpperCase();
+			console.log("正在检查服务:", serviceUUID);
+
+			// 检查是否是目标服务
+			if (targetServiceUUIDs.includes(serviceUUID)) {
+				console.log("✅ 找到目标服务:", serviceUUID);
+
+				// 获取该服务下的所有特征值
+				const characteristicsResult = await getBLEDeviceCharacteristics(service.uuid)
+				console.log(`服务 ${service.uuid} 的特征值获取结果:`, characteristicsResult);
+				if (!characteristicsResult.ok) {
+					console.log(`获取服务 ${service.uuid} 的特征值失败`)
+					continue
+				}
+
+				console.log(`服务 ${service.uuid} 下的特征值:`, characteristicsResult.characteristics)
+
+				// 分别查找通知特征值和写入特征值
+				let notifyCharacteristic = null;
+				let writeCharacteristic = null;
+
+				// 遍历所有特征值
+				for (const characteristic of characteristicsResult.characteristics) {
+					const charUUID = characteristic.uuid.toUpperCase();
+					console.log("检查特征值:", charUUID, "属性:", characteristic.properties);
+
+					// 查找通知特征值
+					if (characteristic.properties && (characteristic.properties.notify || characteristic.properties.indicate)) {
+						notifyCharacteristic = characteristic;
+						console.log("✅ 找到通知特征值:", charUUID);
+
+						// 尝试启用通知
+						const notifyResult = await notifyBLECharacteristicValueChange(
 							service.uuid,
 							characteristic.uuid
 						)
-					if (!notifyResult.ok) {
-						ecBLEConnectionStateChangeCallback({
-							ok: false,
-							errCode: 30000,
-							errMsg: 'notify error',
-						})
-						closeBLEConnection()
-						return
+						if (notifyResult.ok) {
+							console.log("✅ 通知已启用:", characteristic.uuid);
+							notifyEnabled = true;
+						} else {
+							console.log("⚠️ 启用通知失败:", characteristic.uuid, notifyResult);
+						}
+					}
+
+					// 查找写入特征值
+					if (targetCharacteristicUUIDs.includes(charUUID) &&
+						characteristic.properties &&
+						(characteristic.properties.write || characteristic.properties.writeNoResponse)) {
+						writeCharacteristic = characteristic;
+						console.log("✅ 找到写入特征值:", charUUID);
 					}
 				}
-				/**
-				 * 14:49:25.419 uuid:0000FFF0-0000-1000-8000-00805F9B34FB at utils/ecBLE/ecBLEApp.js:298
-				 * 14:49:25.427 characteristic.uuid:0000FFF2-0000-1000-8000-00805F9B34FB at utils/ecBLE/ecBLEApp.js:327
-				 * 14:49:25.443 characteristic.uuid:0000FFF1-0000-1000-8000-00805F9B34FB at utils/ecBLE/ecBLEApp.js:327
-				 */
-				console.log("characteristic.uuid:"+characteristic.uuid)
-				if ((characteristic.uuid.toUpperCase() ===
-						ecGattCharacteristicWriteUUIDOption1) ||
-					(characteristic.uuid.toUpperCase() ===
-						ecGattCharacteristicWriteUUIDOption2)) {
-					ecGattCharacteristicWriteUUID = characteristic.uuid
+
+				// 如果找到了写入特征值,设置全局变量
+				if (writeCharacteristic) {
+					ecGattServerUUID = service.uuid;
+					ecGattCharacteristicWriteUUID = writeCharacteristic.uuid;
+					console.log("服务 UUID:", ecGattServerUUID);
+					console.log("写入特征值 UUID:", ecGattCharacteristicWriteUUID);
+					serviceFound = true;
+					writeCharacteristicFound = true;
+					break serviceLoop;
 				}
+			}
+		}
+
+		// 如果没找到目标服务/特征值,则使用动态查找
+		if (!serviceFound) {
+			console.log("未找到预定义的服务/特征值,使用动态查找...")
+
+			serviceLoop2: for (const service of servicesResult.services) {
+				console.log("正在检查服务:", service.uuid)
+
+				// 获取该服务下的所有特征值
+				const characteristicsResult = await getBLEDeviceCharacteristics(service.uuid)
+				console.log(`服务 ${service.uuid} 的特征值获取结果:`, characteristicsResult);
+				if (!characteristicsResult.ok) {
+					console.log(`获取服务 ${service.uuid} 的特征值失败`)
+					continue
+				}
+
+				console.log(`服务 ${service.uuid} 下的特征值:`, characteristicsResult.characteristics)
+
+				// 分别查找通知特征值和写入特征值
+				let notifyCharacteristic = null;
+
+				// 遍历所有特征值
+				for (const characteristic of characteristicsResult.characteristics) {
+					console.log("动态查找 - 检查特征值:", characteristic.uuid, "属性:", characteristic.properties);
+
+					// 查找通知特征值
+					if (characteristic.properties && (characteristic.properties.notify || characteristic.properties.indicate)) {
+						notifyCharacteristic = characteristic;
+						console.log("✅ 找到通知特征值:", characteristic.uuid);
+
+						// 尝试启用通知
+						const notifyResult = await notifyBLECharacteristicValueChange(
+							service.uuid,
+							characteristic.uuid
+						)
+						if (notifyResult.ok) {
+							console.log("✅ 通知已启用:", characteristic.uuid);
+							notifyEnabled = true;
+						} else {
+							console.log("⚠️ 启用通知失败:", characteristic.uuid, notifyResult);
+						}
+					}
 
-				if ((characteristic.uuid.toUpperCase() ===
-						yiyuancharacterID1) ||
-					(characteristic.uuid.toUpperCase() ===
-						yiyuancharacterID2)) {
-					ecGattCharacteristicWriteUUID = characteristic.uuid
+					// 查找写入特征值
+					if (characteristic.properties &&
+						(characteristic.properties.write || characteristic.properties.writeNoResponse)) {
+
+						// 找到支持写入的特征值,设置全局变量
+						ecGattServerUUID = service.uuid
+						ecGattCharacteristicWriteUUID = characteristic.uuid
+
+						console.log("✅ 找到可写特征值")
+						console.log("服务 UUID:", ecGattServerUUID)
+						console.log("特征值 UUID:", ecGattCharacteristicWriteUUID)
+
+						serviceFound = true
+						writeCharacteristicFound = true;
+						break serviceLoop2; // 使用标签跳出外层循环
+					}
 				}
-				console.log("rghtCID:"+ecGattCharacteristicWriteUUID);
 			}
 		}
+
+		// 如果没有找到可写特征值
+		if (!writeCharacteristicFound) {
+			console.error("❌ 未找到支持写入的特征值")
+			console.log("所有服务和特征值信息:", servicesResult.services);
+			ecBLEConnectionStateChangeCallback({
+				ok: false,
+				errCode: 30000,
+				errMsg: '未找到支持写入的特征值',
+			})
+			closeBLEConnection()
+			return
+		}
+
+		// 检查是否成功启用了通知
+		if (!notifyEnabled) {
+			console.warn("⚠️ 警告:未成功启用任何特征值的通知功能");
+			console.log("⚠️ 设备可能使用轮询方式而非通知方式通信");
+			// 继续连接,因为有些设备可能不使用通知机制
+		} else {
+			console.log("✅ 通知功能已成功启用");
+		}
+
+		// 设置MTU(仅安卓)
 		if (isAndroid) {
-			await setBLEMTU(247)
+			try {
+				await setBLEMTU(247)
+				console.log("MTU 设置成功")
+			} catch (error) {
+				console.log("MTU 设置失败:", error)
+			}
 		}
+
+		// 连接成功回调
+		console.log("=== 准备发送连接成功回调 ===");
+		console.log("服务UUID:", ecGattServerUUID);
+		console.log("写入特征值UUID:", ecGattCharacteristicWriteUUID);
 		ecBLEConnectionStateChangeCallback({
 			ok: true,
 			errCode: 0,
 			errMsg: '',
 		})
 	} else {
+		console.log("=== 设备断开连接 ===");
 		ecBLEConnectionStateChangeCallback({
 			ok: false,
 			errCode: 0,
@@ -373,7 +495,7 @@ uni.onBLEConnectionStateChange(async res => {
 		})
 	}
 })
-	
+
 const createBLEConnection = async id => {
 	ecDeviceId = id
 	const res = await _createBLEConnection()
@@ -386,12 +508,18 @@ const closeBLEConnection = () => {
 	uni.closeBLEConnection({
 		deviceId: ecDeviceId,
 		complete(res) {
-			log(res)
+			log(res);
+			uni.showToast({
+				title: '操作执行成功!',
+				icon: 'success',
+				duration: 2000
+			});
 		},
 	})
 }
 
 uni.onBLECharacteristicValueChange(res => {
+
 	log(res)
 	let x = new Uint8Array(res.value)
 	log(x)
@@ -418,6 +546,7 @@ const _writeBLECharacteristicValue = buffer => {
 			value: buffer,
 			writeType: 'writeNoResponse',
 			success(res) {
+
 				log(res)
 				resolve({
 					ok: true,
@@ -426,6 +555,8 @@ const _writeBLECharacteristicValue = buffer => {
 				})
 			},
 			fail(res) {
+				console.log("写入失败");
+				console.log(res);
 				log(res)
 				resolve({
 					ok: false,
@@ -437,6 +568,7 @@ const _writeBLECharacteristicValue = buffer => {
 	})
 }
 const writeBLECharacteristicValue = async (str, isHex) => {
+	
 	if (str.length === 0)
 		return {
 			ok: false,
@@ -451,12 +583,130 @@ const writeBLECharacteristicValue = async (str, isHex) => {
 			x[i] = parseInt(str.substr(2 * i, 2), 16)
 		}
 	} else {
-		buffer = new Uint8Array(strToUtf8Bytes(str)).buffer
+		// AT指令模式 - 处理ASCII字符串
+		// 转换为UTF-8字节数组
+		const encoder = new TextEncoder();
+		const utf8Bytes = encoder.encode(str);
+		buffer = utf8Bytes.buffer;
+
+		// 显示ASCII格式的日志
+		console.log("TX: " + str); // 发送数据日志
+		const writeData = "RX: " + str;
+		saveWriteDataToLocal(writeData, 'rx');
+	}
+	if (isHex){
+		// 显示十六进制格式的日志(类似 RX: + 的格式)
+		const hexArray = [...new Uint8Array(buffer)]
+			.map(b => b.toString(16).padStart(2, '0').toUpperCase());
+		const hexString = hexArray.join(' ');
+		console.log("RX: " + hexString); // 发送数据日志,格式如 "RX: 01 03 00 01 00 01"
+		// 保存写入数据到本地存储
+		const writeData = "RX: " + hexString;
+		saveWriteDataToLocal(writeData,'rx');
+	}else {
+		const writeData = "RX: " + str;
+		saveWriteDataToLocal(writeData,'rx');
 	}
 
 	return await _writeBLECharacteristicValue(buffer)
 }
 
+
+/**
+ * 向蓝牙特征值写入数据
+ * @param {string} str - 要发送的字符串数据
+ * @param {boolean} isHex - 数据格式标志。
+ *                        false: str 包含十六进制字符串 (e.g., "01030001004695F8")
+ *                        true:  str 包含 ASCII 字符串 (e.g., "Hello")
+ * @returns {Promise<Object>} 写入操作的结果
+ */
+const writeBLECharacteristicValueTwo = async (str, isHex) => {
+
+	if (str.length === 0)
+		return {
+			ok: false,
+			errCode: 30000,
+			errMsg: 'data is null'
+		}
+
+	let buffer;
+
+	// isHex === false 时,发送 ASCII 字符串
+	if (!isHex) {
+		// AT指令模式 - 处理ASCII字符串
+		// 转换为UTF-8字节数组
+		const encoder = new TextEncoder();
+		const utf8Bytes = encoder.encode(str);
+		buffer = utf8Bytes.buffer;
+
+		// 显示ASCII格式的日志
+		console.log("TX (ASCII): " + str); // 发送数据日志
+		const writeData = "TX (ASCII): " + str;
+		saveWriteDataToLocal(writeData, 'tx');
+	}
+	// isHex === true 时,发送十六进制字符串
+	else {
+		// 移除所有空格,确保是连续的十六进制字符串
+		const hexString = str.replace(/\s/g, '');
+
+		// 检查长度是否为偶数
+		if (hexString.length % 2 !== 0) {
+			console.error("Invalid hex string length");
+			return {
+				ok: false,
+				errCode: 30001,
+				errMsg: 'Invalid hex string length'
+			};
+		}
+
+		// 检查是否包含非十六进制字符
+		if (!/^[0-9A-Fa-f]*$/.test(hexString)) {
+			console.error("Invalid hex characters");
+			return {
+				ok: false,
+				errCode: 30002,
+				errMsg: 'Invalid hex characters'
+			};
+		}
+
+		// 创建 ArrayBuffer 并填充数据
+		buffer = new ArrayBuffer(hexString.length / 2);
+		let x = new Uint8Array(buffer);
+		for (let i = 0; i < x.length; i++) {
+			x[i] = parseInt(hexString.substr(2 * i, 2), 16);
+		}
+
+		// 显示十六进制格式的日志
+		const formattedHex = hexString.match(/.{1,2}/g)?.join(' ') || '';
+		console.log("TX (HEX): " + formattedHex); // 发送数据日志
+		const writeData = "TX (HEX): " + formattedHex;
+		saveWriteDataToLocal(writeData, 'tx');
+	}
+
+	console.log("Buffer to send:", new Uint8Array(buffer));
+	return await _writeBLECharacteristicValue(buffer);
+}
+
+
+
+// 新增:模拟 QString::toLatin1() 的行为
+function stringToLatin1Bytes(str) {
+	const bytes = [];
+	for (let i = 0; i < str.length; i++) {
+		const code = str.charCodeAt(i); // 获取 UTF-16 code unit
+		// 检查是否在 Latin-1 范围内 (0-255)
+		if (code >= 0 && code <= 255) {
+			bytes.push(code); // 直接作为字节值
+		} else {
+			// C++ QString::toLatin1() 对于超出范围的字符通常会变成 0 或 '?' (0x3F)
+			// 这里我们选择 0x3F ('?')
+			// 你需要根据 C++ 实际行为调整,可能是 0x00
+			bytes.push(0x3F); // 或者 bytes.push(0x00);
+		}
+	}
+	return new Uint8Array(bytes);
+}
+
 const utf8BytesToStr = utf8Bytes => {
 	let unicodeStr = ''
 	for (let pos = 0; pos < utf8Bytes.length;) {
@@ -534,6 +784,147 @@ const strToUtf8Bytes = str => {
 	return bytes
 }
 
+// 在文件顶部添加全局变量
+let globalWriteLogs = []
+let logListeners = []
+
+// 保存数据到全局变量的函数(移除本地存储)
+const saveWriteDataToLocal = (data, type) => {
+	try {
+		const now = new Date();
+		const year = now.getFullYear();
+		const month = String(now.getMonth() + 1).padStart(2, '0');
+		const day = String(now.getDate()).padStart(2, '0');
+		const hours = String(now.getHours()).padStart(2, '0');
+		const minutes = String(now.getMinutes()).padStart(2, '0');
+		const seconds = String(now.getSeconds()).padStart(2, '0');
+
+		// 创建日志条目
+		const logEntry = {
+			time: `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`,
+			data: data,
+			type: type
+		}
+
+		// 添加到全局变量
+		globalWriteLogs.push(logEntry)
+
+		// 限制全局日志数量为最近100条
+		if (globalWriteLogs.length > 100) {
+			globalWriteLogs = globalWriteLogs.slice(-100)
+		}
+
+		// 通知所有监听器
+		notifyLogListeners()
+	} catch (e) {
+		
+	}
+}
+
+// 添加获取全局日志的方法
+const getGlobalWriteLogs = () => {
+	return globalWriteLogs
+}
+
+// 添加清空全局日志的方法
+const clearGlobalWriteLogs = () => {
+	globalWriteLogs = []
+	notifyLogListeners()
+}
+
+// 添加监听器管理方法
+const addLogListener = (callback) => {
+	logListeners.push(callback)
+}
+
+const removeLogListener = (callback) => {
+	const index = logListeners.indexOf(callback)
+	if (index > -1) {
+		logListeners.splice(index, 1)
+	}
+}
+
+// 通知监听器
+const notifyLogListeners = () => {
+	logListeners.forEach(callback => {
+		callback(globalWriteLogs)
+	})
+}
+
+// 根据类型删除日志的函数(仅操作全局变量)
+const removeWriteDataByType = (type) => {
+	try {
+		const originalLength = globalWriteLogs.length;
+
+		// 过滤掉指定类型的日志
+		globalWriteLogs = globalWriteLogs.filter(log => log.type !== type)
+
+		const deletedCount = originalLength - globalWriteLogs.length;
+
+		// 通知所有监听器更新
+		notifyLogListeners()
+
+		console.log(`已删除类型为 '${type}' 的日志,共删除 ${deletedCount} 条`);
+		return {
+			success: true,
+			deletedCount: deletedCount
+		}
+	} catch (e) {
+		console.error('删除写入数据失败:', e)
+		return {
+			success: false,
+			error: e
+		}
+	}
+}
+
+// 清空所有日志的函数(仅操作全局变量)
+const clearAllWriteData = () => {
+	try {
+		globalWriteLogs = []
+		notifyLogListeners()
+		console.log("已清空所有日志");
+		return {
+			success: true
+		}
+	} catch (e) {
+		console.error('清空所有日志失败:', e)
+		return {
+			success: false,
+			error: e
+		}
+	}
+}
+
+// 获取特定类型的日志
+const getWriteDataByType = (type) => {
+	try {
+		const filteredLogs = globalWriteLogs.filter(log => log.type === type)
+		return filteredLogs
+	} catch (e) {
+		console.error('获取特定类型日志失败:', e)
+		return []
+	}
+}
+
+// 获取所有日志(按时间倒序排列)
+const getAllWriteDataSorted = () => {
+	try {
+		// 创建副本避免修改原数组
+		let writeLogs = [...globalWriteLogs]
+		// 按时间倒序排列(最新的在前面)
+		writeLogs.sort((a, b) => {
+			return new Date(b.time) - new Date(a.time);
+		});
+		return writeLogs
+	} catch (e) {
+		console.error('获取排序日志失败:', e)
+		return []
+	}
+}
+
+
+// 导出新增的方法
 export default {
 	onBluetoothAdapterStateChange,
 	openBluetoothAdapter,
@@ -548,4 +939,14 @@ export default {
 
 	onBLECharacteristicValueChange,
 	writeBLECharacteristicValue,
+	saveWriteDataToLocal,
+	getGlobalWriteLogs,
+	clearGlobalWriteLogs,
+	addLogListener,
+	removeLogListener,
+	removeWriteDataByType,  // 新增
+	clearAllWriteData,       // 新增
+	getWriteDataByType,      // 新增
+	getAllWriteDataSorted,    // 新增
+	writeBLECharacteristicValueTwo
 }

+ 73 - 36
utils/ecBLE/ecBLEWX.js

@@ -297,61 +297,87 @@ const createBLEConnection = async id => {
                 closeBLEConnection()
                 return
             }
-            console.log(servicesResult.services)
+
+            console.log("设备提供的所有服务:", servicesResult.services)
+
+            // 动态查找服务和可写特征值
+            let serviceFound = false;
+
             for (const service of servicesResult.services) {
-                var isRightService = false;
-                if ((service.uuid.toUpperCase() === ecGattServerUUIDOption1) ||
-                    (service.uuid.toUpperCase() === ecGattServerUUIDOption2)) {
-                    ecGattServerUUID = service.uuid
-                    isRightService = true;
-                }
-                if(!isRightService){
-                    continue;
-                }
-                const characteristicsResult = await getBLEDeviceCharacteristics(
-                    service.uuid
-                )
+                // 获取该服务下的所有特征值
+                const characteristicsResult = await getBLEDeviceCharacteristics(service.uuid)
                 if (!characteristicsResult.ok) {
-                    ecBLEConnectionStateChangeCallback(characteristicsResult)
-                    closeBLEConnection()
-                    return
+                    console.log(`获取服务 ${service.uuid} 的特征值失败`)
+                    continue
                 }
-                console.log(characteristicsResult.characteristics);
+
+                console.log(`服务 ${service.uuid} 下的特征值:`, characteristicsResult.characteristics)
+
+                // 查找支持写入的特征值
                 for (const characteristic of characteristicsResult.characteristics) {
-                    if (
-                        characteristic.properties &&
-                        characteristic.properties.notify
-                    ) {
-                        const notifyResult =
-                            await notifyBLECharacteristicValueChange(
+                    // 检查是否支持写入操作
+                    if (characteristic.properties &&
+                        (characteristic.properties.write || characteristic.properties.writeNoResponse)) {
+
+                        // 找到支持写入的特征值,设置全局变量
+                        ecGattServerUUID = service.uuid
+                        ecGattCharacteristicWriteUUID = characteristic.uuid
+
+                        console.log("✅ 找到可写特征值")
+                        console.log("服务 UUID:", ecGattServerUUID)
+                        console.log("特征值 UUID:", ecGattCharacteristicWriteUUID)
+
+                        // 如果该特征值还支持通知,也启用通知
+                        if (characteristic.properties.notify) {
+                            const notifyResult = await notifyBLECharacteristicValueChange(
                                 service.uuid,
                                 characteristic.uuid
                             )
-                        if (!notifyResult.ok) {
-                            ecBLEConnectionStateChangeCallback({
-                                ok: false,
-                                errCode: 30000,
-                                errMsg: 'notify error',
-                            })
-                            closeBLEConnection()
-                            return
+                            if (!notifyResult.ok) {
+                                console.log("启用通知失败:", notifyResult)
+                            } else {
+                                console.log("✅ 通知已启用")
+                            }
                         }
-                    }
 
-                    if ((characteristic.uuid.toUpperCase() === ecGattCharacteristicWriteUUIDOption1 || characteristic.uuid.toUpperCase() === ecGattCharacteristicWriteUUIDOption2)){
-                        ecGattCharacteristicWriteUUID = characteristic.uuid
+                        serviceFound = true
+                        break
                     }
                 }
+
+                if (serviceFound) break
+            }
+
+            // 如果没有找到可写特征值
+            if (!serviceFound) {
+                console.error("❌ 未找到支持写入的特征值")
+                ecBLEConnectionStateChangeCallback({
+                    ok: false,
+                    errCode: 30000,
+                    errMsg: '未找到支持写入的特征值',
+                })
+                closeBLEConnection()
+                return
             }
+
+            // 设置MTU(仅安卓)
             if (isAndroid) {
-                await setBLEMTU(247)
+                try {
+                    await setBLEMTU(247)
+                    console.log("MTU 设置成功")
+                } catch (error) {
+                    console.log("MTU 设置失败:", error)
+                }
             }
+
+            // 连接成功回调
             ecBLEConnectionStateChangeCallback({
                 ok: true,
                 errCode: 0,
                 errMsg: '',
             })
         } else {
+            // 连接断开
             ecBLEConnectionStateChangeCallback({
                 ok: false,
                 errCode: 0,
@@ -359,11 +385,14 @@ const createBLEConnection = async id => {
             })
         }
     })
+
+    // 开始连接
     const res = await _createBLEConnection()
     if (!res.ok) {
         ecBLEConnectionStateChangeCallback(res)
     }
 }
+
 //关闭当前连接
 const closeBLEConnection = () => {
     wx.closeBLEConnection({
@@ -394,6 +423,9 @@ const onBLECharacteristicValueChange = cb => {
 
 const _writeBLECharacteristicValue = buffer => {
     return new Promise(function (resolve, reject) {
+        console.log("================开始写入=================")
+        console.log("准备写入的数据 - 长度:", buffer.byteLength);
+        console.log("准备写入的数据 - 内容:", Array.from(new Uint8Array(buffer)));
         wx.writeBLECharacteristicValue({
             deviceId: ecDeviceId,
             serviceId: ecGattServerUUID,
@@ -401,11 +433,15 @@ const _writeBLECharacteristicValue = buffer => {
             value: buffer,
             writeType: 'write',
             success(res) {
+                console.log("================成功=================")
+                console.log(res)
                 log(res)
                 // {"errno":0,"errCode":0,"errMsg":"writeBLECharacteristicValue:ok"}
                 resolve({ ok: true, errCode: 0, errMsg: '' })
             },
             fail(res) {
+                console.log("================失败=================")
+                console.log(res)
                 log(res)
                 resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg })
             },
@@ -425,7 +461,8 @@ const writeBLECharacteristicValue = async (str, isHex) => {
     } else {
         buffer = new Uint8Array(strToUtf8Bytes(str)).buffer
     }
-
+    console.log("================转换后数据=================")
+    console.log(buffer)
     return await _writeBLECharacteristicValue(buffer)
 }
 

+ 789 - 0
utils/modbus.js

@@ -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();
+}
+