sinergia-vmc-modbus 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Riccardo Rossi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: sinergia-vmc-modbus
3
+ Version: 0.3.0
4
+ Summary: Device library (register map) for Sinergia VMC units with CPRO3OEM-K electronics, over Modbus RTU
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/riccardorossi92/sinergia-vmc-modbus
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: modbus-connection>=4.12
11
+ Dynamic: license-file
12
+
13
+ # sinergia-vmc-modbus
14
+
15
+ > La stessa mappa dei registri è usata (come copia interna) dall'integrazione
16
+ > Home Assistant [ha-sinergia-vmc](https://github.com/riccardorossi92/ha-sinergia-vmc).
17
+
18
+ Device library (standalone, non legata a Home Assistant) per le unità VMC
19
+ Sinergia con elettronica **CPRO3OEM-K** (HRD/HRD+), basata sul documento
20
+ ufficiale *"Manuale di collegamento e impostazioni MODBUS HRD/HRD+"*
21
+ (Sinergia, r.03_10-2023 / r.05) e verificata sul campo su un'unità HRD 30/15 H K DC
22
+ con elettronica CPRO3OEM-K e pannello di comando a muro EVJD920N2VW.
23
+
24
+ Costruita sopra [`modbus-connection`](https://pypi.org/project/modbus-connection/),
25
+ seguendo il pattern "device library" descritto in
26
+ https://developers.home-assistant.io/blog/2026/07/05/modernizing-modbus/
27
+ (vedi [darkrain-nl/sofar-modbus](https://github.com/darkrain-nl/sofar-modbus)
28
+ come riferimento).
29
+
30
+ ## Collegamento fisico
31
+
32
+ RS-485 (A+/B-/GND) sui morsetti dedicati della scheda. Impostazioni seriali
33
+ di fabbrica: **9600 baud, 8 bit, parità NESSUNA, 1 stop bit** (dichiarate
34
+ esplicitamente dal manuale Sinergia; la tabella parametri H11-H14 dello
35
+ stesso documento riporta invece un default "parità Pari" per H13 — le due
36
+ sezioni sono in contraddizione. Se la connessione non risponde, riprova con
37
+ parità Even).
38
+
39
+ ## Indirizzamento registri — nota importante
40
+
41
+ Il manuale Sinergia elenca "Addr HEX" e "Addr DEC" per ogni registro, ma
42
+ `Addr DEC = Addr_HEX_convertito + 1` in tutti i gruppi tranne "COMANDI TEST"
43
+ (dove sembrano coincidere — probabile refuso, per questo quel gruppo non è
44
+ incluso qui). Gli indirizzi usati in questa libreria sono già quelli reali
45
+ da chiamare via Modbus (`Addr_HEX` / `Addr_DEC - 1`), verificati incrociando
46
+ 12+ registri con la scansione reale RS-485 fatta sul campo da un altro
47
+ utente della stessa VMC.
48
+
49
+ ## Installazione
50
+
51
+ ```bash
52
+ pip install sinergia-vmc-modbus
53
+ ```
54
+
55
+ ## Uso
56
+
57
+ ```python
58
+ import asyncio
59
+ from modbus_connection.pymodbus import connect_serial
60
+ from sinergia_vmc_modbus import VmcDevice
61
+
62
+ async def main():
63
+ connection = await connect_serial(
64
+ "/dev/ttyUSB0", baudrate=9600, parity="N", stopbits=1, timeout=3
65
+ )
66
+ unit = connection.for_unit(1) # indirizzo Modbus della scheda (H11, default 1)
67
+ device = VmcDevice(unit)
68
+
69
+ # Abilitazioni obbligatorie (H02/H27/H28) - vedi note nel modulo device.py:
70
+ # senza queste la macchina IGNORA le scritture sui registri corrispondenti.
71
+ await device.enables.write("onoff_by_bms", True)
72
+
73
+ await device.async_poll(device.COMPONENT_NAMES)
74
+ print("Temperatura esterna:", device.probes.temp_outdoor, "°C")
75
+ print("Stato unità:", device.status.unit_status)
76
+
77
+ await device.command.write("onoff_by_supervisor", True) # accende la VMC
78
+
79
+ await connection.close()
80
+
81
+ asyncio.run(main())
82
+ ```
83
+
84
+ ## Struttura
85
+
86
+ - `probes` — sonde di temperatura/umidità (R/O)
87
+ - `outputs` — uscite analogiche ventilatori/compressore/serranda in % (R/O)
88
+ - `alarms` — bitmask allarmi grezze + allarme cumulativo (R/O)
89
+ - `digital_io` — bitmask ingressi/uscite digitali (R/O)
90
+ - `status` — stato dettagliato unità/ventilatori/compressore/serranda (R/O)
91
+ - `maintenance` — ore di funzionamento ventilatore mandata (R/O)
92
+ - `enables` — flag di abilitazione controllo da BMS/Modbus (R/W, **obbligatori**)
93
+ - `command` — accensione, priorità cambio modalità, modalità estate/inverno,
94
+ reset allarmi manuali (R/W)
95
+ - `setpoints` — setpoint estate/inverno/umidità/free-cooling (R/W)
96
+ - `fan_speeds` — velocità minime/massime ventilatori per ogni modalità (R/W)
97
+ - `time_bands` — programmazione settimanale a fasce orarie (R/O), leggibile
98
+ con `time_band(device.time_bands, "mon", 1)` → `(tipo, secondi dalla mezzanotte)`
99
+ - `time_band_setpoints` — setpoint Comfort, offset Economy/Night e velocità
100
+ ventilatori per fascia (R/W)
101
+ - `calibrations` — calibrazione (offset) delle sonde M80-M83, M86 (R/W)
102
+
103
+ Per modificare l'orario di inizio di una fascia usare
104
+ `await async_write_time_band_start(device.time_bands, "tue", 3, 18 * 3600)`:
105
+ la scheda applica le due word dell'orario separatamente e rifiuta (Modbus
106
+ exception 0x04) i valori intermedi oltre 23:59:59, quindi le word vanno
107
+ scritte una alla volta nell'ordine giusto.
108
+
109
+ Inoltre `status.active_time_band` (fascia attiva, valori di `TimeBandCode`),
110
+ `status.board_clock` (orologio della scheda, R/W) ed `enables.time_bands_enabled`
111
+ (PH03, abilitazione fasce orarie), `setpoints.dehum_differential` (PU07,
112
+ differenziale deumidifica).
113
+
114
+ ## Registri non documentati da Sinergia
115
+
116
+ Fasce orarie, contatori ore a 32 bit (`maintenance`) e orologio della scheda
117
+ non compaiono nel manuale Sinergia: sono stati ricavati da una scansione
118
+ completa dei registri confrontata con il manuale applicativo EVCO
119
+ *c-pro 3 OEM DE* (144CP3ODI104), in cui l'ordine dei parametri coincide con
120
+ quello dei registri.
121
+
122
+ - Contatori ore: valori a 32 bit (word bassa per prima) **già in ore**; il
123
+ pannello li mostra in "ore x10" (906.8 sul display = 9068 h).
124
+ - Orologio e date: secondi dal 2000-01-01, ora locale della scheda (non
125
+ segue da solo il cambio ora legale/solare).
126
+ - Lo stato di verifica sul campo di ogni registro non documentato è nel README
127
+ dell'integrazione: [Stato verifica registri non documentati](https://github.com/riccardorossi92/ha-sinergia-vmc#stato-verifica-registri-non-documentati).
128
+
129
+ Registri del gruppo "COMANDI TEST" (PT01-PT11) sono volutamente esclusi
130
+ (vedi nota sopra); possono essere aggiunti in futuro dopo verifica sul campo.
@@ -0,0 +1,118 @@
1
+ # sinergia-vmc-modbus
2
+
3
+ > La stessa mappa dei registri è usata (come copia interna) dall'integrazione
4
+ > Home Assistant [ha-sinergia-vmc](https://github.com/riccardorossi92/ha-sinergia-vmc).
5
+
6
+ Device library (standalone, non legata a Home Assistant) per le unità VMC
7
+ Sinergia con elettronica **CPRO3OEM-K** (HRD/HRD+), basata sul documento
8
+ ufficiale *"Manuale di collegamento e impostazioni MODBUS HRD/HRD+"*
9
+ (Sinergia, r.03_10-2023 / r.05) e verificata sul campo su un'unità HRD 30/15 H K DC
10
+ con elettronica CPRO3OEM-K e pannello di comando a muro EVJD920N2VW.
11
+
12
+ Costruita sopra [`modbus-connection`](https://pypi.org/project/modbus-connection/),
13
+ seguendo il pattern "device library" descritto in
14
+ https://developers.home-assistant.io/blog/2026/07/05/modernizing-modbus/
15
+ (vedi [darkrain-nl/sofar-modbus](https://github.com/darkrain-nl/sofar-modbus)
16
+ come riferimento).
17
+
18
+ ## Collegamento fisico
19
+
20
+ RS-485 (A+/B-/GND) sui morsetti dedicati della scheda. Impostazioni seriali
21
+ di fabbrica: **9600 baud, 8 bit, parità NESSUNA, 1 stop bit** (dichiarate
22
+ esplicitamente dal manuale Sinergia; la tabella parametri H11-H14 dello
23
+ stesso documento riporta invece un default "parità Pari" per H13 — le due
24
+ sezioni sono in contraddizione. Se la connessione non risponde, riprova con
25
+ parità Even).
26
+
27
+ ## Indirizzamento registri — nota importante
28
+
29
+ Il manuale Sinergia elenca "Addr HEX" e "Addr DEC" per ogni registro, ma
30
+ `Addr DEC = Addr_HEX_convertito + 1` in tutti i gruppi tranne "COMANDI TEST"
31
+ (dove sembrano coincidere — probabile refuso, per questo quel gruppo non è
32
+ incluso qui). Gli indirizzi usati in questa libreria sono già quelli reali
33
+ da chiamare via Modbus (`Addr_HEX` / `Addr_DEC - 1`), verificati incrociando
34
+ 12+ registri con la scansione reale RS-485 fatta sul campo da un altro
35
+ utente della stessa VMC.
36
+
37
+ ## Installazione
38
+
39
+ ```bash
40
+ pip install sinergia-vmc-modbus
41
+ ```
42
+
43
+ ## Uso
44
+
45
+ ```python
46
+ import asyncio
47
+ from modbus_connection.pymodbus import connect_serial
48
+ from sinergia_vmc_modbus import VmcDevice
49
+
50
+ async def main():
51
+ connection = await connect_serial(
52
+ "/dev/ttyUSB0", baudrate=9600, parity="N", stopbits=1, timeout=3
53
+ )
54
+ unit = connection.for_unit(1) # indirizzo Modbus della scheda (H11, default 1)
55
+ device = VmcDevice(unit)
56
+
57
+ # Abilitazioni obbligatorie (H02/H27/H28) - vedi note nel modulo device.py:
58
+ # senza queste la macchina IGNORA le scritture sui registri corrispondenti.
59
+ await device.enables.write("onoff_by_bms", True)
60
+
61
+ await device.async_poll(device.COMPONENT_NAMES)
62
+ print("Temperatura esterna:", device.probes.temp_outdoor, "°C")
63
+ print("Stato unità:", device.status.unit_status)
64
+
65
+ await device.command.write("onoff_by_supervisor", True) # accende la VMC
66
+
67
+ await connection.close()
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ ## Struttura
73
+
74
+ - `probes` — sonde di temperatura/umidità (R/O)
75
+ - `outputs` — uscite analogiche ventilatori/compressore/serranda in % (R/O)
76
+ - `alarms` — bitmask allarmi grezze + allarme cumulativo (R/O)
77
+ - `digital_io` — bitmask ingressi/uscite digitali (R/O)
78
+ - `status` — stato dettagliato unità/ventilatori/compressore/serranda (R/O)
79
+ - `maintenance` — ore di funzionamento ventilatore mandata (R/O)
80
+ - `enables` — flag di abilitazione controllo da BMS/Modbus (R/W, **obbligatori**)
81
+ - `command` — accensione, priorità cambio modalità, modalità estate/inverno,
82
+ reset allarmi manuali (R/W)
83
+ - `setpoints` — setpoint estate/inverno/umidità/free-cooling (R/W)
84
+ - `fan_speeds` — velocità minime/massime ventilatori per ogni modalità (R/W)
85
+ - `time_bands` — programmazione settimanale a fasce orarie (R/O), leggibile
86
+ con `time_band(device.time_bands, "mon", 1)` → `(tipo, secondi dalla mezzanotte)`
87
+ - `time_band_setpoints` — setpoint Comfort, offset Economy/Night e velocità
88
+ ventilatori per fascia (R/W)
89
+ - `calibrations` — calibrazione (offset) delle sonde M80-M83, M86 (R/W)
90
+
91
+ Per modificare l'orario di inizio di una fascia usare
92
+ `await async_write_time_band_start(device.time_bands, "tue", 3, 18 * 3600)`:
93
+ la scheda applica le due word dell'orario separatamente e rifiuta (Modbus
94
+ exception 0x04) i valori intermedi oltre 23:59:59, quindi le word vanno
95
+ scritte una alla volta nell'ordine giusto.
96
+
97
+ Inoltre `status.active_time_band` (fascia attiva, valori di `TimeBandCode`),
98
+ `status.board_clock` (orologio della scheda, R/W) ed `enables.time_bands_enabled`
99
+ (PH03, abilitazione fasce orarie), `setpoints.dehum_differential` (PU07,
100
+ differenziale deumidifica).
101
+
102
+ ## Registri non documentati da Sinergia
103
+
104
+ Fasce orarie, contatori ore a 32 bit (`maintenance`) e orologio della scheda
105
+ non compaiono nel manuale Sinergia: sono stati ricavati da una scansione
106
+ completa dei registri confrontata con il manuale applicativo EVCO
107
+ *c-pro 3 OEM DE* (144CP3ODI104), in cui l'ordine dei parametri coincide con
108
+ quello dei registri.
109
+
110
+ - Contatori ore: valori a 32 bit (word bassa per prima) **già in ore**; il
111
+ pannello li mostra in "ore x10" (906.8 sul display = 9068 h).
112
+ - Orologio e date: secondi dal 2000-01-01, ora locale della scheda (non
113
+ segue da solo il cambio ora legale/solare).
114
+ - Lo stato di verifica sul campo di ogni registro non documentato è nel README
115
+ dell'integrazione: [Stato verifica registri non documentati](https://github.com/riccardorossi92/ha-sinergia-vmc#stato-verifica-registri-non-documentati).
116
+
117
+ Registri del gruppo "COMANDI TEST" (PT01-PT11) sono volutamente esclusi
118
+ (vedi nota sopra); possono essere aggiunti in futuro dopo verifica sul campo.
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sinergia-vmc-modbus"
7
+ version = "0.3.0"
8
+ description = "Device library (register map) for Sinergia VMC units with CPRO3OEM-K electronics, over Modbus RTU"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ dependencies = [
14
+ "modbus-connection>=4.12",
15
+ ]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/riccardorossi92/sinergia-vmc-modbus"
19
+
20
+ [tool.setuptools.packages.find]
21
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,59 @@
1
+ """Device library for Sinergia VMC units (CPRO3OEM-K electronics) over Modbus RTU."""
2
+
3
+ from .device import (
4
+ TIME_BAND_DAYS,
5
+ TIME_BANDS_PER_DAY,
6
+ Alarms,
7
+ Calibrations,
8
+ Command,
9
+ CompressorStatusCode,
10
+ DamperStatusCode,
11
+ DigitalIO,
12
+ Enables,
13
+ FanSpeeds,
14
+ FanStatusCode,
15
+ Maintenance,
16
+ ModeStatusCode,
17
+ Outputs,
18
+ Probes,
19
+ RecircDamperStatusCode,
20
+ Setpoints,
21
+ Status,
22
+ TimeBandCode,
23
+ TimeBandSetpoints,
24
+ TimeBands,
25
+ UnitStatusCode,
26
+ VmcDevice,
27
+ async_write_time_band_start,
28
+ time_band,
29
+ )
30
+
31
+ __all__ = [
32
+ "TIME_BANDS_PER_DAY",
33
+ "TIME_BAND_DAYS",
34
+ "Alarms",
35
+ "Calibrations",
36
+ "Command",
37
+ "CompressorStatusCode",
38
+ "DamperStatusCode",
39
+ "DigitalIO",
40
+ "Enables",
41
+ "FanSpeeds",
42
+ "FanStatusCode",
43
+ "Maintenance",
44
+ "ModeStatusCode",
45
+ "Outputs",
46
+ "Probes",
47
+ "RecircDamperStatusCode",
48
+ "Setpoints",
49
+ "Status",
50
+ "TimeBandCode",
51
+ "TimeBandSetpoints",
52
+ "TimeBands",
53
+ "UnitStatusCode",
54
+ "VmcDevice",
55
+ "async_write_time_band_start",
56
+ "time_band",
57
+ ]
58
+
59
+ __version__ = "0.3.0"
@@ -0,0 +1,575 @@
1
+ """Register map for Sinergia VMC units (CPRO3OEM-K electronics, HRD/HRD+).
2
+
3
+ Source: "Manuale di collegamento e impostazioni MODBUS HRD/HRD+" (Sinergia,
4
+ r.03_10-2023 / r.05), cross-checked against a real RS-485 register scan.
5
+ Time bands, hour counters and the board clock are NOT documented by Sinergia:
6
+ they were deduced from a full register scan matched against the EVCO
7
+ "c-pro 3 OEM DE" application manual (144CP3ODI104).
8
+
9
+ IMPORTANT addressing note: the manual's "Addr DEC" column is always
10
+ Addr_HEX + 1 (except in the "COMANDI TEST" group, which is excluded here -
11
+ its own table looks inconsistent with every other group and needs field
12
+ verification before use). Every address below is the REAL address to call
13
+ over Modbus (Addr_HEX / Addr_DEC - 1).
14
+
15
+ IMPORTANT enable note: per the manual, H02/H27/H28 (see `Enables` below)
16
+ MUST be set to 1 for the corresponding writes (on/off, integration,
17
+ dehumidify) to have any effect at all. Without them the unit silently
18
+ ignores the write - no error, nothing happens.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from enum import IntEnum
24
+
25
+ from modbus_connection.model import Component, Device
26
+ from modbus_connection.model.fields import boolean, enum, gauge, integer, uint32
27
+
28
+ __all__ = [
29
+ "Alarms",
30
+ "Calibrations",
31
+ "TimeBandCode",
32
+ "TimeBandSetpoints",
33
+ "TimeBands",
34
+ "Command",
35
+ "DigitalIO",
36
+ "Enables",
37
+ "FanSpeeds",
38
+ "Maintenance",
39
+ "Outputs",
40
+ "Probes",
41
+ "Setpoints",
42
+ "UnitStatusCode",
43
+ "Status",
44
+ "VmcDevice",
45
+ ]
46
+
47
+
48
+ # --------------------------------------------------------------------------- #
49
+ # Enums for the more useful status registers
50
+ # --------------------------------------------------------------------------- #
51
+
52
+
53
+ class UnitStatusCode(IntEnum):
54
+ OFF = 0
55
+ OFF_DI = 1
56
+ OFF_BMS = 2
57
+ OFF_SCHEDULER = 3
58
+ OFF_RTC = 4
59
+ ON = 5
60
+
61
+
62
+ class ModeStatusCode(IntEnum):
63
+ SUMMER_MANUAL = 0
64
+ WINTER_MANUAL = 1
65
+ SUMMER_AUTO = 2
66
+ WINTER_AUTO = 3
67
+ SUMMER_DI = 4
68
+ WINTER_DI = 5
69
+
70
+
71
+ class FanStatusCode(IntEnum):
72
+ DISABLE = 0
73
+ OFF = 1
74
+ WAIT_ON = 2
75
+ ON = 3
76
+ WAIT_OFF = 4
77
+ ALARM = 5
78
+
79
+
80
+ class CompressorStatusCode(IntEnum):
81
+ DISABLE = 0
82
+ ALARM = 1
83
+ MANUAL = 2
84
+ WAIT_ON = 3
85
+ ON = 4
86
+ WAIT_OFF = 5
87
+ OFF = 6
88
+
89
+
90
+ class DamperStatusCode(IntEnum):
91
+ DISABLE = 0
92
+ OFF = 1
93
+ WAIT_OFF = 2
94
+ ON = 3
95
+
96
+
97
+ class TimeBandCode(IntEnum):
98
+ """Tipo di fascia oraria (EVCO c-pro 3 OEM, menù "tb")."""
99
+
100
+ DISABLED = 0
101
+ OFF = 1
102
+ COMFORT = 2
103
+ ECONOMY = 3
104
+ NIGHT = 4
105
+
106
+
107
+ class RecircDamperStatusCode(IntEnum):
108
+ OFF = 0
109
+ ON = 1
110
+ DISABLE = 2
111
+
112
+
113
+ # --------------------------------------------------------------------------- #
114
+ # Read-only components
115
+ # --------------------------------------------------------------------------- #
116
+
117
+
118
+ class Probes(Component):
119
+ """Sonde di temperatura/umidità interne all'unità (R/O)."""
120
+
121
+ register_space = "holding"
122
+
123
+ temp_return_room = gauge(499, 0.1, unit="°C")
124
+ """Sonda temperatura ambiente/ripresa sul pannello a muro.
125
+ Non leggibile se il pannello remoto non è presente/abilitato."""
126
+
127
+ temp_outdoor = gauge(500, 0.1, unit="°C")
128
+ temp_water = gauge(501, 0.1, unit="°C")
129
+ temp_exhaust = gauge(502, 0.1, unit="°C")
130
+ temp_free_cooling = gauge(510, 0.1, unit="°C")
131
+
132
+ temp_evaporation = gauge(511, 0.1, unit="°C")
133
+ """Solo versione Inverter con compressore."""
134
+
135
+ humidity_return_room = integer(505, signed=False, unit="%")
136
+ """Sonda umidità ambiente/ripresa sul pannello a muro.
137
+ Non leggibile se il pannello remoto non è presente/abilitato."""
138
+
139
+
140
+ class Outputs(Component):
141
+ """Uscite analogiche ventilatori/compressore/serranda, in % (R/O)."""
142
+
143
+ register_space = "holding"
144
+
145
+ supply_fan_pct = gauge(639, 0.01, signed=False, unit="%")
146
+ return_fan_pct = gauge(640, 0.01, signed=False, unit="%")
147
+
148
+ compressor_pct = gauge(641, 0.01, signed=False, unit="%")
149
+ """Solo versione Inverter."""
150
+
151
+ external_damper_pct = gauge(642, 0.01, signed=False, unit="%")
152
+
153
+
154
+ class Alarms(Component):
155
+ """Allarmi: bitmask grezze + registro cumulativo (R/O)."""
156
+
157
+ register_space = "holding"
158
+
159
+ # bit00=AL01 ... bit15=AL16
160
+ packed_1 = integer(768, signed=False)
161
+ # bit00=AL17 ... bit15=AL32
162
+ packed_2 = integer(769, signed=False)
163
+ # bit00=AL33, bit01=AL34, bit02=AL35, bit03=riservato, bit04=AL37,
164
+ # bit05=AL38, bit06=AL39
165
+ packed_3 = integer(770, signed=False)
166
+
167
+ active = boolean(1103)
168
+ """Allarme cumulativo attivo (OR di tutti gli allarmi)."""
169
+
170
+
171
+ class DigitalIO(Component):
172
+ """Ingressi/uscite digitali, come bitmask grezze (R/O)."""
173
+
174
+ register_space = "holding"
175
+
176
+ # bit0-1=Remote on/off, bit2-3=Summer/Winter, bit4-5=Dehum request,
177
+ # bit6-7=Thermo request
178
+ logic_di1 = integer(257, signed=False)
179
+
180
+ # bit0-1=Supply fan, bit2-3=Return fan, bit4-5=Compressor,
181
+ # bit6-7=External damper, bit8-9=Recirculation damper,
182
+ # bit10-11=Water flow valve, bit12-13=Water condensation valve,
183
+ # bit14-15=Air condensation valve
184
+ do1 = integer(384, signed=False)
185
+
186
+ # bit2-3=Bypass Free-Cooling, bit4-5=On/Off, bit6-7=Summer/Winter,
187
+ # bit8-9=Serious alarm, bit10-11=Light alarm, bit12-13=High humidity
188
+ do2 = integer(385, signed=False)
189
+
190
+
191
+ class Status(Component):
192
+ """Stato dettagliato dell'unità al momento dell'interrogazione (R/O)."""
193
+
194
+ register_space = "holding"
195
+
196
+ unit_status = enum(1104, UnitStatusCode)
197
+ mode_status = enum(1110, ModeStatusCode)
198
+ actual_setpoint = gauge(1111, 0.1, unit="°C")
199
+
200
+ supply_fan_status = enum(1119, FanStatusCode)
201
+ return_fan_status = enum(1120, FanStatusCode)
202
+ dehum_request = boolean(1121)
203
+ compressor_status = enum(1122, CompressorStatusCode)
204
+
205
+ external_damper_modulating_pct = gauge(1127, 0.01, signed=False, unit="%")
206
+ external_damper_status = enum(1128, DamperStatusCode)
207
+
208
+ freecooling_heating_request = boolean(1132)
209
+ recirc_damper_status = enum(1134, RecircDamperStatusCode)
210
+
211
+ board_clock = uint32(1101, word_order="little", writable=True)
212
+ """Orologio (RTC) della scheda, in secondi dal 2000-01-01, ora locale.
213
+ NON documentato da Sinergia: dedotto da scansione. Scrivibile (da
214
+ verificare sul campo): usato per sincronizzarlo con Home Assistant."""
215
+
216
+ active_time_band = integer(1106, signed=False)
217
+ """Fascia oraria attiva (valori di `TimeBandCode`). NON documentato da
218
+ Sinergia: dedotto da scansione (vale 2=Comfort durante la fascia Comfort),
219
+ da confermare a un cambio di fascia."""
220
+
221
+
222
+ class Maintenance(Component):
223
+ """Contatori ore di funzionamento e manutenzione (EVCO PM00-PM04, PM90).
224
+
225
+ Valori a 32 bit (word bassa all'indirizzo indicato, word alta al
226
+ successivo), espressi direttamente in ore: il pannello li mostra in
227
+ "ore x10" (es. 906.8 sul display = 9068 h, cfr. esempio del manuale
228
+ Sinergia "450 = 4500 h").
229
+ """
230
+
231
+ register_space = "holding"
232
+
233
+ supply_fan_hours = uint32(1604, word_order="little", unit="h")
234
+ """PM01 - ore di funzionamento ventilatore mandata."""
235
+
236
+ return_fan_hours = uint32(1606, word_order="little", unit="h")
237
+ """PM02 - ore di funzionamento ventilatore ripresa."""
238
+
239
+ compressor_hours_limit = uint32(1608, word_order="little", writable=True, unit="h")
240
+ """PM03 - limite ore compressore oltre il quale scatta l'allarme."""
241
+
242
+ compressor_hours = uint32(1610, word_order="little", unit="h")
243
+ """PM04 - ore di funzionamento compressore."""
244
+
245
+ last_maintenance = uint32(1612, word_order="little")
246
+ """PM90 - data ultima manutenzione, in secondi dal 2000-01-01 (ora locale
247
+ della scheda). 2008-01-01 = mai impostata."""
248
+
249
+
250
+ # --------------------------------------------------------------------------- #
251
+ # Writable components
252
+ # --------------------------------------------------------------------------- #
253
+
254
+
255
+ class Enables(Component):
256
+ """Flag di abilitazione OBBLIGATORI per il controllo via Modbus/BMS (R/W).
257
+
258
+ Dal manuale: "Parametri da impostare obbligatoriamente per rendere
259
+ funzionanti le impostazioni tramite MODBUS, se non viene fatto l'unità
260
+ non terrà conto dei registri chiamati."
261
+ """
262
+
263
+ register_space = "holding"
264
+
265
+ onoff_by_bms = boolean(1778, writable=True)
266
+ """H02 - deve essere True per poter controllare l'accensione/spegnimento
267
+ scrivendo su Command.onoff_by_supervisor."""
268
+
269
+ integ_by_bms = boolean(1869, writable=True)
270
+ """H27 - deve essere True per controllare l'integrazione di calore."""
271
+
272
+ dehum_by_bms = boolean(1870, writable=True)
273
+ """H28 - deve essere True per controllare la deumidifica."""
274
+
275
+ time_bands_enabled = boolean(1779, writable=True)
276
+ """PH03 - abilita la regolazione a fasce orarie. NON documentato da
277
+ Sinergia: dedotto da scansione + manuale EVCO (segue H01/H02 nel menù
278
+ "Varie", e i registri successivi coincidono con PH04-PH10)."""
279
+
280
+ onoff_by_keyboard = boolean(1777, writable=True)
281
+ """H01 - abilita accensione/spegnimento da tastiera locale (default True,
282
+ di solito non serve toccarlo)."""
283
+
284
+
285
+ class Command(Component):
286
+ """Comandi operativi e reset allarmi manuali (R/W)."""
287
+
288
+ register_space = "holding"
289
+
290
+ onoff_by_supervisor = boolean(1105, writable=True)
291
+ """Accende/spegne l'unità. Richiede Enables.onoff_by_bms = True."""
292
+
293
+ integ_request_by_bms = boolean(1140, writable=True)
294
+ """Richiesta integrazione di calore. Richiede Enables.integ_by_bms = True."""
295
+
296
+ dehum_request_by_bms = boolean(1141, writable=True)
297
+ """Richiesta deumidifica. Richiede Enables.dehum_by_bms = True."""
298
+
299
+ summer_winter_mode = boolean(1583, writable=True)
300
+ """MOdE - Modalità operativa: False=Estate, True=Inverno."""
301
+
302
+ priority_display_mode = boolean(1878, writable=True)
303
+ """C11 - Priorità cambio modalità: False=Display/BMS, True=DI (ingresso
304
+ digitale). Va impostato se il comando remoto (pannello) è presente e si
305
+ vuole gestire il cambio stagione da contatto digitale/supervisione."""
306
+
307
+ reset_alarm_al02 = boolean(780, writable=True)
308
+ """Reset manuale allarme AL02 (alta umidità) - scrittura a impulso."""
309
+
310
+ reset_alarm_al12 = boolean(786, writable=True)
311
+ """Reset manuale allarme AL12 (alta pressione compressore) - impulso."""
312
+
313
+ fans_hours_limit = uint32(1602, word_order="little", writable=True, unit="h")
314
+ """M00/PM00 - limite ore di lavoro ventilatori oltre il quale scatta
315
+ l'allarme filtri sporchi (32 bit, in ore; default 20000 h = "2000.0"
316
+ sul pannello)."""
317
+
318
+
319
+ class Setpoints(Component):
320
+ """Setpoint di temperatura/umidità (R/W).
321
+
322
+ Utilizzabili solo in presenza del comando remoto, perché le sonde sono
323
+ al suo interno.
324
+ """
325
+
326
+ register_space = "holding"
327
+
328
+ summer = gauge(1584, 0.1, writable=True, unit="°C")
329
+ """SEtC - default 24.0°C, range -15.0..158.0."""
330
+
331
+ winter = gauge(1585, 0.1, writable=True, unit="°C")
332
+ """SEtH - default 20.0°C, range -15.0..158.0."""
333
+
334
+ humidity = integer(1586, signed=False, writable=True, unit="%")
335
+ """PU01 - default 55%, range 0..100."""
336
+
337
+ dehum_differential = integer(1694, signed=False, writable=True, unit="%")
338
+ """PU07 - differenziale deumidifica, default 10%, range 0..100. Con
339
+ compressore on/off la deumidifica parte sopra PU01 + PU07 e si ferma
340
+ sotto PU01. NON documentato da Sinergia: dedotto dal manuale EVCO c-pro 3
341
+ OEM DE (ordine parametri PU02..PS07, ancorato a S06=1703/S07=1704 e
342
+ A19=1735) e da scansione (vale 10 = default)."""
343
+
344
+ freecooling_heating = gauge(1703, 0.1, writable=True, unit="°C")
345
+ """S06 - default 4.0°C, range 0.0..68.0."""
346
+
347
+ freecooling_heating_diff = gauge(1704, 0.1, writable=True, unit="°C")
348
+ """S07 - default 2.0°C, range 0.0..36.0."""
349
+
350
+ high_humidity_warning = integer(1735, signed=False, writable=True, unit="%")
351
+ """A19 - default 100% (=disabilitato se non presente serranda aria
352
+ esterna), range 0..100."""
353
+
354
+ summer_commutation_water = gauge(1633, 0.1, writable=True, unit="°C")
355
+ """C07 - default 30.0°C, range 0.0..158.0."""
356
+
357
+ winter_commutation_water = gauge(1634, 0.1, writable=True, unit="°C")
358
+ """C08 - default 20.0°C, range 0.0..158.0."""
359
+
360
+ max_time_dehum_by_di_warning = integer(1877, signed=False, writable=True, unit="min")
361
+ """PA58 - default 0, range 0..999."""
362
+
363
+
364
+ class FanSpeeds(Component):
365
+ """Velocità minime/massime ventilatori per modalità, in % (R/W).
366
+
367
+ ATTENZIONE dal manuale: i valori vanno scritti/letti con due decimali
368
+ (es. 40% <-> raw 4000): gestito automaticamente dallo scale=0.01 qui
369
+ sotto, quindi si legge/scrive direttamente in %.
370
+ """
371
+
372
+ register_space = "holding"
373
+
374
+ min_supply_vmc_mode = gauge(1644, 0.01, signed=False, writable=True, unit="%")
375
+ """F07 - default 30%, mandata in modalità VMC."""
376
+
377
+ max_supply_vmc_mode = gauge(1645, 0.01, signed=False, writable=True, unit="%")
378
+ """F08 - default 60%, mandata in modalità VMC."""
379
+
380
+ min_supply_integration = gauge(1852, 0.01, signed=False, writable=True, unit="%")
381
+ """F27 - default 50%, mandata in integrazione."""
382
+
383
+ max_supply_integration = gauge(1646, 0.01, signed=False, writable=True, unit="%")
384
+ """F09 - default 85%, mandata in integrazione."""
385
+
386
+ min_supply_dehum = gauge(1853, 0.01, signed=False, writable=True, unit="%")
387
+ """F28 - default 50%, mandata in deumidifica."""
388
+
389
+ max_supply_dehum = gauge(1647, 0.01, signed=False, writable=True, unit="%")
390
+ """F10 - default 85%, mandata in deumidifica."""
391
+
392
+ min_return = gauge(1854, 0.01, signed=False, writable=True, unit="%")
393
+ """F29 - default 40%, ripresa in qualsiasi modalità."""
394
+
395
+ max_return = gauge(1855, 0.01, signed=False, writable=True, unit="%")
396
+ """F30 - default 75%, ripresa in qualsiasi modalità."""
397
+
398
+
399
+ # --------------------------------------------------------------------------- #
400
+ # Fasce orarie (NON documentate da Sinergia)
401
+ #
402
+ # Ricavate incrociando una scansione completa dei registri con il manuale
403
+ # EVCO "c-pro 3 OEM DE - Manuale applicativo" (144CP3ODI104): l'ordine dei
404
+ # parametri nelle tabelle EVCO coincide con quello dei registri.
405
+ # --------------------------------------------------------------------------- #
406
+
407
+ TIME_BAND_DAYS: tuple[str, ...] = ("mon", "tue", "wed", "thu", "fri", "sat", "sun")
408
+ TIME_BANDS_PER_DAY = 4
409
+ _TIME_BANDS_BASE = 1499
410
+
411
+
412
+ def _time_band_fields() -> dict:
413
+ """Tabella fasce: 7 giorni x 4 fasce, 3 registri per fascia.
414
+
415
+ Per ogni fascia: tipo (`TimeBandCode`), poi orario di inizio in secondi
416
+ dalla mezzanotte su 32 bit (word bassa, word alta).
417
+ """
418
+ fields: dict = {
419
+ "__doc__": "Programmazione a fasce orarie e vacanza (R/W).",
420
+ "register_space": "holding",
421
+ "vacation_days": integer(1497, signed=False, unit="d"),
422
+ "vacation_hours": integer(1498, signed=False, unit="h"),
423
+ }
424
+ for d, day in enumerate(TIME_BAND_DAYS):
425
+ for b in range(TIME_BANDS_PER_DAY):
426
+ addr = _TIME_BANDS_BASE + 12 * d + 3 * b
427
+ fields[f"{day}_{b + 1}_type"] = integer(addr, signed=False, writable=True)
428
+ fields[f"{day}_{b + 1}_start"] = uint32(
429
+ addr + 1, word_order="little", writable=True, unit="s"
430
+ )
431
+ return fields
432
+
433
+
434
+ # 58 campi generati: più leggibile che scriverli uno per uno.
435
+ TimeBands = type("TimeBands", (Component,), _time_band_fields())
436
+
437
+
438
+ def time_band(bands: TimeBands, day: str, index: int) -> tuple[int | None, int | None]:
439
+ """Ritorna (tipo, secondi dalla mezzanotte) della fascia `index` (1-4)."""
440
+ return getattr(bands, f"{day}_{index}_type"), getattr(bands, f"{day}_{index}_start")
441
+
442
+
443
+ async def async_write_time_band_start(
444
+ bands: TimeBands, day: str, index: int, seconds: int
445
+ ) -> None:
446
+ """Scrive l'orario di inizio di una fascia, una word alla volta.
447
+
448
+ La scheda applica le due word separatamente e satura a 23:59:59 ogni
449
+ valore intermedio oltre le 24 ore: scrivendo 18:00 (hi=0) sopra 20:00
450
+ (hi=1) con la word bassa per prima, il valore intermedio hi=1/lo=64800
451
+ diventa 23:59:59 e il risultato finale è 05:47:43. Si scrive quindi per
452
+ prima la word alta quando scende e la word bassa quando sale, così ogni
453
+ valore intermedio resta valido.
454
+ """
455
+ address = _TIME_BANDS_BASE + 12 * TIME_BAND_DAYS.index(day) + 3 * (index - 1) + 1
456
+ old = getattr(bands, f"{day}_{index}_start") or 0
457
+ lo, hi = seconds & 0xFFFF, seconds >> 16
458
+ unit = bands.modbus_unit
459
+ if hi < old >> 16:
460
+ await unit.write_register(address + 1, hi)
461
+ await unit.write_register(address, lo)
462
+ else:
463
+ await unit.write_register(address, lo)
464
+ await unit.write_register(address + 1, hi)
465
+
466
+
467
+ class Calibrations(Component):
468
+ """Calibrazione (offset) delle sonde, EVCO PM80-PM86 (R/W).
469
+
470
+ NON documentato da Sinergia: verificato sul campo impostando M80/M86 dal
471
+ pannello (menù Manutentore -> CAL) e confrontando due scansioni.
472
+ """
473
+
474
+ register_space = "holding"
475
+
476
+ room_temperature = gauge(1616, 0.1, writable=True, unit="°C")
477
+ """M80 - sonda temperatura ambiente (pannello a muro), -10.0..10.0."""
478
+
479
+ outdoor_temperature = gauge(1617, 0.1, writable=True, unit="°C")
480
+ """M81 - sonda temperatura aria esterna, -10.0..10.0."""
481
+
482
+ water_temperature = gauge(1618, 0.1, writable=True, unit="°C")
483
+ """M82 - sonda temperatura acqua, -10.0..10.0."""
484
+
485
+ exhaust_temperature = gauge(1619, 0.1, writable=True, unit="°C")
486
+ """M83 - sonda temperatura aria espulsa, -10.0..10.0."""
487
+
488
+ room_humidity = integer(1622, writable=True, unit="%")
489
+ """M86 - sonda umidità ambiente (pannello a muro), -10..10."""
490
+
491
+
492
+ class TimeBandSetpoints(Component):
493
+ """Setpoint e velocità ventilatori per fascia oraria (R/W)."""
494
+
495
+ register_space = "holding"
496
+
497
+ comfort_summer = gauge(1587, 0.1, writable=True, unit="°C")
498
+ """SCC - setpoint freddo fascia comfort (default EVCO 24.0)."""
499
+
500
+ comfort_winter = gauge(1588, 0.1, writable=True, unit="°C")
501
+ """SCH - setpoint caldo fascia comfort (default EVCO 21.0)."""
502
+
503
+ economy_offset_summer = gauge(1589, 0.1, writable=True, unit="°C")
504
+ """OEC - offset freddo fascia economy (default +1.0)."""
505
+
506
+ economy_offset_winter = gauge(1590, 0.1, writable=True, unit="°C")
507
+ """OEH - offset caldo fascia economy (default -1.0)."""
508
+
509
+ night_offset_summer = gauge(1591, 0.1, writable=True, unit="°C")
510
+ """ONC - offset freddo fascia night (default +2.0)."""
511
+
512
+ night_offset_winter = gauge(1592, 0.1, writable=True, unit="°C")
513
+ """ONH - offset caldo fascia night (default -2.0)."""
514
+
515
+ supply_fan_comfort = gauge(1595, 0.01, signed=False, writable=True, unit="%")
516
+ """FSC - velocità ventilatore mandata fascia comfort."""
517
+
518
+ supply_fan_economy = gauge(1596, 0.01, signed=False, writable=True, unit="%")
519
+ """FSE - velocità ventilatore mandata fascia economy."""
520
+
521
+ supply_fan_night = gauge(1597, 0.01, signed=False, writable=True, unit="%")
522
+ """FSN - velocità ventilatore mandata fascia night."""
523
+
524
+ return_fan_comfort = gauge(1859, 0.01, signed=False, writable=True, unit="%")
525
+ """FRC - velocità ventilatore ripresa fascia comfort (meno certo dei
526
+ precedenti: non contiguo a FSC/FSE/FSN, dedotto dai valori)."""
527
+
528
+ return_fan_economy = gauge(1860, 0.01, signed=False, writable=True, unit="%")
529
+ """FRE - velocità ventilatore ripresa fascia economy (vedi FRC)."""
530
+
531
+ return_fan_night = gauge(1861, 0.01, signed=False, writable=True, unit="%")
532
+ """FRN - velocità ventilatore ripresa fascia night (vedi FRC)."""
533
+
534
+
535
+ # --------------------------------------------------------------------------- #
536
+ # Device
537
+ # --------------------------------------------------------------------------- #
538
+
539
+
540
+ class VmcDevice(Device):
541
+ """Unità VMC Sinergia (elettronica CPRO3OEM-K, HRD/HRD+) su Modbus RTU."""
542
+
543
+ #: Nomi dei componenti, nell'ordine consigliato di lettura (usare con
544
+ #: ``device.async_poll(device.COMPONENT_NAMES)``).
545
+ COMPONENT_NAMES: tuple[str, ...] = (
546
+ "probes",
547
+ "outputs",
548
+ "alarms",
549
+ "digital_io",
550
+ "status",
551
+ "maintenance",
552
+ "enables",
553
+ "command",
554
+ "setpoints",
555
+ "fan_speeds",
556
+ "time_bands",
557
+ "time_band_setpoints",
558
+ "calibrations",
559
+ )
560
+
561
+ def __init__(self, unit) -> None: # noqa: ANN001 - ModbusUnit from modbus_connection
562
+ super().__init__(unit)
563
+ self.probes = Probes(unit)
564
+ self.outputs = Outputs(unit)
565
+ self.alarms = Alarms(unit)
566
+ self.digital_io = DigitalIO(unit)
567
+ self.status = Status(unit)
568
+ self.maintenance = Maintenance(unit)
569
+ self.enables = Enables(unit)
570
+ self.command = Command(unit)
571
+ self.setpoints = Setpoints(unit)
572
+ self.fan_speeds = FanSpeeds(unit)
573
+ self.time_bands = TimeBands(unit)
574
+ self.time_band_setpoints = TimeBandSetpoints(unit)
575
+ self.calibrations = Calibrations(unit)
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: sinergia-vmc-modbus
3
+ Version: 0.3.0
4
+ Summary: Device library (register map) for Sinergia VMC units with CPRO3OEM-K electronics, over Modbus RTU
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/riccardorossi92/sinergia-vmc-modbus
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: modbus-connection>=4.12
11
+ Dynamic: license-file
12
+
13
+ # sinergia-vmc-modbus
14
+
15
+ > La stessa mappa dei registri è usata (come copia interna) dall'integrazione
16
+ > Home Assistant [ha-sinergia-vmc](https://github.com/riccardorossi92/ha-sinergia-vmc).
17
+
18
+ Device library (standalone, non legata a Home Assistant) per le unità VMC
19
+ Sinergia con elettronica **CPRO3OEM-K** (HRD/HRD+), basata sul documento
20
+ ufficiale *"Manuale di collegamento e impostazioni MODBUS HRD/HRD+"*
21
+ (Sinergia, r.03_10-2023 / r.05) e verificata sul campo su un'unità HRD 30/15 H K DC
22
+ con elettronica CPRO3OEM-K e pannello di comando a muro EVJD920N2VW.
23
+
24
+ Costruita sopra [`modbus-connection`](https://pypi.org/project/modbus-connection/),
25
+ seguendo il pattern "device library" descritto in
26
+ https://developers.home-assistant.io/blog/2026/07/05/modernizing-modbus/
27
+ (vedi [darkrain-nl/sofar-modbus](https://github.com/darkrain-nl/sofar-modbus)
28
+ come riferimento).
29
+
30
+ ## Collegamento fisico
31
+
32
+ RS-485 (A+/B-/GND) sui morsetti dedicati della scheda. Impostazioni seriali
33
+ di fabbrica: **9600 baud, 8 bit, parità NESSUNA, 1 stop bit** (dichiarate
34
+ esplicitamente dal manuale Sinergia; la tabella parametri H11-H14 dello
35
+ stesso documento riporta invece un default "parità Pari" per H13 — le due
36
+ sezioni sono in contraddizione. Se la connessione non risponde, riprova con
37
+ parità Even).
38
+
39
+ ## Indirizzamento registri — nota importante
40
+
41
+ Il manuale Sinergia elenca "Addr HEX" e "Addr DEC" per ogni registro, ma
42
+ `Addr DEC = Addr_HEX_convertito + 1` in tutti i gruppi tranne "COMANDI TEST"
43
+ (dove sembrano coincidere — probabile refuso, per questo quel gruppo non è
44
+ incluso qui). Gli indirizzi usati in questa libreria sono già quelli reali
45
+ da chiamare via Modbus (`Addr_HEX` / `Addr_DEC - 1`), verificati incrociando
46
+ 12+ registri con la scansione reale RS-485 fatta sul campo da un altro
47
+ utente della stessa VMC.
48
+
49
+ ## Installazione
50
+
51
+ ```bash
52
+ pip install sinergia-vmc-modbus
53
+ ```
54
+
55
+ ## Uso
56
+
57
+ ```python
58
+ import asyncio
59
+ from modbus_connection.pymodbus import connect_serial
60
+ from sinergia_vmc_modbus import VmcDevice
61
+
62
+ async def main():
63
+ connection = await connect_serial(
64
+ "/dev/ttyUSB0", baudrate=9600, parity="N", stopbits=1, timeout=3
65
+ )
66
+ unit = connection.for_unit(1) # indirizzo Modbus della scheda (H11, default 1)
67
+ device = VmcDevice(unit)
68
+
69
+ # Abilitazioni obbligatorie (H02/H27/H28) - vedi note nel modulo device.py:
70
+ # senza queste la macchina IGNORA le scritture sui registri corrispondenti.
71
+ await device.enables.write("onoff_by_bms", True)
72
+
73
+ await device.async_poll(device.COMPONENT_NAMES)
74
+ print("Temperatura esterna:", device.probes.temp_outdoor, "°C")
75
+ print("Stato unità:", device.status.unit_status)
76
+
77
+ await device.command.write("onoff_by_supervisor", True) # accende la VMC
78
+
79
+ await connection.close()
80
+
81
+ asyncio.run(main())
82
+ ```
83
+
84
+ ## Struttura
85
+
86
+ - `probes` — sonde di temperatura/umidità (R/O)
87
+ - `outputs` — uscite analogiche ventilatori/compressore/serranda in % (R/O)
88
+ - `alarms` — bitmask allarmi grezze + allarme cumulativo (R/O)
89
+ - `digital_io` — bitmask ingressi/uscite digitali (R/O)
90
+ - `status` — stato dettagliato unità/ventilatori/compressore/serranda (R/O)
91
+ - `maintenance` — ore di funzionamento ventilatore mandata (R/O)
92
+ - `enables` — flag di abilitazione controllo da BMS/Modbus (R/W, **obbligatori**)
93
+ - `command` — accensione, priorità cambio modalità, modalità estate/inverno,
94
+ reset allarmi manuali (R/W)
95
+ - `setpoints` — setpoint estate/inverno/umidità/free-cooling (R/W)
96
+ - `fan_speeds` — velocità minime/massime ventilatori per ogni modalità (R/W)
97
+ - `time_bands` — programmazione settimanale a fasce orarie (R/O), leggibile
98
+ con `time_band(device.time_bands, "mon", 1)` → `(tipo, secondi dalla mezzanotte)`
99
+ - `time_band_setpoints` — setpoint Comfort, offset Economy/Night e velocità
100
+ ventilatori per fascia (R/W)
101
+ - `calibrations` — calibrazione (offset) delle sonde M80-M83, M86 (R/W)
102
+
103
+ Per modificare l'orario di inizio di una fascia usare
104
+ `await async_write_time_band_start(device.time_bands, "tue", 3, 18 * 3600)`:
105
+ la scheda applica le due word dell'orario separatamente e rifiuta (Modbus
106
+ exception 0x04) i valori intermedi oltre 23:59:59, quindi le word vanno
107
+ scritte una alla volta nell'ordine giusto.
108
+
109
+ Inoltre `status.active_time_band` (fascia attiva, valori di `TimeBandCode`),
110
+ `status.board_clock` (orologio della scheda, R/W) ed `enables.time_bands_enabled`
111
+ (PH03, abilitazione fasce orarie), `setpoints.dehum_differential` (PU07,
112
+ differenziale deumidifica).
113
+
114
+ ## Registri non documentati da Sinergia
115
+
116
+ Fasce orarie, contatori ore a 32 bit (`maintenance`) e orologio della scheda
117
+ non compaiono nel manuale Sinergia: sono stati ricavati da una scansione
118
+ completa dei registri confrontata con il manuale applicativo EVCO
119
+ *c-pro 3 OEM DE* (144CP3ODI104), in cui l'ordine dei parametri coincide con
120
+ quello dei registri.
121
+
122
+ - Contatori ore: valori a 32 bit (word bassa per prima) **già in ore**; il
123
+ pannello li mostra in "ore x10" (906.8 sul display = 9068 h).
124
+ - Orologio e date: secondi dal 2000-01-01, ora locale della scheda (non
125
+ segue da solo il cambio ora legale/solare).
126
+ - Lo stato di verifica sul campo di ogni registro non documentato è nel README
127
+ dell'integrazione: [Stato verifica registri non documentati](https://github.com/riccardorossi92/ha-sinergia-vmc#stato-verifica-registri-non-documentati).
128
+
129
+ Registri del gruppo "COMANDI TEST" (PT01-PT11) sono volutamente esclusi
130
+ (vedi nota sopra); possono essere aggiunti in futuro dopo verifica sul campo.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/sinergia_vmc_modbus/__init__.py
5
+ src/sinergia_vmc_modbus/device.py
6
+ src/sinergia_vmc_modbus.egg-info/PKG-INFO
7
+ src/sinergia_vmc_modbus.egg-info/SOURCES.txt
8
+ src/sinergia_vmc_modbus.egg-info/dependency_links.txt
9
+ src/sinergia_vmc_modbus.egg-info/requires.txt
10
+ src/sinergia_vmc_modbus.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ modbus-connection>=4.12
@@ -0,0 +1 @@
1
+ sinergia_vmc_modbus