diff --git a/package.json b/package.json
index 679864e3..c31233cc 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"react-i18next": "^11.14.2",
"react-icons": "^4.3.1",
"react-json-pretty": "^2.2.0",
+ "react-qr-code": "^2.0.3",
"react-redux": "^7.2.6",
"rfc4648": "^1.5.0",
"swr": "^1.0.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3554e977..d76d08a3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -36,6 +36,7 @@ specifiers:
react-i18next: ^11.14.2
react-icons: ^4.3.1
react-json-pretty: ^2.2.0
+ react-qr-code: ^2.0.3
react-redux: ^7.2.6
rfc4648: ^1.5.0
swr: ^1.0.1
@@ -63,6 +64,7 @@ dependencies:
react-i18next: 11.14.2_i18next@21.5.2+react@17.0.2
react-icons: 4.3.1_react@17.0.2
react-json-pretty: 2.2.0_react-dom@17.0.2+react@17.0.2
+ react-qr-code: 2.0.3_react@17.0.2
react-redux: 7.2.6_react-dom@17.0.2+react@17.0.2
rfc4648: 1.5.0
swr: 1.0.1_react@17.0.2
@@ -4063,6 +4065,10 @@ packages:
engines: {node: '>=6'}
dev: true
+ /qr.js/0.0.0:
+ resolution: {integrity: sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=}
+ dev: false
+
/queue-microtask/1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true
@@ -4149,6 +4155,17 @@ packages:
react-dom: 17.0.2_react@17.0.2
dev: false
+ /react-qr-code/2.0.3_react@17.0.2:
+ resolution: {integrity: sha512-6GDH0l53lksf2JgZwwcoS0D60a1OAal/GQRyNFkMBW19HjSqvtD5S20scmSQsKl+BgWM85Wd5DCcUYoHd+PZnQ==}
+ peerDependencies:
+ react: ^16.x || ^17.x
+ react-native-svg: '*'
+ dependencies:
+ prop-types: 15.7.2
+ qr.js: 0.0.0
+ react: 17.0.2
+ dev: false
+
/react-redux/7.2.6_react-dom@17.0.2+react@17.0.2:
resolution: {integrity: sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==}
peerDependencies:
diff --git a/src/components/Channel.tsx b/src/components/Channel.tsx
index f60b3fa9..d2b0249f 100644
--- a/src/components/Channel.tsx
+++ b/src/components/Channel.tsx
@@ -1,16 +1,19 @@
import React from 'react';
import { useForm } from 'react-hook-form';
+import { FaQrcode } from 'react-icons/fa';
import { FiEdit3, FiSave } from 'react-icons/fi';
+import QRCode from 'react-qr-code';
+import { Card } from '@components/generic/Card';
+import { Checkbox } from '@components/generic/form/Checkbox';
+import { Input } from '@components/generic/form/Input';
+import { IconButton } from '@components/generic/IconButton';
import { Loading } from '@components/generic/Loading';
+import { Modal } from '@components/generic/Modal';
+import { connection } from '@core/connection';
import { Protobuf } from '@meshtastic/meshtasticjs';
-import { connection } from '../core/connection';
-import { Checkbox } from './generic/form/Checkbox';
-import { Input } from './generic/form/Input';
-import { IconButton } from './generic/IconButton';
-
export interface ChannelProps {
channel: Protobuf.Channel;
hideEnabled?: boolean;
@@ -22,6 +25,7 @@ export const Channel = ({
}: ChannelProps): JSX.Element => {
const [edit, setEdit] = React.useState(false);
const [loading, setLoading] = React.useState(false);
+ const [showQr, setShowQr] = React.useState(false);
const { register, handleSubmit } = useForm<{
enabled: boolean;
@@ -80,6 +84,16 @@ export const Channel = ({
return (
+ {
+ setShowQr(false);
+ }}
+ >
+
+
+
+
{edit ? (
<>
{loading && }
@@ -139,12 +153,20 @@ export const Channel = ({
: `Channel: ${channel.index}`}
- {
- setEdit(true);
- }}
- icon={ }
- />
+
+ {
+ setShowQr(true);
+ }}
+ icon={ }
+ />
+ {
+ setEdit(true);
+ }}
+ icon={ }
+ />
+
>
)}
diff --git a/src/components/Connection.tsx b/src/components/Connection.tsx
index 0747d6eb..c6dd267b 100644
--- a/src/components/Connection.tsx
+++ b/src/components/Connection.tsx
@@ -1,267 +1,114 @@
import React from 'react';
-import { FiCheck } from 'react-icons/fi';
-import JSONPretty from 'react-json-pretty';
-
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
+import { Serial } from '@components/connection/Serial';
import { Button } from '@components/generic/Button';
import { Card } from '@components/generic/Card';
-import { Checkbox } from '@components/generic/form/Checkbox';
-import { Input } from '@components/generic/form/Input';
import { Select } from '@components/generic/form/Select';
-import { IconButton } from '@components/generic/IconButton';
import { Modal } from '@components/generic/Modal';
import {
- ble,
+ cleanupListeners,
connection,
connectionUrl,
- serial,
setConnection,
} from '@core/connection';
-import { closeConnectionModal } from '@core/slices/appSlice';
import {
- IBLEConnection,
- IHTTPConnection,
- ISerialConnection,
- Protobuf,
- SettingsManager,
-} from '@meshtastic/meshtasticjs';
-import type {
- BLEConnectionParameters,
- HTTPConnectionParameters,
- SerialConnectionParameters,
-} from '@meshtastic/meshtasticjs/dist/types';
+ closeConnectionModal,
+ connType,
+ setConnectionParams,
+ setConnType,
+} from '@core/slices/appSlice';
+import { Types } from '@meshtastic/meshtasticjs';
-import { DeviceStatus } from './menu/buttons/DeviceStatus';
-
-enum connType {
- HTTP,
- BLE,
- SERIAL,
-}
+import { BLE } from './connection/BLE';
+import { HTTP } from './connection/HTTP';
export const Connection = (): JSX.Element => {
const dispatch = useAppDispatch();
- const [selectedConnType, setSelectedConnType] = React.useState(connType.HTTP);
- const [bleDevices, setBleDevices] = React.useState([]);
- const [serialDevices, setSerialDevices] = React.useState([]);
- const [httpIpSource, setHttpIpSource] = React.useState<'local' | 'remote'>(
- 'local',
- );
- const hostOverrideEnabled = useAppSelector(
- (state) => state.meshtastic.hostOverrideEnabled,
- );
- const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
- const connectionModalOpen = useAppSelector(
- (state) => state.app.connectionModalOpen,
- );
- const ready = useAppSelector((state) => state.meshtastic.ready);
- const connect = async (
- connectionType: connType,
- params:
- | HTTPConnectionParameters
- | SerialConnectionParameters
- | BLEConnectionParameters,
- ): Promise => {
- connection.complete();
- await connection.disconnect();
- if (connectionType === connType.BLE) {
- setConnection(new IBLEConnection());
- } else if (connectionType === connType.HTTP) {
- setConnection(new IHTTPConnection());
- } else {
- setConnection(new ISerialConnection());
- }
-
- // @ts-ignore tmp
- await connection.connect(params);
- };
-
- const updateBleDeviceList = React.useCallback(async (): Promise => {
- const devices = await ble.getDevices();
- setBleDevices(devices);
- }, []);
-
- const updateSerialDeviceList = React.useCallback(async (): Promise => {
- const devices = await serial.getPorts();
- setSerialDevices(devices);
- }, []);
+ const state = useAppSelector((state) => state.meshtastic);
+ const appState = useAppSelector((state) => state.app);
React.useEffect(() => {
- if (ready) {
- dispatch(closeConnectionModal());
- }
- }, [ready, dispatch]);
-
- React.useEffect(() => {
- if (selectedConnType === connType.BLE) {
- void updateBleDeviceList();
- }
- if (selectedConnType === connType.SERIAL) {
- void updateSerialDeviceList();
- }
- }, [selectedConnType, updateBleDeviceList, updateSerialDeviceList]);
-
- React.useEffect(() => {
- const connectionMethod = localStorage.getItem('connectionMethod');
-
- switch (connectionMethod) {
- case 'serial':
- setConnection(new ISerialConnection());
- //show connection dialogue
- break;
- case 'bluetooth':
- setConnection(new IBLEConnection());
- //show connection dialogue
- break;
- default:
- setConnection(new IHTTPConnection());
- void connection.connect({
+ dispatch(
+ setConnectionParams({
+ type: connType.HTTP,
+ params: {
address: connectionUrl,
tls: false,
receiveBatchRequests: false,
fetchInterval: 2000,
- });
- break;
+ },
+ }),
+ );
+ void setConnection(connType.HTTP);
+ }, [dispatch]);
+
+ React.useEffect(() => {
+ if (state.ready) {
+ dispatch(closeConnectionModal());
}
- SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
- }, [hostOverrideEnabled, hostOverride]);
+ }, [state.ready, dispatch]);
return (
{
dispatch(closeConnectionModal());
}}
>
-
- {ready ? (
-
- ) : (
-
-
+
+
+
+
+ {`Connected to: ${
+ state.nodes.find(
+ (node) => node.number === state.radio.hardware.myNodeNum,
+ )?.user?.longName ?? 'Unknown'
+ }`}
+
+
{`Via: ${connType[appState.connType]}`}
- )}
+
+ {state.deviceStatus ===
+ Types.DeviceStatusEnum.DEVICE_DISCONNECTED ? (
+ => {
+ await setConnection();
+ }}
+ >
+ Connect
+
+ ) : (
+ => {
+ await connection.disconnect();
+ cleanupListeners();
+ }}
+ >
+ Disconnect
+
+ )}
+
+
+
diff --git a/src/components/FormFooter.tsx b/src/components/FormFooter.tsx
index d9562da8..003b7073 100644
--- a/src/components/FormFooter.tsx
+++ b/src/components/FormFooter.tsx
@@ -5,9 +5,9 @@ import { FiSave, FiXCircle } from 'react-icons/fi';
import { IconButton } from './generic/IconButton';
export interface FormFooterProps {
- dirty: boolean;
- clearAction: () => void;
- saveAction: () => void;
+ dirty?: boolean;
+ clearAction?: () => void;
+ saveAction?: () => void;
}
export const FormFooter = ({
@@ -21,13 +21,13 @@ export const FormFooter = ({
icon={
}
disabled={!dirty}
onClick={(): void => {
- clearAction();
+ clearAction && clearAction();
}}
/>
{
- saveAction();
+ saveAction && saveAction();
}}
icon={ }
/>
diff --git a/src/components/LoraConfig.tsx b/src/components/LoraConfig.tsx
deleted file mode 100644
index c57ffc37..00000000
--- a/src/components/LoraConfig.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import React from 'react';
-
-import { useForm } from 'react-hook-form';
-
-import { Card } from '@components/generic/Card';
-import { Checkbox } from '@components/generic/form/Checkbox';
-import { Input } from '@components/generic/form/Input';
-import { Loading } from '@components/generic/Loading';
-import { connection } from '@core/connection';
-import { Protobuf } from '@meshtastic/meshtasticjs';
-
-export interface LoraConfigProps {
- channel: Protobuf.Channel;
-}
-
-export const LoraConfig = ({ channel }: LoraConfigProps): JSX.Element => {
- const [loading, setLoading] = React.useState(false);
-
- const { register, handleSubmit } = useForm<{
- enabled: boolean;
- settings: {
- name: string;
- bandwidth?: number;
- codingRate?: number;
- spreadFactor?: number;
- downlinkEnabled?: boolean;
- uplinkEnabled?: boolean;
- txPower?: number;
- psk?: string;
- };
- }>({
- defaultValues: {
- enabled:
- channel.role ===
- (Protobuf.Channel_Role.PRIMARY || Protobuf.Channel_Role.SECONDARY)
- ? true
- : false,
- settings: {
- name: channel.settings?.name,
- bandwidth: channel.settings?.bandwidth,
- codingRate: channel.settings?.codingRate,
- spreadFactor: channel.settings?.spreadFactor,
- downlinkEnabled: channel.settings?.downlinkEnabled,
- uplinkEnabled: channel.settings?.uplinkEnabled,
- txPower: channel.settings?.txPower,
- psk: new TextDecoder().decode(channel.settings?.psk),
- },
- },
- });
-
- const onSubmit = handleSubmit(async (data) => {
- setLoading(true);
- const adminChannel = Protobuf.Channel.create({
- role: data.enabled
- ? Protobuf.Channel_Role.SECONDARY
- : Protobuf.Channel_Role.DISABLED,
- index: channel.index,
- settings: {
- ...data.settings,
- psk: new TextEncoder().encode(data.settings.psk),
- },
- });
-
- await connection.setChannel(adminChannel, (): Promise => {
- setLoading(false);
- return Promise.resolve();
- });
- });
-
- return (
-
- {loading && }
-
- {/* TODO: get gap working */}
-
-
-
- );
-};
diff --git a/src/components/connection/BLE.tsx b/src/components/connection/BLE.tsx
new file mode 100644
index 00000000..5f3edb20
--- /dev/null
+++ b/src/components/connection/BLE.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+
+import { FiCheck } from 'react-icons/fi';
+
+import { connType } from '@app/core/slices/appSlice';
+import { Button } from '@components/generic/Button';
+import { IconButton } from '@components/generic/IconButton';
+import { ble, setConnection } from '@core/connection';
+
+export const BLE = (): JSX.Element => {
+ const [bleDevices, setBleDevices] = React.useState([]);
+
+ const updateBleDeviceList = React.useCallback(async (): Promise => {
+ const devices = await ble.getDevices();
+ setBleDevices(devices);
+ }, []);
+
+ React.useEffect(() => {
+ void updateBleDeviceList();
+ }, [updateBleDeviceList]);
+
+ return (
+
+
+
+ Refresh List
+
+ => {
+ await ble.getDevice();
+ }}
+ >
+ New Device
+
+
+
+
Previously connected devices
+ {bleDevices.map((device, index) => (
+
=> {
+ await setConnection(connType.BLE, {
+ device: device,
+ });
+ }}
+ className="flex justify-between p-2 bg-gray-700 rounded-md"
+ key={index}
+ >
+
{device.name}
+
=> {
+ await setConnection(connType.BLE, {
+ device: device,
+ });
+ }}
+ icon={ }
+ />
+
+ ))}
+
+
+ );
+};
diff --git a/src/components/connection/HTTP.tsx b/src/components/connection/HTTP.tsx
new file mode 100644
index 00000000..a5996437
--- /dev/null
+++ b/src/components/connection/HTTP.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+
+import { Checkbox } from '@components/generic/form/Checkbox';
+import { Input } from '@components/generic/form/Input';
+import { Select } from '@components/generic/form/Select';
+import { connectionUrl } from '@core/connection';
+
+export const HTTP = (): JSX.Element => {
+ const [httpIpSource, setHttpIpSource] = React.useState<'local' | 'remote'>(
+ 'local',
+ );
+
+ return (
+
+ {
+ setHttpIpSource(e.target.value as 'local' | 'remote');
+ }}
+ />
+ {httpIpSource === 'local' ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/src/components/connection/Serial.tsx b/src/components/connection/Serial.tsx
new file mode 100644
index 00000000..58dbb6dd
--- /dev/null
+++ b/src/components/connection/Serial.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+
+import { FiCheck } from 'react-icons/fi';
+import JSONPretty from 'react-json-pretty';
+
+import { Button } from '@components/generic/Button';
+import { IconButton } from '@components/generic/IconButton';
+import { serial, setConnection } from '@core/connection';
+import { connType } from '@core/slices/appSlice';
+
+export const Serial = (): JSX.Element => {
+ const [serialDevices, setSerialDevices] = React.useState([]);
+
+ const updateSerialDeviceList = React.useCallback(async (): Promise => {
+ const devices = await serial.getPorts();
+ setSerialDevices(devices);
+ }, []);
+
+ React.useEffect(() => {
+ void updateSerialDeviceList();
+ }, [updateSerialDeviceList]);
+
+ return (
+
+
+
+ Refresh List
+
+ => {
+ console.log(await serial.getPort());
+ }}
+ >
+ New Device
+
+
+
+
Previously connected devices
+ {serialDevices.map((device, index) => (
+
+
+ {device.getInfo().usbProductId}
+ {device.getInfo().usbVendorId}
+
+
=> {
+ await setConnection(connType.SERIAL, {
+ // @ts-ignore tmp
+ device: device,
+ });
+ }}
+ icon={ }
+ />
+
+
+ ))}
+
+
+ );
+};
diff --git a/src/components/generic/Modal.tsx b/src/components/generic/Modal.tsx
index 1ee81f48..a90aef6a 100644
--- a/src/components/generic/Modal.tsx
+++ b/src/components/generic/Modal.tsx
@@ -3,13 +3,21 @@ import type React from 'react';
import { useAppSelector } from '@app/hooks/redux';
import { Dialog } from '@headlessui/react';
-export interface ModalProps {
+type DefaultDivProps = JSX.IntrinsicElements['div'];
+
+export interface ModalProps extends DefaultDivProps {
children: React.ReactNode;
open: boolean;
onClose: () => void;
}
-export const Modal = ({ children, open, onClose }: ModalProps): JSX.Element => {
+export const Modal = ({
+ children,
+ open,
+ onClose,
+ className,
+ ...props
+}: ModalProps): JSX.Element => {
const darkMode = useAppSelector((state) => state.app.darkMode);
return (
<>
@@ -27,7 +35,7 @@ export const Modal = ({ children, open, onClose }: ModalProps): JSX.Element => {
>
-
diff --git a/src/components/menu/buttons/DeviceStatus.tsx b/src/components/menu/buttons/DeviceStatus.tsx
index 0d9ea68c..31002021 100644
--- a/src/components/menu/buttons/DeviceStatus.tsx
+++ b/src/components/menu/buttons/DeviceStatus.tsx
@@ -9,7 +9,7 @@ import { Types } from '@meshtastic/meshtasticjs';
export const DeviceStatus = (): JSX.Element => {
const dispatch = useAppDispatch();
- const deviceStatus = useAppSelector((state) => state.meshtastic.deviceStatus);
+ const state = useAppSelector((state) => state.meshtastic);
const ready = useAppSelector((state) => state.meshtastic.ready);
return (
@@ -27,18 +27,22 @@ export const DeviceStatus = (): JSX.Element => {
[
Types.DeviceStatusEnum.DEVICE_CONNECTED,
Types.DeviceStatusEnum.DEVICE_CONFIGURED,
- ].includes(deviceStatus)
+ ].includes(state.deviceStatus)
? 'bg-green-400'
: [
Types.DeviceStatusEnum.DEVICE_CONNECTING,
Types.DeviceStatusEnum.DEVICE_RECONNECTING,
Types.DeviceStatusEnum.DEVICE_CONFIGURING,
- ].includes(deviceStatus)
+ ].includes(state.deviceStatus)
? 'bg-yellow-400'
: 'bg-gray-400'
}`}
>
-
{Types.DeviceStatusEnum[deviceStatus]}
+
+ {state.nodes.find(
+ (node) => node.number === state.radio.hardware.myNodeNum,
+ )?.user?.longName ?? 'Unknown'}
+
{ready ? (
diff --git a/src/core/connection.ts b/src/core/connection.ts
index f1166f6f..520059c6 100644
--- a/src/core/connection.ts
+++ b/src/core/connection.ts
@@ -1,3 +1,4 @@
+import { connType } from '@core/slices/appSlice';
import {
addChannel,
addMessage,
@@ -16,6 +17,7 @@ import {
IHTTPConnection,
ISerialConnection,
Protobuf,
+ SettingsManager,
Types,
} from '@meshtastic/meshtasticjs';
@@ -34,14 +36,42 @@ export const connectionUrl = state.hostOverrideEnabled
export const ble = new IBLEConnection();
export const serial = new ISerialConnection();
-export const setConnection = (conn: connectionType): void => {
+export const setConnection = async (conn: connType): Promise
=> {
+ await connection.disconnect();
cleanupListeners();
- connection = conn;
-
+ 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;
+ 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;
+ }
};
-const cleanupListeners = (): void => {
+export const cleanupListeners = (): void => {
connection.onDeviceStatus.cancelAll();
connection.onMyNodeInfo.cancelAll();
connection.onUserPacket.cancelAll();
@@ -53,6 +83,8 @@ const cleanupListeners = (): void => {
};
const registerListeners = (): void => {
+ SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
+
connection.onDeviceStatus.subscribe((status) => {
store.dispatch(setDeviceStatus(status));
diff --git a/src/core/slices/appSlice.ts b/src/core/slices/appSlice.ts
index c2a5550b..a5dc5dc5 100644
--- a/src/core/slices/appSlice.ts
+++ b/src/core/slices/appSlice.ts
@@ -1,13 +1,26 @@
+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;
connectionModalOpen: boolean;
darkMode: boolean;
currentPage: currentPageName;
+ connType: connType;
+ connectionParams: {
+ BLE: Types.BLEConnectionParameters;
+ HTTP: Types.HTTPConnectionParameters;
+ SERIAL: Types.SerialConnectionParameters;
+ };
}
const initialState: AppState = {
@@ -15,6 +28,17 @@ const initialState: AppState = {
connectionModalOpen: true,
darkMode: localStorage.getItem('darkMode') === 'true' ?? false,
currentPage: 'messages',
+ connType: connType.HTTP,
+ connectionParams: {
+ BLE: {},
+ HTTP: {
+ address: 'http://meshtastic.local/',
+ tls: false,
+ receiveBatchRequests: false,
+ fetchInterval: 2000,
+ },
+ SERIAL: {},
+ },
};
export const appSlice = createSlice({
@@ -40,6 +64,28 @@ export const appSlice = createSlice({
setCurrentPage(state, action: PayloadAction) {
state.currentPage = action.payload;
},
+ setConnType(state, action: PayloadAction) {
+ state.connType = action.payload;
+ },
+ setConnectionParams(
+ state,
+ action: PayloadAction<{
+ type: connType;
+ params: Types.ConnectionParameters;
+ }>,
+ ) {
+ switch (action.payload.type) {
+ case connType.BLE:
+ state.connectionParams.BLE = action.payload.params;
+ break;
+ case connType.HTTP:
+ state.connectionParams.HTTP = action.payload.params;
+ break;
+ case connType.SERIAL:
+ state.connectionParams.SERIAL = action.payload.params;
+ break;
+ }
+ },
},
});
@@ -50,6 +96,8 @@ export const {
closeConnectionModal,
setDarkModeEnabled,
setCurrentPage,
+ setConnType,
+ setConnectionParams,
} = appSlice.actions;
export default appSlice.reducer;
diff --git a/src/pages/settings/Channels.tsx b/src/pages/settings/Channels.tsx
index f12760c8..3d7bd580 100644
--- a/src/pages/settings/Channels.tsx
+++ b/src/pages/settings/Channels.tsx
@@ -1,17 +1,23 @@
import React from 'react';
-import { FiCode, FiMenu, FiSave } from 'react-icons/fi';
+import { useForm, useWatch } from 'react-hook-form';
+import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { Channel } from '@components/Channel';
+import { FormFooter } from '@components/FormFooter';
import { Button } from '@components/generic/Button';
import { Card } from '@components/generic/Card';
import { Cover } from '@components/generic/Cover';
+import { Checkbox } from '@components/generic/form/Checkbox';
+import { Input } from '@components/generic/form/Input';
+import { Select } from '@components/generic/form/Select';
import { IconButton } from '@components/generic/IconButton';
-import { LoraConfig } from '@components/LoraConfig';
+import { Loading } from '@components/generic/Loading';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
+import { Protobuf } from '@meshtastic/meshtasticjs';
export interface ChannelsProps {
navOpen?: boolean;
@@ -23,7 +29,122 @@ export const Channels = ({
setNavOpen,
}: ChannelsProps): JSX.Element => {
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
+ const channel = channels[0].channel;
+
const [debug, setDebug] = React.useState(false);
+ const [loading, setLoading] = React.useState(false);
+
+ enum PresetName {
+ 'Long Slow',
+ 'Long Alt',
+ 'Medium',
+ 'Short Fast',
+ }
+
+ const { register, handleSubmit, reset, formState, control } = useForm<{
+ simple: boolean;
+ preset?: PresetName;
+ enabled: boolean;
+ settings: {
+ name: string;
+ bandwidth?: number;
+ codingRate?: number;
+ spreadFactor?: number;
+ downlinkEnabled?: boolean;
+ uplinkEnabled?: boolean;
+ txPower?: number;
+ psk?: string;
+ };
+ }>({
+ defaultValues: {
+ simple: true,
+ preset: PresetName['Long Slow'],
+ enabled:
+ channel.role ===
+ (Protobuf.Channel_Role.PRIMARY || Protobuf.Channel_Role.SECONDARY)
+ ? true
+ : false,
+ settings: {
+ name: channel.settings?.name,
+ bandwidth: channel.settings?.bandwidth,
+ codingRate: channel.settings?.codingRate,
+ spreadFactor: channel.settings?.spreadFactor,
+ downlinkEnabled: channel.settings?.downlinkEnabled,
+ uplinkEnabled: channel.settings?.uplinkEnabled,
+ txPower: channel.settings?.txPower,
+ psk: new TextDecoder().decode(channel.settings?.psk),
+ },
+ },
+ });
+
+ const presets = [
+ {
+ name: PresetName['Long Slow'],
+ config: {
+ bandwidth: 125,
+ codingRate: 8, // 4/8
+
+ spreadFactor: 12, // 4096chips/symbol
+ },
+ },
+ {
+ name: PresetName['Long Alt'],
+ config: {
+ bandwidth: 31.25,
+ codingRate: 8, // 4/8
+ spreadFactor: 9, // 512chips/symbol,
+ },
+ },
+ {
+ name: PresetName['Medium'],
+ config: {
+ bandwidth: 125,
+ codingRate: 5, // 4/5
+ spreadFactor: 7, // 128chips/symbol,
+ },
+ },
+ {
+ name: PresetName['Short Fast'],
+ config: {
+ bandwidth: 500,
+ codingRate: 5, // 4/5
+ spreadFactor: 7, // 128chips/symbol,
+ },
+ },
+ ];
+
+ const watchSimple = useWatch({
+ control,
+ name: 'simple',
+ defaultValue: true,
+ });
+
+ const onSubmit = handleSubmit(async (data) => {
+ setLoading(true);
+ console.log(data);
+ const selectedPreset = data.simple
+ ? presets.find((preset) => preset.name === data.preset)?.config
+ : undefined;
+
+ const adminChannel = Protobuf.Channel.create({
+ role: data.enabled
+ ? Protobuf.Channel_Role.SECONDARY
+ : Protobuf.Channel_Role.DISABLED,
+ index: channel.index,
+ settings: {
+ ...data.settings,
+ ...selectedPreset,
+ psk: new TextEncoder().encode(data.settings.psk),
+ },
+ });
+
+ console.log(adminChannel);
+
+ // await connection.setChannel(adminChannel, (): Promise => {
+ // setLoading(false);
+ // return Promise.resolve();
+ // });
+ });
return (
}
footer={
- }
- active
- border
- >
- Confirm
-
+
}
>
- {channels[0] &&
}
+ {channel && (
+
+ {loading && }
+
+ {/* TODO: get gap working */}
+
+ // setSimpleChannelSettings(e.target.checked)
+ // }
+ />
+
+
+
+ )}
} />
diff --git a/todo.txt b/todo.txt
index 377f28f3..1f480a85 100644
--- a/todo.txt
+++ b/todo.txt
@@ -9,6 +9,8 @@ form prefix should be located in the input (absolute?)
form suffix should focus input
reset store on new connection
redux actions seem to be dispatched twice
+add qr generator in channel editor
+no save button for channel config (bw,sf,cr,tx etc)
meshtastic.js
- fix entering device-reconnecting state and not re-connecting despite packets being received
\ No newline at end of file