示例#1
0
    def tweak_power_led(self, enable):
        """
        Tweak raspberry pi power led

        Note:
            Infos from https://www.jeffgeerling.com/blogs/jeff-geerling/controlling-pwr-act-leds-raspberry-pi

        Args:
            enable (bool): True to turn on led
        """
        if not os.path.exists('/sys/class/leds/led1'):
            self.logger.info('Power led not found on this device')
            return

        raspi = Tools.raspberry_pi_infos()
        off_value = '0' if raspi['model'].lower().find('zero') else '1'
        on_value = '1' if raspi['model'].lower().find('zero') else '0'
        echo_value = on_value if enable else off_value
        console = Console()
        resp = console.command('echo %s > /sys/class/leds/led1/brightness' %
                               echo_value)
        if resp['returncode'] != 0:
            raise CommandError('Error tweaking power led')

        # store led status
        self._set_config_field('enablepowerled', enable)
示例#2
0
    def build_application(self, module_name):
        """
        Build application archive (zip format)
        Archive is not protected by password

        Args:
            module_name (string): module name

        Raises:
            Exception: if build failed
        """
        cmd = self.CLI_BUILD_APP_CMD % (self.CLI, module_name)
        self.logger.debug('Build app cmd: %s' % cmd)

        console = Console()
        res = console.command(cmd, 60.0)
        self.logger.info('Build app result: %s | %s' %
                         (res['stdout'], res['stderr']))
        if res['returncode'] != 0:
            raise CommandError('Error building application. Check Cleep logs.')

        try:
            self.__last_application_build = json.loads(res['stdout'][0])
        except Exception as error:
            self.logger.exception(
                'Error parsing app build command "%s" output' % cmd)
            raise CommandError(
                'Error building application. Check Cleep logs.') from error
示例#3
0
    def download_documentation(self, module_name):
        """
        Download documentation (html as archive tar.gz)

        Args:
            module_name (string): module name

        Returns:
            dict: archive infos::

                {
                    data: filepath
                    filename: new filename
                }

        Raises:
            CommandError: command failed error

        """
        self.logger.info('Download documentation html archive')

        cmd = self.CLI_DOCS_ZIP_PATH_CMD % (self.CLI, module_name)
        self.logger.debug('Doc zip path cmd: %s' % cmd)
        console = Console()
        res = console.command(cmd)
        if res['returncode'] != 0:
            raise CommandError(''.join(res['stdout']))

        zip_path = res['stdout'][0].split('=')[1]
        self.logger.debug('Module "%s" docs path "%s"' %
                          (module_name, zip_path))
        return {'filepath': zip_path, 'filename': os.path.basename(zip_path)}
示例#4
0
    def __cli_check(self, command, timeout=15.0):
        """
        Execute cleep-cli check specified by command

        Args:
            command (string): cli command to execute

        Returns:
            dict: command output
        """
        console = Console()
        res = console.command(command, timeout)
        self.logger.debug('Cli command "%s" output: %s | %s' %
                          (command, res['stdout'], res['stderr']))
        if res['returncode'] != 0:
            self.logger.exception('Command "%s"', command)
            raise CommandError('Command failed')

        try:
            return json.loads(''.join(res['stdout']))
        except Exception as error:
            self.logger.exception('Error parsing command "%s" output' %
                                  command)
            raise CommandError(
                'Error parsing check result. Check Cleep logs') from error
示例#5
0
    def create_application(self, module_name):
        """
        Create new application skel

        Args:
            module_name (string): module name

        Raises:
            CommandError: if command failed
        """
        self.__stop_watcher()

        cmd = self.CLI_NEW_APPLICATION_CMD % (self.CLI, module_name)
        self.logger.debug('Create app cmd: %s' % cmd)

        try:
            console = Console()
            res = console.command(cmd, 10.0)
            self.logger.info('Create app cmd result: %s %s' %
                             (res['stdout'], res['stderr']))
            if res['returncode'] != 0:
                raise CommandError(
                    'Error during application creation. Check Cleep logs.')

            # sync new app content
            console.command(self.CLI_SYNC_MODULE_CMD % module_name)
        finally:
            self.__start_watcher()
示例#6
0
 def _on_audio_registered(self):
     """
     Audio driver registered
     """
     self.asoundconf = EtcAsoundConf(self.cleep_filesystem)
     self.configtxt = ConfigTxt(self.cleep_filesystem)
     self.console = Console()
示例#7
0
    def restart_cleep(self, delay=3.0):
        """
        Restart Cleep
        """
        # backup configuration
        self.backup_cleep_config()

        # send event
        self.cleep_restart_event.send({'delay': delay})

        # and restart cleep
        console = Console()
        console.command_delayed('/etc/cleep/cleephelper.sh restart', delay)
示例#8
0
    def poweroff_device(self, delay=5.0):
        """
        Poweroff device
        """
        # backup configuration
        self.backup_cleep_config()

        # send event
        self.device_poweroff_event.send({'delay': delay})

        # and reboot system
        console = Console()
        console.command_delayed('poweroff', delay)
