SolixBLE 3.6.0__tar.gz → 3.7.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.
Files changed (32) hide show
  1. {solixble-3.6.0 → solixble-3.7.0}/PKG-INFO +1 -1
  2. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/device.py +42 -41
  3. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/solarbank2.py +151 -11
  4. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/prime_device.py +1 -1
  5. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/states.py +75 -0
  6. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE.egg-info/PKG-INFO +1 -1
  7. {solixble-3.6.0 → solixble-3.7.0}/pyproject.toml +1 -1
  8. {solixble-3.6.0 → solixble-3.7.0}/tests/test_devices.py +61 -0
  9. {solixble-3.6.0 → solixble-3.7.0}/LICENSE.txt +0 -0
  10. {solixble-3.6.0 → solixble-3.7.0}/README.md +0 -0
  11. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/__init__.py +0 -0
  12. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/const.py +0 -0
  13. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/__init__.py +0 -0
  14. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/c1000.py +0 -0
  15. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/c1000g2.py +0 -0
  16. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/c300.py +0 -0
  17. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/c300dc.py +0 -0
  18. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/c800.py +0 -0
  19. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/f2000.py +0 -0
  20. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/f3800.py +0 -0
  21. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/generic.py +0 -0
  22. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/prime_charger_160w.py +0 -0
  23. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/prime_charger_250w.py +0 -0
  24. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/devices/solarbank3.py +0 -0
  25. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE/utilities.py +0 -0
  26. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE.egg-info/SOURCES.txt +0 -0
  27. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE.egg-info/dependency_links.txt +0 -0
  28. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE.egg-info/requires.txt +0 -0
  29. {solixble-3.6.0 → solixble-3.7.0}/SolixBLE.egg-info/top_level.txt +0 -0
  30. {solixble-3.6.0 → solixble-3.7.0}/setup.cfg +0 -0
  31. {solixble-3.6.0 → solixble-3.7.0}/tests/test_connection.py +0 -0
  32. {solixble-3.6.0 → solixble-3.7.0}/tests/test_prime.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SolixBLE
3
- Version: 3.6.0
3
+ Version: 3.7.0
4
4
  Summary: Python module for monitoring & controlling Bluetooth Anker Solix devices
5
5
  Author-email: Harvey Lelliott <harveylelliott@duck.com>
6
6
  License: MIT License
@@ -62,8 +62,8 @@ class SolixBLEDevice:
62
62
 
63
63
  self._ble_device: BLEDevice = ble_device
64
64
  self._client: BleakClient | None = None
65
- self._telemetry_payload_small: bytes | None = None
66
- self._telemetry_payload_large: bytes | None = None
65
+ self._fragment_buffers: dict[bytes, dict[int, bytes]] = {}
66
+ self._fragment_totals: dict[bytes, int] = {}
67
67
  self._data: dict[str, bytes] | None = None
68
68
  self._last_data_timestamp: datetime | None = None
69
69
  self._last_packet_timestamp: datetime | None = None
@@ -347,11 +347,6 @@ class SolixBLEDevice:
347
347
  # Extract command
348
348
  packet_cmd = bytes([packet_copy.pop(0), packet_copy.pop(0)])
349
349
 
350
- # Telemetry packets have an extra field which must be popped
351
- if packet_pattern.hex() == "03010f" and packet_cmd.hex() == "c402":
352
- special_value = bytes([packet_copy.pop(0)])
353
- _LOGGER.debug(f"Special value: {special_value.hex()}")
354
-
355
350
  # Extract payload
356
351
  packet_payload = bytes(packet_copy)
357
352
 
@@ -498,7 +493,7 @@ class SolixBLEDevice:
498
493
  )
499
494
  return cipher.encrypt(padded_data)
500
495
 
501
- async def _process_telemetry_packet(self, payload: bytes) -> None:
496
+ async def _process_telemetry_packet(self, payload: bytes, cmd: bytes = None) -> None:
502
497
  """Process a telemetry packet from the device.
503
498
 
504
499
  This performs the default processing of telemetry packets in which
@@ -507,40 +502,46 @@ class SolixBLEDevice:
507
502
  telemetry.
508
503
  """
