conson-xp 2.0.1__py3-none-any.whl → 2.0.2__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: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -1,8 +1,8 @@
1
- conson_xp-2.0.1.dist-info/METADATA,sha256=tjqY8FAUMgkHQOaEMyu0EPYq-T1JRLs5wD7Tqu2lBJA,11319
2
- conson_xp-2.0.1.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
- conson_xp-2.0.1.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-2.0.1.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=w2LWS-22VYMQH5ujXhsjjaQ9D1sDkzAfiiYv3VkLl9M,181
1
+ conson_xp-2.0.2.dist-info/METADATA,sha256=9UzUpaHd8pegRLDFIPEzaMHr2L3ziBK7QhA2EYqSJ28,11319
2
+ conson_xp-2.0.2.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
+ conson_xp-2.0.2.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
+ conson_xp-2.0.2.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
+ xp/__init__.py,sha256=qQYrRExAxcFFDY2HISDUiyYNIaz1ywu0tBMS6dAtJ9I,181
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=9TGP3uTxAU-s-kVChvQ-Fn3-HVYj-QpeQ05Is-20HRo,4788
@@ -81,7 +81,7 @@ xp/models/config/__init__.py,sha256=gEZnX9eE3DjFtLtF32riEjJQLypqQRbyPauBI4Cowbs,
81
81
  xp/models/config/conson_module_config.py,sha256=t1G0LnNNMnjs3ahhz4-Z_5SlEv2FCrcRq13OmvZ2pvA,3009
82
82
  xp/models/homekit/__init__.py,sha256=5HDSOClCu0ArK3IICn3_LDMMLBAzLjBxUUSF73bxSSk,34
83
83
  xp/models/homekit/homekit_accessory.py,sha256=ANjDWlFxeNTstl7lKdmf6vMOC0wc005vpiD6awRcptA,1052
84
- xp/models/homekit/homekit_config.py,sha256=pgZOnocue60LjV8ce46MyJ3mo5CqLix6TmT64qxPOks,3267
84
+ xp/models/homekit/homekit_config.py,sha256=YOhODQpURg_1OU0i-4qMglU4E37feNKKtY1uuZRXSoY,3759
85
85
  xp/models/log_entry.py,sha256=tAiNwouCP2d4jKiHJY9a-2iAi8LWTpG-TZsOPDIstlA,4423
86
86
  xp/models/protocol/__init__.py,sha256=TJ_CJKchA-xgQiv5vCo_ndBBZjrcaTmjT74bR0T-5Cw,38
87
87
  xp/models/protocol/conbus_protocol.py,sha256=gFaXK1VY74aVQhMH69Dr-dTDbDuQDQGs8vKmXqK_te8,9318
@@ -112,7 +112,7 @@ xp/models/term/telegram_display.py,sha256=tXWeEtoIBSnScjha3ZHV9UPICmtBF2bkoLVIjQ
112
112
  xp/models/write_config_type.py,sha256=IqgguaHgKvz4Qt-WaSVu3J2VaXgtS-br9Yp8q_xkIkY,895
113
113
  xp/services/__init__.py,sha256=W9YZyrkh7vm--ZHhAXNQiOYQs5yhhmUHXP5I0Lf1XBg,782
114
114
  xp/services/actiontable/__init__.py,sha256=z6js4EuJ6xKHaseTEhuEvKo1tr9K1XyQiruReJtBiPY,26
115
- xp/services/actiontable/actiontable_serializer.py,sha256=AZpqxfq8-o7FGGtseW52MS6z647x3Ucc1RjR3GLUb20,8335
115
+ xp/services/actiontable/actiontable_serializer.py,sha256=GTpC9_bOdxqQl7cVhpEH9aGusZNRn5WcahwPc2XUWaY,8393
116
116
  xp/services/actiontable/download_state_machine.py,sha256=lqNYN9LGGK2KiVUsmvyRfryWRB4-NOfsp7-9GrFubK4,9978
117
117
  xp/services/actiontable/msactiontable_serializer.py,sha256=RRL6TZ1gpSQw81kAiw2BV3jTqm4fCJC0pWIcO26Cmos,174
118
118
  xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=5K6FxgbV2F4brumNaOH6M8qPyCxIfaqCGOPIYDmFdnk,6998
