Browse Source

1.3.x updates

pull/31/head
Sacha Weatherstone 4 years ago
parent
commit
6ba94fcadc
No known key found for this signature in database GPG Key ID: 7AB2D7E206124B31
  1. 42
      package.json
  2. 1938
      pnpm-lock.yaml
  3. 110
      src/components/layout/Sidebar/Settings/Channels.tsx
  4. 38
      src/components/layout/Sidebar/Settings/Device.tsx
  5. 32
      src/components/layout/Sidebar/Settings/Display.tsx
  6. 33
      src/components/layout/Sidebar/Settings/Index.tsx
  7. 146
      src/components/layout/Sidebar/Settings/LoRa.tsx
  8. 75
      src/components/layout/Sidebar/Settings/Position.tsx
  9. 60
      src/components/layout/Sidebar/Settings/Power.tsx
  10. 8
      src/components/layout/Sidebar/Settings/User.tsx
  11. 42
      src/components/layout/Sidebar/Settings/WiFi.tsx
  12. 48
      src/components/layout/Sidebar/Settings/modules/CannedMessage.tsx
  13. 47
      src/components/layout/Sidebar/Settings/modules/ExternalNotifications.tsx
  14. 42
      src/components/layout/Sidebar/Settings/modules/MQTT.tsx
  15. 41
      src/components/layout/Sidebar/Settings/modules/RangeTest.tsx
  16. 50
      src/components/layout/Sidebar/Settings/modules/Serial.tsx
  17. 45
      src/components/layout/Sidebar/Settings/modules/StoreForward.tsx
  18. 48
      src/components/layout/Sidebar/Settings/modules/Telemetry.tsx
  19. 6
      src/components/menu/BottomNav.tsx
  20. 61
      src/core/connection.ts
  21. 125
      src/core/slices/meshtasticSlice.ts
  22. 87
      src/pages/Extensions/Debug.tsx
  23. 6
      src/pages/Extensions/Logs.tsx
  24. 2
      tsconfig.json

42
package.json

@ -22,22 +22,22 @@
"dependencies": { "dependencies": {
"@emeraldpay/hashicon-react": "^0.5.2", "@emeraldpay/hashicon-react": "^0.5.2",
"@meshtastic/eslint-config": "^1.0.8", "@meshtastic/eslint-config": "^1.0.8",
"@meshtastic/meshtasticjs": "^0.6.55", "@meshtastic/meshtasticjs": "^0.6.63",
"@reduxjs/toolkit": "^1.8.1", "@reduxjs/toolkit": "^1.8.1",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"framer-motion": "^6.2.10", "framer-motion": "^6.3.3",
"mapbox-gl": "^2.8.0", "mapbox-gl": "^2.8.2",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"react": "^18.0.0", "react": "^18.1.0",
"react-dom": "^18.0.0", "react-dom": "^18.1.0",
"react-error-boundary": "^3.1.4", "react-error-boundary": "^3.1.4",
"react-flow-renderer": "^10.1.0", "react-flow-renderer": "^10.2.1",
"react-hook-form": "^7.29.0", "react-hook-form": "^7.30.0",
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"react-json-pretty": "^2.2.0", "react-json-pretty": "^2.2.0",
"react-multi-select-component": "^4.2.4", "react-multi-select-component": "^4.2.5",
"react-redux": "^7.2.8", "react-redux": "^8.0.1",
"react-use-clipboard": "^1.0.8", "react-use-clipboard": "^1.0.8",
"rfc4648": "^1.5.1", "rfc4648": "^1.5.1",
"swr": "^1.3.0", "swr": "^1.3.0",
@ -47,25 +47,25 @@
"vite-plugin-environment": "^1.1.1" "vite-plugin-environment": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@hookform/devtools": "^4.1.0", "@types/chrome": "^0.0.184",
"@types/chrome": "^0.0.181", "@types/mapbox-gl": "^2.7.1",
"@types/mapbox-gl": "^2.6.4", "@types/node": "^17.0.31",
"@types/react": "^18.0.3", "@types/react": "^18.0.9",
"@types/react-dom": "^18.0.0", "@types/react-dom": "^18.0.3",
"@types/w3c-web-serial": "^1.0.2", "@types/w3c-web-serial": "^1.0.2",
"@types/web-bluetooth": "^0.0.14", "@types/web-bluetooth": "^0.0.14",
"@vitejs/plugin-react": "^1.3.0", "@vitejs/plugin-react": "^1.3.2",
"autoprefixer": "^10.4.4", "autoprefixer": "^10.4.7",
"gzipper": "^7.1.0", "gzipper": "^7.1.0",
"postcss": "^8.4.12", "postcss": "^8.4.13",
"rollup-plugin-visualizer": "^5.6.0", "rollup-plugin-visualizer": "^5.6.0",
"tailwindcss": "^3.0.24", "tailwindcss": "^3.0.24",
"tar": "^6.1.11", "tar": "^6.1.11",
"typescript": "^4.6.3", "typescript": "^4.6.4",
"unimported": "^1.19.1", "unimported": "^1.20.0",
"vite": "^2.9.1", "vite": "^2.9.8",
"vite-plugin-cdn-import": "^0.3.5", "vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-pwa": "^0.11.13", "vite-plugin-pwa": "^0.12.0",
"workbox-window": "^6.5.3" "workbox-window": "^6.5.3"
} }
} }

1938
pnpm-lock.yaml

File diff suppressed because it is too large

110
src/components/layout/Sidebar/Settings/Channels.tsx

@ -1,110 +0,0 @@
import type React from 'react';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { Checkbox } from '@components/generic/form/Checkbox';
import { Form } from '@components/generic/form/Form';
import { Input } from '@components/generic/form/Input';
import { Select } from '@components/generic/form/Select';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs';
export const Channels = (): JSX.Element => {
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
const adminChannel =
channels.find(
(channel) => channel.role === Protobuf.Channel_Role.PRIMARY,
) ?? channels[0];
const [usePreset, setUsePreset] = useState(true);
const [loading, setLoading] = useState(false);
const [selectedChannel, setSelectedChannel] = useState<
Protobuf.Channel | undefined
>();
const { register, handleSubmit, reset, formState } = useForm<
DeepOmit<Protobuf.Channel, 'psk'>
>({
defaultValues: {
...adminChannel,
},
});
const onSubmit = handleSubmit(async (data) => {
setLoading(true);
const channelData = Protobuf.Channel.create({
...data,
settings: {
...data.settings,
psk: adminChannel.settings?.psk,
},
});
await connection.setChannel(channelData, (): Promise<void> => {
reset({ ...data });
setLoading(false);
return Promise.resolve();
});
});
return (
<>
{adminChannel && (
<>
<Checkbox
checked={usePreset}
label="Use Presets"
onChange={(e): void => setUsePreset(e.target.checked)}
/>
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
{usePreset ? (
<Select
label="Preset"
optionsEnum={Protobuf.ChannelSettings_ModemConfig}
{...register('settings.modemConfig', {
valueAsNumber: true,
})}
/>
) : (
<>
<Input
label="Bandwidth"
type="number"
suffix="MHz"
{...register('settings.bandwidth', {
valueAsNumber: true,
})}
/>
<Input
label="Spread Factor"
type="number"
suffix="CPS"
min={7}
max={12}
{...register('settings.spreadFactor', {
valueAsNumber: true,
})}
/>
<Input
label="Coding Rate"
type="number"
{...register('settings.codingRate', {
valueAsNumber: true,
})}
/>
</>
)}
<Input
label="Transmit Power"
type="number"
suffix="dBm"
{...register('settings.txPower', { valueAsNumber: true })}
/>
</Form>
</>
)}
</>
);
};

