hololinked


Namehololinked JSON
Version 0.1.2 PyPI version JSON
download
home_pagehttps://github.com/VigneshVSV/hololinked
SummaryA ZMQ-based Object Oriented RPC tool-kit with HTTP support for instrument control/data acquisition or controlling generic python objects.
upload_time2024-06-06 03:08:02
maintainerNone
docs_urlNone
authorVignesh Vaidyanathan
requires_python>=3.7
licenseBSD-3-Clause
keywords data-acquisition zmq-rpc scada/iot web of things
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # hololinked

### Description

For beginners - `hololinked` is a server side pythonic package suited for instrumentation control and data acquisition over network, especially with HTTP. If you have a requirement to control and capture data from your hardware/instrumentation remotely through your network, show the data in a web browser/dashboard, use IoT tools, provide a Qt-GUI or run automated scripts, hololinked can help. One can start small from a single device, and if interested, move ahead to build a bigger system made of individual components. 
<br/> <br/>
For those familiar with RPC & web development - `hololinked` is a ZeroMQ-based Object Oriented RPC toolkit with customizable HTTP end-points. 
The main goal is to develop a pythonic & pure python modern package for instrumentation control and data acquisition through network (SCADA), along with "reasonable" HTTP support for web development.  

<p align="left">

[![Documentation Status](https://readthedocs.org/projects/hololinked/badge/?version=latest)](https://hololinked.readthedocs.io/en/latest/?badge=latest) ![PyPI - Downloads](https://img.shields.io/pypi/dm/hololinked) [![Maintainability](https://api.codeclimate.com/v1/badges/913f4daa2960b711670a/maintainability)](https://codeclimate.com/github/VigneshVSV/hololinked/maintainability) 

<!-- ![PyPI](https://img.shields.io/pypi/v/hololinked?label=pypi%20package) -->


</p>



### Usage/Quickstart

`hololinked` is compatible with the [Web of Things](https://www.w3.org/WoT/) recommended pattern for developing hardware/instrumentation control software. Each device or thing can be controlled systematically when their design in software is segregated into properties, actions and events. In object oriented terms for data acquisition:
- properties are validated get-set attributes of the class which may be used to model device settings, hold captured/computed data etc.
- actions are methods which issue commands to the device or run arbitrary python logic. 
- events can asynchronously communicate/push data to a client, like alarm messages, streaming captured data etc. 

In `hololinked`, the base class which enables this classification is the `Thing` class. Any class that inherits the `Thing` class can instantiate properties, actions and events which become visible to a client in this segragated manner. For example, consider an optical spectrometer device, the following code is possible:

#### Import Statements

```python

from hololinked.server import Thing, Property, action, Event
from hololinked.server.properties import String, Integer, Number, List
```

#### Definition of one's own hardware controlling class

subclass from Thing class to "make a network accessible Thing":

```python 

class OceanOpticsSpectrometer(Thing):
    """
    OceanOptics spectrometers using seabreeze library. Device is identified by serial number. 
    """
    
```

#### Instantiating properties

Say, we wish to make device serial number, integration time and the captured intensity as properties. There are certain predefined properties available like `String`, `Number`, `Boolean` etc. 
or one may define one's own. To create properties:

```python

class OceanOpticsSpectrometer(Thing):
    """class doc"""
    
    serial_number = String(default=None, allow_None=True, URL_path='/serial-number', 
                        doc="serial number of the spectrometer to connect/or connected",
                        http_method=("GET", "PUT"))
    # GET and PUT is default for reading and writing the property respectively. 
    # Use other HTTP methods if necessary.  

    integration_time = Number(default=1000, bounds=(0.001, None), crop_to_bounds=True, 
                            URL_path='/integration-time', 
                            doc="integration time of measurement in milliseconds")

    intensity = List(default=None, allow_None=True, 
                    doc="captured intensity", readonly=True, 
                    fget=lambda self: self._intensity)     

    def __init__(self, instance_name, serial_number, **kwargs):
        super().__init__(instance_name=instance_name, serial_number=serial_number, **kwargs)

```

In non-expert terms, properties look like class attributes however their data containers are instantiated at object instance level by default.
For example, the `integratime_time` property defined above as `Number`, whenever set/written, will be validated as a float or int, cropped to bounds and assigned as an attribute to each instance of the `OceanOpticsSpectrometer` class with an internally generated name. It is not necessary to know this internally generated name as the property value can be accessed again in any python logic, say, `print(self.integration_time)`. 

To overload the get-set (or read-write) of properties, one may do the following:

```python
class OceanOpticsSpectrometer(Thing):

    integration_time = Number(default=1000, bounds=(0.001, None), crop_to_bounds=True, 
                            URL_path='/integration-time', 
                            doc="integration time of measurement in milliseconds")

    @integration_time.setter # by default called on http PUT method
    def apply_integration_time(self, value : float):
        self.device.integration_time_micros(int(value*1000))
        self._integration_time = int(value) 
      
    @integration_time.getter # by default called on http GET method
    def get_integration_time(self) -> float:
        try:
            return self._integration_time
        except AttributeError:
            return self.properties["integration_time"].default 

```

In this case, instead of generating a data container with an internal name, the setter method is called when `integration_time` property is set/written. One might add the hardware device driver (say, supplied by the manufacturer) logic here to apply the property onto the device. In the above example, there is not a way provided by lower level library to read the value from the device, so we store it in a variable after applying it and supply the variable back to the getter method. Normally, one would also want the getter to read from the device directly.
 
Those familiar with Web of Things (WoT) terminology may note that these properties generate the property affordance schema to become accessible by the [node-wot](https://github.com/eclipse-thingweb/node-wot) client. An example of autogenerated property affordance for `integration_time` is as follows:

```JSON
"integration_time": {
    "title": "integration_time",
    "description": "integration time of measurement in milliseconds",
    "constant": false,
    "readOnly": false,
    "writeOnly": false,
    "type": "number",
    "forms": [{
            "href": "https://example.com/spectrometer/integration-time",
            "op": "readproperty",
            "htv:methodName": "GET",
            "contentType": "application/json"
        },{
            "href": "https://example.com/spectrometer/integration-time",
            "op": "writeproperty",
            "htv:methodName": "PUT",
            "contentType": "application/json"
        }
    ],
    "observable": false,
    "minimum": 0.001
},
```
The URL path segment `../spectrometer/..` in href field is taken from the `instance_name` which was specified in the `__init__`. This is a mandatory key word argument to the parent class `Thing` to generate a unique name/id for the instance. One should use URI compatible strings.

#### Specify methods as actions

decorate with `action` decorator on a python method to claim it as a network accessible method:

```python

class OceanOpticsSpectrometer(Thing):

    @action(URL_path='/connect', http_method="POST") # POST is default for actions
    def connect(self, serial_number = None):
        """connect to spectrometer with given serial number"""
        if serial_number is not None:
            self.serial_number = serial_number
        self.device = Spectrometer.from_serial_number(self.serial_number)
        self._wavelengths = self.device.wavelengths().tolist()
```

Methods that are neither decorated with action decorator nor acting as getters-setters of properties remain as plain python methods and are **not** accessible on the network.

In WoT Terminology, again, such a method becomes specified as an action affordance:

```JSON
"connect": {
    "title": "connect",
    "description": "connect to spectrometer with given serial number",
    "forms": [
        {
            "href": "https://example.com/spectrometer/connect",
            "op": "invokeaction",
            "htv:methodName": "POST",
            "contentType": "application/json"
        }
    ],
    "safe": true,
    "idempotent": false,
    "synchronous": true
},

```

#### Defining and pushing events

create a named event using `Event` object that can push any arbitrary data:

```python

    def __init__(self, instance_name, serial_number, **kwargs):
        super().__init__(instance_name=instance_name, serial_number=serial_number, **kwargs)
        self.measurement_event = Event(name='intensity-measurement',
                    URL_path='/intensity/measurement-event')) # only GET HTTP method possible for events

    def capture(self): # not an action, but a plain python method
        self._run = True 
        while self._run:
            self._intensity = self.device.intensities(
                                        correct_dark_counts=True,
                                        correct_nonlinearity=True
                                    )
            self.measurement_event.push(self._intensity.tolist())

    @action(URL_path='/acquisition/start', http_method="POST")
    def start_acquisition(self):
        self._acquisition_thread = threading.Thread(target=self.capture) 
        self._acquisition_thread.start()

    @action(URL_path='/acquisition/stop', http_method="POST")
    def stop_acquisition(self):
        self._run = False 
```

In WoT Terminology, such an event becomes specified as an event affordance with subprotocol SSE:

```JSON
"intensity_measurement_event": {
    "forms": [
        {
            "href": "https://example.com/spectrometer/intensity/measurement-event",
            "subprotocol": "sse",
            "op": "subscribeevent",
            "htv:methodName": "GET",
            "contentType": "text/event-stream"
        }
    ]
}

```

Although the code is the very familiar & age-old RPC server style, one can directly specify HTTP methods and URL path for each property, action and event. A configurable HTTP Server is already available (from `hololinked.server.HTTPServer`) which redirects HTTP requests to the object according to the specified HTTP API on the properties, actions and events. To plug in a HTTP server: 

```python
import ssl, os, logging
from multiprocessing import Process
from hololinked.server import HTTPServer

def start_https_server():
    # You need to create a certificate on your own 
    ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS)
    ssl_context.load_cert_chain(f'assets{os.sep}security{os.sep}certificate.pem',
                        keyfile = f'assets{os.sep}security{os.sep}key.pem')

    HTTPServer(['spectrometer'], port=8083, ssl_context=ssl_context, 
                      log_level=logging.DEBUG).listen()


if __name__ == "__main__":
   
    Process(target=start_https_server).start()
  
    OceanOpticsSpectrometer(
        instance_name='spectrometer',
        serial_number='S14155',
        log_level=logging.DEBUG
    ).run()

```
Here one can see the use of `instance_name` and why it turns up in the URL path.

The intention behind specifying HTTP URL paths and methods directly on object's members is to 
- eliminate the need to implement a detailed HTTP server (& its API) which generally poses problems in queueing commands issued to instruments
- or, write an additional boiler-plate HTTP to RPC bridge
- or, find a reasonable HTTP-RPC implementation which supports all three of properties, actions and events, yet appeals deeply to the object oriented python world. 

See a list of currently supported features [below](#currently-supported). <br/> <br/>
Ultimately, as expected, the redirection from the HTTP side to the object is mediated by ZeroMQ which implements the fully fledged RPC server that queues all the HTTP requests to execute them one-by-one on the hardware/object. The HTTP server can also communicate with the RPC server over ZeroMQ's INPROC (for the non-expert = multithreaded applications, at least in python) or IPC (for the non-expert = multiprocess applications) transport methods. In the example above, IPC is used by default. There is no need for yet another TCP from HTTP to TCP to ZeroMQ transport athough this is also supported. <br/> <br/>
Serialization-Deserialization overheads are also already reduced. For example, when pushing an event from the object which gets automatically tunneled as a HTTP SSE or returning a reply for an action from the object, there is no JSON deserialization-serialization overhead when the message passes through the HTTP server. The message is serialized once on the object side but passes transparently through the HTTP server.     

One may use the HTTP API according to one's beliefs (including letting the package auto-generate it), although it is mainly intended for web development and cross platform clients like the interoperable [node-wot](https://github.com/eclipse-thingweb/node-wot) client. The node-wot client is the recommended Javascript client for this package as one can seamlessly plugin code developed from this package to the rest of the IoT tools, protocols & standardizations, or do scripting on the browser or nodeJS. Please check node-wot docs on how to consume [Thing Descriptions](https://www.w3.org/TR/wot-thing-description11) to call actions, read & write properties or subscribe to events. A Thing Description will be automatically generated if absent as shown in JSON examples above or can be supplied manually. 
To know more about client side scripting, please look into the documentation [How-To](https://hololinked.readthedocs.io/en/latest/howto/index.html) section.

##### NOTE - The package is under active development. Contributors welcome. 

- [example repository](https://github.com/VigneshVSV/hololinked-examples) - detailed examples for both clients and servers
- [helper GUI](https://github.com/VigneshVSV/hololinked-portal) - view & interact with your object's methods, properties and events. 

### To Install

Clone the repository and install in develop mode `pip install -e .` for convenience. The conda env hololinked.yml can also help. 

From pip - ``pip install hololinked``

### Currently Supported

- indicate HTTP verb & URL path directly on object's methods, properties and events.
- control method execution and property write with a custom finite state machine.
- database (Postgres, MySQL, SQLite - based on SQLAlchemy) support for storing and loading properties  when object dies and restarts. 
- auto-generate Thing Description for Web of Things applications (inaccurate, continuously developed but usable). 
- use serializer of your choice (except for HTTP) - MessagePack, JSON, pickle etc. & extend serialization to suit your requirement. HTTP Server will support only JSON serializer to maintain compatibility with node-wot. Default is JSON serializer based on msgspec.
- asyncio compatible - async RPC server event-loop and async HTTP Server - write methods in async 
- have flexibility in process architecture - run HTTP Server & python object in separate processes or in the same process, serve multiple objects with same HTTP server etc. 
- choose from multiple ZeroMQ transport methods. 

Again, please check examples or the code for explanations. Documentation is being activety improved. 

### Currently being worked

- improving accuracy of Thing Descriptions 
- observable properties and read/write multiple properties through node-wot client
- argument schema validation for actions (you can specify one but its not used)
- credentials for authentication 

### Some Day In Future

- mongo DB support for DB operations
- HTTP 2.0 

### Contact

Contributors welcome for all my projects related to hololinked including web apps. Please write to my contact email available at my [website](https://hololinked.dev). The contributing file is currently only boilerplate and need not be adhered. 








            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/VigneshVSV/hololinked",
    "name": "hololinked",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "data-acquisition, zmq-rpc, SCADA/IoT, Web of Things",
    "author": "Vignesh Vaidyanathan",
    "author_email": "vignesh.vaidyanathan@hololinked.dev",
    "download_url": "https://files.pythonhosted.org/packages/f8/ac/8a1e2270f2351f8d992bc3d36872440bd989674a1640bfc39751ded51aa4/hololinked-0.1.2.tar.gz",
    "platform": null,
    "description": "# hololinked\r\n\r\n### Description\r\n\r\nFor beginners - `hololinked` is a server side pythonic package suited for instrumentation control and data acquisition over network, especially with HTTP. If you have a requirement to control and capture data from your hardware/instrumentation remotely through your network, show the data in a web browser/dashboard, use IoT tools, provide a Qt-GUI or run automated scripts, hololinked can help. One can start small from a single device, and if interested, move ahead to build a bigger system made of individual components. \r\n<br/> <br/>\r\nFor those familiar with RPC & web development - `hololinked` is a ZeroMQ-based Object Oriented RPC toolkit with customizable HTTP end-points. \r\nThe main goal is to develop a pythonic & pure python modern package for instrumentation control and data acquisition through network (SCADA), along with \"reasonable\" HTTP support for web development.  \r\n\r\n<p align=\"left\">\r\n\r\n[![Documentation Status](https://readthedocs.org/projects/hololinked/badge/?version=latest)](https://hololinked.readthedocs.io/en/latest/?badge=latest) ![PyPI - Downloads](https://img.shields.io/pypi/dm/hololinked) [![Maintainability](https://api.codeclimate.com/v1/badges/913f4daa2960b711670a/maintainability)](https://codeclimate.com/github/VigneshVSV/hololinked/maintainability) \r\n\r\n<!-- ![PyPI](https://img.shields.io/pypi/v/hololinked?label=pypi%20package) -->\r\n\r\n\r\n</p>\r\n\r\n\r\n\r\n### Usage/Quickstart\r\n\r\n`hololinked` is compatible with the [Web of Things](https://www.w3.org/WoT/) recommended pattern for developing hardware/instrumentation control software. Each device or thing can be controlled systematically when their design in software is segregated into properties, actions and events. In object oriented terms for data acquisition:\r\n- properties are validated get-set attributes of the class which may be used to model device settings, hold captured/computed data etc.\r\n- actions are methods which issue commands to the device or run arbitrary python logic. \r\n- events can asynchronously communicate/push data to a client, like alarm messages, streaming captured data etc. \r\n\r\nIn `hololinked`, the base class which enables this classification is the `Thing` class. Any class that inherits the `Thing` class can instantiate properties, actions and events which become visible to a client in this segragated manner. For example, consider an optical spectrometer device, the following code is possible:\r\n\r\n#### Import Statements\r\n\r\n```python\r\n\r\nfrom hololinked.server import Thing, Property, action, Event\r\nfrom hololinked.server.properties import String, Integer, Number, List\r\n```\r\n\r\n#### Definition of one's own hardware controlling class\r\n\r\nsubclass from Thing class to \"make a network accessible Thing\":\r\n\r\n```python \r\n\r\nclass OceanOpticsSpectrometer(Thing):\r\n    \"\"\"\r\n    OceanOptics spectrometers using seabreeze library. Device is identified by serial number. \r\n    \"\"\"\r\n    \r\n```\r\n\r\n#### Instantiating properties\r\n\r\nSay, we wish to make device serial number, integration time and the captured intensity as properties. There are certain predefined properties available like `String`, `Number`, `Boolean` etc. \r\nor one may define one's own. To create properties:\r\n\r\n```python\r\n\r\nclass OceanOpticsSpectrometer(Thing):\r\n    \"\"\"class doc\"\"\"\r\n    \r\n    serial_number = String(default=None, allow_None=True, URL_path='/serial-number', \r\n                        doc=\"serial number of the spectrometer to connect/or connected\",\r\n                        http_method=(\"GET\", \"PUT\"))\r\n    # GET and PUT is default for reading and writing the property respectively. \r\n    # Use other HTTP methods if necessary.  \r\n\r\n    integration_time = Number(default=1000, bounds=(0.001, None), crop_to_bounds=True, \r\n                            URL_path='/integration-time', \r\n                            doc=\"integration time of measurement in milliseconds\")\r\n\r\n    intensity = List(default=None, allow_None=True, \r\n                    doc=\"captured intensity\", readonly=True, \r\n                    fget=lambda self: self._intensity)     \r\n\r\n    def __init__(self, instance_name, serial_number, **kwargs):\r\n        super().__init__(instance_name=instance_name, serial_number=serial_number, **kwargs)\r\n\r\n```\r\n\r\nIn non-expert terms, properties look like class attributes however their data containers are instantiated at object instance level by default.\r\nFor example, the `integratime_time` property defined above as `Number`, whenever set/written, will be validated as a float or int, cropped to bounds and assigned as an attribute to each instance of the `OceanOpticsSpectrometer` class with an internally generated name. It is not necessary to know this internally generated name as the property value can be accessed again in any python logic, say, `print(self.integration_time)`. \r\n\r\nTo overload the get-set (or read-write) of properties, one may do the following:\r\n\r\n```python\r\nclass OceanOpticsSpectrometer(Thing):\r\n\r\n    integration_time = Number(default=1000, bounds=(0.001, None), crop_to_bounds=True, \r\n                            URL_path='/integration-time', \r\n                            doc=\"integration time of measurement in milliseconds\")\r\n\r\n    @integration_time.setter # by default called on http PUT method\r\n    def apply_integration_time(self, value : float):\r\n        self.device.integration_time_micros(int(value*1000))\r\n        self._integration_time = int(value) \r\n      \r\n    @integration_time.getter # by default called on http GET method\r\n    def get_integration_time(self) -> float:\r\n        try:\r\n            return self._integration_time\r\n        except AttributeError:\r\n            return self.properties[\"integration_time\"].default \r\n\r\n```\r\n\r\nIn this case, instead of generating a data container with an internal name, the setter method is called when `integration_time` property is set/written. One might add the hardware device driver (say, supplied by the manufacturer) logic here to apply the property onto the device. In the above example, there is not a way provided by lower level library to read the value from the device, so we store it in a variable after applying it and supply the variable back to the getter method. Normally, one would also want the getter to read from the device directly.\r\n \r\nThose familiar with Web of Things (WoT) terminology may note that these properties generate the property affordance schema to become accessible by the [node-wot](https://github.com/eclipse-thingweb/node-wot) client. An example of autogenerated property affordance for `integration_time` is as follows:\r\n\r\n```JSON\r\n\"integration_time\": {\r\n    \"title\": \"integration_time\",\r\n    \"description\": \"integration time of measurement in milliseconds\",\r\n    \"constant\": false,\r\n    \"readOnly\": false,\r\n    \"writeOnly\": false,\r\n    \"type\": \"number\",\r\n    \"forms\": [{\r\n            \"href\": \"https://example.com/spectrometer/integration-time\",\r\n            \"op\": \"readproperty\",\r\n            \"htv:methodName\": \"GET\",\r\n            \"contentType\": \"application/json\"\r\n        },{\r\n            \"href\": \"https://example.com/spectrometer/integration-time\",\r\n            \"op\": \"writeproperty\",\r\n            \"htv:methodName\": \"PUT\",\r\n            \"contentType\": \"application/json\"\r\n        }\r\n    ],\r\n    \"observable\": false,\r\n    \"minimum\": 0.001\r\n},\r\n```\r\nThe URL path segment `../spectrometer/..` in href field is taken from the `instance_name` which was specified in the `__init__`. This is a mandatory key word argument to the parent class `Thing` to generate a unique name/id for the instance. One should use URI compatible strings.\r\n\r\n#### Specify methods as actions\r\n\r\ndecorate with `action` decorator on a python method to claim it as a network accessible method:\r\n\r\n```python\r\n\r\nclass OceanOpticsSpectrometer(Thing):\r\n\r\n    @action(URL_path='/connect', http_method=\"POST\") # POST is default for actions\r\n    def connect(self, serial_number = None):\r\n        \"\"\"connect to spectrometer with given serial number\"\"\"\r\n        if serial_number is not None:\r\n            self.serial_number = serial_number\r\n        self.device = Spectrometer.from_serial_number(self.serial_number)\r\n        self._wavelengths = self.device.wavelengths().tolist()\r\n```\r\n\r\nMethods that are neither decorated with action decorator nor acting as getters-setters of properties remain as plain python methods and are **not** accessible on the network.\r\n\r\nIn WoT Terminology, again, such a method becomes specified as an action affordance:\r\n\r\n```JSON\r\n\"connect\": {\r\n    \"title\": \"connect\",\r\n    \"description\": \"connect to spectrometer with given serial number\",\r\n    \"forms\": [\r\n        {\r\n            \"href\": \"https://example.com/spectrometer/connect\",\r\n            \"op\": \"invokeaction\",\r\n            \"htv:methodName\": \"POST\",\r\n            \"contentType\": \"application/json\"\r\n        }\r\n    ],\r\n    \"safe\": true,\r\n    \"idempotent\": false,\r\n    \"synchronous\": true\r\n},\r\n\r\n```\r\n\r\n#### Defining and pushing events\r\n\r\ncreate a named event using `Event` object that can push any arbitrary data:\r\n\r\n```python\r\n\r\n    def __init__(self, instance_name, serial_number, **kwargs):\r\n        super().__init__(instance_name=instance_name, serial_number=serial_number, **kwargs)\r\n        self.measurement_event = Event(name='intensity-measurement',\r\n                    URL_path='/intensity/measurement-event')) # only GET HTTP method possible for events\r\n\r\n    def capture(self): # not an action, but a plain python method\r\n        self._run = True \r\n        while self._run:\r\n            self._intensity = self.device.intensities(\r\n                                        correct_dark_counts=True,\r\n                                        correct_nonlinearity=True\r\n                                    )\r\n            self.measurement_event.push(self._intensity.tolist())\r\n\r\n    @action(URL_path='/acquisition/start', http_method=\"POST\")\r\n    def start_acquisition(self):\r\n        self._acquisition_thread = threading.Thread(target=self.capture) \r\n        self._acquisition_thread.start()\r\n\r\n    @action(URL_path='/acquisition/stop', http_method=\"POST\")\r\n    def stop_acquisition(self):\r\n        self._run = False \r\n```\r\n\r\nIn WoT Terminology, such an event becomes specified as an event affordance with subprotocol SSE:\r\n\r\n```JSON\r\n\"intensity_measurement_event\": {\r\n    \"forms\": [\r\n        {\r\n            \"href\": \"https://example.com/spectrometer/intensity/measurement-event\",\r\n            \"subprotocol\": \"sse\",\r\n            \"op\": \"subscribeevent\",\r\n            \"htv:methodName\": \"GET\",\r\n            \"contentType\": \"text/event-stream\"\r\n        }\r\n    ]\r\n}\r\n\r\n```\r\n\r\nAlthough the code is the very familiar & age-old RPC server style, one can directly specify HTTP methods and URL path for each property, action and event. A configurable HTTP Server is already available (from `hololinked.server.HTTPServer`) which redirects HTTP requests to the object according to the specified HTTP API on the properties, actions and events. To plug in a HTTP server: \r\n\r\n```python\r\nimport ssl, os, logging\r\nfrom multiprocessing import Process\r\nfrom hololinked.server import HTTPServer\r\n\r\ndef start_https_server():\r\n    # You need to create a certificate on your own \r\n    ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS)\r\n    ssl_context.load_cert_chain(f'assets{os.sep}security{os.sep}certificate.pem',\r\n                        keyfile = f'assets{os.sep}security{os.sep}key.pem')\r\n\r\n    HTTPServer(['spectrometer'], port=8083, ssl_context=ssl_context, \r\n                      log_level=logging.DEBUG).listen()\r\n\r\n\r\nif __name__ == \"__main__\":\r\n   \r\n    Process(target=start_https_server).start()\r\n  \r\n    OceanOpticsSpectrometer(\r\n        instance_name='spectrometer',\r\n        serial_number='S14155',\r\n        log_level=logging.DEBUG\r\n    ).run()\r\n\r\n```\r\nHere one can see the use of `instance_name` and why it turns up in the URL path.\r\n\r\nThe intention behind specifying HTTP URL paths and methods directly on object's members is to \r\n- eliminate the need to implement a detailed HTTP server (& its API) which generally poses problems in queueing commands issued to instruments\r\n- or, write an additional boiler-plate HTTP to RPC bridge\r\n- or, find a reasonable HTTP-RPC implementation which supports all three of properties, actions and events, yet appeals deeply to the object oriented python world. \r\n\r\nSee a list of currently supported features [below](#currently-supported). <br/> <br/>\r\nUltimately, as expected, the redirection from the HTTP side to the object is mediated by ZeroMQ which implements the fully fledged RPC server that queues all the HTTP requests to execute them one-by-one on the hardware/object. The HTTP server can also communicate with the RPC server over ZeroMQ's INPROC (for the non-expert = multithreaded applications, at least in python) or IPC (for the non-expert = multiprocess applications) transport methods. In the example above, IPC is used by default. There is no need for yet another TCP from HTTP to TCP to ZeroMQ transport athough this is also supported. <br/> <br/>\r\nSerialization-Deserialization overheads are also already reduced. For example, when pushing an event from the object which gets automatically tunneled as a HTTP SSE or returning a reply for an action from the object, there is no JSON deserialization-serialization overhead when the message passes through the HTTP server. The message is serialized once on the object side but passes transparently through the HTTP server.     \r\n\r\nOne may use the HTTP API according to one's beliefs (including letting the package auto-generate it), although it is mainly intended for web development and cross platform clients like the interoperable [node-wot](https://github.com/eclipse-thingweb/node-wot) client. The node-wot client is the recommended Javascript client for this package as one can seamlessly plugin code developed from this package to the rest of the IoT tools, protocols & standardizations, or do scripting on the browser or nodeJS. Please check node-wot docs on how to consume [Thing Descriptions](https://www.w3.org/TR/wot-thing-description11) to call actions, read & write properties or subscribe to events. A Thing Description will be automatically generated if absent as shown in JSON examples above or can be supplied manually. \r\nTo know more about client side scripting, please look into the documentation [How-To](https://hololinked.readthedocs.io/en/latest/howto/index.html) section.\r\n\r\n##### NOTE - The package is under active development. Contributors welcome. \r\n\r\n- [example repository](https://github.com/VigneshVSV/hololinked-examples) - detailed examples for both clients and servers\r\n- [helper GUI](https://github.com/VigneshVSV/hololinked-portal) - view & interact with your object's methods, properties and events. \r\n\r\n### To Install\r\n\r\nClone the repository and install in develop mode `pip install -e .` for convenience. The conda env hololinked.yml can also help. \r\n\r\nFrom pip - ``pip install hololinked``\r\n\r\n### Currently Supported\r\n\r\n- indicate HTTP verb & URL path directly on object's methods, properties and events.\r\n- control method execution and property write with a custom finite state machine.\r\n- database (Postgres, MySQL, SQLite - based on SQLAlchemy) support for storing and loading properties  when object dies and restarts. \r\n- auto-generate Thing Description for Web of Things applications (inaccurate, continuously developed but usable). \r\n- use serializer of your choice (except for HTTP) - MessagePack, JSON, pickle etc. & extend serialization to suit your requirement. HTTP Server will support only JSON serializer to maintain compatibility with node-wot. Default is JSON serializer based on msgspec.\r\n- asyncio compatible - async RPC server event-loop and async HTTP Server - write methods in async \r\n- have flexibility in process architecture - run HTTP Server & python object in separate processes or in the same process, serve multiple objects with same HTTP server etc. \r\n- choose from multiple ZeroMQ transport methods. \r\n\r\nAgain, please check examples or the code for explanations. Documentation is being activety improved. \r\n\r\n### Currently being worked\r\n\r\n- improving accuracy of Thing Descriptions \r\n- observable properties and read/write multiple properties through node-wot client\r\n- argument schema validation for actions (you can specify one but its not used)\r\n- credentials for authentication \r\n\r\n### Some Day In Future\r\n\r\n- mongo DB support for DB operations\r\n- HTTP 2.0 \r\n\r\n### Contact\r\n\r\nContributors welcome for all my projects related to hololinked including web apps. Please write to my contact email available at my [website](https://hololinked.dev). The contributing file is currently only boilerplate and need not be adhered. \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n",
    "bugtrack_url": null,
    "license": "BSD-3-Clause",
    "summary": "A ZMQ-based Object Oriented RPC tool-kit with HTTP support for instrument control/data acquisition or controlling generic python objects.",
    "version": "0.1.2",
    "project_urls": {
        "Homepage": "https://github.com/VigneshVSV/hololinked"
    },
    "split_keywords": [
        "data-acquisition",
        " zmq-rpc",
        " scada/iot",
        " web of things"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1e2957f1420f9d385bce0539da5525b29b5f5ee15c902471b1946cb40fcd136f",
                "md5": "3b6e8f1a0f103684de51a9c0657c1ec3",
                "sha256": "4063837938e59e55e80076bfbc592371a32bb43f35ff66f70952efe8d23158fb"
            },
            "downloads": -1,
            "filename": "hololinked-0.1.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "3b6e8f1a0f103684de51a9c0657c1ec3",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 181217,
            "upload_time": "2024-06-06T03:08:00",
            "upload_time_iso_8601": "2024-06-06T03:08:00.035093Z",
            "url": "https://files.pythonhosted.org/packages/1e/29/57f1420f9d385bce0539da5525b29b5f5ee15c902471b1946cb40fcd136f/hololinked-0.1.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f8ac8a1e2270f2351f8d992bc3d36872440bd989674a1640bfc39751ded51aa4",
                "md5": "e08c2ad9cd8c693d20264e135eeb0cc9",
                "sha256": "f65723c5053d5257da5bf9257bea8a24882b01da07d21008c169a02868608ef1"
            },
            "downloads": -1,
            "filename": "hololinked-0.1.2.tar.gz",
            "has_sig": false,
            "md5_digest": "e08c2ad9cd8c693d20264e135eeb0cc9",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 171247,
            "upload_time": "2024-06-06T03:08:02",
            "upload_time_iso_8601": "2024-06-06T03:08:02.815722Z",
            "url": "https://files.pythonhosted.org/packages/f8/ac/8a1e2270f2351f8d992bc3d36872440bd989674a1640bfc39751ded51aa4/hololinked-0.1.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-06-06 03:08:02",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "VigneshVSV",
    "github_project": "hololinked",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [],
    "lcname": "hololinked"
}
        
Elapsed time: 1.54724s