@@ -124,7 +124,7 @@ xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAn
124
124
  xp/services/conbus/actiontable/actiontable_download_service.py,sha256=41Hr1753IBpUeHQqO57uS7qxOB0rJt8qCpznzKlUPOM,15028
125
125
  xp/services/conbus/actiontable/actiontable_list_service.py,sha256=oTDSpBkp-MJeaF5bhRnwkSy3na55xqQ4e2ykJzbMCUo,3236
126
126
  xp/services/conbus/actiontable/actiontable_show_service.py,sha256=WISY2VsmSlceGa5_9lpFO-gs5TnTjv6YidQksUjCapk,3058
127
- xp/services/conbus/actiontable/actiontable_upload_service.py,sha256=FaQzOSg8s2zUL5xz9qZY9fvzrdDosc3CoxkVDvNg2SU,13252
127
+ xp/services/conbus/actiontable/actiontable_upload_service.py,sha256=bIAN3Ca3BHTPtZYfPijAgsWP91k-uNFAW7-i-LoTE4I,13553
128
128
  xp/services/conbus/conbus_blink_all_service.py,sha256=toDIZDXBGBYnEishcdnJrVzkmfPi7g5nCDXuyA_wFCs,8536
129
129
  xp/services/conbus/conbus_blink_service.py,sha256=ggLuzeq_UsgCoxRxg2bsNs9p8Lw_shjsj-niRzb5dKk,7953
130
130
  xp/services/conbus/conbus_custom_service.py,sha256=9OIRC2CG_rN96vbv_EZXf7BrX_abhqi5MZx0Se8fEhU,7826
@@ -167,11 +167,11 @@ xp/services/telegram/telegram_service.py,sha256=jPu0Xrh3IpvqPLyuQT5Vf8HHw00vBing
167
167
  xp/services/telegram/telegram_version_service.py,sha256=oXnZ_K7OQ7xD-GEj3zDYp52KlkqVuHpO4bf7gMlC_w4,10574
168
168
  xp/services/term/__init__.py,sha256=BIeOK042bMR-0l6MA80wdW5VuHlpWOXtRER9IG5ilQA,245
169
169
  xp/services/term/homekit_accessory_driver.py,sha256=jHhHHOOvzdDjuYys1cUZHcH2uwZy_Y6o5aGAQqXGAVg,5935
170
- xp/services/term/homekit_service.py,sha256=h1wAguhUG9f8cdolFKsp-V-iZV5x52Wm0L7syKoK6Qs,25183
170
+ xp/services/term/homekit_service.py,sha256=8aEsmC-EwKTxf0Hxp0aFBdo4doUvhVHeVwfuuc1ttWw,28925
171
171
  xp/services/term/protocol_monitor_service.py,sha256=5YBI0Nu7B7gMhaTbUhL6k9LSRfnCIj6CwrCYHiMHavA,10067
172
172
  xp/services/term/state_monitor_service.py,sha256=EK9tNBfamAIV0z0EMsXDYWC-rXv6l6k_bHsC8xyEFSo,17116
173
173
  xp/term/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
174
- xp/term/homekit.py,sha256=A9l9zK6KSBx_Cc8I_AZhAWdQjZYnTGLxkln6CzFn3-Q,8935
174
+ xp/term/homekit.py,sha256=HJH3dZQsdp5rqcuV4EWJbytk7glCyDmj27614nRbIyI,9909
175
175
  xp/term/homekit.tcss,sha256=A1f5-V3mvxAMZK_ERq8lLjNcOWH0U5tblIBbeL3OYYM,1382
176
176
  xp/term/protocol.py,sha256=6MX3mduLei-AgLGaIe8lfOSu4Hi0y3KGePFFM2ssstc,3475
177
177
  xp/term/protocol.tcss,sha256=r_KfxrbpycGHLVXqZc6INBBcUJME0hLrAZkF1oqnab4,2126
@@ -191,4 +191,4 @@ xp/utils/logging.py,sha256=wJ1d-yg97NiZUrt2F8iDMcmnHVwC-PErcI-7dpyiRDc,3777
191
191
  xp/utils/serialization.py,sha256=TS1OwpTOemSvXsCGw3js4JkYYFEqkzrPe8V9QYQefdw,4684
