merge upstream

This commit is contained in:
2026-04-01 08:45:34 +08:00
4 changed files with 92 additions and 56 deletions

View File

@ -163,7 +163,12 @@ class HBY100JProtocol {
this.onNotifyCallback = callback; this.onNotifyCallback = callback;
} }
parseBleData(buffer) { /**
* @param {Uint8Array|ArrayBuffer} buffer
* @param {{ skipSideEffects?: boolean }} [options] skipSideEffects=true仅解析字段不打日志、不触发 onNotify/文件回调(供 BleReceive 与设备页 bleValueNotify 双订阅时避免重复)
*/
parseBleData(buffer, options = {}) {
const skipSideEffects = !!options.skipSideEffects;
const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
if (view.length < 3) return null; if (view.length < 3) return null;
@ -175,8 +180,10 @@ class HBY100JProtocol {
const macBytes = view.slice(1, 7); const macBytes = view.slice(1, 7);
const macAddress = Array.from(macBytes).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(':'); const macAddress = Array.from(macBytes).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(':');
const result = { type: 'mac', macAddress }; const result = { type: 'mac', macAddress };
if (!skipSideEffects) {
console.log('[100J-蓝牙] 设备上报MAC:', macAddress, '原始:', Array.from(view).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ')); console.log('[100J-蓝牙] 设备上报MAC:', macAddress, '原始:', Array.from(view).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '));
if (this.onNotifyCallback) this.onNotifyCallback(result); if (this.onNotifyCallback) this.onNotifyCallback(result);
}
return result; return result;
} }
@ -206,15 +213,19 @@ class HBY100JProtocol {
// 05: 文件更新响应 FB 05 [fileType] [status] FFstatus: 1=成功 2=失败 // 05: 文件更新响应 FB 05 [fileType] [status] FFstatus: 1=成功 2=失败
if (data.length >= 1) result.fileType = data[0]; if (data.length >= 1) result.fileType = data[0];
if (data.length >= 2) result.fileStatus = data[1]; // 1=Success, 2=Failure if (data.length >= 2) result.fileStatus = data[1]; // 1=Success, 2=Failure
if (this._fileResponseResolve) this._fileResponseResolve(result); if (!skipSideEffects && this._fileResponseResolve) this._fileResponseResolve(result);
break; break;
case 0x04: case 0x04:
// 5.5 获取设备电源状态: 电池容量8B + 电压8B + 百分比1B + 车载电源1B + 续航时间2B(分钟) // 5.5 获取设备电源状态: 容量8B + 电压8B + 百分比1B + 车载1B + 续航2B(分钟) + [充电状态1B] + FF
// 充电状态为固件新增,在续航之后;旧固件仅 20 字节 payload不影响 [16..19] 字段
if (data.length >= 20) { if (data.length >= 20) {
result.batteryPercentage = data[16]; result.batteryPercentage = data[16];
result.vehiclePower = data[17]; result.vehiclePower = data[17];
result.batteryRemainingTime = data[18] | (data[19] << 8); // 小端序,单位分钟 result.batteryRemainingTime = data[18] | (data[19] << 8); // 小端序,单位分钟
} }
if (data.length >= 21) {
result.chargingStatus = data[20]; // 0未充电 1充电中 2已充满
}
break; break;
case 0x06: case 0x06:
// 06: 语音播报响应 // 06: 语音播报响应
@ -261,11 +272,12 @@ class HBY100JProtocol {
const funcNames = { 0x01: '复位', 0x02: '基础信息', 0x03: '位置', 0x04: '电源状态', 0x05: '文件更新', 0x06: '语音播报', 0x09: '音量', 0x0A: '爆闪模式', 0x0B: '爆闪频率', 0x0C: '强制报警', 0x0D: 'LED亮度', 0x0E: '工作方式' }; const funcNames = { 0x01: '复位', 0x02: '基础信息', 0x03: '位置', 0x04: '电源状态', 0x05: '文件更新', 0x06: '语音播报', 0x09: '音量', 0x0A: '爆闪模式', 0x0B: '爆闪频率', 0x0C: '强制报警', 0x0D: 'LED亮度', 0x0E: '工作方式' };
const name = funcNames[funcCode] || ('0x' + funcCode.toString(16)); const name = funcNames[funcCode] || ('0x' + funcCode.toString(16));
if (!skipSideEffects) {
console.log('[100J-蓝牙] 设备响应 FB:', name, '解析:', JSON.stringify(result), '原始:', hexStr); console.log('[100J-蓝牙] 设备响应 FB:', name, '解析:', JSON.stringify(result), '原始:', hexStr);
if (this.onNotifyCallback) { if (this.onNotifyCallback) {
this.onNotifyCallback(result); this.onNotifyCallback(result);
} }
}
return result; return result;
} }
@ -879,9 +891,9 @@ export function cache100JVoiceFileForBle(deviceId, voiceListId, filePath) {
}); });
} }
// 暴露给页面:解析蓝牙接收到的数据 // 暴露给页面:解析蓝牙接收到的数据options 见类上 parseBleData 注释)
export function parseBleData(buffer) { export function parseBleData(buffer, options) {
return protocolInstance.parseBleData(buffer); return protocolInstance.parseBleData(buffer, options);
} }
// 暴露给页面:蓝牙连接后主动拉取电源状态(电量、续航) // 暴露给页面:蓝牙连接后主动拉取电源状态(电量、续航)
@ -1121,17 +1133,26 @@ function execWithBleFirst(bleExec, httpExec, logName, onWaiting, opts = {}) {
} catch (e) {} } catch (e) {}
}; };
// 协议层认为已连:仍可能被系统蓝牙关闭/底层已断而滞后,先校验适配器,避免先发蓝牙卡超时再回退
if (protocolInstance.isBleConnected && protocolInstance.bleDeviceId) { if (protocolInstance.isBleConnected && protocolInstance.bleDeviceId) {
console.log('[100J] 语音上传:协议层已连接,执行蓝牙传文件'); return getBleAdapterAvailable().then((adapterOk) => {
return doBle().catch(onBleSendFail); if (!adapterOk) {
protocolInstance.setBleConnectionStatus(false, '');
return go4GOrReject('系统蓝牙已关闭走4G');
} }
console.log('[100J] 语音上传:协议层未就绪,将等待重连或走 4G', { isBleConnected: protocolInstance.isBleConnected, hasBleDeviceId: !!protocolInstance.bleDeviceId }); console.log('[100J]', logName || '指令', '协议层已连接,走蓝牙');
// 无 bleDeviceId系统蓝牙关闭则立即 4G开启则短时等页面扫描连上不再白等 12s return doBle().catch(onBleSendFail);
});
}
console.log('[100J]', logName || '指令', '协议层未就绪', { isBleConnected: protocolInstance.isBleConnected, hasBleDeviceId: !!protocolInstance.bleDeviceId });
// 无 bleDeviceId本地仅蓝牙场景仍弹「请稍候」并等扫描可走 4G 时不弹 loading、不白等 5s直接 4G
if (!protocolInstance.bleDeviceId) { if (!protocolInstance.bleDeviceId) {
return getBleAdapterAvailable().then((adapterOk) => { return getBleAdapterAvailable().then((adapterOk) => {
if (!adapterOk) { if (!adapterOk) {
protocolInstance.setBleConnectionStatus(false, '');
return go4GOrReject('系统蓝牙未开启走4G'); return go4GOrReject('系统蓝牙未开启走4G');
} }
if (no4G) {
if (typeof onWaiting === 'function') onWaiting(); if (typeof onWaiting === 'function') onWaiting();
else showWaitUi('请稍候…'); else showWaitUi('请稍候…');
return waitForBleConnection() return waitForBleConnection()
@ -1139,19 +1160,20 @@ function execWithBleFirst(bleExec, httpExec, logName, onWaiting, opts = {}) {
return connected ? doBle().catch(onBleSendFail) : go4GOrReject('蓝牙未连接'); return connected ? doBle().catch(onBleSendFail) : go4GOrReject('蓝牙未连接');
}) })
.finally(hideWaitUi); .finally(hideWaitUi);
}
return waitForBleConnection(0, BLE_POLL_INTERVAL_MS).then((connected) =>
connected ? doBle().catch(onBleSendFail) : go4GOrReject('无蓝牙连接走4G')
);
}); });
} }
// 有 bleDeviceId 但未连:系统蓝牙关则直接 4G否则短时重连 // 有 bleDeviceId 但未连:用户刚关蓝牙/超出范围时,不再弹「请稍候」等重连 ~2s双通道在线时直接 4G
return getBleAdapterAvailable().then((adapterOk) => { return getBleAdapterAvailable().then((adapterOk) => {
if (!adapterOk) { if (!adapterOk) {
protocolInstance.setBleConnectionStatus(false, '');
return go4GOrReject('系统蓝牙未开启走4G'); return go4GOrReject('系统蓝牙未开启走4G');
} }
if (typeof onWaiting !== 'function') showWaitUi('请稍候…'); console.log('[100J]', logName || '指令', '蓝牙已断开直接走4G不阻塞重连');
return tryReconnectBle() return go4GOrReject(null);
.then((reconnected) => {
return reconnected ? doBle().catch(onBleSendFail) : go4GOrReject('蓝牙未连接');
})
.finally(hideWaitUi);
}); });
} }

