async def test_write(app, init_app, client, m_writer):
    service_status.set_autoconnecting(app, True)
    await asyncio.sleep(0.01)
    conn = connection.fget(app)
    assert conn.connected
    assert service_status.desc(app).is_connected
    assert service_status.desc(app).connection_kind == 'usb'

    await conn.write('testey')
    m_writer.write.assert_called_once_with(b'testey\n')
    m_writer.drain.assert_awaited_once()

    m_writer.reset_mock()
    await conn.write(b'testey')
    m_writer.write.assert_called_once_with(b'testey\n')
    m_writer.drain.assert_awaited_once()

    await conn.start_reconnect()
    m_writer.close.assert_called_once()

    m_writer.is_closing.return_value = True
    assert not conn.connected

    await conn.start_reconnect()
    m_writer.close.assert_called_once()

    with pytest.raises(exceptions.NotConnected):
        await conn.write('stuff')
Ejemplo n.º 2
0
    async def flash(self) -> dict:  # pragma: no cover
        sender = ymodem.FileSender(self._notify)
        cmder = commander.fget(self.app)
        address = service_status.desc(self.app).device_address

        self._notify(
            f'Started updating {self.name}@{address} to version {self.version} ({self.date})'
        )

        try:
            if not service_status.desc(self.app).is_connected:
                self._notify('Controller is not connected. Aborting update.')
                raise exceptions.NotConnected()

            if self.simulation:
                raise NotImplementedError(
                    'Firmware updates not available for simulation controllers'
                )

            self._notify('Sending update command to controller')
            service_status.set_updating(self.app)
            await asyncio.sleep(FLUSH_PERIOD_S
                                )  # Wait for in-progress commands to finish
            await cmder.execute(commands.FirmwareUpdateCommand.from_args())

            self._notify('Shutting down normal communication')
            await cmder.shutdown(self.app)

            self._notify('Waiting for normal connection to close')
            await asyncio.wait_for(service_status.wait_disconnected(self.app),
                                   STATE_TIMEOUT_S)

            self._notify(f'Connecting to {address}')
            conn = await self._connect(address)

            with conn.autoclose():
                await asyncio.wait_for(sender.transfer(conn),
                                       TRANSFER_TIMEOUT_S)

        except Exception as ex:
            self._notify(f'Failed to update firmware: {strex(ex)}')
            raise exceptions.FirmwareUpdateFailed(strex(ex))

        finally:
            self._notify('Scheduling service reboot')
            await shutdown_soon(self.app, UPDATE_SHUTDOWN_DELAY_S)

        self._notify('Firmware updated!')
        return {'address': address, 'version': self.version}
    async def set_display_settings(self):
        write_required = False

        if not service_status.desc(self.app).is_acknowledged:
            return

        display_block = await self.commander.read_block(
            FirmwareBlockIdentity(nid=const.DISPLAY_SETTINGS_NID))

        user_unit = self.global_store.units['temperature']
        expected_unit = 'TEMP_FAHRENHEIT' if user_unit == 'degF' else 'TEMP_CELSIUS'
        block_unit = display_block.data['tempUnit']

        if expected_unit != block_unit:
            write_required = True
            display_block.data['tempUnit'] = expected_unit
            LOGGER.info(f'Spark display temperature unit set to {user_unit}')

        user_tz_name = self.global_store.time_zone['name']
        user_tz = self.global_store.time_zone['posixValue']
        block_tz = display_block.data['timeZone']

        if user_tz != block_tz:
            write_required = True
            display_block.data['timeZone'] = user_tz
            LOGGER.info(
                f'Spark display time zone set to {user_tz} ({user_tz_name})')

        if write_required:
            await self.commander.write_block(display_block)
