class modem_listener: def __init__(self, name, port, rate, verbose): self.name = name self.port = port self.baudrate = rate self.verbose = verbose self.modem = None self.counter = 1 self.pub = rospy.Publisher('dash7', dash7, queue_size=1) self.pubrate = rospy.Rate(10) self.location = [] def set_location(self, x, y, z): self.location = [x, y, z] def location_broadcast(self, msg): br = tf.TransformBroadcaster() br.sendTransform((self.location[0], self.location[1], self.location[2]), tf.transformations.quaternion_from_euler(0.0, 0.0, 0.0, ), msg.header.stamp, self.name, "base_link") def received_command_callback(self, cmd): print cmd if cmd.interface_status != None: if cmd.interface_status.operand.interface_id == 215: # 215 is the interface_id that corresponds to DASH7 rospy.loginfo("Received Dash7 message") message = dash7() message = self.parse(message, cmd) self.pub.publish(message) self.location_broadcast(message) def parse(self, message, cmd): rospy.loginfo("Parse Dash7 message") message.header.stamp = rospy.Time.now() message.header.frame_id = str(cmd.interface_status.operand.interface_status.addressee.id) message.rx_address = str(self.modem.uid) message.tx_address = str(cmd.interface_status.operand.interface_status.addressee.id) message.rx_level = cmd.interface_status.operand.interface_status.rx_level message.link_budget = cmd.interface_status.operand.interface_status.link_budget message.channel_band = cmd.interface_status.operand.interface_status.channel_header.channel_band.name message.channel_class = cmd.interface_status.operand.interface_status.channel_header.channel_class.name message.channel_index = cmd.interface_status.operand.interface_status.channel_index message.counter = self.counter self.counter += 1 return message def listen(self): rospy.loginfo("Port %s with Baudrate %s", self.port, self.baudrate) while True: try: rospy.loginfo('make modem connection') self.modem = Modem(self.port, self.baudrate, receive_callback=self.received_command_callback, show_logging=self.verbose) self.modem.start_reading() while not rospy.is_shutdown(): pass except Exception, e: raise e
def setup_modem(self): # we use Modem here only for reading the modem information, not for parsing. # reading the bytes from serial (after initial connect) is not done using Modem but overridden here modem = Modem(self.config.device, self.config.rate, None) self.serial = modem.dev self.modem_uid = modem.uid modem.stop_reading() # so we can read the serial stream ourself
def __init__(self): self.argparser = argparse.ArgumentParser( fromfile_prefix_chars="@", description="Test throughput over 2 serial D7 modems" ) self.argparser.add_argument("-n", "--msg-count", help="number of messages to transmit", type=int, default=10) self.argparser.add_argument("-p", "--payload-size", help="number of bytes of (appl level) payload to transmit", type=int, default=50) self.argparser.add_argument("-sw", "--serial-transmitter", help="serial device /dev file transmitter node", default=None) self.argparser.add_argument("-sr", "--serial-receiver", help="serial device /dev file receiver node", default=None) self.argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) self.argparser.add_argument("-uid", "--unicast-uid", help="UID to use for unicast transmission, " "when not using receiver " "(in hexstring, for example 0xb57000009151d)", default=None) self.argparser.add_argument("-to", "--receiver-timeout", help="timeout for the receiver (in seconds)", type=int, default=10) self.argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") self.config = self.argparser.parse_args() if self.config.serial_transmitter == None and self.config.serial_receiver == None: self.argparser.error("At least a transmitter or receiver is required.") if self.config.serial_receiver == None and self.config.unicast_uid == None: self.argparser.error("When running without receiver a --unicast-uid parameter is required.") if self.config.serial_transmitter == None: self.transmitter_modem = None print("Running without transmitter") else: self.transmitter_modem = Modem(self.config.serial_transmitter, self.config.rate, None, show_logging=self.config.verbose) access_profile = AccessProfile( scan_type_is_foreground=True, csma_ca_mode=CsmaCaMode.UNC, subnet=03, scan_automation_period=CT(0), subbands=[Subband( channel_header=ChannelHeader(channel_band=ChannelBand.BAND_433, channel_coding=ChannelCoding.PN9, channel_class=ChannelClass.NORMAL_RATE), channel_index_start=16, channel_index_end=16, eirp=10, ccao=0 # TODO ) ] ) print("Write Access Profile") write_ap_cmd = Command.create_with_write_file_action_system_file(file=AccessProfileFile(access_profile=access_profile, access_specifier=0)) self.transmitter_modem.send_command(write_ap_cmd) if self.config.serial_receiver == None: self.receiver_modem = None print("Running without receiver") else: self.receiver_modem = Modem(self.config.serial_receiver, self.config.rate, self.receiver_cmd_callback, show_logging=self.config.verbose) self.receiver_modem.send_command(Command.create_with_write_file_action_system_file(DllConfigFile(active_access_class=2))) print("Receiver scanning on Access Class = 2")
def __init__(self): self.argparser = argparse.ArgumentParser( fromfile_prefix_chars="@", description="Test throughput over 2 serial D7 modems" ) self.argparser.add_argument("-n", "--msg-count", help="number of messages to transmit", type=int, default=10) self.argparser.add_argument("-p", "--payload-size", help="number of bytes of (appl level) payload to transmit", type=int, default=50) self.argparser.add_argument("-sw", "--serial-transmitter", help="serial device /dev file transmitter node", default=None) self.argparser.add_argument("-sr", "--serial-receiver", help="serial device /dev file receiver node", default=None) self.argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) self.argparser.add_argument("-uid", "--unicast-uid", help="UID to use for unicast transmission, " "when not using receiver " "(in hexstring, for example 0xb57000009151d)", default=None) self.argparser.add_argument("-to", "--receiver-timeout", help="timeout for the receiver (in seconds)", type=int, default=10) self.argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") self.config = self.argparser.parse_args() configure_default_logger(self.config.verbose) if self.config.serial_transmitter == None and self.config.serial_receiver == None: self.argparser.error("At least a transmitter or receiver is required.") if self.config.serial_receiver == None and self.config.unicast_uid == None: self.argparser.error("When running without receiver a --unicast-uid parameter is required.") if self.config.serial_transmitter == None: self.transmitter_modem = None print("Running without transmitter") else: self.transmitter_modem = Modem(self.config.serial_transmitter, self.config.rate, None) access_profile = AccessProfile( channel_header=ChannelHeader(channel_band=ChannelBand.BAND_868, channel_coding=ChannelCoding.PN9, channel_class=ChannelClass.NORMAL_RATE), sub_profiles=[SubProfile(subband_bitmap=0x01, scan_automation_period=CT(exp=0, mant=0)), SubProfile(), SubProfile(), SubProfile()], sub_bands=[SubBand( channel_index_start=0, channel_index_end=0, eirp=10, cca=86 # TODO )] ) print("Write Access Profile") write_ap_cmd = Command.create_with_write_file_action_system_file(file=AccessProfileFile(access_profile=access_profile, access_specifier=0)) self.transmitter_modem.execute_command(write_ap_cmd, timeout_seconds=1) if self.config.serial_receiver == None: self.receiver_modem = None print("Running without receiver") else: self.receiver_modem = Modem(self.config.serial_receiver, self.config.rate, self.receiver_cmd_callback) self.receiver_modem.execute_command(Command.create_with_write_file_action_system_file(DllConfigFile(active_access_class=0x01)), timeout_seconds=1) print("Receiver scanning on Access Class = 0x01")
def listen(self): rospy.loginfo("Port %s with Baudrate %s", self.port, self.baudrate) while True: try: rospy.loginfo('make modem connection') self.modem = Modem(self.port, self.baudrate, receive_callback=self.received_command_callback, show_logging=self.verbose) self.modem.start_reading() while not rospy.is_shutdown(): pass except Exception, e: raise e
def on_connect(): global modem if modem == None: modem = Modem(config.device, config.rate, command_received_callback) modem.start_reading() print("modem: " + str(modem.uid)) emit('module_info', { 'uid': hex(modem.uid), 'application_name': modem.firmware_version.application_name, 'git_sha1': modem.firmware_version.git_sha1, 'd7ap_version': modem.firmware_version.d7ap_version }, broadcast=True)
def on_connect(): global modem if modem == None: modem = Modem(config.device, config.rate, command_received_callback) modem.connect() logging.info("modem: " + str(modem.uid)) emit('module_info', { 'uid': modem.uid, 'application_name': modem.firmware_version.application_name, 'git_sha1': modem.firmware_version.git_sha1, 'd7ap_version': modem.firmware_version.d7ap_version }, broadcast=True)
def __init__(self): self.logs = [] self.command = "" self.counter = 0 argparser = argparse.ArgumentParser() argparser.add_argument("-s", "--serial", help="serial device /dev file", default="/dev/tty.usbserial-FTGCT0HY") argparser.add_argument("-b", "--baudrate", help="baudrate for serial device", type=int, default=115200) config = argparser.parse_args() self.modem = Modem(config.serial, config.baudrate, show_logging=False) self.setup_screen()
def __init__(self): argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyACM0") argparser.add_argument("-di", "--device-id", help="gateway device-id", required=True) argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-b", "--broker", help="mqtt broker hostname", default="localhost") argparser.add_argument("-t", "--topic", help="mqtt topic", default="/gw/{}") self.serial = None self.modem_uid = None self.bridge_count = 0 self.next_report = 0 self.config = argparser.parse_args() configure_default_logger(self.config.verbose) self.modem = Modem(self.config.device, self.config.rate, self.on_command_received, skip_alp_parsing=True) self.modem.connect() self.connect_to_mqtt()
from d7a.alp.command import Command from d7a.alp.interface import InterfaceType from d7a.d7anp.addressee import Addressee, IdType from d7a.sp.configuration import Configuration from d7a.sp.qos import QoS from d7a.system_files.uid import UidFile from modem.modem import Modem argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyUSB0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) config = argparser.parse_args() modem = Modem(config.device, config.rate) modem.d7asp_fifo_flush( alp_command=Command.create_with_read_file_action_system_file( file=UidFile(), interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=QoS.RESP_MODE_NO), addressee=Addressee( access_class=0, id_type=IdType.BCAST ) ) ) )
argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyUSB0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-c", "--channel-id", help="for example 868LP000 ; format FFFRCIII where FFF={433, 868, 915}, R={L, N, H, R (LORA)}, C={P (PN9), F (FEC), C (CW)} III=000...280", default="868LP000") argparser.add_argument("-e", "--eirp", help="EIRP in dBm", type=int, default=14) argparser.add_argument("-s", "--specifier", help="specifier for access profile. Default 0 is continuous scan, 1 is bg scan, 2+ is no scan", type=int, default=0) argparser.add_argument("-sp", "--scan_automation_period", help="period in ms of scanning (786 ~ total 1 sec), 0 is continuous scan ", type=int, default=0) argparser.add_argument("-sb", "--subband_bitmap", help="subband bitmap of subprofiles, 0 is default, 1 is scanning", type=int, default=0) config = argparser.parse_args() configure_default_logger(config.verbose) ch = ChannelID.from_string(config.channel_id) modem = Modem(config.device, config.rate, unsolicited_response_received_callback=received_command_callback) modem.connect() channel_header = ChannelHeader( channel_class=ch.channel_header.channel_class, channel_coding=ch.channel_header.channel_coding, channel_band=ch.channel_header.channel_band ) access_profile = AccessProfile( channel_header=channel_header, sub_profiles=[SubProfile(subband_bitmap=config.subband_bitmap, scan_automation_period=CT.compress(config.scan_automation_period))] * 4, sub_bands=[SubBand(eirp=config.eirp, channel_index_start=ch.channel_index, channel_index_end=ch.channel_index)] * 8 ) modem.execute_command(
argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument( "-f", "--file_id", help="File where we're writing the interface configuration", default=0x1D) argparser.add_argument("-t", "--timeout", help="timeout", type=int, default=0) config = argparser.parse_args() configure_default_logger(config.verbose) modem = Modem(config.device, config.rate, unsolicited_response_received_callback=received_command_callback) modem.connect() # D7 Example interface_file = InterfaceConfigurationFile( interface_configuration=InterfaceConfiguration( interface_id=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_PREFERRED, retry_mod=RetryMode.RETRY_MODE_NO, stop_on_err=False, record=False), dorm_to=CT(), addressee=Addressee(nls_method=NlsMethod.NONE, id_type=IdType.UID,
from d7a.d7anp.addressee import Addressee, IdType from d7a.sp.configuration import Configuration from d7a.sp.qos import QoS from d7a.system_files.uid import UidFile from modem.modem import Modem argparser = argparse.ArgumentParser() argparser.add_argument("-s", "--serial", help="serial device /dev file", default="/dev/tty.usbserial-FTGCT0HY") argparser.add_argument("-b", "--baudrate", help="baudrate for serial device", type=int, default=115200) config = argparser.parse_args() modem = Modem(config.serial, config.baudrate, show_logging=False) cmd = Command.create_with_return_file_data_action( file_id=0x40, data=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=QoS.RESP_MODE_NO), addressee=Addressee( access_class=0, id_type=IdType.BCAST ) ) ) last_message = 0
def __init__(self): argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyACM0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-t", "--token", help="Access token for the TB gateway", required=True) argparser.add_argument("-tb", "--thingsboard", help="Thingsboard hostname/IP", default="localhost") argparser.add_argument("-p", "--plugin-path", help="path where plugins are stored", default="") argparser.add_argument("-bp", "--broker-port", help="mqtt broker port", default="1883") argparser.add_argument( "-l", "--logfile", help="specify path if you want to log to file instead of to stdout", default="") argparser.add_argument( "-k", "--keep-data", help= "Save data locally when Thingsboard is disconnected and send it when connection is restored.", default=True) argparser.add_argument( "-b", "--save-bandwidth", help="Send data in binary format to save bandwidth", action="store_true") argparser.add_argument("-sf", "--skip-system-files", help="Do not read system files on boot", action="store_true") self.bridge_count = 0 self.next_report = 0 self.config = argparser.parse_args() self.log = logging.getLogger() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') if self.config.logfile == "": handler = logging.StreamHandler() else: handler = logging.FileHandler(self.config.logfile) handler.setFormatter(formatter) self.log.addHandler(handler) self.log.setLevel(logging.INFO) if self.config.verbose: self.log.setLevel(logging.DEBUG) self.tb = Thingsboard(self.config.thingsboard, self.config.token, self.on_mqtt_message, persistData=self.config.keep_data) if self.config.plugin_path != "": self.load_plugins(self.config.plugin_path) self.modem = Modem(self.config.device, self.config.rate, self.on_command_received, self.config.save_bandwidth) connected = self.modem.connect() while not connected: try: self.log.warning("Not connected to modem, retrying ...") time.sleep(1) connected = self.modem.connect() except KeyboardInterrupt: self.log.info("received KeyboardInterrupt... stopping") self.tb.disconnect() exit(-1) except: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) trace = "".join(lines) self.log.error( "Exception while connecting modem: \n{}".format(trace)) # switch to continuous foreground scan access profile self.modem.execute_command( Command.create_with_write_file_action_system_file( DllConfigFile(active_access_class=0x01)), timeout_seconds=1) if self.config.save_bandwidth: self.log.info("Running in save bandwidth mode") if self.config.plugin_path is not "": self.log.warning( "Save bandwidth mode is enabled, plugin files will not be used" ) # update attribute containing git rev so we can track revision at TB platform git_sha = subprocess.check_output(["git", "describe", "--always"]).strip() ip = self.get_ip() self.tb.sendGwAttributes({ 'UID': self.modem.uid, 'git-rev': git_sha, 'IP': ip, 'save bw': str(self.config.save_bandwidth) }) self.log.info("Running on {} with git rev {} using modem {}".format( ip, git_sha, self.modem.uid)) # read all system files on the local node to store as attributes on TB if not self.config.skip_system_files: self.log.info("Reading all system files ...") for file in SystemFiles().files.values(): self.modem.execute_command_async( Command.create_with_read_file_action_system_file(file))
#!/usr/bin/env python3 import logging # import signal import sys from challenge_status import ChallengeState from modem import modem_io from modem.modem import Modem logging.basicConfig(level=logging.CRITICAL) # s = signal.signal(signal.SIGINT, signal.SIG_IGN) # Having the call here is only necessary for debug ChallengeState.get() print("Connected to /dev/ttyACM0") sys.stdout.flush() try: modem = Modem(modem_io.ConsoleIO()) modem.run() except KeyboardInterrupt: raise except: sys.exit(1) # signal.signal(signal.SIGINT, s)
argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-t", "--temperature", help="temperature threshold value (in degrees C)", type=float, default=0) config = argparser.parse_args() configure_default_logger(config.verbose) modem = Modem(config.device, config.rate, unsolicited_response_received_callback=received_command_callback) modem.connect() # command to be executed as an action: first do an arithmetic comparison of the sensor value with the supplied value... query_sensor_file_cmd = Command(generate_tag_request_action=False) query_sensor_file_cmd.add_action( RegularAction(operation=BreakQuery(operand=QueryOperand( type=QueryType.ARITH_COMP_WITH_VALUE, mask_present=False, params=ArithQueryParams(signed_data_type=False, comp_type=ArithComparisonType.GREATER_THAN), compare_length=Length(2), compare_value=[ ord(b) for b in struct.pack(">H", int(config.temperature * 10)) ],
import argparse from d7a.alp.command import Command from d7a.alp.interface import InterfaceType from d7a.d7anp.addressee import Addressee, IdType from d7a.sp.configuration import Configuration from d7a.sp.qos import QoS, ResponseMode from d7a.system_files.uid import UidFile from modem.modem import Modem # This example can be used with a node running the gateway app included in OSS-7. # The gateway is continuously listening for foreground frames. # Messages pushed by other nodes (running for example the sensor_push app) will be received by the gateway node, # transmitted over serial and the received_command_callback() function below will be called. def received_command_callback(cmd): print cmd argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyUSB0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") config = argparser.parse_args() modem = Modem(config.device, config.rate, unsolicited_response_received_callback=received_command_callback, show_logging=config.verbose) while True: pass
class ChatClient(object): def __init__(self): self.logs = [] self.command = "" self.counter = 0 argparser = argparse.ArgumentParser() argparser.add_argument("-s", "--serial", help="serial device /dev file", default="/dev/tty.usbserial-FTGCT0HY") argparser.add_argument("-b", "--baudrate", help="baudrate for serial device", type=int, default=115200) config = argparser.parse_args() self.modem = Modem(config.serial, config.baudrate, show_logging=False) self.setup_screen() def setup_screen(self): self.screen = curses.initscr() curses.noecho() # no echo, we will render it curses.cbreak() # curses.curs_set(0) # hide cursor, we will fake it with inverted space curses.halfdelay(1) # wait for 1/10 second when waiting for input self.screen.keypad(1) self.setup() def setup(self): (self.height, self.width) = self.screen.getmaxyx() self.setup_viewer_window() self.setup_prompt() self.refresh_logger_window() self.refresh_prompt() def setup_viewer_window(self): # create a pad, taking up the entire window, but the last two lines self.logger = curses.newpad(self.height-1, self.width) def setup_prompt(self): # create a window, taking up the entire window, but the last two lines self.prompt = curses.newpad(2, self.width) name = "ChatClient" self.prompt.addstr(0, 0, name + " " * (self.width-len(name)), curses.A_REVERSE) self.prompt.addstr(1, 0, "> ") self.update_command() def update_command(self): w = len(self.command) self.prompt.addstr(1, 2, self.command) self.prompt.addstr(1, 2 + w, " ", curses.A_REVERSE) # cursor self.prompt.addstr(1, 3 + w, " " * (self.width - 4 - w)) self.refresh_prompt() def clean_up(self, msg=""): self.clean_up_screen() def clean_up_screen(self): curses.nocbreak(); self.screen.keypad(0); curses.echo() curses.endwin() def add(self, text, style=curses.A_NORMAL): text = text.replace('\0', '') self.logs.append({ "text": text, "style": style}) self.refresh_logger_window() def log(self, *msg): string = " ".join(map(str, msg)) for line in string.splitlines(): self.add(line) def refresh_logger_window(self): (height, width) = self.screen.getmaxyx() self.logger.erase() for y, line in enumerate(self.logs[-height+2:]): self.logger.addstr(y, 0, line["text"], line["style"]) self.logger.refresh(0, 0, 0, 0, height-3, width) def refresh_prompt(self): (height, width) = self.screen.getmaxyx() self.prompt.refresh(0, 0, height-2, 0, height, width) def process(self): while True: # handle input from user self.handle_input() # Read from D7 modem and decode packets/commands (cmds, info) = self.modem.read() if len(info["errors"]) < 1: if len(cmds) > 0: origin = cmds[0].interface_status.operation.operand.interface_status.addressee.id data = cmds[0].actions[0].operation.operand.data self.add(str(origin) + ": " + ''.join(map(str, data))) def handle_input(self): c = self.prompt.getch() if c == curses.ERR: return elif c == curses.KEY_RESIZE: self.setup() elif c == curses.ascii.DEL or c == curses.ascii.BS: self.command = self.command[:-1] elif c == ord('\n'): self.send() self.command = "" else: self.command += chr(c) self.update_command() def send(self): cmd = Command.create_with_return_file_data_action( file_id=0x40, data=map(ord, list(self.command)), interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=QoS.RESP_MODE_NO), addressee=Addressee( access_class=0, id_type=IdType.BCAST ) ) ) self.modem.d7asp_fifo_flush(alp_command=cmd) self.add("me: " + self.command, curses.A_REVERSE)
from util.logger import configure_default_logger def received_command_callback(cmd): logging.info(cmd) if cmd.execution_completed: sys.exit(0) argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyUSB0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") config = argparser.parse_args() configure_default_logger(config.verbose) modem = Modem(config.device, config.rate, unsolicited_response_received_callback=received_command_callback) modem.connect() answers = modem.execute_command( alp_command=Command.create_with_read_file_action_system_file( file=PhyStatusFile(channel_status_list_length=10) ) ) for answer in answers: logging.info("answer is {}".format(answer.__str__()))
class Modem2Mqtt(): def __init__(self): argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyACM0") argparser.add_argument("-di", "--device-id", help="gateway device-id", required=True) argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-b", "--broker", help="mqtt broker hostname", default="localhost") argparser.add_argument("-t", "--topic", help="mqtt topic", default="/gw/{}") self.serial = None self.modem_uid = None self.bridge_count = 0 self.next_report = 0 self.config = argparser.parse_args() configure_default_logger(self.config.verbose) self.modem = Modem(self.config.device, self.config.rate, self.on_command_received, skip_alp_parsing=True) self.modem.connect() self.connect_to_mqtt() def connect_to_mqtt(self): self.connected_to_mqtt = False self.mq = mqtt.Client("", True, None, mqtt.MQTTv31) self.mq.on_connect = self.on_mqtt_connect self.mq.on_message = self.on_mqtt_message self.mqtt_topic_incoming = self.config.topic.format(self.config.device_id) #self.mqtt_topic_outgoing = self.config.topic.format(self.modem_uid) self.mq.connect(self.config.broker, 1883, 60) self.mq.loop_start() while not self.connected_to_mqtt: pass # busy wait until connected logging.info("Connected to MQTT broker on {}, sending to topic {}".format( self.config.broker, self.mqtt_topic_incoming )) def on_mqtt_connect(self, client, config, flags, rc): #self.mq.subscribe(self.mqtt_topic_outgoing) self.connected_to_mqtt = True def on_mqtt_message(self, client, config, msg): logging.info("on_message") # TODO # try: self.handle_msg(msg.topic, msg.payload) # except: # exc_type, exc_value, exc_traceback = sys.exc_info() # lines = traceback.format_exception(exc_type, exc_value, exc_traceback) # trace = "".join(lines) # self.log("failed to handle incoming message:", msg.payload, trace) def publish_to_mqtt(self, msg): self.mq.publish(self.mqtt_topic_incoming, msg) def __del__(self): # pragma: no cover try: self.mq.loop_stop() self.mq.disconnect() except: pass def on_command_received(self, cmd): try: logging.info("Command received: binary ALP (size {})".format(len(cmd))) # publish raw ALP command to incoming ALP topic, we will not parse the file contents here (since we don't know how) # so pass it as an opaque BLOB for parsing in backend self.publish_to_mqtt(bytearray(cmd)) except: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) trace = "".join(lines) logging.error("Exception while processing command: \n{}".format(trace)) def run(self): logging.info("Started") keep_running = True while keep_running: try: if platform.system() == "Windows": time.sleep(1) else: signal.pause() except KeyboardInterrupt: logging.info("received KeyboardInterrupt... stopping processing") keep_running = False self.report_stats() def keep_stats(self): self.bridge_count += 1 def report_stats(self): if self.next_report < time.time(): if self.bridge_count > 0: logging.info("bridged %s messages" % str(self.bridge_count)) self.bridge_count = 0 self.next_report = time.time() + 15 # report at most every 15 seconds
help="serial device /dev file modem", default="/dev/ttyACM1") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") config = argparser.parse_args() modem = Modem(config.device, config.rate, receive_callback=received_command_callback, show_logging=config.verbose) modem.d7asp_fifo_flush( alp_command=Command.create_with_read_file_action_system_file( file=UidFile(), interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ALL), addressee=Addressee(access_class=0x01, id_type=IdType.NOID)))) modem.start_reading() while True: pass
class Gateway: def __init__(self): argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyACM0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-t", "--token", help="Access token for the TB gateway", required=True) argparser.add_argument("-tb", "--thingsboard", help="Thingsboard hostname/IP", default="localhost") argparser.add_argument("-p", "--plugin-path", help="path where plugins are stored", default="") argparser.add_argument("-bp", "--broker-port", help="mqtt broker port", default="1883") argparser.add_argument( "-l", "--logfile", help="specify path if you want to log to file instead of to stdout", default="") argparser.add_argument( "-k", "--keep-data", help= "Save data locally when Thingsboard is disconnected and send it when connection is restored.", default=True) argparser.add_argument( "-b", "--save-bandwidth", help="Send data in binary format to save bandwidth", action="store_true") argparser.add_argument("-sf", "--skip-system-files", help="Do not read system files on boot", action="store_true") self.bridge_count = 0 self.next_report = 0 self.config = argparser.parse_args() self.log = logging.getLogger() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') if self.config.logfile == "": handler = logging.StreamHandler() else: handler = logging.FileHandler(self.config.logfile) handler.setFormatter(formatter) self.log.addHandler(handler) self.log.setLevel(logging.INFO) if self.config.verbose: self.log.setLevel(logging.DEBUG) self.tb = Thingsboard(self.config.thingsboard, self.config.token, self.on_mqtt_message, persistData=self.config.keep_data) if self.config.plugin_path != "": self.load_plugins(self.config.plugin_path) self.modem = Modem(self.config.device, self.config.rate, self.on_command_received, self.config.save_bandwidth) connected = self.modem.connect() while not connected: try: self.log.warning("Not connected to modem, retrying ...") time.sleep(1) connected = self.modem.connect() except KeyboardInterrupt: self.log.info("received KeyboardInterrupt... stopping") self.tb.disconnect() exit(-1) except: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) trace = "".join(lines) self.log.error( "Exception while connecting modem: \n{}".format(trace)) # switch to continuous foreground scan access profile self.modem.execute_command( Command.create_with_write_file_action_system_file( DllConfigFile(active_access_class=0x01)), timeout_seconds=1) if self.config.save_bandwidth: self.log.info("Running in save bandwidth mode") if self.config.plugin_path is not "": self.log.warning( "Save bandwidth mode is enabled, plugin files will not be used" ) # update attribute containing git rev so we can track revision at TB platform git_sha = subprocess.check_output(["git", "describe", "--always"]).strip() ip = self.get_ip() self.tb.sendGwAttributes({ 'UID': self.modem.uid, 'git-rev': git_sha, 'IP': ip, 'save bw': str(self.config.save_bandwidth) }) self.log.info("Running on {} with git rev {} using modem {}".format( ip, git_sha, self.modem.uid)) # read all system files on the local node to store as attributes on TB if not self.config.skip_system_files: self.log.info("Reading all system files ...") for file in SystemFiles().files.values(): self.modem.execute_command_async( Command.create_with_read_file_action_system_file(file)) def load_plugins(self, plugin_path): self.log.info("Searching for plugins in path %s" % plugin_path) manager = PluginManagerSingleton.get() manager.setPluginPlaces([plugin_path]) manager.collectPlugins() for plugin in manager.getAllPlugins(): self.log.info("Loading plugin '%s'" % plugin.name) def on_command_received(self, cmd): try: if self.config.save_bandwidth: self.log.info("Command received: binary ALP (size {})".format( len(cmd))) else: self.log.info("Command received: {}".format(cmd)) ts = int(round(time.time() * 1000)) # publish raw ALP command to incoming ALP topic, we will not parse the file contents here (since we don't know how) # so pass it as an opaque BLOB for parsing in backend if self.config.save_bandwidth: self.tb.sendGwAttributes({ 'alp': binascii.hexlify(bytearray(cmd)), 'last_seen': str(datetime.now().strftime("%y-%m-%d %H:%M:%S")) }) return self.tb.sendGwAttributes({ 'alp': jsonpickle.encode(cmd), 'last_seen': str(datetime.now().strftime("%y-%m-%d %H:%M:%S")) }) node_id = self.modem.uid # overwritten below with remote node ID when received over D7 interface # parse link budget (when this is received over D7 interface) and publish separately so we can visualize this in TB if cmd.interface_status != None and cmd.interface_status.operand.interface_id == 0xd7: interface_status = cmd.interface_status.operand.interface_status node_id = '{:x}'.format(interface_status.addressee.id) linkBudget = interface_status.link_budget rxLevel = interface_status.rx_level lastConnect = "D7-" + interface_status.get_short_channel_string( ) self.tb.sendDeviceTelemetry(node_id, ts, { 'lb': linkBudget, 'rx': rxLevel }) self.tb.sendDeviceAttributes(node_id, { 'last_conn': lastConnect, 'last_gw': self.modem.uid }) # store returned file data as attribute on the device for action in cmd.actions: if type(action.operation) is ReturnFileData: data = "" if action.operation.file_data_parsed is not None: if not self.config.save_bandwidth: # for known system files we transmit the parsed data data = jsonpickle.encode( action.operation.file_data_parsed) file_id = "File {}".format( action.operand.offset.id) self.tb.sendGwAttributes({file_id: data}) else: # try if plugin can parse this file parsed_by_plugin = False if not self.config.save_bandwidth: for plugin in PluginManagerSingleton.get( ).getAllPlugins(): for name, value, datapoint_type in plugin.plugin_object.parse_file_data( action.operand.offset, action.operand.length, action.operand.data): parsed_by_plugin = True if isinstance(value, int) or isinstance( value, float): self.tb.sendDeviceTelemetry( node_id, ts, {name: value}) else: self.tb.sendDeviceAttributes( node_id, {name: value}) if not parsed_by_plugin: # unknown file content, just transmit raw data data = jsonpickle.encode(action.operand) filename = "File {}".format( action.operand.offset.id) if action.operation.systemfile_type != None: filename = "File {} ({})".format( SystemFileIds( action.operand.offset.id).name, action.operand.offset.id) self.tb.sendDeviceAttributes( node_id, {filename: data}) except: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) trace = "".join(lines) self.log.error( "Exception while processing command: \n{}".format(trace)) def on_mqtt_message(self, client, config, msg): try: payload = json.loads(msg.payload) uid = payload['device'] method = payload['data']['method'] request_id = payload['data']['id'] self.log.info( "Received RPC command of type {} for {} (request id {})". format(method, uid, request_id)) # if uid != self.modem.uid: # self.log.info("RPC command not for this modem ({}), skipping".format(self.modem.uid)) # return if method == "execute-alp-async": try: cmd = payload['data']['params'] self.log.info("Received command through RPC: %s" % cmd) self.modem.execute_command_async(cmd) self.log.info("Executed ALP command through RPC") # TODO when the command is writing local files we could read them again automatically afterwards, to make sure the digital twin is updated except Exception as e: self.log.exception("Could not deserialize: %s" % e) elif method == "alert": # TODO needs refactoring so different methods can be supported in a plugin, for now this is very specific case as an example self.log.info("Alert (payload={})".format(msg.payload)) if msg.payload != "true" and msg.payload != "false": self.log.info("invalid payload, skipping") return file_data = 0 if msg.payload == "true": file_data = 1 self.log.info("writing alert file") self.modem.execute_command_async( Command.create_with_write_file_action( file_id=0x60, offset=4, data=[file_data], interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ALL), addressee=Addressee(access_class=0x11, id_type=IdType.NOID)))) else: self.log.info("RPC method not supported, skipping") return except: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) trace = "".join(lines) msg_info = "no msg info (missing __dict__ attribute)" # TODO because of out of date paho?? if hasattr(msg, '__dict__'): msg_info = str(msg.__dict__) self.log.error( "Exception while processing MQTT message: {} callstack:\n{}". format(msg_info, trace)) def run(self): self.log.info("Started") keep_running = True while keep_running: try: if platform.system() == "Windows": time.sleep(1) else: signal.pause() except KeyboardInterrupt: self.log.info( "received KeyboardInterrupt... stopping processing") self.tb.disconnect() keep_running = False self.report_stats() def keep_stats(self): self.bridge_count += 1 def report_stats(self): if self.next_report < time.time(): if self.bridge_count > 0: self.log.info("bridged %s messages" % str(self.bridge_count)) self.bridge_count = 0 self.next_report = time.time( ) + 15 # report at most every 15 seconds def get_ip(self): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # doesn't even have to be reachable s.connect(('10.255.255.255', 1)) IP = s.getsockname()[0] except: IP = '127.0.0.1' finally: s.close() return IP
from d7a.sp.qos import QoS, ResponseMode from d7a.system_files.uid import UidFile from modem.modem import Modem def received_command_callback(cmd): print cmd argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyUSB0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") config = argparser.parse_args() modem = Modem(config.device, config.rate, receive_callback=received_command_callback, show_logging=config.verbose) modem.d7asp_fifo_flush( alp_command=Command.create_with_read_file_action_system_file( file=UidFile(), interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ALL), addressee=Addressee( access_class=0, id_type=IdType.NOID ) ) ) ) modem.start_reading()
class ChatClient(object): def __init__(self): self.logs = [] self.command = "" self.counter = 0 argparser = argparse.ArgumentParser() argparser.add_argument("-s", "--serial", help="serial device /dev file", default="/dev/tty.usbserial-FTGCT0HY") argparser.add_argument("-b", "--baudrate", help="baudrate for serial device", type=int, default=115200) config = argparser.parse_args() self.modem = Modem(config.serial, config.baudrate, show_logging=False) self.setup_screen() def setup_screen(self): self.screen = curses.initscr() curses.noecho() # no echo, we will render it curses.cbreak() # curses.curs_set(0) # hide cursor, we will fake it with inverted space curses.halfdelay(1) # wait for 1/10 second when waiting for input self.screen.keypad(1) self.setup() def setup(self): (self.height, self.width) = self.screen.getmaxyx() self.setup_viewer_window() self.setup_prompt() self.refresh_logger_window() self.refresh_prompt() def setup_viewer_window(self): # create a pad, taking up the entire window, but the last two lines self.logger = curses.newpad(self.height - 1, self.width) def setup_prompt(self): # create a window, taking up the entire window, but the last two lines self.prompt = curses.newpad(2, self.width) name = "ChatClient" self.prompt.addstr(0, 0, name + " " * (self.width - len(name)), curses.A_REVERSE) self.prompt.addstr(1, 0, "> ") self.update_command() def update_command(self): w = len(self.command) self.prompt.addstr(1, 2, self.command) self.prompt.addstr(1, 2 + w, " ", curses.A_REVERSE) # cursor self.prompt.addstr(1, 3 + w, " " * (self.width - 4 - w)) self.refresh_prompt() def clean_up(self, msg=""): self.clean_up_screen() def clean_up_screen(self): curses.nocbreak() self.screen.keypad(0) curses.echo() curses.endwin() def add(self, text, style=curses.A_NORMAL): text = text.replace('\0', '') self.logs.append({"text": text, "style": style}) self.refresh_logger_window() def log(self, *msg): string = " ".join(map(str, msg)) for line in string.splitlines(): self.add(line) def refresh_logger_window(self): (height, width) = self.screen.getmaxyx() self.logger.erase() for y, line in enumerate(self.logs[-height + 2:]): self.logger.addstr(y, 0, line["text"], line["style"]) self.logger.refresh(0, 0, 0, 0, height - 3, width) def refresh_prompt(self): (height, width) = self.screen.getmaxyx() self.prompt.refresh(0, 0, height - 2, 0, height, width) def process(self): while True: # handle input from user self.handle_input() # Read from D7 modem and decode packets/commands (cmds, info) = self.modem.read() if len(info["errors"]) < 1: if len(cmds) > 0: origin = cmds[ 0].interface_status.operation.operand.interface_status.addressee.id data = cmds[0].actions[0].operation.operand.data self.add(str(origin) + ": " + ''.join(map(str, data))) def handle_input(self): c = self.prompt.getch() if c == curses.ERR: return elif c == curses.KEY_RESIZE: self.setup() elif c == curses.ascii.DEL or c == curses.ascii.BS: self.command = self.command[:-1] elif c == ord('\n'): self.send() self.command = "" else: self.command += chr(c) self.update_command() def send(self): cmd = Command.create_with_return_file_data_action( file_id=0x40, data=map(ord, list(self.command)), interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=QoS.RESP_MODE_NO), addressee=Addressee(access_class=0, id_type=IdType.BCAST))) self.modem.d7asp_fifo_flush(alp_command=cmd) self.add("me: " + self.command, curses.A_REVERSE)
count = 0 argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyUSB0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") config = argparser.parse_args() modem = Modem(config.device, config.rate, receive_callback=received_command_callback, show_logging=config.verbose) modem.start_reading() # Program loop while True: pass
print("Using mode {} for channel {} with TX EIRP {} dBm".format(config.mode, config.channel_id, config.eirp)) mode = EngineeringModeMode.from_string(config.mode) if (mode == EngineeringModeMode.ENGINEERING_MODE_MODE_PER_TX) and (config.timeout == 0): config.timeout = 255 emFile = EngineeringModeFile(mode=mode, flags=0, timeout=config.timeout, channel_id=ch, eirp=config.eirp) stop = False signal.signal(signal.SIGINT, cleanup) print(list(emFile)) if not config.not_exe: modem = Modem(config.device, config.rate, unsolicited_response_received_callback=received_command_callback, rebooted_callback=rebooted_callback) modem.connect() cmd = Command() if config.forward: cmd.add_forward_action( interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_PREFERRED, retry_mod=RetryMode.RETRY_MODE_NO), addressee=Addressee( access_class=0x11, id_type=IdType.NBID, id=CT.compress(2) ) )
class ThroughtPutTest: def __init__(self): self.argparser = argparse.ArgumentParser( fromfile_prefix_chars="@", description="Test throughput over 2 serial D7 modems" ) self.argparser.add_argument("-n", "--msg-count", help="number of messages to transmit", type=int, default=10) self.argparser.add_argument("-p", "--payload-size", help="number of bytes of (appl level) payload to transmit", type=int, default=50) self.argparser.add_argument("-sw", "--serial-transmitter", help="serial device /dev file transmitter node", default=None) self.argparser.add_argument("-sr", "--serial-receiver", help="serial device /dev file receiver node", default=None) self.argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) self.argparser.add_argument("-uid", "--unicast-uid", help="UID to use for unicast transmission, " "when not using receiver " "(in hexstring, for example 0xb57000009151d)", default=None) self.argparser.add_argument("-to", "--receiver-timeout", help="timeout for the receiver (in seconds)", type=int, default=10) self.argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") self.config = self.argparser.parse_args() if self.config.serial_transmitter == None and self.config.serial_receiver == None: self.argparser.error("At least a transmitter or receiver is required.") if self.config.serial_receiver == None and self.config.unicast_uid == None: self.argparser.error("When running without receiver a --unicast-uid parameter is required.") if self.config.serial_transmitter == None: self.transmitter_modem = None print("Running without transmitter") else: self.transmitter_modem = Modem(self.config.serial_transmitter, self.config.rate, None, show_logging=self.config.verbose) access_profile = AccessProfile( scan_type_is_foreground=True, csma_ca_mode=CsmaCaMode.UNC, subnet=03, scan_automation_period=CT(0), subbands=[Subband( channel_header=ChannelHeader(channel_band=ChannelBand.BAND_433, channel_coding=ChannelCoding.PN9, channel_class=ChannelClass.NORMAL_RATE), channel_index_start=16, channel_index_end=16, eirp=10, ccao=0 # TODO ) ] ) print("Write Access Profile") write_ap_cmd = Command.create_with_write_file_action_system_file(file=AccessProfileFile(access_profile=access_profile, access_specifier=0)) self.transmitter_modem.send_command(write_ap_cmd) if self.config.serial_receiver == None: self.receiver_modem = None print("Running without receiver") else: self.receiver_modem = Modem(self.config.serial_receiver, self.config.rate, self.receiver_cmd_callback, show_logging=self.config.verbose) self.receiver_modem.send_command(Command.create_with_write_file_action_system_file(DllConfigFile(active_access_class=2))) print("Receiver scanning on Access Class = 2") def start(self): self.received_commands = defaultdict(list) payload = range(self.config.payload_size) if self.receiver_modem != None: addressee_id = self.receiver_modem.uid else: addressee_id = int(self.config.unicast_uid, 16) if self.transmitter_modem != None: print("\n==> broadcast, with QoS, transmitter active access class = 0 ====") self.transmitter_modem.send_command(Command.create_with_write_file_action_system_file(DllConfigFile(active_access_class=0))) interface_configuration = Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ANY), addressee=Addressee( access_class=2, id_type=IdType.NOID ) ) self.start_transmitting(interface_configuration=interface_configuration, payload=payload) self.wait_for_receiver(payload) print("\n==> broadcast, no QoS, transmitter active access class = 0 ====") self.transmitter_modem.send_command(Command.create_with_write_file_action_system_file(DllConfigFile(active_access_class=0))) interface_configuration = Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_NO), addressee=Addressee( access_class=2, id_type=IdType.NOID ) ) self.start_transmitting(interface_configuration=interface_configuration, payload=payload) self.wait_for_receiver(payload) print("\n==> unicast, with QoS, transmitter active access class = 0") interface_configuration = Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ANY), addressee=Addressee( access_class=2, id_type=IdType.UID, id=addressee_id ) ) self.start_transmitting(interface_configuration=interface_configuration, payload=payload) self.wait_for_receiver(payload) print("\n==> unicast, no QoS, transmitter active access class = 0") interface_configuration = Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_NO), addressee=Addressee( access_class=2, id_type=IdType.UID, id=addressee_id ) ) self.start_transmitting(interface_configuration=interface_configuration, payload=payload) self.wait_for_receiver(payload) else: # receive only self.receiver_modem.start_reading() self.wait_for_receiver(payload) def start_transmitting(self, interface_configuration, payload): print("Running throughput test with payload size {} and interface_configuration {}\n\nrunning ...\n".format(len(payload), interface_configuration)) if self.receiver_modem != None: self.received_commands = defaultdict(list) self.receiver_modem.start_reading() command = Command.create_with_return_file_data_action( file_id=0x40, data=payload, interface_type=InterfaceType.D7ASP, interface_configuration=interface_configuration ) start = time.time() for i in range(self.config.msg_count): sys.stdout.write("{}/{}\r".format(i + 1, self.config.msg_count)) sys.stdout.flush() self.transmitter_modem.d7asp_fifo_flush(command) end = time.time() print("transmitter: sending {} messages completed in: {} s".format(self.config.msg_count, end - start)) print("transmitter: throughput = {} bps with a payload size of {} bytes".format( (self.config.msg_count * self.config.payload_size * 8) / (end - start), self.config.payload_size) ) def wait_for_receiver(self, payload): if self.receiver_modem == None: print("Running without receiver so we are not waiting for messages to be received ...") else: start = time.time() total_recv = 0 while total_recv < self.config.msg_count and time.time() - start < self.config.receiver_timeout: total_recv = sum(len(v) for v in self.received_commands.values()) time.sleep(2) print("waiting for receiver to finish ... (current nr of recv msgs: {})".format(total_recv)) print("finished receiving or timeout") self.receiver_modem.cancel_read() payload_has_errors = False for sender_cmd in self.received_commands.values(): for cmd in sender_cmd: if type(cmd.actions[0].op) != ReturnFileData and cmd.actions[0].operand.data != payload: payload_has_errors = True print ("receiver: received unexpected command: {}".format(cmd)) if payload_has_errors == False and total_recv == self.config.msg_count: print("receiver: OK: received {} messages with correct payload:".format(total_recv)) for sender, cmds in self.received_commands.items(): print("\t{}: {}".format(sender, len(cmds))) else: print("receiver: NOK: received messages {}:".format(total_recv)) for sender, cmds in self.received_commands.items(): print("\t{}: {}".format(sender, len(cmds))) def receiver_cmd_callback(self, cmd): if cmd.interface_status != None: uid = cmd.interface_status.operand.interface_status.addressee.id self.received_commands[uid].append(cmd) else: print("Unexpected cmd received, reboot?\n\t{}".format(cmd))
def setup_modem(self): # we use Modem here only for reading the modem information, not for parsing. # reading the bytes from serial (after initial connect) is not done using Modem but overridden here modem = Modem(self.config.device, self.config.rate, None, show_logging=self.config.verbose) self.serial = modem.dev self.modem_uid = modem.uid
class Gateway: def __init__(self): argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyACM0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-b", "--broker", help="mqtt broker hostname", default="localhost") argparser.add_argument("-bp", "--broker-port", help="mqtt broker port", default="1883") argparser.add_argument("-p", "--plugin-path", help="path where plugins are stored", default="") argparser.add_argument( "-l", "--logfile", help="specify path if you want to log to file instead of to stdout", default="") self.bridge_count = 0 self.next_report = 0 self.mq = None self.mqtt_topic_incoming_alp = "" self.connected_to_mqtt = False self.config = argparser.parse_args() self.log = logging.getLogger() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') if self.config.logfile == "": handler = logging.StreamHandler() else: handler = logging.FileHandler(self.config.logfile) handler.setFormatter(formatter) self.log.addHandler(handler) self.log.setLevel(logging.INFO) if self.config.verbose: self.log.setLevel(logging.DEBUG) if self.config.plugin_path != "": self.load_plugins(self.config.plugin_path) self.modem = Modem(self.config.device, self.config.rate, self.on_command_received) self.connect_to_mqtt() # update attribute containing git rev so we can track revision at TB platform git_sha = subprocess.check_output(["git", "describe", "--always"]).strip() ip = self.get_ip() # TODO ideally this should be associated with the GW device itself, not with the modem in the GW # not clear how to do this using TB-GW self.publish_to_topic( "/gateway-info", jsonpickle.json.dumps({ "git-rev": git_sha, "ip": ip, "device": self.modem.uid })) # make sure TB knows the modem device is connected. TB considers the device connected as well when there is regular # telemetry data. This is fine for remote nodes which will be auto connected an disconnected in this way. But for the # node in the gateway we do it explicitly to make sure it always is 'online' even when there is no telemetry to be transmitted, # so that we can reach it over RPC self.publish_to_topic( "sensors/connect", jsonpickle.json.dumps({"serialNumber": self.modem.uid})) self.log.info("Running on {} with git rev {} using modem {}".format( ip, git_sha, self.modem.uid)) # read all system files on the local node to store as attributes on TB self.log.info("Reading all system files ...") for file in SystemFiles().files.values(): self.modem.execute_command_async( Command.create_with_read_file_action_system_file(file)) def load_plugins(self, plugin_path): self.log.info("Searching for plugins in path %s" % plugin_path) manager = PluginManagerSingleton.get() manager.setPluginPlaces([plugin_path]) manager.collectPlugins() for plugin in manager.getAllPlugins(): self.log.info("Loading plugin '%s'" % plugin.name) def on_command_received(self, cmd): try: self.log.info("Command received: {}".format(cmd)) if not self.connected_to_mqtt: self.log.warning("Not connected to MQTT, skipping") return # publish raw ALP command to incoming ALP topic, we will not parse the file contents here (since we don't know how) # so pass it as an opaque BLOB for parsing in backend self.publish_to_topic( self.mqtt_topic_incoming_alp, jsonpickle.json.dumps({'alp_command': jsonpickle.encode(cmd)})) node_id = self.modem.uid # overwritten below with remote node ID when received over D7 interface # parse link budget (when this is received over D7 interface) and publish separately so we can visualize this in TB if cmd.interface_status != None and cmd.interface_status.operand.interface_id == 0xd7: interface_status = cmd.interface_status.operand.interface_status node_id = '{:x}'.format(interface_status.addressee.id) self.publish_to_topic( "/parsed/telemetry", jsonpickle.json.dumps({ "gateway": self.modem.uid, "device": node_id, "name": "link_budget", "value": interface_status.link_budget, "timestamp": str(datetime.now()) })) self.publish_to_topic( "/parsed/telemetry", jsonpickle.json.dumps({ "gateway": self.modem.uid, "device": node_id, "name": "rx_level", "value": -interface_status.rx_level, "timestamp": str(datetime.now()) })) self.publish_to_topic( "/parsed/attribute", jsonpickle.json.dumps({ "device": node_id, "name": "last_network_connection", "value": "D7-" + interface_status.get_short_channel_string(), })) # store returned file data as attribute on the device for action in cmd.actions: if type(action.operation) is ReturnFileData: data = "" if action.operation.file_data_parsed is not None: # for known system files we transmit the parsed data data = jsonpickle.encode( action.operation.file_data_parsed) else: # try if plugin can parse this file parsed_by_plugin = False for plugin in PluginManagerSingleton.get( ).getAllPlugins(): for name, value, datapoint_type in plugin.plugin_object.parse_file_data( action.operand.offset, action.operand.data): parsed_by_plugin = True self.publish_to_topic( "/parsed/" + datapoint_type.name, jsonpickle.json.dumps({ "device": node_id, "name": name, "value": value, })) if not parsed_by_plugin: # unknown file content, just transmit raw data data = jsonpickle.encode(action.operand) filename = "File {}".format( action.operand.offset.id) if action.operation.systemfile_type != None: filename = "File {} ({})".format( SystemFileIds( action.operand.offset.id).name, action.operand.offset.id) self.publish_to_topic( "/filecontent", jsonpickle.json.dumps({ "device": node_id, "file-id": filename, "file-data": data })) except: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) trace = "".join(lines) self.log.error( "Exception while processing command: \n{}".format(trace)) def connect_to_mqtt(self): self.log.info("Connecting to MQTT broker on {}".format( self.config.broker)) self.connected_to_mqtt = False self.mq = mqtt.Client("", True, None, mqtt.MQTTv31) self.mqtt_topic_incoming_alp = "/DASH7/incoming/{}".format( self.modem.uid) self.mq.on_connect = self.on_mqtt_connect self.mq.on_message = self.on_mqtt_message self.mq.connect(self.config.broker, self.config.broker_port, 60) self.mq.loop_start() while not self.connected_to_mqtt: pass # busy wait until connected self.log.info("Connected to MQTT broker on {}".format( self.config.broker)) def on_mqtt_connect(self, client, config, flags, rc): self.mq.subscribe("sensor/#") self.connected_to_mqtt = True def on_mqtt_message(self, client, config, msg): try: topic_parts = msg.topic.split('/') method = topic_parts[3] uid = topic_parts[1] request_id = topic_parts[4] self.log.info( "Received RPC command of type {} for {} (request id {})". format(method, uid, request_id)) if uid != self.modem.uid: self.log.info( "RPC command not for this modem ({}), skipping".format( self.modem.uid)) return if method == "execute-alp-async": try: cmd = jsonpickle.decode(jsonpickle.json.loads(msg.payload)) self.log.info("Received command through RPC: %s" % cmd) self.modem.execute_command_async(cmd) self.log.info("Executed ALP command through RPC") # TODO when the command is writing local files we could read them again automatically afterwards, to make sure the digital twin is updated except Exception as e: self.log.exception("Could not deserialize: %s" % e) elif method == "alert": # TODO needs refactoring so different methods can be supported in a plugin, for now this is very specific case as an example self.log.info("Alert (payload={})".format(msg.payload)) if msg.payload != "true" and msg.payload != "false": self.log.info("invalid payload, skipping") return file_data = 0 if msg.payload == "true": file_data = 1 self.log.info("writing alert file") self.modem.execute_command_async( Command.create_with_write_file_action( file_id=0x60, offset=4, data=[file_data], interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ALL), addressee=Addressee(access_class=0x11, id_type=IdType.NOID)))) else: self.log.info("RPC method not supported, skipping") return except: exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) trace = "".join(lines) msg_info = "no msg info (missing __dict__ attribute)" # TODO because of out of date paho?? if hasattr(msg, '__dict__'): msg_info = str(msg.__dict__) self.log.error( "Exception while processing MQTT message: {} callstack:\n{}". format(msg_info, trace)) def publish_to_topic(self, topic, msg): if not self.connected_to_mqtt: self.log.warning("not connected to MQTT, skipping") return self.mq.publish(topic, msg) def __del__(self): try: self.mq.loop_stop() self.mq.disconnect() except: pass def run(self): self.log.info("Started") keep_running = True while keep_running: try: if platform.system() == "Windows": time.sleep(1) else: signal.pause() except serial.SerialException: time.sleep(1) self.log.warning("resetting serial connection...") self.setup_modem() return except KeyboardInterrupt: self.log.info( "received KeyboardInterrupt... stopping processing") keep_running = False self.report_stats() def keep_stats(self): self.bridge_count += 1 def report_stats(self): if self.next_report < time.time(): if self.bridge_count > 0: self.log.info("bridged %s messages" % str(self.bridge_count)) self.bridge_count = 0 self.next_report = time.time( ) + 15 # report at most every 15 seconds def get_ip(self): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # doesn't even have to be reachable s.connect(('10.255.255.255', 1)) IP = s.getsockname()[0] except: IP = '127.0.0.1' finally: s.close() return IP
from d7a.sp.configuration import Configuration from d7a.sp.qos import QoS, ResponseMode from d7a.system_files.uid import UidFile from modem.modem import Modem # This example can be used with a node running the gateway app included in OSS-7. # The gateway is continuously listening for foreground frames. # Messages pushed by other nodes (running for example the sensor_push app) will be received by the gateway node, # transmitted over serial and the received_command_callback() function below will be called. from util.logger import configure_default_logger def received_command_callback(cmd): logging.info(cmd) argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyUSB0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") config = argparser.parse_args() configure_default_logger(config.verbose) modem = Modem(config.device, config.rate, unsolicited_response_received_callback=received_command_callback) modem.connect() while True: pass
def __init__(self): argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyACM0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-b", "--broker", help="mqtt broker hostname", default="localhost") argparser.add_argument("-bp", "--broker-port", help="mqtt broker port", default="1883") argparser.add_argument("-p", "--plugin-path", help="path where plugins are stored", default="") argparser.add_argument( "-l", "--logfile", help="specify path if you want to log to file instead of to stdout", default="") self.bridge_count = 0 self.next_report = 0 self.mq = None self.mqtt_topic_incoming_alp = "" self.connected_to_mqtt = False self.config = argparser.parse_args() self.log = logging.getLogger() formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') if self.config.logfile == "": handler = logging.StreamHandler() else: handler = logging.FileHandler(self.config.logfile) handler.setFormatter(formatter) self.log.addHandler(handler) self.log.setLevel(logging.INFO) if self.config.verbose: self.log.setLevel(logging.DEBUG) if self.config.plugin_path != "": self.load_plugins(self.config.plugin_path) self.modem = Modem(self.config.device, self.config.rate, self.on_command_received) self.connect_to_mqtt() # update attribute containing git rev so we can track revision at TB platform git_sha = subprocess.check_output(["git", "describe", "--always"]).strip() ip = self.get_ip() # TODO ideally this should be associated with the GW device itself, not with the modem in the GW # not clear how to do this using TB-GW self.publish_to_topic( "/gateway-info", jsonpickle.json.dumps({ "git-rev": git_sha, "ip": ip, "device": self.modem.uid })) # make sure TB knows the modem device is connected. TB considers the device connected as well when there is regular # telemetry data. This is fine for remote nodes which will be auto connected an disconnected in this way. But for the # node in the gateway we do it explicitly to make sure it always is 'online' even when there is no telemetry to be transmitted, # so that we can reach it over RPC self.publish_to_topic( "sensors/connect", jsonpickle.json.dumps({"serialNumber": self.modem.uid})) self.log.info("Running on {} with git rev {} using modem {}".format( ip, git_sha, self.modem.uid)) # read all system files on the local node to store as attributes on TB self.log.info("Reading all system files ...") for file in SystemFiles().files.values(): self.modem.execute_command_async( Command.create_with_read_file_action_system_file(file))
def received_command_callback(cmd): logging.info(cmd) if cmd.execution_completed: os._exit(0) argparser = argparse.ArgumentParser() argparser.add_argument("-d", "--device", help="serial device /dev file modem", default="/dev/ttyUSB0") argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") config = argparser.parse_args() configure_default_logger(config.verbose) modem = Modem(config.device, config.rate, unsolicited_response_received_callback=received_command_callback) modem.connect() logging.info("Executing query...") modem.execute_command_async( alp_command=Command.create_with_read_file_action( file_id=0x40, length=8, interface_type=InterfaceType.D7ASP, interface_configuration=Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ALL), addressee=Addressee( access_class=0x11, id_type=IdType.NOID ) ) )
argparser = argparse.ArgumentParser() argparser.add_argument("-s", "--serial", help="serial device /dev file", default="/dev/tty.usbserial-FTGCT0HY") argparser.add_argument("-b", "--baudrate", help="baudrate for serial device", type=int, default=115200) config = argparser.parse_args() modem = Modem(config.serial, config.baudrate, show_logging=False) cmd = Command.create_with_return_file_data_action( file_id=0x40, data=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], interface_type=InterfaceType.D7ASP, interface_configuration=Configuration(qos=QoS(resp_mod=QoS.RESP_MODE_NO), addressee=Addressee( access_class=0, id_type=IdType.BCAST))) last_message = 0 sent = 0 received = 0
class ThroughtPutTest: def __init__(self): self.argparser = argparse.ArgumentParser( fromfile_prefix_chars="@", description="Test throughput over 2 serial D7 modems") self.argparser.add_argument("-n", "--msg-count", help="number of messages to transmit", type=int, default=10) self.argparser.add_argument( "-p", "--payload-size", help="number of bytes of (appl level) payload to transmit", type=int, default=50) self.argparser.add_argument( "-sw", "--serial-transmitter", help="serial device /dev file transmitter node", default=None) self.argparser.add_argument( "-sr", "--serial-receiver", help="serial device /dev file receiver node", default=None) self.argparser.add_argument("-r", "--rate", help="baudrate for serial device", type=int, default=115200) self.argparser.add_argument( "-uid", "--unicast-uid", help="UID to use for unicast transmission, " "when not using receiver " "(in hexstring, for example 0xb57000009151d)", default=None) self.argparser.add_argument( "-to", "--receiver-timeout", help="timeout for the receiver (in seconds)", type=int, default=10) self.argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") self.config = self.argparser.parse_args() if self.config.serial_transmitter == None and self.config.serial_receiver == None: self.argparser.error( "At least a transmitter or receiver is required.") if self.config.serial_receiver == None and self.config.unicast_uid == None: self.argparser.error( "When running without receiver a --unicast-uid parameter is required." ) if self.config.serial_transmitter == None: self.transmitter_modem = None print("Running without transmitter") else: self.transmitter_modem = Modem(self.config.serial_transmitter, self.config.rate, None, show_logging=self.config.verbose) access_profile = AccessProfile( channel_header=ChannelHeader( channel_band=ChannelBand.BAND_868, channel_coding=ChannelCoding.PN9, channel_class=ChannelClass.NORMAL_RATE), sub_profiles=[ SubProfile(subband_bitmap=0x01, scan_automation_period=CT(exp=0, mant=0)), SubProfile(), SubProfile(), SubProfile() ], sub_bands=[ SubBand( channel_index_start=0, channel_index_end=0, eirp=10, cca=86 # TODO ) ]) print("Write Access Profile") write_ap_cmd = Command.create_with_write_file_action_system_file( file=AccessProfileFile(access_profile=access_profile, access_specifier=0)) self.transmitter_modem.send_command(write_ap_cmd) if self.config.serial_receiver == None: self.receiver_modem = None print("Running without receiver") else: self.receiver_modem = Modem(self.config.serial_receiver, self.config.rate, self.receiver_cmd_callback, show_logging=self.config.verbose) self.receiver_modem.send_command( Command.create_with_write_file_action_system_file( DllConfigFile(active_access_class=0x01))) print("Receiver scanning on Access Class = 0x01") def start(self): self.received_commands = defaultdict(list) payload = range(self.config.payload_size) if self.receiver_modem != None: addressee_id = int(self.receiver_modem.uid, 16) else: addressee_id = int(self.config.unicast_uid, 16) if self.transmitter_modem != None: print( "\n==> broadcast, with QoS, transmitter active access class = 0x01 ====" ) self.transmitter_modem.send_command( Command.create_with_write_file_action_system_file( DllConfigFile(active_access_class=0x01))) interface_configuration = Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ANY), addressee=Addressee( access_class=0x01, id_type=IdType.NBID, id=CT(exp=0, mant=1) # we expect one responder )) self.start_transmitting( interface_configuration=interface_configuration, payload=payload) self.wait_for_receiver(payload) print( "\n==> broadcast, no QoS, transmitter active access class = 0x01 ====" ) self.transmitter_modem.send_command( Command.create_with_write_file_action_system_file( DllConfigFile(active_access_class=0x01))) interface_configuration = Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_NO), addressee=Addressee(access_class=0x01, id_type=IdType.NOID)) self.start_transmitting( interface_configuration=interface_configuration, payload=payload) self.wait_for_receiver(payload) print( "\n==> unicast, with QoS, transmitter active access class = 0x01" ) interface_configuration = Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_ANY), addressee=Addressee(access_class=0x01, id_type=IdType.UID, id=addressee_id)) self.start_transmitting( interface_configuration=interface_configuration, payload=payload) self.wait_for_receiver(payload) print( "\n==> unicast, no QoS, transmitter active access class = 0x01" ) interface_configuration = Configuration( qos=QoS(resp_mod=ResponseMode.RESP_MODE_NO), addressee=Addressee(access_class=0x01, id_type=IdType.UID, id=addressee_id)) self.start_transmitting( interface_configuration=interface_configuration, payload=payload) self.wait_for_receiver(payload) else: # receive only self.receiver_modem.start_reading() self.wait_for_receiver(payload) def start_transmitting(self, interface_configuration, payload): print( "Running throughput test with payload size {} and interface_configuration {}\n\nrunning ...\n" .format(len(payload), interface_configuration)) if self.receiver_modem != None: self.received_commands = defaultdict(list) self.receiver_modem.start_reading() command = Command.create_with_return_file_data_action( file_id=0x40, data=payload, interface_type=InterfaceType.D7ASP, interface_configuration=interface_configuration) start = time.time() for i in range(self.config.msg_count): sys.stdout.write("{}/{}\r".format(i + 1, self.config.msg_count)) sys.stdout.flush() self.transmitter_modem.d7asp_fifo_flush(command) end = time.time() print("transmitter: sending {} messages completed in: {} s".format( self.config.msg_count, end - start)) print( "transmitter: throughput = {} bps with a payload size of {} bytes". format((self.config.msg_count * self.config.payload_size * 8) / (end - start), self.config.payload_size)) def wait_for_receiver(self, payload): if self.receiver_modem == None: print( "Running without receiver so we are not waiting for messages to be received ..." ) else: start = time.time() total_recv = 0 while total_recv < self.config.msg_count and time.time( ) - start < self.config.receiver_timeout: total_recv = sum( len(v) for v in self.received_commands.values()) time.sleep(2) print( "waiting for receiver to finish ... (current nr of recv msgs: {})" .format(total_recv)) print("finished receiving or timeout") self.receiver_modem.cancel_read() payload_has_errors = False for sender_cmd in self.received_commands.values(): for cmd in sender_cmd: if type(cmd.actions[0].op ) != ReturnFileData and cmd.actions[ 0].operand.data != payload: payload_has_errors = True print( "receiver: received unexpected command: {}".format( cmd)) if payload_has_errors == False and total_recv == self.config.msg_count: print( "receiver: OK: received {} messages with correct payload:". format(total_recv)) for sender, cmds in self.received_commands.items(): print("\t{}: {}".format(sender, len(cmds))) else: print( "receiver: NOK: received messages {}:".format(total_recv)) for sender, cmds in self.received_commands.items(): print("\t{}: {}".format(sender, len(cmds))) def receiver_cmd_callback(self, cmd): print("recv cmd: ".format(cmd)) if cmd.interface_status != None: uid = cmd.interface_status.operand.interface_status.addressee.id self.received_commands[uid].append(cmd) else: print("Unexpected cmd received, reboot?\n\t{}".format(cmd))
argparser.add_argument("-v", "--verbose", help="verbose", default=False, action="store_true") argparser.add_argument("-otaa", "--over-the-air-activation", help="Enable over the air activation", default=False, action="store_true") config = argparser.parse_args() configure_default_logger(config.verbose) modem = Modem( config.device, config.rate, ) modem.connect() logging.info("Executing query...") if (config.over_the_air_activation): result = modem.execute_command( alp_command=Command.create_with_read_file_action( file_id=0x40, length=8, interface_type=InterfaceType.LORAWAN_OTAA, interface_configuration=LoRaWANInterfaceConfigurationOTAA( request_ack=False, app_port=0x01, device_eui=[0xBE, 0X7A, 0X00, 0X00, 0X00, 0X00, 0X1B, 0X81], app_eui=[0xBE, 0X7A, 0X00, 0X00, 0X00, 0X00, 0X0D, 0X9F], app_key=[