conson-xp 1.50.1__py3-none-any.whl → 1.51.1__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conson-xp
3
- Version: 1.50.1
3
+ Version: 1.51.1
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -450,6 +450,7 @@ xp telegram version
450
450
 
451
451
 
452
452
  xp term
453
+ xp term homekit
453
454
  xp term protocol
454
455
  xp term state
455
456
 
@@ -1,8 +1,8 @@
1
- conson_xp-1.50.1.dist-info/METADATA,sha256=bCbYqnE9XZK73a4laFt68XxyWDTPC-MlmxJa99Y7JTc,11432
2
- conson_xp-1.50.1.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
- conson_xp-1.50.1.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-1.50.1.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=hEOsOLZ2VP2s9c4Fqe0rpmfu56_wnujnc7Wf3f0xDGI,182
1
+ conson_xp-1.51.1.dist-info/METADATA,sha256=15wTMgVInRD7kUjYvZe3AtZPgmPGG_XcXsaGRB3Vjds,11448
2
+ conson_xp-1.51.1.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
+ conson_xp-1.51.1.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
+ conson_xp-1.51.1.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
+ xp/__init__.py,sha256=PTmuTUb4TXiPuSSkKX3UT63IhH_fx4EpMP33cMsA6Nc,182
6
6
  xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
7
7
  xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
8
8
  xp/cli/commands/__init__.py,sha256=G7A1KFRSV0CEeDTqr_khu-K9_sc01CTI2KSfkFcaBRM,4949
@@ -16,7 +16,7 @@ xp/cli/commands/conbus/conbus_custom_commands.py,sha256=TqWo45yqEp_ezmKZ0tymQ0C4
16
16
  xp/cli/commands/conbus/conbus_datapoint_commands.py,sha256=uYsvi37vl2od2XJB0semAF0sH3UrRWTt_qHnBmUBR-w,3788
17
17
  xp/cli/commands/conbus/conbus_discover_commands.py,sha256=gSK5Y4lespNKFNgGtp7VkqStGe4fWxDNNfyPPuJiwXQ,1931
18
18
  xp/cli/commands/conbus/conbus_event_commands.py,sha256=faaSGRNtJCttk-0VO5Z4m4Zz37aOsSgmbuzWbTOUZIQ,3103
19
- xp/cli/commands/conbus/conbus_export_commands.py,sha256=epfoIC1C32IT7SMVxolUDm1xm2q7hz_kvs7Jct4W9FQ,5819
19
+ xp/cli/commands/conbus/conbus_export_commands.py,sha256=pWZlS0MMcc8sZAfXx8xMSn53NT5cg3HpF-msBOU7whI,5992
20
20
  xp/cli/commands/conbus/conbus_lightlevel_commands.py,sha256=WH4ZQHxSbquPlAqd0uRxq8VR-sO4_AnfseMqaoPoGoA,7092
21
21
  xp/cli/commands/conbus/conbus_linknumber_commands.py,sha256=3bkik8nHXY89XfzeUnKWmvKA70r4qJJ79j8FfLL4sL0,3556
22
22
  xp/cli/commands/conbus/conbus_modulenumber_commands.py,sha256=fOIO0f85iDJBfGnRCEN8zK6j4i43uuF_9YKQc_nQ39A,3628
@@ -43,7 +43,7 @@ xp/cli/commands/telegram/telegram_parse_commands.py,sha256=xCDRRFgj41RtStvwROfi-
43
43
  xp/cli/commands/telegram/telegram_version_commands.py,sha256=hAMjSAa7zfMNfNFln63sKeNPcmW89bISkcVs6BgsqOg,1558
44
44
  xp/cli/commands/term/__init__.py,sha256=1NNST_8YJfj5LCujQISwQflK6LyEn7mDmZpMpvI9d-o,116
45
45
  xp/cli/commands/term/term.py,sha256=gjvsv2OE-F_KNWQrWi04fXQ5cGo0l8P-Ortbb5KTA-A,309
