|
16 | 16 | from custom_components.solisconnect.sensor_data.cloud_mapping import ( |
17 | 17 | CLOUD_CID_MAP, |
18 | 18 | CLOUD_INPUT_MAP, |
| 19 | + TOU_SWITCH_CIDS_BY_BIT, |
| 20 | + TOU_SWITCH_REGISTER, |
19 | 21 | encode_cid_value, |
20 | 22 | encode_engineering_value, |
21 | 23 | registers_covered, |
@@ -100,13 +102,18 @@ async def _update_cycle(self, now=None): |
100 | 102 | mapping = CLOUD_CID_MAP.get(cid) |
101 | 103 | if mapping is None: |
102 | 104 | continue # the API returns extra related CIDs beyond those requested |
| 105 | + if mapping.merge_register_bit is not None: |
| 106 | + continue # TOU V2 switch bits are merged together below, not per-CID |
103 | 107 | words = encode_cid_value(mapping, msg) |
104 | 108 | if words: |
105 | | - if mapping.merge_register_bit is not None: |
106 | | - words = self._merge_bit_words(words, mapping.merge_register_bit) |
107 | 109 | self._publish_words(words) |
108 | 110 | cid_words += len(words) |
109 | 111 |
|
| 112 | + switch_words = self._merge_switch_bits(batch) |
| 113 | + if switch_words: |
| 114 | + self._publish_words(switch_words) |
| 115 | + cid_words += len(switch_words) |
| 116 | + |
110 | 117 | controller.last_cloud_success = datetime.now(UTC) |
111 | 118 | # INFO so it is visible in `ha core logs` when diagnosing live installs |
112 | 119 | _LOGGER.info("Cloud poll complete: %s detail registers, %s cid registers", published, cid_words) |
@@ -140,16 +147,37 @@ def _publish_words(self, words: dict[int, int]): |
140 | 147 | cache_save(self.hass, self.controller, register, word) |
141 | 148 | notify_register_update(self.hass, self.controller, register, word) |
142 | 149 |
|
143 | | - def _merge_bit_words(self, words: dict[int, int], bit_position: int) -> dict[int, int]: |
144 | | - """Merge one CID switch value into the shared TOU V2 bitfield register.""" |
145 | | - register, bit_value = next(iter(words.items())) |
146 | | - current = cache_get(self.hass, self.controller, register) |
147 | | - merged = int(current) if current is not None else 0 |
148 | | - if int(bit_value): |
149 | | - merged |= 1 << bit_position |
| 150 | + def _merge_switch_bits(self, batch: dict[int, str]) -> dict[int, int] | None: |
| 151 | + """Build the merged TOU V2 switch register from this poll's batch reads plus the cache. |
| 152 | +
|
| 153 | + A switch CID missing from one poll's batch response must not be treated as "off": on a |
| 154 | + cold cache (nothing published yet) we only publish once every switch bit has been observed |
| 155 | + at least once, so a transiently-missing CID can never permanently zero a bit it never |
| 156 | + actually reported. On a warm cache, a missing CID simply leaves its bit as last observed. |
| 157 | + """ |
| 158 | + observed: dict[int, int] = {} |
| 159 | + for bit, cid in TOU_SWITCH_CIDS_BY_BIT.items(): |
| 160 | + msg = batch.get(cid) |
| 161 | + if msg is None: |
| 162 | + continue |
| 163 | + words = encode_cid_value(CLOUD_CID_MAP[cid], msg) |
| 164 | + if words: |
| 165 | + observed[bit] = words[TOU_SWITCH_REGISTER] |
| 166 | + |
| 167 | + current = cache_get(self.hass, self.controller, TOU_SWITCH_REGISTER) |
| 168 | + if current is None: |
| 169 | + if len(observed) < len(TOU_SWITCH_CIDS_BY_BIT): |
| 170 | + return None # incomplete cold-cache read; defer until a cycle observes every bit |
| 171 | + merged = 0 |
150 | 172 | else: |
151 | | - merged &= ~(1 << bit_position) |
152 | | - return {register: merged} |
| 173 | + merged = int(current) |
| 174 | + |
| 175 | + for bit, value in observed.items(): |
| 176 | + if value: |
| 177 | + merged |= 1 << bit |
| 178 | + else: |
| 179 | + merged &= ~(1 << bit) |
| 180 | + return {TOU_SWITCH_REGISTER: merged} |
153 | 181 |
|
154 | 182 | def _publish_status(self): |
155 | 183 | """Pseudo-registers used by diagnostic derived sensors (same as DataRetrieval).""" |
|
0 commit comments