示例#9
0
    def reboot_device(self, delay=5.0):
        """
        Reboot device
        """
        # backup configuration
        self.backup_cleep_config()

        # send event
        self.device_reboot_event.send({'delay': delay})

        # and reboot system
        console = Console()
        console.command_delayed('reboot', delay)
示例#10
0
    def _execute_command(self, sensor):  # pragma: no cover
        """
        Execute dht22 binary command
        Useful for unit testing
        """
        console = Console()
        cmd = self.DHT22_CMD % sensor["gpios"][0]["pin"]
        self.logger.debug('Read DHT22 sensor values from command "%s"' % cmd)
        resp = console.command(cmd, timeout=11)
        self.logger.debug("Read DHT command response: %s" % resp)
        if resp['error'] or resp['killed']:
            self.logger.error("DHT22 command failed: %s" % resp)

        return json.loads(resp['stdout'][0])
示例#11
0
    def sync_time():
        """
        Synchronize device time using NTP server

        Note:
            This command may lasts some seconds

        Returns:
            bool: True if NTP sync succeed, False otherwise
        """
        console = Console()
        resp = console.command("/usr/sbin/ntpdate-debian", timeout=60.0)

        return resp["returncode"] == 0
示例#12
0
    def tweak_activity_led(self, enable):
        """
        Tweak raspberry pi activity led

        Args:
            enable (bool): True to turn on led
        """
        if not os.path.exists('/sys/class/leds/led0'):
            self.logger.info('Activity led not found on this device')
            return

        raspi = Tools.raspberry_pi_infos()
        off_value = '0' if raspi['model'].lower().find('zero') else '1'
        on_value = '1' if raspi['model'].lower().find('zero') else '0'
        echo_value = on_value if enable else off_value
        console = Console()
        resp = console.command('echo %s > /sys/class/leds/led0/brightness' %
                               echo_value)
        if resp['returncode'] != 0:
            raise CommandError('Error tweaking activity led')

        # store led status
        self._set_config_field('enableactivityled', enable)
示例#13
0
    def set_timezone(self):
        """
        Set timezone according to coordinates

        Returns:
            bool: True if function succeed, False otherwise

        Raises:
            CommandError: if unable to save timezone
        """
        # get position
        position = self._get_config_field("position")
        if not position["latitude"] and not position["longitude"]:
            self.logger.warning(
                "Unable to set timezone from unspecified position (%s)" %
                position)
            return False

        # compute timezone
        current_timezone = None
        try:
            # try to find timezone at position
            current_timezone = self.timezonefinder.timezone_at(
                lat=position["latitude"], lng=position["longitude"])
            if current_timezone is None:
                # extend search to closest position
                # TODO increase delta_degree to extend research, careful it use more CPU !
                current_timezone = self.timezonefinder.closest_timezone_at(
                    lat=position["latitude"], lng=position["longitude"])
        except ValueError:
            # the coordinates were out of bounds
            self.logger.exception("Coordinates out of bounds")
        except Exception:
            self.logger.exception(
                "Error occured searching timezone at position")
        if not current_timezone:
            self.logger.warning(
                "Unable to set device timezone because it was not found")
            return False

        # save timezone value
        self.logger.debug("Save new timezone: %s" % current_timezone)
        if not self._set_config_field("timezone", current_timezone):
            raise CommandError("Unable to save timezone")

        # configure system timezone
        zoneinfo = os.path.join(self.SYSTEM_ZONEINFO_DIR, current_timezone)
        self.logger.debug("Checking zoneinfo file: %s" % zoneinfo)
        if not os.path.exists(zoneinfo):
            raise CommandError('No system file found for "%s" timezone' %
                               current_timezone)
        self.logger.debug('zoneinfo file "%s" exists' % zoneinfo)
        self.cleep_filesystem.rm(self.SYSTEM_LOCALTIME)

        self.logger.debug('Writing timezone "%s" in "%s"' %
                          (current_timezone, self.SYSTEM_TIMEZONE))
        if not self.cleep_filesystem.write_data(self.SYSTEM_TIMEZONE,
                                                "%s" % current_timezone):
            self.logger.error(
                'Unable to write timezone data on "%s". System timezone is not configured!'
                % self.SYSTEM_TIMEZONE)
            return False

        # launch timezone update in background
        self.logger.debug("Updating system timezone")
        command = Console()
        res = command.command(
            "/usr/sbin/dpkg-reconfigure -f noninteractive tzdata",
            timeout=15.0)
        self.logger.debug("Timezone update command result: %s" % res)
        if res["returncode"] != 0:
            self.logger.error("Error reconfiguring system timezone: %s" %
                              res["stderr"])
            return False

        # TODO configure all wpa_supplicant.conf country code

        # propagate changes to cleep
        self.timezone = timezone(current_timezone)
        self._time_task()

        return True
示例#14
0
 def __kill_watchers(self):
     """
     Kill all watchers instances
     """
     console = Console()
     console.command('/usr/bin/pkill -9 -f "cleep-cli watch"')
