7 Module implementing the device interface class for CircuitPython boards. |
7 Module implementing the device interface class for CircuitPython boards. |
8 """ |
8 """ |
9 |
9 |
10 from __future__ import unicode_literals |
10 from __future__ import unicode_literals |
11 |
11 |
12 import os |
|
13 import ctypes |
|
14 from subprocess import check_output |
|
15 |
|
16 from E5Gui import E5MessageBox |
12 from E5Gui import E5MessageBox |
17 |
13 |
18 from .MicroPythonDevices import MicroPythonDevice |
14 from .MicroPythonDevices import MicroPythonDevice |
19 from .MicroPythonReplWidget import HAS_QTCHART |
15 from .MicroPythonReplWidget import HAS_QTCHART |
20 |
16 |
21 import Globals |
17 import Utilities |
22 |
18 |
23 |
19 |
24 class CircuitPythonDevice(MicroPythonDevice): |
20 class CircuitPythonDevice(MicroPythonDevice): |
25 """ |
21 """ |
26 Class implementing the device for CircuitPython boards. |
22 Class implementing the device for CircuitPython boards. |
33 @type MicroPythonReplWidget |
29 @type MicroPythonReplWidget |
34 @param parent reference to the parent object |
30 @param parent reference to the parent object |
35 @type QObject |
31 @type QObject |
36 """ |
32 """ |
37 super(CircuitPythonDevice, self).__init__(microPythonWidget, parent) |
33 super(CircuitPythonDevice, self).__init__(microPythonWidget, parent) |
38 |
|
39 self.__replActive = False |
|
40 self.__plotterActive = False |
|
41 |
34 |
42 def setButtons(self): |
35 def setButtons(self): |
43 """ |
36 """ |
44 Public method to enable the supported action buttons. |
37 Public method to enable the supported action buttons. |
45 """ |
38 """ |
46 super(CircuitPythonDevice, self).setButtons() |
39 super(CircuitPythonDevice, self).setButtons() |
47 self.microPython.setActionButtons(run=True, repl=True, chart=HAS_QTCHART) |
40 ## self.microPython.setActionButtons( |
|
41 ## run=True, repl=True, chart=HAS_QTCHART) |
|
42 # TODO: check, if this really works |
|
43 self.microPython.setActionButtons( |
|
44 run=True, repl=True, files=True, chart=HAS_QTCHART) |
48 |
45 |
49 workspace = self.getWorkspace() |
46 workspace = self.getWorkspace() |
50 if workspace.endswith("CIRCUITPY"): |
47 if workspace.endswith("CIRCUITPY"): |
51 self.microPython.setActionButtons(open=True, save=True) |
48 self.microPython.setActionButtons(open=True, save=True) |
52 |
49 |
68 and a reason why it cannot. |
65 and a reason why it cannot. |
69 @rtype tuple of (bool, str) |
66 @rtype tuple of (bool, str) |
70 """ |
67 """ |
71 return True, "" |
68 return True, "" |
72 |
69 |
73 def setRepl(self, on): |
|
74 """ |
|
75 Public method to set the REPL status and dependent status. |
|
76 |
|
77 @param on flag indicating the active status |
|
78 @type bool |
|
79 """ |
|
80 self.__replActive = on |
|
81 |
|
82 def canStartPlotter(self): |
70 def canStartPlotter(self): |
83 """ |
71 """ |
84 Public method to determine, if a Plotter can be started. |
72 Public method to determine, if a Plotter can be started. |
85 |
73 |
86 @return tuple containing a flag indicating it is safe to start a |
74 @return tuple containing a flag indicating it is safe to start a |
87 Plotter and a reason why it cannot. |
75 Plotter and a reason why it cannot. |
88 @rtype tuple of (bool, str) |
76 @rtype tuple of (bool, str) |
89 """ |
77 """ |
90 return True, "" |
78 return True, "" |
91 |
|
92 def setPlotter(self, on): |
|
93 """ |
|
94 Public method to set the Plotter status and dependent status. |
|
95 |
|
96 @param on flag indicating the active status |
|
97 @type bool |
|
98 """ |
|
99 self.__plotterActive = on |
|
100 |
79 |
101 def canRunScript(self): |
80 def canRunScript(self): |
102 """ |
81 """ |
103 Public method to determine, if a script can be executed. |
82 Public method to determine, if a script can be executed. |
104 |
83 |
116 @type str |
95 @type str |
117 """ |
96 """ |
118 pythonScript = script.split("\n") |
97 pythonScript = script.split("\n") |
119 self.sendCommands(pythonScript) |
98 self.sendCommands(pythonScript) |
120 |
99 |
|
100 # TODO: check, if this really works |
|
101 def canStartFileManager(self): |
|
102 """ |
|
103 Public method to determine, if a File Manager can be started. |
|
104 |
|
105 @return tuple containing a flag indicating it is safe to start a |
|
106 File Manager and a reason why it cannot. |
|
107 @rtype tuple of (bool, str) |
|
108 """ |
|
109 return True, "" |
|
110 |
121 def getWorkspace(self): |
111 def getWorkspace(self): |
122 """ |
112 """ |
123 Public method to get the workspace directory. |
113 Public method to get the workspace directory. |
124 |
114 |
125 @return workspace directory used for saving files |
115 @return workspace directory used for saving files |
126 @rtype str |
116 @rtype str |
127 """ |
117 """ |
128 deviceDirectory = None |
|
129 |
|
130 # Attempts to find the path on the filesystem that represents the |
118 # Attempts to find the path on the filesystem that represents the |
131 # plugged in CIRCUITPY board. |
119 # plugged in CIRCUITPY board. |
132 if Globals.isWindowsPlatform(): |
120 deviceDirectory = Utilities.findVolume("CIRCUITPY") |
133 # we are on a Windows platform |
|
134 def getVolumeName(diskName): |
|
135 """ |
|
136 Local function to determine the volume of a disk or device. |
|
137 |
|
138 Each disk or external device connected to windows has an |
|
139 attribute called "volume name". This function returns the |
|
140 volume name for the given disk/device. |
|
141 |
|
142 Code from http://stackoverflow.com/a/12056414 |
|
143 """ |
|
144 volumeNameBuffer = ctypes.create_unicode_buffer(1024) |
|
145 ctypes.windll.kernel32.GetVolumeInformationW( |
|
146 ctypes.c_wchar_p(diskName), volumeNameBuffer, |
|
147 ctypes.sizeof(volumeNameBuffer), None, None, None, None, 0) |
|
148 return volumeNameBuffer.value |
|
149 |
|
150 # |
|
151 # In certain circumstances, volumes are allocated to USB |
|
152 # storage devices which cause a Windows popup to raise if their |
|
153 # volume contains no media. Wrapping the check in SetErrorMode |
|
154 # with SEM_FAILCRITICALERRORS (1) prevents this popup. |
|
155 # |
|
156 oldMode = ctypes.windll.kernel32.SetErrorMode(1) |
|
157 try: |
|
158 for disk in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": |
|
159 path = "{0}:\\".format(disk) |
|
160 if (os.path.exists(path) and |
|
161 getVolumeName(path) == "CIRCUITPY"): |
|
162 deviceDirectory = path |
|
163 break |
|
164 finally: |
|
165 ctypes.windll.kernel32.SetErrorMode(oldMode) |
|
166 else: |
|
167 # we are on a Linux or macOS platform |
|
168 for mountCommand in ["mount", "/sbin/mount", "/usr/sbin/mount"]: |
|
169 try: |
|
170 mountOutput = check_output(mountCommand).splitlines() |
|
171 mountedVolumes = [x.split()[2] for x in mountOutput] |
|
172 for volume in mountedVolumes: |
|
173 if volume.endswith(b"CIRCUITPY"): |
|
174 deviceDirectory = volume.decode("utf-8") |
|
175 break |
|
176 if deviceDirectory: |
|
177 break |
|
178 except FileNotFoundError: |
|
179 pass |
|
180 |
121 |
181 if deviceDirectory: |
122 if deviceDirectory: |
182 return deviceDirectory |
123 return deviceDirectory |
183 else: |
124 else: |
184 # return the default workspace and give the user a warning |
125 # return the default workspace and give the user a warning |