diff --git a/src/http/server.py b/src/http/server.py index d0046f92a08629054d499dca7d4403c5ba1104b3..06ae4098e9c100241f9e8d4b4a0339ad2627c49a 100644 --- a/src/http/server.py +++ b/src/http/server.py @@ -130,7 +130,7 @@ class HTTPServer(object): 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']: + if query is not None and 'semantic' in query and query['semantic'] in ['data', 'metadata', 'profile']: semantic = True if dataformat == 'json': @@ -175,7 +175,7 @@ class HTTPServer(object): # 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']: + if request.query is not None and 'semantic' in request.query and request.query['semantic'] in ['data', 'metadata', 'profile']: semantic = request.query['semantic'] # retrieving the element diff --git a/src/soil/component.py b/src/soil/component.py index 4bd9e499c2222f07543248b1c960f1fd2d4db714..77fdcdf08ad3e22d772870b6ce2dcd1253557318 100644 --- a/src/soil/component.py +++ b/src/soil/component.py @@ -9,7 +9,6 @@ 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 @@ -32,7 +31,7 @@ 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, - shape: str = None): + profile: str = None): """ Args: @@ -46,7 +45,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. + profile: 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'. @@ -54,7 +53,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, shape) + Element.__init__(self, uuid, name, description, ontology, profile) if uuid[:3] != 'COM': raise Exception('{}: The UUID must start with COM!'.format(uuid)) if not isinstance(functions, list): @@ -189,7 +188,7 @@ class Component(Element): keys = [] if keys is None else keys if not keys: # list is empty - keys = ['uuid', 'name', 'description', 'children', 'ontology', 'shape'] + keys = ['uuid', 'name', 'description', 'children', 'ontology', 'profile'] if 'all' in keys: # serialize complete tree recursively (overrides all other keys) dictionary = super().serialize([], legacy_mode) @@ -287,10 +286,9 @@ 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 + profile = dictionary['profile'] if 'profile' in dictionary else None return Component(dictionary['uuid'], dictionary['name'], dictionary['description'], functions, measurements, - parameters, components, implementation, - ontology, shape) + parameters, components, implementation, ontology, profile) except Exception as e: raise SerialisationException('{}: The component can not be deserialized. {}'.format(uuid, e)) @@ -378,17 +376,20 @@ class Component(Element): 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) + def load_semantics(self, profiles_path: str, metadata_path: str, parent_name: str) -> None: + super().load_semantics(profiles_path, metadata_path, parent_name) for child in self.children: - child.load_semantics(folderpath) + child.load_semantics(profiles_path, metadata_path, f"{parent_name}{self.uuid[4:].capitalize()}") def serialize_semantics(self, kind: str) -> rdflib.Graph: - if kind == 'shape': - result = self._semantic_definition + if self._metadata_profile is None or self._metadata is None: + raise SerialisationException('No semantic information have been provided during initialization.') + + if kind == 'profile': + result = self._metadata_profile + elif kind == 'metadata': + result = self._metadata else: - assert kind == 'data' - # TODO implement - result = None + raise DeviceException('The provided kind of semantic information cannot be returned.') return result diff --git a/src/soil/element.py b/src/soil/element.py index b7772729283bf3ff51e6a0f501da68bb6ee70d4d..9d2d646e0166f8c3ed5986d19764d37451f9abb3 100644 --- a/src/soil/element.py +++ b/src/soil/element.py @@ -17,24 +17,26 @@ class Element(ABC): """ UUID_PATTERN = re.compile(BASE_UUID_PATTERN) - def __init__(self, uuid: str, name: str, description: str, ontology: str = None, shape: str = None): + def __init__(self, uuid: str, name: str, description: str, ontology: str = None, profile: 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): + if profile is not None and not isinstance(profile, 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: - self._uuid = uuid - self._name = name - self._description = description - self._ontology = ontology - self._shape = shape - self._semantic_definition = None + self._uuid: str = uuid + self._name: str = name + self._description: str = description + self._ontology: str = ontology + self._profilename: str = profile + self._metadata_profile: rdflib.Graph = None + self._metadata: rdflib.Graph = None + self._semantic_name: str = None @property def uuid(self): @@ -49,8 +51,8 @@ class Element(ABC): return self._description if item == "ontology": return self._ontology - if item == "shape": - return self._shape + if item == "profile": + return self._profilename raise KeyError("{}: Key error. No attribute is named '{}'".format(self.uuid, item)) def __setitem__(self, key: str, value: Any): @@ -66,10 +68,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": + elif key == "profile": if value is not None and not isinstance(value, str): - raise Exception('{}: Shape is no string!'.format(self.uuid)) - self._shape = value + raise Exception('{}: Profile is no string!'.format(self.uuid)) + self._profilename = value else: raise KeyError( "{}: Key error. No attribute is named '{}' or it should not be changed".format(self.uuid, key)) @@ -82,7 +84,7 @@ class Element(ABC): res['name'] = self._name res['description'] = self._description res['ontology'] = self._ontology - res['shape'] = self._shape + res['profile'] = self._profilename return res @staticmethod @@ -90,14 +92,20 @@ class Element(ABC): def deserialize(dictionary: Dict): ... - def load_semantics(self, folderpath: str) -> None: - if self._shape is None: + def load_semantics(self, profiles_path: str, metadata_path: str, parent_name: str) -> None: + if self._profilename 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") + # load shapes + shape_filename = os.path.join(profiles_path, f"{self._profilename}.shacl.ttl") + self._metadata_profile = rdflib.Graph() + self._metadata_profile.parse(shape_filename) - self._semantic_definition = rdflib.Graph() - self._semantic_definition.parse(shape_file) + # load metadata + self._semantic_name = f'{parent_name}{self.uuid[4:].capitalize()}' + metadata_filename = os.path.join(metadata_path, f"{self._semantic_name}.ttl") + self._metadata = rdflib.Graph() + self._metadata.parse(metadata_filename) @abstractmethod def serialize_semantics(self, kind: str) -> rdflib.Graph: diff --git a/src/soil/figure.py b/src/soil/figure.py index 4fcdda44ca53d0b24ada4fd0614c4584351b983a..d94c2b3c89ebc718df5625a5f05c27af2a4b94d8 100644 --- a/src/soil/figure.py +++ b/src/soil/figure.py @@ -1,4 +1,5 @@ import asyncio +import copy import datetime import inspect import time @@ -6,9 +7,11 @@ from abc import ABC from typing import Any, List, Callable, Union import nest_asyncio +import rdflib import strict_rfc3339 as rfc3339 from .datatype import Datatype +from .semantics import Namespaces nest_asyncio.apply() @@ -39,8 +42,8 @@ def serialize_time(time): class Figure(Element, ABC): 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) + value: Any, getter: Callable, ontology: str = None, profile: str = None): + Element.__init__(self, uuid, name, description, ontology, profile) # if type(datatype) is not str: # raise Exception('{}: Datatype must be passed as string.'.format(uuid)) Figure.check_all(datatype, dimension, range, value) @@ -285,6 +288,21 @@ class Figure(Element, ABC): except RangeException as e: raise e + @staticmethod + def serialize_value(data_graph: rdflib.Graph, value: Any) -> rdflib.term.Identifier: + if isinstance(value, list): + blank_node = rdflib.BNode() + data_graph.add((blank_node, Namespaces.rdf.rest, Namespaces.rdf.nil)) + data_graph.add((blank_node, Namespaces.rdf.first, Figure.serialize_value(data_graph, value[len(value) - 1]))) + for entry in reversed(value[:-1]): + new_blank_node = rdflib.BNode() + data_graph.add((new_blank_node, Namespaces.rdf.rest, blank_node)) + data_graph.add((new_blank_node, Namespaces.rdf.first, Figure.serialize_value(data_graph, entry))) + blank_node = new_blank_node + return blank_node + else: + return rdflib.Literal(value) + @staticmethod def is_scalar(value): return not isinstance(value, list) diff --git a/src/soil/function.py b/src/soil/function.py index 59c60bf93f8c69511b11a8382ef571bfc9c94543..04131ff79633093e1d05d7f1e177e0f1b610e581 100644 --- a/src/soil/function.py +++ b/src/soil/function.py @@ -18,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, shape: str = None): - Element.__init__(self, uuid, name, description, ontology, shape) + implementation: Callable, ontology: str = None, profile: str = None): + Element.__init__(self, uuid, name, description, ontology, profile) if uuid[:3] != 'FUN': raise Exception('{}: The UUID must start with FUN!'.format(uuid)) if not isinstance(arguments, list): @@ -173,12 +173,12 @@ 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 - shape = dictionary['shape'] if 'shape' in dictionary else None - return Function(dictionary['uuid'], dictionary['name'], dictionary['description'], arguments, returns, implementation, ontology, shape) + profile = dictionary['profile'] if 'profile' in dictionary else None + return Function(dictionary['uuid'], dictionary['name'], dictionary['description'], arguments, returns, implementation, ontology, profile) except Exception as e: raise SerialisationException('{}: The function can not be deserialized. {}'.format(uuid, e)) - def load_semantics(self, folderpath: str) -> None: + def load_semantics(self, profiles_path: str, metadata_path: str, parent_name: str) -> None: # This method does nothing intentionally, as we do not have any semantic definition for function pass diff --git a/src/soil/measurement.py b/src/soil/measurement.py index c096c51f0a51c6e17cd59c95a6d0ec0043c44d12..3dee1c151c1e805d002eb57366e8dd96037e1372 100644 --- a/src/soil/measurement.py +++ b/src/soil/measurement.py @@ -1,23 +1,26 @@ +import datetime import warnings -from typing import Dict, Callable, List +from typing import Dict, Callable, List, Any import rdflib from deprecated import deprecated +from rdflib import BNode from .datatype import Datatype from .figure import Figure from ..utils import root_logger from ..utils.constants import HTTP_GET -from ..utils.error import SerialisationException +from ..utils.error import SerialisationException, DeviceException +from .semantics import Semantics, Namespaces logger = root_logger.get(__name__) class Measurement(Figure): - 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) + 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, profile: str = None): + Figure.__init__(self, uuid, name, description, datatype, dimension, range, None, getter, ontology, profile) if uuid[:3] != 'MEA': raise Exception('{}: The UUID must start with MEA!'.format(uuid)) self._unit = unit @@ -110,7 +113,7 @@ class Measurement(Figure): # list is empty provide all attributes of the default-serialization if not keys: keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range', 'timestamp', 'label', - 'covariance', 'unit'] #, 'ontology'] + 'covariance', 'unit'] # , 'ontology'] if 'value' in keys and 'timestamp' not in keys: keys += ['timestamp'] dictionary = {} @@ -163,12 +166,55 @@ 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 + profile = dictionary['profile'] if 'profile' in dictionary else None return Measurement(dictionary['uuid'], dictionary['name'], dictionary['description'], Datatype.from_string(dictionary['datatype']), dictionary['dimension'], - dictionary['range'], implementation, dictionary['unit'], None, ontology, shape) + dictionary['range'], implementation, dictionary['unit'], None, ontology, profile) 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 + if kind == 'profile': + result = self._metadata_profile + elif kind == 'metadata': + result = self._metadata + elif kind == 'data': + data_graph = rdflib.Graph() + data_graph.bind('sosa', Namespaces.sosa) + data_graph.bind(Semantics.prefix, Semantics.namespace) + data_graph.bind('soil', Namespaces.soil) + data_graph.bind('qudt', Namespaces.qudt) + data_graph.bind('unit', Namespaces.unit) + observation_subject = Semantics.namespace[f'{self._semantic_name}Observation'] + + sensor_triples = list(self._metadata.triples((None, Namespaces.sosa.isObservedBy, None))) + assert len(sensor_triples) == 1 + + # create observation node + data_graph.add((observation_subject, Namespaces.rdf.type, rdflib.URIRef(Namespaces.sosa.Observation))) + data_graph.add((observation_subject, Namespaces.schema.name, rdflib.Literal(f'{self._name} Observation'))) + data_graph.add((observation_subject, Namespaces.sosa.observedProperty, Semantics.namespace[self._semantic_name])) + data_graph.add((observation_subject, Namespaces.sosa.hasResult, Semantics.namespace[f'{self._semantic_name}Measurement'])) + data_graph.add((observation_subject, Namespaces.sosa.madeBySensor, sensor_triples[0][2])) + + # create result node + unit_triples = list(self._metadata.triples((None, Namespaces.qudt.applicableUnit, None))) + assert len(unit_triples) == 1 + + measurement_subject = Semantics.namespace[f'{self._semantic_name}Measurement'] + data_graph.add((measurement_subject, Namespaces.rdf.type, rdflib.URIRef(Namespaces.sosa.Result))) + data_graph.add((measurement_subject, Namespaces.rdf.type, rdflib.URIRef(Namespaces.soil.Measurement))) + data_graph.add((measurement_subject, Namespaces.sosa.isResultOf, observation_subject)) + data_graph.add((measurement_subject, Namespaces.qudt.unit, unit_triples[0][2])) + + rdf_value = Figure.serialize_value(data_graph, self.__getitem__('value', 0)) + + data_graph.add((measurement_subject, Namespaces.qudt.value, rdf_value)) + data_graph.add((measurement_subject, Namespaces.schema.dateCreated, rdflib.Literal(datetime.datetime.now().astimezone()))) + + # TODO add uncertainty + + result = data_graph + else: + raise DeviceException('The provided kind of semantic information cannot be returned.') + return result diff --git a/src/soil/parameter.py b/src/soil/parameter.py index b9e1579af24ab2d472d8c71bd2ebe93b29620a1e..d4f86fef7f9f9659a3e9e75b52c1555a63178d63 100644 --- a/src/soil/parameter.py +++ b/src/soil/parameter.py @@ -1,4 +1,5 @@ import asyncio +import copy import inspect from typing import Dict, Callable, Any, List @@ -7,6 +8,7 @@ import rdflib from .datatype import Datatype from .error import ReadOnlyException from .figure import Figure +from .semantics import Semantics, Namespaces from ..utils import root_logger from ..utils.constants import HTTP_GET from ..utils.error import DeviceException, SerialisationException @@ -17,8 +19,9 @@ logger = root_logger.get(__name__) class Parameter(Figure): 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) + value: Any, getter: Callable = None, setter: Callable = None, ontology: str = None, + profile: str = None): + Figure.__init__(self, uuid, name, description, datatype, dimension, range, value, getter, ontology, profile) 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): @@ -119,10 +122,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 + profile = dictionary['profile'] if 'profile' 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, shape) + dictionary['range'], dictionary['value'], getter, setter, ontology, profile) except Exception as e: raise SerialisationException('{}: The variable can not be deserialized. {}'.format(uuid, e)) @@ -134,4 +137,19 @@ class Parameter(Figure): raise ReadOnlyException(self._uuid, self._name) def serialize_semantics(self, kind: str) -> rdflib.Graph: - return self._semantic_definition + if kind == 'profile': + result = self._metadata_profile + elif kind == 'metadata': + result = copy.deepcopy(self._metadata) + + triples = list(result.triples((None, Namespaces.qudt['value'], None))) + if len(triples) > 0: + assert (len(triples) == 1) + result.remove(triples[0]) + + rdf_value = Figure.serialize_value(result, self.__getitem__('value', 0)) + result.add((Semantics.namespace[self._semantic_name], Namespaces.qudt['value'], rdf_value)) + return result + else: + raise DeviceException('The provided kind of semantic information cannot be returned.') + return result diff --git a/src/soil/semantics.py b/src/soil/semantics.py new file mode 100644 index 0000000000000000000000000000000000000000..1f3baeb74b67e98fb09e97739655e2432447931b --- /dev/null +++ b/src/soil/semantics.py @@ -0,0 +1,28 @@ +import rdflib + + +class Semantics(object): + + prefix: str = None + url: str = None + namespace: rdflib.Namespace = None + + def __init__(self, config: dict[str, str]): + Semantics.prefix = config['prefix'] + Semantics.url = config['url'] + Semantics.namespace = rdflib.Namespace(config['url']) + + +class Namespaces(object): + + m4i = rdflib.Namespace('http://w3id.org/nfdi4ing/metadata4ing#') + quantitykind = rdflib.Namespace('http://qudt.org/vocab/quantitykind/') + qudt = rdflib.Namespace('http://qudt.org/schema/qudt/') + rdf = rdflib.namespace.RDF + schema = rdflib.Namespace('https://schema.org/') + si = rdflib.Namespace('https://ptb.de/si/') + soil = rdflib.Namespace('https://purl.org/fair-sensor-services/soil#') + sosa = rdflib.namespace.SOSA + ssn = rdflib.namespace.SSN + ssn_system = rdflib.Namespace('http://www.w3.org/ns/ssn/systems/') + unit = rdflib.Namespace('http://qudt.org/vocab/unit/') diff --git a/src/soil/stream.py b/src/soil/stream.py index 65ab197e49c6ff28f7a637a5224da0ab4c9ae33f..2cba19d9de418745a2c43136a34ecf09daa610a5 100644 --- a/src/soil/stream.py +++ b/src/soil/stream.py @@ -3,6 +3,7 @@ import json from abc import ABC, abstractmethod from typing import List, Callable, Any, Union, Dict +import rdflib from wzl.mqtt.client import MQTTPublisher from . import figure @@ -96,6 +97,16 @@ class Job(ABC): return {} return metadata + def _retrieve_semantic_metadata(self, model: 'Component' = None): + if model is None: + return rdflib.Graph() + try: + uuids = self.topic.split('/') + metadata = model.__getitem__(uuids).serialize_semantics([], 'data') + except Exception: + return rdflib.Graph() + return metadata + def data(self, model: Dict = None) -> Dict: try: data = self._retrieve_metadata(model) @@ -106,6 +117,28 @@ class Job(ABC): except Exception as e: raise JobError() + def semantic_data(self, model: Dict = None) -> Dict: + try: + data = self._retrieve_semantic_metadata(model) + # TODO set mqtt topic as identifier + + triples = list(self._metadata.triples((None, rdflib.URIRef("http://schema.org/name"), None))) + assert (len(triples) == 1) + triple = triples[0] + + subject = triple[0] + predicate = rdflib.URIRef("http://qudt.org/schema/qudt/value") + object = rdflib.Literal(self.value) + + # TODO creating the Measurement + + # data.add(()) + # data['value'] = self.value + # data['timestamp'] = figure.serialize_time(datetime.datetime.now()) + return data + except Exception as e: + raise JobError() + class FixedJob(Job): diff --git a/test/data/Sensor.json b/test/data/Sensor.json deleted file mode 100644 index bea9071e5c3588a591125a1437b56f6781c24501..0000000000000000000000000000000000000000 --- a/test/data/Sensor.json +++ /dev/null @@ -1,231 +0,0 @@ -{ - "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