Skip to content

Bass/Treble/Balance SET commands use wrong frame shape (confirmed via direct RS232 testing) #6

Description

@kirkande1

Summary

async_set_bass, async_set_treble, and async_set_balance in lync_client.py build a frame the real Lync12 hardware either silently ignores or rejects with an Echo Error Status. Confirmed the correct TX encoding via direct hardware testing and have a suggested fix below.

Testing methodology (please read before the findings)

This was tested with a PC connected directly to the Lync12 controller's RS232 port via a USB-RS232 adapter (38400 8N1) — not through a WGW-SLX or any other network gateway. That gateway was bypassed because ours failed independently during troubleshooting (a genuine hardware fault, confirmed by elimination: cable swap, direct-vs-crossed wiring, and the controller responding normally once addressed directly).

If you're controlling your Lync12 through a WGW-SLX or similar gateway, and that gateway performs any protocol-level translation, your results could differ from what's documented here. I believe (based on the gateway's transparent-bridge design, and the fact that other commands — volume, power, source-select — already work correctly through it using this library's existing, un-translated encoding) that no translation occurs. But I haven't independently verified that with a live capture through a working gateway, so treat that as an assumption, not a confirmed fact.

Findings

Sending a bass/treble/balance SET command as currently implemented (COMMON_COMMAND_CODE wrapper, value as trailing extra data) gets one of two results on real hardware:

  • No response at all, or
  • An Echo Error Status (0x1B) with an error code matching the relevant parameter (e.g. error 4 = "Bass setting range error") — identical response regardless of the value sent, confirming it's a frame-shape rejection, not a real range check.

Correct encoding, confirmed with real, audible before/after changes across the full range of each parameter on a live zone:

  • Command byte = the setting's own code (0x18 bass / 0x17 treble / 0x16 balance) used directly, not wrapped under 0x04
  • Value = a plain 8-bit two's-complement signed byte, no +0x80 offset — identical to the RX/response-side encoding this library already parses correctly (see convert_value() / the zone status parsing)
  • Frame: [0x02, 0x00, zone, CMD, value_byte, checksum]

Test data (zone 8, real Lync12 hardware, audio playing, changes confirmed by ear):

Parameter Values tested Result
Bass +10, +5, 0, -5, -10 All correct, audible, direction-matched
Treble +10, -10 Both correct, audible
Balance +18, -18, 0 All correct, audible pan

Suggested fix

async def async_set_bass(self, zone: int, bass: int):
    zone_info = self.get_zone(zone)

    if zone_info.bass == bass:
        return

    return await self._async_send_and_validate(
        lambda z: z.bass == bass,
        zone,
        HtdLyncCommands.BASS_SETTING_CONTROL_COMMAND_CODE,
        bass & 0xff
    )

Same pattern for async_set_treble (TREBLE_SETTING_CONTROL_COMMAND_CODE) and async_set_balance (BALANCE_SETTING_CONTROL_COMMAND_CODE). Note async_set_balance is already structured this way (setting code used directly, no COMMON_COMMAND_CODE wrapper) — it's only missing the & 0xff mask, so it hits the same crash as bass/treble for negative values (bytearray() can't hold a negative int). The & 0xff mask fixes that crash as a side effect of fixing the encoding.

One more thing worth flagging separately: these three commands essentially never echo a confirming response even when they succeed (per the table above), so _async_send_and_validate's response-driven retry loop can time out and raise even when the command actually worked. Might be worth a _send_cmd + optimistic local-state-update path for these specifically, rather than the generic validate-and-retry wrapper used for commands that do reliably echo (power, volume, mute).

Happy to test a patch against real hardware if useful — I have direct RS232 access to a Lync12 at the moment.

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