Skip to content

Commit 8b90c53

Browse files
Merge pull request #40 from Valar-Systems/feature/swap-buttons
firmware: Swap Buttons — reverse on-device open/close roles
2 parents c670870 + 1c97f9b commit 8b90c53

3 files changed

Lines changed: 148 additions & 24 deletions

File tree

‎firmware/common/ropener-product.yaml‎

Lines changed: 148 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ globals:
7777
- { id: b2_consumed, type: bool, restore_value: no, initial_value: 'false' }
7878
- { id: b1_tap_count, type: int, restore_value: no, initial_value: '0' }
7979
- { id: b1_last_tap_ms, type: uint32_t, restore_value: no, initial_value: '0' }
80+
# Button 2 gets its own tap counter too: with "Swap Buttons" ON, Button 2 is the
81+
# close button, so it -- not Button 1 -- carries the 7-tap homing gesture. Only
82+
# whichever button is currently the close button ever increments its counter.
83+
- { id: b2_tap_count, type: int, restore_value: no, initial_value: '0' }
84+
- { id: b2_last_tap_ms, type: uint32_t, restore_value: no, initial_value: '0' }
85+
# Persisted button-swap state. Like global_stepper_direction, it is the single
86+
# source of truth behind a stateless "Swap Buttons" select (see the select block).
87+
- { id: global_btn_swap, type: bool, restore_value: yes, initial_value: 'false' }
8088

8189
# --- Stall handling (homing) ------------------------------------------------
8290
# The Ropener homes sensorlessly: drive at the closed end until StallGuard fires,
@@ -225,6 +233,10 @@ script:
225233
- logger.log: "Home position set to 0"
226234

227235
# Hold-to-run engage (armed on press, fired after hold_run_ms).
236+
# Hold-to-run for each button. The MOTION depends on the button's current role
237+
# (set by the "Swap Buttons" select): the close button drives close, the open
238+
# button drives open. Button 1 is the close button when swap is OFF; Button 2 is
239+
# the close button when swap is ON.
228240
- id: b1_hold_check
229241
mode: restart
230242
then:
@@ -235,22 +247,36 @@ script:
235247
then:
236248
- globals.set: { id: b1_hold_active, value: "true" }
237249
- globals.set: { id: b1_consumed, value: "true" }
238-
- cover.close: ropener_cover
239-
- logger.log: "Button 1 held: closing while held"
250+
- if:
251+
condition: { lambda: 'return !id(global_btn_swap);' }
252+
then:
253+
- cover.close: ropener_cover
254+
- logger.log: "Button 1 held: closing while held"
255+
else:
256+
- cover.open: ropener_cover
257+
- logger.log: "Button 1 held: opening while held"
240258
- id: b2_hold_check
241259
mode: restart
242260
then:
243261
- delay: ${hold_run_ms}ms
244262
- if:
245263
condition:
246-
lambda: 'return id(b2_down) && !id(b1_down) && id(global_state) == 0;'
264+
lambda: 'return id(b2_down) && !id(b1_down) && id(global_state) == 0 && id(b2_tap_count) < 7;'
247265
then:
248266
- globals.set: { id: b2_hold_active, value: "true" }
249267
- globals.set: { id: b2_consumed, value: "true" }
250-
- cover.open: ropener_cover
251-
- logger.log: "Button 2 held: opening while held"
268+
- if:
269+
condition: { lambda: 'return id(global_btn_swap);' }
270+
then:
271+
- cover.close: ropener_cover
272+
- logger.log: "Button 2 held: closing while held"
273+
else:
274+
- cover.open: ropener_cover
275+
- logger.log: "Button 2 held: opening while held"
252276

253-
# Single-tap close for Button 1 (deferred so a 7-tap burst can chain).
277+
# Deferred single-tap CLOSE, one per button. Only the current close button ever
278+
# schedules its commit (the open button opens immediately on release); the delay
279+
# lets a 7-tap homing burst chain without the first tap committing a close.
254280
- id: b1_tap_commit
255281
mode: restart
256282
then:
@@ -262,6 +288,17 @@ script:
262288
- cover.close: ropener_cover
263289
- logger.log: "Button 1 tap: closing"
264290
- globals.set: { id: b1_tap_count, value: "0" }
291+
- id: b2_tap_commit
292+
mode: restart
293+
then:
294+
- delay: ${tap_window_ms}ms
295+
- if:
296+
condition:
297+
lambda: 'return id(global_state) == 0 && id(b2_tap_count) < 7;'
298+
then:
299+
- cover.close: ropener_cover
300+
- logger.log: "Button 2 tap: closing"
301+
- globals.set: { id: b2_tap_count, value: "0" }
265302

