const express = require('express'); const noble = require('@stoprocent/noble'); const app = express(); app.use(express.json()); // UUIDs em formato curto (noble lida melhor assim em muitas plataformas) const SERVICE_UUID = 'fff0'; const WRITE_CHAR_UUID = 'fff2'; let bleState = { connectedPeripheral: null, writeCharacteristic: null, isConnecting: false }; // Lógica idêntica ao calculateCRC do seu HTML function calculateCRC(dataArray) { return dataArray.reduce((acc, byte) => acc ^ byte, 0); } function buildPacket(type, commandHex, params = []) { const header = [0xA5, 0xAA, 0xAC]; const trailer = [0xC5, 0xCC, 0xCA]; const cmdType = type.charCodeAt(0); // 'W' vira 0x57 const cmd = parseInt(commandHex, 16); // Payload para o CRC: [Tipo, Comando, ...Parametros] const crcPayload = [cmdType, cmd, ...params]; const crc = calculateCRC(crcPayload); // Montagem final exatamente como no seu HTML return Buffer.from([ ...header, crc, ...crcPayload, ...trailer ]); } app.post('/connect', (req, res) => { if (bleState.isConnecting) return res.json({ status: "Já está a procurar..." }); console.log("Procurando por Smart.A5.WIFI..."); bleState.isConnecting = true; // Inicia o scan. O Noble no Windows/Linux prefere [] ou nomes vazios no startScanning noble.startScanning([], false); res.json({ status: "Scanning iniciado... Verifique o console do servidor." }); }); noble.on('discover', async (peripheral) => { const localName = peripheral.advertisement.localName; console.log("Encontrado dispositivo:", localName || "Sem Nome", "ID:", peripheral.id); if (localName === 'Smart.A5.WIFI') { console.log("✅ Smart.A5.WIFI encontrado! Ligando..."); noble.stopScanning(); try { await peripheral.connectAsync(); console.log("Ligado ao GATT!"); // No Noble, passamos os UUIDs sem os 0000 e sem traços const { characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync( [SERVICE_UUID], [WRITE_CHAR_UUID] ); if (characteristics.length > 0) { bleState.writeCharacteristic = characteristics[0]; bleState.connectedPeripheral = peripheral; bleState.isConnecting = false; console.log("✅ Tudo pronto! O 'Pires' já pode disparar o cheiro via Postman."); } } catch (err) { console.error("Erro na conexão:", err); bleState.isConnecting = false; } } }); app.post('/execute', async (req, res) => { const { type, command, params } = req.body; // Ex: { "type": "W", "command": "0x08", "params": [1] } if (!bleState.writeCharacteristic) { return res.status(400).json({ error: "Dispositivo não pronto. Execute /connect primeiro." }); } try { const packet = buildPacket(type, command, params || []); console.log("A enviar Pacote:", packet.toString('hex').toUpperCase()); // writeAsync(packet, false) -> false significa 'sem resposta' (Write Without Response) await bleState.writeCharacteristic.writeAsync(packet, false); res.json({ status: "Comando enviado", hex: packet.toString('hex').toUpperCase() }); } catch (err) { res.status(500).json({ error: err.message }); } }); app.listen(3000, () => console.log('Servidor Bluetooth a correr na porta 3000'));