46
- xp/cli/commands/term/term_commands.py,sha256=ypJo9GL0eNwllgJJux2agJkcco6k9HMM-5f3aTbiQ2c,1216
46
+ xp/cli/commands/term/term_commands.py,sha256=Z3pq_xzP0j5YfYFOwASaZUxXDkikSV_E4cFjMb69LUU,1796
47
47
  xp/cli/main.py,sha256=Wbtji5ddW3IEoAfecHrEPk8W_w1bGD20B-NqAWfI_F4,1968
48
48
  xp/cli/utils/__init__.py,sha256=gTGIj60Uai0iE7sr9_TtEpl04fD7krtTzbbigXUsUVU,46
49
49
  xp/cli/utils/click_tree.py,sha256=sr4l9RWTCnASkdvkJKnRRxWSQPlF1DbFdBNu9gL7Ekc,1693
@@ -84,7 +84,7 @@ xp/models/config/__init__.py,sha256=gEZnX9eE3DjFtLtF32riEjJQLypqQRbyPauBI4Cowbs,
84
84
  xp/models/config/conson_module_config.py,sha256=t1G0LnNNMnjs3ahhz4-Z_5SlEv2FCrcRq13OmvZ2pvA,3009
85
85
  xp/models/homekit/__init__.py,sha256=5HDSOClCu0ArK3IICn3_LDMMLBAzLjBxUUSF73bxSSk,34
86
86
  xp/models/homekit/homekit_accessory.py,sha256=ANjDWlFxeNTstl7lKdmf6vMOC0wc005vpiD6awRcptA,1052
87
- xp/models/homekit/homekit_config.py,sha256=OMq0eayAJ6NRr8PXANvQzgEYGW9RN_ycyEmnTlTlHrQ,2938
87
+ xp/models/homekit/homekit_config.py,sha256=EqoiZ1E6l9bBjxKqK1nxVGRfFY5ZtRHH-jZhYtRH2gU,3048
88
88
  xp/models/log_entry.py,sha256=tAiNwouCP2d4jKiHJY9a-2iAi8LWTpG-TZsOPDIstlA,4423
89
89
  xp/models/protocol/__init__.py,sha256=TJ_CJKchA-xgQiv5vCo_ndBBZjrcaTmjT74bR0T-5Cw,38
90
90
  xp/models/protocol/conbus_protocol.py,sha256=hF78N5xvBzMiyWoKd8i_avA8kJ1As_9Pplkw1GMqKzk,9145
@@ -105,7 +105,8 @@ xp/models/telegram/system_telegram.py,sha256=064AlFi_WghoYlVDUtIIbvwtZyUEZj7_auK
105
105
  xp/models/telegram/telegram.py,sha256=-kNloBlwMJ5w1-FAMSLzBPnyOGUNEBG3SG2d0eTi2PY,847
106
106
  xp/models/telegram/telegram_type.py,sha256=IjGEosbs7IDqYT7ktn-FcKS-kAJ4eXW-KJGkkoAGysw,428
107
107
  xp/models/telegram/timeparam_type.py,sha256=z5EQ32SQjDi7zKshtkvDzqaMfPMUeXCWKEGI5VgvBvU,1142
108
- xp/models/term/__init__.py,sha256=aFvzGZHr_dI6USb8MJuYLSLMvxi_ZWMVtokHDt8428s,263
108
+ xp/models/term/__init__.py,sha256=VVZsEyXBEr-TnBlrFFifZ6PjJHUl2kwnRUZx_kC2Ljg,343
109
+ xp/models/term/accessory_state.py,sha256=GcMtCxOeHpc3IPDO1F9j2I6rdhaNV75iQ2md--XY6jo,1650
109
110
  xp/models/term/connection_state.py,sha256=oYcst01uH35kO541jGuXMqvJ2iduiHYryUsMK0d89pQ,1807
110
111
  xp/models/term/module_state.py,sha256=i7u8y_B5ScMRULQb_kMSD_wwKzbrLHlkECsTgNS46PQ,939
111
112
  xp/models/term/protocol_keys_config.py,sha256=tSlkxEwgQuVRYLTaUNd569osQsNCdb9ED4InNgX9rKo,1223
