// 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, }, ];