Ejemplo n.º 4
0
async def test_callback(app, init_app, client, m_reader, m_writer):
    service_status.set_autoconnecting(app, True)
    await asyncio.sleep(0.01)
    conn = connection.fget(app)
    m_data_cb = Mock()
    m_data_cb2 = Mock()

    conn.data_callbacks.add(m_data_cb)
    conn.data_callbacks.add(m_data_cb2)

    m_reader.feed_data('<!connected:sensor>bunnies<fluffy>\n'.encode())
    await asyncio.sleep(0.01)
    m_data_cb.assert_called_with('bunnies')
    m_data_cb2.assert_called_with('bunnies')

    # Close it down
    m_reader.feed_data('puppies\n'.encode())
    m_writer.is_closing.return_value = True

    await asyncio.sleep(0.01)
    m_data_cb.assert_called_with('puppies')
    m_data_cb2.assert_called_with('puppies')
    assert conn.active
    assert not conn.connected
    assert not service_status.desc(app).is_connected
    async def set_display_settings(self):
        if not service_status.desc(self.app).is_acknowledged:
            return

        store = global_store.fget(self.app)
        controller = spark.fget(self.app)
        write_required = False

        display_block = await controller.read_object({
            'nid': const.DISPLAY_SETTINGS_NID
        })

        user_unit = store.units['temperature']
        expected_unit = 'TEMP_FAHRENHEIT' if user_unit == 'degF' else 'TEMP_CELSIUS'
        block_unit = display_block['data']['tempUnit']

        if expected_unit != block_unit:
            write_required = True
            display_block['data']['tempUnit'] = expected_unit
            LOGGER.info(f'Spark display temperature unit set to {user_unit}')

        user_tz_name = store.time_zone['name']
        user_tz = store.time_zone['posixValue']
        block_tz = display_block['data']['timeZone']

        if user_tz != block_tz:
            write_required = True
            display_block['data']['timeZone'] = user_tz
            LOGGER.info(f'Spark display time zone set to {user_tz} ({user_tz_name})')

        if write_required:
            await controller.write_object(display_block)
Ejemplo n.º 6
0
    async def get(self) -> r200[StatusDescription]:
        """
        Get service status

        Tags: System
        """
        desc = service_status.desc(self.request.app)
        return web.json_response(desc.dict())
Ejemplo n.º 7
0
    async def run(self):
        try:
            await asyncio.sleep(self.interval)
            synched = await service_status.wait_synchronized(self.app, wait=False)

            status_data = service_status.desc(self.app)
            blocks = []

            try:
                if synched:
                    blocks, logged_blocks = await controller.fget(self.app).read_all_broadcast_blocks()

                    # Convert list to key/value format suitable for history
                    history_data = {
                        block.id: block.data
                        for block in logged_blocks
                        if not block.id.startswith(const.GENERATED_ID_PREFIX)
                    }

                    await mqtt.publish(self.app,
                                       self.history_topic,
                                       err=False,
                                       message={
                                           'key': self.name,
                                           'data': history_data,
                                       })

            finally:
                # State event is always published
                await mqtt.publish(self.app,
                                   self.state_topic,
                                   err=False,
                                   retain=True,
                                   message={
                                       'key': self.name,
                                       'type': 'Spark.state',
                                       'data': {
                                           'status': status_data.dict(),
                                           'blocks': [v.dict() for v in blocks],
                                           'relations': calculate_relations(blocks),
                                           'drive_chains': calculate_drive_chains(blocks),
                                       },
                                   })

        except Exception as ex:
            LOGGER.debug(f'{self} exception: {strex(ex)}')
            raise ex
    async def _sync_handshake(self):
        """
        Wait for the controller to acknowledge the connection with a handshake,
        while sending prompts by using the Noop command.

        If no handshake is received after `HANDSHAKE_TIMEOUT_S`,
        an asyncio.TimeoutError is raised.

        The handshake is checked, and appropriate errors are raised
        if the device ID or firmware version are incompatible.
        """
        async def prompt_handshake():
            while True:
                try:
                    await asyncio.sleep(PING_INTERVAL_S)
                    LOGGER.info('prompting handshake...')
                    await self.commander.version()
                except Exception as ex:
                    LOGGER.error(strex(ex))
                    pass

        ack_wait_task = asyncio.create_task(
            service_status.wait_acknowledged(self.app))
        prompt_task = asyncio.create_task(prompt_handshake())

        await asyncio.wait([ack_wait_task, prompt_task],
                           return_when=asyncio.FIRST_COMPLETED,
                           timeout=HANDSHAKE_TIMEOUT_S)

        # asyncio.wait() does not cancel tasks
        # cancel() can be safely called if the task is already done
        ack_wait_task.cancel()
        prompt_task.cancel()

        if not await service_status.wait_acknowledged(self.app, wait=False):
            raise asyncio.TimeoutError()

        handshake = service_status.desc(self.app).handshake_info

        if not handshake.is_compatible_firmware:
            raise exceptions.IncompatibleFirmware()

        if not handshake.is_valid_device_id:
            raise exceptions.InvalidDeviceId()