@@ -123,7 +124,7 @@ xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=9JzG9dOrfamu8Ujs
123
124
  xp/services/actiontable/serializer_protocol.py,sha256=PMsZbPwPQD1MJYo_KpZSgpnVQCtXFXSfzXFpCiA6Xi8,2002
124
125
  xp/services/conbus/__init__.py,sha256=Hi35sMKu9o6LpYoi2tQDaQoMb8M5sOt_-LUTxxaCU_0,28
125
126
  xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAnODqpkJUGHHszk,40
126
- xp/services/conbus/actiontable/actiontable_download_service.py,sha256=ZTQCC1D9TLmyDu3b5GAO4VxUVGSr6HuqZI9fxHB1qW8,14992
127
+ xp/services/conbus/actiontable/actiontable_download_service.py,sha256=41Hr1753IBpUeHQqO57uS7qxOB0rJt8qCpznzKlUPOM,15028
127
128
  xp/services/conbus/actiontable/actiontable_list_service.py,sha256=oTDSpBkp-MJeaF5bhRnwkSy3na55xqQ4e2ykJzbMCUo,3236
128
129
  xp/services/conbus/actiontable/actiontable_show_service.py,sha256=WISY2VsmSlceGa5_9lpFO-gs5TnTjv6YidQksUjCapk,3058
129
130
  xp/services/conbus/actiontable/actiontable_upload_service.py,sha256=FaQzOSg8s2zUL5xz9qZY9fvzrdDosc3CoxkVDvNg2SU,13252
@@ -135,7 +136,7 @@ xp/services/conbus/conbus_datapoint_service.py,sha256=WBQC42-6xuPWhMKKRtHtRzwEmV
135
136
  xp/services/conbus/conbus_discover_service.py,sha256=mvqjHFMmEkQjHD9YDIk9gE8MowPMkOIJRmyjX96G5pw,12868
136
137
  xp/services/conbus/conbus_event_list_service.py,sha256=-jl3WHpyidbh-h4NMK2gERqu48mTNFD6rpPo2EyGxeg,3641
137
138
  xp/services/conbus/conbus_event_raw_service.py,sha256=viXuEXw165-RytdqC76wQShJLD7Yd0rtURxWZZ8hyKA,7060
138
- xp/services/conbus/conbus_export_actiontable_service.py,sha256=_2bxmDVSpkGtepuQVbdYX2nwEnSm91GX9P9UzB_INDQ,11043
139
+ xp/services/conbus/conbus_export_actiontable_service.py,sha256=d6Y2RDiVOH-jTMWOIE_gY_WBJnHFrbJGguk-WZWbNPs,11818
139
140
  xp/services/conbus/conbus_export_service.py,sha256=RP8nADTIs4FGUf_BFLRZMtEJZdXV94zg3QrlWaDnhKA,17536
140
141
  xp/services/conbus/conbus_output_service.py,sha256=e57bRkLgPnJuB8hkllNh0kgGkjPt9IK75tuBxd_bOkE,9361
141
142
  xp/services/conbus/conbus_raw_service.py,sha256=OQuV521VOQraf2PGF2B9868vh7sDgmfc19YebrkZnyw,5844
@@ -145,7 +146,7 @@ xp/services/conbus/write_config_service.py,sha256=BCfmLNPRDpwSwRMRYJvx2FXA8IZsdg
145
146
  xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
146
147
  xp/services/homekit/homekit_cache_service.py,sha256=z1TB6icEqd1paoilVTewuFL0lXVCQbvrOJkJvvQECJY,11060
147
148
  xp/services/homekit/homekit_conbus_service.py,sha256=XPKv7Mit1rn7XLaQZcKmlMMUlyj-o0J2z8XBH3NaEIM,3390
