const mqtt = require('mqtt'); const { MatrixClient, SimpleFsStorageProvider, AutojoinRoomsMixin } = require('matrix-bot-sdk'); const config = require('./config.json'); // Matrix client const storage = new SimpleFsStorageProvider("matrix-storage.json"); const matrix = new MatrixClient(config.matrix.homeserverUrl, config.matrix.accessToken, storage); AutojoinRoomsMixin.setupOnClient(matrix); matrix.start().then(() => console.log("Matrix bot started")); function formatMessageCQ(data) { const { callsign, msg, ccode, country, mode, freq, db, dt, locator } = data; // Country flag emoji const emojiFlag = countryCodeToFlagEmoji(ccode); // Convert frequency Hz to MHz with 3 decimal digits const freqMHz = (freq / 1_000_000).toFixed(3); // RSSI visual bars const rssiBars = snrToBars(db); // Google Maps locator search const mapLink = `https://k7fry.com/grid/?qth=${locator}`; return `πŸ“‘ ${callsign} ${emojiFlag} πŸ“${locator} πŸ“Ά ${db} dB | ${dt} s | ${rssiBars} πŸ”Š ${freqMHz}MHz ${mode} `; } function formatMessageWX(data) { const { temperature_F, humidity, rain_mm, wind_dir_deg, wind_avg_m_s, wind_max_m_s, light_lux, uv, mic, mode, timestamp } = data; const emoji = { temp: "🌑️", humidity: "πŸ’§", rain: "🌧️", wind: "πŸƒ", light: "πŸ’‘", uv: "β˜€οΈ", mic: "πŸŽ™οΈ", time: "πŸ•’", mode: "βš™οΈ" }; const dirEmoji = getWindDirectionEmoji(wind_dir_deg); const readableTime = new Date(timestamp).toLocaleString(); return ` ${emoji.temp} Temperature: ${temperature_F.toFixed(1)} Β°C ${emoji.humidity} Humidity: ${humidity}% (may be out of range!) ${emoji.rain} Rainfall: ${rain_mm} mm ${emoji.wind} Wind: ${wind_avg_m_s} m/s avg, ${wind_max_m_s} m/s max ${dirEmoji} (${wind_dir_deg}Β°) ${emoji.light} Light: ${light_lux} lux ${emoji.uv} UV Index: ${uv} ${emoji.time} Time: ${readableTime} `.trim(); } // Helper: SNR (dB) to emoji "bar chart" function snrToBars(db) { const snr = Math.max(Math.min(db, 0), -30); // clamp -30 to 0 const levels = 5; const filled = Math.round(((snr + 30) / 30) * levels); return 'β–“'.repeat(filled) + 'β–‘'.repeat(levels - filled); } // Helper: ISO country code (e.g. "CA") β†’ πŸ‡¨πŸ‡¦ emoji function countryCodeToFlagEmoji(cc) { if (!cc) return ''; return cc.toUpperCase().replace(/./g, c => String.fromCodePoint(0x1f1e6 - 65 + c.charCodeAt(0)) ); } // MQTT client const mqttClient = mqtt.connect(config.mqtt.url, { username: config.mqtt.username, password: config.mqtt.password, clientId: 'mqtt-matrix-' + Math.random().toString(16).slice(2, 10), clean: true, connectTimeout: 4000, reconnectPeriod: 2000, }); mqttClient.on('connect', () => { console.log('MQTT connected'); mqttClient.subscribe(config.mqtt.topic, { qos: 0 }, err => { if (err) console.error('Subscribe error', err); }); }); mqttClient.on('message', async (topic, payload) => { const data = JSON.parse(payload.toString()); console.log(`MQTT message on ${topic}: ${data?.msg}`); // Simple filterβ€”customize as needed if (data?.model?.includes('CQ')) { await matrix.sendMessage(config.matrix.roomId, { msgtype: "m.text", format: "org.matrix.custom.html", formatted_body: formatMessageCQ(data), body: formatMessageCQ(data), }); } else if (data?.msg?.includes('Sharp-SPC775')) { const msg = `🌑️${data.temperature_C} | πŸ’§${data.humidity}`; await matrix.sendMessage(config.matrix.roomId, { msgtype: "m.text", format: "org.matrix.custom.html", formatted_body: msg, body: msg, }); } else if (data?.msg?.includes('Cotech-367959')) { await matrix.sendMessage(config.matrix.roomId, { msgtype: "m.text", format: "org.matrix.custom.html", formatted_body: formatMessageWX(data), body: formatMessageWX(data), }); } });