diff --git a/requirements.txt b/requirements.txt index b55a66628829589c4f0208d470c07172d7ffbd8e..345d0acecfdaa2ac3dc4ae8f7cadbfc426d80f43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ Deprecated==1.2.13 jinja2==3.0.3 nest-asyncio==1.5.6 pytest==7.1.1 +rdflib==7.0.0 sphinx==3.5.2 sphinx-rtd-theme==1.0.0 strict-rfc3339==0.7 diff --git a/setup.py b/setup.py index 66ec6b6504b66cf525c9b0e962a47b1de1a8c1c3..96dc1e2d6bbbf930b9ddd13c788e5291d7bb7327 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() setup(name='wzl-udi', - version='8.2.5', + version='8.3.0', url='https://git-ce.rwth-aachen.de/wzl-mq-public/soil/python', project_urls={ "Bug Tracker": "https://git-ce.rwth-aachen.de/wzl-mq-public/soil/python/-/issues", @@ -21,10 +21,11 @@ setup(name='wzl-udi', "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], - install_requires=['aiohttp~=3.8.4', + install_requires=['aiohttp~=3.9.1', 'Deprecated~=1.2.13', 'nest-asyncio~=1.5.6', 'strict-rfc3339==0.7', - 'wzl-mqtt~=2.5.3' + 'wzl-mqtt~=2.5.3', + 'rdflib~=7.0.0' ], zip_safe=False) diff --git a/src/http/server.py b/src/http/server.py index 17c6f311cb12f221e8f9fc2ad354d6514e99e475..d0046f92a08629054d499dca7d4403c5ba1104b3 100644 --- a/src/http/server.py +++ b/src/http/server.py @@ -1,15 +1,16 @@ # -*- coding: utf-8 -*- import asyncio import functools +import json import traceback -from typing import Dict +from typing import Dict, Union +import rdflib from aiohttp import web from aiohttp.web import middleware from aiohttp.web_request import Request from multidict import MultiDict - from .error import ServerException from ..soil.component import Component from ..soil.element import Element @@ -19,8 +20,8 @@ from ..soil.function import Function from ..soil.measurement import Measurement from ..soil.parameter import Parameter from ..soil.stream import StreamScheduler -from ..utils import serialize from ..utils import root_logger +from ..utils import serialize from ..utils.constants import BASE_UUID_PATTERN, HTTP_GET, HTTP_OPTIONS from ..utils.error import DeviceException, UserException @@ -123,28 +124,48 @@ class HTTPServer(object): queried_attributes += [key] return queried_attributes - def prepare_response(self, body: Dict, element: Element, status: int = 200, query: MultiDict = None): + def prepare_response(self, body: Union[Dict, rdflib.Graph], element: Element, status: int = 200, + query: MultiDict = None): dataformat = self._dataformat - if query is not None and 'format' in query and query['format'] in ['json', 'xml']: + semantic = False + if query is not None and 'format' in query and query['format'] in ['json', 'xml', 'turtle']: dataformat = query['format'] + if query is not None and 'semantic' in query and query['semantic'] in ['data', 'shape']: + semantic = True if dataformat == 'json': + if semantic: + assert isinstance(body, rdflib.Graph) + # rdflib serialization returns a string, so we need to parse it as plain json again to return it properly + body = json.loads(body.serialize(format='json-ld')) return web.json_response(body, status=status) elif dataformat == 'xml': - if element is not None and 200 <= status <= 300: - root = '' - if isinstance(element, Component): - root = 'component' - elif isinstance(element, Function): - root = 'function' - elif isinstance(element, Measurement): - root = 'measurement' - elif isinstance(element, Parameter): - root = 'parameter' + if semantic: + assert isinstance(body, rdflib.Graph) + xml = body.serialize(format='xml') else: - root = 'error' - xml = serialize.to_xml(root, body) + if element is not None and 200 <= status <= 300: + root = '' + if isinstance(element, Component): + root = 'component' + elif isinstance(element, Function): + root = 'function' + elif isinstance(element, Measurement): + root = 'measurement' + elif isinstance(element, Parameter): + root = 'parameter' + else: + root = 'error' + xml = serialize.to_xml(root, body) return web.Response(text=xml, status=status, content_type='application/xml') + elif dataformat == 'turtle': + if semantic: + assert isinstance(body, rdflib.Graph) + text = body.serialize(format='turtle') + else: + return web.Response(text='Can not serialize non semantic information to Turtle', status=400, + content_type='text/plain') + return web.Response(text=text, status=status, content_type=' text/plain') async def get(self, request): logger.info("GET Request from {}".format(request.url)) @@ -152,6 +173,12 @@ class HTTPServer(object): logger.debug('Query Parameters: {}'.format(request.query_string)) keys = self._filter_query(request.query) + # determine whether a semantic answer is expected + semantic = None + if request.query is not None and 'semantic' in request.query and request.query['semantic'] in ['data', 'shape']: + semantic = request.query['semantic'] + + # retrieving the element try: item = self.root[HTTPServer.parse_uuids(request)] except KeyError as e: @@ -160,8 +187,12 @@ class HTTPServer(object): logger.error('Response: {}'.format(response)) return self.prepare_response(response, None, status=404, query=request.query) + # serializing the element try: - response = item.serialize(keys, self._legacy_mode, HTTP_GET) + if semantic: + response = item.serialize_semantics(semantic) + else: + response = item.serialize(keys, self._legacy_mode, HTTP_GET) status = 200 logger.info('Response: {}'.format(response)) except (DeviceException, ServerException, UserException) as e: diff --git a/src/soil/component.py b/src/soil/component.py index 18a0d28e37a53ccf4ef5ff7d6977199804b3b733..4bd9e499c2222f07543248b1c960f1fd2d4db714 100644 --- a/src/soil/component.py +++ b/src/soil/component.py @@ -9,9 +9,11 @@ Children elements can be components, functions, parameters or measurements. # from __future__ import annotations import json import os +import pprint import sys from typing import List, Any, Union, Dict +import rdflib from .element import Element from .error import ChildNotFoundException @@ -29,7 +31,8 @@ class Component(Element): def __init__(self, uuid: str, name: str, description: str, functions: List[Function], measurements: List[Measurement], - parameters: List[Parameter], components: List['Component'], implementation: Any, ontology: str = None): + parameters: List[Parameter], components: List['Component'], implementation: Any, ontology: str = None, + shape: str = None): """ Args: @@ -43,6 +46,7 @@ class Component(Element): components: List of all children components. Might contain dynamic-components. implementation: The class of the sensor layer implementing this component. ontology: Optional field containing the reference to a semantic definition of the components name or purpose. + shape: Optional field containing the name of the shape defining the restrictions of this component using semantic web technologies. Raises: ValueError: The UUID does not start with 'COM'. @@ -50,7 +54,7 @@ class Component(Element): InvalidModelException: One of the lists containing the components' children is not a list or contains elements which are not of the correct type. InvalidMappingException: If something is wrong with the provided mapping. """ - Element.__init__(self, uuid, name, description, ontology) + Element.__init__(self, uuid, name, description, ontology, shape) if uuid[:3] != 'COM': raise Exception('{}: The UUID must start with COM!'.format(uuid)) if not isinstance(functions, list): @@ -80,6 +84,10 @@ class Component(Element): self._parameters = parameters self._implementation = implementation + @property + def children(self) -> List[Element]: + return self._functions + self._measurements + self._components + self._parameters + def __getitem__(self, item: Union[str, List[str]], method: int = HTTP_GET) -> Any: """Returns the value of the specified item. @@ -181,7 +189,7 @@ class Component(Element): keys = [] if keys is None else keys if not keys: # list is empty - keys = ['uuid', 'name', 'description', 'children', 'ontology'] + keys = ['uuid', 'name', 'description', 'children', 'ontology', 'shape'] if 'all' in keys: # serialize complete tree recursively (overrides all other keys) dictionary = super().serialize([], legacy_mode) @@ -262,7 +270,8 @@ class Component(Element): if implementation is not None: child_implementation = None attributes = list(filter(lambda attr: attr[:5] == '_com_', dir(implementation))) - if f'_com_{obj["uuid"][4:].lower()}' in attributes and not isinstance(getattr(implementation, f'_com_{obj["uuid"][4:].lower()}'), dict): + if f'_com_{obj["uuid"][4:].lower()}' in attributes and not isinstance( + getattr(implementation, f'_com_{obj["uuid"][4:].lower()}'), dict): child_implementation = getattr(implementation, f'_com_{obj["uuid"][4:].lower()}') else: for attr in attributes: @@ -278,9 +287,10 @@ class Component(Element): '{}: An component of the component can not be deserialized. {}'.format(uuid, e)) try: ontology = dictionary['ontology'] if 'ontology' in dictionary else None + shape = dictionary['shape'] if 'shape' in dictionary else None return Component(dictionary['uuid'], dictionary['name'], dictionary['description'], functions, measurements, parameters, components, implementation, - ontology) + ontology, shape) except Exception as e: raise SerialisationException('{}: The component can not be deserialized. {}'.format(uuid, e)) @@ -367,3 +377,18 @@ class Component(Element): return Component.deserialize(file, implementation) else: raise Exception('Given file is not a name of a json-file nor a json-like dictionary.') + + def load_semantics(self, folderpath: str) -> None: + super().load_semantics(folderpath) + + for child in self.children: + child.load_semantics(folderpath) + + def serialize_semantics(self, kind: str) -> rdflib.Graph: + if kind == 'shape': + result = self._semantic_definition + else: + assert kind == 'data' + # TODO implement + result = None + return result diff --git a/src/soil/element.py b/src/soil/element.py index 7f0b5b274a5bf9a30035984acb4a9a8facc86b26..b7772729283bf3ff51e6a0f501da68bb6ee70d4d 100644 --- a/src/soil/element.py +++ b/src/soil/element.py @@ -1,10 +1,12 @@ -from abc import abstractmethod, ABC - +import os import re +from abc import abstractmethod, ABC from typing import Any, Dict, List -from ..utils import root_logger +import rdflib + from ..utils.constants import BASE_UUID_PATTERN, HTTP_GET +from ..utils.error import SerialisationException class Element(ABC): @@ -15,13 +17,15 @@ class Element(ABC): """ UUID_PATTERN = re.compile(BASE_UUID_PATTERN) - def __init__(self, uuid: str, name: str, description: str, ontology: str = None): + def __init__(self, uuid: str, name: str, description: str, ontology: str = None, shape: str = None): if not isinstance(name, str) or name == '': raise Exception('{}: Name is no string or the empty string!'.format(uuid)) if not isinstance(description, str) or description == '': raise Exception('{}: Description is no string or the empty string!'.format(uuid)) if ontology is not None and not isinstance(ontology, str): raise Exception('{}: Onthology is no string!'.format(uuid)) + if shape is not None and not isinstance(shape, str): + raise Exception('{}: Shape is no string!'.format(uuid)) if not isinstance(uuid, str) or not Element.UUID_PATTERN.match(uuid): raise Exception('Cannot use uuid {}. Wrong format!'.format(uuid)) else: @@ -29,6 +33,8 @@ class Element(ABC): self._name = name self._description = description self._ontology = ontology + self._shape = shape + self._semantic_definition = None @property def uuid(self): @@ -43,6 +49,8 @@ class Element(ABC): return self._description if item == "ontology": return self._ontology + if item == "shape": + return self._shape raise KeyError("{}: Key error. No attribute is named '{}'".format(self.uuid, item)) def __setitem__(self, key: str, value: Any): @@ -58,6 +66,10 @@ class Element(ABC): if value is not None and not isinstance(value, str): raise Exception('{}: Ontology is no string!'.format(self.uuid)) self._ontology = value + elif key == "shape": + if value is not None and not isinstance(value, str): + raise Exception('{}: Shape is no string!'.format(self.uuid)) + self._shape = value else: raise KeyError( "{}: Key error. No attribute is named '{}' or it should not be changed".format(self.uuid, key)) @@ -70,9 +82,23 @@ class Element(ABC): res['name'] = self._name res['description'] = self._description res['ontology'] = self._ontology + res['shape'] = self._shape return res @staticmethod @abstractmethod def deserialize(dictionary: Dict): ... + + def load_semantics(self, folderpath: str) -> None: + if self._shape is None: + raise SerialisationException("Can not load semantic definition, shape attribute is not defined!") + + shape_file = os.path.join(folderpath, f"{self._shape}.shacl.ttl") + + self._semantic_definition = rdflib.Graph() + self._semantic_definition.parse(shape_file) + + @abstractmethod + def serialize_semantics(self, kind: str) -> rdflib.Graph: + ... diff --git a/src/soil/figure.py b/src/soil/figure.py index 1bcd16100aaaa33b54c39625ea42aa7996de9b9e..4fcdda44ca53d0b24ada4fd0614c4584351b983a 100644 --- a/src/soil/figure.py +++ b/src/soil/figure.py @@ -38,10 +38,9 @@ def serialize_time(time): class Figure(Element, ABC): - def __init__(self, uuid: str, name: str, description: str, datatype: Datatype, dimension: List, range: List, - value: Any, getter: Callable, - ontology: str = None): - Element.__init__(self, uuid, name, description, ontology) + def __init__(self, uuid: str, name: str, description: str, datatype: Datatype, dimension: List[int], range: List, + value: Any, getter: Callable, ontology: str = None, shape: str = None): + Element.__init__(self, uuid, name, description, ontology, shape) # if type(datatype) is not str: # raise Exception('{}: Datatype must be passed as string.'.format(uuid)) Figure.check_all(datatype, dimension, range, value) diff --git a/src/soil/function.py b/src/soil/function.py index 3236f16588a511808c18e029fce0c3addea992aa..59c60bf93f8c69511b11a8382ef571bfc9c94543 100644 --- a/src/soil/function.py +++ b/src/soil/function.py @@ -2,6 +2,8 @@ import functools import inspect from typing import Any, Dict, List, Union, Callable +import rdflib + from .element import Element from .error import InvokationException, NotImplementedException from ..utils import root_logger @@ -16,8 +18,8 @@ logger = root_logger.get(__name__) class Function(Element): def __init__(self, uuid: str, name: str, description: str, arguments: List[Figure], returns: List[Figure], - implementation: Callable, ontology: str = None): - Element.__init__(self, uuid, name, description, ontology) + implementation: Callable, ontology: str = None, shape: str = None): + Element.__init__(self, uuid, name, description, ontology, shape) if uuid[:3] != 'FUN': raise Exception('{}: The UUID must start with FUN!'.format(uuid)) if not isinstance(arguments, list): @@ -171,6 +173,15 @@ class Function(Element): raise SerialisationException('{}: A return of the function can not be deserialized. {}'.format(uuid, e)) try: ontology = dictionary['ontology'] if 'ontology' in dictionary else None - return Function(dictionary['uuid'], dictionary['name'], dictionary['description'], arguments, returns, implementation, ontology) + shape = dictionary['shape'] if 'shape' in dictionary else None + return Function(dictionary['uuid'], dictionary['name'], dictionary['description'], arguments, returns, implementation, ontology, shape) except Exception as e: raise SerialisationException('{}: The function can not be deserialized. {}'.format(uuid, e)) + + def load_semantics(self, folderpath: str) -> None: + # This method does nothing intentionally, as we do not have any semantic definition for function + pass + + def serialize_semantics(self, kind: str) -> rdflib.Graph: + # This method does nothing intentionally, as we do not have any semantic definition for function + return None diff --git a/src/soil/measurement.py b/src/soil/measurement.py index bb14c2d4fbaef893f2986c4e9850ee4f317538dc..c096c51f0a51c6e17cd59c95a6d0ec0043c44d12 100644 --- a/src/soil/measurement.py +++ b/src/soil/measurement.py @@ -1,6 +1,7 @@ import warnings -from typing import Dict +from typing import Dict, Callable, List +import rdflib from deprecated import deprecated from .datatype import Datatype @@ -14,9 +15,9 @@ logger = root_logger.get(__name__) class Measurement(Figure): - def __init__(self, uuid, name, description, datatype, dimension, range, getter, unit, label=None, - ontology: str = None): - Figure.__init__(self, uuid, name, description, datatype, dimension, range, None, getter, ontology) + def __init__(self, uuid: str, name: str, description: str, datatype: Datatype, dimension: List[int], range: List, + getter: Callable, unit: str, label=None, ontology: str = None, shape: str = None): + Figure.__init__(self, uuid, name, description, datatype, dimension, range, None, getter, ontology, shape) if uuid[:3] != 'MEA': raise Exception('{}: The UUID must start with MEA!'.format(uuid)) self._unit = unit @@ -162,8 +163,12 @@ class Measurement(Figure): raise SerialisationException('{}: The measurement can not be deserialized. Unit is missing!'.format(uuid)) try: ontology = dictionary['ontology'] if 'ontology' in dictionary else None + shape = dictionary['shape'] if 'shape' in dictionary else None return Measurement(dictionary['uuid'], dictionary['name'], dictionary['description'], Datatype.from_string(dictionary['datatype']), dictionary['dimension'], - dictionary['range'], implementation, dictionary['unit'], ontology) + dictionary['range'], implementation, dictionary['unit'], None, ontology, shape) except Exception as e: raise SerialisationException('{}: The measurement can not be deserialized. {}'.format(uuid, e)) + + def serialize_semantics(self, kind: str) -> rdflib.Graph: + return self._semantic_definition \ No newline at end of file diff --git a/src/soil/parameter.py b/src/soil/parameter.py index 45c9834b64707432da8b448019293f94d5b6747a..b9e1579af24ab2d472d8c71bd2ebe93b29620a1e 100644 --- a/src/soil/parameter.py +++ b/src/soil/parameter.py @@ -1,6 +1,8 @@ import asyncio import inspect -from typing import Dict +from typing import Dict, Callable, Any, List + +import rdflib from .datatype import Datatype from .error import ReadOnlyException @@ -14,9 +16,9 @@ logger = root_logger.get(__name__) class Parameter(Figure): - def __init__(self, uuid, name, description, datatype, dimension, range, value, getter=None, setter=None, - ontology: str = None): - Figure.__init__(self, uuid, name, description, datatype, dimension, range, value, getter, ontology) + def __init__(self, uuid: str, name: str, description: str, datatype: Datatype, dimension: List[int], range: List, + value: Any, getter: Callable = None, setter: Callable = None, ontology: str = None, shape: str = None): + Figure.__init__(self, uuid, name, description, datatype, dimension, range, value, getter, ontology, shape) if uuid[:3] not in ['PAR', 'ARG', 'RET']: raise Exception('{}: The UUID must start with PAR, ARG or RET!'.format(uuid)) if setter is not None and not callable(setter): @@ -117,9 +119,10 @@ class Parameter(Figure): getter = implementation['getter'] if implementation is not None else None setter = implementation['setter'] if implementation is not None else None ontology = dictionary['ontology'] if 'ontology' in dictionary else None + shape = dictionary['shape'] if 'shape' in dictionary else None return Parameter(dictionary['uuid'], dictionary['name'], dictionary['description'], Datatype.from_string(dictionary['datatype']), dictionary['dimension'], - dictionary['range'], dictionary['value'], getter, setter, ontology) + dictionary['range'], dictionary['value'], getter, setter, ontology, shape) except Exception as e: raise SerialisationException('{}: The variable can not be deserialized. {}'.format(uuid, e)) @@ -129,3 +132,6 @@ class Parameter(Figure): return self._setter else: raise ReadOnlyException(self._uuid, self._name) + + def serialize_semantics(self, kind: str) -> rdflib.Graph: + return self._semantic_definition diff --git a/test/data/Sensor.json b/test/data/Sensor.json index 8b20ff9c670f3b9f6f55cb95507a3c4d62432241..bea9071e5c3588a591125a1437b56f6781c24501 100644 --- a/test/data/Sensor.json +++ b/test/data/Sensor.json @@ -1 +1,231 @@ -{"objects": [], "functions": [{"arguments": [], "returns": [], "uuid": "FUN-Refresh", "name": "Trigger Refresh", "description": "Manually triggers refresh of the sensors"}, {"arguments": [{"constant": false, "range": [1, null], "datatype": "int", "dimension": [], "value": 1, "uuid": "PAR-Interval", "name": "Interval (s)", "description": "Interval in seconds over which to take RMS."}], "returns": [{"constant": false, "range": [null, null], "datatype": "int", "dimension": [], "value": 0, "uuid": "PAR-Count", "name": "Count", "description": "Number of values used for RMS estimation"}, {"constant": false, "range": [null, null], "datatype": "double", "dimension": [], "value": 0, "uuid": "PAR-RMS", "name": "RMS (*C)", "description": "Estimated RMS form measurement sequence"}, {"constant": false, "range": [null, null], "datatype": "double", "dimension": [], "value": 0, "uuid": "PAR-Mean", "name": "Mean (*C)", "description": "Mean of RMS estimation sequence (*C)"}], "uuid": "FUN-MeasureRMS", "name": "Measure RMS", "description": "Measures the RMS over a given period of time for this sensor."}, {"arguments": [{"constant": false, "range": [0, 100], "datatype": "int", "dimension": [], "value": 1, "uuid": "PAR-Interval", "name": "Refresh Interval (s)", "description": "Current refresh interval in seconds."}, {"constant": false, "range": [null, 20], "datatype": "string", "dimension": [], "value": "outside", "uuid": "PAR-Location", "name": "Location", "description": "Description of the location of the sensor."}], "returns": [], "uuid": "FUN-Reset", "name": "Reset", "description": "Resets various values of the sensor."}], "variables": [{"unit": "CEL", "range": [null, null], "datatype": "double", "dimension": [], "value": 0, "uuid": "VAR-Temperature", "name": "Temperature (*C)", "description": "Most recently measured temperature in degree celsius"}, {"unit": "A97", "range": [null, null], "datatype": "double", "dimension": [], "value": 0, "uuid": "VAR-Pressure", "name": "Pressure (hPa)", "description": "Most recently measured pressure in hPa"}, {"unit": "", "range": [0, 100], "datatype": "int", "dimension": [], "value": 0, "uuid": "VAR-Battery", "name": "Batterylevel (Percent)", "description": "Estimated battery level in percent"}, {"unit": "", "range": [0, 100], "datatype": "int", "dimension": [], "value": 0, "uuid": "VAR-Signal", "name": "Signal strength", "description": "Signal strength in percent"}, {"unit": "", "range": [0, 100], "datatype": "int", "dimension": [], "value": 50, "uuid": "VAR-Humidity", "name": "Humidity (Percent)", "description": "Gives the current humidity in percent."}], "parameters": [{"constant": true, "range": [null, null], "datatype": "string", "dimension": [], "value": "AC-DE-48-00-00-80", "uuid": "PAR-Mac", "name": "MAC-Adress", "description": "Bluetooth MAC-Address of the sensor "}, {"constant": false, "range": [0, 100], "datatype": "int", "dimension": [], "value": 0, "uuid": "PAR-Interval", "name": "Refresh Interval (s)", "description": "Current refresh interval in seconds"}, {"constant": false, "range": [null, 20], "datatype": "string", "dimension": [], "value": "undefined", "uuid": "PAR-Location", "name": "Location", "description": "Description of the location of the sensor"}, {"constant": true, "range": ["False", "True"], "datatype": "bool", "dimension": [], "value": true, "uuid": "PAR-Contacted", "name": "Contacted", "description": "Flag whether the temperature is measured contacted (true) or ambient (false)"}], "uuid": "OBJ-Sensor", "name": "Sensor", "description": "An individual Sensor of the distributed system."} \ No newline at end of file +{ + "objects": [], + "functions": [ + { + "arguments": [], + "returns": [], + "uuid": "FUN-Refresh", + "name": "Trigger Refresh", + "description": "Manually triggers refresh of the sensors" + }, + { + "arguments": [ + { + "constant": false, + "range": [ + 1, + null + ], + "datatype": "int", + "dimension": [], + "value": 1, + "uuid": "PAR-Interval", + "name": "Interval (s)", + "description": "Interval in seconds over which to take RMS." + } + ], + "returns": [ + { + "constant": false, + "range": [ + null, + null + ], + "datatype": "int", + "dimension": [], + "value": 0, + "uuid": "PAR-Count", + "name": "Count", + "description": "Number of values used for RMS estimation" + }, + { + "constant": false, + "range": [ + null, + null + ], + "datatype": "double", + "dimension": [], + "value": 0, + "uuid": "PAR-RMS", + "name": "RMS (*C)", + "description": "Estimated RMS form measurement sequence" + }, + { + "constant": false, + "range": [ + null, + null + ], + "datatype": "double", + "dimension": [], + "value": 0, + "uuid": "PAR-Mean", + "name": "Mean (*C)", + "description": "Mean of RMS estimation sequence (*C)" + } + ], + "uuid": "FUN-MeasureRMS", + "name": "Measure RMS", + "description": "Measures the RMS over a given period of time for this sensor." + }, + { + "arguments": [ + { + "constant": false, + "range": [ + 0, + 100 + ], + "datatype": "int", + "dimension": [], + "value": 1, + "uuid": "PAR-Interval", + "name": "Refresh Interval (s)", + "description": "Current refresh interval in seconds." + }, + { + "constant": false, + "range": [ + null, + 20 + ], + "datatype": "string", + "dimension": [], + "value": "outside", + "uuid": "PAR-Location", + "name": "Location", + "description": "Description of the location of the sensor." + } + ], + "returns": [], + "uuid": "FUN-Reset", + "name": "Reset", + "description": "Resets various values of the sensor." + } + ], + "variables": [ + { + "unit": "CEL", + "range": [ + null, + null + ], + "datatype": "double", + "dimension": [], + "value": 0, + "uuid": "VAR-Temperature", + "name": "Temperature (*C)", + "description": "Most recently measured temperature in degree celsius" + }, + { + "unit": "A97", + "range": [ + null, + null + ], + "datatype": "double", + "dimension": [], + "value": 0, + "uuid": "VAR-Pressure", + "name": "Pressure (hPa)", + "description": "Most recently measured pressure in hPa" + }, + { + "unit": "", + "range": [ + 0, + 100 + ], + "datatype": "int", + "dimension": [], + "value": 0, + "uuid": "VAR-Battery", + "name": "Batterylevel (Percent)", + "description": "Estimated battery level in percent" + }, + { + "unit": "", + "range": [ + 0, + 100 + ], + "datatype": "int", + "dimension": [], + "value": 0, + "uuid": "VAR-Signal", + "name": "Signal strength", + "description": "Signal strength in percent" + }, + { + "unit": "", + "range": [ + 0, + 100 + ], + "datatype": "int", + "dimension": [], + "value": 50, + "uuid": "VAR-Humidity", + "name": "Humidity (Percent)", + "description": "Gives the current humidity in percent." + } + ], + "parameters": [ + { + "constant": true, + "range": [ + null, + null + ], + "datatype": "string", + "dimension": [], + "value": "AC-DE-48-00-00-80", + "uuid": "PAR-Mac", + "name": "MAC-Adress", + "description": "Bluetooth MAC-Address of the sensor " + }, + { + "constant": false, + "range": [ + 0, + 100 + ], + "datatype": "int", + "dimension": [], + "value": 0, + "uuid": "PAR-Interval", + "name": "Refresh Interval (s)", + "description": "Current refresh interval in seconds" + }, + { + "constant": false, + "range": [ + null, + 20 + ], + "datatype": "string", + "dimension": [], + "value": "undefined", + "uuid": "PAR-Location", + "name": "Location", + "description": "Description of the location of the sensor" + }, + { + "constant": true, + "range": [ + "False", + "True" + ], + "datatype": "bool", + "dimension": [], + "value": true, + "uuid": "PAR-Contacted", + "name": "Contacted", + "description": "Flag whether the temperature is measured contacted (true) or ambient (false)" + } + ], + "uuid": "OBJ-Sensor", + "name": "Sensor", + "description": "An individual Sensor of the distributed system." +} \ No newline at end of file