Initial 1.3.x rework
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
import { connType } from '@core/slices/appSlice';
|
||||
import {
|
||||
addChannel,
|
||||
addChat,
|
||||
addLogEvent,
|
||||
addMessage,
|
||||
addNode,
|
||||
addPosition,
|
||||
addUser,
|
||||
resetState,
|
||||
setConfig,
|
||||
setDeviceStatus,
|
||||
setLastMeshInterraction,
|
||||
setModuleConfig,
|
||||
setMyNodeInfo,
|
||||
setReady,
|
||||
updateLastInteraction,
|
||||
} from '@core/slices/meshtasticSlice';
|
||||
import { store } from '@core/store';
|
||||
import {
|
||||
IBLEConnection,
|
||||
IHTTPConnection,
|
||||
ISerialConnection,
|
||||
Protobuf,
|
||||
SettingsManager,
|
||||
Types,
|
||||
} from '@meshtastic/meshtasticjs';
|
||||
|
||||
type connectionType = IBLEConnection | IHTTPConnection | ISerialConnection;
|
||||
|
||||
export let connection: connectionType = new IHTTPConnection();
|
||||
|
||||
const appState = store.getState().app;
|
||||
|
||||
export const connectionUrl = appState.connectionParams.HTTP.address;
|
||||
|
||||
export const setConnection = async (conn: connType): Promise<void> => {
|
||||
await connection.disconnect();
|
||||
cleanupListeners();
|
||||
switch (conn) {
|
||||
case connType.HTTP:
|
||||
connection = new IHTTPConnection();
|
||||
break;
|
||||
case connType.BLE:
|
||||
connection = new IBLEConnection();
|
||||
break;
|
||||
case connType.SERIAL:
|
||||
connection = new ISerialConnection();
|
||||
break;
|
||||
}
|
||||
registerListeners();
|
||||
const connectionParams = store.getState().app.connectionParams;
|
||||
console.log(connectionParams);
|
||||
|
||||
switch (conn) {
|
||||
case connType.HTTP:
|
||||
await connection.connect(connectionParams.HTTP);
|
||||
break;
|
||||
case connType.BLE:
|
||||
await connection.connect(
|
||||
// @ts-ignore tmp
|
||||
connectionParams.BLE,
|
||||
);
|
||||
break;
|
||||
case connType.SERIAL:
|
||||
await connection.connect(
|
||||
// @ts-ignore tmp
|
||||
connectionParams.SERIAL,
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export const cleanupListeners = (): void => {
|
||||
connection.onMeshPacket.cancelAll();
|
||||
connection.onDeviceStatus.cancelAll();
|
||||
connection.onMyNodeInfo.cancelAll();
|
||||
connection.onUserPacket.cancelAll();
|
||||
connection.onPositionPacket.cancelAll();
|
||||
connection.onNodeInfoPacket.cancelAll();
|
||||
connection.onAdminPacket.cancelAll();
|
||||
connection.onMeshHeartbeat.cancelAll();
|
||||
connection.onTextPacket.cancelAll();
|
||||
};
|
||||
|
||||
const registerListeners = (): void => {
|
||||
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
|
||||
|
||||
connection.onLogEvent.subscribe((log) => {
|
||||
store.dispatch(addLogEvent(log));
|
||||
});
|
||||
|
||||
connection.onDeviceStatus.subscribe((status) => {
|
||||
store.dispatch(setDeviceStatus(status));
|
||||
|
||||
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
|
||||
store.dispatch(setReady(true));
|
||||
void connection.getConfig(Protobuf.AdminMessage_ConfigType.DEVICE_CONFIG);
|
||||
void connection.getConfig(Protobuf.AdminMessage_ConfigType.WIFI_CONFIG);
|
||||
void connection.getConfig(
|
||||
Protobuf.AdminMessage_ConfigType.POSITION_CONFIG,
|
||||
);
|
||||
void connection.getConfig(
|
||||
Protobuf.AdminMessage_ConfigType.DISPLAY_CONFIG,
|
||||
);
|
||||
void connection.getConfig(Protobuf.AdminMessage_ConfigType.LORA_CONFIG);
|
||||
void connection.getConfig(Protobuf.AdminMessage_ConfigType.POWER_CONFIG);
|
||||
}
|
||||
if (status === Types.DeviceStatusEnum.DEVICE_DISCONNECTED) {
|
||||
store.dispatch(setReady(false));
|
||||
store.dispatch(resetState());
|
||||
cleanupListeners();
|
||||
}
|
||||
});
|
||||
|
||||
connection.onMyNodeInfo.subscribe((nodeInfo) => {
|
||||
store.dispatch(setMyNodeInfo(nodeInfo));
|
||||
});
|
||||
|
||||
connection.onUserPacket.subscribe((user) => {
|
||||
store.dispatch(addUser(user));
|
||||
});
|
||||
|
||||
connection.onPositionPacket.subscribe((position) => {
|
||||
store.dispatch(addPosition(position));
|
||||
});
|
||||
|
||||
connection.onNodeInfoPacket.subscribe(
|
||||
(nodeInfoPacket): void | { payload: Protobuf.NodeInfo; type: string } => {
|
||||
store.dispatch(addNode(nodeInfoPacket.data));
|
||||
store.dispatch(addChat(nodeInfoPacket.data.num));
|
||||
},
|
||||
);
|
||||
|
||||
connection.onAdminPacket.subscribe((adminPacket) => {
|
||||
console.log(adminPacket.data.variant.oneofKind);
|
||||
|
||||
switch (adminPacket.data.variant.oneofKind) {
|
||||
case 'getChannelResponse':
|
||||
store.dispatch(addChannel(adminPacket.data.variant.getChannelResponse));
|
||||
store.dispatch(
|
||||
addChat(adminPacket.data.variant.getChannelResponse.index),
|
||||
);
|
||||
break;
|
||||
case 'getOwnerResponse':
|
||||
store.dispatch(
|
||||
addUser({
|
||||
data: adminPacket.data.variant.getOwnerResponse,
|
||||
packet: adminPacket.packet,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case 'getConfigResponse':
|
||||
store.dispatch(setConfig(adminPacket.data.variant.getConfigResponse));
|
||||
break;
|
||||
case 'getModuleConfigResponse':
|
||||
store.dispatch(
|
||||
setModuleConfig(adminPacket.data.variant.getModuleConfigResponse),
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
connection.onMeshHeartbeat.subscribe(
|
||||
(date): void | { payload: number; type: string } =>
|
||||
store.dispatch(setLastMeshInterraction(date.getTime())),
|
||||
);
|
||||
|
||||
connection.onRoutingPacket.subscribe((routingPacket) => {
|
||||
console.log(routingPacket.data.variant.oneofKind);
|
||||
|
||||
switch (routingPacket.data.variant.oneofKind) {
|
||||
case 'errorReason':
|
||||
console.log(
|
||||
Protobuf.Routing_Error[routingPacket.data.variant.errorReason],
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
updateLastInteraction({
|
||||
id: routingPacket.packet.from,
|
||||
time: new Date(routingPacket.packet.rxTime * 1000),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
connection.onTextPacket.subscribe((message) => {
|
||||
const myNodeNum = store.getState().meshtastic.radio.hardware.myNodeNum;
|
||||
|
||||
store.dispatch(
|
||||
addMessage({
|
||||
message: message,
|
||||
ack: message.packet.from !== myNodeNum,
|
||||
received: message.packet.rxTime
|
||||
? new Date(message.packet.rxTime * 1000)
|
||||
: new Date(),
|
||||
}),
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { Style } from 'mapbox-gl';
|
||||
|
||||
export type MapStyleName =
|
||||
| 'Streets'
|
||||
| 'Outdoors'
|
||||
| 'Light'
|
||||
| 'Dark'
|
||||
| 'Satellite';
|
||||
|
||||
export interface MapStyle {
|
||||
title: MapStyleName;
|
||||
data: Style | string;
|
||||
}
|
||||
|
||||
type MapStyleType = {
|
||||
[mapStyleType in MapStyleName]: MapStyle;
|
||||
};
|
||||
|
||||
export const MapStyles: MapStyleType = {
|
||||
Streets: {
|
||||
title: 'Streets',
|
||||
data: 'mapbox://styles/mapbox/streets-v11?optimize=true',
|
||||
} as MapStyle,
|
||||
Outdoors: {
|
||||
title: 'Outdoors',
|
||||
data: 'mapbox://styles/mapbox/outdoors-v11?optimize=true',
|
||||
} as MapStyle,
|
||||
|
||||
Light: {
|
||||
title: 'Light',
|
||||
data: 'mapbox://styles/sachaw/cl03ij03g001414l20w0w0ivj?optimize=true',
|
||||
} as MapStyle,
|
||||
Dark: {
|
||||
title: 'Dark',
|
||||
data: 'mapbox://styles/sachaw/ckwzwm92e1oep14pjunjqlqbo?optimize=true',
|
||||
} as MapStyle,
|
||||
Satellite: {
|
||||
title: 'Satellite',
|
||||
|
||||
data: {
|
||||
version: 8,
|
||||
layers: [
|
||||
{
|
||||
id: 'esri',
|
||||
type: 'raster',
|
||||
source: 'esri',
|
||||
},
|
||||
],
|
||||
sources: {
|
||||
esri: {
|
||||
type: 'raster',
|
||||
tiles: [
|
||||
'https://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||
],
|
||||
maxzoom: 17,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as MapStyle,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import { createRouter, defineRoute } from 'type-route';
|
||||
|
||||
export const { RouteProvider, useRoute, routes } = createRouter({
|
||||
messages: defineRoute('/'),
|
||||
map: defineRoute('/map'),
|
||||
extensions: defineRoute('/extensions'),
|
||||
});
|
||||
@@ -1,103 +0,0 @@
|
||||
import type { Types } from '@meshtastic/meshtasticjs';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export type currentPageName = 'messages' | 'settings';
|
||||
|
||||
export enum connType {
|
||||
HTTP,
|
||||
BLE,
|
||||
SERIAL,
|
||||
}
|
||||
|
||||
interface AppState {
|
||||
mobileNavOpen: boolean;
|
||||
navCollapsed: boolean;
|
||||
connectionModalOpen: boolean;
|
||||
darkMode: boolean;
|
||||
currentPage: currentPageName;
|
||||
connType: connType;
|
||||
connectionParams: {
|
||||
BLE: Types.BLEConnectionParameters;
|
||||
HTTP: Types.HTTPConnectionParameters;
|
||||
SERIAL: Types.SerialConnectionParameters;
|
||||
};
|
||||
updateAvaliable: boolean;
|
||||
}
|
||||
|
||||
const initialState: AppState = {
|
||||
mobileNavOpen: false,
|
||||
navCollapsed: false,
|
||||
connectionModalOpen: true,
|
||||
darkMode: localStorage.getItem('darkModeDisabled') !== 'true' ?? false,
|
||||
currentPage: 'messages',
|
||||
connType: connType.HTTP,
|
||||
connectionParams: {
|
||||
BLE: {},
|
||||
HTTP: {
|
||||
address:
|
||||
localStorage.getItem('connectionUrl') ?? import.meta.env.PROD
|
||||
? window.location.hostname
|
||||
: (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ??
|
||||
'meshtastic.local',
|
||||
tls: false,
|
||||
receiveBatchRequests: false,
|
||||
fetchInterval: 2000,
|
||||
},
|
||||
SERIAL: {},
|
||||
},
|
||||
updateAvaliable: false,
|
||||
};
|
||||
|
||||
export const appSlice = createSlice({
|
||||
name: 'app',
|
||||
initialState,
|
||||
reducers: {
|
||||
toggleMobileNav(state) {
|
||||
state.mobileNavOpen = !state.mobileNavOpen;
|
||||
},
|
||||
openConnectionModal(state) {
|
||||
state.connectionModalOpen = true;
|
||||
},
|
||||
closeConnectionModal(state) {
|
||||
state.connectionModalOpen = false;
|
||||
},
|
||||
setDarkModeEnabled(state, action: PayloadAction<boolean>) {
|
||||
localStorage.setItem('darkModeDisabled', String(!action.payload));
|
||||
state.darkMode = action.payload;
|
||||
},
|
||||
setCurrentPage(state, action: PayloadAction<currentPageName>) {
|
||||
state.currentPage = action.payload;
|
||||
},
|
||||
setConnType(state, action: PayloadAction<connType>) {
|
||||
state.connType = action.payload;
|
||||
},
|
||||
setConnectionParams(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
type: connType;
|
||||
params: Types.ConnectionParameters;
|
||||
}>,
|
||||
) {
|
||||
// @ts-ignore tmp
|
||||
state.connectionParams[connType[action.payload.type]] =
|
||||
action.payload.params;
|
||||
},
|
||||
setUpdateAvaliable(state, action: PayloadAction<boolean>) {
|
||||
state.updateAvaliable = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
toggleMobileNav,
|
||||
openConnectionModal,
|
||||
closeConnectionModal,
|
||||
setDarkModeEnabled,
|
||||
setCurrentPage,
|
||||
setConnType,
|
||||
setConnectionParams,
|
||||
setUpdateAvaliable,
|
||||
} = appSlice.actions;
|
||||
|
||||
export default appSlice.reducer;
|
||||
@@ -1,70 +0,0 @@
|
||||
import Point from '@arcgis/core/geometry/Point';
|
||||
|
||||
import type { MapStyleName } from '@core/mapStyles';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
interface MapState {
|
||||
firstLoad: boolean;
|
||||
latLng: Point;
|
||||
zoom: number;
|
||||
bearing: number;
|
||||
pitch: number;
|
||||
accessToken: string;
|
||||
style: MapStyleName;
|
||||
hillShade: boolean;
|
||||
exaggeration: boolean;
|
||||
}
|
||||
|
||||
const initialState: MapState = {
|
||||
firstLoad: true,
|
||||
latLng: new Point(),
|
||||
zoom: 2,
|
||||
bearing: 0,
|
||||
pitch: 0,
|
||||
accessToken:
|
||||
'pk.eyJ1Ijoic2FjaGF3IiwiYSI6ImNrNW9meXozZjBsdW0zbHBjM2FnNnV6cmsifQ.3E4n8eFGD9ZOFo-XDVeZnQ',
|
||||
style: localStorage.getItem('darkModeDisabled') !== 'true' ? 'Dark' : 'Light',
|
||||
hillShade: false,
|
||||
exaggeration: true,
|
||||
};
|
||||
|
||||
export const mapSlice = createSlice({
|
||||
name: 'map',
|
||||
initialState,
|
||||
reducers: {
|
||||
setLatLng: (state, action: PayloadAction<Point>) => {
|
||||
state.latLng = action.payload;
|
||||
},
|
||||
setZoom: (state, action: PayloadAction<number>) => {
|
||||
state.zoom = action.payload;
|
||||
},
|
||||
setBearing: (state, action: PayloadAction<number>) => {
|
||||
state.bearing = action.payload;
|
||||
},
|
||||
setPitch: (state, action: PayloadAction<number>) => {
|
||||
state.pitch = action.payload;
|
||||
},
|
||||
setMapStyle(state, action: PayloadAction<MapStyleName>) {
|
||||
state.style = action.payload;
|
||||
},
|
||||
setHillShade(state, action: PayloadAction<boolean>) {
|
||||
state.hillShade = action.payload;
|
||||
},
|
||||
setExaggeration(state, action: PayloadAction<boolean>) {
|
||||
state.exaggeration = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setLatLng,
|
||||
setZoom,
|
||||
setBearing,
|
||||
setPitch,
|
||||
setMapStyle,
|
||||
setHillShade,
|
||||
setExaggeration,
|
||||
} = mapSlice.actions;
|
||||
|
||||
export default mapSlice.reducer;
|
||||
@@ -1,343 +0,0 @@
|
||||
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export interface MessageWithAck {
|
||||
message: Types.TextPacket;
|
||||
ack: boolean;
|
||||
received: Date;
|
||||
}
|
||||
|
||||
export interface Chat {
|
||||
lastInterraction: Date;
|
||||
messages: MessageWithAck[];
|
||||
}
|
||||
|
||||
type ChatEntries = {
|
||||
[key in number]: Chat;
|
||||
};
|
||||
|
||||
interface Route {
|
||||
from: number;
|
||||
to: number;
|
||||
hops: number;
|
||||
|
||||
//speed stats?
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
metrics: Protobuf.DeviceMetrics;
|
||||
data: Protobuf.NodeInfo;
|
||||
}
|
||||
|
||||
export interface Radio {
|
||||
channels: Protobuf.Channel[];
|
||||
config: Config;
|
||||
moduleConfig: ModuleConfig;
|
||||
hardware: Protobuf.MyNodeInfo;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
device: Protobuf.Config_DeviceConfig;
|
||||
position: Protobuf.Config_PositionConfig;
|
||||
power: Protobuf.Config_PowerConfig;
|
||||
wifi: Protobuf.Config_WiFiConfig;
|
||||
display: Protobuf.Config_DisplayConfig;
|
||||
lora: Protobuf.Config_LoRaConfig;
|
||||
}
|
||||
|
||||
export interface ModuleConfig {
|
||||
mqtt: Protobuf.ModuleConfig_MQTTConfig;
|
||||
serial: Protobuf.ModuleConfig_SerialConfig;
|
||||
extNotification: Protobuf.ModuleConfig_ExternalNotificationConfig;
|
||||
storeForward: Protobuf.ModuleConfig_StoreForwardConfig;
|
||||
rangeTest: Protobuf.ModuleConfig_RangeTestConfig;
|
||||
telemetry: Protobuf.ModuleConfig_TelemetryConfig;
|
||||
cannedMessage: Protobuf.ModuleConfig_CannedMessageConfig;
|
||||
}
|
||||
|
||||
interface MeshtasticState {
|
||||
deviceStatus: Types.DeviceStatusEnum;
|
||||
lastMeshInterraction: number;
|
||||
ready: boolean;
|
||||
nodes: Node[];
|
||||
radio: Radio;
|
||||
chats: ChatEntries;
|
||||
logs: Types.LogEventPacket[];
|
||||
}
|
||||
|
||||
const initialState: MeshtasticState = {
|
||||
deviceStatus: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
|
||||
lastMeshInterraction: 0,
|
||||
ready: false,
|
||||
nodes: [],
|
||||
radio: {
|
||||
channels: [],
|
||||
config: {
|
||||
device: Protobuf.Config_DeviceConfig.create(),
|
||||
position: Protobuf.Config_PositionConfig.create(),
|
||||
power: Protobuf.Config_PowerConfig.create(),
|
||||
wifi: Protobuf.Config_WiFiConfig.create(),
|
||||
display: Protobuf.Config_DisplayConfig.create(),
|
||||
lora: Protobuf.Config_LoRaConfig.create(),
|
||||
},
|
||||
moduleConfig: {
|
||||
mqtt: Protobuf.ModuleConfig_MQTTConfig.create(),
|
||||
serial: Protobuf.ModuleConfig_SerialConfig.create(),
|
||||
extNotification:
|
||||
Protobuf.ModuleConfig_ExternalNotificationConfig.create(),
|
||||
storeForward: Protobuf.ModuleConfig_StoreForwardConfig.create(),
|
||||
rangeTest: Protobuf.ModuleConfig_RangeTestConfig.create(),
|
||||
telemetry: Protobuf.ModuleConfig_TelemetryConfig.create(),
|
||||
cannedMessage: Protobuf.ModuleConfig_CannedMessageConfig.create(),
|
||||
},
|
||||
hardware: Protobuf.MyNodeInfo.create(),
|
||||
},
|
||||
chats: {},
|
||||
logs: [],
|
||||
};
|
||||
|
||||
export const meshtasticSlice = createSlice({
|
||||
name: 'meshtastic',
|
||||
initialState,
|
||||
reducers: {
|
||||
addLogEvent: (state, action: PayloadAction<Types.LogEventPacket>) => {
|
||||
state.logs.push(action.payload);
|
||||
},
|
||||
clearLogs: (state) => {
|
||||
state.logs = [];
|
||||
},
|
||||
setDeviceStatus: (state, action: PayloadAction<Types.DeviceStatusEnum>) => {
|
||||
state.deviceStatus = action.payload;
|
||||
},
|
||||
setLastMeshInterraction: (state, action: PayloadAction<number>) => {
|
||||
state.lastMeshInterraction = action.payload;
|
||||
},
|
||||
setReady: (state, action: PayloadAction<boolean>) => {
|
||||
state.ready = action.payload;
|
||||
},
|
||||
setMyNodeInfo: (state, action: PayloadAction<Protobuf.MyNodeInfo>) => {
|
||||
state.radio.hardware = action.payload;
|
||||
},
|
||||
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
|
||||
const node = state.nodes.find(
|
||||
(node) => node.data.num === action.payload.packet.from,
|
||||
);
|
||||
if (node) {
|
||||
node.data.user = action.payload.data;
|
||||
if (action.payload.packet.rxTime) {
|
||||
node.data.lastHeard = new Date(
|
||||
action.payload.packet.rxTime * 1000,
|
||||
).getTime();
|
||||
}
|
||||
} else {
|
||||
state.nodes.push({
|
||||
data: {
|
||||
num: action.payload.packet.from,
|
||||
snr: action.payload.packet.rxSnr,
|
||||
lastHeard: new Date().getTime(),
|
||||
...action.payload.packet,
|
||||
},
|
||||
metrics: Protobuf.DeviceMetrics.create(),
|
||||
});
|
||||
}
|
||||
},
|
||||
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||
const node = state.nodes.find(
|
||||
(node) => node.data.num === action.payload.packet.from,
|
||||
);
|
||||
|
||||
if (node) {
|
||||
node.data.position = action.payload.data;
|
||||
if (action.payload.packet.rxTime) {
|
||||
node.data.lastHeard = new Date(
|
||||
action.payload.packet.rxTime * 1000,
|
||||
).getTime();
|
||||
}
|
||||
}
|
||||
},
|
||||
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
||||
const node = state.nodes.find(
|
||||
(node) => node.data.num === action.payload.num,
|
||||
);
|
||||
|
||||
if (node) {
|
||||
node.data.lastHeard = new Date(
|
||||
action.payload.lastHeard * 1000,
|
||||
).getTime();
|
||||
node.data.snr = action.payload.snr;
|
||||
} else {
|
||||
state.nodes.push({
|
||||
data: action.payload,
|
||||
metrics: Protobuf.DeviceMetrics.create(),
|
||||
});
|
||||
}
|
||||
},
|
||||
addChannel: (state, action: PayloadAction<Protobuf.Channel>) => {
|
||||
if (
|
||||
state.radio.channels.findIndex(
|
||||
(channel) => channel.index === action.payload.index,
|
||||
) !== -1
|
||||
) {
|
||||
state.radio.channels = state.radio.channels.map((channel) => {
|
||||
return channel.index === action.payload.index
|
||||
? action.payload
|
||||
: channel;
|
||||
});
|
||||
} else {
|
||||
state.radio.channels.push(action.payload);
|
||||
}
|
||||
},
|
||||
setConfig: (state, action: PayloadAction<Protobuf.Config>) => {
|
||||
switch (action.payload.payloadVariant.oneofKind) {
|
||||
case 'device': {
|
||||
state.radio.config.device = action.payload.payloadVariant.device;
|
||||
break;
|
||||
}
|
||||
case 'position': {
|
||||
state.radio.config.position = action.payload.payloadVariant.position;
|
||||
break;
|
||||
}
|
||||
case 'power': {
|
||||
state.radio.config.power = action.payload.payloadVariant.power;
|
||||
break;
|
||||
}
|
||||
case 'wifi': {
|
||||
state.radio.config.wifi = action.payload.payloadVariant.wifi;
|
||||
break;
|
||||
}
|
||||
case 'display': {
|
||||
state.radio.config.display = action.payload.payloadVariant.display;
|
||||
break;
|
||||
}
|
||||
case 'lora': {
|
||||
state.radio.config.lora = action.payload.payloadVariant.lora;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
setModuleConfig: (state, action: PayloadAction<Protobuf.ModuleConfig>) => {
|
||||
switch (action.payload.payloadVariant.oneofKind) {
|
||||
case 'cannedMessage': {
|
||||
state.radio.moduleConfig.cannedMessage =
|
||||
action.payload.payloadVariant.cannedMessage;
|
||||
break;
|
||||
}
|
||||
case 'externalNotification': {
|
||||
state.radio.moduleConfig.extNotification =
|
||||
action.payload.payloadVariant.externalNotification;
|
||||
break;
|
||||
}
|
||||
case 'mqtt': {
|
||||
state.radio.moduleConfig.mqtt = action.payload.payloadVariant.mqtt;
|
||||
break;
|
||||
}
|
||||
case 'rangeTest': {
|
||||
state.radio.moduleConfig.rangeTest =
|
||||
action.payload.payloadVariant.rangeTest;
|
||||
break;
|
||||
}
|
||||
case 'serial': {
|
||||
state.radio.moduleConfig.serial =
|
||||
action.payload.payloadVariant.serial;
|
||||
break;
|
||||
}
|
||||
case 'storeForward': {
|
||||
state.radio.moduleConfig.storeForward =
|
||||
action.payload.payloadVariant.storeForward;
|
||||
break;
|
||||
}
|
||||
case 'telemetry': {
|
||||
state.radio.moduleConfig.telemetry =
|
||||
action.payload.payloadVariant.telemetry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
addMessage: (state, action: PayloadAction<MessageWithAck>) => {
|
||||
// todo: is last interraction for just channel chats or dm's to?
|
||||
state.chats[action.payload.message.packet.channel].lastInterraction =
|
||||
new Date();
|
||||
|
||||
if (action.payload.message.packet.to === 0xffffffff) {
|
||||
// TODO: use chatIndex
|
||||
state.chats[action.payload.message.packet.channel].messages.push(
|
||||
action.payload,
|
||||
);
|
||||
} else {
|
||||
const dmIndex =
|
||||
action.payload.message.packet.from === state.radio.hardware.myNodeNum
|
||||
? action.payload.message.packet.to
|
||||
: action.payload.message.packet.from;
|
||||
|
||||
state.chats[dmIndex].messages.push(action.payload);
|
||||
}
|
||||
},
|
||||
ackMessage: (
|
||||
state,
|
||||
action: PayloadAction<{ chatIndex: number; messageId: number }>,
|
||||
) => {
|
||||
console.log(action.payload.chatIndex);
|
||||
|
||||
console.log(state.chats);
|
||||
|
||||
console.log(state.chats[action.payload.chatIndex]);
|
||||
|
||||
state.chats[action.payload.chatIndex].messages.map((message) => {
|
||||
console.log('ack');
|
||||
|
||||
if (message.message.packet.id === action.payload.messageId) {
|
||||
console.log('acked');
|
||||
|
||||
message.ack = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
updateLastInteraction: (
|
||||
state,
|
||||
action: PayloadAction<{ id: number; time: Date }>,
|
||||
) => {
|
||||
const node = state.nodes.find(
|
||||
(node) => node.data.num === action.payload.id,
|
||||
);
|
||||
if (node) {
|
||||
node.data.lastHeard = action.payload.time.getTime();
|
||||
}
|
||||
},
|
||||
addChat: (state, action: PayloadAction<number>) => {
|
||||
state.chats[action.payload] = {
|
||||
messages: [],
|
||||
lastInterraction: new Date(),
|
||||
};
|
||||
},
|
||||
resetState: (state) => {
|
||||
state.deviceStatus = Types.DeviceStatusEnum.DEVICE_DISCONNECTED;
|
||||
state.nodes = [];
|
||||
state.radio = initialState.radio;
|
||||
state.ready = false;
|
||||
state.lastMeshInterraction = 0;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
addLogEvent,
|
||||
clearLogs,
|
||||
setDeviceStatus,
|
||||
setLastMeshInterraction,
|
||||
setReady,
|
||||
setMyNodeInfo,
|
||||
addUser,
|
||||
addPosition,
|
||||
addNode,
|
||||
addChannel,
|
||||
setConfig,
|
||||
setModuleConfig,
|
||||
addMessage,
|
||||
ackMessage,
|
||||
updateLastInteraction,
|
||||
addChat,
|
||||
resetState,
|
||||
} = meshtasticSlice.actions;
|
||||
|
||||
export default meshtasticSlice.reducer;
|
||||
@@ -1,19 +0,0 @@
|
||||
import appReducer from '@core/slices/appSlice';
|
||||
import mapReducer from '@core/slices/mapSlice';
|
||||
import meshtasticReducer from '@core/slices/meshtasticSlice';
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
app: appReducer,
|
||||
meshtastic: meshtasticReducer,
|
||||
map: mapReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
serializableCheck: false,
|
||||
}),
|
||||
});
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
@@ -0,0 +1,32 @@
|
||||
import create from "zustand";
|
||||
|
||||
interface AppState {
|
||||
selectedDevice?: number;
|
||||
devices: {
|
||||
id: number;
|
||||
num: number;
|
||||
}[];
|
||||
|
||||
setSelectedDevice: (deviceId: number) => void;
|
||||
addDevice: (device: { id: number; num: number }) => void;
|
||||
removeDevice: (deviceId: number) => void;
|
||||
}
|
||||
|
||||
export const useAppStore = create<AppState>()((set) => ({
|
||||
selectedDevice: 0,
|
||||
devices: [],
|
||||
currentPage: "messages",
|
||||
|
||||
setSelectedDevice: (deviceId) =>
|
||||
set(() => ({
|
||||
selectedDevice: deviceId,
|
||||
})),
|
||||
addDevice: (device) =>
|
||||
set((state) => ({
|
||||
devices: [...state.devices, device],
|
||||
})),
|
||||
removeDevice: (deviceId) =>
|
||||
set((state) => ({
|
||||
devices: state.devices.filter((device) => device.id !== deviceId),
|
||||
})),
|
||||
}));
|
||||
@@ -0,0 +1,418 @@
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
import { produce } from "immer";
|
||||
import create from "zustand";
|
||||
|
||||
import { IConnection, Protobuf, Types } from "@meshtastic/meshtasticjs";
|
||||
|
||||
export type Page =
|
||||
| "messages"
|
||||
| "map"
|
||||
| "extensions"
|
||||
| "config"
|
||||
| "channels"
|
||||
| "info";
|
||||
|
||||
export interface MessageWithAck {
|
||||
message: Types.TextPacket;
|
||||
ack: boolean;
|
||||
received: Date;
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
config: Protobuf.Channel;
|
||||
lastInterraction: Date;
|
||||
messages: MessageWithAck[];
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
deviceMetrics: Protobuf.DeviceMetrics;
|
||||
environmentMetrics: Protobuf.EnvironmentMetrics;
|
||||
data: Protobuf.NodeInfo;
|
||||
}
|
||||
|
||||
export interface Device {
|
||||
ready: boolean;
|
||||
status: Types.DeviceStatusEnum;
|
||||
channels: Channel[];
|
||||
config: Protobuf.LocalConfig;
|
||||
moduleConfig: Protobuf.LocalModuleConfig;
|
||||
hardware: Protobuf.MyNodeInfo;
|
||||
nodes: Node[];
|
||||
connection?: IConnection;
|
||||
activeChat: number;
|
||||
activePage: Page;
|
||||
|
||||
setReady(ready: boolean): void;
|
||||
setStatus: (status: Types.DeviceStatusEnum) => void;
|
||||
addChannel: (channel: Channel) => void;
|
||||
setConfig: (config: Protobuf.Config) => void;
|
||||
setModuleConfig: (config: Protobuf.ModuleConfig) => void;
|
||||
setHardware: (hardware: Protobuf.MyNodeInfo) => void;
|
||||
setMetrics: (metrics: Types.TelemetryPacket) => void;
|
||||
setActiveChat: (chatIndex: number) => void;
|
||||
setActivePage: (page: Page) => void;
|
||||
addNodeInfo: (nodeInfo: Types.NodeInfoPacket) => void;
|
||||
addUser: (user: Types.UserPacket) => void;
|
||||
addPosition: (position: Types.PositionPacket) => void;
|
||||
addConnection: (connection: IConnection) => void;
|
||||
addMessage: (message: MessageWithAck) => void;
|
||||
ackMessage: (channelIndex: number, messageId: number) => void;
|
||||
}
|
||||
|
||||
export interface DeviceState {
|
||||
devices: Map<number, Device>;
|
||||
remoteDevices: Map<number, undefined>;
|
||||
|
||||
addDevice: (id: number) => Device;
|
||||
removeDevice: (id: number) => void;
|
||||
getDevices: () => Device[];
|
||||
}
|
||||
|
||||
export const useDeviceStore = create<DeviceState>((set, get) => ({
|
||||
devices: new Map(),
|
||||
remoteDevices: new Map(),
|
||||
|
||||
addDevice: (id: number) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
draft.devices.set(id, {
|
||||
ready: false,
|
||||
status: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
|
||||
channels: [],
|
||||
config: Protobuf.LocalConfig.create(),
|
||||
moduleConfig: Protobuf.LocalModuleConfig.create(),
|
||||
hardware: Protobuf.MyNodeInfo.create(),
|
||||
nodes: [],
|
||||
connection: undefined,
|
||||
activeChat: 0,
|
||||
activePage: "messages",
|
||||
|
||||
setReady: (ready: boolean) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
device.ready = ready;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
setStatus: (status: Types.DeviceStatusEnum) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
device.status = status;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
addChannel: (channel: Channel) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
const channelIndex = device.channels.findIndex(
|
||||
(c) => c.config.index === channel.config.index
|
||||
);
|
||||
if (channelIndex !== -1) {
|
||||
const messages = device.channels[channelIndex].messages;
|
||||
device.channels[channelIndex] = channel;
|
||||
device.channels[channelIndex].messages = messages;
|
||||
} else {
|
||||
device.channels.push(channel);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
setConfig: (config: Protobuf.Config) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
|
||||
if (device) {
|
||||
switch (config.payloadVariant.oneofKind) {
|
||||
case "device":
|
||||
device.config.device = config.payloadVariant.device;
|
||||
break;
|
||||
case "position":
|
||||
device.config.position = config.payloadVariant.position;
|
||||
break;
|
||||
case "power":
|
||||
device.config.power = config.payloadVariant.power;
|
||||
break;
|
||||
case "wifi":
|
||||
device.config.wifi = config.payloadVariant.wifi;
|
||||
break;
|
||||
case "display":
|
||||
device.config.display = config.payloadVariant.display;
|
||||
break;
|
||||
case "lora":
|
||||
device.config.lora = config.payloadVariant.lora;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
setModuleConfig: (config: Protobuf.ModuleConfig) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
|
||||
if (device) {
|
||||
switch (config.payloadVariant.oneofKind) {
|
||||
case "mqtt":
|
||||
device.moduleConfig.mqtt = config.payloadVariant.mqtt;
|
||||
break;
|
||||
case "serial":
|
||||
device.moduleConfig.serial = config.payloadVariant.serial;
|
||||
break;
|
||||
case "externalNotification":
|
||||
device.moduleConfig.externalNotification =
|
||||
config.payloadVariant.externalNotification;
|
||||
break;
|
||||
case "storeForward":
|
||||
device.moduleConfig.storeForward =
|
||||
config.payloadVariant.storeForward;
|
||||
break;
|
||||
case "rangeTest":
|
||||
device.moduleConfig.rangeTest =
|
||||
config.payloadVariant.rangeTest;
|
||||
break;
|
||||
case "telemetry":
|
||||
device.moduleConfig.telemetry =
|
||||
config.payloadVariant.telemetry;
|
||||
break;
|
||||
case "cannedMessage":
|
||||
device.moduleConfig.cannedMessage =
|
||||
config.payloadVariant.cannedMessage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
setHardware: (hardware: Protobuf.MyNodeInfo) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
device.hardware = hardware;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
setMetrics: (metrics: Types.TelemetryPacket) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
let node = device?.nodes.find(
|
||||
(n) => n.data.num === metrics.packet.from
|
||||
);
|
||||
if (device && !node) {
|
||||
node = {
|
||||
data: Protobuf.NodeInfo.create({
|
||||
num: metrics.packet.from,
|
||||
snr: metrics.packet.rxSnr,
|
||||
lastHeard: new Date().getSeconds(),
|
||||
}),
|
||||
deviceMetrics: Protobuf.DeviceMetrics.create(),
|
||||
environmentMetrics: Protobuf.EnvironmentMetrics.create(),
|
||||
};
|
||||
|
||||
device.nodes.push(node);
|
||||
}
|
||||
if (node) {
|
||||
switch (metrics.data.variant.oneofKind) {
|
||||
case "deviceMetrics":
|
||||
node.deviceMetrics = metrics.data.variant.deviceMetrics;
|
||||
break;
|
||||
case "environmentMetrics":
|
||||
node.environmentMetrics =
|
||||
metrics.data.variant.environmentMetrics;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
setActiveChat: (chatIndex) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
device.activeChat = chatIndex;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
setActivePage: (page) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
device.activePage = page;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
addNodeInfo: (nodeInfo) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
const node = device.nodes.find(
|
||||
(node) => node.data.num === nodeInfo.data.num
|
||||
);
|
||||
if (node) {
|
||||
node.data = nodeInfo.data;
|
||||
// if (action.payload.packet.rxTime) {
|
||||
// node.data.lastHeard = new Date(
|
||||
// action.payload.packet.rxTime * 1000,
|
||||
// ).getTime();
|
||||
// }
|
||||
} else {
|
||||
device.nodes.push({
|
||||
data: Protobuf.NodeInfo.create(nodeInfo.data),
|
||||
deviceMetrics: Protobuf.DeviceMetrics.create(),
|
||||
environmentMetrics: Protobuf.EnvironmentMetrics.create(),
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
addUser: (user) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
const node = device.nodes.find(
|
||||
(node) => node.data.num === user.packet.from
|
||||
);
|
||||
if (node) {
|
||||
node.data.user = user.data;
|
||||
// if (action.payload.packet.rxTime) {
|
||||
// node.data.lastHeard = new Date(
|
||||
// action.payload.packet.rxTime * 1000,
|
||||
// ).getTime();
|
||||
// }
|
||||
} else {
|
||||
device.nodes.push({
|
||||
data: Protobuf.NodeInfo.create({
|
||||
num: user.packet.from,
|
||||
snr: user.packet.rxSnr,
|
||||
user: user.data,
|
||||
}),
|
||||
deviceMetrics: Protobuf.DeviceMetrics.create(),
|
||||
environmentMetrics: Protobuf.EnvironmentMetrics.create(),
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
addPosition: (position) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
const node = device.nodes.find(
|
||||
(node) => node.data.num === position.packet.from
|
||||
);
|
||||
if (node) {
|
||||
node.data.position = position.data;
|
||||
// if (action.payload.packet.rxTime) {
|
||||
// node.data.lastHeard = new Date(
|
||||
// action.payload.packet.rxTime * 1000,
|
||||
// ).getTime();
|
||||
// }
|
||||
} else {
|
||||
console.log("PUSH333333333333333333333");
|
||||
device.nodes.push({
|
||||
data: Protobuf.NodeInfo.create({
|
||||
num: position.packet.from,
|
||||
position: position.data,
|
||||
}),
|
||||
deviceMetrics: Protobuf.DeviceMetrics.create(),
|
||||
environmentMetrics: Protobuf.EnvironmentMetrics.create(),
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
addConnection: (connection) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
device.connection = connection;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
addMessage: (message) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
device.channels
|
||||
.find(
|
||||
(ch) => ch.config.index === message.message.packet.channel
|
||||
)
|
||||
?.messages.push(message);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
ackMessage: (channelIndex: number, messageId: number) => {
|
||||
set(
|
||||
produce<DeviceState>((draft) => {
|
||||
const device = draft.devices.get(id);
|
||||
if (device) {
|
||||
const channel = device.channels.find(
|
||||
(ch) => ch.config.index === channelIndex
|
||||
);
|
||||
if (channel) {
|
||||
const message = channel.messages.find(
|
||||
(msg) => msg.message.packet.id === messageId
|
||||
);
|
||||
if (message) {
|
||||
message.ack = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const device = get().devices.get(id);
|
||||
|
||||
if (!device) {
|
||||
throw new Error("Device not found");
|
||||
}
|
||||
return device;
|
||||
},
|
||||
removeDevice: (id) =>
|
||||
produce<DeviceState>((draft) => {
|
||||
draft.devices.delete(id);
|
||||
}),
|
||||
getDevices: () => Array.from(get().devices.values()),
|
||||
}));
|
||||
|
||||
export const DeviceContext = createContext<Device | undefined>(undefined);
|
||||
|
||||
export const useDevice = (): Device => {
|
||||
const context = useContext(DeviceContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useDevice must be used within a ConnectionProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
IConnection,
|
||||
Protobuf,
|
||||
SettingsManager,
|
||||
Types,
|
||||
} from "@meshtastic/meshtasticjs";
|
||||
|
||||
import type { Device } from "./stores/deviceStore.js";
|
||||
|
||||
export const subscribeAll = (device: Device, connection: IConnection) => {
|
||||
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
|
||||
|
||||
// onLogEvent
|
||||
// onMeshHeartbeat
|
||||
// onRoutingPacket
|
||||
// onTelemetryPacket
|
||||
|
||||
connection.onRoutingPacket.subscribe((routingPacket) => {
|
||||
console.log(routingPacket);
|
||||
});
|
||||
|
||||
connection.onTelemetryPacket.subscribe((telemetryPacket) => {
|
||||
device.setMetrics(telemetryPacket);
|
||||
});
|
||||
|
||||
connection.onDeviceStatus.subscribe((status) => {
|
||||
device.setStatus(status);
|
||||
|
||||
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
|
||||
// device.setReady(true);
|
||||
} else if (status === Types.DeviceStatusEnum.DEVICE_DISCONNECTED) {
|
||||
device.setReady(false);
|
||||
}
|
||||
});
|
||||
|
||||
connection.onMyNodeInfo.subscribe((nodeInfo) => {
|
||||
device.setHardware(nodeInfo);
|
||||
});
|
||||
|
||||
connection.onUserPacket.subscribe((user) => {
|
||||
device.addUser(user);
|
||||
});
|
||||
|
||||
connection.onPositionPacket.subscribe((position) => {
|
||||
device.addPosition(position);
|
||||
});
|
||||
|
||||
connection.onNodeInfoPacket.subscribe((nodeInfo) => {
|
||||
device.addNodeInfo(nodeInfo);
|
||||
});
|
||||
|
||||
connection.onChannelPacket.subscribe((channel) => {
|
||||
device.addChannel({
|
||||
config: channel.data,
|
||||
lastInterraction: new Date(),
|
||||
messages: [],
|
||||
});
|
||||
});
|
||||
connection.onConfigPacket.subscribe((config) => {
|
||||
device.setConfig(config.data);
|
||||
});
|
||||
connection.onModuleConfigPacket.subscribe((moduleConfig) => {
|
||||
device.setModuleConfig(moduleConfig.data);
|
||||
});
|
||||
|
||||
connection.onTextPacket.subscribe((message) => {
|
||||
device.addMessage({
|
||||
message: message,
|
||||
ack: message.packet.from !== device.hardware.myNodeNum,
|
||||
received: message.packet.rxTime
|
||||
? new Date(message.packet.rxTime * 1000)
|
||||
: new Date(),
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
export const fetcher = async <JSON>(
|
||||
input: RequestInfo,
|
||||
init?: RequestInit,
|
||||
init?: RequestInit
|
||||
): Promise<JSON> => {
|
||||
const res = await fetch(input, init);
|
||||
return res.json() as Promise<JSON>;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const randId = () => {
|
||||
return Math.floor(Math.random() * 1e9);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import type React from "react";
|
||||
|
||||
export const renderOptions = (enumValue: {
|
||||
[s: string]: string | number;
|
||||
}): JSX.Element[] => {
|
||||
const optionsEnumValues = enumValue
|
||||
? Object.entries(enumValue).filter((value) => typeof value[1] === "number")
|
||||
: [];
|
||||
|
||||
return optionsEnumValues.map(([name, value], index) => (
|
||||
<option key={index} value={value}>
|
||||
{name}
|
||||
</option>
|
||||
));
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { LatLon } from "geodesy/mgrs.js";
|
||||
|
||||
export const toMGRS = (lat?: number, lng?: number) => {
|
||||
if (lat && lng && lat !== 0 && lng !== 0) {
|
||||
return new LatLon(lat / 1e7, lng / 1e7).toUtm().toMgrs().toString();
|
||||
}
|
||||
return "No Fix.";
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
export interface IAutor {
|
||||
login: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
followers_url: string;
|
||||
following_url: string;
|
||||
gists_url: string;
|
||||
starred_url: string;
|
||||
subscriptions_url: string;
|
||||
organizations_url: string;
|
||||
repos_url: string;
|
||||
events_url: string;
|
||||
received_events_url: string;
|
||||
type: string;
|
||||
site_admin: false;
|
||||
}
|
||||
|
||||
export interface IAsset {
|
||||
url: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
name: string;
|
||||
label: string;
|
||||
uploader: {
|
||||
login: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
followers_url: string;
|
||||
following_url: string;
|
||||
gists_url: string;
|
||||
starred_url: string;
|
||||
subscriptions_url: string;
|
||||
organizations_url: string;
|
||||
repos_url: string;
|
||||
events_url: string;
|
||||
received_events_url: string;
|
||||
type: string;
|
||||
site_admin: false;
|
||||
};
|
||||
content_type: string;
|
||||
state: string;
|
||||
size: number;
|
||||
download_count: 223;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
browser_download_url: string;
|
||||
}
|
||||
|
||||
export interface IRelease {
|
||||
url: string;
|
||||
assets_url: string;
|
||||
upload_url: string;
|
||||
html_url: string;
|
||||
id: number;
|
||||
author: IAutor;
|
||||
node_id: string;
|
||||
tag_name: string;
|
||||
target_commitish: string;
|
||||
name: string;
|
||||
draft: false;
|
||||
prerelease: false;
|
||||
created_at: string;
|
||||
published_at: string;
|
||||
assets: IAsset[];
|
||||
tarball_url: string;
|
||||
zipball_url: string;
|
||||
body: string;
|
||||
reactions: {
|
||||
url: string;
|
||||
total_count: number;
|
||||
"+1": number;
|
||||
"-1": number;
|
||||
laugh: number;
|
||||
hooray: 1;
|
||||
confused: number;
|
||||
heart: number;
|
||||
rocket: number;
|
||||
eyes: number;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user