32 <li>put: copy a file to the connected device</li> |
33 <li>put: copy a file to the connected device</li> |
33 <li>get: get a file from the connected device</li> |
34 <li>get: get a file from the connected device</li> |
34 <li>rm: remove a file from the connected device</li> |
35 <li>rm: remove a file from the connected device</li> |
35 <li>mkdir: create a new directory</li> |
36 <li>mkdir: create a new directory</li> |
36 <li>rmdir: remove an empty directory</li> |
37 <li>rmdir: remove an empty directory</li> |
|
38 <li>version: get version info about MicroPython</li> |
37 </ul> |
39 </ul> |
38 """ |
40 """ |
39 def __init__(self, parent=None): |
41 def __init__(self, parent=None): |
40 """ |
42 """ |
41 Constructor |
43 Constructor |
313 @type str |
315 @type str |
314 @return flag indicating success |
316 @return flag indicating success |
315 @rtype bool |
317 @rtype bool |
316 @exception IOError raised to indicate an issue with the device |
318 @exception IOError raised to indicate an issue with the device |
317 """ |
319 """ |
318 # TODO: not implemented yet |
320 if not os.path.isfile(hostFileName): |
319 |
321 raise IOError("No such file: {0}".format(hostFileName)) |
320 def get(self, deviceFileName, hostFileName): |
322 |
|
323 with open(hostFileName, "rb") as hostFile: |
|
324 content = hostFile.read() |
|
325 |
|
326 if not deviceFileName: |
|
327 deviceFileName = os.path.basename(hostFileName) |
|
328 |
|
329 commands = [ |
|
330 "fd = open('{0}', 'wb')".format(deviceFileName), |
|
331 "f = fd.write", |
|
332 ] |
|
333 while content: |
|
334 chunk = content[:64] |
|
335 commands.append("f(" + repr(chunk) + ")") |
|
336 content = content[64:] |
|
337 commands.append("fd.close()") |
|
338 |
|
339 out, err = self.__execute(commands) |
|
340 if err: |
|
341 raise IOError(self.__shortError(err)) |
|
342 return True |
|
343 |
|
344 def get(self, deviceFileName, hostFileName=None): |
321 """ |
345 """ |
322 Public method to copy a file from the connected device. |
346 Public method to copy a file from the connected device. |
323 |
347 |
324 @param deviceFileName name of the file to copy |
348 @param deviceFileName name of the file to copy |
325 @type str |
349 @type str |
327 @type str |
351 @type str |
328 @return flag indicating success |
352 @return flag indicating success |
329 @rtype bool |
353 @rtype bool |
330 @exception IOError raised to indicate an issue with the device |
354 @exception IOError raised to indicate an issue with the device |
331 """ |
355 """ |
332 # TODO: not implemented yet |
356 if not hostFileName: |
|
357 hostFileName = deviceFileName |
|
358 |
|
359 commands = [ |
|
360 "\n".join([ |
|
361 "try:", |
|
362 " from microbit import uart as u", |
|
363 "except ImportError:", |
|
364 " try:", |
|
365 " from machine import UART", |
|
366 " u = UART(0, {0})".format(115200), |
|
367 " except Exception:", |
|
368 " try:", |
|
369 " from sys import stdout as u", |
|
370 " except Exception:", |
|
371 " raise Exception('Could not find UART module in" |
|
372 " device.')", |
|
373 ]), |
|
374 "f = open('{0}', 'rb')".format(deviceFileName), |
|
375 "r = f.read", |
|
376 "result = True", |
|
377 "\n".join([ |
|
378 "while result:", |
|
379 " result = r(32)" |
|
380 " if result:", |
|
381 " u.write(result)", |
|
382 ]), |
|
383 "f.close()", |
|
384 ] |
|
385 out, err = self.__execute(commands) |
|
386 if err: |
|
387 raise IOError(self.__shortError(err)) |
|
388 |
|
389 # write the received bytes to the local file |
|
390 with open(hostFileName, "wb") as hostFile: |
|
391 hostFile.write(out) |
|
392 return True |
|
393 |
|
394 # TODO: add rsync function |
|
395 |
|
396 def version(self): |
|
397 """ |
|
398 Public method to get the MicroPython version information of the |
|
399 connected device. |
|
400 |
|
401 @return dictionary containing the version information |
|
402 @rtype dict |
|
403 @exception ValueError raised to indicate that the device might not be |
|
404 running MicroPython or there was an issue parsing the output |
|
405 """ |
|
406 commands = [ |
|
407 "import os", |
|
408 "print(os.uname())", |
|
409 ] |
|
410 try: |
|
411 out, err = self.__execute(commands) |
|
412 if err: |
|
413 raise ValueError(self.__shortError(err)) |
|
414 except ValueError: |
|
415 # just re-raise it |
|
416 raise |
|
417 except Exception: |
|
418 # Raise a value error to indicate being unable to find something |
|
419 # on the device that will return parseable information about the |
|
420 # version. It doesn't matter what the error is, it just needs to |
|
421 # report a failure with the expected ValueError exception. |
|
422 raise ValueError("Unable to determine version information.") |
|
423 |
|
424 rawOutput = out.decode("utf-8").strip() |
|
425 rawOutput = rawOutput[1:-1] |
|
426 items = rawOutput.split(",") |
|
427 result = {} |
|
428 for item in items: |
|
429 key, value = item.strip().split("=") |
|
430 result[key.strip()] = value.strip()[1:-1] |
|
431 return result |
|
432 |
|
433 def syncTime(self): |
|
434 """ |
|
435 Public method to set the time of the connected device to the local |
|
436 computer's time. |
|
437 |
|
438 @exception IOError raised to indicate an issue with the device |
|
439 """ |
|
440 now = time.localtime(time.time()) |
|
441 commands = [ |
|
442 "\n".join([ |
|
443 "def set_time(rtc_time):", |
|
444 " rtc = None", |
|
445 " try:", # Pyboard (it doesn't have machine.RTC()) |
|
446 " import pyb", |
|
447 " rtc = pyb.RTC()", |
|
448 " rtc.datetime(rtc_time)", |
|
449 " except:", |
|
450 " try:", |
|
451 " import machine", |
|
452 " rtc = machine.RTC()", |
|
453 " try:", # ESP8266 uses rtc.datetime() |
|
454 " rtc.datetime(rtc_time)", |
|
455 " except:", # ESP32 uses rtc.init() |
|
456 " rtc.init(rtc_time)", |
|
457 " except:", |
|
458 " pass", |
|
459 ]), |
|
460 "set_time({0})".format((now.tm_year, now.tm_mon, now.tm_mday, |
|
461 now.tm_wday + 1, now.tm_hour, now.tm_min, |
|
462 now.tm_sec, 0)) |
|
463 ] |
|
464 out, err = self.__execute(commands) |
|
465 if err: |
|
466 raise IOError(self.__shortError(err)) |
333 |
467 |
334 |
468 |
335 class MicroPythonFileManager(QObject): |
469 class MicroPythonFileManager(QObject): |
336 """ |
470 """ |
337 Class implementing an interface to the device file system commands with |
471 Class implementing an interface to the device file system commands with |
339 |
473 |
340 @signal longListFiles(result) emitted with a tuple of tuples containing the |
474 @signal longListFiles(result) emitted with a tuple of tuples containing the |
341 name, mode, size and time for each directory entry |
475 name, mode, size and time for each directory entry |
342 @signal currentDir(dirname) emitted to report the current directory of the |
476 @signal currentDir(dirname) emitted to report the current directory of the |
343 device |
477 device |
|
478 @signal getFileDone(deviceFile, localFile) emitted after the file was |
|
479 fetched from the connected device and written to the local file system |
|
480 @signal putFileDone(localFile, deviceFile) emitted after the file was |
|
481 copied to the connected device |
|
482 @signal deleteFileDone(deviceFile) emitted after the file has been deleted |
|
483 on the connected device |
344 |
484 |
345 @signal longListFilesFailed(exc) emitted with a failure message to indicate |
485 @signal longListFilesFailed(exc) emitted with a failure message to indicate |
346 a failed long listing operation |
486 a failed long listing operation |
347 @signal currentDirFailed(exc) emitted with a failure message to indicate |
487 @signal currentDirFailed(exc) emitted with a failure message to indicate |
348 that the current directory is not available |
488 that the current directory is not available |
|
489 @signal getFileFailed(exc) emitted with a failure message to indicate that |
|
490 the file could not be fetched |
|
491 @signal putFileFailed(exc) emitted with a failure message to indicate that |
|
492 the file could not be copied |
|
493 @signal deleteFileFailed(exc) emitted with a failure message to indicate |
|
494 that the file could not be deleted on the device |
349 """ |
495 """ |
350 longListFiles = pyqtSignal(tuple) |
496 longListFiles = pyqtSignal(tuple) |
351 currentDir = pyqtSignal(str) |
497 currentDir = pyqtSignal(str) |
|
498 getFileDone = pyqtSignal(str, str) |
|
499 putFileDone = pyqtSignal(str, str) |
|
500 deleteFileDone = pyqtSignal(str) |
352 |
501 |
353 longListFilesFailed = pyqtSignal(str) |
502 longListFilesFailed = pyqtSignal(str) |
354 currentDirFailed = pyqtSignal(str) |
503 currentDirFailed = pyqtSignal(str) |
|
504 getFileFailed = pyqtSignal(str) |
|
505 putFileFailed = pyqtSignal(str) |
|
506 deleteFileFailed = pyqtSignal(str) |
355 |
507 |
356 def __init__(self, port, parent=None): |
508 def __init__(self, port, parent=None): |
357 """ |
509 """ |
358 Constructor |
510 Constructor |
359 |
511 |
403 self.longListFilesFailed.emit(str(exc)) |
555 self.longListFilesFailed.emit(str(exc)) |
404 |
556 |
405 @pyqtSlot() |
557 @pyqtSlot() |
406 def pwd(self): |
558 def pwd(self): |
407 """ |
559 """ |
408 Public method to get the current directory of the device. |
560 Public slot to get the current directory of the device. |
409 """ |
561 """ |
410 try: |
562 try: |
411 pwd = self.__fs.pwd() |
563 pwd = self.__fs.pwd() |
412 self.currentDir.emit(pwd) |
564 self.currentDir.emit(pwd) |
413 except Exception as exc: |
565 except Exception as exc: |
414 self.currentDirFailed.emit(str(exc)) |
566 self.currentDirFailed.emit(str(exc)) |
|
567 |
|
568 @pyqtSlot(str) |
|
569 @pyqtSlot(str, str) |
|
570 def get(self, deviceFileName, hostFileName=""): |
|
571 """ |
|
572 Public slot to get a file from the connected device. |
|
573 |
|
574 @param deviceFileName name of the file on the device |
|
575 @type str |
|
576 @param hostFileName name of the local file |
|
577 @type str |
|
578 """ |
|
579 if hostFileName and os.path.isdir(hostFileName): |
|
580 # only a local directory was given |
|
581 hostFileName = os.path.join(hostFileName, |
|
582 os.path.basename(deviceFileName)) |
|
583 try: |
|
584 self.__fs.get(deviceFileName, hostFileName) |
|
585 self.getFileDone.emit(deviceFileName, hostFileName) |
|
586 except Exception as exc: |
|
587 self.getFileFailed.emit(str(exc)) |
|
588 |
|
589 @pyqtSlot(str) |
|
590 @pyqtSlot(str, str) |
|
591 def put(self, hostFileName, deviceFileName=""): |
|
592 """ |
|
593 Public slot to put a file onto the device. |
|
594 |
|
595 @param hostFileName name of the local file |
|
596 @type str |
|
597 @param deviceFileName name of the file on the connected device |
|
598 @type str |
|
599 """ |
|
600 try: |
|
601 self.__fs.put(hostFileName, deviceFileName) |
|
602 self.putFileDone(hostFileName, deviceFileName) |
|
603 except Exception as exc: |
|
604 self.putFileFailed(str(exc)) |
|
605 |
|
606 @pyqtSlot(str) |
|
607 def delete(self, deviceFileName): |
|
608 """ |
|
609 Public slot to delete a file on the device. |
|
610 |
|
611 @param deviceFileName name of the file on the connected device |
|
612 @type str |
|
613 """ |
|
614 try: |
|
615 self.__fs.rm(deviceFileName) |
|
616 self.deleteFileDone.emit(deviceFileName) |
|
617 except Exception as exc: |
|
618 self.deleteFileFailed(str(exc)) |
415 |
619 |
416 ################################################################## |
620 ################################################################## |
417 ## Utility methods below |
621 ## Utility methods below |
418 ################################################################## |
622 ################################################################## |
419 |
623 |
440 @rtype str |
644 @rtype str |
441 """ |
645 """ |
442 return stat.filemode(mode) |
646 return stat.filemode(mode) |
443 |
647 |
444 |
648 |
445 def decoratedName(name, mode): |
649 def decoratedName(name, mode, isDir=False): |
446 """ |
650 """ |
447 Function to decorate the given name according to the given mode. |
651 Function to decorate the given name according to the given mode. |
448 |
652 |
449 @param name file or directory name |
653 @param name file or directory name |
450 @type str |
654 @type str |
451 @param mode mode value |
655 @param mode mode value |
452 @type int |
656 @type int |
|
657 @param isDir flag indicating that name is a directory |
|
658 @type bool |
453 @return decorated file or directory name |
659 @return decorated file or directory name |
454 @rtype str |
660 @rtype str |
455 """ |
661 """ |
456 if stat.S_ISDIR(mode): |
662 if stat.S_ISDIR(mode) or isDir: |
457 # append a '/' for directories |
663 # append a '/' for directories |
458 return name + "/" |
664 return name + "/" |
459 else: |
665 else: |
460 # no change |
666 # no change |
461 return name |
667 return name |