You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

304 lines
8.4 KiB

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[];
}
interface CurrentPosition {
latitudeI: number;
longitudeI: number;
altitude: number;
posTimestamp: number;
satsInView: number;
}
type ChatEntries = {
[key in number]: Chat;
};
interface Route {
from: number;
to: number;
hops: number;
//speed stats?
}
export interface Node {
number: number;
lastHeard: Date;
snr: number[];
positions: Protobuf.Position[];
currentPosition?: CurrentPosition;
user?: Protobuf.User;
routes: Route[];
}
export interface Radio {
channels: Protobuf.Channel[];
preferences: Protobuf.RadioConfig_UserPreferences;
hardware: Protobuf.MyNodeInfo;
}
interface MeshtasticState {
deviceStatus: Types.DeviceStatusEnum;
lastMeshInterraction: number;
ready: boolean;
nodes: Node[];
radio: Radio;
hostOverrideEnabled: boolean;
hostOverride: string;
chats: ChatEntries;
logs: Types.LogEventPacket[];
}
const initialState: MeshtasticState = {
deviceStatus: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
lastMeshInterraction: 0,
ready: false,
nodes: [],
radio: {
channels: [],
preferences: Protobuf.RadioConfig_UserPreferences.create(),
hardware: Protobuf.MyNodeInfo.create(),
},
//todo implement
// connectionMethod: localStorage.getItem('connectionMethod'),
hostOverrideEnabled:
localStorage.getItem('hostOverrideEnabled') === 'true' ?? false,
hostOverride: localStorage.getItem('hostOverride') ?? '',
chats: {},
logs: [],
};
export const meshtasticSlice = createSlice({
name: 'meshtastic',
initialState,
reducers: {
addLogEvent: (state, action: PayloadAction<Types.LogEventPacket>) => {
state.logs.push(action.payload);
},
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.number === action.payload.packet.from,
);
if (node) {
node.user = action.payload.data;
if (action.payload.packet.rxTime) {
node.lastHeard = new Date(action.payload.packet.rxTime * 1000);
}
} else {
// todo: add node
console.log('Node not in DB');
console.log(action.payload);
}
},
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
const node = state.nodes.find(
(node) => node.number === action.payload.packet.from,
);
if (node) {
node.positions.push(action.payload.data);
if (
action.payload.data.latitudeI ||
action.payload.data.longitudeI ||
action.payload.data.altitude
) {
node.currentPosition = {
latitudeI:
action.payload.data.latitudeI ?? node.currentPosition?.latitudeI,
longitudeI:
action.payload.data.longitudeI ??
node.currentPosition?.longitudeI,
altitude:
action.payload.data.altitude ?? node.currentPosition?.altitude,
posTimestamp: action.payload.data.posTimestamp,
satsInView:
action.payload.data.satsInView ??
node.currentPosition?.satsInView,
};
}
if (action.payload.packet.rxTime) {
node.lastHeard = new Date(action.payload.packet.rxTime * 1000);
}
}
},
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
const node = state.nodes.find(
(node) => node.number === action.payload.num,
);
if (node) {
console.log('node exists');
node.lastHeard = new Date(action.payload.lastHeard * 1000);
node.snr.push(action.payload.snr);
} else {
state.nodes.push({
number: action.payload.num,
lastHeard: new Date(action.payload.lastHeard * 1000),
snr: [action.payload.snr],
positions: [],
routes: [],
});
}
},
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);
}
},
addRoute: (state, action: PayloadAction<Route>) => {
const node = state.nodes.find(
(node) => node.number === action.payload.from,
);
const exists = node?.routes.findIndex(
(route) =>
route.from === action.payload.from && route.to === action.payload.to,
);
if (exists === -1) {
node?.routes.push(action.payload);
}
},
setPreferences: (
state,
action: PayloadAction<Protobuf.RadioConfig_UserPreferences>,
) => {
state.radio.preferences = action.payload;
},
addMessage: (state, action: PayloadAction<MessageWithAck>) => {
console.log(action.payload);
console.log(
`${action.payload.message.packet.from} -> ${action.payload.message.packet.to}`,
);
state.chats[action.payload.message.packet.channel].lastInterraction =
new Date();
if (action.payload.message.packet.to === 0xffffffff) {
console.log('boradcast');
state.chats[action.payload.message.packet.channel].messages.push(
action.payload,
);
} else {
console.log('dm');
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);
state.chats[action.payload.chatIndex].messages.map((message) => {
if (message.message.packet.id === action.payload.messageId) {
message.ack = true;
}
});
},
updateLastInteraction: (
state,
action: PayloadAction<{ id: number; time: Date }>,
) => {
const node = state.nodes.find(
(node) => node.number === action.payload.id,
);
if (node) {
node.lastHeard = action.payload.time;
}
},
setHostOverrideEnabled: (state, action: PayloadAction<boolean>) => {
state.hostOverrideEnabled = action.payload;
localStorage.setItem('hostOverrideEnabled', String(action.payload));
if (state.hostOverrideEnabled !== action.payload) {
// connection.disconnect();
}
},
setHostOverride: (state, action: PayloadAction<string>) => {
state.hostOverride = action.payload;
localStorage.setItem('hostOverride', action.payload);
if (state.hostOverride !== action.payload) {
// connection.disconnect();
}
},
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,
setDeviceStatus,
setLastMeshInterraction,
setReady,
setMyNodeInfo,
addUser,
addPosition,
addNode,
addChannel,
setPreferences,
addRoute,
addMessage,
ackMessage,
updateLastInteraction,
setHostOverrideEnabled,
setHostOverride,
addChat,
resetState,
} = meshtasticSlice.actions;
export default meshtasticSlice.reducer;