Skip to content

[Bug]: MQTT toggleLid fails in ApiMode with on-demand BLE connection #41

Description

@Flachzange

Summary

When the bridge runs in ApiMode with ble_connection=on-demand, the MQTT toggleLid command can be routed through a direct ServiceMode handler instead of through ApiMode.

In the observed case this caused the command to fail completely: the lid did not move, because the direct handler tried to use a client that does not exist in on-demand mode.

The underlying issue is that ApiMode embeds ServiceMode, while ServiceMode also registers direct MQTT control handlers that assume an already available BLE client.

Observed behavior

Configuration:

[SERVICE]
mode=api
ble_connection=on-demand

Sending the MQTT toggleLid command produced:

Caught exception in on_message: 'NoneType' object has no attribute 'toggle_lid_position'

The toilet did not react to the command.

In on-demand BLE mode there is intentionally no permanently connected ServiceMode.client. However, the MQTT ToggleLidPosition event was still handled directly by ServiceMode, which attempted to execute:

self.client.toggle_lid_position()

with self.client being None.

As a result, the command was never sent to the AquaClean.

Expected behavior

When the application runs in ApiMode, MQTT commands should be routed through ApiMode so that the configured BLE connection lifecycle is respected.

For toggleLid, the expected flow is:

MQTT ToggleLidPosition
    -> ApiMode MQTT handler
    -> run_command("toggle-lid")
    -> establish on-demand BLE connection
    -> client.toggle_lid_position()
    -> disconnect BLE

The MQTT topic and command semantics should remain unchanged.

Cause

ApiMode creates and embeds a ServiceMode instance.

Historically, ServiceMode registered its own direct MQTT handlers for control commands. That is appropriate when ServiceMode owns a persistent BLE client, but it is not safe when the same service object is embedded in ApiMode while ble_connection=on-demand.

In that configuration, direct ServiceMode handlers bypass the ApiMode methods responsible for establishing the temporary BLE connection.

For the observed toggleLid failure, this results in a call on None instead of creating an on-demand connection first.

Affected routing

The concrete failure reproduced here was ToggleLidPosition.

The same routing pattern also applies to other direct MQTT handlers that bypass ApiMode's connection lifecycle. The proposed fix therefore addresses the handler ownership rather than special-casing only toggleLid.

The investigation identified the same architectural issue for at least:

  • ToggleLidPosition
  • ResetFilterCounter
  • Connect

Only the toggleLid failure described above was directly observed as a user-facing failure in this report.

Proposed fix

Allow ServiceMode to disable registration of its direct MQTT control handlers when it is embedded by ApiMode.

ApiMode then registers the affected MQTT handlers itself and routes them through its existing connection-aware methods.

Conceptually:

Standalone ServiceMode
    -> keeps existing direct MQTT handlers

ApiMode
    -> creates ServiceMode without direct control handlers
    -> registers MQTT handlers in ApiMode
    -> routes commands through run_command() / do_connect()
    -> respects persistent vs. on-demand BLE mode

For toggleLid, this changes the execution path from a direct call on ServiceMode.client to:

await self.run_command("toggle-lid")

which uses the correct on-demand connection lifecycle.

Compatibility

The proposed change is intended to preserve existing external behavior:

  • no MQTT topic changes;
  • no payload changes;
  • no command semantic changes;
  • standalone ServiceMode keeps its existing direct-handler behavior;
  • persistent BLE mode continues to use the existing persistent client path;
  • only handler ownership/routing changes when ServiceMode is embedded in ApiMode.

Regression coverage

Regression tests should verify that:

  1. ApiMode creates ServiceMode with direct MQTT control-handler registration disabled;
  2. ApiMode owns the affected MQTT handlers;
  3. ToggleLidPosition is routed through ApiMode.run_command("toggle-lid");
  4. ResetFilterCounter and Connect are routed through the corresponding ApiMode lifecycle-aware methods;
  5. standalone ServiceMode retains its legacy direct-handler behavior.

Environment where reproduced

The failure was reproduced with:

  • application mode: ApiMode
  • BLE connection mode: on-demand
  • MQTT enabled
  • AquaClean Mera connected through the bridge's normal BLE transport

The problem is primarily a bridge-side routing/lifecycle issue and is not believed to depend on a specific AquaClean firmware behavior.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions