Sonance fixes#7
Conversation
Mesa DescriptionTL;DRFixes multiple bugs in the Sonance amplifier protocol, specifically related to EQ command formatting, response parsing, and multi-query zone status retrieval, ensuring correct functionality for Sonance C4630 SE and similar devices. What changed?
Description generated by Mesa. Update settings |
There was a problem hiding this comment.
Performed full review of d994e0c...31e98f7
Analysis
• Silent failure mode in _merge_zone_status_responses() returns partial data without logging or indication to caller, creating observability gaps that make debugging communication issues harder and mask incomplete status as complete
• Configuration validation gap: new min_bass/min_treble/min_balance fields use fallback defaults when missing, allowing misconfigured devices to silently use incorrect EQ bounds without detection
• Inconsistent error handling between single-query path (with warnings via ZoneStatus.from_string()) and multi-query path (silent skipping of failed queries) reduces debuggability across protocol implementations
• Significant test coverage gap: no tests for negative EQ formatting, multi-query zone status flow, partial response handling, or new Sonance regex patterns, leaving edge cases around partial failures unvalidated
Tip
Help
Slash Commands:
/review- Request a full code review/review latest- Review only changes since the last review/describe- Generate PR description. This will update the PR body or issue comment depending on your configuration/help- Get help with Mesa commands and configuration options
0 files reviewed | 5 comments | Edit Agent Settings • Read Docs
| bass_status: '\+L(?P<zone>\d+)(?P<bass>[+-]?\d+)' | ||
| balance_status: '\+B(?P<zone>\d+)(?P<balance>[+-]?\d+)' | ||
|
|
||
| zone_status_queries: |
There was a problem hiding this comment.
The zone_status_queries list defines 7 separate queries that will be executed sequentially for each zone status check. This is a significant behavioral change:
Performance Impact: Single query → 7 sequential queries
- Each query involves serial I/O with response parsing
- For slow serial connections (9600 baud), this could add noticeable latency
- Consider if this could be optimized or cached
Network/Serial Load: 7x increase in command traffic
- Matters for polled status updates
- Consider rate limiting or batch status queries
Reliability: More opportunities for failure
- If 1 of 7 queries times out, you get partial data (see comment on
_merge_zone_status_responses)
Document this trade-off and consider adding a config option for query timeout to fail fast if serial communication is unreliable.
Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: rsnodgrass/pyxantech#7
File: pyxantech/protocols/sonance.yaml#L89
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.
Feedback:
The `zone_status_queries` list defines 7 separate queries that will be executed sequentially for each zone status check. This is a significant behavioral change:
**Performance Impact**: Single query → 7 sequential queries
- Each query involves serial I/O with response parsing
- For slow serial connections (9600 baud), this could add noticeable latency
- Consider if this could be optimized or cached
**Network/Serial Load**: 7x increase in command traffic
- Matters for polled status updates
- Consider rate limiting or batch status queries
**Reliability**: More opportunities for failure
- If 1 of 7 queries times out, you get partial data (see comment on `_merge_zone_status_responses`)
Document this trade-off and consider adding a config option for query timeout to fail fast if serial communication is unreliable.
asyncio.Queue.put() is a coroutine; calling ensure_future on it from a synchronous Protocol callback is unnecessary and fragile. put_nowait is the correct synchronous API for an unbounded queue and avoids scheduling a redundant task on every received byte. Signed-off-by: Nadav Haklai <nadav.haklai@gmail.com>
The Sonance C4630 SE supports signed EQ ranges: bass/treble -8..+8, balance -10..+10. The max values were already defined but min values were missing, causing get_device_config() to return None for min limits and the clamping logic to fall back to 0. Signed-off-by: Nadav Haklai <nadav.haklai@gmail.com>
_set_bass_cmd, _set_treble_cmd, _set_balance_cmd all hardcoded 0 as the minimum value, silently clamping negative inputs. Now reads min_bass, min_treble, min_balance from device config (falling back to 0 if not defined, preserving existing behaviour for other amp types). This allows Sonance C4630 SE to correctly send negative EQ values (-8 to +8 for bass/treble, -10 to +10 for balance). Signed-off-by: Nadav Haklai <nadav.haklai@gmail.com>
…_status
Three fixes for Sonance C4630 SE:
- set_bass/treble/balance: use signed format (:L{zone}{bass:+03d}) so negative
values produce :L1-05 not garbage
- zone_status: :Z{zone}? only returns power; add zone_status_queries list and
_zone_status_query_commands() / _merge_zone_status_responses() to merge
separate V/S/M/L/H/B query results into one ZoneStatus
- response regex: remove spurious BS/TR/BA literals; restrict volume zone group
to [1-6] (+V460 was parsing as zone=46 vol=0 instead of zone=4 vol=60)
Signed-off-by: Nadav Haklai <nadav.haklai@gmail.com>
Sonance C4630 SE sends unsolicited status broadcasts as side effects of commands (e.g. source change triggers zone power notifications). These extra lines arrive in the same TCP burst as the actual query response or as a back-to-back packet, causing two classes of bug: 1. Wrong-zone values: _read_response returned only the first line, which could be an unsolicited broadcast. _merge_zone_status_responses then matched it without checking zone, silently writing another zone's values into the queried zone's status. 2. Dropped responses: the broadcast consumed the read slot; the actual response arrived later and was flushed by the next send(). Fix by returning all complete lines from _read_response (list[str]) instead of only the first, draining the queue non-blocking after the first EOL to capture back-to-back packets, and scanning every returned line in _merge_zone_status_responses for the first match whose zone field equals the queried zone. Signed-off-by: Nadav Haklai <nadav.haklai@gmail.com>
Sonance C4630 SE: fix EQ commands, response regex, and multi-query zone status
Tested on Sonance C4630 SE (6-zone). Three bugs prevented EQ and volume
status from working correctly with this amplifier.
Changes
pyxantech/series/sonance6.yamlAdd
min_bass,min_treble,min_balanceto the device config. Themax values were already present but the min values were missing, causing
get_device_config()to returnNonefor EQ minimums and the clampinglogic to silently floor at 0.
pyxantech/__init__.pyFix EQ command clamping:
_set_bass_cmd,_set_treble_cmd,_set_balance_cmdall hardcoded0as the minimum. They now readmin_bass/min_treble/min_balancefrom device config (falling backto
0, so all other amp types are unaffected).pyxantech/protocols/sonance.yaml— three fixes:Signed EQ commands:
set_bass/set_treble/set_balanceused{value:02}which cannot represent negative numbers. Changed to{value:+03d}so negative values produce:L1-05instead ofgarbage.
Multi-query zone status:
:Z{zone}?only returns power state onSonance — not volume, source, mute, or EQ. Added
zone_status_querieslist;
__init__.pynow sends separate queries for each attribute andmerges the results into one
ZoneStatus. Non-Sonance amp types areunaffected (no
zone_status_queriesdefined in their protocol).Response regex fixes:
\+V(?P<zone>\d+)(?P<volume>\d+)was greedy —+V460matched as zone=46, volume=0. Fixed by restricting zone group to
[1-6].BS/TR/BAliteralsthat don't appear in actual responses (device sends
+L1+5, not+L1BS+5). Removed. Capture groups now match signed values with[+-]?\d+.Affected hardware
Other amp types (xantech8, monoprice6, dax88, zpr68) are unaffected:
the
zone_status_queriespath is only taken when the protocol configdefines that key, and their EQ min-value fallback is
0as before.