start work on lora ingest app
All checks were successful
Build Docker containers / Build (push) Successful in 16s

This commit is contained in:
2025-04-15 20:16:04 -04:00
parent 7b3e6e4fd1
commit 33726de0cb
6 changed files with 584 additions and 0 deletions

1
lora/.npmrc Normal file
View File

@ -0,0 +1 @@
@jsr:registry=https://npm.jsr.io

60
lora/LoraStream.js Normal file
View File

@ -0,0 +1,60 @@
import { Transform } from 'stream';
export class LoraStream extends Transform {
constructor(options) {
super(options);
this.byteBuffer = new Uint8Array([]);
this.textDecoder = new TextDecoder();
}
_transform(chunk, encoding, callback) {
this.byteBuffer = new Uint8Array([
...this.byteBuffer,
...chunk,
]);
let processingExhausted = false;
while (this.byteBuffer.length !== 0 && !processingExhausted) {
const framingIndex = this.byteBuffer.findIndex((byte) => byte === 0x94);
const framingByte2 = this.byteBuffer[framingIndex + 1];
if (framingByte2 === 0xc3) {
if (this.byteBuffer.subarray(0, framingIndex).length) {
this.byteBuffer = this.byteBuffer.subarray(framingIndex);
}
const msb = this.byteBuffer[2];
const lsb = this.byteBuffer[3];
if (msb !== undefined && lsb !== undefined && this.byteBuffer.length >= 4 + (msb << 8) + lsb) {
const packet = this.byteBuffer.subarray(4, 4 + (msb << 8) + lsb);
const malformedDetectorIndex = packet.findIndex(
(byte) => byte === 0x94,
);
if (malformedDetectorIndex !== -1 && packet[malformedDetectorIndex + 1] === 0xc3) {
// malformed
this.byteBuffer = this.byteBuffer.subarray(malformedDetectorIndex);
} else {
this.byteBuffer = this.byteBuffer.subarray(3 + (msb << 8) + lsb + 1);
this.push(packet);
}
} else {
/** Only partioal message in buffer, wait for the rest */
processingExhausted = true;
}
} else {
/** Message not complete, only 1 byte in buffer */
processingExhausted = true;
}
}
callback();
}
_flush(callback) {
try {
if (this._buffer) {
this.push(this._buffer.trim());
}
callback();
} catch (err) {
callback(err);
}
}
}

61
lora/MeshtasticStream.js Normal file
View File

@ -0,0 +1,61 @@
import { Transform } from 'stream';
import { Mesh, Channel, Config, ModuleConfig } from '@meshtastic/protobufs';
import { fromBinary, create } from '@bufbuild/protobuf';
export class MeshtasticStream extends Transform {
constructor(options) {
super({ readableObjectMode: true, ...options });
}
_transform(chunk, encoding, callback) {
const dataPacket = fromBinary(Mesh.FromRadioSchema, chunk);
let schema = null;
switch(dataPacket.payloadVariant.case) {
case 'packet':
schema = Mesh.MeshPacketSchema;
break;
case 'nodeInfo':
schema = Mesh.NodeInfoSchema;
break;
case 'myInfo':
schema = Mesh.MyNodeInfoSchema;
break;
case 'deviceuiConfig':
// can't find, come back to this
break;
case 'config':
// todo: config has another payloadVariant within it
schema = Config.ConfigSchema
break;
case 'moduleConfig':
// todo: config has another payloadVariant within it
schema = ModuleConfig.ModuleConfigSchema
break;
case 'fileInfo':
schema = Mesh.FileInfoSchema
break;
case 'channel':
schema = Channel.ChannelSchema
break;
case 'metadata':
schema = Mesh.DeviceMetadataSchema
break;
case 'configCompleteId':
// done sending init data
break;
}
if (schema !== null) {
this.push(create(schema, dataPacket.payloadVariant.value));
}
callback();
}
_flush(callback) {
try {
if (this._buffer) {
this.push(this._buffer.trim());
}
callback();
} catch (err) {
callback(err);
}
}
}

103
lora/index.js Normal file
View File

