twist-innovation-api 0.0.1__tar.gz → 0.0.3__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 (21) hide show
  1. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/PKG-INFO +55 -25
  2. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/README.md +54 -24
  3. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/setup.py +2 -3
  4. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistAPI.py +18 -21
  5. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistCbShutter.py +4 -1
  6. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistDevice.py +2 -3
  7. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistLight.py +16 -12
  8. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistLouvre.py +18 -14
  9. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistModel.py +28 -8
  10. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistRgb.py +19 -14
  11. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistSensor.py +4 -1
  12. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist_innovation_api.egg-info/PKG-INFO +55 -25
  13. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/LICENSE +0 -0
  14. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/pyproject.toml +0 -0
  15. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/setup.cfg +0 -0
  16. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/TwistTypes.py +0 -0
  17. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/Variants.py +0 -0
  18. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist/__init__.py +0 -0
  19. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist_innovation_api.egg-info/SOURCES.txt +0 -0
  20. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist_innovation_api.egg-info/dependency_links.txt +0 -0
  21. {twist_innovation_api-0.0.1 → twist_innovation_api-0.0.3}/twist_innovation_api.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: twist-innovation-api
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: Python library to talk to the twist-innovation api
5
5
  Home-page: https://github.com/twist-innovation/twist-innovation-api
6
6
  Author: Sibrecht Goudsmedt
