Skip to content

Support the HUB Navizone gateway, and only wire climate features the device reports - #160

Open
mathieuletyrant wants to merge 7 commits into
gduteil:mainfrom
mathieuletyrant:hub-shogun-1758
Open

Support the HUB Navizone gateway, and only wire climate features the device reports#160
mathieuletyrant wants to merge 7 commits into
gduteil:mainfrom
mathieuletyrant:hub-shogun-1758

Conversation

@mathieuletyrant

@mathieuletyrant mathieuletyrant commented Aug 13, 2026

Copy link
Copy Markdown

Adds support for my HUB Navizone air conditioning gateway, and fixes a related issue it exposed.

1. modelId 1758 is missing from the model table

It falls through to the else branch and shows up as "Unknown product (1758)" in the device panel. setupviewv2 reports it as:

modelId 1758 | productId 96 | modelFamily "Air_Conditioning"
longName "HUB Navizone"

(name/customName read HUB SHOGUN - PRT pile on my install, but that is the user-editable label from the Cozytouch app, not the product designation.)

It is the gateway for the air conditioners, which on my setup are modelId 557, 558 and 559 with masterDeviceId pointing at it — the same 557-561 range the 556 Naviclim Hub drives. So 556 and 1758 look like two gateway generations for the same units.

Added to the tested devices list in the README too.

2. Optional climate features were wired regardless of hardware support

That last point has a consequence. fanModes, swingModes and quietModeAvailable are declared per model, and capability.py turned them into capability ids 100801-100804 unconditionally. climate.py then enables FAN_MODE and SWING_MODE purely on those keys being present, so the controls appeared whether or not the hardware backed them. Same for the AC presets, wired to 100505-100507 with no check.

My 557-559 units report none of 100801, 100802, 100803, 100804, 100505 or 100506. Only 100507 (eco) exists. So the model id alone cannot answer what a unit supports — the same id spans hardware with different feature sets.

The failure was silent rather than a crash: get_capability_value falls back to "0" for a capability the device does not have, so the fan and swing selectors showed a fixed bogus value and writes went to an id the device ignores.

get_capability_infos now takes the set of capability ids the device actually reports and only wires each optional feature when it is backed. Replaying my real capability list through it:

key                           my 557 unit    if all declared
fanModeCapabilityId                   no                yes
quietModeCapabilityId                 no                yes
swingModeCapabilityId                 no                yes
swingOnCapabilityId                   no                yes
activityCapabilityId                  no                yes
ecoCapabilityId                      yes                yes
boostCapabilityId                     no                yes

Right-hand column is the regression check: a device that does report them keeps everything. The gating in climate.py already keyed on these keys being present, so it needed no change.

Also drops the unused Hub.get_capability_infos, which passed a modelId int where get_capability_infos expects a modelInfos dict and would have raised on any call. Happy to keep it if you would rather.

Note this touches hub.py, as does #161 — different functions, and they cherry-picked onto each other cleanly, but flagging it.

🤖 Generated with Claude Code

My installation exposes a device with modelId 1758 that is missing from
model.py, so it falls through to the else branch and shows up as
"Unknown product (1758)" in the device panel.

setupviewv2 reports it with longName "HUB Navizone", modelFamily
"Air_Conditioning". It is the gateway for the air conditioners, which on
my setup are modelId 557-559 -- the same range the 556 Naviclim hub
drives, so the two look like sibling gateway generations.

Display only: CozytouchDeviceType.HUB is set but never read, since
capability.py only branches on GAZ_BOILER, TOWEL_RACK, AC and HEAT_PUMP.
Entities for the device were already being created correctly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mathieuletyrant mathieuletyrant changed the title Add modelId 1758 (Shogun AC gateway) to the model table Add modelId 1758 (HUB Navizone) to the model table Aug 13, 2026
@mathieuletyrant

Copy link
Copy Markdown
Author

Correction after dumping the raw setupviewv2 payload: the product name is HUB Navizone, not "HUB Shogun" as I first proposed. Force-pushed.

name and customName both read HUB SHOGUN - PRT pile on my setup, but that is the user-editable label from the Cozytouch app. The product designation is in longName:

modelId 1758 | productId 96 | modelFamily "Air_Conditioning"
longName     "HUB Navizone"
name         "HUB SHOGUN - PRT pile"   <- user-editable

The dump also settles what sits behind it: the three AC units are modelId 557, 558 and 559, with masterDeviceId pointing at the 1758. That is the same 557-561 range the 556 Naviclim Hub drives, so 556 and 1758 look like two gateway generations for the same units rather than separate product lines. I reworded the code comment accordingly.

Worth noting for anyone reading the table later: since the AC modelId does not change across hub generations, it cannot be used on its own to infer what a unit supports. I hit that concretely — see the separate report I am filing about fanModes/swingModes/quietModeAvailable being declared for 557-561 units that do not expose the matching capabilities.