示例#15
0
class UsbAudioDriver(AudioDriver):
    """
    Audio driver for USB audio devices

    Tested hardare:
     * Mini external USB stereo speaker: https://thepihut.com/collections/raspberry-pi-usb-audio/products/mini-external-usb-stereo-speaker
    """

    VOLUME_PATTERN = ("Mono", r"\[(\d*)%\]")

    def __init__(self):
        """
        Constructor
        """
        AudioDriver.__init__(self, "USB audio device")

        self.asoundconf = None
        self.configtxt = None
        self.console = None
        self.volume_control = ""
        self.volume_control_numid = None

    def _on_audio_registered(self):
        """
        Audio driver registered
        """
        self.asoundconf = EtcAsoundConf(self.cleep_filesystem)
        self.configtxt = ConfigTxt(self.cleep_filesystem)
        self.console = Console()

    def _get_card_name(self, devices_names):
        """
        Return card name

        Returns:
            string: card name or None if card not found
        """
        pattern = re.compile("usb", re.IGNORECASE)
        for device_name in devices_names:
            if pattern.match(device_name["device_desc"]):
                return device_name["card_name"]

        return None

    def get_card_capabilities(self):
        """
        Return card capabilities

        Returns:
            tuple: card capabilities::

                (
                    bool: playback capability,
                    bool: capture capability
                )
        """
        return (True, False)

    def _install(self, params=None):
        """
        Install driver

        Args:
            params (dict): additional parameters
        """
        # as the default driver and just in case, delete existing config
        self.asoundconf.delete()

        # install pulseaudio debian package
        resp = self.console.command(
            "apt update -qq && apt install -q --yes pulseaudio", timeout=300)
        if resp["returncode"] != 0:
            self.logger.error("Unable to install USB audio: %s", resp)
            return False

        # installing native audio device consists of enabling dtparam audio in /boot/config.txt
        if not self.configtxt.enable_audio():
            raise Exception("Error enabling USB audio")

        return True

    def _uninstall(self, params=None):
        """
        Uninstall driver

        Args:
            params (dict): additional parameters
        """
        resp = self.console.command("apt purge --q --yes pulseaudio")
        if resp["returncode"] != 0:
            self.logger.error("Unable to uninstall USB audio: %s", resp)
            raise Exception('Unable to uninstall USB audio')

        return True

    def is_installed(self):
        """
        Is driver installed

        Returns:
            bool: True if driver is installed
        """
        resp = self.console.command("dpkg -s pulseaudio")
        return resp["returncode"] == 0

    def enable(self, params=None):
        """
        Enable driver
        """
        if not self.get_card_name():
            raise Exception(
                "No USB audio found. Please connect it before enabling it")

        # as the default driver and just in case, delete existing config
        self.asoundconf.delete()

        # create default /etc/asound.conf
        card_infos = self.get_cardid_deviceid()
        self.logger.debug("card_infos=%s", card_infos)
        if card_infos[0] is None:
            self.logger.error('Unable to get alsa infos for card "%s"',
                              self.get_card_name())
            return False
        self.logger.debug('Write to /etc/asound.conf values "%s:%s"',
                          card_infos[0], card_infos[1])
        if not self.asoundconf.save_default_file(card_infos[0], card_infos[1]):
            self.logger.error(
                'Unable to create /etc/asound.conf for soundcard "%s"',
                self.get_card_name(),
            )
            return False

        # force saving alsa conf (this will create asound.state if needed)
        self.alsa.save()

        return True

    def disable(self, params=None):
        """
        Disable driver

        Args:
            params (dict): additional parameters
        """
        self.logger.debug(
            "Delete /etc/asound.conf and /var/lib/alsa/asound.state")
        if not self.asoundconf.delete():
            self.logger.error("Unable to delete asound.conf file")
            return False

        self.logger.debug("Driver disabled")
        return True

    def is_enabled(self):
        """
        Is driver enabled ?

        Returns:
            bool: True if driver enabled
        """
        card = self.is_card_enabled()
        asound = self.asoundconf.exists()

        return card and asound

    def _set_volumes_controls(self):
        """
        Set controls to control volumes
        """
        # search for appropriate volume control
        controls = self.alsa.get_simple_controls()
        self.volume_control = controls[0] if len(controls) > 0 else ""

        # get volume control numid
        self.volume_control_numid = self.get_control_numid("Volume")

    def get_volumes(self):
        """
        Get volumes

        Returns:
            dict: volumes level::

                {
                    playback (float): playback volume
                    capture (float): capture volume
                }

        """
        return {
            "playback":
            self.alsa.get_volume(self.volume_control, self.VOLUME_PATTERN),
            "capture":
            None,
        }

    def set_volumes(self, playback=None, capture=None):
        """
        Set volumes

        Args:
            playback (float): playback volume (None to disable update)
            capture (float): capture volume (None to disable update)

        Returns:
            dict: volumes level::

                {
                    playback (float): playback volume
                    capture (float): capture volume
                }

        """
        return {
            "playback":
            self.alsa.set_volume(self.volume_control, self.VOLUME_PATTERN,
                                 playback),
            "capture":
            None,
        }

    def require_reboot(self):
        """
        Require reboot after install/uninstall

        Returns:
            bool: True if reboot required
        """
        return False