diff --git a/app/diario-bordo.html b/app/diario-bordo.html
index d6e7c29..5375287 100644
--- a/app/diario-bordo.html
+++ b/app/diario-bordo.html
@@ -1977,7 +1977,11 @@ Marque zonas de proibição (alarme alto) ou atenção (aviso suave). Detecção
Verificando suporte...
+ Parear novo dispositivo Bluetooth
-Limitações: iOS Safari não suporta Web Bluetooth (use no Chrome PC ou Android). Reconexão automática varia por device.
+
+ 📋 Diagnóstico (logs do pareamento)
+
+
+Limitações: iOS Safari não suporta Web Bluetooth . APK Android usa plugin nativo. BMS proprietários (Victron, JBD) podem aparecer mas não expor Battery Service padrão.
@@ -5771,26 +5775,42 @@ async function ensureBleNativeReady(){
_bleNativeInitialized=true;
}
+// Diagnóstico visível: mostra cada passo no card BLE
+function setBleDiag(msg,type){
+ const el=document.getElementById('bt-diag');
+ if(!el)return;
+ const colors={info:'var(--m-text-mid,#b3c5d6)',ok:'var(--m-ok,#10b981)',err:'var(--m-danger,#ef4444)',warn:'var(--m-warn,#f59e0b)'};
+ const time=new Date().toLocaleTimeString('pt-BR',{hour12:false});
+ const line=`${time} · ${escapeHtml(msg)}
`;
+ el.innerHTML=line+el.innerHTML;
+ // Mantém só últimas 12 linhas
+ const lines=el.children;
+ while(lines.length>12)el.removeChild(lines[lines.length-1]);
+ console.log('[ble]',msg);
+}
+
async function pairBluetoothDevice(){
const backend=bleBackend();
- if(!backend){
- toast('Bluetooth indisponível neste dispositivo.');
- return;
- }
+ setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err');
+ if(!backend){toast('Bluetooth indisponível');return}
try{
let deviceId,deviceName;
if(backend==='capacitor'){
+ setBleDiag('Inicializando plugin nativo...');
await ensureBleNativeReady();
+ setBleDiag('Plugin OK · abrindo picker...');
const ble=window.Capacitor.Plugins.BluetoothLe;
const result=await ble.requestDevice({
services:[],
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
allowDuplicates:false,
});
- if(!result?.deviceId){return}
+ if(!result?.deviceId){setBleDiag('Picker cancelado','warn');return}
deviceId=result.deviceId;
deviceName=result.name||'Dispositivo BLE';
+ setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok');
}else{
+ setBleDiag('Abrindo picker do navegador...');
const device=await navigator.bluetooth.requestDevice({
acceptAllDevices:true,
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
@@ -5802,11 +5822,13 @@ async function pairBluetoothDevice(){
device.addEventListener('gattserverdisconnected',()=>{
const c=_bleConnections.get(deviceId);if(c)c.connected=false;
renderBluetoothCard();
+ setBleDiag('Disconnected: '+deviceName,'warn');
});
+ setBleDiag('Selecionado: '+deviceName,'ok');
}
- // Conecta + lê info inicial
+ setBleDiag('Conectando GATT...');
const info=await connectAndRead(deviceId,deviceName);
- // Salva no state
+ setBleDiag('Connect OK · battery='+(info.battery??'N/A')+' · mfr='+(info.manufacturer||'N/A'),info.battery!=null?'ok':'warn');
if(!state.btDevices)state.btDevices=[];
const existing=state.btDevices.find(d=>d.id===deviceId);
if(existing){
@@ -5821,28 +5843,44 @@ async function pairBluetoothDevice(){
}
saveState();
renderBluetoothCard();
- toast('✓ '+deviceName+' pareado'+(info.battery!=null?' · '+info.battery+'%':''));
+ toast('✓ '+deviceName+(info.battery!=null?' · '+info.battery+'%':' (sem leitura de bateria)'));
}catch(e){
- if(e.name==='NotFoundError'||/cancel/i.test(e.message||''))return;
+ if(e.name==='NotFoundError'||/cancel/i.test(e.message||'')){setBleDiag('Cancelado','warn');return}
+ const msg=e.message||e.errorMessage||JSON.stringify(e).slice(0,100)||'erro desconhecido';
+ setBleDiag('ERRO: '+msg,'err');
console.warn('[ble] pair failed',e);
- toast('Erro: '+(e.message||e.errorMessage||'pareamento falhou'));
+ toast('Falhou: '+msg);
}
}
async function connectAndRead(deviceId,deviceName){
- const info={battery:null,manufacturer:null,model:null};
+ const info={battery:null,manufacturer:null,model:null,services:[]};
const backend=bleBackend();
try{
if(backend==='capacitor'){
const ble=window.Capacitor.Plugins.BluetoothLe;
- await ble.connect({deviceId,timeout:15000});
+ try{
+ await ble.connect({deviceId,timeout:30000});
+ setBleDiag('GATT conectado','ok');
+ }catch(e){
+ setBleDiag('connect() falhou: '+(e.message||e.errorMessage||'?'),'err');
+ throw e;
+ }
const conn=_bleConnections.get(deviceId)||{};
conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true;
_bleConnections.set(deviceId,conn);
+ // Discover all services pra diagnóstico
+ try{
+ const r=await ble.getServices({deviceId});
+ const svcs=(r.services||r||[]).map(s=>s.uuid||s).slice(0,8);
+ setBleDiag('Serviços encontrados: '+svcs.length+' ('+svcs.map(u=>u.slice(4,8)).join(', ')+')','info');
+ info.services=svcs;
+ }catch(e){setBleDiag('getServices falhou: '+e.message,'warn')}
// Battery
try{
const r=await ble.read({deviceId,service:BLE_BATTERY_SERVICE,characteristic:BLE_BATTERY_CHAR});
info.battery=parseDataView(r.value).getUint8(0);
+ setBleDiag('Battery Service OK: '+info.battery+'%','ok');
try{
await ble.startNotifications({deviceId,service:BLE_BATTERY_SERVICE,characteristic:BLE_BATTERY_CHAR});
ble.addListener('notification|'+deviceId+'|'+BLE_BATTERY_SERVICE+'|'+BLE_BATTERY_CHAR,(ev)=>{
@@ -5850,16 +5888,19 @@ async function connectAndRead(deviceId,deviceName){
const dev=state.btDevices?.find(d=>d.id===deviceId);
if(dev){dev.lastBattery=newVal;dev.lastSeen=Date.now();saveState();renderBluetoothCard()}
});
- }catch(e){}
- }catch(e){}
+ setBleDiag('Notificações ativas','ok');
+ }catch(e){setBleDiag('startNotifications falhou: '+e.message,'warn')}
+ }catch(e){setBleDiag('Sem Battery Service padrão (BMS pode usar protocolo proprietário)','warn')}
// Device info
try{
const r=await ble.read({deviceId,service:BLE_DEVICE_INFO,characteristic:BLE_MANUFACTURER_CHAR});
info.manufacturer=new TextDecoder().decode(parseDataView(r.value));
+ setBleDiag('Fabricante: '+info.manufacturer,'info');
}catch(e){}
try{
const r=await ble.read({deviceId,service:BLE_DEVICE_INFO,characteristic:BLE_MODEL_CHAR});
info.model=new TextDecoder().decode(parseDataView(r.value));
+ setBleDiag('Modelo: '+info.model,'info');
}catch(e){}
}else{
const conn=_bleConnections.get(deviceId);
diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle
index de77655..778d4fd 100644
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "br.com.pontualtech.shivao"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 14
- versionName "1.9.1"
+ versionCode 15
+ versionName "1.9.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/mobile/package.json b/mobile/package.json
index 450c1ff..e8685a6 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -1,6 +1,6 @@
{
"name": "shivao-mobile",
- "version": "1.9.1",
+ "version": "1.9.2",
"description": "Shivao app nativo (Capacitor wrapper Android/iOS)",
"main": "index.js",
"type": "module",
diff --git a/server/public/index.html b/server/public/index.html
index d6e7c29..5375287 100644
--- a/server/public/index.html
+++ b/server/public/index.html
@@ -1977,7 +1977,11 @@ Marque zonas de proibição (alarme alto) ou atenção (aviso suave). Detecção
Verificando suporte...
+ Parear novo dispositivo Bluetooth
-Limitações: iOS Safari não suporta Web Bluetooth (use no Chrome PC ou Android). Reconexão automática varia por device.
+
+ 📋 Diagnóstico (logs do pareamento)
+
+
+Limitações: iOS Safari não suporta Web Bluetooth . APK Android usa plugin nativo. BMS proprietários (Victron, JBD) podem aparecer mas não expor Battery Service padrão.
@@ -5771,26 +5775,42 @@ async function ensureBleNativeReady(){
_bleNativeInitialized=true;
}
+// Diagnóstico visível: mostra cada passo no card BLE
+function setBleDiag(msg,type){
+ const el=document.getElementById('bt-diag');
+ if(!el)return;
+ const colors={info:'var(--m-text-mid,#b3c5d6)',ok:'var(--m-ok,#10b981)',err:'var(--m-danger,#ef4444)',warn:'var(--m-warn,#f59e0b)'};
+ const time=new Date().toLocaleTimeString('pt-BR',{hour12:false});
+ const line=`${time} · ${escapeHtml(msg)}
`;
+ el.innerHTML=line+el.innerHTML;
+ // Mantém só últimas 12 linhas
+ const lines=el.children;
+ while(lines.length>12)el.removeChild(lines[lines.length-1]);
+ console.log('[ble]',msg);
+}
+
async function pairBluetoothDevice(){
const backend=bleBackend();
- if(!backend){
- toast('Bluetooth indisponível neste dispositivo.');
- return;
- }
+ setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err');
+ if(!backend){toast('Bluetooth indisponível');return}
try{
let deviceId,deviceName;
if(backend==='capacitor'){
+ setBleDiag('Inicializando plugin nativo...');
await ensureBleNativeReady();
+ setBleDiag('Plugin OK · abrindo picker...');
const ble=window.Capacitor.Plugins.BluetoothLe;
const result=await ble.requestDevice({
services:[],
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
allowDuplicates:false,
});
- if(!result?.deviceId){return}
+ if(!result?.deviceId){setBleDiag('Picker cancelado','warn');return}
deviceId=result.deviceId;
deviceName=result.name||'Dispositivo BLE';
+ setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok');
}else{
+ setBleDiag('Abrindo picker do navegador...');
const device=await navigator.bluetooth.requestDevice({
acceptAllDevices:true,
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
@@ -5802,11 +5822,13 @@ async function pairBluetoothDevice(){
device.addEventListener('gattserverdisconnected',()=>{
const c=_bleConnections.get(deviceId);if(c)c.connected=false;
renderBluetoothCard();
+ setBleDiag('Disconnected: '+deviceName,'warn');
});
+ setBleDiag('Selecionado: '+deviceName,'ok');
}
- // Conecta + lê info inicial
+ setBleDiag('Conectando GATT...');
const info=await connectAndRead(deviceId,deviceName);
- // Salva no state
+ setBleDiag('Connect OK · battery='+(info.battery??'N/A')+' · mfr='+(info.manufacturer||'N/A'),info.battery!=null?'ok':'warn');
if(!state.btDevices)state.btDevices=[];
const existing=state.btDevices.find(d=>d.id===deviceId);
if(existing){
@@ -5821,28 +5843,44 @@ async function pairBluetoothDevice(){
}
saveState();
renderBluetoothCard();
- toast('✓ '+deviceName+' pareado'+(info.battery!=null?' · '+info.battery+'%':''));
+ toast('✓ '+deviceName+(info.battery!=null?' · '+info.battery+'%':' (sem leitura de bateria)'));
}catch(e){
- if(e.name==='NotFoundError'||/cancel/i.test(e.message||''))return;
+ if(e.name==='NotFoundError'||/cancel/i.test(e.message||'')){setBleDiag('Cancelado','warn');return}
+ const msg=e.message||e.errorMessage||JSON.stringify(e).slice(0,100)||'erro desconhecido';
+ setBleDiag('ERRO: '+msg,'err');
console.warn('[ble] pair failed',e);
- toast('Erro: '+(e.message||e.errorMessage||'pareamento falhou'));
+ toast('Falhou: '+msg);
}
}
async function connectAndRead(deviceId,deviceName){
- const info={battery:null,manufacturer:null,model:null};
+ const info={battery:null,manufacturer:null,model:null,services:[]};
const backend=bleBackend();
try{
if(backend==='capacitor'){
const ble=window.Capacitor.Plugins.BluetoothLe;
- await ble.connect({deviceId,timeout:15000});
+ try{
+ await ble.connect({deviceId,timeout:30000});
+ setBleDiag('GATT conectado','ok');
+ }catch(e){
+ setBleDiag('connect() falhou: '+(e.message||e.errorMessage||'?'),'err');
+ throw e;
+ }
const conn=_bleConnections.get(deviceId)||{};
conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true;
_bleConnections.set(deviceId,conn);
+ // Discover all services pra diagnóstico
+ try{
+ const r=await ble.getServices({deviceId});
+ const svcs=(r.services||r||[]).map(s=>s.uuid||s).slice(0,8);
+ setBleDiag('Serviços encontrados: '+svcs.length+' ('+svcs.map(u=>u.slice(4,8)).join(', ')+')','info');
+ info.services=svcs;
+ }catch(e){setBleDiag('getServices falhou: '+e.message,'warn')}
// Battery
try{
const r=await ble.read({deviceId,service:BLE_BATTERY_SERVICE,characteristic:BLE_BATTERY_CHAR});
info.battery=parseDataView(r.value).getUint8(0);
+ setBleDiag('Battery Service OK: '+info.battery+'%','ok');
try{
await ble.startNotifications({deviceId,service:BLE_BATTERY_SERVICE,characteristic:BLE_BATTERY_CHAR});
ble.addListener('notification|'+deviceId+'|'+BLE_BATTERY_SERVICE+'|'+BLE_BATTERY_CHAR,(ev)=>{
@@ -5850,16 +5888,19 @@ async function connectAndRead(deviceId,deviceName){
const dev=state.btDevices?.find(d=>d.id===deviceId);
if(dev){dev.lastBattery=newVal;dev.lastSeen=Date.now();saveState();renderBluetoothCard()}
});
- }catch(e){}
- }catch(e){}
+ setBleDiag('Notificações ativas','ok');
+ }catch(e){setBleDiag('startNotifications falhou: '+e.message,'warn')}
+ }catch(e){setBleDiag('Sem Battery Service padrão (BMS pode usar protocolo proprietário)','warn')}
// Device info
try{
const r=await ble.read({deviceId,service:BLE_DEVICE_INFO,characteristic:BLE_MANUFACTURER_CHAR});
info.manufacturer=new TextDecoder().decode(parseDataView(r.value));
+ setBleDiag('Fabricante: '+info.manufacturer,'info');
}catch(e){}
try{
const r=await ble.read({deviceId,service:BLE_DEVICE_INFO,characteristic:BLE_MODEL_CHAR});
info.model=new TextDecoder().decode(parseDataView(r.value));
+ setBleDiag('Modelo: '+info.model,'info');
}catch(e){}
}else{
const conn=_bleConnections.get(deviceId);
diff --git a/server/src/index.js b/server/src/index.js
index 03071ff..4b28ad2 100644
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -347,7 +347,7 @@ app.get('/.well-known/assetlinks.json', (req, res) => {
});
// Atalho: /apk redireciona pra última APK release no Forgejo
-const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.9.1/Shivao-v1.9.1.apk';
+const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.9.2/Shivao-v1.9.2.apk';
app.get('/apk', (req, res) => res.redirect(302, LATEST_APK_URL));
// Página A4 imprimível com QR Code + instruções (cola no barco/marina)