You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
294 lines
9.5 KiB
294 lines
9.5 KiB
import type React from "react";
|
|
import { useEffect } from "react";
|
|
|
|
import { Controller, useForm, useWatch } from "react-hook-form";
|
|
import { toast } from "react-hot-toast";
|
|
|
|
import { FormSection } from "@app/components/form/FormSection.js";
|
|
import { Input } from "@app/components/form/Input.js";
|
|
import { Toggle } from "@app/components/form/Toggle.js";
|
|
import { PositionValidation } from "@app/validation/config/position.js";
|
|
import { Form } from "@components/form/Form";
|
|
import { useDevice } from "@core/providers/useDevice.js";
|
|
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
|
import { Protobuf } from "@meshtastic/meshtasticjs";
|
|
|
|
export const Position = (): JSX.Element => {
|
|
const { config, connection, nodes, hardware } = useDevice();
|
|
|
|
const myNode = nodes.find((n) => n.data.num === hardware.myNodeNum);
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors, isDirty },
|
|
reset,
|
|
control,
|
|
} = useForm<PositionValidation>({
|
|
defaultValues: {
|
|
fixedAlt: myNode?.data.position?.altitude,
|
|
fixedLat: (myNode?.data.position?.latitudeI ?? 0) / 1e7,
|
|
fixedLng: (myNode?.data.position?.longitudeI ?? 0) / 1e7,
|
|
...config.position,
|
|
},
|
|
resolver: classValidatorResolver(PositionValidation),
|
|
});
|
|
|
|
const fixedPositionEnabled = useWatch({
|
|
control,
|
|
name: "fixedPosition",
|
|
defaultValue: false,
|
|
});
|
|
|
|
useEffect(() => {
|
|
reset({
|
|
fixedAlt: myNode?.data.position?.altitude,
|
|
fixedLat: (myNode?.data.position?.latitudeI ?? 0) / 1e7,
|
|
fixedLng: (myNode?.data.position?.longitudeI ?? 0) / 1e7,
|
|
...config.position,
|
|
});
|
|
}, [reset, config.position, myNode?.data.position]);
|
|
|
|
const onSubmit = handleSubmit((data) => {
|
|
const { fixedAlt, fixedLat, fixedLng, ...rest } = data;
|
|
|
|
const configHasChanged = !Protobuf.Config_PositionConfig.equals(
|
|
config.position,
|
|
Protobuf.Config_PositionConfig.create(rest)
|
|
);
|
|
|
|
if (connection) {
|
|
void toast.promise(
|
|
connection.sendPacket(
|
|
Protobuf.Position.toBinary(
|
|
Protobuf.Position.create({
|
|
altitude: fixedAlt,
|
|
latitudeI: fixedLat * 1e7,
|
|
longitudeI: fixedLng * 1e7,
|
|
})
|
|
),
|
|
Protobuf.PortNum.POSITION_APP,
|
|
undefined,
|
|
true,
|
|
undefined,
|
|
true,
|
|
false,
|
|
async () => {
|
|
reset({ ...data });
|
|
await Promise.resolve();
|
|
}
|
|
),
|
|
{
|
|
loading: "Saving...",
|
|
success: "Saved Channel",
|
|
error: "No response received",
|
|
}
|
|
);
|
|
if (configHasChanged) {
|
|
void toast.promise(
|
|
connection.setConfig(
|
|
{
|
|
payloadVariant: {
|
|
oneofKind: "position",
|
|
position: rest,
|
|
},
|
|
},
|
|
async () => {
|
|
reset({ ...data });
|
|
await Promise.resolve();
|
|
}
|
|
),
|
|
{
|
|
loading: "Saving...",
|
|
success: "Saved Position Config, Restarting Node",
|
|
error: "No response received",
|
|
}
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
return (
|
|
<Form
|
|
title="Position Config"
|
|
breadcrumbs={["Config", "Position"]}
|
|
reset={() => reset(config.position)}
|
|
dirty={isDirty}
|
|
onSubmit={onSubmit}
|
|
>
|
|
<Input
|
|
suffix="Seconds"
|
|
label="Broadcast Interval"
|
|
description="How often your position is sent out over the mesh"
|
|
type="number"
|
|
error={errors.positionBroadcastSecs?.message}
|
|
{...register("positionBroadcastSecs", { valueAsNumber: true })}
|
|
/>
|
|
<Controller
|
|
name="positionBroadcastSmartEnabled"
|
|
control={control}
|
|
render={({ field: { value, ...rest } }) => (
|
|
<Toggle
|
|
label="Enable Smart Position"
|
|
description="Only send position when there has been a meaningful change in location"
|
|
checked={value}
|
|
{...rest}
|
|
/>
|
|
)}
|
|
/>
|
|
<Controller
|
|
name="fixedPosition"
|
|
control={control}
|
|
render={({ field: { value, ...rest } }) => (
|
|
<Toggle
|
|
label="Use Fixed Position"
|
|
description="Don't report GPS position, but a manually-specified one"
|
|
checked={value}
|
|
{...rest}
|
|
/>
|
|
)}
|
|
/>
|
|
<FormSection title="Fixed Position">
|
|
<Input
|
|
suffix="m"
|
|
label="Altitude"
|
|
type="number"
|
|
error={errors.fixedAlt?.message}
|
|
disabled={!fixedPositionEnabled}
|
|
{...register("fixedAlt", { valueAsNumber: true })}
|
|
/>
|
|
<Input
|
|
suffix="°"
|
|
label="Latitude"
|
|
type="number"
|
|
error={errors.fixedLat?.message}
|
|
disabled={!fixedPositionEnabled}
|
|
{...register("fixedLat", { valueAsNumber: true })}
|
|
/>
|
|
<Input
|
|
suffix="°"
|
|
label="Longitude"
|
|
type="number"
|
|
error={errors.fixedLng?.message}
|
|
disabled={!fixedPositionEnabled}
|
|
{...register("fixedLng", { valueAsNumber: true })}
|
|
/>
|
|
</FormSection>
|
|
<Controller
|
|
name="gpsEnabled"
|
|
control={control}
|
|
render={({ field: { value, ...rest } }) => (
|
|
<Toggle
|
|
label="GPS Enabled"
|
|
description="Enable the internal GPS module"
|
|
checked={value}
|
|
{...rest}
|
|
/>
|
|
)}
|
|
/>
|
|
<Input
|
|
suffix="Seconds"
|
|
label="GPS Update Interval"
|
|
description="How often a GPS fix should be acquired"
|
|
type="number"
|
|
error={errors.gpsUpdateInterval?.message}
|
|
{...register("gpsUpdateInterval", { valueAsNumber: true })}
|
|
/>
|
|
<Input
|
|
label="Fix Attempt Duration"
|
|
description="How long the device will try to get a fix for"
|
|
type="number"
|
|
error={errors.gpsAttemptTime?.message}
|
|
{...register("gpsAttemptTime", { valueAsNumber: true })}
|
|
/>
|
|
{/* <Controller
|
|
name="positionFlags"
|
|
control={control}
|
|
render={({ field, fieldState }): JSX.Element => {
|
|
const { value, onChange, ...rest } = field;
|
|
const { error } = fieldState;
|
|
const options = Object.entries(
|
|
Protobuf.Config_PositionConfig_PositionFlags
|
|
)
|
|
.filter((value) => typeof value[1] !== "number")
|
|
.filter(
|
|
(value) =>
|
|
parseInt(value[0]) !==
|
|
Protobuf.Config_PositionConfig_PositionFlags.UNSET
|
|
)
|
|
.map((value) => {
|
|
return {
|
|
value: parseInt(value[0]),
|
|
label: value[1].toString().replace("POS_", "").toLowerCase(),
|
|
};
|
|
});
|
|
|
|
const selected = bitwiseDecode(
|
|
value,
|
|
Protobuf.Config_PositionConfig_PositionFlags
|
|
).map((flag) =>
|
|
Protobuf.Config_PositionConfig_PositionFlags[flag]
|
|
.replace("POS_", "")
|
|
.toLowerCase()
|
|
);
|
|
// onChange={(e: { value: number; label: string }[]): void =>
|
|
// onChange(bitwiseEncode(e.map((v) => v.value)))
|
|
// }
|
|
return (
|
|
<FormField
|
|
label="Position Flags"
|
|
description="Description"
|
|
isInvalid={!!errors.positionFlags?.message}
|
|
validationMessage={errors.positionFlags?.message}
|
|
>
|
|
<SelectMenu
|
|
isMultiSelect
|
|
title="Select multiple names"
|
|
options={options}
|
|
selected={selected}
|
|
// onSelect={(item) => {
|
|
// const selected = [...selectedItemsState, item.value]
|
|
// const selectedItems = selected
|
|
// const selectedItemsLength = selectedItems.length
|
|
// let selectedNames = ''
|
|
// if (selectedItemsLength === 0) {
|
|
// selectedNames = ''
|
|
// } else if (selectedItemsLength === 1) {
|
|
// selectedNames = selectedItems.toString()
|
|
// } else if (selectedItemsLength > 1) {
|
|
// selectedNames = selectedItemsLength.toString() + ' selected...'
|
|
// }
|
|
// setSelectedItems(selectedItems)
|
|
// setSelectedItemNames(selectedNames)
|
|
// }}
|
|
// onDeselect={(item) => {
|
|
// const deselectedItemIndex = selectedItemsState.indexOf(item.value)
|
|
// const selectedItems = selectedItemsState.filter((_item, i) => i !== deselectedItemIndex)
|
|
// const selectedItemsLength = selectedItems.length
|
|
// let selectedNames = ''
|
|
// if (selectedItemsLength === 0) {
|
|
// selectedNames = ''
|
|
// } else if (selectedItemsLength === 1) {
|
|
// selectedNames = selectedItems.toString()
|
|
// } else if (selectedItemsLength > 1) {
|
|
// selectedNames = selectedItemsLength.toString() + ' selected...'
|
|
// }
|
|
|
|
// setSelectedItems(selectedItems)
|
|
// setSelectedItemNames(selectedNames)
|
|
// }}
|
|
>
|
|
<Button>
|
|
{selected.map(
|
|
(item, index) =>
|
|
`${item}${index !== selected.length - 1 ? ", " : ""}`
|
|
)}
|
|
</Button>
|
|
</SelectMenu>
|
|
</FormField>
|
|
);
|
|
}}
|
|
/> */}
|
|
</Form>
|
|
);
|
|
};
|
|
|