@ -0,0 +1,103 @@
const { Mesh, Mqtt, Portnums, Telemetry, Config, Channel } = require("@meshtastic/protobufs");
const { fromBinary, toBinary, create } = require("@bufbuild/protobuf");
const { LoraStream } = require("./LoraStream");
const { MeshtasticStream } = require("./MeshtasticStream");
const net = require('net');
const client = new net.Socket();
const IP = '192.168.10.117';
const PORT = 4403;
function sendHello() {
const data = create(Mesh.ToRadioSchema, {
payloadVariant: {
case: "wantConfigId",
value: 1,
}
});
sendToDevice(toBinary(Mesh.ToRadioSchema, data));
}
function sendToDevice(data) {
const bufferLength = data.length;
const header = new Uint8Array([
0x94,
0xC3,
(bufferLength >> 8) & 0xFF,
bufferLength & 0xFF,
]);
data = new Uint8Array([...header, ...data]);
client.write(data);
}
client.connect(PORT, IP, function() {
console.log('Connected');
sendHello();
});
const meshtasticStream = new MeshtasticStream();
client.pipe(new LoraStream()).pipe(meshtasticStream);
meshtasticStream.on('data', (data) => {
parseMeshtastic(data['$typeName'], data);
})
function parseMeshtastic(typeName, data) {
switch(typeName) {
case Mesh.MeshPacketSchema.typeName:
onMeshPacket(data);
break;
case Mesh.NodeInfoSchema.typeName:
console.log('node info');
break;
}
}
function onMeshPacket(envelope) {
const payloadVariant = envelope.payloadVariant.case;
if (payloadVariant === 'encrypted') {
// attempt decryption
}
const dataPacket = envelope.payloadVariant.value;
const portNum = dataPacket.portnum;
if (!portNum) {
return;
}
let schema = null;
switch (portNum) {
case Portnums.PortNum.POSITION_APP:
schema = Mesh.PositionSchema;
break;
case Portnums.PortNum.TELEMETRY_APP:
schema = Telemetry.TelemetrySchema;
break;
case Portnums.PortNum.TEXT_MESSAGE_APP:
// no schema?
break;
case Portnums.PortNum.WAYPOINT_APP:
schema = Mesh.WaypointSchema;
break;
case Portnums.PortNum.TRACEROUTE_APP:
schema = Mesh.RouteDiscoverySchema;
break;
case Portnums.PortNum.NODEINFO_APP:
schema = Mesh.NodeInfoSchema;
break;
case Portnums.PortNum.NEIGHBORINFO_APP:
schema = Mesh.NeighborInfoSchema;
break;
case Portnums.PortNum.MAP_REPORT_APP:
schema = Mqtt.MapReportSchema;
break;
}
if (schema !== null) {
console.log(dataPacket);
const decodedData = fromBinary(schema, dataPacket.payload);
console.log(decodedData);
}
}

341
lora/package-lock.json generated Normal file
View File

@ -0,0 +1,341 @@
{
"name": "lora",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lora",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@bufbuild/protobuf": "^2.2.5",
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.6.2",
"@prisma/client": "^5.11.0",
"command-line-args": "^5.2.1",
"command-line-usage": "^7.0.1"
},
"devDependencies": {
"prisma": "^5.10.2"
}
},
"node_modules/@bufbuild/protobuf": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.5.tgz",
"integrity": "sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@meshtastic/protobufs": {
"name": "@jsr/meshtastic__protobufs",
"version": "2.6.2",
"resolved": "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.6.2.tgz",
"integrity": "sha512-bIENtFnUEru28GrAeSdiBS9skp0hN/3HZunMbF/IjvUrXOlx2fptKVj3b+pzjOWnLBZxllrByV/W+XDmrxqJ6g==",
"dependencies": {
"@bufbuild/protobuf": "^2.2.3"
}
},
"node_modules/@prisma/client": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
"node": ">=16.13"
},
"peerDependencies": {
"prisma": "*"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
}
}
},
"node_modules/@prisma/debug": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0",
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"@prisma/fetch-engine": "5.22.0",
"@prisma/get-platform": "5.22.0"
}
},
"node_modules/@prisma/engines-version": {
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0",
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"@prisma/get-platform": "5.22.0"
}
},
"node_modules/@prisma/get-platform": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/array-back": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
"integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chalk-template": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/chalk-template?sponsor=1"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/command-line-args": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz",
"integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==",
"license": "MIT",
"dependencies": {
"array-back": "^3.1.0",
"find-replace": "^3.0.0",
"lodash.camelcase": "^4.3.0",
"typical": "^4.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/command-line-usage": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz",
"integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==",
"license": "MIT",
"dependencies": {
"array-back": "^6.2.2",
"chalk-template": "^0.4.0",
"table-layout": "^4.1.0",
"typical": "^7.1.1"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/command-line-usage/node_modules/array-back": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz",
"integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
"license": "MIT",
"engines": {
"node": ">=12.17"
}
},
"node_modules/command-line-usage/node_modules/typical": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz",
"integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==",
"license": "MIT",
"engines": {
"node": ">=12.17"
}
},
"node_modules/find-replace": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
"integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
"license": "MIT",
"dependencies": {
"array-back": "^3.0.1"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"license": "MIT"
},
"node_modules/prisma": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/engines": "5.22.0"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": ">=16.13"
},
"optionalDependencies": {
"fsevents": "2.3.3"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/table-layout": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz",
"integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
"license": "MIT",
"dependencies": {
"array-back": "^6.2.2",
"wordwrapjs": "^5.1.0"
},
"engines": {
"node": ">=12.17"
}
},
"node_modules/table-layout/node_modules/array-back": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz",
"integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
"license": "MIT",
"engines": {
"node": ">=12.17"
}
},
"node_modules/typical": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
"integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wordwrapjs": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.0.tgz",
"integrity": "sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==",
"license": "MIT",
"engines": {
"node": ">=12.17"
}
}
}
}

18
lora/package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "lora",
"version": "1.0.0",
"main": "index.js",
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@bufbuild/protobuf": "^2.2.5",
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.6.2",
"@prisma/client": "^5.11.0",
"command-line-args": "^5.2.1",
"command-line-usage": "^7.0.1"
},
"devDependencies": {
"prisma": "^5.10.2"
}
}