Перейти к содержимому

lvgl_clock — часы и погода на LVGL

Часы с секундами, дата по-русски и погода за окном: температура, «ощущается как», минимум и максимум на сегодня, влажность, ветер, значок погоды. Время — из интернета (NTP), погода — с open-meteo.com (бесплатно, без ключа). На фоне всплывают разноцветные пузыри случайного размера и скорости, касание экрана выпускает новые.

Сеть Wi-Fi и город задаются в файле на SD-карте — прошивку под другой город пересобирать не нужно.

Дисплей 800×480 и тач GT911 — как в рецепте lvgl_demo. На карте — шрифты /SD/fonts/PT_Sans-Web-*.ttf и настройки /SD/clock.ini (оба есть в sd-card.zip):

ssid = my_wifi
password = my_password
city = Москва
  • city — город по-русски или латиницей; координаты и часовой пояс находит open-meteo.
  • Вместо города — latitude и longitude в градусах (север и восток — со знаком плюс).
  • utc_offset — часовой пояс вручную, часы от UTC (бывает дробным: 5.5). Без него пояс берётся по месту.
  • Пустой ssid — рецепт сам к Wi-Fi не подключается и ждёт, пока это сделает кто-то другой (/SD/boot.lua в прошивке с Lua).

Файл — в UTF-8; правится в любом редакторе на ПК или по SFTP на живой плате (/SD/clock.ini), новые настройки читаются при загрузке.

Как и lvgl_demo, рецепт собирается без Lua — иначе прошивка не помещается во Flash:

Окно терминала
cmake --preset Release -DH723_WITH_LUA=OFF -DH723_RECIPES="lvgl_clock"
cmake --build build/Release

Прошивка с SSH — 727 КБ. Код LVGL, драйверы дисплея и тача берутся из lvgl_demo, HTTP — из wifi_http (recipe.cmake), поэтому вместе с ними, а также с lcd_ltdc, рецепт не включают.

lvgl_clock: started
lvgl_clock: Wi-Fi: подключение к my_wifi…
gt911: product 911, panel 800x480
lvgl_clock: screen ready
IP Address: 192.168.1.50
lvgl_clock: Wi-Fi: my_wifi, адрес 192.168.1.50
lvgl_clock: Москва -> 55.7520, 37.6178 (585 ms)
wifi_http: connect api.open-meteo.com (94.130.142.35):80 error -116
lvgl_clock: weather 10 C (feels 8), code 2, humidity 81 %, wind 2.9 m/s, UTC+10800 s, historical-forecast-api.open-meteo.com 487 ms

Если провайдер не пускает к адресу api.open-meteo.com (строка с error -116), прогноз приходит со второго сервера (см. ниже). Загрузка процессора (top в SSH) — около 23 % с пузырями и 3 % без них.

Что видно на экране, если что-то не так, — в строке состояния внизу слева: нет файла настроек, подключение к Wi-Fi, город не найден, сервер погоды не отвечает.

  • Две задачи. clockNet подключается к Wi-Fi, находит город и раз в 15 минут спрашивает погоду — сетевые запросы длятся секундами, и экран в это время не замирает. lvglClock рисует. LVGL не потокобезопасна, поэтому сетевая задача экрана не трогает: она кладёт данные в общую структуру (shared) в критической секции и увеличивает счётчик seq. Таймер LVGL раз в 250 мс копирует структуру и перерисовывает погоду, только если счётчик изменился.
  • Город → координаты. geocoding-api.open-meteo.com/v1/search?name=... — название в адресе кодируется побайтно в %XX (UTF-8). В ответе — широта, долгота и название по-русски.
  • Погода. /v1/forecast с параметрами current= (сейчас) и daily= (минимум и максимум на сегодня), timezone=auto — open-meteo добавляет в ответ смещение пояса места utc_offset_seconds, по нему идут часы. Код погоды WMO (0 — ясно, 61 — дождь, 95 — гроза…) превращается в текст и значок в weatherText.
  • Два сервера погоды. Тот же запрос понимают api.open-meteo.com и historical-forecast-api.open-meteo.com — они на разных адресах. Некоторые провайдеры не пускают к первому; рецепт пробует оба и запоминает тот, что ответил.
  • HTTP без TLS. open-meteo отвечает и по обычному HTTP, поэтому хватает сокетов FreeRTOS+TCP (wifiHttpGet из wifi_http) — без Mbed TLS и его ~60 КБ Flash. Ответ приходит кусками chunked; wifiHttpGet склеивает их, тело — обычная строка.
  • JSON без библиотеки. Ответ короткий и одинаковый по структуре: jsonNumber ищет "ключ": после начала нужного объекта ("current":{) и читает число strtof. Для чего-то сложнее подойдёт библиотека разбора JSON, например cJSON.
  • Время. NTP-клиент (libs/ntp) ставит часы RTC по поясу TIME_ZONE (по умолчанию +3). Рецепт переводит их в пояс места: RTC − TIME_ZONE + utc_offset. Пока время не пришло (в RTC 2023 год или раньше), на экране «–:–». Секунды меняются раз в секунду, часы и минуты — раз в минуту, дата — раз в сутки: LVGL перерисовывает только изменённые надписи.
  • Значок погоды — несколько объектов LVGL с закруглением: круг — солнце (ночью — луна, is_day), три круга и прямоугольник — облако, полоски — дождь, кружки — снег.
  • Пузыри. Таймер раз в 330 мс с вероятностью 1/3 выпускает пузырь в случайном месте снизу (lv_rand, зерно — счётчик тактов ядра). У пузыря две анимации lv_anim: подъём за верх экрана (мелкие быстрее) и бесконечное покачивание по горизонтали (lv_anim_set_reverse_duration, lv_anim_path_ease_in_out). В конце подъёма lv_obj_delete_anim_completed_cb удаляет пузырь. Пузыри — в отдельном слое, созданном первым, поэтому рисуются под надписями. Касание (LV_EVENT_PRESSED экрана) — четыре пузыря из точки касания; надписи и карточка не кликабельны, чтобы касание доходило до экрана.
  • Нагрузка. Каждый сдвиг пузыря — перерисовка куска градиентного фона и цифр под ним. Экран обновляется 30 раз в секунду вместо 50 (lv_display_get_refr_timer), пузырей — не больше 10.
  • Другие данные погоды — параметры current= и daily= в fetchWeather (список — в документации open-meteo) и jsonNumber с нужным ключом. Прогноз на несколько дней — forecast_days=3, значения в daily — массивы по дням.
  • Число и размер пузырей — MAX_BUBBLES, bubble(); без пузырей — убрать таймер bubbleTimer, загрузка процессора упадёт до 3 %.
  • Цвета и раскладка — buildScreen. Шрифты с карты рисуются любого размера (lvglLoadFont).

Исходники рецепта: recipes/lvgl_clock