-
-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Meshtastic (Bircom) (#303)
- Loading branch information
Showing
24 changed files
with
387 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { parse } from './meshbir'; | ||
|
||
describe('parse', () => { | ||
describe('position', () => { | ||
it('should translate message to points', () => { | ||
expect( | ||
parse([ | ||
{ | ||
altitude: 1778.12, | ||
ground_speed: 30, | ||
latitude: 32.1927, | ||
longitude: 76.4506, | ||
time: 123460, | ||
type: 'position', | ||
user_id: '12345678-1234-1234-1234-123456789012', | ||
}, | ||
{ | ||
altitude: 1778.12, | ||
ground_speed: 30, | ||
latitude: 32.1927, | ||
longitude: 76.4506, | ||
time: 123450, | ||
type: 'position', | ||
user_id: '12345678-1234-1234-1234-123456789012', | ||
}, | ||
]), | ||
).toMatchInlineSnapshot(` | ||
Map { | ||
"12345678-1234-1234-1234-123456789012" => [ | ||
{ | ||
"alt": 1778.12, | ||
"lat": 32.1927, | ||
"lon": 76.4506, | ||
"name": "meshbir", | ||
"speed": 30, | ||
"timeMs": 123460, | ||
}, | ||
{ | ||
"alt": 1778.12, | ||
"lat": 32.1927, | ||
"lon": 76.4506, | ||
"name": "meshbir", | ||
"speed": 30, | ||
"timeMs": 123450, | ||
}, | ||
], | ||
} | ||
`); | ||
}); | ||
}); | ||
|
||
describe('test', () => { | ||
it('should silently ignore messages', () => { | ||
expect( | ||
parse([ | ||
{ | ||
type: 'message', | ||
user_id: '12345678-1234-1234-1234-123456789012', | ||
time: 123456, | ||
message: 'hello Meshtastic', | ||
}, | ||
]), | ||
).toEqual(new Map()); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// https://bircom.in/ | ||
// https://github.com/vicb/flyXC/issues/301 | ||
|
||
import type { protos, TrackerNames } from '@flyxc/common'; | ||
import { Keys, removeBeforeFromLiveTrack, validateMeshBirAccount } from '@flyxc/common'; | ||
import type { MeshBirMessage } from '@flyxc/common-node'; | ||
import type { ChainableCommander, Redis } from 'ioredis'; | ||
|
||
import type { LivePoint } from './live-track'; | ||
import { makeLiveTrack } from './live-track'; | ||
import type { TrackerUpdates } from './tracker'; | ||
import { TrackerFetcher } from './tracker'; | ||
|
||
const KEEP_HISTORY_MIN = 20; | ||
|
||
export class MeshBirFetcher extends TrackerFetcher { | ||
constructor(state: protos.FetcherState, pipeline: ChainableCommander, protected redis: Redis) { | ||
super(state, pipeline); | ||
} | ||
|
||
protected getTrackerName(): TrackerNames { | ||
return 'meshbir'; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
protected async fetch(devices: number[], updates: TrackerUpdates, timeoutSec: number): Promise<void> { | ||
const messages = (await flushMessageQueue(this.redis)).filter((m) => m != null); | ||
|
||
if (messages.length == 0) { | ||
return; | ||
} | ||
|
||
// Maps meshbir ids to datastore ids. | ||
const meshIdToDsId = new Map<string, number>(); | ||
for (const dsId of devices) { | ||
const tracker = this.getTracker(dsId); | ||
if (tracker == null) { | ||
updates.trackerErrors.set(dsId, `Not found ${tracker.account}`); | ||
continue; | ||
} | ||
if (validateMeshBirAccount(tracker.account) === false) { | ||
updates.trackerErrors.set(dsId, `Invalid account ${tracker.account}`); | ||
continue; | ||
} | ||
meshIdToDsId.set(tracker.account, dsId); | ||
} | ||
|
||
const pointsByMeshId = parse(messages); | ||
|
||
for (const [meshId, points] of pointsByMeshId.entries()) { | ||
const dsId = meshIdToDsId.get(meshId); | ||
if (dsId != null) { | ||
const liveTrack = removeBeforeFromLiveTrack( | ||
makeLiveTrack(points), | ||
Math.round(Date.now() / 1000) - KEEP_HISTORY_MIN * 60, | ||
); | ||
if (liveTrack.timeSec.length > 0) { | ||
updates.trackerDeltas.set(dsId, liveTrack); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
protected shouldFetch(tracker: protos.Tracker) { | ||
return true; | ||
} | ||
} | ||
|
||
export function parse(messages: MeshBirMessage[]): Map<string, LivePoint[]> { | ||
const pointsByMeshId = new Map<string, LivePoint[]>(); | ||
for (const msg of messages) { | ||
if (msg.type == 'position') { | ||
const point: LivePoint = { | ||
lat: msg.latitude, | ||
lon: msg.longitude, | ||
alt: msg.altitude, | ||
speed: msg.ground_speed, | ||
timeMs: msg.time, | ||
name: 'meshbir', | ||
}; | ||
const meshId = validateMeshBirAccount(msg.user_id); | ||
if (meshId !== false) { | ||
const points = pointsByMeshId.get(meshId) ?? []; | ||
points.push(point); | ||
pointsByMeshId.set(meshId, points); | ||
} | ||
} | ||
} | ||
return pointsByMeshId; | ||
} | ||
|
||
// Returns and empty the message queue. | ||
async function flushMessageQueue(redis: Redis): Promise<MeshBirMessage[]> { | ||
try { | ||
const [[_, messages]] = await redis | ||
.multi() | ||
.lrange(Keys.meshBirMsgQueue, 0, -1) | ||
.ltrim(Keys.meshBirMsgQueue, 1, 0) | ||
.exec(); | ||
|
||
// Return older messages first | ||
return (messages as string[]).map((json) => JSON.parse(json)).reverse(); | ||
} catch (e) { | ||
console.error('Error reading meshbir queue', e); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -265,7 +265,18 @@ export class SettingsPage extends LitElement { | |
> | ||
</device-card> | ||
</ion-col> | ||
</ion-row> `, | ||
<ion-col size=12 size-lg=6> | ||
<device-card | ||
.tracker=${'meshbir'} | ||
.binder=${this.binder} | ||
label="UUID" | ||
.hint=${html`<ion-text class="ion-padding-horizontal ion-padding-top block"> | ||
Enter your Meshtastic UUID. It should look like "12345678-ab45-b23c-8549-1f3456c89e12". | ||
</ion-text>`} | ||
> | ||
</device-card> | ||
</ion-col> | ||
</ion-row>`, | ||
)} | ||
</ion-grid> | ||
</ion-content> | ||
|
@@ -489,6 +500,7 @@ export class SettingsPage extends LitElement { | |
<a href="https://www.glidernet.org/" target="_blank">OGN (Open Glider Network)</a> | ||
</li> | ||
<li><a href="https://live.xcontest.org/" target="_blank">XCTrack (XContest live)</a></li> | ||
<li><a href="https://bircom.in/" target="_blank">Meshtastic (Bircom)</a></li> | ||
</ul> | ||
<p> | ||
<a href="mailto:[email protected]?subject=flyXC%20registration%20error" target="_blank"> Contact us by email </a> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { parseMessage } from './meshbir'; | ||
|
||
describe('parseMessage', () => { | ||
it('should parse a position', () => { | ||
expect( | ||
parseMessage({ | ||
type: 'position', | ||
user_id: '12345678-1234-1234-1234-123456789012', | ||
time: 123456, | ||
latitude: 32.1927, | ||
longitude: 76.4506, | ||
altitude: 1778.12, | ||
ground_speed: 30, | ||
}), | ||
).toMatchInlineSnapshot(` | ||
{ | ||
"altitude": 1778.12, | ||
"ground_speed": 30, | ||
"latitude": 32.1927, | ||
"longitude": 76.4506, | ||
"time": 123456, | ||
"type": "position", | ||
"user_id": "12345678-1234-1234-1234-123456789012", | ||
} | ||
`); | ||
}); | ||
|
||
it('should parse a test', () => { | ||
expect( | ||
parseMessage({ | ||
type: 'message', | ||
user_id: '12345678-1234-1234-1234-123456789012', | ||
time: 123456, | ||
message: 'hello Meshtastic', | ||
}), | ||
).toMatchInlineSnapshot(` | ||
{ | ||
"message": "hello Meshtastic", | ||
"time": 123456, | ||
"type": "message", | ||
"user_id": "12345678-1234-1234-1234-123456789012", | ||
} | ||
`); | ||
}); | ||
|
||
it('should throw on invalid message', () => { | ||
expect(() => parseMessage({ type: 'unkown' })).toThrow(); | ||
}); | ||
}); |
Oops, something went wrong.