Освой MicroPython играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

M5Stack: Файловая система (MicroPython)

Примеры с файлами проводились на устройстве M5Stack Fire.

Общую информацию о диске можно узнать в примере о модулях os и uos.

В основном все мои исследования связаны с доступом к карточке SD. Хотя в документации говорится, что устройство поддерживает карточку не более 16 Гб, у меня прекрасно работало с карточкой объёма 32 Гб. Но сможет ли устройство прочитать файл большого размера или работать с полностью забитым диском остаётся под вопросом.

Для работы с файловой системой нужно импортировать классы os и иногда uos.

Получаем список файлов с SD Card и флеш-памяти

Доступ к карточке памяти. Иногда работает криво, бывает при первом включении данные с диска не считываются, а потом всё нормально (при работе с редактором Му).

Получим список файлов и папок на карточке и выводим на экран их имена.


from m5stack import *
from m5ui import *
from uiflow import *
import uos

lcd.clear()
files = uos.listdir('/sd')
lcd.setCursor(0, 0)
for element in files:
  lcd.print(element + '\n')

Аналогично можно получить список файлов флеш-памяти (/flash) или содержимое в её подпапках. Получим список файлов с подпапки /flash/res.


files = uos.listdir('/flash/res/')

В предыдущем примере файлы и папки свалены в одну кучу. Можно разделить папки и файлы отдельно.


import os

FILE_CODE = 0x8000

def isfile(path):
    return os.stat(path)[0] == FILE_CODE


def main():
    files = [i for i in sorted(os.listdir()) if isfile(i)]
    print('files:', files)
    dirs = [i for i in sorted(os.listdir()) if not isfile(i)]
    print('dirs:', dirs)

main()

# Результат на M5Stack
# files: ['boot.py', 'main.py', 'temp.py', 'test.py']
# dirs: ['apps', 'blocks', 'emojiImg', 'img', 'res']

Размер файла

Размер файла можно узнать через седьмое значение кортежа.


# файл с флеш-памяти
lcd.print(str(uos.stat('/flash/main.py')[6]))

# файл с SD-карты
lcd.print(str(uos.stat('/sd/cats.txt')[6]))

Все опыты с файлами я проводил на SD-карте. Если будете экспериментировать на флеш-памяти, то не трогайте важные системные файла, типа boot.py и др.

Запись и чтение текстовых файлов

С записью и чтением текстовых файлов возникло несколько проблем. Когда я пытался создать текстовый документ на компьютере и сохранить его на диске, то при чтении выводились просто нечитаемые символы в виде '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'. Пробовал и ANSI и UTF-8.

Но если создать файл средствами самого микроконтроллера, то файл потом нормально считывается. Позже я посмотрел его через компьютер при помощи редактора Notepad++. Файл сохраняется в кодировке Unix (LF) UTF-8. Но попытка создать опять же такой файл в указанной кодировке на компьютере не помогла, устройство отказывалось его считывать. Здесь надо разбираться, как укротить проблему.

Для начала получим список поддерживаемых команд для объекта file:


dir(file)

Результат: ['__class__', '__enter__', '__exit__', '__next__', 'close', 'read', 'readinto', 'readline', 'write', '__del__', 'flush', 'readlines', 'seek', 'tell'].

Перед работой с файлом, его следует открыть через метод open(). У метода много параметров, в большинстве случаев достаточно двух - имя файла (путь к файлу может быть относительным и абсолютным) и режим. Ещё используется третий параметр для кодировки.

Список режимов:

  • r - открытие на чтение (по умолчанию)
  • w - открытие на запись, содержимое файла удаляется, если файла не существует, то создаётся новый
  • x - открытие на запись, если файла не существует, иначе исключение
  • a - открытие на запись в конец файла
  • b - открытие в двоичном режиме
  • t - открытие в текстовом режиме (по умолчанию)
  • + - открытие на чтение и запись

