adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.1k stars 1.22k forks source link

SAMD21 8.0.0-rc2 crashes when saving file with Thonny #7544

Open fovea1959 opened 1 year ago

fovea1959 commented 1 year ago

CircuitPython version

Adafruit CircuitPython 8.0.0-rc.2 on 2023-02-02; Adafruit NeoPixel Trinkey M0 with samd21e18

Code/REPL

# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
Blink example for boards with ONLY a NeoPixel LED (e.g. without a built-in red LED).
Includes QT Py and various Trinkeys.

Requires two libraries from the Adafruit CircuitPython Library Bundle.
Download the bundle from circuitpython.org/libraries and copy the
following files to your CIRCUITPY/lib folder:
* neopixel.mpy
* adafruit_pixelbuf.mpy

Once the libraries are copied, save this file as code.py to your CIRCUITPY
drive to run it.
"""
import time
import board
import neopixel
import usb_hid
import rainbowio
import math

import touchio

from hid_gamepad import Gamepad

def dim(color, divisor=16):
    color_tuple = color
    if type(color) is int:
        r = (color >> 16) & 0xff
        g = (color >> 8) & 0xff
        b = color & 0xff
        color_tuple = (r, g, b)
    rv = tuple(int(i/divisor) for i in color_tuple)
    return rv

gp = Gamepad(usb_hid.devices)

pixels = neopixel.NeoPixel(board.NEOPIXEL, 4)

b0 = touchio.TouchIn(board.TOUCH1)
b1 = touchio.TouchIn(board.TOUCH2)

def p(i, t, color):
    v = t.value
    if v:
        gp.press_buttons(i+1)
    else:
        gp.release_buttons(i+1)
    # print(i, t.threshold, t.raw_value)
    pixels[i+2] = color if v else 0x000000

gp.reset_all()

color_wheel_index = 0
b3 = True

with b0, b1:
    while True:
        t0 = time.monotonic()
        while (time.monotonic() - t0 < 0.5):
            pixels[0] = dim(rainbowio.colorwheel(color_wheel_index))
            pixels[1] = dim(rainbowio.colorwheel(color_wheel_index+64))
            p(0, b0, dim(rainbowio.colorwheel(color_wheel_index+128)))
            p(1, b1, dim(rainbowio.colorwheel(color_wheel_index+192)))

            jx = min (color_wheel_index - 127, 127)
            jy = math.sin (color_wheel_index * 2 * 3.14159 / 256)
            gp.move_joysticks(x=jx, y=int(jy*127))

            color_wheel_index = (color_wheel_index + 1) % 256

            time.sleep(0.01)

        # do half second stuff here
        if b3:
            gp.press_buttons(3)
            gp.release_buttons(4)
        else:
            gp.release_buttons(3)
            gp.press_buttons(4)
        b3 = not b3

Behavior

open code.py file from NeoTrinket. when I save file from Thonny, it throws a:

PROBLEM IN THONNY'S BACK-END: Exception while handling 'write_file' (ConnectionError: EOF).
See Thonny's backend.log for more info.
You may need to press "Stop/Restart" or hard-reset your CircuitPython device and try again.

Process ended with exit code 1.

Thonny log ends with:

11:40:58.747 ERROR   thonny.backend: Exception while handling 'write_file'
Traceback (most recent call last):
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\backend.py", line 282, in _handle_normal_command
    response = handler(cmd)
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\bare_metal_backend.py", line 1094, in _cmd_write_file
    return super(BareMetalMicroPythonBackend, self)._cmd_write_file(cmd)
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\backend.py", line 568, in _cmd_write_file
    self._write_file(
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\bare_metal_backend.py", line 1267, in _write_file
    self._write_file_via_serial(source_fp, target_path, file_size, callback)
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\bare_metal_backend.py", line 1313, in _write_file_via_serial
    out, err = self._execute(
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\mp_back.py", line 525, in _execute
    self._execute_with_consumer(script, consume_output)
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\bare_metal_backend.py", line 703, in _execute_with_consumer
    self._process_output_until_active_prompt(output_consumer)
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\bare_metal_backend.py", line 855, in _process_output_until_active_prompt
    new_data = self._connection.soft_read_until(
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\connection.py", line 67, in soft_read_until
    return self.read_until(terminator, timeout, timeout_is_soft=True)
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\connection.py", line 83, in read_until
    self.check_for_error()
  File "C:\Users\dwegs\AppData\Local\Programs\Thonny\lib\site-packages\thonny\plugins\micropython\connection.py", line 135, in check_for_error
    raise ConnectionError(self._error)
ConnectionError: EOF

CircuitPython is throwing this to the console:

Auto-reload is off.
Running in safe mode! Not running saved code.

You are in safe mode because:
CircuitPython core code crashed hard. Whoops!
Crash into the HardFault_Handler.
Please file an issue with the contents of your CIRCUITPY drive at
https://github.com/adafruit/circuitpython/issues

Press any key to enter the REPL. Use CTRL-D to reload.

After this, the CircuitPython just does 3 yellow flashes over and over until reset.

Description

No response

Additional information

Problem does not exist with 7.3.3.

boot.py:

import usb_hid, usb_midi

usb_midi.disable()

# This is only one example of a gamepad descriptor, and may not suit your needs.
GAMEPAD_REPORT_DESCRIPTOR = bytes((
    0x05, 0x01,  # Usage Page (Generic Desktop Ctrls)
    0x09, 0x05,  # Usage (Game Pad)
    0xA1, 0x01,  # Collection (Application)
    #
    0x85, 0x04,  #   Report ID (4)
    0x05, 0x09,  #   Usage Page (Button)
    0x19, 0x01,  #   Usage Minimum (Button 1)
    0x29, 0x10,  #   Usage Maximum (Button 16)
    0x15, 0x00,  #   Logical Minimum (0)
    0x25, 0x01,  #   Logical Maximum (1)
    0x75, 0x01,  #   Report Size (1)
    0x95, 0x10,  #   Report Count (16)
    0x81, 0x02,  #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    #
    0x05, 0x01,  #   Usage Page (Generic Desktop Ctrls)
    0x15, 0x81,  #   Logical Minimum (-127)
    0x25, 0x7F,  #   Logical Maximum (127)
    0x09, 0x30,  #   Usage (X)
    0x09, 0x31,  #   Usage (Y)
    0x09, 0x32,  #   Usage (Z)
    0x09, 0x35,  #   Usage (Rz)
    0x75, 0x08,  #   Report Size (8)
    0x95, 0x04,  #   Report Count (4)
    0x81, 0x02,  #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    #
    0xC0,        # End Collection
))

gamepad = usb_hid.Device(
    report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
    usage_page=0x01,           # Generic Desktop Control
    usage=0x05,                # Gamepad
    report_ids=(4,),           # Descriptor uses report ID 4.
    in_report_lengths=(6,),    # This gamepad sends 6 bytes in its report.
    out_report_lengths=(0,),   # It receives a 1 byte report.
)

usb_hid.enable((
    #usb_hid.Device.KEYBOARD,
    usb_hid.Device.MOUSE,
    #usb_hid.Device.CONSUMER_CONTROL,
    gamepad,
))

hid.gamepad.py:

# SPDX-FileCopyrightText: 2018 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
`Gamepad`
====================================================

* Author(s): Dan Halbert
"""

