Skip to content

Commit 3ea19ee

Browse files
committed
add event docs with latest info
1 parent 9bc400d commit 3ea19ee

File tree

4 files changed

+208
-95
lines changed

4 files changed

+208
-95
lines changed

docs/beginners-guide/articles/actions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,10 +433,10 @@ class DCPowerSupply(Thing):
433433
# The suitability of this example in a realistic use case is untested
434434
```
435435

436-
For long running actions that do not return, call them with `noblock` flag on the client, otherwise except a `TimeoutError`:
436+
For long running actions that do not return, call them with `oneway` flag on the client, otherwise except a `TimeoutError`:
437437

438438
```py
439-
client.invoke_action("monitor_over_voltage", period=10, noblock=True)
439+
client.invoke_action("monitor_over_voltage", period=10, oneway=True)
440440
```
441441

442442
## Thing Description Metadata
Lines changed: 162 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,180 @@
1-
Events
2-
======
1+
# Events
32

4-
[API Reference](../../../api-reference/events/index.md)
3+
[API Reference](../../api-reference/events/index.md)
54

6-
Events are pushed to the client through a publish-subscribe mechanism. Call ``push()`` method on the event to
7-
publish data to clients:
5+
Events are pushed to the client through a publish-subscribe mechanism through all the protocols. Call `push()` method on the event to publish data to clients:
86

9-
```py title="Definition" linenums="1" hl_lines="17-21"
10-
--8<-- "docs/howto/code/events/definition.py:5:25"
7+
```py title="Definition" linenums="1" hl_lines="21-23"
8+
--8<-- "docs/beginners-guide/code/events/definition.py:3:25"
119
```
1210

11+
## Subscription
12+
1313
One can subscribe to the event using the attribute name:
1414

15-
```py title="Subscription" linenums="1" hl_lines="9-10"
16-
--8<-- "docs/howto/code/events/event_client.py:1:10"
17-
```
18-
15+
=== "threaded"
16+
17+
In the default synchronous mode, the `subscribe_event` method spawns a thread that listens to the event in backgound:
18+
19+
```py title="Subscription" linenums="1" hl_lines="9"
20+
from hololinked.client import ClientFactory
21+
22+
energy_meter = ClientFactory.http(url="http://localhost:8000/energy-meter")
23+
# energy_meter = ClientFactory.zmq(id="energy_meter", access_point="IPC")
24+
25+
def event_cb(event_data):
26+
print(event_data)
1927

20-
One can also supply multiple callbacks which may called in series or threaded:
28+
energy_meter.subscribe_event(name="data_point_event", callbacks=event_cb)
29+
```
30+
31+
=== "async"
32+
33+
In the asynchronous mode, the `subscribe_event` method creates an event listening task in the running async loop:
2134

22-
=== "Sequential"
35+
```py title="Subscription" linenums="1" hl_lines="9"
36+
from hololinked.client import ClientFactory
2337

24-
```py title="Providing Callbacks" linenums="1" hl_lines="9"
25-
--8<-- "docs/howto/code/events/event_client.py:13:22"
38+
energy_meter = ClientFactory.http(url="http://localhost:8000/energy-meter")
39+
# energy_meter = ClientFactory.zmq(id="energy_meter", access_point="IPC")
40+
41+
def event_cb(event_data):
42+
print(event_data)
43+
44+
energy_meter.subscribe_event(
45+
name="data_point_event",
46+
callbacks=event_cb,
47+
asynch=True
48+
)
2649
```
2750

28-
=== "Threaded"
51+
---
52+
53+
One can also supply multiple callbacks which may called in series, threaded or async:
54+
55+
=== "sequential"
56+
57+
The background thread that listens to the event executes the callbacks in series in its own thread:
58+
59+
```py title="Sequential Callbacks" linenums="1" hl_lines="9"
60+
def event_cb1(event_data):
61+
print("First Callback", event_data)
62+
63+
def event_cb2(event_data):
64+
print("Second callback", event_data)
65+
66+
energy_meter.subscribe_event(
67+
name="statistics_event",
68+
callbacks=[event_cb1, event_cb2]
69+
)
70+
```
71+
72+
So please be careful while using GUI frameworks like PyQt where you can paint the GUI only from the main thread.
73+
You would need to use signals and slots or other mechanisms.
74+
75+
=== "threaded"
76+
77+
The background thread that listens to the event executes the callbacks by spawning new threads:
78+
79+
```py title="Thread Callbacks" linenums="1" hl_lines="9-10"
80+
def event_cb1(event_data):
81+
print("First Callback", event_data)
82+
83+
def event_cb2(event_data):
84+
print("Second callback", event_data)
2985

