Wed, 10 Jul 2019 20:21:57 +0200

Detlev Offenbach <>
Wed, 10 Jul 2019 20:21:57 +0200
changeset 7059
child 7065

Continued implementing the MicroPython support.

# -*- coding: utf-8 -*-

# Copyright (c) 2019 Detlev Offenbach <>

Module implementing the device interface class for CircuitPython boards.

from __future__ import unicode_literals

import os
import ctypes
from subprocess import check_output

from E5Gui import E5MessageBox

from .MicroPythonDevices import MicroPythonDevice
from .MicroPythonReplWidget import HAS_QTCHART

import Globals

class CircuitPythonDevice(MicroPythonDevice):
    Class implementing the device for CircuitPython boards.
    def __init__(self, microPythonWidget, parent=None):
        @param microPythonWidget reference to the main MicroPython widget
        @type MicroPythonReplWidget
        @param parent reference to the parent object
        @type QObject
        super(CircuitPythonDevice, self).__init__(microPythonWidget, parent)
        self.__replActive = False
        self.__plotterActive = False
    def setButtons(self):
        Public method to enable the supported action buttons.
        super(CircuitPythonDevice, self).setButtons()
        self.microPython.setActionButtons(repl=True, chart=HAS_QTCHART)
    def forceInterrupt(self):
        Public method to determine the need for an interrupt when opening the
        serial connection.
        @return flag indicating an interrupt is needed
        @rtype bool
        return False
    def canStartRepl(self):
        Public method to determine, if a REPL can be started.
        @return tuple containing a flag indicating it is safe to start a REPL
            and a reason why it cannot.
        @rtype tuple of (bool, str)
        return True, ""
    def setRepl(self, on):
        Public method to set the REPL status and dependent status.
        @param on flag indicating the active status
        @type bool
        self.__replActive = on
    def canStartPlotter(self):
        Public method to determine, if a Plotter can be started.
        @return tuple containing a flag indicating it is safe to start a
            Plotter and a reason why it cannot.
        @rtype tuple of (bool, str)
        return True, ""
    def setPlotter(self, on):
        Public method to set the Plotter status and dependent status.
        @param on flag indicating the active status
        @type bool
        self.__plotterActive = on
    def getWorkspace(self):
        Public method to get the workspace directory.
        @return workspace directory used for saving files
        @rtype str
        deviceDirectory = None
        # Attempts to find the path on the filesystem that represents the
        # plugged in CIRCUITPY board.
        if Globals.isWindowsPlatform():
            # we are on a Windows platform
            def getVolumeName(diskName):
                Local function to determine the volume of a disk or device.
                Each disk or external device connected to windows has an
                attribute called "volume name". This function returns the
                volume name for the given disk/device.

                Code from
                volumeNameBuffer = ctypes.create_unicode_buffer(1024)
                    ctypes.c_wchar_p(diskName), volumeNameBuffer,
                    ctypes.sizeof(volumeNameBuffer), None, None, None, None, 0)
                return volumeNameBuffer.value
            # In certain circumstances, volumes are allocated to USB
            # storage devices which cause a Windows popup to raise if their
            # volume contains no media. Wrapping the check in SetErrorMode
            # with SEM_FAILCRITICALERRORS (1) prevents this popup.
            oldMode = ctypes.windll.kernel32.SetErrorMode(1)
                for disk in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
                    path = '{}:\\'.format(disk)
                    if (os.path.exists(path) and
                            getVolumeName(path) == 'CIRCUITPY'):
                        return path
            # we are on a Linux or macOS platform
            for mountCommand in ['mount', '/sbin/mount']:
                    mountOutput = check_output(mountCommand).splitlines()
                    mountedVolumes = [x.split()[2] for x in mountOutput]
                    for volume in mountedVolumes:
                        if volume.endswith(b'CIRCUITPY'):
                            deviceDirectory = volume.decode('utf-8')
                except FileNotFoundError:
        if deviceDirectory:
            return deviceDirectory
            # return the default workspace and give the user a warning
      "Workspace Directory"),
      "Python files for CircuitPython devices are stored on"
                        " the device. Therefore, to edit these files you need"
                        " to have the device plugged in. Until you plug in a"
                        " device, the standard directory will be used."))
            return super(CircuitPythonDevice, self).getWorkspace()

eric ide