2 заметки с тегом

python

procd — контроль процессов для OpenWrt

На нескольких устройствах, которые я использую в умном доме, установлены одноплатные компьютеры Onion Omega2. Это что-то между Raspberry Pi и ESP8266. Компьютер стоимостью 10$ на базе MIPS-процессора с Linux, Wi-Fi, Ethernet, UART, ШИМ, I2C, SPI, USB и GPIO. Классная игрушка!
Операционная система омеги базируется на OpenWrt, поэтому можно использовать много популярных библиотек, например Python. Только вот нет привычного менеджера процессов systemd. Я где-то читал, что команда создателей OpenWrt работает над интеграцией systemd, но по срокам пока ничего не известно.

Зато есть procd — родной для OpenWrt менеджер процессов. Он позволяет следить за процессами и перезапускать их в случае сбоев.
Для настройки сервиса достаточно создать файл в /etc/init.d/

Пример файла:

#!/bin/sh /etc/rc.common
USE_PROCD=1
START=98
STOP=1

start_service() {
	procd_open_instance
	procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
	procd_set_param command /root/relay/controller.py
	procd_set_param stdout 1 # forward stdout of the command to logd
	procd_set_param stderr 1 # same for stderr
	procd_close_instance
}

Что тут важно:
параметр /root/relay/controller.py — это путь к нашему скрипту Python
параметр respawn — если процесс завершился раньше чем respawn_threshold, то это считается сбоем. И после respawn_retry попыток сервис останавливается.

Последовательность действий:

  1. Создаем скрипт запуска:
nano /etc/init.d/controller
  1. Устанавливаем права на запуск скрипта:
chmod +x /etc/init.d/controller
  1. Добавляем сервис в автозапуск при перезагрузке:
/etc/init.d/controller enable
  1. Запускаем сервис:
/etc/init.d/controller start

Для проверки можем убить процесс. Например, так: killall controller.py
procd сразу же его перезапустит.

 3 комментария    166   2020   init.d   linux   omega2   openwrt   procd   python   systemd

Работа с очередью MQTT в Python

Заметка для начинающих. Хочу показать пример работы с MQTT на Python.
Я использую MQTT в реле, которое управляет световыми приборами в доме. Реле подписывается на топик и ждет команды. В зависимости от поступившей команды, реле подает напряжение на приборы.

Импортируем класс и создаем инстанс

Тут я сразу создаю флажок connected_flag. Он мне будет нужен для работы с коллбэками.

import paho.mqtt.client as mqtt

mqttc = mqtt.Client('relay_listener')
mqttc.on_message = on_message
mqttc.on_connect = on_connect
mqttc.on_disconnect = on_disconnect
mqttc.on_publish = on_publish
mqttc.on_subscribe = on_subscribe
# Uncomment to enable debug messages
#mqttc.on_log = on_log
# Признак успешного соединения
mqttc.connected_flag=False

Подключаемся к серверу

Теперь самое интересное. Я использую loop_start() для того, чтобы mqtt клиент начал делать попытки подключения. Как только подключение установится, сработает событие on_connect, в котором я поставлю флажок connected_flag.

Первая распространенная ошибка — делать только одну попытку подключения. Нет никакой гарантии что связь с сервером будет в момент первой попытки соединения. Мы же работаем с «домашним» сетевым оборудованием. В итоге скрипт периодически будет вылетать с ошибкой при запуске и заметим мы это не сразу.

Сразу после loop_stop() я снимаю флаг mqttc._thread_terminate. Думаю, это баг текущей версии компонента MQTT. Если этого не сделать, то перестанет работать loop_forever().

loop_forever() подвешивает основной поток навечно в режиме ожидания.

mqttc.loop_start()
mqttc.connect(mqtt_credentials_from_file_dict.get("localnet").get("host"), 1883, 60)

# Попытки соединения
while not mqttc.connected_flag: #wait in loop
	time.sleep(1)

mqttc.loop_stop()
mqttc._thread_terminate = False

mqttc.loop_forever()

Подписываемся на топик

Вторая распространенная ошибка. Посмотрим на on_connect.
При обрыве соединения с сервером важно каждый раз вызывать subscribe. Если сделать subscribe только в момент первого соединения, то клиент забудет название канала, на который подписан.

def on_connect(mqttc, obj, flags, rc):
	if rc == 0:
		mqttc.connected_flag=True
		res = mqttc.subscribe(listen_topic, 2)
		if res[0] != mqtt.MQTT_ERR_SUCCESS:
			logging.info(str(datetime.datetime.now()) + " The client is not subscribed")

	if rc == 1:
		logging.info(str(datetime.datetime.now()) +  " Connection failed: incorrect protocol version")
	if rc == 2:
		logging.info(str(datetime.datetime.now()) +  " Connection failed: invalid client identifier")
	if rc == 3:
		logging.info(str(datetime.datetime.now()) +  " Connection failed: server unavailable")
	if rc == 4:
		logging.info(str(datetime.datetime.now()) +  " Connection failed: bad app_id or access_key")
	if rc == 5:
		logging.info(str(datetime.datetime.now()) +  " Connection failed: not authorised")

Получаем и обрабатываем сообщение

Тут вроде бы ничего особеного. Но есть третья распространенная ошибка. Не забывайте, что Python может работать с несколькими потоками. И если мы будем обращаться к одному и тому-же ресурсу одновременно, то получим ошибку. Так что добавляем Lock() или аналог при необходимости.

def on_message(mqttc, obj, msg):
	print(str(datetime.datetime.now()) +  " Recieved data. Topic: "+msg.topic+". Msg:"+str(msg.payload))
 Нет комментариев    73   2020   MQTT   python   умный дом