30-
```py title="Providing Callbacks" linenums="1" hl_lines="9-10"
31-
--8<-- "docs/howto/code/events/event_client.py:13:18"
32-
--8<-- "docs/howto/code/events/event_client.py:24:"
86+
energy_meter.subscribe_event(
87+
name="statistics_event",
88+
callbacks=[event_cb1, event_cb2],
89+
thread_callbacks=True
90+
)
3391
```
92+
Again, please be careful while using GUI frameworks like PyQt where you can paint the GUI only from the main thread.
93+
94+
=== "async"
95+
96+
Applies only when listening to event with `async=True`, the `async` method creates new tasks in the current loop:
97+
98+
```py title="Thread Callbacks" linenums="1"
99+
async def event_cb1(event_data):
100+
print("First Callback", event_data)
101+
await some_async_function1(event_data)
102+
103+
async def event_cb2(event_data):
104+
print("Second callback", event_data)
105+
await some_async_function2(event_data)
106+
107+
energy_meter.subscribe_event(
108+
name="statistics_event",
109+
callbacks=[event_cb1, event_cb2],
110+
asynch=True,
111+
create_task_for_cbs=True
112+
)
113+
```
114+
115+
---
116+
117+
To unsubscribe:
118+
119+
```py title="Unsubscription" linenums="1"
120+
energy_meter.unsubscribe_event(name="data_point_event")
121+
```
122+
123+
## Payload Schema
34124

35125
Schema may be supplied for the validation of the event data on the client:
36126

