Skip to content

Commit 87afe71

Browse files
committed
fix: button feedbck, more reliable buttons after deep sleep and ned advertising data features
1 parent cb27a40 commit 87afe71

10 files changed

Lines changed: 248 additions & 27 deletions

‎include/opendisplay_structs.h‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@
6767
* new version heading on each bump -- see AGENT INSTRUCTIONS below)
6868
* --------------------------------------------------------------------------
6969
* Unreleased (since 2.0)
70+
* - LedFlags bit4: OD_LED_FLAG_BUTTON_PRESS (short LED flash on physical
71+
* button press / button-wake synthetic click; default off).
72+
* - BuzzerFlags bit1: OD_BUZZER_FLAG_BUTTON_PRESS (short chirp on physical
73+
* button press / button-wake synthetic click; default off).
74+
* - MsdStatusBits bit3: OD_MSD_STATUS_ENCRYPTION_ENABLED (1 = app-layer
75+
* encryption active). Firmware sets it when encryption_enabled and master
76+
* key are non-zero; dynamic[11] is all 0xFF when no display is configured.
7077
* - Doc-only: fixed two comment shapes the codegen parser mis-read and added
7178
* the CODEGEN AUTHORING RULES banner section to prevent recurrence. Split the
7279
* combined BusFlags/PinBitmap @bits comment into one comment per group; folded
@@ -735,11 +742,12 @@ enum LedType {
735742
OD_LED_TYPE_FOUR_SEPARATE = 3 /**< @doc "four separate LEDs" */
736743
};
737744

738-
/* LedConfig.led_flags @bits LedFlags (bits 4-7 reserved). */
745+
/* LedConfig.led_flags @bits LedFlags (bits 5-7 reserved). */
739746
#define OD_LED_FLAG_LED1_INVERT (1u << 0) /* @doc "invert LED channel 1 polarity" */
740747
#define OD_LED_FLAG_LED2_INVERT (1u << 1) /* @doc "invert LED channel 2 polarity" */
741748
#define OD_LED_FLAG_LED3_INVERT (1u << 2) /* @doc "invert LED channel 3 polarity" */
742749
#define OD_LED_FLAG_LED4_INVERT (1u << 3) /* @doc "invert LED channel 4 polarity" */
750+
#define OD_LED_FLAG_BUTTON_PRESS (1u << 4) /* @doc "short flash on physical button press (incl. button-wake synthetic click); default off" */
743751

744752
/** @struct LedConfig @packet 0x21 @repeatable max=4
745753
* @doc "LED channel pins + invert flags. Up to 4 instances. 22 bytes. NOTE: the
@@ -963,8 +971,9 @@ OD_STATIC_ASSERT(sizeof(struct TouchController) == 32, "TouchController wire siz
963971
* 0x29 buzzer (config.yaml packet name: passive_buzzer)
964972
* ----------------------------------------------------------------------- */
965973

966-
/* BuzzerConfig.flags @bits BuzzerFlags (bits 1-7 reserved). */
974+
/* BuzzerConfig.flags @bits BuzzerFlags (bits 2-7 reserved). */
967975
#define OD_BUZZER_FLAG_ENABLE_ACTIVE_HIGH (1u << 0) /* @doc "enable pin is active-high when set; otherwise active-low" */
976+
#define OD_BUZZER_FLAG_BUTTON_PRESS (1u << 1) /* @doc "short chirp on physical button press (incl. button-wake synthetic click); default off" */
968977

969978
/** @struct BuzzerConfig @packet 0x29 @repeatable max=4
970979
* @doc "Buzzer (passive piezo, PWM-driven). Up to 4 instances. 32 bytes. The tone
@@ -1225,7 +1234,8 @@ OD_STATIC_ASSERT(sizeof(struct AuthProof) == 32, "AuthProof wire size");
12251234
#define OD_MSD_STATUS_BATTERY_VOLTAGE_BIT8 (1u << 0) /* @doc "high bit of the 10-bit battery voltage (units of 10 mV); combine with battery_voltage_low" */
12261235
#define OD_MSD_STATUS_REBOOT_FLAG (1u << 1) /* @doc "device rebooted since last read" */
12271236
#define OD_MSD_STATUS_CONNECTION_REQUESTED (1u << 2) /* @doc "device is requesting a connection" */
1228-
#define OD_MSD_STATUS_RESERVED_3 (1u << 3) /* @reserved @doc "reserved; must be 0 (placeholder name for a future status flag; sits between the flags and the bits 4-7 counter)" */
1237+
#define OD_MSD_STATUS_ENCRYPTION_ENABLED (1u << 3) /* @doc "application-layer encryption active (encryption_enabled and non-zero master key)" */
1238+
#define OD_MSD_STATUS_RESERVED_3 OD_MSD_STATUS_ENCRYPTION_ENABLED /* legacy doc name */
12291239
#define OD_MSD_STATUS_MAIN_LOOP_COUNTER_SHIFT 4u /* @doc "bits 4-7: free-running main-loop nibble counter (liveness)" */
12301240
#define OD_MSD_STATUS_MAIN_LOOP_COUNTER_MASK 0xF0u /* @doc "mask for the bits 4-7 main-loop counter nibble" */
12311241

