Skip to content

Commit c2d5124

Browse files
Refactor, extend functionality and add examples
1 parent 98491aa commit c2d5124

26 files changed

Lines changed: 680 additions & 262 deletions

examples/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .utils import *

examples/cameraDiscoveryExample.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from context_logger import get_logger, setup_logging
2+
3+
from examples import setup_shutdown
4+
from hello import Hello, Group, ServiceQuery, DiscoveryEvent
5+
6+
setup_logging('hello')
7+
8+
log = get_logger('CameraDiscovery')
9+
10+
11+
def main() -> None:
12+
shutdown_event = setup_shutdown()
13+
14+
# Define the group to discover camera services
15+
group = Group(name='effectiverange/sniper', url='udp://239.0.1.1:5555')
16+
17+
# Define the query to discover camera services
18+
query = ServiceQuery(name='.+', role='camera')
19+
20+
# Use a discoverer to find camera services
21+
with Hello.builder().discoverer().default() as discoverer:
22+
# Define an event handler to process discovery events
23+
def process_event(event: DiscoveryEvent) -> None:
24+
log.info('Service discovery event', type=event.type.name, service=event.service)
25+
26+
# Register the event handler to process discovery events
27+
discoverer.register(process_event)
28+
29+
# Start the discoverer with the specified group
30+
discoverer.start(group)
31+
32+
# Send the service query
33+
discoverer.discover(query)
34+
35+
shutdown_event.wait()
36+
37+
38+
if __name__ == '__main__':
39+
main()

examples/cameraServiceExample.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from context_logger import get_logger, setup_logging
2+
3+
from examples import setup_shutdown
4+
from hello import ServiceInfo, Hello, Group
5+
6+
setup_logging('hello')
7+
8+
log = get_logger('CameraService')
9+
10+
11+
def main() -> None:
12+
shutdown_event = setup_shutdown()
13+
14+
# Define the group to advertise the camera service
15+
group = Group(name='effectiverange/sniper', url='udp://239.0.1.1:5555')
16+
17+
# Define the service information for the camera
18+
info = ServiceInfo(name='er-sniper-camera-1', role='camera', urls={
19+
'device-api': 'grpc://er-sniper-camera-1/device',
20+
'video-stream': 'blob:http://er-sniper-camera-1/video'
21+
})
22+
23+
# Use a scheduled advertizer to periodically announce the camera service
24+
with Hello.builder().advertizer().scheduled() as advertizer:
25+
# Start the advertizer with the specified group
26+
advertizer.start(group)
27+
28+
# Immediately advertise the service information
29+
advertizer.advertise(info)
30+
31+
# Schedule periodic advertisements every 10 seconds
32+
advertizer.schedule(interval=10)
33+
34+
shutdown_event.wait()
35+
36+
37+
if __name__ == '__main__':
38+
main()

examples/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from signal import signal, SIGINT, SIGTERM
2+
from threading import Event
3+
from typing import Any
4+
5+
6+
def setup_shutdown() -> Event:
7+
shutdown_event = Event()
8+
9+
def handler(signum: int, frame: Any) -> None:
10+
shutdown_event.set()
11+
12+
signal(SIGINT, handler)
13+
signal(SIGTERM, handler)
14+
15+
return shutdown_event

hello/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .group import *
22
from .sender import *
33
from .receiver import *
4+
from .scheduler import *
45
from .service import *
56
from .advertizer import *
67
from .discoverer import *

hello/advertizer.py

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
from common_utility import IReusableTimer
66
from context_logger import get_logger
77

8-
from hello import ServiceInfo, Group, Sender, GroupAccess, Receiver, ServiceMatcher, ServiceQuery
8+
from hello import ServiceInfo, Group, Sender, Receiver, ServiceMatcher, ServiceQuery, DefaultScheduler
99

1010
log = get_logger('Advertizer')
1111

1212

1313
class Advertizer:
1414

15-
def start(self, address: str, group: Group, info: ServiceInfo | None = None) -> None:
15+
def start(self, group: Group, info: ServiceInfo | None = None) -> None:
1616
raise NotImplementedError()
1717

1818
def stop(self) -> None:
@@ -35,8 +35,8 @@ def __enter__(self) -> Advertizer:
3535
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
3636
self.stop()
3737

38-
def start(self, address: str, group: Group, info: ServiceInfo | None = None) -> None:
39-
self._sender.start(GroupAccess(address, group.hello()))
38+
def start(self, group: Group, info: ServiceInfo | None = None) -> None:
39+
self._sender.start(group.hello())
4040
self._group = group
4141
self._info = info
4242

