From c07e74729081ed4f21b3c5e085c325a2b04794f1 Mon Sep 17 00:00:00 2001 From: terratec <989319+terratec@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:32:46 +0100 Subject: [PATCH] Add C300X DC --- SolixBLE.py | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) diff --git a/SolixBLE.py b/SolixBLE.py index 81f7d39..5f91b6a 100644 --- a/SolixBLE.py +++ b/SolixBLE.py @@ -107,6 +107,35 @@ class LightStatus(Enum): #: The light is on high. HIGH = 3 +class OverloadStatus(Enum): + """The overload status of a port on the device.""" + + #: Overload status is unknown. + UNKNOWN = -1 + + #: No overload status. + NO_OVERLOAD = 0 + + #: Overload for port C1 + C1_OVERLOAD = 8 + + #: Overload for port C2 + C2_OVERLOAD = 9 + + #: Overload for port C3 + C3_OVERLOAD = 10 + +class TemperatureUnit(Enum): + """The temperature unit.""" + + #: The status of the temperature unit is unknown. + UNKNOWN = -1 + + #: The temperature unit is Celsius + CELSIUS = 0 + + #: The temperature unit is Fahrenheit + FAHRENHEIT = 1 class SolixBLEDevice: """Solix BLE device object.""" @@ -324,6 +353,24 @@ def _parse_int(self, index: int) -> int: """ return int.from_bytes(self._data[index : index + 2], byteorder="little") + def _parse_int32(self, index: int) -> int: + """Parse a 32-bit integer at the index in the telemetry bytes. + + :param index: Index of 32-bit integer in array. + :returns: 32-bit integer. + :raises IndexError: If index is out of range. + """ + return int.from_bytes(self._data[index : index + 4], byteorder="little") + + def _parse_str(self, index: int, length: int) -> str: + """Parse a string with length at the index in the telemetry bytes. + + :param index: Index of 16-bit integer in array. + :param length: length of string in array. + :raises IndexError: If index or length is out of range. + """ + return self._data[index : index + length].decode("ascii") + def _parse_telemetry(self, data: bytearray) -> None: """Update internal values using the telemetry data. @@ -870,6 +917,330 @@ def battery_percentage(self) -> int: return self._data[169] if self._data is not None else DEFAULT_METADATA_INT +class C300DC(SolixBLEDevice): + + _EXPECTED_TELEMETRY_LENGTH: int = 252 + + @property + def car_socket_timeout(self) -> int: + """Car Socket Timeout. + + :returns: Car socket timeout in seconds or default int value. + """ + return self._parse_int32(15) if self._data is not None else DEFAULT_METADATA_INT + + @property + def hours_remaining(self) -> float: + """Time remaining to full/empty. + + Note that any hours over 24 are overflowed to the + days remaining. Use time_remaining if you want + days to be included. + + :returns: Hours remaining or default float value. + """ + return ( + self._data[22] / 10.0 if self._data is not None else DEFAULT_METADATA_FLOAT + ) + + @property + def days_remaining(self) -> int: + """Time remaining to full/empty. + + :returns: Days remaining or default int value. + """ + return self._data[23] if self._data is not None else DEFAULT_METADATA_INT + + @property + def time_remaining(self) -> float: + """Time remaining to full/empty. + + This includes any hours which were overflowed + into days. + + :returns: Hours remaining or default float value. + """ + if ( + self.hours_remaining == DEFAULT_METADATA_FLOAT + or self.days_remaining == DEFAULT_METADATA_INT + ): + return DEFAULT_METADATA_FLOAT + + return (self.days_remaining * 24) + self.hours_remaining + + @property + def usb_c1_power(self) -> int: + """USB C1 Power. + + :returns: USB port C1 power or default int value. + """ + return self._parse_int(27) if self._data is not None else DEFAULT_METADATA_INT + + @property + def usb_c2_power(self) -> int: + """USB C2 Power. + + :returns: USB port C2 power or default int value. + """ + return self._parse_int(32) if self._data is not None else DEFAULT_METADATA_INT + + @property + def usb_c3_power(self) -> int: + """USB C3 Power. + + :returns: USB port C3 power or default int value. + """ + return self._parse_int(37) if self._data is not None else DEFAULT_METADATA_INT + + @property + def usb_c4_power(self) -> int: + """USB C4 Power. + + :returns: USB port C4 power or default int value. + """ + return self._parse_int(42) if self._data is not None else DEFAULT_METADATA_INT + + @property + def usb_a1_power(self) -> int: + """USB A1 Power. + + :returns: USB port A1 power or default int value. + """ + return self._parse_int(47) if self._data is not None else DEFAULT_METADATA_INT + + @property + def usb_a2_power(self) -> int: + """USB A2 Power. + + :returns: USB port A2 power or default int value. + """ + return self._parse_int(52) if self._data is not None else DEFAULT_METADATA_INT + + @property + def car_socket_power(self) -> int: + """Car Socket Power. + + :returns: Car socket power or default int value. + """ + return self._parse_int(57) if self._data is not None else DEFAULT_METADATA_INT + + @property + def solar_power_in(self) -> int: + """Solar Power In. + + :returns: Total solar power in or default int value. + """ + return self._parse_int(62) if self._data is not None else DEFAULT_METADATA_INT + + @property + def power_in(self) -> int: + """Total Power In. + + :returns: Total power in or default int value. + """ + return self._parse_int(67) if self._data is not None else DEFAULT_METADATA_INT + + @property + def power_out(self) -> int: + """Total Power Out. + + :returns: Total power out or default int value. + """ + return self._parse_int(72) if self._data is not None else DEFAULT_METADATA_INT + + @property + def battery_temperature(self) -> int: + """Battery Temperature Celsius. + + :returns: Battery temperature celsius or default int value. + """ + return self._data[112] if self._data is not None else DEFAULT_METADATA_INT + + @property + def total_in_out(self) -> PortStatus: + """Total Power In/Out Status. + + :returns: Status of the total power in/out. + """ + return PortStatus( + self._data[116] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def battery_percentage(self) -> int: + """Battery Percentage. + + :returns: Percentage charge of battery or default int value. + """ + return self._data[120] if self._data is not None else DEFAULT_METADATA_INT + + @property + def usb_port_c1(self) -> PortStatus: + """USB C1 Port Status. + + :returns: Status of the USB C1 port. + """ + return PortStatus( + self._data[128] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def usb_port_c2(self) -> PortStatus: + """USB C2 Port Status. + + :returns: Status of the USB C2 port. + """ + return PortStatus( + self._data[132] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def usb_port_c3(self) -> PortStatus: + """USB C3 Port Status. + + :returns: Status of the USB C3 port. + """ + return PortStatus( + self._data[136] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def usb_port_c4(self) -> PortStatus: + """USB C4 Port Status. + + :returns: Status of the USB C4 port. + """ + return PortStatus( + self._data[140] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def usb_port_a1(self) -> PortStatus: + """USB A1 Port Status. + + :returns: Status of the USB A1 port. + """ + return PortStatus( + self._data[144] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def usb_port_a2(self) -> PortStatus: + """USB A2 Port Status. + + :returns: Status of the USB A2 port. + """ + return PortStatus( + self._data[148] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def car_socket_port(self) -> PortStatus: + """Car Socket Port Status. + + :returns: Status of the Car socket port. + """ + return PortStatus( + self._data[152] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def port_overload(self) -> PortStatus: + """Port Overload Status. + + :returns: Overload Status of a port. + """ + return OverloadStatus( + self._data[160] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def serial_number(self) -> str: + """Serial Number. + + :returns: Serial number as string or default string value. + """ + return self._parse_str(168, 16) if self._data is not None else DEFAULT_METADATA_STRING + + @property + def device_standby(self) -> int: + """Device Standby Setting. + + :returns: Configured device standby in minutes or default int value. + """ + return self._parse_int(187) if self._data is not None else DEFAULT_METADATA_INT + + @property + def display_timeout(self) -> int: + """Display Timeout Setting. + + :returns: Configured display timeout in seconds or default int value. + """ + return self._parse_int(192) if self._data is not None else DEFAULT_METADATA_INT + + @property + def display_brightness(self) -> int: + """Display Brightness Setting. + + :returns: Configured display brightness or default int value. + """ + return self._data[202] if self._data is not None else DEFAULT_METADATA_INT + + @property + def light_status(self) -> int: + """Light Status. + + :returns: Status of the light or default int value. + """ + return LightStatus( + self._data[206] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def temperature_unit(self) -> int: + """Temperature Unit Setting. + + :returns: Configured temperature unit or default int value. + """ + return TemperatureUnit( + self._data[210] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def display_status(self) -> int: + """Display Status. + + :returns: Status of the display or default int value. + """ + return self._data[214] if self._data is not None else DEFAULT_METADATA_INT + + @property + def light_timeout(self) -> int: + """Light Timeout Setting. + + :returns: Configured light timeout in minutes or default int value. + """ + return self._parse_int(218) if self._data is not None else DEFAULT_METADATA_INT + + @property + def solar_in_port(self) -> PortStatus: + """Solar In Port Status. + + :returns: Status of the Solar In port. + """ + return PortStatus( + self._data[227] if self._data is not None else DEFAULT_METADATA_INT + ) + + @property + def car_socket_auto_standby_mode(self) -> int: + """Car socket auto standby mode. + + :returns: Car socket auto standby mode or default int value. + """ + return self._data[231] if self._data is not None else DEFAULT_METADATA_INT + + class Generic(SolixBLEDevice): """ Generic to be used for adding support for an unsupported device.