#!/usr/bin/env python3 # # Copyright (c) 2020 Fondazione Bruno Kessler # Author(s): Cristina Costa (ccosta@fbk.eu) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """EmPOWER Runtime LoRaWAN Gateway Class.""" import logging from datetime import datetime from enum import Enum, unique from pymodm import MongoModel, fields # from empower_core.eui64 import EUI64 from empower_core.eui64 import EUI64Field from empower.managers.lommmanager.lnsp import DEFAULT_OWNER @unique class LGtwState(Enum): """Enumerate lGTW states.""" # TODO Full test of lGtw State Machine DISCONNECTED = "disconnected" CONNECTED = "connected" ONLINE = "online" RMTSH = "remote shell" # Remote Shell mode on @property def next_valid(self): """Return the next valid state for lgtw. The lgtw state machine is the following: disconnected <-> connected -> online online <-> rmsh online -> disconnected """ if self == LGtwState.DISCONNECTED: return [self, LGtwState.CONNECTED] if self == LGtwState.CONNECTED: return [self, LGtwState.DISCONNECTED, LGtwState.ONLINE] if self == LGtwState.ONLINE: return [self, LGtwState.DISCONNECTED, LGtwState.RMTSH] if self == LGtwState.RMTSH: return [self, LGtwState.DISCONNECTED, LGtwState.ONLINE] return [] @staticmethod def to_list(): """Return a list of possible state values.""" return [i.value for i in LGtwState] class LoRaWANgtw(MongoModel): """LoRaWAN Gateway class. Attributes: name (str): LoRaWAN Gateway name lgtw_euid (EUI64Field): LoRaWAN Gateway EUID owner (EUI64Field): Owner of the lGTW name (CharField): A human-radable name of this LoRaWAN Gateway desc (CharField): A human-radable description of this LoRaWAN Gateway state (LGtwState): This lGTW state log (): Logging facility lgtw_config (DictField): Stores the conf. of the lGTW sent by the LNS last_seen_ts (IntegerField): Timestamp of the last message received last_seen (DateTimeField): Sequence number of the last message received connection (): WS connection rmtsh (bool): Implements interactive remote sheel prod (bool): gps (bool): Implements GPS brand (str): lgtw brand model (str): lgtw model antenna (dict): Antenna params (type, placement, location, alt.) privacy (dict): Privacy settings (for status, location, owner) radio_data (list): Stats collected rx_messages (int): Number of received messages tx_messages (int): Number of transmitted messages Methods: is_connected(self): Return True if lGTW is connected is_rmt_shell(self): Return True if lGTW is in rmtsh state is_online(self): Return True if lGTW is online set_connected(self): Set lGTW to connected state set_disconnected(self): Set lGTW to disconnected state set_online(self): Set lGTW to online state set_rmt_shell(self): Set lGTW to remote shell state set_send_rtt_on(self): Send RTT message ON. set_send_rtt_off(self): Send RTT message OFF is_send_rtt_on(self): Return True if RTT messages are set to on to_dict(self): Return a JSON-serializable dic repr. the lGTW """ lgtw_euid = EUI64Field(primary_key=True) # 64 bit gtw identifier, EUI-64 name = fields.CharField(required=True) desc = fields.CharField(required=False) owner = EUI64Field(required=True, default=DEFAULT_OWNER) lgtw_config = fields.DictField(required=True) # NOTE # lgtw_config data is sent by LNSS in reply to a "version" # message from the Basic Station using a "router_config" message # lgtw_config contains the following data: # "NetID" : [ INT, .. ] // List of Integers # "JoinEui" : [ [INT, INT], .. ] // List of Integer Ranges: # // [beginning, end] inclusive # "region" : STRING // CharField, ref. to # // regional settings Freq. Plan # // e.g. "EU863", "US902", ... # "hwspec" : STRING // CharField # "freq_range" : [ INT, INT ] // Integer Range (list) # // [min, max] in (hz) # "DRs" : [ [INT, INT, INT], .. ] // List of Int Triplets (list) # // [sf, bw, dnonly] # "sx1301_conf": [ SX1301CONF, .. ] // DictField # "nocca" : BOOL // BooleanField # "nodc" : BOOL // BooleanField # "nodwell" : BOOL // BooleanField lgtw_version = fields.DictField(required=False, blank=True) # lgtw version information is sent by the Basic Station at connection time # NOTE maybe there is no need to save it in the database ipaddr = fields.GenericIPAddressField(required=False, blank=True) last_seen = fields.DateTimeField(required=False) last_seen_ts = fields.IntegerField(required=False) # DEFAULTS rmtsh = False prod = False gps = False brand = "IMST" model = "iC880A" antenna = {"type": "xxx", "placement": "indoor", "location": [46, 11, 0]} # location e.g. PointField (long, lat) # (derived from GeoJSONField) connection = None privacy = {"status": "public", "location": "public", "owner": "public"} radio_data = [] # stats collected rx_messages = 0 tx_messages = 0 # Private attributes _state = LGtwState.DISCONNECTED # Protected attributes __send_rtt_on = True __has_pps_signal = False class Meta: """Define custom collection name.""" collection_name = "lomm_lgtw" def __init__(self, **kwargs): """Initialize.""" super().__init__(**kwargs) self.log = logging.getLogger("%s" % self.__class__.__module__) @property def state(self): """Return the state.""" return self._state @state.setter def state(self, new_state): """Set the lgtw state.""" if new_state not in self.state.next_valid: raise IOError("Invalid transistion %s -> %s" % (self._state.value, new_state.value)) if self._state != new_state: self.log.info("New LoRAWAN GTW %s state transition %s->%s", self.lgtw_euid, self.state.value, new_state.value) self._state = new_state self.connection.lgtw_new_state(self._state, new_state) def is_connected(self): """Return if lGTW is connected.""" return self.state == LGtwState.CONNECTED or LGtwState.ONLINE def is_rmt_shell(self): """Return if lGTW is in remote shell state.""" return self.state == LGtwState.RMTSH def is_online(self): """Return if lGTW is online.""" return self.state == LGtwState.ONLINE def set_connected(self): """Move lGTW to connected state.""" self.state = LGtwState.CONNECTED def set_disconnected(self): """Move lGTW to disconnected state.""" self.state = LGtwState.DISCONNECTED def set_online(self): """Move lGTW to online state.""" self.state = LGtwState.ONLINE def set_rmt_shell(self): """Move lGTW to remote shell state.""" self.state = LGtwState.RMTSH def set_send_rtt_on(self): """Send RTT message ON.""" if not self.__send_rtt_on: self.__send_rtt_on = True self.connection.lgtw_rtt_on_set() def set_send_rtt_off(self): """Send RTT message OFF.""" if self.__send_rtt_on: self.__send_rtt_on = False self.connection.lgtw_rtt_off_set() def is_send_rtt_on(self): """Return if RTT messages are set to ON.""" return self.__send_rtt_on def to_dict(self): """Return a JSON-serializable dictionary representing the PNFDev.""" if not self.last_seen: self.last_seen = datetime.now() date = datetime.timestamp(self.last_seen) return { 'name': self.name, 'lgtw_euid': self.lgtw_euid.id6, 'desc': self.desc, 'state': self.state.value, 'owner': str(self.owner), 'lgtw_version': self.lgtw_version, 'lgtw_config': self.lgtw_config, 'last_seen': self.last_seen, 'last_seen_ts': date, 'ipaddr': self.ipaddr }