Режимы могут быть объединены. По умолчанию режим равен rt (чтение текстового файла). Для чтения в двоичном режиме используйте режим rb.

После окончания работы с файлом, его нужно обязательно закрыть через метод close().

Создание файла и запись в него

Создадим файл с указанным именем и запишем в него две строчки текста при помощи метода write(). Я проделал это в режиме REPL.


file = open ("/sd/demo.txt", "w")
file.write("First Line \n")
file.write("Second Line")
file.close()

При вводе в режиме REPL после ввода команды write() выводится возвращаемое значение метода - количество записанный в файл байт.

Параметр w в методе open() означает write, т.е. мы открываем файл в режиме записи. Метод возвращает объект TextIOWrapper.


file = open("/sd/demo.txt", "w")
type(file)
# <class 'TextIOWrapper'>

Чтение файла

Теперь пробуем прочитать файл. Есть два варианта: прочитать весь файл сразу целиком или считывать построчно.

Прочитаем весь файл целиком методом read() (REPL). Обратите внимание, что содержимое вывелось в одну строку с использованием служебного символа перевода строки. Мы позже к нему вернёмся.


file = open("/sd/demo.txt", "r")
file.read() # 'First Line \nSecond Line'
file.close()

В методе можно указать число символов для чтения (целое число).


file.read(2)

Есть альтернативные варианты чтения файлов. В первом варианте возвращается строковый объект и символ перевода строки не выводится. Во втором варианте возвращается объект bytes.


data = open('/sd/demo.txt').read() # объект str
print(data)

data = open('/sd/demo.txt', 'rb').read() # объект bytes
print(data)

Теперь пробуем прочитать файл построчно при помощи readline().


from m5stack import *
from m5ui import *
from uiflow import *
import uos

lcd.clear()
file = open("/sd/demo.txt", "r")
lcd.setCursor(0, 0)
lcd.print(file.readline())
lcd.print(file.readline())
file.close()

Если выводить данные на экран, как в примере выше, то увидим две строки без служебных символов.


First Line
Second Line

Если выводить данные в режиме REPL, то у первой строки увидим немного другой ответ.


'First Line \n'

Если не хочется видеть символ \n, то можно добавить вызов метода rstrip().


file.readline().rstrip("\n")

Если мы не знаем число строк, то можем использовать цикл и ждать, когда достигнем строки с нулевой длиной (конец файла).

По умолчанию функция print() выводит текст, автоматически добавляя символ перевода строки в конце. Мы подавляем этот символ, указывая end='', поскольку строки, считанные из файла, и без того оканчиваются символом перевода строки.


file = open("/sd/demo.txt", "r")
lcd.setCursor(0, 0)

while True:
    line = file.readline()
	if len(line) == 0: # конец файла
	    break
	lcd.print(line, end='')

file.close()

Переименовать файл

Файл можно переименовать при помощи метода rename().


os.rename("/sd/readme.txt", "/sd/cats.txt")
os.listdir("/sd")

Удалить файл или папку

Чтобы удалить файл из диска, вызовем метод remove() и посмотрим список файлов на диске, чтобы убедиться в удалении файла.


os.remove("/sd/readme.txt")
os.listdir("/sd")

Для удаления папки используется метод rmdir(). Напишем универсальный метод для удаления файла или папки.


import os

FILE_CODE = 0x8000

def isfile(path):
    return os.stat(path)[0] == FILE_CODE

def any_remove(path):
    func = os.remove if isfile(path) else os.rmdir
    func(path)

Пример создания папок.


import os

def exists(path):
    try:
        os.stat(path)
    except OSError:
        return False
    return True

def mkdir_safe(path):
    if not exists(path):
        os.mkdir(path)

def makedirs(path):
    items = path.strip('/').split('/')
    count = len(items)
    paths = ['/' + '/'.join(items[0:i + 1]) for i in range(count)]
    for path in paths:
        os.mkdir(path)
Реклама