38
src/components/layout/Sidebar/Settings/Device.tsx

@ -11,26 +11,34 @@ import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs'; import { Protobuf } from '@meshtastic/meshtasticjs';
export const Device = (): JSX.Element => { export const Device = (): JSX.Element => {
const preferences = useAppSelector( const deviceConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.config.device,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState, reset } = const { register, handleSubmit, formState, reset } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.Config_DeviceConfig>({
defaultValues: preferences, defaultValues: deviceConfig,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(deviceConfig);
}, [reset, preferences]); }, [reset, deviceConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => { void connection.setConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'device',
}); device: data,
},
},
async () => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
@ -39,10 +47,14 @@ export const Device = (): JSX.Element => {
{...register('serialDisabled')} {...register('serialDisabled')}
/> />
<Checkbox label="Factory Reset Device" {...register('factoryReset')} /> <Checkbox label="Factory Reset Device" {...register('factoryReset')} />
<Checkbox label="Debug Log Enabled" {...register('debugLogEnabled')} /> <Checkbox label="Enabled Debug Log" {...register('debugLogEnabled')} />
<Checkbox
label="Disable Serial COnsole"
{...register('serialDisabled')}
/>
<Select <Select
label="Role" label="Role"
optionsEnum={Protobuf.Role} optionsEnum={Protobuf.Config_DeviceConfig_Role}
{...register('role', { valueAsNumber: true })} {...register('role', { valueAsNumber: true })}
/> />
</Form> </Form>

32
src/components/layout/Sidebar/Settings/Display.tsx

@ -11,26 +11,34 @@ import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs'; import { Protobuf } from '@meshtastic/meshtasticjs';
export const Display = (): JSX.Element => { export const Display = (): JSX.Element => {
const preferences = useAppSelector( const displayConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.config.display,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState, reset } = const { register, handleSubmit, formState, reset } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.Config_DisplayConfig>({
defaultValues: preferences, defaultValues: displayConfig,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(displayConfig);
}, [reset, preferences]); }, [reset, displayConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => { void connection.setConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'display',
}); display: data,
},
},
async () => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
@ -48,7 +56,7 @@ export const Display = (): JSX.Element => {
/> />
<Select <Select
label="GPS Display Units" label="GPS Display Units"
optionsEnum={Protobuf.GpsCoordinateFormat} optionsEnum={Protobuf.Config_DisplayConfig_GpsCoordinateFormat}
{...register('gpsFormat', { valueAsNumber: true })} {...register('gpsFormat', { valueAsNumber: true })}
/> />
</Form> </Form>

33
src/components/layout/Sidebar/Settings/Index.tsx

@ -22,10 +22,9 @@ import {
import { CollapsibleSection } from '@components/generic/Sidebar/CollapsibleSection'; import { CollapsibleSection } from '@components/generic/Sidebar/CollapsibleSection';
import { ExternalSection } from '@components/generic/Sidebar/ExternalSection'; import { ExternalSection } from '@components/generic/Sidebar/ExternalSection';
import { SidebarOverlay } from '@components/generic/Sidebar/SidebarOverlay'; import { SidebarOverlay } from '@components/generic/Sidebar/SidebarOverlay';
import { Channels } from '@components/layout/Sidebar/Settings/Channels';
import { ChannelsGroup } from '@components/layout/Sidebar/Settings/channels/ChannelsGroup'; import { ChannelsGroup } from '@components/layout/Sidebar/Settings/channels/ChannelsGroup';
import { Display } from '@components/layout/Sidebar/Settings/Display'; import { Display } from '@components/layout/Sidebar/Settings/Display';
import { GPS } from '@components/layout/Sidebar/Settings/GPS'; import { Position } from '@app/components/layout/Sidebar/Settings/Position';
import { Interface } from '@components/layout/Sidebar/Settings/Interface'; import { Interface } from '@components/layout/Sidebar/Settings/Interface';
import { LoRa } from '@components/layout/Sidebar/Settings/LoRa'; import { LoRa } from '@components/layout/Sidebar/Settings/LoRa';
import { CannedMessage } from '@components/layout/Sidebar/Settings/modules/CannedMessage'; import { CannedMessage } from '@components/layout/Sidebar/Settings/modules/CannedMessage';
@ -48,14 +47,9 @@ export interface SettingsProps {
export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => { export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
const [modulesOpen, setModulesOpen] = useState(false); const [modulesOpen, setModulesOpen] = useState(false);
const [channelsOpen, setChannelsOpen] = useState(false); const [channelsOpen, setChannelsOpen] = useState(false);
const { const moduleConfig = useAppSelector(
rangeTestModuleEnabled, (state) => state.meshtastic.radio.moduleConfig,
extNotificationModuleEnabled, );
serialModuleEnabled,
storeForwardModuleEnabled,
mqttDisabled,
cannedMessageModuleEnabled,
} = useAppSelector((state) => state.meshtastic.radio.preferences);
const hasGps = true; const hasGps = true;
const hasWifi = true; const hasWifi = true;
@ -76,8 +70,8 @@ export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
<CollapsibleSection icon={<FiSmartphone />} title="Device"> <CollapsibleSection icon={<FiSmartphone />} title="Device">
<WiFi /> <WiFi />
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection icon={<FiMapPin />} title="GPS"> <CollapsibleSection icon={<FiMapPin />} title="Position">
<GPS /> <Position />
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection icon={<FiPower />} title="Power"> <CollapsibleSection icon={<FiPower />} title="Power">
<Power /> <Power />
@ -91,9 +85,6 @@ export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
<CollapsibleSection icon={<FiRss />} title="LoRa"> <CollapsibleSection icon={<FiRss />} title="LoRa">
<LoRa /> <LoRa />
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection icon={<FiLayers />} title="Primary Channel">
<Channels />
</CollapsibleSection>
<ExternalSection <ExternalSection
onClick={(): void => { onClick={(): void => {
setChannelsOpen(true); setChannelsOpen(true);
@ -125,35 +116,35 @@ export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
<CollapsibleSection <CollapsibleSection
icon={<FiWifi />} icon={<FiWifi />}
title="MQTT" title="MQTT"
status={!mqttDisabled} status={!moduleConfig.mqtt.disabled}
> >
<MQTT /> <MQTT />
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection <CollapsibleSection
icon={<FiAlignLeft />} icon={<FiAlignLeft />}
title="Serial" title="Serial"
status={serialModuleEnabled} status={moduleConfig.serial.enabled}
> >
<SerialSettingsPanel /> <SerialSettingsPanel />
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection <CollapsibleSection
icon={<FiBell />} icon={<FiBell />}
title="External Notifications" title="External Notifications"
status={extNotificationModuleEnabled} status={moduleConfig.extNotification.enabled}
> >
<ExternalNotificationsSettingsPlanel /> <ExternalNotificationsSettingsPlanel />
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection <CollapsibleSection
icon={<FiFastForward />} icon={<FiFastForward />}
title="Store & Forward" title="Store & Forward"
status={storeForwardModuleEnabled} status={moduleConfig.storeForward.enabled}
> >
<StoreForwardSettingsPanel /> <StoreForwardSettingsPanel />
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection <CollapsibleSection
icon={<FiRss />} icon={<FiRss />}
title="Range Test" title="Range Test"
status={rangeTestModuleEnabled} status={moduleConfig.rangeTest.enabled}
> >
<RangeTestSettingsPanel /> <RangeTestSettingsPanel />
</CollapsibleSection> </CollapsibleSection>
@ -167,7 +158,7 @@ export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
<CollapsibleSection <CollapsibleSection
icon={<FiMessageSquare />} icon={<FiMessageSquare />}
title="Canned Message" title="Canned Message"
status={cannedMessageModuleEnabled} status={moduleConfig.cannedMessage.enabled}
> >
<CannedMessage /> <CannedMessage />
</CollapsibleSection> </CollapsibleSection>

146
src/components/layout/Sidebar/Settings/LoRa.tsx

@ -12,52 +12,130 @@ import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs'; import { Protobuf } from '@meshtastic/meshtasticjs';
export const LoRa = (): JSX.Element => { export const LoRa = (): JSX.Element => {
const preferences = useAppSelector( const myNodeNum = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.hardware.myNodeNum,
);
const loraConfig = useAppSelector(
(state) => state.meshtastic.radio.config.lora,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [usePreset, setUsePreset] = useState(true);
// const { register, handleSubmit, formState, reset } =
// useForm<Protobuf.RadioConfig_UserPreferences>({
// defaultValues: preferences,
// });
const { register, handleSubmit, formState, reset } = const { register, handleSubmit, formState, reset } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.Config_LoRaConfig>({
defaultValues: preferences, defaultValues: loraConfig,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(loraConfig);
}, [reset, preferences]); }, [reset, loraConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => {
reset({ ...data }); const packet = Protobuf.AdminMessage.create({
setLoading(false); variant: {
await Promise.resolve(); oneofKind: 'setConfig',
setConfig: {
payloadVariant: {
oneofKind: 'lora',
lora: data,
},
},
},
}); });
void connection.sendPacket(
Protobuf.AdminMessage.toBinary(packet),
Protobuf.PortNum.ADMIN_APP,
myNodeNum,
true,
0,
true,
false,
async (num) => {
return await Promise.resolve();
},
);
// void connection.setPreferences(data, async () => {
// reset({ ...data });
// setLoading(false);
// await Promise.resolve();
// });
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <>
<Input <Checkbox
label="Hop Count" checked={usePreset}
type="number" label="Use Presets"
suffix="Hops" onChange={(e): void => setUsePreset(e.target.checked)}
{...register('hopLimit', { valueAsNumber: true })}
/>
<Checkbox label="Transmit Disabled" {...register('isLoraTxDisabled')} />
<Select
label="Operating Role"
optionsEnum={Protobuf.Role}
{...register('role')}
/>
<Input
label="Frequency Offset"
type="number"
suffix="Hz"
{...register('frequencyOffset', { valueAsNumber: true })}
/>
<Select
label="Region"
optionsEnum={Protobuf.RegionCode}
{...register('region', { valueAsNumber: true })}
/> />
</Form> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
{usePreset ? (
<Select
label="Preset"
optionsEnum={Protobuf.Config_LoRaConfig_ModemPreset}
{...register('modemPreset', {
valueAsNumber: true,
})}
/>
) : (
<>
<Input
label="Bandwidth"
type="number"
suffix="MHz"
{...register('bandwidth', {
valueAsNumber: true,
})}
/>
<Input
label="Spread Factor"
type="number"
suffix="CPS"
min={7}
max={12}
{...register('spreadFactor', {
valueAsNumber: true,
})}
/>
<Input
label="Coding Rate"
type="number"
{...register('codingRate', {
valueAsNumber: true,
})}
/>
</>
)}
<Input
label="Transmit Power"
type="number"
suffix="dBm"
{...register('txPower', { valueAsNumber: true })}
/>
<Input
label="Hop Count"
type="number"
suffix="Hops"
{...register('hopLimit', { valueAsNumber: true })}
/>
<Checkbox label="Transmit Disabled" {...register('txDisabled')} />
<Input
label="Frequency Offset"
type="number"
suffix="Hz"
{...register('frequencyOffset', { valueAsNumber: true })}
/>
<Select
label="Region"
optionsEnum={Protobuf.Config_LoRaConfig_RegionCode}
{...register('region', { valueAsNumber: true })}
/>
</Form>
</>
); );
}; };

75
src/components/layout/Sidebar/Settings/GPS.tsx → src/components/layout/Sidebar/Settings/Position.tsx

@ -13,35 +13,44 @@ import { bitwiseDecode, bitwiseEncode } from '@core/utils/bitwise';
import { useAppSelector } from '@hooks/useAppSelector'; import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs'; import { Protobuf } from '@meshtastic/meshtasticjs';
export const GPS = (): JSX.Element => { export const Position = (): JSX.Element => {
const preferences = useAppSelector( const positionConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.config.position,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.Config_PositionConfig>({
defaultValues: { defaultValues: positionConfig,
...preferences, // defaultValues: {
positionBroadcastSecs: // ...preferences,
preferences.positionBroadcastSecs === 0 // positionBroadcastSecs:
? preferences.role === Protobuf.Role.Router // preferences.positionBroadcastSecs === 0
? 43200 // ? preferences.role === Protobuf.Role.Router
: 900 // ? 43200
: preferences.positionBroadcastSecs, // : 900
}, // : preferences.positionBroadcastSecs,
// },
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(positionConfig);
}, [reset, preferences]); }, [reset, positionConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => { void connection.setConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'position',
}); position: data,
},
},
async () => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
return ( return (
@ -90,12 +99,15 @@ export const GPS = (): JSX.Element => {
<div className="w-full"> <div className="w-full">
{label && <Label label={label} error={error?.message} />} {label && <Label label={label} error={error?.message} />}
<MultiSelect <MultiSelect
options={Object.entries(Protobuf.PositionFlags) options={Object.entries(
Protobuf.Config_PositionConfig_PositionFlags,
)
.filter((value) => typeof value[1] !== 'number') .filter((value) => typeof value[1] !== 'number')
.filter( .filter(
(value) => (value) =>
parseInt(value[0]) !== parseInt(value[0]) !==
Protobuf.PositionFlags.POS_UNDEFINED, Protobuf.Config_PositionConfig_PositionFlags
.POS_UNDEFINED,
) )
.map((value) => { .map((value) => {
return { return {
@ -103,14 +115,17 @@ export const GPS = (): JSX.Element => {
label: value[1].toString().replace('POS_', ''), label: value[1].toString().replace('POS_', ''),
}; };
})} })}
value={bitwiseDecode(value, Protobuf.PositionFlags).map( value={bitwiseDecode(
(flag) => { value,
return { Protobuf.Config_PositionConfig_PositionFlags,
value: flag, ).map((flag) => {
label: Protobuf.PositionFlags[flag].replace('POS_', ''), return {
}; value: flag,
}, label: Protobuf.Config_PositionConfig_PositionFlags[
)} flag
].replace('POS_', ''),
};
})}
onChange={(e: { value: number; label: string }[]): void => onChange={(e: { value: number; label: string }[]): void =>
onChange(bitwiseEncode(e.map((v) => v.value))) onChange(bitwiseEncode(e.map((v) => v.value)))
} }

60
src/components/layout/Sidebar/Settings/Power.tsx

@ -12,45 +12,59 @@ import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs'; import { Protobuf } from '@meshtastic/meshtasticjs';
export const Power = (): JSX.Element => { export const Power = (): JSX.Element => {
const preferences = useAppSelector( const powerConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.config.power,
);
const deviceConfig = useAppSelector(
(state) => state.meshtastic.radio.config.device,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState, reset } = const { register, handleSubmit, formState, reset } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.Config_PowerConfig>({
defaultValues: { defaultValues: powerConfig,
...preferences, // defaultValues: {
isLowPower: // ...preferences,
preferences.role === Protobuf.Role.Router // isLowPower:
? true // preferences.role === Protobuf.Role.Router
: preferences.isLowPower, // ? true
}, // : preferences.isLowPower,
// },
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(powerConfig);
}, [reset, preferences]); }, [reset, powerConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => { void connection.setConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'power',
}); power: data,
},
},
async () => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Select <Select
label="Charge current" label="Charge current"
optionsEnum={Protobuf.ChargeCurrent} optionsEnum={Protobuf.Config_PowerConfig_ChargeCurrent}
{...register('chargeCurrent', { valueAsNumber: true })} {...register('chargeCurrent', { valueAsNumber: true })}
/> />
<Checkbox <Checkbox
label="Powered by low power source (solar)" label="Powered by low power source (solar)"
disabled={preferences.role === Protobuf.Role.Router} disabled={
deviceConfig.role === Protobuf.Config_DeviceConfig_Role.Router
}
validationMessage={ validationMessage={
preferences.role === Protobuf.Role.Router deviceConfig.role === Protobuf.Config_DeviceConfig_Role.Router
? 'Enabled by default in router mode' ? 'Enabled by default in router mode'
: '' : ''
} }
@ -81,12 +95,6 @@ export const Power = (): JSX.Element => {
type="number" type="number"
{...register('phoneTimeoutSecs', { valueAsNumber: true })} {...register('phoneTimeoutSecs', { valueAsNumber: true })}
/> />
<Input
label="Phone SDS Timeout"
suffix="Seconds"
type="number"
{...register('phoneSdsTimeoutSec', { valueAsNumber: true })}
/>
<Input <Input
label="Mesh SDS Timeout" label="Mesh SDS Timeout"
suffix="Seconds" suffix="Seconds"

8
src/components/layout/Sidebar/Settings/User.tsx

@ -24,7 +24,6 @@ export const User = (): JSX.Element => {
longName: string; longName: string;
shortName: string; shortName: string;
isLicensed: boolean; isLicensed: boolean;
team: Protobuf.Team;
antAzimuth: number; antAzimuth: number;
antGainDbi: number; antGainDbi: number;
txPowerDbm: number; txPowerDbm: number;
@ -33,7 +32,6 @@ export const User = (): JSX.Element => {
longName: node?.data.user?.longName, longName: node?.data.user?.longName,
shortName: node?.data.user?.shortName, shortName: node?.data.user?.shortName,
isLicensed: node?.data.user?.isLicensed, isLicensed: node?.data.user?.isLicensed,
team: node?.data.user?.team,
antAzimuth: node?.data.user?.antAzimuth, antAzimuth: node?.data.user?.antAzimuth,
antGainDbi: node?.data.user?.antGainDbi, antGainDbi: node?.data.user?.antGainDbi,
txPowerDbm: node?.data.user?.txPowerDbm, txPowerDbm: node?.data.user?.txPowerDbm,
@ -45,7 +43,6 @@ export const User = (): JSX.Element => {
longName: node?.data.user?.longName, longName: node?.data.user?.longName,
shortName: node?.data.user?.shortName, shortName: node?.data.user?.shortName,
isLicensed: node?.data.user?.isLicensed, isLicensed: node?.data.user?.isLicensed,
team: node?.data.user?.team,
}); });
}, [reset, node]); }, [reset, node]);
@ -86,11 +83,6 @@ export const User = (): JSX.Element => {
disabled disabled
/> />
<Checkbox label="Licenced Operator?" {...register('isLicensed')} /> <Checkbox label="Licenced Operator?" {...register('isLicensed')} />
<Select
label="Team (DEPRECATED)"
optionsEnum={Protobuf.Team}
{...register('team', { valueAsNumber: true })}
/>
<Input <Input
label="Transmit Power" label="Transmit Power"
suffix="dBm" suffix="dBm"

42
src/components/layout/Sidebar/Settings/WiFi.tsx

@ -11,47 +11,51 @@ import { useAppSelector } from '@hooks/useAppSelector';
import type { Protobuf } from '@meshtastic/meshtasticjs'; import type { Protobuf } from '@meshtastic/meshtasticjs';
export const WiFi = (): JSX.Element => { export const WiFi = (): JSX.Element => {
const preferences = useAppSelector( const wifiConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.config.wifi,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.Config_WiFiConfig>({
defaultValues: preferences, defaultValues: wifiConfig,
}); });
const WifiApMode = useWatch({ const WifiApMode = useWatch({
control, control,
name: 'wifiApMode', name: 'apMode',
defaultValue: false, defaultValue: false,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(wifiConfig);
}, [reset, preferences]); }, [reset, wifiConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => { void connection.setConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'wifi',
}); wifi: data,
},
},
async () => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox label="Enable WiFi AP" {...register('wifiApMode')} /> <Checkbox label="Enable WiFi AP" {...register('apMode')} />
<Input <Input label="WiFi SSID" disabled={WifiApMode} {...register('ssid')} />
label="WiFi SSID"
disabled={WifiApMode}
{...register('wifiSsid')}
/>
<Input <Input
type="password" type="password"
autoComplete="off" autoComplete="off"
label="WiFi PSK" label="WiFi PSK"
disabled={WifiApMode} disabled={WifiApMode}
{...register('wifiPassword')} {...register('psk')}
/> />
</Form> </Form>
); );

48
src/components/layout/Sidebar/Settings/modules/CannedMessage.tsx

@ -12,13 +12,13 @@ import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs'; import { Protobuf } from '@meshtastic/meshtasticjs';
export const CannedMessage = (): JSX.Element => { export const CannedMessage = (): JSX.Element => {
const preferences = useAppSelector( const cannedMessageConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.moduleConfig.cannedMessage,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.ModuleConfig_CannedMessageConfig>({
defaultValues: preferences, defaultValues: cannedMessageConfig,
}); });
const moduleEnabled = useWatch({ const moduleEnabled = useWatch({
@ -28,23 +28,28 @@ export const CannedMessage = (): JSX.Element => {
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(cannedMessageConfig);
}, [reset, preferences]); }, [reset, cannedMessageConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => { void connection.setModuleConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'cannedMessage',
}); cannedMessage: data,
},
},
async () => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox <Checkbox label="Module Enabled" {...register('enabled')} />
label="Module Enabled"
{...register('cannedMessageModuleEnabled')}
/>
<Checkbox <Checkbox
label="Rotary Encoder #1 Enabled" label="Rotary Encoder #1 Enabled"
{...register('rotary1Enabled')} {...register('rotary1Enabled')}
@ -70,31 +75,28 @@ export const CannedMessage = (): JSX.Element => {
<Select <Select
label="Clockwise event" label="Clockwise event"
disabled={moduleEnabled} disabled={moduleEnabled}
optionsEnum={Protobuf.InputEventChar} optionsEnum={Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar}
{...register('inputbrokerEventCw', { valueAsNumber: true })} {...register('inputbrokerEventCw', { valueAsNumber: true })}
/> />
<Select <Select
label="Counter Clockwise event" label="Counter Clockwise event"
disabled={moduleEnabled} disabled={moduleEnabled}
optionsEnum={Protobuf.InputEventChar} optionsEnum={Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar}
{...register('inputbrokerEventCcw', { valueAsNumber: true })} {...register('inputbrokerEventCcw', { valueAsNumber: true })}
/> />
<Select <Select
label="Press event" label="Press event"
disabled={moduleEnabled} disabled={moduleEnabled}
optionsEnum={Protobuf.InputEventChar} optionsEnum={Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar}
{...register('inputbrokerEventPress', { valueAsNumber: true })} {...register('inputbrokerEventPress', { valueAsNumber: true })}
/> />
<Checkbox label="Up Down enabled" {...register('updown1Enabled')} /> <Checkbox label="Up Down enabled" {...register('updown1Enabled')} />
<Input <Input
label="Allow Input Source" label="Allow Input Source"
disabled={moduleEnabled} disabled={moduleEnabled}
{...register('cannedMessageModuleAllowInputSource')} {...register('allowInputSource')}
/>
<Checkbox
label="Send Bell"
{...register('cannedMessageModuleSendBell')}
/> />
<Checkbox label="Send Bell" {...register('sendBell')} />
</Form> </Form>
); );
}; };

47
src/components/layout/Sidebar/Settings/modules/ExternalNotifications.tsx

@ -13,46 +13,51 @@ import type { Protobuf } from '@meshtastic/meshtasticjs';
export const ExternalNotificationsSettingsPlanel = (): JSX.Element => { export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const preferences = useAppSelector( const extNotificationConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.moduleConfig.extNotification,
); );
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.ModuleConfig_ExternalNotificationConfig>({
defaultValues: preferences, defaultValues: extNotificationConfig,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(extNotificationConfig);
}, [reset, preferences]); }, [reset, extNotificationConfig]);
const onSubmit = handleSubmit(async (data) => { const onSubmit = handleSubmit(async (data) => {
setLoading(true); setLoading(true);
await connection.setPreferences(data, async (): Promise<void> => { await connection.setModuleConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'externalNotification',
}); externalNotification: data,
},
},
async (): Promise<void> => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
const moduleEnabled = useWatch({ const moduleEnabled = useWatch({
control, control,
name: 'extNotificationModuleEnabled', name: 'enabled',
defaultValue: false, defaultValue: false,
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox <Checkbox label="Module Enabled" {...register('enabled')} />
label="Module Enabled"
{...register('extNotificationModuleEnabled')}
/>
<Input <Input
type="number" type="number"
label="Output MS" label="Output MS"
suffix="ms" suffix="ms"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('extNotificationModuleOutputMs', { {...register('outputMs', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -60,24 +65,24 @@ export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
type="number" type="number"
label="Output" label="Output"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('extNotificationModuleOutput', { {...register('output', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
<Checkbox <Checkbox
label="Active" label="Active"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('extNotificationModuleActive')} {...register('active')}
/> />
<Checkbox <Checkbox
label="Message" label="Message"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('extNotificationModuleAlertMessage')} {...register('alertMessage')}
/> />
<Checkbox <Checkbox
label="Bell" label="Bell"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('extNotificationModuleAlertBell')} {...register('alertBell')}
/> />
</Form> </Form>
); );

42
src/components/layout/Sidebar/Settings/modules/MQTT.tsx

@ -11,57 +11,65 @@ import { useAppSelector } from '@hooks/useAppSelector';
import type { Protobuf } from '@meshtastic/meshtasticjs'; import type { Protobuf } from '@meshtastic/meshtasticjs';
export const MQTT = (): JSX.Element => { export const MQTT = (): JSX.Element => {
const preferences = useAppSelector( const mqttConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.moduleConfig.mqtt,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.ModuleConfig_MQTTConfig>({
defaultValues: preferences, defaultValues: mqttConfig,
}); });
const moduleEnabled = useWatch({ const moduleEnabled = useWatch({
control, control,
name: 'mqttDisabled', name: 'disabled',
defaultValue: false, defaultValue: false,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(mqttConfig);
}, [reset, preferences]); }, [reset, mqttConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => { void connection.setModuleConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'mqtt',
}); mqtt: data,
},
},
async () => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox label="Module Disabled" {...register('mqttDisabled')} /> <Checkbox label="Module Disabled" {...register('disabled')} />
<Input <Input
label="MQTT Server Address" label="MQTT Server Address"
disabled={moduleEnabled} disabled={moduleEnabled}
{...register('mqttServer')} {...register('address')}
/> />
<Input <Input
label="MQTT Username" label="MQTT Username"
disabled={moduleEnabled} disabled={moduleEnabled}
{...register('mqttUsername')} {...register('username')}
/> />
<Input <Input
label="MQTT Password" label="MQTT Password"
type="password" type="password"
autoComplete="off" autoComplete="off"
disabled={moduleEnabled} disabled={moduleEnabled}
{...register('mqttPassword')} {...register('password')}
/> />
<Checkbox <Checkbox
label="Encryption Enabled" label="Encryption Enabled"
disabled={moduleEnabled} disabled={moduleEnabled}
{...register('mqttEncryptionEnabled')} {...register('encryptionEnabled')}
/> />
</Form> </Form>
); );

41
src/components/layout/Sidebar/Settings/modules/RangeTest.tsx

@ -13,53 +13,58 @@ import type { Protobuf } from '@meshtastic/meshtasticjs';
export const RangeTestSettingsPanel = (): JSX.Element => { export const RangeTestSettingsPanel = (): JSX.Element => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const preferences = useAppSelector( const rangeTestConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.moduleConfig.rangeTest,
); );
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.ModuleConfig_RangeTestConfig>({
defaultValues: preferences, defaultValues: rangeTestConfig,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(rangeTestConfig);
}, [reset, preferences]); }, [reset, rangeTestConfig]);
const onSubmit = handleSubmit(async (data) => { const onSubmit = handleSubmit(async (data) => {
setLoading(true); setLoading(true);
await connection.setPreferences(data, async (): Promise<void> => { await connection.setModuleConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'rangeTest',
}); rangeTest: data,
},
},
async (): Promise<void> => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
const moduleEnabled = useWatch({ const moduleEnabled = useWatch({
control, control,
name: 'rangeTestModuleEnabled', name: 'enabled',
defaultValue: false, defaultValue: false,
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox <Checkbox label="Module Enabled" {...register('enabled')} />
label="Module Enabled"
{...register('rangeTestModuleEnabled')}
/>
<Input <Input
type="number" type="number"
label="Message Interval" label="Message Interval"
disabled={!moduleEnabled} disabled={!moduleEnabled}
suffix="Seconds" suffix="Seconds"
{...register('rangeTestModuleSender', { {...register('sender', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
<Checkbox <Checkbox
label="Save CSV to storage" label="Save CSV to storage"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('rangeTestModuleSave')} {...register('save')}
/> />
</Form> </Form>
); );

50
src/components/layout/Sidebar/Settings/modules/Serial.tsx

@ -13,48 +13,52 @@ import type { Protobuf } from '@meshtastic/meshtasticjs';
export const SerialSettingsPanel = (): JSX.Element => { export const SerialSettingsPanel = (): JSX.Element => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const preferences = useAppSelector( const serialConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.moduleConfig.serial,
); );
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.ModuleConfig_SerialConfig>({
defaultValues: preferences, defaultValues: serialConfig,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(serialConfig);
}, [reset, preferences]); }, [reset, serialConfig]);
const onSubmit = handleSubmit(async (data) => { const onSubmit = handleSubmit(async (data) => {
setLoading(true); setLoading(true);
await connection.setPreferences(data, async (): Promise<void> => { await connection.setModuleConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'serial',
}); serial: data,
},
},
async (): Promise<void> => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
const moduleEnabled = useWatch({ const moduleEnabled = useWatch({
control, control,
name: 'serialModuleEnabled', name: 'enabled',
defaultValue: false, defaultValue: false,
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox label="Module Enabled" {...register('serialModuleEnabled')} /> <Checkbox label="Module Enabled" {...register('enabled')} />
<Checkbox <Checkbox label="Echo" disabled={!moduleEnabled} {...register('echo')} />
label="Echo"
disabled={!moduleEnabled}
{...register('serialModuleEcho')}
/>
<Input <Input
type="number" type="number"
label="RX" label="RX"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('serialModuleRxd', { {...register('rxd', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -62,7 +66,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
type="number" type="number"
label="TX Pin" label="TX Pin"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('serialModuleTxd', { {...register('txd', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -70,7 +74,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
type="number" type="number"
label="Baud Rate" label="Baud Rate"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('serialModuleBaud', { {...register('baud', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -78,7 +82,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
type="number" type="number"
label="Timeout" label="Timeout"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('serialModuleTimeout', { {...register('timeout', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -86,7 +90,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
type="number" type="number"
label="Mode" label="Mode"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('serialModuleMode', { {...register('mode', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />

45
src/components/layout/Sidebar/Settings/modules/StoreForward.tsx

@ -13,51 +13,56 @@ import type { Protobuf } from '@meshtastic/meshtasticjs';
export const StoreForwardSettingsPanel = (): JSX.Element => { export const StoreForwardSettingsPanel = (): JSX.Element => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const preferences = useAppSelector( const storeForwardConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.moduleConfig.storeForward,
); );
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.ModuleConfig_StoreForwardConfig>({
defaultValues: preferences, defaultValues: storeForwardConfig,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(storeForwardConfig);
}, [reset, preferences]); }, [reset, storeForwardConfig]);
const onSubmit = handleSubmit(async (data) => { const onSubmit = handleSubmit(async (data) => {
setLoading(true); setLoading(true);
await connection.setPreferences(data, async (): Promise<void> => { await connection.setModuleConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'storeForward',
}); storeForward: data,
},
},
async (): Promise<void> => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
const moduleEnabled = useWatch({ const moduleEnabled = useWatch({
control, control,
name: 'storeForwardModuleEnabled', name: 'enabled',
defaultValue: false, defaultValue: false,
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox <Checkbox label="Module Enabled" {...register('enabled')} />
label="Module Enabled"
{...register('storeForwardModuleEnabled')}
/>
<Checkbox <Checkbox
label="Heartbeat Enabled" label="Heartbeat Enabled"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('storeForwardModuleHeartbeat')} {...register('heartbeat')}
/> />
<Input <Input
type="number" type="number"
label="Number of records" label="Number of records"
suffix="Records" suffix="Records"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('storeForwardModuleRecords', { {...register('records', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -65,7 +70,7 @@ export const StoreForwardSettingsPanel = (): JSX.Element => {
type="number" type="number"
label="History return max" label="History return max"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('storeForwardModuleHistoryReturnMax', { {...register('historyReturnMax', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -73,7 +78,7 @@ export const StoreForwardSettingsPanel = (): JSX.Element => {
type="number" type="number"
label="History return window" label="History return window"
disabled={!moduleEnabled} disabled={!moduleEnabled}
{...register('storeForwardModuleHistoryReturnWindow', { {...register('historyReturnWindow', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />

48
src/components/layout/Sidebar/Settings/modules/Telemetry.tsx

@ -12,41 +12,49 @@ import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs'; import { Protobuf } from '@meshtastic/meshtasticjs';
export const Telemetry = (): JSX.Element => { export const Telemetry = (): JSX.Element => {
const preferences = useAppSelector( const telemetryConfig = useAppSelector(
(state) => state.meshtastic.radio.preferences, (state) => state.meshtastic.radio.moduleConfig.telemetry,
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { register, handleSubmit, formState, reset, control } = const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({ useForm<Protobuf.ModuleConfig_TelemetryConfig>({
defaultValues: preferences, defaultValues: telemetryConfig,
}); });
useEffect(() => { useEffect(() => {
reset(preferences); reset(telemetryConfig);
}, [reset, preferences]); }, [reset, telemetryConfig]);
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit((data) => {
setLoading(true); setLoading(true);
void connection.setPreferences(data, async () => { void connection.setModuleConfig(
reset({ ...data }); {
setLoading(false); payloadVariant: {
await Promise.resolve(); oneofKind: 'telemetry',
}); telemetry: data,
},
},
async () => {
reset({ ...data });
setLoading(false);
await Promise.resolve();
},
);
}); });
return ( return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> <Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox <Checkbox
label="Measurement Enabled" label="Measurement Enabled"
{...register('telemetryModuleEnvironmentMeasurementEnabled')} {...register('environmentMeasurementEnabled')}
/> />
<Checkbox <Checkbox
label="Displayed on Screen" label="Displayed on Screen"
{...register('telemetryModuleEnvironmentScreenEnabled')} {...register('environmentScreenEnabled')}
/> />
<Input <Input
label="Read Error Count Threshold" label="Read Error Count Threshold"
type="number" type="number"
{...register('telemetryModuleEnvironmentReadErrorCountThreshold', { {...register('environmentReadErrorCountThreshold', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -54,7 +62,7 @@ export const Telemetry = (): JSX.Element => {
label="Update Interval" label="Update Interval"
suffix="Seconds" suffix="Seconds"
type="number" type="number"
{...register('telemetryModuleEnvironmentUpdateInterval', { {...register('environmentUpdateInterval', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
@ -62,25 +70,25 @@ export const Telemetry = (): JSX.Element => {
label="Recovery Interval" label="Recovery Interval"
suffix="Seconds" suffix="Seconds"
type="number" type="number"
{...register('telemetryModuleEnvironmentRecoveryInterval', { {...register('environmentRecoveryInterval', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
<Checkbox <Checkbox
label="Display Farenheit" label="Display Farenheit"
{...register('telemetryModuleEnvironmentDisplayFahrenheit')} {...register('environmentDisplayFahrenheit')}
/> />
<Select <Select
label="Sensor Type" label="Sensor Type"
optionsEnum={Protobuf.RadioConfig_UserPreferences_TelemetrySensorType} optionsEnum={Protobuf.TelemetrySensorType}
{...register('telemetryModuleEnvironmentSensorType', { {...register('environmentSensorType', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />
<Input <Input
label="Sensor Pin" label="Sensor Pin"
type="number" type="number"
{...register('telemetryModuleEnvironmentSensorPin', { {...register('environmentSensorPin', {
valueAsNumber: true, valueAsNumber: true,
})} })}
/> />

6
src/components/menu/BottomNav.tsx

@ -134,13 +134,13 @@ export const BottomNav = (): JSX.Element => {
<BottomNavItem tooltip="MQTT Status"> <BottomNavItem tooltip="MQTT Status">
{primaryChannelSettings?.uplinkEnabled && {primaryChannelSettings?.uplinkEnabled &&
primaryChannelSettings?.downlinkEnabled && primaryChannelSettings?.downlinkEnabled &&
!meshtasticState.radio.preferences.mqttDisabled ? ( !meshtasticState.radio.moduleConfig.mqtt.disabled ? (
<RiArrowUpDownLine className="h-4" /> <RiArrowUpDownLine className="h-4" />
) : primaryChannelSettings?.uplinkEnabled && ) : primaryChannelSettings?.uplinkEnabled &&
!meshtasticState.radio.preferences.mqttDisabled ? ( !meshtasticState.radio.moduleConfig.mqtt.disabled ? (
<RiArrowUpLine className="h-4" /> <RiArrowUpLine className="h-4" />
) : primaryChannelSettings?.downlinkEnabled && ) : primaryChannelSettings?.downlinkEnabled &&
!meshtasticState.radio.preferences.mqttDisabled ? ( !meshtasticState.radio.moduleConfig.mqtt.disabled ? (
<RiArrowDownLine className="h-4" /> <RiArrowDownLine className="h-4" />
) : ( ) : (
<FiX className="h-4" /> <FiX className="h-4" />

61
src/core/connection.ts

@ -6,13 +6,13 @@ import {
addMessage, addMessage,
addNode, addNode,
addPosition, addPosition,
addRoute,
addUser, addUser,
resetState, resetState,
setConfig,
setDeviceStatus, setDeviceStatus,
setLastMeshInterraction, setLastMeshInterraction,
setModuleConfig,
setMyNodeInfo, setMyNodeInfo,
setPreferences,
setReady, setReady,
updateLastInteraction, updateLastInteraction,
} from '@core/slices/meshtasticSlice'; } from '@core/slices/meshtasticSlice';
@ -90,24 +90,21 @@ const registerListeners = (): void => {
store.dispatch(addLogEvent(log)); store.dispatch(addLogEvent(log));
}); });
connection.onMeshPacket.subscribe((packet) => {
store.dispatch(
addRoute({
from: packet.from,
to:
packet.to === 0xffffffff
? store.getState().meshtastic.radio.hardware.myNodeNum
: packet.to,
hops: packet.hopLimit,
}),
);
});
connection.onDeviceStatus.subscribe((status) => { connection.onDeviceStatus.subscribe((status) => {
store.dispatch(setDeviceStatus(status)); store.dispatch(setDeviceStatus(status));
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) { if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
store.dispatch(setReady(true)); 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) { if (status === Types.DeviceStatusEnum.DEVICE_DISCONNECTED) {
store.dispatch(setReady(false)); store.dispatch(setReady(false));
@ -136,6 +133,8 @@ const registerListeners = (): void => {
); );
connection.onAdminPacket.subscribe((adminPacket) => { connection.onAdminPacket.subscribe((adminPacket) => {
console.log(adminPacket.data.variant.oneofKind);
switch (adminPacket.data.variant.oneofKind) { switch (adminPacket.data.variant.oneofKind) {
case 'getChannelResponse': case 'getChannelResponse':
store.dispatch(addChannel(adminPacket.data.variant.getChannelResponse)); store.dispatch(addChannel(adminPacket.data.variant.getChannelResponse));
@ -143,15 +142,6 @@ const registerListeners = (): void => {
addChat(adminPacket.data.variant.getChannelResponse.index), addChat(adminPacket.data.variant.getChannelResponse.index),
); );
break; break;
case 'getRadioResponse':
if (adminPacket.data.variant.getRadioResponse.preferences) {
store.dispatch(
setPreferences(
adminPacket.data.variant.getRadioResponse.preferences,
),
);
}
break;
case 'getOwnerResponse': case 'getOwnerResponse':
store.dispatch( store.dispatch(
addUser({ addUser({
@ -159,6 +149,15 @@ const registerListeners = (): void => {
packet: adminPacket.packet, packet: adminPacket.packet,
}), }),
); );
break;
case 'getConfigResponse':
store.dispatch(setConfig(adminPacket.data.variant.getConfigResponse));
break;
case 'getModuleConfigResponse':
store.dispatch(
setModuleConfig(adminPacket.data.variant.getModuleConfigResponse),
);
break;
} }
}); });
@ -168,6 +167,20 @@ const registerListeners = (): void => {
); );
connection.onRoutingPacket.subscribe((routingPacket) => { 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( store.dispatch(
updateLastInteraction({ updateLastInteraction({
id: routingPacket.packet.from, id: routingPacket.packet.from,

125
src/core/slices/meshtasticSlice.ts

@ -32,10 +32,30 @@ export interface Node {
export interface Radio { export interface Radio {
channels: Protobuf.Channel[]; channels: Protobuf.Channel[];
preferences: Protobuf.RadioConfig_UserPreferences; config: Config;
moduleConfig: ModuleConfig;
hardware: Protobuf.MyNodeInfo; 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 { interface MeshtasticState {
deviceStatus: Types.DeviceStatusEnum; deviceStatus: Types.DeviceStatusEnum;
lastMeshInterraction: number; lastMeshInterraction: number;
@ -53,7 +73,24 @@ const initialState: MeshtasticState = {
nodes: [], nodes: [],
radio: { radio: {
channels: [], channels: [],
preferences: Protobuf.RadioConfig_UserPreferences.create(), 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(), hardware: Protobuf.MyNodeInfo.create(),
}, },
chats: {}, chats: {},
@ -151,23 +188,71 @@ export const meshtasticSlice = createSlice({
state.radio.channels.push(action.payload); state.radio.channels.push(action.payload);
} }
}, },
addRoute: (state, action: PayloadAction<Route>) => { setConfig: (state, action: PayloadAction<Protobuf.Config>) => {
// const node = state.nodes.find( switch (action.payload.payloadVariant.oneofKind) {
// (node) => node.num === action.payload.from, case 'device': {
// ); state.radio.config.device = action.payload.payloadVariant.device;
// const exists = node?.routes.findIndex( break;
// (route) => }
// route.from === action.payload.from && route.to === action.payload.to, case 'position': {
// ); state.radio.config.position = action.payload.payloadVariant.position;
// if (exists === -1) { break;
// node?.routes.push(action.payload); }
// } 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;
}
}
}, },
setPreferences: ( setModuleConfig: (state, action: PayloadAction<Protobuf.ModuleConfig>) => {
state, switch (action.payload.payloadVariant.oneofKind) {
action: PayloadAction<Protobuf.RadioConfig_UserPreferences>, case 'cannedMessage': {
) => { state.radio.moduleConfig.cannedMessage =
state.radio.preferences = action.payload; 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>) => { addMessage: (state, action: PayloadAction<MessageWithAck>) => {
// todo: is last interraction for just channel chats or dm's to? // todo: is last interraction for just channel chats or dm's to?
@ -246,8 +331,8 @@ export const {
addPosition, addPosition,
addNode, addNode,
addChannel, addChannel,
setPreferences, setConfig,
addRoute, setModuleConfig,
addMessage, addMessage,
ackMessage, ackMessage,
updateLastInteraction, updateLastInteraction,

87
src/pages/Extensions/Debug.tsx

@ -4,11 +4,15 @@ import { Button } from '@components/generic/button/Button';
import { Card } from '@components/generic/Card'; import { Card } from '@components/generic/Card';
import { connection } from '@core/connection'; import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector'; import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf } from '@meshtastic/meshtasticjs';
export const Debug = (): JSX.Element => { export const Debug = (): JSX.Element => {
const hardwareInfo = useAppSelector( const hardwareInfo = useAppSelector(
(state) => state.meshtastic.radio.hardware, (state) => state.meshtastic.radio.hardware,
); );
const myNodeNum = useAppSelector(
(state) => state.meshtastic.radio.hardware.myNodeNum,
);
const node = useAppSelector((state) => const node = useAppSelector((state) =>
state.meshtastic.nodes.find( state.meshtastic.nodes.find(
(node) => node.data.num === hardwareInfo.myNodeNum, (node) => node.data.num === hardwareInfo.myNodeNum,
@ -20,25 +24,92 @@ export const Debug = (): JSX.Element => {
<Card className="flex-grow"> <Card className="flex-grow">
<div className="grid grid-cols-4 gap-4"> <div className="grid grid-cols-4 gap-4">
<Button <Button
onClick={async (): Promise<void> => { onClick={(): void => {
await connection.configure(); void connection.configure();
}} }}
> >
Configure Configure
</Button> </Button>
<Button <Button
onClick={async (): Promise<void> => { onClick={(): void => {
await connection.getPreferences(); void connection.getAllChannels();
}} }}
> >
Get Preferences Get All Channels
</Button> </Button>
<Button <Button
onClick={async (): Promise<void> => { onClick={(): void => {
await connection.getAllChannels(); const packet = Protobuf.AdminMessage.create({
variant: {
oneofKind: 'getConfigRequest',
getConfigRequest:
Protobuf.AdminMessage_ConfigType.LORA_CONFIG,
},
});
void connection.sendPacket(
Protobuf.AdminMessage.toBinary(packet),
Protobuf.PortNum.ADMIN_APP,
myNodeNum,
true,
0,
true,
false,
async (num) => {
return await Promise.resolve();
},
);
}} }}
> >
Get All Channels Get All Config
</Button>
<Button
onClick={(): void => {
void connection.getConfig(
Protobuf.AdminMessage_ConfigType.DISPLAY_CONFIG,
async (id) => {
console.log(`RESPONSE - ${id}`);
return Promise.resolve();
},
);
}}
>
Get Display Config
</Button>
<Button
onClick={(): void => {
void connection.getConfig(
Protobuf.AdminMessage_ConfigType.LORA_CONFIG,
async (id) => {
console.log(`RESPONSE - ${id}`);
return Promise.resolve();
},
);
}}
>
Get LoRa Config
</Button>
<Button
onClick={(): void => {
void connection.setConfig(
Protobuf.Config.create({
payloadVariant: {
oneofKind: 'lora',
lora: Protobuf.Config_LoRaConfig.create({
modemPreset:
Protobuf.Config_LoRaConfig_ModemPreset.MidSlow,
}),
},
}),
async (id) => {
console.log(`RESPONSE - ${id}`);
return Promise.resolve();
},
);
}}
>
Set LoRa Config
</Button> </Button>
</div> </div>
</Card> </Card>

6
src/pages/Extensions/Logs.tsx

@ -22,15 +22,15 @@ export const Logs = (): JSX.Element => {
[Types.Emitter.sendText]: 'text-rose-500', [Types.Emitter.sendText]: 'text-rose-500',
[Types.Emitter.sendPacket]: 'text-pink-500', [Types.Emitter.sendPacket]: 'text-pink-500',
[Types.Emitter.sendRaw]: 'text-fuchsia-500', [Types.Emitter.sendRaw]: 'text-fuchsia-500',
[Types.Emitter.setPreferences]: 'text-purple-500', [Types.Emitter.setConfig]: 'text-purple-500',
[Types.Emitter.confirmSetPreferences]: 'text-violet-500', [Types.Emitter.confirmSetConfig]: 'text-violet-500',
[Types.Emitter.setOwner]: 'text-indigo-500', [Types.Emitter.setOwner]: 'text-indigo-500',
[Types.Emitter.setChannel]: 'text-blue-500', [Types.Emitter.setChannel]: 'text-blue-500',
[Types.Emitter.confirmSetChannel]: 'text-sky-500', [Types.Emitter.confirmSetChannel]: 'text-sky-500',
[Types.Emitter.deleteChannel]: 'text-cyan-500', [Types.Emitter.deleteChannel]: 'text-cyan-500',
[Types.Emitter.getChannel]: 'text-teal-500', [Types.Emitter.getChannel]: 'text-teal-500',
[Types.Emitter.getAllChannels]: 'text-emerald-500', [Types.Emitter.getAllChannels]: 'text-emerald-500',
[Types.Emitter.getPreferences]: 'text-green-500', [Types.Emitter.getConfig]: 'text-green-500',
[Types.Emitter.getOwner]: 'text-lime-500', [Types.Emitter.getOwner]: 'text-lime-500',
[Types.Emitter.configure]: 'text-yellow-500', [Types.Emitter.configure]: 'text-yellow-500',
[Types.Emitter.handleFromRadio]: 'text-amber-500', [Types.Emitter.handleFromRadio]: 'text-amber-500',

2
tsconfig.json

@ -27,7 +27,7 @@
"importHelpers": true, "importHelpers": true,
"removeComments": true, "removeComments": true,
"strictNullChecks": true, "strictNullChecks": true,
"types": ["vite/client", "vite-plugin-pwa/client"], "types": ["vite/client", "vite-plugin-pwa/client", "node"],
"importsNotUsedAsValues": "error" "importsNotUsedAsValues": "error"
} }
} }

Loading…
Cancel
Save