MicroPython Skill Builders — #10 Файлы
Добро пожаловать в десятый выпуск серии MicroPython Skill Builders, цель которой — улучшить ваши навыки программирования на MicroPython с одновременным знакомством с новыми компонентами и техниками написания кода — с использованием Raspberry Pi Pico!
Сегодня мы рассмотрим, как работать с файлами в MicroPython на Pico: создавать их, а затем считывать и обрабатывать данные. Этот урок основывается на предыдущем уроке о строках.
Что вам понадобится
Предполагается, что вы уже установили Thonny на компьютер и настроили Raspberry Pi Pico с актуальной прошивкой MicroPython (UF2). Если нет — воспользуйтесь руководством по началу работы с Raspberry Pi Pico, где всё это подробно описано.
Вам понадобится:
Raspberry Pi Pico с контактными гребёнками (пинами)
Кабель Micro-USB (для питания и программирования Pico)
Дополнительно (для раздела «Что попробовать» в конце):
Потенциометр 10 кОм
Датчик DHT11 или DHT12
Соединительные провода (папа-папа и папа-мама, желательно разных цветов)
Что такое файл?
Файл — это набор информации, который прежде всего идентифицируется по своему имени, например: data.csv или myfile.txt.
Зачем нужны файлы?
Если вы, например, сохраняете серию показаний датчика в виде списка в вашей программе, эти данные будут потеряны при отключении питания Pico. Это связано с тем, что оперативная память (RAM) используется для хранения переменных во время выполнения программы: хотя она очень быстрая, она также является энергозависимой и «забывает» данные при отключении питания.
Если мы сохраним показания в файл во Flash-памяти, мы сможем прочитать их при следующем включении Pico.
Пример 1 — Создание файла
В примере ниже мы создадим простой файл CSV (Comma Separated Values — значения, разделённые запятыми). Это обычный текстовый файл с шестью записями.
Каждая запись состоит из двух полей: целого числа (без десятичной точки) и числа с плавающей точкой (с десятичной точкой). Каждое поле состоит из набора текстовых символов.
Вот пример такого текстового файла в том виде, в котором он выводится при печати в Thonny:
736,2247.38
547,6768.42
837,8634.73
415,4498.85
165,6724.25
872,566.26
Здесь 6 строк (записей). Запятая в каждой строке разделяет запись на два поля.
Каждая запись заканчивается символом \n — символом перевода строки. Он невидим при выводе, однако обеспечивает перенос следующей записи на новую строку.
Чтобы наглядно это показать, вот как выглядел бы вывод, если бы мы заменили символ /n переноса строки на обычный символ N:
736,2247.38N547,6768.42N837,8634.73N415,4498.85N165,6724.25N872,566.26N
Такой файл может быть очень длинным. Flash-память Raspberry Pi Pico составляет 2 МБ — 2 мегабайта — 2 миллиона байт! Байт — это 8 бит — двоичных цифр, 1 или 0 (а знаете ли вы, что половина байта называется нибблом!)
Мы напишем программу для создания такого файла, а затем извлечём и обработаем из него данные.
Создание файла
Запустите код ниже в Thonny:
# Data File Example
# Tony Goodhew for thepihut.com 25 July 2023
import random # Random Number Library - Built-In
print("\n#### Basic FILE Test ####\n")
# Part #1 - Create a .csv file
print("Values Recorded in file \n")
with open("/test.csv", "w") as f: # w = overwrite if already there / a = append to end of file
for count in range(6):
irand = random.randint(0, 999) # Generate a random integer
rand = random.randint(0, 999999)
fp = rand/100.0 # Generate a random FP number
record = str(irand) + "," + str(fp) # Build record string
f.write(record + "\n") # Write record to file with RTN/newline
print(record)
f.close()
Программа выдаёт вывод, подобный приведённому ниже, а в разделе Files в левом нижнем углу окна Thonny появляется новый файл.
Нет раздела Files? Выберите View > Files, чтобы показать эту панель в Thonny.
Файл не появляется? Возможно, нужно обновить представление, чтобы увидеть вновь созданный файл (нажмите на маленькую кнопку меню «бургер» в разделе Files).
#### Basic FILE Test ####
Values Recorded in file
949,5474.87
202,5445.35
416,2676.86
564,7689.08
59,4670.19
558,3469.9
Как работает код создания файла?
Новый файл создаётся инструкцией:
with open("/test.csv", "w")
«w» указывает режим записи и перезапишет существующий файл с таким же именем, если он уже существует. Использование «a» вместо этого добавило бы новые данные в конец существующего файла или создало бы новый, если файл не найден.
Мы настраиваем счётный цикл на 6 итераций. Внутри цикла используется функция random.randint(0, n) для генерации пары случайных целых чисел разного размера. Второе делится на 100.0, чтобы превратить его в число с плавающей точкой, fp.
Затем мы строим запись файла, преобразуя числа в строки и объединяя их с разделяющим символом запятой:
record = str(irand) + "," + str(fp) # Build record string
После этого мы записываем запись в файл, добавляя в конец обязательный символ новой строки:
f.write(record + "\n") # Write record to file with RTN/newline
Наконец, мы выводим содержимое записи в окно Shell, чтобы видеть, что происходит.
Когда цикл завершается, мы закрываем файл инструкцией:
f.close()
Пример 2 — Обработка данных файла
Теперь мы обработаем данные из файла, созданного выше.
Добавьте строки ниже в конец файла из предыдущего примера и запустите его в Thonny:
# Part #2 - Process the file by first copying it into memory
print("\nProcessing lines from file: Method #1 - input whole file first\n")
with open("/test.csv", "r") as f: # Open the file for reading - "r"
lines = f.readlines() # Read whole file into memory
print(len(lines),"\n") # Print number of lines stored
for line in lines: # Get the next line from RAM
length = len(line) # Find length of the current line
line = line[:length-1] # Remove last character - New Line
p = line.find(",") # Find position of the comma
first = line[:p] # Characters up to comma
i2 = int(first) # Convert to integer
last = line[p + 1:] # Characters after the comma
fp2 =float(last) # Convert to floating-point number
result = i2 * fp2 # Process the values = Calculate a dummy result
print("Int =",i2,"\tFP =",fp2, "\tAnswer =",result) # Tabbed printing
f.close() # Close the file when finished
Как работает код обработки?
Мы выводим заголовочную строку и открываем файл в режиме ЧТЕНИЯ, «r»:
with open("/test.csv", "r") as f: # Open the file for reading - "r"
Следующая инструкция читает весь файл в RAM, разбивая его на набор отдельных строк по каждому символу новой строки:
lines = f.readlines() # Read whole file into memory
print(len(lines),"\n") # Print number of lines stored
Инструкция print подтверждает, что хранится 6 строк.
Затем мы перебираем строки одну за другой, выполняя следующие операции:
Находим длину текущей строки: length = len(line)
Удаляем символ новой строки с конца: line = line[:length-1]
Находим позицию запятой в строке: p = line.find(«,»)
Используем позицию, чтобы извлечь строку перед запятой: first = line[:p]
Преобразуем строку в целое число: i2 = int(first)
Используем позицию, чтобы извлечь строку после запятой: last = line[p + 1:]
Преобразуем строку в число с плавающей точкой: fp2 = float(last)
Обрабатываем числа, умножая их: result = i2 * fp2
Выводим числа и произведение с использованием «\t» для выравнивания символом табуляции
Наконец, мы закрываем файл после обработки всех строк с помощью f.close()
После запуска программы убедитесь, что числа, сохранённые в первой части, точно восстановлены и обработаны во второй части.
Нехватка RAM!
Измените счётчик цикла с 6 на 100 и запустите снова. Должно работать нормально и довольно быстро, даже со всем этим выводом.
Теперь попробуйте изменить число итераций на 10000. Да, десять тысяч! В Pico достаточно Flash-памяти для этого. Запустите код снова — это займёт значительно больше времени, но успешно сохранит все записи; однако обработка может завершиться с ошибкой из-за нехватки RAM.
К сожалению, RAM не хватает для хранения всех строк одновременно при попытке прочитать и сохранить их по отдельности.
Решение проблемы нехватки RAM
Нам нужен другой метод для обработки этого большого файла и обхода ограничений RAM. Он очень прост — нужно всего лишь читать строки по одной и обрабатывать их на ходу.
Замените код обработки (Часть #2) блоком ниже (Часть #3) и попробуйте снова. Это занимает время, но завершается без сбоев:
# Part #3 - Process the file by reading one record at a time from the file in ROM
with open("/test.csv", "r") as f: # Open the file for reading - "r"
print("\nProcessing lines from file: Method #2 - Input single lines\n") # Read one line at a time
line = f.readline() # Read in characters to make a line which ends with New line character from ROM/Flash memory
while line != '': # Empty string - Nothing left to read - End of File found
length = len(line) # Find length of current line
line = line[:length-1] # Remove last character New Line
p = line.find(",") # Find position of the comma
first = line[:p] # Characters up to comma
i2 = int(first) # Convert to integer
last = line[p + 1:] # Characters after the comma
fp2 =float(last) # Convert to floating-point number
result = i2 * fp2 # Process the values = Calculate a dummy result
print("Int =",i2,"\tFP =",fp2, "\tAnswer =",result) # Tabbed printing
line = f.readline()
f.close() # Close the file when finished
Что попробовать
Добавьте счётчик в цикл последнего кода и проверьте, что он действительно читает все 10000 записей
Подключите потенциометр и записывайте значения при его вращении в течение 30 секунд
Считывайте температуру и влажность с датчика и сохраните 20 записей с интервалом в полминуты
Считайте их обратно и выведите аккуратно (только один знак после запятой)
Считайте их обратно и вычислите среднее, максимальное и минимальное значения температуры и влажности
Сохраняйте текущее время в начале каждой записи
Вот подсказка для последнего пункта…
import time
for i in range(10):
now = time.time() # Seconds since epoch
print(now)
time.sleep(1)
Об авторе
Тони Гудью — бывший учитель информатики, начавший писать код ещё в 1968 году, когда это называлось программированием — он начинал с FORTRAN IV на IBM 1130! Активный участник сообщества Raspberry Pi, сейчас его главные интересы — программирование на MicroPython, путешествия и фотография.