Безопасное завершение работы Raspbian кнопкой, подключенной к Raspberry Pi
Расскажу о создании несложного скрипта и службы для systemd, которые будут отслеживать нажатие подключенной к GPIO кнопки и запускать процесс завершения работы операционной системы Raspbian, что потом позволит безопасно отключить питание Raspberry Pi.
Программа будет написана на языке Python3, также будет создан небольшой файл-юнит (задание) для системного менеджера GNU/Linux Raspbian - Systemd. Опишу реализацию простого примера с одной кнопкой, а также более сложного - с выводом сообщения на OLED-дисплей SD1306.
Содержание:
- Для чего это нужно
- Как это работает
- Подготовка
- Подключение кнопки
- Исходный код программы
- Скрипт-задание для systemd
- Пример с применением экрана SSD1306
- Заключение
Для чего это нужно
На основе мини-компьютера Raspberry Pi можно строить самые различные по возможностям и сложности проекты: системы автоматизации для умного дома, сетевой маршрутизатор, мини сервер, систему сбора телеметрии, домашний робот, станок с ЧПУ, устройства IOT (Internet Of Things) и многое другое.
При проектировании подобных устройств очень часто возникает необходимость выключать компьютер Raspberry Pi в автоматическом или ручном режиме, например:
- при разряде питающих батарей;
- при завершении сбора каких-то данных в указанное время;
- просто для экономии электричества;
- и т.п.
Как и для любого другого компьютера, работу и взаимодействие с аппаратным обеспечением малинки выполняет операционная система (ОС), в данном случае это - Raspbian GNU/Linux.
ОС отвечает за обмен данными между процессором, памятью, накопителями данных и различными периферийными интерфейсами. В качестве накопителя данных в малинке по умолчанию используется Micro-SD карта, которая изготовлена по флешь-технологии.
Перед выключением питания модуля Raspberry Pi необходимо выполнить корректное завершение работы ОС. Непредвиденное или случайное отключение питания платформы может создать не мало проблем:
- повредить структуру файловых систем на накопителях;
- привести к потере важных данных;
- сделать загрузку ОС невозможной;
- повредить карту Micro-SD;
- вывести из строя какое-то периферийное устройство.
Для ручного завершения работы ОС на компьютере можно использовать опции графической оболочки, в Raspbian также есть такая возможность: это можно сделать из графической оболочки или же через консоль командой "poweroff".
На корпусах стационарных компьютеров и ноутбуков, как правило, присутствует кнопка управления питанием, которая позволяет включать и выключать компьютер используя для этого интерфейс ACPI (Advanced Configuration and Power Interface).
В случае с малинкой, такая кнопка пока что заранее не предвидена производителем. Завершение работы ОС можно удобно выполнять из графического менеджера или же из консоли, подключившись к малинке локально или удаленно по SSH (Secure SHell).
В случае когда малинка работает без графической оболочки и подключенного дисплея (как сервер, роутер и т.п.) не всегда удобно подключаться к ней каждый раз с другого компьютера и завершать работу ОС консольной командой "poweroff". Здесь возможно что очень не хватает той самой аппаратной кнопки завершения работы, которая присутствует на большинстве компьютеров.
Подключением и настройкой работы такой кнопки к в Raspberry Pi мы и займемся в этой статье.
Как это работает
Суть идеи очень проста: отслеживать событие по нажатию на кнопку и запускать процесс корректного завершения ОС, что в последствии позволит безопасно выключить питание платформы. Для пояснения принципов реализации данного метода я нарисовал небольшую блок-схему:
Рис. 1. Завершение работы Raspbian и выключение Raspberry Pi используя кнопку, GPIO, Systemd и Python3.
При запуске операционной системы (ОС) Raspbian обрабатывается задание (юнит, unit) для Systemd, которое в свою очередь запускает программу-скрипт на языке Python3.
Этот скрипт после своего запуска остается работать в фоновом режиме как служба (демон), с реализацией этой возможности нам поможет модуль для python под названием "daemonize".
В программе скрипта выполняется отслеживание состояния одного из каналов GPIO c использованием прерываний, это возможно благодаря модулю "RPi.GPIO".
К отслеживаемому каналу подвязан физический пин на гребенке контактов GPIO в Raspberry Pi, к этому пину и подключена кнопка. После нажатия на кнопку сработает функция прерывания, которая запустит команду завершения работы "poweroff" с системными привилегиями.
Таким образом, ОС сможет корректно завершить все задачи, закрыть сетевые подключения и открытые файловые дескрипторы, отмонтировать файловые системы и подать сигнал аппаратного выключения платформе Raspberry Pi.
Давайте попробуем реализовать данный функционал в минимальной конфигурации, но прежде нужно провести некоторые подготовительные работы.
Подготовка
Как уже было сказано выше, для работы нам понадобится Python3 и два модуля - "daemonize" и "RPi.GPIO". Первый модуль отсутствует в установке Raspbian по умолчанию, а второй есть в наличии, но его также желательно обновить.
Выполним эти задачи следующими командами в консоли Raspbian:
sudo apt-get update
sudo apt-get install python3 python3-pip python3-rpi.gpio
pip3 install daemonize
Проверим доступность установленных компонентов, для этого запустим интерпретатор Python 3:
python3
Должна быть выведена версия python (3.x) и отображена строка для запросов ">>>". Теперь выполним импорт библиотек и получим некоторую полезную информацию.
Команды, которые нужно вводить, начинаются в каждой строке после символов ">>>", остальные же данные из примера ниже - это выводимая этими командами информация:
>>> import RPi.GPIO as GPIO
>>> GPIO.VERSION
'0.6.3'
>>> GPIO.RPI_INFO
{'REVISION': 'a02082', 'RAM': '1024M', 'TYPE': 'Pi 3 Model B', 'MANUFACTURER': 'Sony', 'PROCESSOR': 'BCM2837', 'P1_REVISION': 3}
>>> from daemonize import Daemonize
>>>
Если все запустилось успешно и не было никаких сообщений об ошибках, то можно двигаться дальше.
О том, для чего нужен модуль "RPi.GPIO" и как с ним работать, я уже подробно рассказывал в предыдущей статье, а вот модуль "daemonize" - это что-то новое.
Библиотека "daemonize" позволяет превратить программу, написанную на языке Python, в процесс, который может работать на Unix-подобных операционных системах.
С ее помощью мы сможем "деймонизировать" написанную программу, которая после своего запуска продолжит работать в фоне и выполнять нужные нам полезные задачи. Она будет работать как процесс - со своими свойствами и PID (Process ID).
Подключение кнопки
Правила и особенности подключении кнопки к GPIO в Raspberri Pi я уже описывал в прошлых статьях - статья 1, статья 2. Здесь мы также будем подключать кнопку используя два резистора, как в примере из "статьи 2".
Рис. 2. Схема безопасного подключения кнопки к GPIO в Raspberry Pi.
Здесь будет использован канал IO12, вы же можете использовать любой другой доступный канал, изменив его номер в коде программы.
Исходный код программы
Первым делом нужно определиться где будет храниться файл с программой (скрипт), написанной на языке программирования Python 3. В операционной системе GNU/Linux для этой цели неплохо подойдут директории "/usr/local/bin/" и "/usr/local/sbin/".
В последней из них я уже предлагал выполнять хранение скрипта с правилами для брандмауэра IPTables. Давайте здесь также используем эту же директорию.
Откроем на редактирование новый файл, который потом будет сохранен в директории "/usr/local/sbin/", и назовем его "gpio-poweroff-daemon.py":
sudo nano /usr/local/sbin/gpio-poweroff-daemon.py
Код программы достаточно простой, он выглядит следующим образом:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Raspberry Pi Power-OFF daemon for switch on GPIO
# https://ph0en1x.net
import RPi.GPIO as GPIO
from daemonize import Daemonize
from subprocess import call
from time import sleep
def poweroff_callback(channel):
""" GPIO theraded callback function. """
call('poweroff', shell=True)
def main():
""" Main program function. """
print('Power-Switch watchdog started.')
GPIO.setmode(GPIO.BCM)
GPIO.setup(12, GPIO.IN)
GPIO.add_event_detect(
12,
GPIO.FALLING,
callback=poweroff_callback,
bouncetime=300)
# Main program cycle.
while True:
sleep(5)
daemon = Daemonize(
app="power-switch",
pid='/tmp/gpio-poweroff-daemon.pid',
action=main)
daemon.start()
Для выхода из редактора "nano" нажимаем сочетание клавиш CTRL+X, потом подтверждаем сохранение содержимого файла клавишей X.
Чтобы скрипт можно было запускать на исполнение, назначим ему соответствующие права (x = eXecute):
sudo chmod +x /usr/local/sbin/gpio-poweroff-daemon.py
Запускаем скрипт и проверить его работоспособность:
sudo /usr/local/sbin/gpio-poweroff-daemon.py
Итак, скрипт запущен и работает как фоновый процесс. Узнать ID этого процесса (PID, число) можно одной из следующих команд:
ps ax | grep [g]pio-poweroff-daemon | awk '{print $1}'
cat /tmp/gpio-poweroff-daemon.pid
Зная ID процесса его можно принудительно завершить:
sudo kill -9 `cat /tmp/gpio-poweroff-daemon.pid`
В момент когда скрипт работает, нажав на подключенную к каналу 12 кнопку, операционная система Raspbian запустит команду "poweroff" и тем самым начнет процесс завершения работы и подготовки к выключению малинки.
Чтобы повторно загрузить ОС, нужно отключить малинку от питания +5В и потом снова включить.
Для автоматизации запуска скрипта при загрузке ОС перейдем к следующему пункту.
Скрипт-задание для systemd
Для автоматического запуска скрипта, написанного на Python, при загрузке ОС мы создадим новый юнит (задание) для системного менеджера GNU/Linux - systemd. Данный файл будет размещен в директории "/etc/systemd/system/", где systemd сможет успешно обнаружить и обработать наш файл с заданием.
Откроем новый файл для редактирования:
sudo nano /etc/systemd/system/gpio-poweoff.service
Поместим в него следующее содержимое:
[Unit]
Description=GPIO Power-OFF Daemon
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/gpio-poweroff-daemon.py
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Разрешим менеджеру systemd обработку этого файла с заданием:
sudo systemctl enable gpio-poweoff.service
Запустим задание на выполнение:
sudo systemctl start gpio-poweoff.service
Теперь мы можем посмотреть статус задания:
systemctl status gpio-poweoff.service
Будут отображены пути к файлу со скриптом и к файлу с юнитом, текущее состояние процесса и его PID, а также логи выполнения задания.
Перезагружаем Raspbian и дополнительно проверяем статус задания "gpio-poweroff.service", а также работу кнопки, которая должна отреагировать на свое нажатие завершением работы системы.
Пример с применением экрана SSD1306
В этом примере мы будем использовать OLED-дисплей SSD1306 для вывода сообщения с обратным отсчетом до начала запуска процедуры завершения работы системы.
Алгоритм работы: при нажатии на кнопку выводим на экран сообщение о завершении работы, а также числа от 4 до 1 с задержкой 1 секунда (отсчет), потом запускаем команду для завершения работы - poweroff.
Схема подключения кнопки к малинке остается той же самой что и раньше (смотрим рисунок 2). Останется лишь дополнительно подключить OLED-дисплей. О том, как работать с дисплеем SSD1306, подключенным по шине I2C, я уже подробно рассказывал в одной из статей. Там же показана схема подключения дисплея к Raspberry Pi 2, для 3-й версии - все аналогично.
Включим шину I2C:
sudo raspi-config
Идем по пунктам меню: “Advanced Options” - “I2C” - "Yes" - жмем "Finish". Перезагружаем ОС командой "reboot".
Установим необходимые пакеты для работы с шиной I2C:
sudo apt-get update
sudo apt-get install -y python-smbus i2c-tools
Проверяем определился ли подключенный к шине I2C дисплей SSD1306:
sudo i2cdetect -y 1
Пример вывода команды:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
В моем случае, адрес дисплея SSD1306 - "3c" (0x3c).
Больше информации по шине I2C и работе с ней вы можете узнать из статьи про I2C в Raspberry Pi и PCF8591.
Для работы с дисплеем будем использовать старую, но проверенную версию "0.2.0" библиотеки от Ричарда Хала. Создадим для ее загрузки и распаковки временную директорию:
cd /tmp && mkdir ssd1306 && cd ssd1306
Выполним загрузку архива с библиотекой и распакуем его:
wget -c "https://ph0en1x.net/uploads/File/ssd1306-0.2.0-no-doc.tar.gz"
tar -xf ssd1306-0.2.0-no-doc.tar.gz
В папку "/usr/local/sbin/", где находится скрипт "gpio-poweroff-daemon.py", необходимо скопировать из этой библиотеки два файла - "device.py" и "render.py". Выполним копирование файлов, добавив к их именам префикс "oled_":
sudo cp oled/device.py /usr/local/sbin/oled_device.py
sudo cp oled/render.py /usr/local/sbin/oled_render.py
Также нам понадобится библиотека "Pillow", установим ее:
sudo pip3 install Pillow
Для новой программы будем использовать тот же скрипт, что и в предыдущем примере:
sudo nano /usr/local/sbin/gpio-poweroff-daemon.py
Нужно заменить все содержимое файла (для быстрого удаления строк используйте комбинацию клавиш CTRL+K) следующим кодом:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Raspberry Pi Power-OFF daemon for switch on GPIO (v2)
# https://ph0en1x.net
import RPi.GPIO as GPIO
from daemonize import Daemonize
from oled_device import ssd1306
from oled_render import canvas
from PIL import ImageFont
from subprocess import call
from time import sleep
poweroff_is_enabled = False
def poweroff_callback(channel):
""" GPIO theraded callback function. """
global poweroff_is_enabled
poweroff_is_enabled = True
def message():
""" Show countdown message on display. """
device = ssd1306(port=1, address=0x3C)
font_path = '/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf'
font = ImageFont.truetype(font_path, 20)
for i in reversed(range(0,5)):
with canvas(device) as draw:
draw.text((2, 0), "Shutdown...", font=font, fill=255)
count_str = str(i) if i else 'Bye!'
draw.text((50, 30), count_str, font=font, fill=255)
sleep(1)
def main():
""" Main program function. """
print('Power-Switch watchdog started.')
global poweroff_is_enabled
GPIO.setmode(GPIO.BCM)
GPIO.setup(12, GPIO.IN)
GPIO.add_event_detect(
12,
GPIO.FALLING,
callback=poweroff_callback,
bouncetime=300)
# Main program cycle.
while True:
if poweroff_is_enabled:
message()
sleep(1)
call('poweroff', shell=True)
sleep(1)
daemon = Daemonize(
app="power-switch",
pid='/tmp/gpio-poweroff-daemon.pid',
action=main)
daemon.start()
Для запуска скрипта будет использоваться уже созданный в предыдущем примере юнит "gpio-poweoff.service". Запуск, остановку и просмотр статуса программы в ручном режиме можно выполнить следующими командами:
sudo systemctl start gpio-poweoff.service
sudo systemctl status gpio-poweoff.service
sudo systemctl stop gpio-poweoff.service
В этой программе была добавлена глобальная переменная "poweroff_is_enabled", которая при вызове прерыванием функции обратной связи"poweroff_callback" изменит свое значение на "True".
Состояние глобальной переменной "poweroff_is_enabled" отслеживается в основном цикле программы, если оно изменится на "True" - будет запущена функция "message", которая выведет на экран сообщение с отсчетом. После завершения отсчета, система запустит команду "poweroff", которая начнет процедуру завершения работы ОС.
В функции для вывода сообщений на экран SSD1306 использован внешний файл с TTF-шрифтом, который расположен по пути: '/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf'.
Использование TTF-шрифта позволило задать размер для символов и сделать их более удобными к прочтению. В системной папке '/usr/share/fonts/truetype/' есть и другие шрифты, которые вы можете использовать:
ls /usr/share/fonts/truetype/
Список папок с TTF-шрифтами, доступными на моей Raspbian:
dejavu droid freefont lato liberation2 noto openoffice piboto
В завершение приведу небольшое демонстрационное видео для данного примера, в качестве подопытного здесь выступает мой самодельный мини-сервер.
Заключение
Добавление кнопки к Raspberry Pi, нажатие которой будет корректно и безопасно завершать работу операционной системы Raspbian GNU/Linux - это просто и удобно. Данное не сложное решение обезопасит файловые системы малинки от повреждения и потери данных, продлит срок службы карты памяти и платформе в целом.
Полезные ссылки:
Здравствуйте!
Очень понравилась Ваша статья про создание роутера на базе малинки. Скажите, пожалуйста, планируется ли вторая часть? Интересует программная часть. Спасибо!
PS. Также очень интересно посмотреть на изготовление корпуса с кнопками, где не сами тактовые кнопки будут торчать из корпуса, а специальные "крышки" (не знаю как их назвать), для эстетики
Здравствуйте! Статья по программной части для самодельного роутера получилась достаточно объемная и на 90% уже завершена. Постараюсь на следующей неделе опубликовать.
Одна из идей насчет эстетики: кнопочную панель можно сместить в середину, чтобы колпачки кнопок были на уровне с корпусом, после этого рисуем в графическом редакторе трафарет с подписями и распечатываем его на пленке, обрезаем пленку и приклеиваем на корпус поверх кнопок - готово.
Также кнопки можно было сделать сенсорными, но это уже отдельная история. Итак уже отклонились от темы статьи "Безопасное завершение работы Raspbian...".
UPD: Статья опубликована: Самодельный роутер и мини-сервер на Raspberry Pi - Часть 2 (программы)
а реально ли сделать включение и отключение распберри одной кнопкой, как на обычном пк?
Айрат, реально. Для этого понадобится собрать небольшую схему управления питанием на основе микроконтроллера (МК) и нескольких транзисторов, а также написать программу для МК.
Микроконтроллер будет отвечать за ждущий режим, при нажатии на кнопку он подаст питание на малинку. При повторном нажатии на кнопку - отправит на один из GPIO сигнал что нужно завершать работу операционной системы и будет ждать примерно 30-40 сек, а потом отключит питание малинки.
Похожая штука реализована в моем самодельном роботе на основе Raspberry Pi, статьи о его изготовлении будут опубликованы позже.
А если вместо МК простую логику, не вариант?
Первый вариант для своего робота делал именно на простой логике: использовал микросхему К561ТМ2 (одна половинка для мигания светодиодом ждущего режима, вторая - как триггер для сигналов ON-OFF), реле времени на кремниевых транзисторах и конденсаторах, ключи на полевиках IRF9610.
Плюс такого варианта в сравнении со схемой на МК - простота сборки и ничего не нужно прошивать, минус - сложно перестраивать на другие режимы и таймауты (придется менять или даже добавлять компоненты), фиксированный алгоритм работы.