Пример #1
0
class TerkinDatalogger:

    # Application metadata.
    name = 'Terkin MicroPython Datalogger'
    version = __version__

    # For the singleton factory.
    __instance__ = None

    def __init__(self, settings, platform_info=None):

        # Fulfill singleton factory.
        TerkinDatalogger.__instance__ = self

        # Obtain configuration settings.
        self.settings = TerkinConfiguration()
        self.settings.add(settings)
        self.settings.add_user_file()

        self.application_info = ApplicationInfo(name=self.name,
                                                version=self.version,
                                                settings=self.settings,
                                                application=self,
                                                platform_info=platform_info)

        # Configure logging.
        logging_enabled = self.settings.get('main.logging.enabled', False)
        if not logging_enabled:
            log.info('Disabling logging to save bytes')
            logging.disable_logging()

        # Initialize transient storage.
        self.storage = TransientStorage()

        # Initialize device.
        self.device = TerkinDevice(self.application_info)

        # Button manager instance (optional).
        self.button_manager = None

        # Initialize sensor domain.
        self.sensor_manager = SensorManager()

        self.duty_chrono = GenericChronometer()

    @staticmethod
    def getInstance(settings=None):
        """
        Singleton factory.
        """
        if TerkinDatalogger.__instance__ is None:
            if settings is None:
                raise Exception(
                    "Settings are None but instance wasn't created before.")
            else:
                TerkinDatalogger(settings)

        return TerkinDatalogger.__instance__

    def setup(self):
        pass

    def start(self):

        self.duty_chrono.reset()

        # Report about wakeup reason and run wakeup tasks.
        self.device.resume()

        # Start the watchdog for sanity.
        self.device.watchdog.start()

        # Configure RGB-LED according to settings.
        self.device.configure_rgb_led()

        # Alternative startup signalling: 2 x green.
        self.device.blink_led(0x000b00, count=2)

        self.device.run_gc()

        # Turn off LTE modem and Bluetooth as we don't use them yet.
        # Todo: Revisit where this should actually go.
        # The modem driver takes about six seconds to initialize, so adjust the watchdog accordingly.
        self.device.watchdog.reconfigure_minimum_timeout(15000)
        if not self.settings.get('main.fastboot', False):
            self.device.power_off_lte_modem()
        self.device.power_off_bluetooth()
        self.device.watchdog.resume()

        log.info('Starting %s', self.application_info.fullname)

        # Dump configuration settings.
        log_configuration = self.settings.get('main.logging.configuration',
                                              False)
        if log_configuration:
            self.settings.dump()

        # Initialize buttons / touch pads.
        buttons_enabled = self.settings.get('sensors.system.buttons.enabled',
                                            False)
        if buttons_enabled:
            from terkin.sensor.button import ButtonManager
            self.button_manager = ButtonManager()
            self.start_buttons()

        # Disable this if you don't want serial access.
        #self.device.enable_serial()

        # Hello world.
        self.device.print_bootscreen()

        # Start networking and telemetry subsystems.

        # Conditionally start network services and telemetry if networking is available.
        try:
            self.device.start_networking()
        except Exception as ex:
            log.exc(ex, 'Networking subsystem failed')
            self.device.status.networking = False

        self.device.start_telemetry()

        # Todo: Signal readyness by publishing information about the device (Microhomie).
        # e.g. ``self.device.publish_properties()``

        # Setup sensors.
        self.device.watchdog.feed()
        bus_settings = self.settings.get('sensors.busses', [])
        self.sensor_manager.setup_busses(bus_settings)
        self.register_sensors()

        # Power up sensor peripherals.
        self.sensor_manager.power_on()

        # Ready.
        self.start_mainloop()

    def start_mainloop(self):

        # Todo: Refactor by using timers.

        # Enter the main loop.
        while True:

            # Feed the watchdog timer to keep the system alive.
            self.device.watchdog.feed()

            # Indicate activity.
            # Todo: Optionally disable this output.
            log.info('--- loop ---')

            # Run downstream mainloop handlers.
            self.loop()

            # Give the system some breath.
            machine.idle()

    def loop(self):
        """
        Main duty cycle loop.
        """

        if not self.settings.get('main.deepsleep', False):
            self.duty_chrono.reset()

        #log.info('Terkin loop')

        # Alternative loop signalling: 1 x blue.
        # https://forum.pycom.io/topic/2067/brightness-of-on-board-led/7
        self.device.blink_led(0x00000b, count=2)

        # Read sensors.
        readings = self.read_sensors()

        # Remember current reading
        self.storage.last_reading = readings

        # Run the garbage collector.
        self.device.run_gc()

        # Transmit data.
        transmission_success = self.transmit_readings(readings)

        # Signal transmission outcome.
        if transmission_success:
            self.device.blink_led(0x00000b)
        else:
            self.device.blink_led(0x0b0000)

        # Run the garbage collector.
        self.device.run_gc()

        # Sleep how ever.
        self.sleep()

    def sleep(self):
        """
        Sleep until the next measurement cycle.
        """

        lightsleep = self.settings.get('main.lightsleep', False)
        deepsleep = self.settings.get('main.deepsleep', False)
        interval = self.get_sleep_time()

        # Amend deep sleep intent when masked through maintenance mode.
        if self.device.status.maintenance is True:
            lightsleep = False
            deepsleep = False
            log.info('Device is in maintenance mode. Skipping deep sleep and '
                     'adjusting interval to {} seconds'.format(interval))

        # Use deep sleep if requested.
        try:
            if deepsleep:

                # Shut down sensor peripherals.
                self.sensor_manager.power_off()

                # Shut down device peripherals.
                self.device.power_off()

            # Send device to deep sleep.
            self.device.hibernate(interval,
                                  lightsleep=lightsleep,
                                  deepsleep=deepsleep)

        # When hibernation fails, fall back to regular "time.sleep".
        except Exception as ex:
            log.exc(ex, 'Failed to hibernate, falling back to regular sleep')
            # Todo: Emit error message here.
            log.info('Sleeping for {} seconds'.format(interval))
            time.sleep(interval)

    def get_sleep_time(self):
        interval = self.settings.get('main.interval', 60.0)

        # Configuration switchover backward compatibility / defaults.
        if isinstance(interval, (float, int)):
            self.settings.set('main.interval', {})
            self.settings.setdefault('main.interval.field', interval)
        self.settings.setdefault('main.interval.maintenance', 5.0)

        # Compute interval.
        interval = self.settings.get('main.interval.field')

        # Amend deep sleep intent when masked through maintenance mode.
        if self.device.status.maintenance is True:
            interval = self.settings.get('main.interval.maintenance')

        # Compute sleeping duration from measurement interval and elapsed time.
        elapsed = self.duty_chrono.read()
        sleep_time = interval - elapsed

        if sleep_time <= 0:
            sleep_time = interval

        return sleep_time

    def register_sensors(self):
        """
        Add system sensors.
        """

        log.info('Registering system sensors')

        system_sensors = [
            SystemMemoryFree,
            SystemTemperature,
            SystemBatteryLevel,
            SystemUptime,
        ]

        for sensor_factory in system_sensors:
            sensor_name = sensor_factory.__name__
            try:
                sensor = sensor_factory()
                if not sensor.enabled():
                    log.info('Sensor %s not enabled, skipping', sensor_name)
                    continue
                if hasattr(sensor, 'setup') and callable(sensor.setup):
                    sensor.setup(self.settings)
                self.sensor_manager.register_sensor(sensor)
            except Exception as ex:
                log.exc(ex, 'Registering system sensor "%s" failed',
                        sensor_name)

        # Add WiFi metrics.
        try:
            self.sensor_manager.register_sensor(
                SystemWiFiMetrics(self.device.networking.wifi_manager.station))
        except Exception as ex:
            log.exc(ex, 'Enabling SystemWiFiMetrics sensor failed')

    def read_sensors(self):
        """
        Read sensors
        """

        # Collect observations.
        data = {}
        richdata = {}

        # Iterate all registered sensors.
        sensors = self.sensor_manager.sensors
        log.info('Reading %s sensor ports', len(sensors))
        for sensor in sensors:

            # Signal sensor reading to user.
            sensorname = sensor.__class__.__name__
            log.info('Reading sensor port "%s"', sensorname)

            # Read sensor port.
            try:

                # Disable garbage collector to guarantee reasonable
                # realtime behavior before invoking sensor reading.
                with gc_disabled():
                    reading = sensor.read()

                # Evaluate sensor outcome.
                if reading is None or reading is AbstractSensor.SENSOR_NOT_INITIALIZED:
                    continue

                # Add sensor reading to observations.
                data.update(reading)

                # Record reading for prettified output.
                self.record_reading(sensor, reading, richdata)

            except Exception as ex:
                # Because of the ``gc_disabled`` context manager used above,
                # the propagation of exceptions has to be tweaked like that.
                log.exc(ex, 'Reading sensor "%s" failed', sensorname)

            # Feed the watchdog.
            self.device.watchdog.feed()

            self.device.run_gc()

        # Debugging: Print sensor data before running telemetry.
        prettify_log = self.settings.get('sensors.prettify_log', False)
        if prettify_log:
            log.info('Sensor data:\n\n%s', ddformat(richdata, indent=11))
        else:
            log.info('Sensor data:  %s', data)

        return data

    def record_reading(self, sensor, reading, richdata):
        for key, value in reading.items():
            richdata[key] = {'value': value}
            if hasattr(sensor,
                       'settings') and 'description' in sensor.settings:
                richdata[key]['description'] = sensor.settings.get(
                    'description')
                # Hack to propagate the correct detail-description to prettified output.
                # TODO: Attach settings directly to its reading, while actually reading it.
                if 'devices' in sensor.settings:
                    for device_settings in sensor.settings['devices']:
                        device_address = device_settings['address'].lower()
                        if device_address in key:
                            if hasattr(sensor, 'get_device_description'):
                                device_description = sensor.get_device_description(
                                    device_address)
                                if device_description:
                                    richdata[key][
                                        'description'] = device_description

    def transmit_readings(self, data):
        """Transmit data"""

        # TODO: Optionally disable telemetry.
        if self.device.telemetry is None:
            log.warning('Telemetry disabled')
            return False

        telemetry_status = self.device.telemetry.transmit(data)
        count_total = len(telemetry_status)
        success = all(telemetry_status.values())

        # Evaluate telemetry status outcome.
        if success:
            log.info('Telemetry status: SUCCESS ({}/{})'.format(
                count_total, count_total))
        else:
            count_failed = len([
                item for item in telemetry_status.values() if item is not True
            ])
            log.warning(
                'Telemetry status: FAILURE. {} out of {} targets failed. '
                'Status: {}'.format(count_failed, count_total,
                                    telemetry_status))

        return success

    def start_buttons(self):

        # RGB-LED: 2
        # POWER-ENABLE: 3
        # SD-Card: 4, 8
        # LTE 19, 20
        # Misc: 13, 14, 9, 23

        # Physical location when looking at the board with the RGB-LED oriented to the top.

        # Location: Left side, 6th pin from top.
        self.button_manager.setup_touchpad('P4',
                                           name='Touch3',
                                           location='Module-Left-Top-6th')

        # Location: Left side, 5th pin from bottom.
        self.button_manager.setup_touchpad('P8',
                                           name='Touch2',
                                           location='Module-Left-Bottom-5th')

        # Location: Right side.
        self.button_manager.setup_touchpad('P23',
                                           name='Touch6',
                                           location='Module-Right-Top-4th')

        # Location: Right side.
        # ValueError: invalid pin for touchpad
        """
Пример #2
0
class TerkinDatalogger:

    # Application metadata.
    name = 'Terkin MicroPython Datalogger'
    version = __version__

    def __init__(self, settings):

        # Obtain configuration settings.
        self.settings = TerkinConfiguration()
        self.settings.add(settings)

        # Configure logging.
        logging_enabled = self.settings.get('main.logging.enabled', False)
        if not logging_enabled:
            logging.disable_logging()

        # Initialize device.
        self.device = TerkinDevice(name=self.name, version=self.version, settings=self.settings)

        # Button manager instance (optional).
        self.button_manager = None

        # Initialize sensor domain.
        self.sensor_manager = SensorManager()

    @property
    def appname(self):
        return '{} {}'.format(self.name, self.version)

    def start(self):

        # Report about wakeup reason and run wakeup tasks.
        self.device.resume()

        # Turn off LTE modem and Bluetooth as we don't use them yet.
        # Todo: Revisit where this should actually go.
        self.device.power_off_lte_modem()
        self.device.power_off_bluetooth()

        log.info('Starting %s', self.appname)

        # Start the watchdog for sanity.
        self.device.start_watchdog()

        # Configure RGB-LED according to settings.
        self.device.configure_rgb_led()

        # Dump configuration settings.
        log_configuration = self.settings.get('main.logging.configuration', False)
        if log_configuration:
            self.settings.dump()

        # Initialize buttons / touch pads.
        buttons_enabled = self.settings.get('sensors.system.buttons.enabled', False)
        if buttons_enabled:
            self.button_manager = ButtonManager()
            self.start_buttons()

        # Disable this if you don't want serial access.
        #self.device.enable_serial()

        # Hello world.
        self.device.print_bootscreen()

        # Bootstrap infrastructure.
        self.device.start_networking()

        # Conditionally start telemetry if networking is available.
        if self.device.status.networking:
            self.device.start_telemetry()

        # Todo: Signal readyness by publishing information about the device (Microhomie).
        # e.g. ``self.device.publish_properties()``

        # Setup sensors.
        self.device.feed_watchdog()
        bus_settings = self.settings.get('sensors.busses')
        self.sensor_manager.register_busses(bus_settings)
        self.register_sensors()

        # Power up sensor peripherals.
        self.sensor_manager.power_on()

        # Ready.
        self.start_mainloop()

    def start_mainloop(self):

        # Todo: Refactor by using timers.

        # Enter the main loop.
        while True:

            # Feed the watchdog timer to keep the system alive.
            self.device.feed_watchdog()

            # Indicate activity.
            # Todo: Optionally disable this output.
            log.info('--- loop ---')

            # Run downstream mainloop handlers.
            self.loop()

            # Yup.
            machine.idle()

    def loop(self):
        """
        Main duty cycle loop.
        """

        #log.info('Terkin loop')

        # Read sensors.
        readings = self.read_sensors()

        # Transmit data.
        self.transmit_readings(readings)

        # Run the garbage collector.
        self.device.run_gc()

        # Sleep how ever.
        self.sleep()

    def sleep(self):
        """
        Sleep until the next measurement cycle.
        """
        interval = self.settings.get('main.interval')
        #print(dir(machine))

        # Use deep sleep if requested.
        try:
            deep = self.settings.get('main.deepsleep', False)
            if deep:

                # Shut down sensor peripherals.
                self.sensor_manager.power_off()

                # Shut down device peripherals.
                self.device.power_off()

            # Send device to deep sleep.
            self.device.hibernate(interval, deep=deep)

        # When hibernation fails, fall back to regular "time.sleep".
        except:
            log.exception('Failed to hibernate, falling back to regular sleep')
            # Todo: Emit error message here.
            log.info('Sleeping for {} seconds'.format(interval))
            time.sleep(interval)

    def register_sensors(self):
        """
        Add system sensors.
        """

        log.info('Registering Terkin sensors')

        system_sensors = [
            SystemMemoryFree,
            SystemTemperature,
            SystemBatteryLevel,
            SystemUptime,
        ]

        # Create environmental sensor adapters.
        for sensor_factory in system_sensors:
            sensor = sensor_factory()
            if hasattr(sensor, 'setup') and callable(sensor.setup):
                sensor.setup(self.settings)
            self.sensor_manager.register_sensor(sensor)

        # Add WiFi metrics.
        try:
            self.sensor_manager.register_sensor(SystemWiFiMetrics(self.device.networking.wifi_manager.station))
        except:
            log.exception('Enabling SystemWiFiMetrics sensor failed')

    def read_sensors(self):
        """Read sensors"""
        data = {}
        sensors = self.sensor_manager.sensors
        log.info('Reading %s sensor ports', len(sensors))
        for sensor in sensors:

            sensorname = sensor.__class__.__name__
            log.info('Reading sensor port "%s"', sensorname)

            try:
                reading = sensor.read()
                if reading is None or reading is AbstractSensor.SENSOR_NOT_INITIALIZED:
                    continue
                data.update(reading)

            except:
                log.exception('Reading sensor "%s" failed', sensorname)

            self.device.feed_watchdog()

        # Debugging: Print sensor data before running telemetry.
        log.info('Sensor data:  %s', data)

        return data

    def transmit_readings(self, data):
        """Transmit data"""

        # TODO: Optionally disable telemetry.
        if self.device.telemetry is None:
            log.warning('Telemetry disabled')
            return False

        telemetry_status = self.device.telemetry.transmit(data)
        count_total = len(telemetry_status)
        success = all(telemetry_status.values())

        # Evaluate telemetry status outcome.
        if success:
            log.info('Telemetry status: SUCCESS ({}/{})'.format(count_total, count_total))
        else:
            count_failed = len([item for item in telemetry_status.values() if item is not True])
            log.warning('Telemetry status: FAILURE. {} out of {} targets failed. '
                        'Status: {}'.format(count_failed, count_total, telemetry_status))

        return success

    def start_buttons(self):

        # RGB-LED: 2
        # POWER-ENABLE: 3
        # SD-Card: 4, 8
        # LTE 19, 20
        # Misc: 13, 14, 9, 23

        # Physical location when looking at the board with the RGB-LED oriented to the top.

        # Location: Left side, 6th pin from top.
        self.button_manager.setup_touchpad('P4', name='Touch3', location='Module-Left-Top-6th')

        # Location: Left side, 5th pin from bottom.
        self.button_manager.setup_touchpad('P8', name='Touch2', location='Module-Left-Bottom-5th')

        # Location: Right side.
        self.button_manager.setup_touchpad('P23', name='Touch6', location='Module-Right-Top-4th')
Пример #3
0
class TerkinDatalogger:
    """ 
    Main class of project.
    Handles loop & sleep, registers sensors, reads their data and stores them.
    Shows up as 'datalogger' in the rest of the program.
    """

    # Application metadata.
    name = 'Terkin MicroPython Datalogger'
    version = __version__

    # For the singleton factory.
    __instance__ = None

    def __init__(self, settings, platform_info=None):

        # Reference to the chronometer used for general timekeeping.
        self.duty_chrono = bootloader.duty_chrono

        # Fulfill singleton factory.
        TerkinDatalogger.__instance__ = self

        # Signal startup with first available timestamp.
        log.info('Starting Terkin datalogger')

        # Obtain configuration settings.
        self.settings = TerkinConfiguration()
        self.settings.add(settings)
        self.settings.add_user_file()

        # Configure logging.
        logging_enabled = self.settings.get('main.logging.enabled', False)
        if not logging_enabled:
            log.info('Disabling logging to save bytes')
            logging.disable_logging()

        # Initialize ApplicationInfo object.
        self.application_info = ApplicationInfo(name=self.name,
                                                version=self.version,
                                                settings=self.settings,
                                                application=self,
                                                platform_info=platform_info)

        # Initialize transient storage.
        self.storage = TransientStorage()

        # Initialize device.
        self.device = TerkinDevice(self.application_info)

        # Button manager instance (optional).
        self.button_manager = None

        # Initialize sensor domain.
        self.sensor_manager = SensorManager()

    @staticmethod
    def getInstance(settings=None):
        """Singleton factory.

        :param settings:  (Default value = None)

        """
        if TerkinDatalogger.__instance__ is None:
            if settings is None:
                raise Exception(
                    "Settings are None but instance wasn't created before.")
            else:
                TerkinDatalogger(settings)

        return TerkinDatalogger.__instance__

    def setup(self):
        """ """
        pass

    def start(self):
        """ """

        # Report about wakeup reason and run wakeup tasks.
        self.device.resume()

        # Start the watchdog for sanity.
        self.device.watchdog.start()

        # Configure RGB-LED according to settings.
        self.device.configure_rgb_led()

        # Alternative startup signalling: 2 x green.
        self.device.blink_led(0x000b00, count=2)

        # Free up some memory.
        self.device.run_gc()

        # Turn off LTE modem and Bluetooth as we don't use them yet.
        # TODO: Make this configurable.
        self.device.power_off_lte_modem()
        self.device.power_off_bluetooth()

        log.info('Starting %s', self.application_info.fullname)

        # Dump configuration settings.
        log_configuration = self.settings.get('main.logging.configuration',
                                              False)
        if log_configuration:
            self.settings.dump()

        # Disable this if you don't want serial access.
        #self.device.enable_serial()

        # Hello world.
        self.device.print_bootscreen()

        # Start networking and telemetry subsystems.

        # Conditionally start network services and telemetry if networking is available.
        try:
            self.device.start_networking()
        except Exception as ex:
            log.exc(ex, 'Networking subsystem failed')
            self.device.status.networking = False

        self.device.start_telemetry()

        # Todo: Signal readyness by publishing information about the device (Microhomie).
        # e.g. ``self.device.publish_properties()``

        # Setup sensors.
        self.device.watchdog.feed()
        bus_settings = self.settings.get('sensors.busses', [])
        self.sensor_manager.setup_busses(bus_settings)
        self.register_sensors()

        # Power up sensor peripherals.
        self.sensor_manager.power_on()

        # Ready.
        self.start_mainloop()

    def start_mainloop(self):
        """ """

        # Todo: Refactor by using timers.

        # Enter the main loop.
        while True:

            # Feed the watchdog timer to keep the system alive.
            self.device.watchdog.feed()

            # Indicate activity.
            # Todo: Optionally disable this output.
            log.info('--- loop ---')

            # Run downstream mainloop handlers.
            self.loop()

            # Give the system some breath.
            machine.idle()

    def loop(self):
        """Main duty cycle loop."""

        if not self.settings.get('main.deepsleep', False):
            self.duty_chrono.reset()

        #log.info('Terkin loop')

        # Alternative loop signalling: 1 x blue.
        # https://forum.pycom.io/topic/2067/brightness-of-on-board-led/7
        self.device.blink_led(0x00000b, count=2)

        # Read sensors.
        readings = self.read_sensors()

        # Remember current reading
        self.storage.last_reading = readings

        # Run the garbage collector.
        self.device.run_gc()

        # Transmit data.
        transmission_success = self.transmit_readings(readings)

        # Signal transmission outcome.
        if transmission_success:
            self.device.blink_led(0x00000b)
        else:
            self.device.blink_led(0x0b0000)

        # Run the garbage collector.
        self.device.run_gc()

        # Sleep how ever.
        self.sleep()

    def sleep(self):
        """Sleep until the next measurement cycle."""

        lightsleep = self.settings.get('main.lightsleep', False)
        deepsleep = self.settings.get('main.deepsleep', False)
        interval = self.get_sleep_time()

        # Amend deep sleep intent when masked through maintenance mode.
        if self.device.status.maintenance is True:
            lightsleep = False
            deepsleep = False
            log.info('Device is in maintenance mode. Skipping deep sleep and '
                     'adjusting interval to {} seconds'.format(interval))

        # Use deep sleep if requested.
        try:
            if deepsleep:

                # Shut down sensor peripherals.
                self.sensor_manager.power_off()

                # Shut down device peripherals.
                self.device.power_off()

            # Send device to deep sleep.
            self.device.hibernate(interval,
                                  lightsleep=lightsleep,
                                  deepsleep=deepsleep)

        # When hibernation fails, fall back to regular "time.sleep".
        except Exception as ex:
            log.exc(ex, 'Failed to hibernate, falling back to regular sleep')
            # Todo: Emit error message here.
            log.info('Sleeping for {} seconds'.format(interval))
            time.sleep(interval)

    def get_sleep_time(self):
        """ """
        interval = self.settings.get('main.interval', 60.0)

        # Configuration switchover backward compatibility / defaults.
        if isinstance(interval, (float, int)):
            self.settings.set('main.interval', {})
            self.settings.setdefault('main.interval.field', interval)
        self.settings.setdefault('main.interval.maintenance', 5.0)

        # Compute interval.
        interval = self.settings.get('main.interval.field')

        # Amend deep sleep intent when masked through maintenance mode.
        if self.device.status.maintenance is True:
            interval = self.settings.get('main.interval.maintenance')

        # Compute sleeping duration from measurement interval and elapsed time.
        elapsed = self.duty_chrono.read()
        sleep_time = interval - elapsed

        if sleep_time <= 0:
            sleep_time = interval

        return sleep_time

    def register_sensors(self):
        """
        Configure and register sensor objects.
        There are three types of sensors: system, environment & busses. Only the former two are assigned to the latter (if applicable).
        Definitions are in 'settings.py'.
        The sensor are registered by calling their respective classes from terkin/drivers/
        """

        # Add sensors.
        log.info('Registering sensors')
        sensor_infos = []

        # Get list of system sensors from configuration settings.
        sensor_infos += self.settings.get('sensors.system', [])

        # Get list of environmental sensors from configuration settings.
        sensor_infos += self.settings.get('sensors.environment', [])

        # Backward compatibility for environmental sensors.
        if sensor_infos is None:
            sensor_infos += self.settings.get('sensors.registry',
                                              {}).values() or []

        # Scan sensor definitions, create and register sensor objects.
        for sensor_info in sensor_infos:

            sensor_type = sensor_info.get('type', 'unknown').lower()
            sensor_id = sensor_info.get('id',
                                        sensor_info.get('key', sensor_type))
            description = sensor_info.get('description')

            # Skip sensor if disabled in configuration.
            if sensor_info.get('enabled') is False:
                log.info(
                    'Sensor with id={} and type={} is disabled, skipping registration'
                    .format(sensor_id, sensor_type))
                continue

            # skip WiFi sensor registration when WiFi is disabled
            if sensor_type == 'system.wifi':
                if not self.settings.get('networking.wifi.enabled'):
                    log.info('WiFi is disabled, skipping sensor registration')
                    continue

            # Resolve associated bus object.
            sensor_bus = None
            sensor_bus_name = None
            if 'bus' in sensor_info:
                sensor_info_bus = sensor_info['bus']
                sensor_bus = self.sensor_manager.get_bus_by_name(
                    sensor_info_bus)

                # Skip sensor if associated bus is disabled in configuration.
                if sensor_bus is None:
                    log.info(
                        'Bus {} for sensor with id={} and type={} is disabled, '
                        'skipping registration'.format(sensor_info_bus,
                                                       sensor_id, sensor_type))
                    continue
                sensor_bus_name = sensor_bus.name

            # Human readable sensor address.
            if 'address' in sensor_info:
                sensor_address = hex(sensor_info.get('address'))
            else:
                sensor_address = None

            # Report sensor registration to user.
            message = 'Setting up sensor with with id={} and type={} on bus={} with address={} ' \
                      'described as "{}"'.format(sensor_id, sensor_type, sensor_bus_name, sensor_address, description)
            log.info(message)

            try:

                # Sensor reporting about free system memory.
                if sensor_type == 'system.memfree':
                    sensor_object = SystemMemoryFree(sensor_info)

                # Sensor which reports system temperature.
                elif sensor_type == 'system.temperature':
                    sensor_object = SystemTemperature(sensor_info)

                # Sensor which reports battery voltage.
                elif sensor_type == 'system.battery-voltage':
                    sensor_object = SystemBatteryLevel(sensor_info)

                # Sensor which reports system uptime metrics.
                elif sensor_type == 'system.uptime':
                    sensor_object = SystemUptime(sensor_info)

                # Sensor which reports WiFi metrics.
                elif sensor_type == 'system.wifi':
                    try:
                        sensor_object = SystemWiFiMetrics(
                            sensor_info,
                            self.device.networking.wifi_manager.station)
                    except Exception as ex:
                        log.exc(ex, 'Enabling SystemWiFiMetrics sensor failed')
                        continue

                # Initialize buttons / touch pads.
                elif sensor_type == 'system.touch-buttons':
                    from terkin.sensor.button import ButtonManager
                    self.button_manager = ButtonManager()
                    self.start_buttons()

                # Setup and register HX711 sensors.
                elif sensor_type == 'hx711':
                    sensor_object = HX711Sensor(settings=sensor_info)
                    sensor_object.set_address(
                        sensor_info.get('number',
                                        sensor_info.get('address', 0)))
                    sensor_object.register_pin('dout', sensor_info['pin_dout'])
                    sensor_object.register_pin('pdsck',
                                               sensor_info['pin_pdsck'])
                    sensor_object.register_parameter('scale',
                                                     sensor_info['scale'])
                    sensor_object.register_parameter('offset',
                                                     sensor_info['offset'])
                    sensor_object.register_parameter(
                        'gain', sensor_info.get('gain', 128))

                    # Select driver module. Use "gerber" (vanilla) or "heisenberg" (extended).
                    # hx711_sensor.select_driver('gerber')
                    sensor_object.select_driver('heisenberg')

                    # Start sensor.
                    sensor_object.start()

                # Setup and register DS18X20 sensors.
                elif sensor_type == 'ds18b20':
                    sensor_object = DS18X20Sensor(settings=sensor_info)
                    sensor_object.acquire_bus(sensor_bus)

                    # Start sensor.
                    sensor_object.start()

                # Setup and register BME280 sensors.
                elif sensor_type == 'bme280':

                    sensor_object = BME280Sensor(settings=sensor_info)
                    if 'address' in sensor_info:
                        sensor_object.set_address(sensor_info['address'])
                    sensor_object.acquire_bus(sensor_bus)

                    # Start sensor.
                    sensor_object.start()

                else:
                    log.warning(
                        'Sensor with id={} has unknown type, skipping registration. '
                        'Sensor settings:\n{}'.format(sensor_id, sensor_info))
                    continue

                # Register sensor object with sensor manager.
                self.sensor_manager.register_sensor(sensor_object)

            except Exception as ex:
                log.exc(
                    ex,
                    'Setting up sensor with id={} and type={} failed'.format(
                        sensor_id, sensor_type))

            # Clean up memory after creating each sensor object.
            #self.device.run_gc()

    def read_sensors(self):
        """
        Read measurements from all sensor objects that have been registered in the sensor_manager.
        Reading is done with the read() function of each respective sensor object.
        """

        # Collect observations.
        data = {}
        richdata = {}

        # Iterate all registered sensors.
        sensors = self.sensor_manager.sensors
        log.info('Reading %s sensor ports', len(sensors))
        for sensor in sensors:

            # Signal sensor reading to user.
            sensorname = sensor.__class__.__name__
            log.info('Reading sensor port "%s"', sensorname)

            # Read sensor port.
            try:

                # Disable garbage collector to guarantee reasonable
                # realtime behavior before invoking sensor reading.
                with gc_disabled():
                    reading = sensor.read()

                # Evaluate sensor outcome.
                if reading is None or reading is AbstractSensor.SENSOR_NOT_INITIALIZED:
                    continue

                # Add sensor reading to observations.
                data.update(reading)

                # Record reading for prettified output.
                self.record_reading(sensor, reading, richdata)

            except Exception as ex:
                # Because of the ``gc_disabled`` context manager used above,
                # the propagation of exceptions has to be tweaked like that.
                log.exc(ex, 'Reading sensor "%s" failed', sensorname)

            # Feed the watchdog.
            self.device.watchdog.feed()

            # Clean up memory after reading each sensor object.
            #self.device.run_gc()

        # Debugging: Print sensor data before running telemetry.
        prettify_log = self.settings.get('sensors.prettify_log', False)
        if prettify_log:
            log.info('Sensor data:\n\n%s', ddformat(richdata, indent=11))
        else:
            log.info('Sensor data:  %s', data)

        return data

    def record_reading(self, sensor, reading, richdata):
        """

        :param sensor: 
        :param reading: 
        :param richdata: 

        """
        for key, value in reading.items():
            richdata[key] = {'value': value}
            if hasattr(sensor,
                       'settings') and 'description' in sensor.settings:
                richdata[key]['description'] = sensor.settings.get(
                    'description')
                # Hack to propagate the correct detail-description to prettified output.
                # TODO: Attach settings directly to its reading, while actually reading it.
                if 'devices' in sensor.settings:
                    for device_settings in sensor.settings['devices']:
                        device_address = device_settings['address'].lower()
                        if device_address in key:
                            if hasattr(sensor, 'get_device_description'):
                                device_description = sensor.get_device_description(
                                    device_address)
                                if device_description:
                                    richdata[key][
                                        'description'] = device_description

    def transmit_readings(self, data):
        """Transmit data

        :param data: 

        """

        # TODO: Optionally disable telemetry.
        if self.device.telemetry is None:
            log.warning('Telemetry disabled')
            return False

        telemetry_status = self.device.telemetry.transmit(data)
        count_total = len(telemetry_status)
        success = all(telemetry_status.values())

        # Evaluate telemetry status outcome.
        if success:
            log.info('Telemetry status: SUCCESS ({}/{})'.format(
                count_total, count_total))
        else:
            count_failed = len([
                item for item in telemetry_status.values() if item is not True
            ])
            log.warning(
                'Telemetry status: FAILURE. {} out of {} targets failed. '
                'Status: {}'.format(count_failed, count_total,
                                    telemetry_status))

        return success

    def start_buttons(self):
        """
        Configure ESP32 touchpads.
        """

        # RGB-LED: 2
        # POWER-ENABLE: 3
        # SD-Card: 4, 8
        # LTE 19, 20
        # Misc: 13, 14, 9, 23

        # Physical location when looking at the board with the RGB-LED oriented to the top.

        # Location: Left side, 6th pin from top.
        self.button_manager.setup_touchpad('P4',
                                           name='Touch3',
                                           location='Module-Left-Top-6th')

        # Location: Left side, 5th pin from bottom.
        self.button_manager.setup_touchpad('P8',
                                           name='Touch2',
                                           location='Module-Left-Bottom-5th')

        # Location: Right side.
        self.button_manager.setup_touchpad('P23',
                                           name='Touch6',
                                           location='Module-Right-Top-4th')

        # Location: Right side.
        # ValueError: invalid pin for touchpad
        """