{"id":13341742,"url":"https://github.com/hololinked-dev/hololinked","last_synced_at":"2026-03-06T07:01:51.121Z","repository":{"id":203821489,"uuid":"708549511","full_name":"hololinked-dev/hololinked","owner":"hololinked-dev","description":"beginner friendly data acquisition \u0026 IoT in python","archived":false,"fork":false,"pushed_at":"2026-02-28T19:48:01.000Z","size":2540,"stargazers_count":23,"open_issues_count":48,"forks_count":7,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-28T22:51:12.934Z","etag":null,"topics":["data-acquisiton","instrumentation-and-control","iot","laboratory-automation","remote-data-logging","rpc","scada","web-of-things","wot","zmq"],"latest_commit_sha":null,"homepage":"https://docs.hololinked.dev","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hololinked-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"license.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"VigneshVSV","open_collective":"hololinked-dev","buy_me_a_coffee":"vigneshvsv"}},"created_at":"2023-10-22T21:51:28.000Z","updated_at":"2026-02-21T09:39:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"5645db5c-3d0e-43e8-89c3-9fc0f626f91c","html_url":"https://github.com/hololinked-dev/hololinked","commit_stats":null,"previous_names":["vigneshvsv/daqpy","vigneshvsv/hololinked","hololinked-dev/hololinked"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/hololinked-dev/hololinked","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hololinked-dev%2Fhololinked","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hololinked-dev%2Fhololinked/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hololinked-dev%2Fhololinked/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hololinked-dev%2Fhololinked/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hololinked-dev","download_url":"https://codeload.github.com/hololinked-dev/hololinked/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hololinked-dev%2Fhololinked/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30164884,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T04:43:31.446Z","status":"ssl_error","status_checked_at":"2026-03-06T04:40:30.133Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-acquisiton","instrumentation-and-control","iot","laboratory-automation","remote-data-logging","rpc","scada","web-of-things","wot","zmq"],"created_at":"2024-07-29T19:25:43.742Z","updated_at":"2026-03-06T07:01:51.085Z","avatar_url":"https://github.com/hololinked-dev.png","language":"Python","funding_links":["https://github.com/sponsors/VigneshVSV","https://opencollective.com/hololinked-dev","https://buymeacoffee.com/vigneshvsv"],"categories":[],"sub_categories":[],"readme":"# hololinked - Pythonic Object-Oriented Supervisory Control \u0026 Data Acquisition / Internet of Things\n\n## Description\n\n`hololinked` is a beginner-friendly, extensible pythonic tool suited for instrumentation control and data acquisition over network (IoT \u0026 SCADA).\n\nAs a novice, you have a requirement to control and capture data from your hardware, say in your electronics or science lab, and you want to show the data in a dashboard, provide a PyQt GUI or run automated scripts, `hololinked` can help. Even for isolated desktop applications or a small setup without networking, one can still separate the concerns of the tools that interact with the hardware \u0026 the hardware itself.\n\nIf you are a web developer or an industry professional looking for a web standards compatible (high-speed) IoT runtime, `hololinked` can be a decent choice. By conforming to [W3C Web of Things](https://www.w3.org/WoT/), one can expect a consistent API and flexible bidirectional message flow to interact with your devices, irrespective of the underlying protocol. Currently HTTP, MQTT \u0026 ZMQ are supported. See [Use Cases Table](https://docs.hololinked.dev/introduction/use-cases).\n\nThis implementation is based on RPC, built ground-up in python keeping both the latest web technologies and python principles in mind.\n\n[![CI Pipeline](https://github.com/hololinked-dev/hololinked/actions/workflows/ci-pipeline.yml/badge.svg)](https://github.com/hololinked-dev/hololinked/actions/workflows/ci-pipeline.yml) [![Documentation Status](https://img.shields.io/github/actions/workflow/status/hololinked-dev/docs/ci.yaml?label=Build%20And%20Publish%20Docs)](https://github.com/hololinked-dev/docs) [![SAST](https://img.shields.io/badge/sast%20reports-bandit-yellow.svg)](https://docs.hololinked.dev/introduction/security-scanning) ![Ruff](https://img.shields.io/badge/linter-ruff-blue?logo=ruff\u0026logoColor=white) [![PyPI](https://img.shields.io/pypi/v/hololinked?label=pypi%20package)](https://pypi.org/project/hololinked/) [![Anaconda](https://anaconda.org/conda-forge/hololinked/badges/version.svg)](https://anaconda.org/conda-forge/hololinked) [![codecov](https://codecov.io/github/hololinked-dev/hololinked/graph/badge.svg?token=5DI4XJ2KX9)](https://codecov.io/github/hololinked-dev/hololinked) [![Conda Downloads](https://img.shields.io/conda/d/conda-forge/hololinked)](https://anaconda.org/conda-forge/hololinked) [![PyPI - Downloads](https://img.shields.io/pypi/dm/hololinked?label=pypi%20downloads)](https://pypistats.org/packages/hololinked) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.15155942.svg)](https://doi.org/10.5281/zenodo.12802841) [![Discord](https://img.shields.io/discord/1265289049783140464?label=Discord%20Members\u0026logo=discord)](https://discord.com/invite/kEz87zqQXh) [![email](https://img.shields.io/badge/email-brown)](mailto:info@hololinked.dev) [![Feedback Form](https://img.shields.io/badge/feedback%20form-red)](https://forms.gle/FB4XwkUDt1wV4GGPA)\n\n## To Install\n\nFrom pip - `pip install hololinked` \u003cbr\u003e\nFrom conda - \u003cbr\u003e\n`pip install aiomqtt` (needs to be installed separately) \u003cbr\u003e\n`conda install -c conda-forge hololinked` \u003cbr\u003e\n\nOr, clone the repository (main branch for latest codebase, which can also contain bugs) and install `pip install .` / `pip install -e .`. The [uv environment `uv.lock`](#setup-development-environment) can also help to setup all dependencies. Currently the dependencies are hard pinned to promote stability, therefore consider using a virtual environment.\n\n## Usage/Quickstart\n\nEach device or thing can be controlled systematically when their design in software is segregated into properties, actions and events. In object oriented terms:\n\n- the hardware is represented by a class\n- properties are validated get-set attributes of the class which may be used to model settings, hold captured/computed data or generic network accessible quantities\n- actions are methods which issue commands like connect/disconnect, execute a control routine, start/stop measurement, or run arbitrary python logic\n- events can asynchronously communicate/push arbitrary data to a client, like alarm messages, streaming measured quantities etc.\n\nFor example, consider an optical spectrometer, the following code is possible:\n\n### Import Statements\n\n```python\nfrom hololinked.core import Thing, Property, action, Event # interactions with hardware\nfrom hololinked.core.properties import String, Integer, Number, List # some property types\nfrom seabreeze.spectrometers import Spectrometer # a device driver\n```\n\n### Definition of one's own Hardware Controlling Class\n\nsubclass from `Thing` class to make a \"network accessible Thing\":\n\n```python\nclass OceanOpticsSpectrometer(Thing):\n    \"\"\"\n    OceanOptics spectrometers using seabreeze library. Device is identified by serial number.\n    \"\"\"\n```\n\n### Instantiating Properties\n\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. or one may define one's own using [pydantic or JSON schema](https://docs.hololinked.dev/howto/articles/properties/#schema-constrained-property). To create properties:\n\n```python\nclass OceanOpticsSpectrometer(Thing):\n    \"\"\"class doc\"\"\"\n\n    serial_number = String(default=None, allow_None=True,\n                        doc=\"serial number of the spectrometer to connect/or connected\")\n\n    integration_time = Number(default=1000, bounds=(0.001, None), crop_to_bounds=True,\n                        doc=\"integration time of measurement in milliseconds\")\n\n    intensity = List(default=None, allow_None=True, doc=\"captured intensity\", readonly=True,\n                        fget=lambda self: self._intensity)\n\n    def __init__(self, id, serial_number, **kwargs):\n        super().__init__(id=id, serial_number=serial_number, **kwargs)\n```\n\nIn non-expert terms, properties look like class attributes however their data containers are instantiated at object instance level by default. This is possible due to [python descriptor protocol](https://realpython.com/python-descriptors/). For example, the `integration_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 using the dot operator, say, `print(self.integration_time)`.\n\nOne may overload the get-set (or read-write) of properties to customize their behavior:\n\n```python\nclass OceanOpticsSpectrometer(Thing):\n\n    integration_time = Number(default=1000, bounds=(0.001, None), crop_to_bounds=True,\n                            doc=\"integration time of measurement in milliseconds\")\n\n    @integration_time.setter\n    def set_integration_time(self, value : float):\n        self.device.write_integration_time_micros(int(value*1000))\n        # seabreeze does not provide a write_integration_time_micros method,\n        # this is only an example\n\n    @integration_time.getter\n    def get_integration_time(self) -\u003e float:\n        try:\n            return self.device.read_integration_time_micros() / 1000\n            # seabreeze does not provide a read_integration_time_micros method,\n            # this is only an example\n        except AttributeError:\n            return self.properties[\"integration_time\"].default\n\n```\n\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 logic here (say, supplied by the manufacturer) or a protocol that applies the property directly onto the device. One would also want the getter to read from the device directly as well.\n\nThose familiar with Web of Things (WoT) terminology may note that these properties generate the property affordance. An example for `integration_time` is as follows:\n\n```JSON\n\"integration_time\": {\n    \"title\": \"integration_time\",\n    \"description\": \"integration time of measurement in milliseconds\",\n    \"type\": \"number\",\n    \"forms\": [{\n            \"href\": \"https://example.com/spectrometer/integration-time\",\n            \"op\": \"readproperty\",\n            \"htv:methodName\": \"GET\",\n            \"contentType\": \"application/json\"\n        },{\n            \"href\": \"https://example.com/spectrometer/integration-time\",\n            \"op\": \"writeproperty\",\n            \"htv:methodName\": \"PUT\",\n            \"contentType\": \"application/json\"\n        }\n    ],\n    \"minimum\": 0.001\n},\n```\n\nIf you are **not familiar** with Web of Things or the term \"property affordance\", consider the above JSON as a description of\nwhat the property represents and how to interact with it from somewhere else (in this case, over HTTP). Such a JSON is both human-readable, yet consumable by any application that may use the property - say, a client provider to create a client object to interact with the property or a GUI application to autogenerate a suitable input field for this property.\n\n[![Property Documentation](https://img.shields.io/badge/Property%20Docs-Read%20More-blue?logo=readthedocs)](https://docs.hololinked.dev/beginners-guide/articles/properties/) [![Try it Out](https://img.shields.io/badge/Try%20it%20Out-Live%20Demo-brightgreen?logo=python)](https://control-panel.hololinked.dev/#https://examples.hololinked.dev/simulations/oscilloscope/resources/wot-td)\n\n### Specify Methods as Actions\n\ndecorate with `action` decorator on a python method to claim it as a network accessible method:\n\n```python\n\nclass OceanOpticsSpectrometer(Thing):\n\n    @action(input_schema={\"type\": \"object\", \"properties\": {\"serial_number\": {\"type\": \"string\"}}})\n    def connect(self, serial_number = None):\n        \"\"\"connect to spectrometer with given serial number\"\"\"\n        if serial_number is not None:\n            self.serial_number = serial_number\n        self.device = Spectrometer.from_serial_number(self.serial_number)\n        self._wavelengths = self.device.wavelengths().tolist()\n\n    @action()\n    def disconnect(self):\n        \"\"\"disconnect from the spectrometer\"\"\"\n        self.device.close()\n```\n\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.\n\nIn WoT Terminology, again, such a method becomes specified as an action affordance (or a description of what the action represents and how to interact with it):\n\n```JSON\n\"connect\": {\n    \"title\": \"connect\",\n    \"description\": \"connect to spectrometer with given serial number\",\n    \"forms\": [\n        {\n            \"href\": \"https://example.com/spectrometer/connect\",\n            \"op\": \"invokeaction\",\n            \"htv:methodName\": \"POST\",\n            \"contentType\": \"application/json\"\n        }\n    ],\n    \"input\": {\n        \"type\": \"object\",\n        \"properties\": {\n            \"serial_number\": {\n                \"type\": \"string\"\n            }\n        },\n        \"additionalProperties\": false\n    }\n},\n```\n\n\u003e input and output schema (\"input\" field above which describes the argument type `serial_number`) are optional and are discussed in docs\n\n[![Actions Documentation](https://img.shields.io/badge/Actions%20Docs-Read%20More-blue?logo=readthedocs)](https://docs.hololinked.dev/beginners-guide/articles/actions/) [![Try it Out](https://img.shields.io/badge/Try%20it%20Out-Live%20Demo-brightgreen?logo=python)](https://control-panel.hololinked.dev/#https://examples.hololinked.dev/simulations/oscilloscope/resources/wot-td)\n\n### Defining and Pushing Events\n\ncreate a named event using `Event` object that can push any arbitrary serializable data:\n\n```python\nclass OceanOpticsSpectrometer(Thing):\n\n    intensity_measurement_event = Event(name='intensity-measurement-event',\n            doc=\"\"\"event generated on measurement of intensity,\n            max 30 per second even if measurement is faster.\"\"\",\n            schema=intensity_event_schema)\n            # schema is optional and will be discussed in documentation,\n            # assume the intensity_event_schema variable is valid\n\n    def capture(self): # not an action, but a plain python method\n        self._run = True\n        last_time = time.time()\n        while self._run:\n            self._intensity = self.device.intensities(\n                                        correct_dark_counts=False,\n                                        correct_nonlinearity=False\n                                    )\n            curtime = datetime.datetime.now()\n            measurement_timestamp = curtime.strftime('%d.%m.%Y %H:%M:%S.') + '{:03d}'.format(\n                                                            int(curtime.microsecond /1000))\n            if time.time() - last_time \u003e 0.033: # restrict speed to avoid overloading\n                self.intensity_measurement_event.push({\n                    \"timestamp\" : measurement_timestamp,\n                    \"value\" : self._intensity.tolist()\n                })\n                last_time = time.time()\n\n    @action()\n    def start_acquisition(self):\n        if self._acquisition_thread is not None and self._acquisition_thread.is_alive():\n            return\n        self._acquisition_thread = threading.Thread(target=self.capture)\n        self._acquisition_thread.start()\n\n    @action()\n    def stop_acquisition(self):\n        self._run = False\n```\n\nEvents can stream live data without polling or push data to a client whose generation in time is uncontrollable.\n\nIn WoT Terminology, such an event becomes specified as an event affordance (or a description of\nwhat the event represents and how to subscribe to it) with subprotocol SSE:\n\n```JSON\n\"intensity_measurement_event\": {\n    \"title\": \"intensity-measurement-event\",\n    \"description\": \"event generated on measurement of intensity, max 30 per second even if measurement is faster.\",\n    \"forms\": [\n        {\n          \"href\": \"https://example.com/spectrometer/intensity/measurement-event\",\n          \"subprotocol\": \"sse\",\n          \"op\": \"subscribeevent\",\n          \"htv:methodName\": \"GET\",\n          \"contentType\": \"text/plain\"\n        }\n    ],\n    \"data\": {\n        \"type\": \"object\",\n        \"properties\": {\n            \"value\": {\n                \"type\": \"array\",\n                \"items\": {\n                    \"type\": \"number\"\n                }\n            },\n            \"timestamp\": {\n                \"type\": \"string\"\n            }\n        }\n    }\n}\n```\n\n\u003e data schema (\"data\" field above which describes the event payload) are optional and discussed in documentation\n\nEvents follow a pub-sub model with '1 publisher to N subscribers' per `Event` object, through any supported protocol like HTTP server sent events (brokerless) or MQTT (brokered).\n\n[![Events Documentation](https://img.shields.io/badge/Events%20Docs-Read%20More-blue?logo=readthedocs)](https://docs.hololinked.dev/beginners-guide/articles/events/) [![Try it Out](https://img.shields.io/badge/Try%20it%20Out-Live%20Demo-brightgreen?logo=python)](https://control-panel.hololinked.dev/#https://examples.hololinked.dev/simulations/oscilloscope/resources/wot-td)\n\n### Start with a Protocol Server\n\nOne can start the Thing object with one or more protocols simultaneously. Currently HTTP, MQTT \u0026 ZMQ is supported. With HTTP server:\n\n```python\nimport ssl, os, logging\n\nif __name__ == '__main__':\n    ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER)\n    ssl_context.load_cert_chain(f'assets{os.sep}security{os.sep}certificate.pem',\n                        keyfile = f'assets{os.sep}security{os.sep}key.pem')\n    ssl_context.minimum_version = ssl.TLSVersion.TLSv1_3\n\n    OceanOpticsSpectrometer(\n        id='spectrometer',\n        serial_number='S14155',\n        log_level=logging.DEBUG\n    ).run_with_http_server(\n        port=9000, ssl_context=ssl_context\n    )\n```\n\nThe base URL is constructed as `http(s)://\u003chostname\u003e:\u003cport\u003e/\u003cthing_id\u003e`\n\nWith ZMQ:\n\n```python\n\nif __name__ == '__main__':\n    OceanOpticsSpectrometer(\n        id='spectrometer',\n        serial_number='S14155',\n    ).run(\n        access_points=['IPC', 'tcp://*:9999']\n    )\n    # both interprocess communication \u0026 TCP\n```\n\nMultiple:\n\n```python\nfrom hololinked.server import HTTPServer, MQTTPublisher, ZMQServer\n\nif __name__ == '__main__':\n    http_server = HTTPServer(port=9000, ssl_context=http_ssl_context)\n    mqtt_publisher = MQTTPublisher(hostname='mqtt.example.com', ssl_context=mqtt_ssl_context)\n\n    OceanOpticsSpectrometer(\n        id='spectrometer',\n        serial_number='S14155',\n    ).run(\n        servers=[http_server, mqtt_publisher]\n    )\n    # HTTP \u0026 MQTT\n```\n\nThere are other improved ways to configure protocol servers, please refer documentation for details.\n\n[![Protocol Servers Documentation](https://img.shields.io/badge/Protocol%20Servers%20Docs-Read%20More-blue?logo=readthedocs)](https://docs.hololinked.dev/beginners-guide/articles/protocols/general/)\n[![Resources to Get Started](https://img.shields.io/badge/Resources-Get%20Started-orange?logo=book)](#resources)\n\n## Client Side Applications\n\nTo compose client objects, the JSON description of the properties, actions and events are used, which are summarized into a [Thing Description](https://www.w3.org/TR/wot-thing-description11). These descriptions are autogenerated, so at least in the beginner stages, you dont need to know how they work. The following code would be possible:\n\n### Python Clients\n\nImport the `ClientFactory` and create an instance of the client for the desired protocol:\n\n```python\nfrom hololinked.client import ClientFactory\n\n# for HTTP\nthing = ClientFactory.http(url=\"http://localhost:8000/spectrometer/resources/wot-td\")\n# For HTTP, one needs to append `/resource/wot-td` to the base URL to construct the full URL as `http(s)://\u003chostname\u003e:\u003cport\u003e/\u003cthing_id\u003e/resources/wot-td`. At this endpoint, the Thing Description will be autogenerated and loaded to compose a client.\n\n# zmq IPC\nthing = ClientFactory.zmq(thing_id='spectrometer', access_point='IPC')\n# zmq TCP\nthing = ClientFactory.zmq(thing_id='spectrometer', access_point='tcp://localhost:9999')\n# For ZMQ, Thing Description loading is automatically mediated simply by specifying how to access the Thing\n```\n\nTo issue operations:\n\n\u003cdetails open\u003e\n\u003csummary\u003eRead Property\u003c/summary\u003e\n\n```python\nthing.read_property(\"integration_time\")\n# or use dot operator\nthing.integration_time\n```\n\nwithin an async function:\n\n```python\nasync def func():\n    await thing.async_read_property(\"integration_time\")\n    # dot operator not supported\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e \n\u003csummary\u003eWrite Property\u003c/summary\u003e\n\n```python\nthing.write_property(\"integration_time\", 2000)\n# or use dot operator\nthing.integration_time = 2000\n```\n\nwithin an async function:\n\n```python\nasync def func():\n    await thing.async_write_property(\"integration_time\", 2000)\n    # dot operator not supported\n```\n\n\u003cdetails open\u003e \n\u003csummary\u003eInvoke Action\u003c/summary\u003e\n\n```python\nthing.invoke_action(\"connect\", serial_number=\"S14155\")\n# or use dot operator\nthing.connect(serial_number=\"S14155\")\n```\n\nwithin an async function:\n\n```python\nasync def func():\n    await thing.async_invoke_action(\"connect\", serial_number=\"S14155\")\n    # dot operator not supported\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003eSubscribe to Event\u003c/summary\u003e\n\n```python\n\nthing.subscribe_event(\"intensity_measurement_event\", callbacks=lambda value: print(value))\n```\n\nThere is no async subscribe, as events by nature appear at arbitrary times only when pushed by the server. Yet, events can be asynchronously listened and callbacks can be asynchronously invoked. Please refer documentation. To unsubscribe:\n\n```python\nthing.unsubscribe_event(\"intensity_measurement_event\")\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003eObserve Property\u003c/summary\u003e\n\n```python\n\nthing.observe_property(\"integration_time\", callbacks=lambda value: print(value))\n```\n\nOnly observable properties (property where `observable` was set to `True`) can be observed. To unobserve:\n\n```python\nthing.unobserve_property(\"integration_time\")\n```\n\n\u003c/details\u003e\n\nOperations which rely on request-reply pattern (properties and actions) also support one-way and no-block calls:\n\n- `oneway` - issue the operation and dont collect the reply\n- `noblock` - issue the operation, obtain a message ID and collect the reply when you want\n\n[![Python Client Docs](https://img.shields.io/badge/Python%20Client%20Docs-Read%20More-blue?logo=readthedocs)](https://docs.hololinked.dev/beginners-guide/articles/object-proxy/)\n\n### Javascript Clients\n\nSimilary, one could consume the Thing Description in a Node.js script using Eclipse [ThingWeb node-wot](https://github.com/eclipse-thingweb/node-wot):\n\n```js\nconst { Servient } = require(\"@node-wot/core\");\nconst HttpClientFactory = require(\"@node-wot/binding-http\").HttpClientFactory;\n\nconst servient = new Servient();\nservient.addClientFactory(new HttpClientFactory());\n\nservient.start().then((WoT) =\u003e {\n    fetch(\"http://localhost:8000/spectrometer/resources/wot-td\")\n        .then((res) =\u003e res.json())\n        .then((td) =\u003e WoT.consume(td))\n        .then((thing) =\u003e {\n        thing.readProperty(\"integration_time\").then(async(interactionOutput) =\u003e {\n            console.log(\"Integration Time: \", await interactionOutput.value());\n        })\n)});\n```\n\nIf you're using HTTPS, just make sure the server certificate is valid or trusted by the client.\n\n```js\nconst HttpsClientFactory = require(\"@node-wot/binding-http\").HttpsClientFactory;\nservient.addClientFactory(new HttpsClientFactory({ allowSelfSigned: true }));\n```\n\n(example [here](https://gitlab.com/hololinked/examples/clients/node-clients/phymotion-controllers-app/-/blob/main/src/App.tsx?ref_type=heads#L77))\n\nTo issue operations:\n\n\u003cdetails open\u003e\n\u003csummary\u003eRead Property\u003c/summary\u003e\n\n`thing.readProperty(\"integration_time\").then(async(interactionOutput) =\u003e {\n  console.log(\"Integration Time:\", await interactionOutput.value());\n});`\n\n\u003c/details\u003e\n\u003cdetails open\u003e \n\u003csummary\u003eWrite Property\u003c/summary\u003e\n\n`thing.writeProperty(\"integration_time\", 2000).then(() =\u003e {\n  console.log(\"Integration Time updated\");\n});`\n\n\u003c/details\u003e\n\u003cdetails open\u003e\n\u003csummary\u003eInvoke Action\u003c/summary\u003e\n\n`thing.invokeAction(\"connect\", { serial_number: \"S14155\" }).then(() =\u003e {\n  console.log(\"Device connected\");\n});`\n\n\u003c/details\u003e\n\u003cdetails open\u003e\n\u003csummary\u003eSubscribe to Event\u003c/summary\u003e\n\n`thing.subscribeEvent(\"intensity_measurement_event\", async (interactionOutput) =\u003e {\n  console.log(\"Received event:\", await interactionOutput.value());\n});`\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003eObserve Property\u003c/summary\u003e\n\n`thing.observeProperty(\"integration_time\", async (interactionOutput) =\u003e {\n    console.log(\"Observed integration_time:\", await interactionOutput.value());\n});`\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eLinks to React Examples\u003c/summary\u003e\nIn React, the Thing Description may be fetched inside `useEffect` hook, the client passed via a `useContext` hook (or a global state manager). The individual operations can be performed in their own callbacks attached to DOM elements:\n\n- [fetch TD](https://gitlab.com/hololinked/examples/clients/node-clients/phymotion-controllers-app/-/blob/main/src/App.tsx?ref_type=heads#L96)\n- [issue operations](https://gitlab.com/hololinked/examples/clients/node-clients/phymotion-controllers-app/-/blob/main/src/components/movements.tsx?ref_type=heads#L54)\n\u003c/details\u003e\n\n\u003cbr\u003e\n\n[![node-wot docs](https://img.shields.io/badge/nodewot%20docs-Read%20More-blue?logo=JavaScript)](https://thingweb.io/docs/node-wot/API)\n\n## Resources\n\n- [examples repository](https://github.com/hololinked-dev/examples) - detailed examples for both clients and servers\n- [infrastructure components](https://github.com/hololinked-dev/daq-system-infrastructure) - docker compose files to setup postgres or mongo databases with admin interfaces, Identity and Access Management system among other components.\n- [helper GUI](https://github.com/hololinked-dev/thing-control-panel) - view \u0026 interact with your object's actions, properties and events.\n- [live demo](https://control-panel.hololinked.dev/#https://examples.hololinked.dev/simulations/oscilloscope/resources/wot-td) - an example of an oscilloscope available for live test\n\n\u003e You may use a script deployment/automation tool to remote stop and start servers, in an attempt to remotely control your hardware scripts.\n\n## Contributing\n\nSee [organization info](https://github.com/hololinked-dev) for details regarding contributing to this package. There are:\n\n- [good first issues](https://github.com/hololinked-dev/hololinked/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n- [setup development environment](https://docs.hololinked.dev/introduction/contributing#setup-development-environment)\n- [discord group](https://discord.com/invite/kEz87zqQXh)\n- [weekly meetings](https://github.com/hololinked-dev/#monthly-meetings) and\n- [project planning](https://github.com/orgs/hololinked-dev/projects/4) to discuss activities around this repository.\n\n## Currently Supported Features\n\nSome other features that are currently supported:\n\n- use a custom finite state machine.\n- database (Postgres, MySQL, SQLite - based on SQLAlchemy) support for storing and loading properties when the object dies and restarts.\n- auto-generate Thing Description for Web of Things applications.\n- use serializer of your choice (except for HTTP) - MessagePack, JSON, pickle etc. \u0026 extend serialization to suit your requirement\n- asyncio event loops on server side\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhololinked-dev%2Fhololinked","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhololinked-dev%2Fhololinked","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhololinked-dev%2Fhololinked/lists"}