diff -r bdd583f96e96 -r a8fad276cbd5 eric6/MicroPython/CircuitPythonDevices.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/CircuitPythonDevices.py Wed Jul 10 20:21:57 2019 +0200 @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +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): + """ + Constructor + + @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 http://stackoverflow.com/a/12056414 + """ + volumeNameBuffer = ctypes.create_unicode_buffer(1024) + ctypes.windll.kernel32.GetVolumeInformationW( + 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) + try: + for disk in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': + path = '{}:\\'.format(disk) + if (os.path.exists(path) and + getVolumeName(path) == 'CIRCUITPY'): + return path + finally: + ctypes.windll.kernel32.SetErrorMode(oldMode) + else: + # we are on a Linux or macOS platform + for mountCommand in ['mount', '/sbin/mount']: + try: + 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: + next + + if deviceDirectory: + return deviceDirectory + else: + # return the default workspace and give the user a warning + E5MessageBox.warning( + self.microPythonWidget, + self.tr("Workspace Directory"), + self.tr("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()