replace disabled sensor gpio (99) with code logic

This commit is contained in:
proddy
2025-11-17 17:49:30 +01:00
parent 25ea7d8b0c
commit 090387ef37
7 changed files with 75 additions and 68 deletions

View File

@@ -1,4 +1,4 @@
import { useCallback, useContext, useMemo, useState } from 'react'; import { useCallback, useContext, useMemo, useRef, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
@@ -54,7 +54,6 @@ const MS_PER_SECOND = 1000;
const MS_PER_MINUTE = 60 * MS_PER_SECOND; const MS_PER_MINUTE = 60 * MS_PER_SECOND;
const MS_PER_HOUR = 60 * MS_PER_MINUTE; const MS_PER_HOUR = 60 * MS_PER_MINUTE;
const MS_PER_DAY = 24 * MS_PER_HOUR; const MS_PER_DAY = 24 * MS_PER_HOUR;
const DEFAULT_GPIO = 99; // not set
const MIN_TEMP_ID = -100; const MIN_TEMP_ID = -100;
const MAX_TEMP_ID = 100; const MAX_TEMP_ID = 100;
const GPIO_25 = 25; const GPIO_25 = 25;
@@ -128,15 +127,21 @@ const Sensors = () => {
const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false); const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false);
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false); const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
const [creating, setCreating] = useState<boolean>(false); const [creating, setCreating] = useState<boolean>(false);
const firstAvailableGPIO = useRef<number>(undefined);
const { data: sensorData, send: fetchSensorData } = useRequest(readSensorData, { const { data: sensorData, send: fetchSensorData } = useRequest(readSensorData, {
initialData: { initialData: {
ts: [], ts: [],
as: [], as: [],
analog_enabled: false, analog_enabled: false,
valid_gpio_list: [], valid_gpio_list: [] as number[],
platform: 'ESP32' platform: 'ESP32'
} }
}).onSuccess((event) => {
// store the first available GPIO in a ref
if (event.data.valid_gpio_list.length > 0) {
firstAvailableGPIO.current = event.data.valid_gpio_list[0];
}
}); });
const { send: sendTemperatureSensor } = useRequest( const { send: sendTemperatureSensor } = useRequest(
@@ -186,10 +191,14 @@ const Sensors = () => {
sortToggleType: SortToggleType.AlternateWithReset, sortToggleType: SortToggleType.AlternateWithReset,
sortFns: { sortFns: {
GPIO: (array) => GPIO: (array) =>
[...array].sort((a, b) => (a as AnalogSensor).g - (b as AnalogSensor).g), [...array].sort(
(a, b) => ((a as AnalogSensor)?.g ?? 0) - ((b as AnalogSensor)?.g ?? 0)
),
NAME: (array) => NAME: (array) =>
[...array].sort((a, b) => [...array].sort((a, b) =>
(a as AnalogSensor).n.localeCompare((b as AnalogSensor).n) ((a as AnalogSensor)?.n ?? '').localeCompare(
(b as AnalogSensor)?.n ?? ''
)
), ),
TYPE: (array) => TYPE: (array) =>
[...array].sort((a, b) => (a as AnalogSensor).t - (b as AnalogSensor).t), [...array].sort((a, b) => (a as AnalogSensor).t - (b as AnalogSensor).t),
@@ -338,19 +347,23 @@ const Sensors = () => {
}, [fetchSensorData]); }, [fetchSensorData]);
const addAnalogSensor = useCallback(() => { const addAnalogSensor = useCallback(() => {
if (firstAvailableGPIO.current === undefined) {
toast.error('No available GPIO found');
return;
}
setCreating(true); setCreating(true);
setSelectedAnalogSensor({ setSelectedAnalogSensor({
id: Math.floor(Math.random() * (MAX_TEMP_ID - MIN_TEMP_ID) + MIN_TEMP_ID), id: Math.floor(Math.random() * (MAX_TEMP_ID - MIN_TEMP_ID) + MIN_TEMP_ID),
n: '', n: '',
g: DEFAULT_GPIO, g: firstAvailableGPIO.current,
u: 0, u: DeviceValueUOM.NONE,
v: 0, v: 0,
o: 0, o: 0,
t: 0,
f: 1, f: 1,
t: AnalogType.NOTUSED,
d: false, d: false,
o_n: '', s: false,
s: false o_n: ''
}); });
setAnalogDialogOpen(true); setAnalogDialogOpen(true);
}, []); }, []);
@@ -447,7 +460,7 @@ const Sensors = () => {
item={as} item={as}
onClick={() => updateAnalogSensor(as)} onClick={() => updateAnalogSensor(as)}
> >
<Cell stiff>{as.g !== 99 ? as.g : ''}</Cell> <Cell stiff>{as.g}</Cell>
<Cell>{as.n}</Cell> <Cell>{as.n}</Cell>
<Cell stiff>{AnalogTypeNames[as.t]} </Cell> <Cell stiff>{AnalogTypeNames[as.t]} </Cell>
{(as.t === AnalogType.DIGITAL_OUT && {(as.t === AnalogType.DIGITAL_OUT &&
@@ -455,14 +468,10 @@ const Sensors = () => {
as.g !== GPIO_26) || as.g !== GPIO_26) ||
as.t === AnalogType.DIGITAL_IN || as.t === AnalogType.DIGITAL_IN ||
as.t === AnalogType.PULSE ? ( as.t === AnalogType.PULSE ? (
<Cell stiff> <Cell stiff>{as.v ? LL.ON() : LL.OFF()}</Cell>
{as.g !== 99 ? (as.v ? LL.ON() : LL.OFF()) : ''}
</Cell>
) : ( ) : (
<Cell stiff> <Cell stiff>
{as.t !== AnalogType.NOTUSED && as.g !== 99 {as.t !== AnalogType.NOTUSED ? formatValue(as.v, as.u) : ''}
? formatValue(as.v, as.u)
: ''}
</Cell> </Cell>
)} )}
</Row> </Row>

View File

@@ -152,20 +152,6 @@ const SensorsAnalogDialog = ({
[creating, LL] [creating, LL]
); );
// Ensure the current GPIO is in the list when no creating
// note GPIO 99 means not set
const availableGPIOs = useMemo(() => {
const filteredList = analogGPIOList.filter((gpio) => gpio !== 99);
if (
editItem.g !== undefined &&
editItem.g !== 99 &&
!filteredList.includes(editItem.g)
) {
return [...filteredList, editItem.g].sort((a, b) => a - b);
}
return filteredList;
}, [analogGPIOList, editItem.g]);
return ( return (
<Dialog sx={dialogStyle} open={open} onClose={handleClose}> <Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>{dialogTitle}</DialogTitle> <DialogTitle>{dialogTitle}</DialogTitle>
@@ -175,12 +161,12 @@ const SensorsAnalogDialog = ({
name="g" name="g"
label="GPIO" label="GPIO"
value={editItem.g} value={editItem.g}
sx={{ width: '8ch' }} sx={{ width: '9ch' }}
disabled={editItem.s} disabled={editItem.s}
select select
onChange={updateFormValue} onChange={updateFormValue}
> >
{availableGPIOs?.map((gpio: number) => ( {analogGPIOList?.map((gpio: number) => (
<MenuItem key={gpio} value={gpio}> <MenuItem key={gpio} value={gpio}>
{gpio} {gpio}
</MenuItem> </MenuItem>

View File

@@ -89,15 +89,15 @@ export interface TemperatureSensor {
export interface AnalogSensor { export interface AnalogSensor {
id: number; id: number;
g: number; // GPIO g: number; // GPIO
n: string; n: string; // name
v: number; v: number; // value
u: number; u: number; // uom
o: number; o: number; // offset
f: number; f: number; // factor
t: number; t: number; // type
d: boolean; // deleted flag d: boolean; // deleted flag
s: boolean; // system sensor flag s: boolean; // system sensor flag
o_n?: string; o_n?: string; // original name
} }
export interface WriteTemperatureSensor { export interface WriteTemperatureSensor {

View File

@@ -310,14 +310,6 @@ export const analogSensorItemValidation = (
{ required: true, message: 'Name is required' }, { required: true, message: 'Name is required' },
NAME_PATTERN, NAME_PATTERN,
uniqueAnalogNameValidator(sensors, sensor.o_n) uniqueAnalogNameValidator(sensors, sensor.o_n)
],
g: [
{
required: true,
type: 'number',
min: 1,
message: 'GPIO is required'
}
] ]
}); });
}; };

View File

@@ -987,7 +987,7 @@ const emsesp_sensordata = {
{ id: 1, g: 35, n: 'motor', v: 0, u: 0, o: 17, f: 0, t: 0, d: false, s: false }, { id: 1, g: 35, n: 'motor', v: 0, u: 0, o: 17, f: 0, t: 0, d: false, s: false },
{ {
id: 2, id: 2,
g: 37, g: 34,
n: 'External_switch', n: 'External_switch',
v: 13, v: 13,
u: 0, u: 0,
@@ -1047,7 +1047,7 @@ const emsesp_sensordata = {
} }
], ],
analog_enabled: true, analog_enabled: true,
valid_gpio_list: [0, 2, 5, 12, 13, 15, 18, 19, 23, 25, 26, 27, 33, 37, 38] valid_gpio_list: [] as number[]
}; };
const activity = { const activity = {
@@ -4540,6 +4540,25 @@ router
.get(EMSESP_SENSOR_DATA_ENDPOINT, () => { .get(EMSESP_SENSOR_DATA_ENDPOINT, () => {
// random change the zolder temperature 0-100 // random change the zolder temperature 0-100
emsesp_sensordata.ts[2].t = Math.floor(Math.random() * 100); emsesp_sensordata.ts[2].t = Math.floor(Math.random() * 100);
// Build list of available GPIOs (S3 board pins) excluding used ones
// and sort it
const allGPIOs = [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 21, 33, 34, 35, 36, 37, 38, 45, 46];
const usedGPIOs = new Set([
settings.led_gpio,
settings.dallas_gpio,
settings.pbutton_gpio,
settings.rx_gpio,
settings.tx_gpio,
...emsesp_sensordata.as.map((item) => item.g)
]);
emsesp_sensordata.valid_gpio_list = allGPIOs
.filter((gpio) => !usedGPIOs.has(gpio))
.sort((a, b) => a - b);
// console.log('valid_gpio_list', emsesp_sensordata.valid_gpio_list);
return emsesp_sensordata; return emsesp_sensordata;
}) })
.get(EMSESP_DEVICEDATA_ENDPOINT1, (request) => .get(EMSESP_DEVICEDATA_ENDPOINT1, (request) =>

View File

@@ -157,12 +157,13 @@ void AnalogSensor::reload(bool get_nvs) {
if (!found) { if (!found) {
// check if the GPIO is valid before registering. If not, force set the sensor to disabled, but don't remove it // check if the GPIO is valid before registering. If not, force set the sensor to disabled, but don't remove it
// should only trigger if uploading a customization file with invalid gpios. // should only trigger if uploading a customization file with invalid gpios.
AnalogType type = static_cast<AnalogType>(sensor.type);
if (!EMSESP::system_.is_valid_gpio(sensor.gpio)) { if (!EMSESP::system_.is_valid_gpio(sensor.gpio)) {
LOG_WARNING("Bad GPIO %d for Sensor %s. Disabling.", sensor.gpio, sensor.name.c_str()); LOG_WARNING("Bad GPIO %d for Sensor %s. Disabling.", sensor.gpio, sensor.name.c_str());
sensors_.emplace_back(99, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type, sensor.is_system); type = AnalogType::NOTUSED;
} else {
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type, sensor.is_system);
} }
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, type, sensor.is_system);
sensors_.back().ha_registered = false; // this will trigger recreate of the HA config sensors_.back().ha_registered = false; // this will trigger recreate of the HA config
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) { if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
sensors_.back().set_value(sensor.offset); sensors_.back().set_value(sensor.offset);

View File

@@ -146,7 +146,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
obj["t"] = sensor.type(); obj["t"] = sensor.type();
obj["s"] = sensor.is_system(); obj["s"] = sensor.is_system();
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED && sensor.gpio() != 99) { if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) {
obj["v"] = Helpers::transformNumFloat(sensor.value()); // is optional and is a float obj["v"] = Helpers::transformNumFloat(sensor.value()); // is optional and is a float
} else { } else {
obj["v"] = 0; // must have a value for web sorting to work obj["v"] = 0; // must have a value for web sorting to work
@@ -436,7 +436,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
uint8_t count = 0; uint8_t count = 0;
for (const auto & sensor : EMSESP::analogsensor_.sensors()) { for (const auto & sensor : EMSESP::analogsensor_.sensors()) {
// ignore system and disabled sensors // ignore system and disabled sensors
if (sensor.is_system() || sensor.type() == AnalogSensor::AnalogType::NOTUSED || sensor.gpio() == 99) { if (sensor.is_system() || sensor.type() == AnalogSensor::AnalogType::NOTUSED) {
continue; continue;
} }
JsonObject node = nodes.add<JsonObject>(); JsonObject node = nodes.add<JsonObject>();