37127
```py title="" linenums="1" hl_lines="13"
38-
--8<-- "docs/howto/code/events/definition.py:6:6"
39-
--8<-- "docs/howto/code/events/definition.py:50:62"
40-
```
41-
42-
There is no separate validation on the server side.
43-
44-
45-
<!-- .. list-table:: Thing Description for Events
46-
:header-rows: 1
47-
48-
* - Key
49-
- Supported
50-
- Comment
51-
* - subscription
52-
- ✖
53-
-
54-
* - data
55-
- ✔
56-
- payload schema for the event
57-
* - dataResponse
58-
- ✖
59-
- schema for response message after arrival of an event, will be supported in future
60-
* - cancellation
61-
- ✖
62-
- Server sent events can be cancelled by the client directly -->
128+
class GentecMaestroEnergyMeter(Thing):
129+
130+
data_point_event_schema = {
131+
"type": "object",
132+
"properties": {
133+
"timestamp": {"type": "string", "format": "date-time"},
134+
"energy": {"type": "number"}
135+
},
136+
"required": ["timestamp", "energy"],
137+
}
138+
139+
data_point_event = Event(
140+
doc="Event raised when a new data point is available",
141+
label="Data Point Event",
142+
schema=data_point_event_schema,
143+
)
144+
```
145+
146+
There is no separate validation on the server side.
147+
148+
???+ "Schema as seen in Thing Description"
149+
150+
```py
151+
GentecMaestro.data_point_event.to_affordance().json()
152+
```
153+
154+
```json
155+
{
156+
"description": "Event raised when a new data point is available",
157+
"data": {
158+
"type": "object",
159+
"properties": {
160+
"timestamp": {
161+
"type": "string",
162+
"format": "date-time"
163+
},
164+
"energy": {
165+
"type": "number"
166+
}
167+
},
168+
"required": ["timestamp", "energy"]
169+
}
170+
}
171+
```
172+
173+
## Thing Description Metadata
174+
175+
| Key | Supported | Comment |
176+
| ------------ | --------- | ---------------------------------------------------------------------------------- |
177+
| subscription || |
178+
| data || payload schema for the event |
179+
| dataResponse || schema for response message after arrival of an event, will be supported in future |
180+
| cancellation | - | Server sent events can be cancelled by the client directly |
Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,66 @@
1-
import datetime, threading
2-
from hololinked.server import Thing, Event, Property
3-
import json
1+
import datetime
2+
import threading
3+
from hololinked.core import Thing, Event, Property
44

55

66
class GentecMaestroEnergyMeter(Thing):
77
"""
88
Simple example to implement acquisition loops and events
9-
to push the captured data. Customize it for your application or
9+
to push the captured data. Customize it for your application or
1010
implement your own.
1111
"""
1212

13-
data_point_event = Event(friendly_name='data-point-event',
14-
doc='Event raised when a new data point is available',
15-
label='Data Point Event')
16-
13+
data_point_event = Event(
14+
doc="Event raised when a new data point is available",
15+
label="Data Point Event",
16+
)
17+
1718
def loop(self):
1819
self._run = True
1920
while self._run:
20-
self._last_measurement = self.current_value
21+
self._last_measurement = self.read_current_value()
2122
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
22-
self.data_point_event.push(dict(
23-
timestamp=timestamp,
24-
energy=self._last_measurement
25-
))
23+
self.data_point_event.push(
24+
dict(timestamp=timestamp, energy=self._last_measurement)
25+
)
2626
if self._statistics_enabled:
2727
self.statistics_event.push(self.statistics)
28-
29-
statistics_event = Event(friendly_name='statistics-event',
30-
doc='Event raised when a new statistics is available',
31-
label='Statistics Event')
3228

29+
statistics_event = Event(
30+
friendly_name="statistics-event",
31+
doc="Event raised when a new statistics is available",
32+
label="Statistics Event",
33+
)
3334

34-
statistics = Property(doc="Get latest computed statistics",
35-
readonly=True)
35+
statistics = Property(doc="Get latest computed statistics", readonly=True)
3636

3737
@statistics.getter
3838
def get_statistics(self):
3939
"""get latest computed statistics"""
4040
raw_data = self.serial_comm_handle.execute_instruction("*VSU\r\n", 1000)
4141
if raw_data is not None:
42-
data = raw_data.split('\t')
42+
data = raw_data.split("\t")
4343
ret = {}
4444
for qty in data:
45-
name, value = qty.split(':')
45+
name, value = qty.split(":")
4646
ret[name] = float(value)
4747
return ret
4848
raise RuntimeError("Could not get statistics")
49-
50-
49+
5150
data_point_event_schema = {
5251
"type": "object",
53-
"properties": {
54-
"timestamp": {"type": "string"},
55-
"energy": {"type": "number"}
56-
},
57-
"required": ["timestamp", "energy"]
52+
"properties": {"timestamp": {"type": "string"}, "energy": {"type": "number"}},
53+
"required": ["timestamp", "energy"],
5854
}
5955

60-
data_point_event = Event(doc='Event raised when a new data point is available',
61-
label='Data Point Event', schema=data_point_event_schema)
62-
63-
56+
data_point_event = Event(
57+
doc="Event raised when a new data point is available",
58+
label="Data Point Event",
59+
schema=data_point_event_schema,
60+
)
61+
62+
6463
if __name__ == "__main__":
6564
threading.Thread(target=start_https_server).start()
6665
# add your HTTP server starter code in the above method
67-
GentecMaestroEnergyMeter(
68-
instance_name='gentec-maestro'
69-
).run(zmq_protocols='IPC')
66+
GentecMaestroEnergyMeter(instance_name="gentec-maestro").run(zmq_protocols="IPC")
Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
1-
from hololinked.client import ObjectProxy
1+
from hololinked.client import ClientFactory
2+
3+
energy_meter = ClientFactory.http(url="http://localhost:8000/energy-meter")
4+
# energy_meter = ClientFactory.zmq(id="energy_meter", access_point="IPC")
25

3-
# events works also through inter-process communication, apart from TCP
4-
energy_meter_proxy = ObjectProxy(instance_name='gentec-maestro', protocol='IPC')
56

67
def event_cb(event_data):
78
print(event_data)
89

9-
energy_meter_proxy.subscribe_event(name='data_point_event',
10-
callbacks=event_cb)
10+
11+
energy_meter.subscribe_event(name="data_point_event", callbacks=event_cb)
1112

1213

1314
def event_cb1(event_data):
1415
print(event_data)
1516

17+
1618
def event_cb2(event_data):
1719
print("Second callback", event_data)
1820

19-
energy_meter_proxy.subscribe_event(
20-
name='statistics_event',
21-
callbacks=[event_cb1, event_cb2]
22-
)
2321

24-
energy_meter_proxy.subscribe_event(
25-
name='statistics_event',
26-
callbacks=[event_cb1, event_cb2],
27-
thread_callbacks=True
28-
)
22+
energy_meter.subscribe_event(name="statistics_event", callbacks=[event_cb1, event_cb2])
23+
24+
energy_meter.subscribe_event(
25+
name="statistics_event", callbacks=[event_cb1, event_cb2], thread_callbacks=True
26+
)

0 commit comments

Comments
 (0)