diff --git a/src/hasp/hasp_task.cpp b/src/hasp/hasp_task.cpp
index 09650380..3957d60d 100644
--- a/src/hasp/hasp_task.cpp
+++ b/src/hasp/hasp_task.cpp
@@ -15,6 +15,10 @@ For full license information read the LICENSE file in the project folder */
#include "hasp_gui.h"
#endif
+#if HASP_USE_GPIO > 0
+#include "sys/gpio/hasp_gpio.h"
+#endif
+
#ifdef HASP_USE_STAT_COUNTER
extern uint16_t statLoopCounter; // measures the average looptime
#endif
@@ -24,6 +28,10 @@ void task_every_second_cb(lv_task_t* task)
{
haspEverySecond(); // sleep timer & statusupdate
+#if HASP_USE_GPIO > 0
+ gpioEverySecond();
+#endif
+
#if HASP_MQTT_TELNET > 0
mqttEverySecond();
#endif
diff --git a/src/hasp_config.h b/src/hasp_config.h
index 04160d30..ad81657b 100644
--- a/src/hasp_config.h
+++ b/src/hasp_config.h
@@ -93,6 +93,7 @@ const char FP_GUI_REPEAT_TIME[] PROGMEM = "repeat";
const char FP_DEBUG_TELEPERIOD[] PROGMEM = "tele";
const char FP_DEBUG_ANSI[] PROGMEM = "ansi";
const char FP_GPIO_CONFIG[] PROGMEM = "config";
+const char FP_GPIO_ADC_MAX[] PROGMEM = "adc_max"; // per-slot ADC ceiling for ambient-light scaling
const char FP_HASP_CONFIG_FILE[] PROGMEM = "/config.json";
diff --git a/src/hasp_gui.cpp b/src/hasp_gui.cpp
index 17eb72be..4cd54c32 100644
--- a/src/hasp_gui.cpp
+++ b/src/hasp_gui.cpp
@@ -159,6 +159,10 @@ void guiCalibrate(void)
// }
lv_obj_invalidate(lv_disp_get_layer_sys(NULL));
+
+#if HASP_USE_CONFIG > 0
+ configWrite(); // Persist calibration data so it survives reboot
+#endif
#endif
}
diff --git a/src/lang/en_US.h b/src/lang/en_US.h
index 0ec1b257..3b49eba2 100644
--- a/src/lang/en_US.h
+++ b/src/lang/en_US.h
@@ -231,6 +231,7 @@
#define D_GPIO_LIGHT_RELAY "Light Relay"
#define D_GPIO_PWM "PWM"
#define D_GPIO_DAC "DAC"
+#define D_GPIO_ADC_BACKLIGHT "Ambient Light (Auto Backlight)"
#define D_GPIO_SERIAL_DIMMER "Serial Dimmer"
#define D_GPIO_UNKNOWN "Unknown"
#define D_GPIO_PIN "Pin"
diff --git a/src/main.cpp b/src/main.cpp
index b21d9e24..2555ee7f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,6 +17,10 @@
#include "sys/net/hasp_time.h"
#include "dev/device.h"
+#if HASP_USE_GPIO > 0
+#include "sys/gpio/hasp_gpio.h"
+#endif
+
#if HASP_USE_CONFIG > 0
#include "hasp_debug.h"
#include "hasp_macro.h"
@@ -215,6 +219,10 @@ IRAM_ATTR void loop()
/* Runs Every Second */
haspEverySecond(); // sleep timer & statusupdate
+#if HASP_USE_GPIO > 0
+ gpioEverySecond();
+#endif
+
#if HASP_USE_MQTT > 0
mqttEverySecond();
#endif
diff --git a/src/sys/gpio/hasp_gpio.cpp b/src/sys/gpio/hasp_gpio.cpp
index 48bfc496..70886ec9 100644
--- a/src/sys/gpio/hasp_gpio.cpp
+++ b/src/sys/gpio/hasp_gpio.cpp
@@ -329,6 +329,17 @@ static void gpio_setup_pin(uint8_t index)
break;
}
+ case hasp_gpio_type_t::HASP_ADC:
+ pinMode(gpio->pin, INPUT);
+ gpio->max = 4095; // 12-bit ADC full range (overrides the default 255 set above)
+#if defined(ARDUINO_ARCH_ESP32)
+ analogSetPinAttenuation(gpio->pin, ADC_11db); // full 0-3.3V range
+ gpio->val = analogRead(gpio->pin); // seed the smoothing filter
+#else
+ gpio->val = analogRead(gpio->pin);
+#endif
+ break;
+
case hasp_gpio_type_t::FREE:
return;
@@ -391,6 +402,33 @@ static inline bool gpio_is_output(hasp_gpio_config_t* gpio)
return (gpio->type > hasp_gpio_type_t::USED) && (gpio->type < 0x80);
}
+void gpioEverySecond(void)
+{
+#if defined(ARDUINO_ARCH_ESP32)
+ for(uint8_t i = 0; i < HASP_NUM_GPIO_CONFIG; i++) {
+ if(!gpioConfigInUse(i) || gpioConfig[i].type != hasp_gpio_type_t::HASP_ADC) continue;
+
+ // Exponential moving average (alpha = 1/8) to smooth noisy ADC readings
+ uint16_t raw = analogRead(gpioConfig[i].pin);
+ gpioConfig[i].val = (gpioConfig[i].val * 7 + raw) >> 3;
+
+ // Only adjust backlight when the screen is currently on
+ if(!haspDevice.get_backlight_power()) continue;
+
+ // Map smoothed ADC value (0..ceiling) to backlight level (0-255).
+ // 'max' defaults to 4095 (full 12-bit range) but can be reduced for
+ // in-case installs where the sensor never sees full daylight, so the
+ // full backlight range is still exercised.
+ uint16_t ceiling = gpioConfig[i].max > 0 ? gpioConfig[i].max : 4095;
+ uint8_t level = (uint8_t)min((uint32_t)255, (uint32_t)gpioConfig[i].val * 255 / ceiling);
+ if(gpioConfig[i].inverted) level = 255 - level;
+ if(level < 10) level = 10; // always keep screen readable in darkness
+
+ haspDevice.set_backlight_level(level);
+ }
+#endif
+}
+
void gpioEvery5Seconds(void)
{
for(uint8_t i = 0; i < HASP_NUM_GPIO_CONFIG; i++) {
@@ -1001,6 +1039,40 @@ bool gpioGetConfig(const JsonObject& settings)
changed = true;
}
+ /* Save per-slot ADC ceiling values (for ambient-light auto-backlight calibration).
+ * Only written for slots with a non-default max (i.e. user has calibrated for their install).
+ * A value of 0 in the array means "use type default" (4095 for ADC). */
+ bool hasAdcMax = false;
+ for(uint8_t j = 0; j < HASP_NUM_GPIO_CONFIG; j++) {
+ if(gpioConfig[j].type == hasp_gpio_type_t::HASP_ADC && gpioConfig[j].max != 4095 && gpioConfig[j].max != 0) {
+ hasAdcMax = true;
+ break;
+ }
+ }
+ if(hasAdcMax) {
+ /* Compare against what's already in JSON before marking changed */
+ JsonArray existingAdcArr = settings[FPSTR(FP_GPIO_ADC_MAX)].as
" D_GPIO_GROUP "
"); httpMessage += F("Default State
"); httpMessage += F("Resistor
"); httpMessage += F("" D_GPIO_GROUP "