192
192
  xp/utils/state_machine.py,sha256=W9AY4ntRZnFeHAa5d43hm37j53uJPlqkRvWTPiBhJ_0,2464
193
193
  xp/utils/time_utils.py,sha256=K17godWpL18VEypbTlvNOEDG6R3huYnf29yjkcnwRpU,3796
194
- conson_xp-2.0.1.dist-info/RECORD,,
194
+ conson_xp-2.0.2.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__ = "2.0.1"
7
+ __version__ = "2.0.2"
8
8
  __manufacturer__ = "salchichon"
9
9
  __model__ = "xp.cli"
10
10
  __serial__ = "2025.09.23.000"
@@ -66,6 +66,10 @@ class HomekitAccessoryConfig(BaseModel):
66
66
  on_action: on code for the accessory.
67
67
  off_action: off code for the accessory.
68
68
  toggle_action: Optional toggle action code for the accessory.
69
+ dimup_action: Optional dim up action code for the dimmable accessory.
70
+ dimdown_action: Optional dim down action code for the dimmable accessory.
71
+ levelup_action: Optional level up action code for the dimmable accessory.
72
+ leveldown_action: Optional level down action code for the dimmable accessory.
69
73
  hap_accessory: Optional HAP accessory identifier.
70
74
  """
71
75
 
@@ -78,6 +82,10 @@ class HomekitAccessoryConfig(BaseModel):
78
82
  on_action: str
79
83
  off_action: str
80
84
  toggle_action: Optional[str] = None
85
+ dimup_action: Optional[str] = None
86
+ dimdown_action: Optional[str] = None
87
+ levelup_action: Optional[str] = None
88
+ leveldown_action: Optional[str] = None
81
89
  hap_accessory: Optional[int] = None
82
90
 
83
91
 
@@ -65,8 +65,8 @@ class ActionTableSerializer(ActionTableSerializerProtocol):
65
65
  link_number = de_bcd(data[i + 1])
66
66
  module_input = de_bcd(data[i + 2])
67
67
 
68
- # Extract output and command from byte 3
69
- module_output = lower3(data[i + 3])
68
+ # Extract output (0-indexed in wire format, convert to 1-indexed) and command
69
+ module_output = lower3(data[i + 3]) + 1
70
70
  command_raw = upper5(data[i + 3])
71
71
 
72
72
  parameter_raw = byte_to_unsigned(data[i + 4])
@@ -125,8 +125,8 @@ class ActionTableSerializer(ActionTableSerializerProtocol):
125
125
  link_byte = to_bcd(entry.link_number)
126
126
  input_byte = to_bcd(entry.module_input)
127
127
 
128
- # Combine output (lower 3 bits) and command (upper 5 bits)
129
- output_command_byte = (entry.module_output & 0x07) | (
128
+ # Combine output (lower 3 bits, 0-indexed) and command (upper 5 bits)
129
+ output_command_byte = ((entry.module_output - 1) & 0x07) | (
130
130
  (entry.command.value & 0x1F) << 3
131
131
  )
132
132
 
@@ -81,6 +81,7 @@ class ActionTableUploadService:
81
81
  # Upload state
82
82
  self.upload_data_chunks: list[str] = []
83
83
  self.current_chunk_index: int = 0
84
+ self._eof_sent: bool = False
84
85
 
85
86
  # Set up logging
86
87
  self.logger = logging.getLogger(__name__)
@@ -173,7 +174,7 @@ class ActionTableUploadService:
173
174
  )
174
175
  self.current_chunk_index += 1
175
176
  self.on_progress.emit(".")
176
- else:
177
+ elif not self._eof_sent:
177
178
  # All chunks sent, send EOF
178
179
  self.logger.debug("All chunks sent, sending EOF")
179
180
  self.conbus_protocol.send_telegram(
@@ -182,7 +183,13 @@ class ActionTableUploadService:
182
183
  system_function=SystemFunction.EOF,
183
184
  data_value="00",
184
185
  )
186
+ self.on_progress.emit("END")
187
+ self.logger.debug("EOF sent, waiting for last ACK")
188
+ self._eof_sent = True
189
+ else:
190
+ self.logger.debug("Last ACK received, closing connection")
185
191
  self.on_finish.emit(True)
192
+
186
193
  elif reply_telegram.system_function == SystemFunction.NAK:
187
194
  self.logger.debug("Received NAK during upload")
188
195
  self.failed("Upload failed: NAK received")
@@ -81,6 +81,9 @@ class HomekitService:
81
81
  # Set up HomeKit callback
82
82
  self._accessory_driver.set_callback(self._on_homekit_set)
83
83
 
84
+ # Track active level action: (accessory_id, action_type) or None
85
+ self._active_level_action: Optional[tuple[str, str]] = None
86
+
84
87
  # Connect to protocol signals
85
88
  self._connect_signals()
86
89
 
@@ -421,12 +424,15 @@ class HomekitService:
421
424
  Returns:
422
425
  True if command was sent, False otherwise.
423
426
  """
