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)
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
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)}
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
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()
def _on_audio_registered(self): """ Audio driver registered """ self.asoundconf = EtcAsoundConf(self.cleep_filesystem) self.configtxt = ConfigTxt(self.cleep_filesystem) self.console = Console()
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)
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)
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)
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])
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
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)
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
def __kill_watchers(self): """ Kill all watchers instances """ console = Console() console.command('/usr/bin/pkill -9 -f "cleep-cli watch"')
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