X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/blobdiff_plain/61d13d919e4e54d2163419ed7983cdbe31ad7568..0c3fbc1b0a51c47cc78a8595ef353773e1824f55:/src/adafruit_blinka/microcontroller/generic_linux/sysfs_pwmout.py diff --git a/src/adafruit_blinka/microcontroller/generic_linux/sysfs_pwmout.py b/src/adafruit_blinka/microcontroller/generic_linux/sysfs_pwmout.py index d0686f7..9bf2602 100644 --- a/src/adafruit_blinka/microcontroller/generic_linux/sysfs_pwmout.py +++ b/src/adafruit_blinka/microcontroller/generic_linux/sysfs_pwmout.py @@ -1,21 +1,40 @@ -# Much code from https://github.com/vsergeev/python-periphery/blob/master/periphery/pwm.py -# Copyright (c) 2015-2016 vsergeev / Ivan (Vanya) A. Sergeev -# License: MIT +# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries +# +# SPDX-License-Identifier: MIT +""" +Much code from https://github.com/vsergeev/python-periphery/blob/master/periphery/pwm.py +Copyright (c) 2015-2016 vsergeev / Ivan (Vanya) A. Sergeev +License: MIT +""" import os -import digitalio +from time import sleep +from errno import EACCES try: from microcontroller.pin import pwmOuts except ImportError: - raise RuntimeError("No PWM outputs defined for this board") + raise RuntimeError("No PWM outputs defined for this board") from ImportError + +# pylint: disable=unnecessary-pass class PWMError(IOError): """Base class for PWM errors.""" + pass -class PWMOut(object): +# pylint: enable=unnecessary-pass + + +class PWMOut: + """Pulse Width Modulation Output Class""" + + # Number of retries to check for successful PWM export on open + PWM_STAT_RETRIES = 10 + # Delay between check for scucessful PWM export on open (100ms) + PWM_STAT_DELAY = 0.1 + # Sysfs paths _sysfs_path = "/sys/class/pwm/" _channel_path = "pwmchip{}" @@ -52,6 +71,8 @@ class PWMOut(object): """ self._pwmpin = None + self._channel = None + self._period = 0 self._open(pin, duty_cycle, frequency, variable_frequency) def __del__(self): @@ -74,28 +95,58 @@ class PWMOut(object): if self._channel is None: raise RuntimeError("No PWM channel found for this Pin") - channel_path = os.path.join(self._sysfs_path, self._channel_path.format(self._channel)) + if variable_frequency: + print("Variable Frequency is not supported, continuing without it...") + + channel_path = os.path.join( + self._sysfs_path, self._channel_path.format(self._channel) + ) if not os.path.isdir(channel_path): - raise ValueError("PWM channel does not exist, check that the required modules are loaded.") + raise ValueError( + "PWM channel does not exist, check that the required modules are loaded." + ) - pin_path = os.path.join(channel_path, self._pin_path.format(self._pwmpin)) try: - with open(os.path.join(channel_path, self._unexport_path), "w") as f_unexport: + with open( + os.path.join(channel_path, self._unexport_path), "w", encoding="utf-8" + ) as f_unexport: f_unexport.write("%d\n" % self._pwmpin) - except IOError as e: - pass # not unusual, it doesnt already exist + except IOError: + pass # not unusual, it doesnt already exist try: - with open(os.path.join(channel_path, self._export_path), "w") as f_export: + with open( + os.path.join(channel_path, self._export_path), "w", encoding="utf-8" + ) as f_export: f_export.write("%d\n" % self._pwmpin) except IOError as e: - raise PWMError(e.errno, "Exporting PWM pin: " + e.strerror) + raise PWMError(e.errno, "Exporting PWM pin: " + e.strerror) from IOError + + # Loop until 'period' is writable, because application of udev rules + # after the above pin export is asynchronous. + # Without this loop, the following properties may not be writable yet. + for i in range(PWMOut.PWM_STAT_RETRIES): + try: + with open( + os.path.join( + channel_path, self._pin_path.format(self._pwmpin), "period" + ), + "w", + encoding="utf-8", + ): + break + except IOError as e: + if e.errno != EACCES or ( + e.errno == EACCES and i == PWMOut.PWM_STAT_RETRIES - 1 + ): + raise PWMError(e.errno, "Opening PWM period: " + e.strerror) from e + sleep(PWMOut.PWM_STAT_DELAY) - #self._set_enabled(False) # This line causes a write error when trying to enable + # self._set_enabled(False) # This line causes a write error when trying to enable # Look up the period, for fast duty cycle updates self._period = self._get_period() - #self.duty_cycle = 0 # This line causes a write error when trying to enable + # self.duty_cycle = 0 # This line causes a write error when trying to enable # set frequency self.frequency = freq @@ -107,21 +158,30 @@ class PWMOut(object): def deinit(self): """Deinit the sysfs PWM.""" if self._channel is not None: - self.duty_cycle = 0 try: - channel_path = os.path.join(self._sysfs_path, self._channel_path.format(self._channel)) - with open(os.path.join(channel_path, self._unexport_path), "w") as f_unexport: + channel_path = os.path.join( + self._sysfs_path, self._channel_path.format(self._channel) + ) + with open( + os.path.join(channel_path, self._unexport_path), + "w", + encoding="utf-8", + ) as f_unexport: f_unexport.write("%d\n" % self._pwmpin) except IOError as e: - raise PWMError(e.errno, "Unexporting PWM pin: " + e.strerror) + raise PWMError( + e.errno, "Unexporting PWM pin: " + e.strerror + ) from IOError self._channel = None self._pwmpin = None def _is_deinited(self): if self._pwmpin is None: - raise ValueError("Object has been deinitialize and can no longer " - "be used. Create a new object.") + raise ValueError( + "Object has been deinitialize and can no longer " + "be used. Create a new object." + ) def _write_pin_attr(self, attr, value): # Make sure the pin is active @@ -131,10 +191,11 @@ class PWMOut(object): self._sysfs_path, self._channel_path.format(self._channel), self._pin_path.format(self._pwmpin), - attr) + attr, + ) - with open(path, 'w') as f_attr: - #print(value, path) + with open(path, "w", encoding="utf-8") as f_attr: + # print(value, path) f_attr.write(value + "\n") def _read_pin_attr(self, attr): @@ -145,9 +206,10 @@ class PWMOut(object): self._sysfs_path, self._channel_path.format(self._channel), self._pin_path.format(self._pwmpin), - attr) + attr, + ) - with open(path, 'r') as f_attr: + with open(path, "r", encoding="utf-8") as f_attr: return f_attr.read().strip() # Mutable properties @@ -157,7 +219,9 @@ class PWMOut(object): try: period_ns = int(period_ns) except ValueError: - raise PWMError(None, "Unknown period value: \"%s\"" % period_ns) + raise PWMError( + None, 'Unknown period value: "%s"' % period_ns + ) from ValueError # Convert period from nanoseconds to seconds period = period_ns / 1e9 @@ -179,6 +243,8 @@ class PWMOut(object): # Update our cached period self._period = float(period) + period = property(_get_period, _set_period) + """Get or set the PWM's output period in seconds. Raises: @@ -193,7 +259,9 @@ class PWMOut(object): try: duty_cycle_ns = int(duty_cycle_ns) except ValueError: - raise PWMError(None, "Unknown duty cycle value: \"%s\"" % duty_cycle_ns) + raise PWMError( + None, 'Unknown duty cycle value: "%s"' % duty_cycle_ns + ) from ValueError # Convert duty cycle from nanoseconds to seconds duty_cycle = duty_cycle_ns / 1e9 @@ -257,28 +325,31 @@ class PWMOut(object): if enabled == "1": return True - elif enabled == "0": + if enabled == "0": return False - raise PWMError(None, "Unknown enabled value: \"%s\"" % enabled) + raise PWMError(None, 'Unknown enabled value: "%s"' % enabled) def _set_enabled(self, value): + """Get or set the PWM's output enabled state. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not bool. + + :type: bool + """ if not isinstance(value, bool): raise TypeError("Invalid enabled type, should be string.") self._write_pin_attr(self._pin_enable_path, "1" if value else "0") - """Get or set the PWM's output enabled state. - - Raises: - PWMError: if an I/O or OS error occurs. - TypeError: if value type is not bool. - - :type: bool - """ - # String representation def __str__(self): - return "PWM%d, pin %s (freq=%f Hz, duty_cycle=%f%%)" % \ - (self._channel, self._pin, self.frequency, self.duty_cycle * 100,) + return "PWM%d, pin %s (freq=%f Hz, duty_cycle=%f%%)" % ( + self._channel, + self._pin, + self.frequency, + self.duty_cycle * 100, + )