@@ -79,46 +79,76 @@ twine upload dist/*
79
79
  Import the package in your Python scripts:
80
80
 
81
81
  ```python
82
- from twist import TwistAPI, TwistModel
83
-
84
- # Other includes for this example
85
82
  import asyncio
86
- from typing import Callable
83
+ import yaml
84
+ from typing import Callable, Awaitable
85
+ import aiomqtt
87
86
 
88
- # Initialize the mqtt broker here and define the functions needed
87
+ from twist import TwistAPI, TwistModel
89
88
 
90
- # Create a callback function
91
- callback_f: Callable[[str, str], None] | None = None
89
+ # Load configuration
90
+ with open("config.yaml", 'r') as file:
91
+ config = yaml.safe_load(file)
92
92
 
93
- # Callback function when a Mqtt message is received from the broker and forward it to the twist API
94
- def on_message(client, userdata, msg):
95
- if callback_f is not None:
96
- callback_f(msg.topic, msg.payload.decode())
93
+ mqtt_broker = config["mqtt_broker"]
94
+ mqtt_user = config["mqtt_user"]
95
+ mqtt_pass = config["mqtt_pass"]
96
+ mqtt_port = config["mqtt_port"]
97
97
 
98
- # Publish message to the broker
99
- def mqtt_publish(topic, payload):
98
+ async def _noop(topic: str, payload: str):
100
99
  pass
101
100
 
102
- # Subscribe to a topic from the broker
103
- def mqtt_subscribe(topic, callback):
104
- global callback_f
105
- callback_f = callback
101
+ callback_f :Callable[[str, str], Awaitable[None]] = _noop
106
102
 
107
- # Callback function when a model received an update
108
- def on_model_update(model: TwistModel):
103
+
104
+ async def on_model_update(model: TwistModel):
109
105
  model.print_context()
110
106
 
107
+
111
108
  async def main():
112
- twist_api = TwistAPI(8, on_model_update)
113
- twist_api.add_mqtt(mqtt_publish, mqtt_subscribe)
114
- twist_model_list: list[TwistModel] = await twist_api.search_models()
109
+ global callback_f
110
+
111
+ async with aiomqtt.Client(
112
+ hostname=mqtt_broker,
113
+ port=mqtt_port,
114
+ username=mqtt_user,
115
+ password=mqtt_pass
116
+ ) as mqtt_client:
115
117
 
116
- while True:
117
- await asyncio.sleep(1)
118
+ # Async publish method
119
+ async def mqtt_publish(topic: str, payload: str):
120
+ await mqtt_client.publish(topic, payload)
121
+
122
+ # Async subscribe method
123
+ async def mqtt_subscribe(topic: str, callback: Callable[[str, str], None]):
124
+ global callback_f
125
+ callback_f = callback
126
+
127
+ async def listen():
128
+ await mqtt_client.subscribe(topic)
129
+ async for message in mqtt_client.messages:
130
+ assert callback_f is not None
131
+ await callback_f(message.topic.value, message.payload.decode())
132
+
133
+ asyncio.create_task(listen())
134
+
135
+ # Initialize Twist API with async methods
136
+ twist_api = TwistAPI(8)
137
+ await twist_api.add_mqtt(mqtt_publish, mqtt_subscribe)
138
+
139
+ twist_model_list: list[TwistModel] = await twist_api.search_models()
140
+
141
+ for model in twist_model_list:
142
+ model.register_update_cb(on_model_update)
143
+ print(f"{type(model)} has Model id: {model.model_id}, Device id: {model.parent_device.twist_id}")
144
+
145
+ while True:
146
+ await asyncio.sleep(1)
118
147
 
119
148
 
120
149
  if __name__ == "__main__":
121
150
  asyncio.run(main())
151
+
122
152
  ```
123
153
 
124
154
  ## License
@@ -56,46 +56,76 @@ twine upload dist/*
56
56
  Import the package in your Python scripts:
57
57
 
58
58
  ```python
59
- from twist import TwistAPI, TwistModel
60
-
61
- # Other includes for this example
62
59
  import asyncio
63
- from typing import Callable
60
+ import yaml
61
+ from typing import Callable, Awaitable
62
+ import aiomqtt
64
63
 
65
- # Initialize the mqtt broker here and define the functions needed
64
+ from twist import TwistAPI, TwistModel
66
65
 
67
- # Create a callback function
68
- callback_f: Callable[[str, str], None] | None = None
66
+ # Load configuration
67
+ with open("config.yaml", 'r') as file:
68
+ config = yaml.safe_load(file)
69
69
 
70
- # Callback function when a Mqtt message is received from the broker and forward it to the twist API
71
- def on_message(client, userdata, msg):
72
- if callback_f is not None:
73
- callback_f(msg.topic, msg.payload.decode())
70
+ mqtt_broker = config["mqtt_broker"]
71
+ mqtt_user = config["mqtt_user"]
72
+ mqtt_pass = config["mqtt_pass"]
73
+ mqtt_port = config["mqtt_port"]
74
74
 
75
- # Publish message to the broker
76
- def mqtt_publish(topic, payload):
75
+ async def _noop(topic: str, payload: str):
77
76
  pass
78
77
 
79
- # Subscribe to a topic from the broker
80
- def mqtt_subscribe(topic, callback):
81
- global callback_f
82
- callback_f = callback
78
+ callback_f :Callable[[str, str], Awaitable[None]] = _noop
83
79
 
84
- # Callback function when a model received an update
85
- def on_model_update(model: TwistModel):
80
+
81
+ async def on_model_update(model: TwistModel):
86
82
  model.print_context()
87
83
 
84
+
88
85
  async def main():
89
- twist_api = TwistAPI(8, on_model_update)
90
- twist_api.add_mqtt(mqtt_publish, mqtt_subscribe)
91
- twist_model_list: list[TwistModel] = await twist_api.search_models()
86
+ global callback_f
87
+
88
+ async with aiomqtt.Client(
89
+ hostname=mqtt_broker,
90
+ port=mqtt_port,
91
+ username=mqtt_user,
92
+ password=mqtt_pass
93
+ ) as mqtt_client:
92
94
 
93
- while True:
94
- await asyncio.sleep(1)
95
+ # Async publish method
96
+ async def mqtt_publish(topic: str, payload: str):
97
+ await mqtt_client.publish(topic, payload)
98
+
99
+ # Async subscribe method
100
+ async def mqtt_subscribe(topic: str, callback: Callable[[str, str], None]):
101
+ global callback_f
102
+ callback_f = callback
103
+
104
+ async def listen():
105
+ await mqtt_client.subscribe(topic)
106
+ async for message in mqtt_client.messages:
107
+ assert callback_f is not None
108
+ await callback_f(message.topic.value, message.payload.decode())
109
+
110
+ asyncio.create_task(listen())
111
+
112
+ # Initialize Twist API with async methods
113
+ twist_api = TwistAPI(8)
114
+ await twist_api.add_mqtt(mqtt_publish, mqtt_subscribe)
115
+
116
+ twist_model_list: list[TwistModel] = await twist_api.search_models()
117
+
118
+ for model in twist_model_list:
119
+ model.register_update_cb(on_model_update)
120
+ print(f"{type(model)} has Model id: {model.model_id}, Device id: {model.parent_device.twist_id}")
121
+
122
+ while True:
123
+ await asyncio.sleep(1)
95
124
 
96
125
 
97
126
  if __name__ == "__main__":
98
127
  asyncio.run(main())
128
+
99
129
  ```
100
130
 
101
131
  ## License
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="twist-innovation-api",
5
- version="0.0.1",
5
+ version="0.0.3",
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
8
  # "requests" # For REST API
@@ -19,5 +19,4 @@ setup(
19
19
  "Operating System :: OS Independent",
20
20
  ],
21
21
  python_requires=">=3.7",
22
- )
23
-
22
+ )
@@ -13,18 +13,17 @@
13
13
 
14
14
 
15
15
  import asyncio
16
- from typing import Callable
17
- import random
18
16
  import json
17
+ import random
18
+
19
+ from .TwistDevice import TwistDevice, TwistModel
19
20
  from .TwistTypes import DeviceVariant
20
- from .TwistDevice import TwistModel, TwistDevice
21
21
 
22
22
 
23
23
  class TwistAPI:
24
- def __init__(self, installation_id: int, model_update: Callable[[TwistModel], None]):
24
+ def __init__(self, installation_id: int):
25
25
  self._ext_publish = None
26
26
  self._subscribe = None
27
- self._callback = model_update
28
27
 
29
28
  self.function_map = {
30
29
  "context": self._context_msg,
@@ -36,14 +35,14 @@ class TwistAPI:
36
35
  # TODO: this should come from the API
37
36
  self.installation_id = installation_id
38
37
 
39
- def add_mqtt(self, publisher, subscriber):
38
+ async def add_mqtt(self, publisher, subscriber):
40
39
  self._ext_publish = publisher
41
40
  self._subscribe = subscriber
42
41
 
43
- self._subscribe(f"v2/{self.installation_id}/rx/#", self._on_message_received)
42
+ await self._subscribe(f"v2/{self.installation_id}/rx/#", self._on_message_received)
44
43
 
45
44
  async def search_models(self):
46
- self.getboard(0xffffffff)
45
+ await self.getboard(0xffffffff)
47
46
  await asyncio.sleep(3)
48
47
 
49
48
  model_list: list[TwistModel] = list()
@@ -53,7 +52,7 @@ class TwistAPI:
53
52
 
54
53
  return model_list
55
54
 
56
- def _publish(self, twist_id, opcode, payload: dict | None = None, model_id=None):
55
+ async def _publish(self, twist_id, opcode, payload: dict | None = None, model_id=None):
57
56
  if self._ext_publish is not None:
58
57
  topic = f"v2/{self.installation_id}/tx/{twist_id}/{opcode}"
59
58
  if payload is None:
@@ -62,13 +61,13 @@ class TwistAPI:
62
61
  if model_id is not None:
63
62
  topic = f"{topic}/{model_id}"
64
63
 
65
- self._ext_publish(topic, json.dumps(payload))
64
+ await self._ext_publish(topic, json.dumps(payload))
66
65
 
67
- def getboard(self, twist_id):
68
- self._publish(twist_id, "getboard")
66
+ async def getboard(self, twist_id):
67
+ await self._publish(twist_id, "getboard")
69
68
 
70
- def activate_event(self, model: TwistModel, data: json):
71
- self._publish(model.parent_device.twist_id, "activate_event", data, model.model_id)
69
+ async def activate_event(self, model: TwistModel, data: json):
70
+ await self._publish(model.parent_device.twist_id, "activate_event", data, model.model_id)
72
71
 
73
72
  def _parse_topic(self, topic):
74
73
  tpc_delim = topic.split('/')
@@ -83,19 +82,17 @@ class TwistAPI:
83
82
  "model_id": model_id
84
83
  }
85
84
 
86
- def _on_message_received(self, topic, payload, qos=None):
85
+ async def _on_message_received(self, topic, payload, qos=None):
87
86
  data = self._parse_topic(topic)
88
87
  if any(dev.twist_id == data["twist_id"] for dev in self.device_list) or data["opcode"] == "getboard":
89
88
  if data["opcode"] in self.function_map:
90
- self.function_map[data["opcode"]](data["twist_id"], payload, data["model_id"])
89
+ await self.function_map[data["opcode"]](data["twist_id"], payload, data["model_id"])
91
90
 
92
- def _context_msg(self, twist_id, payload, model_id):
91
+ async def _context_msg(self, twist_id, payload, model_id):
93
92
  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)
93
+ await device.context_msg(model_id, payload)
97
94
 
98
- def _get_board(self, twist_id, payload, model_id=None):
95
+ async def _get_board(self, twist_id, payload, model_id=None):
99
96
  data = json.loads(payload)
100
97
  if not any(dev.twist_id == twist_id for dev in self.device_list):
101
98
  self.device_list.append(TwistDevice(twist_id, data["h"], DeviceVariant(data["v"]), self))
@@ -23,8 +23,11 @@ class TwistCbShutter(TwistModel):
23
23
  def __init__(self, model_id: int, parent_device: TwistDevice):
24
24
  super().__init__(model_id, parent_device)
25
25
 
26
- def context_msg(self, payload: str):
26
+ async def context_msg(self, payload: str):
27
27
  self.parse_general_context(payload)
28
28
 
29
+ if self._update_callback is not None:
30
+ await self._update_callback(self)
31
+
29
32
  def print_context(self):
30
33
  print(f"Cb Shutter Device: {self.parent_device.twist_id}, Model: {self.model_id},")
@@ -37,6 +37,5 @@ class TwistDevice:
37
37
  self.model_list.append(model(model_id, self))
38
38
  model_id += 1
39
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]
40
+ async def context_msg(self, model_id: int, payload: str):
41
+ await self.model_list[model_id].context_msg(payload)
@@ -34,22 +34,23 @@ class TwistLight(TwistModel):
34
34
  self.operating_time = 0
35
35
  self.current = 0
36
36
 
37
- def turn_on(self):
38
- self._activate_event(TwistLight.EventIndexes.SET)
37
+ async def turn_on(self):
38
+ await self._activate_event(TwistLight.EventIndexes.SET)
39
39
 
40
- def turn_off(self):
41
- self._activate_event(TwistLight.EventIndexes.CLEAR)
40
+ async def turn_off(self):
41
+ await self._activate_event(TwistLight.EventIndexes.CLEAR)
42
42
 
43
- def toggle(self):
44
- self._activate_event(TwistLight.EventIndexes.TOGGLE)
43
+ async def toggle(self):
44
+ await self._activate_event(TwistLight.EventIndexes.TOGGLE)
45
45
 
46
- def set_value(self, value: int, fading_time: int | None = None):
46
+ async def set_value(self, value: int, fading_time: int | None = None):
47
+ value = value * 655.35
47
48
  if fading_time is None:
48
- self._activate_event(TwistLight.EventIndexes.VALUE, value)
49
+ await self._activate_event(TwistLight.EventIndexes.VALUE, value)
49
50
  else:
50
- self._activate_event(TwistLight.EventIndexes.VALUE_FADING, value, fading_time)
51
+ await self._activate_event(TwistLight.EventIndexes.VALUE_FADING, value, fading_time)
51
52
 
52
- def _activate_event(self, index: TwistLight.EventIndexes, value: int | None = None, fading_time: int | None = None):
53
+ async def _activate_event(self, index: TwistLight.EventIndexes, value: int | None = None, fading_time: int | None = None):
53
54
  data = {
54
55
  "i": index.value
55
56
  }
@@ -61,9 +62,9 @@ class TwistLight(TwistModel):
61
62
  else:
62
63
  data["vl"] = [value, fading_time]
63
64
 
64
- self.parent_device.api.activate_event(self, data)
65
+ await self.parent_device.api.activate_event(self, data)
65
66
 
66
- def context_msg(self, payload: str):
67
+ async def context_msg(self, payload: str):
67
68
  data = self.parse_general_context(payload)
68
69
 
69
70
  for ctx in data["cl"]:
@@ -79,6 +80,9 @@ class TwistLight(TwistModel):
79
80
  elif index == 7:
80
81
  self.current = value[0]
81
82
 
83
+ if self._update_callback is not None:
84
+ await self._update_callback(self)
85
+
82
86
  def print_context(self):
83
87
  print(
84
88
  f"Light Device: {self.parent_device.twist_id}, Model: {self.model_id}, Actual: {self.actual_state}, "
@@ -12,6 +12,7 @@
12
12
  # https://www.gnu.org/licenses/gpl-3.0.html
13
13
 
14
14
  from __future__ import annotations
15
+
15
16
  from typing import TYPE_CHECKING
16
17
 
17
18
  if TYPE_CHECKING:
@@ -34,7 +35,7 @@ class TwistLouvre(TwistModel):
34
35
  self.operating_time = 0
35
36
  self.current = 0
36
37
 
37
- def context_msg(self, payload: str):
38
+ async def context_msg(self, payload: str):
38
39
  data = self.parse_general_context(payload)
39
40
 
40
41
  for ctx in data["cl"]:
@@ -50,26 +51,29 @@ class TwistLouvre(TwistModel):
50
51
  elif index == 7:
51
52
  self.current = value[0]
52
53
 
53
- def open(self):
54
- self._activate_event(TwistLouvre.EventIndexes.OPEN)
54
+ if self._update_callback is not None:
55
+ await self._update_callback(self)
56
+
57
+ async def open(self):
58
+ await self._activate_event(TwistLouvre.EventIndexes.OPEN)
55
59
 
56
- def stop(self):
57
- self._activate_event(TwistLouvre.EventIndexes.STOP)
60
+ async def stop(self):
61
+ await self._activate_event(TwistLouvre.EventIndexes.STOP)
58
62
 
59
- def close(self):
60
- self._activate_event(TwistLouvre.EventIndexes.CLOSE)
63
+ async def close(self):
64
+ await self._activate_event(TwistLouvre.EventIndexes.CLOSE)
61
65
 
62
- def toggle(self):
63
- self._activate_event(TwistLouvre.EventIndexes.TOGGLE)
66
+ async def toggle(self):
67
+ await self._activate_event(TwistLouvre.EventIndexes.TOGGLE)
64
68
 
65
- def set_value(self, value: int, fading_time: int | None = None):
69
+ async def set_value(self, value: int, fading_time: int | None = None):
66
70
  if fading_time is not None:
67
71
  raise NotImplementedError("Fading time can't be used in this model")
68
72
  else:
69
- self._activate_event(TwistLouvre.EventIndexes.VALUE, value)
73
+ await self._activate_event(TwistLouvre.EventIndexes.VALUE, int(value * 655.35))
70
74
 
71
- def _activate_event(self, index: TwistLouvre.EventIndexes, value: int | None = None,
72
- fading_time: int | None = None):
75
+ async def _activate_event(self, index: TwistLouvre.EventIndexes, value: int | None = None,
76
+ fading_time: int | None = None):
73
77
  data = {
74
78
  "i": index.value
75
79
  }
@@ -81,7 +85,7 @@ class TwistLouvre(TwistModel):
81
85
  else:
82
86
  data["vl"] = [value, fading_time]
83
87
 
84
- self.parent_device.api.activate_event(self, data)
88
+ await self.parent_device.api.activate_event(self, data)
85
89
 
86
90
  def print_context(self):
87
91
  print(
@@ -12,12 +12,15 @@
12
12
  # https://www.gnu.org/licenses/gpl-3.0.html
13
13
 
14
14
  from __future__ import annotations
15
+
15
16
  from typing import TYPE_CHECKING
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from TwistDevice import TwistDevice
19
20
 
20
21
  import json
22
+
23
+ from typing import Callable, Awaitable
21
24
  from .TwistTypes import ContextErrors
22
25
 
23
26
 
@@ -34,9 +37,13 @@ class TwistModel():
34
37
  "prio": None
35
38
  }
36
39
 
40
+ self._update_callback: Callable[[TwistModel], Awaitable[None]] | None = None
41
+
42
+
37
43
  def print_context(self):
38
44
  print("function is not supported")
39
45
 
46
+
40
47
  def parse_general_context(self, payload: str):
41
48
  data = json.loads(payload)
42
49
 
@@ -54,29 +61,42 @@ class TwistModel():
54
61
  self.errors["prio"] = value
55
62
  return data
56
63
 
64
+
57
65
  def _get_value_from_context(self, ctx: dict):
58
66
  return ctx["i"], ctx["vl"]
59
67
 
60
- def context_msg(self, payload):
68
+
69
+ async def context_msg(self, payload):
61
70
  raise NotImplementedError("Function not supported for this model")
62
71
 
63
- def turn_on(self):
72
+
73
+ async def turn_on(self):
64
74
  raise NotImplementedError("Function not supported for this model")
65
75
 
66
- def turn_off(self):
76
+
77
+ async def turn_off(self):
67
78
  raise NotImplementedError("Function not supported for this model")
68
79
 
69
- def open(self):
80
+
81
+ async def open(self):
70
82
  raise NotImplementedError("Function not supported for this model")
71
83
 
72
- def stop(self):
84
+
85
+ async def stop(self):
73
86
  raise NotImplementedError("Function not supported for this model")
74
87
 
75
- def close(self):
88
+
89
+ async def close(self):
76
90
  raise NotImplementedError("Function not supported for this model")
77
91
 
78
- def toggle(self):
92
+
93
+ async def toggle(self):
79
94
  raise NotImplementedError("Function not supported for this model")
80
95
 
81
- def set_value(self, value: int | list[int, int] | list[int, int, int], fading_time: int | None = None):
96
+
97
+ async def set_value(self, value: int | list[int, int] | list[int, int, int], fading_time: int | None = None):
82
98
  raise NotImplementedError("Function not supported for this model")
99
+
100
+
101
+ def register_update_cb(self, cb: Callable[[TwistModel], Awaitable[None]]):
102
+ self._update_callback = cb
@@ -12,6 +12,7 @@
12
12
  # https://www.gnu.org/licenses/gpl-3.0.html
13
13
 
14
14
  from __future__ import annotations
15
+
15
16
  from typing import TYPE_CHECKING
16
17
 
17
18
  if TYPE_CHECKING:
@@ -42,23 +43,23 @@ class TwistRgb(TwistModel):
42
43
 
43
44
  self.operating_time = 0
44
45
 
45
- def turn_on(self):
46
- self._activate_event(TwistRgb.EventIndexes.SET)
46
+ async def turn_on(self):
47
+ await self._activate_event(TwistRgb.EventIndexes.SET)
47
48
 
48
- def turn_off(self):
49
- self._activate_event(TwistRgb.EventIndexes.CLEAR)
49
+ async def turn_off(self):
50
+ await self._activate_event(TwistRgb.EventIndexes.CLEAR)
50
51
 
51
- def toggle(self):
52
- self._activate_event(TwistRgb.EventIndexes.TOGGLE)
52
+ async def toggle(self):
53
+ await self._activate_event(TwistRgb.EventIndexes.TOGGLE)
53
54
 
54
- def set_value(self, value: list[int, int, int], fading_time: int | None = None):
55
+ async def set_value(self, value: list[int, int, int], fading_time: int | None = None):
55
56
  if fading_time is None:
56
- self._activate_event(TwistRgb.EventIndexes.VALUE, value)
57
+ await self._activate_event(TwistRgb.EventIndexes.VALUE, value)
57
58
  else:
58
- self._activate_event(TwistRgb.EventIndexes.VALUE_FADING, value, fading_time)
59
+ await self._activate_event(TwistRgb.EventIndexes.VALUE_FADING, value, fading_time)
59
60
 
60
- def _activate_event(self, index: TwistRgb.EventIndexes, value: list[int, int, int] | None = None,
61
- fading_time: int | None = None):
61
+ async def _activate_event(self, index: TwistRgb.EventIndexes, value: tuple[int, int, int] | None = None,
62
+ fading_time: int | None = None):
62
63
  data = {
63
64
  "i": index.value
64
65
  }
@@ -66,14 +67,15 @@ class TwistRgb(TwistModel):
66
67
  if value is None:
67
68
  data["vl"] = []
68
69
  elif fading_time is None:
69
- data["vl"] = value
70
+ data["vl"] = [int(v / 655.35) for v in value]
70
71
  else:
72
+ value = list(value)
71
73
  value.append(fading_time)
72
74
  data["vl"] = value
73
75
 
74
- self.parent_device.api.activate_event(self, data)
76
+ await self.parent_device.api.activate_event(self, data)
75
77
 
76
- def context_msg(self, payload: str):
78
+ async def context_msg(self, payload: str):
77
79
  data = self.parse_general_context(payload)
78
80
 
79
81
  for ctx in data["cl"]:
@@ -91,6 +93,9 @@ class TwistRgb(TwistModel):
91
93
  if index == 6:
92
94
  self.operating_time = value[0]
93
95
 
96
+ if self._update_callback is not None:
97
+ await self._update_callback(self)
98
+
94
99
  def print_context(self):
95
100
  print(f"Rgb Device: {self.parent_device.twist_id}, Model: {self.model_id}, "
96
101
  f"Actual: h{self.actual_h} s{self.actual_s} v{self.actual_v}")
@@ -24,8 +24,11 @@ class TwistSensor(TwistModel):
24
24
  def __init__(self, model_id: int, parent_device: TwistDevice):
25
25
  super().__init__(model_id, parent_device)
26
26
 
27
- def context_msg(self, payload: str):
27
+ async def context_msg(self, payload: str):
28
28
  self.parse_general_context(payload)
29
29
 
30
+ if self._update_callback is not None:
31
+ await self._update_callback(self)
32
+
30
33
  def print_context(self):
31
34
  print(f"Sensor Device: {self.parent_device.twist_id}, Model: {self.model_id}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: twist-innovation-api
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: Python library to talk to the twist-innovation api
5
5
  Home-page: https://github.com/twist-innovation/twist-innovation-api
6
6
  Author: Sibrecht Goudsmedt
@@ -79,46 +79,76 @@ twine upload dist/*
79
79
  Import the package in your Python scripts:
80
80
 
81
81
  ```python
82
- from twist import TwistAPI, TwistModel
83
-
84
- # Other includes for this example
85
82
  import asyncio
86
- from typing import Callable
83
+ import yaml
84
+ from typing import Callable, Awaitable
85
+ import aiomqtt
87
86
 
88
- # Initialize the mqtt broker here and define the functions needed
87
+ from twist import TwistAPI, TwistModel
89
88
 
90
- # Create a callback function
91
- callback_f: Callable[[str, str], None] | None = None
89
+ # Load configuration
90
+ with open("config.yaml", 'r') as file:
91
+ config = yaml.safe_load(file)
92
92
 
93
- # Callback function when a Mqtt message is received from the broker and forward it to the twist API
94
- def on_message(client, userdata, msg):
95
- if callback_f is not None:
96
- callback_f(msg.topic, msg.payload.decode())
93
+ mqtt_broker = config["mqtt_broker"]
94
+ mqtt_user = config["mqtt_user"]
95
+ mqtt_pass = config["mqtt_pass"]
96
+ mqtt_port = config["mqtt_port"]
97
97
 
98
- # Publish message to the broker
99
- def mqtt_publish(topic, payload):
98
+ async def _noop(topic: str, payload: str):
100
99
  pass
101
100
 
102
- # Subscribe to a topic from the broker
103
- def mqtt_subscribe(topic, callback):
104
- global callback_f
105
- callback_f = callback
101
+ callback_f :Callable[[str, str], Awaitable[None]] = _noop
106
102
 
107
- # Callback function when a model received an update
108
- def on_model_update(model: TwistModel):
103
+
104
+ async def on_model_update(model: TwistModel):
109
105
  model.print_context()
110
106
 
107
+
111
108
  async def main():
112
- twist_api = TwistAPI(8, on_model_update)
113
- twist_api.add_mqtt(mqtt_publish, mqtt_subscribe)
114
- twist_model_list: list[TwistModel] = await twist_api.search_models()
109
+ global callback_f
110
+
111
+ async with aiomqtt.Client(
112
+ hostname=mqtt_broker,
113
+ port=mqtt_port,
114
+ username=mqtt_user,
115
+ password=mqtt_pass
116
+ ) as mqtt_client:
115
117
 
116
- while True:
117
- await asyncio.sleep(1)
118
+ # Async publish method
119
+ async def mqtt_publish(topic: str, payload: str):
120
+ await mqtt_client.publish(topic, payload)
121
+
122
+ # Async subscribe method
123
+ async def mqtt_subscribe(topic: str, callback: Callable[[str, str], None]):
124
+ global callback_f
125
+ callback_f = callback
126
+
127
+ async def listen():
128
+ await mqtt_client.subscribe(topic)
129
+ async for message in mqtt_client.messages:
130
+ assert callback_f is not None
131
+ await callback_f(message.topic.value, message.payload.decode())
132
+
133
+ asyncio.create_task(listen())
134
+
135
+ # Initialize Twist API with async methods
136
+ twist_api = TwistAPI(8)
137
+ await twist_api.add_mqtt(mqtt_publish, mqtt_subscribe)
138
+
139
+ twist_model_list: list[TwistModel] = await twist_api.search_models()
140
+
141
+ for model in twist_model_list:
142
+ model.register_update_cb(on_model_update)
143
+ print(f"{type(model)} has Model id: {model.model_id}, Device id: {model.parent_device.twist_id}")
144
+
145
+ while True:
146
+ await asyncio.sleep(1)
118
147
 
119
148
 
120
149
  if __name__ == "__main__":
121
150
  asyncio.run(main())
151
+
122
152
  ```
123
153
 
124
154
  ## License