feat(ble): probe via Web Bluetooth pro Chrome PC v1.10.12
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Karlão sugeriu testar no notebook (Chrome) onde Web Bluetooth API é mais madura que plugin Capacitor v6. Implementado bmsProbeWebBluetooth que usa navigator.bluetooth direto: - getPrimaryService(ff00) - getCharacteristics() lista chars - writeValueWithoutResponse / writeValue conforme properties - characteristicvaluechanged event listener - Wake sequence + 3 protocolos JBD/JK/Daly Quando Web descobrir o protocolo certo, copio a lógica pro path Capacitor (APK Android também vai funcionar). requestDevice pra browser web agora inclui ff00/fff0/ffe0 nos optionalServices pra Web Bluetooth permitir acesso. Sem APK rebuild (web only) — só deploy backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
330d5aaa62
commit
638ed5e37b
2 changed files with 158 additions and 16 deletions
|
|
@ -5892,7 +5892,7 @@ async function pairBluetoothDevice(){
|
|||
setBleDiag('Abrindo picker do navegador...');
|
||||
const device=await navigator.bluetooth.requestDevice({
|
||||
acceptAllDevices:true,
|
||||
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
|
||||
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO,'0000ff00-0000-1000-8000-00805f9b34fb','0000fff0-0000-1000-8000-00805f9b34fb','0000ffe0-0000-1000-8000-00805f9b34fb'],
|
||||
});
|
||||
if(!device){return}
|
||||
deviceId=device.id;
|
||||
|
|
@ -5922,11 +5922,11 @@ async function pairBluetoothDevice(){
|
|||
}
|
||||
saveState();
|
||||
renderBluetoothCard();
|
||||
// Se detectou JBD BMS, ativa parser proprietário
|
||||
if(info.isJBD){
|
||||
// Se detectou JBD BMS OU backend é web (sempre tenta probe — descobre serviço dinâmico), ativa probe
|
||||
if(info.isJBD||backend==='web'){
|
||||
const ok=await bmsAttachJBD(deviceId,deviceName);
|
||||
if(ok)toast('✓ '+deviceName+' · JBD BMS ativo');
|
||||
else toast('✓ '+deviceName+' (BMS detectado mas falha no parser)');
|
||||
if(ok)toast('✓ '+deviceName+' · BMS ativo');
|
||||
else toast('✓ '+deviceName+' (sem BMS detectável)');
|
||||
}else{
|
||||
toast('✓ '+deviceName+(info.battery!=null?' · '+info.battery+'%':' (sem leitura de bateria)'));
|
||||
}
|
||||
|
|
@ -6056,10 +6056,81 @@ function bytesToBase64(arr){
|
|||
return btoa(bin);
|
||||
}
|
||||
|
||||
// Probe: lista characteristics + identifica notify/write chars + tenta protocolos
|
||||
// Probe via Web Bluetooth API (Chrome PC)
|
||||
async function bmsProbeWebBluetooth(deviceId,deviceName){
|
||||
const conn=_bleConnections.get(deviceId);
|
||||
const device=conn?.device;
|
||||
if(!device){setBleDiag('Device sem referência (re-pareie)','err');return false}
|
||||
try{
|
||||
setBleDiag(`📦 Shivao v${APP_VERSION} · Probe WEB iniciado`,'info');
|
||||
const server=device.gatt.connected?device.gatt:await device.gatt.connect();
|
||||
setBleDiag('GATT web conectado','ok');
|
||||
let svc=null;
|
||||
try{svc=await server.getPrimaryService('0000ff00-0000-1000-8000-00805f9b34fb');setBleDiag('Service ff00 OK','info')}
|
||||
catch(e){setBleDiag('Service ff00 falhou: '+e.message,'err');return false}
|
||||
const chars=await svc.getCharacteristics();
|
||||
setBleDiag(`Svc ff00 · ${chars.length} chars`,'info');
|
||||
let notifyChar=null,writeChar=null;
|
||||
for(const c of chars){
|
||||
const p=c.properties;
|
||||
const propsStr=[p.notify&&'notify',p.indicate&&'indicate',p.write&&'write',p.writeWithoutResponses&&'wnr',p.read&&'read'].filter(Boolean).join(',');
|
||||
const cu=c.uuid.toLowerCase();
|
||||
setBleDiag(` ${cu.slice(4,8)} [${propsStr}]`,'info');
|
||||
if(!notifyChar&&(p.notify||p.indicate))notifyChar=c;
|
||||
if(!writeChar&&(p.write||p.writeWithoutResponses))writeChar=c;
|
||||
}
|
||||
if(!notifyChar||!writeChar){setBleDiag('Sem chars notify+write','err');return false}
|
||||
setBleDiag(`Notify=${notifyChar.uuid.slice(4,8)} Write=${writeChar.uuid.slice(4,8)}`,'ok');
|
||||
notifyChar.addEventListener('characteristicvaluechanged',(ev)=>{
|
||||
const dv=ev.target.value;
|
||||
const hex=Array.from(new Uint8Array(dv.buffer)).map(b=>b.toString(16).padStart(2,'0')).join(' ');
|
||||
setBleDiag(`← RX ${dv.byteLength}b: ${hex.slice(0,100)}${hex.length>100?'...':''}`,'ok');
|
||||
const first=new Uint8Array(dv.buffer)[0];
|
||||
if(first===0xDD)bmsHandleChunk(deviceId,dv,deviceName);
|
||||
else if(first===0xAA)bmsHandleJK(deviceId,dv,deviceName);
|
||||
else if(first===0xA5)bmsHandleDaly(deviceId,dv,deviceName);
|
||||
});
|
||||
await notifyChar.startNotifications();
|
||||
setBleDiag('Notify ativo · iniciando wake...','ok');
|
||||
await new Promise(r=>setTimeout(r,500));
|
||||
try{
|
||||
const fn=writeChar.properties.writeWithoutResponses?'writeValueWithoutResponse':'writeValue';
|
||||
await writeChar[fn](new Uint8Array([0x5A,0x5A,0x5A,0x5A]));
|
||||
setBleDiag('Wake 5A x4 enviado','info');
|
||||
}catch(e){setBleDiag('Wake skip: '+e.message,'info')}
|
||||
await new Promise(r=>setTimeout(r,1500));
|
||||
const PROTOCOLS=[
|
||||
{name:'JBD-0x03',bytes:[0xDD,0xA5,0x03,0x00,0xFF,0xFD,0x77]},
|
||||
{name:'JK-getInfo',bytes:[0xAA,0x55,0x90,0xEB,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10]},
|
||||
{name:'Daly-getInfo',bytes:[0xA5,0x80,0x90,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBD]},
|
||||
];
|
||||
for(const p of PROTOCOLS){
|
||||
try{
|
||||
setBleDiag(`→ TX ${p.name}`,'info');
|
||||
const fn=writeChar.properties.writeWithoutResponses?'writeValueWithoutResponse':'writeValue';
|
||||
await writeChar[fn](new Uint8Array(p.bytes));
|
||||
setBleDiag(`✔ write ${p.name} OK`,'info');
|
||||
await new Promise(r=>setTimeout(r,2500));
|
||||
const dev=state.btDevices?.find(d=>d.id===deviceId);
|
||||
if(dev?.bms?.voltage||dev?._lastRxAt){
|
||||
setBleDiag(`✓ ${p.name} respondeu!`,'ok');
|
||||
if(dev){dev.bmsProtocol=p.name;dev.isJBD=true;saveState()}
|
||||
conn.notifyChar=notifyChar;conn.writeChar=writeChar;
|
||||
return true;
|
||||
}
|
||||
setBleDiag(`✗ ${p.name} sem RX`,'info');
|
||||
}catch(e){setBleDiag(`${p.name} erro: ${e.message}`,'warn')}
|
||||
}
|
||||
setBleDiag('⚠ Nenhum protocolo respondeu','err');
|
||||
return false;
|
||||
}catch(e){setBleDiag('Probe web falhou: '+e.message,'err');return false}
|
||||
}
|
||||
|
||||
// Probe Capacitor: lista characteristics + identifica notify/write chars + tenta protocolos
|
||||
async function bmsProbeAndAttach(deviceId,deviceName){
|
||||
const backend=bleBackend();
|
||||
if(backend!=='capacitor'){setBleDiag('Probe requer Capacitor (APK)','warn');return false}
|
||||
if(backend==='web')return bmsProbeWebBluetooth(deviceId,deviceName);
|
||||
if(backend!=='capacitor'){setBleDiag('Backend desconhecido','warn');return false}
|
||||
const ble=window.Capacitor.Plugins.BluetoothLe;
|
||||
try{
|
||||
setBleDiag(`📦 Shivao v${APP_VERSION} · Probe iniciado`,'info');
|
||||
|
|
@ -6369,7 +6440,7 @@ async function removeBluetoothDevice(id){
|
|||
renderBluetoothCard();
|
||||
}
|
||||
|
||||
const APP_VERSION='1.10.11';
|
||||
const APP_VERSION='1.10.12';
|
||||
function renderBluetoothCard(){
|
||||
const el=document.getElementById('bt-list');
|
||||
const supportEl=document.getElementById('bt-support');
|
||||
|
|
|
|||
|
|
@ -5892,7 +5892,7 @@ async function pairBluetoothDevice(){
|
|||
setBleDiag('Abrindo picker do navegador...');
|
||||
const device=await navigator.bluetooth.requestDevice({
|
||||
acceptAllDevices:true,
|
||||
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
|
||||
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO,'0000ff00-0000-1000-8000-00805f9b34fb','0000fff0-0000-1000-8000-00805f9b34fb','0000ffe0-0000-1000-8000-00805f9b34fb'],
|
||||
});
|
||||
if(!device){return}
|
||||
deviceId=device.id;
|
||||
|
|
@ -5922,11 +5922,11 @@ async function pairBluetoothDevice(){
|
|||
}
|
||||
saveState();
|
||||
renderBluetoothCard();
|
||||
// Se detectou JBD BMS, ativa parser proprietário
|
||||
if(info.isJBD){
|
||||
// Se detectou JBD BMS OU backend é web (sempre tenta probe — descobre serviço dinâmico), ativa probe
|
||||
if(info.isJBD||backend==='web'){
|
||||
const ok=await bmsAttachJBD(deviceId,deviceName);
|
||||
if(ok)toast('✓ '+deviceName+' · JBD BMS ativo');
|
||||
else toast('✓ '+deviceName+' (BMS detectado mas falha no parser)');
|
||||
if(ok)toast('✓ '+deviceName+' · BMS ativo');
|
||||
else toast('✓ '+deviceName+' (sem BMS detectável)');
|
||||
}else{
|
||||
toast('✓ '+deviceName+(info.battery!=null?' · '+info.battery+'%':' (sem leitura de bateria)'));
|
||||
}
|
||||
|
|
@ -6056,10 +6056,81 @@ function bytesToBase64(arr){
|
|||
return btoa(bin);
|
||||
}
|
||||
|
||||
// Probe: lista characteristics + identifica notify/write chars + tenta protocolos
|
||||
// Probe via Web Bluetooth API (Chrome PC)
|
||||
async function bmsProbeWebBluetooth(deviceId,deviceName){
|
||||
const conn=_bleConnections.get(deviceId);
|
||||
const device=conn?.device;
|
||||
if(!device){setBleDiag('Device sem referência (re-pareie)','err');return false}
|
||||
try{
|
||||
setBleDiag(`📦 Shivao v${APP_VERSION} · Probe WEB iniciado`,'info');
|
||||
const server=device.gatt.connected?device.gatt:await device.gatt.connect();
|
||||
setBleDiag('GATT web conectado','ok');
|
||||
let svc=null;
|
||||
try{svc=await server.getPrimaryService('0000ff00-0000-1000-8000-00805f9b34fb');setBleDiag('Service ff00 OK','info')}
|
||||
catch(e){setBleDiag('Service ff00 falhou: '+e.message,'err');return false}
|
||||
const chars=await svc.getCharacteristics();
|
||||
setBleDiag(`Svc ff00 · ${chars.length} chars`,'info');
|
||||
let notifyChar=null,writeChar=null;
|
||||
for(const c of chars){
|
||||
const p=c.properties;
|
||||
const propsStr=[p.notify&&'notify',p.indicate&&'indicate',p.write&&'write',p.writeWithoutResponses&&'wnr',p.read&&'read'].filter(Boolean).join(',');
|
||||
const cu=c.uuid.toLowerCase();
|
||||
setBleDiag(` ${cu.slice(4,8)} [${propsStr}]`,'info');
|
||||
if(!notifyChar&&(p.notify||p.indicate))notifyChar=c;
|
||||
if(!writeChar&&(p.write||p.writeWithoutResponses))writeChar=c;
|
||||
}
|
||||
if(!notifyChar||!writeChar){setBleDiag('Sem chars notify+write','err');return false}
|
||||
setBleDiag(`Notify=${notifyChar.uuid.slice(4,8)} Write=${writeChar.uuid.slice(4,8)}`,'ok');
|
||||
notifyChar.addEventListener('characteristicvaluechanged',(ev)=>{
|
||||
const dv=ev.target.value;
|
||||
const hex=Array.from(new Uint8Array(dv.buffer)).map(b=>b.toString(16).padStart(2,'0')).join(' ');
|
||||
setBleDiag(`← RX ${dv.byteLength}b: ${hex.slice(0,100)}${hex.length>100?'...':''}`,'ok');
|
||||
const first=new Uint8Array(dv.buffer)[0];
|
||||
if(first===0xDD)bmsHandleChunk(deviceId,dv,deviceName);
|
||||
else if(first===0xAA)bmsHandleJK(deviceId,dv,deviceName);
|
||||
else if(first===0xA5)bmsHandleDaly(deviceId,dv,deviceName);
|
||||
});
|
||||
await notifyChar.startNotifications();
|
||||
setBleDiag('Notify ativo · iniciando wake...','ok');
|
||||
await new Promise(r=>setTimeout(r,500));
|
||||
try{
|
||||
const fn=writeChar.properties.writeWithoutResponses?'writeValueWithoutResponse':'writeValue';
|
||||
await writeChar[fn](new Uint8Array([0x5A,0x5A,0x5A,0x5A]));
|
||||
setBleDiag('Wake 5A x4 enviado','info');
|
||||
}catch(e){setBleDiag('Wake skip: '+e.message,'info')}
|
||||
await new Promise(r=>setTimeout(r,1500));
|
||||
const PROTOCOLS=[
|
||||
{name:'JBD-0x03',bytes:[0xDD,0xA5,0x03,0x00,0xFF,0xFD,0x77]},
|
||||
{name:'JK-getInfo',bytes:[0xAA,0x55,0x90,0xEB,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10]},
|
||||
{name:'Daly-getInfo',bytes:[0xA5,0x80,0x90,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBD]},
|
||||
];
|
||||
for(const p of PROTOCOLS){
|
||||
try{
|
||||
setBleDiag(`→ TX ${p.name}`,'info');
|
||||
const fn=writeChar.properties.writeWithoutResponses?'writeValueWithoutResponse':'writeValue';
|
||||
await writeChar[fn](new Uint8Array(p.bytes));
|
||||
setBleDiag(`✔ write ${p.name} OK`,'info');
|
||||
await new Promise(r=>setTimeout(r,2500));
|
||||
const dev=state.btDevices?.find(d=>d.id===deviceId);
|
||||
if(dev?.bms?.voltage||dev?._lastRxAt){
|
||||
setBleDiag(`✓ ${p.name} respondeu!`,'ok');
|
||||
if(dev){dev.bmsProtocol=p.name;dev.isJBD=true;saveState()}
|
||||
conn.notifyChar=notifyChar;conn.writeChar=writeChar;
|
||||
return true;
|
||||
}
|
||||
setBleDiag(`✗ ${p.name} sem RX`,'info');
|
||||
}catch(e){setBleDiag(`${p.name} erro: ${e.message}`,'warn')}
|
||||
}
|
||||
setBleDiag('⚠ Nenhum protocolo respondeu','err');
|
||||
return false;
|
||||
}catch(e){setBleDiag('Probe web falhou: '+e.message,'err');return false}
|
||||
}
|
||||
|
||||
// Probe Capacitor: lista characteristics + identifica notify/write chars + tenta protocolos
|
||||
async function bmsProbeAndAttach(deviceId,deviceName){
|
||||
const backend=bleBackend();
|
||||
if(backend!=='capacitor'){setBleDiag('Probe requer Capacitor (APK)','warn');return false}
|
||||
if(backend==='web')return bmsProbeWebBluetooth(deviceId,deviceName);
|
||||
if(backend!=='capacitor'){setBleDiag('Backend desconhecido','warn');return false}
|
||||
const ble=window.Capacitor.Plugins.BluetoothLe;
|
||||
try{
|
||||
setBleDiag(`📦 Shivao v${APP_VERSION} · Probe iniciado`,'info');
|
||||
|
|
@ -6369,7 +6440,7 @@ async function removeBluetoothDevice(id){
|
|||
renderBluetoothCard();
|
||||
}
|
||||
|
||||
const APP_VERSION='1.10.11';
|
||||
const APP_VERSION='1.10.12';
|
||||
function renderBluetoothCard(){
|
||||
const el=document.getElementById('bt-list');
|
||||
const supportEl=document.getElementById('bt-support');
|
||||
|
|
|
|||
Loading…
Reference in a new issue