266303
# Two-button "set home" combo.
267304
- id: combo_home
@@ -325,29 +362,40 @@ binary_sensor:
325362
- script.stop: b1_hold_check
326363
- script.stop: b2_hold_check
327364
- script.stop: b1_tap_commit
365+
- script.stop: b2_tap_commit
328366
- cover.stop: ropener_cover
329367
- globals.set: { id: b1_hold_active, value: "false" }
330368
- globals.set: { id: b2_hold_active, value: "false" }
331369
- globals.set: { id: b1_consumed, value: "true" }
332370
- globals.set: { id: b2_consumed, value: "true" }
333371
- script.execute: combo_home
334372
else:
335-
- script.stop: b1_tap_commit
336-
- lambda: |-
337-
uint32_t now = millis();
338-
if (now - id(b1_last_tap_ms) > ${tap_window_ms}) id(b1_tap_count) = 0;
339-
id(b1_tap_count) += 1;
340-
id(b1_last_tap_ms) = now;
341-
- globals.set: { id: b1_consumed, value: "false" }
373+
# Idle, single button. Behaviour depends on Button 1's role.
342374
- if:
343-
condition: { lambda: 'return id(b1_tap_count) >= 7;' }
375+
condition: { lambda: 'return !id(global_btn_swap);' }
344376
then:
345-
- script.stop: b1_hold_check
346-
- globals.set: { id: b1_tap_count, value: "0" }
347-
- globals.set: { id: b1_consumed, value: "true" }
348-
- script.execute: home_start
349-
- logger.log: "Button 1 x7: starting homing"
377+
# CLOSE button: count taps (7 -> homing), defer the close.
378+
- script.stop: b1_tap_commit
379+
- lambda: |-
380+
uint32_t now = millis();
381+
if (now - id(b1_last_tap_ms) > ${tap_window_ms}) id(b1_tap_count) = 0;
382+
id(b1_tap_count) += 1;
383+
id(b1_last_tap_ms) = now;
384+
- globals.set: { id: b1_consumed, value: "false" }
385+
- if:
386+
condition: { lambda: 'return id(b1_tap_count) >= 7;' }
387+
then:
388+
- script.stop: b1_hold_check
389+
- globals.set: { id: b1_tap_count, value: "0" }
390+
- globals.set: { id: b1_consumed, value: "true" }
391+
- script.execute: home_start
392+
- logger.log: "Button 1 x7: starting homing"
393+
else:
394+
- script.execute: b1_hold_check
350395
else:
396+
# OPEN button: no homing gesture; open immediately on release.
397+
- globals.set: { id: b1_tap_count, value: "0" }
398+
- globals.set: { id: b1_consumed, value: "false" }
351399
- script.execute: b1_hold_check
352400
on_release:
353401
- globals.set: { id: b1_down, value: "false" }
@@ -366,7 +414,15 @@ binary_sensor:
366414
then:
367415
- globals.set: { id: b1_consumed, value: "false" }
368416
else:
369-
- script.execute: b1_tap_commit
417+
# Tap: the close button defers (allows 7-tap chaining); the open
418+
# button opens immediately.
419+
- if:
420+
condition: { lambda: 'return !id(global_btn_swap);' }
421+
then:
422+
- script.execute: b1_tap_commit
423+
else:
424+
- cover.open: ropener_cover
425+
- logger.log: "Button 1 tap: opening"
370426

