diff --git a/interface/src/app/main/Sensors.tsx b/interface/src/app/main/Sensors.tsx index f1ba46979..b2e90736e 100644 --- a/interface/src/app/main/Sensors.tsx +++ b/interface/src/app/main/Sensors.tsx @@ -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 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_HOUR = 60 * MS_PER_MINUTE; const MS_PER_DAY = 24 * MS_PER_HOUR; -const DEFAULT_GPIO = 99; // not set const MIN_TEMP_ID = -100; const MAX_TEMP_ID = 100; const GPIO_25 = 25; @@ -128,15 +127,21 @@ const Sensors = () => { const [temperatureDialogOpen, setTemperatureDialogOpen] = useState(false); const [analogDialogOpen, setAnalogDialogOpen] = useState(false); const [creating, setCreating] = useState(false); + const firstAvailableGPIO = useRef(undefined); const { data: sensorData, send: fetchSensorData } = useRequest(readSensorData, { initialData: { ts: [], as: [], analog_enabled: false, - valid_gpio_list: [], + valid_gpio_list: [] as number[], 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( @@ -186,10 +191,14 @@ const Sensors = () => { sortToggleType: SortToggleType.AlternateWithReset, sortFns: { 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) => [...array].sort((a, b) => - (a as AnalogSensor).n.localeCompare((b as AnalogSensor).n) + ((a as AnalogSensor)?.n ?? '').localeCompare( + (b as AnalogSensor)?.n ?? '' + ) ), TYPE: (array) => [...array].sort((a, b) => (a as AnalogSensor).t - (b as AnalogSensor).t), @@ -338,19 +347,23 @@ const Sensors = () => { }, [fetchSensorData]); const addAnalogSensor = useCallback(() => { + if (firstAvailableGPIO.current === undefined) { + toast.error('No available GPIO found'); + return; + } setCreating(true); setSelectedAnalogSensor({ id: Math.floor(Math.random() * (MAX_TEMP_ID - MIN_TEMP_ID) + MIN_TEMP_ID), n: '', - g: DEFAULT_GPIO, - u: 0, + g: firstAvailableGPIO.current, + u: DeviceValueUOM.NONE, v: 0, o: 0, - t: 0, f: 1, + t: AnalogType.NOTUSED, d: false, - o_n: '', - s: false + s: false, + o_n: '' }); setAnalogDialogOpen(true); }, []); @@ -447,7 +460,7 @@ const Sensors = () => { item={as} onClick={() => updateAnalogSensor(as)} > - {as.g !== 99 ? as.g : ''} + {as.g} {as.n} {AnalogTypeNames[as.t]} {(as.t === AnalogType.DIGITAL_OUT && @@ -455,14 +468,10 @@ const Sensors = () => { as.g !== GPIO_26) || as.t === AnalogType.DIGITAL_IN || as.t === AnalogType.PULSE ? ( - - {as.g !== 99 ? (as.v ? LL.ON() : LL.OFF()) : ''} - + {as.v ? LL.ON() : LL.OFF()} ) : ( - {as.t !== AnalogType.NOTUSED && as.g !== 99 - ? formatValue(as.v, as.u) - : ''} + {as.t !== AnalogType.NOTUSED ? formatValue(as.v, as.u) : ''} )} diff --git a/interface/src/app/main/SensorsAnalogDialog.tsx b/interface/src/app/main/SensorsAnalogDialog.tsx index c9d99c42c..179d1fc2b 100644 --- a/interface/src/app/main/SensorsAnalogDialog.tsx +++ b/interface/src/app/main/SensorsAnalogDialog.tsx @@ -152,20 +152,6 @@ const SensorsAnalogDialog = ({ [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 ( {dialogTitle} @@ -175,12 +161,12 @@ const SensorsAnalogDialog = ({ name="g" label="GPIO" value={editItem.g} - sx={{ width: '8ch' }} + sx={{ width: '9ch' }} disabled={editItem.s} select onChange={updateFormValue} > - {availableGPIOs?.map((gpio: number) => ( + {analogGPIOList?.map((gpio: number) => ( {gpio} diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts index 280dbc1c3..cea98fd44 100644 --- a/interface/src/app/main/types.ts +++ b/interface/src/app/main/types.ts @@ -89,15 +89,15 @@ export interface TemperatureSensor { export interface AnalogSensor { id: number; g: number; // GPIO - n: string; - v: number; - u: number; - o: number; - f: number; - t: number; + n: string; // name + v: number; // value + u: number; // uom + o: number; // offset + f: number; // factor + t: number; // type d: boolean; // deleted flag s: boolean; // system sensor flag - o_n?: string; + o_n?: string; // original name } export interface WriteTemperatureSensor { diff --git a/interface/src/app/main/validators.ts b/interface/src/app/main/validators.ts index 7ededce0c..8f320dcfb 100644 --- a/interface/src/app/main/validators.ts +++ b/interface/src/app/main/validators.ts @@ -310,14 +310,6 @@ export const analogSensorItemValidation = ( { required: true, message: 'Name is required' }, NAME_PATTERN, uniqueAnalogNameValidator(sensors, sensor.o_n) - ], - g: [ - { - required: true, - type: 'number', - min: 1, - message: 'GPIO is required' - } ] }); }; diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index 1268aeb1f..7fe7db9f9 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -276,10 +276,10 @@ function updateMask(entity: any, de: any, dd: any) { const old_custom_name = dd.nodes[dd_objIndex].cn; console.log( 'comparing names, old (' + - old_custom_name + - ') with new (' + - new_custom_name + - ')' + old_custom_name + + ') with new (' + + new_custom_name + + ')' ); if (old_custom_name !== new_custom_name) { changed = true; @@ -375,15 +375,15 @@ function check_upgrade(version: string) { console.log( 'Upgrade this version (' + - THIS_VERSION + - ') to dev (' + - dev_version + - ') is ' + - (DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + - ' and to stable (' + - stable_version + - ') is ' + - (STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + THIS_VERSION + + ') to dev (' + + dev_version + + ') is ' + + (DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + + ' and to stable (' + + stable_version + + ') is ' + + (STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') ); data = { emsesp_version: THIS_VERSION, @@ -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: 2, - g: 37, + g: 34, n: 'External_switch', v: 13, u: 0, @@ -1047,7 +1047,7 @@ const emsesp_sensordata = { } ], 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 = { @@ -4540,6 +4540,25 @@ router .get(EMSESP_SENSOR_DATA_ENDPOINT, () => { // random change the zolder temperature 0-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; }) .get(EMSESP_DEVICEDATA_ENDPOINT1, (request) => diff --git a/src/core/analogsensor.cpp b/src/core/analogsensor.cpp index cfa19f91c..988cca9a6 100644 --- a/src/core/analogsensor.cpp +++ b/src/core/analogsensor.cpp @@ -157,12 +157,13 @@ void AnalogSensor::reload(bool get_nvs) { if (!found) { // 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. + AnalogType type = static_cast(sensor.type); if (!EMSESP::system_.is_valid_gpio(sensor.gpio)) { 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); - } else { - sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type, sensor.is_system); + type = AnalogType::NOTUSED; } + 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 if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) { sensors_.back().set_value(sensor.offset); diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 1ba81c416..3db967c7d 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -146,7 +146,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { obj["t"] = sensor.type(); 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 } else { 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; for (const auto & sensor : EMSESP::analogsensor_.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; } JsonObject node = nodes.add();