427
+ config = self._find_accessory_config_by_id(accessory_id)
424
428
  state = self._accessory_states.get(accessory_id)
425
- if not state:
429
+ if not config or not state or not config.dimup_action:
430
+ self.logger.warning(f"No config for accessory {accessory_id}")
426
431
  return False
427
- # TODO: Implement dimmer control
428
- self.on_status_message.emit(f"Dimmer+ {state.accessory_name} (not implemented)")
429
- return False
432
+
433
+ self.send_action(config.dimup_action)
434
+ self.on_status_message.emit(f"Dim+ {state.accessory_name}")
435
+ return True
430
436
 
431
437
  def decrease_dimmer(self, accessory_id: str) -> bool:
432
438
  """
@@ -438,12 +444,103 @@ class HomekitService:
438
444
  Returns:
439
445
  True if command was sent, False otherwise.
440
446
  """
447
+ config = self._find_accessory_config_by_id(accessory_id)
441
448
  state = self._accessory_states.get(accessory_id)
442
- if not state:
449
+ if not config or not state or not config.dimdown_action:
450
+ self.logger.warning(f"No config for accessory {accessory_id}")
443
451
  return False
444
- # TODO: Implement dimmer control
445
- self.on_status_message.emit(f"Dimmer- {state.accessory_name} (not implemented)")
446
- return False
452
+
453
+ self.send_action(config.dimdown_action)
454
+ self.on_status_message.emit(f"Dim- {state.accessory_name}")
455
+ return True
456
+
457
+ def levelup_selected(self, accessory_id: str) -> bool:
458
+ """
459
+ Increase level for accessory (toggle Make/Break).
460
+
461
+ First press sends Make (M), second press sends Break (B).
462
+
463
+ Args:
464
+ accessory_id: Accessory ID (e.g., "A12_1").
465
+
466
+ Returns:
467
+ True if command was sent, False otherwise.
468
+ """
469
+ config = self._find_accessory_config_by_id(accessory_id)
470
+ state = self._accessory_states.get(accessory_id)
471
+ if not config or not state or not config.levelup_action:
472
+ self.logger.warning(f"No config for accessory {accessory_id}")
473
+ return False
474
+
475
+ return self._send_level_action(
476
+ accessory_id, "levelup", config.levelup_action, state.accessory_name
477
+ )
478
+
479
+ def leveldown_selected(self, accessory_id: str) -> bool:
480
+ """
481
+ Decrease level for accessory (toggle Make/Break).
482
+
483
+ First press sends Make (M), second press sends Break (B).
484
+
485
+ Args:
486
+ accessory_id: Accessory ID (e.g., "A12_1").
487
+
488
+ Returns:
489
+ True if command was sent, False otherwise.
490
+ """
491
+ config = self._find_accessory_config_by_id(accessory_id)
492
+ state = self._accessory_states.get(accessory_id)
493
+ if not config or not state or not config.leveldown_action:
494
+ self.logger.warning(f"No config for accessory {accessory_id}")
495
+ return False
496
+
497
+ return self._send_level_action(
498
+ accessory_id, "leveldown", config.leveldown_action, state.accessory_name
499
+ )
500
+
501
+ def _send_level_action(
502
+ self, accessory_id: str, action_type: str, action: str, name: str
503
+ ) -> bool:
504
+ """
505
+ Send level action with Make/Break toggle.
506
+
507
+ Args:
508
+ accessory_id: Accessory ID.
509
+ action_type: "levelup" or "leveldown".
510
+ action: Action code (e.g., "E02L13I15").
511
+ name: Accessory name for status message.
512
+
513
+ Returns:
514
+ True if command was sent.
515
+ """
516
+ current = self._active_level_action
517
+
518
+ # If same action is active, send Break and clear
519
+ if current and current[0] == accessory_id and current[1] == action_type:
520
+ self._conbus_protocol.send_raw_telegram(f"{action}B")
521
+ self._active_level_action = None
522
+ direction = "+" if action_type == "levelup" else "-"
523
+ self.on_status_message.emit(f"Level{direction} {name} [B]")
524
+ return True
525
+
526
+ # If different action is active, send Break for it first
527
+ if current:
528
+ old_config = self._find_accessory_config_by_id(current[0])
529
+ if old_config:
530
+ old_action = (
531
+ old_config.levelup_action
532
+ if current[1] == "levelup"
533
+ else old_config.leveldown_action
534
+ )
535
+ if old_action:
536
+ self._conbus_protocol.send_raw_telegram(f"{old_action}B")
537
+
538
+ # Send Make for new action
539
+ self._conbus_protocol.send_raw_telegram(f"{action}M")
540
+ self._active_level_action = (accessory_id, action_type)
541
+ direction = "+" if action_type == "levelup" else "-"
542
+ self.on_status_message.emit(f"Level{direction} {name} [M]")
543
+ return True
447
544
 
