diff --git a/.gitignore b/.gitignore
index 9f2444e..82ff6f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/node_modules/
matrix_storage.json
config.json
+/matrix-storage.json
diff --git a/index.js b/index.js
index 002ce86..365947f 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,7 @@
const mqtt = require('mqtt');
const { MatrixClient, SimpleFsStorageProvider, AutojoinRoomsMixin } = require('matrix-bot-sdk');
const config = require('./config.json');
+const rules = require('./rules.js');
// Matrix client
const storage = new SimpleFsStorageProvider("matrix-storage.json");
@@ -8,93 +9,15 @@ const matrix = new MatrixClient(config.matrix.homeserverUrl, config.matrix.acces
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} `;
+async function postMessage(msg) {
+ await matrix.sendMessage(config.matrix.roomId, {
+ msgtype: "m.text",
+ format: "org.matrix.custom.html",
+ formatted_body: msg,
+ body: msg,
+ });
}
-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,
@@ -114,30 +37,25 @@ mqttClient.on('connect', () => {
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),
- });
- }
+ // console.log(`MQTT message on ${topic}: ${data?.msg}`);
+
+ rules.forEach((r) => {
+ if (r.rule(data)) {
+ console.log(`${topic} aplying ${r.name}`);
+ postMessage(r.msg(data));
+ }
+ });
});
+
+
+// Simple filterโcustomize as needed
+/* if (data?.model?.includes('CQ')) {
+ * await postMessage(formatMessageCQ(data));
+
+ * } else if (data?.msg?.includes('Sharp-SPC775')) {
+ * const msg = `๐ก๏ธ${data.temperature_C} | ๐ง${data.humidity}`;
+ * await postMessage(msg);
+ * }
+ * else if (data?.msg?.includes('Cotech-367959')) {
+ * await postMessage(formatMessageCQWX(data));
+ * } */
diff --git a/rules.js b/rules.js
new file mode 100644
index 0000000..f6448b9
--- /dev/null
+++ b/rules.js
@@ -0,0 +1,141 @@
+// 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))
+ );
+}
+// Helper: emoji fr wind direction
+function getWindDirectionEmoji(degree) {
+ // Normalize degree to 0-359
+ const d = ((degree % 360) + 360) % 360;
+
+ // Define ranges and corresponding emoji
+ const directions = [
+ { min: 337.5, max: 360, emoji: 'โฌ๏ธ' }, // North -> Down (wind comes from North, goes South)
+ { min: 0, max: 22.5, emoji: 'โฌ๏ธ' }, // North
+ { min: 22.5, max: 67.5, emoji: 'โ๏ธ' }, // NE
+ { min: 67.5, max: 112.5, emoji: 'โฌ
๏ธ' }, // East -> Left
+ { min: 112.5, max: 157.5, emoji: 'โ๏ธ' }, // SE
+ { min: 157.5, max: 202.5, emoji: 'โฌ๏ธ' }, // South -> Up
+ { min: 202.5, max: 247.5, emoji: 'โ๏ธ' }, // SW
+ { min: 247.5, max: 292.5, emoji: 'โก๏ธ' }, // West -> Right
+ { min: 292.5, max: 337.5, emoji: 'โ๏ธ' } // NW
+ ];
+
+ for (const dir of directions) {
+ if (d >= dir.min && d < dir.max) {
+ return dir.emoji;
+ }
+ }
+
+ return 'โ'; // fallback
+}
+
+function formatMessageWX(data) {
+ const {
+ temperature_C,
+ temperature_F,
+ humidity,
+ rain_mm,
+ wind_dir_deg,
+ wind_avg_m_s,
+ wind_max_m_s,
+ light_lux,
+ uv,
+ mic,
+ mode,
+ model,
+ 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();
+ // Acurite-986 is giving celsius under _F
+ const temperature = temperature_C || temperature_F;
+
+ const response =
+ `${emoji.mic} reporting ${model}\n` +
+ (temperature !== undefined ?
+ `${emoji.temp} Temperature: ${temperature.toFixed(1)} ยฐC\n` : '') +
+ (humidity !== undefined ?
+ `${emoji.humidity} Humidity: ${humidity}%\n` : '') +
+ (rain_mm !==undefined ?
+ `${emoji.rain} Rainfall: ${rain_mm} mm\n` : '') +
+ (wind_dir_deg !==undefined ?
+ `${emoji.wind} Wind: ${wind_avg_m_s} m/s avg, ${wind_max_m_s}m/s max
+ ${dirEmoji} (${wind_dir_deg}ยฐ)\n` : '') +
+ (light_lux !== undefined ?
+ `${emoji.light} Light: ${light_lux} lux\n` : '') +
+ (uv !== undefined ?
+ `${emoji.uv} UV Index: ${uv}\n` : '') +
+ `${emoji.time} Time: ${readableTime}\n`;
+
+ console.log(`hh response`, response);
+ return response.trim();
+}
+
+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} `;
+}
+
+module.exports = [
+ {
+ name: "CQ",
+ rule: (data) => data?.msg?.includes('CQ'),
+ msg: formatMessageCQ,
+ },
+ {
+ name: "WX",
+ // Not to miss the zero degree
+ rule: (data) => data?.temperature_C !== undefined
+ || data?.temperature_F !== undefined,
+ msg: formatMessageWX,
+ },
+];