matter-python-client 0.5.16a0.dev20260410__tar.gz → 0.6.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.
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/PKG-INFO +8 -2
- matter_python_client-0.6.0/chip/ChipUtility.py +8 -0
- matter_python_client-0.6.0/chip/clusters/ClusterObjects.py +351 -0
- matter_python_client-0.6.0/chip/clusters/Objects.py +157 -0
- matter_python_client-0.6.0/chip/clusters/Types.py +32 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/AccessControl.py +516 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/AccountLogin.py +236 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Actions.py +564 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ActivatedCarbonFilterMonitoring.py +310 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/AdministratorCommissioning.py +281 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/AirQuality.py +175 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ApplicationBasic.py +305 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ApplicationLauncher.py +285 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/AudioOutput.py +240 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/BasicInformation.py +711 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Binding.py +173 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/BooleanState.py +173 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/BooleanStateConfiguration.py +367 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/BridgedDeviceBasicInformation.py +753 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/CarbonDioxideConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/CarbonMonoxideConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Channel.py +546 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ColorControl.py +1628 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/CommissionerControl.py +249 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ContentAppObserver.py +184 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ContentControl.py +765 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ContentLauncher.py +416 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Descriptor.py +262 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/DeviceEnergyManagement.py +755 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/DeviceEnergyManagementMode.py +308 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/DiagnosticLogs.py +210 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/DishwasherAlarm.py +278 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/DishwasherMode.py +307 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/DoorLock.py +2331 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/DraftElectricalMeasurementCluster.py +297 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/EcosystemInformation.py +228 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ElectricalEnergyMeasurement.py +356 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ElectricalPowerMeasurement.py +586 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/EnergyEvse.py +955 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/EnergyEvseMode.py +308 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/EnergyPreference.py +256 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/EthernetNetworkDiagnostics.py +335 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/EveCluster.py +495 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/FanControl.py +436 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/FixedLabel.py +167 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/FlowMeasurement.py +207 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/FormaldehydeConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/GeneralCommissioning.py +501 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/GeneralDiagnostics.py +585 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Globals.py +316 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/GroupKeyManagement.py +387 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Groups.py +334 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/HeimanCluster.py +278 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/HepaFilterMonitoring.py +310 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/IcdManagement.py +471 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Identify.py +241 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/IlluminanceMeasurement.py +235 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/InovelliCluster.py +189 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/JointFabricAdministrator.py +333 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/JointFabricDatastore.py +1048 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/KeypadInput.py +277 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/LaundryDryerControls.py +183 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/LaundryWasherControls.py +224 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/LaundryWasherMode.py +308 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/LevelControl.py +605 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/LocalizationConfiguration.py +171 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/LowPower.py +150 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/MediaInput.py +276 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/MediaPlayback.py +700 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Messages.py +363 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/MicrowaveOvenControl.py +344 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/MicrowaveOvenMode.py +306 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ModeSelect.py +294 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/NeoCluster.py +207 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/NetworkCommissioning.py +622 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/NitrogenDioxideConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OccupancySensing.py +454 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OnOff.py +368 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OperationalCredentials.py +586 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OperationalState.py +412 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OtaSoftwareUpdateProvider.py +283 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OtaSoftwareUpdateRequestor.py +361 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OvenCavityOperationalState.py +412 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OvenMode.py +314 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/OzoneConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Pm10ConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Pm1ConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Pm25ConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/PowerSource.py +1001 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/PowerSourceConfiguration.py +153 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/PowerTopology.py +178 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/PressureMeasurement.py +301 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/PumpConfigurationAndControl.py +903 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/RadonConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/RefrigeratorAlarm.py +273 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/RefrigeratorAndTemperatureControlledCabinetMode.py +306 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/RelativeHumidityMeasurement.py +207 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/RvcCleanMode.py +305 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/RvcOperationalState.py +448 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/RvcRunMode.py +311 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ScenesManagement.py +579 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ServiceArea.py +425 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/SmokeCoAlarm.py +653 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/SoftwareDiagnostics.py +270 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Switch.py +336 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/TargetNavigator.py +257 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/TemperatureControl.py +268 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/TemperatureMeasurement.py +207 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/Thermostat.py +1740 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ThermostatUserInterfaceConfiguration.py +221 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ThirdRealityMeteringCluster.py +207 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ThreadBorderRouterManagement.py +334 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ThreadNetworkDiagnostics.py +1516 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ThreadNetworkDirectory.py +280 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/TimeFormatLocalization.py +224 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/TimeSynchronization.py +685 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/TotalVolatileOrganicCompoundsConcentrationMeasurement.py +380 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/UnitLocalization.py +186 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/UserLabel.py +167 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/ValveConfigurationAndControl.py +439 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/WakeOnLan.py +171 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/WaterHeaterManagement.py +355 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/WaterHeaterMode.py +307 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/WaterTankLevelMonitoring.py +310 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/WiFiNetworkDiagnostics.py +497 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/WiFiNetworkManagement.py +202 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/WindowCovering.py +835 -0
- matter_python_client-0.6.0/chip/clusters/cluster_defs/__init__.py +250 -0
- matter_python_client-0.6.0/chip/clusters/enum.py +50 -0
- matter_python_client-0.6.0/chip/tlv/__init__.py +392 -0
- matter_python_client-0.6.0/chip/tlv/tlvlist.py +79 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_python_client.egg-info/PKG-INFO +8 -2
- matter_python_client-0.6.0/matter_python_client.egg-info/SOURCES.txt +161 -0
- matter_python_client-0.6.0/matter_python_client.egg-info/requires.txt +14 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_python_client.egg-info/top_level.txt +1 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/client/client.py +11 -19
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/client/connection.py +7 -6
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/client/models/device_types.py +499 -643
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/client/models/node.py +1 -2
- matter_python_client-0.6.0/matter_server/common/custom_clusters.py +27 -0
- matter_python_client-0.6.0/matter_server/common/helpers/__init__.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/common/helpers/api.py +2 -5
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/common/helpers/json.py +2 -1
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/common/helpers/util.py +5 -5
- matter_python_client-0.6.0/matter_server/py.typed +0 -0
- matter_python_client-0.6.0/pyproject.toml +262 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/tests/test_client_integration.py +3 -6
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/tests/test_imports.py +46 -6
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/tests/test_integration.py +64 -9
- matter_python_client-0.5.16a0.dev20260410/matter_python_client.egg-info/SOURCES.txt +0 -29
- matter_python_client-0.5.16a0.dev20260410/matter_python_client.egg-info/requires.txt +0 -8
- matter_python_client-0.5.16a0.dev20260410/matter_server/common/custom_clusters.py +0 -2148
- matter_python_client-0.5.16a0.dev20260410/pyproject.toml +0 -61
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/README.md +0 -0
- {matter_python_client-0.5.16a0.dev20260410/matter_server/common/helpers → matter_python_client-0.6.0/chip}/__init__.py +0 -0
- /matter_python_client-0.5.16a0.dev20260410/matter_server/py.typed → /matter_python_client-0.6.0/chip/clusters/__init__.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_python_client.egg-info/dependency_links.txt +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_python_client.egg-info/not-zip-safe +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/__init__.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/client/__init__.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/client/exceptions.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/client/models/__init__.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/common/__init__.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/common/const.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/common/errors.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/matter_server/common/models.py +0 -0
- {matter_python_client-0.5.16a0.dev20260410 → matter_python_client-0.6.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matter-python-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Python Client for the OHF Matter Server
|
|
5
5
|
Author-email: Open Home Foundation <hello@openhomefoundation.io>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -17,12 +17,18 @@ Classifier: Topic :: Home Automation
|
|
|
17
17
|
Requires-Python: >=3.12
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
Requires-Dist: aiohttp
|
|
20
|
+
Requires-Dist: dacite
|
|
20
21
|
Requires-Dist: orjson
|
|
21
|
-
Requires-Dist: home-assistant-chip-clusters==2025.7.0
|
|
22
22
|
Provides-Extra: test
|
|
23
|
+
Requires-Dist: codespell==2.4.1; extra == "test"
|
|
24
|
+
Requires-Dist: isort==7.0.0; extra == "test"
|
|
25
|
+
Requires-Dist: mypy==1.19.1; extra == "test"
|
|
26
|
+
Requires-Dist: pylint==4.0.4; extra == "test"
|
|
23
27
|
Requires-Dist: pytest>=9.0; extra == "test"
|
|
24
28
|
Requires-Dist: pytest-asyncio>=0.24; extra == "test"
|
|
25
29
|
Requires-Dist: pytest-aiohttp>=1.0; extra == "test"
|
|
30
|
+
Requires-Dist: pytest-cov>=7.0; extra == "test"
|
|
31
|
+
Requires-Dist: ruff==0.14.9; extra == "test"
|
|
26
32
|
|
|
27
33
|
# Python Client for the OHF Matter Server
|
|
28
34
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Minimal reimplementation of chip.ChipUtility for matter-python-client."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class classproperty(property):
|
|
5
|
+
"""Descriptor that works like @classmethod + @property combined."""
|
|
6
|
+
|
|
7
|
+
def __get__(self, obj, owner):
|
|
8
|
+
return classmethod(self.fget).__get__(None, owner)()
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""Base classes for Matter cluster objects and global registration dictionaries.
|
|
2
|
+
|
|
3
|
+
Provides the infrastructure that generated cluster code (Objects.py) and
|
|
4
|
+
custom_clusters.py build upon.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import enum
|
|
8
|
+
import typing
|
|
9
|
+
from dataclasses import asdict, dataclass, field, make_dataclass
|
|
10
|
+
from typing import Any, ClassVar, Dict, List, Mapping, Union
|
|
11
|
+
|
|
12
|
+
from .. import ChipUtility
|
|
13
|
+
from .. import tlv
|
|
14
|
+
from ..clusters.Types import Nullable, NullValue
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def GetUnionUnderlyingType(typeToCheck, matchingType=None):
|
|
18
|
+
"""Retrieve the underlying type from a Union type annotation."""
|
|
19
|
+
if not (typing.get_origin(typeToCheck) == typing.Union):
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
for t in typing.get_args(typeToCheck):
|
|
23
|
+
if matchingType is None:
|
|
24
|
+
type_none = type(None)
|
|
25
|
+
if t != type_none and t != Nullable:
|
|
26
|
+
return t
|
|
27
|
+
else:
|
|
28
|
+
if t == matchingType:
|
|
29
|
+
return t
|
|
30
|
+
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ClusterObjectFieldDescriptor:
|
|
36
|
+
Label: str = ""
|
|
37
|
+
Tag: typing.Optional[int] = None
|
|
38
|
+
Type: type = type(None)
|
|
39
|
+
|
|
40
|
+
def _PutSingleElementToTLV(
|
|
41
|
+
self, tag, val, elementType, writer: tlv.TLVWriter, debugPath: str = "?"
|
|
42
|
+
):
|
|
43
|
+
if issubclass(elementType, ClusterObject):
|
|
44
|
+
if not isinstance(val, dict):
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"Field {debugPath}.{self.Label} expected a struct, but got {type(val)}"
|
|
47
|
+
)
|
|
48
|
+
elementType.descriptor.DictToTLVWithWriter(
|
|
49
|
+
f"{debugPath}.{self.Label}", tag, val, writer
|
|
50
|
+
)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
val = elementType(val)
|
|
55
|
+
except Exception:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"Field {debugPath}.{self.Label} expected {elementType}, but got {type(val)}"
|
|
58
|
+
)
|
|
59
|
+
writer.put(tag, val)
|
|
60
|
+
|
|
61
|
+
def PutFieldToTLV(
|
|
62
|
+
self, tag, val, writer: tlv.TLVWriter, debugPath: str = "?"
|
|
63
|
+
):
|
|
64
|
+
if val == NullValue:
|
|
65
|
+
if GetUnionUnderlyingType(self.Type, Nullable) is None:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Field {debugPath}.{self.Label} was not nullable, but got a null"
|
|
68
|
+
)
|
|
69
|
+
writer.put(tag, None)
|
|
70
|
+
elif val is None:
|
|
71
|
+
if GetUnionUnderlyingType(self.Type, type(None)) is None:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Field {debugPath}.{self.Label} was not optional, but encountered None"
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
elementType = GetUnionUnderlyingType(self.Type)
|
|
77
|
+
if elementType is None:
|
|
78
|
+
elementType = self.Type
|
|
79
|
+
|
|
80
|
+
if not isinstance(val, List):
|
|
81
|
+
self._PutSingleElementToTLV(tag, val, elementType, writer, debugPath)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
writer.startArray(tag)
|
|
85
|
+
(elementType,) = typing.get_args(elementType)
|
|
86
|
+
for i, v in enumerate(val):
|
|
87
|
+
self._PutSingleElementToTLV(
|
|
88
|
+
None, v, elementType, writer, debugPath + f"[{i}]"
|
|
89
|
+
)
|
|
90
|
+
writer.endContainer()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class ClusterObjectDescriptor:
|
|
95
|
+
Fields: List[ClusterObjectFieldDescriptor]
|
|
96
|
+
|
|
97
|
+
def GetFieldByTag(self, tag: int) -> typing.Optional[ClusterObjectFieldDescriptor]:
|
|
98
|
+
for _field in self.Fields:
|
|
99
|
+
if _field.Tag == tag:
|
|
100
|
+
return _field
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
def GetFieldByLabel(
|
|
104
|
+
self, label: str
|
|
105
|
+
) -> typing.Optional[ClusterObjectFieldDescriptor]:
|
|
106
|
+
for _field in self.Fields:
|
|
107
|
+
if _field.Label == label:
|
|
108
|
+
return _field
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
def _ConvertNonArray(self, debugPath: str, elementType, value: Any) -> Any:
|
|
112
|
+
if not issubclass(elementType, ClusterObject):
|
|
113
|
+
if issubclass(elementType, enum.Enum):
|
|
114
|
+
value = elementType(value)
|
|
115
|
+
if not isinstance(value, elementType):
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"Failed to decode field {debugPath}, expected type {elementType}, got {type(value)}"
|
|
118
|
+
)
|
|
119
|
+
return value
|
|
120
|
+
if not isinstance(value, Mapping):
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"Failed to decode field {debugPath}, struct expected."
|
|
123
|
+
)
|
|
124
|
+
return elementType.descriptor.TagDictToLabelDict(debugPath, value)
|
|
125
|
+
|
|
126
|
+
def TagDictToLabelDict(
|
|
127
|
+
self, debugPath: str, tlvData: Dict[int, Any]
|
|
128
|
+
) -> Dict[str, Any]:
|
|
129
|
+
ret: typing.Dict[Any, Any] = {}
|
|
130
|
+
for tag, value in tlvData.items():
|
|
131
|
+
descriptor = self.GetFieldByTag(tag)
|
|
132
|
+
if not descriptor:
|
|
133
|
+
ret[tag] = value
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
if value is None:
|
|
137
|
+
ret[descriptor.Label] = NullValue
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
if typing.get_origin(descriptor.Type) == typing.Union:
|
|
141
|
+
realType = GetUnionUnderlyingType(descriptor.Type)
|
|
142
|
+
if realType is None:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"Field {debugPath}.{descriptor.Label} has no valid underlying data model type"
|
|
145
|
+
)
|
|
146
|
+
valueType = realType
|
|
147
|
+
else:
|
|
148
|
+
valueType = descriptor.Type
|
|
149
|
+
|
|
150
|
+
if typing.get_origin(valueType) == list:
|
|
151
|
+
listElementType = typing.get_args(valueType)[0]
|
|
152
|
+
ret[descriptor.Label] = [
|
|
153
|
+
self._ConvertNonArray(
|
|
154
|
+
f"{debugPath}[{i}]", listElementType, v
|
|
155
|
+
)
|
|
156
|
+
for i, v in enumerate(value)
|
|
157
|
+
]
|
|
158
|
+
continue
|
|
159
|
+
ret[descriptor.Label] = self._ConvertNonArray(
|
|
160
|
+
f"{debugPath}.{descriptor.Label}", valueType, value
|
|
161
|
+
)
|
|
162
|
+
return ret
|
|
163
|
+
|
|
164
|
+
def TLVToDict(self, tlvBuf: bytes) -> Dict[str, Any]:
|
|
165
|
+
tlvData = tlv.TLVReader(tlvBuf).get().get("Any", {})
|
|
166
|
+
return self.TagDictToLabelDict("", tlvData)
|
|
167
|
+
|
|
168
|
+
def DictToTLVWithWriter(
|
|
169
|
+
self, debugPath: str, tag, data: Mapping, writer: tlv.TLVWriter
|
|
170
|
+
):
|
|
171
|
+
writer.startStructure(tag)
|
|
172
|
+
for _field in self.Fields:
|
|
173
|
+
val = data.get(_field.Label, None)
|
|
174
|
+
_field.PutFieldToTLV(
|
|
175
|
+
_field.Tag, val, writer, debugPath + f".{_field.Label}"
|
|
176
|
+
)
|
|
177
|
+
writer.endContainer()
|
|
178
|
+
|
|
179
|
+
def DictToTLV(self, data: dict) -> bytes:
|
|
180
|
+
tlvwriter = tlv.TLVWriter(bytearray())
|
|
181
|
+
self.DictToTLVWithWriter("", None, data, tlvwriter)
|
|
182
|
+
return bytes(tlvwriter.encoding)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ClusterObject:
|
|
186
|
+
def ToTLV(self):
|
|
187
|
+
return self.descriptor.DictToTLV(asdict(self))
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def FromDict(cls, data: dict):
|
|
191
|
+
from dacite import from_dict
|
|
192
|
+
|
|
193
|
+
return from_dict(data_class=cls, data=data)
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def FromTLV(cls, data: bytes):
|
|
197
|
+
return cls.FromDict(data=cls.descriptor.TLVToDict(data))
|
|
198
|
+
|
|
199
|
+
@ChipUtility.classproperty
|
|
200
|
+
def descriptor(cls):
|
|
201
|
+
raise NotImplementedError()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# Global registration dictionaries populated via __init_subclass__
|
|
205
|
+
ALL_CLUSTERS: typing.Dict = {}
|
|
206
|
+
ALL_ATTRIBUTES: typing.Dict = {}
|
|
207
|
+
ALL_ACCEPTED_COMMANDS: typing.Dict = {}
|
|
208
|
+
ALL_GENERATED_COMMANDS: typing.Dict = {}
|
|
209
|
+
ALL_EVENTS: typing.Dict = {}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class ClusterCommand(ClusterObject):
|
|
213
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
214
|
+
super().__init_subclass__(*args, **kwargs)
|
|
215
|
+
try:
|
|
216
|
+
if cls.is_client:
|
|
217
|
+
if cls.cluster_id not in ALL_ACCEPTED_COMMANDS:
|
|
218
|
+
ALL_ACCEPTED_COMMANDS[cls.cluster_id] = {}
|
|
219
|
+
ALL_ACCEPTED_COMMANDS[cls.cluster_id][cls.command_id] = cls
|
|
220
|
+
else:
|
|
221
|
+
if cls.cluster_id not in ALL_GENERATED_COMMANDS:
|
|
222
|
+
ALL_GENERATED_COMMANDS[cls.cluster_id] = {}
|
|
223
|
+
ALL_GENERATED_COMMANDS[cls.cluster_id][cls.command_id] = cls
|
|
224
|
+
except NotImplementedError:
|
|
225
|
+
# handle case where the ClusterAttribute class is not (fully) subclassed
|
|
226
|
+
# and accessing the id property throws a NotImplementedError.
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
@ChipUtility.classproperty
|
|
230
|
+
def cluster_id(cls) -> int:
|
|
231
|
+
raise NotImplementedError()
|
|
232
|
+
|
|
233
|
+
@ChipUtility.classproperty
|
|
234
|
+
def command_id(cls) -> int:
|
|
235
|
+
raise NotImplementedError()
|
|
236
|
+
|
|
237
|
+
@ChipUtility.classproperty
|
|
238
|
+
def is_client(cls) -> bool:
|
|
239
|
+
raise NotImplementedError()
|
|
240
|
+
|
|
241
|
+
@ChipUtility.classproperty
|
|
242
|
+
def must_use_timed_invoke(cls) -> bool:
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class Cluster(ClusterObject):
|
|
247
|
+
id: Any
|
|
248
|
+
|
|
249
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
250
|
+
super().__init_subclass__(*args, **kwargs)
|
|
251
|
+
ALL_CLUSTERS[cls.id] = cls
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def data_version(self) -> int:
|
|
255
|
+
return self._data_version
|
|
256
|
+
|
|
257
|
+
def SetDataVersion(self, version: int) -> None:
|
|
258
|
+
self._data_version = version
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class ClusterAttributeDescriptor:
|
|
262
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
263
|
+
super().__init_subclass__(*args, **kwargs)
|
|
264
|
+
if cls.standard_attribute:
|
|
265
|
+
if cls.cluster_id not in ALL_ATTRIBUTES:
|
|
266
|
+
ALL_ATTRIBUTES[cls.cluster_id] = {}
|
|
267
|
+
ALL_ATTRIBUTES[cls.cluster_id][cls.attribute_id] = cls
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def ToTLV(cls, tag: Union[int, None], value):
|
|
271
|
+
writer = tlv.TLVWriter()
|
|
272
|
+
wrapped_value = cls._cluster_object(Value=value)
|
|
273
|
+
cls.attribute_type.PutFieldToTLV(
|
|
274
|
+
tag, asdict(wrapped_value)["Value"], writer, ""
|
|
275
|
+
)
|
|
276
|
+
return writer.encoding
|
|
277
|
+
|
|
278
|
+
@classmethod
|
|
279
|
+
def FromTLV(cls, tlvBuffer: bytes):
|
|
280
|
+
obj_class = cls._cluster_object
|
|
281
|
+
return obj_class.FromDict(
|
|
282
|
+
obj_class.descriptor.TagDictToLabelDict(
|
|
283
|
+
"", {0: tlv.TLVReader(tlvBuffer).get().get("Any", {})}
|
|
284
|
+
)
|
|
285
|
+
).Value
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
def FromTagDictOrRawValue(cls, val: Any):
|
|
289
|
+
obj_class = cls._cluster_object
|
|
290
|
+
return obj_class.FromDict(
|
|
291
|
+
obj_class.descriptor.TagDictToLabelDict("", {0: val})
|
|
292
|
+
).Value
|
|
293
|
+
|
|
294
|
+
@ChipUtility.classproperty
|
|
295
|
+
def cluster_id(cls) -> int:
|
|
296
|
+
raise NotImplementedError()
|
|
297
|
+
|
|
298
|
+
@ChipUtility.classproperty
|
|
299
|
+
def attribute_id(cls) -> int:
|
|
300
|
+
raise NotImplementedError()
|
|
301
|
+
|
|
302
|
+
@ChipUtility.classproperty
|
|
303
|
+
def attribute_type(cls) -> ClusterObjectFieldDescriptor:
|
|
304
|
+
raise NotImplementedError()
|
|
305
|
+
|
|
306
|
+
@ChipUtility.classproperty
|
|
307
|
+
def must_use_timed_write(cls) -> bool:
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
@ChipUtility.classproperty
|
|
311
|
+
def standard_attribute(cls) -> bool:
|
|
312
|
+
return True
|
|
313
|
+
|
|
314
|
+
@ChipUtility.classproperty
|
|
315
|
+
def _cluster_object(cls) -> ClusterObject:
|
|
316
|
+
return make_dataclass(
|
|
317
|
+
"InternalClass",
|
|
318
|
+
[
|
|
319
|
+
("Value", cls.attribute_type.Type, field(default=None)),
|
|
320
|
+
(
|
|
321
|
+
"descriptor",
|
|
322
|
+
ClassVar[ClusterObjectDescriptor],
|
|
323
|
+
field(
|
|
324
|
+
default=ClusterObjectDescriptor(
|
|
325
|
+
Fields=[
|
|
326
|
+
ClusterObjectFieldDescriptor(
|
|
327
|
+
Label="Value", Tag=0, Type=cls.attribute_type.Type
|
|
328
|
+
)
|
|
329
|
+
]
|
|
330
|
+
)
|
|
331
|
+
),
|
|
332
|
+
),
|
|
333
|
+
],
|
|
334
|
+
bases=(ClusterObject,),
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class ClusterEvent(ClusterObject):
|
|
339
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
340
|
+
super().__init_subclass__(*args, **kwargs)
|
|
341
|
+
if cls.cluster_id not in ALL_EVENTS:
|
|
342
|
+
ALL_EVENTS[cls.cluster_id] = {}
|
|
343
|
+
ALL_EVENTS[cls.cluster_id][cls.event_id] = cls
|
|
344
|
+
|
|
345
|
+
@ChipUtility.classproperty
|
|
346
|
+
def cluster_id(cls) -> int:
|
|
347
|
+
raise NotImplementedError()
|
|
348
|
+
|
|
349
|
+
@ChipUtility.classproperty
|
|
350
|
+
def event_id(cls) -> int:
|
|
351
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cluster object definitions.
|
|
3
|
+
This file is auto-generated, DO NOT edit.
|
|
4
|
+
Users can import chip.clusters.Objects to get all cluster definitions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Re-export all cluster classes from per-cluster files
|
|
8
|
+
from chip.clusters.cluster_defs import * # noqa: F401,F403
|
|
9
|
+
|
|
10
|
+
# Also re-export base classes and primitive types for backward compatibility
|
|
11
|
+
from chip.clusters.ClusterObjects import ( # noqa: F401
|
|
12
|
+
Cluster,
|
|
13
|
+
ClusterAttributeDescriptor,
|
|
14
|
+
ClusterCommand,
|
|
15
|
+
ClusterEvent,
|
|
16
|
+
ClusterObject,
|
|
17
|
+
ClusterObjectDescriptor,
|
|
18
|
+
ClusterObjectFieldDescriptor,
|
|
19
|
+
)
|
|
20
|
+
from chip.clusters.Types import NullValue, Nullable # noqa: F401
|
|
21
|
+
from chip.tlv import float32, uint # noqa: F401
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Cluster",
|
|
25
|
+
"ClusterAttributeDescriptor",
|
|
26
|
+
"ClusterCommand",
|
|
27
|
+
"ClusterEvent",
|
|
28
|
+
"ClusterObject",
|
|
29
|
+
"ClusterObjectDescriptor",
|
|
30
|
+
"ClusterObjectFieldDescriptor",
|
|
31
|
+
"NullValue",
|
|
32
|
+
"Nullable",
|
|
33
|
+
"float32",
|
|
34
|
+
"uint",
|
|
35
|
+
"Globals",
|
|
36
|
+
"AccessControl",
|
|
37
|
+
"AccountLogin",
|
|
38
|
+
"Actions",
|
|
39
|
+
"ActivatedCarbonFilterMonitoring",
|
|
40
|
+
"AdministratorCommissioning",
|
|
41
|
+
"AirQuality",
|
|
42
|
+
"ApplicationBasic",
|
|
43
|
+
"ApplicationLauncher",
|
|
44
|
+
"AudioOutput",
|
|
45
|
+
"BasicInformation",
|
|
46
|
+
"Binding",
|
|
47
|
+
"BooleanState",
|
|
48
|
+
"BooleanStateConfiguration",
|
|
49
|
+
"BridgedDeviceBasicInformation",
|
|
50
|
+
"CarbonDioxideConcentrationMeasurement",
|
|
51
|
+
"CarbonMonoxideConcentrationMeasurement",
|
|
52
|
+
"Channel",
|
|
53
|
+
"ColorControl",
|
|
54
|
+
"CommissionerControl",
|
|
55
|
+
"ContentAppObserver",
|
|
56
|
+
"ContentControl",
|
|
57
|
+
"ContentLauncher",
|
|
58
|
+
"Descriptor",
|
|
59
|
+
"DeviceEnergyManagement",
|
|
60
|
+
"DeviceEnergyManagementMode",
|
|
61
|
+
"DiagnosticLogs",
|
|
62
|
+
"DishwasherAlarm",
|
|
63
|
+
"DishwasherMode",
|
|
64
|
+
"DoorLock",
|
|
65
|
+
"DraftElectricalMeasurementCluster",
|
|
66
|
+
"EcosystemInformation",
|
|
67
|
+
"ElectricalEnergyMeasurement",
|
|
68
|
+
"ElectricalPowerMeasurement",
|
|
69
|
+
"EnergyEvse",
|
|
70
|
+
"EnergyEvseMode",
|
|
71
|
+
"EnergyPreference",
|
|
72
|
+
"EthernetNetworkDiagnostics",
|
|
73
|
+
"EveCluster",
|
|
74
|
+
"FanControl",
|
|
75
|
+
"FixedLabel",
|
|
76
|
+
"FlowMeasurement",
|
|
77
|
+
"FormaldehydeConcentrationMeasurement",
|
|
78
|
+
"GeneralCommissioning",
|
|
79
|
+
"GeneralDiagnostics",
|
|
80
|
+
"GroupKeyManagement",
|
|
81
|
+
"Groups",
|
|
82
|
+
"HeimanCluster",
|
|
83
|
+
"HepaFilterMonitoring",
|
|
84
|
+
"IcdManagement",
|
|
85
|
+
"Identify",
|
|
86
|
+
"IlluminanceMeasurement",
|
|
87
|
+
"InovelliCluster",
|
|
88
|
+
"JointFabricAdministrator",
|
|
89
|
+
"JointFabricDatastore",
|
|
90
|
+
"KeypadInput",
|
|
91
|
+
"LaundryDryerControls",
|
|
92
|
+
"LaundryWasherControls",
|
|
93
|
+
"LaundryWasherMode",
|
|
94
|
+
"LevelControl",
|
|
95
|
+
"LocalizationConfiguration",
|
|
96
|
+
"LowPower",
|
|
97
|
+
"MediaInput",
|
|
98
|
+
"MediaPlayback",
|
|
99
|
+
"Messages",
|
|
100
|
+
"MicrowaveOvenControl",
|
|
101
|
+
"MicrowaveOvenMode",
|
|
102
|
+
"ModeSelect",
|
|
103
|
+
"NeoCluster",
|
|
104
|
+
"NetworkCommissioning",
|
|
105
|
+
"NitrogenDioxideConcentrationMeasurement",
|
|
106
|
+
"OccupancySensing",
|
|
107
|
+
"OnOff",
|
|
108
|
+
"OperationalCredentials",
|
|
109
|
+
"OperationalState",
|
|
110
|
+
"OtaSoftwareUpdateProvider",
|
|
111
|
+
"OtaSoftwareUpdateRequestor",
|
|
112
|
+
"OvenCavityOperationalState",
|
|
113
|
+
"OvenMode",
|
|
114
|
+
"OzoneConcentrationMeasurement",
|
|
115
|
+
"Pm10ConcentrationMeasurement",
|
|
116
|
+
"Pm1ConcentrationMeasurement",
|
|
117
|
+
"Pm25ConcentrationMeasurement",
|
|
118
|
+
"PowerSource",
|
|
119
|
+
"PowerSourceConfiguration",
|
|
120
|
+
"PowerTopology",
|
|
121
|
+
"PressureMeasurement",
|
|
122
|
+
"PumpConfigurationAndControl",
|
|
123
|
+
"RadonConcentrationMeasurement",
|
|
124
|
+
"RefrigeratorAlarm",
|
|
125
|
+
"RefrigeratorAndTemperatureControlledCabinetMode",
|
|
126
|
+
"RelativeHumidityMeasurement",
|
|
127
|
+
"RvcCleanMode",
|
|
128
|
+
"RvcOperationalState",
|
|
129
|
+
"RvcRunMode",
|
|
130
|
+
"ScenesManagement",
|
|
131
|
+
"ServiceArea",
|
|
132
|
+
"SmokeCoAlarm",
|
|
133
|
+
"SoftwareDiagnostics",
|
|
134
|
+
"Switch",
|
|
135
|
+
"TargetNavigator",
|
|
136
|
+
"TemperatureControl",
|
|
137
|
+
"TemperatureMeasurement",
|
|
138
|
+
"Thermostat",
|
|
139
|
+
"ThermostatUserInterfaceConfiguration",
|
|
140
|
+
"ThirdRealityMeteringCluster",
|
|
141
|
+
"ThreadBorderRouterManagement",
|
|
142
|
+
"ThreadNetworkDiagnostics",
|
|
143
|
+
"ThreadNetworkDirectory",
|
|
144
|
+
"TimeFormatLocalization",
|
|
145
|
+
"TimeSynchronization",
|
|
146
|
+
"TotalVolatileOrganicCompoundsConcentrationMeasurement",
|
|
147
|
+
"UnitLocalization",
|
|
148
|
+
"UserLabel",
|
|
149
|
+
"ValveConfigurationAndControl",
|
|
150
|
+
"WakeOnLan",
|
|
151
|
+
"WaterHeaterManagement",
|
|
152
|
+
"WaterHeaterMode",
|
|
153
|
+
"WaterTankLevelMonitoring",
|
|
154
|
+
"WiFiNetworkDiagnostics",
|
|
155
|
+
"WiFiNetworkManagement",
|
|
156
|
+
"WindowCovering",
|
|
157
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Nullable type and NullValue singleton for Matter data model."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Nullable:
|
|
5
|
+
def __repr__(self):
|
|
6
|
+
return "Null"
|
|
7
|
+
|
|
8
|
+
def __eq__(self, other):
|
|
9
|
+
if isinstance(other, Nullable):
|
|
10
|
+
return True
|
|
11
|
+
return False
|
|
12
|
+
|
|
13
|
+
def __ne__(self, other):
|
|
14
|
+
return not self.__eq__(other)
|
|
15
|
+
|
|
16
|
+
def __lt__(self, other):
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
def __le__(self, other):
|
|
20
|
+
return True # self < other or self == other, both are always True for this sentinel
|
|
21
|
+
|
|
22
|
+
def __gt__(self, other):
|
|
23
|
+
return False # negation of __le__
|
|
24
|
+
|
|
25
|
+
def __ge__(self, other):
|
|
26
|
+
return isinstance(other, Nullable) # True when compared to another Nullable, False otherwise
|
|
27
|
+
|
|
28
|
+
def __hash__(self):
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
NullValue = Nullable()
|