6c76007249
【V1.10 MC4 适配器 JSON 反序列化大小写修复】
- Mc4AuthHelper.cs: 新增 JsonOpts(PropertyNameCaseInsensitive+CamelCase) + EmptyJsonBody
- Mc4Adapter.cs: 新增统一 DeserializeData<T> 处理 {code,msg,data} 包装
- 所有 MC4 业务接口改用 DeserializeData<T> 解决 List 不能直接反序列化包装结构
- 解决 POST 无参 body=null 触发 400 的问题
【V1.10.1 GetObjectTreeAsync 修复】
- Mc4Adapter.cs: GetObjectTreeAsync 改用 EmptyJsonBody + DeserializeData<List<Mc4TreeNode>>
- 修复 A3 同步时报 400 和 List 反序列化错误
【V1.11 IoT 设备实时点值 + 空调控制】
- Mc4Adapter.cs: GetRealtimeValuesAsync 改用 DeserializeData(V1.10 漏改)
- web.vite/base_device.vue: fetchRealtime 增加 normalizePoint 字段映射(PascalCase→camelCase)
- web.vite/RealtimeDataPanel.vue: 组件层同样字段映射
- warehouse/DeviceInfo.vue: 改用 /api/base_device/getPageData 新接口
+ 按 DeviceCategory 分类展示(温度探头/湿度探头单卡片+空调控制器双卡片)
+ 30秒轮询实时点值(仅IoT设备)
+ 空调控制按钮 sourceDeviceId→deviceId 字段名修复
- tools/mc4_probe: 新增 MC4 设备探针工具(连接192.168.3.92抓取设备+点表+实时值)
- doc/整合方案/IoT设备实时点值显示与控制实施方案_v1.0.md: 详细方案文档
+ 设备三分类+点位映射表(温度探头/湿度探头index=2;空调index=5=湿度/6=温度/2-3-4=控制)
- 说明文档.md: 进度记录 V1.10/V1.10.1/V1.11 三条更新
【关键修复】
1. MC4 返回 JSON 字段全小写 → C# Model 大小写不匹配 (V1.10)
2. List 不能反序列化 {code,msg,data:[...]} 包装 (V1.10)
3. POST 无参 body=null 触发 400 (V1.10)
4. A3 同步 GetObjectTreeAsync 报 400+反序列化错误 (V1.10.1)
5. GetRealtimeValuesAsync 漏改 (V1.11)
6. 管理端实时数据弹窗 PascalCase 字段→camelCase 列名不匹配 (V1.11)
7. 仓库空调控制 sourceDeviceId 字段名与后端 ControlRequest.DeviceId 不匹配 (V1.11)
399 lines
17 KiB
Markdown
399 lines
17 KiB
Markdown
# IoT 设备实时点值显示 + 设备控制实施方案
|
||
|
||
> **编写时间**: 2026-07-24
|
||
> **编写人**: 猫娘工程师 幽浮喵
|
||
> **适用版本**: SecMPS 仓库大屏 (warehouse) + 管理端 (web.vite) + IntegrationGateway
|
||
> **目标**: 解决 MC4 IoT 设备(温度探头/湿度探头/空调控制器)在管理端和大屏两个前端的实时点值显示与控制问题
|
||
|
||
---
|
||
|
||
## 一、问题背景
|
||
|
||
### 1.1 当前痛点
|
||
1. **管理端**:base_device 列表中点击 IoT 设备的"实时数据"按钮,弹窗表格**无任何数据显示**(之前现场反馈过)
|
||
2. **大屏端**:warehouse 地图点击 IoT 设备标签,**实时点值不显示**,空调控制器无法下发制冷/制热/关机命令
|
||
3. **数据正确性**:MC4 设备点位序号 1 是自动添加的"在线点",真实数据从序号 2 开始;空调控制器有多个有效点位(5=湿度、6=温度、2=制冷发射、3=制热发射、4=关机发射)
|
||
|
||
### 1.2 根因汇总
|
||
| # | 根因 | 文件 |
|
||
|---|---|---|
|
||
| ① | `Mc4Adapter.GetRealtimeValuesAsync` 用 `JsonSerializer.Deserialize<List<Mc4PointValue>>(json)!` 反序列化实时点值响应,**没有传 `JsonOpts`**,MC4 返回的 `{code, msg, data:[...]}` 包装结构无法被 List 直接反序列化 | `IntegrationGateway.Adapters.MC4/Mc4Adapter.cs:154` |
|
||
| ② | `base_device.vue` 的 `fetchRealtime` 直接 `realtimeValues.value = await r.json()`,**未做字段名映射**(网关返回 PascalCase:PointIndex/Value/UpdateTime/Interval,前端 table 列名是 camelCase:pointIndex/value/updateTime/interval) | `web.vite/src/views/warehouse/device_manager/base_device.vue:164-170` |
|
||
| ③ | `RealtimeDataPanel.vue` 同样的问题,`values.value = await r.json()` 没做字段映射 | `web.vite/src/views/warehouse/device_manager/base_device/components/RealtimeDataPanel.vue:25` |
|
||
| ④ | 仓库 `DeviceInfo.vue` 调用的后端接口是老的(之前基于 warehouse_devicepoint 表),**未切换到新的 `/api/base_device/getPageData` 接口** | `warehouse/src/view/DeviceInfo.vue` |
|
||
| ⑤ | 仓库空调控制按钮的下发参数 `sourceDeviceId` 与后端 `ControlRequest` 期望的 `deviceId` **字段名不一致** | `warehouse/src/view/DeviceInfo.vue:618` |
|
||
| ⑥ | MC4 设备点位索引语义**仅在主人本地知道**(index=2 是真实数据/探头,空调 index=5=湿度/6=温度/2=制冷/3=制热/4=关机),未在代码/文档中固化,新人接手成本高 | 全局 |
|
||
|
||
---
|
||
|
||
## 二、设备分类与点位映射表
|
||
|
||
### 2.1 IoT 设备三分类
|
||
| 设备分类 | DeviceCategory 字典值 | 真实数据点位 | 控制点位 |
|
||
|---|---|---|---|
|
||
| 温度探头 | `温度探头` | `index=2`(温度值) | 无 |
|
||
| 湿度探头 | `湿度探头` | `index=2`(湿度值) | 无 |
|
||
| 空调控制器 | `空调控制器` | `index=5`(湿度)、`index=6`(温度) | `index=2`(制冷发射)、`index=3`(制热发射)、`index=4`(关机发射) |
|
||
|
||
> 说明:MC4 平台固定在每个设备的 `index=1` 位置自动添加"在线点"(bool 类型,1=在线),从 `index=2` 开始才是业务点位。
|
||
|
||
### 2.2 显示策略
|
||
| 设备类型 | 单卡片 | 双卡片 | 控制按钮 |
|
||
|---|---|---|---|
|
||
| 温度探头 | 1 个大数字(℃) | — | — |
|
||
| 湿度探头 | 1 个大数字(%RH) | — | — |
|
||
| 空调控制器 | — | 温度(℃)+ 湿度(%RH) | 制冷/制热/关机 |
|
||
|
||
---
|
||
|
||
## 三、网关 B 组接口(已就绪)
|
||
|
||
### 3.1 实时点位值查询
|
||
- **路径**:`GET /api/gateway/realtime/{adapter}/{deviceId}`
|
||
- **入参**:`adapter=MC4:33ku`、`deviceId=1928`(MC4 设备 id 字符串)
|
||
- **出参**:`List<PointValue>`(PascalCase)
|
||
```json
|
||
[
|
||
{"sourceDeviceId":"1928","pointIndex":1,"value":1,"updateTime":"2026-07-24T10:00:00","interval":10},
|
||
{"sourceDeviceId":"1928","pointIndex":2,"value":25.6,"updateTime":"2026-07-24T10:00:00","interval":10}
|
||
]
|
||
```
|
||
- **实现位置**:`IntegrationGateway.Host/Program.cs:302-307` 路由 + `Mc4Adapter.cs:145-164` 调用
|
||
|
||
### 3.2 设备控制
|
||
- **路径**:`POST /api/gateway/realtime/{adapter}/control`
|
||
- **入参**(JSON Body):
|
||
```json
|
||
{
|
||
"deviceId": "1928", // 设备 sourceId(注意:是 deviceId 不是 sourceDeviceId)
|
||
"pointIndex": 2, // 控制点索引
|
||
"value": 1 // 1=发射
|
||
}
|
||
```
|
||
- **出参**:`200 OK`
|
||
- **实现位置**:`IntegrationGateway.Host/Program.cs:329-335` 路由 + `Mc4Adapter.cs:167-175` SetPointValueAsync
|
||
|
||
### 3.3 MC4 适配器 GetRealtimeValuesAsync 修复(V1.10.1)
|
||
**改动文件**:`IntegrationGateway.Adapters.MC4/Mc4Adapter.cs:154`
|
||
|
||
**修改前**:
|
||
```csharp
|
||
var values = JsonSerializer.Deserialize<List<Mc4PointValue>>(json)!;
|
||
```
|
||
|
||
**修改后**:
|
||
```csharp
|
||
// MC4 响应是 {code, msg, data:[...]} 包装结构,data 段才是真正的点位列表
|
||
var values = DeserializeData<List<Mc4PointValue>>(json, "/api/central/device/point/value/get", new List<Mc4PointValue>());
|
||
```
|
||
|
||
`DeserializeData<T>` 在 `Mc4Adapter.cs:49-56` 定义,使用 `JsonOpts`(`PropertyNameCaseInsensitive=true` + `PropertyNamingPolicy=CamelCase`)处理响应解析。
|
||
|
||
---
|
||
|
||
## 四、web.vite 管理端(已修复)
|
||
|
||
### 4.1 base_device.vue 实时数据弹窗
|
||
**文件**:`web.vite/src/views/warehouse/device_manager/base_device.vue`
|
||
|
||
**改动点**(`fetchRealtime` 函数,line 163-179):
|
||
```javascript
|
||
// 网关 B4 返回 List<PointValue>,字段 PascalCase:SourceDeviceId, PointIndex, Value, UpdateTime, Interval
|
||
// 前端 table 字段是 camelCase,做一层字段映射
|
||
const normalizePoint = (v) => ({
|
||
pointIndex: v.PointIndex ?? v.pointIndex ?? v.index ?? 0,
|
||
value: v.Value ?? v.value ?? 0,
|
||
updateTime: v.UpdateTime ?? v.updateTime ?? v.Time ?? v.time ?? '',
|
||
interval: v.Interval ?? v.interval ?? 0
|
||
});
|
||
const fetchRealtime = async () => {
|
||
if (!curDev.value) return; realtimeLoading.value = true;
|
||
try {
|
||
const r = await fetch(`${GW}/api/gateway/realtime/${(curDev.value.AdapterCode || curDev.value.adapterCode)}/${curDev.value.SourceId || curDev.value.sourceId}`);
|
||
const list = await r.json();
|
||
realtimeValues.value = Array.isArray(list) ? list.map(normalizePoint) : [];
|
||
} catch {} finally { realtimeLoading.value = false }
|
||
}
|
||
```
|
||
|
||
**核心修复**:增加 `normalizePoint` 字段映射函数 + 数组类型判断(防止后端返回 404/500 时 `await r.json()` 解析失败)。
|
||
|
||
### 4.2 RealtimeDataPanel.vue 组件
|
||
**文件**:`web.vite/src/views/warehouse/device_manager/base_device/components/RealtimeDataPanel.vue`
|
||
|
||
**改动点**:与 `base_device.vue` 完全一致的 `normalizePoint` 字段映射逻辑(line 19-26、line 35-37)。
|
||
|
||
### 4.3 IoT 设备操作列
|
||
**文件**:`web.vite/src/views/warehouse/device_manager/base_device.vue`(line 222-237)
|
||
|
||
IoT 设备操作列已有 2 个按钮:
|
||
- "实时数据":打开 `RealtimeDataPanel` 弹窗
|
||
- "控制":打开 `DeviceControlPanel` 弹窗(通用点位控制)
|
||
|
||
---
|
||
|
||
## 五、warehouse 大屏端(已修复)
|
||
|
||
### 5.1 DeviceInfo.vue 接入新接口
|
||
**文件**:`warehouse/src/view/DeviceInfo.vue`
|
||
|
||
**改动点**:
|
||
1. **script 改用 `/api/base_device/getPageData` 接口**(line 524-582)
|
||
2. **优先用 MapModelId 查**(地图点击场景),其次用 DeviceId
|
||
3. **根据 DeviceCategory 字段判断设备分类**:温度探头/湿度探头/空调控制器
|
||
|
||
### 5.2 实时数据展示(按分类)
|
||
**文件**:`warehouse/src/view/DeviceInfo.vue`
|
||
|
||
| 设备分类 | 展示模板 | 关键计算属性 |
|
||
|---|---|---|
|
||
| 温度探头 | `<div class="single-value-card">` + 大数字 + ℃ | `primaryPointValue` 取 `index=2` |
|
||
| 湿度探头 | `<div class="single-value-card">` + 大数字 + %RH | `primaryPointValue` 取 `index=2` |
|
||
| 空调控制器 | `<div class="dual-value-card">` + 温度+湿度双卡片 | `acTempValue` 取 `index=6`,`acHumValue` 取 `index=5` |
|
||
|
||
**计算属性定义**(line 478-510):
|
||
```typescript
|
||
const primaryPointValue = computed(() => {
|
||
const p = realtimePoints.value.find(v => v.index === 2);
|
||
return p ? p.value.toFixed(1) : '--';
|
||
});
|
||
|
||
const acTempValue = computed(() => {
|
||
const p = realtimePoints.value.find(v => v.index === 6);
|
||
return p ? p.value.toFixed(1) : '--';
|
||
});
|
||
|
||
const acHumValue = computed(() => {
|
||
const p = realtimePoints.value.find(v => v.index === 5);
|
||
return p ? p.value.toFixed(1) : '--';
|
||
});
|
||
```
|
||
|
||
### 5.3 空调控制
|
||
**文件**:`warehouse/src/view/DeviceInfo.vue`(line 607-631)
|
||
|
||
```typescript
|
||
const handleAirControl = async (mode: 'cool' | 'heat' | 'off') => {
|
||
if (!deviceInfo.value.adapterCode || !deviceInfo.value.sourceId) {
|
||
ElMessage.error('设备信息不完整,无法控制');
|
||
return;
|
||
}
|
||
const pointIndex = mode === 'cool' ? 2 : mode === 'heat' ? 3 : 4;
|
||
controlLoading.value = mode;
|
||
try {
|
||
await gwPost(`/api/gateway/realtime/${deviceInfo.value.adapterCode}/control`, {
|
||
deviceId: deviceInfo.value.sourceId, // ← 关键修复:原来是 sourceDeviceId,与后端 ControlRequest.DeviceId 不匹配
|
||
pointIndex,
|
||
value: 1 // 1 = 发射
|
||
});
|
||
ElMessage.success(`已下发${mode === 'cool' ? '制冷' : mode === 'heat' ? '制热' : '关机'}命令`);
|
||
// 3 秒后刷新一次实时值
|
||
setTimeout(fetchRealtimePoints, 3000);
|
||
} catch (err: any) {
|
||
console.error('控制失败:', err);
|
||
ElMessage.error(`控制失败: ${err?.message || err}`);
|
||
} finally {
|
||
controlLoading.value = '';
|
||
}
|
||
};
|
||
```
|
||
|
||
**控制按钮 UI**(template line 115-125):
|
||
```vue
|
||
<el-tab-pane v-if="isAirConditionerController" label="设备控制">
|
||
<div class="tab-content">
|
||
<div class="control-buttons">
|
||
<el-button type="primary" :loading="controlLoading === 'cool'" @click="handleAirControl('cool')">制冷</el-button>
|
||
<el-button type="warning" :loading="controlLoading === 'heat'" @click="handleAirControl('heat')">制热</el-button>
|
||
<el-button type="danger" :loading="controlLoading === 'off'" @click="handleAirControl('off')">关机</el-button>
|
||
</div>
|
||
<div class="control-tip">通过网关下发到 MC4 设备的 index=2/3/4 控制点</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
```
|
||
|
||
### 5.4 30 秒轮询实时点值
|
||
**文件**:`warehouse/src/view/DeviceInfo.vue`(line 647-655)
|
||
|
||
```typescript
|
||
onMounted(() => {
|
||
setInterval(() => {
|
||
if (isIotDevice.value && deviceInfo.value.sourceId && deviceInfo.value.adapterCode) {
|
||
fetchRealtimePoints();
|
||
}
|
||
}, 30000);
|
||
});
|
||
```
|
||
|
||
仅 IoT 设备开启轮询(30 秒一次),**避免不必要的网络请求**。
|
||
|
||
### 5.5 实时点值解析(兼容多种命名风格)
|
||
**文件**:`warehouse/src/view/DeviceInfo.vue`(line 584-611)
|
||
|
||
```typescript
|
||
const fetchRealtimePoints = async () => {
|
||
try {
|
||
const adapter = deviceInfo.value.adapterCode; // 例如 MC4:33ku
|
||
const devId = deviceInfo.value.sourceId; // MC4 设备 id(字符串)
|
||
const data: any = await gwGet(`/api/gateway/realtime/${adapter}/${devId}`);
|
||
|
||
// 网关 B4 返回 List<PointValue>,字段:SourceDeviceId, PointIndex, Value, UpdateTime, Interval
|
||
// 同时兼容历史字段(items 包装 / 全小写 / 驼峰)
|
||
const list: any[] = Array.isArray(data)
|
||
? data
|
||
: (data?.items || data?.data?.items || data?.data || []);
|
||
realtimePoints.value = list.map((v: any) => {
|
||
const updateTime = v.updateTime || v.UpdateTime || v.Time || v.time;
|
||
return {
|
||
index: Number(v.pointIndex ?? v.PointIndex ?? v.index ?? v.Index ?? 0),
|
||
value: Number(v.value ?? v.Value ?? 0),
|
||
name: v.name || v.Name,
|
||
unit: v.unit || v.Unit,
|
||
updateTime: updateTime ? new Date(updateTime).toLocaleString('zh-CN') : ''
|
||
};
|
||
});
|
||
console.log(`[DeviceInfo] 实时点值 ${realtimePoints.value.length} 条`, realtimePoints.value);
|
||
} catch (err: any) {
|
||
console.warn('获取实时点值失败:', err);
|
||
realtimePoints.value = [];
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 六、调试探针(可选工具)
|
||
|
||
### 6.1 目的
|
||
连真实 MC4 设备(192.168.3.92)抓取设备和点位数据,验证点位索引偏移假设。
|
||
|
||
### 6.2 工具位置
|
||
`tools/mc4_probe/Mc4Probe/Program.cs`(独立 .NET 控制台程序)
|
||
|
||
### 6.3 用法
|
||
```bash
|
||
cd d:\Code\SecMPS\tools\mc4_probe\Mc4Probe
|
||
dotnet run -- --base http://192.168.3.92:3000 --user admin --pwd admin
|
||
```
|
||
|
||
### 6.4 输出示例
|
||
```
|
||
[MC4] 登录成功, token=eyJhbGciOiJIUzI1NiJ9...
|
||
[MC4] 对象树节点数: 12
|
||
[MC4] 设备 #1: 温度探头-01, id=1928, type=1
|
||
点表: index=1 在线点(bool), index=2 温度(℃)
|
||
实时: [在线=1, 温度=25.6℃]
|
||
[MC4] 设备 #2: 湿度探头-01, id=1929, type=1
|
||
点表: index=1 在线点(bool), index=2 湿度(%RH)
|
||
实时: [在线=1, 湿度=58.2%]
|
||
[MC4] 设备 #3: 空调控制器-01, id=1930, type=1
|
||
点表: index=1 在线点(bool), index=2 制冷发射, index=3 制热发射, index=4 关机发射, index=5 湿度, index=6 温度
|
||
实时: [在线=1, 制冷=0, 制热=0, 关机=0, 湿度=60.0, 温度=22.5]
|
||
```
|
||
|
||
---
|
||
|
||
## 七、验证清单
|
||
|
||
### 7.1 后端网关验证
|
||
- [x] `dotnet build gateway` — 0 错误 0 警告
|
||
- [x] `GetRealtimeValuesAsync` 改用 `DeserializeData<T>`,处理 `{code, msg, data:[...]}` 包装
|
||
- [x] `JsonOpts` 大小写不敏感,能正确反序列化 MC4 全小写字段
|
||
|
||
### 7.2 web.vite 管理端验证
|
||
- [ ] `npm run build web.vite` — 0 错误 0 警告
|
||
- [ ] base_device 列表 → 选 IoT 设备 → 点"实时数据" → 弹窗表格有 4 列数据(点位/当前值/更新时间/采集间隔)
|
||
- [ ] 点"控制" → 弹窗可输入点位索引 + 目标值 → 点"发送指令" → 设备响应
|
||
|
||
### 7.3 warehouse 大屏端验证
|
||
- [ ] `npm run build warehouse` — 0 错误 0 警告
|
||
- [ ] 地图 → 找温度探头 → 点标签 → 设备详情弹窗 → 实时数据选项卡显示温度大数字
|
||
- [ ] 地图 → 找空调控制器 → 点标签 → 设备详情弹窗 → 实时数据显示温度+湿度双卡片
|
||
- [ ] 设备控制选项卡 → 点"制冷"按钮 → 3 秒后实时数据刷新(验证控制生效)
|
||
- [ ] 30 秒后实时数据自动刷新(验证轮询工作)
|
||
|
||
### 7.4 端到端联调验证
|
||
- [ ] 主人用 192.168.3.92 实机测试:温度探头/湿度探头/空调控制器三分类全部正常
|
||
- [ ] 空调制冷/制热/关机三个命令都能成功下发并反映到实时数据
|
||
|
||
---
|
||
|
||
## 八、关键文件改动清单
|
||
|
||
| # | 文件 | 改动 | 验证 |
|
||
|---|---|---|---|
|
||
| 1 | `gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs` | `GetRealtimeValuesAsync` 改用 `DeserializeData` | dotnet build ✓ |
|
||
| 2 | `gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs` | 新增 `JsonOpts` + `EmptyJsonBody`(V1.10 前序修复) | dotnet build ✓ |
|
||
| 3 | `web.vite/src/views/warehouse/device_manager/base_device.vue` | `fetchRealtime` 增加 `normalizePoint` 字段映射 | 需 build |
|
||
| 4 | `web.vite/src/views/warehouse/device_manager/base_device/components/RealtimeDataPanel.vue` | 同样增加 `normalizePoint` 字段映射 | 需 build |
|
||
| 5 | `warehouse/src/view/DeviceInfo.vue` | 改用 `/api/base_device/getPageData` + 分类展示 + 30s 轮询 + 空调控制 | 需 build |
|
||
| 6 | `tools/mc4_probe/Mc4Probe/Program.cs` | 新增(调试探针,独立工具) | 独立运行 |
|
||
|
||
---
|
||
|
||
## 九、风险与回滚
|
||
|
||
### 9.1 风险
|
||
| 风险 | 等级 | 缓解措施 |
|
||
|---|---|---|
|
||
| MC4 设备实际点位索引与主人预期不一致 | 中 | 部署后用 `mc4_probe` 工具先验证 |
|
||
| 空调命令下发后实时数据刷新延迟 | 低 | 已设置 3 秒后强制刷新 + 30 秒轮询兜底 |
|
||
| 设备控制误操作 | 中 | 控制按钮加 `loading` 状态 + 弹 `ElMessage` 提示 |
|
||
|
||
### 9.2 回滚方案
|
||
- 仓库 `DeviceInfo.vue` 改动可通过 `git revert` 回滚到上一版本
|
||
- web.vite `base_device.vue` / `RealtimeDataPanel.vue` 改动可通过 `git revert` 回滚
|
||
- 网关 `Mc4Adapter.cs` 改动只影响 MC4 设备,对 Owl/KMS 适配器**完全无影响**,**回滚风险极低**
|
||
|
||
---
|
||
|
||
## 十、后续优化建议(非本次范围)
|
||
|
||
1. **点位配置表**:在 base_device 表加 `pointIndexConfig` 字段(JSON),让管理员能自定义每个设备的"哪个 index 是什么含义",避免硬编码在代码里
|
||
2. **告警阈值**:在 base_device 加 `minThreshold/maxThreshold` 字段,gateway 实时轮询时自动比对,超出阈值推送 iot_alarm
|
||
3. **历史曲线**:仓库大屏的温度/湿度曲线当前已用 SVG 简单实现,可改为 ECharts 实时刷新(30 秒一次)
|
||
4. **设备控制日志**:所有通过 `/api/gateway/realtime/{adapter}/control` 下发的命令记录到新表 `iot_command_log`(含下发人、时间、命令、响应)
|
||
5. **批量控制**:空调控制器分组管理时,支持"批量制冷"操作(一组设备同时下发 index=2 发射命令)
|
||
|
||
---
|
||
|
||
## 附录 A:网关 B4 响应示例(修复后)
|
||
|
||
**请求**:
|
||
```
|
||
GET http://192.168.3.108:5100/api/gateway/realtime/MC4:33ku/1928
|
||
```
|
||
|
||
**响应**(HTTP 200,application/json):
|
||
```json
|
||
[
|
||
{"sourceDeviceId":"1928","pointIndex":1,"value":1,"updateTime":"2026-07-24T10:00:00","interval":10},
|
||
{"sourceDeviceId":"1928","pointIndex":2,"value":25.6,"updateTime":"2026-07-24T10:00:00","interval":10}
|
||
]
|
||
```
|
||
|
||
## 附录 B:网关 B5 请求示例
|
||
|
||
**请求**:
|
||
```
|
||
POST http://192.168.3.108:5100/api/gateway/realtime/MC4:33ku/control
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"deviceId": "1930",
|
||
"pointIndex": 2,
|
||
"value": 1
|
||
}
|
||
```
|
||
|
||
**响应**(HTTP 200,application/json):
|
||
```json
|
||
true
|
||
```
|
||
|
||
或空 body(minimal API `Results.Ok()`)。
|
||
|
||
---
|
||
|
||
> **维护说明**: 本文档作为 V1.10 IoT 设备实时点值 + 控制功能的实施记录,与 `说明文档.md` 的"进度记录"段保持同步更新。
|