mathieuletyrant and others added 2 commits August 13, 2026 09:56
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fanModes, swingModes and quietModeAvailable are declared per model, and
capability.py turned them into capability ids 100801-100804
unconditionally. climate.py then enables FAN_MODE and SWING_MODE purely
on the presence of those keys, so the controls appeared whether or not
the hardware backed them. Same for the AC presets, wired to 100505-100507
with no check.

The model id cannot answer this on its own. On my setup a 1758 gateway
drives modelId 557-559 units -- the same range the 556 Naviclim hub
drives -- and those units report none of 100801, 100802, 100803, 100804,
100505 or 100506. Only 100507 (eco) exists. So the same model id spans
hardware with different feature sets.

The symptom was silent rather than a crash: get_capability_value falls
back to "0" for a capability the device does not have, so the fan and
swing selectors showed a fixed bogus value and writes went to an id the
device ignores.

get_capability_infos now takes the set of capability ids the device
reports and only wires each optional feature when it is backed. Devices
that do report them are unaffected. The gating in climate.py already
keyed on these keys being present, so it needed no change.

Also drops the unused Hub.get_capability_infos, which passed a modelId
int where get_capability_infos expects a modelInfos dict and would have
raised on any call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mathieuletyrant mathieuletyrant changed the title Add modelId 1758 (HUB Navizone) to the model table Support the HUB Navizone gateway, and only wire climate features the device reports Aug 13, 2026
@mathieuletyrant

Copy link
Copy Markdown
Author
Screenshot 2026-08-13 at 10 00 32

@mathieuletyrant

Copy link
Copy Markdown
Author

Cross-referencing: this covers two open reports of the same device.

Worth noting on naming, since the two issues call it Shogun and this PR names it HUB Navizone: on my install name/customName also read HUB SHOGUN - PRT pile, but those are the user-editable labels from the Cozytouch app. The product designation in longName is HUB Navizone, and #117 frames the whole system as "Navizone Shogun". I went with the value Atlantic's own API reports, but if you would rather the table match what users see in the app, HUB Shogun is a one-line change — your call.

The 1734 half of #143 is already mapped on main via the or modelId == 1734 case, so this PR should close out what remains of both.

…temperature

The 557-561 and 1734 branch set currentTemperatureAvailable = False, so
currentValueCapabilityId was never wired and those climate entities had no
ambient reading at all -- only a setpoint. Capability 117 is mapped as its
own sensor either way, so the temperature was visible in HA while the
climate entity itself reported nothing, leaving thermostat cards,
current_temperature consumers and Matter bridges without it.

My units do report 117, with a distinct value per room, so the flag was
wrong for this hardware. Flipping it for the whole range would repeat the
mistake in the other direction: get_capability_value falls back to "0" for
a capability the device lacks, so a unit in that range without 117 would
show a confident 0 degrees, which is worse than showing nothing.

So the model flag stays as an explicit opt-out, but the device now has the
final say -- the same rule this branch already applies to the fan, swing
and preset ids. The AC branch no longer forces the flag off, and units
that do not report 117 simply get no current temperature.

Applied to the heat pump z1/z2 overrides too, for 117 and 118, so every
path that wires a current temperature checks the device first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mathieuletyrant

mathieuletyrant commented Aug 13, 2026

Copy link
Copy Markdown
Author

Added a fourth commit extending the same rule to the current temperature, which also gives an answer to #138.

The 557-561 / 1734 branch sets currentTemperatureAvailable = False, so currentValueCapabilityId is never wired and those climate entities have no ambient reading — only a setpoint. Capability 117 is mapped as its own sensor regardless, so the temperature shows up in HA while the climate entity itself reports nothing, which leaves thermostat cards, current_temperature consumers and Matter bridges without it.

My three units do report 117, with a distinct value per room (24.14 / 24.95 / 24.83), so the flag is wrong for this hardware — same conclusion #138 reaches.

Where I would differ from #138 is the remedy. Flipping the flag to True for the whole range repeats the original mistake in the other direction: get_capability_value falls back to "0" for a capability the device lacks, so a unit in that range without 117 would display a confident 0 °C. A wrong number is worse than a missing one.

So the model flag stays as an explicit opt-out and the device gets the final say, exactly as this PR already does for the fan, swing and preset ids:

if (
    modelInfos.get("currentTemperatureAvailable", True)
    and 117 in availableCapabilityIds
):
    capability["currentValueCapabilityId"] = 117

Replayed both ways:

my units (117 reported)          current_temperature -> 117
unit in range without 117        current_temperature -> not wired

Applied to the heat pump z1/z2 overrides too (117 and 118), so every path that wires a current temperature checks the device first.

Credit to @rafal83 for spotting the underlying problem in #138 — this is the same fix with a guard against the range being wrong for someone else.

Screenshot 2026-08-13 at 10 29 49

On a zoned install only the master room picks the operating mode. A slave
accepts a mode write and stores it, but keeps running whatever the master
imposes, so capability 7 is a request rather than a state. Capability 181
carries the effective mode.

The integration only ever read 7, so HA showed the request as if it were
reality: setting a slave to heat while the system cooled left the card
claiming heat with nothing to contradict it. Reproduced on my install --
capability 7 = 4 on a slave, 181 = 3, and the room blowing cold. The
official app has the same blind spot, colouring the room warm.