148
- xp/services/homekit/homekit_config_validator.py,sha256=bLDOY8dbXvqvrDLtn4PijZA38qNmQWcQFWIY6hjLrl0,10875
149
+ xp/services/homekit/homekit_config_validator.py,sha256=jf09jHIFbZg7YpDbGsGHT1p4a1vpUED2xR6iZN19cfM,10875
149
150
  xp/services/homekit/homekit_conson_validator.py,sha256=tmUxBzytX9FbUWTR1XdbAi_qb_whAdGPSaml98Czszg,3858
150
151
  xp/services/homekit/homekit_dimminglight.py,sha256=EzfGhy3zZkbFPfN72Dh_eSb5mJQOpxGi6ZwnyEOSHxU,5819
151
152
  xp/services/homekit/homekit_dimminglight_service.py,sha256=0Ve6cXtY7v7JCv7gibOBWjPfCU7KK4Lk6GjIu9_GhyE,5282
@@ -184,9 +185,12 @@ xp/services/telegram/telegram_output_service.py,sha256=9deqtcPndRqJ-3XQUWlJhXaVc
184
185
  xp/services/telegram/telegram_service.py,sha256=jPu0Xrh3IpvqPLyuQT5Vf8HHw00vBingONHdxf_9TkI,13315
185
186
  xp/services/telegram/telegram_version_service.py,sha256=oXnZ_K7OQ7xD-GEj3zDYp52KlkqVuHpO4bf7gMlC_w4,10574
186
187
  xp/services/term/__init__.py,sha256=BIeOK042bMR-0l6MA80wdW5VuHlpWOXtRER9IG5ilQA,245
188
+ xp/services/term/homekit_service.py,sha256=3g6Twhg6wcRT2E01Daa2J41lzdyX6gVjfz5wgutOvgQ,19307
187
189
  xp/services/term/protocol_monitor_service.py,sha256=5YBI0Nu7B7gMhaTbUhL6k9LSRfnCIj6CwrCYHiMHavA,10067
188
- xp/services/term/state_monitor_service.py,sha256=4CmeHf5k9mf67AleOB7byM7_g5WwbAOnmgxl6I2vBwg,17116
190
+ xp/services/term/state_monitor_service.py,sha256=EK9tNBfamAIV0z0EMsXDYWC-rXv6l6k_bHsC8xyEFSo,17116
189
191
  xp/term/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
192
+ xp/term/homekit.py,sha256=IYJEVhQUFUcmC_TubYxcvlU5IKF-2sL_giBNKO35fjU,3642
193
+ xp/term/homekit.tcss,sha256=qeR_OV8D_9Mxb-aPNz-MH0ZJOsdCk-fJ-zv6CQV5ihw,1382
190
194
  xp/term/protocol.py,sha256=6MX3mduLei-AgLGaIe8lfOSu4Hi0y3KGePFFM2ssstc,3475
191
195
  xp/term/protocol.tcss,sha256=r_KfxrbpycGHLVXqZc6INBBcUJME0hLrAZkF1oqnab4,2126
192
196
  xp/term/state.py,sha256=FBpYV_bWYJh9o17qcMx6sHgUARQS-uNOtUt6G7Vs1n8,3274
@@ -195,13 +199,14 @@ xp/term/widgets/__init__.py,sha256=ftWmN_fmjxy2E8Qfm-YSRmzKfgL0KTBCTpgvYWCPbUY,2
195
199
  xp/term/widgets/help_menu.py,sha256=KLkdIXfhARLFNEs2lv1u0sYBz9LzOCcDLxbMGzc7e5Y,1812
196
200
  xp/term/widgets/modules_list.py,sha256=qAG-n0nK0YdNE9v4C3-sHgxLvF1i1FR7v_GArdaoUQw,7831
197
201
  xp/term/widgets/protocol_log.py,sha256=E68QmSMpOFrvrPTo_gOQVfyiDqY5c_y8fkNKnQw6Vwo,2650
198
- xp/term/widgets/status_footer.py,sha256=YYAT0431p6jmrzzpVgaPhu7yGkRroWGv4e99t2XlkHI,3297
202
+ xp/term/widgets/room_list.py,sha256=3q3otusnQn4qFRbTY0-QbpMP3vPmywM0izYRA_KjXn0,7871
203
+ xp/term/widgets/status_footer.py,sha256=biV3EzfVSgm1T7Ofi88LXsTFCkD5mI_6Cpe-RpuOSxA,3429
199
204
  xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
