Merge branch 'new-20250827' of http://47.107.152.87:3000/liubiao/APP into new-20250827
This commit is contained in:
@ -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 };
|
||||||
console.log('[100J-蓝牙] 设备上报MAC:', macAddress, '原始:', Array.from(view).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '));
|
if (!skipSideEffects) {
|
||||||
if (this.onNotifyCallback) this.onNotifyCallback(result);
|
console.log('[100J-蓝牙] 设备上报MAC:', macAddress, '原始:', Array.from(view).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '));
|
||||||
|
if (this.onNotifyCallback) this.onNotifyCallback(result);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,15 +213,19 @@ class HBY100JProtocol {
|
|||||||
// 05: 文件更新响应 FB 05 [fileType] [status] FF,status: 1=成功 2=失败
|
// 05: 文件更新响应 FB 05 [fileType] [status] FF,status: 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,10 +272,11 @@ 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));
|
||||||
console.log('[100J-蓝牙] 设备响应 FB:', name, '解析:', JSON.stringify(result), '原始:', hexStr);
|
if (!skipSideEffects) {
|
||||||
|
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,37 +1133,47 @@ 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]', logName || '指令', '协议层已连接,走蓝牙');
|
||||||
|
return doBle().catch(onBleSendFail);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
console.log('[100J] 语音上传:协议层未就绪,将等待重连或走 4G', { isBleConnected: protocolInstance.isBleConnected, hasBleDeviceId: !!protocolInstance.bleDeviceId });
|
console.log('[100J]', logName || '指令', '协议层未就绪', { isBleConnected: protocolInstance.isBleConnected, hasBleDeviceId: !!protocolInstance.bleDeviceId });
|
||||||
// 无 bleDeviceId:系统蓝牙关闭则立即 4G;开启则短时等页面扫描连上(不再白等 12s)
|
// 无 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 (typeof onWaiting === 'function') onWaiting();
|
if (no4G) {
|
||||||
else showWaitUi('请稍候…');
|
if (typeof onWaiting === 'function') onWaiting();
|
||||||
return waitForBleConnection()
|
else showWaitUi('请稍候…');
|
||||||
.then((connected) => {
|
return waitForBleConnection()
|
||||||
return connected ? doBle().catch(onBleSendFail) : go4GOrReject('蓝牙未连接');
|
.then((connected) => {
|
||||||
})
|
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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: {
|
||||||
|
/** 警示灯频率 UI:slider 为 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 {
|
||||||
|
|||||||
@ -1728,28 +1728,18 @@ 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);
|
return this.getService(deviceId, targetServiceId, writeCharId,
|
||||||
} else {
|
notifyCharId);
|
||||||
console.log("不订阅消息");
|
|
||||||
}
|
|
||||||
return Promise.resolve(true);
|
|
||||||
} else {
|
|
||||||
console.log("开始获取服务", targetServiceId)
|
|
||||||
return this.getService(deviceId, targetServiceId, writeCharId,
|
|
||||||
notifyCharId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else { //已连接过,直接订阅消息
|
} else { //已连接过,直接订阅消息
|
||||||
// console.log("11111111");
|
// console.log("11111111");
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user