diff --git a/README.md b/README.md index d51bb916..f6e8d017 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,17 @@ ### 一、项目简介 -#### 1. 物美智能 [wumei-smart](http://wumei.live/) 是一个简单易用的生活物联网平台。可用于搭建物联网平台以及二次开发和学习。 +#### 1. WumeiSmart是一个简单易用的生活物联网平台。可用于搭建物联网平台以及二次开发和学习。[演示地址>>](https://iot.wumei.live/) -#### 2. 设备接入使用EMQX消息服务器,加密认证;后端采用Spring boot;前端采用Vue;移动端采用Uniapp;数据库采用Mysql、TDengine和Redis;设备端支持ESP32、ESP8266、树莓派等;系统架构图如下: +#### 2. 项目采用AGPL3协议,可用于个人学习和使用,商业用途需要赞助项目,获得授权。[查看详情 >>](https://wumei.live/doc/pages/sponsor/) + +#### 3. 设备接入使用EMQX消息服务器,加密认证;后端采用Spring boot;前端采用Vue;移动端采用Uniapp;数据库采用Mysql、TDengine和Redis;设备端支持ESP32、ESP8266、树莓派等;系统架构图如下: -#### 3. 项目采用AGPL3协议,可用于个人学习和使用,商业用途需要赞助项目,获得授权。[查看详情 >>](https://wumei.live/doc/pages/sponsor/) - -#### 4. 参考设备 -| [空气检测仪](https://wumei.live/doc/pages/hardware/) | [Wifi通断器](https://wumei.live/doc/pages/hardware/) | [智能插座](https://wumei.live/doc/pages/hardware/) | [智能开关](https://wumei.live/doc/pages/hardware/) | [Wifi通断器](https://wumei.live/doc/pages/hardware/) | +#### 4. 相关硬件 +|[空气检测仪](https://wumei.live/doc/pages/hardware/) | [物联网开发板](https://wumei.live/doc/pages/hardware/) | [Air724开发板](https://wumei.live/doc/pages/hardware/) | [智能开关](https://wumei.live/doc/pages/hardware/) | [查看更多>>](https://wumei.live/doc/pages/hardware/) | | :----: | :----------: |:----------: |:----------: |:----------: | -| ![](https://oscimg.oschina.net/oscnet/up-ad98a81677e5e68d660866770e3266ca4cf.png) | ![](https://oscimg.oschina.net/oscnet/up-c38ce010e18349cfa526600c60e49792738.png) | ![](https://oscimg.oschina.net/oscnet/up-4ce09be3599e3ff7ed91fe182572abd258b.jpg) | ![](https://oscimg.oschina.net/oscnet/up-c4a7971510127324d6566dd6ea95d571483.jpg) | ![](https://oscimg.oschina.net/oscnet/up-e99eb758dc5abe387e880dbcb30ee21c063.jpg) | - +| ![](https://oscimg.oschina.net/oscnet/up-ad98a81677e5e68d660866770e3266ca4cf.png) | ![](https://oscimg.oschina.net/oscnet/up-68caf860d0659444e73f42717a03d2fdf72.png) | ![](https://oscimg.oschina.net/oscnet/up-cf690f6058042dccb567ff382ea9432ebab.png) |![](https://oscimg.oschina.net/oscnet/up-c4a7971510127324d6566dd6ea95d571483.jpg) | ![](https://oscimg.oschina.net/oscnet/up-4ce09be3599e3ff7ed91fe182572abd258b.jpg) | @@ -20,15 +19,15 @@ - 权限管理: 用户管理、部门管理、岗位管理、菜单管理、角色管理、字典和参数管理等 - 系统监控: 操作日志、登录日志、系统日志、在线用户、服务监控、连接池监控、缓存监控等 - 产品管理: 产品、产品物模型、产品分类、产品固件、设备授权码等 -- 设备管理: 控制、分组、定时、日志、统计、定位、分享、配置、配网、禁用、OTA升级、实时状态、自动注册、影子模式、实时监测、加密认证等 +- 设备管理: 设备控制、设备分组、设备定时、设备日志、监测统计、设备定位、设备分享、智能配网、设备禁用、OTA升级、实时状态、影子模式、实时监测、加密认证等 - EMQ管理: Mqtt客户端、监听器、消息主题、消息订阅、插件管理、规则引擎、资源 - 硬件 SDK: 支持WIFI和MQTT连接、物模型响应、实时监测、定时上报监测数据、AES加密、NTP时间等 - 物模型管理: 属性(设备状态和监测数据),功能(执行特定任务),事件(设备主动上报给云端) - 其他功能:多租户、统计、新闻资讯、通知公告、支持TDengine时序数据库 -- 计划开发完善功能:设备告警、场景联动、云云对接智能音箱、第三方登录、短信登录、APP界面自定义、视频流处理等 +- 开发中功能:设备告警、场景联动、云云对接智能音箱、第三方登录、短信登录、APP界面自定义、视频流处理等 -### 四、技术栈 +### 三、技术栈 * 服务端 - 相关技术:Spring boot、MyBatis、Spring Security、Jwt、Mysql、Redis、TDengine、EMQX、Mqtt等 - 开发工具:IDEA @@ -39,97 +38,52 @@ - 相关技术:uniapp、[uView](https://www.uviewui.com/)、[uChart](https://www.ucharts.cn/) - 开发工具:HBuilder * 硬件端 - - 相关技术: ESP-IDF、Arduino、FreeRTOS等 - - 开发工具:Visual Studio Code 和 Arduino - -### 五、硬件接入 -1. 设备认证 - * 加密认证(推荐) - * 简单认证 - * EMQX支持的其他认证方式 -2. 设备交互 - * 发布物模型、设备信息、时钟同步相关Mqtt主题 - * 订阅物模型、设备升级、时钟同步相关Mqtt主题 - -###### 项目提供了示例SDK,使用ESP8266芯片,基于Arduino开发。(设备烧录使用串口模块,例如Ch340,大部分开发板自带了)接线图如下: -![烧录代码](https://oscimg.oschina.net/oscnet/up-ed61da9a62390de451715686d6a6b37c190.png) + - 相关技术: ESP-IDF、Arduino、FreeRTOS、Python、Lua等 + - 开发工具:Visual Studio Code 和 Arduino等 -### 六、项目目录 +### 四、项目目录      spring-boot --------------- 后端
     vue ----------------------- 前端
     docker -------------------- docker部署文件