448
545
  def refresh_all(self) -> None:
449
546
  """
xp/term/homekit.py CHANGED
@@ -41,6 +41,8 @@ class HomekitApp(App[None]):
41
41
  ("minus", "turn_off_selected", "Off"),
42
42
  ("plus", "dim_up", "Dim+"),
43
43
  ("quotation_mark", "dim_down", "Dim-"),
44
+ ("asterisk", "level_up", "Level+"),
45
+ ("ç", "level_down", "Level-"),
44
46
  ]
45
47
 
46
48
  def __init__(self, homekit_service: HomekitService) -> None:
@@ -106,12 +108,17 @@ class HomekitApp(App[None]):
106
108
  - - : Turn OFF
107
109
  - + : Dim up
108
110
  - " : Dim down
111
+ - * : Level up
112
+ - ç : Level down
109
113
 
110
114
  Args:
111
115
  event: Key press event.
112
116
  """
113
117
  key = event.key
114
118
 
119
+ # Debug: show received key
120
+ self.homekit_service.on_status_message.emit(f"Key: {key}")
121
+
115
122
  # Selection keys (a-z0-9)
116
123
  if len(key) == 1 and (("a" <= key <= "z") or ("0" <= key <= "9")):
117
124
  accessory_id = self.homekit_service.select_accessory(key)
@@ -140,6 +147,12 @@ class HomekitApp(App[None]):
140
147
  elif key in ("quotation_mark", '"'):
141
148
  self.homekit_service.decrease_dimmer(self.selected_accessory_id)
142
149
  event.prevent_default()
150
+ elif key in ("asterisk", "star", "*"):
151
+ self.homekit_service.levelup_selected(self.selected_accessory_id)
152
+ event.prevent_default()
153
+ elif key in ("cedille", "ç"):
154
+ self.homekit_service.leveldown_selected(self.selected_accessory_id)
155
+ event.prevent_default()
143
156
 
144
157
  def _select_row(self, action_key: str) -> None:
145
158
  """
@@ -243,6 +256,16 @@ class HomekitApp(App[None]):
243
256
  if self.selected_accessory_id:
244
257
  self.homekit_service.decrease_dimmer(self.selected_accessory_id)
245
258
 
259
+ def action_level_up(self) -> None:
260
+ """Increase level on selected accessory."""
261
+ if self.selected_accessory_id:
262
+ self.homekit_service.levelup_selected(self.selected_accessory_id)
263
+
264
+ def action_level_down(self) -> None:
265
+ """Decrease level on selected accessory."""
266
+ if self.selected_accessory_id:
267
+ self.homekit_service.leveldown_selected(self.selected_accessory_id)
268
+
246
269
  async def on_unmount(self) -> None:
247
270
  """Stop AccessoryDriver and clean up service when app unmounts."""
248
271
  await self.homekit_service.stop()