fix(M06-E-R1): 校正智慧插座协议
This commit is contained in:
@@ -1934,8 +1934,8 @@ async function assertDeviceTopology(pool, context) {
|
||||
const socket = await repository.createAsset(actor, {
|
||||
storeId, roomId, deviceId: 'M06B_SOCKET_001', imei: '860000000000002',
|
||||
iccid: '89860000000000000002', deviceType: 'SMART_SOCKET',
|
||||
model: 'JL-SOCKET', firmwareVersion: '1.0.0', signalStrength: 16,
|
||||
capabilities: ['POWER', 'METERING']
|
||||
model: 'JL-SOCKET-16A', firmwareVersion: '1.0.0', signalStrength: 16,
|
||||
capabilities: ['POWER', 'METERING', 'RATED_16A']
|
||||
});
|
||||
const lock = await repository.createAsset(actor, {
|
||||
storeId, roomId, deviceId: 'M06B_LOCK_001', imei: '',
|
||||
@@ -2281,6 +2281,150 @@ async function assertIotMessages(pool, context) {
|
||||
[context.tenantId, `/devicewill/${device.deviceId}`]
|
||||
);
|
||||
assert.equal(Number(willDeadRows[0].total), 0);
|
||||
|
||||
const [socketRows] = await pool.query(
|
||||
`SELECT id, store_id AS storeId, room_id AS roomId, device_id AS deviceId
|
||||
FROM qipai_devices
|
||||
WHERE tenant_id = ? AND device_id = 'M06B_SOCKET_001'`,
|
||||
[context.tenantId]
|
||||
);
|
||||
const socket = socketRows[0];
|
||||
assert.ok(socket, 'M06-E-R1 requires the smart socket fixture.');
|
||||
await service.handle(`/devicesend/${socket.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: socket.deviceId,
|
||||
read: 'workInfo',
|
||||
slotNum: 1,
|
||||
onOff: 'on',
|
||||
vol: '0220.5000',
|
||||
cur: '01.250',
|
||||
pow: '00275.000',
|
||||
temp: '45.50',
|
||||
energy: '0012.3400',
|
||||
timestamp: 1782120020
|
||||
})));
|
||||
const [workInfoRows] = await pool.query(
|
||||
`SELECT JSON_UNQUOTE(JSON_EXTRACT(normalized_payload, '$.payload.voltage')) AS voltage,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(normalized_payload, '$.payload.current')) AS currentValue,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(normalized_payload, '$.payload.power')) AS powerValue,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(normalized_payload, '$.payload.temperature')) AS temperature,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(normalized_payload, '$.payload.energy')) AS energy,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(normalized_payload, '$.payload.switchOn')) AS switchOn
|
||||
FROM qipai_iot_device_events
|
||||
WHERE tenant_id = ? AND device_id = ? AND event_type = 'workInfo'`,
|
||||
[context.tenantId, socket.id]
|
||||
);
|
||||
assert.deepEqual(workInfoRows, [{
|
||||
voltage: '220.5', currentValue: '1.25', powerValue: '275',
|
||||
temperature: '45.5', energy: '12.34', switchOn: 'true'
|
||||
}]);
|
||||
|
||||
const socketResultFixtures = [
|
||||
['1782120000008', 'off', 'update', 'DEVICE_UPDATE'],
|
||||
['1782120000009', 'off', 'paraerror', 'DEVICE_PARAERROR'],
|
||||
['1782120000010', 'localtask', 'error', 'DEVICE_ERROR']
|
||||
];
|
||||
for (const [commandId, action, result, failureCode] of socketResultFixtures) {
|
||||
await service.createCommand({
|
||||
tenantId: context.tenantId,
|
||||
assetId: String(socket.id),
|
||||
storeId: String(socket.storeId),
|
||||
roomId: String(socket.roomId),
|
||||
commandId,
|
||||
commandType: action,
|
||||
payload: { action, id: commandId, slotNum: 1 },
|
||||
traceId: `m06e-${result}`
|
||||
});
|
||||
await service.markPublished(context.tenantId, commandId);
|
||||
await service.handle(`/devicesend/${socket.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: socket.deviceId, id: commandId, action, result
|
||||
})));
|
||||
const [resultRows] = await pool.query(
|
||||
`SELECT status, failure_code AS failureCode
|
||||
FROM qipai_iot_commands WHERE tenant_id = ? AND command_id = ?`,
|
||||
[context.tenantId, commandId]
|
||||
);
|
||||
assert.deepEqual(resultRows, [{ status: 'FAILED', failureCode }]);
|
||||
}
|
||||
|
||||
for (let closeReason = 1; closeReason <= 7; closeReason += 1) {
|
||||
await service.handle(`/devicesend/${socket.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: socket.deviceId,
|
||||
event: 'special',
|
||||
slotNum: 1,
|
||||
state: 'off',
|
||||
closeReason,
|
||||
energy: `${closeReason}.0`,
|
||||
timestamp: 1782120030 + closeReason
|
||||
})));
|
||||
}
|
||||
const [socketAlertRows] = await pool.query(
|
||||
`SELECT alert_type AS alertType
|
||||
FROM qipai_device_alerts
|
||||
WHERE tenant_id = ? AND device_id = ?
|
||||
AND alert_type IN ('SOCKET_PULL_OUT_STOP', 'SOCKET_CHARGE_FULL_STOP',
|
||||
'SOCKET_OVERPOWER', 'SOCKET_OVERCURRENT', 'SOCKET_OVERTEMPERATURE')
|
||||
ORDER BY alert_type`,
|
||||
[context.tenantId, socket.id]
|
||||
);
|
||||
assert.deepEqual(socketAlertRows, [
|
||||
{ alertType: 'SOCKET_CHARGE_FULL_STOP' },
|
||||
{ alertType: 'SOCKET_OVERCURRENT' },
|
||||
{ alertType: 'SOCKET_OVERPOWER' },
|
||||
{ alertType: 'SOCKET_OVERTEMPERATURE' },
|
||||
{ alertType: 'SOCKET_PULL_OUT_STOP' }
|
||||
]);
|
||||
|
||||
await service.handle(`/devicesend/${socket.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: socket.deviceId,
|
||||
read: 'workInfo',
|
||||
temp: '45',
|
||||
temperature: '46',
|
||||
timestamp: 1782120040
|
||||
})));
|
||||
const [conflictRows] = await pool.query(
|
||||
`SELECT processing_status AS processingStatus,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(normalized_payload, '$.payload.temperature')) AS temperature
|
||||
FROM qipai_iot_device_events
|
||||
WHERE tenant_id = ? AND device_id = ? AND payload_hash = SHA2(?, 256)`,
|
||||
[context.tenantId, socket.id, JSON.stringify({
|
||||
DeviceID: socket.deviceId,
|
||||
read: 'workInfo', temp: '45', temperature: '46', timestamp: 1782120040
|
||||
})]
|
||||
);
|
||||
assert.deepEqual(conflictRows, [{ processingStatus: 'PROTOCOL_ERROR', temperature: '45' }]);
|
||||
const [conflictDeadRows] = await pool.query(
|
||||
`SELECT error_code AS errorCode FROM qipai_iot_dead_letters
|
||||
WHERE tenant_id = ? AND device_id = ? AND error_code = 'SOCKET_TEMPERATURE_CONFLICT'`,
|
||||
[context.tenantId, socket.id]
|
||||
);
|
||||
assert.deepEqual(conflictDeadRows, [{ errorCode: 'SOCKET_TEMPERATURE_CONFLICT' }]);
|
||||
|
||||
await service.createCommand({
|
||||
tenantId: context.tenantId,
|
||||
assetId: String(socket.id),
|
||||
storeId: String(socket.storeId),
|
||||
roomId: String(socket.roomId),
|
||||
commandId: '1782120000011',
|
||||
commandType: 'setReturnKey',
|
||||
payload: {
|
||||
action: 'setReturnKey', id: '1782120000011',
|
||||
old: 'old-return-secret', new: 'new-return-secret'
|
||||
},
|
||||
traceId: 'm06e-return-key-redaction'
|
||||
});
|
||||
const [returnKeyRows] = await pool.query(
|
||||
`SELECT JSON_UNQUOTE(JSON_EXTRACT(request_payload, '$.old')) AS oldValue,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(request_payload, '$.new')) AS newValue,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(request_payload, '$.oldHash')) AS oldHash,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(request_payload, '$.newHash')) AS newHash
|
||||
FROM qipai_iot_commands
|
||||
WHERE tenant_id = ? AND command_id = '1782120000011'`,
|
||||
[context.tenantId]
|
||||
);
|
||||
assert.equal(returnKeyRows[0].oldValue, null);
|
||||
assert.equal(returnKeyRows[0].newValue, null);
|
||||
assert.match(returnKeyRows[0].oldHash, /^[a-f0-9]{64}$/);
|
||||
assert.match(returnKeyRows[0].newHash, /^[a-f0-9]{64}$/);
|
||||
}
|
||||
|
||||
async function assertCleaningTaskTransactions(pool, context) {
|
||||
@@ -2913,8 +3057,12 @@ try {
|
||||
'unknown vendor result event dead-letter alert and command failure',
|
||||
'CtrlDevice credential request hashing and masking',
|
||||
'magstate and taskfinish remain events rather than ACKs',
|
||||
'raw devicewill close marks the device offline without invalid-payload dead-letter'
|
||||
,
|
||||
'raw devicewill close marks the device offline without invalid-payload dead-letter',
|
||||
'smart socket workInfo exact-decimal normalization',
|
||||
'smart socket update paraerror and error command failures',
|
||||
'special closeReason 3 through 7 alerts without 1 through 2 false alarms',
|
||||
'conflicting temp and temperature protocol dead-letter',
|
||||
'smart socket return-key request hashing and masking',
|
||||
'concurrent cleaning claim has one winner',
|
||||
'cleaning state and event transaction rollback',
|
||||
'WAITING CLAIMED and REJECTED reassignment cleanup',
|
||||
|
||||
Reference in New Issue
Block a user