-     sdk ----------------------- 硬件SDK
+     sdk ----------------------- 硬件SDK,不同设备的SDK示例在不断增加
     app ----------------------- 移动端打包文件 -###### 移动端适配多端 -|安卓/Android|苹果/IOS|微信小程序| 网页/H5|Vue2.0 -| :---: | :---: | :---: | :---: |:---: | -| √ | √| √ | √ | √ | - -### 七、相关文档 +### 五、相关文档 ##### 权限管理基于ruoyi-vue系统,Mqtt消息服务器基于EMQX4.0开源版,SDK示例使用ESP8266 Core For Arduino开发 * [项目文档](https://wumei.live/doc/) * [物美智能官网](http://wumei.live/) * [若依权限管理系统ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue) * [Mqtt消息服务器EMQX4.0](https://github.com/emqx/emqx) -* [ESP8266 Core For Arduino](https://github.com/esp8266/Arduino) * [uCharts高性能跨平台图表库](https://www.ucharts.cn) * [TDengine时序数据库](https://www.taosdata.com/?zh) -### 八、其他 -* 互助交流群:946029159 1073236354 -* [演示地址>>](https://iot.wumei.live/) -* 小程序演示 - -![](https://oscimg.oschina.net/oscnet/up-a6feaa7aa6ea54551bd9feb97ebfb0ff206.jpg) - -##### 项目贡献者 - -|[小驿物联](https://gitee.com/iot-xiaoyi) |[Guanshubiao](https://gitee.com/guanshubiao)|[CrazyDull](https://gitee.com/crazyDull) |[Kami0314](https://github.com/kami0314)|[YBZX](https://github.com/YBZX) -|--|--|--|--|--| - -| [SXH](https://gitee.com/sixiaohu) | [Redamancy_zxp](https://gitee.com/redamancy-zxp) | [LEE](https://gitee.com/yueming188) | [LemonTree](https://gitee.com/fishhunterplus) | [Tang](https://gitee.com/mexiaotang) -|--|--|--|--|--| - -| [xxmfl](https://gitee.com/xxmfl) | -|--| +### 六、其他 +1. QQ交流群:🚀946029159 🚀1073236354 +2. 项目贡献者 + - [小驿物联](https://gitee.com/iot-xiaoyi)、 [Guanshubiao](https://gitee.com/guanshubiao)、[CrazyDull](https://gitee.com/crazyDull)、 [Kami0314](https://github.com/kami0314)、 [YBZX](https://github.com/YBZX) + - [SXH](https://gitee.com/sixiaohu)、 [Redamancy_zxp](https://gitee.com/redamancy-zxp)、 [LEE](https://gitee.com/yueming188)、 [LemonTree](https://gitee.com/fishhunterplus)、 [Tang](https://gitee.com/mexiaotang)、 [Tang](https://gitee.com/mexiaotang)、 [xxmfl](https://gitee.com/xxmfl) -### 九、部分图片 +### 七、部分图片 -![](https://oscimg.oschina.net/oscnet/up-75a392de73aff6110e7345399aed1cc78fb.png) -![](https://oscimg.oschina.net/oscnet/up-94aa4573358d29b485d71bb251964d2bfb3.png) +![](https://oscimg.oschina.net/oscnet/up-bae37a65120d87a357b5d366e2f7e343f24.png) +![](https://oscimg.oschina.net/oscnet/up-6d89f1558797a9becf07c20f92c1407a13a.png) - - + + - - + + - - - - - - - - - - - - - - - - - + +