Ejemplo n.º 9
0
    async def flash(self) -> dict:  # pragma: no cover
        ota = ymodem.OtaClient(self._notify)
        cmder = commander.fget(self.app)
        status_desc = service_status.desc(self.app)
        address = status_desc.device_address
        platform = status_desc.device_info.platform

        self._notify(
            f'Started updating {self.name}@{address} to version {self.version} ({self.date})'
        )

        try:
            if not status_desc.is_connected:
                self._notify('Controller is not connected. Aborting update.')
                raise exceptions.NotConnected()

            if self.simulation:
                raise NotImplementedError(
                    'Firmware updates not available for simulation controllers'
                )

            self._notify('Preparing update')
            service_status.set_updating(self.app)
            await asyncio.sleep(FLUSH_PERIOD_S
                                )  # Wait for in-progress commands to finish

            if platform != 'esp32':  # pragma: no cover
                self._notify('Sending update command to controller')
                await cmder.execute(commands.FirmwareUpdateCommand.from_args())

            self._notify('Waiting for normal connection to close')
            await cmder.shutdown(self.app)
            await asyncio.wait_for(service_status.wait_disconnected(self.app),
                                   STATE_TIMEOUT_S)

            if platform == 'esp32':  # pragma: no cover
                # ESP connections will always be a TCP address
                host, _ = address.split(':')
                self._notify(f'Sending update prompt to {host}')
                self._notify(
                    'The Spark will now download and apply the new firmware')
                self._notify('The update is done when the service reconnects')
                fw_url = ESP_URL_FMT.format(**self.app['ini'])
                await http.session(self.app
                                   ).post(f'http://{host}:80/firmware_update',
                                          data=fw_url)

            else:
                self._notify(f'Connecting to {address}')
                conn = await ymodem.connect(address)

                with conn.autoclose():
                    await asyncio.wait_for(
                        ota.send(conn, f'firmware/brewblox-{platform}.bin'),
                        TRANSFER_TIMEOUT_S)
                    self._notify('Update done!')

        except Exception as ex:
            self._notify(f'Failed to update firmware: {strex(ex)}')
            raise exceptions.FirmwareUpdateFailed(strex(ex))

        finally:
            self._notify('Restarting service...')
            await shutdown_soon(self.app, UPDATE_SHUTDOWN_DELAY_S)

        return {'address': address, 'version': self.version}
 def get_autoconnecting(self):
     return service_status.desc(self.app).is_autoconnecting
    def device_name(self) -> str:
        # Simulation services are identified by service name
        if self.app['config']['simulation']:
            return 'simulator__' + self.app['config']['name']

        return service_status.desc(self.app).device_info.device_id
Ejemplo n.º 12
0
async def test_sim_status(app, init_app, client, m_connect):
    app['config']['simulation'] = True
    service_status.set_autoconnecting(app, True)
    await asyncio.sleep(0.01)
    assert service_status.desc(app).connection_kind == 'simulation'