12 Commits

Author SHA1 Message Date
2995840376
18686dba7c feat(T): 设备动态描述细化到开关通道, 并支持多路控制的一次性配对去重
- APP 侧生成带通道名的描述: 打开 JD2BTSA 开关1 / 全部开关
  (TuyaSdkService.describeSwitchChannels 按 schema 通道名生成, 场景/背光等非电源开关不参与)
- /devices/status 新增 dpsDesc: APP 上报 DP 时带上语义描述, 面板来源的动态同样具体
- 后端 DeviceActivityService 配对增强: 多路设备一次控制多路时, 回执只带其中一路
  也判定为同一次操作(回执 dp 必须是主动控制 dp 的子集且值一致), 避免又出现两条动态
2026-10-28 11:54:32 +08:00
2995840376
15534a3b11 feat(S): 支持多路开关设备(两位插座/四位排插)的状态判断与一键控制
- TuyaSdkService 新增 SwitchState/readSwitchState: 按电源开关通道聚合,
  场景/背光/指示灯/倒计时等非电源开关按名称与分数排除
  (JD6BTSA 4位开关+2位场景开关 -> 只识别4路电源开关; JD2BTSA -> 2路)
- 卡片状态: 任一路开=已打开, 部分开=部分开启, 全关=已关闭
  (此前只读第一个通道, 打开另一路时首页仍显示已关闭)
- 卡片点击: 多路设备一键控制全部电源通道
- 乐观值锁定与快捷统计同步改为多路聚合
- 设备页(DevicesFragment/DeviceGridAdapter)同步合并后端最新DP与多路判断
2026-10-28 11:41:58 +08:00
2995840376
a894013de7 fix(R): 设备卡片开关状态改用后端最新DP, 修复面板操作后首页仍显示已关闭
- 根因: 卡片状态只读涂鸦 SDK 数据层的 dps, 而 Mesh 本地通道控制/子设备无云端推送时
  SDK 不更新, 面板已打开首页仍显示已关闭
- 后端是状态汇聚点(APP回传/中控屏上报/涂鸦云订阅), 设备列表返回 capabilities.dps,
  APP 合并后端DP(物模型code归一化为dpId)覆盖SDK值
- APP 面板与首页开关控制成功后上报最新DP到后端并通知首页刷新卡片
- 中控屏上报的属性状态同步写入 device.capabilities.dps
- 首页心跳(5s)同时轻量刷新设备列表, 感知其它入口(面板/中控屏)的设备状态变化
2026-10-28 11:34:27 +08:00
2995840376
197f97ec1d chore(Q): 首页近期动态心跳间隔 30s 调整为 5s 2026-10-28 11:21:09 +08:00
2995840376
3a91a50dec fix(Q): 主动控制与设备回执配对去重, 消除APP操作产生两条动态
- 后端 DeviceActivityService.recordEvent 新增配对判定: source=panel/state 的回执,
  若 6s 内同设备同物模型属性同动作已有 app/voice/scene 主动控制记录, 则视为同一次操作的
  回执并跳过(回执判定前等 1.2s, 给 APP 上报留落库时间)
- 生产验证: 仅面板操作保留 1 条 panel; APP 操作+回执只保留 1 条 app
- Android 首页新增 30s 动态心跳(页面可见时): 兜底拉取后端侧产生的动态(如面板操作),
  只刷动态列表不碰设备列表, 不会造成常用设备跳位
2026-10-28 11:17:10 +08:00
2995840376
f3aaf8fc35 fix(P): Android 首页常用设备不再因控制而重排/闪动
- 会话内顺序稳定: 记录上次渲染顺序, 新设备追加末尾, 后端顺序变化也不跳位
- 切家庭时重置顺序基准
- 点击卡片开关时通知页面: DP 回传期间跳过整表刷新, 4s 后延迟对齐一次
2026-10-28 11:11:46 +08:00
2995840376
da28d3b543 feat(O): Android 首页近期动态实时刷新
- 新增 ActivityEventBus(进程级静态回调, 主线程派发)
- PlatformServiceManager/DeviceControlActivity/TuyaSdkService 在动态落库成功后 notify
- HomeFragment 注册监听并 400ms 防抖重载近期动态, onResume/onHiddenChanged 兜底刷新
2026-10-28 10:48:10 +08:00
2995840376
066d374673 fix(M): 修复品类中文乱码, 子设备在线状态按父网关判定
乱码: 服务器 mysql 客户端在 C locale 下 character_set_client=latin1, 迁移脚本经 Windows type|plink 管道执行时 UTF-8 字节被按 latin1 解读, 中文双重编码。修复: V25/V26 脚本首行加 SET NAMES utf8mb4, deploy-all.bat 的 mysql 调用补 --default-character-set=utf8mb4, 新增 V26 按 code 写回正确中文(幂等)。已验证 product_category.name 的 hex 为正确 UTF-8。

子设备在线: 涂鸦 Mesh/Zigbee 子设备无独立在线通道, SDK 与云端快照对其 online 恒 false, APP 上报后被写成离线。后端新增 resolveEffectiveOnlineStatus —— 上报在线即 online; 子设备上报离线时以父网关为准(父在线则 online); 设备列表输出再做一次父网关聚合, 历史数据立即生效。前端 DeviceInfo 增报 parentDeviceId, 并按品类推断 is_gateway。