200
205
  xp/utils/checksum.py,sha256=Px1S3dFGA-_plavBxrq3IqmprNlgtNDunE3whg6Otwg,1722
201
- xp/utils/dependencies.py,sha256=dlSuKVmuA7AZlqINrQkZxfHuU8mzSHDZnMRRDzqjRAI,24067
206
+ xp/utils/dependencies.py,sha256=XHOAq5nIbXD8oq4FBp3wWvinDa4Ti7cUktJtUB0z51A,25339
202
207
  xp/utils/event_helper.py,sha256=zD0K3TPfGEThU9vUNlDtglTai3Cmm30727iwjDZy6Dk,1007
203
208
  xp/utils/logging.py,sha256=wJ1d-yg97NiZUrt2F8iDMcmnHVwC-PErcI-7dpyiRDc,3777
204
209
  xp/utils/serialization.py,sha256=TS1OwpTOemSvXsCGw3js4JkYYFEqkzrPe8V9QYQefdw,4684
205
210
  xp/utils/state_machine.py,sha256=W9AY4ntRZnFeHAa5d43hm37j53uJPlqkRvWTPiBhJ_0,2464
206
211
  xp/utils/time_utils.py,sha256=K17godWpL18VEypbTlvNOEDG6R3huYnf29yjkcnwRpU,3796
207
- conson_xp-1.50.1.dist-info/RECORD,,
212
+ conson_xp-1.51.1.dist-info/RECORD,,
xp/__init__.py CHANGED
@@ -4,7 +4,7 @@ XP CLI tool for remote console bus operations.
4
4
  conson-xp package.
5
5
  """
6
6
 
7
- __version__ = "1.50.1"
7
+ __version__ = "1.51.1"
8
8
  __manufacturer__ = "salchichon"
9
9
  __model__ = "xp.cli"
10
10
  __serial__ = "2025.09.23.000"
@@ -117,16 +117,21 @@ def export_conbus_actiontable(ctx: click.Context) -> None:
117
117
  xp conbus export actiontable
118
118
  """
119
119
 
120
- def on_progress(serial_number: str, current: int, total: int) -> None:
120
+ def on_progress(
121
+ serial_number: str, actiontable_type: str, current: int, total: int
122
+ ) -> None:
121
123
  """
122
124
  Handle progress updates during export.
123
125
 
124
126
  Args:
125
127
  serial_number: Serial number of discovered device.
128
+ actiontable_type: Type of action table being exported.
126
129
  current: Current device number.
127
130
  total: Total devices discovered.
128
131
  """
129
- click.echo(f"Querying device {current}/{total}: {serial_number}...")
132
+ click.echo(
133
+ f"Querying device {current}/{total}: {serial_number} / {actiontable_type}."
134
+ )
130
135
 
