Implementing the observable property
Implementing the observable property for the door state.
Creating the implementation class
To use the feature "door controller" and the just created subscription method, we need to implement it in a sub-class: the implementation class. We create a file called door_controller.py
in the connector folder (you should see a __init__.py
and __main__.py
as well as the greeting_provider.py
file there already)
├── src
├── connector
├── feature
├── door_controller
├── __init__.py
├── door_controller_base.py
├── __init__.py
├── __main__.py
├── door_controller.py
In door_controller.py
we initialize our DoorController class.
from .features.door_controller import DoorControllerBase
class DoorController(DoorControllerBase):
def __init__(self):
super().__init__()
self._door_open_change_event.set()
Code explanation
In the first line, the base class we created before is imported. Then, we define a class called DoorController that inherits from the DoorControllerBase class. In the __init__
method, we call the __init__
method of the base class and set the event we created there. This initialises the DoorController and the change event for the _door_open
property is activated. We will use this in our implementation of the subscribe_door_open
method.
Implementation of the subscribe_door_open method
While in the base class, we defined the name, kind, parameters, type hints, and documentation of the method, the implementation class contains the logic of the method:
async def subscribe_door_open(self):
yield self._door_open
self._door_open_change_event.clear()
while True:
await self._door_open_change_event.wait()
self._door_open_change_event.clear()
yield self._door_open
Code explanation
Every time we call this method we want to directly get the current door state back. That's why first we yield the state and then clear the event (it might be already cleared at this point but if not we need to clear it). while True
creates an endless loop, which means as long as we stay subscribed to this function, this loop repeats. Within the loop, we have 4 steps:
- Wait for the event we defined previously. As this event is set during the initialisation, the first time this loop is called, we move on to the second step.
- This line clears the event, so we can reuse it.
- We
yield
the_door_open
property which is set by other methods that update the state of the door like the open or close method. - We go back to step 1 and wait for the
_door_open
event to be triggered again.
Registering the feature
Open the __init__.py
file in the connector directory:
├── src
├── connector
├── feature
├── __init__.py
├── __main__.py
├── door_controller.py
Import the DoorController
feature and register it with the connector application using the app.register()
method. Within this file you can also add and update the connector information, such as name, type, description, version, and the vendor url. The connector information is not just visible to the client, but also provides valuable information for auto-discovery as it is also part of the mDNS message that is broadcasted via zeroconf/bonjour in your local network. That makes it very easy to find, ultimately enabling plug 'n play!
from unitelabs.cdk import Connector
from .door_controller import DoorController
async def create_app():
"""Creates the connector application"""
app = Connector(
{
"sila_server": {
"name": "Thermocycler",
"type": "Thermocycler",
"description": "Control the temperature of your samples with this thermocycler.",
"version": "0.1.0",
"vendor_url": "https://unitelabs.io/",
}
}
)
app.register(DoorController())
return app
Environmental variables
The connector application uses environmental variables to determine the IP and port it should run on. Each connector also requires a unique identifier, so they don't get mixed up by a client. You can create a .env
file in the source directory to define theses variables. The .env
file is loaded on connector start.
├── src
├── connector
├── .env
├── .gitignore
├── LICENSE
├── ...
Define the UUID
, HOST
, and PORT
and you're ready to go. The cloud server endpoint environmental variables are set
to default as we won't be needing them for this tutorial yet.
CLOUD_SERVER_ENDPOINT__ENDPOINT = localhost:443
CLOUD_SERVER_ENDPOINT__SECURE = True
SILA_SERVER__UUID = a1c7147d-a11f-4ac5-8518-af4f62ac6e2d
SILA_SERVER__HOST = 0.0.0.0
SILA_SERVER__PORT = 50051
Testing
Congrats, you have implemented your first observable property! Let's test it by running:
$ poetry run connector start
You can interact with the connector with the SiLA Browser to see if the subscription method works. The SiLA Browser should automatically detect your connector. Since we initialise the door in a closed state(_door_open=false
, see __init__
method in the base class), we expect "No" as an answer in the SiLA Browser as soon as we run the subscription method.