// MQTT-адаптер: приём позиций от FLEX-сервера (navtelecom//state) + // состояние каналов Wiren Board (/devices//controls/). import mqtt from 'mqtt'; import { config } from '../config.js'; import { addPosition, upsertEntity, updateChannel } from '../store.js'; import { imeiOwner } from '../tenants.js'; const TELE_KEYS = ['pwr_ext', 'pwr_int', 'temp1', 'temp2', 'gsm', 'speed', 'sat', 'event_code']; export function startMqtt() { if (!config.mqtt) return false; const c = mqtt.connect(config.mqtt.url, { username: config.mqtt.username, password: config.mqtt.password, reconnectPeriod: 5000, }); c.on('connect', () => { console.log('[mqtt] подключён', config.mqtt.url); c.subscribe('navtelecom/+/state'); c.subscribe(`${config.mqtt.prefix}/+/controls/+`); }); c.on('message', (topic, buf) => { try { let m = topic.match(/^navtelecom\/([^/]+)\/state$/); if (m) { const imei = m[1]; const p = JSON.parse(buf.toString()); const lat = p.lat ?? p.latitude; const lon = p.lon ?? p.longitude; const telemetry = {}; TELE_KEYS.forEach((k) => p[k] != null && (telemetry[k] = Number(p[k]))); const id = `nt-${imei}`; const clientId = imeiOwner(imei) || 'unassigned'; upsertEntity({ id, clientId, name: `Трекер ${imei}`, type: 'transport' }); // тег арендатора по IMEI if (lat != null && lon != null) addPosition(id, Number(lat), Number(lon), telemetry); else upsertEntity({ id, online: true, telemetry }); return; } m = topic.match(new RegExp(`${config.mqtt.prefix}/([^/]+)/controls/([^/]+)$`)); if (m) { const v = buf.toString(); updateChannel(`${m[1]}/${m[2]}`, { on: v === '1' || v === 'true', ...(isNaN(+v) ? {} : { level: +v }) }); } } catch { /* malformed payload — ignore */ } }); c.on('error', (e) => console.error('[mqtt]', e.message)); return true; }