|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the device interface class for CircuitPython boards. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import os |
|
13 import ctypes |
|
14 from subprocess import check_output |
|
15 |
|
16 from E5Gui import E5MessageBox |
|
17 |
|
18 from .MicroPythonDevices import MicroPythonDevice |
|
19 from .MicroPythonReplWidget import HAS_QTCHART |
|
20 |
|
21 import Globals |
|
22 |
|
23 |
|
24 class CircuitPythonDevice(MicroPythonDevice): |
|
25 """ |
|
26 Class implementing the device for CircuitPython boards. |
|
27 """ |
|
28 def __init__(self, microPythonWidget, parent=None): |
|
29 """ |
|
30 Constructor |
|
31 |
|
32 @param microPythonWidget reference to the main MicroPython widget |
|
33 @type MicroPythonReplWidget |
|
34 @param parent reference to the parent object |
|
35 @type QObject |
|
36 """ |
|
37 super(CircuitPythonDevice, self).__init__(microPythonWidget, parent) |
|
38 |
|
39 self.__replActive = False |
|
40 self.__plotterActive = False |
|
41 |
|
42 def setButtons(self): |
|
43 """ |
|
44 Public method to enable the supported action buttons. |
|
45 """ |
|
46 super(CircuitPythonDevice, self).setButtons() |
|
47 self.microPython.setActionButtons(repl=True, chart=HAS_QTCHART) |
|
48 |
|
49 def forceInterrupt(self): |
|
50 """ |
|
51 Public method to determine the need for an interrupt when opening the |
|
52 serial connection. |
|
53 |
|
54 @return flag indicating an interrupt is needed |
|
55 @rtype bool |
|
56 """ |
|
57 return False |
|
58 |
|
59 def canStartRepl(self): |
|
60 """ |
|
61 Public method to determine, if a REPL can be started. |
|
62 |
|
63 @return tuple containing a flag indicating it is safe to start a REPL |
|
64 and a reason why it cannot. |
|
65 @rtype tuple of (bool, str) |
|
66 """ |
|
67 return True, "" |
|
68 |
|
69 def setRepl(self, on): |
|
70 """ |
|
71 Public method to set the REPL status and dependent status. |
|
72 |
|
73 @param on flag indicating the active status |
|
74 @type bool |
|
75 """ |
|
76 self.__replActive = on |
|
77 |
|
78 def canStartPlotter(self): |
|
79 """ |
|
80 Public method to determine, if a Plotter can be started. |
|
81 |
|
82 @return tuple containing a flag indicating it is safe to start a |
|
83 Plotter and a reason why it cannot. |
|
84 @rtype tuple of (bool, str) |
|
85 """ |
|
86 return True, "" |
|
87 |
|
88 def setPlotter(self, on): |
|
89 """ |
|
90 Public method to set the Plotter status and dependent status. |
|
91 |
|
92 @param on flag indicating the active status |
|
93 @type bool |
|
94 """ |
|
95 self.__plotterActive = on |
|
96 |
|
97 def getWorkspace(self): |
|
98 """ |
|
99 Public method to get the workspace directory. |
|
100 |
|
101 @return workspace directory used for saving files |
|
102 @rtype str |
|
103 """ |
|
104 deviceDirectory = None |
|
105 |
|
106 # Attempts to find the path on the filesystem that represents the |
|
107 # plugged in CIRCUITPY board. |
|
108 if Globals.isWindowsPlatform(): |
|
109 # we are on a Windows platform |
|
110 def getVolumeName(diskName): |
|
111 """ |
|
112 Local function to determine the volume of a disk or device. |
|
113 |
|
114 Each disk or external device connected to windows has an |
|
115 attribute called "volume name". This function returns the |
|
116 volume name for the given disk/device. |
|
117 |
|
118 Code from http://stackoverflow.com/a/12056414 |
|
119 """ |
|
120 volumeNameBuffer = ctypes.create_unicode_buffer(1024) |
|
121 ctypes.windll.kernel32.GetVolumeInformationW( |
|
122 ctypes.c_wchar_p(diskName), volumeNameBuffer, |
|
123 ctypes.sizeof(volumeNameBuffer), None, None, None, None, 0) |
|
124 return volumeNameBuffer.value |
|
125 |
|
126 # |
|
127 # In certain circumstances, volumes are allocated to USB |
|
128 # storage devices which cause a Windows popup to raise if their |
|
129 # volume contains no media. Wrapping the check in SetErrorMode |
|
130 # with SEM_FAILCRITICALERRORS (1) prevents this popup. |
|
131 # |
|
132 oldMode = ctypes.windll.kernel32.SetErrorMode(1) |
|
133 try: |
|
134 for disk in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': |
|
135 path = '{}:\\'.format(disk) |
|
136 if (os.path.exists(path) and |
|
137 getVolumeName(path) == 'CIRCUITPY'): |
|
138 return path |
|
139 finally: |
|
140 ctypes.windll.kernel32.SetErrorMode(oldMode) |
|
141 else: |
|
142 # we are on a Linux or macOS platform |
|
143 for mountCommand in ['mount', '/sbin/mount']: |
|
144 try: |
|
145 mountOutput = check_output(mountCommand).splitlines() |
|
146 mountedVolumes = [x.split()[2] for x in mountOutput] |
|
147 for volume in mountedVolumes: |
|
148 if volume.endswith(b'CIRCUITPY'): |
|
149 deviceDirectory = volume.decode('utf-8') |
|
150 except FileNotFoundError: |
|
151 next |
|
152 |
|
153 if deviceDirectory: |
|
154 return deviceDirectory |
|
155 else: |
|
156 # return the default workspace and give the user a warning |
|
157 E5MessageBox.warning( |
|
158 self.microPythonWidget, |
|
159 self.tr("Workspace Directory"), |
|
160 self.tr("Python files for CircuitPython devices are stored on" |
|
161 " the device. Therefore, to edit these files you need" |
|
162 " to have the device plugged in. Until you plug in a" |
|
163 " device, the standard directory will be used.")) |
|
164 |
|
165 return super(CircuitPythonDevice, self).getWorkspace() |