131
136
  def on_device_actiontable_exported(
132
137
  module: ConsonModuleConfig,
@@ -176,4 +181,5 @@ def export_conbus_actiontable(ctx: click.Context) -> None:
176
181
  service.on_device_actiontable_exported.connect(on_device_actiontable_exported)
177
182
  service.on_finish.connect(on_finish)
178
183
  service.set_timeout(5)
184
+ service.configure()
179
185
  service.start_reactor()
@@ -48,3 +48,26 @@ def state_monitor(ctx: Context) -> None:
48
48
 
49
49
  # Resolve StateMonitorApp from container and run
50
50
  ctx.obj.get("container").get_container().resolve(StateMonitorApp).run()
51
+
52
+
53
+ @term.command("homekit")
54
+ @click.pass_context
55
+ def homekit_monitor(ctx: Context) -> None:
56
+ r"""
57
+ Start TUI for HomeKit accessory monitoring.
58
+
59
+ Displays HomeKit rooms and accessories with real-time state updates
60
+ in an interactive terminal interface. Press action keys (a-z) to
61
+ toggle accessories.
62
+
63
+ Args:
64
+ ctx: Click context object.
65
+
66
+ Examples:
67
+ \b
68
+ xp term homekit
69
+ """
70
+ from xp.term.homekit import HomekitApp
71
+
72
+ # Resolve HomekitApp from container and run
73
+ ctx.obj.get("container").get_container().resolve(HomekitApp).run()
@@ -61,6 +61,7 @@ class HomekitAccessoryConfig(BaseModel):
61
61
  service: Service type for the accessory.
62
62
  on_action: on code for the accessory.
63
63
  off_action: off code for the accessory.
64
+ toggle_action: Optional toggle action code for the accessory.
64
65
  hap_accessory: Optional HAP accessory identifier.
65
66
  """
66
67
 
@@ -72,6 +73,7 @@ class HomekitAccessoryConfig(BaseModel):
72
73
  service: str
73
74
  on_action: str
74
75
  off_action: str
76
+ toggle_action: Optional[str] = None
75
77
  hap_accessory: Optional[int] = None
76
78
 
77
79
 
@@ -1,5 +1,6 @@
1
1
  """Terminal UI models."""
2
2
 
3
+ from xp.models.term.accessory_state import AccessoryState
3
4
  from xp.models.term.module_state import ModuleState
4
5
  from xp.models.term.protocol_keys_config import (
5
6
  ProtocolKeyConfig,
@@ -7,6 +8,7 @@ from xp.models.term.protocol_keys_config import (
7
8
  )
8
9
 
9
10
  __all__ = [
11
+ "AccessoryState",
10
12
  "ModuleState",
11
13
  "ProtocolKeyConfig",
12
14
  "ProtocolKeysConfig",
@@ -0,0 +1,50 @@
1
+ """Accessory state data model for Homekit TUI."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+
8
+ @dataclass
9
+ class AccessoryState:
10
+ """
11
+ State of a HomeKit accessory for TUI display.
12
+
13
+ Attributes:
14
+ room_name: Room containing the accessory (e.g., "Salon").
15
+ accessory_name: Accessory display name (e.g., "Variateur salon").
16
+ action: Action key (a-z) for toggle control.
17
+ output_state: Output state ("ON", "OFF", "?").
18
+ dimming_state: Dimming percentage for dimmable modules, "-" if OFF, empty otherwise.
19
+ module_name: Module identifier (e.g., "A12").
20
+ serial_number: Module serial number.
21
+ module_type: Module type (e.g., "XP24", "XP33LED").
22
+ error_status: Status code ("OK" or error like "E10").
23
+ output: Module output number (1-based for display).
24
+ sort: Sort accessories according to homekit.yml configuration.
25
+ last_update: Last communication timestamp. None if never updated.
26
+ toggle_action: Raw toggle action telegram (e.g., "E02L12I02").
27
+ """
28
+
29
+ room_name: str
30
+ accessory_name: str
31
+ action: str
32
+ output_state: str
33
+ dimming_state: str
34
+ module_name: str
35
+ serial_number: str
36
+ module_type: str
37
+ error_status: str
38
+ output: int
39
+ sort: int
40
+ last_update: Optional[datetime] = None
41
+ toggle_action: Optional[str] = None
42
+
43
+ def is_dimmable(self) -> bool:
44
+ """
45
+ Check if accessory is dimmable.
46
+
47
+ Returns:
48
+ True if module type is XP33LR or XP33LED, False otherwise.
49
+ """
50
+ return self.module_type in ("XP33LR", "XP33LED")
@@ -292,6 +292,8 @@ class ActionTableDownloadService(DownloadStateMachine):
292
292
  raise RuntimeError("Cannot configure while download in progress")
293
293
  self.logger.info("Configuring actiontable download")
294
294
  self.serial_number = serial_number
295
+ self.actiontable_data = []
296
+
295
297
  if actiontable_type == ActionTableType.ACTIONTABLE:
296
298
  self.serializer = self.actiontable_serializer
297
299
  elif actiontable_type == ActionTableType.MSACTIONTABLE_XP20:
@@ -3,7 +3,7 @@
3
3
  import asyncio
4
4
  import logging
5
5
  from pathlib import Path
6
- from queue import SimpleQueue
6
+ from queue import Empty, SimpleQueue
7
7
  from typing import Any, Optional, Tuple
8
8
 
9
9
  import yaml
@@ -38,7 +38,7 @@ class ConbusActiontableExportService:
38
38
  """
39
39
 
40
40
  # Signals (class attributes)
41
- on_progress: Signal = Signal(str, int, int) # serial, current, total
41
+ on_progress: Signal = Signal(str, str, int, int) # serial, current, total
42
42
  on_device_actiontable_exported: Signal = Signal(
43
43
  ConsonModuleConfig, ActionTableType, str
44
44
  )
@@ -64,25 +64,35 @@ class ConbusActiontableExportService:
64
64
  self.logger = logging.getLogger(__name__)
65
65
  self.download_service = download_service
66
66
  self._module_list: ConsonModuleListConfig = module_list
67
+ self._module_dic: dict[str, ConsonModuleConfig] = {
68
+ module.serial_number: module for module in module_list.root
69
+ }
67
70
  # State management
68
- self.device_queue: SimpleQueue[Tuple[ConsonModuleConfig, ActionTableType]] = (
71
+ self.device_queue: SimpleQueue[Tuple[str, ActionTableType]] = (
69
72
  SimpleQueue()
70
73
  ) # FIFO
71
74
  for module in self._module_list.root:
72
- self.device_queue.put((module, ActionTableType.ACTIONTABLE))
73
- if module.module_type == "xp20":
74
- self.device_queue.put((module, ActionTableType.MSACTIONTABLE_XP20))
75
- if module.module_type == "xp24":
76
- self.device_queue.put((module, ActionTableType.MSACTIONTABLE_XP24))
77
- if module.module_type == "xp33":
78
- self.device_queue.put((module, ActionTableType.MSACTIONTABLE_XP33))
75
+ self.logger.info("Export module %s", module)
76
+ if module.module_type.lower() == "xp20":
77
+ self.device_queue.put(
78
+ (module.serial_number, ActionTableType.MSACTIONTABLE_XP20)
79
+ )
80
+ if module.module_type.lower() == "xp24":
81
+ self.device_queue.put(
82
+ (module.serial_number, ActionTableType.MSACTIONTABLE_XP24)
83
+ )
84
+ if module.module_type.lower() == "xp33":
85
+ self.device_queue.put(
86
+ (module.serial_number, ActionTableType.MSACTIONTABLE_XP33)
87
+ )
88
+ self.device_queue.put((module.serial_number, ActionTableType.ACTIONTABLE))
89
+
90
+ self.logger.info("Export module %s", self.device_queue.qsize())
79
91
 
80
92
  self.current_module: Optional[ConsonModuleConfig] = None
81
- self.curent_actiontable_type: Optional[ActionTableType] = None
93
+ self.current_actiontable_type: Optional[ActionTableType] = None
82
94
  self.export_result = ConbusExportResponse(success=False)
83
95
  self.export_status = "OK"
84
- # Connect protocol signals
85
- self._connect_signals()
86
96
 
87
97
  def on_module_actiontable_received(
88
98
  self, actiontable: Any, short_actiontable: list[str]
@@ -94,7 +104,7 @@ class ConbusActiontableExportService:
94
104
  actiontable: Full actiontable data.
95
105
  short_actiontable: Short representation of the actiontable.
96
106
  """
97
- if not self.curent_actiontable_type:
107
+ if not self.current_actiontable_type:
98
108
  self._fail("Invalid state (curent_actiontable_type)")
99
109
  return
100
110
 
@@ -102,17 +112,17 @@ class ConbusActiontableExportService:
102
112
  self._fail("Invalid state (current_module)")
103
113
  return
104
114
 
105
- if self.curent_actiontable_type == ActionTableType.ACTIONTABLE:
115
+ if self.current_actiontable_type == ActionTableType.ACTIONTABLE:
106
116
  self.current_module.action_table = short_actiontable
107
- elif self.curent_actiontable_type == ActionTableType.MSACTIONTABLE_XP20:
117
+ elif self.current_actiontable_type == ActionTableType.MSACTIONTABLE_XP20:
108
118
  self.current_module.xp20_msaction_table = short_actiontable
109
- elif self.curent_actiontable_type == ActionTableType.MSACTIONTABLE_XP24:
119
+ elif self.current_actiontable_type == ActionTableType.MSACTIONTABLE_XP24:
110
120
  self.current_module.xp24_msaction_table = short_actiontable
111
- elif self.curent_actiontable_type == ActionTableType.MSACTIONTABLE_XP33:
121
+ elif self.current_actiontable_type == ActionTableType.MSACTIONTABLE_XP33:
112
122
  self.current_module.xp33_msaction_table = short_actiontable
113
123
 
114
124
  self.on_device_actiontable_exported.emit(
115
- self.current_module, self.curent_actiontable_type, actiontable
125
+ self.current_module, self.current_actiontable_type, short_actiontable
116
126
  )
117
127
 
118
128
  def on_module_finish(self) -> None:
@@ -127,10 +137,13 @@ class ConbusActiontableExportService:
127
137
  serial_number = (
128
138
  self.current_module.serial_number if self.current_module else "UNKNOWN"
129
139
  )
140
+ current_actiontable_type = self.current_actiontable_type or "UNKNOWN"
130
141
  total_modules = len(self._module_list.root)
131
142
  current_index = total_modules - self.device_queue.qsize()
132
143
 
133
- self.on_progress.emit(serial_number, current_index, total_modules)
144
+ self.on_progress.emit(
145
+ serial_number, current_actiontable_type, current_index, total_modules
146
+ )
134
147
 
135
148
  def on_module_error(self, error_message: str) -> None:
136
149
  """
@@ -162,7 +175,6 @@ class ConbusActiontableExportService:
162
175
  "enabled",
163
176
  "conbus_ip",
164
177
  "conbus_port",
165
- "action_table",
166
178
  }
167
179
  }
168
180
  },
@@ -203,16 +215,24 @@ class ConbusActiontableExportService:
203
215
  True if there is a module to export, False otherwise.
204
216
  """
205
217
  self.download_service.reset()
206
- (self.current_module, self.curent_actiontable_type) = (
207
- self.device_queue.get_nowait()
208
- )
209
- if not (self.current_module or self.curent_actiontable_type):
218
+ try:
219
+ (current_serial_number, self.current_actiontable_type) = (
220
+ self.device_queue.get_nowait()
221
+ )
222
+ except Empty:
223
+ return False
224
+
225
+ self.current_module = self._module_dic[current_serial_number]
226
+ if not (self.current_module or self.current_actiontable_type):
210
227
  self.logger.error("No module to export")
211
228
  return False
212
229
 
230
+ self.logger.info(
231
+ f"Downloading {self.current_module.serial_number} / {self.current_actiontable_type}"
232
+ )
213
233
  self.download_service.configure(
214
234
  self.current_module.serial_number,
215
- self.curent_actiontable_type,
235
+ self.current_actiontable_type,
216
236
  )
217
237
  self.download_service.do_connect()
218
238
  return True
@@ -275,7 +295,7 @@ class ConbusActiontableExportService:
275
295
 
276
296
  def _disconnect_signals(self) -> None:
277
297
  """Disconnect download service signals from handlers."""
278
- self.download_service.on_actiontable_received.connect(
298
+ self.download_service.on_actiontable_received.disconnect(
279
299
  self.on_module_actiontable_received
280
300
  )
281
301
  self.download_service.on_finish.disconnect(self.on_module_finish)
@@ -221,7 +221,7 @@ class CrossReferenceValidator:
221
221
  # Define output limits by module type
222
222
  output_limits = {
223
223
  "XP130": 0, # Example limits
224
- "XP20": 0,
224
+ "XP20": 8,
225
225
  "XP24": 4,
226
226
  "XP33": 3,
227
227
  "XP33LR": 3,