‎src/ble_transport_esp32.cpp‎

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,30 @@ static volatile uint16_t s_disconnectReason = 0;
4444
static volatile uint32_t s_connectedWord = 0; // identity of the last connect
4545
static volatile uint32_t s_disconnectedWord = 0; // identity of the last disconnect
4646

47+
static uint32_t s_advBoostUntil = 0;
48+
static constexpr uint32_t ESP_ADV_BOOST_MS = 3000u;
49+
static constexpr uint16_t ESP_ADV_INTERVAL_MIN = 0x0100u; // 160 ms
50+
static constexpr uint16_t ESP_ADV_INTERVAL_MAX = 0x0640u; // 1000 ms
51+
static constexpr uint16_t ESP_ADV_BOOST_MIN = 0x0020u; // 20 ms
52+
static constexpr uint16_t ESP_ADV_BOOST_MAX = 0x0030u; // 30 ms
53+
54+
static void applyAdvInterval(BLEAdvertising* pAdvertising, bool fast) {
55+
if (pAdvertising == nullptr) {
56+
return;
57+
}
58+
if (fast) {
59+
pAdvertising->setMinInterval(ESP_ADV_BOOST_MIN);
60+
pAdvertising->setMaxInterval(ESP_ADV_BOOST_MAX);
61+
} else {
62+
pAdvertising->setMinInterval(ESP_ADV_INTERVAL_MIN);
63+
pAdvertising->setMaxInterval(ESP_ADV_INTERVAL_MAX);
64+
}
65+
}
66+
67+
static bool advBoostActive(uint32_t now) {
68+
return s_advBoostUntil != 0u && now < s_advBoostUntil;
69+
}
70+
4771
// --- the instance table (CONNECTION_POLICY R3 requirement 5) -----------------
4872
// Sized by the connection cap. CONFIG_BT_NIMBLE_MAX_CONNECTIONS is 3 in the
4973
// precompiled sdkconfig.h for S3/C3/C6 and absent for classic ESP32 (NimBLE's own
@@ -381,6 +405,7 @@ void BleTransport::startAdvertising() {
381405
// name). Scan response is off by default in NimBLE 2.x, so no
382406
// enableScanResponse() needed.
383407
pAdvertising->setAdvertisementData(s_advertisementData);
408+
applyAdvInterval(pAdvertising, advBoostActive(millis()));
384409
s_server->getAdvertising()->start();
385410
od_log_info("=== BLE advertising started successfully ===");
386411
}
@@ -550,6 +575,7 @@ void BleTransport::setManufacturerData(const uint8_t* msd, uint8_t len) {
550575
// enableScanResponse()/setPreferredParams() reset NimBLE's custom-data flag
551576
// and would make start() drop this manufacturer-data payload.
552577
pAdvertising->setAdvertisementData(fresh);
578+
applyAdvInterval(pAdvertising, advBoostActive(millis()));
553579
delay(50);
554580
pAdvertising->start();
555581
}
@@ -584,11 +610,32 @@ void BleTransport::requestFastLink() {
584610
}
585611

586612
void BleTransport::boostAdvertising() {
587-
// No-op: the temporary fast-advertising interval is nRF-only today.
613+
s_advBoostUntil = millis() + ESP_ADV_BOOST_MS;
588614
}
589615

590616
void BleTransport::tick() {
591-
// No-op: nothing periodic to restore, since boostAdvertising() is a no-op.
617+
static bool was_boosted = false;
618+
const uint32_t now = millis();
619+
const bool boosting = advBoostActive(now);
620+
if (boosting) {
621+
was_boosted = true;
622+
return;
623+
}
624+
if (!was_boosted || s_server == nullptr || connectedCount() > 0) {
625+
was_boosted = false;
626+
s_advBoostUntil = 0;
627+
return;
628+
}
629+
was_boosted = false;
630+
s_advBoostUntil = 0;
631+
BLEAdvertising* pAdvertising = s_server->getAdvertising();
632+
if (pAdvertising == nullptr) {
633+
return;
634+
}
635+
pAdvertising->stop();
636+
applyAdvInterval(pAdvertising, false);
637+
delay(50);
638+
pAdvertising->start();
592639
}
593640