diff --git a/sdk/Arduino/WumeiArduino/Config.cpp b/sdk/Arduino/WumeiArduino/Config.cpp index c4d29ac7..9c81763a 100644 --- a/sdk/Arduino/WumeiArduino/Config.cpp +++ b/sdk/Arduino/WumeiArduino/Config.cpp @@ -69,8 +69,9 @@ void connectWifi() { if (isApMode) { - // 关闭AP配网模式 + // 关闭AP配网模式,延迟2秒确保返回状态码给手机 isApMode = false; + delay(2000); server.stop(); ledStatus(false); } diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Product.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Product.java index 63e25362..7a319b5b 100644 --- a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Product.java +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Product.java @@ -64,19 +64,19 @@ public class Product extends BaseEntity } /** 状态(1-未发布,2-已发布,不能修改) */ - @Excel(name = "状态", readConverterExp = "1=-未发布,2-已发布,不能修改") + @Excel(name = "状态", readConverterExp = "1==未发布,2=已发布,不能修改") private Integer status; /** 设备类型(1-直连设备、2-网关子设备、3-网关设备) */ - @Excel(name = "设备类型", readConverterExp = "1=-直连设备、2-网关子设备、3-网关设备") + @Excel(name = "设备类型", readConverterExp = "1=直连设备、2=网关子设备、3=网关设备") private Integer deviceType; - /** 联网方式(1-wifi、2-蓝牙、3-wifi+蓝牙) */ - @Excel(name = "联网方式", readConverterExp = "1=-wifi、2-蓝牙、3-wifi+蓝牙") + /** 联网方式(1=-wifi、2-蜂窝(2G/3G/4G/5G)、3-以太网、4-其他) */ + @Excel(name = "联网方式", readConverterExp = "1=-wifi、2=蜂窝(2G/3G/4G/5G)、3=以太网、4=其他") private Integer networkMethod; /** 认证方式(1-账号密码、2-证书、3-Http) */ - @Excel(name = "认证方式", readConverterExp = "1=-账号密码、2-证书、3-Http") + @Excel(name = "认证方式", readConverterExp = "1=账号密码、2=证书、3=Http") private Integer vertificateMethod; /** 图片地址 */ diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceServiceImpl.java index 574ca886..c815cd5e 100644 --- a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceServiceImpl.java +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceServiceImpl.java @@ -574,7 +574,7 @@ public class DeviceServiceImpl implements IDeviceService { return AjaxResult.error("用户已经拥有设备:" + existDevice.getDeviceName() + ", 设备编号:" + existDevice.getSerialNumber()); } // 先删除设备的所有用户 - deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null,existDevice.getDeviceId())); + deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null, existDevice.getDeviceId())); // 添加新的设备用户 DeviceUser deviceUser = new DeviceUser(); deviceUser.setUserId(sysUser.getUserId()); @@ -902,6 +902,7 @@ public class DeviceServiceImpl implements IDeviceService { * @return 结果 */ @Override + @Transactional(rollbackFor = Exception.class) public int reportDevice(Device device, Device deviceEntity) { // 未采用设备定位则清空定位,定位方式(1=ip自动定位,2=设备定位,3=自定义) if (deviceEntity.getLocationWay() != 2) { @@ -910,11 +911,38 @@ public class DeviceServiceImpl implements IDeviceService { } int result = 0; if (deviceEntity != null) { - // 更新设备信息 + // 联网方式为Wifi的需要更新用户信息(1=wifi、2=蜂窝(2G/3G/4G/5G)、3=以太网、4=其他) + Product product = productService.selectProductByProductId(deviceEntity.getProductId()); + if (product.getNetworkMethod() == 1) { + if (deviceEntity.getUserId().longValue() != device.getUserId().longValue()) { + // 先删除设备的所有用户 + deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null, deviceEntity.getDeviceId())); + // 添加新的设备用户 + SysUser sysUser = userService.selectUserById(device.getUserId()); + DeviceUser deviceUser = new DeviceUser(); + deviceUser.setUserId(sysUser.getUserId()); + deviceUser.setUserName(sysUser.getUserName()); + deviceUser.setPhonenumber(sysUser.getPhonenumber()); + deviceUser.setDeviceId(deviceEntity.getDeviceId()); + deviceUser.setDeviceName(deviceEntity.getDeviceName()); + deviceUser.setTenantId(deviceEntity.getTenantId()); + deviceUser.setTenantName(deviceEntity.getTenantName()); + deviceUser.setIsOwner(1); + deviceUser.setCreateTime(DateUtils.getNowDate()); + deviceUserMapper.insertDeviceUser(deviceUser); + // 更新设备用户信息 + device.setUserId(device.getUserId()); + device.setUserName(sysUser.getUserName()); + } + }else{ + // 其他联网设备不更新用户信息 + device.setUserId(null); + } device.setUpdateTime(DateUtils.getNowDate()); if (deviceEntity.getActiveTime() == null || deviceEntity.getActiveTime().equals("")) { device.setActiveTime(DateUtils.getNowDate()); } + // 不更新物模型 device.setThingsModelValue(null); result = deviceMapper.updateDeviceBySerialNumber(device); } diff --git a/vue/src/views/index.vue b/vue/src/views/index.vue index aa3dfc75..093e4d7c 100644 --- a/vue/src/views/index.vue +++ b/vue/src/views/index.vue @@ -230,7 +230,7 @@
赞助用户:
-
可用于商业用途,并提供移动端源码,包含一年的更新。但是不能低价或批量转售源码,不能随意分发源码。目前赞助费为3000元,项目不断完善后会对应增加费用。
+
可用于商业用途,并提供移动端源码,包含一年的更新。但是不能低价或批量转售源码,不能随意分发源码。目前赞助费为3500元,项目不断完善后会对应增加费用。
diff --git a/vue/src/views/iot/device/device-edit.vue b/vue/src/views/iot/device/device-edit.vue index 4dca6ad6..37715c9b 100644 --- a/vue/src/views/iot/device/device-edit.vue +++ b/vue/src/views/iot/device/device-edit.vue @@ -15,7 +15,7 @@ - + 生成 @@ -152,7 +152,7 @@
- +
设备二维码
@@ -232,7 +232,7 @@ export default { data() { return { // 二维码内容 - qrText:'wumei-smart', + qrText: 'wumei-smart', // 打开设备配置对话框 openSummary: false, // 是否加载完成 @@ -271,6 +271,11 @@ export default { required: true, message: "设备名称不能为空", trigger: "blur" + }, { + min: 2, + max: 32, + message: '设备名称长度在 2 到 32 个字符', + trigger: 'blur' }], firmwareVersion: [{ required: true, @@ -294,61 +299,61 @@ export default { }, destroyed() { // 取消订阅主题 - this.mqttUnSubscribe(this.form); + this.mqttUnSubscribe(this.form); }, methods: { /* 连接Mqtt消息服务器 */ - async connectMqtt() { - if (this.$mqttTool.client == null) { - await this.$mqttTool.connect(this.vuex_token); - } - this.mqttCallback(); - }, + async connectMqtt() { + if (this.$mqttTool.client == null) { + await this.$mqttTool.connect(this.vuex_token); + } + this.mqttCallback(); + }, /* Mqtt回调处理 */ - mqttCallback() { - this.$mqttTool.client.on('message', (topic, message, buffer) => { - let topics = topic.split('/'); - let productId = topics[1]; - let deviceNum = topics[2]; - message = JSON.parse(message.toString()); - if (topics[3] == 'status') { - console.log('接收到【设备状态-详情】主题:', topic); - console.log('接收到【设备状态-详情】内容:', message); - // 更新列表中设备的状态 - if (this.form.serialNumber == deviceNum) { + mqttCallback() { + this.$mqttTool.client.on('message', (topic, message, buffer) => { + let topics = topic.split('/'); + let productId = topics[1]; + let deviceNum = topics[2]; + message = JSON.parse(message.toString()); + if (topics[3] == 'status') { + console.log('接收到【设备状态-详情】主题:', topic); + console.log('接收到【设备状态-详情】内容:', message); + // 更新列表中设备的状态 + if (this.form.serialNumber == deviceNum) { this.oldDeviceStatus = message.status; this.form.status = message.status; - this.form.isShadow = message.isShadow; - this.form.rssid = message.rssid; - } - } - }); - }, + this.form.isShadow = message.isShadow; + this.form.rssid = message.rssid; + } + } + }); + }, /** Mqtt订阅主题 */ - mqttSubscribe(device) { - // 订阅当前设备状态和实时监测 - let topicStatus = '/' + device.productId + '/' + device.serialNumber + '/status/post'; - let topicProperty = '/' + device.productId + '/' + device.serialNumber + '/property/post'; - let topicFunction = '/' + device.productId + '/' + device.serialNumber + '/function/post'; - let topics = []; - topics.push(topicStatus); - topics.push(topicProperty); - topics.push(topicFunction); - this.$mqttTool.subscribe(topics); - }, - /** Mqtt取消订阅主题 */ - mqttUnSubscribe(device) { - // 订阅当前设备状态和实时监测 - let topicStatus = '/' + device.productId + '/' + device.serialNumber + '/status/post'; - let topicProperty = '/' + device.productId + '/' + device.serialNumber + '/property/post'; - let topicFunction = '/' + device.productId + '/' + device.serialNumber + '/function/post'; - let topics = []; - topics.push(topicStatus); - topics.push(topicProperty); - topics.push(topicFunction); - console.log('取消订阅', topics); - this.$mqttTool.unsubscribe(topics); - }, + mqttSubscribe(device) { + // 订阅当前设备状态和实时监测 + let topicStatus = '/' + device.productId + '/' + device.serialNumber + '/status/post'; + let topicProperty = '/' + device.productId + '/' + device.serialNumber + '/property/post'; + let topicFunction = '/' + device.productId + '/' + device.serialNumber + '/function/post'; + let topics = []; + topics.push(topicStatus); + topics.push(topicProperty); + topics.push(topicFunction); + this.$mqttTool.subscribe(topics); + }, + /** Mqtt取消订阅主题 */ + mqttUnSubscribe(device) { + // 订阅当前设备状态和实时监测 + let topicStatus = '/' + device.productId + '/' + device.serialNumber + '/status/post'; + let topicProperty = '/' + device.productId + '/' + device.serialNumber + '/property/post'; + let topicFunction = '/' + device.productId + '/' + device.serialNumber + '/function/post'; + let topics = []; + topics.push(topicStatus); + topics.push(topicProperty); + topics.push(topicFunction); + console.log('取消订阅', topics); + this.$mqttTool.unsubscribe(topics); + }, // 获取子组件订阅的设备状态 getDeviceStatus(status) { @@ -374,7 +379,7 @@ export default { this.oldDeviceStatus = this.form.status; this.loadMap(); //Mqtt订阅 - this.mqttSubscribe(this.form); + this.mqttSubscribe(this.form); }); }, /**加载地图*/ @@ -429,6 +434,20 @@ export default { }, /** 提交按钮 */ submitForm() { + if (this.form.serialNumber == null || this.form.serialNumber == 0) { + this.$modal.alertError("设备编号不能为空"); + return; + } + let reg = /^[0-9a-zA-Z]+$/; + if (!reg.test(this.form.serialNumber)) { + this.$modal.alertError("设备编号只能是字母和数字"); + return; + } + if (this.form.productId == null || this.form.productId == 0) { + this.$modal.alertError("所属产品不能为空"); + return; + } + this.$refs["form"].validate(valid => { if (valid) { if (this.form.deviceId != 0) { @@ -444,7 +463,8 @@ export default { } else { addDevice(this.form).then(response => { this.form = response.data; - this.oldDeviceStatus = this.from.status; + console.log(response); + this.oldDeviceStatus = this.form.status; if (this.form.deviceId == null || this.form.deviceId == 0) { this.$modal.alertError("设备编号已经存在,添加设备失败"); } else { @@ -475,13 +495,13 @@ export default { }, /**关闭物模型 */ openSummaryDialog() { - let json={ - type:1,// 1=扫码关联设备 - deviceNumber:this.form.serialNumber, - productId:this.form.productId, - productName:this.form.productName, + let json = { + type: 1, // 1=扫码关联设备 + deviceNumber: this.form.serialNumber, + productId: this.form.productId, + productName: this.form.productName, }; - this.qrText=JSON.stringify(json); + this.qrText = JSON.stringify(json); this.openSummary = true; }, /**关闭物模型 */ diff --git a/vue/src/views/iot/device/running-status.vue b/vue/src/views/iot/device/running-status.vue index 368209c6..a2cbc1ca 100644 --- a/vue/src/views/iot/device/running-status.vue +++ b/vue/src/views/iot/device/running-status.vue @@ -217,6 +217,7 @@ export default { this.MonitorChart(); }); }); + this.connectMqtt(); } } }, @@ -259,10 +260,16 @@ export default { } }, created() { - // 回调处理 - this.mqttCallback(); + }, methods: { + /* 连接Mqtt消息服务器 */ + async connectMqtt() { + if (this.$mqttTool.client == null) { + await this.$mqttTool.connect(this.vuex_token); + } + this.mqttCallback(); + }, /* Mqtt回调处理 */ mqttCallback() { this.$mqttTool.client.on('message', (topic, message, buffer) => {