509
504
 
510
- # Anker devices seem to split data across multiple
511
- # packets so we need to wait until we have both
512
- # packets before we can decrypt all of the data
513
- if len(payload) < 50:
514
- self._telemetry_payload_small = payload
515
-
516
- # If we receive a big packet it invalidates the
517
- # last small one since the big one comes before
518
- # the small one
519
- elif len(payload) > 230:
520
- self._telemetry_payload_large = payload
521
- self._telemetry_payload_small = None
505
+ # First byte encodes fragment info (high nibble = index, low = total)
506
+ fragment_index = (payload[0] >> 4) & 0x0F
507
+ fragment_total = payload[0] & 0x0F
522
508
 
523
- else:
524
- _LOGGER.warning(
525
- f"Telemetry payload has an unexpected length of {len(payload)}!"
509
+ # Multi-part message
510
+ if fragment_total > 1:
511
+ fragment_data = payload[1:]
512
+ cmd_key = bytes(cmd)
513
+ _LOGGER.debug(
514
+ f"Fragment {fragment_index}/{fragment_total} for cmd {cmd.hex()}, {len(fragment_data)} bytes"
526
515
  )
527
516
 
528
- if (
529
- self._telemetry_payload_small is None
530
- or self._telemetry_payload_large is None
531
- ):
532
- _LOGGER.debug("Missing other payload!")
533
- return
517
+ # Store fragment
518
+ if cmd_key not in self._fragment_buffers or fragment_index == 1:
519
+ self._fragment_buffers[cmd_key] = {}
520
+ self._fragment_totals[cmd_key] = fragment_total
534
521
 
535
- new_payload = self._telemetry_payload_large + self._telemetry_payload_small
522
+ self._fragment_buffers[cmd_key][fragment_index] = fragment_data
536
523
 
537
- # If we are accepting the new payload we invalidate
538
- # the partial payloads
539
- self._telemetry_payload_large = None
540
- self._telemetry_payload_small = None
524
+ # Wait until all fragments have arrived
525
+ if len(self._fragment_buffers[cmd_key]) < fragment_total:
526
+ _LOGGER.debug("Waiting for remaining fragments...")
527
+ return
541
528
 
542
- _LOGGER.debug(f"Merged payload: {new_payload.hex()}")
543
- decrypted_payload = self._decrypt_payload(new_payload)
529
+ # Reassemble in order
530
+ payload = b"".join(
531
+ self._fragment_buffers[cmd_key][i]
532
+ for i in sorted(self._fragment_buffers[cmd_key])
533
+ )
534
+ del self._fragment_buffers[cmd_key]
535
+ del self._fragment_totals[cmd_key]
536
+ _LOGGER.debug(
537
+ f"Reassembled payload: {len(payload)} bytes"
538
+ )
539
+
540
+ else:
541
+ # Strip fragment info
542
+ payload = payload[1:]
543
+
544
+ decrypted_payload = self._decrypt_payload(payload)
544
545
  _LOGGER.debug(f"Decrypted payload: {decrypted_payload.hex()}")
545
546
  parameters = self._parse_payload(decrypted_payload)
546
547
  return await self._process_telemetry(parameters)
@@ -584,7 +585,7 @@ class SolixBLEDevice:
584
585
  ) -> None:
585
586
  """Process a notification from the device."""
586
587
 
587
- _LOGGER.debug(f"The client the notification is from is: {client}")
588
+ _LOGGER.debug(f"The client the notification is from: {client}")
588
589
 
589
590
  if self._client is not client:
590
591
  _LOGGER.debug("Ignoring notification from old client")
@@ -625,9 +626,9 @@ class SolixBLEDevice:
625
626
  match cmd.hex():
626
627
 
627
628
  # Telemetry messages
628
- case "c402" | "4300":
629
+ case "c402" | "4300" | "c405":
629
630
  _LOGGER.debug("Received telemetry message!")
630
- return await self._process_telemetry_packet(payload)
631
+ return await self._process_telemetry_packet(payload, cmd)
631
632
 
632
633
  # Unknown messages
633
634
  case _:
@@ -1041,8 +1042,8 @@ class SolixBLEDevice:
1041
1042
  self._data = None
1042
1043
  self._last_data_timestamp = None
1043
1044
 
1044
- self._telemetry_payload_small = None
1045
- self._telemetry_payload_large = None
1045
+ self._fragment_buffers = {}
1046
+ self._fragment_totals = {}
1046
1047
  self._shared_secret = None
1047
1048
  self._last_packet_timestamp = None
1048
1049
  self._negotiation_timestamp = None
@@ -4,8 +4,38 @@
4
4
 
5
5
  """
6
6
 
7
- from ..const import DEFAULT_METADATA_FLOAT, DEFAULT_METADATA_STRING
7
+ from enum import Enum
8
+
9
+ from ..const import (
10
+ DEFAULT_METADATA_BOOL,
11
+ DEFAULT_METADATA_FLOAT,
12
+ DEFAULT_METADATA_STRING,
13
+ )
8
14
  from ..device import SolixBLEDevice
15
+ from ..states import GridStatus, LightMode, SBPowerCutoff, SBUsageMode, TemperatureUnit
16
+
17
+
18
+ class MaxLoadSB2(Enum):
19
+ """
20
+ Maximum output power of the Solarbank 2 in watts.
21
+
22
+ Only specific values are allowed.
23
+ """
24
+
25
+ #: The maximum load is unknown.
26
+ UNKNOWN = -1
27
+
28
+ #: 350 watts.
29
+ W350 = 350
30
+
31
+ #: 600 watts.
32
+ W600 = 600
33
+
34
+ #: 800 watts.
35
+ W800 = 800
36
+
37
+ #: 1000 watts.
38
+ W1000 = 1000
9
39
 
10
40
 
11
41
  class Solarbank2(SolixBLEDevice):
@@ -15,10 +45,6 @@ class Solarbank2(SolixBLEDevice):
15
45
  Use this class to connect and monitor a Solarbank 2 power station.
16
46
  This model is also known as the A17C1.
17
47
 
18
- .. note::
19
- This model was added using data from anker-solix-api. It has not been
20
- tested!
21
-
22
48
  .. note::
23
49
  It should be possible to add more sensors. I think devices with lots of
24
50
  telemetry values split them up into multiple messages but I have not
@@ -132,9 +158,9 @@ class Solarbank2(SolixBLEDevice):
132
158
 
133
159
  @property
134
160
  def pv_yield(self) -> float:
135
- """Solar power generated.
161
+ """Solar energy generated in kWh.
136
162
 
137
- :returns: Total solar power generated or default float value.
163
+ :returns: Total solar energy generated or default float value.
138
164
  """
139
165
  if self._data is None:
140
166
  return DEFAULT_METADATA_FLOAT
@@ -143,18 +169,20 @@ class Solarbank2(SolixBLEDevice):
143
169
 
144
170
  @property
145
171
  def charged_energy(self) -> float:
146
- """Energy used to charge the battery?
172
+ """Total accumulated energy that passed through the battery in kWh
147
173
 
148
- :returns: I don't know what this means or default float value.
174
+ :returns: The amount of energy or default float value.
149
175
  """
150
176
  if self._data is None:
151
177
  return DEFAULT_METADATA_FLOAT
152
178
 
153
- return self._parse_int("b2", begin=1) / 10000.0
179
+ # The / 100 000 is correct despite all other divisors being 10 000.
180
+ # This is the "Storage" stats field in the Anker app
181
+ return self._parse_int("b2", begin=1) / 100000.0
154
182
 
155
183
  @property
156
184
  def output_energy(self) -> float:
157
- """Output energy.
185
+ """Output energy in kWh.
158
186
 
159
187
  :returns: Total energy output or default float value.
160
188
  """
@@ -305,3 +333,115 @@ class Solarbank2(SolixBLEDevice):
305
333
  return DEFAULT_METADATA_FLOAT
306
334
 
307
335
  return self._parse_int("d3", begin=1) / 10.0
