feat: add stock-like switch/button input modes for the SW terminal (#1) - #94
Conversation
* add sw input mode constants and pure state machine Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * add sw input handler and wire it into the application Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * route sw input edges from keyevents and the native monitor through the handler Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * expose sw input mode, relay target and invert in settings Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * publish initial sw input level and button mode discovery over mqtt Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * add /device/input state endpoint Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * add jvm tests for the sw input state machine Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * document sw input modes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * label cally as wall display x1i per the official kb Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * fix follow mode test expectation to include the js event Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * note the json content type requirement in the sw input curl examples Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * add pr apk build workflow runs unit tests and builds a release apk on every pull request, uploading it as a workflow artifact so changes can be sideloaded and live-tested before merging. signing secrets are optional: fork prs fall back to the debug key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * decode sw input keycodes as rising and falling edge pulses of one input hardware testing on a wall display x1i showed the sw terminal reports each contact transition as a ~5ms down+up key pulse whose keycode encodes the direction: 141/KEY_F11 = line went active, 142/KEY_F12 = line went inactive. the old code treated 141 down/up as press/release and 142 as a second input, so falling edges were swallowed and multi-way taster circuits only toggled on every second press. the state machine now consumes decoded levels, only the pulse's down event is fed in, and the opposite-level debounce is replaced by a same-level duplicate window so genuine repeated presses still register. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * warn when pr apks are signed with an ephemeral debug key and add a keystore generator without the SIGNING_* secrets every ci run signs with a keystore generated fresh on that runner, so no artifact can ever update a previously installed one. the build step now surfaces that as a run warning and tools/ gains a script that generates a persistent keystore and prints the secrets to set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW * print a portable base64 command from the keystore generator base64 -w0 is gnu-only; bsd/macos base64 has no -w flag. openssl base64 -A produces single-line output on both platforms and the script already uses openssl anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW --------- Co-authored-by: Claude <noreply@anthropic.com>
|
Tested successfully on a X1i display connected to several tasters (Kreuzschaltung) that need to operate with edge switch function. So far no issues running this build. My setting via |
There was a problem hiding this comment.
Pull request overview
This PR adds stock-firmware-like behavior for the Shelly Wall Display SW terminal by introducing a dedicated, unit-tested SW input state machine and wiring it through the app so external switches/push-buttons can locally control relays while still reporting to MQTT/JS/REST.
Changes:
- Introduces a pure Java
SwInputStateMachineplus an AndroidSwInputHandlerthat deduplicates edge pulses and applies Button/Switch/Detached modes. - Wires SW input handling through app lifecycle and UI focus holders (MainActivity / SettingsActivity / screensaver), adds settings UI + SharedPreferences keys, and publishes/republishes input state via MQTT and a new
/device/inputREST endpoint. - Adds documentation + unit tests, plus a PR APK build workflow and a helper script to generate persistent signing secrets.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/generate-signing-keystore.sh | Adds a helper to generate a release keystore and print SIGNING_* secrets for CI signing. |
| README.md | Mentions SW terminal external switch/button support and links to documentation. |
| docs/sw-input.md | Documents SW input hardware behavior, modes, settings keys, MQTT/JS behavior, and curl examples. |
| app/src/test/java/me/rapierxbox/shellyelevatev2/helper/SwInputStateMachineTest.java | Adds JVM unit tests covering key decoding, deduping, and mode behaviors. |
| app/src/main/res/values/strings.xml | Adds settings UI strings for SW input mode/relay/invert. |
| app/src/main/res/values/arrays.xml | Adds sw_input_modes array to back the SW mode spinner. |
| app/src/main/res/layout/settings_fragment.xml | Adds settings UI controls for SW input mode, relay mapping, and invert. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/ShellyElevateApplication.java | Boots SwInputHandler early and disposes it on termination. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/SettingsFragment.kt | Binds SW input settings, adds relay mapping spinner behavior, and persists relay mapping. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/SettingsActivity.kt | Forwards SW key events while settings activity has focus. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/activities/DigitalClockAndDateScreenSaverActivity.kt | Forwards SW key events while this screensaver activity has focus. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/mqtt/MQTTServer.java | Republishes known SW input levels after MQTT reconnect to resync HA. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/mqtt/MqttDiscoveryConfigBuilder.java | Adds HA discovery for SW input “button event” entity when configured in button mode. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/MainActivity.kt | Routes SW keycodes to the shared handler and removes the prior per-key handling. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/HttpServer.java | Adds /device/input?num=0 to expose input level + current mode. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/helper/SwInputStateMachine.java | Implements the mode logic + dedup window and keycode decoding helpers. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/helper/SwInputHandler.java | Centralizes SW input ingestion (Android KeyEvent + native monitor), applies settings, drives relay/MQTT/JS. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/helper/DeviceSensorManager.java | Routes native SW key pulses to the SW handler so input works regardless of focused activity. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/DeviceModel.java | Corrects the CALLY display name to “Shelly Wall Display X1i” per official KB. |
| app/src/main/java/me/rapierxbox/shellyelevatev2/Constants.java | Adds SW input settings keys and mode constants. |
| .github/workflows/pr-apk.yml | Adds a PR workflow that runs unit tests and builds/uploads a (release) APK artifact. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // 141/142: physical sw inputs, handled centrally so every activity shares one path | ||
| 141, 142 -> { mSwInputHandler?.onKeyEvent(event); return true } |
| echo " gh secret set SIGNING_KEY_ALIAS --body \"$ALIAS\"" | ||
| echo " gh secret set SIGNING_STORE_PASSWORD --body \"$PASSWORD\"" | ||
| echo " gh secret set SIGNING_KEY_PASSWORD --body \"$PASSWORD\"" | ||
| echo " gh secret set SIGNING_KEYSTORE_BASE64 --body \"\$(openssl base64 -A -in $KEYSTORE)\"" |
| exit 1 | ||
| fi | ||
|
|
||
| command -v keytool >/dev/null || { echo "keytool not found (install a JDK)" >&2; exit 1; } |
Fixes #29, #93
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
runs unit tests and builds a release apk on every pull request, uploading it as a workflow artifact so changes can be sideloaded and live-tested before merging. signing secrets are optional: fork prs fall back to the debug key.
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
hardware testing on a wall display x1i showed the sw terminal reports each contact transition as a ~5ms down+up key pulse whose keycode encodes the direction: 141/KEY_F11 = line went active, 142/KEY_F12 = line went inactive. the old code treated 141 down/up as press/release and 142 as a second input, so falling edges were swallowed and multi-way taster circuits only toggled on every second press. the state machine now consumes decoded levels, only the pulse's down event is fed in, and the opposite-level debounce is replaced by a same-level duplicate window so genuine repeated presses still register.
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
without the SIGNING_* secrets every ci run signs with a keystore generated fresh on that runner, so no artifact can ever update a previously installed one. the build step now surfaces that as a run warning and tools/ gains a script that generates a persistent keystore and prints the secrets to set.
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW
base64 -w0 is gnu-only; bsd/macos base64 has no -w flag. openssl base64 -A produces single-line output on both platforms and the script already uses openssl anyway.
Claude-Session: https://claude.ai/code/session_01FdbXCtBx5iM1nSWTGaD2jW