Автоматизация PetKit Smart Spray K3 через Bluetooth и интеграция с Home Assistant
Я думаю у всех есть домашние питомцы и за всеми так или иначе надо убирать - разбросанный силикагель (для кошек как пример). В один из дней, знатно наступив на один из силикагелевых шариков, которые разбросала моя кошка, я понял, что мне это надоело и установив Home Assistant, а так же датчик движения в кошачий лоток (который, к слову, выглядит как пуфик с дыркой для входа кошки) я написал простенький скрипт на то, чтобы робот-пылесос убирал определенную зону после того как кошка сделает свои грязные дела. Все заработало и мои, а так же моей жены телесные травмы свелись к минимуму (все же задержка перед уборкой аж целых 5 минут), но я начал думать о том что было бы неплохо еще и запах убивать после особо пахучих дел, но времени на это не было (да и желания, так как готовых решений не было, тем более появился ребенок и время на подобные дела вообще свелось к минимуму).
Шло время и вопрос о запахе из лотка становился все острее, все-таки не розами пахнет.
Обычные освежители не подходили, так как они довольно массивные, и я не знаю их влияния на кошачий нюх, а тем более здоровье, но тут мне на глаза попался Smart Spray от PetKit, который решал все мои проблемы (еще и фонарик там был). Вот собственно и он:
Я уже было обрадовался, что все проблемы решены, но данный девайс работал только при помощи приложения (если не был в связке с умным лотком все от того же производителя). Умный лоток за 45к покупать не очень хотелось, да и не подходит он к интерьеру своим дизайном, плюс еще розетку под это дело надо, расходники и т. д. Но руки растут из правильного места, да и умом природа не обделила, и я стал думать, как им можно управлять через синие зубы (Bluetooth).Выписал на TaoBao простенький свисток от Nordic Semiconductor:
Прошил его для работы в режиме сниффера трафика и начал смотреть, что ж там такое происходит. Если вкратце, то методом великого тыка я нашел следующие сервисы и их характеристики:
Сервис: 00001800-0000-1000-8000-00805f9b34fb
├── Характеристика: 00002a00-0000-1000-8000-00805f9b34fb [read, notify]
├── Характеристика: 00002a01-0000-1000-8000-00805f9b34fb [read]
└── Характеристика: 00002a04-0000-1000-8000-00805f9b34fb [read]
Сервис: 00001801-0000-1000-8000-00805f9b34fb
└── Характеристика: 00002a05-0000-1000-8000-00805f9b34fb [indicate]
Сервис: 0000aaa0-0000-1000-8000-00805f9b34fb
├── Характеристика: 0000aaa2-0000-1000-8000-00805f9b34fb [write-without-response, write]
└── Характеристика: 0000aaa1-0000-1000-8000-00805f9b34fb [read, notify]
А так же с помощью простенького скрипта кидал в единственную характеристику для записи (write) команды, которые удалось перехватить. Вот, к слову, и он:
Код для тестирования реакций спрея
import asyncio
from bleak import BleakClient
# MAC-адрес вашего устройства
device_address = "aа:аа:аа:аа:аа:аа"
# UUID характеристики для записи
characteristic_uuid = "0000aaa2-0000-1000-8000-00805f9b34fb"
# Команды для передачи в байтах (hex)
commands = [
'fafcfdd501000000fb', # Инициализации
'fafcfd56010108000000*********fb', # Аутентификация
#'fafcfddc010a02000103fb', # Команда включения спрея
'fafcfddc010b02000203fb', # Команда включения света на пару секунд если не включен спрей
#'fafcfddc010b02000203fb', # команда включения света навсегда, если я включаю спрей, но в ячейке 0a включить свет не получится, только в 0b
]
async def send_command(client, command):
"""Отправка команды устройству"""
byte_command = bytes.fromhex(command)
await client.write_gatt_char(characteristic_uuid, byte_command)
print(f"Команда {command} отправлена")
await asyncio.sleep(0.5) # Пауза после каждой команды
# Попробуйте читать ответ, если устройство его отправляет
try:
response = await client.read_gatt_char(characteristic_uuid)
print(f"Ответ: {response.hex()}")
except:
pass
async def main():
async with BleakClient(device_address) as client:
print(f"Подключен к {device_address}") # Отправка всех команд
for command in commands:
await send_command(client, command)
await asyncio.sleep(0.5) # Добавляем задержку между отправками, если необходимо
if name == "__main__":
asyncio.run(main())
В какой-то момент я понял, что команды не получится отправить без инициализации и аутентификации. И опять-таки методом проб и ошибок я подобрал эти команды. К слову, аутентификация имела свою хитрость, но обо всем по порядку. И вот я уже управлял спреем и лампочкой у спрея со своего ноутбука — радости не было предела. Я начал писать интеграцию для Home Assistant, чем занимался впервые, к слову. Позже я релизнул свою интеграцию, чтобы другие тоже могли пользоваться.
Через какое-то время я увидел сообщение в GitHub о проблеме в моем репозитории: мол, не работает, помогите. Я начал разбираться, в чем же дело, и понял: ключ аутентификации не так прост, как я думал. Ведь это не просто команда 'fafcfd56010108000000*********fb'
— в этой команде был скрыт секретный ключ устройства из облака PetKit, который и используется для работы устройства. Об этом, к слову, узнал не я — мне помог пользователь с GitHub с ником Jezza34000. Он занимался разработкой API для PetKit, да и вообще довольно плотно был знаком с облачным сервисом этой компании. Я использовал его библиотеку для доработки своей интеграции, чтобы можно было добавлять девайс прямо из аккаунта PetKit и использовать секретный ключ из облака. В целом, я и сейчас работаю над улучшением интеграции — ведь еще нужно знать заряд батареи и уровень жидкости, но основные функции данная интеграция уже выполняет.
Кому интересно, вот ссылка на репозиторий