def _setup_open_node(self): """ Setup open node connection * Flash firmware * Start serial interface """ ret_val = 0 ret = self.g_m.open_node.setup(self.on_class.FW_AUTOTEST) ret_val += self._check(ret, 'open_node_setup', ret) self.on_serial = OpenNodeConnection() ret = self.on_serial.start() ret_val += self._check(ret, 'open_node_connection', ret) return ret_val
def _setup_linux_open_node(self): """ Setup Linux open node connection * Boot open node * Connect through serial and get ip address * SSH connect to it * get """ assert self.on_class.TYPE in ('a8', 'rpi3') # Should be adapted if already booted, so not enabled for cn_no assert self.cn_class.TYPE == 'iotlab' ret_val = 0 ret = self.g_m.open_node.setup(None, debug=False) self._assert(ret, 'linux_node_setup', ret, 'Linux Node Setup failed') match = self.g_m.open_node.wait_booted(timeout=300) self._assert(tst_ok(match != ''), 'linux_node_boot_timeout', 300, 'Linux Node Boot failed') # get ip address using serial # run socats commands to access Linux node on gateway self.linux_connection = open_linux_interface.OpenLinuxConnection() LOGGER.debug("Wait that Linux node starts") try: self.linux_connection.start() except open_linux_interface.LinuxConnectionError as err: self._assert(1, f'linux_node_init_error: {err.err_msg}', str(err), 'Setup Connection failed') # save mac address linux_mac_addr = self.linux_connection.get_mac_addr() self.ret_dict['mac']['ON'] = linux_mac_addr test_ok = (MAC_RE.match(linux_mac_addr) is not None) ret_val += self._check(tst_ok(test_ok), 'linux_mac_addr', linux_mac_addr) ret = self.linux_connection.flash() self._assert(ret, 'linux_node_flash_autotest', ret, 'Linux Open Node Flash Failed') # Create Linux open node connection through serial redirection self.on_serial = OpenNodeConnection(self.linux_connection.ip_addr) ret = self.on_serial.start() ret_val += self._check(ret, 'linux_open_node_serial', ret) return ret_val
def test_complete_auto_tests(self): """ Test a regular autotest """ # call with channel/flash/no_gps and blinking leds extra = query_string('channel=22&flash=1&gps=') ret = self.server.put('/autotest/blink', extra_environ=extra) ret_dict = ret.json print >> sys.stderr, ret_dict self.assertEquals([], ret_dict['error']) self.assertIn('on_serial_echo', ret_dict['success']) self.assertTrue('GWT' in ret_dict['mac']) self.assertEquals(0, ret_dict['ret']) # test that ON still on if self.board_cfg.board_type == 'a8': # Don't know ip address, just check TTY self.assertTrue(os.path.exists(self.board_cfg.board_class.TTY)) else: self.g_m.open_node.serial_redirection.start() ret = OpenNodeConnection.send_one_command(['get_time']) self.g_m.open_node.serial_redirection.stop() self.assertIsNotNone(ret) not_tested = (set(self.g_m.open_node.AUTOTEST_AVAILABLE) - set(AutoTestManager.TESTED_FEATURES)) self.assertEquals(not_tested, set())
def test_complete_auto_tests(self): """ Test a regular autotest """ # call with channel/flash/no_gps and blinking leds extra = query_string('channel=22&flash=1&gps=') ret = self.server.put('/autotest/blink', extra_environ=extra) ret_dict = ret.json print >> sys.stderr, ret_dict self.assertEqual([], ret_dict['error']) self.assertIn('on_serial_echo', ret_dict['success']) self.assertTrue('GWT' in ret_dict['mac']) self.assertEqual(0, ret_dict['ret']) # test that ON still on if self.board_cfg.board_type == 'a8': # Don't know ip address, just check TTY self.assertTrue(os.path.exists(self.board_cfg.board_class.TTY)) else: self.g_m.open_node.serial_redirection.start() ret = OpenNodeConnection.send_one_command(['get_time']) self.g_m.open_node.serial_redirection.stop() self.assertIsNotNone(ret) if self.board_cfg.linux_on_class is not None: autotest = self.board_cfg.linux_on_class.AUTOTEST_AVAILABLE not_tested = (set(autotest) - set(AutoTestManager.TESTED_FEATURES)) else: not_tested = (set(self.g_m.open_node.AUTOTEST_AVAILABLE) - set(AutoTestManager.TESTED_FEATURES)) self.assertEqual(not_tested, set())
def _setup_open_node_a8(self): """ Setup open node a8-m3 connection * Boot open node * Connect through serial and get ip address * SSH connect to it * get """ assert self.on_class.TYPE == 'a8' # Should be adapted if already booted, so not enabled for cn_no assert self.cn_class.TYPE == 'iotlab' ret_val = 0 ret = self.g_m.open_node.setup(None, debug=False) self._assert(ret, 'open_a8_setup', ret, 'Open Node Setup failed') match = self.g_m.open_node.wait_booted(timeout=300) self._assert(tst_ok(match != ''), 'open_a8_boot_timeout', 300, 'Open Node Boot failed') # get ip address using serial # run socats commands to access a8-m3 open node on gateway self.a8_connection = open_a8_interface.OpenA8Connection() LOGGER.debug("Wait that open a8 node starts") try: self.a8_connection.start() except open_a8_interface.A8ConnectionError as err: # pragma: no cover self._assert(1, 'open_a8_init_error: %s' % err.err_msg, str(err), 'Setup Connection failed') # save mac address a8_mac_addr = self.a8_connection.get_mac_addr() self.ret_dict['mac']['a8'] = a8_mac_addr test_ok = (MAC_RE.match(a8_mac_addr) is not None) ret_val += self._check(tst_ok(test_ok), 'a8_mac_addr', a8_mac_addr) ret = self.a8_connection.flash(self.g_m.open_node.A8_M3_FW_AUTOTEST) self._assert(ret, 'flash a8_autotest.elf', ret, 'OpenNodeFlash Failed') # Create open node a8-m3 connection through node-a8 serial redirection self.on_serial = OpenNodeConnection(self.a8_connection.ip_addr) ret = self.on_serial.start() ret_val += self._check(ret, 'open_a8_serial', ret) return ret_val
def send_n_cmds(command, num_times, step=0.5): """ Send a command multiple times and return array of answers """ answers = [] cmd = command.split() for _itr in range(0, num_times): # pylint:disable=unused-variable ans = OpenNodeConnection.send_one_command(cmd) ans = ' '.join(ans) if ans is not None else None answers.append(ans) time.sleep(step) return answers
def send_n_cmds(command, num_times, step=0.5): """ Send a command multiple times and return array of answers """ answers = [] cmd = command.split() for _itr in range(0, num_times): # pylint:disable=unused-variable ans = OpenNodeConnection.send_one_command(cmd) ans = ' '.join(ans) if ans is not None else None answers.append(ans) time.sleep(step) return answers
def _setup_open_node(self): """ Setup open node connection * Flash firmware * Start serial interface """ ret_val = 0 ret = self.g_m.open_node.setup(self.open_node.FW_AUTOTEST) ret_val += self._check(ret, "open_node_setup", ret) self.on_serial = OpenNodeConnection() ret = self.on_serial.start() ret_val += self._check(ret, "open_node_connection", ret) return ret_val
def _setup_open_node_a8(self): """ Setup open node a8-m3 connection * Boot open node * Connect through serial and get ip address * SSH connect to it * get """ assert self.open_node.TYPE == "a8" ret_val = 0 ret = self.g_m.open_node.setup(None, debug=False) self._assert(ret, "open_a8_setup", ret, "Open Node Setup failed") match = self.g_m.open_node.wait_booted(timeout=300) self._assert(tst_ok(match != ""), "open_a8_boot_timeout", 300, "Open Node Boot failed") # get ip address using serial # run socats commands to access a8-m3 open node on gateway self.a8_connection = open_a8_interface.OpenA8Connection() LOGGER.debug("Wait that open a8 node starts") try: self.a8_connection.start() except open_a8_interface.A8ConnectionError as err: # pragma: no cover self._assert(1, "open_a8_init_error: %s" % err.err_msg, str(err), "Setup Connection failed") # save mac address a8_mac_addr = self.a8_connection.get_mac_addr() self.ret_dict["mac"]["a8"] = a8_mac_addr test_ok = MAC_RE.match(a8_mac_addr) is not None ret_val += self._check(tst_ok(test_ok), "a8_mac_addr", a8_mac_addr) ret = self.a8_connection.flash(self.g_m.open_node.A8_M3_FW_AUTOTEST) self._assert(ret, "flash a8_autotest.elf", ret, "OpenNodeFlash Failed") # Create open node a8-m3 connection through node-a8 serial redirection self.on_serial = OpenNodeConnection(self.a8_connection.ip_addr) ret = self.on_serial.start() ret_val += self._check(ret, "open_a8_serial", ret) return ret_val
class AutoTestManager: """ Gateway and open node auto tests """ # Global used in tests to store checked open node features TESTED_FEATURES = set() def __init__(self, gateway_manager): self.g_m = gateway_manager board_cfg = board_config.BoardConfig() self.on_class = board_cfg.board_class self.on_board_type = board_cfg.board_type self.cn_class = board_cfg.cn_class self.linux_on_class = board_cfg.linux_on_class self.on_serial = None self.linux_connection = None self.ret_dict = {'ret': None, 'success': [], 'error': [], 'mac': {}} self.cn_measures = [] def _measures_handler(self, measure_str): """ control node measures Handler """ self.cn_measures.append(measure_str.split(' ')) @staticmethod def get_local_mac_addr(): """ Get eth0 mac address """ mac_addr = check_output(MAC_CMD, stderr=STDOUT, shell=True) return mac_addr.decode().strip() def setup_control_node(self): """ setup connection with control_node""" LOGGER.info("Setup autotests") ret_val = 0 # configure Control Node ret_val += self.g_m.control_node.autotest_setup(self._measures_handler) gwt_mac_addr = self.get_local_mac_addr() self.ret_dict['mac']['GWT'] = gwt_mac_addr test_ok = (MAC_RE.match(gwt_mac_addr) is not None) ret_val += self._check(tst_ok(test_ok), 'gw_mac_addr', gwt_mac_addr) self._assert(ret_val, 'setup_cn_connection', ret_val, 'Setup control node failed') def _setup_open_node(self): """ Setup open node connection * Flash firmware * Start serial interface """ ret_val = 0 ret = self.g_m.open_node.setup(self.on_class.FW_AUTOTEST) ret_val += self._check(ret, 'open_node_setup', ret) self.on_serial = OpenNodeConnection() ret = self.on_serial.start() ret_val += self._check(ret, 'open_node_connection', ret) return ret_val def _setup_linux_open_node(self): """ Setup Linux open node connection * Boot open node * Connect through serial and get ip address * SSH connect to it * get """ assert self.on_class.TYPE in ('a8', 'rpi3') # Should be adapted if already booted, so not enabled for cn_no assert self.cn_class.TYPE == 'iotlab' ret_val = 0 ret = self.g_m.open_node.setup(None, debug=False) self._assert(ret, 'linux_node_setup', ret, 'Linux Node Setup failed') match = self.g_m.open_node.wait_booted(timeout=300) self._assert(tst_ok(match != ''), 'linux_node_boot_timeout', 300, 'Linux Node Boot failed') # get ip address using serial # run socats commands to access Linux node on gateway self.linux_connection = open_linux_interface.OpenLinuxConnection() LOGGER.debug("Wait that Linux node starts") try: self.linux_connection.start() except open_linux_interface.LinuxConnectionError as err: self._assert(1, f'linux_node_init_error: {err.err_msg}', str(err), 'Setup Connection failed') # save mac address linux_mac_addr = self.linux_connection.get_mac_addr() self.ret_dict['mac']['ON'] = linux_mac_addr test_ok = (MAC_RE.match(linux_mac_addr) is not None) ret_val += self._check(tst_ok(test_ok), 'linux_mac_addr', linux_mac_addr) ret = self.linux_connection.flash() self._assert(ret, 'linux_node_flash_autotest', ret, 'Linux Open Node Flash Failed') # Create Linux open node connection through serial redirection self.on_serial = OpenNodeConnection(self.linux_connection.ip_addr) ret = self.on_serial.start() ret_val += self._check(ret, 'linux_open_node_serial', ret) return ret_val def _setup_open_node_connection(self): """ Setup the connection with Open Node Should be done on DC""" ret_val = 0 ret_val += self._open_node_start() if self.linux_on_class is not None: ret_val += self._setup_linux_open_node() else: ret_val += self._setup_open_node() if ret_val != 0: # pragma: no cover raise FatalError('Setup Open Node failed') def _teardown_open_node(self, stop): """ Stop open node connection * Flash firmware * Start serial interface """ ret_val = 0 ret_val += self._on_serial_stop() ret_val += self.g_m.open_node.serial_redirection.stop() # Teardown to stop open node when not blinking if stop: ret_val += self.g_m.open_node.teardown() LOGGER.debug("Stop open node, no blinking") return ret_val def _on_serial_stop(self): """Stop open node serial without crash.""" ret_val = 0 try: self.on_serial.stop() except AttributeError: # pragma: no cover ret_val += 1 # access NoneType attribute finally: self.on_serial = None return ret_val def teardown(self, blink): """ cleanup """ ret_val = 0 LOGGER.info("Teardown autotests") # ensure DC alim ret_val += self._open_node_start() ret_val += self.set_result_leds(blink) ret_val += self._teardown_open_node(stop=(not blink)) self.g_m.control_node.autotest_teardown(stop_on=(not blink)) LOGGER.debug("cn_serial stopped") return self._check(ret_val, 'teardown', ret_val) def auto_tests(self, channel=None, blink=False, flash=False, gps=False): """ run auto-tests on nodes and gateway using 'gateway_manager' """ ret_val = 0 self.TESTED_FEATURES.clear() try: self.setup_control_node() # Tests using Battery # dc -> battery: a8 reboot # battery -> dc: No reboot # a8 may not work with new batteries (not enough power) # so check battery and then switch to DC ret_val += self.test_consumption_batt() # switch to DC and configure open node self._setup_open_node_connection() # add more delay for slow boards if self.on_board_type == 'zigduino': time.sleep(3) else: time.sleep(1) self.check_echo() self.check_get_time() self.get_uid() # Other tests, run on DC ret = self._open_node_start() ret_val += self._check(ret, 'switch_to_dc', ret) # Test using leds commands self.set_leds_off_and_on() # test IMU ret_val += self.test_gyro() ret_val += self.test_magneto() ret_val += self.test_accelero() # test m3-on communication ret_val += self.test_gpio() ret_val += self.test_i2c() # radio tests ret_val += self.test_radio_ping_pong(channel) ret_val += self.test_radio_with_rssi(channel) # test consumption measures ret_val += self.test_consumption_dc() # m3 specific tests # cannot test this with a8 I think ret_val += self.test_leds_with_consumption() # test m3 specific sensors ret_val += self.test_pressure() ret_val += self.test_light() ret_val += self.test_flash(flash) # run test_gps if requested ret_val += self.test_gps(gps) except FatalError as err: # Fatal Error during test, don't run further tests LOGGER.error("Fatal Error in tests, stop further tests: %s", str(err)) ret_val += 1 # shutdown node if test failed ret_val += self.teardown(blink and (ret_val == 0)) self.ret_dict['ret'] = ret_val return self.ret_dict def _check(self, ret, operation, log_message=''): """ Check the operation Adds `operation` to ret_dict in the correct failed or success entry :param ret: 0 means success non zero means failure :return: 0 if `ret` == 0, a positive value otherwise """ if int(ret) == 0: self.ret_dict['success'].append(operation) LOGGER.debug('autotest: %r OK: %r', operation, log_message) else: self.ret_dict['error'].append(operation) LOGGER.error('Autotest: %r: %r', operation, log_message) return abs(int(ret)) def _assert(self, ret, operation, log_message, exc_message): """ Shortcut for check + FatalError """ ret_val = self._check(ret, operation, log_message) if ret_val: raise FatalError(exc_message) def _run_test(self, num, cmd, parse_function): """ Run a test 'num' times. In case of success parse answer with 'parse_function' """ values = [] for _itr in range(0, num): # pylint:disable=unused-variable (ret, answer) = self._on_call(cmd) if ret: continue values.append(parse_function(answer)) return values def _on_serial_send_command(self, cmd): """Set command to open node serial.""" # Also register called commands. self.TESTED_FEATURES.add(cmd[0]) return self.on_serial.send_command(cmd) def _on_call(self, cmd): """ Call command to Open Node and expect correct answer cmd args ACK cmd [answer_args] """ answer = self._on_serial_send_command(cmd) if (answer is None) or (answer[0:2] != ['ACK', cmd[0]]): self._check(1, f"On Command: {cmd}", answer) return (1, answer) return (0, answer) # Test implementation @autotest_checker('leds_off', 'leds_blink') def set_result_leds(self, blink): """Make leds blink in case of success. Turn off on failure.""" try: self._set_results_leds(blink) return 0 except FatalError: LOGGER.error('Set blinking leds failed.') return 1 def _set_results_leds(self, blink): """Make leds blink in case of success. Turn off on failure.""" # Clean leds state self._on_call(['leds_off', '7']) if not blink: # pragma: no cover return # Blink leds on success self._on_call(['leds_blink', '7', '500']) self._control_node_leds_blink() @autotest_control_node_checker('leds') def _control_node_leds_blink(self): """Set control nodes blink.""" self.g_m.control_node.protocol.green_led_blink() # Require 'echo' command def check_echo(self): """ run the echo command on the serial port """ # echo arg1 arg2: ['arg1', 'arg2'] cmd = ['echo', 'HELLO', 'WORLD'] answer = self._on_serial_send_command(cmd) _answer = answer or [] # Replace None test_ok = _answer[0:2] == ['HELLO', 'WORLD'] self._assert(tst_ok(test_ok), 'on_serial_echo', answer, "echo failed. Can't communicate with open node") # Require 'get_time' command def check_get_time(self): """ runs the 'get_time' command Error on this check are fatal """ # get_time: ['ACK', 'get_time', '122953', 'tick_32khz'] answer = self._on_call(['get_time'])[1] values = self._run_test(5, ['get_time'], (lambda x: x[2].isdigit())) test_ok = (any(values)) self._assert(tst_ok(test_ok), 'on_serial_get_time', answer, "get_time failed. Can't communicate with ON") @autotest_checker('get_uid') def get_uid(self): """ runs the 'get_uid' command And add the resulting UID to the global return dictionary """ # get_uid: ['ACK', 'get_uid', '05D8FF323632483343037109'] values = self._run_test(1, ['get_uid'], (lambda x: x[2])) test_ok = len(values) if test_ok: uid_str = values[0] # UID: split every 4 char uid_split = [''.join(x) for x in zip(*[iter(uid_str)] * 4)] uid = ':'.join(uid_split) self.ret_dict['open_node_uid'] = uid else: # pragma: no cover pass ret_val = self._check(tst_ok(test_ok), 'get_uid', values) return ret_val @autotest_checker('leds_on', 'leds_off') def set_leds_off_and_on(self): """Turn leds off and on.""" # Clean leds state self._on_call(['leds_off', '7']) time.sleep(1) self._on_call(['leds_on', '7']) # sensors and flash @autotest_checker('test_flash') def test_flash(self, flash): """ test Flash """ if not flash: return 0 values = self._run_test(1, ['test_flash'], (lambda x: x)) test_ok = len(values) return self._check(tst_ok(test_ok), 'test_flash', values) @autotest_checker('get_pressure') def test_pressure(self): """ test pressure sensor """ # ['ACK', 'get_pressure', '9.944219E2', 'mbar'] values = self._run_test(10, ['get_pressure'], (lambda x: float(x[2]))) test_ok = len(set(values)) > 1 return self._check(tst_ok(test_ok), 'test_pressure', values) @autotest_checker('get_light', 'leds_on', 'leds_off') def test_light(self): """ test light sensor with leds""" # ['ACK', 'get_light', '5.2001953E1', 'lux'] # get light with leds self._on_call(['leds_on', '7']) values_on = self._run_test(5, ['get_light'], (lambda x: float(x[2]))) # get light without leds self._on_call(['leds_off', '7']) values_off = self._run_test(5, ['get_light'], (lambda x: float(x[2]))) values = values_on + values_off test_ok = len(set(values)) > 1 return self._check(tst_ok(test_ok), 'get_light', values) # Test GPS def _test_pps_open_node(self, timeout): """ Test the pps on open a8 m3 node""" ret_val = 0 # start pps on open node (ret, answer) = self._on_call(['test_pps_start']) if ret: # pragma: no cover return self._check(1, 'test_pps_start', answer) end_time = time.time() + timeout while time.time() < end_time: time.sleep(5) (ret, answer) = self._on_call(['test_pps_get']) if ret: # pragma: no cover return self._check(1, 'test_pps_get', answer) # get pps value pps_count = int(answer[2]) if pps_count > 2: ret_val = self._check(0, 'test_pps_open_node', pps_count) break else: ret_val = self._check(1, 'test_pps_open_node_timeout', 0) self._on_call(['test_pps_stop']) return ret_val def _test_pps_open_node_invalid(self): """Test an invalid command sent to open a8 m3 node.""" self._on_call(['test_pps_invalid']) @autotest_checker('test_pps_start', 'test_pps_get', 'test_pps_stop') def test_gps(self, gps): """ Test the gps """ if not gps: return 0 ret_val = 0 # ret_val += self._test_gps_serial() ret_val += self._test_pps_open_node(120.0) # try to get pps, max 2 min ret_val = self._check(ret_val, 'test_gps', ret_val) return ret_val # Control Node <--> Open Node Interraction @autotest_checker('test_gpio') @autotest_control_node_checker('open_node_gpio') def test_gpio(self): """ test GPIO connections """ return self._test_on_cn(5, ['test_gpio']) @autotest_checker('test_i2c') @autotest_control_node_checker('open_node_i2c') def test_i2c(self): """ test i2c communication """ return self._test_on_cn(1, ['test_i2c']) def _test_on_cn(self, num, cn_command, on_cmd=None, args=None): """ Run a test command between open node and control node setup control node run num times on open node teardown control node """ on_cmd = on_cmd or cn_command # on_cmd is the same as cn_command args = args or [] debug_str = f'{cn_command[0]}_on_cn' ret_val = 0 # setup control node ret_val += self.g_m.control_node.protocol.send_cmd(cn_command + ['start'] + args) # Run num times values = self._run_test(num, on_cmd + args, (lambda x: 0)) test_ok = (0 in values) # at least one success ret_val += self._check(tst_ok(test_ok), debug_str, values) # teardown ret = self.g_m.control_node.protocol.send_cmd(cn_command + ['stop']) ret_val += self._check(ret, debug_str, 'cleanup error') return ret_val # Inertial Measurement Unit @autotest_checker('get_magneto') def test_magneto(self): """ test magneto sensor """ # ['ACK', 'get_magneto' '4.328358E-2', '6.716418E-2', '-3.880597E-1', # 'gauss'] return self._test_xyz_sensor('get_magneto') @autotest_checker('get_gyro') def test_gyro(self): """ test gyro sensor """ # ['ACK', 'get_gyro', '1.07625', '1.75', '5.2500002E-2', 'dps'] return self._test_xyz_sensor('get_gyro') @autotest_checker('get_accelero') def test_accelero(self): """ test accelerator sensor """ # ['ACK', 'get_accelero', '3.6E-2', '-1.56E-1', '1.0320001', 'g'] return self._test_xyz_sensor('get_accelero') def _test_xyz_sensor(self, sensor): """ Test sensors returning 'xyz' float values """ # ['ACK', sensor, '3.6E-2', '-1.56E-1', '1.0320001', unit] values = self._run_test(10, [sensor], (lambda x: tuple(float(val) for val in x[2:5]))) test_ok = len(set(values)) > 1 # got different values return self._check(tst_ok(test_ok), sensor, values) # Radio tests @autotest_checker('radio_ping_pong') @autotest_control_node_checker('radio') def test_radio_ping_pong(self, channel): """ test Radio Ping-pong with control-node """ if channel is None: return 0 return self._test_on_cn(10, ['test_radio_ping_pong'], ['radio_ping_pong'], [str(channel), '3dBm']) @autotest_checker('radio_pkt') @autotest_control_node_checker('radio') def test_radio_with_rssi(self, channel): """ Test radio with rssi""" self.cn_measures = [] if channel is None: return 0 # pkt length = 125 # one measure every ~0.01 seconds ret_val = 0 radio = Radio("rssi", [channel], period=10, num_per_channel=0) cmd_on = ['radio_pkt', str(channel), '3dBm'] # get RSSI while sending 10 packets length 125 ret_val += self.g_m.control_node.protocol.config_radio(radio) for _itr in range(0, 10): # pylint:disable=unused-variable self._on_call(cmd_on) time.sleep(0.5) ret_val += self.g_m.control_node.protocol.config_radio(None) # ('11', '-91') # -91 == no radio detected measures = extract_measures(self.cn_measures) values = [v[1] for v in measures['radio']['values']] # check that there are values other than -91 measured test_ok = set([-91]) != set(values) ret_val += self._check(tst_ok(test_ok), 'rssi_measures', set(values)) return ret_val # Consumption tests @autotest_control_node_checker('consumption') def test_consumption_dc(self): """ Try consumption for DC """ ret_val = 0 # one measure every ~0.1 seconds conso = Consumption(self.g_m.open_node.ALIM, 'dc', '588', '64', True, True, True) ret_val += self._open_node_start() self.cn_measures = [] # store 2 secs of measures ret_val += self.g_m.control_node.protocol.config_consumption(conso) time.sleep(2) # get measures for 2 seconds ret_val += self.g_m.control_node.protocol.config_consumption(None) time.sleep(2) # wait 2 seconds for flush # (0.257343, 3.216250, 0.080003) measures = extract_measures(self.cn_measures) values = measures['consumption']['values'] # Value ranges may be validated with an Idle firmware test_ok = len(set(values)) > 1 ret_val += self._check(tst_ok(test_ok), 'consumption_dc', values) return ret_val @autotest_control_node_checker('battery') # no more batteries @autotest_control_node_checker('consumption') def test_consumption_batt(self): # pragma: no cover """ Try consumption for Battery """ # Disable battery tests as they have been unplugged return 0 ret_val = 0 # pylint: disable=unreachable ret_val += self.g_m.control_node.open_start('battery') # set a firmware on m3 to ensure corret consumption measures # on a8, linux is consuming enough I think if self.on_class.TYPE == 'm3': # pragma: no branch time.sleep(1) ret = self.g_m.open_node.flash(self.on_class.FW_AUTOTEST) ret_val += self._check(ret, 'flash_m3_on_battery', ret) # configure consumption # one measure every ~0.1 seconds conso = Consumption(self.g_m.open_node.ALIM, 'battery', 1100, 64, True, True, True) self.cn_measures = [] ret_val += self.g_m.control_node.protocol.config_consumption(conso) ret_val += self.g_m.control_node.open_stop('battery') time.sleep(1) ret_val += self.g_m.control_node.open_start('battery') time.sleep(1) # stop ret_val += self.g_m.control_node.protocol.config_consumption(None) time.sleep(1) # Flush last values # (0.257343, 3.216250, 0.080003) measures = extract_measures(self.cn_measures) values = measures['consumption']['values'] test_ok = len(set(values)) > 1 ret_val += self._check(tst_ok(test_ok), 'consumption_batt', values) # Value ranges may be validated with an Idle firmware return ret_val @autotest_checker('leds_consumption', 'leds_on', 'leds_off') @autotest_control_node_checker('consumption') def test_leds_with_consumption(self): """ Test Leds with consumption Start consumption measure Then switch different leds on and save the timestamp where it's done Finally compare that consumption with no leds on was lower than with one or more leds on. """ self._on_call(['leds_off', '7']) # one measure every ~0.1 seconds ret_val = 0 conso = Consumption(self.g_m.open_node.ALIM, 'dc', '1100', '64', True, True, True) ret_val += self._open_node_start() self.cn_measures = [] leds_timestamps = [] # get consumption for all leds mode: # no leds, each led, all leds ret_val += self.g_m.control_node.protocol.config_consumption(conso) for leds in ['0', '1', '2', '4', '7']: self._on_call(['leds_on', leds]) time.sleep(0.5) leds_timestamps.append(time.time()) time.sleep(0.5) self._on_call(['leds_off', '7']) ret_val += self.g_m.control_node.protocol.config_consumption(None) time.sleep(1) # wait last values # (0.257343, 3.216250, 0.080003) measures = extract_measures(self.cn_measures) values = [v[0] for v in measures['consumption']['values']] timestamps = measures['consumption']['timestamps'] led_consumption = [] LOGGER.debug("t0, tEnd: %r - %r", timestamps[0], timestamps[-1]) LOGGER.debug("leds_timestamps: %r", leds_timestamps) for led_time in leds_timestamps: try: led_consumption.append(values[bisect(timestamps, led_time)]) except IndexError as err: # pragma: no cover LOGGER.debug(err) led_consumption.append(float('NaN')) # check that consumption is higher with each led than with no leds on led_0 = led_consumption.pop(0) test_ok = all(led_0 < v for v in led_consumption) ret_val += self._check(tst_ok(test_ok), 'leds_using_conso', (led_0, led_consumption)) return ret_val @autotest_control_node_checker('open_node_power') def _open_node_start(self): return self.g_m.control_node.open_start('dc')
class AutoTestManager(object): # pylint:disable=too-many-public-methods """ Gateway and open node auto tests """ # Global used in tests to store checked open node features TESTED_FEATURES = set() def __init__(self, gateway_manager): self.g_m = gateway_manager self.open_node = board_config.BoardConfig().board_class self.on_serial = None self.a8_connection = None self.ret_dict = {"ret": None, "success": [], "error": [], "mac": {}} self.cn_measures = [] def _measures_handler(self, measure_str): """ control node measures Handler """ self.cn_measures.append(measure_str.split(" ")) @staticmethod def get_local_mac_addr(): """ Get eth0 mac address """ mac_addr = check_output(MAC_CMD, stderr=STDOUT, shell=True).strip() return mac_addr def setup_control_node(self): """ setup connection with control_node""" LOGGER.info("Setup autotests") ret_val = 0 # configure Control Node ret_val += self.g_m.control_node.reset() self.g_m.control_node.cn_serial.measures_debug = self._measures_handler self.g_m.control_node.cn_serial.start() ret_val += self.g_m.control_node.protocol.set_time() gwt_mac_addr = self.get_local_mac_addr() self.ret_dict["mac"]["GWT"] = gwt_mac_addr test_ok = MAC_RE.match(gwt_mac_addr) is not None ret_val += self._check(tst_ok(test_ok), "gw_mac_addr", gwt_mac_addr) self._assert(ret_val, "setup_cn_connection", ret_val, "Setup control node failed") def _setup_open_node(self): """ Setup open node connection * Flash firmware * Start serial interface """ ret_val = 0 ret = self.g_m.open_node.setup(self.open_node.FW_AUTOTEST) ret_val += self._check(ret, "open_node_setup", ret) self.on_serial = OpenNodeConnection() ret = self.on_serial.start() ret_val += self._check(ret, "open_node_connection", ret) return ret_val def _setup_open_node_a8(self): """ Setup open node a8-m3 connection * Boot open node * Connect through serial and get ip address * SSH connect to it * get """ assert self.open_node.TYPE == "a8" ret_val = 0 ret = self.g_m.open_node.setup(None, debug=False) self._assert(ret, "open_a8_setup", ret, "Open Node Setup failed") match = self.g_m.open_node.wait_booted(timeout=300) self._assert(tst_ok(match != ""), "open_a8_boot_timeout", 300, "Open Node Boot failed") # get ip address using serial # run socats commands to access a8-m3 open node on gateway self.a8_connection = open_a8_interface.OpenA8Connection() LOGGER.debug("Wait that open a8 node starts") try: self.a8_connection.start() except open_a8_interface.A8ConnectionError as err: # pragma: no cover self._assert(1, "open_a8_init_error: %s" % err.err_msg, str(err), "Setup Connection failed") # save mac address a8_mac_addr = self.a8_connection.get_mac_addr() self.ret_dict["mac"]["a8"] = a8_mac_addr test_ok = MAC_RE.match(a8_mac_addr) is not None ret_val += self._check(tst_ok(test_ok), "a8_mac_addr", a8_mac_addr) ret = self.a8_connection.flash(self.g_m.open_node.A8_M3_FW_AUTOTEST) self._assert(ret, "flash a8_autotest.elf", ret, "OpenNodeFlash Failed") # Create open node a8-m3 connection through node-a8 serial redirection self.on_serial = OpenNodeConnection(self.a8_connection.ip_addr) ret = self.on_serial.start() ret_val += self._check(ret, "open_a8_serial", ret) return ret_val def _setup_open_node_connection(self): """ Setup the connection with Open Node Should be done on DC""" ret_val = 0 ret_val += self.g_m.control_node.open_start("dc") # setup # A8 node is very different from the generic way if self.open_node.TYPE == "a8": ret_val += self._setup_open_node_a8() else: ret_val += self._setup_open_node() self.on_serial.empty() # flush messages that can be bufferred if ret_val != 0: # pragma: no cover raise FatalError("Setup Open Node failed") def teardown(self, blink): """ cleanup """ ret_val = 0 LOGGER.info("Teardown autotests") # ensure DC alim ret_val += self.g_m.control_node.open_start("dc") try: self.on_serial.stop() except AttributeError: # pragma: no cover ret_val += 1 # access NoneType attribute finally: self.on_serial = None if not blink: ret_val += self.g_m.open_node.teardown() LOGGER.debug("Stop open node, no blinking") ret_val += self.g_m.control_node.open_stop("dc") else: # Can't call teardown as it flashes 'idle' ret_val += self.g_m.open_node.serial_redirection.stop() LOGGER.debug("Set status on LEDs") self.g_m.control_node.cn_serial.stop() LOGGER.debug("cn_serial stopped") return self._check(ret_val, "teardown", ret_val) def auto_tests(self, channel=None, blink=False, flash=False, gps=False): """ run auto-tests on nodes and gateway using 'gateway_manager' """ ret_val = 0 self.TESTED_FEATURES.clear() try: self.setup_control_node() # Tests using Battery # dc -> battery: a8 reboot # battery -> dc: No reboot # a8 may not work with new batteries (not enough power) # so check battery and then switch to DC ret_val += self.test_consumption_batt() # switch to DC and configure open node self._setup_open_node_connection() time.sleep(1) self.check_echo() self.check_get_time() self.get_uid() # Other tests, run on DC ret = self.g_m.control_node.open_start("dc") ret_val += self._check(ret, "switch_to_dc", ret) # Test using leds commands self.set_leds_off_and_on() # test IMU ret_val += self.test_gyro() ret_val += self.test_magneto() ret_val += self.test_accelero() # test m3-on communication ret_val += self.test_gpio() ret_val += self.test_i2c() # radio tests ret_val += self.test_radio_ping_pong(channel) ret_val += self.test_radio_with_rssi(channel) # test consumption measures ret_val += self.test_consumption_dc() # m3 specific tests # cannot test this with a8 I think ret_val += self.test_leds_with_consumption() # test m3 specific sensors ret_val += self.test_pressure() ret_val += self.test_light() ret_val += self.test_flash(flash) # run test_gps if requested ret_val += self.test_gps(gps) # set_leds self.set_result_leds(ret_val) except FatalError as err: # Fatal Error during test, don't run further tests LOGGER.error("Fatal Error in tests, stop further tests: %s", str(err)) ret_val += 1 # shutdown node if test failed ret_val += self.teardown(blink and (ret_val == 0)) self.ret_dict["ret"] = ret_val return self.ret_dict def _check(self, ret, operation, log_message=""): """ Check the operation Adds `operation` to ret_dict in the correct failed or success entry :param ret: 0 means success non zero means failure :return: 0 if `ret` == 0, a positive value otherwise """ if int(ret) == 0: self.ret_dict["success"].append(operation) LOGGER.debug("autotest: %r OK: %r", operation, log_message) else: self.ret_dict["error"].append(operation) LOGGER.error("Autotest: %r: %r", operation, log_message) return abs(int(ret)) def _assert(self, ret, operation, log_message, exc_message): """ Shortcut for check + FatalError """ ret_val = self._check(ret, operation, log_message) if ret_val: raise FatalError(exc_message) def _run_test(self, num, cmd, parse_function): """ Run a test 'num' times. In case of success parse answer with 'parse_function' """ values = [] for _itr in range(0, num): # pylint:disable=unused-variable (ret, answer) = self._on_call(cmd) if ret: continue values.append(parse_function(answer)) return values def _on_serial_send_command(self, cmd): """Set command to open node serial.""" # Also register called commands. self.TESTED_FEATURES.add(cmd[0]) return self.on_serial.send_command(cmd) def _on_call(self, cmd): """ Call command to Open Node and expect correct answer cmd args ACK cmd [answer_args] """ answer = self._on_serial_send_command(cmd) if (answer is None) or (answer[0:2] != ["ACK", cmd[0]]): self._check(1, "On Command: %r" % cmd, answer) return (1, answer) return (0, answer) # Test implementation @autotest_checker("leds_off", "leds_blink") def set_result_leds(self, ret_val): """ Make leds blink in case of success. Turn off on failure """ # Clean leds state self._on_call(["leds_off", "7"]) if ret_val != 0: # pragma: no cover return # Blink leds on success self._on_call(["leds_blink", "7", "500"]) self.g_m.control_node.protocol.green_led_blink() # Require 'echo' command def check_echo(self): """ run the echo command on the serial port """ # echo arg1 arg2: ['arg1', 'arg2'] cmd = ["echo", "HELLO", "WORLD"] answer = self._on_serial_send_command(cmd) _answer = answer or [] # Replace None test_ok = _answer[0:2] == ["HELLO", "WORLD"] self._assert(tst_ok(test_ok), "on_serial_echo", answer, "echo failed. Can't communicate with open node") # Require 'get_time' command def check_get_time(self): """ runs the 'get_time' command Error on this check are fatal """ # get_time: ['ACK', 'get_time', '122953', 'tick_32khz'] answer = self._on_call(["get_time"])[1] values = self._run_test(5, ["get_time"], (lambda x: x[2].isdigit())) test_ok = any(values) self._assert(tst_ok(test_ok), "on_serial_get_time", answer, "get_time failed. Can't communicate with ON") @autotest_checker("get_uid") def get_uid(self): """ runs the 'get_uid' command And add the resulting UID to the global return dictionary """ # get_uid: ['ACK', 'get_uid', '05D8FF323632483343037109'] values = self._run_test(1, ["get_uid"], (lambda x: x[2])) test_ok = len(values) if test_ok: uid_str = values[0] # UID: split every 4 char uid_split = ["".join(x) for x in zip(*[iter(uid_str)] * 4)] uid = ":".join(uid_split) self.ret_dict["open_node_m3_uid"] = uid else: # pragma: no cover pass ret_val = self._check(tst_ok(test_ok), "get_uid", values) return ret_val @autotest_checker("leds_on", "leds_off") def set_leds_off_and_on(self): """Turn leds off and on.""" # Clean leds state self._on_call(["leds_off", "7"]) time.sleep(1) self._on_call(["leds_on", "7"]) # sensors and flash @autotest_checker("test_flash") def test_flash(self, flash): """ test Flash """ if not flash: return 0 values = self._run_test(1, ["test_flash"], (lambda x: x)) test_ok = len(values) return self._check(tst_ok(test_ok), "test_flash", values) @autotest_checker("get_pressure") def test_pressure(self): """ test pressure sensor """ # ['ACK', 'get_pressure', '9.944219E2', 'mbar'] values = self._run_test(10, ["get_pressure"], (lambda x: float(x[2]))) test_ok = len(set(values)) > 1 return self._check(tst_ok(test_ok), "test_pressure", values) @autotest_checker("get_light", "leds_on", "leds_off") def test_light(self): """ test light sensor with leds""" # ['ACK', 'get_light', '5.2001953E1', 'lux'] # get light with leds self._on_call(["leds_on", "7"]) values_on = self._run_test(5, ["get_light"], (lambda x: float(x[2]))) # get light without leds self._on_call(["leds_off", "7"]) values_off = self._run_test(5, ["get_light"], (lambda x: float(x[2]))) values = values_on + values_off test_ok = len(set(values)) > 1 return self._check(tst_ok(test_ok), "get_light", values) # Test GPS def _test_pps_open_node(self, timeout): """ Test the pps on open a8 m3 node""" ret_val = 0 # start pps on open node (ret, answer) = self._on_call(["test_pps_start"]) if ret: # pragma: no cover return self._check(1, "test_pps_start", answer) end_time = time.time() + timeout while time.time() < end_time: time.sleep(5) (ret, answer) = self._on_call(["test_pps_get"]) if ret: # pragma: no cover return self._check(1, "test_pps_get", answer) # get pps value pps_count = int(answer[2]) if pps_count > 2: ret_val = self._check(0, "test_pps_open_node", pps_count) break else: ret_val = self._check(1, "test_pps_open_node_timeout", 0) self._on_call(["test_pps_stop"]) return ret_val @autotest_checker("test_pps_start", "test_pps_get", "test_pps_stop") def test_gps(self, gps): """ Test the gps """ if not gps: return 0 ret_val = 0 # ret_val += self._test_gps_serial() ret_val += self._test_pps_open_node(120.0) # try to get pps, max 2 min ret_val = self._check(ret_val, "test_gps", ret_val) return ret_val # Control Node <--> Open Node Interraction @autotest_checker("test_gpio") def test_gpio(self): """ test GPIO connections """ return self._test_on_cn(5, ["test_gpio"]) @autotest_checker("test_i2c") def test_i2c(self): """ test i2c communication """ return self._test_on_cn(1, ["test_i2c"]) def _test_on_cn(self, num, cn_command, on_cmd=None, args=None): """ Run a test command between open node and control node setup control node run num times on open node teardown control node """ on_cmd = on_cmd or cn_command # on_cmd is the same as cn_command args = args or [] debug_str = "%s_on_cn" % cn_command[0] ret_val = 0 # setup control node ret_val += self.g_m.control_node.protocol.send_cmd(cn_command + ["start"] + args) # Run num times values = self._run_test(num, on_cmd + args, (lambda x: 0)) test_ok = 0 in values # at least one success ret_val += self._check(tst_ok(test_ok), debug_str, values) # teardown ret = self.g_m.control_node.protocol.send_cmd(cn_command + ["stop"]) ret_val += self._check(ret, debug_str, "cleanup error") return ret_val # Inertial Measurement Unit @autotest_checker("get_magneto") def test_magneto(self): """ test magneto sensor """ # ['ACK', 'get_magneto' '4.328358E-2', '6.716418E-2', '-3.880597E-1', # 'gauss'] return self._test_xyz_sensor("get_magneto") @autotest_checker("get_gyro") def test_gyro(self): """ test gyro sensor """ # ['ACK', 'get_gyro', '1.07625', '1.75', '5.2500002E-2', 'dps'] return self._test_xyz_sensor("get_gyro") @autotest_checker("get_accelero") def test_accelero(self): """ test accelerator sensor """ # ['ACK', 'get_accelero', '3.6E-2', '-1.56E-1', '1.0320001', 'g'] return self._test_xyz_sensor("get_accelero") def _test_xyz_sensor(self, sensor): """ Test sensors returning 'xyz' float values """ # ['ACK', sensor, '3.6E-2', '-1.56E-1', '1.0320001', unit] values = self._run_test(10, [sensor], (lambda x: tuple([float(val) for val in x[2:5]]))) test_ok = len(set(values)) > 1 # got different values return self._check(tst_ok(test_ok), sensor, values) # Radio tests @autotest_checker("radio_ping_pong") def test_radio_ping_pong(self, channel): """ test Radio Ping-pong with control-node """ if channel is None: return 0 return self._test_on_cn(10, ["test_radio_ping_pong"], ["radio_ping_pong"], [str(channel), "3dBm"]) @autotest_checker("radio_pkt") def test_radio_with_rssi(self, channel): """ Test radio with rssi""" self.cn_measures = [] if channel is None: return 0 # pkt length = 125 # one measure every ~0.01 seconds ret_val = 0 radio = Radio("rssi", [channel], period=10, num_per_channel=0) cmd_on = ["radio_pkt", str(channel), "3dBm"] # get RSSI while sending 10 packets length 125 ret_val += self.g_m.control_node.protocol.config_radio(radio) for _itr in range(0, 10): # pylint:disable=unused-variable self._on_call(cmd_on) time.sleep(0.5) ret_val += self.g_m.control_node.protocol.config_radio(None) # ('11', '-91') # -91 == no radio detected measures = extract_measures(self.cn_measures) values = [v[1] for v in measures["radio"]["values"]] # check that there are values other than -91 measured test_ok = set([-91]) != set(values) ret_val += self._check(tst_ok(test_ok), "rssi_measures", set(values)) return ret_val # Consumption tests def test_consumption_dc(self): """ Try consumption for DC """ ret_val = 0 # one measure every ~0.1 seconds conso = Consumption(self.g_m.open_node.ALIM, "dc", "1100", "64", True, True, True) ret_val += self.g_m.control_node.open_start("dc") self.cn_measures = [] # store 2 secs of measures ret_val += self.g_m.control_node.protocol.config_consumption(conso) time.sleep(2) # get measures for 2 seconds ret_val += self.g_m.control_node.protocol.config_consumption(None) time.sleep(2) # wait 2 seconds for flush # (0.257343, 3.216250, 0.080003) measures = extract_measures(self.cn_measures) values = measures["consumption"]["values"] # Value ranges may be validated with an Idle firmware test_ok = len(set(values)) > 1 ret_val += self._check(tst_ok(test_ok), "consumption_dc", values) return ret_val def test_consumption_batt(self): # pragma: no cover """ Try consumption for Battery """ # Disable battery tests as they have been unplugged return 0 ret_val = 0 # pylint: disable=unreachable ret_val += self.g_m.control_node.open_start("battery") # set a firmware on m3 to ensure corret consumption measures # on a8, linux is consuming enough I think if self.open_node.TYPE == "m3": # pragma: no branch time.sleep(1) ret = self.g_m.open_node.flash(self.open_node.FW_AUTOTEST) ret_val += self._check(ret, "flash_m3_on_battery", ret) # configure consumption # one measure every ~0.1 seconds conso = Consumption(self.g_m.open_node.ALIM, "battery", 1100, 64, True, True, True) self.cn_measures = [] ret_val += self.g_m.control_node.protocol.config_consumption(conso) ret_val += self.g_m.control_node.open_stop("battery") time.sleep(1) ret_val += self.g_m.control_node.open_start("battery") time.sleep(1) # stop ret_val += self.g_m.control_node.protocol.config_consumption(None) time.sleep(1) # Flush last values # (0.257343, 3.216250, 0.080003) measures = extract_measures(self.cn_measures) values = measures["consumption"]["values"] test_ok = len(set(values)) > 1 ret_val += self._check(tst_ok(test_ok), "consumption_batt", values) # Value ranges may be validated with an Idle firmware return ret_val @autotest_checker("leds_consumption", "leds_on", "leds_off") def test_leds_with_consumption(self): """ Test Leds with consumption Start consumption measure Then switch different leds on and save the timestamp where it's done Finally compare that consumption with no leds on was lower than with one or more leds on. """ self._on_call(["leds_off", "7"]) # one measure every ~0.1 seconds ret_val = 0 conso = Consumption(self.g_m.open_node.ALIM, "dc", "1100", "64", True, True, True) ret_val += self.g_m.control_node.open_start("dc") self.cn_measures = [] leds_timestamps = [] # get consumption for all leds mode: # no leds, each led, all leds ret_val += self.g_m.control_node.protocol.config_consumption(conso) for leds in ["0", "1", "2", "4", "7"]: self._on_call(["leds_on", leds]) time.sleep(0.5) leds_timestamps.append(time.time()) time.sleep(0.5) self._on_call(["leds_off", "7"]) ret_val += self.g_m.control_node.protocol.config_consumption(None) time.sleep(1) # wait last values # (0.257343, 3.216250, 0.080003) measures = extract_measures(self.cn_measures) values = [v[0] for v in measures["consumption"]["values"]] timestamps = measures["consumption"]["timestamps"] led_consumption = [] LOGGER.debug("t0, tEnd: %r - %r", timestamps[0], timestamps[-1]) LOGGER.debug("leds_timestamps: %r", leds_timestamps) for led_time in leds_timestamps: try: led_consumption.append(values[bisect(timestamps, led_time)]) except IndexError as err: # pragma: no cover LOGGER.debug(err) led_consumption.append(float("NaN")) # check that consumption is higher with each led than with no leds on led_0 = led_consumption.pop(0) test_ok = all([led_0 < v for v in led_consumption]) ret_val += self._check(tst_ok(test_ok), "leds_using_conso", (led_0, led_consumption)) return ret_val