@@ -62,9 +62,9 @@ def __init__(self, sender: Sender, receiver: Receiver, max_response_delay: float
6262
self._receiver = receiver
6363
self._max_delay = max_response_delay
6464

65-
def start(self, address: str, group: Group, info: ServiceInfo | None = None) -> None:
66-
super().start(address, group, info)
67-
self._receiver.start(GroupAccess(address, group.query()))
65+
def start(self, group: Group, info: ServiceInfo | None = None) -> None:
66+
super().start(group, info)
67+
self._receiver.start(group.query())
6868
self._receiver.register(self._handle_message)
6969

7070
def stop(self) -> None:
@@ -89,42 +89,27 @@ def _handle_query(self, query: ServiceQuery, info: ServiceInfo) -> None:
8989
self.advertise(info)
9090

9191

92-
class ScheduledAdvertizer(Advertizer):
93-
94-
def schedule(self, info: ServiceInfo | None = None, interval: float = 10, one_shot: bool = False) -> None:
95-
raise NotImplementedError()
96-
97-
98-
class DefaultScheduledAdvertizer(ScheduledAdvertizer):
92+
class ScheduledAdvertizer(DefaultScheduler[ServiceInfo], Advertizer):
9993

10094
def __init__(self, advertizer: Advertizer, timer: IReusableTimer) -> None:
95+
super().__init__(timer)
10196
self._advertizer = advertizer
102-
self._timer = timer
10397

104-
def __enter__(self) -> ScheduledAdvertizer:
98+
def __enter__(self) -> 'ScheduledAdvertizer':
10599
return self
106100

107101
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
108102
self.stop()
109103

110-
def start(self, address: str, group: Group, info: ServiceInfo | None = None) -> None:
111-
self._advertizer.start(address, group, info)
104+
def start(self, group: Group, info: ServiceInfo | None = None) -> None:
105+
self._advertizer.start(group, info)
112106

113107
def stop(self) -> None:
114-
self._timer.cancel()
108+
super().stop()
115109
self._advertizer.stop()
116110

117111
def advertise(self, info: ServiceInfo | None = None) -> None:
118112
self._advertizer.advertise(info)
119113

120-
def schedule(self, info: ServiceInfo | None = None, interval: float = 60, one_shot: bool = False) -> None:
121-
if one_shot:
122-
self._timer.start(interval, self.advertise, [info])
123-
log.info('One-shot service advertisement scheduled', service=info, interval=interval)
124-
else:
125-
self._timer.start(interval, self._advertise_and_restart, [info])
126-
log.info('Periodic service advertisement scheduled', service=info, interval=interval)
127-
128-
def _advertise_and_restart(self, info: ServiceInfo | None = None) -> None:
114+
def _execute(self, info: ServiceInfo | None = None) -> None:
129115
self.advertise(info)
130-
self._timer.restart()

hello/api.py

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,93 @@
1+
from dataclasses import dataclass
12
from typing import Any
23

34
from common_utility import ReusableTimer
45
from zmq import Context
56

6-
from hello import Advertizer, Discoverer, RadioSender, DishReceiver, DefaultAdvertizer, DefaultDiscoverer, \
7-
ScheduledAdvertizer, RespondingAdvertizer, DefaultScheduledAdvertizer
7+
from hello import RadioSender, DishReceiver, DefaultAdvertizer, DefaultDiscoverer, \
8+
RespondingAdvertizer, ScheduledAdvertizer, ScheduledDiscoverer
89

910

10-
class Hello:
11+
@dataclass
12+
class HelloConfig:
13+
context: Context[Any] = Context()
14+
receiver_max_workers: int = 1
15+
receiver_poll_timeout: float = 0.1
16+
advertizer_responder: bool = True
17+
advertizer_max_delay: float = 0.1
1118

12-
def default_advertizer(self, respond: bool = True, delay: float = 0.1) -> Advertizer:
13-
raise NotImplementedError()
1419

15-
def scheduled_advertizer(self, respond: bool = True, delay: float = 0.1) -> ScheduledAdvertizer:
16-
raise NotImplementedError()
20+
class Hello(object):
1721

18-
def discoverer(self) -> Discoverer:
19-
raise NotImplementedError()
22+
@classmethod
23+
def default_config(cls) -> HelloConfig:
24+
return HelloConfig()
2025

21-
22-
class DefaultHello(Hello):
23-
24-
def __init__(self, context: Context[Any] | None = None, max_workers: int = 1, poll_timeout: float = 0.1) -> None:
25-
self._context = context if context else Context()
26-
self._max_workers = max_workers
27-
self._poll_timeout = poll_timeout
28-
29-
def default_advertizer(self, respond: bool = True, delay: float = 0.1) -> Advertizer:
30-
sender = RadioSender(self._context)
31-
if respond:
32-
receiver = DishReceiver(self._context, self._max_workers, self._poll_timeout)
33-
return RespondingAdvertizer(sender, receiver, delay)
26+
@classmethod
27+
def default_advertizer(cls, config: HelloConfig) -> DefaultAdvertizer:
28+
sender = RadioSender(config.context)
29+
if config.advertizer_responder:
30+
receiver = DishReceiver(config.context, config.receiver_max_workers, config.receiver_poll_timeout)
31+
return RespondingAdvertizer(sender, receiver, config.advertizer_max_delay)
3432
else:
3533
return DefaultAdvertizer(sender)
3634

37-
def scheduled_advertizer(self, respond: bool = True, delay: float = 0.1) -> ScheduledAdvertizer:
38-
advertizer = self.default_advertizer(respond, delay)
39-
return DefaultScheduledAdvertizer(advertizer, ReusableTimer())
35+
@classmethod
36+
def scheduled_advertizer(cls, config: HelloConfig) -> ScheduledAdvertizer:
37+
advertizer = cls.default_advertizer(config)
38+
return ScheduledAdvertizer(advertizer, ReusableTimer())
4039

41-
def discoverer(self) -> Discoverer:
42-
sender = RadioSender(self._context)
43-
receiver = DishReceiver(self._context, self._max_workers, self._poll_timeout)
40+
@classmethod
41+
def default_discoverer(cls, config: HelloConfig) -> DefaultDiscoverer:
42+
sender = RadioSender(config.context)
43+
receiver = DishReceiver(config.context, config.receiver_max_workers, config.receiver_poll_timeout)
4444
return DefaultDiscoverer(sender, receiver)
45+
46+
@classmethod
47+
def scheduled_discoverer(cls, config: HelloConfig) -> ScheduledDiscoverer:
48+
discoverer = cls.default_discoverer(config)
49+
return ScheduledDiscoverer(discoverer, ReusableTimer())
50+
51+
@classmethod
52+
def builder(cls) -> 'HelloBuilder':
53+
return HelloBuilder()
54+
55+
56+
class AdvertizerBuilder(object):
57+
58+
def __init__(self, config: HelloConfig) -> None:
59+
self._config = config
60+
61+
def default(self) -> DefaultAdvertizer:
62+
return Hello.default_advertizer(self._config)
63+
64+
def scheduled(self) -> ScheduledAdvertizer:
65+
return Hello.scheduled_advertizer(self._config)
66+
67+
68+
class DiscovererBuilder(object):
69+
70+
def __init__(self, config: HelloConfig) -> None:
71+
self._config = config
72+
73+
def default(self) -> DefaultDiscoverer:
74+
return Hello.default_discoverer(self._config)
75+
76+
def scheduled(self) -> ScheduledDiscoverer:
77+
return Hello.scheduled_discoverer(self._config)
78+
79+
80+
class HelloBuilder(object):
81+
82+
def __init__(self) -> None:
83+
self._config = Hello.default_config()
84+
85+
def config(self, config: HelloConfig) -> 'HelloBuilder':
86+
self._config = config
87+
return self
88+
89+
def advertizer(self) -> AdvertizerBuilder:
90+
return AdvertizerBuilder(self._config)
91+
92+
def discoverer(self) -> DiscovererBuilder:
93+
return DiscovererBuilder(self._config)

hello/discoverer.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from enum import Enum
33
from typing import Any, Protocol
44

5+
from common_utility import IReusableTimer
56
from context_logger import get_logger
67

7-
from hello import Group, ServiceQuery, Sender, Receiver, GroupAccess, ServiceInfo, ServiceMatcher
8+
from hello import Group, ServiceQuery, Sender, Receiver, ServiceInfo, ServiceMatcher, DefaultScheduler
89

910
log = get_logger('Discoverer')
1011

@@ -26,7 +27,7 @@ def __call__(self, event: DiscoveryEvent) -> None: ...
2627

2728
class Discoverer:
2829

29-
def start(self, address: str, group: Group, query: ServiceQuery | None = None) -> None:
30+
def start(self, group: Group, query: ServiceQuery | None = None) -> None:
3031
raise NotImplementedError()
3132

3233
def stop(self) -> None:
@@ -64,13 +65,13 @@ def __enter__(self) -> Discoverer:
6465
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
6566
self.stop()
6667

67-
def start(self, address: str, group: Group, query: ServiceQuery | None = None) -> None:
68+
def start(self, group: Group, query: ServiceQuery | None = None) -> None:
6869
self._group = group
6970
if query:
7071
self._matcher = ServiceMatcher(query)
71-
self._sender.start(GroupAccess(address, group.query()))
72+
self._sender.start(group.query())
7273
self._receiver.register(self._handle_message)
73-
self._receiver.start(GroupAccess(address, group.hello()))
74+
self._receiver.start(group.hello())
7475

7576
def stop(self) -> None:
7677
self._group = None
@@ -132,3 +133,29 @@ def _handle_event(self, event: DiscoveryEvent) -> None:
132133
callback(event)
133134
except Exception as error:
134135
log.warn('Error in event handler execution', event=event, error=error)
136+
137+
138+
class ScheduledDiscoverer(DefaultScheduler[ServiceQuery], Discoverer):
139+
140+
def __init__(self, discoverer: Discoverer, timer: IReusableTimer) -> None:
141+
super().__init__(timer)
142+
self._discoverer = discoverer
143+
144+
def __enter__(self) -> 'ScheduledDiscoverer':
145+
return self
146+
147+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
148+
self.stop()
149+
150+
def start(self, group: Group, query: ServiceQuery | None = None) -> None:
151+
self._discoverer.start(group, query)
152+
153+
def stop(self) -> None:
154+
super().stop()
155+
self._discoverer.stop()
156+
157+
def discover(self, query: ServiceQuery | None = None) -> None:
158+
self._discoverer.discover(query)
159+
160+
def _execute(self, query: ServiceQuery | None = None) -> None:
161+
self.discover(query)

0 commit comments

Comments
 (0)