fix: BLE protocol handling matches actual device behavior

- discoverChars uses properties (write/notify) to distinguish FFE1
  command characteristic from FFE1 service UUID
- handleValueChange: vendor_33 mode emits vendor_status (FFE4 heartbeat)
  and vendor_data separately, no longer tries parseFrame on raw bytes
- parseVendorStatus handles 1-byte heartbeat and 33-byte state readback
  with proper IO1-IO5 field parsing
这个提交包含在:
Guoguo
2026-05-12 05:59:16 -07:00
父节点 624f52f4cc
当前提交 cd088dbddc
修改 2 个文件,包含 39 行新增10 行删除
+18 -6
查看文件
@@ -82,13 +82,20 @@ function handleNotification(frame) {
} }
function handleValueChange(res) { function handleValueChange(res) {
if (protocol.PROTOCOL_MODE === 'vendor_33') {
var bytes = protocol.bufferToBytes(res.value)
var uuid = (res.characteristicId || '').toUpperCase()
var isStatus = uuid.indexOf(CHAR.STATUS) !== -1
if (isStatus) {
emit('vendor_status', { raw: bytes, heartbeat: bytes[0] })
} else {
emit('vendor_data', { raw: bytes, hex: protocol.bytesToHex(bytes) })
}
return
}
var frame = protocol.parseFrame(res.value) var frame = protocol.parseFrame(res.value)
if (frame) { if (frame) {
handleNotification(frame) handleNotification(frame)
return
}
if (protocol.PROTOCOL_MODE === 'vendor_33') {
emit('status', protocol.parseVendorStatus(res.value))
} }
} }
@@ -104,9 +111,14 @@ function discoverChars(deviceId, serviceId, group) {
for (var i = 0; i < chars.length; i++) { for (var i = 0; i < chars.length; i++) {
var c = chars[i] var c = chars[i]
var uuid = c.uuid.toUpperCase() var uuid = c.uuid.toUpperCase()
var props = c.properties || {}
if (uuid.indexOf(CHAR.DEVICE_INFO) !== -1) _chars.deviceInfo = { uuid: c.uuid, serviceId: serviceId } if (uuid.indexOf(CHAR.DEVICE_INFO) !== -1) _chars.deviceInfo = { uuid: c.uuid, serviceId: serviceId }
if (uuid.indexOf(CHAR.COMMAND) !== -1) _chars.command = { uuid: c.uuid, serviceId: serviceId } if (uuid.indexOf(CHAR.COMMAND) !== -1 && (props.write || props.writeNoResponse)) {
if (uuid.indexOf(CHAR.STATUS) !== -1) _chars.status = { uuid: c.uuid, serviceId: serviceId } _chars.command = { uuid: c.uuid, serviceId: serviceId }
}
if (uuid.indexOf(CHAR.STATUS) !== -1 && props.notify) {
_chars.status = { uuid: c.uuid, serviceId: serviceId }
}
if (uuid.indexOf(CHAR.BOND_INFO) !== -1) _chars.bondInfo = { uuid: c.uuid, serviceId: serviceId } if (uuid.indexOf(CHAR.BOND_INFO) !== -1) _chars.bondInfo = { uuid: c.uuid, serviceId: serviceId }
if (uuid.indexOf(CHAR.OTA_CONTROL) !== -1) _chars.otaControl = { uuid: c.uuid, serviceId: serviceId } if (uuid.indexOf(CHAR.OTA_CONTROL) !== -1) _chars.otaControl = { uuid: c.uuid, serviceId: serviceId }
if (uuid.indexOf(CHAR.OTA_DATA) !== -1) _chars.otaData = { uuid: c.uuid, serviceId: serviceId } if (uuid.indexOf(CHAR.OTA_DATA) !== -1) _chars.otaData = { uuid: c.uuid, serviceId: serviceId }
+19 -2
查看文件
@@ -196,12 +196,29 @@ function buildVendorCommand33(options) {
function parseVendorStatus(buffer) { function parseVendorStatus(buffer) {
var bytes = bufferToBytes(buffer) var bytes = bufferToBytes(buffer)
if (bytes.length === 1) {
return { heartbeat: bytes[0], is_heartbeat: true, raw_hex: bytesToHex(bytes) }
}
if (bytes.length === 33) {
var ios = []
for (var i = 0; i < 5; i++) {
var off = i * 6
ios.push({
red: bytes[off], infrared: bytes[off + 1],
uv: bytes[off + 2], warm_yellow: bytes[off + 3],
gain: (bytes[off + 4] << 8) | bytes[off + 5]
})
}
return { return {
mode_state: bytes.length > 0 ? bytes[0] : MODE_STATE.IDLE, is_heartbeat: false,
raw_bytes: bytes, ios: ios,
hold_time: (bytes[30] << 8) | bytes[31],
checksum: bytes[32],
raw_hex: bytesToHex(bytes) raw_hex: bytesToHex(bytes)
} }
} }
return { is_heartbeat: false, raw_bytes: bytes, raw_hex: bytesToHex(bytes) }
}
// --- frame encoding/decoding --- // --- frame encoding/decoding ---