View File

@ -173,10 +173,10 @@
<view class="line"></view> <view class="line"></view>
<view class="header paddingTop0"> <view class="header paddingTop0">
<text class="sliderTxt">频率</text> <text class="sliderTxt">频率</text>
<text class="sliderVal">{{ formData.strobeFrequency }}HZ</text> <text class="sliderVal">{{ strobeFrequencySlider }}HZ</text>
</view> </view>
<view class="slider-container"> <view class="slider-container">
<slider min="1" max="10" step="1" :disabled="false" :value="formData.strobeFrequency" <slider min="1" max="10" step="1" :disabled="false" :value="strobeFrequencySlider"
activeColor="#bbe600" backgroundColor="#686767" block-size="20" block-color="#ffffffde" activeColor="#bbe600" backgroundColor="#686767" block-size="20" block-color="#ffffffde"
@change="onFreqChanging" @changing="onFreqChanging" class="custom-slider" /> @change="onFreqChanging" @changing="onFreqChanging" class="custom-slider" />
</view> </view>
@ -331,7 +331,7 @@
sta_VoiceType: '0', sta_VoiceType: '0',
volume: 10, volume: 10,
sta_LightType: '', sta_LightType: '',
strobeFrequency: 0.5, strobeFrequency: 1,
lightBrightness: 10, lightBrightness: 10,
sta_system: '', sta_system: '',
warnTime: 0, warnTime: 0,
@ -552,8 +552,9 @@
// 设备按键, app同步 // 设备按键, app同步
} else if (funcType == '14') { } else if (funcType == '14') {
// 调节亮度,音量,频率相关字段 // 调节亮度,音量,频率相关字段
these.formData.strobeFrequency = led_strobe.frequency || these.formData.strobeFrequency = these.normalizeStrobeFreq(
0.5; //频率 led_strobe.frequency != null ? led_strobe.frequency : 1
);
these.formData.volume = volume || 10; //音量 these.formData.volume = volume || 10; //音量
these.formData.lightBrightness = brightness.red || these.formData.lightBrightness = brightness.red ||
10; //亮度值 10; //亮度值
@ -693,6 +694,10 @@
this.$nextTick(() => this.sync100JBleUiFromHelper()); this.$nextTick(() => this.sync100JBleUiFromHelper());
}, },
computed: { computed: {
/** 与 slider min/max(1~10) 对齐;离线未拉到详情时避免 0.5 等非法值导致滑块渲染飞出 */
strobeFrequencySlider() {
return this.normalizeStrobeFreq(this.formData.strobeFrequency);
},
getbleStatu() { getbleStatu() {
if (this.formData.bleStatu === true) { if (this.formData.bleStatu === true) {
return '已连接'; return '已连接';
@ -711,6 +716,15 @@
}, },
methods: { methods: {
/** 警示灯频率 UIslider 为 1~10与协议 0~12 取交集 */
normalizeStrobeFreq(v) {
const n = Number(v);
if (!Number.isFinite(n)) return 1;
const r = Math.round(n);
if (r < 1) return 1;
if (r > 10) return 10;
return r;
},
/** 与 BleHelper 实际连接状态对齐(系统关蓝牙再开、从后台回前台等) */ /** 与 BleHelper 实际连接状态对齐(系统关蓝牙再开、从后台回前台等) */
sync100JBleUiFromHelper() { sync100JBleUiFromHelper() {
const mac = (this.device && this.device.deviceMac) || (this.deviceInfo && this.deviceInfo.deviceMac); const mac = (this.device && this.device.deviceMac) || (this.deviceInfo && this.deviceInfo.deviceMac);
@ -796,6 +810,7 @@
}) })
); );
Object.assign(this.formData, validData); Object.assign(this.formData, validData);
that.formData.strobeFrequency = that.normalizeStrobeFreq(that.formData.strobeFrequency);
that.deviceInfo = res.data; that.deviceInfo = res.data;
that.$nextTick(() => that.sync100JBleUiFromHelper && that.sync100JBleUiFromHelper()); that.$nextTick(() => that.sync100JBleUiFromHelper && that.sync100JBleUiFromHelper());
const strobeEnable = res.data.strobeEnable ?? 0; // 0=关闭1=开启 const strobeEnable = res.data.strobeEnable ?? 0; // 0=关闭1=开启
@ -1368,6 +1383,9 @@
if (parsedData.batteryRemainingTime !== undefined) { if (parsedData.batteryRemainingTime !== undefined) {
this.$set(this.deviceInfo, 'batteryRemainingTime', parsedData.batteryRemainingTime); this.$set(this.deviceInfo, 'batteryRemainingTime', parsedData.batteryRemainingTime);
} }
if (parsedData.chargingStatus !== undefined) {
this.$set(this.deviceInfo, 'chargingStatus', parsedData.chargingStatus);
}
@ -1417,15 +1435,17 @@
else if (this.formData.sta_VoiceType === '7') this.formData.sta_VoiceType = '-1'; else if (this.formData.sta_VoiceType === '7') this.formData.sta_VoiceType = '-1';
} }
if (parsedData.volume !== undefined) this.formData.volume = parsedData.volume; if (parsedData.volume !== undefined) this.formData.volume = parsedData.volume;
if (parsedData.strobeFrequency !== undefined) this.formData.strobeFrequency = parsedData if (parsedData.strobeFrequency !== undefined) {
.strobeFrequency; this.formData.strobeFrequency = this.normalizeStrobeFreq(parsedData.strobeFrequency);
}
if (parsedData.redBrightness !== undefined) this.formData.lightBrightness = parsedData if (parsedData.redBrightness !== undefined) this.formData.lightBrightness = parsedData
.redBrightness; .redBrightness;
} }
// 0x09 音量、0x0D 亮度:单独响应时同步 // 0x09 音量、0x0D 亮度:单独响应时同步
if (fc === 0x09 && parsedData.volume !== undefined) this.formData.volume = parsedData.volume; if (fc === 0x09 && parsedData.volume !== undefined) this.formData.volume = parsedData.volume;
if (fc === 0x0B && parsedData.strobeFrequency !== undefined) this.formData.strobeFrequency = parsedData if (fc === 0x0B && parsedData.strobeFrequency !== undefined) {
.strobeFrequency; this.formData.strobeFrequency = this.normalizeStrobeFreq(parsedData.strobeFrequency);
}
if (fc === 0x0D && parsedData.redBrightness !== undefined) this.formData.lightBrightness = parsedData if (fc === 0x0D && parsedData.redBrightness !== undefined) this.formData.lightBrightness = parsedData
.redBrightness; .redBrightness;
}, },
@ -1932,6 +1952,9 @@
.slider-container { .slider-container {
padding: 0px; padding: 0px;
width: 100%;
overflow: hidden;
box-sizing: border-box;
} }
.addIco { .addIco {

View File

@ -1728,29 +1728,19 @@ class BleHelper {
return linkDevice(deviceId); return linkDevice(deviceId);
}).then((res) => { }).then((res) => {
if (res) { //新连接 if (res) { //新连接(含 createBLEConnection 刚成功)
// console.log("11111111"); // console.log("11111111");
if (fIndex == -1) { if (fIndex == -1) {
// console.log("开始获取服务", targetServiceId) // console.log("开始获取服务", targetServiceId)
return this.getService(deviceId, targetServiceId, writeCharId, return this.getService(deviceId, targetServiceId, writeCharId,
notifyCharId); //获取服务 notifyCharId); //获取服务
} else { } else {
if (f.wirteCharactId && f.notifyCharactId) { // 设备已在 LinkedList例如缓存/重连):不能仅用缓存的 write/notify UUID 直接订阅。
if (!f.notifyState) { // 重连后须先 getBLEDeviceServices否则部分机型 notifyBLECharacteristicValueChange 报 no service(10004)notify 收不到任何数据。
// console.log("开始订阅特征"); console.log("已缓存设备重新连接,重新发现服务并订阅", deviceId);
this.subScribe(deviceId, true);
} else {
console.log("不订阅消息");
}
return Promise.resolve(true);
} else {
console.log("开始获取服务", targetServiceId)
return this.getService(deviceId, targetServiceId, writeCharId, return this.getService(deviceId, targetServiceId, writeCharId,
notifyCharId); notifyCharId);
} }
}
} else { //已连接过,直接订阅消息 } else { //已连接过,直接订阅消息
// console.log("11111111"); // console.log("11111111");
if (fIndex > -1 && f) { if (fIndex > -1 && f) {

View File

@ -694,7 +694,8 @@ class BleReceive {
let receiveData = {}; let receiveData = {};
try { try {
if (!receive.bytes || receive.bytes.length < 3) return receiveData; if (!receive.bytes || receive.bytes.length < 3) return receiveData;
const parsed = parseBleData(receive.bytes); // 与 HBY100-J 页 bleValueNotify 共用 notify避免 parseBleData 执行两次重复日志、FB05 双次 resolve、onNotify 双次
const parsed = parseBleData(receive.bytes, { skipSideEffects: true });
if (!parsed) return receiveData; if (!parsed) return receiveData;
if (parsed.longitude !== undefined) receiveData.longitude = parsed.longitude; if (parsed.longitude !== undefined) receiveData.longitude = parsed.longitude;
if (parsed.latitude !== undefined) receiveData.latitude = parsed.latitude; if (parsed.latitude !== undefined) receiveData.latitude = parsed.latitude;