diff --git a/interface/src/app/main/Sensors.tsx b/interface/src/app/main/Sensors.tsx
index bbb8fd636..891b41954 100644
--- a/interface/src/app/main/Sensors.tsx
+++ b/interface/src/app/main/Sensors.tsx
@@ -54,7 +54,7 @@ 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 = 21; // Safe GPIO for all platforms
+const DEFAULT_GPIO = -1; // not set
const MIN_TEMP_ID = -100;
const MAX_TEMP_ID = 100;
const GPIO_25 = 25;
@@ -134,6 +134,7 @@ const Sensors = () => {
ts: [],
as: [],
analog_enabled: false,
+ valid_gpio_list: [],
platform: 'ESP32'
}
});
@@ -573,10 +574,10 @@ const Sensors = () => {
onSave={onAnalogDialogSave}
creating={creating}
selectedItem={selectedAnalogSensor}
+ analogGPIOList={sensorData.valid_gpio_list}
validator={analogSensorItemValidation(
sensorData.as,
selectedAnalogSensor,
- creating,
sensorData.platform
)}
/>
diff --git a/interface/src/app/main/SensorsAnalogDialog.tsx b/interface/src/app/main/SensorsAnalogDialog.tsx
index ee5087d12..6473dec2a 100644
--- a/interface/src/app/main/SensorsAnalogDialog.tsx
+++ b/interface/src/app/main/SensorsAnalogDialog.tsx
@@ -35,6 +35,7 @@ interface DashboardSensorsAnalogDialogProps {
onSave: (as: AnalogSensor) => void;
creating: boolean;
selectedItem: AnalogSensor;
+ analogGPIOList: number[];
validator: Schema;
}
@@ -44,6 +45,7 @@ const SensorsAnalogDialog = ({
onSave,
creating,
selectedItem,
+ analogGPIOList,
validator
}: DashboardSensorsAnalogDialogProps) => {
const { LL } = useI18nContext();
@@ -156,28 +158,23 @@ const SensorsAnalogDialog = ({
{dialogTitle}
+
+ {analogGPIOList?.map((gpio: number) => (
+
+ ))}
+
-
- {creating && (
-
-
- {LL.WARN_GPIO()}
-
-
- )}
-
-
{analogTypeMenuItems}
@@ -207,6 +205,7 @@ const SensorsAnalogDialog = ({
sx={{ width: '15ch' }}
select
onChange={updateFormValue}
+ disabled={editItem.s}
>
{uomMenuItems}
@@ -222,6 +221,7 @@ const SensorsAnalogDialog = ({
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}
+ disabled={editItem.s}
slotProps={{
input: {
startAdornment: (
@@ -243,6 +243,7 @@ const SensorsAnalogDialog = ({
type="number"
variant="outlined"
onChange={updateFormValue}
+ disabled={editItem.s}
slotProps={{
input: {
startAdornment: (
@@ -264,6 +265,7 @@ const SensorsAnalogDialog = ({
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}
+ disabled={editItem.s}
slotProps={{
htmlInput: { step: '0.001' }
}}
@@ -280,6 +282,7 @@ const SensorsAnalogDialog = ({
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}
+ disabled={editItem.s}
/>
)}
@@ -293,6 +296,7 @@ const SensorsAnalogDialog = ({
type="number"
variant="outlined"
onChange={updateFormValue}
+ disabled={editItem.s}
slotProps={{
htmlInput: { step: '0.001' }
}}
@@ -309,6 +313,7 @@ const SensorsAnalogDialog = ({
type="number"
variant="outlined"
onChange={updateFormValue}
+ disabled={editItem.s}
slotProps={{
htmlInput: { min: '0', max: '255', step: '1' }
}}
@@ -325,6 +330,7 @@ const SensorsAnalogDialog = ({
select
variant="outlined"
onChange={updateFormValue}
+ disabled={editItem.s}
>
@@ -338,6 +344,7 @@ const SensorsAnalogDialog = ({
sx={{ width: '15ch' }}
select
onChange={updateFormValue}
+ disabled={editItem.s}
>
@@ -351,6 +358,7 @@ const SensorsAnalogDialog = ({
value={editItem.u}
select
onChange={updateFormValue}
+ disabled={editItem.s}
>
+ {fieldErrors && Object.keys(fieldErrors).length > 0 && (
+
+ {Object.values(fieldErrors).map((errArr, idx) =>
+ Array.isArray(errArr)
+ ? errArr.map((err, j) => (
+
+ {err.message}
+
+ ))
+ : null
+ )}
+
+ )}
{editItem.s && (
diff --git a/interface/src/app/main/types.ts b/interface/src/app/main/types.ts
index 26dd876c0..280dbc1c3 100644
--- a/interface/src/app/main/types.ts
+++ b/interface/src/app/main/types.ts
@@ -111,6 +111,7 @@ export interface SensorData {
ts: TemperatureSensor[];
as: AnalogSensor[];
analog_enabled: boolean;
+ valid_gpio_list: number[];
platform: string;
}
diff --git a/interface/src/app/main/validators.ts b/interface/src/app/main/validators.ts
index ad4d6f95b..7ededce0c 100644
--- a/interface/src/app/main/validators.ts
+++ b/interface/src/app/main/validators.ts
@@ -45,121 +45,15 @@ const VALIDATION_LIMITS = {
HEX_BASE: 16
} as const;
-// Helper to create GPIO validator from invalid ranges
-const createGPIOValidator = (
- invalidRanges: Array,
- maxValue: number
-) => ({
- validator(
- _rule: InternalRuleItem,
- value: number,
- callback: (error?: string) => void
- ) {
- if (!value) {
- callback();
- return;
- }
-
- if (value < 0 || value > maxValue) {
- callback(ERROR_MESSAGES.GPIO_INVALID);
- return;
- }
-
- for (const range of invalidRanges) {
- if (typeof range === 'number') {
- if (value === range) {
- callback(ERROR_MESSAGES.GPIO_INVALID);
- return;
- }
- } else {
- const [start, end] = range;
- if (value >= start && value <= end) {
- callback(ERROR_MESSAGES.GPIO_INVALID);
- return;
- }
- }
- }
-
- callback();
- }
-});
-
-export const GPIO_VALIDATOR = createGPIOValidator(
- [[6, 11], 1, 20, 24, [28, 31]],
- 40
-);
-
-export const GPIO_VALIDATORC3 = createGPIOValidator([[11, 19]], 21);
-
-export const GPIO_VALIDATORS2 = createGPIOValidator(
- [
- [19, 20],
- [22, 32]
- ],
- 40
-);
-
-export const GPIO_VALIDATORS3 = createGPIOValidator(
- [
- [19, 20],
- [22, 37],
- [39, 42]
- ],
- 48
-);
-
-const GPIO_FIELD_NAMES = [
- 'led_gpio',
- 'dallas_gpio',
- 'pbutton_gpio',
- 'tx_gpio',
- 'rx_gpio'
-] as const;
-
type ValidationRules = Array<{
required?: boolean;
message?: string;
[key: string]: unknown;
}>;
-const createGPIOValidations = (
- validator: typeof GPIO_VALIDATOR
-): Record =>
- GPIO_FIELD_NAMES.reduce(
- (acc, field) => {
- const fieldName = field.replace('_gpio', '').toUpperCase();
- acc[field] = [
- { required: true, message: `${fieldName} GPIO is required` },
- validator
- ];
- return acc;
- },
- {} as Record
- );
-
-const PLATFORM_VALIDATORS = {
- ESP32: GPIO_VALIDATOR,
- ESP32C3: GPIO_VALIDATORC3,
- ESP32S2: GPIO_VALIDATORS2,
- ESP32S3: GPIO_VALIDATORS3
-} as const;
-
export const createSettingsValidator = (settings: Settings) => {
const schema: Record = {};
- // Add GPIO validations for CUSTOM board profiles
- if (
- settings.board_profile === 'CUSTOM' &&
- settings.platform in PLATFORM_VALIDATORS
- ) {
- Object.assign(
- schema,
- createGPIOValidations(
- PLATFORM_VALIDATORS[settings.platform as keyof typeof PLATFORM_VALIDATORS]
- )
- );
- }
-
// Syslog validations
if (settings.syslog_enabled) {
schema.syslog_host = [
@@ -401,52 +295,29 @@ export const temperatureSensorItemValidation = (
n: [NAME_PATTERN, uniqueTemperatureNameValidator(sensors, sensor.o_n)]
});
-export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({
- validator(
- _rule: InternalRuleItem,
- gpio: number,
- callback: (error?: string) => void
- ) {
- if (sensors.some((as) => as.g === gpio)) {
- callback(ERROR_MESSAGES.GPIO_DUPLICATE);
- return;
- }
- callback();
- }
-});
-
export const uniqueAnalogNameValidator = (
sensors: AnalogSensor[],
o_name?: string
) => createUniqueFieldNameValidator(sensors, (s) => s.n, o_name);
-const getPlatformGPIOValidator = (platform: string) => {
- switch (platform) {
- case 'ESP32S3':
- return GPIO_VALIDATORS3;
- case 'ESP32S2':
- return GPIO_VALIDATORS2;
- case 'ESP32C3':
- return GPIO_VALIDATORC3;
- default:
- return GPIO_VALIDATOR;
- }
-};
-
export const analogSensorItemValidation = (
sensors: AnalogSensor[],
- sensor: AnalogSensor,
- creating: boolean,
- platform: string
+ sensor: AnalogSensor
) => {
- const gpioValidator = getPlatformGPIOValidator(platform);
-
return new Schema({
- n: [NAME_PATTERN, uniqueAnalogNameValidator(sensors, sensor.o_n)],
+ // name is required and must be unique
+ n: [
+ { required: true, message: 'Name is required' },
+ NAME_PATTERN,
+ uniqueAnalogNameValidator(sensors, sensor.o_n)
+ ],
g: [
- { required: true, message: 'GPIO is required' },
- gpioValidator,
- ...(creating ? [isGPIOUniqueValidator(sensors)] : [])
+ {
+ required: true,
+ type: 'number',
+ min: 1,
+ message: 'GPIO is required'
+ }
]
});
};
diff --git a/interface/src/i18n/cz/index.ts b/interface/src/i18n/cz/index.ts
index 61b156e6c..704ba83cc 100644
--- a/interface/src/i18n/cz/index.ts
+++ b/interface/src/i18n/cz/index.ts
@@ -60,7 +60,6 @@ const cz: Translation = {
DUTY_CYCLE: 'Pracovní cyklus',
UNIT: 'Jednotka',
STARTVALUE: 'Počáteční hodnota',
- WARN_GPIO: 'Upozornění: buďte opatrní při přiřazování GPIO!',
EDIT: 'Upravit',
SENSOR: 'Senzor',
TEMP_SENSOR: 'Teplotní senzor',
diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts
index cb481cb0e..1e578fc24 100644
--- a/interface/src/i18n/de/index.ts
+++ b/interface/src/i18n/de/index.ts
@@ -60,7 +60,6 @@ const de: Translation = {
DUTY_CYCLE: 'Arbeitszyklus',
UNIT: 'Maßeinheit',
STARTVALUE: 'Startwert',
- WARN_GPIO: 'Warnung: Vorsicht bei der korrekten Wahl des GPIO!',
EDIT: 'Editiere',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperatursensor',
diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts
index 81f07294d..38a6e3185 100644
--- a/interface/src/i18n/en/index.ts
+++ b/interface/src/i18n/en/index.ts
@@ -60,7 +60,6 @@ const en: Translation = {
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Start Value',
- WARN_GPIO: 'Warning: be careful when assigning a GPIO!',
EDIT: 'Edit',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperature Sensor',
diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts
index cda12b9f0..cfbbaa81d 100644
--- a/interface/src/i18n/fr/index.ts
+++ b/interface/src/i18n/fr/index.ts
@@ -60,7 +60,6 @@ const fr: Translation = {
DUTY_CYCLE: 'Cycle de fonctionnement',
UNIT: 'Unité',
STARTVALUE: 'Valeur de départ',
- WARN_GPIO: 'Attention: soyez vigilant en choisissant un GPIO!',
EDIT: 'Éditer',
SENSOR: 'Capteur',
TEMP_SENSOR: 'Capteur de température',
diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts
index 44cf4f9b6..3dba434f6 100644
--- a/interface/src/i18n/it/index.ts
+++ b/interface/src/i18n/it/index.ts
@@ -60,7 +60,6 @@ const it: Translation = {
DUTY_CYCLE: 'Ciclo di lavoro',
UNIT: 'UoM',
STARTVALUE: 'Valore di partenza',
- WARN_GPIO: 'Avvertimento: prestare attenzione quando si assegna un GPIO!',
EDIT: 'Modifica',
SENSOR: 'Sensore',
TEMP_SENSOR: 'Sensore Temperatura',
diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts
index affdddfe2..579202eb6 100644
--- a/interface/src/i18n/nl/index.ts
+++ b/interface/src/i18n/nl/index.ts
@@ -60,7 +60,6 @@ const nl: Translation = {
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Startwaarde',
- WARN_GPIO: 'Waarschuwing: let op met het koppelen van de juiste GPIO pin!',
EDIT: 'Wijzigen',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperatuur sensor',
diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts
index 0c1e88133..a2fbbaa6c 100644
--- a/interface/src/i18n/no/index.ts
+++ b/interface/src/i18n/no/index.ts
@@ -60,7 +60,6 @@ const no: Translation = {
DUTY_CYCLE: 'Duty Cycle',
UNIT: 'UoM',
STARTVALUE: 'Startverdi',
- WARN_GPIO: 'Advarsel: vær forsiktig ved aktivering av GPIO!',
EDIT: 'Endre',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperatursensor',
diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts
index bbc90b380..6dd874a32 100644
--- a/interface/src/i18n/pl/index.ts
+++ b/interface/src/i18n/pl/index.ts
@@ -60,7 +60,6 @@ const pl: BaseTranslation = {
DUTY_CYCLE: 'Wypełnienie',
UNIT: 'J.m.',
STARTVALUE: 'Wartość początkowa',
- WARN_GPIO: 'Uwaga! Zachowaj ostrożność przypisując GPIO do urządzenia!',
EDIT: 'Edycja',
SENSOR: '{{c|ustawienia c||ustawień c|}}zujnika',
TEMP_SENSOR: 'czujnika temperatury',
diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts
index aa22a83cb..52e6f9c75 100644
--- a/interface/src/i18n/sk/index.ts
+++ b/interface/src/i18n/sk/index.ts
@@ -60,7 +60,6 @@ const sk: Translation = {
DUTY_CYCLE: 'Pracovný cyklus',
UNIT: 'UoM',
STARTVALUE: 'Počiatočná hodnota',
- WARN_GPIO: 'Upozornenie: Buďte opatrní pri priraďovaní GPIO!',
EDIT: 'Editovať',
SENSOR: 'Snímač',
TEMP_SENSOR: 'Snímač teploty',
diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts
index 9a07450b8..644484269 100644
--- a/interface/src/i18n/sv/index.ts
+++ b/interface/src/i18n/sv/index.ts
@@ -60,7 +60,6 @@ const sv: Translation = {
DUTY_CYCLE: 'Pulskvot',
UNIT: 'Måttenhet',
STARTVALUE: 'Startvärde',
- WARN_GPIO: 'Varning: Var försiktig vid aktivering av GPIO!',
EDIT: 'Ändra',
SENSOR: 'Sensor',
TEMP_SENSOR: 'Temperatursensor',
diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts
index e212ea274..79824decd 100644
--- a/interface/src/i18n/tr/index.ts
+++ b/interface/src/i18n/tr/index.ts
@@ -60,7 +60,6 @@ const tr: Translation = {
DUTY_CYCLE: 'Görev Çevrimi',
UNIT: 'ÖB',
STARTVALUE: 'Başlangıç değeri',
- WARN_GPIO: 'Uyarı: bir GPIO atarken dikkatli olun!',
EDIT: 'Değiştir',
SENSOR: 'Sensör',
TEMP_SENSOR: 'Sıcaklık Sensörü',
diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts
index eb8f18acb..ce7484bea 100644
--- a/mock-api/restServer.ts
+++ b/mock-api/restServer.ts
@@ -1046,7 +1046,8 @@ const emsesp_sensordata = {
s: true
}
],
- analog_enabled: true
+ analog_enabled: true,
+ valid_gpio_list: [6, 11, 20, 24, 28, 31, 1, 40]
};
const activity = {
diff --git a/src/core/system.cpp b/src/core/system.cpp
index ac0ce94ef..4064ca4cd 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -2320,4 +2320,41 @@ uint8_t System::systemStatus() {
return systemStatus_;
}
+// return the list of valid GPIOs
+std::vector System::valid_gpio_list() const {
+ // get free gpios based on board type
+#if CONFIG_IDF_TARGET_ESP32C3
+ std::vector valid_gpios = {11, 19, 21};
+#elif CONFIG_IDF_TARGET_ESP32S2
+ std::vector valid_gpios = {19, 20, 22, 32, 40};
+#elif CONFIG_IDF_TARGET_ESP32S3
+ std::vector valid_gpios = {19, 20, 22, 37, 39, 42, 48};
+#elif CONFIG_IDF_TARGET_ESP32
+ std::vector valid_gpios = {6, 11, 20, 24, 28, 31, 1, 40};
+#else
+ std::vector valid_gpios = {};
+#endif
+
+ // filter out GPIOs already used in application settings
+ for (const auto & gpio : valid_gpios) {
+ if (gpio == pbutton_gpio_ || gpio == led_gpio_ || gpio == dallas_gpio_ || gpio == rx_gpio_ || gpio == tx_gpio_) {
+ valid_gpios.erase(std::remove(valid_gpios.begin(), valid_gpios.end(), gpio), valid_gpios.end());
+ }
+ }
+
+ // filter out GPIOs already used in analog sensors, if enabled
+ if (analog_enabled_) {
+ for (const auto & sensor : EMSESP::analogsensor_.sensors()) {
+ if (std::find(valid_gpios.begin(), valid_gpios.end(), sensor.gpio()) != valid_gpios.end()) {
+ valid_gpios.erase(std::find(valid_gpios.begin(), valid_gpios.end(), sensor.gpio()));
+ }
+ }
+ }
+
+ // sort the list of valid GPIOs
+ std::sort(valid_gpios.begin(), valid_gpios.end());
+
+ return valid_gpios;
+}
+
} // namespace emsesp
diff --git a/src/core/system.h b/src/core/system.h
index 01bf97f88..7e4e08f32 100644
--- a/src/core/system.h
+++ b/src/core/system.h
@@ -137,6 +137,8 @@ class System {
void systemStatus(uint8_t status_code);
uint8_t systemStatus();
+ std::vector valid_gpio_list() const;
+
static void extractSettings(const char * filename, const char * section, JsonObject output);
static bool saveSettings(const char * filename, const char * section, JsonObject input);
diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp
index dc5005bd6..4b78399f4 100644
--- a/src/web/WebDataService.cpp
+++ b/src/web/WebDataService.cpp
@@ -157,6 +157,11 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
root["analog_enabled"] = EMSESP::analog_enabled();
root["platform"] = EMSESP_PLATFORM;
+ JsonArray valid_gpio_list = root["valid_gpio_list"].to();
+ for (const auto & gpio : EMSESP::system_.valid_gpio_list()) {
+ valid_gpio_list.add(gpio);
+ }
+
response->setLength();
request->send(response);
}