def topic_to_topology(self, topic): """ Decode MQTT topic segments implementing the »basic strategy«. The topology hierarchy is directly specified by the MQTT topic and is made up of a two path segments:: realm / node The topology identifiers are specified as: - "realm" is the designated root realm. You should prefix the topic name with this label when opting in for all features of the telemetry platform. For other purposes, feel free to publish to any MQTT topic you like. - "node" is the node identifier. Choose anything you like. This usually gets transmitted from an embedded device node. """ # decode the topic m = self.matcher.match(topic) if m: address = SmartBunch(m.groupdict()) else: address = {} return address
def topology_to_storage(self, topology, message_type=None): """ Encode topology segment identifiers to database address. A database server usually has the concept of multiple databases, each with multiple tables. With other databases than RDBMS, they might be named differently, but the concept in general is the same. The topology information (realm, node) will get mapped to the database name and measurement name to compute the storage location for measurements. - realm + node = database name - sensors | events = table name """ # Derive database table suffix from message type. table_suffix = self.get_table_suffix(topology, message_type) # Use topology information as blueprint for storage address. storage = SmartBunch(topology) # Format and sanitize all input parameters used for database addressing. # Todo: Investigate using tags additionally to / instead of only "storage.measurement". sanitize = self.sanitize_db_identifier storage.label = sanitize('{}'.format(storage.node)) storage.database = sanitize('{}_{}'.format(storage.realm, storage.node)) storage.measurement = sanitize('{}'.format(table_suffix)) storage.measurement_events = sanitize('{}'.format('events')) return storage
def classify(self): log.debug(u'Classifying beekeeper fields {fieldnames}', fieldnames=self.fieldnames) # TODO: Can we account for multiple occurrences of "weightX" fields for mapping more than one scale? weight_synonyms = u'(weight|wght|gewicht)' temperature_synonyms = u'(temperature|temp|temperatur)' outside_synonyms = u'(outside|out|air|außen|aussen)' candidates = { 'weight_total': [ self.from_words(weight_synonyms, 'total', exclude=['stddev']), self.from_words(weight_synonyms, exclude=['stddev']), ], 'temperature_outside': [ self.from_words(temperature_synonyms, outside_synonyms), self.from_words(temperature_synonyms), ], 'temperature_inside': [ self.from_words(temperature_synonyms, 'inside'), self.from_words(temperature_synonyms), ], } #pprint(candidates) results = SmartBunch() for name, patterns in candidates.items(): fieldname = self.find_match(patterns) if fieldname is not None: results[name] = fieldname log.info(u'Classified beekeeper fields "{fields}" from "{fieldnames}"', fields=results.dump(), fieldnames=self.fieldnames) return results
def topology_to_storage(self, topology): """ Encode topology segment identifiers to database address. A database server usually has the concept of multiple databases, each with multiple tables. With other databases than RDBMS, they might be named differently, but the concept in general is the same. When mapping the topology quadruple (realm, network, gateway, node) in the form of: - realm + network = database name - gateway + node = table name We have a perfect fit for computing the slot where to store the measurements. """ # Todo: Investigate using tags additionally to / instead of database.measurement # Todo: Move specific stuff about WeeWX or Tasmota to some device-specific knowledgebase. # data: Regular endpoint # loop: WeeWX # SENSOR: Sonoff-Tasmota if topology.slot.startswith('data') or topology.slot.startswith('loop') \ or topology.slot.endswith('SENSOR') or topology.slot.endswith('STATE'): suffix = 'sensors' elif topology.slot.startswith('event'): suffix = 'events' else: suffix = 'unknown' # Use topology information as blueprint for storage address storage = SmartBunch(topology) # Format and sanitize all input parameters used for database addressing sanitize = self.sanitize_db_identifier storage.label = sanitize('{}-{}'.format(storage.gateway, storage.node)) storage.database = sanitize('{}_{}'.format(storage.realm, storage.network)) storage.measurement = sanitize('{}_{}_{}'.format( storage.gateway, storage.node, suffix)) storage.measurement_events = sanitize('{}_{}_{}'.format( storage.gateway, storage.node, 'events')) return storage
def topic_to_topology(self, topic): """ Decode MQTT topic segments implementing the »quadruple hierarchy strategy«. The topology hierarchy is directly specified by the MQTT topic and is made up of a minimum of four identifiers describing the core structure:: realm / network / gateway / node The topology identifiers are specified as: - "realm" is the designated root realm. You should prefix the topic name with this label when opting in for all features of the telemetry platform. For other purposes, feel free to publish to any MQTT topic you like. - "network" is your personal realm. Choose anything you like or use an `Online GUID Generator <https://www.guidgenerator.com/>`_ to gain maximum uniqueness. - "gateway" is your gateway identifier. Choose anything you like. This does not have to be very unique, so you might use labels having the names of sites. While you are the owner of this namespace hierarchy, remember these labels might be visible on the collaborative ether, though. So the best thing would be to give kind of nicknames to your sites which don't identify their location. - "1" is your node identifier. Choose anything you like. This usually gets transmitted from an embedded device node. Remember one device node might have multiple sensors attached, which is beyond the scope of the collection platform: We just accept bunches of named measurement values, no matter which sensors they might originate from. In other words: We don't need nor favor numeric sensor identifiers, let's give them names! """ # regular expression pattern for decoding MQTT topic address segments pattern = r'^(?P<realm>.+?)/(?P<network>.+?)/(?P<gateway>.+?)/(?P<node>.+?)(?:/(?P<slot>.+?))?$' # decode the topic p = re.compile(pattern) m = p.match(topic) if m: address = SmartBunch(m.groupdict()) else: address = {} return address
def get_dashboard_identity(self, storage_location, topology=None): # Compute effective topology information topology = topology or {} realm = topology.get('realm', 'default') network = topology.get('network', storage_location.database) # Derive dashboard uid and name from topology information identity = SmartBunch( uid=u'{realm}-{network}-instant'.format(realm=realm, network=network), name=u'{realm}-{network}'.format(realm=realm, network=network), title=u'{realm}-{network}'.format(realm=realm, network=network), # TODO: Use real title after fully upgrading to new Grafana API (i.e. don't use get-by-slug anymore!) #title=u'Hiveeyes Rohdaten im Netzwerk ' + network, ) #print identity.prettify() return identity
def get_dashboard_identity(self, storage_location, topology=None): # Compute effective topology information topology = topology or {} realm = topology.get('realm', 'default') network = topology.get('network', storage_location.database) # Derive dashboard uid and name from topology information nodename = u'{gateway} / {node}'.format(gateway=topology.gateway, node=topology.node) identity = SmartBunch( #uid=u'{realm}-{network}-instant'.format(realm=realm, network=network), name=self.strategy.topology_to_label(topology), title=self.strategy.topology_to_label(topology), # TODO: Use real title after fully upgrading to new Grafana API (i.e. don't use get-by-slug anymore!) # title=u'Hiveeyes Umweltcockpit für Meßknoten {nodename} im Netzwerk {network}'.format(nodename=nodename, network=network), # title=u'Hiveeyes Ertragscockpit für Meßknoten {nodename} im Netzwerk {network}'.format(nodename=nodename, network=network), ) #print identity.prettify() return identity
def get_dashboard_identity(self, storage_location, topology=None): # Compute effective topology information topology = topology or {} realm = topology.get('realm', 'default') if 'network' in topology: name = topology.network else: name = topology.node # Derive dashboard uid and name from topology information identity = SmartBunch( #uid=u'{realm}-{name}-instant'.format(realm=realm, name=name), name=u'{realm}-{name}'.format(realm=realm, name=name), title=u'{realm}-{name}'.format(realm=realm, name=name), # TODO: Use real title after fully upgrading to new Grafana API (i.e. don't use get-by-slug anymore!) #title=u'Raw data for realm={realm} network={network}'.format(realm=realm, network=network), ) #print identity.prettify() return identity
def topic_to_topology(self, topic): """ Decode MQTT topic segments implementing the »quadruple hierarchy strategy«. The topology hierarchy is directly specified by the MQTT topic and is made up of a minimum of four identifiers describing the core structure:: realm / network / gateway / node The topology identifiers are specified as: - "realm" is the designated root realm. You should prefix the topic name with this label when opting in for all features of the telemetry platform. For other purposes, feel free to publish to any MQTT topic you like. - "network" is your personal realm. Choose anything you like or use an `Online GUID Generator <https://www.guidgenerator.com/>`_ to gain maximum uniqueness. - "gateway" is your gateway identifier. Choose anything you like. This does not have to be very unique, so you might use labels having the names of sites. While you are the owner of this namespace hierarchy, remember these labels might be visible on the collaborative ether, though. You might want to assign nicknames to your sites to not identify their location. - "node" is the node identifier. Choose anything you like. This usually gets transmitted from an embedded device node. """ # Decode the topic. m = self.matcher.match(topic) if m: address = SmartBunch(m.groupdict()) else: address = {} return address