import { state } from './store.js'; import { temperatureFormat, nodesDisconnectedAge, BASE_PATH } from './config.js'; import moment from 'moment'; export const timeSpans = { '1d': {name: '1 Day', amount: 86400}, '3d': {name: '3 Days', amount: 259200}, '7d': {name: '1 Week', amount: 604800}, '14d': {name: '2 Weeks', amount: 1209600}, '30d': {name: '1 Month', amount: 2592000}, '90d': {name: '90 Days', amount: 7776000}, }; export function getTimeSpans() { return timeSpans; }; export function getTimeSpan(value) { return timeSpans[value]; }; // convert node id to a hex colour export function getNodeColor(nodeId) { return "#" + (nodeId & 0x00FFFFFF).toString(16).padStart(6, '0'); }; export function getNodeTextColor(nodeId) { // extract rgb components const r = (nodeId & 0xFF0000) >> 16; const g = (nodeId & 0x00FF00) >> 8; const b = nodeId & 0x0000FF; // calculate brightness const brightness = ((r * 0.299) + (g * 0.587) + (b * 0.114)) / 255; // determine text color based on brightness return brightness > 0.5 ? "#000000" : "#FFFFFF"; }; export function getRegionFrequencyRange(regionName) { // determine lora frequency range based on region_name // https://github.com/meshtastic/firmware/blob/a4c22321fca6fc8da7bab157c3812055603512ba/src/mesh/RadioInterface.cpp#L21 const regionNameToLoraFrequencyRange = { "US": "902-928 MHz", "EU_433": "433-434 MHz", "EU_868": "869.4-869.65 MHz", "CN": "470-510 MHz", "JP": "920.8-927.8 MHz", "ANZ": "915-928 MHz", "RU": "868.7-869.2 MHz", "KR": "920-923 MHz", "TW": "920-925 MHz", "IN": "865-867 MHz", "NZ_865": "864-868 MHz", "TH": "920-925 MHz", "UA_433": "433-434.7 MHz", "UA_868": "868-868.6 MHz", "MY_433": "433-435 MHz", "MY_919": "919-924 MHz", "SG_923": "917-925 MHz", "LORA_24": "2.4-2.4835 GHz", "UNSET": "902-928 MHz", } return regionNameToLoraFrequencyRange[regionName] ?? null; }; export function getShareLinkForNode(nodeId) { return window.location.origin + `/?node_id=${nodeId}`; }; export function copyShareLinkForNode(nodeId) { // make sure copy to clipboard is supported if (!navigator.clipboard || !navigator.clipboard.writeText) { alert("Clipboard not supported. Site must be served via https on iOS."); return; } // copy share link to clipboard const url = getShareLinkForNode(nodeId); navigator.clipboard.writeText(url); // tell user we copied it alert("Link copied to clipboard!"); }; export function getColorForSnr(snr) { if(snr >= 0) return "#16a34a"; // good if(snr < 0) return "#dc2626"; // bad }; export function getPositionPrecisionInMeters(positionPrecision) { switch (positionPrecision){ case 2: return 5976446; case 3: return 2988223; case 4: return 1494111; case 5: return 747055; case 6: return 373527; case 7: return 186763; case 8: return 93381; case 9: return 46690; case 10: return 23345; case 11: return 11672; // Android LOW_PRECISION case 12: return 5836; case 13: return 2918; case 14: return 1459; case 15: return 729; case 16: return 364; // Android MED_PRECISION case 17: return 182; case 18: return 91; case 19: return 45; case 20: return 22; case 21: return 11; case 22: return 5; case 23: return 2; case 24: return 1; case 32: return 0; // Android HIGH_PRECISION } return null; }; export function getTerrainProfileImage(node1, node2) { // line colour between nodes const lineColour = "0000FF"; // blue // node 1 (left side of image) const node1MarkerColour = "0000FF"; // blue const node1Latitude = node1.latitude; const node1Longitude = node1.longitude; const node1ElevationMSL = ""; // node1.altitude ?? ""; // node 2 (right side of image) const node2MarkerColour = "0000FF"; // blue const node2Latitude = node2.latitude; const node2Longitude = node2.longitude; const node2ElevationMSL = ""; // node2.altitude ?? ""; // generate terrain profile image url return "https://heywhatsthat.com/bin/profile-0904.cgi?" + new URLSearchParams({ src: "meshtastic.liamcottle.net", axes: 1, // include grid lines and a scale metric: 1, // show metric units curvature: 0, // don't include the curvature of the earth in the graphic width: 500, height: 200, pt0: `${node1Latitude},${node1Longitude},${lineColour},${node1ElevationMSL},${node1MarkerColour}`, pt1: `${node2Latitude},${node2Longitude},${lineColour},${node2ElevationMSL},${node2MarkerColour}`, }).toString(); }; export function formatPositionPrecision(positionPrecision) { // get position precision in meters const positionPrecisionInMeters = getPositionPrecisionInMeters(positionPrecision); if (positionPrecisionInMeters == null){ return "?"; } // format kilometers if (positionPrecisionInMeters > 1000){ const positionPrecisionInKilometers = Math.ceil(positionPrecisionInMeters / 1000); return `±${positionPrecisionInKilometers}km`; } // format meters return `±${positionPrecisionInMeters}m`; }; // escape strings for tooltips etc, to prevent html/script injection // not used in vuejs, as that auto escapes export function escapeString(string) { return string.replace(//g, ">"); }; export function isMobile() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); }; // returns true if the element or one of its parents has the class classname export function elementOrAnyAncestorHasClass(element, className) { // check if element contains class if (element.classList && element.classList.contains(className)) { return true; } // check if parent node has the class if (element.parentNode) { return elementOrAnyAncestorHasClass(element.parentNode, className); } // couldn't find the class return false; }; export function formatUptimeSeconds(secondsToFormat) { secondsToFormat = Number(secondsToFormat); const days = Math.floor(secondsToFormat / (3600 * 24)); const hours = Math.floor((secondsToFormat % (3600 * 24)) / 3600); const minutes = Math.floor((secondsToFormat % 3600) / 60); const seconds = Math.floor(secondsToFormat % 60); const daysPlural = days === 1 ? 'day' : 'days'; return `${days} ${daysPlural} ${hours}h ${minutes}m ${seconds}s`; }; export function celsiusToFahrenheit(celsius) { return (celsius * 9/5) + 32; }; /** operates on state */ // find node by id export function findNodeById(id) { const node = state.nodes.find((node) => node.node_id.toString() === id.toString()); if (node) return node; return null; }; // find node marker by id export function findNodeMarkerById(id) { return state.nodeMarkers[id] ?? null; }; /** operates on config */ export function formatTemperature(celsius) { switch (temperatureFormat.value) { case "celsius": { return `${Number(celsius).toFixed(0)}ºC`; } case "fahrenheit": { const fahrenheit = celsiusToFahrenheit(celsius); return `${fahrenheit.toFixed(0)}ºF`; } } } export function getTemperatureUnit() { switch (temperatureFormat.value) { case "celsius": return "ºC"; case "fahrenheit": return "ºF"; } }; export function buildPath(endpoint) { return `${BASE_PATH}${endpoint}` }; // determine if node was recently heard uplinking packets to mqtt export function hasNodeUplinkedToMqttRecently(node) { const now = moment(); const millisecondsSinceNodeLastUplinkedToMqtt = now.diff(moment(node.mqtt_connection_state_updated_at)); return millisecondsSinceNodeLastUplinkedToMqtt < nodesDisconnectedAge.value * 1000; }