APP: 网关下挂设备不再用涂鸦 SDK 快照覆盖后端结论; 列表状态改为在线/离线/状态未知三态; uniapp 端修复状态比大写 ONLINE 导致恒离线、且把状态字符串当 DP JSON 解析的问题。生产验证: JD2BTSA/JD6BTSA 由 offline 变为 online。
2026-10-28 01:25:46 +08:00
2995840376
903b44c84b feat(L): 新增品类管理, 收口涂鸦设备写入的归属校验
品类管理: 新建 product_category 品类字典表(V25, 灌入 8 个预设品类并回填存量 product.category), 后端提供 ProductCategory 增删改查/启停/字典接口, 后台新增「品类管理」页面(设备管理组, 与产品管理同级), 产品管理的品类下拉改为读字典(失败回退内置枚举)。

孤儿设备根治: TuyaController.bindDevices 收口三道校验 —— 绑定门禁(本平台未绑定涂鸦账号直接拒绝)、归属白名单(上报设备须在该用户涂鸦账号真实清单内, 拉取失败降级放行)、owner 校验(他人名下设备不改写; 恢复已删设备时归属改到当前用户)。

兜底自愈: 新增 OrphanTuyaDeviceCleanupTask, 每天 04:30 回收无绑定记录用户的涂鸦设备与 AI 档案(逻辑删除, 可恢复)。

Android: 查看设备时的补同步先确认后端绑定状态, 未绑定不再写库; 同步被拒时记录原因而非静默失败。单测 TuyaControllerBindTest 5 用例覆盖门禁/白名单/owner/恢复归属。
2026-10-28 01:10:59 +08:00
2995840376
4c2d92571e feat(K): 规则保存时校验动作参数必须用物模型 code, 禁止 dpId
背景: 规则动作 params 的 key 会被原样当作涂鸦 code 下发(dp_id 从不参与下发), 历史测试数据里出现过 switch_1 与 1 混用, 导致下发失败或指令相互矛盾, 且保存时无任何校验。

后端: 新增 RuleActionValidator, createRule/updateRule 前校验动作参数 key 必须命中 product_property.code; 命中 dp_id 时直接提示应改用的 code; 同步校验值域(bool 取值、数值 min/max 区间、enum 候选值); 目标设备不存在、无产品物模型等场景给出明确业务错误。

前端: 后台规则表单补齐执行动作编辑(JSON), 提交前本地解析校验, 原本表单不提交 actions 导致新建规则必然失败。

Android: 物模型不可用时的回退路径改为优先使用涂鸦 code(反射读取), 避免提交 dpId 被后端拒绝。

V24: scene_run 表不存在时容错跳过, 便于在未执行 V16 的旧结构库上整段执行。
2026-10-28 00:52:04 +08:00
2995840376
10344e0b05 feat(D): Android 端消费物模型语义, 开关通道以物模型为唯一权威
ControlSchema.ControlItem:
- 新增 semantic / grp / isPrimary / enumLabels 字段
- 拆分隐式 key() 为显式 dpKey()(涂鸦 publishDps 用数字dpId) 与
  codeKey()(平台无关稳定标识, 场景规则/Mesh/云端用)
  原实现两条链路口径不一致(一处dpId一处code), 极易用错

DeviceControlActivity:
- 4 处 it.key() -> it.dpKey()(publishDps 路径)
- 主开关选择新增物模型兜底: 后端 mainSwitchCode -> is_primary -> semantic=power
  (语义由后端 SpecSemanticResolver 落库, 取代客户端按名称猜测)

新增 SpecSwitchResolver:
- 物模型驱动的开关通道解析器
- 通道来源 = control-schema 中 grp=switch 的功能点(即物模型 switch 组)
- 物模型不可用(设备无产品/未同步/网络失败)返回 null -> 调用方回退本地打分
- 保证离线与新设备场景不降级

DeviceRowAdapter:
- 新增物模型通道缓存(setSpecChannels), 回填后按设备局部刷新(不整表重绘)
- 统一内部 SwChannel 视图, 屏蔽物模型/本地打分两种来源差异
- 通道解析优先级: 后端物模型 > 本地打分兜底

DevicesFragment:
- 首次加载后异步拉取各设备 control-schema, 解析开关通道回填 adapter
- 通道属静态数据, 只拉一次, 避免每次刷新发 N 个请求

SceneDetailActivity:
- 规则下发的动作 key 改用 codeKey(), 并注释说明与 publishDps 的区别

验证: javac 编译 ControlSchema + SpecSwitchResolver 通过(两者不依赖 Android SDK);
其余改动为结构性替换, 待 Android Studio 编译验证
2026-10-27 13:23:24 +08:00
2995840376
0ce191c448 chore: 初始化 iotsaas 版本库基线
纳入范围:
- backend/  Spring Boot 多模块后端(iot-common/device/rule/data/alarm/auth)
- frontend/admin/  Vue3 + Element Plus 管理后台
- Android/   涂鸦原生 APP (com.xhai.iot)
- backend/sql/  Flyway 风格迁移脚本(V2~V21)
- docs/       对接文档与设计文档

排除范围:
- 构建产物: node_modules/ target/ build/ dist/ .gradle/
- 签名密钥: *.jks *.keystore (严禁入库)
- 本机配置: Android/local.properties
- 运行日志: logs/ *.log
- AI 协作本地数据 .codebuddy/ 与第三方 demo tuya-demo/

.gitattributes 统一以 LF 存储, Windows 批处理保留 CRLF
2026-10-27 12:00:41 +08:00