371427
- id: !extend btn2
372428
on_press:
@@ -375,33 +431,64 @@ binary_sensor:
375431
condition: { lambda: 'return id(global_state) == 3;' }
376432
then:
377433
- script.stop: b2_hold_check
434+
- script.stop: b2_tap_commit
378435
- script.execute: set_home_zero
379436
- globals.set: { id: b2_consumed, value: "true" }
437+
- globals.set: { id: b2_tap_count, value: "0" }
380438
- logger.log: "Homing cancelled (Button 2)"
381439
else:
382440
- if:
383441
condition: { lambda: 'return id(global_state) == 1 || id(global_state) == 2;' }
384442
then:
385443
- script.stop: b2_hold_check
444+
- script.stop: b2_tap_commit
386445
- cover.stop: ropener_cover
387446
- globals.set: { id: b2_consumed, value: "true" }
447+
- globals.set: { id: b2_tap_count, value: "0" }
388448
else:
389449
- if:
390450
condition: { lambda: 'return id(b1_down);' }
391451
then:
392452
- script.stop: b1_hold_check
393453
- script.stop: b2_hold_check
394454
- script.stop: b1_tap_commit
455+
- script.stop: b2_tap_commit
395456
- cover.stop: ropener_cover
396457
- globals.set: { id: b1_hold_active, value: "false" }
397458
- globals.set: { id: b2_hold_active, value: "false" }
398459
- globals.set: { id: b1_consumed, value: "true" }
399460
- globals.set: { id: b2_consumed, value: "true" }
400461
- globals.set: { id: b1_tap_count, value: "0" }
462+
- globals.set: { id: b2_tap_count, value: "0" }
401463
- script.execute: combo_home
402464
else:
403-
- globals.set: { id: b2_consumed, value: "false" }
404-
- script.execute: b2_hold_check
465+
# Idle, single button. Behaviour depends on Button 2's role.
466+
- if:
467+
condition: { lambda: 'return id(global_btn_swap);' }
468+
then:
469+
# CLOSE button (swap ON): count taps (7 -> homing), defer close.
470+
- script.stop: b2_tap_commit
471+
- lambda: |-
472+
uint32_t now = millis();
473+
if (now - id(b2_last_tap_ms) > ${tap_window_ms}) id(b2_tap_count) = 0;
474+
id(b2_tap_count) += 1;
475+
id(b2_last_tap_ms) = now;
476+
- globals.set: { id: b2_consumed, value: "false" }
477+
- if:
478+
condition: { lambda: 'return id(b2_tap_count) >= 7;' }
479+
then:
480+
- script.stop: b2_hold_check
481+
- globals.set: { id: b2_tap_count, value: "0" }
482+
- globals.set: { id: b2_consumed, value: "true" }
483+
- script.execute: home_start
484+
- logger.log: "Button 2 x7: starting homing"
485+
else:
486+
- script.execute: b2_hold_check
487+
else:
488+
# OPEN button: no homing gesture; open immediately on release.
489+
- globals.set: { id: b2_tap_count, value: "0" }
490+
- globals.set: { id: b2_consumed, value: "false" }
491+
- script.execute: b2_hold_check
405492
on_release:
406493
- globals.set: { id: b2_down, value: "false" }
407494
- script.stop: b2_hold_check
@@ -419,8 +506,15 @@ binary_sensor:
419506
then:
420507
- globals.set: { id: b2_consumed, value: "false" }
421508
else:
422-
- cover.open: ropener_cover
423-
- logger.log: "Button 2 tap: opening"
509+
# Tap: the close button defers (allows 7-tap chaining); the open
510+
# button opens immediately.
511+
- if:
512+
condition: { lambda: 'return id(global_btn_swap);' }
513+
then:
514+
- script.execute: b2_tap_commit
515+
else:
516+
- cover.open: ropener_cover
517+
- logger.log: "Button 2 tap: opening"
424518

425519
# Rename core's "Button 3 (Wi-Fi reset)" to Ropener's "WiFi Reset" and attach
426520
# the hold-to-erase-credentials action.
@@ -490,6 +584,36 @@ select:
490584
global_speed->update();
491585
global_preferences->sync();
492586
587+
# Swap the two physical buttons' open/close roles -- same select UI as Motor
588+
# Direction, and its sibling install-orientation setting (sits right under it in
589+
# Setup). "Normal" = Button 1 closes, Button 2 opens (the shipped layout).
590+
# "Swapped" = Button 1 opens, Button 2 closes -- for a mirror-image install (e.g.
591+
# pull-rope on the left) where the flipped unit makes the buttons feel reversed.
592+
# The 7-tap homing gesture and the deferred single-tap always follow the CLOSE
593+
# button. Like Motor Direction it holds NO state of its own: it reads and writes
594+
# the persisted global_btn_swap, so it never fights the restored value. Only
595+
# remaps the on-device buttons; the web UI, Home Assistant, and the schedule
596+
# open/close are unaffected. (A left-side install usually wants Motor Direction =
597+
# Reversed too.)
598+
- platform: template
599+
name: "Swap Buttons"
600+
id: sel_btn_swap
601+
icon: "mdi:swap-horizontal"
602+
entity_category: CONFIG
603+
web_server: { sorting_weight: 2, sorting_group_id: group_setup }
604+
update_interval: 1s
605+
options:
606+
- "Normal"
607+
- "Swapped ↔"
608+
lambda: |-
609+
if (id(global_btn_swap)) return std::string("Swapped ↔");
610+
return std::string("Normal");
611+
set_action:
612+
- lambda: |-
613+
id(global_btn_swap) = (x.find("Swap") != std::string::npos);
614+
global_btn_swap->update();
615+
global_preferences->sync();
616+
493617
# --- Rename core tuning entities to Ropener's existing entity_ids -------------
494618
number:
495619
# Raw IRUN (register 1-31) is meaningless to users -- the friendly "Motor
File renamed without changes.
1.39 MB
Binary file not shown.

0 commit comments

Comments
 (0)