diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 00000000..90d81cbb --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,37 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: + push: + branches: [ python3 ] + pull_request: + branches: [ python3 ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.6 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f tests-requirements.txt ]; then pip install -r tests-requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 openvisualizer --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 openvisualizer --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest tests/ov diff --git a/.gitignore b/.gitignore index 7f95331b..015b07aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,8 @@ # cache files *.pyc -.sconsign.dblite # folders -venv/ +venv openvisualizer.egg-info # IDE files diff --git a/openvisualizer/__init__.py b/openvisualizer/__init__.py index d5b3e95f..04a3abf7 100644 --- a/openvisualizer/__init__.py +++ b/openvisualizer/__init__.py @@ -1,9 +1,8 @@ -VERSION = '2.0.0' +VERSION = '2.1.0' PACKAGE_NAME = 'openvisualizer' APPNAME = PACKAGE_NAME - # cannot use os.path.join according to pkg_resources DEFAULT_LOGGING_CONF = '/'.join(("config", "logging.conf")) WINDOWS_COLORS = '/'.join(('config', 'colors_win.conf')) diff --git a/openvisualizer/__main__.py b/openvisualizer/__main__.py index 9c317969..4ce6d2b8 100644 --- a/openvisualizer/__main__.py +++ b/openvisualizer/__main__.py @@ -1,4 +1,249 @@ -from openvisualizer.main import main +import logging +import logging.config +import os +import signal +import sys +from collections import namedtuple +from configparser import SafeConfigParser +from typing import Optional +from xmlrpc.server import SimpleXMLRPCServer + +import appdirs +import click +import coloredlogs +import pkg_resources as pkg_rs + +from openvisualizer import APPNAME, PACKAGE_NAME, DEFAULT_LOGGING_CONF, WINDOWS_COLORS, UNIX_COLORS, VERSION +from openvisualizer.server import OpenVisualizer + +server_object: Optional[OpenVisualizer] = None + + +def sigint_handler(sig, frame): + if server_object is not None: + server_object.shutdown() + + +signal.signal(signal.SIGINT, sigint_handler) + +log = logging.getLogger('Main') + +ServerConfig = namedtuple('ServerConfig', + [ + 'host', + 'port', + 'wireshark_debug', + 'tun', + 'lconf', + 'page_zero', + 'mqtt_broker', + 'root' + ]) + +pass_config = click.make_pass_decorator(ServerConfig, ensure=True) + + +class ColoredFormatter(coloredlogs.ColoredFormatter): + """ Class that matches coloredlogs.ColoredFormatter arguments with logging.Formatter """ + + def __init__(self, fmt=None, datefmt=None, style=None): + self.parser = SafeConfigParser() + + if sys.platform.startswith('win32'): + log_colors_conf = pkg_rs.resource_filename(PACKAGE_NAME, WINDOWS_COLORS) + else: + log_colors_conf = pkg_rs.resource_filename(PACKAGE_NAME, UNIX_COLORS) + + self.parser.read(log_colors_conf) + + ls = self.parse_section('levels', 'keys') + fs = self.parse_section('fields', 'keys') + + coloredlogs.ColoredFormatter.__init__(self, fmt=fmt, datefmt=datefmt, level_styles=ls, field_styles=fs) + + def parse_section(self, section, option): + dictionary = {} + + if not self.parser.has_section(section) or not self.parser.has_option(section, option): + log.warning('Unknown section {} or option {}'.format(section, option)) + return dictionary + + subsections = map(str.strip, self.parser.get(section, option).split(',')) + + for subsection in subsections: + if not self.parser.has_section(str(subsection)): + log.warning('Unknown section name: {}'.format(subsection)) + continue + + dictionary[subsection] = {} + options = self.parser.options(subsection) + + for opt in options: + res = self.parse_options(subsection, opt.strip().lower()) + if res is not None: + dictionary[subsection][opt] = res + + return dictionary + + def parse_options(self, section, option): + res = None + if option == 'bold' or option == 'faint': + try: + return self.parser.getboolean(section, option) + except ValueError: + log.error('Illegal value: {} for option: {}'.format(self.parser.get(section, option), option)) + elif option == 'color': + try: + res = self.parser.getint(section, option) + except ValueError: + res = self.parser.get(section, option) + else: + log.warning('Unknown option name: {}'.format(option)) + + return res + + +@click.group(invoke_without_command=True) +@click.option('--host', default='localhost', help='Specify address of the OpenVisualizer server', show_default=True) +@click.option('--port', default=9000, help='Specify to port to use', show_default=True) +@click.option('--version', '-v', is_flag=True, help='Print version information OpenVisualizer') +@click.option('--tun', is_flag=True, help="Enable the TUN interface") +@click.option('--wireshark-debug', '-w', is_flag=True, help="Enable wireshark debugging") +@click.option('--lconf', default=pkg_rs.resource_filename(PACKAGE_NAME, DEFAULT_LOGGING_CONF), + help="Provide a logging configuration") +@click.option('--page-zero', is_flag=True, help="Uses page number 0 in page dispatch (only works with single hop)") +@click.option('--mqtt-broker', default=None, type=str, help='Specify address MQTT server for network stats.') +@click.option('--root', '-r', type=str, help='Mark a mote as DAGroot, e.g. /dev/ttyUSB* or COM*') +@click.pass_context +def cli(ctx, host, port, version, wireshark_debug, tun, lconf, page_zero, mqtt_broker, root): + banner = [""] + banner += [" ___ _ _ _ ___ _ _ "] + banner += ["| . | ___ ___ ._ _ | | | |/ __>| \\ |"] + banner += ["| | || . \\/ ._>| ' || | | |\\__ \\| |"] + banner += ["`___'| _/\\___.|_|_||__/_/ <___/|_\\_|"] + banner += [" |_| openwsn.org"] + banner += [""] + + click.secho('\n'.join(banner)) + + if version: + click.echo(f"OpenVisualizer (server) v{VERSION}") + sys.exit(0) + + if ctx.invoked_subcommand is None: + click.echo('Use one of the following subcommands: ', nl=False) + click.secho('hardware, simulation, iotlab, or testbed', bold=True) + sys.exit(0) + + if tun and os.name == 'posix' and not os.getuid() == 0: + res = click.prompt("TUN requires admin privileges, (C)ontinue without privileges and with TUN or (A)bort", + default="A") + if res != "C" and res != 'c': + sys.exit(0) + + # create directories to store logs and application data + try: + os.makedirs(appdirs.user_log_dir(APPNAME)) + except OSError as err: + if err.errno != 17: + log.critical(err) + return + + try: + os.makedirs(appdirs.user_data_dir(APPNAME)) + except OSError as err: + if err.errno != 17: + log.critical(err) + return + + ctx.obj = ServerConfig(host, port, wireshark_debug, tun, lconf, page_zero, mqtt_broker, root) + load_logging_conf(ctx.obj) + + +@click.command() +@click.option('--baudrate', '-b', default=['115200'], help='A list of baudrates to test', show_default=True) +@click.option('--port-mask', '-p', help='Define a port mask for probing hardware, e.g., /dev/ttyUSB*', type=str, + multiple=True) +@pass_config +def hardware(config, baudrate, port_mask): + """ OpenVisualizer in hardware mode.""" + + if isinstance(baudrate, str): + baudrate = [baudrate] + + start_server(OpenVisualizer(config, OpenVisualizer.Mode.HARDWARE, baudrate=baudrate, port_mask=port_mask), config) + + +@click.command() +@pass_config +@click.argument('num_of_motes', nargs=1, type=int) +@click.option('--topology', '-t', default='fully-meshed', help='Specify the simulation topology', show_default=True) +def simulation(config, num_of_motes, topology): + """ OpenVisualizer in simulation mode.""" + + start_server( + OpenVisualizer(config, OpenVisualizer.Mode.SIMULATION, num_of_motes=num_of_motes, topology=topology), config) + + +@click.command() +@pass_config +def testbed(config): + """ Attaches OpenVisualizer to the opentestbed. """ + + start_server(OpenVisualizer(config, OpenVisualizer.Mode.TESTBED), config) + + +@click.command() +@click.argument('mote_ids', nargs=-1, type=str) +@click.option('--user', '-u', help='IoTLAB username') +@click.option('--site', '-s', help='IoTLAB frontend site') +@click.option('--key-file', '-k', help='SSH private key file, e.g., ~/.ssh/id_rsa') +@click.option('--debug', '-d', help='Enable debugging output', is_flag=True) +@pass_config +def iotlab(config, mote_ids, debug, user, site, key_file): + """ Attaches OpenVisualizer to IoT-LAB.""" + + start_server( + OpenVisualizer(config, + OpenVisualizer.Mode.IOTLAB, + iotlab_user=user, + iotlab_site=site, + iotlab_key_file=key_file, + iotlab_motes=mote_ids, + debug=debug), + config) + + +def start_server(server_instance, config): + global server_object + server_object = server_instance + with SimpleXMLRPCServer((config.host, config.port), allow_none=True, logRequests=False, + bind_and_activate=False) as server: + + server.allow_reuse_address = True + + server.server_bind() + server.server_activate() + + server.register_instance(server_instance, allow_dotted_names=False) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + + +def load_logging_conf(config): + try: + log_dir = appdirs.user_log_dir(APPNAME) + logging.config.fileConfig(fname=config.lconf, defaults={'log_dir': log_dir}) + except IOError as err: + log.error(f"Failed to load config: {err}") + + +cli.add_command(hardware) +cli.add_command(simulation) +cli.add_command(testbed) +cli.add_command(iotlab) if __name__ == "__main__": - main() + cli() diff --git a/openvisualizer/bspemulator/bspboard.py b/openvisualizer/bspemulator/bspboard.py deleted file mode 100644 index 757b262b..00000000 --- a/openvisualizer/bspemulator/bspboard.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -from openvisualizer.bspemulator.bspmodule import BspModule - - -class BspBoard(BspModule): - """ Emulates the 'board' BSP module """ - - _name = 'BspBoard' - - def __init__(self, motehandler): - # initialize the parent - super(BspBoard, self).__init__(motehandler) - - # local variables - self.timeline = self.engine.timeline - - # ======================== public ========================================== - - # === commands - - def cmd_init(self): - """ Emulates: void board_init() """ - - # log the activity - self.log.debug('cmd_init') - - # remember that module has been initialized - self.is_initialized = True - - def cmd_sleep(self): - """ Emulates: void board_init() """ - - try: - # log the activity - self.log.debug('cmd_sleep') - - self.motehandler.cpu_done.release() - - # block the mote until CPU is released by ISR - self.motehandler.cpu_running.acquire() - - except Exception as err: - self.log.critical(err) - - # ======================== private ========================================= diff --git a/openvisualizer/bspemulator/bspdebugpins.py b/openvisualizer/bspemulator/bspdebugpins.py deleted file mode 100644 index 4ffcb4e7..00000000 --- a/openvisualizer/bspemulator/bspdebugpins.py +++ /dev/null @@ -1,449 +0,0 @@ -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -import logging - -from openvisualizer.bspemulator import vcdlogger -from openvisualizer.bspemulator.bspmodule import BspModule - - -class BspDebugPins(BspModule): - """ Emulates the 'debugpins' BSP module. """ - - _name = 'BspDebugPins' - - def __init__(self, motehandler, vcdlog): - # initialize the parent - super(BspDebugPins, self).__init__(motehandler) - - # local variables - self.timeline = self.engine.timeline - self.framePinHigh = False - self.slotPinHigh = False - self.fsmPinHigh = False - self.taskPinHigh = False - self.isrPinHigh = False - self.radioPinHigh = False - self.kaPinHigh = False - self.syncPacketPinHigh = False - self.syncAckPinHigh = False - self.debugPinHigh = False - - self.vcdlog = vcdlog - - if self.vcdlog: - self.vcdLogger = vcdlogger.VcdLogger() - - # ======================== public ========================================== - - # === commands - - def cmd_init(self): - """Emulates: void debugpins_init() """ - - # log the activity - self.log.debug('cmd_init') - - # remember that module has been initialized - self.is_initialized = True - - # frame - - def cmd_frame_toggle(self): - """ Emulates: void debugpins_frame_toggle() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_frame_toggle') - - # change the internal state - self.framePinHigh = not self.framePinHigh - - # log VCD - self._log_vcd('frame') - - def cmd_frame_clr(self): - """ Emulates: void debugpins_frame_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_frame_clr') - - # change the internal state - self.framePinHigh = False - - # log VCD - self._log_vcd('frame') - - def cmd_frame_set(self): - """ Emulates: void debugpins_frame_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_frame_set') - - # change the internal state - self.framePinHigh = True - - # log VCD - self._log_vcd('frame') - - # slot - - def cmd_slot_toggle(self): - """ Emulates: void debugpins_slot_toggle() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_slot_toggle') - - # change the internal state - self.slotPinHigh = not self.slotPinHigh - - # log VCD - self._log_vcd('slot') - - def cmd_slot_clr(self): - """ Emulates: void debugpins_slot_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_slot_clr') - - # change the internal state - self.slotPinHigh = False - - # log VCD - self._log_vcd('slot') - - def cmd_slot_set(self): - """ Emulates: void debugpins_slot_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_slot_set') - - # change the internal state - self.slotPinHigh = True - - # log VCD - self._log_vcd('slot') - - # fsm - - def cmd_fsm_toggle(self): - """ Emulates: void debugpins_fsm_toggle() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_fsm_toggle') - - # change the internal state - self.fsmPinHigh = not self.fsmPinHigh - - # log VCD - self._log_vcd('fsm') - - def cmd_fsm_clr(self): - """ Emulates: void debugpins_fsm_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_fsm_clr') - - # change the internal state - self.fsmPinHigh = False - - # log VCD - self._log_vcd('fsm') - - def cmd_fsm_set(self): - """ Emulates: void debugpins_fsm_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_fsm_set') - - # change the internal state - self.fsmPinHigh = True - - # log VCD - self._log_vcd('fsm') - - # task - - def cmd_task_toggle(self): - """ Emulates: void debugpins_task_toggle() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_task_toggle') - - # change the internal state - self.taskPinHigh = not self.taskPinHigh - - # log VCD - self._log_vcd('task') - - def cmd_task_clr(self): - """ Emulates: void debugpins_task_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_task_clr') - - # change the internal state - self.taskPinHigh = False - - # log VCD - self._log_vcd('task') - - def cmd_task_set(self): - """ Emulates: void debugpins_task_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_task_set') - - # change the internal state - self.taskPinHigh = True - - # log VCD - self._log_vcd('task') - - # isr - - def cmd_isr_toggle(self): - """ Emulates: void debugpins_isr_toggle() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_isr_toggle') - - # change the internal state - self.isrPinHigh = not self.isrPinHigh - - # log VCD - self._log_vcd('isr') - - def cmd_isr_clr(self): - """ Emulates: void debugpins_isr_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_isr_clr') - - # change the internal state - self.isrPinHigh = False - - # log VCD - self._log_vcd('isr') - - def cmd_isr_set(self): - """ Emulates: void debugpins_isr_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_isr_set') - - # change the internal state - self.isrPinHigh = True - - # log VCD - self._log_vcd('isr') - - # radio - - def cmd_radio_toggle(self): - """ Emulates: void debugpins_radio_toggle() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_radio_toggle') - - # change the internal state - self.radioPinHigh = not self.radioPinHigh - - # log VCD - self._log_vcd('radio') - - def cmd_radio_clr(self): - """ Emulates: void debugpins_radio_clr()""" - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_radio_clr') - - # change the internal state - self.radioPinHigh = False - - # log VCD - self._log_vcd('radio') - - def cmd_radio_set(self): - """ Emulates: void debugpins_radio_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_radio_set') - - # change the internal state - self.radioPinHigh = True - - # log VCD - self._log_vcd('radio') - - # ka - - def cmd_ka_clr(self): - """ Emulates: void debugpins_ka_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_ka_clr') - - # change the internal state - self.kaPinHigh = False - - # log VCD - self._log_vcd('ka') - - def cmd_ka_set(self): - """ Emulates: void debugpins_ka_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_ka_set') - - # change the internal state - self.kaPinHigh = True - - # log VCD - self._log_vcd('ka') - - # syncPacket - - def cmd_sync_packet_clr(self): - """ Emulates: void debugpins_syncPacket_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_sync_packet_clr') - - # change the internal state - self.syncPacketPinHigh = False - - # log VCD - self._log_vcd('syncPacket') - - def cmd_sync_packet_set(self): - """ Emulates: void debugpins_syncPacket_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_sync_packet_set') - - # change the internal state - self.syncPacketPinHigh = True - - # log VCD - self._log_vcd('syncPacket') - - # syncAck - - def cmd_sync_ack_clr(self): - """ Emulates: void debugpins_syncAck_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_sync_ack_clr') - - # change the internal state - self.syncAckPinHigh = False - - # log VCD - self._log_vcd('syncAck') - - def cmd_sync_ack_set(self): - """ Emulates: void debugpins_syncAck_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_sync_ack_set') - - # change the internal state - self.syncAckPinHigh = True - - # log VCD - self._log_vcd('syncAck') - - # debug - - def cmd_debug_clr(self): - """ Emulates: void debugpins_debug_clr() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_debug_clr') - - # change the internal state - self.debugPinHigh = False - - # log VCD - self._log_vcd('debug') - - def cmd_debug_set(self): - """ Emulates: void debugpins_debug_set() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_debug_set') - - # change the internal state - self.debugPinHigh = True - - # log VCD - self._log_vcd('debug') - - # === getters - - def get_frame_pin_high(self): - return self.framePinHigh - - def get_slot_pin_high(self): - return self.slotPinHigh - - def get_fsm_pin_high(self): - return self.fsmPinHigh - - def get_isr_pin_high(self): - return self.isrPinHigh - - def get_radio_pin_high(self): - return self.radioPinHigh - - def get_ka_pin_high(self): - return self.kaPinHigh - - def get_sync_packet_pin_high(self): - return self.syncPacketPinHigh - - def get_sync_ack_pin_high(self): - return self.syncAckPinHigh - - def get_debug_pin_high(self): - return self.debugPinHigh - - # ======================== private ========================================= - - def _log_vcd(self, signal): - if self.vcdlog: - self.vcdLogger.log( - ts=self.timeline.get_current_time(), - mote=self.motehandler.get_id(), - signal=signal, - state=getattr(self, '{0}PinHigh'.format(signal)), - ) diff --git a/openvisualizer/bspemulator/bspuart.py b/openvisualizer/bspemulator/bspuart.py deleted file mode 100644 index 6f2c6187..00000000 --- a/openvisualizer/bspemulator/bspuart.py +++ /dev/null @@ -1,332 +0,0 @@ -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -import logging -import threading - -from openvisualizer.bspemulator.bspmodule import BspModule - - -class BspUart(BspModule): - """ Emulates the 'uart' BSP module """ - - _name = 'BspUart' - - INTR_TX = 'uart.tx' - INTR_RX = 'uart.rx' - BAUDRATE = 115200 - - XOFF = 0x13 - XON = 0x11 - XONXOFF_ESCAPE = 0x12 - XONXOFF_MASK = 0x10 - - def __init__(self, motehandler): - # initialize the parent - super(BspUart, self).__init__(motehandler) - - # local variables - self.timeline = self.engine.timeline - self.interrupts_enabled = False - self.tx_interrupt_flag = False - self.rx_interrupt_flag = False - self.uart_rx_buffer = [] - self.uart_rx_buffer_sem = threading.Semaphore() - self.uart_rx_buffer_sem.acquire() - self.uart_rx_buffer_lock = threading.Lock() - self.uart_tx_buffer = [] # the bytes to be sent over UART - self.uart_tx_next = None # the byte that was just signaled to mote - self.uart_tx_buffer_lock = threading.Lock() - self.wait_for_done_reading = threading.Lock() - self.wait_for_done_reading.acquire() - self.f_xon_xoff_escaping = False - self.xon_xoff_escaped_byte = 0 - - # ======================== public ========================================== - - # === interact with UART - - def read(self): - """ Read a byte from the mote. """ - - # wait for something to appear in the RX buffer - self.uart_rx_buffer_sem.acquire() - - # copy uart_rx_buffer - with self.uart_rx_buffer_lock: - assert len(self.uart_rx_buffer) > 0 - return_val = [chr(b) for b in self.uart_rx_buffer] - self.uart_rx_buffer = [] - - # return that element - return return_val - - def write(self, bytes_to_write): - """ Write a string of bytes to the mote. """ - - assert len(bytes_to_write) - - if len(self.uart_tx_buffer) != 0: - return 0 - - with self.uart_tx_buffer_lock: - self.uart_tx_buffer = [ord(b) for b in bytes_to_write] - - self.engine.pause() - self._schedule_next_tx() - self.engine.resume() - - return len(bytes_to_write) - - def done_reading(self): - self.wait_for_done_reading.release() - - # === commands - - def cmd_init(self): - """ Emulates: void uart_init() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_init') - - # remember that module has been intialized - self.is_initialized = True - - def cmd_enable_interrupts(self): - """ Emulates: void uart_enableInterrupts() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_enable_interrupts') - - # update variables - self.interrupts_enabled = True - - def cmd_disable_interrupts(self): - """ Emulates: void cmd_disable_interrupts() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_disableInterrupts') - - # update variables - self.interrupts_enabled = False - - def cmd_clear_rx_interrupts(self): - """ Emulates: void uart_clearRxInterrupts() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_clear_rx_interrupts') - - # update variables - self.rx_interrupt_flag = False - - def cmd_clear_tx_interrupts(self): - """ Emulates: void uart_clearTxInterrupts() """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_clear_tx_interrupts') - - # update variables - self.tx_interrupt_flag = False - - def cmd_write_byte(self, byte_to_write): - """ Emulates: void uart_writeByte(uint8_t byte_to_write) """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_write_byte byte_to_write=' + str(byte_to_write)) - - # set tx interrupt flag - self.tx_interrupt_flag = True - - # calculate the time at which the byte will have been sent - done_sending_time = self.timeline.get_current_time() + float(1.0 / float(self.BAUDRATE)) - - # schedule uart TX interrupt in 1/BAUDRATE seconds - self.timeline.schedule_event(done_sending_time, self.motehandler.get_id(), self.intr_tx, self.INTR_TX) - - if byte_to_write == self.XON or byte_to_write == self.XOFF or byte_to_write == self.XONXOFF_ESCAPE: - self.f_xon_xoff_escaping = True - self.xon_xoff_escaped_byte = byte_to_write - # add to receive buffer - with self.uart_rx_buffer_lock: - self.uart_rx_buffer += [self.XONXOFF_ESCAPE] - else: - # add to receive buffer - with self.uart_rx_buffer_lock: - self.uart_rx_buffer += [byte_to_write] - - # release the semaphore indicating there is something in RX buffer - self.uart_rx_buffer_sem.release() - - # wait for the moteProbe to be done reading - self.wait_for_done_reading.acquire() - - def cmd_set_cts(self, state): - """ Emulates: void uart_setCTS(bool state) """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_set_cts state=' + str(state)) - - # set tx interrupt flag - self.tx_interrupt_flag = True - - # calculate the time at which the byte will have been sent - done_sending_time = self.timeline.get_current_time() + float(1.0 / float(self.BAUDRATE)) - - # schedule uart TX interrupt in 1/BAUDRATE seconds - self.timeline.schedule_event(done_sending_time, self.motehandler.get_id(), self.intr_tx, self.INTR_TX) - - # add to receive buffer - with self.uart_rx_buffer_lock: - if state: - self.uart_rx_buffer += [self.XON] - else: - self.uart_rx_buffer += [self.XOFF] - - # release the semaphore indicating there is something in RX buffer - self.uart_rx_buffer_sem.release() - - # wait for the moteProbe to be done reading - self.wait_for_done_reading.acquire() - - def cmd_write_circular_buffer_fastsim(self, buf): - """ Emulates: void uart_writeCircularBuffer_FASTSIM(uint8_t* buffer, uint8_t len) """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_write_circular_buffer_fastsim buffer=' + str(buf)) - - self._write_buffer(buf) - - def uart_write_buffer_by_len_fastsim(self, buf): - """ Emulates: void uart_writeBufferByLen_FASTSIM(uint8_t* buffer, uint8_t len) """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('uart_write_buffer_by_len_fastsim buffer=' + str(buf)) - - self._write_buffer(buf) - - def _write_buffer(self, buf): - # set tx interrupt flag - self.tx_interrupt_flag = True - - # calculate the time at which the buffer will have been sent - done_sending_time = self.timeline.get_current_time() + float(float(len(buf)) / float(self.BAUDRATE)) - - # schedule uart TX interrupt in len(buffer)/BAUDRATE seconds - self.timeline.schedule_event(done_sending_time, self.motehandler.get_id(), self.intr_tx, self.INTR_TX) - - # add to receive buffer - with self.uart_rx_buffer_lock: - i = 0 - while i != len(buf): - if buf[i] == self.XON or buf[i] == self.XOFF or buf[i] == self.XONXOFF_ESCAPE: - new_item = (self.XONXOFF_ESCAPE, buf[i] ^ self.XONXOFF_MASK) - buf[i:i + 1] = new_item - i += 1 - self.uart_rx_buffer += buf - - # release the semaphore indicating there is something in RX buffer - self.uart_rx_buffer_sem.release() - - # wait for the moteProbe to be done reading - self.wait_for_done_reading.acquire() - - def cmd_read_byte(self): - """ Emulates: uint8_t uart_readByte()""" - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_read_byte') - - # retrieve the byte last sent - with self.uart_tx_buffer_lock: - return self.uart_tx_next - - # ======================== interrupts ====================================== - - def intr_tx(self): - """ Mote is done sending a byte over the UART. """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('intr_tx') - - if self.f_xon_xoff_escaping: - self.f_xon_xoff_escaping = False - - # set tx interrupt flag - self.tx_interrupt_flag = True - - # calculate the time at which the byte will have been sent - done_sending_time = self.timeline.get_current_time() + float(1.0 / float(self.BAUDRATE)) - - # schedule uart TX interrupt in 1/BAUDRATE seconds - self.timeline.schedule_event(done_sending_time, self.motehandler.get_id(), self.intr_tx, self.INTR_TX) - - # add to receive buffer - with self.uart_rx_buffer_lock: - self.uart_rx_buffer += [self.xon_xoff_escaped_byte ^ self.XONXOFF_MASK] - - # release the semaphore indicating there is something in RX buffer - self.uart_rx_buffer_sem.release() - - # wait for the moteProbe to be done reading - self.wait_for_done_reading.acquire() - - else: - # send interrupt to mote - self.motehandler.mote.uart_isr_tx() - - # do *not* kick the scheduler - return False - - def intr_rx(self): - """ Interrupt to indicate to mote it received a byte from the UART. """ - - # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('intr_rx') - - with self.uart_tx_buffer_lock: - - # make sure there is a byte to TX - assert len(self.uart_tx_buffer) - - # get the byte that is being transmitted - self.uart_tx_next = self.uart_tx_buffer.pop(0) - - # schedule the next interrupt, if any bytes left - if len(self.uart_tx_buffer): - self._schedule_next_tx() - - # send RX interrupt to mote - self.motehandler.mote.uart_isr_rx() - - # do *not* kick the scheduler - return False - - # ======================== private ========================================= - - def _schedule_next_tx(self): - - # calculate time at which byte will get out - time_next_tx = self.timeline.get_current_time() + float(1.0 / float(self.BAUDRATE)) - - # schedule that event - self.timeline.schedule_event( - time_next_tx, - self.motehandler.get_id(), - self.intr_rx, - self.INTR_RX, - ) diff --git a/openvisualizer/bspemulator/hwmodule.py b/openvisualizer/bspemulator/hwmodule.py deleted file mode 100644 index 51e9512f..00000000 --- a/openvisualizer/bspemulator/hwmodule.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License -import logging -from abc import ABCMeta - -from openvisualizer.simengine import simengine - - -class HwModule(object): - """ Parent class for all hardware modules. """ - __metaclass__ = ABCMeta - - @property - def _name(self): - raise NotImplementedError - - def __init__(self, motehandler): - # store variables - self.motehandler = motehandler - - # local variables - self.engine = simengine.SimEngine() - - # logging - self.log = logging.getLogger(self._name + '_' + str(self.motehandler.get_id())) - self.log.setLevel(logging.DEBUG) - self.log.addHandler(logging.NullHandler()) diff --git a/openvisualizer/bspemulator/vcdlogger.py b/openvisualizer/bspemulator/vcdlogger.py deleted file mode 100644 index 7f33f717..00000000 --- a/openvisualizer/bspemulator/vcdlogger.py +++ /dev/null @@ -1,139 +0,0 @@ -import os -import threading - - -class VcdLogger(object): - ACTIVITY_DUR = 1000 # 1000ns=1us - FILENAME = 'debugpins.vcd' - FILENAME_SWAP = 'debugpins.vcd.swap' - ENDVAR_LINE = '$upscope $end\n' - ENDDEF_LINE = '$enddefinitions $end\n' - - # ======================== singleton pattern =============================== - - _instance = None - _init = False - - SIGNAMES = ['frame', 'slot', 'fsm', 'task', 'isr', 'radio', 'ka', 'syncPacket', 'syncAck', 'debug'] - - def __new__(cls, *args, **kwargs): - if not cls._instance: - cls._instance = super(VcdLogger, cls).__new__(cls, *args, **kwargs) - return cls._instance - - # ======================== main ============================================ - - def __init__(self): - - # don't re-initialize an instance (singleton pattern) - if self._init: - return - self._init = True - - # local variables - self.f = open(self.FILENAME, 'w') - self.sig_name = {} - self.last_ts = {} - self.data_lock = threading.RLock() - self.enabled = False - self.sig_char = ord('!') - - # create header - header = [] - header += ['$timescale 1ns $end\n'] - header += ['$scope module logic $end\n'] - # variables will be declared here by self._addMote() - header += [self.ENDVAR_LINE] - header += [self.ENDDEF_LINE] - header = ''.join(header) - - # write header - self.f.write(header) - - # ======================== public ========================================== - - def set_enabled(self, enabled): - assert enabled in [True, False] - - with self.data_lock: - self.enabled = enabled - - def log(self, ts, mote, signal, state): - - assert signal in self.SIGNAMES - assert state in [True, False] - - # stop here if not enables - with self.data_lock: - if not self.enabled: - return - - # translate state to val - if state: - val = 1 - else: - val = 0 - - with self.data_lock: - - # add mote if needed - if mote not in self.sig_name: - self._add_mote(mote) - - # format - output = [] - ts_temp = int(ts * 1000000) * 1000 - if ((mote, signal) in self.last_ts) and self.last_ts[(mote, signal)] == ts: - ts_temp += self.ACTIVITY_DUR - output += ['#{0}\n'.format(ts_temp)] - output += ['{0}{1}\n'.format(val, self.sig_name[mote][signal])] - output = ''.join(output) - - # write - self.f.write(output) - - # remember ts - self.last_ts[(mote, signal)] = ts - - # ======================== private ========================================= - - def _add_mote(self, mote): - assert mote not in self.sig_name - - # === populate sig_name - self.sig_name[mote] = {} - for signal in self.SIGNAMES: - self.sig_name[mote][signal] = chr(self.sig_char) - self.sig_char += 1 - - # === close FILENAME - self.f.close() - - # === FILENAME -> FILENAME_SWAP - fswap = open(self.FILENAME_SWAP, 'w') - for line in open(self.FILENAME, 'r'): - # declare variables - if line == self.ENDVAR_LINE: - for signal in self.SIGNAMES: - fswap.write( - '$var wire 1 {0} {1}_{2} $end\n'.format( - self.sig_name[mote][signal], - mote, - signal, - ), - ) - # print line - fswap.write(line) - # initialize variables - if line == self.ENDDEF_LINE: - for signal in self.SIGNAMES: - fswap.write('#0\n') - fswap.write('0{0}\n'.format(self.sig_name[mote][signal])) - fswap.close() - - # === FILENAME_SWAP -> FILENAME - os.remove(self.FILENAME) - os.rename(self.FILENAME_SWAP, self.FILENAME) - - # === re-open FILENAME - self.f = open(self.FILENAME, 'a') diff --git a/openvisualizer/client/main.py b/openvisualizer/client/__main__.py similarity index 78% rename from openvisualizer/client/main.py rename to openvisualizer/client/__main__.py index 0cadcdd1..54946617 100644 --- a/openvisualizer/client/main.py +++ b/openvisualizer/client/__main__.py @@ -2,12 +2,14 @@ import json import logging import socket +import sys import time -import xmlrpclib +from xmlrpc.client import ServerProxy, Fault import bottle import click +from openvisualizer import VERSION from openvisualizer.client.plugins.plugin import Plugin from openvisualizer.client.utils import transform_into_ipv6 from openvisualizer.client.webserver import WebServer @@ -17,20 +19,31 @@ class Proxy(object): def __init__(self, host, port): url = 'http://{}:{}'.format(host, str(port)) - self.rpc_server = xmlrpclib.ServerProxy(url) + self.rpc_server = ServerProxy(url) pass_proxy = click.make_pass_decorator(Proxy, ensure=True) pass_plugins = click.make_pass_decorator(Plugin, ensure=True) -@click.group() +@click.group(invoke_without_command=True) @click.option('--server', default='localhost', help='Specify address of the Openvisualizer server') +@click.option('--version', is_flag=True, help='Print the OpenVisualizer version') @click.option('--port', default=9000, help='Specify to port to use') @click.option('--debug', is_flag=True, help="Enable debugging") @click.pass_context -def cli(ctx, server, port, debug): +def cli(ctx, version, server, port, debug): ctx.obj = Proxy(server, port) + + if version: + click.echo(f"OpenVisualizer (client) v{VERSION}") + sys.exit(0) + + if ctx.invoked_subcommand is None: + click.echo('Use one of the following subcommands: ', nl=False) + click.secho('network, view, or shutdown', bold=True) + sys.exit(0) + if debug: logging.basicConfig( filename='openv-client.log', @@ -47,31 +60,18 @@ def cli(ctx, server, port, debug): def shutdown(proxy): """Shutdown the Openvisualizer server""" - click.echo("Shutting down Openvisualizer server ... ", nl=False) + click.echo("Shutting down OpenVisualizer server... ", nl=False) try: - proxy.rpc_server.shutdown() - except socket.error: + proxy.rpc_server.remote_shutdown() + except Fault: pass click.secho("Ok!", fg='green', bold=True) -@click.command() -@pass_proxy -def list_methods(proxy): - """List all methods supported by the Openvisualizer server.""" - - try: - methods = proxy.rpc_server.system.listMethods() - except socket.error as err: - if errno.ECONNREFUSED: - click.secho("Connection refused. Is server running?", fg='red') - else: - click.echo(err) - else: - click.secho("List of support calls:", bold=True, underline=True) - for method in methods: - if not str(method).startswith("system"): - click.echo(" - {}".format(method)) +@click.group() +def network(): + """ Commands to interact with the network behind the OpenVisualizer server. """ + pass @click.command() @@ -86,7 +86,7 @@ def wireshark_debug(proxy): _ = proxy.rpc_server.enable_wireshark_debug() click.echo("{} --> {}".format(status, proxy.rpc_server.get_wireshark_debug())) - except xmlrpclib.Fault as err: + except Fault as err: click.secho("Server fault: {}".format(err.faultString), fg='red') return except socket.error as err: @@ -103,39 +103,39 @@ def wireshark_debug(proxy): @pass_proxy def motes(proxy): """Print the address and serial-port of each mote connected to the Openvisualizer server.""" + try: temp_mote_dict = proxy.rpc_server.get_mote_dict() - addr_port_dict = {} + address_port_dict = {} # check if we have all the info resolve the entire IPv6 address if None not in temp_mote_dict.values(): - for addr in temp_mote_dict: - mote_state = proxy.rpc_server.get_mote_state(addr) + for address in temp_mote_dict: + mote_state = proxy.rpc_server.get_mote_state(address) id_manager = json.loads(mote_state[MoteState.ST_IDMANAGER])[0] - full_addr = transform_into_ipv6(id_manager['myPrefix'][:-9] + '-' + id_manager['my64bID'][:-5]) - addr_port_dict[full_addr] = temp_mote_dict[addr] + full_address = transform_into_ipv6(id_manager['myPrefix'][:-9] + '-' + id_manager['my64bID'][:-5]) + address_port_dict[full_address] = temp_mote_dict[address] else: logging.warning("Not all addresses could be resolved.") - except socket.error as err: if errno.ECONNREFUSED: click.secho("Connection refused. Is server running?", fg='red') else: click.echo(err) - except xmlrpclib.Fault as err: + except Fault as err: click.secho("Caught server fault -- {}".format(err), fg='red') else: # if we were unable to resolve all the IPv6 addresses, use the intermediate results - if len(addr_port_dict) != len(temp_mote_dict): - addr_port_dict = temp_mote_dict + if len(address_port_dict) != len(temp_mote_dict): + address_port_dict = temp_mote_dict - i = 0 - port, addr = None, None - while port is None and i < len(addr_port_dict): - addr, port = addr_port_dict.items()[i] - i += 1 + port, address = None, None + for key in address_port_dict: + address, port = key, address_port_dict[key] + if port is not None: + break - len_addr = len(addr) if addr is not None else 0 + len_addr = len(address) if address is not None else 0 len_port = len(port) if port is not None else 0 heading = " | {:^{}} | {:^{}} | {:^13} |".format("MOTE ID", @@ -145,77 +145,71 @@ def motes(proxy): click.echo("".join([" "] + ["-"] * (len(heading) - 1))) click.echo(heading) click.echo("".join([" "] + ["-"] * (len(heading) - 1))) - for addr, port in addr_port_dict.items(): + for address, port in address_port_dict.items(): if port is None: - click.echo(" | {:^15} | {:^15} | ".format(port, addr), nl='') + click.echo(" | {:^15} | {:^15} | ".format(str(port), str(address)), nl='') click.secho("{:^13}".format('No response'), fg='red', nl='') click.echo(" |") else: - click.echo(" | {:^15} | {:^15} | ".format(addr, port), nl='') + click.echo(" | {:^15} | {:^15} | ".format(str(address), str(port)), nl='') click.secho("{:^13}".format('Ok!'), fg='green', nl='') click.echo(" |") click.echo("".join([" "] + ["-"] * (len(heading) - 1))) @click.command() -@click.option('--mote', default='all', help='Specify the mote(s) to (re)boot', show_default=True, type=str) @pass_proxy -def boot(proxy, mote): - """Boot motes attached to Openvisualizer.""" - addresses = mote.split(',') +def pause(proxy): + """ [SIM-ONLY] Pauses or unpauses the simulation engine. """ try: - _ = proxy.rpc_server.boot_motes(addresses) + paused = proxy.rpc_server.pause_simulation() + if paused: + click.secho("Paused", fg='yellow') + else: + click.secho("Unpaused", fg="green") + except socket.error as err: if errno.ECONNREFUSED: click.secho("Connection refused. Is server running?", fg='red') else: click.echo(err) - except xmlrpclib.Fault as err: + except Fault as err: click.secho("Caught server fault -- {}".format(err.faultString), fg='red') - else: - for a in addresses: - click.echo("Booting mote: {} ... ".format(a), nl=False) - click.secho("Ok!", bold=True, fg='green') @click.command() -@click.option('--rpc-host', default='localhost', help='Host address for webserver', show_default=True) -@click.option('--rpc-port', default='9000', help='Port number for webserver', show_default=True) -@click.option('--web-host', default='localhost', help='Host address for webserver', show_default=True) -@click.option('--web-port', default='8080', help='Port number for webserver', show_default=True) -@click.option('--debug', default='DEBUG', help='provide debug level [DEBUG, INFO, WARNING, ERROR, CRITICAL]', - show_default=True) -def web(rpc_host, rpc_port, web_port, web_host, debug): - """ Start a web server which acts as an RPC client for OpenVisualizer Server.""" - - bottle_server = bottle.Bottle() - - WebServer(bottle_server, (rpc_host, rpc_port), debug) +@pass_proxy +def runtime(proxy): + """ [SIM-ONLY] Get real and simulated runtime. """ + try: + elapsed = proxy.rpc_server.get_runtime() + click.echo(f"Real runtime: {elapsed[0]}") + click.echo(f"Simulated runtime: {elapsed[1]}") - if debug != 'DEBUG': - bottle.debug(False) - bottle_server.run(host=web_host, port=web_port, quiet=True) - else: - bottle.debug(True) - bottle_server.run(host=web_host, port=web_port) + except socket.error as err: + if errno.ECONNREFUSED: + click.secho("Connection refused. Is server running?", fg='red') + else: + click.echo(err) + except Fault as err: + click.secho("Caught server fault -- {}".format(err.faultString), fg='red') @click.command() @click.argument("port_or_address", nargs=1, type=str, required=False) @pass_proxy def root(proxy, port_or_address): - """Set a mote as dagroot or get the current's DAG root address.""" + """Set a mote as DAGroot or return the current DAGroot address.""" if port_or_address is None: try: dag_root = proxy.rpc_server.get_dagroot() if dag_root is None: - click.echo("No DAG root configured\n") + click.secho("No DAGroot configured\n", fg='yellow') click.echo(click.get_current_context().get_help()) return - dag_root = "".join('%02x' % b for b in dag_root) mote_state = proxy.rpc_server.get_mote_state(dag_root) except socket.error as err: if errno.ECONNREFUSED: @@ -223,24 +217,24 @@ def root(proxy, port_or_address): else: click.echo(err) return - except xmlrpclib.Fault as err: + except Fault as err: click.secho("Caught server fault -- {}".format(err.faultString), fg='red') else: id_manager = json.loads(mote_state[MoteState.ST_IDMANAGER])[0] - click.echo('Current DAG root: {}'.format( + click.echo('Current DAGroot: {}'.format( transform_into_ipv6(id_manager['myPrefix'][:-9] + '-' + id_manager['my64bID'][:-5]))) else: try: - _ = proxy.rpc_server.set_root(port_or_address) + _ = proxy.rpc_server.set_dagroot(port_or_address) except socket.error as err: if errno.ECONNREFUSED: click.secho("Connection refused. Is server running?", fg='red') else: click.echo(err) return - except xmlrpclib.Fault as err: + except Fault as err: click.secho("Caught server fault -- {}".format(err.faultString), fg='red') click.secho("\nMake sure the motes are booted and provide a 16B mote address or a port ID to set the DAG " "root.", fg='red') @@ -288,6 +282,28 @@ def start_view(proxy, mote, refresh_rate, graphic=None): click.secho(view_thread.error_msg, fg='red') +@click.command() +@click.option('--rpc-host', default='localhost', help='Host address for webserver', show_default=True) +@click.option('--rpc-port', default='9000', help='Port number for webserver', show_default=True) +@click.option('--web-host', default='localhost', help='Host address for webserver', show_default=True) +@click.option('--web-port', default='8080', help='Port number for webserver', show_default=True) +@click.option('--debug', default='DEBUG', help='provide debug level [DEBUG, INFO, WARNING, ERROR, CRITICAL]', + show_default=True) +def web(rpc_host, rpc_port, web_port, web_host, debug): + """ Start a web server which acts as an RPC client for OpenVisualizer Server.""" + + bottle_server = bottle.Bottle() + + WebServer(bottle_server, (rpc_host, rpc_port), debug) + + if debug != 'DEBUG': + bottle.debug(False) + bottle_server.run(host=web_host, port=web_port, quiet=True) + else: + bottle.debug(True) + bottle_server.run(host=web_host, port=web_port) + + @click.command() @click.argument("mote", nargs=1, type=str) @click.option('--refresh-rate', default=1.0, help='Set the refresh rate of the view (in seconds)', type=float, @@ -344,13 +360,15 @@ def neighbors(proxy, mote, refresh_rate): cli.add_command(shutdown) -# cli.add_command(list_methods) -cli.add_command(wireshark_debug) -cli.add_command(motes) -cli.add_command(boot) -cli.add_command(root) +cli.add_command(network) cli.add_command(view) +network.add_command(wireshark_debug) +network.add_command(motes) +network.add_command(pause) +network.add_command(root) +network.add_command(runtime) + view.add_command(macstats) view.add_command(pktqueue) view.add_command(schedule) @@ -358,3 +376,6 @@ def neighbors(proxy, mote, refresh_rate): view.add_command(msf) view.add_command(neighbors) view.add_command(web) + +if __name__ == "__main__": + cli() diff --git a/openvisualizer/client/plugins/motestatus.py b/openvisualizer/client/plugins/motestatus.py index 01825712..c06776a7 100644 --- a/openvisualizer/client/plugins/motestatus.py +++ b/openvisualizer/client/plugins/motestatus.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import json import logging diff --git a/openvisualizer/client/plugins/msf.py b/openvisualizer/client/plugins/msf.py index 3668c6e0..cf937f73 100644 --- a/openvisualizer/client/plugins/msf.py +++ b/openvisualizer/client/plugins/msf.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import logging from openvisualizer.client.plugins.plugin import Plugin diff --git a/openvisualizer/client/plugins/neighbors.py b/openvisualizer/client/plugins/neighbors.py index 0a4a8569..c1b0923a 100644 --- a/openvisualizer/client/plugins/neighbors.py +++ b/openvisualizer/client/plugins/neighbors.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import json import logging from math import ceil diff --git a/openvisualizer/client/plugins/pktqueue.py b/openvisualizer/client/plugins/pktqueue.py index bf790049..7dc94487 100644 --- a/openvisualizer/client/plugins/pktqueue.py +++ b/openvisualizer/client/plugins/pktqueue.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import json import logging import sys diff --git a/openvisualizer/client/plugins/plugin.py b/openvisualizer/client/plugins/plugin.py index e77dbfd6..72045ef2 100644 --- a/openvisualizer/client/plugins/plugin.py +++ b/openvisualizer/client/plugins/plugin.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from openvisualizer.client.view import View diff --git a/openvisualizer/client/view.py b/openvisualizer/client/view.py index 576d701c..498eea78 100644 --- a/openvisualizer/client/view.py +++ b/openvisualizer/client/view.py @@ -7,7 +7,7 @@ import threading import time from abc import ABCMeta, abstractmethod -from xmlrpclib import Fault +from xmlrpc.client import Fault from blessed import Terminal diff --git a/openvisualizer/client/web_files/templates/moteview.tmpl b/openvisualizer/client/web_files/templates/moteview.tmpl index 7afc2d8d..bb56b4bc 100644 --- a/openvisualizer/client/web_files/templates/moteview.tmpl +++ b/openvisualizer/client/web_files/templates/moteview.tmpl @@ -284,7 +284,6 @@ idJson = $.parseJSON(json.IdManager)[0]; syncJson = $.parseJSON(json.IsSync)[0]; dagrankJson = $.parseJSON(json.MyDagRank)[0]; - outbufJson = $.parseJSON(json.OutputBuffer)[0]; backoffJson = $.parseJSON(json.Backoff)[0]; macstatsJson = $.parseJSON(json.MacStats)[0]; schedJson = $.parseJSON(json.Schedule); @@ -305,8 +304,6 @@ $("#asn_fld").text( hasJson ? asnJson.asn : ''); $("#join_fld").text( hasJson ? joinedJson.joinedAsn : ''); $("#dagrank_fld").text( hasJson ? dagrankJson.myDAGrank : ''); - $("#outread_fld").text( hasJson ? outbufJson.index_read : ''); - $("#outwrite_fld").text( hasJson ? outbufJson.index_write : ''); $("#backexp_fld").text( hasJson ? backoffJson.backoffExponent : ''); $("#backoff_fld").text( hasJson ? backoffJson.backoff : ''); $("#mincorrection_fld").text(hasJson ? macstatsJson.minCorrection : ''); diff --git a/openvisualizer/client/webserver.py b/openvisualizer/client/webserver.py index df90c5cf..46c435a3 100644 --- a/openvisualizer/client/webserver.py +++ b/openvisualizer/client/webserver.py @@ -10,7 +10,7 @@ import logging import re import socket -import xmlrpclib +from xmlrpc.client import ServerProxy, Fault import bottle import pkg_resources @@ -37,9 +37,11 @@ def __init__(self, bottle_srv, rpc_server_addr, debug): logger.debug('create instance') # store params - self.rpc_server = xmlrpclib.ServerProxy('http://{}:{}'.format(*rpc_server_addr)) + self.rpc_server = ServerProxy('http://{}:{}'.format(*rpc_server_addr)) self.bottle_srv = bottle_srv + _ = debug + self._define_routes() # To find page templates @@ -53,7 +55,7 @@ def __init__(self, bottle_srv, rpc_server_addr, debug): def _define_routes(self): """ - Matches web URL to impelementing method. Cannot use @route annotations on the methods due to the class-based + Matches web URL to implementing method. Cannot use @route annotations on the methods due to the class-based implementation. """ self.bottle_srv.route(path='/', callback=self._show_moteview) @@ -72,11 +74,11 @@ def _define_routes(self): self.bottle_srv.route(path='/topology', callback=self._topology_page) self.bottle_srv.route(path='/topology/data', callback=self._topology_data) self.bottle_srv.route(path='/topology/download', callback=self._topology_download) - self.bottle_srv.route(path='/topology/motes', method='POST', callback=self._topology_motes_update) - self.bottle_srv.route(path='/topology/connections', method='PUT', callback=self._topology_connections_create) - self.bottle_srv.route(path='/topology/connections', method='POST', callback=self._topology_connections_update) - self.bottle_srv.route(path='/topology/connections', method='DELETE', callback=self._topology_connections_delete) - self.bottle_srv.route(path='/topology/route', method='GET', callback=self._topology_route_retrieve) + self.bottle_srv.route(path='/topology/motes', method='POST', callback=self._topology_update_positions) + self.bottle_srv.route(path='/topology/connections', method='PUT', callback=self._topology_create_connections) + self.bottle_srv.route(path='/topology/connections', method='POST', callback=self._topology_update_connections) + self.bottle_srv.route(path='/topology/connections', method='DELETE', callback=self._topology_delete_connections) + self.bottle_srv.route(path='/topology/route', method='GET', callback=self._topology_get_route) self.bottle_srv.route(path='/static/', callback=WebServer._server_static) @bottle.view('moteview.tmpl') @@ -116,21 +118,19 @@ def _toggle_dagroot(self, moteid): logger.debug('Toggle root status for moteid {0}'.format(moteid)) try: ms = self.rpc_server.get_mote_state(moteid) - except xmlrpclib.Fault as err: - logger.error("A fault occurred: {}".format(err)) - return '{"result" : "fail"}' - except socket.error as err: + except (socket.error, Fault) as err: logger.error(err) - return '{}' + return '{"result" : "fail"}' if ms: if logger.isEnabledFor(logging.DEBUG): logger.debug('Found mote {0} in mote_states'.format(moteid)) try: - self.rpc_server.set_root(moteid) - except socket.error as err: + self.rpc_server.set_dagroot(moteid) + except (socket.error, Fault) as err: logger.error(err) - return '{}' + return '{"result" : "fail"}' + return '{"result" : "success"}' else: if logger.isEnabledFor(logging.DEBUG): @@ -148,14 +148,12 @@ def _get_mote_data(self, moteid): logger.debug('Get JSON data for moteid {0}'.format(moteid)) try: states = self.rpc_server.get_mote_state(moteid) - except xmlrpclib.Fault as err: - logger.error("Could not fetch mote state for mote {}: {}".format(moteid, err)) - return states - except socket.error as err: + except (Fault, socket.error) as err: logger.error(err) - return {} + if logger.isEnabledFor(logging.DEBUG): logger.debug('Found mote {0} in mote_states'.format(moteid)) + return states def _set_wireshark_debug(self, enabled): @@ -171,15 +169,12 @@ def _set_wireshark_debug(self, enabled): _ = self.rpc_server.disable_wireshark_debug() else: logger.error('Illegal value for \'_set_wireshark_debug\'') - except xmlrpclib.Fault as err: - logger.error("Caught a server fault: {}".format(err)) - except socket.error as err: + except (Fault, socket.error) as err: logger.error(err) @staticmethod def _set_gologic_debug(enabled): logger.info('Enable GoLogic debug : {0}'.format(enabled)) - # vcdlogger.VcdLogger().set_enabled(enabled == 'true') return '{"result" : "success"}' @bottle.view('eventBus.tmpl') @@ -191,7 +186,7 @@ def _show_event_bus(self): def _show_dag(self): try: states, edges = self.rpc_server.get_dag() - except socket.error as err: + except (Fault, socket.error) as err: logger.error(err) return {} @@ -204,9 +199,10 @@ def _show_connectivity(self): def _show_motes_connectivity(self): try: states, edges = self.rpc_server.get_motes_connectivity() - except socket.error as err: + except (Fault, socket.error) as err: logger.error(err) return {} + return {'states': states, 'edges': edges} @bottle.view('routing.tmpl') @@ -223,11 +219,12 @@ def _topology_data(self): data = {} try: data = self.rpc_server.get_network_topology() - except socket.error as err: + except (socket.error, Fault) as err: logger.error(err) + return data - def _topology_motes_update(self): + def _topology_update_positions(self): """ Update the network topology (simulation only)""" motes_temp = {} @@ -249,11 +246,11 @@ def _topology_motes_update(self): motes_temp[index][param] = v try: - _ = self.rpc_server.update_network_topology(json.dumps(motes_temp)) - except socket.error as err: + _ = self.rpc_server.update_positions(json.dumps(motes_temp)) + except (socket.error, Fault) as err: logger.error(err) - def _topology_connections_create(self): + def _topology_create_connections(self): data = bottle.request.forms assert sorted(data.keys()) == sorted(['fromMote', 'toMote']) @@ -265,7 +262,7 @@ def _topology_connections_create(self): except socket.error as err: logger.error(err) - def _topology_connections_update(self): + def _topology_update_connections(self): data = bottle.request.forms assert sorted(data.keys()) == sorted(['fromMote', 'toMote', 'pdr']) @@ -274,11 +271,11 @@ def _topology_connections_update(self): pdr = float(data['pdr']) try: - _ = self.rpc_server.update_motes_connection(from_mote, to_mote, pdr) - except socket.error as err: + _ = self.rpc_server.update_connections(from_mote, to_mote, pdr) + except (socket.error, Fault) as err: logger.error(err) - def _topology_connections_delete(self): + def _topology_delete_connections(self): data = bottle.request.forms assert sorted(data.keys()) == sorted(['fromMote', 'toMote']) @@ -286,21 +283,20 @@ def _topology_connections_delete(self): to_mote = int(data['toMote']) try: - _ = self.rpc_server.delete_motes_connection(from_mote, to_mote) - except socket.error as err: + _ = self.rpc_server.delete_connections(from_mote, to_mote) + except (Fault, socket.error) as err: logger.error(err) - def _topology_route_retrieve(self): + def _topology_get_route(self): data = bottle.request.query - assert data.keys() == ['destination'] destination_eui = [0x14, 0x15, 0x92, 0xcc, 0x00, 0x00, 0x00, int(data['destination'])] route = {} try: - route = self.rpc_server.retrieve_routing_path(destination_eui) - except socket.error: - pass + route = self.rpc_server.get_routing_path(destination_eui) + except (socket.error, Fault) as err: + logger.error(err) return route @@ -315,9 +311,6 @@ def _topology_download(self): logger.error(err) return {} - if dagroot is not None: - dagroot = ''.join('%02x' % b for b in dagroot) - data['DAGroot'] = dagroot bottle.response.headers['Content-disposition'] = 'attachment; filename=topology_data_' + now.strftime( diff --git a/openvisualizer/config/logging.conf b/openvisualizer/config/logging.conf index d08f0b85..b483c264 100644 --- a/openvisualizer/config/logging.conf +++ b/openvisualizer/config/logging.conf @@ -8,7 +8,7 @@ format=%(asctime)s %(levelname)s %(message)s datefmt=%H:%M:%S [formatter_console] -class=openvisualizer.main.ColoredFormatter +class=openvisualizer.__main__.ColoredFormatter format=%(asctime)s %(levelname)s %(message)s datefmt=%H:%M:%S @@ -20,7 +20,7 @@ keys=std, console [handler_std] class=handlers.RotatingFileHandler # args: filename, open mode, max file size, backup file count -args=('openv-server.log',) +args=('openv-server.log', 'a', 2000000, 5) formatter=std [handler_console] @@ -55,7 +55,8 @@ keys= SourceRoute, JRC, Topology, - OpenVisualizerServer, + Main, + OpenVisualizer, Utils, OVtracer, CoAP @@ -202,11 +203,17 @@ handlers=std propagate=0 qualname=Topology -[logger_OpenVisualizerServer] +[logger_Main] level=VERBOSE handlers=std, console propagate=0 -qualname=OpenVisualizerServer +qualname=Main + +[logger_OpenVisualizer] +level=VERBOSE +handlers=std, console +propagate=0 +qualname=OpenVisualizer [logger_Utils] level=VERBOSE diff --git a/openvisualizer/eventbus/eventbusclient.py b/openvisualizer/eventbus/eventbusclient.py index f1099d24..88a151ad 100644 --- a/openvisualizer/eventbus/eventbusclient.py +++ b/openvisualizer/eventbus/eventbusclient.py @@ -14,7 +14,7 @@ log.addHandler(logging.NullHandler()) -class EventBusClient(object): +class EventBusClient: WILDCARD = '*' PROTO_ICMPv6 = 'icmpv6' @@ -24,10 +24,14 @@ class EventBusClient(object): PROTO_UDP, ] - def __init__(self, name, registrations): + def __init__(self, name, registrations=None, **kwargs): + + if registrations is None: + registrations = [] assert type(name) == str assert type(registrations) == list + for r in registrations: assert type(r) == dict for k in r.keys(): @@ -114,7 +118,6 @@ def _event_bus_notification(self, signal, sender, data): except TypeError as err: output = "ERROR could not call {0}, err={1}".format(callback, err) log.critical(output) - print output def _signals_equivalent(self, s1, s2): return_val = True diff --git a/openvisualizer/eventbus/eventbusmonitor.py b/openvisualizer/eventbus/eventbusmonitor.py index 8d13ace9..7b216c3c 100644 --- a/openvisualizer/eventbus/eventbusmonitor.py +++ b/openvisualizer/eventbus/eventbusmonitor.py @@ -22,9 +22,9 @@ log.addHandler(logging.NullHandler()) -class EventBusMonitor(object): +class EventBusMonitor: - def __init__(self): + def __init__(self, wireshark_debug): # log log.debug("create instance") @@ -34,7 +34,7 @@ def __init__(self): # local variables self.data_lock = threading.Lock() self.stats = {} - self.wireshark_debug_enabled = True + self.wireshark_debug_enabled = wireshark_debug self.dagoot_eui64 = [0x00] * 8 self.sim_mode = False @@ -52,7 +52,7 @@ def get_stats(self): with self.data_lock: temp_stats = copy.deepcopy(self.stats) - # format as a dictionnary + # format as a dictionary return_val = [ { 'sender': k[0], @@ -70,7 +70,7 @@ def set_wireshark_debug(self, is_enabled): Well-suited to viewing the packets in Wireshark. See http://wiki.wireshark.org/IEEE_802.15.4 for ZEP details. """ with self.data_lock: - self.wireshark_debug_enabled = (True and is_enabled) + self.wireshark_debug_enabled = is_enabled log.info('%s export of ZEP mesh debug packets to Internet', 'Enabled' if self.wireshark_debug_enabled else 'Disabled') @@ -149,14 +149,15 @@ def _eventbus_notification(self, signal, sender, data): zep = self._wrap_mac_and_zep(previous_hop=self.dagoot_eui64, next_hop=next_hop, lowpan=lowpan) self._dispatch_mesh_debug_packet(zep) - def _wrap_mac_and_zep(self, previous_hop, next_hop, lowpan): + @staticmethod + def _wrap_mac_and_zep(previous_hop: bytes, next_hop: bytes, lowpan: bytes): """ Returns Exegin ZEP protocol header and dummy 802.15.4 header wrapped around outgoing 6LoWPAN layer packet. """ - phop = previous_hop[:] + phop = list(previous_hop[:]) phop.reverse() - nhop = next_hop[:] + nhop = list(next_hop[:]) nhop.reverse() # ZEP @@ -178,13 +179,14 @@ def _wrap_mac_and_zep(self, previous_hop, next_hop, lowpan): mac += [0xfe, 0xca] # destination PAN ID mac += nhop # destination address mac += phop # source address - mac += lowpan + mac += list(lowpan) # CRC mac += calculate_fcs(mac) return zep + mac - def _wrap_zep_crc(self, body, frequency): + @staticmethod + def _wrap_zep_crc(body, frequency): # ZEP header zep = [ord('E'), ord('X')] # Protocol ID String @@ -207,13 +209,13 @@ def _wrap_zep_crc(self, body, frequency): def _dispatch_mesh_debug_packet(self, zep): """ - Wraps ZEP-based debug packet, for outgoing mesh 6LoWPAN message, with UDP and IPv6 headers. Then forwards as + Wraps ZEP-based debug packet, for outgoing mesh 6LoWPAN message, with UDP and IPv6 headers. Then forwards as an event to the Internet interface. """ # UDP udp = UDP(sport=0, dport=17754) - udp.add_payload("".join([chr(i) for i in zep])) + udp.add_payload(bytes(zep)) # Common address for source and destination addr = [] @@ -225,6 +227,6 @@ def _dispatch_mesh_debug_packet(self, zep): ip = IPv6(version=6, tc=0, src=addr, hlim=64, dst=addr) ip = ip / udp - data = [ord(b) for b in raw(ip)] + data = [b for b in raw(ip)] dispatcher.send(sender=self.name, signal='v6ToInternet', data=data) diff --git a/openvisualizer/jrc/jrc.py b/openvisualizer/jrc/jrc.py index e0a84a2a..4f782d4f 100644 --- a/openvisualizer/jrc/jrc.py +++ b/openvisualizer/jrc/jrc.py @@ -6,7 +6,6 @@ # https://openwsn.atlassian.net/wiki/display/OW/License """ -import binascii import logging.handlers import os import threading @@ -29,7 +28,7 @@ # ======================== Top Level jrc Class ============================= -class JRC(object): +class JRC: def __init__(self): coap_resource = JoinResource() self.coap_server = CoapServer(coap_resource, ContextHandler(coap_resource).security_context_lookup) @@ -39,7 +38,7 @@ def close(self): # ======================== Security Context Handler ========================= -class ContextHandler(object): +class ContextHandler: # value of the OSCORE Master Secret from 6TiSCH TD master_secret = "DEADBEEFCAFEDEADBEEFCAFEDEADBEEF" master_salt = "" @@ -51,8 +50,8 @@ def __init__(self, join_resource): def security_context_lookup(self, kid, kid_context): eui64 = kid_context - sender_id = "JRC" - recipient_id = "" + sender_id = b"JRC" + recipient_id = b"" # if eui-64 is found in the list of joined nodes, return the appropriate context # this is important for replay protection @@ -69,21 +68,21 @@ def security_context_lookup(self, kid, kid_context): # if eui-64 is not found, create a new tentative context but only add it to the list of joined nodes in the GET # handler of the join resource file_path = os.path.abspath(os.path.join(user_data_dir('openvisualizer'), "oscore_context_{0}.json". - format(binascii.hexlify(eui64)))) + format(eui64.hex()))) if not os.path.exists(os.path.dirname(file_path)): os.makedirs(os.path.dirname(file_path)) log.verbose("New node: {0}. Creating new OSCORE context in {1}.". - format(format_ipv6_addr(Utils.str2buf(eui64)), file_path)) + format(format_ipv6_addr(eui64), file_path)) # FIXME: until persistency is implemented in firmware, we need to overwrite the security context for each run # FIXME: this is a security issue as AEAD nonces get reused and should not be used in a production environment self.security_context_create_overwrite(file_path, - binascii.hexlify(eui64), + eui64.hex(), self.master_salt, self.master_secret, - binascii.hexlify(sender_id), - binascii.hexlify(recipient_id)) + sender_id.hex(), + recipient_id.hex()) context = Oscoap.SecurityContext(securityContextFilePath=file_path) @@ -104,8 +103,13 @@ def security_context_create_overwrite(file_path, id_context, master_salt, master "sequenceNumber": 0, } - with open(file_path, "w") as context_file: - json.dump(ctx_dict, context_file, indent=4, sort_keys=True) + try: + with open(file_path, "w") as context_file: + json.dump(ctx_dict, context_file, indent=4, sort_keys=True) + except PermissionError as err: + log.error("Permission error when opening OSCORE context storage file.") + log.error("Try removing the file or running openv-server with admin privileges") + log.error(err) # ======================== Interface with OpenVisualizer ====================================== @@ -160,8 +164,7 @@ def __init__(self, coap_resource, context_handler=None): # ======================== public ========================================== def close(self): - # nothing to do - pass + self.coap_server.close() # ======================== private ========================================= @@ -170,7 +173,7 @@ def close(self): def _get_l2_security_key_notif(self, sender, signal, data): """ Return L2 security key for the network. """ - return {'index': [self.coap_resource.networkKeyIndex], 'value': self.coap_resource.networkKey} + return {'index': [self.coap_resource.networkKeyIndex], 'value': list(self.coap_resource.networkKey)} def _register_dagroot_notif(self, sender, signal, data): # register for the global address of the DAG root @@ -226,14 +229,13 @@ def _receive_from_mesh(self, sender, signal, data): Receive packet from the mesh destined for jrc's CoAP server. Forwards the packet to the virtual CoAP server running in test mode (PyDispatcher). """ - sender = format_ipv6_addr(data[0]) # FIXME pass source port within the signal and open coap client at this port self.coap_client = \ coap.coap(ipAddress=sender, udpPort=Defs.DEFAULT_UDP_PORT, testing=True, receiveCallback=self._receive_from_coap) # low level forward of the CoAP message - self.coap_client.socketUdp.sendUdp(destIp='', destPort=Defs.DEFAULT_UDP_PORT, msg=data[1]) + self.coap_client.socketUdp.sendUdp(destIp='', destPort=Defs.DEFAULT_UDP_PORT, msg=bytes(data[1])) return True def _receive_from_coap(self, timestamp, sender, data): @@ -290,7 +292,7 @@ class JoinResource(coapResource.coapResource): def __init__(self): self.joinedNodes = [] - self.networkKey = Utils.str2buf(os.urandom(16)) # random key every time OpenVisualizer is initialized + self.networkKey = os.urandom(16) # random key every time OpenVisualizer is initialized self.networkKeyIndex = 0x01 # L2 key index # initialize parent class @@ -302,19 +304,17 @@ def POST(self, options=[], payload=[]): # noqa: N802 log.verbose("received JRC join request") - link_layer_keyset = [self.networkKeyIndex, Utils.buf2str(self.networkKey)] + link_layer_keyset = [self.networkKeyIndex, self.networkKey] configuration = {CoJPLabel.COJP_PARAMETERS_LABELS_LLKEYSET: link_layer_keyset} - configuration_serialized = cbor.dumps(configuration) - - resp_payload = [ord(b) for b in configuration_serialized] + resp_payload = cbor.dumps(configuration) object_security = Oscoap.objectSecurityOptionLookUp(options) if object_security: # we need to add the pledge to a list of joined nodes, if not present already - eui64 = Utils.buf2str(object_security.kidContext) + eui64 = object_security.kidContext found = False for node in self.joinedNodes: if node['eui64'] == eui64: diff --git a/openvisualizer/main.py b/openvisualizer/main.py deleted file mode 100644 index b633e96d..00000000 --- a/openvisualizer/main.py +++ /dev/null @@ -1,999 +0,0 @@ -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -""" -Contains application model for OpenVisualizer. Expects to be called by top-level UI module. See main() for startup use. -""" -import json -import logging.config -import os -import platform -import shutil -import signal -import sys -import tempfile -import time -from ConfigParser import SafeConfigParser -from SimpleXMLRPCServer import SimpleXMLRPCServer -from argparse import ArgumentParser -from xmlrpclib import Fault - -import appdirs -import coloredlogs -import pkg_resources -import verboselogs -from iotlabcli.parser import common - -from openvisualizer import PACKAGE_NAME, WINDOWS_COLORS, UNIX_COLORS, DEFAULT_LOGGING_CONF, APPNAME -from openvisualizer.eventbus import eventbusmonitor -from openvisualizer.eventbus.eventbusclient import EventBusClient -from openvisualizer.jrc import jrc -from openvisualizer.motehandler.moteconnector import moteconnector -from openvisualizer.motehandler.moteprobe import emulatedmoteprobe -from openvisualizer.motehandler.moteprobe import testbedmoteprobe -from openvisualizer.motehandler.moteprobe.iotlabmoteprobe import IotlabMoteProbe -from openvisualizer.motehandler.moteprobe.serialmoteprobe import SerialMoteProbe -from openvisualizer.motehandler.motestate import motestate -from openvisualizer.motehandler.motestate.motestate import MoteState -from openvisualizer.openlbr import openlbr -from openvisualizer.opentun.opentun import OpenTun -from openvisualizer.opentun.opentunnull import OpenTunNull -from openvisualizer.rpl import topology, rpl -from openvisualizer.simengine import simengine, motehandler -from openvisualizer.utils import extract_component_codes, extract_log_descriptions, extract_6top_rcs, \ - extract_6top_states - -verboselogs.install() - -log = logging.getLogger('OpenVisualizerServer') -coloredlogs.install(level='WARNING', logger=log, fmt='%(asctime)s [%(name)s:%(levelname)s] %(message)s', - datefmt='%H:%M:%S') - - -class ColoredFormatter(coloredlogs.ColoredFormatter): - """ Class that matches coloredlogs.ColoredFormatter arguments with logging.Formatter """ - - def __init__(self, fmt=None, datefmt=None): - self.parser = SafeConfigParser() - - if sys.platform.startswith('win32'): - log_colors_conf = pkg_resources.resource_filename(PACKAGE_NAME, WINDOWS_COLORS) - else: - log_colors_conf = pkg_resources.resource_filename(PACKAGE_NAME, UNIX_COLORS) - - self.parser.read(log_colors_conf) - - ls = self.parse_section('levels', 'keys') - fs = self.parse_section('fields', 'keys') - - coloredlogs.ColoredFormatter.__init__(self, fmt=fmt, datefmt=datefmt, level_styles=ls, field_styles=fs) - - def parse_section(self, section, option): - dictionary = {} - - if not self.parser.has_section(section) or not self.parser.has_option(section, option): - log.warning('Unknown section {} or option {}'.format(section, option)) - return dictionary - - subsections = map(str.strip, self.parser.get(section, option).split(',')) - - for subsection in subsections: - if not self.parser.has_section(str(subsection)): - log.warning('Unknown section name: {}'.format(subsection)) - continue - - dictionary[subsection] = {} - options = self.parser.options(subsection) - - for opt in options: - res = self.parse_options(subsection, opt.strip().lower()) - if res is not None: - dictionary[subsection][opt] = res - - return dictionary - - def parse_options(self, section, option): - res = None - if option == 'bold' or option == 'faint': - try: - return self.parser.getboolean(section, option) - except ValueError: - log.error('Illegal value: {} for option: {}'.format(self.parser.get(section, option), option)) - elif option == 'color': - try: - res = self.parser.getint(section, option) - except ValueError: - res = self.parser.get(section, option) - else: - log.warning('Unknown option name: {}'.format(option)) - - return res - - -class OpenVisualizerServer(SimpleXMLRPCServer, EventBusClient): - """ - Class implements and RPC server that allows monitoring and (remote) management of a mesh network. - """ - - def __init__(self, host, port, simulator_mode, debug, vcdlog, - use_page_zero, sim_topology, testbed_motes, mqtt_broker, - opentun, fw_path, auto_boot, root, port_mask, baudrate, - topo_file, iotlab_motes, iotlab_passwd, iotlab_user): - - # store params - self.host = host - - try: - self.port = int(port) - self.simulator_mode = int(simulator_mode) - except ValueError as err: - log.error(err) - - if self.simulator_mode == 0 and sim_topology is not None: - log.warning("Simulation topology specified but no --sim= given, switching to hardware mode") - - self.sim_topology = sim_topology - - self.debug = debug - if self.debug and not opentun: - log.warning("Wireshark debugging requires opentun") - - self.use_page_zero = use_page_zero - self.vcdlog = vcdlog - - if fw_path is not None: - self.fw_path = os.path.expanduser(fw_path) - else: - self.fw_path = fw_path - - self.root = root - self.dagroot = None - self.auto_boot = auto_boot - - if topo_file is not None: - self.topo_file = os.path.expanduser(topo_file) - else: - self.topo_file = topo_file - - # if a topology file is specified, overwrite the simulation and topology options - if self.topo_file: - self.load_motes_from_topology_file() - - if self.fw_path is None: - try: - self.fw_path = os.environ['OPENWSN_FW_BASE'] - except KeyError: - log.critical("Neither OPENWSN_FW_BASE or '--fw-path' was specified.") - os.kill(os.getpid(), signal.SIGTERM) - - # local variables - self.ebm = eventbusmonitor.EventBusMonitor() - self.openlbr = openlbr.OpenLbr(use_page_zero) - self.rpl = rpl.RPL() - self.jrc = jrc.JRC() - self.topology = topology.Topology() - self.mote_probes = [] - - # create opentun call last since indicates prefix - self.opentun = OpenTun.create(opentun) - - if self.debug and opentun: - self.ebm.wireshark_debug_enabled = True - else: - self.ebm.wireshark_debug_enabled = False - - if self.simulator_mode: - self.simengine = simengine.SimEngine(self.sim_topology) - self.simengine.start() - - self.temp_dir = self.copy_sim_fw() - - if self.temp_dir is None: - log.critical("failed to import simulation files! Exiting now!") - os.kill(os.getpid(), signal.SIGTERM) - - sys.path.append(os.path.join(self.temp_dir)) - motehandler.read_notif_ids(os.path.join(self.temp_dir, 'openwsnmodule_obj.h')) - - import oos_openwsn # pylint: disable=import-error - - self.mote_probes = [] - for _ in range(self.simulator_mode): - mote_handler = motehandler.MoteHandler(oos_openwsn.OpenMote(), self.vcdlog) - self.simengine.indicate_new_mote(mote_handler) - self.mote_probes += [emulatedmoteprobe.EmulatedMoteProbe(emulated_mote=mote_handler)] - - # load the saved topology from the topology file - if self.topo_file: - self.load_topology() - elif iotlab_motes: - # in "IoT-LAB" mode, motes are connected to TCP ports - self.mote_probes = IotlabMoteProbe.probe_iotlab_motes( - iotlab_motes=iotlab_motes, - iotlab_user=iotlab_user, - iotlab_passwd=iotlab_passwd, - ) - elif testbed_motes: - motes_finder = testbedmoteprobe.OpentestbedMoteFinder(mqtt_broker) - mote_list = motes_finder.get_opentestbed_motelist() - for p in mote_list: - self.mote_probes.append(testbedmoteprobe.OpentestbedMoteProbe(mqtt_broker, testbedmote_eui64=p)) - else: - # in "hardware" mode, motes are connected to the serial port - self.mote_probes = SerialMoteProbe.probe_serial_ports(port_mask=port_mask, baudrate=baudrate) - - # create a MoteConnector for each MoteProbe - try: - fw_defines = self.extract_stack_defines() - except IOError as err: - log.critical("could not updated firmware definitions: {}".format(err)) - os.kill(os.getpid(), signal.SIGTERM) - return - - self.mote_connectors = [moteconnector.MoteConnector(mp, fw_defines, mqtt_broker) for mp in self.mote_probes] - - # create a MoteState for each MoteConnector - self.mote_states = [motestate.MoteState(mc) for mc in self.mote_connectors] - - # set up EventBusClient - EventBusClient.__init__(self, name='OpenVisualizerServer', registrations=[]) - - # set up RPC server - try: - SimpleXMLRPCServer.__init__(self, (self.host, self.port), allow_none=True, logRequests=False) - - self.register_introspection_functions() - - # register RPCs - self.register_function(self.shutdown) - self.register_function(self.get_mote_dict) - self.register_function(self.boot_motes) - self.register_function(self.set_root) - self.register_function(self.get_mote_state) - self.register_function(self.get_dagroot) - self.register_function(self.get_dag) - self.register_function(self.get_motes_connectivity) - self.register_function(self.get_wireshark_debug) - self.register_function(self.enable_wireshark_debug) - self.register_function(self.disable_wireshark_debug) - self.register_function(self.get_ebm_stats) - self.register_function(self.get_network_topology) - self.register_function(self.update_network_topology) - self.register_function(self.create_motes_connection) - self.register_function(self.update_motes_connection) - self.register_function(self.delete_motes_connection) - self.register_function(self.retrieve_routing_path) - - # boot all simulated motes - if self.simulator_mode and self.auto_boot: - self.boot_motes(['all']) - - # set a mote (hardware or emulated) as DAG root of the network - if self.root is not None: - if self.simulator_mode and self.auto_boot is False: - log.warning("Cannot set root when motes are not booted! ") - else: - log.info("Setting DAG root...") - # make sure that the simulated motes are booted and the hardware motes have communicated - # their mote ID - time.sleep(1.5) - self.set_root(self.root) - except Exception as e: - log.critical(e) - self.shutdown() - return - - @staticmethod - def cleanup_temporary_files(files): - """ Clean up temporary simulation files """ - for f in files: - log.verbose("cleaning up files: {}".format(f)) - shutil.rmtree(f, ignore_errors=True) - - def load_motes_from_topology_file(self): - """ Import the number of motes from the topology file. """ - topo_config = self._load_saved_topology() - if topo_config is None: - return - - # set/override the amount of simulated motes and set temporary topology to fully-meshed (otherwise - # connections might be deleted due to pdr == 0 in Pister-hack model) - self.simulator_mode = len(topo_config['motes']) - self.sim_topology = 'fully-meshed' - - def load_topology(self): - """ Import the network topology from a json file. """ - if not self.simulator_mode: - log.error("Only supported in simulator mode") - return - - log.success("loading topology from file.") - - topo_config = self._load_saved_topology() - if topo_config is None: - return - - # delete each connections automatically established during motes creation - connections_to_delete = self.simengine.propagation.retrieve_connections() - for co in connections_to_delete: - from_mote = int(co['fromMote']) - to_mote = int(co['toMote']) - self.simengine.propagation.delete_connection(from_mote, to_mote) - - motes = topo_config['motes'] - for mote in motes: - mh = self.simengine.get_mote_handler_by_id(mote['id']) - mh.set_location(mote['lat'], mote['lon']) - - # implements new connections - connect = topo_config['connections'] - for co in connect: - from_mote = int(co['fromMote']) - to_mote = int(co['toMote']) - pdr = float(co['pdr']) - self.simengine.propagation.create_connection(from_mote, to_mote) - self.simengine.propagation.update_connection(from_mote, to_mote, pdr) - - try: - # recover dagroot - self.root = topo_config['DAGroot'] - except KeyError: - pass - - def _load_saved_topology(self): - """ Check if we can find the file locally, if not search the example directory. """ - - local_path = '/'.join(('topologies', str(self.topo_file))) - - try: - if os.path.isfile(self.topo_file): - filename = self.topo_file - f = open(filename, 'r') - elif pkg_resources.resource_exists(PACKAGE_NAME, local_path): - f = pkg_resources.resource_stream(PACKAGE_NAME, local_path) - else: - log.error('could not open file: {}'.format(self.topo_file)) - return - - topo_config = json.load(f) - f.close() - except (IOError, ValueError) as err: - log.error('failed to load topology from file: {}'.format(err)) - return - - return topo_config - - def copy_sim_fw(self): - """ - Copy simulation files from build folder in openwsn-fw to a temporary directory. - The latter is subsequently added to the python path. - """ - - hosts = ['amd64-linux', 'x86-linux', 'amd64-windows', 'x86-windows'] - if os.name == 'nt': - index = 2 if platform.architecture()[0] == '64bit' else 3 - else: - index = 0 if platform.architecture()[0] == '64bit' else 1 - - host = hosts[index] - - # in openwsn-fw, directory containing 'openwsnmodule_obj.h' - inc_dir = os.path.join(self.fw_path, 'bsp', 'boards', 'python') - if not os.path.exists(inc_dir): - log.critical("path '{}' does not exist".format(inc_dir)) - return - - # in openwsn-fw, directory containing extension library - lib_dir = os.path.join(self.fw_path, 'build', 'python_gcc', 'projects', 'common') - if not os.path.exists(lib_dir): - log.critical("path '{}' does not exist".format(lib_dir)) - return - - temp_dir = tempfile.mkdtemp() - - # Build source and destination pathnames. - arch_and_os = host.split('-') - lib_ext = 'pyd' if arch_and_os[1] == 'windows' else 'so' - source_name = 'oos_openwsn.{0}'.format(lib_ext) - dest_name = 'oos_openwsn-{0}.{1}'.format(arch_and_os[0], lib_ext) - dest_dir = os.path.join(temp_dir, arch_and_os[1]) - - try: - shutil.copy(os.path.join(inc_dir, 'openwsnmodule_obj.h'), temp_dir) - except IOError: - log.critical("could not find {} file".format('openwsnmodule_obj.h')) - return - - log.verbose( - "copying '{}' to temporary dir '{}'".format(os.path.join(inc_dir, 'openwsnmodule_obj.h'), temp_dir)) - - try: - os.makedirs(os.path.join(dest_dir)) - except OSError: - pass - - try: - shutil.copy(os.path.join(lib_dir, source_name), os.path.join(dest_dir, dest_name)) - except IOError: - log.critical("Could not find: {}".format(str(os.path.join(lib_dir, source_name)))) - return - - log.verbose( - "copying '{}' to '{}'".format(os.path.join(lib_dir, source_name), os.path.join(dest_dir, dest_name))) - - # Copy the module directly to sim_files directory if it matches this host. - if arch_and_os[0] == 'amd64': - arch_match = platform.architecture()[0] == '64bit' - else: - arch_match = platform.architecture()[0] == '32bit' - if arch_and_os[1] == 'windows': - os_match = os.name == 'nt' - else: - os_match = os.name == 'posix' - - if arch_match and os_match: - try: - shutil.copy(os.path.join(lib_dir, source_name), temp_dir) - except IOError: - log.critical("could not find {}".format(str(os.path.join(lib_dir, source_name)))) - return - - return temp_dir - - def extract_stack_defines(self): - """ Extract firmware definitions for the OpenVisualizer parser from the OpenWSN-FW files. """ - log.info('extracting firmware definitions.') - definitions = { - "components": extract_component_codes(os.path.join(self.fw_path, 'inc', 'opendefs.h')), - "log_descriptions": extract_log_descriptions(os.path.join(self.fw_path, 'inc', 'opendefs.h')), - "sixtop_returncodes": extract_6top_rcs(os.path.join(self.fw_path, 'openstack', '02b-MAChigh', 'sixtop.h')), - "sixtop_states": extract_6top_states(os.path.join(self.fw_path, 'openstack', '02b-MAChigh', 'sixtop.h')), - } - - return definitions - - # ======================== RPC functions ================================ - - def shutdown(self): - """ Closes all thread-based components. """ - log.debug('RPC: {}'.format(self.shutdown.__name__)) - - self.opentun.close() - self.rpl.close() - self.jrc.close() - for probe in self.mote_probes: - probe.close() - if probe.daemon is False: - probe.join() - - if self.simulator_mode: - OpenVisualizerServer.cleanup_temporary_files([self.temp_dir]) - - os.kill(os.getpid(), signal.SIGTERM) - - def get_dag(self): - return self.topology.get_dag() - - def boot_motes(self, addresses): - # boot all emulated motes, if applicable - log.debug('RPC: {}'.format(self.boot_motes.__name__)) - - if self.simulator_mode: - self.simengine.pause() - now = self.simengine.timeline.get_current_time() - if len(addresses) == 1 and addresses[0] == "all": - for rank in range(self.simengine.get_num_motes()): - mh = self.simengine.get_mote_handler(rank) - if not mh.hw_supply.mote_on: - self.simengine.timeline.schedule_event(now, mh.get_id(), mh.hw_supply.switch_on, - mh.hw_supply.INTR_SWITCHON) - else: - raise Fault(faultCode=-1, faultString="Mote already booted.") - else: - for address in addresses: - try: - address = int(address) - except ValueError: - raise Fault(faultCode=-1, faultString="Invalid mote address: {}".format(address)) - - for rank in range(self.simengine.get_num_motes()): - mh = self.simengine.get_mote_handler(rank) - if address == mh.get_id(): - if not mh.hw_supply.mote_on: - self.simengine.timeline.schedule_event(now, mh.get_id(), mh.hw_supply.switch_on, - mh.hw_supply.INTR_SWITCHON) - else: - raise Fault(faultCode=-1, faultString="Mote already booted.") - - self.simengine.resume() - return True - else: - raise Fault(faultCode=-1, faultString="Method not supported on real hardware") - - def set_root(self, port_or_address): - log.debug('RPC: {}'.format(self.set_root.__name__)) - - mote_dict = self.get_mote_dict() - if port_or_address in mote_dict: - port = mote_dict[port_or_address] - elif port_or_address in mote_dict.values(): - port = port_or_address - else: - raise Fault(faultCode=-1, faultString="Unknown port or address: {}".format(port_or_address)) - - for ms in self.mote_states: - try: - if ms.mote_connector.serialport == port: - ms.trigger_action(MoteState.TRIGGER_DAGROOT) - self.dagroot = ms.get_state_elem(ms.ST_IDMANAGER).get_16b_addr() - log.success('Setting mote {} as root'.format(''.join(['%02x' % b for b in self.dagroot]))) - return True - except ValueError as err: - log.error(err) - break - raise Fault(faultCode=-1, faultString="Could not set {} as root".format(port)) - - def get_dagroot(self): - log.debug('RPC: {}'.format(self.get_dagroot.__name__)) - return self.dagroot - - def get_mote_state(self, mote_id): - """ - Returns the MoteState object for the provided connected mote. - :param mote_id: 16-bit ID of mote - :rtype: MoteState or None if not found - """ - log.debug('RPC: {}'.format(self.get_mote_state.__name__)) - - for ms in self.mote_states: - id_manager = ms.get_state_elem(ms.ST_IDMANAGER) - if id_manager and id_manager.get_16b_addr(): - addr = ''.join(['%02x' % b for b in id_manager.get_16b_addr()]) - if addr == mote_id: - return OpenVisualizerServer._extract_mote_states(ms) - else: - error_msg = "Unknown mote ID: {}".format(mote_id) - log.warning("returning fault: {}".format(error_msg)) - raise Fault(faultCode=-1, faultString=error_msg) - - def enable_wireshark_debug(self): - if isinstance(self.opentun, OpenTunNull): - raise Fault(faultCode=-1, faultString="Wireshark debugging requires opentun to be active on the server") - else: - self.ebm.wireshark_debug_enabled = True - - def disable_wireshark_debug(self): - if isinstance(self.opentun, OpenTunNull): - raise Fault(faultCode=-1, faultString="Wireshark debugging requires opentun to be active on the server") - else: - self.ebm.wireshark_debug_enabled = False - - def get_wireshark_debug(self): - return self.ebm.wireshark_debug_enabled - - def get_ebm_stats(self): - return self.ebm.get_stats() - - def get_motes_connectivity(self): - motes = [] - states = [] - edges = [] - src_s = None - - for ms in self.mote_states: - id_manager = ms.get_state_elem(ms.ST_IDMANAGER) - if id_manager and id_manager.get_16b_addr(): - src_s = ''.join(['%02X' % b for b in id_manager.get_16b_addr()]) - motes.append(src_s) - neighbor_table = ms.get_state_elem(ms.ST_NEIGHBORS) - for neighbor in neighbor_table.data: - if len(neighbor.data) == 0: - break - if neighbor.data[0]['used'] == 1 and neighbor.data[0]['parentPreference'] == 1: - dst_s = ''.join(['%02X' % b for b in neighbor.data[0]['addr'].addr[-2:]]) - edges.append({'u': src_s, 'v': dst_s}) - break - - motes = list(set(motes)) - for mote in motes: - d = {'id': mote, 'value': {'label': mote}} - states.append(d) - return states, edges - - def update_network_topology(self, connections): - connections = json.loads(connections) - - if not self.simulator_mode: - return False - - for (_, v) in connections.items(): - mh = self.simengine.get_mote_handler_by_id(v['id']) - mh.set_location(v['lat'], v['lon']) - - return True - - def get_network_topology(self): - motes = [] - rank = 0 - - if not self.simulator_mode: - return {} - - while True: - try: - mh = self.simengine.get_mote_handler(rank) - mote_id = mh.get_id() - (lat, lon) = mh.get_location() - motes += [{'id': mote_id, 'lat': lat, 'lon': lon}] - rank += 1 - except IndexError: - break - - # connections - connections = self.simengine.propagation.retrieve_connections() - - data = {'motes': motes, 'connections': connections} - return data - - def create_motes_connection(self, from_mote, to_mote): - if not self.simulator_mode: - return False - - self.simengine.propagation.create_connection(from_mote, to_mote) - return True - - def update_motes_connection(self, from_mote, to_mote, pdr): - if not self.simulator_mode: - return False - - self.simengine.propagation.update_connection(from_mote, to_mote, pdr) - return True - - def delete_motes_connection(self, from_mote, to_mote): - if not self.simulator_mode: - return False - - self.simengine.propagation.delete_connection(from_mote, to_mote) - return True - - def retrieve_routing_path(self, destination): - route = self._dispatch_and_get_result(signal='getSourceRoute', data=destination) - route = [r[-1] for r in route] - data = {'route': route} - - return data - - def get_mote_dict(self): - """ Returns a dictionary with key-value entry: (mote_id: serialport) """ - log.debug('RPC: {}'.format(self.get_mote_dict.__name__)) - - mote_dict = {} - - for ms in self.mote_states: - addr = ms.get_state_elem(motestate.MoteState.ST_IDMANAGER).get_16b_addr() - if addr: - mote_dict[''.join(['%02x' % b for b in addr])] = ms.mote_connector.serialport - else: - mote_dict[ms.mote_connector.serialport] = None - - return mote_dict - - @staticmethod - def _extract_mote_states(ms): - states = { - ms.ST_IDMANAGER: ms.get_state_elem(ms.ST_IDMANAGER).to_json('data'), - ms.ST_ASN: ms.get_state_elem(ms.ST_ASN).to_json('data'), - ms.ST_ISSYNC: ms.get_state_elem(ms.ST_ISSYNC).to_json('data'), - ms.ST_MYDAGRANK: ms.get_state_elem(ms.ST_MYDAGRANK).to_json('data'), - ms.ST_KAPERIOD: ms.get_state_elem(ms.ST_KAPERIOD).to_json('data'), - ms.ST_OUPUTBUFFER: ms.get_state_elem(ms.ST_OUPUTBUFFER).to_json('data'), - ms.ST_BACKOFF: ms.get_state_elem(ms.ST_BACKOFF).to_json('data'), - ms.ST_MACSTATS: ms.get_state_elem(ms.ST_MACSTATS).to_json('data'), - ms.ST_SCHEDULE: ms.get_state_elem(ms.ST_SCHEDULE).to_json('data'), - ms.ST_QUEUE: ms.get_state_elem(ms.ST_QUEUE).to_json('data'), - ms.ST_NEIGHBORS: ms.get_state_elem(ms.ST_NEIGHBORS).to_json('data'), - ms.ST_JOINED: ms.get_state_elem(ms.ST_JOINED).to_json('data'), - ms.ST_MSF: ms.get_state_elem(ms.ST_MSF).to_json('data'), - } - return states - - -def _add_iotlab_parser_args(parser): - """ Adds arguments specific to IotLab Support """ - description = """ - Commands for motes running on IotLab (iot-lab_A8-M3 excluded). - For large experiments run directly on the ssh frontend to not open multiple - connections. - When not already authenticated (use iotlab-auth) and iotlab account - USERNAME and PASSWORD must be provided. - """ - iotlab_parser = parser.add_argument_group('iotlab', description) - iotlab_parser.add_argument( - '--iotlab-motes', - default='', - type=str, - nargs='+', - help='comma-separated list of IoT-LAB motes (e.g. "wsn430-9,wsn430-34,wsn430-3")', - ) - common.add_auth_arguments(iotlab_parser, False) - - -def _add_parser_args(parser): - """ Adds arguments specific to the OpenVisualizer application """ - parser.add_argument( - '-s', '--sim', - dest='simulator_mode', - default=0, - type=int, - help='Run a simulation with the given amount of emulated motes.', - ) - - parser.add_argument( - '--fw-path', - dest='fw_path', - type=str, - help='Provide the path to the OpenWSN firmware. This option overrides the optional OPENWSN_FW_BASE environment ' - 'variable.', - ) - - parser.add_argument( - '-o', '--simtopo', - dest='sim_topology', - action='store', - help='Force a predefined topology (linear or fully-meshed). Only available in simulation mode.', - ) - - parser.add_argument( - '--root', - dest='set_root', - action='store', - type=str, - help='Set a simulated or hardware mote as root, specify the mote\'s port or address.', - ) - - parser.add_argument( - '-d', '--wireshark-debug', - dest='debug', - default=False, - action='store_true', - help='Enables debugging with wireshark (requires opentun).', - ) - - parser.add_argument( - '-l', '--lconf', - dest='lconf', - action='store', - help='Provide a logging configuration.', - ) - - parser.add_argument( - '--vcdlog', - dest='vcdlog', - default=False, - action='store_true', - help='Use VCD logger.', - ) - - parser.add_argument( - '-z', '--pagezero', - dest='use_page_zero', - default=False, - action='store_true', - help='Use page number 0 in page dispatch (only works with one-hop).', - ) - - parser.add_argument( - '-b', '--opentestbed', - dest='testbed_motes', - default=False, - action='store_true', - help='Connect to motes from opentestbed over the MQTT server (see option \'--mqtt-broker\')', - ) - - parser.add_argument( - '--mqtt-broker', - dest='mqtt_broker', - default='argus.paris.inria.fr', - action='store', - help='MQTT broker address to use', - ) - - parser.add_argument( - '--opentun', - dest='opentun', - default=False, - action='store_true', - help='Use a TUN device to route packets to the Internet.', - ) - - parser.add_argument( - '-H', - '--host', - dest='host', - default='localhost', - action='store', - help='Host address for the RPC address.', - ) - - parser.add_argument( - '-P', - '--port', - dest='port', - type=int, - default=9000, - action='store', - help='Port number for the RPC server.', - ) - - parser.add_argument( - '--port-mask', - dest='port_mask', - type=str, - action='store', - nargs='+', - help='Port mask for serial port detection, e.g, /dev/tty/USB*.', - ) - - parser.add_argument( - '--baudrate', - dest='baudrate', - default=[115200], - action='store', - nargs='+', - help='List of baudrates to probe for, e.g 115200 500000.', - ) - - parser.add_argument( - '--no-boot', - dest='auto_boot', - default=True, - action='store_false', - help='Disables automatic boot of emulated motes.', - ) - - parser.add_argument( - '--load-topology', - dest='topo_file', - type=str, - action='store', - help='Provide a topology for the simulation, when in use this option will override all the other ' - 'simulation options.', - ) - - -# ============================ main ============================================ - -def main(): - """ Entry point for the OpenVisualizer server. """ - - banner = [""] - banner += [" ___ _ _ _ ___ _ _ "] - banner += ["| . | ___ ___ ._ _ | | | |/ __>| \\ |"] - banner += ["| | || . \\/ ._>| ' || | | |\\__ \\| |"] - banner += ["`___'| _/\\___.|_|_||__/_/ <___/|_\\_|"] - banner += [" |_| openwsn.org"] - banner += [""] - - print '\n'.join(banner) - - parser = ArgumentParser() - _add_parser_args(parser) - _add_iotlab_parser_args(parser) - args = parser.parse_args() - - # create directories to store logs and application data - try: - os.makedirs(appdirs.user_log_dir(APPNAME)) - except OSError as err: - if err.errno != 17: - log.critical(err) - return - - try: - os.makedirs(appdirs.user_data_dir(APPNAME)) - except OSError as err: - if err.errno != 17: - log.critical(err) - return - - # loading the logging configuration - if not args.lconf and pkg_resources.resource_exists(PACKAGE_NAME, DEFAULT_LOGGING_CONF): - try: - logging.config.fileConfig(pkg_resources.resource_stream(PACKAGE_NAME, DEFAULT_LOGGING_CONF)) - except IOError as err: - log.critical("permission error: {}".format(err)) - return - log.verbose("loading logging configuration: {}".format(DEFAULT_LOGGING_CONF)) - elif args.lconf: - logging.config.fileConfig(args.lconf) - log.verbose("loading logging configuration: {}".format(args.lconf)) - else: - log.error("could not load logging configuration.") - - options = ['log files directory = {0}'.format(appdirs.user_log_dir(APPNAME)), - 'data files directory = {0}'.format(appdirs.user_data_dir(APPNAME)), - 'host address server = {0}'.format(args.host), - 'port number server = {0}'.format(args.port)] - - if args.fw_path: - options.append('firmware path = {0}'.format(args.fw_path)) - else: - try: - options.append('firmware path = {0}'.format(os.environ['OPENWSN_FW_BASE'])) - except KeyError: - log.warning( - "unknown openwsn-fw location, specify with option '--fw-path' or by exporting the OPENWSN_FW_BASE " - "environment variable.") - - if args.simulator_mode: - options.append('simulation = {0}'.format(args.simulator_mode)) - if args.sim_topology: - options.append('simulation topology = {0}'.format(args.sim_topology)) - else: - options.append('simulation topology = {0}'.format('Pister-hack')) - - options.append('auto-boot sim motes = {0}'.format(args.auto_boot)) - - if args.set_root: - options.append('set root = {0}'.format(args.set_root)) - - if args.opentun: - options.append('opentun = {0}'.format('True')) - if args.debug: - options.append('wireshark debug = {0}'.format(True)) - - options.append('use page zero = {0}'.format(args.use_page_zero)) - options.append('use VCD logger = {0}'.format(args.vcdlog)) - - if not args.simulator_mode and args.port_mask: - options.append('serial port mask = {0}'.format(args.port_mask)) - if not args.simulator_mode and args.baudrate: - options.append('baudrates to probe = {0}'.format(args.baudrate)) - - if args.testbed_motes: - options.append('opentestbed = {0}'.format(args.testbed_motes)) - options.append('mqtt broker = {0}'.format(args.mqtt_broker)) - - if args.topo_file: - options.append('load topology from file = {0}'.format(args.topo_file)) - - if args.topo_file and (args.simulator_mode or args.sim_topology or args.set_root): - log.warning("simulation options or root option might be overwritten by the configuration in '{}'".format( - args.topo_file)) - - log.info('initializing OV Server with options:\n\t- {0}'.format('\n\t- '.join(options))) - - log.debug('sys.path:\n\t{0}'.format('\n\t'.join(str(p) for p in sys.path))) - - server = OpenVisualizerServer( - host=args.host, - port=args.port, - simulator_mode=args.simulator_mode, - debug=args.debug, - use_page_zero=args.use_page_zero, - vcdlog=args.vcdlog, - sim_topology=args.sim_topology, - port_mask=args.port_mask, - baudrate=args.baudrate, - testbed_motes=args.testbed_motes, - mqtt_broker=args.mqtt_broker, - opentun=args.opentun, - fw_path=args.fw_path, - auto_boot=args.auto_boot, - root=args.set_root, - topo_file=args.topo_file, - iotlab_motes=args.iotlab_motes, - iotlab_user=args.username, - iotlab_passwd=args.password, - ) - - try: - log.info("starting RPC server") - server.serve_forever() - except KeyboardInterrupt: - pass - - server.shutdown() diff --git a/openvisualizer/motehandler/moteconnector/moteconnector.py b/openvisualizer/motehandler/moteconnector/moteconnector.py index 24384694..25da121c 100644 --- a/openvisualizer/motehandler/moteconnector/moteconnector.py +++ b/openvisualizer/motehandler/moteconnector/moteconnector.py @@ -21,18 +21,17 @@ class MoteConnector(EventBusClient): - def __init__(self, mote_probe, stack_defines, mqtt_broker): + def __init__(self, mote_probe, mqtt_broker): # log log.debug("create instance") self.mote_probe = mote_probe - self.stack_defines = stack_defines # store params self.serialport = self.mote_probe.portname # local variables - self.parser = openparser.OpenParser(mqtt_broker, stack_defines, self.serialport) + self.parser = openparser.OpenParser(mqtt_broker, self.serialport) self.state_lock = threading.Lock() self.network_prefix = None self._subscribed_data_for_dagroot = False diff --git a/openvisualizer/motehandler/moteconnector/openparser/openparser.py b/openvisualizer/motehandler/moteconnector/openparser/openparser.py index 16c3ecde..96f0f5b3 100644 --- a/openvisualizer/motehandler/moteconnector/openparser/openparser.py +++ b/openvisualizer/motehandler/moteconnector/openparser/openparser.py @@ -38,7 +38,7 @@ class OpenParser(parser.Parser): SERFRAME_ACTION_NO = ord('N') SERFRAME_ACTION_TOGGLE = ord('T') - def __init__(self, mqtt_broker, stack_defines, mote_port): + def __init__(self, mqtt_broker, mote_port): # log log.debug("create instance") @@ -47,12 +47,12 @@ def __init__(self, mqtt_broker, stack_defines, mote_port): # subparser objects self.parser_status = parserstatus.ParserStatus() - self.parser_verbose = ParserLogs(self.SERFRAME_MOTE2PC_VERBOSE, stack_defines) - self.parser_info = ParserLogs(self.SERFRAME_MOTE2PC_INFO, stack_defines) - self.parser_warning = ParserLogs(self.SERFRAME_MOTE2PC_WARNING, stack_defines) - self.parser_success = ParserLogs(self.SERFRAME_MOTE2PC_SUCCESS, stack_defines) - self.parser_error = ParserLogs(self.SERFRAME_MOTE2PC_ERROR, stack_defines) - self.parser_critical = ParserLogs(self.SERFRAME_MOTE2PC_CRITICAL, stack_defines) + self.parser_verbose = ParserLogs(self.SERFRAME_MOTE2PC_VERBOSE) + self.parser_info = ParserLogs(self.SERFRAME_MOTE2PC_INFO) + self.parser_warning = ParserLogs(self.SERFRAME_MOTE2PC_WARNING) + self.parser_success = ParserLogs(self.SERFRAME_MOTE2PC_SUCCESS) + self.parser_error = ParserLogs(self.SERFRAME_MOTE2PC_ERROR) + self.parser_critical = ParserLogs(self.SERFRAME_MOTE2PC_CRITICAL) self.parser_data = parserdata.ParserData(mqtt_broker, mote_port) self.parser_packet = parserpacket.ParserPacket() self.parser_printf = parserprintf.ParserPrintf() diff --git a/openvisualizer/motehandler/moteconnector/openparser/parserdata.py b/openvisualizer/motehandler/moteconnector/openparser/parserdata.py index d1aa0e4e..e2fa474a 100644 --- a/openvisualizer/motehandler/moteconnector/openparser/parserdata.py +++ b/openvisualizer/motehandler/moteconnector/openparser/parserdata.py @@ -78,7 +78,7 @@ def parse_input(self, data): # asn comes in the next 5bytes. asn_bytes = data[2:7] - (self._asn) = struct.unpack('HBBhH', ''.join([chr(c) for c in data])) + mote_id, component, error_code, arg1, arg2 = struct.unpack('>HBBhH', bytes(data)) except struct.error: raise ParserException(ParserException.ExceptionType.DESERIALIZE.value, "could not extract data from {0}".format(data)) @@ -68,8 +83,8 @@ def parse_input(self, data): if error_code == 0x25: # replace args of sixtop command/return code id by string - arg1 = self.stack_defines["sixtop_returncodes"][arg1] - arg2 = self.stack_defines["sixtop_states"][arg2] + arg1 = self.sixtop_rcs[str(arg1)] + arg2 = self.sixtop_states[str(arg2)] # turn into string output = "{MOTEID:x} [{COMPONENT}] {ERROR_DESC}".format( @@ -100,13 +115,13 @@ def parse_input(self, data): def _translate_component(self, component): try: - return self.stack_defines["components"][component] + return self.component_codes[str(component)] except KeyError: return "unknown component code {0}".format(component) def _translate_log_description(self, error_code, arg1, arg2): try: - return self.stack_defines["log_descriptions"][error_code].format( + return self.log_descriptions[str(error_code)].format( arg1, arg2) except KeyError: return "unknown error {0} arg1={1} arg2={2}".format(error_code, arg1, arg2) diff --git a/openvisualizer/motehandler/moteconnector/openparser/parserstatus.py b/openvisualizer/motehandler/moteconnector/openparser/parserstatus.py index 9cbfe299..0d24122a 100644 --- a/openvisualizer/motehandler/moteconnector/openparser/parserstatus.py +++ b/openvisualizer/motehandler/moteconnector/openparser/parserstatus.py @@ -92,16 +92,6 @@ def __init__(self): self._add_fields_parser( 3, 3, - 'OutputBuffer', - ' bool: + """ + Attaches to the emulated mote's uart queue. + + :return: True + """ + self._serial = self.emulated_mote.uart + + if isinstance(self._serial, Uart): + return True + else: + return False diff --git a/openvisualizer/motehandler/moteprobe/iotlabmoteprobe.py b/openvisualizer/motehandler/moteprobe/iotlabmoteprobe.py index c275e649..0a94ba16 100644 --- a/openvisualizer/motehandler/moteprobe/iotlabmoteprobe.py +++ b/openvisualizer/motehandler/moteprobe/iotlabmoteprobe.py @@ -6,19 +6,18 @@ import logging import os -import re import signal import socket import time from contextlib import closing +from typing import Optional, List import sshtunnel -from iotlabcli import auth -from moteprobe import MoteProbe, MoteProbeNoData +from openvisualizer.motehandler.moteprobe.moteprobe import MoteProbe, MoteProbeNoData log = logging.getLogger('MoteProbe') -log.setLevel(logging.ERROR) +log.setLevel(logging.INFO) log.addHandler(logging.NullHandler()) @@ -31,28 +30,42 @@ class IotlabMoteProbe(MoteProbe): IOTLAB_FRONTEND_BASE_URL = 'iot-lab.info' - def __init__(self, iotlab_mote, iotlab_user=None, iotlab_passwd=None): - self.iotlab_mote = iotlab_mote + def __init__(self, + iotlab_mote: str, + iotlab_user: str, + iotlab_key_file: str, + iotlab_site: Optional[str] = None, + xonxoff: bool = True, + debug: bool = False): - if self.IOTLAB_FRONTEND_BASE_URL in self.iotlab_mote: - # Recover user credentials - self.iotlab_user, self.iotlab_passwd = auth.get_user_credentials(iotlab_user, iotlab_passwd) + if iotlab_site is not None: + self.iotlab_mote = "".join([iotlab_mote, ".", iotlab_site, ".", self.IOTLAB_FRONTEND_BASE_URL]) + else: + self.iotlab_mote = iotlab_mote - # match the site from the mote's address - reg = r'[0-9a-zA-Z\-]+-\d+\.([a-z]+)' - match = re.search(reg, iotlab_mote) - self.iotlab_site = match.group(1) + log.info(self.iotlab_mote) + self.debug = debug + self.iotlab_user = iotlab_user + self.iotlab_key_file = iotlab_key_file + self.iotlab_site = iotlab_site self.iotlab_tunnel = None + self.xonxoff = xonxoff + self._cts = False self.socket = None # initialize the parent class - MoteProbe.__init__(self, portname=iotlab_mote) + super().__init__(portname=iotlab_mote, daemon=True) # ======================== public ================================== @classmethod - def probe_iotlab_motes(cls, iotlab_motes, iotlab_user, iotlab_passwd): + def probe_iotlab_motes(cls, + iotlab_motes: List[str], + iotlab_user: str, + iotlab_key_file: str, + iotlab_site: Optional[str] = None, + debug=False): mote_probes = [] probe = None log.debug("probing motes: {}".format(iotlab_motes)) @@ -60,13 +73,11 @@ def probe_iotlab_motes(cls, iotlab_motes, iotlab_user, iotlab_passwd): for mote in iotlab_motes: log.debug("probe {}".format(mote)) try: - probe = cls( - iotlab_mote=mote, - iotlab_user=iotlab_user, - iotlab_passwd=iotlab_passwd) + probe = cls(mote, iotlab_user, iotlab_key_file, iotlab_site, debug) + while probe.socket is None and probe.isAlive(): pass - if probe.test_serial(pkts=2): + if probe.test_serial(): log.success("{} Ok.".format(probe._portname)) mote_probes.append(probe) else: @@ -106,15 +117,40 @@ def _get_free_port(): s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) return s.getsockname()[1] + def _set_cts(self, data): + xoff_idx = -1 + xon_idx = -1 + if chr(self.XON) in data: + xon_idx = len(data) - 1 - data[::-1].index(chr(self.XON)) + if chr(self.XOFF) in data: + xoff_idx = len(data) - 1 - data[::-1].index(chr(self.XOFF)) + if xoff_idx > xon_idx: + self._cts = False + elif xon_idx > xoff_idx: + self._cts = True + def _rcv_data(self, rx_bytes=1024): try: - return self.socket.recv(rx_bytes) + data = self.socket.recv(rx_bytes) + if self.xonxoff: + self._set_cts(data) + return data except socket.timeout: raise MoteProbeNoData - def _send_data(self, data): - hdlc_data = self.hdlc.hdlcify(data) - self.socket.send(hdlc_data) + def _send_data(self, data: str): + if self.socket is None: + return + + hdlc_data = bytearray([ord(b) for b in self.hdlc.hdlcify(data)]) + hdlc_len = len(hdlc_data) + bytes_written = 0 + + while not self.quit and bytes_written != hdlc_len: + if self.xonxoff and not self._cts: + continue + else: + bytes_written += self.socket.send(hdlc_data) def _detach(self): if self.socket is not None: @@ -125,26 +161,45 @@ def _detach(self): log.debug('stopping ssh tunnel to {}'.format(self._portname)) self.iotlab_tunnel.stop() - def _attach(self): - if hasattr(self, 'iotlab_site'): + def _attach(self) -> bool: + """ + Tries to ssh into the IoT-LAB frontend and connect to the running motes' serial port. + + :return: True if successful else False + """ + + if self.IOTLAB_FRONTEND_BASE_URL in self.iotlab_mote: port = self._get_free_port() sshtunnel.SSH_TIMEOUT = self.IOTLAB_SSH_TIMEOUT - self.iotlab_tunnel = sshtunnel.open_tunnel('{}.{}'.format(self.iotlab_site, self.IOTLAB_FRONTEND_BASE_URL), - ssh_username=self.iotlab_user, - ssh_password=self.iotlab_passwd, - remote_bind_address=( - self.iotlab_mote, self.IOTLAB_MOTE_TCP_PORT), - local_bind_address=('0.0.0.0', port)) - self.iotlab_tunnel.start() + + server = '{}.{}'.format(self.iotlab_site, self.IOTLAB_FRONTEND_BASE_URL) + + log.info(f'Opening SSH tunnel to: {server}') + + try: + self.iotlab_tunnel = sshtunnel.open_tunnel(server, + ssh_username=self.iotlab_user, + ssh_private_key=self.iotlab_key_file, + debug_level=self.debug, + remote_bind_address=( + self.iotlab_mote, self.IOTLAB_MOTE_TCP_PORT), + local_bind_address=('0.0.0.0', port)) + self.iotlab_tunnel.start() + except (sshtunnel.BaseSSHTunnelForwarderError, ValueError): + log.error("Failed to open ssh tunnel, perhaps invalid credentials? Add '-d' to enable debugging.") + return False + time.sleep(0.1) log.debug('{}: ssh tunnel started'.format(self.iotlab_mote)) self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.settimeout(self.IOTLAB_SOCKET_TIMEOUT) + # self.socket.settimeout(self.IOTLAB_SOCKET_TIMEOUT) self.socket.connect(('127.0.0.1', port)) log.debug('{}: socket connected'.format(self.iotlab_mote)) else: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((self.iotlab_mote, self.IOTLAB_MOTE_TCP_PORT)) + + return True diff --git a/openvisualizer/motehandler/moteprobe/mockmoteprobe.py b/openvisualizer/motehandler/moteprobe/mockmoteprobe.py index 05192847..96bdab7a 100644 --- a/openvisualizer/motehandler/moteprobe/mockmoteprobe.py +++ b/openvisualizer/motehandler/moteprobe/mockmoteprobe.py @@ -4,7 +4,8 @@ # Released under the BSD 3-Clause license as published at the link below. # https://openwsn.atlassian.net/wiki/display/OW/License -from moteprobe import MoteProbe + +from openvisualizer.motehandler.moteprobe.moteprobe import MoteProbe # ============================ class =================================== @@ -17,7 +18,7 @@ def __init__(self, mock_name, daemon=False, buffer=None): self._buffer = buffer # initialize the parent class - MoteProbe.__init__(self, portname=mock_name, daemon=daemon) + super().__init__(portname=mock_name, daemon=daemon) self.send_to_parser = self.receive_data_from_mote_probe self.send_to_parser_data = None diff --git a/openvisualizer/motehandler/moteprobe/moteprobe.py b/openvisualizer/motehandler/moteprobe/moteprobe.py index 964b4789..0cd6aace 100644 --- a/openvisualizer/motehandler/moteprobe/moteprobe.py +++ b/openvisualizer/motehandler/moteprobe/moteprobe.py @@ -6,7 +6,6 @@ import abc import logging -import sys import threading import time @@ -78,9 +77,12 @@ def __init__(self, portname, daemon=False): def run(self): try: - log.debug("start running") - log.debug("attach to port {0}".format(self._portname)) - self._attach() + if log.isEnabledFor(logging.DEBUG): + log.debug("start running") + log.debug("attaching to port {0}".format(self._portname)) + + if not self._attach(): + return while not self.quit: # read bytes from serial pipe try: @@ -88,18 +90,15 @@ def run(self): except MoteProbeNoData: continue except Exception as err: - log.warning(err) + log.error(err) time.sleep(1) break else: self._parse_bytes(rx_bytes) - if hasattr(self, 'emulated_mote'): - self.serial.done_reading() - log.warning('{}; exit loop'.format(self._portname)) + log.info('{}; exit loop'.format(self._portname)) except Exception as err: err_msg = format_crash_message(self.name, err) log.critical(err_msg) - sys.exit(-1) finally: self._detach() @@ -123,21 +122,21 @@ def test_serial(self, pkts=1, timeout=2): tester = SerialTester(self) tester.set_num_test_pkt(pkts) tester.set_timeout(timeout) - tester.test(blocking=True) + tester.test() return tester.get_stats()['numOk'] >= 1 # ======================== private ================================= @abc.abstractmethod - def _attach(self): + def _attach(self) -> bool: raise NotImplementedError("Should be implemented by child class") @abc.abstractmethod - def _detach(self): + def _detach(self) -> None: raise NotImplementedError("Should be implemented by child class") @abc.abstractmethod - def _send_data(self, data): + def _send_data(self, data: str): raise NotImplementedError("Should be implemented by child class") @abc.abstractmethod @@ -177,42 +176,41 @@ def _rx_buf_add(self, byte): elif byte != chr(self.XON) and byte != chr(self.XOFF): self.rx_buf += byte - def _parse_bytes(self, octets): + def _parse_bytes(self, octets: bytes): """ Parses bytes received from serial pipe """ for byte in octets: if not self.receiving: - if self.hdlc_flag and byte != self.hdlc.HDLC_FLAG: + if self.hdlc_flag and chr(byte) != self.hdlc.HDLC_FLAG: # start of frame if log.isEnabledFor(logging.DEBUG): log.debug("%s: start of HDLC frame %s %s", self.name, format_string_buf(self.hdlc.HDLC_FLAG), - format_string_buf(byte), - ) + format_string_buf(chr(byte))) self.receiving = True # discard received self.hdlc_flag self.hdlc_flag = False self.xonxoff_escaping = False self.rx_buf = self.hdlc.HDLC_FLAG - self._rx_buf_add(byte) - elif byte == self.hdlc.HDLC_FLAG: + self._rx_buf_add(chr(byte)) + elif chr(byte) == self.hdlc.HDLC_FLAG: # received hdlc flag self.hdlc_flag = True else: # drop garbage pass else: - if byte != self.hdlc.HDLC_FLAG: + if chr(byte) != self.hdlc.HDLC_FLAG: # middle of frame - self._rx_buf_add(byte) + self._rx_buf_add(chr(byte)) else: # end of frame, received self.hdlc_flag if log.isEnabledFor(logging.DEBUG): - log.debug("{}: end of hdlc frame {}".format(self.name, format_string_buf(byte))) + log.debug("{}: end of hdlc frame {}".format(self.name, format_string_buf(chr(byte)))) self.hdlc_flag = True self.receiving = False - self._rx_buf_add(byte) + self._rx_buf_add(chr(byte)) valid_frame = self._handle_frame() if valid_frame: diff --git a/openvisualizer/motehandler/moteprobe/openhdlc.py b/openvisualizer/motehandler/moteprobe/openhdlc.py index 4909f00a..0c926ccd 100644 --- a/openvisualizer/motehandler/moteprobe/openhdlc.py +++ b/openvisualizer/motehandler/moteprobe/openhdlc.py @@ -63,7 +63,7 @@ class OpenHdlc(object): # ============================ public ====================================== - def hdlcify(self, in_buf): + def hdlcify(self, in_buf: str): """ Build an hdlc frame. diff --git a/openvisualizer/motehandler/moteprobe/serialmoteprobe.py b/openvisualizer/motehandler/moteprobe/serialmoteprobe.py index 2084dd37..9384b0fe 100644 --- a/openvisualizer/motehandler/moteprobe/serialmoteprobe.py +++ b/openvisualizer/motehandler/moteprobe/serialmoteprobe.py @@ -7,16 +7,17 @@ import logging import os import signal +from typing import List, Optional import serial -from moteprobe import MoteProbe, MoteProbeNoData +from openvisualizer.motehandler.moteprobe.moteprobe import MoteProbe, MoteProbeNoData -try: - import _winreg as winreg -except ImportError: - import glob - import platform +if os.name=='nt': # Windows + import winreg as winreg +elif os.name=='posix': # Linux + import glob + import platform # To recognize MAC OS X log = logging.getLogger('MoteProbe') log.setLevel(logging.ERROR) @@ -32,7 +33,7 @@ def __init__(self, port, baudrate): self._serial = None # initialize the parent class - MoteProbe.__init__(self, portname=port) + super().__init__(portname=port, daemon=True) # ======================== public ================================== @@ -46,7 +47,7 @@ def serial(self): return self._serial @classmethod - def probe_serial_ports(cls, baudrate, port_mask=None): + def probe_serial_ports(cls, baudrate: List[int], port_mask: Optional[List[str]] = None): ports = cls._get_ports_from_mask(port_mask) mote_probes = [] probe = None @@ -86,16 +87,18 @@ def probe_serial_ports(cls, baudrate, port_mask=None): # ======================== private ================================= - def _send_data(self, data): - hdlc_data = self.hdlc.hdlcify(data) + def _send_data(self, data: str) -> None: + + hdlc_data = bytearray([ord(b) for b in self.hdlc.hdlcify(data)]) bytes_written = 0 self._serial.flush() - while bytes_written != len(bytearray(hdlc_data)): + + while bytes_written != len(hdlc_data): bytes_written += self._serial.write(hdlc_data) - def _rcv_data(self, rx_bytes=1): + def _rcv_data(self, rx_bytes: int = 1) -> bytes: data = self._serial.read(rx_bytes) - if data == 0: + if data == b'': raise MoteProbeNoData else: return data @@ -105,16 +108,28 @@ def _detach(self): log.warning('closing serial port {}'.format(self._portname)) self._serial.close() - def _attach(self): + def _attach(self) -> bool: + """ + Tries to connect to the motes serial port. + + :return: True if successful else False + """ log.debug("attaching to serial port: {} @ {}".format(self._port, self._baudrate)) + self._serial = serial.Serial(self._port, self._baudrate, timeout=1, xonxoff=True, rtscts=False, dsrdtr=False) + log.debug("self._serial: {}".format(self._serial)) + if self._serial is None: + return False + else: + return True + @staticmethod - def _get_ports_from_mask(port_mask=None): + def _get_ports_from_mask(port_mask: Optional[List[str]] = None) -> List[str]: ports = [] - if port_mask is None: + if len(port_mask) == 0: if os.name == 'nt': path = 'HARDWARE\\DEVICEMAP\\SERIALCOMM' try: @@ -130,13 +145,11 @@ def _get_ports_from_mask(port_mask=None): pass elif os.name == 'posix': if platform.system() == 'Darwin': - port_mask = ['/dev/tty.usbserial-*'] + ports += [s for s in glob.glob('/dev/tty.usbserial-*')] else: - port_mask = ['/dev/ttyUSB*'] - for mask in port_mask: - ports += [s for s in glob.glob(mask)] + ports += [s for s in glob.glob('/dev/ttyUSB*')] else: - for mask in port_mask: - ports += [s for s in glob.glob(mask)] + for p in port_mask: + ports += [s for s in glob.glob(p)] return ports diff --git a/openvisualizer/motehandler/moteprobe/serialtester.py b/openvisualizer/motehandler/moteprobe/serialtester.py index b141a802..7e837998 100644 --- a/openvisualizer/motehandler/moteprobe/serialtester.py +++ b/openvisualizer/motehandler/moteprobe/serialtester.py @@ -7,13 +7,14 @@ import logging import random import threading +from typing import List from openvisualizer.eventbus.eventbusclient import EventBusClient from openvisualizer.motehandler.moteconnector.openparser import openparser log = logging.getLogger('SerialTester') -log.setLevel(logging.ERROR) -log.addHandler(logging.NullHandler()) +log.setLevel(logging.DEBUG) +log.addHandler(logging.StreamHandler()) class SerialTester(EventBusClient): @@ -32,15 +33,13 @@ def __init__(self, mote_probe): self.moteProbeSerialPort = self.mote_probe.portname # local variables - self.data_lock = threading.RLock() + self.data_lock = threading.Lock() + self.wait_flag = threading.Event() self.test_pkt_len = self.DFLT_TESTPKT_LENGTH self.num_test_pkt = self.DFLT_NUM_TESTPKT self.timeout = self.DFLT_TIMEOUT - self.trace_cb = None - self.busy_testing = False self.last_sent = [] self.last_received = [] - self.wait_for_reply = threading.Event() self._reset_stats() # give this thread a name @@ -49,83 +48,42 @@ def __init__(self, mote_probe): # initialize parent self.mote_probe.send_to_parser = self._receive_data_from_mote_serial - def quit(self): - self.go_on = False + def _receive_data_from_mote_serial(self, data: List[int]): - # ======================== public ========================================== - - def _receive_data_from_mote_serial(self, data): - - # handle data - if chr(data[0]) == chr(openparser.OpenParser.SERFRAME_MOTE2PC_DATA): - # don't handle if I'm not testing - with self.data_lock: - if not self.busy_testing: - return - with self.data_lock: + with self.data_lock: + if chr(data[0]) == chr(openparser.OpenParser.SERFRAME_MOTE2PC_DATA): self.last_received = data[1 + 2 + 5:] # type (1B), moteId (2B), ASN (5B) - # wake up other thread - self.wait_for_reply.set() + self.wait_flag.set() + + # ======================== public ========================================== # ===== setup test - def set_test_pkt_length(self, new_length): + def set_test_pkt_length(self, new_length: int): assert type(new_length) == int - with self.data_lock: - self.test_pkt_len = new_length + self.test_pkt_len = new_length - def set_num_test_pkt(self, new_num): + def set_num_test_pkt(self, new_num: int): assert type(new_num) == int - with self.data_lock: - self.num_test_pkt = new_num + self.num_test_pkt = new_num - def set_timeout(self, new_timeout): + def set_timeout(self, new_timeout: int): assert type(new_timeout) == int - with self.data_lock: - self.timeout = new_timeout - - def set_trace(self, new_trace_cb): - assert (callable(new_trace_cb)) or (new_trace_cb is None) - with self.data_lock: - self.trace_cb = new_trace_cb - - # ===== run test - - def test(self, blocking=True): - if blocking: - self._run_test() - else: - threading.Thread(target=self._run_test).start() - - # ===== get test results + self.timeout = new_timeout def get_stats(self): - with self.data_lock: - return_val = self.stats.copy() - return return_val + return self.stats.copy() - # ======================== private ========================================= - - def _run_test(self): - - # I'm testing - with self.data_lock: - self.busy_testing = True - - # gather test parameters - with self.data_lock: - test_pkt_len = self.test_pkt_len - num_test_pkt = self.num_test_pkt - timeout = self.timeout + def test(self): # reset stats self._reset_stats() # send packets and collect stats - for pkt_num in range(num_test_pkt): + for pkt_num in range(self.num_test_pkt): # prepare random packet to send - packet_to_send = [random.randint(0x00, 0xff) for _ in range(test_pkt_len)] + packet_to_send = [random.randint(0x00, 0xff) for _ in range(self.test_pkt_len)] # remember as last sent packet with self.data_lock: @@ -142,15 +100,15 @@ def _run_test(self): self.stats['numSent'] += 1 # log - self._log('--- packet {0}'.format(pkt_num)) - self._log('sent: {0}'.format(self.format_list(self.last_sent))) + log.debug('--- packet {0}'.format(pkt_num)) + log.debug('sent: {0}'.format(self.format_list(self.last_sent))) # wait for answer - self.wait_for_reply.clear() - if self.wait_for_reply.wait(timeout): + self.wait_flag.clear() + if self.wait_flag.wait(timeout=self.timeout): # log - self._log('received: {0}'.format(self.format_list(self.last_received))) + log.debug('received: {0}'.format(self.format_list(self.last_received))) # echo received with self.data_lock: @@ -158,23 +116,9 @@ def _run_test(self): self.stats['numOk'] += 1 else: self.stats['numCorrupted'] += 1 - self._log('!! corrupted.') else: - # timeout with self.data_lock: self.stats['numTimeout'] += 1 - self._log('!! timeout.') - - # I'm not testing - with self.data_lock: - self.busy_testing = False - - def _log(self, msg): - if log.isEnabledFor(logging.DEBUG): - log.debug(msg) - with self.data_lock: - if self.trace_cb: - self.trace_cb(msg) def _reset_stats(self): with self.data_lock: @@ -185,5 +129,6 @@ def _reset_stats(self): 'numTimeout': 0, } - def format_list(self, lst): + @staticmethod + def format_list(lst: list): return '-'.join(['%02x' % b for b in lst]) diff --git a/openvisualizer/motehandler/moteprobe/testbedmoteprobe.py b/openvisualizer/motehandler/moteprobe/testbedmoteprobe.py index 87b7a24a..e2b8e9c2 100644 --- a/openvisualizer/motehandler/moteprobe/testbedmoteprobe.py +++ b/openvisualizer/motehandler/moteprobe/testbedmoteprobe.py @@ -4,14 +4,14 @@ # Released under the BSD 3-Clause license as published at the link below. # https://openwsn.atlassian.net/wiki/display/OW/License -import Queue +import queue import json import logging import time import paho.mqtt.client as mqtt -from moteprobe import MoteProbe +from openvisualizer.motehandler.moteprobe.moteprobe import MoteProbe log = logging.getLogger('MoteProbe') log.setLevel(logging.ERROR) @@ -39,7 +39,7 @@ def __init__(self, mqtt_broker, testbedmote_eui64): name = 'opentestbed_{0}'.format(testbedmote_eui64) # initialize the parent class - MoteProbe.__init__(self, portname=name, daemon=True) + super().__init__(portname=name, daemon=True) @property def serial(self): @@ -71,7 +71,7 @@ def _detach(self): def _attach(self): # create queue for receiving serialbytes messages - self.serialbytes_queue = Queue.Queue(maxsize=10) + self.serialbytes_queue = queue.Queue(maxsize=10) self.mqtt_client.loop_start() @@ -90,7 +90,7 @@ def _on_mqtt_message(self, client, userdata, message): else: try: self.serialbytes_queue.put(serial_bytes, block=False) - except Queue.Full: + except queue.Full: log.warning("queue overflow/full") diff --git a/openvisualizer/motehandler/motestate/elements.py b/openvisualizer/motehandler/motestate/elements.py index 1bd44b15..14c47a01 100644 --- a/openvisualizer/motehandler/motestate/elements.py +++ b/openvisualizer/motehandler/motestate/elements.py @@ -1,6 +1,7 @@ import json import time from abc import ABCMeta +from typing import Optional from openvisualizer.motehandler.motestate.opentype import TypeAsn, OpenType, TypeCellType, TypeAddr, TypeComponent, \ TypeRssi @@ -23,7 +24,7 @@ def update(self): self.meta[0]['lastUpdated'] = time.time() self.meta[0]['numUpdates'] += 1 - def to_json(self, aspect='all', is_pretty_print=False): + def to_json(self, aspect='all', is_pretty_print=False) -> str: """ Dumps state to JSON. @@ -88,19 +89,6 @@ def _elem_to_dict(cls, elem): return return_val -class StateOutputBuffer(StateElem): - def update(self, notif=None, creator=None, owner=None): - super(StateOutputBuffer, self).update() - - assert notif - - if len(self.data) == 0: - self.data.append({}) - - self.data[0]['index_write'] = notif.index_write - self.data[0]['index_read'] = notif.index_read - - class StateAsn(StateElem): def update(self, notif=None, creator=None, owner=None): super(StateAsn, self).update() @@ -318,14 +306,17 @@ def __init__(self, event_bus_client, mote_connector): super(StateIdManager, self).__init__() self.ebc = event_bus_client self.mote_connector = mote_connector - self.is_dagroot = None + self._is_dagroot = None - def get_16b_addr(self): + def get_16b_addr(self) -> Optional[str]: try: - return self.data[0]['my16bID'].addr[:] + return ''.join(['%02x' % b for b in self.data[0]['my16bID'].addr[:]]) except IndexError: return None + def is_dagroot(self): + return self._is_dagroot + def update(self, notif=None, creator=None, owner=None): super(StateIdManager, self).update() @@ -380,7 +371,7 @@ def update(self, notif=None, creator=None, owner=None): ] # announce information about the DAG root to the eventBus - if self.is_dagroot != self.data[0]['isDAGroot']: + if self._is_dagroot != self.data[0]['isDAGroot']: # dispatch self.ebc.dispatch( signal='infoDagRoot', @@ -392,7 +383,7 @@ def update(self, notif=None, creator=None, owner=None): ) # record is_dagroot - self.is_dagroot = self.data[0]['isDAGroot'] + self._is_dagroot = self.data[0]['isDAGroot'] class StateMyDagRank(StateElem): diff --git a/openvisualizer/motehandler/motestate/motestate.py b/openvisualizer/motehandler/motestate/motestate.py index 8d2b4a5d..05ee64e7 100644 --- a/openvisualizer/motehandler/motestate/motestate.py +++ b/openvisualizer/motehandler/motestate/motestate.py @@ -14,7 +14,7 @@ from openvisualizer.eventbus.eventbusclient import EventBusClient from openvisualizer.motehandler.moteconnector.openparser import parserstatus -from openvisualizer.motehandler.motestate.elements import StateOutputBuffer, StateAsn, StateJoined, StateMacStats, \ +from openvisualizer.motehandler.motestate.elements import StateAsn, StateJoined, StateMacStats, \ StateTable, StateScheduleRow, StateBackoff, StateQueue, StateNeighborsRow, StateIsSync, StateIdManager, \ StateMyDagRank, StateKaPeriod, StateMSF @@ -24,7 +24,6 @@ class MoteState(EventBusClient): - ST_OUPUTBUFFER = 'OutputBuffer' ST_ASN = 'Asn' ST_MACSTATS = 'MacStats' ST_SCHEDULEROW = 'ScheduleRow' @@ -41,7 +40,6 @@ class MoteState(EventBusClient): ST_JOINED = 'Joined' ST_MSF = 'MSF' ST_ALL = [ - ST_OUPUTBUFFER, ST_ASN, ST_MACSTATS, ST_SCHEDULE, @@ -120,7 +118,6 @@ def __init__(self, mote_connector): self.state_lock = threading.Lock() self.state = {} - self.state[self.ST_OUPUTBUFFER] = StateOutputBuffer() self.state[self.ST_ASN] = StateAsn() self.state[self.ST_JOINED] = StateJoined() self.state[self.ST_MSF] = StateMSF() @@ -176,8 +173,6 @@ def __init__(self, mote_connector): self.state[self.ST_KAPERIOD] = StateKaPeriod() self.notif_handlers = { - self.parser_status.named_tuple[self.ST_OUPUTBUFFER]: - self.state[self.ST_OUPUTBUFFER].update, self.parser_status.named_tuple[self.ST_ASN]: self.state[self.ST_ASN].update, self.parser_status.named_tuple[self.ST_MACSTATS]: @@ -265,5 +260,6 @@ def _received_status_notif(self, data): if not found: raise SystemError("No handler for data {0}".format(data)) - def _is_namedtuple_instance(self, var, tuple_instance): + @staticmethod + def _is_namedtuple_instance(var, tuple_instance): return var._fields == tuple_instance._fields diff --git a/openvisualizer/openlbr/openlbr.py b/openvisualizer/openlbr/openlbr.py index 752512ae..ac55c927 100644 --- a/openvisualizer/openlbr/openlbr.py +++ b/openvisualizer/openlbr/openlbr.py @@ -273,7 +273,7 @@ def _v6_to_mesh_notif(self, sender, signal, data): for fragment in self.fragmentor.do_fragment(lowpan_bytes): self.dispatch( signal='bytesToMesh', - data=(lowpan['nextHop'], fragment), + data=(list(lowpan['nextHop']), fragment), ) time.sleep(0.01) @@ -340,7 +340,6 @@ def _mesh_to_v6_notif(self, sender, signal, data): # icmp header if len(ipv6dic['payload']) < 5: log.critical("wrong payload lenght on ICMPv6 packet {0}".format(",".join(str(c) for c in data))) - print "wrong payload lenght on ICMPv6 packet {0}".format(",".join(str(c) for c in data)) return ipv6dic['icmpv6_type'] = ipv6dic['payload'][0] @@ -556,7 +555,7 @@ def reassemble_lowpan(self, lowpan): return_val = [] if self.use_page_zero: - print 'Page dispatch page number zero is not supported!\n' + log.critical('Page dispatch page number zero is not supported!\n') raise SystemError() # the 6lowpan packet contains 4 parts @@ -856,7 +855,6 @@ def lowpan_to_ipv6(self, data): output += ['org_time is {0}'.format(format_addr(o_time))] output = '\n'.join(output) log.error(output) - print output ptr += length + 1 else: @@ -1012,7 +1010,8 @@ def lowpan_to_ipv6(self, data): pkt_ipv6['pre_hop'] = mac_prev_hop return pkt_ipv6 - def reassemble_ipv6_packet(self, pkt): + @staticmethod + def reassemble_ipv6_packet(pkt): pktw = [((6 << 4) + (pkt['traffic_class'] >> 4)), (((pkt['traffic_class'] & 0x0F) << 4) + (pkt['flow_label'] >> 16)), ((pkt['flow_label'] >> 8) & 0x00FF), @@ -1038,12 +1037,14 @@ def _get_source_route(self, destination): def _set_prefix_notif(self, sender, signal, data): """ Record the network prefix. """ + with self.state_lock: self.network_prefix = data log.info('Set network prefix {0}'.format(format_ipv6_addr(data))) def _info_dagroot_notif(self, sender, signal, data): """ Record the DAGroot's EUI64 address. """ + if data['isDAGroot'] == 1: with self.state_lock: self.dagRootEui64 = data['eui64'][:] diff --git a/openvisualizer/openlbr/sixlowpan_frag.py b/openvisualizer/openlbr/sixlowpan_frag.py index a3e00b64..3b7b4aba 100644 --- a/openvisualizer/openlbr/sixlowpan_frag.py +++ b/openvisualizer/openlbr/sixlowpan_frag.py @@ -1,9 +1,3 @@ -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - import logging from openvisualizer.utils import buf2int, hex2buf @@ -15,14 +9,14 @@ # ============================ parameters ====================================== -class ReassembleEntry(object): +class ReassembleEntry: def __init__(self, wanted, received, frag): self.total_bytes = wanted self.recvd_bytes = received self.fragments = frag -class Fragmentor(object): +class Fragmentor: """ Class which performs fragmentation and reassembly of 6LoWPAN packets for transport of IEEE 802.15.4 networks. @@ -127,7 +121,7 @@ def do_fragment(self, ip6_pkt): else: # subsequent fragment dispatch_size = hex2buf("{:02x}".format((self.FRAGN_DISPATCH << 8) | original_length)) - offset = [len(fragment_list) * (self.MAX_FRAGMENT_SIZE / 8)] + offset = [len(fragment_list) * int((self.MAX_FRAGMENT_SIZE / 8))] frag_header.extend(dispatch_size) frag_header.extend(datagram_tag) frag_header.extend(offset) diff --git a/openvisualizer/opentun/__init__.py b/openvisualizer/opentun/__init__.py index 68ac5a20..852a729c 100644 --- a/openvisualizer/opentun/__init__.py +++ b/openvisualizer/opentun/__init__.py @@ -1,12 +1,12 @@ import sys -import opentunnull # noqa: F401 +from . import opentunnull # noqa: F401 if sys.platform.startswith('win32'): - import opentunwindows # noqa: F401 + from . import opentunwindows # noqa: F401 if sys.platform.startswith('linux'): - import opentunlinux # noqa: F401 + from . import opentunlinux # noqa: F401 if sys.platform.startswith('darwin'): - import opentunmacos # noqa: F401 + from . import opentunmacos # noqa: F401 diff --git a/openvisualizer/opentun/opentun.py b/openvisualizer/opentun/opentun.py index 4af95973..0aa6c29d 100644 --- a/openvisualizer/opentun/opentun.py +++ b/openvisualizer/opentun/opentun.py @@ -123,7 +123,7 @@ def close(self): dst = self.IPV6PREFIX + self.IPV6HOST dst[15] += 1 # Payload and destination port are arbitrary - sock.sendto('stop', (format_ipv6_addr(dst), 18004)) + sock.sendto('stop'.encode('utf-8'), (format_ipv6_addr(dst), 18004)) # Give thread some time to exit time.sleep(0.05) except Exception as err: diff --git a/openvisualizer/opentun/opentunlinux.py b/openvisualizer/opentun/opentunlinux.py index 1a7fdb01..93cdebcf 100644 --- a/openvisualizer/opentun/opentunlinux.py +++ b/openvisualizer/opentun/opentunlinux.py @@ -57,9 +57,6 @@ def run(self): # wait for data p = os.read(self.tun_if, self.ETHERNET_MTU) - # convert input from a string to a byte list - p = [ord(b) for b in p] - # debug info log.debug('packet captured on tun interface: {0}'.format(format_buf(p))) @@ -128,11 +125,10 @@ def _v6_to_internet_notif(self, sender, signal, data): data = self.VIRTUAL_TUN_ID + data # convert data to string - data = ''.join([chr(b) for b in data]) try: # write over tuntap interface - os.write(self.tun_if, data) + os.write(self.tun_if, bytes(data)) log.debug("data dispatched to tun correctly {0}, {1}".format(signal, sender)) except Exception as err: err_msg = format_critical_message(err) @@ -149,24 +145,24 @@ def _create_tun_if(self): # ===== log.info("opening tun interface") return_val = os.open("/dev/net/tun", os.O_RDWR) - ifs = ioctl(return_val, self.TUN_SET_IFF, struct.pack("16sH", "tun%d", self.IFF_TUN)) - ifname = ifs[:16].strip("\x00") + ifs = ioctl(return_val, self.TUN_SET_IFF, struct.pack("16sH", "tun%d".encode('utf-8'), self.IFF_TUN)) + if_name = ifs[:16].strip(b'\x00').decode('utf-8') # ===== log.debug("configuring the IPv6 address") prefix_str = format_ipv6_addr(OpenTun.IPV6PREFIX) host_str = format_ipv6_addr(OpenTun.IPV6HOST) - _ = os.system('ip tuntap add dev ' + ifname + ' mode tun user root') - _ = os.system('ip link set ' + ifname + ' up') - _ = os.system('ip -6 addr add ' + prefix_str + ':' + host_str + '/64 dev ' + ifname) - _ = os.system('ip -6 addr add fe80::' + host_str + '/64 dev ' + ifname) + _ = os.system('ip tuntap add dev ' + if_name + ' mode tun user root') + _ = os.system('ip link set ' + if_name + ' up') + _ = os.system('ip -6 addr add ' + prefix_str + ':' + host_str + '/64 dev ' + if_name) + _ = os.system('ip -6 addr add fe80::' + host_str + '/64 dev ' + if_name) # ===== log.debug("adding a static route route") # added 'metric 1' for router-compatibility constraint # (show ping packet on wireshark but don't send to mote at all) - os.system('ip -6 route add ' + prefix_str + ':1415:9200::/96 dev ' + ifname + ' metric 1') + os.system('ip -6 route add ' + prefix_str + ':1415:9200::/96 dev ' + if_name + ' metric 1') # trying to set a gateway for this route # os.system('ip -6 route add ' + prefixStr + '::/64 via ' + IPv6Prefix + ':' + hostStr + '/64') @@ -176,10 +172,7 @@ def _create_tun_if(self): # ===== log.info('created following virtual interfaces') - os.system('ip addr show ' + ifname) - - # =====start radvd - # os.system('radvd start') + os.system('ip addr show ' + if_name) except IOError as err: # happens when not root diff --git a/openvisualizer/opentun/opentunmacos.py b/openvisualizer/opentun/opentunmacos.py index ca985148..e05dce1d 100644 --- a/openvisualizer/opentun/opentunmacos.py +++ b/openvisualizer/opentun/opentunmacos.py @@ -53,9 +53,6 @@ def run(self): # wait for data p = os.read(self.tun_if, self.ETHERNET_MTU) - # convert input from a string to a byte list - p = [ord(b) for b in p] - # debug info log.debug('packet captured on tun interface: {0}'.format(format_buf(p))) @@ -113,7 +110,7 @@ def _v6_to_internet_notif(self, sender, signal, data): try: # write over tuntap interface - os.write(self.tun_if, data) + os.write(self.tun_if, data.encode('utf-8')) log.debug("data dispatched to tun correctly {0}, {1}".format(signal, sender)) except Exception as err: err_msg = format_critical_message(err) diff --git a/openvisualizer/opentun/opentunwindows.py b/openvisualizer/opentun/opentunwindows.py index c017de97..0009aaa7 100644 --- a/openvisualizer/opentun/opentunwindows.py +++ b/openvisualizer/opentun/opentunwindows.py @@ -12,7 +12,7 @@ from openvisualizer.utils import format_crash_message, format_critical_message if sys.platform.startswith("win32"): - import _winreg as reg # pylint: disable=import-error + import winreg as reg # pylint: disable=import-error import win32file # pylint: disable=import-error import win32event # pylint: disable=import-error import pywintypes # pylint: disable=import-error @@ -236,7 +236,7 @@ def _get_tuntap_component_id(self): """ with reg.OpenKey(reg.HKEY_LOCAL_MACHINE, self.ADAPTER_KEY) as adapters: try: - for i in xrange(10000): + for i in range(10000): key_name = reg.EnumKey(adapters, i) with reg.OpenKey(adapters, key_name) as adapter: try: diff --git a/openvisualizer/rpl/rpl.py b/openvisualizer/rpl/rpl.py index 7cdf027f..4cfabd13 100644 --- a/openvisualizer/rpl/rpl.py +++ b/openvisualizer/rpl/rpl.py @@ -88,12 +88,6 @@ def __init__(self): self.latency_stats = {} self.parents_dao_seq = {} - # ======================== public ========================================== - - def close(self): - # nothing to do - pass - # ======================== private ========================================= # ==== handle EventBus notifications diff --git a/openvisualizer/server.py b/openvisualizer/server.py new file mode 100644 index 00000000..6e7e1f1a --- /dev/null +++ b/openvisualizer/server.py @@ -0,0 +1,303 @@ +# Copyright (c) 2010-2013, Regents of the University of California. +# All rights reserved. +# +# Released under the BSD 3-Clause license as published at the link below. +# https://openwsn.atlassian.net/wiki/display/OW/License + +""" +Contains application model for OpenVisualizer. Expects to be called by top-level UI module. See main() for startup use. +""" +import json +import logging.config +import os +import signal +from enum import IntEnum +from threading import Timer +from typing import Optional, Dict, Tuple, Any +from xmlrpc.client import Fault + +from openvisualizer.eventbus import eventbusmonitor +from openvisualizer.eventbus.eventbusclient import EventBusClient +from openvisualizer.jrc import jrc +from openvisualizer.motehandler.moteconnector.moteconnector import MoteConnector +from openvisualizer.motehandler.moteprobe.emulatedmoteprobe import EmulatedMoteProbe +from openvisualizer.motehandler.moteprobe.iotlabmoteprobe import IotlabMoteProbe +from openvisualizer.motehandler.moteprobe.serialmoteprobe import SerialMoteProbe +from openvisualizer.motehandler.motestate import motestate +from openvisualizer.motehandler.motestate.motestate import MoteState +from openvisualizer.openlbr import openlbr +from openvisualizer.opentun.opentun import OpenTun +from openvisualizer.opentun.opentunnull import OpenTunNull +from openvisualizer.rpl import rpl, topology +from openvisualizer.simulator.simengine import SimEngine + +log = logging.getLogger('OpenVisualizer') + + +class OpenVisualizer(EventBusClient): + """ Class implements an RPC server that allows (remote) monitoring and interaction a mesh network. """ + + class Mode(IntEnum): + HARDWARE = 0 + SIMULATION = 1 + IOTLAB = 2 + TESTBED = 3 + + def __init__(self, config, mode, **kwargs): + + super().__init__(name='OpenVisualizer') + + log.info("Starting OpenVisualizer ... ") + + self.mode = mode + + if self.mode == self.Mode.HARDWARE: + baudrate = kwargs.get('baudrate') + port_mask = kwargs.get('port_mask') + self.mote_probes = SerialMoteProbe.probe_serial_ports(port_mask=port_mask, baudrate=baudrate) + elif self.mode == self.Mode.SIMULATION: + if kwargs.get('topology') is not None: + self.simulator = SimEngine(kwargs.get("num_of_motes"), kwargs.get('topology')) + else: + self.simulator = SimEngine(kwargs.get("num_of_motes")) + + self.mote_probes = [EmulatedMoteProbe(m_if) for m_if in self.simulator.mote_interfaces] + self.simulator.start() + elif self.mode == self.Mode.IOTLAB: + self.mote_probes = IotlabMoteProbe.probe_iotlab_motes( + iotlab_motes=kwargs.get('iotlab_motes'), + iotlab_user=kwargs.get('iotlab_user'), + iotlab_site=kwargs.get('iotlab_site'), + iotlab_key_file=kwargs.get('iotlab_key_file'), + debug=kwargs.get("debug")) + elif self.mode == self.Mode.TESTBED: + pass + else: + log.critical("Unknown OpenVisualizer mode") + raise KeyboardInterrupt() + + # store configuration + self.root = config.root + self.page_zero = config.page_zero + + self.ebm = eventbusmonitor.EventBusMonitor(kwargs.get("wireshark_debug")) + self.lbr = openlbr.OpenLbr(self.page_zero) + self.rpl = rpl.RPL() + self.jrc = jrc.JRC() + self.topology = topology.Topology() + self.tun = OpenTun.create(config.tun) + + self.mote_connectors = [MoteConnector(mp, config.mqtt_broker) for mp in self.mote_probes] + self.mote_states = [MoteState(mc) for mc in self.mote_connectors] + + self._dagroot = None + + if self.root: + log.info(f"Setting DAGroot: {self.root}") + Timer(2, self.set_dagroot, args=(self.root,)).start() + + def shutdown(self) -> None: + """ Shutdown server and all its thread-based components. """ + + if self.mode == self.Mode.SIMULATION: + self.simulator.shutdown() + self.simulator.join() + + self.tun.close() + self.jrc.close() + + for probe in self.mote_probes: + probe.close() + probe.join() + + raise KeyboardInterrupt() + + # ======================== RPC functions ================================ + + @staticmethod + def remote_shutdown() -> None: + """ Function called from client """ + + # we cannot call the shutdown function directly, otherwise the KeyBoardInterrupt would be returned to the client + # instead of being intercept by the __main__.py module. + def keyboard_interrupt(): + os.kill(os.getpid(), signal.SIGINT) + + Timer(1, keyboard_interrupt, args=()).start() + + def get_dag(self): + return self.topology.get_dag() + + def get_runtime(self) -> Tuple[float, float]: + """ Get the real and simulated runtime of the simulation """ + + if self.mode != self.Mode.SIMULATION: + raise Fault(faultCode='-1', faultString="Only available during simulation") + + return self.simulator.runtime_getter() + + def pause_simulation(self) -> bool: + """ Pauses or unpauses simulation engine. """ + + if self.mode != self.Mode.SIMULATION: + raise Fault(faultCode='-1', faultString="Can only pause a simulation") + else: + return self.simulator.pause() + + def get_dagroot(self) -> str: + """ Getter for the network DAGroot. """ + + for ms in self.mote_states: + if ms.get_state_elem(motestate.MoteState.ST_IDMANAGER).is_dagroot(): + return ms.get_state_elem(motestate.MoteState.ST_IDMANAGER).get_16b_addr() + + def set_dagroot(self, port_or_address: str) -> None: + """ Setter for the network DAGroot. """ + + mote_dict = self.get_mote_dict() + if port_or_address in mote_dict: + port = mote_dict[port_or_address] + elif port_or_address in mote_dict.values(): + port = port_or_address + else: + raise Fault(faultCode='-1', faultString="Unknown port or address: {}".format(port_or_address)) + + for ms in self.mote_states: + try: + if ms.mote_connector.serialport == port: + return ms.trigger_action(MoteState.TRIGGER_DAGROOT) + except ValueError as err: + log.error(err) + break + raise Fault(faultCode='-1', faultString="Could not set {} as root".format(port)) + + def get_mote_dict(self) -> Dict[str, str]: + """ Returns a dictionary with key-value entry: {mote_id: serial-port} """ + + mote_dict = {} + + for ms in self.mote_states: + address = ms.get_state_elem(motestate.MoteState.ST_IDMANAGER).get_16b_addr() + if address: + mote_dict[address] = ms.mote_connector.serialport + else: + mote_dict[ms.mote_connector.serialport] = address + + return mote_dict + + def get_mote_state(self, mote_id) -> Optional[Dict[int, str]]: + """ + Returns the MoteState object for the provided connected mote. + :param mote_id: 16-bit ID of mote + :rtype: MoteState or None if not found + """ + + for ms in self.mote_states: + id_manager = ms.get_state_elem(ms.ST_IDMANAGER) + if id_manager and id_manager.get_16b_addr(): + address = id_manager.get_16b_addr() + if address == mote_id: + return OpenVisualizer._extract_mote_states(ms) + else: + raise Fault(faultCode='-1', faultString="Unknown mote ID: {}".format(mote_id)) + + def get_motes_connectivity(self) -> tuple: + motes = [] + states = [] + edges = [] + src_s = None + + for ms in self.mote_states: + id_manager = ms.get_state_elem(ms.ST_IDMANAGER) + if id_manager and id_manager.get_16b_addr(): + src_s = id_manager.get_16b_addr() + motes.append(src_s) + neighbor_table = ms.get_state_elem(ms.ST_NEIGHBORS) + for neighbor in neighbor_table.data: + if len(neighbor.data) == 0: + break + if neighbor.data[0]['used'] == 1 and neighbor.data[0]['parentPreference'] == 1: + dst_s = ''.join(['%02X' % b for b in neighbor.data[0]['addr'].addr[-2:]]) + dst_s = ''.join(['%02X' % int(b) for b in neighbor.data[0]['addr'].addr[-2:]]) + edges.append({'u': src_s, 'v': dst_s}) + break + + motes = list(set(motes)) + for mote in motes: + d = {'id': mote, 'value': {'label': mote}} + states.append(d) + + return states, edges + + def get_network_topology(self): + if self.mode != self.Mode.SIMULATION: + raise Fault(faultCode='-1', faultString="Only available during simulation") + + motes = self.simulator.positions_getter() + connections = self.simulator.connections_getter() + + return {'motes': motes, 'connections': connections} + + def update_positions(self, new_positions): + new_positions = json.loads(new_positions) + + if self.mode != self.Mode.SIMULATION: + raise Fault(faultCode='-1', faultString="Only available during simulation") + + self.simulator.positions_setter(new_positions) + + def update_connections(self, from_mote, to_mote, pdr): + if self.mode != self.Mode.SIMULATION: + raise Fault(faultCode='-1', faultString="Only available during simulation") + + self.simulator.connections_setter(from_mote, to_mote, pdr) + + def delete_connections(self, from_mote, to_mote): + if self.mode != self.Mode.SIMULATION: + raise Fault(faultCode='-1', faultString="Only available during simulation") + + # when you create a new connection, set PDR max + self.simulator.connections_setter(from_mote, to_mote, 0) + + def get_routing_path(self, destination) -> Dict[str, Any]: + route = self._dispatch_and_get_result(signal='getSourceRoute', data=destination) + route = [r[-1] for r in route] + data = {'route': route} + + return data + + def enable_wireshark_debug(self) -> None: + if isinstance(self.tun, OpenTunNull): + raise Fault(faultCode='-1', faultString="Wireshark debugging requires tun to be active on the server") + else: + self.ebm.wireshark_debug_enabled = True + + def disable_wireshark_debug(self) -> None: + if isinstance(self.tun, OpenTunNull): + raise Fault(faultCode='-1', faultString="Wireshark debugging requires tun to be active on the server") + else: + self.ebm.wireshark_debug_enabled = False + + def get_wireshark_debug(self) -> bool: + return self.ebm.wireshark_debug_enabled + + def get_ebm_stats(self): + return self.ebm.get_stats() + + @staticmethod + def _extract_mote_states(ms) -> Dict[int, str]: + states = { + ms.ST_IDMANAGER: ms.get_state_elem(ms.ST_IDMANAGER).to_json('data'), + ms.ST_ASN: ms.get_state_elem(ms.ST_ASN).to_json('data'), + ms.ST_ISSYNC: ms.get_state_elem(ms.ST_ISSYNC).to_json('data'), + ms.ST_MYDAGRANK: ms.get_state_elem(ms.ST_MYDAGRANK).to_json('data'), + ms.ST_KAPERIOD: ms.get_state_elem(ms.ST_KAPERIOD).to_json('data'), + ms.ST_BACKOFF: ms.get_state_elem(ms.ST_BACKOFF).to_json('data'), + ms.ST_MACSTATS: ms.get_state_elem(ms.ST_MACSTATS).to_json('data'), + ms.ST_SCHEDULE: ms.get_state_elem(ms.ST_SCHEDULE).to_json('data'), + ms.ST_QUEUE: ms.get_state_elem(ms.ST_QUEUE).to_json('data'), + ms.ST_NEIGHBORS: ms.get_state_elem(ms.ST_NEIGHBORS).to_json('data'), + ms.ST_JOINED: ms.get_state_elem(ms.ST_JOINED).to_json('data'), + ms.ST_MSF: ms.get_state_elem(ms.ST_MSF).to_json('data'), + } + return states diff --git a/openvisualizer/simengine/__init__.py b/openvisualizer/simengine/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/openvisualizer/simengine/idmanager.py b/openvisualizer/simengine/idmanager.py deleted file mode 100644 index 7719e0a0..00000000 --- a/openvisualizer/simengine/idmanager.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -import logging - - -class IdManager(object): - """ The module which assigns ID to the motes. """ - - def __init__(self): - # store params - from openvisualizer.simengine import simengine - self.engine = simengine.SimEngine() - - # local variables - self.current_id = 0 - - # logging - self.log = logging.getLogger('IdManager') - self.log.setLevel(logging.INFO) - self.log.addHandler(logging.NullHandler()) - - # ======================== public ========================================== - - def get_id(self): - # increment the running ID - self.current_id += 1 - - # debug - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('assigning ID=' + str(self.current_id)) - - return self.current_id - - # ======================== private ========================================= - - # ======================== helpers ========================================= diff --git a/openvisualizer/simengine/locationmanager.py b/openvisualizer/simengine/locationmanager.py deleted file mode 100644 index 44bd6ddc..00000000 --- a/openvisualizer/simengine/locationmanager.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -import logging -import random - - -class LocationManager(object): - """ The module which assigns locations to the motes. """ - - def __init__(self): - # store params - from openvisualizer.simengine import simengine - self.engine = simengine.SimEngine() - - # local variables - - # logging - self.log = logging.getLogger('LocationManager') - self.log.setLevel(logging.DEBUG) - self.log.addHandler(logging.NullHandler()) - - # ======================== public ========================================== - - def get_location(self): - # get random location around Cory Hall, UC Berkeley - lat = 37.875095 - 0.0005 + random.random() * 0.0010 - lon = -122.257473 - 0.0005 + random.random() * 0.0010 - - # debug - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('assigning location ({0} {1})'.format(lat, lon)) - - return lat, lon - - # ======================== private ========================================= - - # ======================== helpers ========================================= diff --git a/openvisualizer/simengine/motehandler.py b/openvisualizer/simengine/motehandler.py deleted file mode 100644 index 8a8040cc..00000000 --- a/openvisualizer/simengine/motehandler.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -import logging -import threading - -from openvisualizer.bspemulator import bspboard -from openvisualizer.bspemulator import bspdebugpins -from openvisualizer.bspemulator import bspeui64 -from openvisualizer.bspemulator import bspleds -from openvisualizer.bspemulator import bspradio -from openvisualizer.bspemulator import bspsctimer -from openvisualizer.bspemulator import bspuart -from openvisualizer.bspemulator import hwcrystal -from openvisualizer.bspemulator import hwsupply -from openvisualizer.simengine import simengine - -# ============================ get notification IDs ============================ -# Contains the list of notifIds used in the following functions. -notif_string = [] - - -def read_notif_ids(header_path): - """ - Contextual parent must call this method before other use of mote handler. - - ``header_path`` Path to openwsnmodule_obj.h, containing notifIds - - Required since this module cannot know where to find the header file. - """ - - import re - - f = open(header_path) - lines = f.readlines() - f.close() - - global notif_string - for line in lines: - m = re.search('MOTE_NOTIF_([a-zA-Z0-9_]+)', line) - if m: - if m.group(1) not in notif_string: - notif_string += [m.group(1)] - - -def notif_id(s): - assert s in notif_string - return notif_string.index(s) - - -# ============================ classes ========================================= - -class MoteHandler(threading.Thread): - - def __init__(self, mote, vcdlog): - - # store params - self.engine = simengine.SimEngine() - self.mote = mote - - # === local variables - self.loghandler = self.engine.log_handler - # unique identifier of the mote - self.id = self.engine.id_manager.get_id() - # position of the mote - self.location = self.engine.location_manager.get_location() - # stats - self.num_rx_commands = 0 - self.num_tx_commands = 0 - # hw - self.hw_supply = hwsupply.HwSupply(self) - self.hw_crystal = hwcrystal.HwCrystal(self) - # bsp - self.bsp_board = bspboard.BspBoard(self) - self.bsp_debugpins = bspdebugpins.BspDebugPins(self, vcdlog) - self.bsp_eui64 = bspeui64.BspEui64(self) - self.bsp_leds = bspleds.BspLeds(self) - self.bsp_sctimer = bspsctimer.BspSctimer(self) - self.bsp_radio = bspradio.BspRadio(self) - self.bsp_uart = bspuart.BspUart(self) - # status - self.booted = False - self.cpu_running = threading.Lock() - self.cpu_running.acquire() - self.cpu_done = threading.Lock() - self.cpu_done.acquire() - - # === install callbacks - # board - mote.set_callback(notif_id('board_init'), self.bsp_board.cmd_init) - mote.set_callback(notif_id('board_sleep'), self.bsp_board.cmd_sleep) - # debugpins - mote.set_callback(notif_id('debugpins_init'), self.bsp_debugpins.cmd_init) - mote.set_callback(notif_id('debugpins_frame_toggle'), self.bsp_debugpins.cmd_frame_toggle) - mote.set_callback(notif_id('debugpins_frame_clr'), self.bsp_debugpins.cmd_frame_clr) - mote.set_callback(notif_id('debugpins_frame_set'), self.bsp_debugpins.cmd_frame_set) - mote.set_callback(notif_id('debugpins_slot_toggle'), self.bsp_debugpins.cmd_slot_toggle) - mote.set_callback(notif_id('debugpins_slot_clr'), self.bsp_debugpins.cmd_slot_clr) - mote.set_callback(notif_id('debugpins_slot_set'), self.bsp_debugpins.cmd_slot_set) - mote.set_callback(notif_id('debugpins_fsm_toggle'), self.bsp_debugpins.cmd_fsm_toggle) - mote.set_callback(notif_id('debugpins_fsm_clr'), self.bsp_debugpins.cmd_fsm_clr) - mote.set_callback(notif_id('debugpins_fsm_set'), self.bsp_debugpins.cmd_fsm_set) - mote.set_callback(notif_id('debugpins_task_toggle'), self.bsp_debugpins.cmd_task_toggle) - mote.set_callback(notif_id('debugpins_task_clr'), self.bsp_debugpins.cmd_task_clr) - mote.set_callback(notif_id('debugpins_task_set'), self.bsp_debugpins.cmd_task_set) - mote.set_callback(notif_id('debugpins_isr_toggle'), self.bsp_debugpins.cmd_isr_toggle) - mote.set_callback(notif_id('debugpins_isr_clr'), self.bsp_debugpins.cmd_isr_clr) - mote.set_callback(notif_id('debugpins_isr_set'), self.bsp_debugpins.cmd_isr_set) - mote.set_callback(notif_id('debugpins_radio_toggle'), self.bsp_debugpins.cmd_radio_toggle) - mote.set_callback(notif_id('debugpins_radio_clr'), self.bsp_debugpins.cmd_radio_clr) - mote.set_callback(notif_id('debugpins_radio_set'), self.bsp_debugpins.cmd_radio_set) - mote.set_callback(notif_id('debugpins_ka_clr'), self.bsp_debugpins.cmd_ka_clr) - mote.set_callback(notif_id('debugpins_ka_set'), self.bsp_debugpins.cmd_ka_set) - mote.set_callback(notif_id('debugpins_syncPacket_clr'), self.bsp_debugpins.cmd_sync_packet_clr) - mote.set_callback(notif_id('debugpins_syncPacket_set'), self.bsp_debugpins.cmd_sync_packet_set) - mote.set_callback(notif_id('debugpins_syncAck_clr'), self.bsp_debugpins.cmd_sync_ack_clr) - mote.set_callback(notif_id('debugpins_syncAck_set'), self.bsp_debugpins.cmd_sync_ack_set) - mote.set_callback(notif_id('debugpins_debug_clr'), self.bsp_debugpins.cmd_debug_clr) - mote.set_callback(notif_id('debugpins_debug_set'), self.bsp_debugpins.cmd_debug_set) - # eui64 - mote.set_callback(notif_id('eui64_get'), self.bsp_eui64.cmd_get) - # leds - mote.set_callback(notif_id('leds_init'), self.bsp_leds.cmd_init) - mote.set_callback(notif_id('leds_error_on'), self.bsp_leds.cmd_error_on) - mote.set_callback(notif_id('leds_error_off'), self.bsp_leds.cmd_error_off) - mote.set_callback(notif_id('leds_error_toggle'), self.bsp_leds.cmd_error_toggle) - mote.set_callback(notif_id('leds_error_isOn'), self.bsp_leds.cmd_error_is_on) - mote.set_callback(notif_id('leds_radio_on'), self.bsp_leds.cmd_radio_on) - mote.set_callback(notif_id('leds_radio_off'), self.bsp_leds.cmd_radio_off) - mote.set_callback(notif_id('leds_radio_toggle'), self.bsp_leds.cmd_radio_toggle) - mote.set_callback(notif_id('leds_radio_isOn'), self.bsp_leds.cmd_radio_is_on) - mote.set_callback(notif_id('leds_sync_on'), self.bsp_leds.cmd_sync_on) - mote.set_callback(notif_id('leds_sync_off'), self.bsp_leds.cmd_sync_off) - mote.set_callback(notif_id('leds_sync_toggle'), self.bsp_leds.cmd_sync_toggle) - mote.set_callback(notif_id('leds_sync_isOn'), self.bsp_leds.cmd_sync_is_on) - mote.set_callback(notif_id('leds_debug_on'), self.bsp_leds.cmd_debug_on) - mote.set_callback(notif_id('leds_debug_off'), self.bsp_leds.cmd_debug_off) - mote.set_callback(notif_id('leds_debug_toggle'), self.bsp_leds.cmd_debug_toggle) - mote.set_callback(notif_id('leds_debug_isOn'), self.bsp_leds.cmd_debug_is_on) - mote.set_callback(notif_id('leds_all_on'), self.bsp_leds.cmd_all_on) - mote.set_callback(notif_id('leds_all_off'), self.bsp_leds.cmd_all_off) - mote.set_callback(notif_id('leds_all_toggle'), self.bsp_leds.cmd_all_toggle) - mote.set_callback(notif_id('leds_circular_shift'), self.bsp_leds.cmd_circular_shift) - mote.set_callback(notif_id('leds_increment'), self.bsp_leds.cmd_increment) - # radio - mote.set_callback(notif_id('radio_init'), self.bsp_radio.cmd_init) - mote.set_callback(notif_id('radio_reset'), self.bsp_radio.cmd_reset) - mote.set_callback(notif_id('radio_setFrequency'), self.bsp_radio.cmd_set_frequency) - mote.set_callback(notif_id('radio_rfOn'), self.bsp_radio.cmd_rf_on) - mote.set_callback(notif_id('radio_rfOff'), self.bsp_radio.cmd_rf_off) - mote.set_callback(notif_id('radio_loadPacket'), self.bsp_radio.cmd_load_packet) - mote.set_callback(notif_id('radio_txEnable'), self.bsp_radio.cmd_tx_enable) - mote.set_callback(notif_id('radio_txNow'), self.bsp_radio.cmd_tx_now) - mote.set_callback(notif_id('radio_rxEnable'), self.bsp_radio.cmd_rx_enable) - mote.set_callback(notif_id('radio_rxNow'), self.bsp_radio.cmd_rx_now) - mote.set_callback(notif_id('radio_getReceivedFrame'), self.bsp_radio.cmd_get_received_frame) - # sctimer - mote.set_callback(notif_id('sctimer_init'), self.bsp_sctimer.cmd_init) - mote.set_callback(notif_id('sctimer_setCompare'), self.bsp_sctimer.cmd_set_compare) - mote.set_callback(notif_id('sctimer_readCounter'), self.bsp_sctimer.cmd_read_counter) - mote.set_callback(notif_id('sctimer_enable'), self.bsp_sctimer.cmd_enable) - mote.set_callback(notif_id('sctimer_disable'), self.bsp_sctimer.cmd_disable) - # uart - mote.set_callback(notif_id('uart_init'), self.bsp_uart.cmd_init) - mote.set_callback(notif_id('uart_enableInterrupts'), self.bsp_uart.cmd_enable_interrupts) - mote.set_callback(notif_id('uart_disableInterrupts'), self.bsp_uart.cmd_disable_interrupts) - mote.set_callback(notif_id('uart_clearRxInterrupts'), self.bsp_uart.cmd_clear_rx_interrupts) - mote.set_callback(notif_id('uart_clearTxInterrupts'), self.bsp_uart.cmd_clear_tx_interrupts) - mote.set_callback(notif_id('uart_writeByte'), self.bsp_uart.cmd_write_byte) - mote.set_callback(notif_id('uart_writeCircularBuffer_FASTSIM'), self.bsp_uart.cmd_write_circular_buffer_fastsim) - mote.set_callback(notif_id('uart_writeBufferByLen_FASTSIM'), self.bsp_uart.uart_write_buffer_by_len_fastsim) - mote.set_callback(notif_id('uart_readByte'), self.bsp_uart.cmd_read_byte) - mote.set_callback(notif_id('uart_setCTS'), self.bsp_uart.cmd_set_cts) - - # logging this module - self.log = logging.getLogger('MoteHandler_' + str(self.id)) - self.log.setLevel(logging.INFO) - self.log.addHandler(logging.NullHandler()) - - # logging this mote's modules - for logger_name in [ - 'MoteHandler_' + str(self.id), - # hw - 'HwSupply_' + str(self.id), - 'HwCrystal_' + str(self.id), - # bsp - 'BspBoard_' + str(self.id), - 'BspDebugpins_' + str(self.id), - 'BspEui64_' + str(self.id), - 'BspLeds_' + str(self.id), - 'BspSctimer_' + str(self.id), - 'BspRadio_' + str(self.id), - 'BspUart_' + str(self.id), - ]: - temp = logging.getLogger(logger_name) - temp.setLevel(logging.INFO) - temp.addHandler(self.loghandler) - - # initialize parent class - super(MoteHandler, self).__init__() - - # give this thread a name - self.setName('MoteHandler_' + str(self.id)) - - # thread daemon mode - self.setDaemon(True) - - # log - self.log.info('thread initialized') - - def run(self): - - # log - self.log.info('thread starting') - - # switch on the mote - self.hw_supply.switch_on() - - assert 0 - - # ======================== public ========================================== - - def get_id(self): - return self.id - - def get_location(self): - return self.location - - def set_location(self, lat, lon): - self.location = (lat, lon) - - def handle_event(self, function_to_call): - - if not self.booted: - - assert function_to_call == self.hw_supply.switch_on - - # I'm not booted - self.booted = True - - # start the thread's execution - self.start() - - # wait for CPU to be done - self.cpu_done.acquire() - - else: - # call the funcion (mote runs in ISR) - kick_scheduler = function_to_call() - - assert kick_scheduler in [True, False] - - if kick_scheduler: - # release the mote's CPU (mote runs in task mode) - self.cpu_running.release() - - # wait for CPU to be done - self.cpu_done.acquire() - - # ======================== private ========================================= diff --git a/openvisualizer/simengine/propagation.py b/openvisualizer/simengine/propagation.py deleted file mode 100644 index bd527e84..00000000 --- a/openvisualizer/simengine/propagation.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -import logging -import random -import threading -from math import radians, cos, sin, asin, sqrt, log10 - -from openvisualizer.eventbus.eventbusclient import EventBusClient - - -class Propagation(EventBusClient): - """ The propagation model of the engine. """ - - SIGNAL_WIRELESSTXSTART = 'wirelessTxStart' - SIGNAL_WIRELESSTXEND = 'wirelessTxEnd' - - FREQUENCY_GHz = 2.4 - TX_POWER_dBm = 0.0 - PISTER_HACK_LOSS = 40.0 - SENSITIVITY_dBm = -101.0 - GREY_AREA_dB = 15.0 - - def __init__(self, sim_topology): - - # store params - from openvisualizer.simengine import simengine - self.engine = simengine.SimEngine() - self.sim_topology = sim_topology - - # local variables - self.data_lock = threading.Lock() - self.connections = {} - self.pending_tx_end = [] - - # logging - self.log = logging.getLogger('Propagation') - self.log.setLevel(logging.DEBUG) - self.log.addHandler(logging.NullHandler()) - - # initialize parents class - super(Propagation, self).__init__( - name='Propagation', - registrations=[ - { - 'sender': self.WILDCARD, - 'signal': self.SIGNAL_WIRELESSTXSTART, - 'callback': self._indicate_tx_start, - }, - { - 'sender': self.WILDCARD, - 'signal': self.SIGNAL_WIRELESSTXEND, - 'callback': self._indicate_tx_end, - }, - ], - ) - - # ======================== public ========================================== - - def create_connection(self, from_mote, to_mote): - - with self.data_lock: - - if not self.sim_topology: - - # ===== Pister-hack model - - # retrieve position - mh_from = self.engine.get_mote_handler_by_id(from_mote) - (lat_from, lon_from) = mh_from.get_location() - mh_to = self.engine.get_mote_handler_by_id(to_mote) - (lat_to, lon_to) = mh_to.get_location() - - # compute distance - lon_from, lat_from, lon_to, lat_to = map(radians, [lon_from, lat_from, lon_to, lat_to]) - d_lon = lon_to - lon_from - d_lat = lat_to - lat_from - a = sin(d_lat / 2) ** 2 + cos(lat_from) * cos(lat_to) * sin(d_lon / 2) ** 2 - c = 2 * asin(sqrt(a)) - d_km = 6367 * c - - # compute reception power (first Friis, then apply Pister-hack) - p_rx = self.TX_POWER_dBm - (20 * log10(d_km) + 20 * log10(self.FREQUENCY_GHz) + 92.45) - p_rx -= self.PISTER_HACK_LOSS * random.random() - - # turn into PDR - if p_rx < self.SENSITIVITY_dBm: - pdr = 0.0 - elif p_rx > self.SENSITIVITY_dBm + self.GREY_AREA_dB: - pdr = 1.0 - else: - pdr = (p_rx - self.SENSITIVITY_dBm) / self.GREY_AREA_dB - - elif self.sim_topology == 'linear': - - # linear network - if from_mote == to_mote + 1: - pdr = 1.0 - else: - pdr = 0.0 - - elif self.sim_topology == 'fully-meshed': - - pdr = 1.0 - - else: - - raise NotImplementedError('unsupported sim_topology={0}'.format(self.sim_topology)) - - # ==== create, update or delete connection - - if pdr: - if from_mote not in self.connections: - self.connections[from_mote] = {} - self.connections[from_mote][to_mote] = pdr - - if to_mote not in self.connections: - self.connections[to_mote] = {} - self.connections[to_mote][from_mote] = pdr - else: - self.delete_connection(to_mote, from_mote) - - def retrieve_connections(self): - - retrieved_connections = [] - return_val = [] - with self.data_lock: - - for from_mote in self.connections: - for to_mote in self.connections[from_mote]: - if (to_mote, from_mote) not in retrieved_connections: - return_val += [ - { - 'fromMote': from_mote, - 'toMote': to_mote, - 'pdr': self.connections[from_mote][to_mote], - }, - ] - retrieved_connections += [(from_mote, to_mote)] - - return return_val - - def update_connection(self, from_mote, to_mote, pdr): - - with self.data_lock: - self.connections[from_mote][to_mote] = pdr - self.connections[to_mote][from_mote] = pdr - - def delete_connection(self, from_mote, to_mote): - - with self.data_lock: - - try: - del self.connections[from_mote][to_mote] - if not self.connections[from_mote]: - del self.connections[from_mote] - - del self.connections[to_mote][from_mote] - if not self.connections[to_mote]: - del self.connections[to_mote] - except KeyError: - pass # did not exist - - # ======================== indication from eventBus ======================== - - def _indicate_tx_start(self, sender, signal, data): - - (from_mote, packet, channel) = data - - if from_mote in self.connections: - for (to_mote, pdr) in self.connections[from_mote].items(): - if random.random() <= pdr: - # indicate start of transmission - mh = self.engine.get_mote_handler_by_id(to_mote) - mh.bsp_radio.indicate_tx_start(from_mote, packet, channel) - - # remember to signal end of transmission - self.pending_tx_end += [(from_mote, to_mote)] - - def _indicate_tx_end(self, sender, signal, data): - - from_mote = data - - if from_mote in self.connections: - for (to_mote, pdr) in self.connections[from_mote].items(): - try: - self.pending_tx_end.remove((from_mote, to_mote)) - except ValueError: - pass - else: - mh = self.engine.get_mote_handler_by_id(to_mote) - mh.bsp_radio.indicate_tx_end(from_mote) - - # ======================== private ========================================= - - # ======================== helpers ========================================= diff --git a/openvisualizer/simengine/simengine.py b/openvisualizer/simengine/simengine.py deleted file mode 100644 index f8aeadc5..00000000 --- a/openvisualizer/simengine/simengine.py +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -import logging -import threading -import time - -from openvisualizer.simengine import timeline, propagation, idmanager, locationmanager - - -class SimEngineStats(object): - def __init__(self): - self.durationRunning = 0 - self.running = False - self.txStart = None - - def indicate_start(self): - self.txStart = time.time() - self.running = True - - def indicate_stop(self): - if self.txStart: - self.durationRunning += time.time() - self.txStart - self.running = False - - def get_duration_running(self): - if self.running: - return self.durationRunning + (time.time() - self.txStart) - else: - return self.durationRunning - - -class SimEngine(object): - """ The main simulation engine. """ - - # ======================== singleton pattern =============================== - - _instance = None - _init = False - - def __new__(cls, *args, **kwargs): - if not cls._instance: - cls._instance = super(SimEngine, cls).__new__(cls, *args, **kwargs) - return cls._instance - - # ======================== main ============================================ - - def __init__(self, sim_topology='', log_handler=logging.StreamHandler(), log_level=logging.WARNING): - - # don't re-initialize an instance (singleton pattern) - if self._init: - return - self._init = True - - # store params - self.log_handler = log_handler - self.log_handler.setFormatter( - logging.Formatter(fmt='%(asctime)s [%(name)s:%(levelname)s] %(message)s', datefmt='%H:%M:%S')) - - # local variables - self.moteHandlers = [] - self.timeline = timeline.TimeLine() - self.propagation = propagation.Propagation(sim_topology) - self.id_manager = idmanager.IdManager() - self.location_manager = locationmanager.LocationManager() - self.pauseSem = threading.Lock() - self.isPaused = False - self.stopAfterSteps = None - self.delay = 0 - self.stats = SimEngineStats() - - # logging this module - self.log = logging.getLogger('SimEngine') - self.log.setLevel(logging.INFO) - self.log.addHandler(logging.NullHandler()) - - # logging core modules - for logger_name in ['SimEngine', 'Timeline', 'Propagation', 'IdManager', 'LocationManager']: - temp = logging.getLogger(logger_name) - temp.setLevel(log_level) - temp.addHandler(log_handler) - - def start(self): - - # log - self.log.info('starting') - - # start timeline - self.timeline.start() - - # ======================== public ========================================== - - # === controlling execution speed - - def set_delay(self, delay): - self.delay = delay - - def pause(self): - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('pause') - if not self.isPaused: - self.pauseSem.acquire() - self.isPaused = True - self.stats.indicate_stop() - - def step(self, num_steps): - self.stopAfterSteps = num_steps - if self.isPaused: - self.pauseSem.release() - self.isPaused = False - - def resume(self): - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('resume') - self.stopAfterSteps = None - if self.isPaused: - self.pauseSem.release() - self.isPaused = False - self.stats.indicate_start() - - def pause_or_delay(self): - if self.isPaused: - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('pauseOrDelay: pause') - self.pauseSem.acquire() - self.pauseSem.release() - else: - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('pauseOrDelay: delay {0}'.format(self.delay)) - time.sleep(self.delay) - - if self.stopAfterSteps is not None: - if self.stopAfterSteps > 0: - self.stopAfterSteps -= 1 - if self.stopAfterSteps == 0: - self.pause() - - assert (self.stopAfterSteps is None or self.stopAfterSteps >= 0) - - def is_running(self): - return not self.isPaused - - # === called from the main script - - def indicate_new_mote(self, new_mote_handler): - - # add this mote to my list of motes - self.moteHandlers.append(new_mote_handler) - - # create connections to already existing motes - for mh in self.moteHandlers[:-1]: - self.propagation.create_connection( - from_mote=new_mote_handler.get_id(), - to_mote=mh.get_id(), - ) - - # === called from timeline - - def indicate_first_event_passed(self): - self.stats.indicate_start() - - # === getting information about the system - - def get_num_motes(self): - return len(self.moteHandlers) - - def get_mote_handler(self, rank): - return self.moteHandlers[rank] - - def get_mote_handler_by_id(self, mote_id): - return_val = None - for h in self.moteHandlers: - if h.get_id() == mote_id: - return_val = h - break - assert return_val - return return_val - - def get_stats(self): - return self.stats - - # ======================== private ========================================= - - # ======================== helpers ========================================= diff --git a/openvisualizer/simengine/timeline.py b/openvisualizer/simengine/timeline.py deleted file mode 100644 index 27589aef..00000000 --- a/openvisualizer/simengine/timeline.py +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2010-2013, Regents of the University of California. -# All rights reserved. -# -# Released under the BSD 3-Clause license as published at the link below. -# https://openwsn.atlassian.net/wiki/display/OW/License - -import logging -import threading - - -class TimeLineStats(object): - - def __init__(self): - self.numEvents = 0 - - def increment_events(self): - self.numEvents += 1 - - def get_num_events(self): - return self.numEvents - - -class TimeLineEvent(object): - - def __init__(self, mote_id, at_time, cb, desc): - self.at_time = at_time - self.mote_id = mote_id - self.desc = desc - self.cb = cb - - def __str__(self): - return '{0} {1}: {2}'.format(self.at_time, self.mote_id, self.desc) - - -class TimeLine(threading.Thread): - """ The timeline of the engine. """ - - def __init__(self): - - # store params - from openvisualizer.simengine import simengine - self.engine = simengine.SimEngine() - - # local variables - self.current_time = 0 # current time - self.timeline = [] # list of upcoming events - self.first_event_passed = False - self.first_event = threading.Lock() - self.first_event.acquire() - self.first_event_lock = threading.Lock() - self.stats = TimeLineStats() - - # logging - self.log = logging.getLogger('Timeline') - self.log.setLevel(logging.DEBUG) - self.log.addHandler(logging.NullHandler()) - - # initialize parent class - super(TimeLine, self).__init__() - - # set thread name - self.setName('TimeLine') - - # thread daemon mode - self.setDaemon(True) - - def run(self): - # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('starting') - - # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('waiting for first event') - - # wait for the first event to be scheduled - self.first_event.acquire() - self.engine.indicate_first_event_passed() - - # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('first event scheduled') - - # apply the delay - self.engine.pause_or_delay() - - while True: - # detect the end of the simulation - if len(self.timeline) == 0: - output = '' - output += 'end of simulation reached\n' - output += ' - current_time=' + str(self.get_current_time()) + '\n' - self.log.warning(output) - raise StopIteration(output) - - # pop the event at the head of the timeline - event = self.timeline.pop(0) - - # make sure that this event is later in time than the previous - if not self.current_time <= event.at_time: - self.log.critical("Current time {} exceeds event time: {}".format(self.current_time, event)) - assert False - - # record the current time - self.current_time = event.at_time - - # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('\n\nnow {0:.6f}, executing {1}@{2}'.format(event.at_time, event.desc, event.mote_id)) - - # call the event's callback - self.engine.get_mote_handler_by_id(event.mote_id).handle_event(event.cb) - - # update statistics - self.stats.increment_events() - - # apply the delay - self.engine.pause_or_delay() - - # ======================== public ========================================== - - def get_current_time(self): - return self.current_time - - def schedule_event(self, at_time, mote_id, cb, desc): - """ - Add an event into the timeline - - :param at_time: The time at which this event should be called. - :param mote_id: Mote identifier - :param cb: The function to call when this event happens. - :param desc: A unique description (a string) of this event. - """ - - # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('scheduling {0}@{1} at {2:.6f}'.format(desc, mote_id, at_time)) - - # make sure that I'm scheduling an event in the future - try: - assert (self.current_time <= at_time) - except AssertionError: - self.engine.pause() - output = "" - output += "current_time: {}\n".format(str(self.current_time)) - output += "at_time: {}\n".format(str(at_time)) - output += "mote_id: {}\n".format(mote_id) - output += "desc: {}\n".format(str(desc)) - self.log.critical(output) - raise - - # create a new event - new_event = TimeLineEvent(mote_id, at_time, cb, desc) - - # remove any event already in the queue with same description - for i in range(len(self.timeline)): - if (self.timeline[i].mote_id == mote_id and - self.timeline[i].desc == desc): - self.timeline.pop(i) - break - - # look for where to put this event - i = 0 - while i < len(self.timeline): - if new_event.at_time > self.timeline[i].at_time: - i += 1 - else: - break - - # insert the new event - self.timeline.insert(i, new_event) - - # start the timeline, if applicable - with self.first_event_lock: - if not self.first_event_passed: - self.first_event_passed = True - self.first_event.release() - - def cancel_event(self, mote_id, desc): - """ - Cancels all events identified by their description - - :param mote_id: Mote identifier - :param desc: A unique description (a string) of this event. - - :returns: The number of events canceled. - """ - - # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cancelEvent {0}@{1}'.format(desc, mote_id)) - - # initialize return variable - num_events_canceled = 0 - - # remove any event already the queue with same description - i = 0 - while i < len(self.timeline): - if self.timeline[i].mote_id == mote_id and self.timeline[i].desc == desc: - self.timeline.pop(i) - num_events_canceled += 1 - else: - i += 1 - - # return the number of events canceled - return num_events_canceled - - def get_events(self): - return [[ev.at_time, ev.mote_id, ev.desc] for ev in self.timeline] - - def get_stats(self): - return self.stats - - # ======================== private ========================================= - - def _print_timeline(self): - output = '' - for event in self.timeline: - output += '\n' + str(event) - return output - - # ======================== helpers ========================================= diff --git a/openvisualizer/bspemulator/__init__.py b/openvisualizer/simulator/__init__.py similarity index 100% rename from openvisualizer/bspemulator/__init__.py rename to openvisualizer/simulator/__init__.py diff --git a/openvisualizer/motehandler/moteprobe/__init__.py b/openvisualizer/simulator/bspemulator/__init__.py similarity index 100% rename from openvisualizer/motehandler/moteprobe/__init__.py rename to openvisualizer/simulator/bspemulator/__init__.py diff --git a/openvisualizer/simulator/bspemulator/bspboard.py b/openvisualizer/simulator/bspemulator/bspboard.py new file mode 100644 index 00000000..da4e931c --- /dev/null +++ b/openvisualizer/simulator/bspemulator/bspboard.py @@ -0,0 +1,173 @@ +# Copyright (c) 2010-2013, Regents of the University of California. +# All rights reserved. +# +# Released under the BSD 3-Clause license as published at the link below. +# https://openwsn.atlassian.net/wiki/display/OW/License + +import logging +import time +from multiprocessing import get_logger +from threading import Lock, BrokenBarrierError +from typing import Callable + +from openvisualizer.simulator.bspemulator.bspmodule import BspModule + + +class Interrupt: + __slots__ = ('mote_id', 'at_time', 'cb', 'desc') + + def __init__(self, mote_id, at_time, cb, desc): + self.at_time: int = at_time + self.mote_id: int = mote_id + self.cb: Callable = cb + self.desc: str = desc + + def __repr__(self): + return f"" + + +class BspBoard(BspModule): + """ Emulates the 'board' BSP module """ + + def __init__(self, mote): + # initialize the parent + super(BspBoard, self).__init__(mote) + + self.current_time = 0 + + self.intr_lock = Lock() + self.pending_intr = [] + + # logging + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) + + # ======================== public ========================================== + + # === commands + + def cmd_init(self): + """ Emulates: void board_init() """ + + # log the activity + self.logger.debug('cmd_init') + + # remember that module has been initialized + self.is_initialized = True + + def cmd_sleep(self): + """ Emulates: void board_sleep() """ + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug("cmd_sleep") + + self._handle_intr() + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.info("cmd_wakeup") + + def cmd_barrier_slot_sync(self): + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug("cmd_slot_sync") + + # check if paused + self.mote.pause_event.wait() + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.info(f'[{self.mote.mote_id}] Waiting at slot_barrier') + try: + self.mote.slot_barrier.wait() + except BrokenBarrierError: + self.logger.info("Exiting at slot_barrier ...") + time.sleep(100) + + def cmd_barrier_msg_sync(self): + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug("cmd_msg_sync") + + # check if paused + self.mote.pause_event.wait() + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug(f'[{self.mote.mote_id}] Waiting at msg_barrier') + try: + self.mote.msg_barrier.wait() + except BrokenBarrierError: + self.logger.info("Exiting at msg_barrier ...") + time.sleep(100) + + def cmd_barrier_ack_sync(self): + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug("cmd_ack_sync") + + # check if paused + self.mote.pause_event.wait() + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug(f'[{self.mote.mote_id}] Waiting at ack_barrier') + try: + self.mote.ack_barrier.wait() + except BrokenBarrierError: + self.logger.info("Exiting at ack_barrier ...") + time.sleep(100) + + def schedule_intr(self, at_time, mote_id, cb, desc): + + interrupt = Interrupt(at_time=at_time, mote_id=mote_id, cb=cb, desc=desc) + + with self.intr_lock: + try: + assert self.current_time <= interrupt.at_time + except AssertionError: + output = ["\nCannot schedule an event in the past ..."] + output += [f"\t - Current time: {self.current_time}"] + output += [f"\t - Event time: {interrupt.at_time}"] + self.logger.critical("\n".join(output)) + + # if this event already exists, remove the old one and add the new one + for i in range(len(self.pending_intr)): + if self.pending_intr[i].mote_id == mote_id and self.pending_intr[i].desc == desc: + self.pending_intr.pop(i) + break + + i = 0 + while i < len(self.pending_intr): + if interrupt.at_time > self.pending_intr[i].at_time: + i += 1 + else: + break + + self.pending_intr.insert(i, interrupt) + + def get_current_time(self): + with self.intr_lock: + ct = self.current_time + + return ct + + # ======================== private ========================================= + + def _handle_intr(self): + + self.intr_lock.acquire() + try: + interrupt: Interrupt = self.pending_intr.pop(0) + except IndexError: + self.intr_lock.release() + return + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug("cmd_isr_start") + + assert interrupt.at_time >= self.current_time + self.current_time = interrupt.at_time + self.intr_lock.release() + + interrupt.cb() + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug("cmd_isr_done") diff --git a/openvisualizer/simulator/bspemulator/bspdebugpins.py b/openvisualizer/simulator/bspemulator/bspdebugpins.py new file mode 100644 index 00000000..14af6ddf --- /dev/null +++ b/openvisualizer/simulator/bspemulator/bspdebugpins.py @@ -0,0 +1,360 @@ +# Copyright (c) 2010-2013, Regents of the University of California. +# All rights reserved. +# +# Released under the BSD 3-Clause license as published at the link below. +# https://openwsn.atlassian.net/wiki/display/OW/License + +import logging +from multiprocessing import get_logger + +from openvisualizer.simulator.bspemulator.bspmodule import BspModule + + +class BspDebugPins(BspModule): + """ Emulates the 'debugpins' BSP module. """ + + def __init__(self, mote): + # initialize the parent + super(BspDebugPins, self).__init__(mote) + + # logging + + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) + + # local variables + self.frame_pin_high = False + self.slot_pin_high = False + self.fsm_pin_high = False + self.task_pin_high = False + self.isr_pin_high = False + self.radio_pin_high = False + self.ka_pin_high = False + self.sync_packet_pin_high = False + self.sync_ack_pin_high = False + self.debug_pin_high = False + + # ======================== public ========================================== + + # === commands + + def cmd_init(self): + """Emulates: void debugpins_init() """ + + # log the activity + self.logger.debug('cmd_init') + + # remember that module has been initialized + self.is_initialized = True + + # frame + + def cmd_frame_toggle(self): + """ Emulates: void debugpins_frame_toggle() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_frame_toggle') + + # change the internal state + self.frame_pin_high = not self.frame_pin_high + + def cmd_frame_clr(self): + """ Emulates: void debugpins_frame_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_frame_clr') + + # change the internal state + self.frame_pin_high = False + + def cmd_frame_set(self): + """ Emulates: void debugpins_frame_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_frame_set') + + # change the internal state + self.frame_pin_high = True + + # slot + + def cmd_slot_toggle(self): + """ Emulates: void debugpins_slot_toggle() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_slot_toggle') + + # change the internal state + self.slot_pin_high = not self.slot_pin_high + + def cmd_slot_clr(self): + """ Emulates: void debugpins_slot_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_slot_clr') + + # change the internal state + self.slot_pin_high = False + + def cmd_slot_set(self): + """ Emulates: void debugpins_slot_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_slot_set') + + # change the internal state + self.slot_pin_high = True + + # fsm + + def cmd_fsm_toggle(self): + """ Emulates: void debugpins_fsm_toggle() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_fsm_toggle') + + # change the internal state + self.fsm_pin_high = not self.fsm_pin_high + + def cmd_fsm_clr(self): + """ Emulates: void debugpins_fsm_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_fsm_clr') + + # change the internal state + self.fsm_pin_high = False + + def cmd_fsm_set(self): + """ Emulates: void debugpins_fsm_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_fsm_set') + + # change the internal state + self.fsm_pin_high = True + + # task + + def cmd_task_toggle(self): + """ Emulates: void debugpins_task_toggle() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_task_toggle') + + # change the internal state + self.task_pin_high = not self.task_pin_high + + def cmd_task_clr(self): + """ Emulates: void debugpins_task_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_task_clr') + + # change the internal state + self.task_pin_high = False + + def cmd_task_set(self): + """ Emulates: void debugpins_task_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_task_set') + + # change the internal state + self.task_pin_high = True + + # isr + + def cmd_isr_toggle(self): + """ Emulates: void debugpins_isr_toggle() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_isr_toggle') + + # change the internal state + self.isr_pin_high = not self.isr_pin_high + + def cmd_isr_clr(self): + """ Emulates: void debugpins_isr_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_isr_clr') + + # change the internal state + self.isr_pin_high = False + + def cmd_isr_set(self): + """ Emulates: void debugpins_isr_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_isr_set') + + # change the internal state + self.isr_pin_high = True + + # radio + + def cmd_radio_toggle(self): + """ Emulates: void debugpins_radio_toggle() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_radio_toggle') + + # change the internal state + self.radio_pin_high = not self.radio_pin_high + + def cmd_radio_clr(self): + """ Emulates: void debugpins_radio_clr()""" + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_radio_clr') + + # change the internal state + self.radio_pin_high = False + + def cmd_radio_set(self): + """ Emulates: void debugpins_radio_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_radio_set') + + # change the internal state + self.radio_pin_high = True + + # ka + + def cmd_ka_clr(self): + """ Emulates: void debugpins_ka_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_ka_clr') + + # change the internal state + self.ka_pin_high = False + + def cmd_ka_set(self): + """ Emulates: void debugpins_ka_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_ka_set') + + # change the internal state + self.ka_pin_high = True + + # syncPacket + + def cmd_sync_packet_clr(self): + """ Emulates: void debugpins_syncPacket_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_sync_packet_clr') + + # change the internal state + self.sync_packet_pin_high = False + + def cmd_sync_packet_set(self): + """ Emulates: void debugpins_syncPacket_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_sync_packet_set') + + # change the internal state + self.sync_packet_pin_high = True + + # syncAck + + def cmd_sync_ack_clr(self): + """ Emulates: void debugpins_syncAck_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_sync_ack_clr') + + # change the internal state + self.sync_ack_pin_high = False + + def cmd_sync_ack_set(self): + """ Emulates: void debugpins_syncAck_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_sync_ack_set') + + # change the internal state + self.sync_ack_pin_high = True + + # debug + + def cmd_debug_clr(self): + """ Emulates: void debugpins_debug_clr() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_debug_clr') + + # change the internal state + self.debug_pin_high = False + + def cmd_debug_set(self): + """ Emulates: void debugpins_debug_set() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_debug_set') + + # change the internal state + self.debug_pin_high = True + + # === getters + + def get_frame_pin_high(self): + return self.frame_pin_high + + def get_slot_pin_high(self): + return self.slot_pin_high + + def get_fsm_pin_high(self): + return self.fsm_pin_high + + def get_isr_pin_high(self): + return self.isr_pin_high + + def get_radio_pin_high(self): + return self.radio_pin_high + + def get_ka_pin_high(self): + return self.ka_pin_high + + def get_sync_packet_pin_high(self): + return self.sync_packet_pin_high + + def get_sync_ack_pin_high(self): + return self.sync_ack_pin_high + + def get_debug_pin_high(self): + return self.debug_pin_high + + # ======================== private ========================================= diff --git a/openvisualizer/bspemulator/bspeui64.py b/openvisualizer/simulator/bspemulator/bspeui64.py similarity index 60% rename from openvisualizer/bspemulator/bspeui64.py rename to openvisualizer/simulator/bspemulator/bspeui64.py index 4057087e..75fa6430 100644 --- a/openvisualizer/bspemulator/bspeui64.py +++ b/openvisualizer/simulator/bspemulator/bspeui64.py @@ -5,19 +5,24 @@ # https://openwsn.atlassian.net/wiki/display/OW/License import logging +from multiprocessing import get_logger -from openvisualizer.bspemulator.bspmodule import BspModule +from openvisualizer.simulator.bspemulator.bspmodule import BspModule class BspEui64(BspModule): """ Emulates the 'eui64' BSP module """ - _name = 'BspEui64' - - def __init__(self, motehandler): + def __init__(self, mote): # initialize the parent - super(BspEui64, self).__init__(motehandler) + super(BspEui64, self).__init__(mote) + + # logging + + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) # ======================== public ========================================== @@ -27,18 +32,18 @@ def cmd_get(self): """ Emulates: void eui64_get(uint8_t* addressToWrite)""" # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_get') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_get') # get my 16-bit ID - my_id = self.motehandler.get_id() + my_id = self.mote.mote_id # format my EUI64 my_eui64 = [0x14, 0x15, 0x92, 0xcc, 0x00, 0x00, ((my_id >> 8) & 0xff), ((my_id >> 0) & 0xff)] # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('returning ' + str(my_eui64)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('returning ' + str(my_eui64)) # respond return my_eui64 diff --git a/openvisualizer/bspemulator/bspleds.py b/openvisualizer/simulator/bspemulator/bspleds.py similarity index 71% rename from openvisualizer/bspemulator/bspleds.py rename to openvisualizer/simulator/bspemulator/bspleds.py index d709101a..fea4754e 100644 --- a/openvisualizer/bspemulator/bspleds.py +++ b/openvisualizer/simulator/bspemulator/bspleds.py @@ -5,18 +5,18 @@ # https://openwsn.atlassian.net/wiki/display/OW/License import logging +from multiprocessing import get_logger -from openvisualizer.bspemulator.bspmodule import BspModule +from openvisualizer.simulator.bspemulator.bspmodule import BspModule class BspLeds(BspModule): """ Emulates the 'leds' BSP module """ - _name = 'BspLeds' - def __init__(self, motehandler): + def __init__(self, mote): # initialize the parent - super(BspLeds, self).__init__(motehandler) + super(BspLeds, self).__init__(mote) # local variables self.error_led_on = False @@ -24,6 +24,11 @@ def __init__(self, motehandler): self.sync_led_on = False self.debug_led_on = False + # logging + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) + # ======================== public ========================================== # === commands @@ -32,8 +37,8 @@ def cmd_init(self): """ Emulates: void leds_init() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_init') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_init') # remember that module has been intialized self.is_initialized = True @@ -44,8 +49,8 @@ def cmd_error_on(self): """ Emulates void leds_error_on() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_error_on') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_error_on') # change the internal state self.error_led_on = True @@ -54,8 +59,8 @@ def cmd_error_off(self): """ Emulates: void leds_error_off() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_error_off') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_error_off') # change the internal state self.error_led_on = False @@ -64,8 +69,8 @@ def cmd_error_toggle(self): """ Emulates: void leds_error_toggle() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_error_toggle') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_error_toggle') # change the internal state self.error_led_on = not self.error_led_on @@ -74,8 +79,8 @@ def cmd_error_is_on(self): """ Emulates: uint8_t leds_error_isOn() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_error_isOn') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_error_isOn') if self.error_led_on: return_val = 1 @@ -90,8 +95,8 @@ def cmd_radio_on(self): """ Emulates: void leds_radio_on() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_radio_on') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_radio_on') # change the internal state self.radio_led_on = True @@ -100,8 +105,8 @@ def cmd_radio_off(self): """ Emulates: void leds_radio_off() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_radio_off') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_radio_off') # change the internal state self.radio_led_on = False @@ -110,8 +115,8 @@ def cmd_radio_toggle(self): """ Emulates: void leds_radio_toggle() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_radio_toggle') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_radio_toggle') # change the internal state self.radio_led_on = not self.radio_led_on @@ -120,8 +125,8 @@ def cmd_radio_is_on(self): """ Emulates: uint8_t leds_radio_isOn() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_radio_isOn') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_radio_isOn') if self.radio_led_on: return_val = 1 @@ -136,8 +141,8 @@ def cmd_sync_on(self): """ Emulates: void leds_sync_on() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_sync_on') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_sync_on') # change the internal state self.sync_led_on = True @@ -147,8 +152,8 @@ def cmd_sync_off(self): void leds_sync_off()""" # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_sync_off') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_sync_off') # change the internal state self.sync_led_on = False @@ -157,8 +162,8 @@ def cmd_sync_toggle(self): """ Emulates: void leds_sync_toggle() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_sync_toggle') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_sync_toggle') # change the internal state self.sync_led_on = not self.sync_led_on @@ -167,8 +172,8 @@ def cmd_sync_is_on(self): """ Emulates: uint8_t leds_sync_isOn() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_sync_isOn') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_sync_isOn') if self.sync_led_on: return_val = 1 @@ -183,8 +188,8 @@ def cmd_debug_on(self): """ Emulates: void leds_debug_on() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_debug_on') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_debug_on') # change the internal state self.debug_led_on = True @@ -193,8 +198,8 @@ def cmd_debug_off(self): """ Emulates: void leds_debug_off() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_debug_off') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_debug_off') # change the internal state self.debug_led_on = False @@ -203,8 +208,8 @@ def cmd_debug_toggle(self): """ Emulates: void leds_debug_toggle() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_debug_toggle') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_debug_toggle') # change the internal state self.debug_led_on = not self.debug_led_on @@ -213,8 +218,8 @@ def cmd_debug_is_on(self): """ Emulates: uint8_t leds_debug_isOn() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_debug_isOn') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_debug_isOn') if self.debug_led_on: return_val = 1 @@ -229,8 +234,8 @@ def cmd_all_on(self): """ Emulates: void leds_all_on() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_all_on') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_all_on') # change the internal state self.error_led_on = True @@ -242,8 +247,8 @@ def cmd_all_off(self): """ Emulates: void leds_all_off() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_all_off') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_all_off') # change the internal state self.error_led_on = False @@ -255,8 +260,8 @@ def cmd_all_toggle(self): """ Emulates: void leds_all_toggle() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_all_toggle') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_all_toggle') # change the internal state self.error_led_on = not self.error_led_on @@ -268,8 +273,8 @@ def cmd_circular_shift(self): """ Emulates: void leds_circular_shift() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_circular_shift') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_circular_shift') (self.error_led_on, self.radio_led_on, @@ -283,8 +288,8 @@ def cmd_increment(self): """ Emulates: void leds_increment() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_increment') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_increment') # get the current value val = 0 diff --git a/openvisualizer/bspemulator/bspmodule.py b/openvisualizer/simulator/bspemulator/bspmodule.py similarity index 51% rename from openvisualizer/bspemulator/bspmodule.py rename to openvisualizer/simulator/bspemulator/bspmodule.py index 141d79bb..2ec13c7d 100644 --- a/openvisualizer/bspemulator/bspmodule.py +++ b/openvisualizer/simulator/bspemulator/bspmodule.py @@ -3,32 +3,25 @@ # # Released under the BSD 3-Clause license as published at the link below. # https://openwsn.atlassian.net/wiki/display/OW/License -import logging + from abc import ABCMeta -from openvisualizer.simengine import simengine +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from openvisualizer.simulator.emulatedmote import EmulatedMote -class BspModule(object): - """ Emulates the 'board' BSP module. """ - __metaclass__ = ABCMeta - @property - def _name(self): - raise NotImplementedError +class BspModule(metaclass=ABCMeta): + """ Emulates the 'board' BSP module. """ - def __init__(self, motehandler): + def __init__(self, mote: 'EmulatedMote'): # store variables - self.motehandler = motehandler + self.mote = mote + self.handler = mote.handler # local variables self.is_initialized = False - self.engine = simengine.SimEngine() - - # logging - self.log = logging.getLogger(self._name + '_' + str(self.motehandler.get_id())) - self.log.setLevel(logging.INFO) - self.log.addHandler(logging.NullHandler()) # ======================== private ========================================= diff --git a/openvisualizer/bspemulator/bspradio.py b/openvisualizer/simulator/bspemulator/bspradio.py similarity index 52% rename from openvisualizer/bspemulator/bspradio.py rename to openvisualizer/simulator/bspemulator/bspradio.py index 156c537b..e1944333 100644 --- a/openvisualizer/bspemulator/bspradio.py +++ b/openvisualizer/simulator/bspemulator/bspradio.py @@ -5,10 +5,10 @@ # https://openwsn.atlassian.net/wiki/display/OW/License import logging +import threading +from multiprocessing import get_logger -from openvisualizer.bspemulator.bspmodule import BspModule -from openvisualizer.simengine import propagation -from openvisualizer.eventbus.eventbusclient import EventBusClient +from openvisualizer.simulator.bspemulator.bspmodule import BspModule class RadioState: @@ -28,26 +28,20 @@ class RadioState: TURNING_OFF = 'TURNING_OFF' # Turning the RF chain off. -class BspRadio(BspModule, EventBusClient): +class BspRadio(BspModule): """ Emulates the 'radio' BSP module """ - _name = 'BspRadio' - INTR_STARTOFFRAME_MOTE = 'radio.startofframe_fromMote' INTR_ENDOFFRAME_MOTE = 'radio.endofframe_fromMote' INTR_STARTOFFRAME_PROPAGATION = 'radio.startofframe_fromPropagation' INTR_ENDOFFRAME_PROPAGATION = 'radio.endofframe_fromPropagation' - def __init__(self, motehandler): + def __init__(self, mote): # initialize the parents - BspModule.__init__(self, motehandler) - EventBusClient.__init__(self, name='BspRadio_{0}'.format(self.motehandler.get_id()), registrations=[]) + BspModule.__init__(self, mote) # local variables - self.timeline = self.engine.timeline - self.propagation = self.engine.propagation - self.sctimer = self.motehandler.bsp_sctimer # local variables self.frequency = None # frequency the radio is tuned to @@ -58,10 +52,20 @@ def __init__(self, motehandler): self.rssi = -50 self.lqi = 100 self.crc_passes = True + self.state = None + + # logging + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) # set initial state self._change_state(RadioState.STOPPED) + rx_thread = threading.Thread(target=self._listen_incoming) + rx_thread.setDaemon(True) + rx_thread.start() + # ======================== public ========================================== # === commands @@ -70,8 +74,8 @@ def cmd_init(self): """ Emulates: void radio_init() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_init') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_init') # change state self._change_state(RadioState.STOPPED) @@ -86,21 +90,21 @@ def cmd_reset(self): """ Emulates: void radio_reset() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_reset') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_reset') # change state self._change_state(RadioState.STOPPED) def cmd_set_frequency(self, frequency): - """ Emulates: void radio_setrequency(uint8_t frequency) """ + """ Emulates: void radio_setFrequency(uint8_t frequency) """ # store params self.frequency = frequency # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_set_frequency frequency=' + str(self.frequency)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_set_frequency frequency=' + str(self.frequency)) # change state self._change_state(RadioState.SETTING_FREQUENCY) @@ -112,8 +116,8 @@ def cmd_rf_on(self): """ Emulates: void radio_rfOn() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_rf_on') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_rf_on') # update local variable self.is_rf_on = True @@ -122,8 +126,8 @@ def cmd_rf_off(self): """ Emulates: void radio_rfOff() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_rf_off') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_rf_off') # change state self._change_state(RadioState.TURNING_OFF) @@ -135,7 +139,7 @@ def cmd_rf_off(self): self._change_state(RadioState.RFOFF) # wiggle de debugpin - self.motehandler.bsp_debugpins.cmd_radio_clr() + self.mote.bsp_debugpins.cmd_radio_clr() def cmd_load_packet(self, packet_to_load): """ Emulates: void radio_loadPacket(uint8_t* packet, uint8_t len) """ @@ -144,8 +148,8 @@ def cmd_load_packet(self, packet_to_load): assert (len(packet_to_load) <= 127) # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_load_packet len={0}'.format(len(packet_to_load))) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_load_packet len={0}'.format(len(packet_to_load))) # change state self._change_state(RadioState.LOADING_PACKET) @@ -154,8 +158,8 @@ def cmd_load_packet(self, packet_to_load): self.tx_buf = [len(packet_to_load)] + packet_to_load # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('tx_buf={0}'.format(self.tx_buf)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('tx_buf={0}'.format(self.tx_buf)) # change state self._change_state(RadioState.PACKET_LOADED) @@ -164,8 +168,8 @@ def cmd_tx_enable(self): """ Emulates: void radio_txEnable() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_tx_enable') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_tx_enable') # change state self._change_state(RadioState.ENABLING_TX) @@ -174,36 +178,37 @@ def cmd_tx_enable(self): self._change_state(RadioState.TX_ENABLED) # wiggle de debugpin - self.motehandler.bsp_debugpins.cmd_radio_set() + self.mote.bsp_debugpins.cmd_radio_set() def cmd_tx_now(self): """ Emulates: void radio_txNow() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_tx_now') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_tx_now') # change state self._change_state(RadioState.TRANSMITTING) # get current time - current_time = self.timeline.get_current_time() + current_time = self.mote.bsp_board.get_current_time() # calculate when the "start of frame" event will take place start_of_frame_time = current_time + self.delay_tx # schedule "start of frame" event - self.timeline.schedule_event(start_of_frame_time, - self.motehandler.get_id(), - self.intr_start_of_frame_from_mote, - self.INTR_STARTOFFRAME_MOTE) + self.mote.bsp_board.schedule_intr( + at_time=start_of_frame_time, + mote_id=self.mote.mote_id, + cb=self.intr_start_of_frame_from_mote, + desc=self.INTR_STARTOFFRAME_MOTE) def cmd_rx_enable(self): """ Emulates: void radio_rxEnable() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_rx_enable') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_rx_enable') # change state self._change_state(RadioState.ENABLING_RX) @@ -212,14 +217,14 @@ def cmd_rx_enable(self): self._change_state(RadioState.LISTENING) # wiggle de debugpin - self.motehandler.bsp_debugpins.cmd_radio_set() + self.mote.bsp_debugpins.cmd_radio_set() def cmd_rx_now(self): """ Emulates: void radio_rxNow() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_rx_now') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_rx_now') # change state self._change_state(RadioState.LISTENING) @@ -235,43 +240,46 @@ def cmd_get_received_frame(self): uint8_t* pCrc) """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_get_received_frame') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_get_received_frame') # ==== prepare response - rx_buffer = self.rx_buf[1:] rssi = self.rssi lqi = self.lqi crc = self.crc_passes # respond - return rx_buffer, rssi, lqi, crc + return self.rx_buf, rssi, lqi, crc # ======================== interrupts ====================================== def intr_start_of_frame_from_mote(self): - # indicate transmission starts on eventBus - self.dispatch( - signal=propagation.Propagation.SIGNAL_WIRELESSTXSTART, - data=(self.motehandler.get_id(), self.tx_buf, self.frequency), - ) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug(f"Sending packet from {self.mote.mote_id}") + + try: + self.mote.radio.tx.put([self.mote.mote_id, self.tx_buf, self.frequency]) + # wait until we get the 'go' from the propagation thread + self.mote.radio.tx.join() + except (EOFError, BrokenPipeError): + self.logger.error('Queue closed') - # schedule the "end of frame" event - current_time = self.timeline.get_current_time() + current_time = self.mote.bsp_board.get_current_time() end_of_frame_time = current_time + BspRadio._packet_length_to_duration(len(self.tx_buf)) - self.timeline.schedule_event( - end_of_frame_time, - self.motehandler.get_id(), - self.intr_end_of_frame_from_mote, - self.INTR_ENDOFFRAME_MOTE, + + self.mote.bsp_board.schedule_intr( + at_time=end_of_frame_time, + mote_id=self.mote.mote_id, + cb=self.intr_end_of_frame_from_mote, + desc=self.INTR_ENDOFFRAME_MOTE, ) # signal start of frame to mote - counter_val = self.sctimer.cmd_read_counter() + counter_val = self.mote.bsp_sctimer.cmd_read_counter() # indicate to the mote - self.motehandler.mote.radio_isr_startFrame(counter_val) + self.mote.mote.radio_isr_startFrame(counter_val) # kick the scheduler return True @@ -279,27 +287,25 @@ def intr_start_of_frame_from_mote(self): def intr_start_of_frame_from_propagation(self): # signal start of frame to mote - counter_val = self.sctimer.cmd_read_counter() + counter_val = self.mote.bsp_sctimer.cmd_read_counter() + 36 - # indicate to the mote - self.motehandler.mote.radio_isr_startFrame(counter_val) + # log + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('intr_start_of_frame_from_propagation counter_val={0}'.format(counter_val)) + + # indicate to mote + self.mote.mote.radio_isr_startFrame(counter_val) - # do NOT kick the scheduler + # kick the scheduler return True def intr_end_of_frame_from_mote(self): - # indicate transmission ends on eventBus - self.dispatch( - signal=propagation.Propagation.SIGNAL_WIRELESSTXEND, - data=self.motehandler.get_id(), - ) - # signal end of frame to mote - counter_val = self.sctimer.cmd_read_counter() + counter_val = self.mote.bsp_sctimer.cmd_read_counter() # indicate to the mote - self.motehandler.mote.radio_isr_endFrame(counter_val) + self.mote.mote.radio_isr_endFrame(counter_val) # kick the scheduler return True @@ -307,58 +313,61 @@ def intr_end_of_frame_from_mote(self): def intr_end_of_frame_from_propagation(self): # signal end of frame to mote - counter_val = self.sctimer.cmd_read_counter() + counter_val = self.mote.bsp_sctimer.cmd_read_counter() # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('intr_end_of_frame_from_propagation counter_val={0}'.format(counter_val)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('intr_end_of_frame_from_propagation counter_val={0}'.format(counter_val)) # indicate to the mote - self.motehandler.mote.radio_isr_endFrame(counter_val) + self.mote.mote.radio_isr_endFrame(counter_val) - # do NOT kick the scheduler + # kick the scheduler return True - # ======================== indication from Propagation ===================== - - def indicate_tx_start(self, mote_id, packet, channel): - - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug( - 'indicate_tx_start from mote_id={0} channel={1} len={2}'.format(mote_id, channel, len(packet))) - - if self.is_initialized and self.state == RadioState.LISTENING and self.frequency == channel: - self._change_state(RadioState.RECEIVING) - - self.rx_buf = packet - - # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('rx_buf={0}'.format(self.rx_buf)) - - # schedule start of frame - self.timeline.schedule_event( - self.timeline.get_current_time(), - self.motehandler.get_id(), - self.intr_start_of_frame_from_propagation, - self.INTR_STARTOFFRAME_PROPAGATION, - ) - - def indicate_tx_end(self, mote_id): - - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('indicate_tx_end from mote_id={0}'.format(mote_id)) - - if self.is_initialized and self.state == RadioState.RECEIVING: - self._change_state(RadioState.LISTENING) - - # schedule end of frame - self.timeline.schedule_event( - self.timeline.get_current_time(), - self.motehandler.get_id(), - self.intr_end_of_frame_from_propagation, - self.INTR_ENDOFFRAME_PROPAGATION, - ) + def _listen_incoming(self): + while True: + try: + origin, packet, channel = self.mote.radio.rx.get() + except EOFError: + self.logger.error('Queue closed') + break + + self.mote.radio.rx.task_done() + + if self.is_initialized and self.state == RadioState.LISTENING and self.frequency == channel: + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug(f"Got message from mote_{origin} length {packet[0]} bytes (channel={channel})") + + self.rx_buf = [i[0] for i in packet[1:]] + + # schedule start of frame + self.mote.bsp_board.schedule_intr( + at_time=self.mote.bsp_board.get_current_time(), + mote_id=self.mote.mote_id, + cb=self.intr_start_of_frame_from_propagation, + desc=self.INTR_STARTOFFRAME_PROPAGATION, + ) + + # schedule end of frame + end_of_frame_time = \ + self.mote.bsp_board.get_current_time() + BspRadio._packet_length_to_duration(len(self.rx_buf)) + self.mote.bsp_board.schedule_intr( + at_time=end_of_frame_time, + mote_id=self.mote.mote_id, + cb=self.intr_end_of_frame_from_propagation, + desc=self.INTR_ENDOFFRAME_PROPAGATION, + ) + + elif self.frequency != channel: + self.logger.debug(f"Wrong channel: {self.frequency} != {channel}") + else: + # just drop the packet + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug(f"[{self.mote.mote_id - 1}] Radio not in correct state: {self.state}") + + # notify the propagation thread that we are done here # ======================== private ========================================= @@ -368,5 +377,5 @@ def _packet_length_to_duration(num_bytes): def _change_state(self, new_state): self.state = new_state - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('state={0}'.format(self.state)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('state={0}'.format(self.state)) diff --git a/openvisualizer/bspemulator/bspsctimer.py b/openvisualizer/simulator/bspemulator/bspsctimer.py similarity index 55% rename from openvisualizer/bspemulator/bspsctimer.py rename to openvisualizer/simulator/bspemulator/bspsctimer.py index 8dd1388d..aa851d02 100644 --- a/openvisualizer/bspemulator/bspsctimer.py +++ b/openvisualizer/simulator/bspemulator/bspsctimer.py @@ -5,29 +5,31 @@ # https://openwsn.atlassian.net/wiki/display/OW/License import logging +from multiprocessing import get_logger -import bspmodule -from openvisualizer.utils import format_critical_message +from openvisualizer.simulator.bspemulator.bspmodule import BspModule -class BspSctimer(bspmodule.BspModule): +class BspSctimer(BspModule): """ Emulates the 'sctimer' BSP module. """ - _name = 'BspSctimer' - INTR_COMPARE = 'sctimer.compare' INTR_OVERFLOW = 'sctimer.overflow' ROLLOVER = 0xffffffff + 1 LOOP_THRESHOLD = 0xffffff + 1 - def __init__(self, motehandler): + def __init__(self, mote): # initialize the parent - super(BspSctimer, self).__init__(motehandler) + super(BspSctimer, self).__init__(mote) + + # logging + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) # local variables - self.timeline = self.engine.timeline - self.hw_crystal = self.motehandler.hw_crystal + self.hw_crystal = self.mote.hw_crystal self.running = False self.compare_armed = False self.time_last_reset = None @@ -42,11 +44,8 @@ def cmd_init(self): """ Emulates: void sctimer_init() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_init') - - # reset the timer - self._cmd_reset_internal() + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_init') # remember the time of last reset self.time_last_reset = self.hw_crystal.get_time_last_tick() @@ -56,12 +55,11 @@ def cmd_init(self): overflow_time = self.hw_crystal.get_time_in(self.ROLLOVER) # schedule overflow event - self.timeline.schedule_event( + self.mote.bsp_board.schedule_intr( at_time=overflow_time, - mote_id=self.motehandler.get_id(), + mote_id=self.mote.mote_id, cb=self.intr_overflow, - desc=self.INTR_OVERFLOW, - ) + desc=self.INTR_OVERFLOW) # the counter is now running self.running = True @@ -80,8 +78,8 @@ def cmd_set_compare(self, compare_value): self.cmd_enable() # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_set_compare compare_value=' + str(compare_value)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_set_compare compare_value=' + str(compare_value)) # get current counter value counter_val = self.hw_crystal.get_ticks_since(self.time_last_reset) @@ -97,24 +95,24 @@ def cmd_set_compare(self, compare_value): compare_time = self.hw_crystal.get_time_in(ticks_before_event) # schedule compare event - self.timeline.schedule_event(compare_time, - self.motehandler.get_id(), - self.intr_compare, - self.INTR_COMPARE) + self.mote.bsp_board.schedule_intr( + at_time=compare_time, + mote_id=self.mote.mote_id, + cb=self.intr_compare, + desc=self.INTR_COMPARE) # the compare is now scheduled self.compare_armed = True except Exception as err: - err_msg = format_critical_message(err) - self.log.critical(err_msg) + self.logger.critical(err) def cmd_read_counter(self): """ Emulates: uin16_t sctimer_readCounter() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_read_counter') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_read_counter') # get current counter value counter_val = self.hw_crystal.get_ticks_since(self.time_last_reset) @@ -126,8 +124,8 @@ def cmd_enable(self): """ Emulates: void sctimer_enable() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_enable') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_enable') self.int_enabled = True @@ -135,51 +133,12 @@ def cmd_disable(self): """ Emulates: void sctimer_disable() """ # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('cmd_disable') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_disable') # disable interrupt self.int_enabled = False - # ======================== private ========================================= - - def _cmd_reset_internal(self): - - # cancel the compare event - self.compare_armed = False - num_canceled = self.timeline.cancel_event( - mote_id=self.motehandler.get_id(), - desc=self.INTR_COMPARE, - ) - assert (num_canceled <= 1) - - # cancel the (internal) overflow event - self.running = False - num_canceled = self.timeline.cancel_event( - mote_id=self.motehandler.get_id(), - desc=self.INTR_OVERFLOW, - ) - assert (num_canceled <= 1) - - # reset the counter value - self.counterVal = 0 - - # remember the time of last reset - self.time_last_reset = self.hw_crystal.get_time_last_tick() - self.time_last_compare = self.time_last_reset - - # calculate time at overflow event (in 'ROLLOVER' ticks) - overflow_time = self.hw_crystal.get_time_in(self.ROLLOVER) - - # schedule overflow event - - self.timeline.schedule_event( - at_time=overflow_time, - mote_id=self.motehandler.get_id(), - cb=self.intr_overflow, - desc=self.INTR_OVERFLOW, - ) - # ======================== interrupts ====================================== def intr_overflow(self): @@ -189,22 +148,19 @@ def intr_overflow(self): self.time_last_reset = self.hw_crystal.get_time_last_tick() # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('time_last_reset=' + str(self.time_last_reset)) - self.log.debug('ROLLOVER=' + str(self.ROLLOVER)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('time_last_reset=' + str(self.time_last_reset)) + self.logger.debug('ROLLOVER=' + str(self.ROLLOVER)) # reschedule the next overflow event # Note: the intr_overflow will fire every self.ROLLOVER next_overflow_time = self.hw_crystal.get_time_in(self.ROLLOVER) - self.log.debug('next_overflow_time=' + str(next_overflow_time)) - self.timeline.schedule_event( + self.logger.debug('next_overflow_time=' + str(next_overflow_time)) + self.mote.bsp_board.schedule_intr( at_time=next_overflow_time, - mote_id=self.motehandler.get_id(), + mote_id=self.mote.mote_id, cb=self.intr_overflow, - desc=self.INTR_OVERFLOW, - ) - - print "cycle cycle\n" + desc=self.INTR_OVERFLOW) # do NOT kick the scheduler return False @@ -216,13 +172,13 @@ def intr_compare(self): self.time_last_compare = self.hw_crystal.get_time_last_tick() # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('time_last_compare=' + str(self.time_last_compare)) - self.log.debug('ROLLOVER=' + str(self.ROLLOVER)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('time_last_compare=' + str(self.time_last_compare)) + self.logger.debug('ROLLOVER=' + str(self.ROLLOVER)) if self.int_enabled: # send interrupt to mote - self.motehandler.mote.sctimer_isr() + self.mote.mote.sctimer_isr() # kick the scheduler return True diff --git a/openvisualizer/simulator/bspemulator/bspuart.py b/openvisualizer/simulator/bspemulator/bspuart.py new file mode 100644 index 00000000..50d62074 --- /dev/null +++ b/openvisualizer/simulator/bspemulator/bspuart.py @@ -0,0 +1,310 @@ +# Copyright (c) 2010-2013, Regents of the University of California. +# All rights reserved. +# +# Released under the BSD 3-Clause license as published at the link below. +# https://openwsn.atlassian.net/wiki/display/OW/License + +import logging +import threading +from multiprocessing import get_logger + +from openvisualizer.simulator.bspemulator.bspmodule import BspModule + + +class BspUart(BspModule): + """ Emulates the 'uart' BSP module """ + + INTR_TX = 'uart.tx' + INTR_RX = 'uart.rx' + BAUDRATE = 115200 + + XOFF = 0x13 + XON = 0x11 + XONXOFF_ESCAPE = 0x12 + XONXOFF_MASK = 0x10 + + def __init__(self, mote): + # initialize the parent + super(BspUart, self).__init__(mote) + + # logging + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) + + # local variables + self.interrupts_enabled = False + self.tx_interrupt_flag = False + self.rx_interrupt_flag = False + + self.uart_tx_buffer = [] # the bytes to be sent over UART + self.uart_tx_next = None # the byte that was just signaled to mote + self.uart_tx_buffer_lock = threading.Lock() + + self.f_xon_xoff_escaping = False + self.xon_xoff_escaped_byte = 0 + + rx_thread = threading.Thread(target=self._listen_incoming) + rx_thread.setDaemon(True) + rx_thread.start() + + # ======================== public ========================================== + + # === commands + + def cmd_init(self): + """ Emulates: void uart_init() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_init') + + # remember that module has been initialized + self.is_initialized = True + + def cmd_enable_interrupts(self): + """ Emulates: void uart_enableInterrupts() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_enable_interrupts') + + # update variables + self.interrupts_enabled = True + + def cmd_disable_interrupts(self): + """ Emulates: void cmd_disable_interrupts() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_disableInterrupts') + + # update variables + self.interrupts_enabled = False + + def cmd_clear_rx_interrupts(self): + """ Emulates: void uart_clearRxInterrupts() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_clear_rx_interrupts') + + # update variables + self.rx_interrupt_flag = False + + def cmd_clear_tx_interrupts(self): + """ Emulates: void uart_clearTxInterrupts() """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_clear_tx_interrupts') + + # update variables + self.tx_interrupt_flag = False + + def cmd_write_byte(self, byte_to_write): + """ Emulates: void uart_writeByte(uint8_t byte_to_write) """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_write_byte byte_to_write=' + str(byte_to_write)) + + # set tx interrupt flag + self.tx_interrupt_flag = True + + # calculate the time at which the byte will have been sent + done_sending_time = self.mote.bsp_board.get_current_time() + float(1.0 / float(self.BAUDRATE)) + + # schedule uart TX interrupt in 1/BAUDRATE seconds + self.mote.bsp_board.schedule_intr( + at_time=done_sending_time, + mote_id=self.mote.mote_id, + cb=self.intr_tx, + desc=self.INTR_TX) + + if byte_to_write == self.XON or byte_to_write == self.XOFF or byte_to_write == self.XONXOFF_ESCAPE: + self.f_xon_xoff_escaping = True + self.xon_xoff_escaped_byte = byte_to_write + + try: + self.mote.uart.tx.put([byte_to_write]) + except (EOFError, BrokenPipeError): + self.logger.error('Queue closed') + + def cmd_set_cts(self, state): + """ Emulates: void uart_setCTS(bool state) """ + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_set_cts state=' + str(state)) + + # set tx interrupt flag + self.tx_interrupt_flag = True + + # calculate the time at which the byte will have been sent + done_sending_time = self.mote.bsp_board.get_current_time() + float(1.0 / float(self.BAUDRATE)) + + # schedule uart TX interrupt in 1/BAUDRATE seconds + self.mote.bsp_board.schedule_intr( + at_time=done_sending_time, + mote_id=self.mote.mote_id, + cb=self.intr_tx, + desc=self.INTR_TX) + + try: + if state: + self.mote.uart.tx.put([self.XON]) + else: + self.mote.uart.tx.put([self.XOFF]) + except (EOFError, BrokenPipeError): + self.logger.error('Queue closed') + + def cmd_write_circular_buffer_fastsim(self, buf): + """ Emulates: void uart_writeCircularBuffer_FASTSIM(uint8_t* buffer, uint8_t len) """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_write_circular_buffer_fastsim buffer=' + str(buf)) + + self._write_buffer(buf) + + def uart_write_buffer_by_len_fastsim(self, buf): + """ Emulates: void uart_writeBufferByLen_FASTSIM(uint8_t* buffer, uint8_t len) """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('uart_write_buffer_by_len_fastsim buffer=' + str(buf)) + + self._write_buffer(buf) + + def _write_buffer(self, buf): + # set tx interrupt flag + self.tx_interrupt_flag = True + + # calculate the time at which the buffer will have been sent + done_sending_time = self.mote.bsp_board.get_current_time() + float(float(len(buf)) / float(self.BAUDRATE)) + + # schedule uart TX interrupt in len(buffer)/BAUDRATE seconds + self.mote.bsp_board.schedule_intr( + at_time=done_sending_time, + mote_id=self.mote.mote_id, + cb=self.intr_tx, + desc=self.INTR_TX) + + # add to receive buffer + i = 0 + temp_buf = [] + + while i != len(buf): + if buf[i] == self.XON or buf[i] == self.XOFF or buf[i] == self.XONXOFF_ESCAPE: + new_item = (self.XONXOFF_ESCAPE, buf[i] ^ self.XONXOFF_MASK) + temp_buf.append(new_item[0]) + temp_buf.append(new_item[1]) + else: + temp_buf.append(buf[i]) + i += 1 + + try: + self.mote.uart.tx.put([temp_buf]) + except (EOFError, BrokenPipeError): + self.logger.error('Queue closed') + + def cmd_read_byte(self): + """ Emulates: uint8_t uart_readByte()""" + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('cmd_read_byte') + + # retrieve the byte last sent + with self.uart_tx_buffer_lock: + return self.uart_tx_next + + # ======================== interrupts ====================================== + + def intr_tx(self): + """ Mote is done sending a byte over the UART. """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('intr_tx') + + if self.f_xon_xoff_escaping: + self.f_xon_xoff_escaping = False + + # set tx interrupt flag + self.tx_interrupt_flag = True + + # calculate the time at which the byte will have been sent + done_sending_time = self.mote.bsp_board.get_current_time() + float(1.0 / float(self.BAUDRATE)) + + # schedule uart TX interrupt in 1/BAUDRATE seconds + self.mote.bsp_board.schedule_intr( + at_time=done_sending_time, + mote_id=self.mote.mote_id, + cb=self.intr_tx, + desc=self.INTR_TX) + + # add to receive buffer + try: + self.mote.uart.tx.put([self.xon_xoff_escaped_byte ^ self.XONXOFF_MASK]) + except (EOFError, BrokenPipeError): + self.logger.error('Queue closed') + + else: + # send interrupt to mote + self.mote.mote.uart_isr_tx() + + # do *not* kick the scheduler + return False + + def intr_rx(self): + """ Interrupt to indicate to mote it received a byte from the UART. """ + + # log the activity + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('intr_rx') + + with self.uart_tx_buffer_lock: + + # make sure there is a byte to TX + assert len(self.uart_tx_buffer) + + # get the byte that is being transmitted + self.uart_tx_next = self.uart_tx_buffer.pop(0) + + # schedule the next interrupt, if any bytes left + if len(self.uart_tx_buffer): + self._schedule_next_tx() + + # send RX interrupt to mote + self.mote.mote.uart_isr_rx() + + # do *not* kick the scheduler + return False + + # ======================== private ========================================= + + def _schedule_next_tx(self): + # calculate time at which byte will get out + time_next_tx = self.mote.bsp_board.get_current_time() + float(1.0 / float(self.BAUDRATE)) + + # schedule that event + self.mote.bsp_board.schedule_intr( + at_time=time_next_tx, + mote_id=self.mote.mote_id, + cb=self.intr_rx, + desc=self.INTR_RX, + ) + + def _listen_incoming(self): + while True: + try: + rcv_buf = self.mote.uart.rx.get() + except (EOFError, BrokenPipeError): + self.logger.error('Queue closed') + break + + with self.uart_tx_buffer_lock: + self.uart_tx_buffer.extend(rcv_buf) + + self._schedule_next_tx() diff --git a/openvisualizer/bspemulator/hwcrystal.py b/openvisualizer/simulator/bspemulator/hwcrystal.py similarity index 78% rename from openvisualizer/bspemulator/hwcrystal.py rename to openvisualizer/simulator/bspemulator/hwcrystal.py index e774dca0..66c75b8a 100644 --- a/openvisualizer/bspemulator/hwcrystal.py +++ b/openvisualizer/simulator/bspemulator/hwcrystal.py @@ -1,4 +1,3 @@ -#!/usr/bin/python # Copyright (c) 2010-2013, Regents of the University of California. # All rights reserved. # @@ -7,54 +6,55 @@ import logging import random +from multiprocessing import get_logger -from openvisualizer.bspemulator.hwmodule import HwModule +from openvisualizer.simulator.bspemulator.hwmodule import HwModule class HwCrystal(HwModule): """ Emulates the mote's crystal. """ - _name = 'HwCrystal' - FREQUENCY = 32768 MAXDRIFT = 0 # ppm - def __init__(self, motehandler): + def __init__(self, mote): # initialize the parent - super(HwCrystal, self).__init__(motehandler) + super(HwCrystal, self).__init__(mote) # local variables - self.timeline = self.engine.timeline self.frequency = self.FREQUENCY self.max_drift = self.MAXDRIFT # local variables self.drift = float(random.uniform(-self.max_drift, self.max_drift)) - # the duration of one tick. Since it is constant, it is only calculated once by _getPeriod(). Therefore, do not - # use directly, rather use property period - self._period = None - # ts_tick is a timestamp associated with any tick in the past. Since the period is constant, it is used to # ensure alignement of timestamps to an integer number of ticks. self.ts_tick = None + self._period = float(1) / float(self.frequency) # nominal period + self._period += float(self.drift / 1000000.0) * float(self._period) # apply drift + + # logging + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) + # ======================== public ========================================== def start(self): """ Start the crystal. """ # get the timestamp of a - self.ts_tick = self.timeline.get_current_time() + self.ts_tick = self.mote.bsp_board.get_current_time() # log - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('crystal starts at ' + str(self.ts_tick)) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('crystal starts at ' + str(self.ts_tick)) def get_time_last_tick(self): """ Return the timestamp of the last tick. - :returns: The timestamp of the last tick. """ @@ -70,13 +70,12 @@ def get_time_last_tick(self): ^ | time_last_tick - ''' # make sure crystal has been started assert self.ts_tick is not None - current_time = self.timeline.get_current_time() + current_time = self.mote.bsp_board.get_current_time() time_since_last = current_time - self.ts_tick ticks_since_last = round(float(time_since_last) / float(self.period)) @@ -89,7 +88,6 @@ def get_time_last_tick(self): def get_time_in(self, num_ticks): """ Return the time it will be in a given number of ticks. - :param num_ticks: The number of ticks of interest. :returns: The time it will be in a given number of ticks. """ @@ -118,16 +116,15 @@ def get_time_in(self, num_ticks): return time_last_tick + num_ticks * self.period - def get_ticks_since(self, event_time): + def get_ticks_since(self, interrupt_time): """ Return the number of ticks since some timestamp. - - :param event_time: The time of the event of interest. + :param interrupt_time: The time of the event of interest. :returns: The number of ticks since the time passed. """ ''' - eventTime current_time + interrupt_time current_time | | period V V <----------> ----------------------------------------------------------------------- @@ -145,19 +142,19 @@ def get_ticks_since(self, event_time): assert self.ts_tick is not None # get the current time - current_time = self.timeline.get_current_time() + current_time = self.mote.bsp_board.get_current_time() - # make sure that event_time passed is in the past - assert (event_time <= current_time) + # make sure that interrupt_time passed is in the past + assert (interrupt_time <= current_time) # get the time of the last tick time_last_tick = self.get_time_last_tick() # return the number of ticks - if time_last_tick < event_time: + if time_last_tick < interrupt_time: return_val = 0 else: - return_val = int(float(time_last_tick - event_time) / float(self.period)) + return_val = int(float(time_last_tick - interrupt_time) / float(self.period)) return return_val @@ -165,9 +162,4 @@ def get_ticks_since(self, event_time): @property def period(self): - - if self._period is None: - self._period = float(1) / float(self.frequency) # nominal period - self._period += float(self.drift / 1000000.0) * float(self._period) # apply drift - return self._period diff --git a/openvisualizer/simulator/bspemulator/hwmodule.py b/openvisualizer/simulator/bspemulator/hwmodule.py new file mode 100644 index 00000000..113cb801 --- /dev/null +++ b/openvisualizer/simulator/bspemulator/hwmodule.py @@ -0,0 +1,20 @@ +# Copyright (c) 2010-2013, Regents of the University of California. +# All rights reserved. +# +# Released under the BSD 3-Clause license as published at the link below. +# https://openwsn.atlassian.net/wiki/display/OW/License + +from abc import ABCMeta +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from openvisualizer.simulator.emulatedmote import EmulatedMote + + +class HwModule(metaclass=ABCMeta): + """ Parent class for all hardware modules. """ + + def __init__(self, mote: 'EmulatedMote'): + # store variables + self.mote = mote + self.handler = mote.handler diff --git a/openvisualizer/bspemulator/hwsupply.py b/openvisualizer/simulator/bspemulator/hwsupply.py similarity index 63% rename from openvisualizer/bspemulator/hwsupply.py rename to openvisualizer/simulator/bspemulator/hwsupply.py index eb3c10a1..3c2dce64 100644 --- a/openvisualizer/bspemulator/hwsupply.py +++ b/openvisualizer/simulator/bspemulator/hwsupply.py @@ -1,24 +1,26 @@ -#!/usr/bin/python # Copyright (c) 2010-2013, Regents of the University of California. # All rights reserved. # # Released under the BSD 3-Clause license as published at the link below. # https://openwsn.atlassian.net/wiki/display/OW/License + import logging +from multiprocessing import get_logger -from openvisualizer.bspemulator.hwmodule import HwModule +from openvisualizer.simulator.bspemulator.hwmodule import HwModule class HwSupply(HwModule): """ Emulates the mote's power supply """ - _name = 'HwModule' - - INTR_SWITCHON = 'hw_supply.switchOn' - - def __init__(self, motehandler): + def __init__(self, mote): # initialize the parent - super(HwSupply, self).__init__(motehandler) + super(HwSupply, self).__init__(mote) + + # logging + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) # local variables self.mote_on = False @@ -28,8 +30,8 @@ def __init__(self, motehandler): def switch_on(self): # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('switchOn') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('switchOn') # filter error if self.mote_on: @@ -39,16 +41,16 @@ def switch_on(self): self.mote_on = True # have the crystal start now - self.motehandler.hw_crystal.start() + self.mote.hw_crystal.start() # send command to mote - self.motehandler.mote.supply_on() + self.mote.mote.supply_on() def switch_off(self): # log the activity - if self.log.isEnabledFor(logging.DEBUG): - self.log.debug('switchOff') + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug('switchOff') # filter error if not self.mote_on: @@ -57,6 +59,9 @@ def switch_off(self): # change local variable self.mote_on = False + # send command to mote + self.mote.mote.supply_off() + def is_on(self): return self.mote_on diff --git a/openvisualizer/simulator/emulatedmote.py b/openvisualizer/simulator/emulatedmote.py new file mode 100644 index 00000000..719e24dc --- /dev/null +++ b/openvisualizer/simulator/emulatedmote.py @@ -0,0 +1,212 @@ +import logging +import time +from multiprocessing import get_logger +from multiprocessing.process import current_process +from threading import Thread +from typing import TYPE_CHECKING, Tuple + +from openvisualizer.simulator.bspemulator.bspboard import BspBoard +from openvisualizer.simulator.bspemulator.bspdebugpins import BspDebugPins +from openvisualizer.simulator.bspemulator.bspeui64 import BspEui64 +from openvisualizer.simulator.bspemulator.bspleds import BspLeds +from openvisualizer.simulator.bspemulator.bspradio import BspRadio +from openvisualizer.simulator.bspemulator.bspsctimer import BspSctimer +from openvisualizer.simulator.bspemulator.bspuart import BspUart +from openvisualizer.simulator.bspemulator.hwcrystal import HwCrystal +from openvisualizer.simulator.bspemulator.hwsupply import HwSupply + +if TYPE_CHECKING: + from openvisualizer.simulator.moteprocess import Uart, Radio + from openvisualizer.simulator.simengine import MoteProcessInterface + from multiprocessing import Barrier, Event + +try: + import colorama as c + + color = True + c.init() +except ImportError: + color = False + + +class EmulatedMote: + + def __init__(self, mote, mote_interface: 'MoteProcessInterface'): + super(EmulatedMote, self).__init__() + + # Emulated mote process ID + self.mote_id = mote_interface.mote_id + + self.mote = mote + + self.uart: 'Uart' = mote_interface.uart + self.radio: 'Radio' = mote_interface.radio + self.cmd_if = mote_interface.cmd_if + + self.slot_barrier: 'Barrier' = mote_interface.slot_barrier + self.msg_barrier: 'Barrier' = mote_interface.msg_barrier + self.ack_barrier: 'Barrier' = mote_interface.ack_barrier + self.pause_event: 'Event' = mote_interface.pause_event + + self.cmd_listener_t = Thread(target=self.listener, args=()) + self.cmd_listener_t.setDaemon(True) + self.cmd_listener_t.start() + + self.start_time = time.time() + + # logging functions + self.handler = logging.StreamHandler() + ft = logging.Formatter(fmt='%(asctime)s [%(name)s:%(levelname)s] %(message)s', datefmt='%H:%M:%S') + self.handler.setFormatter(ft) + self.handler.setLevel(logging.DEBUG) + + self.logger = get_logger() + self.logger.addHandler(self.handler) + self.logger.setLevel(logging.INFO) + + # initialize Python BSP backend + self.hw_supply = HwSupply(self) + self.hw_crystal = HwCrystal(self) + self.bsp_board = BspBoard(self) + self.bsp_debugpins = BspDebugPins(self) + self.bsp_eui64 = BspEui64(self) + self.bsp_leds = BspLeds(self) + self.bsp_sctimer = BspSctimer(self) + self.bsp_radio = BspRadio(self) + self.bsp_uart = BspUart(self) + + # install BSP callback functions + self.mote.set_callback(self.mote.MOTE_NOTIF_board_init, self.bsp_board.cmd_init) + self.mote.set_callback(self.mote.MOTE_NOTIF_board_sleep, self.bsp_board.cmd_sleep) + self.mote.set_callback(self.mote.MOTE_NOTIF_board_slot_sync, self.bsp_board.cmd_barrier_slot_sync) + self.mote.set_callback(self.mote.MOTE_NOTIF_board_msg_sync, self.bsp_board.cmd_barrier_msg_sync) + self.mote.set_callback(self.mote.MOTE_NOTIF_board_ack_sync, self.bsp_board.cmd_barrier_ack_sync) + # debugpins + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_init, self.bsp_debugpins.cmd_init) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_frame_toggle, self.bsp_debugpins.cmd_frame_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_frame_clr, self.bsp_debugpins.cmd_frame_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_frame_set, self.bsp_debugpins.cmd_frame_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_slot_toggle, self.bsp_debugpins.cmd_slot_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_slot_clr, self.bsp_debugpins.cmd_slot_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_slot_set, self.bsp_debugpins.cmd_slot_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_fsm_toggle, self.bsp_debugpins.cmd_fsm_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_fsm_clr, self.bsp_debugpins.cmd_fsm_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_fsm_set, self.bsp_debugpins.cmd_fsm_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_task_toggle, self.bsp_debugpins.cmd_task_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_task_clr, self.bsp_debugpins.cmd_task_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_task_set, self.bsp_debugpins.cmd_task_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_isr_toggle, self.bsp_debugpins.cmd_isr_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_isr_clr, self.bsp_debugpins.cmd_isr_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_isr_set, self.bsp_debugpins.cmd_isr_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_radio_toggle, self.bsp_debugpins.cmd_radio_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_radio_clr, self.bsp_debugpins.cmd_radio_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_radio_set, self.bsp_debugpins.cmd_radio_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_ka_clr, self.bsp_debugpins.cmd_ka_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_ka_set, self.bsp_debugpins.cmd_ka_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_syncPacket_clr, self.bsp_debugpins.cmd_sync_packet_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_syncPacket_set, self.bsp_debugpins.cmd_sync_packet_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_syncAck_clr, self.bsp_debugpins.cmd_sync_ack_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_syncAck_set, self.bsp_debugpins.cmd_sync_ack_set) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_debug_clr, self.bsp_debugpins.cmd_debug_clr) + self.mote.set_callback(self.mote.MOTE_NOTIF_debugpins_debug_set, self.bsp_debugpins.cmd_debug_set) + # eui64 + self.mote.set_callback(self.mote.MOTE_NOTIF_eui64_get, self.bsp_eui64.cmd_get) + # leds + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_init, self.bsp_leds.cmd_init) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_error_on, self.bsp_leds.cmd_error_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_error_off, self.bsp_leds.cmd_error_off) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_error_toggle, self.bsp_leds.cmd_error_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_error_isOn, self.bsp_leds.cmd_error_is_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_radio_on, self.bsp_leds.cmd_radio_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_radio_off, self.bsp_leds.cmd_radio_off) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_radio_toggle, self.bsp_leds.cmd_radio_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_radio_isOn, self.bsp_leds.cmd_radio_is_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_sync_on, self.bsp_leds.cmd_sync_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_sync_off, self.bsp_leds.cmd_sync_off) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_sync_toggle, self.bsp_leds.cmd_sync_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_sync_isOn, self.bsp_leds.cmd_sync_is_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_debug_on, self.bsp_leds.cmd_debug_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_debug_off, self.bsp_leds.cmd_debug_off) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_debug_toggle, self.bsp_leds.cmd_debug_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_debug_isOn, self.bsp_leds.cmd_debug_is_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_all_on, self.bsp_leds.cmd_all_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_all_off, self.bsp_leds.cmd_all_off) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_all_toggle, self.bsp_leds.cmd_all_toggle) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_circular_shift, self.bsp_leds.cmd_circular_shift) + self.mote.set_callback(self.mote.MOTE_NOTIF_leds_increment, self.bsp_leds.cmd_increment) + # radio + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_init, self.bsp_radio.cmd_init) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_reset, self.bsp_radio.cmd_reset) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_setFrequency, self.bsp_radio.cmd_set_frequency) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_rfOn, self.bsp_radio.cmd_rf_on) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_rfOff, self.bsp_radio.cmd_rf_off) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_loadPacket, self.bsp_radio.cmd_load_packet) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_txEnable, self.bsp_radio.cmd_tx_enable) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_txNow, self.bsp_radio.cmd_tx_now) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_rxEnable, self.bsp_radio.cmd_rx_enable) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_rxNow, self.bsp_radio.cmd_rx_now) + self.mote.set_callback(self.mote.MOTE_NOTIF_radio_getReceivedFrame, self.bsp_radio.cmd_get_received_frame) + # sctimer + self.mote.set_callback(self.mote.MOTE_NOTIF_sctimer_init, self.bsp_sctimer.cmd_init) + self.mote.set_callback(self.mote.MOTE_NOTIF_sctimer_setCompare, self.bsp_sctimer.cmd_set_compare) + self.mote.set_callback(self.mote.MOTE_NOTIF_sctimer_readCounter, self.bsp_sctimer.cmd_read_counter) + self.mote.set_callback(self.mote.MOTE_NOTIF_sctimer_enable, self.bsp_sctimer.cmd_enable) + self.mote.set_callback(self.mote.MOTE_NOTIF_sctimer_disable, self.bsp_sctimer.cmd_disable) + # uart + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_init, self.bsp_uart.cmd_init) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_enableInterrupts, self.bsp_uart.cmd_enable_interrupts) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_disableInterrupts, self.bsp_uart.cmd_disable_interrupts) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_clearRxInterrupts, self.bsp_uart.cmd_clear_rx_interrupts) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_clearTxInterrupts, self.bsp_uart.cmd_clear_tx_interrupts) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_writeByte, self.bsp_uart.cmd_write_byte) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_writeCircularBuffer_FASTSIM, + self.bsp_uart.cmd_write_circular_buffer_fastsim) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_writeBufferByLen_FASTSIM, + self.bsp_uart.uart_write_buffer_by_len_fastsim) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_readByte, self.bsp_uart.cmd_read_byte) + self.mote.set_callback(self.mote.MOTE_NOTIF_uart_setCTS, self.bsp_uart.cmd_set_cts) + + def start(self) -> None: + self.logger.info(f"Booting mote_{self.mote_id} (PID = {current_process().pid}) ...") + + self.hw_supply.switch_on() + + now = time.time() + self.logger.info(f"Elapsed time: {now - self.start_time}") + self.logger.info(f"Simulation time: {self.bsp_board.get_current_time()}") + + def listener(self): + while True: + try: + rcv = self.cmd_if.get() + self.cmd_if.task_done() + + res = eval('self.' + rcv + '_cmd')() + self.cmd_if.put(str(res)) + self.cmd_if.join() + except (EOFError, BrokenPipeError): + self.logger.error('Queue closed') + break + + # commands to interact with emulated motes + + def get_runtime_cmd(self) -> Tuple[float, float]: + now = time.time() + real = now - self.start_time + bsp_time = self.bsp_board.get_current_time() + return real, bsp_time + + +def create_mote(mote_interface: 'MoteProcessInterface'): + try: + import openmote as mote + EmulatedMote(mote, mote_interface).start() + except ImportError: + if color: + print(c.Back.RED + c.Fore.WHITE + "Could not import python module 'openwsn'" + c.Style.RESET_ALL) + print(c.Back.RED + c.Fore.WHITE + "Failed to instantiate emulated mote" + c.Style.RESET_ALL) + print(c.Back.RED + c.Fore.WHITE + "Kill simulation with CTRL-C" + c.Style.RESET_ALL + "\n") + else: + print("Could not import python module 'openwsn'") + print("Failed to instantiate emulated mote") + print("Kill simulation with CTRL-C\n") diff --git a/openvisualizer/simulator/link.py b/openvisualizer/simulator/link.py new file mode 100644 index 00000000..79545baf --- /dev/null +++ b/openvisualizer/simulator/link.py @@ -0,0 +1,12 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from multiprocessing.queues import Queue + + +class Link: + """ Module that describes the link quality between two motes""" + + def __init__(self, pdr: int, rx: 'Queue'): + self.pdr = pdr + self.rx = rx diff --git a/openvisualizer/simulator/location.py b/openvisualizer/simulator/location.py new file mode 100644 index 00000000..3cf6ad7e --- /dev/null +++ b/openvisualizer/simulator/location.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# Copyright (c) 2010-2013, Regents of the University of California. +# All rights reserved. +# +# Released under the BSD 3-Clause license as published at the link below. +# https://openwsn.atlassian.net/wiki/display/OW/License + +import random +from typing import Tuple + + +class LocationManager: + """ The module which assigns locations to the motes. """ + + def __init__(self): + # get random location around Cory Hall, UC Berkeley + self.lat = 37.875095 - 0.0005 + random.random() * 0.0010 + self.lon = -122.257473 - 0.0005 + random.random() * 0.0010 + + # ======================== public ========================================== + + @property + def location(self) -> Tuple[float, float]: + # debug + return self.lat, self.lon + + @location.setter + def location(self, new_location: Tuple[float, float]): + self.lat = new_location[0] + self.lon = new_location[1] + + # ======================== private ========================================= diff --git a/openvisualizer/simulator/moteprocess.py b/openvisualizer/simulator/moteprocess.py new file mode 100644 index 00000000..440f9d31 --- /dev/null +++ b/openvisualizer/simulator/moteprocess.py @@ -0,0 +1,26 @@ +import multiprocessing +from collections import namedtuple +from typing import TYPE_CHECKING +from multiprocessing import Manager + +if TYPE_CHECKING: + from multiprocessing import Barrier, Event + +Radio = namedtuple('Radio', ['tx', 'rx']) +Uart = namedtuple('Uart', ['tx', 'rx']) + + +class MoteProcessInterface(object): + + def __init__(self, mote_id: int, slot_b: 'Barrier', msg_b: 'Barrier', ack_b: 'Barrier', pause_event: 'Event'): + self.mote_id = mote_id + self.slot_barrier = slot_b + self.msg_barrier = msg_b + self.ack_barrier = ack_b + self.pause_event = pause_event + + # communication channels + manager = Manager() + self.uart = Uart(manager.Queue(), manager.Queue()) + self.radio = Radio(manager.Queue(), manager.Queue()) + self.cmd_if = multiprocessing.JoinableQueue() diff --git a/openvisualizer/simulator/simengine.py b/openvisualizer/simulator/simengine.py new file mode 100644 index 00000000..7a481df1 --- /dev/null +++ b/openvisualizer/simulator/simengine.py @@ -0,0 +1,165 @@ +import logging +import random +import time +from multiprocessing import Process, Barrier, Event +from multiprocessing.process import current_process +from threading import Thread + +from openvisualizer.simulator.emulatedmote import create_mote +from openvisualizer.simulator.moteprocess import MoteProcessInterface +from openvisualizer.simulator.topology import Topology + + +class SimEngine(Thread): + """ Discrete event simulator. Spawns a process for each emulated mote. """ + + KEEP_RUNNING: bool = True + ADDRESS = ("localhost", 6000) + + def __init__(self, num_of_motes: int, topology: str = 'fully-meshed'): + # time line thread + super(SimEngine, self).__init__() + + self.name = "SimEngine" + + # unpause the simulator + self._pause_event = Event() + self._pause_event.set() + + self._start_time = time.time() + + self.num_of_motes = num_of_motes + + # internal objects to synchronize the individual mote processes. + self._slot_barrier = Barrier(num_of_motes) + self._msg_barrier = Barrier(num_of_motes) + self._ack_barrier = Barrier(num_of_motes) + + # create the mote interfaces + self.mote_interfaces = [ + MoteProcessInterface( + i, # mote id + self._slot_barrier, # barrier for ASN synchronization + self._msg_barrier, # barrier for message synchronization + self._ack_barrier, # barrier for acknowledgment synchronization + self._pause_event) # pause event + for i in range(1, self.num_of_motes + 1)] + + self.topology_t = Topology(self.mote_interfaces, topology) + + self.mote_processes = [Process(target=create_mote, args=(m_if,)) for m_if in self.mote_interfaces] + self.mote_cmd_ifs = {m_if.mote_id: m_if.cmd_if for m_if in self.mote_interfaces} + + self.mote_ids = [m_if.mote_id for m_if in self.mote_interfaces] + + # set up logger + handler = logging.StreamHandler() + ft = logging.Formatter(fmt='%(asctime)s [%(name)s:%(levelname)s] %(message)s', datefmt='%H:%M:%S') + handler.setFormatter(ft) + handler.setLevel(logging.DEBUG) + + self.logger = logging.getLogger("SimEngine") + self.logger.addHandler(handler) + self.logger.setLevel(logging.DEBUG) + + def pause(self): + """ Un/Pause the simulation engine. """ + if self._pause_event.is_set(): + self._pause_event.clear() + return True + else: + self._pause_event.set() + return False + + def run(self) -> None: + self.logger.info(f'Starting engine (PID = {current_process().pid})') + + self.topology_t.start() + + for mote in self.mote_processes: + time.sleep(0.2) + mote.start() + + while self.KEEP_RUNNING: + time.sleep(0.1) + + self.topology_t.stop() + self.topology_t.join() + + # terminate mote processes + self.logger.info("Terminating and joining mote processes {}".format([p.pid for p in self.mote_processes])) + + time.sleep(1) + for mote in self.mote_processes: + if mote.is_alive(): + mote.terminate() + mote.join() + + self.logger.info("Leaving SimEngine loop") + + def shutdown(self): + self.KEEP_RUNNING = False + + def connections_getter(self): + retrieved_connections = [] + return_val = [] + + for from_mote in self.mote_ids: + for to_mote in self.mote_ids: + if (to_mote, from_mote) not in retrieved_connections and to_mote != from_mote and \ + self.topology_t.connection_matrix[from_mote - 1][to_mote - 1] is not None: + return_val += [ + { + 'fromMote': from_mote, + 'toMote': to_mote, + 'pdr': self.topology_t.connection_matrix[from_mote - 1][to_mote - 1].pdr, + }, + ] + retrieved_connections += [(from_mote, to_mote)] + + return return_val + + def connections_setter(self, from_mote: int, to_mote: int, pdr: int): + + if pdr > 0: + if self.topology_t.connection_matrix[int(from_mote) - 1][int(to_mote) - 1]: + self.topology_t.connection_matrix[int(from_mote) - 1][int(to_mote) - 1].pdr = int(pdr) + else: + self.topology_t.connection_matrix[int(from_mote) - 1][int(to_mote) - 1] = \ + self.topology_t.create_link(pdr, int(to_mote)) + else: + self.topology_t.delete_link(from_mote, to_mote) + + def positions_getter(self): + mote_positions = [] + for m in self.mote_ids: + mote_positions += \ + [ + { + 'id': m, + 'lat': self.topology_t.position_list[m - 1].lat, + 'lon': self.topology_t.position_list[m - 1].lon + } + ] + + return mote_positions + + def positions_setter(self, new_positions): + for m in new_positions: + self.topology_t.position_list[int(m)].lat = new_positions[m]['lat'] + self.topology_t.position_list[int(m)].lon = new_positions[m]['lon'] + + def runtime_getter(self): + # choose a random mote + address = random.randint(1, len(self.mote_ids)) + + while not self.mote_cmd_ifs[address].empty(): + time.sleep(0.01) + + self.mote_cmd_ifs[address].put('get_runtime') + self.mote_cmd_ifs[address].join() + + rcv = self.mote_cmd_ifs[address].get() + self.mote_cmd_ifs[address].task_done() + + return eval(rcv) diff --git a/openvisualizer/simulator/topology.py b/openvisualizer/simulator/topology.py new file mode 100644 index 00000000..d0a1b103 --- /dev/null +++ b/openvisualizer/simulator/topology.py @@ -0,0 +1,113 @@ +import logging +import queue +from random import randint +from threading import Thread +from typing import List, Dict, Optional + +from openvisualizer.simulator.link import Link +from openvisualizer.simulator.location import LocationManager +from openvisualizer.simulator.moteprocess import Radio + + +class Topology(Thread): + def __init__(self, mote_ifs, topology_name="fully-meshed"): + super().__init__() + + # Dict [mote-id : radio{tx,rx}] + self.radio_qs: Dict[int, Radio] = {m_if.mote_id: m_if.radio for m_if in mote_ifs} + self.mote_ids = self.radio_qs.keys() + + self.go_on = True + + if topology_name in {'fully-meshed', 'linear', 'random'}: + self.topology_name = topology_name + else: + self.topology_name = 'fully-meshed' + + self._connection_matrix: List[List[Optional[Link]]] = [[None for m in self.mote_ids] for n in self.mote_ids] + self._position_list: List[LocationManager] = [LocationManager() for m in self.mote_ids] + + self.create_topology() + + handler = logging.StreamHandler() + ft = logging.Formatter(fmt='%(asctime)s [%(name)s:%(levelname)s] %(message)s', datefmt='%H:%M:%S') + handler.setFormatter(ft) + handler.setLevel(logging.DEBUG) + + self.logger = logging.getLogger("Topology") + self.logger.addHandler(handler) + self.logger.setLevel(logging.DEBUG) + + def run(self) -> None: + while self.go_on: + for mote in self.radio_qs.keys(): + try: + origin, packet, freq = self.radio_qs[mote].tx.get_nowait() + + links: List[Link] = self.connection_matrix[origin - 1] + + for link in links: + if link is not None: + if randint(1, 100) <= link.pdr: + link.rx.put([origin, packet, freq]) + link.rx.join() + else: + pass + + # notify sender that message was successfully passed along + self.radio_qs[mote].tx.task_done() + + except queue.Empty: + continue + except (EOFError, BrokenPipeError): + self.logger.error('Queue closed') + self.go_on = False + break + + self.logger.info("Exiting propagation loop") + + def create_topology(self): + if self.topology_name == "fully-meshed": + for from_mote in self.mote_ids: + for to_mote in self.mote_ids: + if from_mote == to_mote: + continue + + self._connection_matrix[from_mote - 1][to_mote - 1] = self.create_link(100, to_mote) + + elif self.topology_name == "linear": + for from_mote in self.mote_ids: + for to_mote in self.mote_ids: + if from_mote == to_mote: + continue + + if from_mote + 1 == to_mote or from_mote == to_mote + 1: + self._connection_matrix[from_mote - 1][to_mote - 1] = self.create_link(100, to_mote) + + elif self.topology_name == "random": + for from_mote in self.mote_ids: + for to_mote in self.mote_ids: + if from_mote == to_mote: + continue + + self._connection_matrix[from_mote - 1][to_mote - 1] = self.create_link(randint(0, 100), to_mote) + else: + self.logger.error('Unknown topology') + + def create_link(self, pdr: int, to_mote: int) -> 'Link': + return Link(pdr=pdr, rx=self.radio_qs[to_mote].rx) + + def delete_link(self, from_mote, to_mote) -> None: + self._connection_matrix[from_mote - 1][to_mote - 1] = None + self._connection_matrix[to_mote - 1][from_mote - 1] = None + + @property + def connection_matrix(self): + return self._connection_matrix + + @property + def position_list(self): + return self._position_list + + def stop(self) -> None: + self.go_on = False diff --git a/openvisualizer/topologies/0001-mesh.json b/openvisualizer/topologies/0001-mesh.json deleted file mode 100644 index b674a436..00000000 --- a/openvisualizer/topologies/0001-mesh.json +++ /dev/null @@ -1 +0,0 @@ -{"connections": [{"fromMote": 1, "toMote": 2, "pdr": 1.0}, {"fromMote": 1, "toMote": 4, "pdr": 1.0}, {"fromMote": 2, "toMote": 4, "pdr": 1.0}, {"fromMote": 2, "toMote": 6, "pdr": 1.0}, {"fromMote": 3, "toMote": 4, "pdr": 1.0}, {"fromMote": 3, "toMote": 5, "pdr": 1.0}, {"fromMote": 4, "toMote": 5, "pdr": 1.0}, {"fromMote": 4, "toMote": 6, "pdr": 1.0}, {"fromMote": 5, "toMote": 6, "pdr": 1.0}], "motes": [{"lat": 37.87555371618762, "lon": -122.25802509927024, "id": 1}, {"lat": 37.875147290614436, "lon": -122.25823515906504, "id": 2}, {"lat": 37.87518315488792, "lon": -122.2566987075463, "id": 3}, {"lat": 37.87524531846254, "lon": -122.25757342064209, "id": 4}, {"lat": 37.874809787643855, "lon": -122.25710741906954, "id": 5}, {"lat": 37.87473092334277, "lon": -122.25787189707734, "id": 6}], "DAGroot": null} \ No newline at end of file diff --git a/openvisualizer/topologies/0002-star.json b/openvisualizer/topologies/0002-star.json deleted file mode 100644 index 77270639..00000000 --- a/openvisualizer/topologies/0002-star.json +++ /dev/null @@ -1 +0,0 @@ -{"connections": [{"fromMote": 1, "toMote": 2, "pdr": 1.0}, {"fromMote": 1, "toMote": 3, "pdr": 1.0}, {"fromMote": 1, "toMote": 4, "pdr": 1.0}, {"fromMote": 1, "toMote": 5, "pdr": 1.0}, {"fromMote": 1, "toMote": 6, "pdr": 1.0}], "motes": [{"lat": 37.87509598650428, "lon": -122.25759092425096, "id": 1}, {"lat": 37.874729939052514, "lon": -122.25784057624395, "id": 2}, {"lat": 37.875517807129, "lon": -122.25738268306627, "id": 3}, {"lat": 37.8753043278405, "lon": -122.25807861561823, "id": 4}, {"lat": 37.87519625791454, "lon": -122.25692360086381, "id": 5}, {"lat": 37.874745922468485, "lon": -122.25717153357121, "id": 6}], "DAGroot": null} \ No newline at end of file diff --git a/openvisualizer/topologies/README.md b/openvisualizer/topologies/README.md deleted file mode 100644 index d0ce27a1..00000000 --- a/openvisualizer/topologies/README.md +++ /dev/null @@ -1,12 +0,0 @@ -This directory contains several example network topologies saved in JSON format. -The topologies can be loaded by OpenVisualizer with the command: - -`openv-server --load-topology=` - -To inspect the topology you can use the web interface: - -`openv-server --load-topology= --webserver=` - -Under the tab _topology_ you can see and manipulate the network topology. - -![openvisualizer_web_interface](../../images/web_interface_topology.png "A star topology") diff --git a/openvisualizer/utils.py b/openvisualizer/utils.py index c245f03c..08cfed5a 100644 --- a/openvisualizer/utils.py +++ b/openvisualizer/utils.py @@ -85,7 +85,7 @@ def format_buf(buf): def format_ipv6_addr(addr): # group by 2 bytes - addr = [buf2int(addr[2 * i:2 * i + 2]) for i in range(len(addr) / 2)] + addr = [buf2int(addr[2 * i:2 * i + 2]) for i in range(int(len(addr) / 2))] return ':'.join(["%x" % b for b in addr]) @@ -117,7 +117,7 @@ def hex2buf(s): return_val = [] - for i in range(len(s) / 2): + for i in range(int(len(s) / 2)): real_idx = i * 2 return_val.append(int(s[real_idx:real_idx + 2], 16)) @@ -230,83 +230,3 @@ def format_crash_message(thread_name, error): return_val += [format_critical_message(error)] return_val = '\n'.join(return_val) return return_val - - -def extract_component_codes(fw_definitions_path): - # find component codes in opendefs.h - log.verbose("extracting firmware component names") - - codes_found = {} - for line in open(fw_definitions_path, 'r'): - m = re.search(' *COMPONENT_([^ .]*) *= *(.*), *', line) - if m: - name = m.group(1) - try: - code = int(m.group(2), 16) - except ValueError: - log.error("component '{}' - {} is not a hex number".format(name, m.group(2))) - else: - log.debug("extracted component '{}' with code {}".format(name, code)) - codes_found[code] = name - - return codes_found - - -def extract_log_descriptions(fw_definitions_path): - # find error codes in opendefs.h - log.verbose("extracting firmware log descriptions.") - - codes_found = {} - for line in open(fw_definitions_path, 'r'): - m = re.search(' *ERR_.* *= *([xXA-Fa-f0-9]*), *// *(.*)', line) - if m: - desc = m.group(2).strip() - try: - code = int(m.group(1), 16) - except ValueError: - log.error("log description '{}' - {} is not a hex number".format(desc, m.group(2))) - else: - log.debug("extracted log description '{}' with code {}".format(desc, code)) - codes_found[code] = desc - - return codes_found - - -def extract_6top_rcs(fw_6top_definitions_path): - # find sixtop return codes in sixtop.h - log.verbose("extracting 6top return codes.") - - codes_found = {} - for line in open(fw_6top_definitions_path, 'r'): - m = re.search(' *#define *IANA_6TOP_RC_([^ .]*) *([xXA-Za-z0-9]+) *// *([^ .]*).*', line) - if m: - name = m.group(3) - try: - code = int(m.group(2), 16) - except ValueError: - log.error("return code '{}': {} is not a hex number".format(name, m.group(2))) - else: - log.debug("extracted 6top RC '{}' with code {}".format(name, code)) - codes_found[code] = name - - return codes_found - - -def extract_6top_states(fw_6top_definitions_path): - # find sixtop state codes in sixtop.h - log.verbose("extracting 6top states.") - - codes_found = {} - for line in open(fw_6top_definitions_path, 'r'): - m = re.search(' *SIX_STATE_([^ .]*) *= *([^ .]*), *', line) - if m: - name = m.group(1) - try: - code = int(m.group(2), 16) - except ValueError: - log.error("state '{}' - {} is not a hex number".format(name, m.group(2))) - else: - log.debug("extracted 6top state '{}' with code {}".format(name, code)) - codes_found[code] = name - - return codes_found diff --git a/requirements.txt b/requirements.txt index 20aedc77..1406baf0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,9 @@ PyDispatcher bottle Sphinx intelhex -setuptools<=44.1.1 -openwsn-coap>=0.0.7 +setuptools pycryptodome +openwsn-coap==0.0.8 cbor hkdf paho-mqtt diff --git a/scripts/serialtester_cli.py b/scripts/serialtester_cli.py index 205c114b..26c393e5 100755 --- a/scripts/serialtester_cli.py +++ b/scripts/serialtester_cli.py @@ -1,27 +1,13 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 -import logging import time import click -import coloredlogs -from openvisualizer.motehandler.moteprobe import serialmoteprobe +from openvisualizer.motehandler.moteprobe.emulatedmoteprobe import EmulatedMoteProbe from openvisualizer.motehandler.moteprobe.serialmoteprobe import SerialMoteProbe from openvisualizer.motehandler.moteprobe.serialtester import SerialTester - -for logger in [logging.getLogger(__name__), serialmoteprobe.log]: - coloredlogs.install(logger=logger, fmt="%(asctime)s [%(name)s:%(levelname)s] %(message)s", datefmt="%H:%m:%S", - level='WARNING') - - -def serialtest_tracer(msg): - if '---' in msg: - click.secho('\n' + msg, fg='blue', bold=True) - elif 'received' in msg: - click.secho(msg, fg='green') - else: - click.secho(msg) +from openvisualizer.simulator.simengine import SimEngine @click.command() @@ -36,41 +22,39 @@ def cli(port, baudrate, verbose, runs, pktlen, timeout): click.secho("Serial Tester Script...", bold=True) - smp = SerialMoteProbe(port=port, baudrate=baudrate) + if port.lower().startswith('emulated'): + is_simulated = True + simulator = SimEngine(1) + smp = EmulatedMoteProbe(simulator.mote_interfaces[0]) + simulator.start() + else: + is_simulated = False + smp = SerialMoteProbe(port=port, baudrate=baudrate) while smp.serial is None: time.sleep(0.1) - logger.info("initialized serial object") - tester = SerialTester(smp) - if verbose: - tester.set_trace(serialtest_tracer) - tester.set_num_test_pkt(runs) tester.set_test_pkt_length(pktlen) tester.set_timeout(timeout) + # wait until booted + if is_simulated: + click.echo('Waiting for booting node') + time.sleep(2) + + # start test click.secho("\nTest Setup:", bold=True) click.secho("----------------") click.secho("Iterations: {:>6}".format(runs)) click.secho("Packet length: {:>3}".format(pktlen)) click.secho("Echo timeout: {:>4}".format(timeout)) - click.secho("\nTest Progress:\n") - # start test - if verbose: - tester.test(blocking=True) - else: - tester.test(blocking=False) - - with click.progressbar(range(runs)) as bar: - for x in bar: - while tester.stats['numOk'] < x: - time.sleep(0.2) + click.echo('\n\nStart test...') + tester.test() - time.sleep(0.5) res = tester.get_stats() click.secho("\n\nTest Statistics:", bold=True) click.secho("----------------") @@ -81,15 +65,8 @@ def cli(port, baudrate, verbose, runs, pktlen, timeout): click.secho("\nKill with Ctrl-C.\n") - while True: - try: - time.sleep(0.5) - except KeyboardInterrupt: - smp.close() - smp.join() - break - - logger.info("quitting script") + smp.close() + smp.join() if __name__ == "__main__": diff --git a/setup.py b/setup.py index 58d64c31..5f6f0782 100644 --- a/setup.py +++ b/setup.py @@ -16,8 +16,8 @@ def _read_requirements(file_name): requirements file contains only module lines and comments. """ requirements = [] - with open(os.path.join(file_name)) as f: - for line in f: + with open(os.path.join(file_name)) as file: + for line in file: if not line.startswith('#'): requirements.append(line) return requirements @@ -33,12 +33,12 @@ def _read_requirements(file_name): setup( name=PACKAGE_NAME, packages=find_packages(exclude=['tests', '*.tests', 'tests.*', '*.tests.*']), - python_requires='<=2.7.18', + python_requires='>=3.6', include_package_data=True, entry_points={ 'console_scripts': [ - 'openv-server = openvisualizer.__main__:main', - 'openv-client = openvisualizer.client.main:cli', + 'openv-server = openvisualizer.__main__:cli', + 'openv-client = openvisualizer.client.__main__:cli', 'openv-serial = scripts.serialtester_cli:cli', 'openv-tun = scripts.ping_responder:cli', ], @@ -62,7 +62,7 @@ def _read_requirements(file_name): 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', 'Topic :: Communications', 'Topic :: Home Automation', 'Topic :: Internet', diff --git a/tests-requirements.txt b/tests-requirements.txt index b7bf815f..93253de9 100644 --- a/tests-requirements.txt +++ b/tests-requirements.txt @@ -1,2 +1,2 @@ mock -pytest==4.6.9 +pytest diff --git a/tests/conftest.py b/tests/conftest.py index c0628773..912b156a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ import socket import struct import time -import xmlrpclib +import xmlrpc.client from fcntl import ioctl from subprocess import Popen @@ -29,7 +29,7 @@ # Connect to a running instance of openv-server and retrieve the addresses of the motes in the network url = 'http://{}:{}'.format(HOST, str(PORT)) try: - rpc_server = xmlrpclib.ServerProxy(url) + rpc_server = xmlrpc.client.ServerProxy(url) mote_ids = rpc_server.get_mote_dict().keys() if None not in mote_ids: @@ -62,7 +62,7 @@ "'--opentun'") else: log.error(err) -except xmlrpclib as err: +except xmlrpc as err: log.error("Caught server fault -- {}".format(err)) @@ -153,21 +153,7 @@ def etun(): @pytest.fixture() def server(): - arguments = ['openv-server', '--sim=2', '--no-boot'] - server_proc = Popen(arguments, shell=False) - - # give openv-server time to boot - time.sleep(3) - - yield server_proc - - # kill the server - server_proc.terminate() - - -@pytest.fixture() -def server_booted(): - arguments = ['openv-server', '--sim=2'] + arguments = ['openv-server', 'simulation', '2'] server_proc = Popen(arguments, shell=False) # give openv-server time to boot diff --git a/tests/ov/test_frag.py b/tests/ov/test_frag.py index 36fb9ada..0f39019f 100644 --- a/tests/ov/test_frag.py +++ b/tests/ov/test_frag.py @@ -1,6 +1,7 @@ #!/usr/bin/env python2 import logging.handlers +import os from random import randint, shuffle import pytest @@ -36,15 +37,15 @@ for i in range(NUM_OF_TEST_VECTORS): # create an IPv6 packet with a random payload pkt = ip6.IPv6(src='bbbb::2', dst='bbbb::1', hlim=64) - pkt.add_payload("".join([chr(randint(0, 255)) for j in range(randint(MIN_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE))])) + pkt.add_payload(os.urandom(randint(MIN_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE))) # fragment the packet fragment_list = lo.sixlowpan_fragment(pkt) for j in range(len(fragment_list)): - fragment_list[j] = [ord(b) for b in raw(fragment_list[j])] + fragment_list[j] = [b for b in raw(fragment_list[j])] - pkt = [ord(b) for b in raw(pkt)] + pkt = [b for b in raw(pkt)] TEST_VECTORS.append((pkt, fragment_list)) @@ -81,17 +82,20 @@ def test_fragment_packet(random_6lwp_fragments): log.debug("Original packet (len: {}) -- {}".format(len(ip_pkt), ip_pkt)) - frags = [lo.SixLoWPAN("".join([chr(b) for b in f])) for f in fragmentor.do_fragment(ip_pkt)] + frags = [] + for frag in fragmentor.do_fragment(ip_pkt): + frags.append(lo.SixLoWPAN(bytes(frag))) + log.debug(frags) reassembled = lo.sixlowpan_defragment(frags) if len(reassembled) == 0: # the packet was not fragmented - log.debug(list(bytearray(raw(frags[0])))) + log.debug([int(b) for b in (raw(frags[0]))]) log.debug(ip_pkt) - assert ip_pkt == list(bytearray(raw(frags[0]))) + assert ip_pkt == [int(b) for b in (raw(frags[0]))] else: - log.debug(list(bytearray(raw(reassembled[1])))) + log.debug([int(b) for b in raw(reassembled[1])]) log.debug(ip_pkt) - assert ip_pkt == list(bytearray(raw(reassembled[1]))) + assert ip_pkt == [int(b) for b in raw(reassembled[1])] diff --git a/tests/ov/test_iotlabmoteprobe.py b/tests/ov/test_iotlabmoteprobe.py index 8d78dd96..bf4405de 100644 --- a/tests/ov/test_iotlabmoteprobe.py +++ b/tests/ov/test_iotlabmoteprobe.py @@ -1,4 +1,4 @@ -# !/usr/bin/env python2 +# !/usr/bin/env python3 import logging.handlers import socket @@ -53,11 +53,11 @@ def test_iotlabmoteprobe__attach_error_on_frontend(caplog): with caplog.at_level(logging.DEBUG, logger="MoteProbe"): mote = IotlabMoteProbe('dummy-10') timeout = 100 - while mote.isAlive() and timeout: + while mote.is_alive() and timeout: time.sleep(0.01) timeout = timeout - 1 - assert mote.isAlive() is False - assert 'Name or service not known' in caplog.text + assert mote.is_alive() is False + # assert 'Name or service not known' in caplog.text except Exception as e: mote.close() mote.join() diff --git a/tests/ov/test_moteprobe.py b/tests/ov/test_moteprobe.py index 3dc577ff..1b266d61 100644 --- a/tests/ov/test_moteprobe.py +++ b/tests/ov/test_moteprobe.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 import logging.handlers import time @@ -144,7 +144,7 @@ def test_moteprobe_run_and_exit(m_attach, m_detach): try: my_mock = MockMoteProbe('mock') # Thread should be running - assert my_mock.isAlive() + assert my_mock.is_alive() time.sleep(0.01) # Thread should have attached to serial pipe assert m_attach.called @@ -158,8 +158,8 @@ def test_moteprobe_run_and_exit(m_attach, m_detach): raise e -@pytest.mark.parametrize('prob_running', [('mock')], indirect=["prob_running"]) -def test_moteprobe_init(prob_running): +@pytest.mark.parametrize('prob_running', ['mock'], indirect=["prob_running"]) +def test_moteprobe_init(prob_running: MockMoteProbe): # Verify naming assert prob_running.portname == 'mock' assert prob_running.name == 'MoteProbe@mock' @@ -169,11 +169,11 @@ def test_moteprobe_init(prob_running): assert hasattr(prob_running, '_detach') assert hasattr(prob_running, '_attach') # Thread should be running - assert prob_running.isAlive() + assert prob_running.is_alive() -@pytest.mark.parametrize('probe_stopped', [('mock')], indirect=["probe_stopped"]) -def test_moteprobe__rx_buf_add(probe_stopped): +@pytest.mark.parametrize('probe_stopped', ['mock'], indirect=["probe_stopped"]) +def test_moteprobe__rx_buf_add(probe_stopped: MockMoteProbe): assert probe_stopped.rx_buf == '' for c in FRAME_IN_1: probe_stopped._rx_buf_add(chr(c)) @@ -192,8 +192,8 @@ def test_moteprobe__rx_buf_add(probe_stopped): assert probe_stopped.rx_buf == ''.join(chr(c) for c in FRAME_OUT_3) -@pytest.mark.parametrize('probe_stopped', [('mock')], indirect=["probe_stopped"]) -def test_moteprobe__handle_frame(probe_stopped): +@pytest.mark.parametrize('probe_stopped', ['mock'], indirect=["probe_stopped"]) +def test_moteprobe__handle_frame(probe_stopped: MockMoteProbe): probe_stopped.rx_buf = ''.join(chr(c) for c in VALID_FRAME_1) valid = probe_stopped._handle_frame() assert valid is True @@ -205,14 +205,15 @@ def test_moteprobe__handle_frame(probe_stopped): assert valid is False -@pytest.mark.parametrize('probe_stopped', [('mock')], indirect=["probe_stopped"]) -def test_moteprobe__parse_bytes(probe_stopped): +@pytest.mark.parametrize('probe_stopped', ['mock'], indirect=["probe_stopped"]) +def test_moteprobe__parse_bytes(probe_stopped: MockMoteProbe): # receive valid frame - probe_stopped._parse_bytes(chr(c) for c in FRAME_IN_4) + + probe_stopped._parse_bytes(bytearray(FRAME_IN_4)) assert probe_stopped.send_to_parser_data == FRAME_OUT_4 # garbage and valid frame, this verifies that it re-uses the end hdlc # flag from the invalid frame - probe_stopped._parse_bytes(chr(c) for c in FRAME_IN_5) + probe_stopped._parse_bytes(bytearray(FRAME_IN_5)) assert probe_stopped.send_to_parser_data == FRAME_OUT_5 @@ -223,7 +224,7 @@ def test_moteprobe__attach_error(m_attach, caplog): with caplog.at_level(logging.INFO, logger="MoteProbe"): my_mock = MockMoteProbe('mock') time.sleep(0.01) - assert my_mock.isAlive() is False + assert my_mock.is_alive() is False assert '_attach_failed' in caplog.text except Exception as e: my_mock.close() @@ -240,8 +241,8 @@ def test_moteprobe__rcv_data_error(m_rcv_data, caplog): time.sleep(0.01) assert m_rcv_data.called assert '_rcv_failed' in caplog.text - # should still be alive after a wrongfull receive - assert my_mock.isAlive() is True + # should still be alive after a failed receive + assert my_mock.is_alive() is True except Exception as e: my_mock.close() my_mock.join() diff --git a/tests/ov/test_sourceroute.py b/tests/ov/test_sourceroute.py index 82417454..db954139 100644 --- a/tests/ov/test_sourceroute.py +++ b/tests/ov/test_sourceroute.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 import json import logging diff --git a/tests/ov/test_utils.py b/tests/ov/test_utils.py index c14cf7b8..e37d473e 100644 --- a/tests/ov/test_utils.py +++ b/tests/ov/test_utils.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import json import logging.handlers diff --git a/tests/server_client/__init__.py b/tests/server_client/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/server_client/test_client.py b/tests/server_client/test_client.py deleted file mode 100644 index f2b858e5..00000000 --- a/tests/server_client/test_client.py +++ /dev/null @@ -1,92 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openvisualizer.client.main import cli - - -def test_start_cli(): - """ Tests entry point command. """ - runner = CliRunner() - result = runner.invoke(cli, []) - assert result.exit_code == 0 - assert "Usage:" in result.output - - -def test_list_support_rpcs(server): - runner = CliRunner() - result = runner.invoke(cli, ['list-methods']) - assert result.exit_code == 0 - assert "boot_motes" in result.output - - -def test_list_support_rpcs_booted(server_booted): - runner = CliRunner() - result = runner.invoke(cli, ['list-methods']) - assert result.exit_code == 0 - assert "boot_motes" in result.output - - -def test_get_motes(server): - runner = CliRunner() - result = runner.invoke(cli, ['motes']) - assert result.exit_code == 0 - assert "Attached motes (address | port):" in result.output - - -def test_get_motes_booted(server_booted): - runner = CliRunner() - result = runner.invoke(cli, ['motes']) - assert result.exit_code == 0 - assert "Attached motes (address | port):" in result.output - - -@pytest.mark.parametrize('opts', ['--mote=all', '--mote=0001']) -def test_boot_command(server, opts): - runner = CliRunner() - result = runner.invoke(cli, ['boot', opts]) - assert result.exit_code == 0 - - -@pytest.mark.parametrize('opts', ['--mote=all', '--mote=0001']) -def test_boot_command_booted(server_booted, opts): - runner = CliRunner() - result = runner.invoke(cli, ['boot', opts]) - assert result.exit_code == 0 - - -@pytest.mark.parametrize( - 'opts, out', - [('', 'No DAG root configured'), - ('0001', 'Ok!'), ('emulated1', 'Ok!'), - ('6846', 'Unknown port or address'), - ('emulated', 'Unknown port or address')]) -def test_set_root_booted(server_booted, opts, out): - runner = CliRunner() - if opts != '': - result = runner.invoke(cli, ['root', opts]) - else: - result = runner.invoke(cli, ['root']) - assert result.exit_code == 0 - assert out in result.output - - -@pytest.mark.parametrize( - 'opts, out', - [('', 'No DAG root configured'), - ('0001', 'Unknown port or address'), ('emulated1', 'Could not set None as root'), - ('6846', 'Unknown port or address'), ('emulated', 'Unknown port or address')]) -def test_set_root(server, opts, out): - runner = CliRunner() - if opts != '': - result = runner.invoke(cli, ['root', opts]) - else: - result = runner.invoke(cli, ['root']) - assert result.exit_code == 0 - assert out in result.output - - -def test_start_view(server): - runner = CliRunner() - result = runner.invoke(cli, ['view']) - assert result.exit_code == 0 - assert 'Usage:' in result.output diff --git a/tests/server_client/test_server.py b/tests/server_client/test_server.py deleted file mode 100644 index ca85fd36..00000000 --- a/tests/server_client/test_server.py +++ /dev/null @@ -1,59 +0,0 @@ -import logging -import os -import time -from subprocess import Popen - -from pytest import mark, param - -LOG_FILE = 'openv-server.log' - -logger = logging.getLogger(__name__) - - -@mark.parametrize('option, logs', - [('', ['Discovered serial-port(s):']), # testing plain openv-server boot (1) - ('--sim=2', # boot openv-server with 2 emulated motes (2) - ['- simulation = 2', - '- simulation topology = Pister-hack', - '- auto-boot sim motes = True', - '[OPENWSN] booted']), - # verify emulated moted do not boot - param('--sim=2 --no-boot', ['[OPENWSN] booted'], marks=mark.xfail(reason='motes not booted')), - ('--simtopo=linear', # specify a simulation topology but do not set a number of emulated motes (3) - ['Simulation topology specified but no --sim= given, switching to hardware mode', - 'Discovered serial-port(s):']), - ('--sim=5 --root=0001', # set simulation with five motes and specify root mote (4) - ['Setting mote 0001 as root', - '- simulation topology = Pister-hack', - '- auto-boot sim motes = True', - '- simulation = 5']), - ('--sim=2 --root=0001 --no-boot', # do not boot motes but try to set root (5) - ['Cannot set root', - '- set root = 0001', - '- auto-boot sim motes = False']), - ('--sim=2 --no-boot --load-topology=0002-star.json', # specify sim options and load topology (6) - ['option might be overwritten by the configuration in \'0002-star.json\'']), - ]) -def test_start_server(option, logs): - try: - os.remove(LOG_FILE) - except OSError: - logger.warning('Could not remove old log file: {}'.format(LOG_FILE)) - pass - opts = option.split(' ') - arguments = ['openv-server'] - if opts != ['']: - arguments.extend(opts) - p = Popen(arguments) - - # give the server time to boot - time.sleep(3) - - # kill server - p.terminate() - - with open(LOG_FILE, 'r') as f: - output = "".join(f.readlines()) - for log in logs: - assert log in output - assert 'Starting RPC server' in output