twist-innovation-api 0.0.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.
twist/TwistAPI.py ADDED
@@ -0,0 +1,102 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+
15
+ import asyncio
16
+ from typing import Callable
17
+ import random
18
+ import json
19
+ from .TwistTypes import DeviceVariant
20
+ from .TwistDevice import TwistModel, TwistDevice
21
+
22
+
23
+ class TwistAPI:
24
+ def __init__(self, installation_id: int, model_update: Callable[[TwistModel], None]):
25
+ self._ext_publish = None
26
+ self._subscribe = None
27
+ self._callback = model_update
28
+
29
+ self.function_map = {
30
+ "context": self._context_msg,
31
+ "getboard": self._get_board
32
+ }
33
+
34
+ self.device_list: list[TwistDevice] = list()
35
+
36
+ # TODO: this should come from the API
37
+ self.installation_id = installation_id
38
+
39
+ def add_mqtt(self, publisher, subscriber):
40
+ self._ext_publish = publisher
41
+ self._subscribe = subscriber
42
+
43
+ self._subscribe(f"v2/{self.installation_id}/rx/#", self._on_message_received)
44
+
45
+ async def search_models(self):
46
+ self.getboard(0xffffffff)
47
+ await asyncio.sleep(3)
48
+
49
+ model_list: list[TwistModel] = list()
50
+ for device in self.device_list:
51
+ for model in device.model_list:
52
+ model_list.append(model)
53
+
54
+ return model_list
55
+
56
+ def _publish(self, twist_id, opcode, payload: dict | None = None, model_id=None):
57
+ if self._ext_publish is not None:
58
+ topic = f"v2/{self.installation_id}/tx/{twist_id}/{opcode}"
59
+ if payload is None:
60
+ payload = dict()
61
+ payload["key"] = random.randint(1, 65535)
62
+ if model_id is not None:
63
+ topic = f"{topic}/{model_id}"
64
+
65
+ self._ext_publish(topic, json.dumps(payload))
66
+
67
+ def getboard(self, twist_id):
68
+ self._publish(twist_id, "getboard")
69
+
70
+ def activate_event(self, model: TwistModel, data: json):
71
+ self._publish(model.parent_device.twist_id, "activate_event", data, model.model_id)
72
+
73
+ def _parse_topic(self, topic):
74
+ tpc_delim = topic.split('/')
75
+
76
+ model_id = None
77
+ if len(tpc_delim) == 6:
78
+ model_id = int(tpc_delim[5])
79
+
80
+ return {
81
+ "twist_id": int(tpc_delim[3]),
82
+ "opcode": tpc_delim[4],
83
+ "model_id": model_id
84
+ }
85
+
86
+ def _on_message_received(self, topic, payload, qos=None):
87
+ data = self._parse_topic(topic)
88
+ if any(dev.twist_id == data["twist_id"] for dev in self.device_list) or data["opcode"] == "getboard":
89
+ if data["opcode"] in self.function_map:
90
+ self.function_map[data["opcode"]](data["twist_id"], payload, data["model_id"])
91
+
92
+ def _context_msg(self, twist_id, payload, model_id):
93
+ device = next((d for d in self.device_list if d.twist_id == twist_id), None)
94
+ model = device.context_msg(model_id, payload)
95
+
96
+ self._callback(model)
97
+
98
+ def _get_board(self, twist_id, payload, model_id=None):
99
+ data = json.loads(payload)
100
+ if not any(dev.twist_id == twist_id for dev in self.device_list):
101
+ self.device_list.append(TwistDevice(twist_id, data["h"], DeviceVariant(data["v"]), self))
102
+ print(f"New device with id: {twist_id}")
@@ -0,0 +1,30 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from __future__ import annotations
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from TwistDevice import TwistDevice
19
+ from .TwistDevice import TwistModel
20
+
21
+
22
+ class TwistCbShutter(TwistModel):
23
+ def __init__(self, model_id: int, parent_device: TwistDevice):
24
+ super().__init__(model_id, parent_device)
25
+
26
+ def context_msg(self, payload: str):
27
+ self.parse_general_context(payload)
28
+
29
+ def print_context(self):
30
+ print(f"Cb Shutter Device: {self.parent_device.twist_id}, Model: {self.model_id},")
twist/TwistDevice.py ADDED
@@ -0,0 +1,42 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from __future__ import annotations
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from TwistAPI import TwistAPI
19
+
20
+ from .TwistTypes import DeviceVariant
21
+ from .TwistModel import TwistModel
22
+
23
+ from .Variants import model_dict
24
+
25
+ class TwistDevice:
26
+ def __init__(self, twist_id: int, hw: int, var: DeviceVariant, api: TwistAPI):
27
+ self.twist_id = twist_id
28
+ self.hw = hw
29
+ self.var = var
30
+ self.api = api
31
+
32
+ self.model_list: list[TwistModel] = list()
33
+
34
+ model_id = 0
35
+ if self.var in model_dict:
36
+ for model in model_dict[self.var]:
37
+ self.model_list.append(model(model_id, self))
38
+ model_id += 1
39
+
40
+ def context_msg(self, model_id: int, payload: str):
41
+ self.model_list[model_id].context_msg(payload)
42
+ return self.model_list[model_id]
twist/TwistLight.py ADDED
@@ -0,0 +1,85 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from __future__ import annotations
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from TwistDevice import TwistDevice
19
+ from .TwistTypes import ContextErrors
20
+ from .TwistDevice import TwistModel
21
+ from enum import Enum
22
+
23
+
24
+ class TwistLight(TwistModel):
25
+ class EventIndexes(Enum):
26
+ SET = 0
27
+ CLEAR = 1
28
+ VALUE = 2
29
+ TOGGLE = 3
30
+ VALUE_FADING = 4
31
+
32
+ def __init__(self, model_id: int, parent_device: TwistDevice):
33
+ super().__init__(model_id, parent_device)
34
+ self.operating_time = 0
35
+ self.current = 0
36
+
37
+ def turn_on(self):
38
+ self._activate_event(TwistLight.EventIndexes.SET)
39
+
40
+ def turn_off(self):
41
+ self._activate_event(TwistLight.EventIndexes.CLEAR)
42
+
43
+ def toggle(self):
44
+ self._activate_event(TwistLight.EventIndexes.TOGGLE)
45
+
46
+ def set_value(self, value: int, fading_time: int | None = None):
47
+ if fading_time is None:
48
+ self._activate_event(TwistLight.EventIndexes.VALUE, value)
49
+ else:
50
+ self._activate_event(TwistLight.EventIndexes.VALUE_FADING, value, fading_time)
51
+
52
+ def _activate_event(self, index: TwistLight.EventIndexes, value: int | None = None, fading_time: int | None = None):
53
+ data = {
54
+ "i": index.value
55
+ }
56
+
57
+ if value is None:
58
+ data["vl"] = []
59
+ elif fading_time is None:
60
+ data["vl"] = [value]
61
+ else:
62
+ data["vl"] = [value, fading_time]
63
+
64
+ self.parent_device.api.activate_event(self, data)
65
+
66
+ def context_msg(self, payload: str):
67
+ data = self.parse_general_context(payload)
68
+
69
+ for ctx in data["cl"]:
70
+ index, value = self._get_value_from_context(ctx)
71
+ if index < ContextErrors.MAX.value:
72
+ if ContextErrors(index) == ContextErrors.ACTUAL:
73
+ self.actual_state = round(value[0] / 655.35, 0)
74
+ elif ContextErrors(index) == ContextErrors.REQUESTED:
75
+ self.requested_state = round(value[0] / 655.35, 0)
76
+ else:
77
+ if index == 6:
78
+ self.operating_time = value[0]
79
+ elif index == 7:
80
+ self.current = value[0]
81
+
82
+ def print_context(self):
83
+ print(
84
+ f"Light Device: {self.parent_device.twist_id}, Model: {self.model_id}, Actual: {self.actual_state}, "
85
+ f"Requested: {self.requested_state}, Operating: {self.operating_time}, Current: {self.current}")
twist/TwistLouvre.py ADDED
@@ -0,0 +1,89 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from __future__ import annotations
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from TwistDevice import TwistDevice
19
+ from .TwistTypes import ContextErrors
20
+ from .TwistDevice import TwistModel
21
+ from enum import Enum
22
+
23
+
24
+ class TwistLouvre(TwistModel):
25
+ class EventIndexes(Enum):
26
+ OPEN = 0
27
+ STOP = 1
28
+ CLOSE = 2
29
+ TOGGLE = 3
30
+ VALUE = 4
31
+
32
+ def __init__(self, model_id: int, parent_device: TwistDevice):
33
+ super().__init__(model_id, parent_device)
34
+ self.operating_time = 0
35
+ self.current = 0
36
+
37
+ def context_msg(self, payload: str):
38
+ data = self.parse_general_context(payload)
39
+
40
+ for ctx in data["cl"]:
41
+ index, value = self._get_value_from_context(ctx)
42
+ if index < ContextErrors.MAX.value:
43
+ if ContextErrors(index) == ContextErrors.ACTUAL:
44
+ self.actual_state = round(value[0] / 655.35, 0)
45
+ elif ContextErrors(index) == ContextErrors.REQUESTED:
46
+ self.requested_state = round(value[0] / 655.35, 0)
47
+ else:
48
+ if index == 6:
49
+ self.operating_time = value[0]
50
+ elif index == 7:
51
+ self.current = value[0]
52
+
53
+ def open(self):
54
+ self._activate_event(TwistLouvre.EventIndexes.OPEN)
55
+
56
+ def stop(self):
57
+ self._activate_event(TwistLouvre.EventIndexes.STOP)
58
+
59
+ def close(self):
60
+ self._activate_event(TwistLouvre.EventIndexes.CLOSE)
61
+
62
+ def toggle(self):
63
+ self._activate_event(TwistLouvre.EventIndexes.TOGGLE)
64
+
65
+ def set_value(self, value: int, fading_time: int | None = None):
66
+ if fading_time is not None:
67
+ raise NotImplementedError("Fading time can't be used in this model")
68
+ else:
69
+ self._activate_event(TwistLouvre.EventIndexes.VALUE, value)
70
+
71
+ def _activate_event(self, index: TwistLouvre.EventIndexes, value: int | None = None,
72
+ fading_time: int | None = None):
73
+ data = {
74
+ "i": index.value
75
+ }
76
+
77
+ if value is None:
78
+ data["vl"] = []
79
+ elif fading_time is None:
80
+ data["vl"] = [value]
81
+ else:
82
+ data["vl"] = [value, fading_time]
83
+
84
+ self.parent_device.api.activate_event(self, data)
85
+
86
+ def print_context(self):
87
+ print(
88
+ f"Louvre Device: {self.parent_device.twist_id}, Model: {self.model_id}, Actual: {self.actual_state}, "
89
+ f"Requested: {self.requested_state}, Operating: {self.operating_time}, Current: {self.current}")
twist/TwistModel.py ADDED
@@ -0,0 +1,82 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from __future__ import annotations
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from TwistDevice import TwistDevice
19
+
20
+ import json
21
+ from .TwistTypes import ContextErrors
22
+
23
+
24
+ class TwistModel():
25
+ def __init__(self, model_id: int, parent_device: TwistDevice):
26
+ self.actual_state: int = 0
27
+ self.requested_state: int = 0
28
+ self.model_id: int = model_id
29
+ self.parent_device = parent_device
30
+ self.errors = {
31
+ "error": None,
32
+ "warning": None,
33
+ "info": None,
34
+ "prio": None
35
+ }
36
+
37
+ def print_context(self):
38
+ print("function is not supported")
39
+
40
+ def parse_general_context(self, payload: str):
41
+ data = json.loads(payload)
42
+
43
+ for ctx in data["cl"]:
44
+ index, value = self._get_value_from_context(ctx)
45
+ if index >= ContextErrors.MAX.value:
46
+ pass
47
+ elif ContextErrors(index) == ContextErrors.ERROR:
48
+ self.errors["error"] = value
49
+ elif ContextErrors(index) == ContextErrors.WARNING:
50
+ self.errors["warning"] = value
51
+ elif ContextErrors(index) == ContextErrors.INFO:
52
+ self.errors["info"] = value
53
+ elif ContextErrors(index) == ContextErrors.PRIO:
54
+ self.errors["prio"] = value
55
+ return data
56
+
57
+ def _get_value_from_context(self, ctx: dict):
58
+ return ctx["i"], ctx["vl"]
59
+
60
+ def context_msg(self, payload):
61
+ raise NotImplementedError("Function not supported for this model")
62
+
63
+ def turn_on(self):
64
+ raise NotImplementedError("Function not supported for this model")
65
+
66
+ def turn_off(self):
67
+ raise NotImplementedError("Function not supported for this model")
68
+
69
+ def open(self):
70
+ raise NotImplementedError("Function not supported for this model")
71
+
72
+ def stop(self):
73
+ raise NotImplementedError("Function not supported for this model")
74
+
75
+ def close(self):
76
+ raise NotImplementedError("Function not supported for this model")
77
+
78
+ def toggle(self):
79
+ raise NotImplementedError("Function not supported for this model")
80
+
81
+ def set_value(self, value: int | list[int, int] | list[int, int, int], fading_time: int | None = None):
82
+ raise NotImplementedError("Function not supported for this model")
twist/TwistRgb.py ADDED
@@ -0,0 +1,96 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from __future__ import annotations
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from TwistDevice import TwistDevice
19
+
20
+ from .TwistTypes import ContextErrors
21
+ from .TwistDevice import TwistModel
22
+ from enum import Enum
23
+
24
+
25
+ class TwistRgb(TwistModel):
26
+ class EventIndexes(Enum):
27
+ SET = 0
28
+ CLEAR = 1
29
+ VALUE = 2
30
+ TOGGLE = 3
31
+ VALUE_FADING = 4
32
+
33
+ def __init__(self, model_id: int, parent_device: TwistDevice):
34
+ super().__init__(model_id, parent_device)
35
+
36
+ self.actual_h = 0
37
+ self.actual_s = 0
38
+ self.actual_v = 0
39
+ self.requested_h = 0
40
+ self.requested_s = 0
41
+ self.requested_v = 0
42
+
43
+ self.operating_time = 0
44
+
45
+ def turn_on(self):
46
+ self._activate_event(TwistRgb.EventIndexes.SET)
47
+
48
+ def turn_off(self):
49
+ self._activate_event(TwistRgb.EventIndexes.CLEAR)
50
+
51
+ def toggle(self):
52
+ self._activate_event(TwistRgb.EventIndexes.TOGGLE)
53
+
54
+ def set_value(self, value: list[int, int, int], fading_time: int | None = None):
55
+ if fading_time is None:
56
+ self._activate_event(TwistRgb.EventIndexes.VALUE, value)
57
+ else:
58
+ self._activate_event(TwistRgb.EventIndexes.VALUE_FADING, value, fading_time)
59
+
60
+ def _activate_event(self, index: TwistRgb.EventIndexes, value: list[int, int, int] | None = None,
61
+ fading_time: int | None = None):
62
+ data = {
63
+ "i": index.value
64
+ }
65
+
66
+ if value is None:
67
+ data["vl"] = []
68
+ elif fading_time is None:
69
+ data["vl"] = value
70
+ else:
71
+ value.append(fading_time)
72
+ data["vl"] = value
73
+
74
+ self.parent_device.api.activate_event(self, data)
75
+
76
+ def context_msg(self, payload: str):
77
+ data = self.parse_general_context(payload)
78
+
79
+ for ctx in data["cl"]:
80
+ index, value = self._get_value_from_context(ctx)
81
+ if index < ContextErrors.MAX.value:
82
+ if ContextErrors(index) == ContextErrors.ACTUAL:
83
+ self.actual_h = round(value[0] / 655.35, 0)
84
+ self.actual_s = round(value[1] / 655.35, 0)
85
+ self.actual_v = round(value[2] / 655.35, 0)
86
+ elif ContextErrors(index) == ContextErrors.REQUESTED:
87
+ self.requested_h = round(value[0] / 655.35, 0)
88
+ self.requested_s = round(value[0] / 655.35, 0)
89
+ self.requested_v = round(value[0] / 655.35, 0)
90
+ else:
91
+ if index == 6:
92
+ self.operating_time = value[0]
93
+
94
+ def print_context(self):
95
+ print(f"Rgb Device: {self.parent_device.twist_id}, Model: {self.model_id}, "
96
+ f"Actual: h{self.actual_h} s{self.actual_s} v{self.actual_v}")
twist/TwistSensor.py ADDED
@@ -0,0 +1,31 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from __future__ import annotations
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from TwistDevice import TwistDevice
19
+
20
+ from .TwistDevice import TwistModel
21
+
22
+
23
+ class TwistSensor(TwistModel):
24
+ def __init__(self, model_id: int, parent_device: TwistDevice):
25
+ super().__init__(model_id, parent_device)
26
+
27
+ def context_msg(self, payload: str):
28
+ self.parse_general_context(payload)
29
+
30
+ def print_context(self):
31
+ print(f"Sensor Device: {self.parent_device.twist_id}, Model: {self.model_id}")
twist/TwistTypes.py ADDED
@@ -0,0 +1,72 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from enum import Enum
15
+
16
+ class DeviceVariant(Enum):
17
+ NO_VARIANT = 0xFFFF
18
+ NONE = 0x0000
19
+ LED_4_BUTTON_4 = 0x0001
20
+ LED_1_BUTTON_1 = 0x0002
21
+ MONO_LIGHT_4 = 0x0003
22
+ MONO_LIGHT_2 = 0x0004
23
+ TEMP_1_HUM_1_PIR_1_LUX_1_VOC_1 = 0x0005
24
+ MONO_LIGHT_1_BUTTON_1 = 0x0006
25
+ RGB_1_TUNABLE_WHITE_1 = 0x0007
26
+ BUTTON_8 = 0x0008
27
+ MONO_LIGHT_2_LUX2BRIGHTNESS_1 = 0x0009
28
+ RGB_1_MONO_LIGHT_2 = 0x000A
29
+ LUX_1_UV_1_TEMP_1_HUM_1_WINDSPEED_1_GUSTSPEED_1_WINDDIR_1_RAINFALL_1_PRES_1 = 0x000B
30
+ RGB_1 = 0x000C
31
+ BUTTON_6_LUX_6 = 0x00D2 # Corrected 0x000D2 to 0x00D2
32
+ BUTTON_4 = 0x000E
33
+ TUNABLE_WHITE_2 = 0x000F
34
+ WIND_1_LUX_1_RAIN_1 = 0x0010
35
+ BUTTON_1 = 0x0011
36
+ PULSE_CONTACT_2 = 0x0012
37
+ TBSHUTTER_1 = 0x0013
38
+ MONO_LIGHT_32 = 0x0142
39
+ REPEATER_1 = 0x0015
40
+ REPEATER_1_LED_1 = 0x0016
41
+ LED_12 = 0x0172
42
+ BUTTON_12 = 0x0182
43
+ CBSHUTTER_1_VOLTAGE_1_CURRENT_1 = 0x0019
44
+ TBSHUTTER_6 = 0x001A
45
+ CBSHUTTER_1_MONO_LIGHT_1 = 0x001B
46
+ CBSHUTTER_1 = 0x001C
47
+ MONO_LIGHT_1 = 0x001D
48
+ HEATER_1 = 0x001E
49
+ PBSHUTTER_1 = 0x001F
50
+ CBSHUTTER_2_MONO_LIGHT_4_TEMPERATURE_1 = 0x0020
51
+ GATEWAY_1_WEATHER_1_TEMPERATURE_1_WIND_1_RAIN_1 = 0x0021
52
+ CBSHUTTER_3 = 0x0022
53
+ BUTTON_10 = 0x0023
54
+ MATRIX_1 = 0x0024
55
+ RAIN_1 = 0x0025
56
+ RAIN_1_TEMPERATURE_1_WIND_1_WEATHER_1 = 0x0026
57
+ LOUVRE_2_MONO_LIGHT_4_TEMPERATURE_1 = 0x0027
58
+ LOUVRE_2_MONO_LIGHT_4 = 0x0028
59
+
60
+
61
+ class Models(Enum):
62
+ LOUVRE = 0
63
+ MONO_LIGHT = 1
64
+
65
+ class ContextErrors(Enum):
66
+ ERROR = 0
67
+ WARNING = 1
68
+ INFO = 2
69
+ PRIO = 3
70
+ ACTUAL = 4
71
+ REQUESTED = 5
72
+ MAX = 6
twist/Variants.py ADDED
@@ -0,0 +1,29 @@
1
+ # Copyright (C) 2025 Twist Innovation
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ #
11
+ # See the GNU General Public License for more details:
12
+ # https://www.gnu.org/licenses/gpl-3.0.html
13
+
14
+ from .TwistLight import TwistLight
15
+ from .TwistLouvre import TwistLouvre
16
+ from .TwistRgb import TwistRgb
17
+ from .TwistSensor import TwistSensor
18
+ from .TwistCbShutter import TwistCbShutter
19
+ from .TwistTypes import DeviceVariant
20
+
21
+ model_dict = {
22
+ DeviceVariant.LOUVRE_2_MONO_LIGHT_4: [TwistLouvre, TwistLouvre, TwistLight, TwistLight, TwistLight,
23
+ TwistLight],
24
+ DeviceVariant.RGB_1: [TwistRgb],
25
+ DeviceVariant.MONO_LIGHT_4: [TwistLight, TwistLight, TwistLight, TwistLight],
26
+ DeviceVariant.GATEWAY_1_WEATHER_1_TEMPERATURE_1_WIND_1_RAIN_1: [TwistSensor, TwistSensor, TwistSensor,
27
+ TwistSensor, TwistSensor],
28
+ DeviceVariant.CBSHUTTER_1_MONO_LIGHT_1: [TwistCbShutter, TwistLight],
29
+ }
twist/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """
2
+ Twist Innovation API Library
3
+ A Python library to interact with Twist Innovation devices
4
+ """
5
+ from .TwistModel import TwistModel
6
+ from .TwistAPI import TwistAPI
7
+
8
+ __all__ = ["TwistAPI", "TwistModel"]
9
+