diff --git a/app/diario-bordo.html b/app/diario-bordo.html
index b1fd736..9002e62 100644
--- a/app/diario-bordo.html
+++ b/app/diario-bordo.html
@@ -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');
diff --git a/server/public/index.html b/server/public/index.html
index b1fd736..9002e62 100644
--- a/server/public/index.html
+++ b/server/public/index.html
@@ -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');