181 uses the same value scale as 7, so it is read through the model's
HVACModes and translated to HVACAction. That sidesteps having to identify
the master at all: every room reports what it is doing, so no guessing
which one commands, which is what blocked this until now.

HVACMode.AUTO is deliberately left unmapped -- a system in auto is really
heating, cooling or idle, and there is no HVACAction for it, so returning
nothing beats guessing.

Gated on 181 being reported, like the other optional features, so devices
that do not expose it are unaffected.

Only 0 and 3 were observed on my hardware; 4, 7 and 8 are extrapolated
from the capability 7 scale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mathieuletyrant

Copy link
Copy Markdown
Author

Fifth commit, and this one explains a user-visible confusion I could not account for before.

On a zoned install only the master room picks the operating mode. A slave accepts a mode write and stores it, but keeps running whatever the master imposes. So capability 7 is a request, not a state — and the integration only ever read 7.

Reproduced on my install. I set a slave room to heat from HA:

ROOM_1 (slave)   capability   7 = 4   modified t=1786611193   <- my write, accepted and persisted
                 capability 181 = 3   modified t=1786611058   <- effective mode, never followed
ROOM_0 (master)  capability   7 = 3                           <- master is on cool

The write was not rejected — no Execution_state error, the value stuck. The room simply kept blowing cold while HA displayed heat with nothing to contradict it. Atlantic's own app has the same blind spot: it colours the room warm.

Capability 181 carries the effective mode, on the same value scale as 7, so it is read through the model's HVACModes and translated to HVACAction. climate.py had no hvac_action at all until now.

What makes this workable is that it removes the need to identify the master. I had parked this problem precisely because nothing in the payload reliably designates the master room, and guessing would have broken installs whose master is not the first unit. With 181 the question disappears — every room reports what it is doing.

Result on the same slave: hvac_mode: heat, hvac_action: cooling. The contradiction becomes legible instead of invisible.

Two caveats stated plainly:

  • I only observed 181 at 0 and 3. Values 4, 7 and 8 are extrapolated from the capability 7 scale.
  • HVACMode.AUTO is deliberately left unmapped. A system in auto is really heating, cooling or idle, and HVACAction has no auto, so returning nothing beats guessing.

Gated on 181 being reported, like everything else here, so devices that do not expose it are unaffected.

@mathieuletyrant

Copy link
Copy Markdown
Author

The capability list in #86 confirms what capability 181 is:

181  SERVICE_IN_PROGRESS_MODE
117  ROOM_AMBIENT_TEMPERATURE

SERVICE_IN_PROGRESS_MODE is literally the service currently running, which is what this PR reads it as: what the unit is actually doing, as opposed to what it was asked for. That is the distinction hvac_action exists for.

The caveat about values 4, 7 and 8 being extrapolated from the capability 7 scale still stands — the list gives names, not value tables.

It also settles a red herring I chased on the way. 217 and 100023 read 3083 on all three of my rooms, which looked temptingly like 30.83 °C next to an outdoor reading of 30 on the wall unit. They are SYSTEM_AVAILABLE_MODES_CAPABILITIES and SYSTEM_SUPPORTED_MODES_CAPABILITIES — bitmasks. For what it is worth, no capability in the list contains OUTDOOR, OUTSIDE or EXTERIOR, so outdoor temperature does not appear to live in this namespace at all.

mathieuletyrant and others added 2 commits August 16, 2026 11:43
Capability 172 has been mapped for every device since the gas boiler days,
so an air conditioner gets a writable "Away Mode Temperature" number. The
hardware does not honour it: a Navizone setup captured mid-absence has 172
sitting at the value Home Assistant wrote days earlier, while the weekly
program keeps driving the setpoints hour by hour -- 177 moved to 26 at 03:00
against the cooling slot [180, 26], and 40 moved to 21 at 07:00 against the
heating slot [420, 21], both well inside the absence window. Absence on this
family stops the units until the return date, which is why the Cozytouch app
offers a return date and no temperature at all.

Gate it behind awayModeTemperatureAvailable and clear the flag on the air
conditioners and on the two hubs that drive them, leaving the heating
products untouched.

Map 100261 while here, the per-room mirror of the hub's away mode flag. Its
neighbour 100260 carries the absence window per room and stays unmapped: the
window is written for the whole setup, not room by room.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The room units behind a Naviclim or Navizone hub report capability 100507,
so they get an eco_mode switch and PRESET_ECO on the climate. The Cozytouch
app offers no eco mode for them anywhere, neither on the home screen nor in
a room's own screen, and 100507 has sat at 0 since provisioning on the setup
we have a capture of.

Gate both entry points behind ecoModeAvailable, scoped to 557-561: those are
the units a hub drives, which is what the report covers. 1734 shares the AC
branch but is a separate product with nothing reported either way, so it
keeps its eco mode.

With activity and boost already absent from these units, dropping eco leaves
the climate without PRESET_NONE, so its presets become basic, prog and
override -- which is what actually drives them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant