Compare commits
5 Commits
4ed100b97e
...
e14904e59b
Author | SHA1 | Date |
---|---|---|
Adam Goldsmith | e14904e59b | |
Adam Goldsmith | 2feabd4d87 | |
Adam Goldsmith | 6c015c322c | |
Adam Goldsmith | 9426e2ead6 | |
Adam Goldsmith | 269cdb65f1 |
|
@ -7,6 +7,7 @@
|
|||
"aprs-parser": "github:ad1217/npm-aprs-parser#no-dynamic-require",
|
||||
"distinct-colors": "^1.0.4",
|
||||
"ol": "^6.15.1",
|
||||
"parse-duration": "^1.1.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue3-openlayers": "^0.1.75"
|
||||
},
|
||||
|
|
|
@ -14,6 +14,9 @@ dependencies:
|
|||
ol:
|
||||
specifier: ^6.15.1
|
||||
version: 6.15.1
|
||||
parse-duration:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
vue:
|
||||
specifier: ^3.3.4
|
||||
version: 3.3.4
|
||||
|
@ -720,6 +723,10 @@ packages:
|
|||
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
|
||||
dev: false
|
||||
|
||||
/parse-duration@1.1.0:
|
||||
resolution: {integrity: sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==}
|
||||
dev: false
|
||||
|
||||
/parse-headers@2.0.5:
|
||||
resolution: {integrity: sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==}
|
||||
dev: false
|
||||
|
|
|
@ -54,5 +54,6 @@ wss.on('connection', (ws) => {
|
|||
.split('\n')
|
||||
.filter((line) => line !== '')
|
||||
.forEach((line) => ws.send(line));
|
||||
ws.send("FINISHED REPLAY");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<tr :class="{ timedOut, lowVoltage, neverHeard: !stationStatus.lastHeard }">
|
||||
<tr :class="{ timedOut, isLowVoltage, neverHeard: !stationStatus.lastHeard }">
|
||||
<td :title="callsign">{{ tacticalAndOrCall }}</td>
|
||||
<template v-if="stationStatus.lastHeard">
|
||||
<td>{{ formatTime(stationStatus.lastHeard) }}</td>
|
||||
|
@ -24,17 +24,22 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import config from './status_config.yaml';
|
||||
import parseDuration from 'parse-duration';
|
||||
|
||||
const props = defineProps({
|
||||
callsign: String,
|
||||
tactical: String,
|
||||
timeoutLength: String,
|
||||
lowVoltage: Number,
|
||||
finishedReplay: Boolean,
|
||||
messages: Array,
|
||||
now: Date,
|
||||
});
|
||||
|
||||
function notify(title, body) {
|
||||
return new Notification(title, { body: body, requireInteraction: true });
|
||||
if (props.finishedReplay) {
|
||||
return new Notification(title, { body: body, requireInteraction: true });
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(time, isDuration = false) {
|
||||
|
@ -44,6 +49,10 @@ function formatTime(time, isDuration = false) {
|
|||
);
|
||||
}
|
||||
|
||||
const timeoutLengthMs = computed(() => {
|
||||
return parseDuration(props.timeoutLength);
|
||||
});
|
||||
|
||||
function prettyDuration(duration) {
|
||||
let date = new Date(duration);
|
||||
return [
|
||||
|
@ -70,15 +79,16 @@ const timedOut = computed(() => {
|
|||
return false;
|
||||
} else {
|
||||
return (
|
||||
props.now.getTime() - stationStatus.value.lastHeard > config.timeoutLength
|
||||
props.now.getTime() - stationStatus.value.lastHeard >
|
||||
timeoutLengthMs.value
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const lowVoltage = computed(() => {
|
||||
const isLowVoltage = computed(() => {
|
||||
return (
|
||||
stationStatus.value.lastVoltage &&
|
||||
stationStatus.value.lastVoltage < config.lowVoltage
|
||||
stationStatus.value.lastVoltage < props.lowVoltage
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -114,28 +124,25 @@ const stationStatus = computed(() => {
|
|||
return status;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.lowVoltage,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
notify(
|
||||
`${tacticalAndOrCall}'s battery has dropepd below ${config.lowVoltage}V`,
|
||||
`Voltage: ${stationStatus.value.lastVoltage}`
|
||||
);
|
||||
}
|
||||
watch(isLowVoltage, (newVal) => {
|
||||
if (newVal) {
|
||||
notify(
|
||||
`${tacticalAndOrCall}'s battery has dropepd below ${props.lowVoltage}V`,
|
||||
`Voltage: ${stationStatus.value.lastVoltage}`
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
watch(timedOut, (newVal) => {
|
||||
if (newVal) {
|
||||
notify(
|
||||
`${tacticalAndOrCall.value} has not been heard for over ${prettyDuration(
|
||||
config.timeoutLength
|
||||
timeoutLengthMs.value
|
||||
)}!`,
|
||||
`Last Heard: ${formatTime(
|
||||
stationStatus.value.lastHeard
|
||||
)} (${prettyDuration(
|
||||
props.now.value - stationStatus.value.lastHeard
|
||||
props.now.getTime() - stationStatus.value.lastHeard
|
||||
)} ago!)`
|
||||
);
|
||||
}
|
||||
|
@ -147,7 +154,7 @@ tr.timedOut {
|
|||
background-color: red;
|
||||
}
|
||||
|
||||
tr.lowVoltage {
|
||||
tr.isLowVoltage {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,11 @@
|
|||
</tr>
|
||||
|
||||
<StationRow
|
||||
v-for="(tactical, callsign) in trackedStations"
|
||||
v-for="(stationProps, callsign) in trackedStations"
|
||||
:key="callsign"
|
||||
:callsign="callsign"
|
||||
:tactical="tactical"
|
||||
v-bind="{ ...config.default, ...stationProps }"
|
||||
:finishedReplay="finishedReplay"
|
||||
:messages="messagesFromStation[callsign] || []"
|
||||
:now="now"
|
||||
>
|
||||
|
@ -42,12 +43,29 @@ import config from './status_config.yaml';
|
|||
|
||||
const parser = new APRSParser();
|
||||
let aprsStream = null;
|
||||
const finishedReplay = ref(false);
|
||||
const messages = ref([]);
|
||||
const messagesFromStation = ref({});
|
||||
const now = ref(new Date());
|
||||
const trackedStations = ref(config.trackedStations);
|
||||
const trackedStations = ref(normalizeConfigStations());
|
||||
const canNotify = ref(Notification.permission === 'granted');
|
||||
|
||||
function normalizeConfigStations() {
|
||||
return [...Object.entries(config.trackedStations)]
|
||||
.map(([callsign, tacticalOrProps]) => {
|
||||
if (typeof tacticalOrProps === 'string') {
|
||||
return [callsign, { tactical: tacticalOrProps }];
|
||||
} else {
|
||||
return [callsign, tacticalOrProps];
|
||||
}
|
||||
})
|
||||
.reduce((acc, [callsign, props]) => {
|
||||
console.log(callsign, props);
|
||||
acc[callsign] = props;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Connect to websocket aprs stream
|
||||
connectToStream();
|
||||
|
@ -66,7 +84,9 @@ function connectToStream() {
|
|||
}, 5000);
|
||||
};
|
||||
aprsStream.onmessage = (event) => {
|
||||
if (event.data !== '') {
|
||||
if (event.data === 'FINISHED REPLAY') {
|
||||
finishedReplay.value = true;
|
||||
} else if (event.data !== '') {
|
||||
handleMessage(JSON.parse(event.data));
|
||||
}
|
||||
};
|
||||
|
@ -96,7 +116,7 @@ function handleMessage(packet) {
|
|||
message.data.text.split(';').map((tac_assoc) => {
|
||||
let [call, tac] = tac_assoc.split('=', 2);
|
||||
if (tac) {
|
||||
trackedStations.value[call] = tac;
|
||||
trackedStations.value[call].tactical = tac;
|
||||
} else {
|
||||
delete trackedStations.value[call];
|
||||
}
|
||||
|
|
|
@ -1,18 +1,31 @@
|
|||
timeoutLength: 600000 # 10 * 60 * 1000 = 10 minutes
|
||||
lowVoltage: 11.9
|
||||
|
||||
TACTICAL_whitelist:
|
||||
- KC1GDW-7
|
||||
- W1FN
|
||||
|
||||
default:
|
||||
timeoutLength: 10 minutes
|
||||
lowVoltage: 11.9
|
||||
|
||||
trackedStations:
|
||||
# Digis/iGates
|
||||
W1FN-1: Moose Mt
|
||||
W1FN-5: Hanover
|
||||
W1FN-9: Bath
|
||||
N1GMC-1: Dame Hill Rd, Orford
|
||||
N1GMC-2: Sunday Mt, Orford
|
||||
KB1FDA-1: Corinth
|
||||
W1FN-1:
|
||||
tactical: Moose Mt
|
||||
timeoutLength: 25 minutes
|
||||
W1FN-5:
|
||||
tactical: Hanover
|
||||
timeoutLength: 25 minutes
|
||||
W1FN-9:
|
||||
tactical: Bath
|
||||
timeoutLength: 25 minutes
|
||||
N1GMC-1:
|
||||
tactical: Dame Hill Rd, Orford
|
||||
timeoutLength: 25 minutes
|
||||
N1GMC-2:
|
||||
tactical: Sunday Mt, Orford
|
||||
timeoutLength: 25 minutes
|
||||
KB1FDA-1:
|
||||
tactical: Corinth
|
||||
timeoutLength: 25 minutes
|
||||
|
||||
# Vehicles
|
||||
K1EHZ-10: Recovery
|
||||
|
|
Loading…
Reference in New Issue