diff --git a/README.rst b/README.rst index 918da02..ba82ae2 100644 --- a/README.rst +++ b/README.rst @@ -22,10 +22,9 @@ Getting Started This library runs on ev3dev_. Before continuing, make sure that you have set up your EV3 or other ev3dev device as explained in the `ev3dev Getting Started guide`_. -Make sure that you have a kernel version that includes ``-10-ev3dev`` or higher (a -larger number). You can check the kernel version by selecting "About" in Brickman -and scrolling down to the "kernel version". If you don't have a compatible version, -`upgrade the kernel before continuing`_. +Make sure you have an ev3dev-stretch version greater than ``2.2.0``. You can check +the kernel version by selecting "About" in Brickman and scrolling down to the +"kernel version". If you don't have a compatible version, `upgrade the kernel before continuing`_. Usage ----- @@ -41,7 +40,7 @@ your own solution. If you don't know how to do that, you are probably better off choosing the recommended option above. The template for a Python script -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Every Python program should have a few basic parts. Use this template to get started: @@ -49,7 +48,8 @@ to get started: .. code-block:: python #!/usr/bin/env python3 - from ev3dev2.motor import LargeMotor, OUTPUT_A, SpeedPercent + from ev3dev2.motor import LargeMotor, OUTPUT_A, OUTPUT_B, SpeedPercent, MoveTank + from ev3dev2.sensor import INPUT_1 from ev3dev2.sensor.lego import TouchSensor from ev3dev2.led import Leds @@ -64,6 +64,10 @@ or additional utilities. You should use the ``.py`` extension for your file, e.g. ``my-file.py``. +If you encounter an error such as ``/usr/bin/env: 'python3\r': No such file or directory``, +you must switch your editor's "line endings" setting for the file from "CRLF" to just "LF". +This is usually in the status bar at the bottom. For help, see `our FAQ page`_. + Important: Make your script executable (non-Visual Studio Code only) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -80,7 +84,7 @@ from the command line by preceding the file name with ``./``: ``./my-file.py`` Controlling the LEDs with a touch sensor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This code will turn the left LED red whenever the touch sensor is pressed, and +This code will turn the LEDs red whenever the touch sensor is pressed, and back to green when it's released. Plug a touch sensor into any sensor port before trying this out. @@ -89,11 +93,21 @@ trying this out. ts = TouchSensor() leds = Leds() + print("Press the touch sensor to change the LED color!") + while True: if ts.is_pressed: - leds.set_color(leds.led_groups.LEFT, leds.led_colors.GREEN) + leds.set_color("LEFT", "GREEN") + leds.set_color("RIGHT", "GREEN") else: - leds.set_color(leds.led_groups.LEFT, leds.led_colors.RED) + leds.set_color("LEFT", "RED") + leds.set_color("RIGHT", "RED") + +If you'd like to use a sensor on a specific port, specify the port like this: + +.. code-block:: python + + ts = TouchSensor(INPUT_1) Running a single motor ~~~~~~~~~~~~~~~~~~~~~~ @@ -144,7 +158,7 @@ If you want to make your robot speak, you can use the ``Sound.speak`` method: from ev3dev2.sound import Sound sound = Sound() - sound.speak('Welcome to the E V 3 dev project!').wait() + sound.speak('Welcome to the E V 3 dev project!') Make sure to check out the `User Resources`_ section for more detailed information on these features and many others. @@ -192,7 +206,7 @@ to type the password (the default is ``maker``) when prompted. .. code-block:: bash sudo apt-get update - sudo apt-get install --only-upgrade python3-ev3dev + sudo apt-get install --only-upgrade python3-ev3dev2 Developer Resources @@ -209,9 +223,6 @@ Python 2.x and Python 3.x Compatibility Some versions of the ev3dev_ distribution come with both `Python 2.x`_ and `Python 3.x`_ installed but this library is compatible only with Python 3. -As of the 2016-10-17 ev3dev image, the version of this library which is included runs on -Python 3 and this is the only version that will be supported from here forward. - .. _ev3dev: http://ev3dev.org .. _ev3dev.org: ev3dev_ .. _Getting Started: ev3dev-getting-started_ @@ -221,9 +232,10 @@ Python 3 and this is the only version that will be supported from here forward. .. _detailed instructions for USB connections: ev3dev-usb-internet_ .. _via an SSH connection: http://www.ev3dev.org/docs/tutorials/connecting-to-ev3dev-with-ssh/ .. _ev3dev-usb-internet: http://www.ev3dev.org/docs/tutorials/connecting-to-the-internet-via-usb/ -.. _our Read the Docs page: http://python-ev3dev.readthedocs.org/en/stable/ +.. _our Read the Docs page: http://python-ev3dev.readthedocs.org/en/ev3dev-stretch/ .. _ev3python.com: http://ev3python.com/ -.. _FAQ: http://python-ev3dev.readthedocs.io/en/stable/faq.html +.. _FAQ: http://python-ev3dev.readthedocs.io/en/ev3dev-stretch/faq.html +.. _our FAQ page: FAQ_ .. _ev3dev-lang-python: https://github.com/rhempel/ev3dev-lang-python .. _our Issues tracker: https://github.com/rhempel/ev3dev-lang-python/issues .. _EXPLOR3R: demo-robot_ diff --git a/docs/conf.py b/docs/conf.py index 8f3d526..1291fc3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -317,7 +317,14 @@ nitpick_ignore = [ ('py:class', 'ev3dev2.display.FbMem'), - ('py:class', 'ev3dev2.button.ButtonBase') + ('py:class', 'ev3dev2.button.ButtonBase'), + ('py:class', 'int'), + ('py:class', 'float'), + ('py:class', 'string'), + ('py:class', 'iterable'), + ('py:class', 'tuple'), + ('py:class', 'list'), + ('py:exc', 'ValueError') ] def setup(app): diff --git a/docs/other.rst b/docs/other.rst index a65a81c..c74a0a7 100644 --- a/docs/other.rst +++ b/docs/other.rst @@ -30,31 +30,69 @@ Leds .. autoclass:: ev3dev2.led.Leds :members: - .. rubric:: EV3 platform +LED group and color names +~~~~~~~~~~~~~~~~~~~~~~~~~ - Led groups: +.. rubric:: EV3 platform - .. py:data:: LEFT - .. py:data:: RIGHT +Led groups: - Colors: +- ``LEFT`` +- ``RIGHT`` - .. py:data:: RED - .. py:data:: GREEN - .. py:data:: AMBER - .. py:data:: ORANGE - .. py:data:: YELLOW +Colors: - .. rubric:: BrickPI platform +- ``BLACK`` +- ``RED`` +- ``GREEN`` +- ``AMBER`` +- ``ORANGE`` +- ``YELLOW`` - Led groups: +.. rubric:: BrickPI platform - .. py:data:: LED1 - .. py:data:: LED2 +Led groups: - Colors: +- ``LED1`` +- ``LED2`` - .. py:data:: BLUE +Colors: + +- ``BLACK`` +- ``BLUE`` + +.. rubric:: BrickPI3 platform + +Led groups: + +- ``LED`` + +Colors: + +- ``BLACK`` +- ``BLUE`` + +.. rubric:: PiStorms platform + +Led groups: + +- ``LEFT`` +- ``RIGHT`` + +Colors: + +- ``BLACK`` +- ``RED`` +- ``GREEN`` +- ``BLUE`` +- ``YELLOW`` +- ``CYAN`` +- ``MAGENTA`` + +.. rubric:: EVB platform + +None. + Power Supply ------------ @@ -76,7 +114,7 @@ Display :show-inheritance: Bitmap fonts -^^^^^^^^^^^^ +~~~~~~~~~~~~ The :py:class:`ev3dev2.display.Display` class allows to write text on the LCD using python imaging library (PIL) interface (see description of the ``text()`` method diff --git a/ev3dev2/led.py b/ev3dev2/led.py index 83a1c8c..69b516e 100644 --- a/ev3dev2/led.py +++ b/ev3dev2/led.py @@ -292,17 +292,27 @@ def set_color(self, group, color, pct=1): reduced proportionally. Example:: + my_leds = Leds() my_leds.set_color('LEFT', 'AMBER') + + With a custom color:: + + my_leds = Leds() + my_leds.set_color('LEFT', (0.5, 0.3)) """ # If this is a platform without LEDs there is nothing to do if not self.leds: return + color_tuple = color + if isinstance(color, str): + assert color in self.led_colors, "%s is an invalid LED color, valid choices are %s" % (color, ','.join(self.led_colors.keys())) + color_tuple = self.led_colors[color] + assert group in self.led_groups, "%s is an invalid LED group, valid choices are %s" % (group, ','.join(self.led_groups.keys())) - assert color in self.led_colors, "%s is an invalid LED color, valid choices are %s" % (color, ','.join(self.led_colors.keys())) - for led, value in zip(self.led_groups[group], self.led_colors[color]): + for led, value in zip(self.led_groups[group], color_tuple): led.brightness_pct = value * pct def set(self, group, **kwargs): @@ -310,6 +320,7 @@ def set(self, group, **kwargs): Set attributes for each led in group. Example:: + my_leds = Leds() my_leds.set_color('LEFT', brightness_pct=0.5, trigger='timer') """ diff --git a/ev3dev2/sound.py b/ev3dev2/sound.py index e2280b5..6f4e125 100644 --- a/ev3dev2/sound.py +++ b/ev3dev2/sound.py @@ -55,11 +55,11 @@ class Sound(object): Examples:: - # Play 'bark.wav', return immediately: + # Play 'bark.wav': Sound.play('bark.wav') - # Introduce yourself, wait for completion: - Sound.speak('Hello, I am Robot').wait() + # Introduce yourself: + Sound.speak('Hello, I am Robot') # Play a small song Sound.play_song(( @@ -78,9 +78,9 @@ class Sound(object): channel = None # play_types - PLAY_WAIT_FOR_COMPLETE = 0 - PLAY_NO_WAIT_FOR_COMPLETE = 1 - PLAY_LOOP = 2 + PLAY_WAIT_FOR_COMPLETE = 0 #: Play the sound and block until it is complete + PLAY_NO_WAIT_FOR_COMPLETE = 1 #: Start playing the sound but return immediately + PLAY_LOOP = 2 #: Never return; start the sound immediately after it completes, until the program is killed PLAY_TYPES = ( PLAY_WAIT_FOR_COMPLETE, @@ -92,30 +92,40 @@ def _validate_play_type(self, play_type): assert play_type in self.PLAY_TYPES, \ "Invalid play_type %s, must be one of %s" % (play_type, ','.join(str(t) for t in self.PLAY_TYPES)) - def beep(self, args=''): + def beep(self, args='', play_type=PLAY_WAIT_FOR_COMPLETE): """ Call beep command with the provided arguments (if any). See `beep man page`_ and google `linux beep music`_ for inspiration. + :param string args: Any additional arguments to be passed to ``beep`` (see the `beep man page`_ for details) + + :param play_type: The behavior of ``beep`` once playback has been initiated + :type play_type: ``Sound.PLAY_WAIT_FOR_COMPLETE`` or ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` + + :return: When ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` is specified, returns the returns the spawn subprocess from ``subprocess.Popen``; ``None`` otherwise + .. _`beep man page`: https://linux.die.net/man/1/beep .. _`linux beep music`: https://www.google.com/search?q=linux+beep+music """ with open(os.devnull, 'w') as n: - return Popen(shlex.split('/usr/bin/beep %s' % args), stdout=n) + subprocess = Popen(shlex.split('/usr/bin/beep %s' % args), stdout=n) + if play_type == Sound.PLAY_WAIT_FOR_COMPLETE: + subprocess.wait() + return None + else: + return subprocess - def tone(self, *args): + + def tone(self, *args, play_type=PLAY_WAIT_FOR_COMPLETE): """ .. rubric:: tone(tone_sequence) - Play tone sequence. The tone_sequence parameter is a list of tuples, - where each tuple contains up to three numbers. The first number is - frequency in Hz, the second is duration in milliseconds, and the third - is delay in milliseconds between this and the next tone in the - sequence. + Play tone sequence. Here is a cheerful example:: - Sound.tone([ + my_sound = Sound() + my_sound.tone([ (392, 350, 100), (392, 350, 100), (392, 350, 100), (311.1, 250, 100), (466.2, 25, 100), (392, 350, 100), (311.1, 250, 100), (466.2, 25, 100), (392, 700, 100), (587.32, 350, 100), (587.32, 350, 100), @@ -134,14 +144,29 @@ def tone(self, *args): (466.16, 25, 100), (440, 25, 100), (466.16, 50, 400), (311.13, 25, 200), (392, 350, 100), (311.13, 250, 100), (466.16, 25, 100), (392.00, 300, 150), (311.13, 250, 100), (466.16, 25, 100), (392, 700) - ]).wait() + ]) Have also a look at :py:meth:`play_song` for a more musician-friendly way of doing, which uses the conventional notation for notes and durations. + :param list[tuple(float,float,float)] tone_sequence: The sequence of tones to play. The first number of each tuple is frequency in Hz, the second is duration in milliseconds, and the third is delay in milliseconds between this and the next tone in the sequence. + + :param play_type: The behavior of ``tone`` once playback has been initiated + :type play_type: ``Sound.PLAY_WAIT_FOR_COMPLETE`` or ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` + + :return: When ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` is specified, returns the returns the spawn subprocess from ``subprocess.Popen``; ``None`` otherwise + .. rubric:: tone(frequency, duration) - Play single tone of given frequency (Hz) and duration (milliseconds). + Play single tone of given frequency and duration. + + :param float frequency: The frequency of the tone in Hz + :param float duration: The duration of the tone in milliseconds + + :param play_type: The behavior of ``tone`` once playback has been initiated + :type play_type: ``Sound.PLAY_WAIT_FOR_COMPLETE`` or ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` + + :return: When ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` is specified, returns the returns the spawn subprocess from ``subprocess.Popen``; ``None`` otherwise """ def play_tone_sequence(tone_sequence): def beep_args(frequency=None, duration=None, delay=None): @@ -155,31 +180,30 @@ def beep_args(frequency=None, duration=None, delay=None): return args - return self.beep(' -n '.join([beep_args(*t) for t in tone_sequence])) + return self.beep(' -n '.join([beep_args(*t) for t in tone_sequence]), play_type=play_type) if len(args) == 1: return play_tone_sequence(args[0]) elif len(args) == 2: return play_tone_sequence([(args[0], args[1])]) else: - raise Exception("Unsupported number of parameters in Sound.tone()") + raise Exception("Unsupported number of parameters in Sound.tone(): expected 1 or 2, got " + str(len(args))) def play_tone(self, frequency, duration, delay=0.0, volume=100, play_type=PLAY_WAIT_FOR_COMPLETE): """ Play a single tone, specified by its frequency, duration, volume and final delay. - Args: - frequency (int): the tone frequency, in Hertz - duration (float): tone duration, in seconds - delay (float): delay after tone, in seconds (can be useful when chaining calls to ``play_tone``) - volume (int): sound volume in percent (between 0 and 100) - play_type (int): one off Sound.PLAY_xxx play types (wait, no wait, loop) + :param int frequency: the tone frequency, in Hertz + :param float duration: Tone duration, in seconds + :param float delay: Delay after tone, in seconds (can be useful when chaining calls to ``play_tone``) + :param int volume: The play volume, in percent of maximum volume + + :param play_type: The behavior of ``play_tone`` once playback has been initiated + :type play_type: ``Sound.PLAY_WAIT_FOR_COMPLETE``, ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` or ``Sound.PLAY_LOOP`` - Returns: - the sound playing subprocess PID when no wait play type is selected, None otherwise + :return: When ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` is specified, returns the PID of the underlying beep command; ``None`` otherwise - Raises: - ValueError: if invalid value for parameter(s) + :raises ValueError: if invalid parameter """ self._validate_play_type(play_type) @@ -210,18 +234,16 @@ def play_tone(self, frequency, duration, delay=0.0, volume=100, def play_note(self, note, duration, volume=100, play_type=PLAY_WAIT_FOR_COMPLETE): """ Plays a note, given by its name as defined in ``_NOTE_FREQUENCIES``. - Args: - note (str) the note symbol with its octave number - duration (float): tone duration, in seconds - volume (int) the play volume, in percent of maximum volume - play_type (int) the type of play (wait, no wait, loop), as defined - by the ``PLAY_xxx`` constants + :param string note: The note symbol with its octave number + :param float duration: Tone duration, in seconds + :param int volume: The play volume, in percent of maximum volume - Returns: - the PID of the underlying beep command if no wait play type, None otherwise + :param play_type: The behavior of ``play_note`` once playback has been initiated + :type play_type: ``Sound.PLAY_WAIT_FOR_COMPLETE``, ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` or ``Sound.PLAY_LOOP`` - Raises: - ValueError: is invalid parameter (note, duration,...) + :return: When ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` is specified, returns the PID of the underlying beep command; ``None`` otherwise + + :raises ValueError: is invalid parameter (note, duration,...) """ self._validate_play_type(play_type) try: @@ -238,12 +260,12 @@ def play_note(self, note, duration, volume=100, play_type=PLAY_WAIT_FOR_COMPLETE def play(self, wav_file, play_type=PLAY_WAIT_FOR_COMPLETE): """ Play a sound file (wav format). - Args: - wav_file (str): the sound file path - play_type (int): one off Sound.PLAY_xxx play types (wait, no wait, loop) + :param string wav_file: The sound file path + + :param play_type: The behavior of ``play`` once playback has been initiated + :type play_type: ``Sound.PLAY_WAIT_FOR_COMPLETE``, ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` or ``Sound.PLAY_LOOP`` - Returns: - subprocess.Popen: the spawn subprocess when no wait play type is selected, None otherwise + :returns: When ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` is specified, returns the spawn subprocess from ``subprocess.Popen``; ``None`` otherwise """ self._validate_play_type(play_type) @@ -265,13 +287,14 @@ def play(self, wav_file, play_type=PLAY_WAIT_FOR_COMPLETE): def play_file(self, wav_file, volume=100, play_type=PLAY_WAIT_FOR_COMPLETE): """ Play a sound file (wav format) at a given volume. - Args: - wav_file (str): the sound file path - volume (int) the play volume, in percent of maximum volume - play_type (int): one off Sound.PLAY_xxx play types (wait, no wait, loop) + + :param string wav_file: The sound file path + :param int volume: The play volume, in percent of maximum volume - Returns: - subprocess.Popen: the spawn subprocess when no wait play type is selected, None otherwise + :param play_type: The behavior of ``play_file`` once playback has been initiated + :type play_type: ``Sound.PLAY_WAIT_FOR_COMPLETE``, ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` or ``Sound.PLAY_LOOP`` + + :returns: When ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` is specified, returns the spawn subprocess from ``subprocess.Popen``; ``None`` otherwise """ self.set_volume(volume) self.play(wav_file, play_type) @@ -281,14 +304,14 @@ def speak(self, text, espeak_opts='-a 200 -s 130', volume=100, play_type=PLAY_WA Uses the ``espeak`` external command. - Args: - text (str): the text to speak - espeak_opts (str): espeak command options - volume (int) the play volume, in percent of maximum volume - play_type (int): one off Sound.PLAY_xxx play types (wait, no wait, loop) + :param string text: The text to speak + :param string espeak_opts: ``espeak`` command options (advanced usage) + :param int volume: The play volume, in percent of maximum volume + + :param play_type: The behavior of ``speak`` once playback has been initiated + :type play_type: ``Sound.PLAY_WAIT_FOR_COMPLETE``, ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` or ``Sound.PLAY_LOOP`` - Returns: - subprocess.Popen: the spawn subprocess when no wait play type is selected, None otherwise + :returns: When ``Sound.PLAY_NO_WAIT_FOR_COMPLETE`` is specified, returns the spawn subprocess from ``subprocess.Popen``; ``None`` otherwise """ self._validate_play_type(play_type) self.set_volume(volume) @@ -314,8 +337,8 @@ def speak(self, text, espeak_opts='-a 200 -s 130', volume=100, play_type=PLAY_WA def _get_channel(self): """ - Returns: - str: the detected sound channel + :returns: The detected sound channel + :rtype: string """ if self.channel is None: # Get default channel as the first one that pops up in @@ -422,16 +445,13 @@ def play_song(self, song, tempo=120, delay=0.05): Only 4/4 signature songs are supported with respect to note durations. - Args: - song (iterable[tuple(str, str)]): the song - tempo (int): the song tempo, given in quarters per minute - delay (float): delay between notes (in seconds) + :param iterable[tuple(string, string)] song: the song + :param int tempo: the song tempo, given in quarters per minute + :param float delay: delay between notes (in seconds) - Returns: - subprocess.Popen: the spawn subprocess + :return: the spawn subprocess from ``subprocess.Popen`` - Raises: - ValueError: if invalid note in song or invalid play parameters + :raises ValueError: if invalid note in song or invalid play parameters """ if tempo <= 0: raise ValueError('invalid tempo (%s)' % tempo)