336
+
337
+ @property
338
+ def error_code(self) -> int:
339
+ """Device error code.
340
+
341
+ :returns: Error code or default int value.
342
+ """
343
+ return self._parse_int("a5", begin=1)
344
+
345
+ @property
346
+ def temperature_unit(self) -> TemperatureUnit:
347
+ """Temperature unit setting.
348
+
349
+ :returns: Temperature unit (Celsius or Fahrenheit).
350
+ """
351
+ return TemperatureUnit(self._parse_int("a9", begin=1))
352
+
353
+ @property
354
+ def output_cutoff_data(self) -> SBPowerCutoff:
355
+ """
356
+ Output cutoff threshold in %.
357
+
358
+ Minimum battery SOC to maintain.
359
+
360
+ :returns: Output cutoff battery SOC threshold.
361
+ """
362
+ return SBPowerCutoff(self._parse_int("b4", begin=1))
363
+
364
+ @property
365
+ def lowpower_input_data(self) -> int:
366
+ """Low power input data.
367
+
368
+ :returns: Low power input data or default int value.
369
+ """
370
+ return self._parse_int("b5", begin=1)
371
+
372
+ @property
373
+ def input_cutoff_data(self) -> SBPowerCutoff:
374
+ """Input cutoff threshold in %.
375
+
376
+ :returns: Input cutoff battery SOC threshold.
377
+ """
378
+ return SBPowerCutoff(self._parse_int("b6", begin=1))
379
+
380
+ @property
381
+ def max_load(self) -> MaxLoadSB2:
382
+ """
383
+ Maximum output power in watts.
384
+
385
+ Maximum legal value depends on country of operation.
386
+
387
+ :returns: Maximum load as a MaxLoadSB2 enum value.
388
+ """
389
+ return MaxLoadSB2(self._parse_int("c2", begin=1))
390
+
391
+ @property
392
+ def usage_mode(self) -> SBUsageMode:
393
+ """Usage mode.
394
+
395
+ :returns: Usage mode as a SBUsageMode enum value.
396
+ """
397
+ return SBUsageMode(self._parse_int("c6", begin=1))
398
+
399
+ @property
400
+ def home_load_preset(self) -> int:
401
+ """Home load preset in watts.
402
+
403
+ :returns: Home load preset in watts or default int value.
404
+ """
405
+ return self._parse_int("c7", begin=1)
406
+
407
+ @property
408
+ def light_mode(self) -> LightMode:
409
+ """Light mode. Normal or Mood.
410
+
411
+ :returns: Light mode.
412
+ """
413
+ return LightMode(self._parse_int("d2", begin=1))
414
+
415
+ @property
416
+ def grid_status(self) -> GridStatus:
417
+ """Grid connection status.
418
+
419
+ :returns: Grid status.
420
+ """
421
+ return GridStatus(self._parse_int("e0", begin=1))
422
+
423
+ @property
424
+ def light_on(self) -> bool | None:
425
+ """Whether the light is switched on.
426
+ Original value is inverted because it is called "light_off_switch"
427
+
428
+ :returns: True if light is on, False if off.
429
+ """
430
+ return (
431
+ not bool(self._parse_int("e1", begin=1))
432
+ if self._data is not None
433
+ else DEFAULT_METADATA_BOOL
434
+ )
435
+
436
+ @property
437
+ def battery_heating(self) -> bool | None:
438
+ """Whether the battery is currently heating.
439
+
440
+ :returns: True if heating, False if not heating.
441
+ """
442
+ return (
443
+ bool(self._parse_int("e8", begin=1))
444
+ if self._data is not None
445
+ else DEFAULT_METADATA_BOOL
446
+ )
447
+
@@ -487,7 +487,7 @@ class PrimeDevice(SolixBLEDevice):
487
487
  # Packet processing #
488
488
  #####################
489
489
 