594641
bool BleTransport::eventPending() const {

‎src/buzzer_control.cpp‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,30 @@ void passiveBuzzerPowerOffAlert(void) {
385385
buzzer_set_enable(b, false);
386386
buzzer_drive_off(b);
387387
}
388+
389+
void passiveBuzzerButtonPressAlert(void) {
390+
buzzer_stop_internal();
391+
const BuzzerConfig* b = nullptr;
392+
for (uint8_t i = 0; i < globalConfig.passive_buzzer_count; i++) {
393+
const BuzzerConfig* cand = &globalConfig.passive_buzzers[i];
394+
const uint8_t pin = cand->drive_pin;
395+
if (pin == 0 || pin == 0xFF) {
396+
continue;
397+
}
398+
if ((cand->flags & OD_BUZZER_FLAG_BUTTON_PRESS) == 0u) {
399+
continue;
400+
}
401+
b = cand;
402+
break;
403+
}
404+
if (!b) {
405+
return;
406+
}
407+
const uint32_t centihz = buzzer_index_to_centihz(nA5);
408+
buzzer_set_enable(b, true);
409+
buzzer_hw_tone_start(b->drive_pin, centihz, b->duty_percent);
410+
delay(60);
411+
buzzer_hw_tone_stop(b->drive_pin);
412+
buzzer_set_enable(b, false);
413+
buzzer_drive_off(b);
414+
}

‎src/buzzer_control.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ enum BuzzerNote : uint8_t {
9494
void initPassiveBuzzers(void);
9595
void handleBuzzerActivate(uint8_t* data, uint16_t len);
9696
void passiveBuzzerPowerOffAlert(void);
97+
void passiveBuzzerButtonPressAlert(void);
9798
void buzzerService(void); // non-blocking playback tick, called from loop()
9899
/**
99100
* Silence the buzzer immediately. DEEP SLEEP ONLY -- not a session-teardown API.

‎src/device_control.cpp‎

Lines changed: 101 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
#include "touch_input.h"
44
#include "power_latch.h"
55
#include "buzzer_control.h"
6+
#include "display_service.h"
7+
#include "ble_transport.h"
68
#include "od_log.h"
79
#include <string.h>
810

911
#ifdef TARGET_ESP32
12+
#include "wake_button.h"
1013
void enterDeepSleep(bool force = false, uint16_t overrideSleepSeconds = 0);
1114
#endif
1215

@@ -235,6 +238,10 @@ static void pollAdcButtons() {
235238
((l->press_count & 0x0F) << 3) |
236239
((state & 0x01) << 7));
237240
if (l->byte_index < 11) dynamicreturndata[l->byte_index] = data;
241+
if (state != 0u) {
242+
ble.boostAdvertising();
243+
buttonPressFeedback();
244+
}
238245
updatemsdata();
239246
od_log_debug("ADC btn pin %u adc=%d idx=%d id=%u cnt=%u state=%u",
240247
l->pin, adc, btn, l->last_button_id, l->press_count, state);
@@ -330,9 +337,10 @@ static void led_all_off(struct LedConfig* led) {
330337
if (led == NULL) {
331338
return;
332339
}
333-
bool invertRed = (led->led_flags & 0x01) != 0;
334-
bool invertGreen = (led->led_flags & 0x02) != 0;
335-
bool invertBlue = (led->led_flags & 0x04) != 0;
340+
bool invertRed = (led->led_flags & OD_LED_FLAG_LED1_INVERT) != 0;
341+
bool invertGreen = (led->led_flags & OD_LED_FLAG_LED2_INVERT) != 0;
342+
bool invertBlue = (led->led_flags & OD_LED_FLAG_LED3_INVERT) != 0;
343+
bool invert4 = (led->led_flags & OD_LED_FLAG_LED4_INVERT) != 0;
336344
if (led->led_1_r != 0xFF) {
337345
digitalWrite(led->led_1_r, invertRed ? HIGH : LOW);
338346
}
@@ -342,6 +350,9 @@ static void led_all_off(struct LedConfig* led) {
342350
if (led->led_3_b != 0xFF) {
343351
digitalWrite(led->led_3_b, invertBlue ? HIGH : LOW);
344352
}
353+
if (led->led_4 != 0xFF) {
354+
digitalWrite(led->led_4, invert4 ? HIGH : LOW);
355+
}
345356
}
346357

347358
static void led_stop_internal(bool clear_mode) {
@@ -596,6 +607,88 @@ void handleLedStop(uint8_t* data, uint16_t len) {
596607
sendResponse(successResponse, sizeof(successResponse));
597608
}
598609

610+
static void led_channels_on(struct LedConfig* led) {
611+
if (led == NULL) {
612+
return;
613+
}
614+
const bool invert1 = (led->led_flags & OD_LED_FLAG_LED1_INVERT) != 0;
615+
const bool invert2 = (led->led_flags & OD_LED_FLAG_LED2_INVERT) != 0;
616+
const bool invert3 = (led->led_flags & OD_LED_FLAG_LED3_INVERT) != 0;
617+
const bool invert4 = (led->led_flags & OD_LED_FLAG_LED4_INVERT) != 0;
618+
if (led->led_1_r != 0xFF) {
619+
digitalWrite(led->led_1_r, invert1 ? LOW : HIGH);
620+
}
621+
if (led->led_2_g != 0xFF) {
622+
digitalWrite(led->led_2_g, invert2 ? LOW : HIGH);
623+
}
624+
if (led->led_3_b != 0xFF) {
625+
digitalWrite(led->led_3_b, invert3 ? LOW : HIGH);
626+
}
627+
if (led->led_4 != 0xFF) {
628+
digitalWrite(led->led_4, invert4 ? LOW : HIGH);
629+
}
630+
}
631+
632+
static void ledButtonPressAlert(void) {
633+
if (s_led.active) {
634+
return;
635+
}
636+
struct LedConfig* led = nullptr;
637+
for (uint8_t i = 0; i < globalConfig.led_count; i++) {
638+
struct LedConfig* cand = &globalConfig.leds[i];
639+
if ((cand->led_flags & OD_LED_FLAG_BUTTON_PRESS) == 0u) {
640+
continue;
641+
}
642+
if (cand->led_1_r == 0xFF && cand->led_2_g == 0xFF &&
643+
cand->led_3_b == 0xFF && cand->led_4 == 0xFF) {
644+
continue;
645+
}
646+
led = cand;
647+
break;
648+
}
649+
if (!led) {
650+
return;
651+
}
652+
led_channels_on(led);
653+
delay(60);
654+
led_all_off(led);
655+
}
656+
657+
void buttonPressFeedback(void) {
658+
ledButtonPressAlert();
659+
passiveBuzzerButtonPressAlert();
660+
}
661+
662+
static void publishButtonMsd(ButtonState* btn, uint8_t pressed) {
663+
if (btn == nullptr || btn->byte_index >= 11) {
664+
return;
665+
}
666+
btn->current_state = pressed ? 1u : 0u;
667+
const uint8_t buttonData = (uint8_t)((btn->button_id & 0x07u) |
668+
((btn->press_count & 0x0Fu) << 3) |
669+
((btn->current_state & 0x01u) << 7));
670+
dynamicreturndata[btn->byte_index] = buttonData;
671+
ble.boostAdvertising();
672+
updatemsdata();
673+
}
674+
675+
#if defined(TARGET_ESP32)
676+
void buttonWakeDeliverSyntheticClick(void) {
677+
const int idx = wakeButtonFindIndex();
678+
if (idx < 0) {
679+
return;
680+
}
681+
ButtonState* btn = &buttonStates[idx];
682+
btn->press_count = (uint8_t)((btn->press_count + 1u) & 0x0Fu);
683+
od_log_info("Button wake: synthetic click id=%u pin=%u", btn->button_id, btn->pin);
684+
publishButtonMsd(btn, 1u);
685+
buttonPressFeedback();
686+
delay(80);
687+
publishButtonMsd(btn, 0u);
688+
ble.tick();
689+
}
690+
#endif
691+
599692
void processButtonEvents() {
600693
powerButtonPoll();
601694
pollConfiguredPowerOffButtons(); // no-op unless the board declares a latch
@@ -615,24 +708,13 @@ void processButtonEvents() {
615708
uint8_t logicalState = logicalPressed ? 1 : 0;
616709
btn->current_state = logicalState;
617710
od_log_debug("Button: %u, Press count: %u, Current state: %u", btn->button_id, btn->press_count, btn->current_state);
618-
uint8_t buttonData = (btn->button_id & 0x07) |
619-
((btn->press_count & 0x0F) << 3) |
620-
((btn->current_state & 0x01) << 7);
621-
if (btn->byte_index < 11) {
622-
dynamicreturndata[btn->byte_index] = buttonData;
711+
if (logicalState != 0u) {
712+
buttonPressFeedback();
623713
}
714+
publishButtonMsd(btn, logicalState);
624715
}
625-
// ORDER IS LOAD-BEARING: boost first, publish second. updatemsdata() ends in
626-
// setManufacturerData(), which calls applyAdvInterval() and then restarts
627-
// advertising -- so the interval is chosen DURING the publish. Boosting
628-
// afterwards set the deadline too late to affect the packet it exists for:
629-
// the press went out at the 160 ms slow interval (~1 advertisement in a
630-
// typical 230 ms press window, which a passive scanner routinely misses)
631-
// while the release 230 ms later got the 20 ms boosted interval, because by
632-
// then s_advBoostUntil was set. Net effect: a host saw "not pressed"
633-
// reliably and "pressed" almost never.
634-
ble.boostAdvertising(); // no-op where the stack has no fast-adv window
635-
updatemsdata();
716+
// boostAdvertising() runs inside publishButtonMsd(); order is load-bearing
717+
// for nRF (interval chosen during setManufacturerData restart).
636718
}
637719
}
638720

‎src/device_control.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
void reboot();
77
void processButtonEvents();
8+
void buttonPressFeedback(void);
9+
#if defined(TARGET_ESP32)
10+
void buttonWakeDeliverSyntheticClick(void);
11+
#endif
812
void flashLed(uint8_t color, uint8_t brightness);
913
void processLedFlash();
1014
void initButtons();

‎src/display_service.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1859,13 +1859,18 @@ void updatemsdata(){
18591859
uint8_t statusByte = (((batteryVoltage10mv >> 8) & 0x01) ? OD_MSD_STATUS_BATTERY_VOLTAGE_BIT8 : 0) |
18601860
(rebootFlag ? OD_MSD_STATUS_REBOOT_FLAG : 0) |
18611861
(connectionRequested ? OD_MSD_STATUS_CONNECTION_REQUESTED : 0) |
1862+
(isEncryptionEnabled() ? OD_MSD_STATUS_ENCRYPTION_ENABLED : 0) |
18621863
(((uint8_t)(mloopcounter << OD_MSD_STATUS_MAIN_LOOP_COUNTER_SHIFT)) & OD_MSD_STATUS_MAIN_LOOP_COUNTER_MASK);
18631864
// Build the 16-byte advertisement via the canonical wire struct (all little-endian),
18641865
// then copy into the global msd_payload[16] that the BLE adv APIs below consume.
18651866
struct MsdAdvertisement m;
18661867
memset(&m, 0, sizeof m);
18671868
m.company_id = 0x2446;
1868-
memcpy(m.dynamic, dynamicreturndata, sizeof m.dynamic);
1869+
if (globalConfig.display_count == 0) {
1870+
memset(m.dynamic, 0xFF, sizeof m.dynamic);
1871+
} else {
1872+
memcpy(m.dynamic, dynamicreturndata, sizeof m.dynamic);
1873+
}
18691874
m.chip_temperature = temperatureByte;
18701875
m.battery_voltage_low = batteryVoltageLowByte;
18711876
m.status = statusByte;

‎src/main.cpp‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ void setup() {
272272
updatemsdata();
273273
if (is_deep_sleep_wake) { od_log_info("[wake] >> initButtons"); od_log_flush(); }
274274
initButtons();
275+
if (woke_by_button) {
276+
buttonWakeDeliverSyntheticClick();
277+
}
275278
if (is_deep_sleep_wake) { od_log_info("[wake] >> initTouchInput"); od_log_flush(); }
276279
initTouchInput();
277280
#ifdef TARGET_ESP32
@@ -1054,7 +1057,7 @@ void loop() {
10541057
} else {
10551058
platformIdle();
10561059
}
1057-
ble.tick(); // no-op on ESP32
1060+
ble.tick(); // restores ESP32 fast-adv window after button events
10581061
processButtonEvents();
10591062
processTouchInput();
10601063
buzzerService();
@@ -1073,7 +1076,7 @@ void idleDelay(uint32_t delayMs) {
10731076
// which is also what makes WDT CONFIG.SLEEP=1 safe: the CPU sleeps inside
10741077
// delay() below, and the watchdog keeps counting through it.
10751078
odWatchdogFeed();
1076-
ble.tick(); // no-op on ESP32
1079+
ble.tick();
10771080
processButtonEvents();
10781081
processTouchInput();
10791082
processLedFlash();

0 commit comments

Comments
 (0)