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');