Графический интерфейс программы. Разработка приложений на python.
В работе рассмотрены примеры использования python для создания кросс-платформенных сервисов визуализации значений параметров датчиков в реальном времени.
Теоретическая часть
Одна из отличительных особенностей языка Python - его универсальность. В предыдущих работах microPython использовался как язык программирования встраиваемых систем на микроконтроллерах семейства Arduino. В данной работе приводится его более традиционное применение - разработка приложений и сервисов.
Выполнение python скриптов
Выполнение Python скриптов возможно несколькими способами. Простейший - внутри интерпретатора Python.
$ python3 #запуск интерпретатора python в терминале Linux
Python 3.11.2 (main, Feb 8 2023, 00:00:00) [GCC 13.0.1 20230208 (Red Hat 13.0.1-0)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello World")
Hello World
>>>
Это не самый удобный вариант для разработки ПО, однако наиболее доступный для выполнения простейших операций и проверки работы отдельных функций.
Следующий вариант - запуск Python скрипта из файла.
$ touch hello.py #создание файла hello.py
$ echo "print(123)" > hello.py #запись в этот файл строки print(123)
$ python3 hello.py #исполнение файла интерпретатором python
123
Однако для удобства работы со скриптами Python в исследовательских целях наиболее часто применим Jupyter Notebook - интерактивный блокнот с исполняемой средой Python.
Jupyter Notebook
Jupyter Notebook поставляется в качестве расширения к python и устанавливается разными методами. Предыдущая лабораторная работа затрагивала использование Jupyter как части программного комплекса GNS3. Желающие могут установить jypiter notebook в виртуальной машине с ubuntu/debian в два шага:
предполагается, что python в системе уже установлен, а также пользователю не требуется установка и настройка окружений среды, таких как conda
$ pip3 install jupyter #установка пакета
$ jupyter notebook #запуск notebook
Для простоты эксперимента проведение лабораторной работы доступно и в экспериментальном проекте от Jupiter - браузерной версии программы JupyterLite, доступной по ссылке:
https://jupyter.org/try-jupyter/

Рисунок 1 – Интерфейс Jupyter Notebook
Исполнение Python файлов в данной среде выполняется в специальном формате Notebook файла ipynb. Это позволяет разделить сегменты кода python, оформление файла в Markdown и прочие элементы.
Для создания нового блокнота достаточно нажать на Python в секции Notebook. При этом в левой секции появится файл Untitled.ipynb.

Рисунок 2 – Интерфейс редактора
Откроется окно редактирования файла. Первая кнопка - сохранение, при первом сохранении будет предложено переименовать файл
Функционал всех кнопок управления виден при наведении курсора:
- Сохранение документа
- Добавить ячейку ниже текущей
- Вырезать данную ячейку
- Скопировать данную ячейку
- Вставить ячейку из буфера обмена
- Запустить эту и следующие ячейки
- Прервать выполнение
- Перезапустить интерпретатор
- Перезапустить интерпретатор и выполнить ячейки
- Выбор формата текущей ячейки (нас интересуют Code и Markdown)
Ячейки в Notebook
Ячейки (cells) - блоки кода, которые форматируются или выполняются интерпретатором в соответствие с их предназначением. Для code происходит выполнение ячеек в Python. Для markdown происходит преобразование кода в форматированный текст, что позволяет оформить документ.

Рисунок 4 – Ячейка Markdown и ячейка Code
Основные инструменты Python
Большинство функций, которые может нам дать Python, содержатся в его библиотеках. Существует три варианта их использования:
import time #подключение библиотеки напрямую
import numpy as np #подключение библиотеки с подменой названия
from matplotlib import pyplot as plt #подключение части библиотеки
Основные библиотеки python
-
numpy (как правило, импортируется под названием np) - один из самых фундаментальных пакетов в Python - универсальный пакет для обработки массивов. Он предоставляет высокопроизводительные объекты многомерных массивов и инструменты для работы с массивами.
-
scipy (sc) - содержит модули для эффективных математических процедур, таких как линейная алгебра, интерполяция, оптимизация, интеграция и статистика. Основной функционал библиотеки SciPy построен на NumPy и его массивах.
-
matplotlib (plt) - функционал для построения различных графиков, включая гистограммы, столбцовые, точечные, круговые диаграммы.
-
requests - позволяет делать http(s) запросы с минимальным количеством кода.
-
random - генератор псевдо-случайных чисел в различных численных форматах.
-
math - математические операции, тригонометрия, формулы.
-
os - функции операционной системы.
Подробнее с функционалом, доступным внутри python, можно и стоит ознакомиться на сторонних сайтах, например: https://losst.pro/standartnye-biblioteki-python.
Используемые в работе библиотеки и методы
pyodide.http, requests, json
В силу особенностей среды JupyterLite (использование интерпретатора Pyodide вместо Python) нарушена работа части библиотек, в том числе requests. Если вы используете JupyterLite, применяйте следующий код для выполнения запросов:
from pyodide.http import open_url
url = "https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new"
request = open_url(url).read()
При выполнении ЛР в Jupyter Notebook или запуске скрипта в python, применима библиотека requests:
import requests
url = "https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new"
request = requests.get(url=url)
Пример использования http запроса
Сервис https://open-meteo.com предоставляет открытый API для прогноза погоды и истории данных о погоде. Составленный в соответствие с документацией запрос позволяет получить величину средней температуры в Москве за некоторый день ноября 2023 года с помощью request/pyodide.http и библиотеки json, преобразовывающей текст запроса в python словарь.
from pyodide.http import open_url
import json
day = 9
url = f"https://archive-api.open-meteo.com/v1/era5?latitude=37.21&longitude=55.98&start_date=2023-11-{day:02}&end_date=2023-11-{day:02}&daily=temperature_2m_mean"
request = open_url(url).read()
request = json.loads(request)
print(request)
print(request['daily']['temperature_2m_mean'][0])
Результат запроса:
{
"latitude":37.223198,
"longitude":56.02649,
"generationtime_ms":0.03802776336669922,
"utc_offset_seconds":0,
"timezone":"GMT",
"timezone_abbreviation":"GMT",
"elevation":1440.0,
"daily_units":{
"time":"iso8601",
"temperature_2m_mean":"°C"
},
"daily":{
"time":[
"2023-11-09"
],
"temperature_2m_mean":[
7.2
]
}
}
7.2
Аналогичным образом можно запрограммировать измерительное устройство на базе ESP32 и получать данные с него в формате json.
time
Из библиотеки time потребуется функция sleep, позволяющая задержать выполнение скрипта на определённый промежуток времени. Также мы применим несколько функций, сохраняющих текущее время.
import time
print("Start")
time.sleep(1) #пауза на 1 секунду
print("Stop")
matplotlib
Библиотека matplotlib будет использована для построения графиков. В следующем примере можно познакомиться с основными функциями для построения линейных графиков зависимости величин.
Для начала создадим тестовые данные:
plots = [
{
'name': "Temperature outside",
'unit': "T, ºC",
'values': [20, 22, 22, 20, 21, 24, 25],
'days': [10, 11, 12, 13, 14, 15, 16]
},
{
'name': "Rain probability",
'unit': "R, %",
'values': [10, 5, 15, 30, 30, 5, 5],
'days': [10, 11, 12, 13, 14, 15, 16]
}
]
В массиве plots содержатся словари, каждый отвечающий за одну из диаграм на графике. К ним можно добавить минимум и максимум по осям, цвет и стиль оформления линий, можно задать, на каких подграфиках (subplot) будут отрисовываться диаграмы - все эти данные будут обработаны позднее.
Также для удобства применим функцию enumerate, чтобы пронумеровать каждый элемент массива, и используем for in конструкцию как более удобный аналог for i in range():
for i, plot in enumerate(plots):
...
Полный пример matplotlib:
from matplotlib import pyplot as plt
plots = [
{
'name': "Temperature outside",
'unit': "T, ºC",
'values': [20, 22, 22, 20, 21, 24, 25],
'days': [10, 11, 12, 13, 14, 15, 16]
},
{
'name': "Rain probability",
'unit': "R, %",
'values': [10, 5, 15, 30, 30, 5, 5],
'days': [10, 11, 12, 13, 14, 15, 16]
}
]
fig, axs = plt.subplots(len(plots), figsize=(12,4))
#создать subplot с размером 12*3 для каждого графика
fig.suptitle('Weather forecast')
#заголовок для всего графика
for i, plot in enumerate(plots):
axs[i].plot(plot['days'], plot['values'], label=plot['name'])
axs[i].legend()
axs[i].set_xlabel('t, д')
axs[i].set_ylabel(plot['unit'])
i=i+1
plt.tight_layout() #увеличенные отступы
plt.show() #показать график
Обновление графиков в реальном времени
Существует несколько способов обновлять графики matplotlib в реальном времени, им посвящены достаточно длительные обсуждения на stackoverflow. Для работы с jupyter подходит следующий:
from matplotlib import pyplot as plt
from IPython.display import clear_output
import time
fig, axs = plt.subplots(1, figsize=(12,4))
for i in range(10): #показать 10 кадров
clear_output(wait=True)
#очистка предыдущего графика
###
# построение некоторого графика
plt.plot(range(i), [(i-4)**2 for i in range(i)])
plt.tight_layout()
plt.show() #показ графика заново
###
time.sleep(0.5) #сон на полсекунды
Для удобства восприятия кода стоит вынести код для рисования графиков, основанный на примере из предыдущего раздела, в отдельную функцию. Тогда основной цикл программы будет выглядеть следующим образом:
for i in range(10): #отрисовать 10 кадров
clear_output(wait=True)
plot_graphs() #от plt.subplots() до plt.show()
time.sleep(0.5)
Практическая часть
Требуется разработать программу на python, выполняющую роль графического интерфейса для некоторой измерительной системы. Программа строит не менее двух графиков (разные subplot или в одной координатной плоскости). График обновляется несколько раз в реальном времени, показания добавляются или замещаются более новыми.
Предлагается следующая структура программы:
from matplotlib import pyplot as plt
from IPython.display import clear_output
import time
from pyodide.http import open_url
import json
plots = [
{
'name': "Mean temperature",
'unit': "T, ºC",
'values': [],
'days': []
},
...
]
def plot_graphs(): #функция рисования графиков
fig, axs = plt.subplots(len(plots), figsize=(12,4))
...
plt.show()
def update_plots(day): #функция добавления данных
...
plots[0]['values'].append(temp)
plots[0]['days'].append(day)
for i in range(1,10):
update_plots(i)
clear_output(wait=True)
plot_graphs()
time.sleep(0.5)
Требования к программе:
- Производится корректное отображение графиков, графики не дублируются
- На графиках присутствуют подписи осей, подписаны названия измеряемых величин (title либо legend)
- Данные для работы берутся либо из открытых данных (api погоды, онлайн генераторы тестовых данных), либо генерируются случайным образом (библиотека random), либо берутся из некоторого набора подготовленных данных; но массив данных (plots в примерах) обязан быть пустым и пополняться только по мере работы скрипта.
- Данные не обязаны быть правильными. В случае недоступности открытых данных за ноябрь-декабрь 2023 допустимо использовать данные за 2022.
Варианты работы:
Вариант работы соответствует номеру студента в группе.
| N | График 1 | График 2 | Месяц |
|---|---|---|---|
| 1 | Количество учебных пар | Длительность светового дня | Февраль |
| 2 | Средняя температура за день | Максимальная температура | Март |
| 3 | Средняя температура за день | Минимальная температура | Апрель |
| 4 | Количество учебных пар | Интенсивность дождя | Сентябрь |
| 5 | Время рассвета | Длительность светового дня | Октябрь |
| 6 | Время заката | Длительность светового дня | Ноябрь |
| 7 | Уровень выпавшего снега | Длительность заката | Декабрь |
Оформление работы
Работа оформляется в виде ipynb файла с описанием элементов программы (ячеек кода), рабочим кодом программы.