490
- async def _process_telemetry_packet(self, payload: bytes) -> None:
490
+ async def _process_telemetry_packet(self, payload: bytes, cmd: bytes = None) -> None:
491
491
  """
492
492
  Process a telemetry packet from an Anker Prime device.
493
493
 
@@ -90,6 +90,19 @@ class LightStatus(Enum):
90
90
  SOS = 4
91
91
 
92
92
 
93
+ class LightMode(Enum):
94
+ """The light mode of the device."""
95
+
96
+ #: The light mode is unknown.
97
+ UNKNOWN = -1
98
+
99
+ #: Normal light mode.
100
+ NORMAL = 0
101
+
102
+ #: Mood light mode.
103
+ MOOD = 1
104
+
105
+
93
106
  class DisplayTimeout(Enum):
94
107
  """Display timeout on device in seconds. Only specific values are allowed."""
95
108
 
@@ -123,6 +136,68 @@ class TemperatureUnit(Enum):
123
136
  #: Display unit is Fahrenheit.
124
137
  FAHRENHEIT = 1
125
138
 
139
+ class GridStatus(Enum):
140
+ """The grid connection status."""
141
+
142
+ #: The grid status is unknown.
143
+ UNKNOWN = -1
144
+
145
+ #: Grid is connected and OK.
146
+ OK = 1
147
+
148
+ #: Undocumented in API, but device operates as expected and
149
+ #: outputs power to grid. Maybe a pure "dispense" state because
150
+ #: SB2 can't draw power from the grid
151
+ OK_AS_WELL_I_GUESS = 2
152
+
153
+ #: Grid is connecting.
154
+ CONNECTING = 3
155
+
156
+ #: No grid connection.
157
+ NO_GRID = 6
158
+
159
+
160
+ class SBUsageMode(Enum):
161
+ """Usage mode of a Solarbank device."""
162
+
163
+ #: The usage mode is unknown.
164
+ UNKNOWN = -1
165
+
166
+ #: Manual (schedule) mode.
167
+ MANUAL = 1
168
+
169
+ #: Smart meter mode.
170
+ SMARTMETER = 2
171
+
172
+ #: Smart plugs mode.
173
+ SMARTPLUGS = 3
174
+
175
+ #: Backup mode.
176
+ BACKUP = 4
177
+
178
+ #: Use time mode.
179
+ USE_TIME = 5
180
+
181
+ #: Smart mode.
182
+ SMART = 7
183
+
184
+ #: Time slot mode.
185
+ TIME_SLOT = 8
186
+
187
+
188
+ class SBPowerCutoff(Enum):
189
+ """Power cutoff threshold of a Solarbank device in %."""
190
+
191
+ #: The cutoff threshold is unknown.
192
+ UNKNOWN = -1
193
+
194
+ #: 5 %.
195
+ P5 = 5
196
+
197
+ #: 10 %.
198
+ P10 = 10
199
+
200
+
126
201
  class PortOverload(Enum):
127
202
  """The overload status of a port."""
128
203
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SolixBLE
3
- Version: 3.6.0
3
+ Version: 3.7.0
4
4
  Summary: Python module for monitoring & controlling Bluetooth Anker Solix devices
5
5
  Author-email: Harvey Lelliott <harveylelliott@duck.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "SolixBLE"
3
- version = "3.6.0"
3
+ version = "3.7.0"
4
4
  dependencies = [
5
5
  "bleak>=0.19.0",
6
6
  "cryptography",
@@ -22,9 +22,12 @@ from SolixBLE import (
22
22
  PortStatus,
23
23
  PrimeCharger160w,
24
24
  PrimeDevice,
25
+ Solarbank2,
25
26
  SolixBLEDevice,
26
27
  TemperatureUnit,
27
28
  )
29
+ from SolixBLE.devices.solarbank2 import MaxLoadSB2
30
+ from SolixBLE.states import GridStatus, LightMode, SBPowerCutoff, SBUsageMode
28
31
  from tests.const import (
29
32
  MOCK_BLE_DEVICE,
30
33
  NEGOTIATION_RESPONSES_PRIME,
@@ -614,6 +617,51 @@ from tests.helpers import MockDevice
614
617
  },
615
618
  id="c300_dc_mixed_values",
616
619
  ),
620
+ pytest.param(
621
+ Solarbank2,
622
+ "a10131a2110041504347513830453030303030303030a302013aa4020101a503020000a605030100060aa7050300000631a8050300030306a9020100aa020111ab050300000000ac0503f4010000ad02013aae020100af020100b0050300000000b10503e0bd0200b20503723c0a00b305038d840200b4020105b5020104b6020105b7050388130000b8020101b9020100ba050328000000bb020100bc050300000000bd050300000000be050300000000bf050300000000c0110000000000000000000000000000000000c1020100c203022003c40503f4010000c5020100c6020101c703023200c8050300000000c9050306000000ca050300000000cb050300000000cc050300000000cd050300000000d2020100d30503f4010000d4110000000000000000000000000000000000d503020000d6110000000000000000000000000000000000d703020000d8110000000000000000000000000000000000d903020000da110000000000000000000000000000000000db03020000dc110000000000000000000000000000000000dd03020000de110000000000000000000000000000000000df03020000e0020102e1020101e2020100e3020100e4020100e5020100e6020100e7020100e8020100e9020100ea020101fe05039a46d969fb050300000000fc1604010101010001010101010100000000000000000000",
623
+ {
624
+ "serial_number": "APCGQ80E00000000",
625
+ "battery_percentage": 58,
626
+ "battery_percentage_aggregate": 58,
627
+ "error_code": 0,
628
+ "software_version": "1.6.8.1.6.5.3.7.7",
629
+ "software_version_controller": "8.2.2.4.7.6.8.0.0",
630
+ "software_version_expansion": "1.0.0.8.6.0.6.7.2",
631
+ "temperature_unit": TemperatureUnit.CELSIUS,
632
+ "temperature": 17,
633
+ "solar_power_in": 0.0,
634
+ "solar_pv_1_power_in": 0.0,
635
+ "solar_pv_2_power_in": 0.0,
636
+ "solar_pv_3_power_in": 0.0,
637
+ "solar_pv_4_power_in": 0.0,
638
+ "ac_power_out": 50.0,
639
+ "ac_power_out_sockets": 0.0,
640
+ "battery_charge_power": 0.0,
641
+ "battery_discharge_power": 50.0,
642
+ "pv_yield": 17.968,
643
+ "charged_energy": 6.70834,
644
+ "output_energy": 16.5005,
645
+ "grid_to_home_power": 0.0,
646
+ "pv_to_grid_power": 0.0,
647
+ "grid_import_energy": 0.0,
648
+ "grid_export_energy": 0.0,
649
+ "house_demand": 50.0,
650
+ "consumed_energy": 0.0006,
651
+ "power_out": 50.0,
652
+ "max_load": MaxLoadSB2.W800,
653
+ "output_cutoff_data": SBPowerCutoff.P5,
654
+ "lowpower_input_data": 4,
655
+ "input_cutoff_data": SBPowerCutoff.P5,
656
+ "usage_mode": SBUsageMode.MANUAL,
657
+ "home_load_preset": 50,
658
+ "light_mode": LightMode.NORMAL,
659
+ "grid_status": GridStatus.OK_AS_WELL_I_GUESS,
660
+ "light_on": False,
661
+ "battery_heating": False,
662
+ },
663
+ id="solarbank2_telemetry",
664
+ ),
617
665
  ],
618
666
  )
619
667
  async def test_values(
@@ -692,6 +740,19 @@ async def test_values(
692
740
  "0c4d9db9ef376fcfe627b9b73089eda514315d4bf67fb7eb299f2894ef7a059c",
693
741
  id="c1000_2",
694
742
  ),
743
+ pytest.param(
744
+ Solarbank2,
745
+ [
746
+ "ff090e00030001080100a1010152",
747
+ "ff091b00030001080300a10102a202fd00a30144a40101a50102ff",
748
+ "ff093800030001082900a10103a2054553503332a307302e302e302e33a41041504347513830453030303030303030a50600000000000039",
749
+ "ff090b00030001080500f2",
750
+ "ff094d00030001082100a140f809d676751fba1346f21198c8a583b1ef9b9a617fb804455c388d07090e6dc2976c1bb1cf06aee1f30a3286af9dd80f8f0c594010f60755292addedfe41385972",
751
+ None,
752
+ ],
753
+ "6a2c89888de58cce1e15d98eb22669898ec29bcb1519ce19f950439aac9dbcb5",
754
+ id="solarbank2_1",
755
+ ),
695
756
  ],
696
757
  )
697
758
  async def test_negotiation(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes