Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/hasp/hasp_task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/hasp_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
4 changes: 4 additions & 0 deletions src/hasp_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions src/lang/en_US.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 8 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
88 changes: 88 additions & 0 deletions src/sys/gpio/hasp_gpio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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<JsonArray>();
uint8_t k = 0;
for(JsonVariant v : existingAdcArr) {
if(k < HASP_NUM_GPIO_CONFIG) {
uint16_t m = (gpioConfig[k].type == hasp_gpio_type_t::HASP_ADC) ? gpioConfig[k].max : 0;
if(v.as<uint16_t>() != m) changed = true;
} else {
changed = true;
}
k++;
}
if(k != HASP_NUM_GPIO_CONFIG) changed = true;

if(changed) { /* Rebuild only when necessary */
JsonArray adcArr = settings[FPSTR(FP_GPIO_ADC_MAX)].to<JsonArray>();
for(uint8_t j = 0; j < HASP_NUM_GPIO_CONFIG; j++) {
uint16_t m = (gpioConfig[j].type == hasp_gpio_type_t::HASP_ADC) ? gpioConfig[j].max : 0;
adcArr.add(m);
}
}
}

if(changed) configOutput(settings, TAG_GPIO);
return changed;
}
Expand Down Expand Up @@ -1042,6 +1114,22 @@ bool gpioSetConfig(const JsonObject& settings)
changed |= status;
}

/* Load per-slot ADC ceiling values. These are applied before gpioSetup() calls
* gpio_setup_pin(), which preserves any non-zero max already set here. */
if(!settings[FPSTR(FP_GPIO_ADC_MAX)].isNull()) {
int j = 0;
JsonArray adcArr = settings[FPSTR(FP_GPIO_ADC_MAX)].as<JsonArray>();
for(JsonVariant v : adcArr) {
if(j < HASP_NUM_GPIO_CONFIG) {
uint16_t m = v.as<uint16_t>();
if(m > 0 && gpioConfig[j].type == hasp_gpio_type_t::HASP_ADC) {
gpioConfig[j].max = m;
}
}
j++;
}
}

return changed;
}
#endif // HASP_USE_CONFIG
1 change: 1 addition & 0 deletions src/sys/gpio/hasp_gpio.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ extern "C" {

void gpioSetup(void);
IRAM_ATTR void gpioLoop(void);
void gpioEverySecond(void);
void gpioEvery5Seconds(void);

void gpio_set_normalized_group_values(hasp_update_value_t& value);
Expand Down
10 changes: 8 additions & 2 deletions src/sys/svc/hasp_http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1769,10 +1769,12 @@ static void webHandleGpioConfig()
uint8_t pinfunc = webServer.arg("func").toInt();
bool inverted = webServer.arg("state").toInt();
gpioSavePinConfig(id, pin, type, group, pinfunc, inverted);
configWrite(); // persist to config.json
}

if(webServer.hasArg("del")) {
gpioSavePinConfig(id, pin, hasp_gpio_type_t::FREE, 0, 0, false);
configWrite(); // persist to config.json
}
}

Expand Down Expand Up @@ -1861,6 +1863,9 @@ static void webHandleGpioConfig()
case hasp_gpio_type_t::TOUCH:
httpMessage += D_GPIO_TOUCH;
break;
case hasp_gpio_type_t::HASP_ADC:
httpMessage += D_GPIO_ADC_BACKLIGHT;
break;
case hasp_gpio_type_t::LED:
httpMessage += D_GPIO_LED;
break;
Expand Down Expand Up @@ -2068,6 +2073,7 @@ static void webHandleGpioInput()
httpMessage += getOption(hasp_gpio_type_t::SMOKE, "Smoke", conf.type);
httpMessage += getOption(hasp_gpio_type_t::VIBRATION, "Vibration", conf.type);
httpMessage += getOption(hasp_gpio_type_t::WINDOW, "Window", conf.type);
httpMessage += getOption(hasp_gpio_type_t::HASP_ADC, D_GPIO_ADC_BACKLIGHT, conf.type);
httpMessage += F("</select></p>");

httpMessage += F("<p><b>" D_GPIO_GROUP "</b> <select id='group' name='group'>");
Expand All @@ -2082,8 +2088,8 @@ static void webHandleGpioInput()
httpMessage += F("</select></p>");

httpMessage += F("<p><b>Default State</b> <select id='state' name='state'>");
httpMessage += getOption(0, "Normally Open", conf.inverted);
httpMessage += getOption(1, "Normally Closed", conf.inverted);
httpMessage += getOption(0, D_GPIO_STATE_NORMAL, conf.inverted);
httpMessage += getOption(1, D_GPIO_STATE_INVERTED, conf.inverted);
httpMessage += F("</select></p>");

httpMessage += F("<p><b>Resistor</b> <select id='func' name='func'>");
Expand Down
6 changes: 6 additions & 0 deletions src/sys/svc/hasp_http_async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,9 @@ void webHandleGpioConfig(AsyncWebServerRequest* request)
case hasp_gpio_type_t::TOUCH:
httpMessage += F(D_GPIO_TOUCH);
break;
case hasp_gpio_type_t::HASP_ADC:
httpMessage += F(D_GPIO_ADC_BACKLIGHT);
break;
case hasp_gpio_type_t::LED:
httpMessage += F(D_GPIO_LED);
break;
Expand Down Expand Up @@ -1808,6 +1811,9 @@ void webHandleGpioInput(AsyncWebServerRequest* request)
selected = (conf.type == hasp_gpio_type_t::WINDOW);
httpMessage += getOption(hasp_gpio_type_t::WINDOW, F("Window"), selected);

selected = (conf.type == hasp_gpio_type_t::HASP_ADC);
httpMessage += getOption(hasp_gpio_type_t::HASP_ADC, F(D_GPIO_ADC_BACKLIGHT), selected);

httpMessage += F("</select></p>");

httpMessage += F("<p><b>" D_GPIO_GROUP "</b> <select id='group' name='group'>");
Expand Down
23 changes: 23 additions & 0 deletions user_setups/esp32/esp32-2432s024.ini
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,29 @@ build_flags =
-D LED_BLUE=16
;endregion

;region -- Ambient light sensor (GT36516) ----------------
; Light-dependent resistor on IO34.
; IO34 is input-only on ESP32 (ADC1 channel 6, no internal pull resistors).
;
; PCB HARDWARE BUG: The published schematic shows a 1M/1M voltage divider
; (1MΩ to 3.3V, 1MΩ to GND) with the LDR between the divider mid-point and IO34.
; In practice the LDR measures 470Ω (bright) to 4kΩ (dark), which with a 1MΩ
; pull-up produces only 1–13mV at IO34 — well below the ESP32 ADC floor (~150mV).
; analogRead() returns 0 across all light conditions with the stock resistor.
;
; REQUIRED HARDWARE FIX: Replace the 1MΩ pull-up resistor (3.3V side) with 1.5kΩ.
; 1.5kΩ is the geometric mean of the LDR's resistance range (√(470×4000) ≈ 1.4kΩ)
; and centres the dynamic range across the full ADC scale:
; Bright (470Ω LDR) : ~0.79V → ADC ≈ 981
; Desk (1.2kΩ LDR) : ~1.47V → ADC ≈ 1825
; Dark (4kΩ LDR) : ~2.40V → ADC ≈ 2979
; The 1MΩ resistor to GND can be left in place; it has negligible effect.
;
; Configure via web UI: GPIO Settings → Add New Pin (Input) → IO34
; Type: Ambient Light (Auto Backlight)
; Default State: Inverted ← required: bright light lowers voltage on this circuit
;endregion

;region -- TFT_eSPI build options ------------------------
${esp32.hspi} ; Use HSPI hardware SPI bus
;-D USER_SETUP_LOADED=1
Expand Down