Не так давно, примерно год назад я наконец получил свою долгожданную посылку. Flipper Zero. Для тех кто не знает, Flipper Zero — это небольшое устройство, которое позволяет заниматься всякими весёлыми затеями в жизни. Веселье начинается с Sub-1 GHz антенны и продолжается микро-скриптами для ПК. Интересующихся, прошу под кат.
Ваше знакомство буду выполнять в хронологическом порядке разделов в меню самого флипера.
Приложения
Я бы не стал выбирать самым основным пунктом данный, так как задача флипера, на мой взгляд, работа с физическим миром вокруг нас, а не программные приколюхи внутри устройства. Как устройство по работе с приложениями — флипер не лучший вариант. Я бы рекомендовал использовать Raspberry Pi или на худой конец Arduino.
Тем не менее, внутри достаточно много приложений уже написанных прекрасными пользователями интернета. Скажу сразу, я установил себе кастомную прошивку DarkFlipper. Об этом позже.
Из предустановок — 8-битные игры типа DOOM, надстройки для распиновки GPIO, аудио-проигрыватель, калькуляторы и доп. приложения и доп. приложения для антенны в 1 ГГц.
Повторюсь, данный раздел совсем не самый интересный.
Sub-GHz
Тут всё просто. В диапазоне от 300 до 928 МГц в AM и FM модификациях можно сканировать все сырые и не сырые данные.
Для тех, кто не очень понимает зачем, могу привести простой пример — коды шлагбаумов. В данном диапазоне передаются данные в шлагбаумы. Как пример, я смог скопировать свой ключ от шлагбаума. Любых шлагбаумов и ворот.
Как это можно реализовать? Расскажу свою историю. Я живу в центре города, где есть такое поверие: «нет шлагбаума — нет парковки». Живя в уникальном, не побоюсь этого слова, доме, где в один небольшой двор размером с небольшой «плевочек» с детской площадкой и местом для парковки примерно на 30 машин установили аж 3 шлагбаума. Три, Карл!
Боль данного пункта заключается в том, что шлагбаумы были установлены трёх разных фирм, трёх разных диапазонов, да ещё и двух типов закрытия. Будучи законопослушным гражданином, я решил запросить данный пульт для парковки своего авто. Позвонив по номерам, которые были упорно замазаны на шлагбауме, я попал не много, не мало в город Реутов (сам нахожусь я в Москве). Там, мне сказали, что сделать можно, но без физического ключа — ничего не получится. На вопрос где его добыть, я получил неутешительный ответ: «Не знаю». Месяц поисков и чудом я выяснил у соседей, что данный ключ находится у «старшей по подъезду» (рубрика «Центр Москвы»). Старшая по подъезду переехала, а подъезд остался. Не ленясь, я дозвонился до данной хозяйки медного шлагбаума. Выяснилось, что данные три шлагбаума были установлены до закона о централизованной установке шлагбаумов и каждый подъезд поставил то что хочет и то как хочет данный аппарат. Каждый шлагбаум — совместная собственность, где все бумаги носит при себе старшая по подъезду. Цирк продолжился. Требования для получения заветного ключа были следующие:
-
Заплатить 7 тысяч рублей за вызов бригады (оно и понятно, из Реутова-то ехать)
-
Купить резидентную парковку для бесплатной парковки в районе дома, что стоило 3 тысячи рублей в год. Она у меня была, так как ставить машину у дома я не мог.
-
Отдать «на сканирование в архив» старшей по подъезду, проживающей в другом доме все документы на машину и меня.
Если первые два пункта были хотя бы понятны и хоть на сколько-то разумны (хотя сейчас резидентной парковки сейчас у меня нет, так как ставя машину в дворовой территории, она не нужна), то последний пункт меня возмутил. И я бы не против был бы и купить за 10 тысяч «ключик от домика», даже был бы не против купить данную подписку у женщины, которая поставила данный шлагбаум, ведь благодаря ней, во дворе уменьшилось количество такси и сторонних машин. Но вот отдавать документы куда-то в другой дом незнакомому человеку, как-то не хотелось. Я любезно отказался от данного предложения.
Шаг в сторону парковки подарила мне компания Прайм Пульт. Вопрос решился просто.
Кто не понял — это ключ, который копирует сигнал. Нужно было просто понять в каком диапазоне работает ключ. Цена вопроса за 10 ключей была 3 тысячи рублей.
Зная фирму производителя (в моём случае Nice Flor-S), можно узнать герцовку данного производителя (в моём случае 433,92 МГц). Далее — всё просто.
Флиппер же помог мне сделать то же самое, но не в отдельном шлагбауме, а на всех трёх простым чтением сырых (RAW) данных. Также, в кастомной прошивке есть подбор ключей по диапазону (приложение находится в отдельной папке в меню Applications).
125 кГц RFID
Переводя с русского на русский, данный раздел нужен для чтения ключей и смарт карт доступа.
Данные ключи сейчас активно используются в новостройках для открытия балконов или (простите) комнат для мусора. В многих учреждениях, открывается турникеты для входа в помещения.
Я работаю в университете и у нас такие же ключи доступа. Как прочитать её — просто. Нажать кнопку «Read» и положить её под флипер. Примерно так:
NFC
Аналог ключей, но с большей защитой. Бывает два типа защиты — PSK. В нём — всё также как и в 125 кГц ключах.
Второй вариант — ASK.
Тут нужно сделать 2 пункта — записать ключ и считыватель. Мужчина на видео по ASK — создатель флипера Павел Жовнер @zhovner
Инфракрасный порт
Инфракрасный излучатель позволяет управлять всем с ИК-приёмником. Тут справится даже ребёнок. Выбираем универсальный пульт:
Затем, выбираем конкретный тип прибора:
Далее, переворачиваем флипер, так чтобы сверху оказался ИК-передатчик и после нажатия на кнопку — отправляются все типы ИК-сигналов.
GPIO
Жаба меня задушила купить Wi-Fi модуль для флипера. По нему не смогу ничего рассказать. Спасибо YouTube за контент.
Как дополнение, распиновка:
iButton
Под надписью iButton спрятался сканнер ключей. Работает всё просто. Сканируем ключ на + и — к разным выпирающим точкам:
Далее, эмулируем их же и подносим к домофону:
Bad USB
Мой любимый раздел. По умолчанию, доступно всего 2 демо для Mac и Windows. Так как сижу на винде, скриншот вывода:
Конечно, этим всё не заканчивается. Далеко не заканчивается. Из моих любимых уже готовых скриптов под винду на PowerShell — разработка от Jakoby. Там много различных вариаций использования от простой смены обоев, до Wi-Fi стиллера.
U2F
Двухфакторная аутентификация возможна и на флипере. Всё что нужно — подключить его без утилиты qFlipper.
К слову про qFlipper. Приложение можно скачать здесь. Смысл приложения прост — работа со сторонним устройством (ПК или же телефоном). Также есть работа через блютуз.
Часы и настройки
Тут что-то добавить сложно. Часы необходимы, если хотите узнать время, а в настройках можно доработать ваш флипер.
Прошивки
Из моих фаворитов кастомных прошивок — DarkFlippers.
На мой взгляд, данная прошивка самая стабильная из кастомных. Изменений довольно много. Все они описаны по ссылке на репозиторий гитхаба.
Второй крутой вариант — вариант от TalkingSasquach. Там также есть крутые обои такого типа:
Где, что и как купить?
Сейчас с поставками флипера сложности. Я свой флипер получил спустя почти год после его официального выхода, как пользователь поддерживающий проект на кикстарте. В комплекте: коробочка, USB type-C, стикер и инструкция по быстрому старту.
Сам флипер, как сказано в официальном телеграмм-канале Павла, купить можно через Joom. Всё остальное (чехол, Wi-Fi модуль, плату) можно купить в Амперке.
А кому оно надо?
Как итог моего использования, могу сказать следующее (учтите, это только моё мнение): устройство прикольное, но нужно «понимающим» пользователям или тем, у кого много ключ-карт и всякого рода пультов.
В реальной жизни я использовал флипер порой и веселее включая кондиционеры и проекторы в кабинетах, где оно всё терялось. Ну и конечно, убивалка времени — классная. Лично для меня — устройство очень удобно и главное — приятно. Однако, есть два момента. Первый — я гик и кайфую с этого. Второй — купил я устройство в хорошие годы за 8 000 — 10 000 тысяч рублей (в зависимости от курса банка за евробаксы). Особые весельчаки взламывают лючки теслы.
Потенциал у устройства есть. Об этом потенциале говорит Линус Себастьян (канал Linus Tech Tips). По мнению Себастьяна, Flipper Zero — это на самом деле это один из самых универсальных хакерских инструментов, которые когда-либо появлялись на рынке.
Как всегда, выбор остаётся за вами. Я же пойду дальше юзать свой флипер и играться с новыми и новыми прошивками.
А вы как относитесь к флиперу?
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Как вам Flipper Zero?
18.78%
Купил. Топ за свои деньги!
80
7.04%
Купил. Круто, но хотелось бы подешевле
30
2.11%
Купил. Не доволен.
9
50.7%
Не покупал. Коплю и/или жду цену в 200 рублей.
216
21.36%
Не покупал и не буду
91
Проголосовали 426 пользователей.
Воздержались 79 пользователей.
21 октября 2022 г.
Flipper Zero, помимо встроенной Змеи по умолчанию и дружелюбного аватара дельфина, представляет собой
невероятно мощное маленькое устройство
. Точно описанный
как многофункциональный инструмент для гиков, он обеспечивает несколько диапазонов частот RFID, Bluetooth,
радиосвязь ниже 1 ГГц, контакты GPIO для отладки, USB для выполнения
BadUSB
атаки, инфракрасный порт и даже разъем для перехвата и имитации ключей iButton.
Это не совсем похоже на взлом Watch Dogs, но это самое близкое, что я нашел, и с небольшой подготовительной работой
и некоторыми очень простыми трюками вы можете легко убедить своих друзей, что это граничит с магией.
:::предупреждение
Предупреждение. Теоретически возможно использовать взломы и Flipper Zero в гнусных целях, включая
приемы, описанные в этой статье. Но знаешь… не делай этого. Приемы, которые я описываю в этой статье, предназначены
только для забавы и развлекательных целей и представляют собой базовые приемы для начинающих, призванные
проиллюстрировать лежащие в основе протоколы и их слабости.
:::
- Угнать за 60 секунд
- Познакомьтесь с музыкой
- Запасной ключ
1. Угнать за 60 секунд
:::предупреждение
Предупреждение. Не угоняйте автомобили. Это незаконно и, как правило, плохая идея (хотя я
использовал это, когда ключ от машины был заперт, заставляя свою вторую половинку взять ключ от ее ключа на
расстоянии 50 миль, отправить его мне по электронной почте и воспроизвести его, чтобы войти).
:::
n Это
отличный старт (умный каламбур), чтобы убедить людей, что вы полностью погрузились в научно-фантастический хакер,
несмотря на то, что это очень просто. Проще говоря, вы научитесь открывать машину нажатием кнопки без ключа (не
работает на автомобилях без радиоключей и может не работать на всех радиоключах).
- Возьмите радиоключ достаточно далеко от машины, чтобы он не активировался (или возьмите себе мешок Фарадея).
- Возьмите Flipper Zero и выберите Sub-GHz > Прочитайте RAW, затем нажмите центральную кнопку, чтобы начать
запись. - Удерживайте кнопку разблокировки на радиоключе в течение нескольких секунд и убедитесь, что вы принимаете
передаваемый код (см. Gif)
Вот и все. Если вы хотите использовать его немедленно, подойдите достаточно близко к машине и нажмите центральную
кнопку, чтобы отправить код и (надеюсь) разблокировать его удаленно, к шоку и трепету всех вокруг. Если вы
предпочитаете быть немного более тонким, вместо этого выберите Сохранить код, дайте ему удобное имя, и вы можете
вернуться в Sub-GHz > Сохраненный позже для передачи, он все еще должен работать, поскольку ключ не должен
повторно использовать один и тот же код.
Как это работает?
Это форма повторной атаки, когда для
разблокировки автомобиля используется действующий код. Хитрость заключается в том, что современные автомобили обычно
используют скользящие или скачкообразные коды, чтобы люди не могли сделать именно это, поэтому, когда вы записывали
код, вы хотели быть вне досягаемости автомобиля.
Обычно автомобиль принимает каждый код разблокировки только один раз, затем помечает его как использованный и
отказывается принимать в будущем. Поскольку случаются случайные нажатия клавиш или сигналы могут не сработать с
первого раза, для разблокировки можно использовать ряд кодов.
Что вы сделали, так это украли один из этих действительных кодов, не дав ему попасть в машину, чтобы пометить его
как недействительный. После того, как вы его использовали, вы обнаружите, что снова разблокировать машину не
получится, каждый из них одноразовый (хотя вы можете записать несколько кодов, чтобы получить несколько
дополнительных применений, и сделать то же самое с кодами блокировки). .
Вы также можете обнаружить, что если вы слишком долго ждете, чтобы использовать его, он больше не будет работать,
так как было использовано слишком много нажатий клавиш, и поэтому диапазон допустимых кодов расширился. n n Тот же
метод использовался в течение многих лет для кражи автомобилей с использованием глушителя для предотвращения
получения автомобиля кода при его захвате с помощью приемника. Конечно, по мере того, как автомобили становятся
«умнее», возникает __множество других
проблем__ чем просто украсть вашу.
2. Лицом к лицу с музыкой — взламывайте телевизоры с помощью Flipper
Zero
:::предупреждение
Предупреждение. В неправильном такте это может привести к тому, что вас ударят. Как и в случае со
всеми другими перечисленными, существуют риски, используйте ответственно.
:::
Вы когда-нибудь заходили в бар и обнаруживали, что телевизор слишком, слишком громкий? Или он застрял на канале,
который вы не хотите смотреть, а пульт загадочным образом пропал?
Именно здесь инфракрасный порт Flipper Zero может пригодиться для простого, старомодного взлома.
Если повезет, универсальных кодов будет достаточно. В противном случае вам может потребоваться узнать коды пульта
дистанционного управления с реального пульта или получить их в Интернете и загрузить на свой Flipper Zero.
Чтобы получить универсальные коды, просто перейдите в Инфракрасный > Универсальные пульты (Learn New Remote и
Saved Remotes говорят сами за себя), затем телевизоры (или кондиционер, если вам нужно им управлять), и он
предоставит вам приятный интерфейс для включения и выключения питания, изменения громкости или канала. . Даже если
канал и громкость не работают, универсальные коды питания работают в большинстве случаев.
Как это
работает?
Подавляющее большинство телевизоров по-прежнему используют инфракрасные коды от пультов< /strong> для управления ими
(старая уловка, чтобы проверить, работают ли батареи в пульте дистанционного управления, заключается в том, чтобы
направить его на камеру мобильного телефона и нажать кнопку — мобильные камеры чувствительны к инфракрасному
излучению, поэтому вы увидите, как мигает лампочка на пульте). скрин, если работает). Все, что вы делаете, это
используете стандартные коды или записываете коды для определенного телевизора и снова воспроизводите их через
инфракрасный порт. На самом деле, вы можете использовать это для чего угодно с помощью инфракрасного пульта
дистанционного управления, вам просто нужно захватить команду, сохранить ее и воспроизвести по желанию.
3.
Запасной ключ
:::предупреждение
Предупреждение: я не должен этого говорить, но на всякий случай не используйте это, чтобы
вламываться в гостиничные номера людей!
:::
Это не будет работать для всех электронных замков в отелях, но работает для многих. Это связано с тем, что
большинство отелей не утруждают себя вложениями в высоконадежные запорные механизмы, а только самые простые с
минимальным уровнем безопасности. Я объясню подробнее об этом позже, просто имейте в виду, что это не будет работать
везде.
Вас когда-нибудь раздражало, что вы получаете только один ключ в отеле или в дачном поселке? Ну, это больше не
проблема с этим простым взломом Flipper Zero. Считыватель RFID может считывать большинство бесконтактных
карт-ключей, используемых в отелях, офисах и других местах. Простой доступ через NFC > Считайте (или 125 кГц RFID
для низкочастотных карт), затем отсканируйте карту, сохраните ее и при необходимости эмулируйте.
Даже если на карте есть страницы, защищенные паролем, часто системы электронных замков не используют шифрование,
поэтому вы можете просто эмулировать карту, чтобы открыть дверь.
По мере того, как места обновляют свою систему замков, этот становится менее полезным, но сочетание стоимости и
устаревших технологий означает, что он будет работать большую часть времени (и отели не известны наличием наивысшая
безопасность). В этой статье я не буду рассказывать, как взломать шифрование, но часто это
возможно, если использовать несколько дополнительных инструментов и потратить время.
Как это работает?
РЧИД-карты питаются от считывателя и активируются, когда они считываются. В старых картах не было возможности
шифрования, и даже в современных шифрование часто используется неэффективно.
Когда мы говорим о гостиничных номерах, номер комнаты закодирован в карточке. В зависимости от используемой системы
могут быть опции для кодирования всего этажа, набора комнат или мастер-ключа для разблокировки везде (как
используется службами по уборке).
Существует дюжина или около того различных схем, которые обычно используются, и в большинстве из них у каждого
устройства записи карт есть встроенный в него ключ, чтобы привязать его к определенному отелю и предотвратить
отпирание номера 101 по всему миру одним и тем же ключом.
Ключи также зашифрованы, как правило, со временем заезда и выезда, поэтому единственная причина сохранять ключи от
отеля — это если вы хотите получить больше данных, чтобы попытаться взломать шифрование, чтобы вы могли сделать свой
собственный мастер-ключ (не рекомендуется). по юридическим причинам, но забавно, если вы заинтересованы в этих
схемах), или, конечно, написать их как запасные ключи, а не размахивать своим Flipper Zero повсюду.
С помощью некоторых хаков Flipper Zero вы можете сделать гораздо больше, чтобы произвести впечатление на друзей
и повеселиться, и хотя есть и другие инструменты, я должен сказать, что это, безусловно, самый простой в
использовании и самый многофункциональный. я сталкивался.
Напишите в комментариях свои любимые лайфхаки Flipper Zero, чтобы произвести впечатление на ваших друзей, или
лайфхаки, которые вы бы хотели, чтобы я рассмотрел в будущих статьях.
Оригинал
Привет!
В этой статье мы разберёмся, как написать собственную программу-приложение для Flipper Zero!
Сердцем гаджета Flipper Zero является 32-битный микроконтроллер STM32. Программирование Дельфина сильно отличается от программирования привычных нам Arduino. Помимо самого микроконтроллера во Флиппере есть радиомодуль, NFC-модуль, кардридер, модуль Bluetooth, дисплей, микросхема управления подсветкой и так далее. Эффективно управлять всеми этими устройствами в одном цикле loop
, как это обычно выглядит в среде разработки Arduino, уже нельзя.
На помощь приходит операционная система реального времени или RTOS (real-time operating system). RTOS разграничивает логические части всей программы в разные потоки и сама осуществляет переключение между ними, а также выделяет необходимые для работы потоков ресурсы.
Так как Флиппер построен на чипе STM32, в нём используется, наверное, самая популярная операционка для этого типа микроконтроллеров — FreeRTOS.
Мы не будем подробно разбирать, как реализована операционная система на Flipper Zero, а лучше сосредоточимся на написании приложений.
Это важно! Проект Flipper Zero активно развивается, и на момент выхода данной статьи официальной документации по созданию собственных приложений нет, а сам API полностью не описан. Однако принцип построения приложений вполне понятен в процессе изучения исходного кода встроенных в Флиппер приложений. Вполне возможно, что со временем API изменится, и код из этой статьи станет неактуальным. Разработчики Flipper Zero обещают, что документация появится, когда стабилизируется API. Статья актуальна для прошивки Флиппера 0.75.0.
Прошивку можно собрать на следующих платформах:
- Windows 10+ с PowerShell и Git (архитектура x86_64).
- macOS 12+ с Command Line tools (архитектура x86_64 и arm64).
- Ubuntu 20.04+ с
build-essential
и Git (архитектура x86_64).
У пользователей macOS не должно быть проблем со сборкой прошивки благодаря Homebrew. Ну а если вы собираете прошивку на Windows и столкнулись с трудностями, попробуйте воспользоваться WSL.
Писать код для собственных приложений мы будем на языке программирования C на настольном компьютере под управлением Linux, а именно Ubuntu 22.04.1 LTS (Jammy Jellyfish).
Установка необходимого софта
Перед тем как писать новое приложение, нам придётся научиться собирать всю прошивку микроконтроллера.
Сперва скачаем и установим официальную программу qFlipper для работы с Флиппером через графический интерфейс. Мы будем использовать qFlipper для удобной загрузки наших готовых приложений на SD-карту во Флиппере.
Скачиваем версию программы для OS Linux куда-нибудь, например в домашнюю директорию. На момент выхода статьи программа qFlipper имеет версию 1.2.2, а сам файл называется qFlipper-x86_64-1.2.2.AppImage
. Также установите необходимую для работы программы библиотеку libfuse2
, если её нет в вашей системе.
wget https://update.flipperzero.one/builds/qFlipper/1.2.2/qFlipper-x86_64-1.2.2.AppImage
sudo apt install libfuse2
Установите разрешение на запуск qFlipper и добавьте в систему udev
правила доступа к USB Serial-порту для обычного пользователя. Иначе понадобится вести разработку от лица суперпользователя, что небезопасно для всей системы в случае неосторожности.
sudo chmod +x qFlipper-x86_64-1.2.2.AppImage
./qFlipper-x86_64-1.2.2.AppImage rules install
Запустите qFlipper и подключите ваш Flipper Zero к компьютеру по USB.
./qFlipper-x86_64-1.2.2.AppImage
Убедитесь, что ваш Флиппер появился в программе qFlipper, всё работает как положено и установлена свежая прошивка.
Для быстрой и удобной установки необходимого софта понадобится диспетчер пакетов Homebrew.
Установите Homebrew:
sudo apt install curl
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
После установки добавьте переменные окружения PATH
, MANPATH
для Homebrew. Это можно сделать, добавив следующую строку в .profile
-файл для вашего юзера:
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/тут_ваш_username/.profile
Прошивка для Flipper Zero хранится в репозитории flipperzero-firmware на GitHub.
Склонируйте к себе репозиторий прошивки Flipper Zero со всеми модулями. Репозиторий займет чуть больше 2 ГБ пространства.
git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
Установите перечисленный в описании репозитория софт:
sudo apt update
sudo apt install openocd clang-format-13 dfu-util protobuf-compiler
Перейдите в директорию репозитория и установите все необходимые пакеты с помощью Homebrew:
cd flipperzero-firmware
brew bundle --verbose
Готово! Прошивка для Флиппера, как и пользовательские приложения, собираются с помощью утилиты fbt
(Flipper Build Tool). Для создания собственных приложений нет необходимости каждый раз собирать всю прошивку целиком, однако при первом запуске утилиты fbt
будут скачаны необходимые gcc-arm
тулчейны.
Соберите прошивку:
./fbt
Все результаты сборки и бинарные файлы будут помещены в директорию /dist
.
Пример 1. Простейшее приложение
Создадим простейшее компилируемое приложение.
Мы будем создавать приложение типа FAP (Flipper Application Package). Готовое приложение этого типа представляет собой файл формата .fap
. По сути, .fap
-приложение — это исполняемый файл .elf
с дополнительными встроенными данными.
Кстати! Весь исходный код приложений из этой стати мы выложили на GitHub в репозитории flipperzero-examples.
При создании пользовательские приложения помещаются в отдельные папки в специально организованную директорию applications_user
:
Придумайте имя для приложения. Мы назвали наше первое приложение example_1
, этим же именем назвали и папку. В неё помещаются все файлы, которые относятся к вашему приложению: исходный код, изображения и прочее.
В папке example_1
создадим файл исходного кода на языке С — example_1_app.c
. Вот как выглядит код простейшего приложения.
#include <furi.h>
int32_t example_1_app(void* p) {
UNUSED(p);
return 0;
}
Конвенционально в приложении точкой входа является функция, которая имеет имя приложения и суффикс app
. Точка входа в наше приложение — функция example_1_app
. Главная функция традиционно возвращает код ошибки числом типа int32_t
. Возвращаемый ноль сообщает об отсутствии ошибок.
При компиляции кода для Флиппера любой warning воспринимается как ошибка. Да, ваш код должен быть чистеньким. Неиспользованные в функции аргументы вызывают warning, поэтому для обозначения неиспользуемого указателя p
мы используем макрос UNUSED
. Реализация данного макроса описана в заголовке furi.h
. FURI расшифровывается как «Flipper Universal Registry Implementation». Этим заголовочным файлом мы, по сути, подключаем вce core API Флиппера.
Нашему приложению понадобится иконка. Для пользовательских приложений, которые находятся на самом Флиппере в разделе Applications
, в качестве иконок используются изображения PNG с глубиной цвета 1 бит (чёрно-белые) и размером 10×10 пикселей.
Раздел Apllications
с пользовательскими приложениями:
Можно использовать одно из уже имеющихся изображений, но всегда лучше добавить что-то своё. В GIMP мы нарисовали вот такой смайл в чёрных очках:
Нарисовать свою иконку можно и в Paint. Файл изображения помещаем в папку нашего приложения example_1
.
Помимо исходного кода и иконки нам нужен файл манифеста приложения — application.fam
. Этот файл является обязательным. Наш манифест приложения имеет следующий вид:
App(
appid="example_1",
name="Example 1 application",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_1_app",
cdefines=["APP_EXAMPLE_1"],
stack_size=1 * 1024,
order=90,
fap_icon="emoji_smile_icon_10x10px.png",
fap_category="Misc",
)
Разберёмся, за что отвечают данные параметры. Параметры для всех видов приложений:
appid
— строка, которая используется как ID приложения при конфигурации сборкиfbt
, а также для разрешения зависимостей и конфликтов. Есть смысл использовать здесь непосредственно имя вашего приложения.name
— читабельное имя приложения, которое будет отображаться в меню приложений на Флиппере.apptype
— тип приложения. Существуют разные типы для тестовых, системных, сервисных, архивных приложений и для приложений, которые должны быть в главном меню Флиппера. В конце сборки наше приложение будет типаFAP
. Для приложений подобного рода используется типEXTERNAL
(FlipperAppType.EXTERNAL
).entry_point
— точка входа приложения. Имя главной функции, с выполнения которой начнётся работа вашего приложения. Если в качестве точки входа вы хотите использовать функцию C++, то она должна быть обёрнута вextern "C"
.cdefines
— препроцессорное глобальное объявление для других приложений, когда текущее приложение включено в активную конфигурацию сборки.stack_size
— размер стека в байтах, выделяемый для приложения при его запуске. Обратите внимание, что выделение слишком маленького стека приведёт к сбою системы из-за переполнения стека, а выделение слишком большого уменьшит полезный объём heap-памяти для обработки данных приложениями.order
— порядок приложения внутри своей группы при сортировке записей. Чем ниже значение, тем выше по списку окажется ваше приложение.
Параметры для внешних приложений типа EXTERNAL
:
fap_icon
— путь и имя PNG-изображения размером 10×10 пикселей, которое используется как иконка. Здесь пишем путь и имя нашей PNG-иконки.fap_category
— подкатегория приложения. Определяет путь.fap
-файла в папке приложений в файловой системе. Может быть пустым. Мы поместили наше приложение в категориюMisc
на Флиппере.
Если все файлы на месте, мы можем начинать сборку приложения.
В терминале переходим в корневую директорию прошивки flipperzero-firmware
. Сборка осуществляется командой ./fbt fap_{APPID}
, где {APPID}
— это ID, указанный в .fam
-файле манифеста приложения.
./fbt fap_example_1
Сбилдить все имеющиеся в прошивке приложения FAP можно командой ./fbt faps
.
Готовое FAP-приложение находится в директории build
в скрытой директории .extapps
. Наш файл приложения называется example_1.fap
.
Используя программу qFlipper, перенесём файл приложения на SD-карту в директорию /apps/Misc
. Файл можно перенести мышкой прямо в окно программы.
После последнего обновления все приложения FAP можно сбилдить и перенести на Флиппер одной командой из консоли:
./fbt fap_deploy
Готово! Теперь наше приложение появилось на Флиппере в разделе Misc
:
Главная функция example_1_app()
сейчас пуста, поэтому при запуске приложения программа просто завершит свою работу и мы не увидим никаких изменений на экране.
Как видно, ваше приложение может вовсе не иметь графического интерфейса и отработать как бы «за кулисами». Но у нашего Флиппера есть экранчик, поэтому нельзя не сделать графический интерфейс.
Пример 2. Графический интерфейс
Добавим нашему приложению графический интерфейс.
Чтобы не путаться между пунктами статьи, мы сделаем новое приложение с именем example_2
, но по сути будем продолжать предыдущее приложение.
Новое приложение поместим в директорию applications_user/example_2
. Соответственно, файл с исходным кодом приложения имеет имя example_2_app.c
.
Для создания графического интерфейса вам придётся значительно расширить исходный код приложения. Создадим в директории приложения заголовочный файл example_2_app.h
для описания типов данных, структур и прототипов функций.
Включим уже знакомый нам заголовочный файл ядра furi.h
. Для графического интерфейса понадобится заголовок gui/gui.h
.
#pragma once
#include <furi.h>
#include <gui/gui.h>
struct Example2App {
Gui* gui;
ViewPort* view_port;
};
typedef struct Example2App Example2App;
В заголовочном файле мы создали структуру Example2App
, которая будет хранить указатели на все важные компоненты нашего приложения, и ввели новый тип для этой структуры. В структуре нашего приложения есть указатели на графический интерфейс Gui
и на ViewPort
. ViewPort
— это структура, которая используется для отрисовки единичного полного экрана. К ней привязываются указатели на callback-функции отрисовки графических объектов на экране и функции обработки различных событий (Events), например нажатие клавиш.
Исходный код приложения в файле example_2_app.c
теперь выглядит так:
#include "example_2_app.h"
#include <furi.h>
#include <gui/gui.h>
Example2App* example_2_app_alloc() {
Example2App* app = malloc(sizeof(Example2App));
app->view_port = view_port_alloc();
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
void example_2_app_free(Example2App* app) {
furi_assert(app);
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
}
int32_t example_2_app(void *p) {
UNUSED(p);
Example2App* app = example_2_app_alloc();
furi_delay_ms(10000);
example_2_app_free(app);
return 0;
}
Разберёмся, что есть что. Программирование под Флиппер очень требовательно к ресурсам операционной системы, и нам нужно за этим следить. Это необходимо, чтобы наше приложение работало правильно и не вызывало зависаний и глюков. Поэтому, если мы хотим что-то добавить в приложение или интерфейс, то самостоятельно выделяем под это память, а когда удаляем — освобождаем.
Описываем функцию, которая будет выделять память под структуру нашего приложения и инициализировать его:
Example2App* example_2_app_alloc()
И функцию, которая освобождает занятую приложением память:
void example_2_app_free(Example2App* app)
В функции выделения памяти мы сначала выделяем память под структуру app
типа Example2App
для нашего приложения. Затем выделяем память для view_port
:
app->view_port = view_port_alloc();
Получаем указатель на текущий Gui
Флиппера — gui
. Перехватываем управление Gui
и говорим операционной системе, что у нашего приложения есть некий интерфейс c отрисовкой экрана view_port
и мы хотим его отобразить.
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
В функции освобождения памяти мы, соответственно, работаем в обратном направлении. Выключаем рендер нашего view_port
:
view_port_enabled_set(app->view_port, false);
Отключаем view_port
от Gui
и освобождаем память, занятую view_port
:
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
В конце мы передаём управление графическим интерфейсом от нашего приложения обратно операционной системе:
furi_record_close(RECORD_GUI);
Так как мы пишем на С и регулярно используем указатели, в ядре FURI существует крайне полезная для нас функция furi_assert()
, которая экстренно остановит работу приложения и выдаст сообщение, если мы вдруг передадим указатель на пустой участок памяти.
Точкой входа приложения на этот раз будет функция с именем example_2_app
:
int32_t example_2_app(void *p) {
UNUSED(p);
Example2App* app = example_2_app_alloc();
furi_delay_ms(10000);
example_2_app_free(app);
return 0;
}
При запуске приложения мы аллоцируем память для структуры приложения, перехватываем управление Gui
и рендерим наш ViewPort
. После этого мы простаиваем 10 секунд функцией furi_delay_ms()
, освобождаем все занятые ресурсы, передаём управление Gui
операционной системе и завершаем работу приложения.
Кроме того, у нас получился хороший шаблон с выделением/освобождением памяти для будущих приложений.
Иконку для приложения оставляем прежней.
Вносим правки в файл манифеста приложения application.fam
. Здесь всё остаётся прежним, за исключением нового параметра requires
. В этом параметре мы указываем, что для работы нашему приложению нужен сервис, отвечающий за графические интерфейсы gui
.
App(
appid="example_2",
name="Example 2 application",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_2_app",
cdefines=["APP_EXAMPLE_2"],
requires=[
"gui",
],
stack_size=1 * 1024,
order=90,
fap_icon="emoji_smile_icon_10x10px.png",
fap_category="Misc",
)
Собираем новое приложение:
./fbt fap_example_2
Используя программу qFlipper, перенесём новое FAP-приложение из папки build
на SD-карту в папку /apps/Misc
. Или используем терминал и команду ./fbt fap_deploy
.
Находим новое приложение в списке и запускаем его:
Сейчас для нашего ViewPort
не описаны конкретные функции отрисовки графических объектов. Программа приложения просто отобразит пустой графический интерфейс в течение 10 секунд и завершит работу.
Пример 3. Текст и изображения
Добавим в интерфейс нашего приложения какие-нибудь графические объекты. Некоторые графические объекты могут иметь довольно сложную реализацию, например: меню, выпадающие списки, файловые браузеры, различные формы ввода. Мы же начнём с простых объектов.
Снова создадим копию предыдущего приложения, но теперь под именем example_3
.
Для рендера графических объектов создадим в исходном файле example_3_app.c
callback-функцию отрисовки example_3_app_draw_callback
:
static void example_3_app_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
}
Callback-функция имеет определённую сигнатуру и два аргумента canvas
, то есть «холст», на котором мы будем рисовать, и контекст ctx
. Контекстом могут быть другие данные, в зависимости от которых рендерится canvas
. Пока что мы оставим контекст неиспользованным, то есть UNUSED
.
Первым делом перед рендером очистим наш экран:
canvas_clear(canvas);
Графические объекты размещаются на экране согласно системе координат. Экран Флиппера имеет разрешение 128×64, а начало координат находится в левом верхнем углу:
Добавим текст в интерфейс нашего приложения.
Подключим заголовочный файл с графическими текстовыми элементами gui/elements.h
. Текст отрисовывается функцией canvas_draw_str()
с указанием координаты (x; y) исходной точки текста и, собственно, самой строки. Возможен выбор шрифта для текста. Шрифт устанавливается функцией canvas_set_font()
. Шрифт может быть главным — FontPrimary
(высота 8 пикселей), второстепенным — FontSecondary
(высота 7 пикселей), FontKeyboard
или FontBigNumbers
. При желании вы сможете создать и собственный шрифт.
Например, напишем главным шрифтом строку «This is an example app!» вверху экрана и примерно по центру, по координатам (4; 8). По умолчанию начало координат текста находится в левом нижнем углу.
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 8, "This is an example app!");
В заголовочном файле gui/elements.h
можно найти различные имплементации для отрисовки простых элементов, скроллбаров, кнопок или выравнивания текста.
Например, снизу от нашей первой надписи разместим длинный двухстрочный текст «Some long long long long aligned multiline text», написанный второстепенным шрифтом и автоматически выровненный по верхней и правой границам. Для этого воспользуемся функцией elements_multiline_text_aligned()
:
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 15, AlignRight, AlignTop, "Some long long long long n aligned multiline text");
Теперь разберёмся, как вывести на экран картинку.
Для эксперимента мы нарисовали чёрно-белый логотип Амперки в PNG разрешением в 128×35 пикселей:
В папке с вашим приложением создайте специальную директорию для хранения изображений. Например, мы назвали свою папку images
. В данную папку поместите все изображения, которые планируете выводить на экран. Мы назвали нашу картинку amperka_ru_logo_128x35px.png
и поместили её в созданную папку images
:
В манифесте приложения application.fam
добавляем новый параметр fap_icon_assets
с путём до директории с изображениями:
App(
appid="example_3",
name="Example 3 application",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_3_app",
cdefines=["APP_EXAMPLE_3"],
requires=[
"gui",
],
stack_size=1 * 1024,
order=90,
fap_icon="emoji_smile_icon_10x10px.png",
fap_category="Misc",
fap_icon_assets="images",
)
Теперь при сборке приложения все изображения из папки images
будут переведены в код, а сам код будет сгенерирован в специальном заголовочном файле с именем {APPID}_icons.h
, где {APPID}
— это ID, указанный в .fam
-файле манифеста приложения.
Наше приложение имеет ID example_3
, значит заголовочный файл получит имя example_3_icons.h
. Добавим данный файл в заголовок приложения:
#include "example_3_icons.h"
Теперь мы можем получить указатель на область памяти, где хранится наше изображение в виде массива байтов. Имя указателя будет соответствовать имени самого файла изображения, но с приставкой I_ИМЯ_ВАШЕГО_ФАЙЛА
. Для нашей картинки с именем amperka_ru_logo_128x35px.png
имя указателя будет I_amperka_ru_logo_128x35px
.
Теперь, имея адрес изображения, мы можем отрендерить его на экране функцией canvas_draw_icon()
. Выведем изображение внизу экрана по координатам (0; 29):
canvas_draw_icon(canvas, 0, 29, &I_amperka_ru_logo_128x35px);
Пока что хватит графических элементов. Результат должен выглядеть следующим образом:
Наша callback-функция отрисовки готова, и её необходимо привязать к структуре ViewPort
нашего приложения. Это нужно сделать в функции инициализации нашего приложения сразу после выделения памяти под ViewPort
. В качестве контекста мы ничего не отправляем (NULL
). После привязки первый раз callback-функция будет выполнена автоматически.
view_port_draw_callback_set(app->view_port, example_3_app_draw_callback, NULL);
Заголовочный файл нашего третьего приложения не изменился, за исключением имени приложения и подключаемого заголовка с изображениями.
Код example_3_app.h
:
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include "example_3_icons.h"
struct Example3App {
Gui* gui;
ViewPort* view_port;
};
typedef struct Example3App Example3App;
Код example_3_app.с
:
#include "example_3_app.h"
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
static void example_3_app_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
canvas_draw_icon(canvas, 0, 29, &I_amperka_ru_logo_128x35px);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 8, "This is an example app!");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 15, AlignRight, AlignTop, "Some long long long long n aligned multiline text");
}
Example3App* example_3_app_alloc() {
Example3App* app = malloc(sizeof(Example3App));
app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, example_3_app_draw_callback, NULL);
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
void example_3_app_free(Example3App* app) {
furi_assert(app);
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
}
int32_t example_3_app(void *p) {
UNUSED(p);
Example3App* app = example_3_app_alloc();
furi_delay_ms(10000);
example_3_app_free(app);
return 0;
}
Главную функцию приложения example_3_app
оставляем как есть. Приложение снова отработает 10 секунд и завершит свою работу, но на этот раз у нас будет графика.
Собираем новое приложение:
./fbt fap_example_3
Используя программу qFlipper, перенесём новое FAP-приложение из папки build
на SD-карту в папку /apps/Misc
. Или используем терминал и команду ./fbt fap_deploy
.
Находим новое приложение в списке и запускаем его:
Взглянем на результат:
Пример 4. Кнопки
Разберёмся, как обрабатывать нажатия кнопок на Флиппере.
Продолжаем предыдущее приложение под новым именем example_4
.
Для использования кнопок нам понадобится очередь сообщений (MessageQueue) и ещё одна callback-функция для обработки этой очереди.
Зачем нужна очередь сообщений? Поскольку за нас работает операционная система, то callback-функция ввода с кнопок, как и callback-функция рендера графики выполняется в контексте других потоков ОС Flipper Zero, а не в потоке нашего приложения. Поток, отвечающий за нажатие кнопок, не может вызвать какую-либо функцию напрямую из нашего приложения. Но потоки могут отправлять друг другу сообщения в любой момент выполнения, этим мы и воспользуемся.
Добавляем в структуру нашего приложения Example4App
очередь event_queue
типа FuriMessageQueue
:
struct Example4App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
};
В функции инициализации приложения example_4_app_alloc()
выделяем память под новую очередь.
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
Наша очередь будет на 8 сообщений типа InputEvent
, который отвечает за нажатие клавиш. Структуры данных и функции для работы с сообщениями от кнопок находятся в заголовочном файле input/input.h
.
В функции освобождения памяти нашего приложения, соответственно, освобождаем занятую очередью память.
furi_message_queue_free(app->event_queue);
Теперь создадим callback-функцию для обработки этой очереди.
static void example_4_app_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
Как и в случае с callback-функцией отрисовки, эта имеет два аргумента: input_event
и контекст ctx
. Событие input_event
сигнализирует о каком-либо взаимодействии с кнопками. В отличие от функции отрисовки, в этот раз контекст не пустой. Мы положили в контекст очередь сообщений нашего приложения. Таким образом актуальные события ввода с кнопок окажутся в потоке нашего приложения.
Привязываем новую callback-функцию к графическому интерфейсу ViewPort
нашего приложения. В качестве контекста указываем очередь сообщений приложения event_queue
:
view_port_input_callback_set(app->view_port, example_4_app_input_callback, app->event_queue);
Готово! Теперь информация о состоянии кнопок находится в нашем распоряжении, и её можно обработать.
Сейчас наше приложение работает 10 секунд, а затем завершает работу. Давайте сделаем так, чтобы приложение закрывалось не автоматически, а при нажатии на клавишу «Назад» на Флиппере.
Обработчик сообщений из очереди напишем в главной функции нашего приложения (точке входа) в бесконечном цикле:
while (1) {
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.type == InputTypePress) {
if (event.key == InputKeyBack)
break;
}
}
}
Пока очередь пуста, крутимся в бесконечном цикле. Если в нашей очереди есть событие (FuriStatusOk
), нажалась кнопка (InputTypePress
), и это была кнопка «Назад» (InputKeyBack
), то выходим из цикла и, как следствие, движемся к завершению работы приложения.
Код example_4_app.h
:
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include "example_4_icons.h"
struct Example4App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
};
typedef struct Example4App Example4App;
Код example_4_app.с
:
#include "example_4_app.h"
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <input/input.h>
static void example_4_app_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
canvas_draw_icon(canvas, 0, 29, &I_amperka_ru_logo_128x35px);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 8, "This is an example app!");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 15, AlignRight, AlignTop, "Some long long long long n aligned multiline text");
}
static void example_4_app_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
Example4App* example_4_app_alloc() {
Example4App* app = malloc(sizeof(Example4App));
app->view_port = view_port_alloc();
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
view_port_draw_callback_set(app->view_port, example_4_app_draw_callback, NULL);
view_port_input_callback_set(app->view_port, example_4_app_input_callback, app->event_queue);
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
void example_4_app_free(Example4App* app) {
furi_assert(app);
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_message_queue_free(app->event_queue);
furi_record_close(RECORD_GUI);
}
int32_t example_4_app(void *p) {
UNUSED(p);
Example4App* app = example_4_app_alloc();
InputEvent event;
while (1) {
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.type == InputTypePress) {
if (event.key == InputKeyBack)
break;
}
}
}
example_4_app_free(app);
return 0;
}
Собираем новое приложение и переносим его на Flipper Zero.
./fbt fap_example_4
./fbt fap_deploy
Взглянем на результат:
Изменим наше приложение и добавим ещё пару кнопок.
Например, будем по-разному рендерить изображение на экране в зависимости от нажатой кнопки.
Пусть у нас будет три состояния интерфейса: рендерится только текст, рендерится только изображение, либо рендерится и то, и другое. Переключаться между этими режимами будем при долгом нажатии на кнопки «Влево» или «Вправо».
Введём в структуру нашего приложения переменную, которая будет отвечать за то, какой режим рендерится в данный момент. Назовем её draw_mode
.
typedef enum {
DRAW_ALL,
DRAW_ONLY_TEXT,
DRAW_ONLY_PICTURES,
TOTAL_DRAW_MODES = 3,
} DrawMode;
struct Example4App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
DrawMode draw_mode;
};
Чтобы draw_mode
был доступен для нашей callback-функции рендера экрана, передадим в неё указатель на всю структуру приложения app
в качестве контекста:
view_port_draw_callback_set(app->view_port, example_4_app_draw_callback, app);
Теперь изменим саму callback-функцию рендера. Пусть разные графические объекты рендерятся в зависимости от текущего значения draw_mode
:
static void example_4_app_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
Example4App* app = ctx;
canvas_clear(canvas);
DrawMode mode = app->draw_mode;
if (mode == DRAW_ONLY_PICTURES || mode == DRAW_ALL)
canvas_draw_icon(canvas, 0, 29, &I_amperka_ru_logo_128x35px);
if (mode == DRAW_ONLY_TEXT|| mode == DRAW_ALL) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 8, "This is an example app!");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 15, AlignRight, AlignTop, "Some long long long long n aligned multiline text");
}
}
В заключении обработаем новые события кнопок в бесконечном цикле главной функции приложения:
while (1) {
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.type == InputTypePress) {
if (event.key == InputKeyBack)
break;
} else if (event.type == InputTypeLong) {
DrawMode mode = app->draw_mode;
if (event.key == InputKeyLeft)
app->draw_mode = (mode - 1 + TOTAL_DRAW_MODES) % TOTAL_DRAW_MODES;
else if (event.key == InputKeyRight)
app->draw_mode = (mode + 1) % TOTAL_DRAW_MODES;
view_port_update(app->view_port);
}
}
}
Теперь если будет зарегистрировано событие длительного нажатия (InputTypeLong
) кнопки «Влево» (InputKeyLeft
) или «Вправо» (InputKeyRight
), наш режим отрисовки app->draw_mode
будет меняться от 0 до TOTAL_DRAW_MODES
.
Функция view_port_update()
запускает ререндер нашего интерфейса view_port
. Функция не обязательная, операционная система сама производит ререндер раз в несколько миллисекунд, но мы можем форсировать это функцией.
Соберём обновлённое приложение, загрузим его на Флиппер, запустим и посмотрим результат:
Пример 5. Оповещения
Помимо дисплея Flipper Zero имеет и другой способ сообщать нам о происходящих в программе событиях — оповещения (Notifications). Нам доступно управление следующими встроенными девайсами:
- RGB-cветодиод.
- Вибромотор.
- Пьезопищалка.
Продолжаем предыдущее приложение под новым именем example_5
.
За оповещения в операционной системе Flipper Zero отвечает отдельный поток, и мы не можем вызывать его функции из нашего приложения напрямую. Но мы можем отсылать в этот поток сообщения — NotificationMessage
. Из этих сообщений формируются последовательности NotificationSequence
, которые уже непосредственно отправляются в поток.
Описание структур сообщений и их последовательностей находится в заголовочном файле notification/notification_messages.h
, добавляем его в наше приложение.
В главной структуре указываем, что наше приложение собирается использовать оповещения NotificationApp
:
struct Example5App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
NotificationApp* notifications;
DrawMode draw_mode;
};
В функции инициализации приложения example_5_app_alloc()
перехватываем управление оповещениями:
app->notifications = furi_record_open(RECORD_NOTIFICATION);
А в функции освобождения памяти приложения example_5_app_free
отдаём управление обратно:
furi_record_close(RECORD_NOTIFICATION);
Можно использовать и готовые последовательности сообщений, а можно сделать их самостоятельно, это несложно. Вы можете изучить соответствующий заголовочный файл и какие последовательности там доступны.
Создадим нашу последовательность сообщений для управления RGB-светодиодом. Назовем её example_led_sequence
и разместим в заголовке нашего приложения.
Пусть светодиод мигнёт фиолетовым цветом RGB(255, 0, 255) три раза с интервалом 500 мс, а затем погаснет. Сообщение будет выглядеть следующим образом:
const NotificationSequence example_led_sequence = {
&message_red_255,
&message_blue_255,
&message_delay_500,
&message_red_0,
&message_blue_0,
&message_delay_500,
&message_red_255,
&message_blue_255,
&message_delay_500,
&message_red_0,
&message_blue_0,
&message_delay_500,
&message_red_255,
&message_blue_255,
&message_delay_500,
&message_red_0,
&message_blue_0,
NULL,
};
Последовательности сообщений для управления вибромотором составляются схожим образом. Создадим последовательность для вибромотора с именем example_vibro_sequence
и разместим её в заголовке.
Пусть по сигналу наш вибромотор включится на 3 секунды, а затем выключится. Последовательность сообщений будет выглядеть так:
const NotificationSequence example_vibro_sequence = {
&message_vibro_on,
&message_do_not_reset,
&message_delay_1000,
&message_delay_1000,
&message_delay_1000,
&message_vibro_off,
NULL,
};
По умолчанию максимально долгая описанная задержка составляет 1 секунду, поэтому мы три раза использовали сообщение message_delay_1000
.
Теперь создадим последовательность сообщений для пьезодинамика. Назовем её example_sound_sequence
.
Здесь нам уже доступна полная MIDI-клавиатура прямо из коробки! Описание всех нот и их частот можно посмотреть в заголовочном файле notification_messages_notes.h
.
Добавим в наш Флиппер классическую мелодию звонка телефонов Nokia:
Последовательность сообщений с данной мелодией выглядит так:
const NotificationSequence example_sound_sequence = {
&message_note_e5,
&message_delay_100,
&message_note_d5,
&message_delay_100,
&message_note_fs4,
&message_delay_250,
&message_note_gs4,
&message_delay_250,
&message_note_cs5,
&message_delay_100,
&message_note_b4,
&message_delay_100,
&message_note_d4,
&message_delay_250,
&message_note_e4,
&message_delay_250,
&message_note_b4,
&message_delay_100,
&message_note_a4,
&message_delay_100,
&message_note_cs4,
&message_delay_250,
&message_note_e4,
&message_delay_250,
&message_note_a4,
&message_delay_500,
NULL,
};
Отлично! Теперь нужно решить, когда запускать данные оповещения. Пусть при нажатии на кнопку «Вверх» (InputKeyUp
) включится светодиод, при нажатии на кнопку «Вниз» (InputKeyDown
) включится вибромотор, а при нажатии на кнопку «Ок» (InputKeyOk
) заиграет мелодия.
Добавляем обработку для новых кнопок в бесконечный цикл в главной функции нашего приложения example_5_app()
:
while (1) {
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.type == InputTypePress) {
if (event.key == InputKeyBack)
break;
else if (event.key == InputKeyUp)
notification_message(app->notifications, &example_led_sequence);
else if (event.key == InputKeyDown)
notification_message(app->notifications, &example_vibro_sequence);
else if (event.key == InputKeyOk)
notification_message(app->notifications, &example_sound_sequence);
} else if (event.type == InputTypeLong) {
DrawMode mode = app->draw_mode;
if (event.key == InputKeyLeft)
app->draw_mode = (mode - 1 + TOTAL_DRAW_MODES) % TOTAL_DRAW_MODES;
else if (event.key == InputKeyRight)
app->draw_mode = (mode + 1) % TOTAL_DRAW_MODES;
view_port_update(app->view_port);
}
}
}
Отправка сообщений осуществляется функцией notification_message()
с указанием соответствующей последовательности.
Собираем новое приложение:
./fbt fap_example_5
Используя программу qFlipper, переносим новый FAP-файл из папки build
на SD-карту в папку /apps/Misc
. Или загружаем приложение командой ./fbt fap_deploy
.
Запускаем приложение:
Смотрим на результат:
Пример 6. GPIO
На Flipper Zero 18 контактов GPIO, среди которых есть как пины питания, так и пины ввода-вывода. Логическое напряжение питания — 3,3 В, и пины нетолерантны к 5 В (за исключением пина iButton). По сути, пины Флиппера соответствуют пинам установленного в нём микроконтроллера STM32WB55 и обладают теми же настраиваемыми альтернативными функциями (ADC, USART, SPI и др.).
Распиновка Флиппера:
Подробное назначение пинов можно посмотреть в заголовочном файле furi_hal_resources.h
. FURI HAL — специальный HAL Flipper Zero, который призван упростить для нас взаимодействие с железом.
В FURI HAL GPIO-структура имеет имя GpioPin
. Без разборки Флиппера на куски нам доступны:
const GpioPin gpio_ext_pc0
— порт GPIOC, пин 0 (номер 16 на Флиппере).const GpioPin gpio_ext_pc1
— порт GPIOC, пин 1 (номер 15 на Флиппере).const GpioPin gpio_ext_pc3
— порт GPIOC, пин 3 (номер 7 на Флиппере).const GpioPin gpio_ext_pb2
— порт GPIOB, пин 2 (номер 6 на Флиппере).const GpioPin gpio_ext_pb3
— порт GPIOB, пин 3 (номер 5 на Флиппере).const GpioPin gpio_ext_pa4
— порт GPIOA, пин 4 (номер 4 на Флиппере).const GpioPin gpio_ext_pa6
— порт GPIOA, пин 6 (номер 3 на Флиппере).const GpioPin gpio_ext_pa7
— порт GPIOA, пин 7 (номер 2 на Флиппере).const GpioPin ibutton_gpio
— порт GPIOB, пин 14 (номер 17 на Флиппере).
Также доступны пины с функцией USART по умолчанию:
const GpioPin gpio_usart_tx
— порт GPIOB, пин 6 (номер 13 на Флиппере).const GpioPin gpio_usart_rx
— порт GPIOB, пин 7 (номер 14 на Флиппере).
Ещё есть пины интерфейса SWD (Serial Wire Debug) для отладки, маркированные на корпусе как SIO, SWC. Все остальные пины микроконтроллера используются для управления начинкой Flipper Zero: дисплеем, кнопками, USB, NFC, I²C, SPI и т. д.
Сделаем приложение, через которое мы сможем управлять GPIO. Сперва сделаем простой DigitalWrite
, DigitalRead
. Читать значение будем с пина А6, а писать значение в пин А7.
Подключим к Флипперу простую кнопку к пину А6 и светодиод к пину А7. Максимальный ток на пине — 20 мА, для светодиода хватит. Питание берём с шины 3,3 В. Установим светодиод и кнопку на макетную плату:
Назовём новое приложение example_6
и сделаем его на основе нашего предыдущего примера номер 4. Предварительно уберём из приложения всё, что касается оповещений, рендера графических элементов и обработки кнопок, чтобы остался пустой интерфейс.
Для управления GPIO нам нужен HAL Флиппера. Подключаем файл furi_hal.h
в заголовок нашего приложения.
В главной структуре Example6App
нашего приложения создадим два пина для входа и выхода: input_pin
, output_pin
и две булевы переменные для хранения текущих значений на этих пинах: input_value
, output_value
.
Наш заголовочный файл принимает следующий вид:
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
struct Example6App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
const GpioPin* input_pin;
const GpioPin* output_pin;
bool input_value;
bool output_value;
};
typedef struct Example6App Example6App;
В функции инициализации приложения example_6_app_alloc()
задаём номера пинов. Функцией furi_hal_gpio_init()
инициалзируем пины. Для ввода устанавливаем режим GpioModeInput
и включаем подтяжку GpioPullUp
, а для вывода режим GpioModeOutputPushPull
и отключаем подтяжку GpioPullNo
. Оба пина опрашиваются на максимальной скорости GpioSpeedVeryHigh
:
app->input_pin = &gpio_ext_pa6;
app->output_pin = &gpio_ext_pa7;
furi_hal_gpio_init(app->input_pin, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_init(app->output_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
В главной функции приложения example_6_app()
(и по совместительству в нашей точке входа) считываем и отправляем соответствующие значения в бесконечном цикле:
furi_hal_gpio_write(app->output_pin, app->output_value);
app->input_value = furi_hal_gpio_read(app->input_pin);
Пусть выходное значение зависит от состояния кнопки «Ок» Флиппера. Кнопка нажата — сигнал есть, отжата — сигнала нет. Как и прежде, клавишей «Назад» завершаем работу приложения. Добавим соответствующую обработку кнопки «Ок» в бесконечный цикл нашей программы:
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.key == InputKeyBack) {
if (event.type == InputTypePress)
break;
} else if (event.key == InputKeyOk) {
if (event.type == InputTypePress)
app->output_value = true;
else if (event.type == InputTypeRelease)
app->output_value = false;
}
Наконец, добавим немного графики в callback-функцию отрисовки интерфейса. Просто выведем текстом текущее входное и выходное значение. Для отрисовки больших цифр можно использовать шрифт FontBigNumbers
. В качестве контекста передаём в функцию отрисовки главную структуру приложения.
static void example_6_app_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
Example6App* app = ctx;
canvas_clear(canvas);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 32, 17, AlignCenter, AlignTop, "Output PA7:");
elements_multiline_text_aligned(canvas, 96, 17, AlignCenter, AlignTop, "Input PA6:");
canvas_set_font(canvas, FontBigNumbers);
elements_multiline_text_aligned(canvas, 32, 32, AlignCenter, AlignTop, app->output_value ? "1" : "0");
elements_multiline_text_aligned(canvas, 96, 32, AlignCenter, AlignTop, app->input_value ? "1" : "0");
}
Интерфейс будет выглядеть так:
Собираем новое приложение и загружаем его на Флиппер:
./fbt fap_example_6
./fbt fap_deploy
Протестируем приложение на Флиппере:
PWM и ADC
С генерацией ШИМ-сигналов всё обстоит намного сложнее. Здесь уже не обойтись одной-двумя функциями из FURI HAL, а сам код сильно разрастается.
В прошивке Flipper Zero уже есть приложение Signal Generator для генерации ШИМ-сигнала на пинах PA7 и PA4. Вы можете самостоятельно изучить исходный код для генерации ШИМ в директории applications/plugins/signal_generator/ и реплицировать его в ваше приложение.
А вот официальной документации на чтение аналоговых сигналов пока нет. Кроме этого альтернативные функции аналого-цифрового преобразователя на пинах ещё не имплементированы в FURI HAL. Однако сам ADC на микроконтроллере STM32 и его возможности никуда от нас не делись.
На просторах интернета мы нашли пример использования ADC. Мы разместили в репозитории с примерами flipperzero-examples приложение adc_example
, которое cчитывает аналоговое значение с пина PC3 и выводит его на экран. Код ещё нуждается в доработке, и вы можете использовать его в своих приложениях, однако мы советуем дождаться официальной документации и примеров.
Опорным напряжением является выборочно или 2.5 В или 2.048 В. Подключив к флипперу потенциометр и взяв питание с пина 3.3 В понадобится простой делитель напряжения в пределах тысячи Ом. Разрешение АЦП — 12 бит.
Читаем напряжение от 0 до 2.5В на пине PC3 и меняем его потенциометром:
Заключение
На этом мы заканчиваем базовое знакомство с пользовательскими приложениями для Flipper Zero.
Покопавшись в документации, нам удалось зайти подальше банального «Hello, world!» и написать несколько приложений для примера работы с GUI, кнопками и встроенной периферией Флиппера — RGB-светодиодом, вибромотором и баззером.
Ждём обновления официальной документации гаджета и надеемся, что наши примеры помогут вам в создании своих приложений для Flipper Zero!
Полезные ссылки
- Гаджет Flipper Zero в каталоге Амперки
- Посмотреть все проекты
Не так давно, примерно год назад я наконец получил свою долгожданную посылку. Flipper Zero. Для тех кто не знает, Flipper Zero — это небольшое устройство, которое позволяет заниматься всякими весёлыми затеями в жизни. Веселье начинается с Sub-1 GHz антенны и продолжается микро-скриптами для ПК. Интересующихся, прошу под кат.
Ваше знакомство буду выполнять в хронологическом порядке разделов в меню самого флипера.
Приложения
Я бы не стал выбирать самым основным пунктом данный, так как задача флипера, на мой взгляд, работа с физическим миром вокруг нас, а не программные приколюхи внутри устройства. Как устройство по работе с приложениями — флипер не лучший вариант. Я бы рекомендовал использовать Raspberry Pi или на худой конец Arduino.
Тем не менее, внутри достаточно много приложений уже написанных прекрасными пользователями интернета. Скажу сразу, я установил себе кастомную прошивку DarkFlipper. Об этом позже.
Из предустановок — 8-битные игры типа DOOM, надстройки для распиновки GPIO, аудио-проигрыватель, калькуляторы и доп. приложения и доп. приложения для антенны в 1 ГГц.
Повторюсь, данный раздел совсем не самый интересный.
Sub-GHz
Тут всё просто. В диапазоне от 300 до 928 МГц в AM и FM модификациях можно сканировать все сырые и не сырые данные.
Для тех, кто не очень понимает зачем, могу привести простой пример — коды шлагбаумов. В данном диапазоне передаются данные в шлагбаумы. Как пример, я смог скопировать свой ключ от шлагбаума. Любых шлагбаумов и ворот.
Как это можно реализовать? Расскажу свою историю. Я живу в центре города, где есть такое поверие: «нет шлагбаума — нет парковки». Живя в уникальном, не побоюсь этого слова, доме, где в один небольшой двор размером с небольшой «плевочек» с детской площадкой и местом для парковки примерно на 30 машин установили аж 3 шлагбаума. Три, Карл!
Боль данного пункта заключается в том, что шлагбаумы были установлены трёх разных фирм, трёх разных диапазонов, да ещё и двух типов закрытия. Будучи законопослушным гражданином, я решил запросить данный пульт для парковки своего авто. Позвонив по номерам, которые были упорно замазаны на шлагбауме, я попал не много, не мало в город Реутов (сам нахожусь я в Москве). Там, мне сказали, что сделать можно, но без физического ключа — ничего не получится. На вопрос где его добыть, я получил неутешительный ответ: «Не знаю». Месяц поисков и чудом я выяснил у соседей, что данный ключ находится у «старшей по подъезду» (рубрика «Центр Москвы»). Старшая по подъезду переехала, а подъезд остался. Не ленясь, я дозвонился до данной хозяйки медного шлагбаума. Выяснилось, что данные три шлагбаума были установлены до закона о централизованной установке шлагбаумов и каждый подъезд поставил то что хочет и то как хочет данный аппарат. Каждый шлагбаум — совместная собственность, где все бумаги носит при себе старшая по подъезду. Цирк продолжился. Требования для получения заветного ключа были следующие:
-
Заплатить 7 тысяч рублей за вызов бригады (оно и понятно, из Реутова-то ехать)
-
Купить резидентную парковку для бесплатной парковки в районе дома, что стоило 3 тысячи рублей в год. Она у меня была, так как ставить машину у дома я не мог.
-
Отдать «на сканирование в архив» старшей по подъезду, проживающей в другом доме все документы на машину и меня.
Если первые два пункта были хотя бы понятны и хоть на сколько-то разумны (хотя сейчас резидентной парковки сейчас у меня нет, так как ставя машину в дворовой территории, она не нужна), то последний пункт меня возмутил. И я бы не против был бы и купить за 10 тысяч «ключик от домика», даже был бы не против купить данную подписку у женщины, которая поставила данный шлагбаум, ведь благодаря ней, во дворе уменьшилось количество такси и сторонних машин. Но вот отдавать документы куда-то в другой дом незнакомому человеку, как-то не хотелось. Я любезно отказался от данного предложения.
Шаг в сторону парковки подарила мне компания Прайм Пульт. Вопрос решился просто.
Кто не понял — это ключ, который копирует сигнал. Нужно было просто понять в каком диапазоне работает ключ. Цена вопроса за 10 ключей была 3 тысячи рублей.
Зная фирму производителя (в моём случае Nice Flor-S), можно узнать герцовку данного производителя (в моём случае 433,92 МГц). Далее — всё просто.
Флиппер же помог мне сделать то же самое, но не в отдельном шлагбауме, а на всех трёх простым чтением сырых (RAW) данных. Также, в кастомной прошивке есть подбор ключей по диапазону (приложение находится в отдельной папке в меню Applications).
125 кГц RFID
Переводя с русского на русский, данный раздел нужен для чтения ключей и смарт карт доступа.
Данные ключи сейчас активно используются в новостройках для открытия балконов или (простите) комнат для мусора. В многих учреждениях, открывается турникеты для входа в помещения.
Я работаю в университете и у нас такие же ключи доступа. Как прочитать её — просто. Нажать кнопку «Read» и положить её под флипер. Примерно так:
NFC
Аналог ключей, но с большей защитой. Бывает два типа защиты — PSK. В нём — всё также как и в 125 кГц ключах.
Второй вариант — ASK.
Тут нужно сделать 2 пункта — записать ключ и считыватель. Мужчина на видео по ASK — создатель флипера Павел Жовнер @zhovner
Инфракрасный порт
Инфракрасный излучатель позволяет управлять всем с ИК-приёмником. Тут справится даже ребёнок. Выбираем универсальный пульт:
Затем, выбираем конкретный тип прибора:
Далее, переворачиваем флипер, так чтобы сверху оказался ИК-передатчик и после нажатия на кнопку — отправляются все типы ИК-сигналов.
GPIO
Жаба меня задушила купить Wi-Fi модуль для флипера. По нему не смогу ничего рассказать. Спасибо YouTube за контент.
Как дополнение, распиновка:
iButton
Под надписью iButton спрятался сканнер ключей. Работает всё просто. Сканируем ключ на + и — к разным выпирающим точкам:
Далее, эмулируем их же и подносим к домофону:
Bad USB
Мой любимый раздел. По умолчанию, доступно всего 2 демо для Mac и Windows. Так как сижу на винде, скриншот вывода:
Конечно, этим всё не заканчивается. Далеко не заканчивается. Из моих любимых уже готовых скриптов под винду на PowerShell — разработка от Jakoby. Там много различных вариаций использования от простой смены обоев, до Wi-Fi стиллера.
U2F
Двухфакторная аутентификация возможна и на флипере. Всё что нужно — подключить его без утилиты qFlipper.
К слову про qFlipper. Приложение можно скачать здесь. Смысл приложения прост — работа со сторонним устройством (ПК или же телефоном). Также есть работа через блютуз.
Часы и настройки
Тут что-то добавить сложно. Часы необходимы, если хотите узнать время, а в настройках можно доработать ваш флипер.
Прошивки
Из моих фаворитов кастомных прошивок — DarkFlippers.
На мой взгляд, данная прошивка самая стабильная из кастомных. Изменений довольно много. Все они описаны по ссылке на репозиторий гитхаба.
Второй крутой вариант — вариант от TalkingSasquach. Там также есть крутые обои такого типа:
Где, что и как купить?
Сейчас с поставками флипера сложности. Я свой флипер получил спустя почти год после его официального выхода, как пользователь поддерживающий проект на кикстарте. В комплекте: коробочка, USB type-C, стикер и инструкция по быстрому старту.
Сам флипер, как сказано в официальном телеграмм-канале Павла, купить можно через Joom. Всё остальное (чехол, Wi-Fi модуль, плату) можно купить в Амперке.
А кому оно надо?
Как итог моего использования, могу сказать следующее (учтите, это только моё мнение): устройство прикольное, но нужно «понимающим» пользователям или тем, у кого много ключ-карт и всякого рода пультов.
В реальной жизни я использовал флипер порой и веселее включая кондиционеры и проекторы в кабинетах, где оно всё терялось. Ну и конечно, убивалка времени — классная. Лично для меня — устройство очень удобно и главное — приятно. Однако, есть два момента. Первый — я гик и кайфую с этого. Второй — купил я устройство в хорошие годы за 8 000 — 10 000 тысяч рублей (в зависимости от курса банка за евробаксы). Особые весельчаки взламывают лючки теслы.
Потенциал у устройства есть. Об этом потенциале говорит Линус Себастьян (канал Linus Tech Tips). По мнению Себастьяна, Flipper Zero — это на самом деле это один из самых универсальных хакерских инструментов, которые когда-либо появлялись на рынке.
Как всегда, выбор остаётся за вами. Я же пойду дальше юзать свой флипер и играться с новыми и новыми прошивками.
А вы как относитесь к флиперу?
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Как вам Flipper Zero?
18.76%
Купил. Топ за свои деньги!
94
8.38%
Купил. Круто, но хотелось бы подешевле
42
1.8%
Купил. Не доволен.
9
51.3%
Не покупал. Коплю и/или жду цену в 200 рублей.
257
19.76%
Не покупал и не буду
99
Проголосовал 501 пользователь.
Воздержались 92 пользователя.
Flipper Zero reading an NFC security key.
Adrian Kingsley-Hughes/ZDNET
I love my Flipper Zero, but what I hate is all the fake stuff that people looking for attention are uploading to TikTok. No, the Flipper Zero can’t change gas station signs, can’t clone credit and debit cards, and can’t (normally, anyway) turn off the displays at your favorite burger joint.
But that doesn’t mean that the Flipper Zero can’t do some very cool, and also very useful things.
Also: Flipper Zero can be used to crash iPhones running iOS 17, but there’s a way to foil the attack
Over the past few months, I’ve been making use of the suite of hardware tools that are built into the Flipper Zero. What’s built into this tiny toy-like tool?
There’s a sub-GHz wireless antenna that can capture and transmit wireless codes to operate wireless devices and access control systems, such as garage door remotes, boom barriers, IoT sensors, and even remote keyless systems.
RFID support allows it to read, store, and emulate a number of different RFID cards.
Also: Do RFID blocking cards actually work? My Flipper Zero revealed the truth
It can also read, write, store, and emulate NFC tags.
On the front, there’s a 1-Wire connector that can read and emulate iButton (aka DS1990A, CYFRAL, Touch Memory, or Dallas key) contact keys.
There’s also a built-in infrared transceiver that can both capture and transmit IR codes to control things like TVs.
Finally, on the top there are GPIO connectors that allow the Flipper Zero to connect to other gadgets in the real world.
Also: How to unlock the Flipper Zero’s true power
That’s a lot of features crammed into a tiny, $169 device.
But every week, I hear from buyers who are frustrated and disappointed because their Flipper Zero won’t do the things that it can seemingly do based on fake social media videos.
Here are some of the things I’ve been doing with my Flipper Zero over the past few weeks.
ZDNET RECOMMENDS
Flipper Zero
Flipper Zero is a portable multi-tool for pen-testers and geeks in a toy-like shell.
View at Flipper Zero store
Note: I’m running third-party software on my Flipper Zero, which gives me access to a bunch of additional features. But worry not, loading third-party software doesn’t invalidate your warranty and you can go back to the stock software easily at any time using the Flipper Zero app on a desktop, laptop, or mobile device..
The sub-GHz wireless antenna can pick up the signals from car key fobs (and can record them, although playing them back to modern cars won’t unlock them because of a feature called «rolling codes» that changes the code with each use).
Also: The best security keys you can buy (and how they work)
This is a handy way to test if the key is working and the battery is good.
NFC is everywhere nowadays, and the Flipper Zero allows you to work with this wireless protocol. It’s built into plastic cards and fobs, and used for all sorts of things, from unlocking hotel room doors to controlling barriers.
NFC can be read by the Flipper Zero. Many NFC cards can also be copied and cloned (this depends on the security used for the card, and I can’t give you any hard and fast rules as to which NFC devices can be cloned).
Also: The best VPN services (and how to choose the right one for you)
Note that while the Flipper Zero can read NFC cards and fobs, it cannot decode the card’s encrypted security code, also known as CSC, CVV, CVC, CAV, and a bunch of other three-letter abbreviations.
Flipper Zero cannot decode the card’s encrypted security code, so it cannot clone bank cards.
Adrian Kingsley-Hughes/ZDNET
Along with NFC, the Flipper Zero can read and clone RFID, including hotel cards (as in the picture, above).
Also: How RFID tags can make in-person clothes shopping less frustrating
RFID cards and tags can be locked to prevent them from being overwritten, but the Flipper Zero can bypass many of these mechanisms. Here, it is offering to unlock the card if I present it to a valid reader, allowing me to clone the card and unlock the door using both the card and the Flipper Zero.
I’ve used this to clone access cards and fobs for all sorts of buildings, and many times the staff — and sometimes owners of the buildings — were unaware of the fact that this trick was possible.
The Flipper Zero can unlock some RFID cards and tags.
Adrian Kingsley-Hughes/ZDNET
Yes, the Flipper Zero can bypass the security on some Sentry Safe electronic safes using an output from the GPIO.
This is definitely not something you want to be doing if it isn’t your safe or you don’t have permission, but it goes to show just how insecure the «complimentary» safes found in hotels, spas, and Airbnbs actually are.
The Flipper Zero has a built-in infrared module, and this in turn can be programmed to operate a wide range of devices, from TVs to AC units. Pretty much any consumer device that has an IR remote control interface can be controlled using the Flipper Zero.
Another cool use of this functionality is to test if infrared remote controls are working. Point the remote control at the Flipper Zero in the «read IR» mode and it will detect the signals.
Testing a dismantled infrared remote control.
Adrian Kingsley-Hughes/ZDNET
Flipper Zero can act as a BadUSB device, which means that when connected to a port it is seen as a Human Interface Device (HID), such as a keyboard.
Also: The best VPN services for iPhone and iPad (yes, you need to use one)
A BadUSB device can change system settings, open backdoors, retrieve data, initiate reverse shells, or do anything that can be achieved with physical access. These tasks are completed by using a set of commands written in the Rubber Ducky Scripting Language, also known as DuckyScript.
Flipper Zero opens a browser and navigates to a webpage with no user input.
Adrian Kingsley-Hughes/ZDNET
The Flipper Zero can use the GPIO to output electrical signals and act as a signal generator. I last used this to simulate an antilock braking system module on a vehicle to confirm that all the wiring and computers in the car were working.
GPIO pinout.
Adrian Kingsley-Hughes/ZDNET
Now, I don’t recommend you do this unless your «victims» give you permission, because it can annoy people and is very likely to be illegal in most places, but the ease with which a Flipper Zero can crash an iPhone or carry out a denial of service (DoS) attack on Android devices is scary.
With a few taps, the Flipper Zero can flood devices within a 30-foot radius with popups, making them near impossible to use. And so far, the only defense against this technique is to turn off Bluetooth.
Featured reviews
Thank you so much for buying Flipper Zero! We’re excited for you to explore all the possibilities this device offers and can’t wait to hear about the amazing things you’ll accomplish with it. Enjoy your new Flipper Zero!
Powering on
Power on your Flipper Zero by pressing and holding the %back%BACK button for three seconds.
Rebooting
If your Flipper Zero freezes while in use, press and hold the %left%LEFT and %back%BACK buttons to reboot it.
Inserting a microSD card
The microSD card stores various types of data, such as keys, cards, remotes, databases, and more. Flipper Zero supports microSD cards of up to 256 GB, but a 4 GB microSD card is sufficient to store all the necessary data. Keep in mind that the process of mounting a microSD card with a high storage capacity may require additional time.
Unlike modern smartphones and computers that use a high-speed SDIO interface, Flipper Zero uses a slower energy-efficient SPI interface. Despite being slower than SDIO, Flipper Zero’s SPI interface can read data at almost 400 KB per second, which is sufficient for the device’s tasks.
If the card mounting failed
Not all microSD cards function immediately after insertion
In case you see the message above on the screen after inserting the microSD card, it may be due to one of the following reasons:
-
The microSD card doesn’t have an SPI interface. -> Try using a different microSD card.
-
The microSD card has a file system other than FAT12, FAT16, FAT32, or exFAT. -> Format the microSD card.
-
There is no file system on the microSD card. -> Format the microSD card.
-
The microSD card is damaged. -> Try using a different microSD card.
Updating the firmware
The Flipper Zero firmware is under active development and changes frequently. We recommend updating the firmware of your Flipper Zero to the latest version right away.
With Flipper Mobile App, you can update your Flipper Zero via Bluetooth. The application is available on iOS and Android:
Connecting to Flipper Zero
After you downloaded Flipper Mobile App and activated Bluetooth on your phone, connect the mobile application to your Flipper Zero:
Activate Bluetooth on your Flipper Zero by following these steps:
1) Go to Main Menu -> Settings -> Bluetooth.
2) Set Bluetooth to ON.
In Flipper Mobile App, tap Connect.
On the next page, next to the detected Flipper Zero’s name, tap Connect.
You can connect Flipper Zero to your phone via Bluetooth
In Flipper Mobile App, enter the pairing code displayed on the Flipper Zero screen.
Tap Pair to finalize pairing.
Updating Flipper Zero via Flipper Mobile App
To update your Flipper Zero via Flipper Mobile App, do the following:
In the Main Menu tab, tap the Update button.
Tap the Update button to confirm the action.
The update process via Flipper Mobile App usually takes 2-3 minutes.
You can update your Flipper Zero via Flipper Mobile App
For additional information regarding the firmware update steps, visit Firmware update.
Customizing system preferences
Once you update your Flipper Zero, you can modify system settings, such as choosing a left-handed mode, setting your preferred units for measurements, and selecting your time and date formats by going to Main Menu -> Settings -> System.
Customize your Flipper Zero system settings
Join the community
Join our Discord server and forum community! There, you can ask questions, seek advice, and share your thoughts with others.
Want to learn more? Check out the following pages:
Просмотров 1.7к. Опубликовано
Обновлено
Узнал я про этот чудесный девайс случайно. Уже и не вспомню, что искал, но наткнулся на статью на Хабре о том, что русский хакер придумал создать девайс для всякого такого и решил собрать на это денег на кикстартере. Собрал в результате гораздо больше, чем планировал и проект Flipper Zero был успешно реализован. Даже более чем.
Хорошо об этом написал камрад vas3k, на блог которого я набрёл тоже разыскивая инфу про Flipper Zero.
Конечно, это игрушка! Игрушка для айтишников. Причём для айтишников, связанных с отраслью систем безопасности. А я как раз такой и есть.
Как только я прочитал статью на Хабре, то сразу захотел заиметь себе такой девайс, но на тот момент все произведённые девайсы были распроданы. Потянулось ожидание… Потом наступил тотальный трындец – ни из Америки, ни из Европы не привезти.
Совсем было отчаялся, но вдруг увидел, что желанный девайс появился в ассортименте Амперки. Да дороже и скидок для реселлеров нет, но девайс есть!
Сразу заказал пару в комплекте с силиконовыми чехлами – себе и товарищу.
Содержание
- Начало
- Возможности
- Личный кейс использования
- Реальная польза
Начало
Первое, что нужно сделать – вставить в девайс карту памяти. Без неё – никуда.
Дальше, ставим на смартфон приложение, коннектим с девайсом и устанавливаем актуальную версию прошивки. На ГитХабе есть дополнительный софт, который расширяет возможности устройства, но с ГитХабом я пока на Вы, поэтому оставим этот вопрос для самостоятельного изучения.
Возможности
- Чтение, запись и эмуляция идентификаторов формата iButton (DALLAS)
- Чтение, запись и эмуляция идентификаторов формата EM-MARINE (125 kHz)
- Чтение и эмуляция идентификаторов формата MIFARE (13,56 kHz MHz)
- Перехват и эмуляция сигналов управления от радиобрелоков (433 – 915 МГц)
- Перехват и эмуляция сигналов управления от ИК-пультов
Личный кейс использования
Первым делом я сохранил в базу данных ключ домофона компании Интерсвязь. Думал, что там защита, но либо защиты нет, либо Flipper Zero умеет копировать UID вместе с защитой. С этим ещё предстоит разобраться. Тем не менее, код ключа скопирован и в режиме эмуляции дверь открывается.
Дальше считал коды ключей формата Touch Memory, они же DALLAS, которые используются для постановки/снятия на охрану офиса.
Следующее, что хочу сделать – сграбить код брелка ворот, ограничивающих доступ на территорию двора соседнего дома. Не то, чтобы мне это было очень надо – машину ставлю на охраняемой стоянке, а двор моего дома открыт всем ветрам и не только, но чисто из спортивного интереса. Может оказаться, что используется какой-нибудь динамический или шифрованный код и ничего не получится, но попытаться стоит.
Ещё один случай – уходим из офиса и надо выключить кондиционер, но выясняется, что батарейки в пульте сели и пульт, соответственно, не пашет. Варианта два – идти за лестницей и выключать кондиционер из розетки за потолком либо бежать в магазин за батарейками. Но есть же Flipper! достаю сей мега-девайс, выбираю Infrared->Universal Remotes->Air Conditioners и включаю перебор кодов кнопки OFF. Пара секунд и кондёр выключен!
Реальная польза
Нежданно-негаданно, но девайс пригодился и в работе! Работаем с компанией И-ПРО – производителем GSM-сигнализаций и прочих сопутствующих устройств. Заказал 10 брелков PU-06, которые используются в качестве управляющих устройств и которые, по идее, должны, при нажатии на кнопку, выдавать уникальный код. Оказалось, что разные брелки выдают один и тот же код, то есть являются клонами друг друга. Для систем безопасности такое недопустимо.
Понятно,что подвели китайские друзья, а к чести производителя стоит сказать, что они сразу предложили заменить брелки.
В данном контексте интересно не это, а то, что первым вопросом тех.поддержки был: “Есть ли у вас средства, позволяющие увидеть передаваемый брелком код?”. Их есть у меня!!! Записал видосик, на котором видно, что при нажатии на кнопку разных брелков выдаётся один и тот же код. Больше вопросов не было. Ну не красота ли?
Привет!
В этой статье мы разберёмся, как написать собственную программу-приложение для Flipper Zero!
Сердцем гаджета Flipper Zero является 32-битный микроконтроллер STM32. Программирование Дельфина сильно отличается от программирования привычных нам Arduino. Помимо самого микроконтроллера во Флиппере есть радиомодуль, NFC-модуль, кардридер, модуль Bluetooth, дисплей, микросхема управления подсветкой и так далее. Эффективно управлять всеми этими устройствами в одном цикле loop
, как это обычно выглядит в среде разработки Arduino, уже нельзя.
На помощь приходит операционная система реального времени или RTOS (real-time operating system). RTOS разграничивает логические части всей программы в разные потоки и сама осуществляет переключение между ними, а также выделяет необходимые для работы потоков ресурсы.
Так как Флиппер построен на чипе STM32, в нём используется, наверное, самая популярная операционка для этого типа микроконтроллеров — FreeRTOS.
Мы не будем подробно разбирать, как реализована операционная система на Flipper Zero, а лучше сосредоточимся на написании приложений.
Это важно! Проект Flipper Zero активно развивается, и на момент выхода данной статьи официальной документации по созданию собственных приложений нет, а сам API полностью не описан. Однако принцип построения приложений вполне понятен в процессе изучения исходного кода встроенных в Флиппер приложений. Вполне возможно, что со временем API изменится, и код из этой статьи станет неактуальным. Разработчики Flipper Zero обещают, что документация появится, когда стабилизируется API. Статья актуальна для прошивки Флиппера 0.75.0.
Прошивку можно собрать на следующих платформах:
- Windows 10+ с PowerShell и Git (архитектура x86_64).
- macOS 12+ с Command Line tools (архитектура x86_64 и arm64).
- Ubuntu 20.04+ с
build-essential
и Git (архитектура x86_64).
У пользователей macOS не должно быть проблем со сборкой прошивки благодаря Homebrew. Ну а если вы собираете прошивку на Windows и столкнулись с трудностями, попробуйте воспользоваться WSL.
Писать код для собственных приложений мы будем на языке программирования C на настольном компьютере под управлением Linux, а именно Ubuntu 22.04.1 LTS (Jammy Jellyfish).
Установка необходимого софта
Перед тем как писать новое приложение, нам придётся научиться собирать всю прошивку микроконтроллера.
Сперва скачаем и установим официальную программу qFlipper для работы с Флиппером через графический интерфейс. Мы будем использовать qFlipper для удобной загрузки наших готовых приложений на SD-карту во Флиппере.
Скачиваем версию программы для OS Linux куда-нибудь, например в домашнюю директорию. На момент выхода статьи программа qFlipper имеет версию 1.2.2, а сам файл называется qFlipper-x86_64-1.2.2.AppImage
. Также установите необходимую для работы программы библиотеку libfuse2
, если её нет в вашей системе.
wget https://update.flipperzero.one/builds/qFlipper/1.2.2/qFlipper-x86_64-1.2.2.AppImage
sudo apt install libfuse2
Установите разрешение на запуск qFlipper и добавьте в систему udev
правила доступа к USB Serial-порту для обычного пользователя. Иначе понадобится вести разработку от лица суперпользователя, что небезопасно для всей системы в случае неосторожности.
sudo chmod +x qFlipper-x86_64-1.2.2.AppImage
./qFlipper-x86_64-1.2.2.AppImage rules install
Запустите qFlipper и подключите ваш Flipper Zero к компьютеру по USB.
./qFlipper-x86_64-1.2.2.AppImage
Убедитесь, что ваш Флиппер появился в программе qFlipper, всё работает как положено и установлена свежая прошивка.
Для быстрой и удобной установки необходимого софта понадобится диспетчер пакетов Homebrew.
Установите Homebrew:
sudo apt install curl
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
После установки добавьте переменные окружения PATH
, MANPATH
для Homebrew. Это можно сделать, добавив следующую строку в .profile
-файл для вашего юзера:
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/тут_ваш_username/.profile
Прошивка для Flipper Zero хранится в репозитории flipperzero-firmware на GitHub.
Склонируйте к себе репозиторий прошивки Flipper Zero со всеми модулями. Репозиторий займет чуть больше 2 ГБ пространства.
git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
Установите перечисленный в описании репозитория софт:
sudo apt update
sudo apt install openocd clang-format-13 dfu-util protobuf-compiler
Перейдите в директорию репозитория и установите все необходимые пакеты с помощью Homebrew:
cd flipperzero-firmware
brew bundle --verbose
Готово! Прошивка для Флиппера, как и пользовательские приложения, собираются с помощью утилиты fbt
(Flipper Build Tool). Для создания собственных приложений нет необходимости каждый раз собирать всю прошивку целиком, однако при первом запуске утилиты fbt
будут скачаны необходимые gcc-arm
тулчейны.
Соберите прошивку:
./fbt
Все результаты сборки и бинарные файлы будут помещены в директорию /dist
.
Пример 1. Простейшее приложение
Создадим простейшее компилируемое приложение.
Мы будем создавать приложение типа FAP (Flipper Application Package). Готовое приложение этого типа представляет собой файл формата .fap
. По сути, .fap
-приложение — это исполняемый файл .elf
с дополнительными встроенными данными.
Кстати! Весь исходный код приложений из этой стати мы выложили на GitHub в репозитории flipperzero-examples.
При создании пользовательские приложения помещаются в отдельные папки в специально организованную директорию applications_user
:
Придумайте имя для приложения. Мы назвали наше первое приложение example_1
, этим же именем назвали и папку. В неё помещаются все файлы, которые относятся к вашему приложению: исходный код, изображения и прочее.
В папке example_1
создадим файл исходного кода на языке С — example_1_app.c
. Вот как выглядит код простейшего приложения.
#include <furi.h>
int32_t example_1_app(void* p) {
UNUSED(p);
return 0;
}
Конвенционально в приложении точкой входа является функция, которая имеет имя приложения и суффикс app
. Точка входа в наше приложение — функция example_1_app
. Главная функция традиционно возвращает код ошибки числом типа int32_t
. Возвращаемый ноль сообщает об отсутствии ошибок.
При компиляции кода для Флиппера любой warning воспринимается как ошибка. Да, ваш код должен быть чистеньким. Неиспользованные в функции аргументы вызывают warning, поэтому для обозначения неиспользуемого указателя p
мы используем макрос UNUSED
. Реализация данного макроса описана в заголовке furi.h
. FURI расшифровывается как «Flipper Universal Registry Implementation». Этим заголовочным файлом мы, по сути, подключаем вce core API Флиппера.
Нашему приложению понадобится иконка. Для пользовательских приложений, которые находятся на самом Флиппере в разделе Applications
, в качестве иконок используются изображения PNG с глубиной цвета 1 бит (чёрно-белые) и размером 10×10 пикселей.
Раздел Apllications
с пользовательскими приложениями:
Можно использовать одно из уже имеющихся изображений, но всегда лучше добавить что-то своё. В GIMP мы нарисовали вот такой смайл в чёрных очках:
Нарисовать свою иконку можно и в Paint. Файл изображения помещаем в папку нашего приложения example_1
.
Помимо исходного кода и иконки нам нужен файл манифеста приложения — application.fam
. Этот файл является обязательным. Наш манифест приложения имеет следующий вид:
App(
appid="example_1",
name="Example 1 application",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_1_app",
cdefines=["APP_EXAMPLE_1"],
stack_size=1 * 1024,
order=90,
fap_icon="emoji_smile_icon_10x10px.png",
fap_category="Misc",
)
Разберёмся, за что отвечают данные параметры. Параметры для всех видов приложений:
appid
— строка, которая используется как ID приложения при конфигурации сборкиfbt
, а также для разрешения зависимостей и конфликтов. Есть смысл использовать здесь непосредственно имя вашего приложения.name
— читабельное имя приложения, которое будет отображаться в меню приложений на Флиппере.apptype
— тип приложения. Существуют разные типы для тестовых, системных, сервисных, архивных приложений и для приложений, которые должны быть в главном меню Флиппера. В конце сборки наше приложение будет типаFAP
. Для приложений подобного рода используется типEXTERNAL
(FlipperAppType.EXTERNAL
).entry_point
— точка входа приложения. Имя главной функции, с выполнения которой начнётся работа вашего приложения. Если в качестве точки входа вы хотите использовать функцию C++, то она должна быть обёрнута вextern "C"
.cdefines
— препроцессорное глобальное объявление для других приложений, когда текущее приложение включено в активную конфигурацию сборки.stack_size
— размер стека в байтах, выделяемый для приложения при его запуске. Обратите внимание, что выделение слишком маленького стека приведёт к сбою системы из-за переполнения стека, а выделение слишком большого уменьшит полезный объём heap-памяти для обработки данных приложениями.order
— порядок приложения внутри своей группы при сортировке записей. Чем ниже значение, тем выше по списку окажется ваше приложение.
Параметры для внешних приложений типа EXTERNAL
:
fap_icon
— путь и имя PNG-изображения размером 10×10 пикселей, которое используется как иконка. Здесь пишем путь и имя нашей PNG-иконки.fap_category
— подкатегория приложения. Определяет путь.fap
-файла в папке приложений в файловой системе. Может быть пустым. Мы поместили наше приложение в категориюMisc
на Флиппере.
Если все файлы на месте, мы можем начинать сборку приложения.
В терминале переходим в корневую директорию прошивки flipperzero-firmware
. Сборка осуществляется командой ./fbt fap_{APPID}
, где {APPID}
— это ID, указанный в .fam
-файле манифеста приложения.
./fbt fap_example_1
Сбилдить все имеющиеся в прошивке приложения FAP можно командой ./fbt faps
.
Готовое FAP-приложение находится в директории build
в скрытой директории .extapps
. Наш файл приложения называется example_1.fap
.
Используя программу qFlipper, перенесём файл приложения на SD-карту в директорию /apps/Misc
. Файл можно перенести мышкой прямо в окно программы.
После последнего обновления все приложения FAP можно сбилдить и перенести на Флиппер одной командой из консоли:
./fbt fap_deploy
Готово! Теперь наше приложение появилось на Флиппере в разделе Misc
:
Главная функция example_1_app()
сейчас пуста, поэтому при запуске приложения программа просто завершит свою работу и мы не увидим никаких изменений на экране.
Как видно, ваше приложение может вовсе не иметь графического интерфейса и отработать как бы «за кулисами». Но у нашего Флиппера есть экранчик, поэтому нельзя не сделать графический интерфейс.
Пример 2. Графический интерфейс
Добавим нашему приложению графический интерфейс.
Чтобы не путаться между пунктами статьи, мы сделаем новое приложение с именем example_2
, но по сути будем продолжать предыдущее приложение.
Новое приложение поместим в директорию applications_user/example_2
. Соответственно, файл с исходным кодом приложения имеет имя example_2_app.c
.
Для создания графического интерфейса вам придётся значительно расширить исходный код приложения. Создадим в директории приложения заголовочный файл example_2_app.h
для описания типов данных, структур и прототипов функций.
Включим уже знакомый нам заголовочный файл ядра furi.h
. Для графического интерфейса понадобится заголовок gui/gui.h
.
#pragma once
#include <furi.h>
#include <gui/gui.h>
struct Example2App {
Gui* gui;
ViewPort* view_port;
};
typedef struct Example2App Example2App;
В заголовочном файле мы создали структуру Example2App
, которая будет хранить указатели на все важные компоненты нашего приложения, и ввели новый тип для этой структуры. В структуре нашего приложения есть указатели на графический интерфейс Gui
и на ViewPort
. ViewPort
— это структура, которая используется для отрисовки единичного полного экрана. К ней привязываются указатели на callback-функции отрисовки графических объектов на экране и функции обработки различных событий (Events), например нажатие клавиш.
Исходный код приложения в файле example_2_app.c
теперь выглядит так:
#include "example_2_app.h"
#include <furi.h>
#include <gui/gui.h>
Example2App* example_2_app_alloc() {
Example2App* app = malloc(sizeof(Example2App));
app->view_port = view_port_alloc();
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
void example_2_app_free(Example2App* app) {
furi_assert(app);
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
}
int32_t example_2_app(void *p) {
UNUSED(p);
Example2App* app = example_2_app_alloc();
furi_delay_ms(10000);
example_2_app_free(app);
return 0;
}
Разберёмся, что есть что. Программирование под Флиппер очень требовательно к ресурсам операционной системы, и нам нужно за этим следить. Это необходимо, чтобы наше приложение работало правильно и не вызывало зависаний и глюков. Поэтому, если мы хотим что-то добавить в приложение или интерфейс, то самостоятельно выделяем под это память, а когда удаляем — освобождаем.
Описываем функцию, которая будет выделять память под структуру нашего приложения и инициализировать его:
Example2App* example_2_app_alloc()
И функцию, которая освобождает занятую приложением память:
void example_2_app_free(Example2App* app)
В функции выделения памяти мы сначала выделяем память под структуру app
типа Example2App
для нашего приложения. Затем выделяем память для view_port
:
app->view_port = view_port_alloc();
Получаем указатель на текущий Gui
Флиппера — gui
. Перехватываем управление Gui
и говорим операционной системе, что у нашего приложения есть некий интерфейс c отрисовкой экрана view_port
и мы хотим его отобразить.
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
В функции освобождения памяти мы, соответственно, работаем в обратном направлении. Выключаем рендер нашего view_port
:
view_port_enabled_set(app->view_port, false);
Отключаем view_port
от Gui
и освобождаем память, занятую view_port
:
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
В конце мы передаём управление графическим интерфейсом от нашего приложения обратно операционной системе:
furi_record_close(RECORD_GUI);
Так как мы пишем на С и регулярно используем указатели, в ядре FURI существует крайне полезная для нас функция furi_assert()
, которая экстренно остановит работу приложения и выдаст сообщение, если мы вдруг передадим указатель на пустой участок памяти.
Точкой входа приложения на этот раз будет функция с именем example_2_app
:
int32_t example_2_app(void *p) {
UNUSED(p);
Example2App* app = example_2_app_alloc();
furi_delay_ms(10000);
example_2_app_free(app);
return 0;
}
При запуске приложения мы аллоцируем память для структуры приложения, перехватываем управление Gui
и рендерим наш ViewPort
. После этого мы простаиваем 10 секунд функцией furi_delay_ms()
, освобождаем все занятые ресурсы, передаём управление Gui
операционной системе и завершаем работу приложения.
Кроме того, у нас получился хороший шаблон с выделением/освобождением памяти для будущих приложений.
Иконку для приложения оставляем прежней.
Вносим правки в файл манифеста приложения application.fam
. Здесь всё остаётся прежним, за исключением нового параметра requires
. В этом параметре мы указываем, что для работы нашему приложению нужен сервис, отвечающий за графические интерфейсы gui
.
App(
appid="example_2",
name="Example 2 application",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_2_app",
cdefines=["APP_EXAMPLE_2"],
requires=[
"gui",
],
stack_size=1 * 1024,
order=90,
fap_icon="emoji_smile_icon_10x10px.png",
fap_category="Misc",
)
Собираем новое приложение:
./fbt fap_example_2
Используя программу qFlipper, перенесём новое FAP-приложение из папки build
на SD-карту в папку /apps/Misc
. Или используем терминал и команду ./fbt fap_deploy
.
Находим новое приложение в списке и запускаем его:
Сейчас для нашего ViewPort
не описаны конкретные функции отрисовки графических объектов. Программа приложения просто отобразит пустой графический интерфейс в течение 10 секунд и завершит работу.
Пример 3. Текст и изображения
Добавим в интерфейс нашего приложения какие-нибудь графические объекты. Некоторые графические объекты могут иметь довольно сложную реализацию, например: меню, выпадающие списки, файловые браузеры, различные формы ввода. Мы же начнём с простых объектов.
Снова создадим копию предыдущего приложения, но теперь под именем example_3
.
Для рендера графических объектов создадим в исходном файле example_3_app.c
callback-функцию отрисовки example_3_app_draw_callback
:
static void example_3_app_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
}
Callback-функция имеет определённую сигнатуру и два аргумента canvas
, то есть «холст», на котором мы будем рисовать, и контекст ctx
. Контекстом могут быть другие данные, в зависимости от которых рендерится canvas
. Пока что мы оставим контекст неиспользованным, то есть UNUSED
.
Первым делом перед рендером очистим наш экран:
canvas_clear(canvas);
Графические объекты размещаются на экране согласно системе координат. Экран Флиппера имеет разрешение 128×64, а начало координат находится в левом верхнем углу:
Добавим текст в интерфейс нашего приложения.
Подключим заголовочный файл с графическими текстовыми элементами gui/elements.h
. Текст отрисовывается функцией canvas_draw_str()
с указанием координаты (x; y) исходной точки текста и, собственно, самой строки. Возможен выбор шрифта для текста. Шрифт устанавливается функцией canvas_set_font()
. Шрифт может быть главным — FontPrimary
(высота 8 пикселей), второстепенным — FontSecondary
(высота 7 пикселей), FontKeyboard
или FontBigNumbers
. При желании вы сможете создать и собственный шрифт.
Например, напишем главным шрифтом строку «This is an example app!» вверху экрана и примерно по центру, по координатам (4; 8). По умолчанию начало координат текста находится в левом нижнем углу.
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 8, "This is an example app!");
В заголовочном файле gui/elements.h
можно найти различные имплементации для отрисовки простых элементов, скроллбаров, кнопок или выравнивания текста.
Например, снизу от нашей первой надписи разместим длинный двухстрочный текст «Some long long long long aligned multiline text», написанный второстепенным шрифтом и автоматически выровненный по верхней и правой границам. Для этого воспользуемся функцией elements_multiline_text_aligned()
:
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 15, AlignRight, AlignTop, "Some long long long long \n aligned multiline text");
Теперь разберёмся, как вывести на экран картинку.
Для эксперимента мы нарисовали чёрно-белый логотип Амперки в PNG разрешением в 128×35 пикселей:
В папке с вашим приложением создайте специальную директорию для хранения изображений. Например, мы назвали свою папку images
. В данную папку поместите все изображения, которые планируете выводить на экран. Мы назвали нашу картинку amperka_ru_logo_128x35px.png
и поместили её в созданную папку images
:
В манифесте приложения application.fam
добавляем новый параметр fap_icon_assets
с путём до директории с изображениями:
App(
appid="example_3",
name="Example 3 application",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_3_app",
cdefines=["APP_EXAMPLE_3"],
requires=[
"gui",
],
stack_size=1 * 1024,
order=90,
fap_icon="emoji_smile_icon_10x10px.png",
fap_category="Misc",
fap_icon_assets="images",
)
Теперь при сборке приложения все изображения из папки images
будут переведены в код, а сам код будет сгенерирован в специальном заголовочном файле с именем {APPID}_icons.h
, где {APPID}
— это ID, указанный в .fam
-файле манифеста приложения.
Наше приложение имеет ID example_3
, значит заголовочный файл получит имя example_3_icons.h
. Добавим данный файл в заголовок приложения:
#include "example_3_icons.h"
Теперь мы можем получить указатель на область памяти, где хранится наше изображение в виде массива байтов. Имя указателя будет соответствовать имени самого файла изображения, но с приставкой I_ИМЯ_ВАШЕГО_ФАЙЛА
. Для нашей картинки с именем amperka_ru_logo_128x35px.png
имя указателя будет I_amperka_ru_logo_128x35px
.
Теперь, имея адрес изображения, мы можем отрендерить его на экране функцией canvas_draw_icon()
. Выведем изображение внизу экрана по координатам (0; 29):
canvas_draw_icon(canvas, 0, 29, &I_amperka_ru_logo_128x35px);
Пока что хватит графических элементов. Результат должен выглядеть следующим образом:
Наша callback-функция отрисовки готова, и её необходимо привязать к структуре ViewPort
нашего приложения. Это нужно сделать в функции инициализации нашего приложения сразу после выделения памяти под ViewPort
. В качестве контекста мы ничего не отправляем (NULL
). После привязки первый раз callback-функция будет выполнена автоматически.
view_port_draw_callback_set(app->view_port, example_3_app_draw_callback, NULL);
Заголовочный файл нашего третьего приложения не изменился, за исключением имени приложения и подключаемого заголовка с изображениями.
Код example_3_app.h
:
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include "example_3_icons.h"
struct Example3App {
Gui* gui;
ViewPort* view_port;
};
typedef struct Example3App Example3App;
Код example_3_app.с
:
#include "example_3_app.h"
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
static void example_3_app_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
canvas_draw_icon(canvas, 0, 29, &I_amperka_ru_logo_128x35px);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 8, "This is an example app!");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 15, AlignRight, AlignTop, "Some long long long long \n aligned multiline text");
}
Example3App* example_3_app_alloc() {
Example3App* app = malloc(sizeof(Example3App));
app->view_port = view_port_alloc();
view_port_draw_callback_set(app->view_port, example_3_app_draw_callback, NULL);
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
void example_3_app_free(Example3App* app) {
furi_assert(app);
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
}
int32_t example_3_app(void *p) {
UNUSED(p);
Example3App* app = example_3_app_alloc();
furi_delay_ms(10000);
example_3_app_free(app);
return 0;
}
Главную функцию приложения example_3_app
оставляем как есть. Приложение снова отработает 10 секунд и завершит свою работу, но на этот раз у нас будет графика.
Собираем новое приложение:
./fbt fap_example_3
Используя программу qFlipper, перенесём новое FAP-приложение из папки build
на SD-карту в папку /apps/Misc
. Или используем терминал и команду ./fbt fap_deploy
.
Находим новое приложение в списке и запускаем его:
Взглянем на результат:
Пример 4. Кнопки
Разберёмся, как обрабатывать нажатия кнопок на Флиппере.
Продолжаем предыдущее приложение под новым именем example_4
.
Для использования кнопок нам понадобится очередь сообщений (MessageQueue) и ещё одна callback-функция для обработки этой очереди.
Зачем нужна очередь сообщений? Поскольку за нас работает операционная система, то callback-функция ввода с кнопок, как и callback-функция рендера графики выполняется в контексте других потоков ОС Flipper Zero, а не в потоке нашего приложения. Поток, отвечающий за нажатие кнопок, не может вызвать какую-либо функцию напрямую из нашего приложения. Но потоки могут отправлять друг другу сообщения в любой момент выполнения, этим мы и воспользуемся.
Добавляем в структуру нашего приложения Example4App
очередь event_queue
типа FuriMessageQueue
:
struct Example4App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
};
В функции инициализации приложения example_4_app_alloc()
выделяем память под новую очередь.
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
Наша очередь будет на 8 сообщений типа InputEvent
, который отвечает за нажатие клавиш. Структуры данных и функции для работы с сообщениями от кнопок находятся в заголовочном файле input/input.h
.
В функции освобождения памяти нашего приложения, соответственно, освобождаем занятую очередью память.
furi_message_queue_free(app->event_queue);
Теперь создадим callback-функцию для обработки этой очереди.
static void example_4_app_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
Как и в случае с callback-функцией отрисовки, эта имеет два аргумента: input_event
и контекст ctx
. Событие input_event
сигнализирует о каком-либо взаимодействии с кнопками. В отличие от функции отрисовки, в этот раз контекст не пустой. Мы положили в контекст очередь сообщений нашего приложения. Таким образом актуальные события ввода с кнопок окажутся в потоке нашего приложения.
Привязываем новую callback-функцию к графическому интерфейсу ViewPort
нашего приложения. В качестве контекста указываем очередь сообщений приложения event_queue
:
view_port_input_callback_set(app->view_port, example_4_app_input_callback, app->event_queue);
Готово! Теперь информация о состоянии кнопок находится в нашем распоряжении, и её можно обработать.
Сейчас наше приложение работает 10 секунд, а затем завершает работу. Давайте сделаем так, чтобы приложение закрывалось не автоматически, а при нажатии на клавишу «Назад» на Флиппере.
Обработчик сообщений из очереди напишем в главной функции нашего приложения (точке входа) в бесконечном цикле:
while (1) {
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.type == InputTypePress) {
if (event.key == InputKeyBack)
break;
}
}
}
Пока очередь пуста, крутимся в бесконечном цикле. Если в нашей очереди есть событие (FuriStatusOk
), нажалась кнопка (InputTypePress
), и это была кнопка «Назад» (InputKeyBack
), то выходим из цикла и, как следствие, движемся к завершению работы приложения.
Код example_4_app.h
:
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include "example_4_icons.h"
struct Example4App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
};
typedef struct Example4App Example4App;
Код example_4_app.с
:
#include "example_4_app.h"
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <input/input.h>
static void example_4_app_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
canvas_draw_icon(canvas, 0, 29, &I_amperka_ru_logo_128x35px);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 8, "This is an example app!");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 15, AlignRight, AlignTop, "Some long long long long \n aligned multiline text");
}
static void example_4_app_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
Example4App* example_4_app_alloc() {
Example4App* app = malloc(sizeof(Example4App));
app->view_port = view_port_alloc();
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
view_port_draw_callback_set(app->view_port, example_4_app_draw_callback, NULL);
view_port_input_callback_set(app->view_port, example_4_app_input_callback, app->event_queue);
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
void example_4_app_free(Example4App* app) {
furi_assert(app);
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_message_queue_free(app->event_queue);
furi_record_close(RECORD_GUI);
}
int32_t example_4_app(void *p) {
UNUSED(p);
Example4App* app = example_4_app_alloc();
InputEvent event;
while (1) {
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.type == InputTypePress) {
if (event.key == InputKeyBack)
break;
}
}
}
example_4_app_free(app);
return 0;
}
Собираем новое приложение и переносим его на Flipper Zero.
./fbt fap_example_4
./fbt fap_deploy
Взглянем на результат:
Изменим наше приложение и добавим ещё пару кнопок.
Например, будем по-разному рендерить изображение на экране в зависимости от нажатой кнопки.
Пусть у нас будет три состояния интерфейса: рендерится только текст, рендерится только изображение, либо рендерится и то, и другое. Переключаться между этими режимами будем при долгом нажатии на кнопки «Влево» или «Вправо».
Введём в структуру нашего приложения переменную, которая будет отвечать за то, какой режим рендерится в данный момент. Назовем её draw_mode
.
typedef enum {
DRAW_ALL,
DRAW_ONLY_TEXT,
DRAW_ONLY_PICTURES,
TOTAL_DRAW_MODES = 3,
} DrawMode;
struct Example4App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
DrawMode draw_mode;
};
Чтобы draw_mode
был доступен для нашей callback-функции рендера экрана, передадим в неё указатель на всю структуру приложения app
в качестве контекста:
view_port_draw_callback_set(app->view_port, example_4_app_draw_callback, app);
Теперь изменим саму callback-функцию рендера. Пусть разные графические объекты рендерятся в зависимости от текущего значения draw_mode
:
static void example_4_app_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
Example4App* app = ctx;
canvas_clear(canvas);
DrawMode mode = app->draw_mode;
if (mode == DRAW_ONLY_PICTURES || mode == DRAW_ALL)
canvas_draw_icon(canvas, 0, 29, &I_amperka_ru_logo_128x35px);
if (mode == DRAW_ONLY_TEXT|| mode == DRAW_ALL) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 8, "This is an example app!");
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 15, AlignRight, AlignTop, "Some long long long long \n aligned multiline text");
}
}
В заключении обработаем новые события кнопок в бесконечном цикле главной функции приложения:
while (1) {
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.type == InputTypePress) {
if (event.key == InputKeyBack)
break;
} else if (event.type == InputTypeLong) {
DrawMode mode = app->draw_mode;
if (event.key == InputKeyLeft)
app->draw_mode = (mode - 1 + TOTAL_DRAW_MODES) % TOTAL_DRAW_MODES;
else if (event.key == InputKeyRight)
app->draw_mode = (mode + 1) % TOTAL_DRAW_MODES;
view_port_update(app->view_port);
}
}
}
Теперь если будет зарегистрировано событие длительного нажатия (InputTypeLong
) кнопки «Влево» (InputKeyLeft
) или «Вправо» (InputKeyRight
), наш режим отрисовки app->draw_mode
будет меняться от 0 до TOTAL_DRAW_MODES
.
Функция view_port_update()
запускает ререндер нашего интерфейса view_port
. Функция не обязательная, операционная система сама производит ререндер раз в несколько миллисекунд, но мы можем форсировать это функцией.
Соберём обновлённое приложение, загрузим его на Флиппер, запустим и посмотрим результат:
Пример 5. Оповещения
Помимо дисплея Flipper Zero имеет и другой способ сообщать нам о происходящих в программе событиях — оповещения (Notifications). Нам доступно управление следующими встроенными девайсами:
- RGB-cветодиод.
- Вибромотор.
- Пьезопищалка.
Продолжаем предыдущее приложение под новым именем example_5
.
За оповещения в операционной системе Flipper Zero отвечает отдельный поток, и мы не можем вызывать его функции из нашего приложения напрямую. Но мы можем отсылать в этот поток сообщения — NotificationMessage
. Из этих сообщений формируются последовательности NotificationSequence
, которые уже непосредственно отправляются в поток.
Описание структур сообщений и их последовательностей находится в заголовочном файле notification/notification_messages.h
, добавляем его в наше приложение.
В главной структуре указываем, что наше приложение собирается использовать оповещения NotificationApp
:
struct Example5App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
NotificationApp* notifications;
DrawMode draw_mode;
};
В функции инициализации приложения example_5_app_alloc()
перехватываем управление оповещениями:
app->notifications = furi_record_open(RECORD_NOTIFICATION);
А в функции освобождения памяти приложения example_5_app_free
отдаём управление обратно:
furi_record_close(RECORD_NOTIFICATION);
Можно использовать и готовые последовательности сообщений, а можно сделать их самостоятельно, это несложно. Вы можете изучить соответствующий заголовочный файл и какие последовательности там доступны.
Создадим нашу последовательность сообщений для управления RGB-светодиодом. Назовем её example_led_sequence
и разместим в заголовке нашего приложения.
Пусть светодиод мигнёт фиолетовым цветом RGB(255, 0, 255) три раза с интервалом 500 мс, а затем погаснет. Сообщение будет выглядеть следующим образом:
const NotificationSequence example_led_sequence = {
&message_red_255,
&message_blue_255,
&message_delay_500,
&message_red_0,
&message_blue_0,
&message_delay_500,
&message_red_255,
&message_blue_255,
&message_delay_500,
&message_red_0,
&message_blue_0,
&message_delay_500,
&message_red_255,
&message_blue_255,
&message_delay_500,
&message_red_0,
&message_blue_0,
NULL,
};
Последовательности сообщений для управления вибромотором составляются схожим образом. Создадим последовательность для вибромотора с именем example_vibro_sequence
и разместим её в заголовке.
Пусть по сигналу наш вибромотор включится на 3 секунды, а затем выключится. Последовательность сообщений будет выглядеть так:
const NotificationSequence example_vibro_sequence = {
&message_vibro_on,
&message_do_not_reset,
&message_delay_1000,
&message_delay_1000,
&message_delay_1000,
&message_vibro_off,
NULL,
};
По умолчанию максимально долгая описанная задержка составляет 1 секунду, поэтому мы три раза использовали сообщение message_delay_1000
.
Теперь создадим последовательность сообщений для пьезодинамика. Назовем её example_sound_sequence
.
Здесь нам уже доступна полная MIDI-клавиатура прямо из коробки! Описание всех нот и их частот можно посмотреть в заголовочном файле notification_messages_notes.h
.
Добавим в наш Флиппер классическую мелодию звонка телефонов Nokia:
Последовательность сообщений с данной мелодией выглядит так:
const NotificationSequence example_sound_sequence = {
&message_note_e5,
&message_delay_100,
&message_note_d5,
&message_delay_100,
&message_note_fs4,
&message_delay_250,
&message_note_gs4,
&message_delay_250,
&message_note_cs5,
&message_delay_100,
&message_note_b4,
&message_delay_100,
&message_note_d4,
&message_delay_250,
&message_note_e4,
&message_delay_250,
&message_note_b4,
&message_delay_100,
&message_note_a4,
&message_delay_100,
&message_note_cs4,
&message_delay_250,
&message_note_e4,
&message_delay_250,
&message_note_a4,
&message_delay_500,
NULL,
};
Отлично! Теперь нужно решить, когда запускать данные оповещения. Пусть при нажатии на кнопку «Вверх» (InputKeyUp
) включится светодиод, при нажатии на кнопку «Вниз» (InputKeyDown
) включится вибромотор, а при нажатии на кнопку «Ок» (InputKeyOk
) заиграет мелодия.
Добавляем обработку для новых кнопок в бесконечный цикл в главной функции нашего приложения example_5_app()
:
while (1) {
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.type == InputTypePress) {
if (event.key == InputKeyBack)
break;
else if (event.key == InputKeyUp)
notification_message(app->notifications, &example_led_sequence);
else if (event.key == InputKeyDown)
notification_message(app->notifications, &example_vibro_sequence);
else if (event.key == InputKeyOk)
notification_message(app->notifications, &example_sound_sequence);
} else if (event.type == InputTypeLong) {
DrawMode mode = app->draw_mode;
if (event.key == InputKeyLeft)
app->draw_mode = (mode - 1 + TOTAL_DRAW_MODES) % TOTAL_DRAW_MODES;
else if (event.key == InputKeyRight)
app->draw_mode = (mode + 1) % TOTAL_DRAW_MODES;
view_port_update(app->view_port);
}
}
}
Отправка сообщений осуществляется функцией notification_message()
с указанием соответствующей последовательности.
Собираем новое приложение:
./fbt fap_example_5
Используя программу qFlipper, переносим новый FAP-файл из папки build
на SD-карту в папку /apps/Misc
. Или загружаем приложение командой ./fbt fap_deploy
.
Запускаем приложение:
Смотрим на результат:
Пример 6. GPIO
На Flipper Zero 18 контактов GPIO, среди которых есть как пины питания, так и пины ввода-вывода. Логическое напряжение питания — 3,3 В, и пины нетолерантны к 5 В (за исключением пина iButton). По сути, пины Флиппера соответствуют пинам установленного в нём микроконтроллера STM32WB55 и обладают теми же настраиваемыми альтернативными функциями (ADC, USART, SPI и др.).
Распиновка Флиппера:
Подробное назначение пинов можно посмотреть в заголовочном файле furi_hal_resources.h
. FURI HAL — специальный HAL Flipper Zero, который призван упростить для нас взаимодействие с железом.
В FURI HAL GPIO-структура имеет имя GpioPin
. Без разборки Флиппера на куски нам доступны:
const GpioPin gpio_ext_pc0
— порт GPIOC, пин 0 (номер 16 на Флиппере).const GpioPin gpio_ext_pc1
— порт GPIOC, пин 1 (номер 15 на Флиппере).const GpioPin gpio_ext_pc3
— порт GPIOC, пин 3 (номер 7 на Флиппере).const GpioPin gpio_ext_pb2
— порт GPIOB, пин 2 (номер 6 на Флиппере).const GpioPin gpio_ext_pb3
— порт GPIOB, пин 3 (номер 5 на Флиппере).const GpioPin gpio_ext_pa4
— порт GPIOA, пин 4 (номер 4 на Флиппере).const GpioPin gpio_ext_pa6
— порт GPIOA, пин 6 (номер 3 на Флиппере).const GpioPin gpio_ext_pa7
— порт GPIOA, пин 7 (номер 2 на Флиппере).const GpioPin ibutton_gpio
— порт GPIOB, пин 14 (номер 17 на Флиппере).
Также доступны пины с функцией USART по умолчанию:
const GpioPin gpio_usart_tx
— порт GPIOB, пин 6 (номер 13 на Флиппере).const GpioPin gpio_usart_rx
— порт GPIOB, пин 7 (номер 14 на Флиппере).
Ещё есть пины интерфейса SWD (Serial Wire Debug) для отладки, маркированные на корпусе как SIO, SWC. Все остальные пины микроконтроллера используются для управления начинкой Flipper Zero: дисплеем, кнопками, USB, NFC, I²C, SPI и т. д.
Сделаем приложение, через которое мы сможем управлять GPIO. Сперва сделаем простой DigitalWrite
, DigitalRead
. Читать значение будем с пина А6, а писать значение в пин А7.
Подключим к Флипперу простую кнопку к пину А6 и светодиод к пину А7. Максимальный ток на пине — 20 мА, для светодиода хватит. Питание берём с шины 3,3 В. Установим светодиод и кнопку на макетную плату:
Назовём новое приложение example_6
и сделаем его на основе нашего предыдущего примера номер 4. Предварительно уберём из приложения всё, что касается оповещений, рендера графических элементов и обработки кнопок, чтобы остался пустой интерфейс.
Для управления GPIO нам нужен HAL Флиппера. Подключаем файл furi_hal.h
в заголовок нашего приложения.
В главной структуре Example6App
нашего приложения создадим два пина для входа и выхода: input_pin
, output_pin
и две булевы переменные для хранения текущих значений на этих пинах: input_value
, output_value
.
Наш заголовочный файл принимает следующий вид:
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
struct Example6App {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* event_queue;
const GpioPin* input_pin;
const GpioPin* output_pin;
bool input_value;
bool output_value;
};
typedef struct Example6App Example6App;
В функции инициализации приложения example_6_app_alloc()
задаём номера пинов. Функцией furi_hal_gpio_init()
инициалзируем пины. Для ввода устанавливаем режим GpioModeInput
и включаем подтяжку GpioPullUp
, а для вывода режим GpioModeOutputPushPull
и отключаем подтяжку GpioPullNo
. Оба пина опрашиваются на максимальной скорости GpioSpeedVeryHigh
:
app->input_pin = &gpio_ext_pa6;
app->output_pin = &gpio_ext_pa7;
furi_hal_gpio_init(app->input_pin, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_init(app->output_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
В главной функции приложения example_6_app()
(и по совместительству в нашей точке входа) считываем и отправляем соответствующие значения в бесконечном цикле:
furi_hal_gpio_write(app->output_pin, app->output_value);
app->input_value = furi_hal_gpio_read(app->input_pin);
Пусть выходное значение зависит от состояния кнопки «Ок» Флиппера. Кнопка нажата — сигнал есть, отжата — сигнала нет. Как и прежде, клавишей «Назад» завершаем работу приложения. Добавим соответствующую обработку кнопки «Ок» в бесконечный цикл нашей программы:
if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk) {
if (event.key == InputKeyBack) {
if (event.type == InputTypePress)
break;
} else if (event.key == InputKeyOk) {
if (event.type == InputTypePress)
app->output_value = true;
else if (event.type == InputTypeRelease)
app->output_value = false;
}
Наконец, добавим немного графики в callback-функцию отрисовки интерфейса. Просто выведем текстом текущее входное и выходное значение. Для отрисовки больших цифр можно использовать шрифт FontBigNumbers
. В качестве контекста передаём в функцию отрисовки главную структуру приложения.
static void example_6_app_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
Example6App* app = ctx;
canvas_clear(canvas);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 32, 17, AlignCenter, AlignTop, "Output PA7:");
elements_multiline_text_aligned(canvas, 96, 17, AlignCenter, AlignTop, "Input PA6:");
canvas_set_font(canvas, FontBigNumbers);
elements_multiline_text_aligned(canvas, 32, 32, AlignCenter, AlignTop, app->output_value ? "1" : "0");
elements_multiline_text_aligned(canvas, 96, 32, AlignCenter, AlignTop, app->input_value ? "1" : "0");
}
Интерфейс будет выглядеть так:
Собираем новое приложение и загружаем его на Флиппер:
./fbt fap_example_6
./fbt fap_deploy
Протестируем приложение на Флиппере:
PWM и ADC
С генерацией ШИМ-сигналов всё обстоит намного сложнее. Здесь уже не обойтись одной-двумя функциями из FURI HAL, а сам код сильно разрастается.
В прошивке Flipper Zero уже есть приложение Signal Generator для генерации ШИМ-сигнала на пинах PA7 и PA4. Вы можете самостоятельно изучить исходный код для генерации ШИМ в директории applications/plugins/signal_generator/ и реплицировать его в ваше приложение.
А вот официальной документации на чтение аналоговых сигналов пока нет. Кроме этого альтернативные функции аналого-цифрового преобразователя на пинах ещё не имплементированы в FURI HAL. Однако сам ADC на микроконтроллере STM32 и его возможности никуда от нас не делись.
На просторах интернета мы нашли пример использования ADC. Мы разместили в репозитории с примерами flipperzero-examples приложение adc_example
, которое cчитывает аналоговое значение с пина PC3 и выводит его на экран. Код ещё нуждается в доработке, и вы можете использовать его в своих приложениях, однако мы советуем дождаться официальной документации и примеров.
Опорным напряжением является выборочно или 2.5 В или 2.048 В. Подключив к флипперу потенциометр и взяв питание с пина 3.3 В понадобится простой делитель напряжения в пределах тысячи Ом. Разрешение АЦП — 12 бит.
Читаем напряжение от 0 до 2.5В на пине PC3 и меняем его потенциометром:
Заключение
На этом мы заканчиваем базовое знакомство с пользовательскими приложениями для Flipper Zero.
Покопавшись в документации, нам удалось зайти подальше банального «Hello, world!» и написать несколько приложений для примера работы с GUI, кнопками и встроенной периферией Флиппера — RGB-светодиодом, вибромотором и баззером.
Ждём обновления официальной документации гаджета и надеемся, что наши примеры помогут вам в создании своих приложений для Flipper Zero!
Полезные ссылки
- Гаджет Flipper Zero в каталоге Амперки
- Посмотреть все проекты