Moved to rules system
This commit is contained in:
parent
ff6296fd4c
commit
2ae2a675af
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
matrix_storage.json
|
matrix_storage.json
|
||||||
config.json
|
config.json
|
||||||
|
/matrix-storage.json
|
||||||
|
140
index.js
140
index.js
@ -1,6 +1,7 @@
|
|||||||
const mqtt = require('mqtt');
|
const mqtt = require('mqtt');
|
||||||
const { MatrixClient, SimpleFsStorageProvider, AutojoinRoomsMixin } = require('matrix-bot-sdk');
|
const { MatrixClient, SimpleFsStorageProvider, AutojoinRoomsMixin } = require('matrix-bot-sdk');
|
||||||
const config = require('./config.json');
|
const config = require('./config.json');
|
||||||
|
const rules = require('./rules.js');
|
||||||
|
|
||||||
// Matrix client
|
// Matrix client
|
||||||
const storage = new SimpleFsStorageProvider("matrix-storage.json");
|
const storage = new SimpleFsStorageProvider("matrix-storage.json");
|
||||||
@ -8,93 +9,15 @@ const matrix = new MatrixClient(config.matrix.homeserverUrl, config.matrix.acces
|
|||||||
AutojoinRoomsMixin.setupOnClient(matrix);
|
AutojoinRoomsMixin.setupOnClient(matrix);
|
||||||
matrix.start().then(() => console.log("Matrix bot started"));
|
matrix.start().then(() => console.log("Matrix bot started"));
|
||||||
|
|
||||||
function formatMessageCQ(data) {
|
async function postMessage(msg) {
|
||||||
const {
|
await matrix.sendMessage(config.matrix.roomId, {
|
||||||
callsign,
|
msgtype: "m.text",
|
||||||
msg,
|
format: "org.matrix.custom.html",
|
||||||
ccode,
|
formatted_body: msg,
|
||||||
country,
|
body: msg,
|
||||||
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} 📍<a href="${mapLink}">${locator}</a>
|
|
||||||
📶 ${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
|
// MQTT client
|
||||||
const mqttClient = mqtt.connect(config.mqtt.url, {
|
const mqttClient = mqtt.connect(config.mqtt.url, {
|
||||||
username: config.mqtt.username,
|
username: config.mqtt.username,
|
||||||
@ -114,30 +37,25 @@ mqttClient.on('connect', () => {
|
|||||||
|
|
||||||
mqttClient.on('message', async (topic, payload) => {
|
mqttClient.on('message', async (topic, payload) => {
|
||||||
const data = JSON.parse(payload.toString());
|
const data = JSON.parse(payload.toString());
|
||||||
console.log(`MQTT message on ${topic}: ${data?.msg}`);
|
// 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
|
// Simple filter—customize as needed
|
||||||
if (data?.model?.includes('CQ')) {
|
/* if (data?.model?.includes('CQ')) {
|
||||||
await matrix.sendMessage(config.matrix.roomId, {
|
* await postMessage(formatMessageCQ(data));
|
||||||
msgtype: "m.text",
|
|
||||||
format: "org.matrix.custom.html",
|
* } else if (data?.msg?.includes('Sharp-SPC775')) {
|
||||||
formatted_body: formatMessageCQ(data),
|
* const msg = `🌡️${data.temperature_C} | 💧${data.humidity}`;
|
||||||
body: formatMessageCQ(data),
|
* await postMessage(msg);
|
||||||
});
|
* }
|
||||||
} else if (data?.msg?.includes('Sharp-SPC775')) {
|
* else if (data?.msg?.includes('Cotech-367959')) {
|
||||||
const msg = `🌡️${data.temperature_C} | 💧${data.humidity}`;
|
* await postMessage(formatMessageCQWX(data));
|
||||||
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),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
141
rules.js
Normal file
141
rules.js
Normal file
@ -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} 📍<a href="${mapLink}">${locator}</a>
|
||||||
|
📶 ${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,
|
||||||
|
},
|
||||||
|
];
|
Loading…
Reference in New Issue
Block a user