import struct
import time

from adafruit_hid import find_device

class Gamepad:
    """Emulate a generic gamepad controller with 16 buttons,
    numbered 1-16, and two joysticks, one controlling
    ``x` and ``y`` values, and the other controlling ``z`` and
    ``r_z`` (z rotation or ``Rz``) values.

    The joystick values could be interpreted
    differently by the receiving program: those are just the names used here.
    The joystick values are in the range -127 to 127."""

    def __init__(self, devices):
        """Create a Gamepad object that will send USB gamepad HID reports.

        Devices can be a list of devices that includes a gamepad device or a gamepad device
        itself. A device is any object that implements ``send_report()``, ``usage_page`` and
        ``usage``.
        """
        self._gamepad_device = find_device(devices, usage_page=0x1, usage=0x05)

        # Reuse this bytearray to send mouse reports.
        # Typically controllers start numbering buttons at 1 rather than 0.
        # report[0] buttons 1-8 (LSB is button 1)
        # report[1] buttons 9-16
        # report[2] joystick 0 x: -127 to 127
        # report[3] joystick 0 y: -127 to 127
        # report[4] joystick 1 x: -127 to 127
        # report[5] joystick 1 y: -127 to 127
        self._report = bytearray(6)

        # Remember the last report as well, so we can avoid sending
        # duplicate reports.
        self._last_report = bytearray(6)

        # Store settings separately before putting into report. Saves code
        # especially for buttons.
        self._buttons_state = 0
        self._joy_x = 0
        self._joy_y = 0
        self._joy_z = 0
        self._joy_r_z = 0

        # Send an initial report to test if HID device is ready.
        # If not, wait a bit and try once more.
        try:
            self.reset_all()
        except OSError:
            time.sleep(1)
            self.reset_all()

    def press_buttons(self, *buttons):
        """Press and hold the given buttons."""
        for button in buttons:
            self._buttons_state |= 1 << self._validate_button_number(button) - 1
        self._send()

    def release_buttons(self, *buttons):
        """Release the given buttons."""
        for button in buttons:
            self._buttons_state &= ~(1 << self._validate_button_number(button) - 1)
        self._send()

    def release_all_buttons(self):
        """Release all the buttons."""

        self._buttons_state = 0
        self._send()

    def click_buttons(self, *buttons):
        """Press and release the given buttons."""
        self.press_buttons(*buttons)
        self.release_buttons(*buttons)

    def move_joysticks(self, x=None, y=None, z=None, r_z=None):
        """Set and send the given joystick values.
        The joysticks will remain set with the given values until changed

        One joystick provides ``x`` and ``y`` values,
        and the other provides ``z`` and ``r_z`` (z rotation).
        Any values left as ``None`` will not be changed.

        All values must be in the range -127 to 127 inclusive.

        Examples::

            # Change x and y values only.
            gp.move_joysticks(x=100, y=-50)

            # Reset all joystick values to center position.
            gp.move_joysticks(0, 0, 0, 0)
        """
        if x is not None:
            self._joy_x = self._validate_joystick_value(x)
        if y is not None:
            self._joy_y = self._validate_joystick_value(y)
        if z is not None:
            self._joy_z = self._validate_joystick_value(z)
        if r_z is not None:
            self._joy_r_z = self._validate_joystick_value(r_z)
        self._send()

    def reset_all(self):
        """Release all buttons and set joysticks to zero."""
        self._buttons_state = 0
        self._joy_x = 0
        self._joy_y = 0
        self._joy_z = 0
        self._joy_r_z = 0
        self._send(always=True)

    def _send(self, always=False):
        """Send a report with all the existing settings.
        If ``always`` is ``False`` (the default), send only if there have been changes.
        """
        struct.pack_into(
            "<Hbbbb",
            self._report,
            0,
            self._buttons_state,
            self._joy_x,
            self._joy_y,
            self._joy_z,
            self._joy_r_z,
        )

        if always or self._last_report != self._report:
            self._gamepad_device.send_report(self._report)
            # Remember what we sent, without allocating new storage.
            self._last_report[:] = self._report

    @staticmethod
    def _validate_button_number(button):
        if not 1 <= button <= 16:
            raise ValueError("Button number must in range 1 to 16")
        return button

    @staticmethod
    def _validate_joystick_value(value):
        if not -127 <= value <= 127:
            raise ValueError("Joystick value must be in range -127 to 127")
        return value
dhalbert commented 1 year ago

Thonny writes files by sending invisible Python statements to execute to the REPL. We shouldn't hard-crash, but given the size of the code files, I can see that we might run out of RAM in this case. However it should not hard-fault.

You can work around this for now by just using another editor or copying the files directly (as you may already know).