Как я воевал с китайским умным туалетом для котов
- суббота, 10 мая 2025 г. в 00:00:10
В очередной раз, листая озон, я наткнулся на девайс, который привлек мое внимание. Самоочищающийся лоток для котов Petkit Pura Max, вещь весьма интересная, особенно, если у тебя три кота. Пушистые бандиты у меня крупные, потребляют много калорий и соответственно часто ходят в лоток.
Одна уборка за ними — это целый квест, а если хочешь уехать из дома на день‑другой, это обязательно нужно кого‑то просить убирать за ними. Умную кормушку и фонтан для воды я купил давно, остался только умный лоток. Но жаба знатно начала меня душить, когда я увидел цену этого девайса в районе 40 тысяч рублей, и в тот раз покупать его не стал.
Про эту мысль я уже забыл, пока однажды не зашел на китайский Poizon и не увидел там этот же лоток, но уже за какие‑то смешные 10 тысяч рублей, с доставкой в РФ 12 тысяч.
Недолго думая, я заказываю этот лоток через байера, и примерно через месяц становлюсь счастливым обладателем этого чудного девайса, пора бы радоваться, но нет?
Распаковав, я начал настраивать лоток и подключать к оригинальному приложению, но тут меня ждал сюрприз, данный девайс был заблокирован в моем регионе, мне рекомендовали вернуть его в Китай, либо связаться с продавцом. Продавца тут винить не в чем, он свою работу по доставке выполнил, кто же виноват, что не почитал регион действия лотка? Но у меня, честно говоря, даже в мыслях не было, что кто-то заморочится такой блокировкой.
Умный горшок 1:0 Инженер
Первым делом я пошел читать форумы, где нашел еще много таких же коллег по несчастью, они обсуждали разные варианты, как разблокировать этот горшок, там были и варианты с китайским впн, и установки старых версий apk приложений, кто‑то даже припаял кастомную плату которая через какой‑то период времени замыкает нужные контакты и вызывает отчистку.
Ничего из выше описанного мне не помогло.
Что ж поделать, придется проявить инженерную смекалку.
В заблокированном туалете не работала функция самоочистки и не было коннекта к родному приложению, но при нажатии кнопок руками туалет отчищался и моей первой мыслью стало каким-то образом имитировать ручное нажатие кнопок в нужной последовательности. Сказано, сделано: приобрел хаб от Tuya, к нему датчик движения и 2 кнопки fingerbot, которые имитируют нажатие пальцем. Немного поколдовав с настройками и выбрав оптимальные значения задержек, эта схема заработала, я приклеил все эти датчики к туалету и через пять минут после того, как кот сходил в лоток, кнопки нажимали нужную последовательность. И вуаля, туалет отчищался сам.
Умный горшок 1:1 Инженер
Тут я сравнял счет, теперь можно было уехать на день-другой, а лоток работал сам.
Немного порадовавшись, я обнаружил несколько неприятных нюансов такой работы.
Однажды мы уехали на сутки из дома, и жена, имея привычку выключать все лишнее из розеток, случайно выключила умный хаб Tuya из розетки, вся конструкция перестала работать и пришлось срочно звонить знакомым, чтобы выручили.
Излишние срабатывание, иногда один из котов просто проходил мимо и вызывал срабатывание датчика, при этом он мог в сам лоток даже не сходить.
Коты отгрызали датчики и кнопки, приклеенные на лоток, когда им было скучно.
Когда коты в очередной раз отгрызли датчики, я понял, что моя схема работы далека от идеала, и нужно с этим что-то делать. На работе закончится контест по CTF и окрыленный 66м местом в общем зачете, я решил, что пора по-настоящему взяться за решение этого вопроса.
Для начала я подумал, что раз лоток просит подключение к вай фаю, значит он общается через интернет с серверами Petkit и, возможно, если там не https, у меня получится прочитать этот трафик и найти там какую-то подсказку. Но встал вопрос: как именно завернуть трафик от лотка на прокси? В случае с проксированием трафика телефона все просто, мы в настройках вай фая на девайсе вводим адрес прокси. Но что делать, если это IOT девайс? Тут мне в голову пришла мысль подменить айпи адрес сервера Petkit через DNS.
Наливаем кружку горячего чая и начинаем работу.
Первое, что нужно сделать - подменить резолв домена на наш айпи адрес, для этого я арендовал простенький linux сервер, и на нем установим свой dns прокси.
Для начала устанавливаем bind9
sudo apt update
sudo apt install bind9
После установки проверяем что bind9 работает
nslookup google.com 127.0.0.1
Ответ должен быть похожим на такой:
Server: 127.0.0.1
Address: 127.0.0.1#53
Non-authoritative answer:
Name: google.com
Address: 216.58.207.238
Name: google.com
Address: 2a00:1450:400f:80d::200e
Дальше настраиваем BIND9
Разрешаем BIND9 работать через брандмауэр
sudo ufw allow Bind9
Дальше редактируем конфиг
sudo vim /etc/bind/named.conf.options
По дефолту BIND9 работает только для локальной сети, нам необходимо разрешить все запросы к нему
allow-query { any; };
Так же поскольку наш DNS сервер это просто прокси, то нужно настроить DNS сервера куда будут перенаправляться запросы в случае если наш сервер не будет содержать необходимых данных, я пропишу DNS от Cloudflare
forwarders {
1.1.1.1;
1.0.0.1;
};
Итоговый конфиг у меня получился такой
options {
directory "/var/cache/bind";
forwarders {
1.1.1.1;
1.0.0.1;
};
allow-query { any; };
dnssec-validation auto;
listen-on-v6 { any; };
};
Дальше создаем файл новой зоны /etc/bind/
db.eu-pet.com
и прописываем туда ip адрес нашего сервера
;
; BIND data file for api.eu-pet.com
;
$TTL 86400
@ IN SOA ns1.eu-pet.com. admin.eu-pet.com. (
2024050701 ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL
; Name servers
@ IN NS ns1.eu-pet.com.
; A records
@ IN A <your_server_ip_address>
api IN A <your_server_ip_address>
ns1 IN A <your_server_ip_address>
; Wildcard
;* IN A <your_server_ip_address>
Добавляем наш конфиг в BIND9 /etc/bind/named.conf.local
zone "eu-pet.com" {
type master;
file "/etc/bind/db.eu-pet.com";
};
Проверяем что все настройки корректные и перезапускаем BIND9
sudo named-checkconf
sudo named-checkzone eu-pet.com /etc/bind/db.eu-pet.com
sudo systemctl restart bind9
Теперь выходим с удаленного сервера и проверяем сработал ли DNS
nslookup api.eu-pet.com <your_ip_address>
Должны увидеть такой ответ
Server: 192.168.0.1
Address: 192.168.0.1#53
Name: api.eu-pet.com
Address: <your_ip_address>
Значит наш DNS корректно перенаправляет трафик и мы можем взяться за написание нашей прокси.
Я написал простой прокси на golang, который поднял в докере на том же арендованном сервере, он ловил входящие запросы от лотка, отправлял их дальше на сервера petkit, получал оттуда ответы и затем подменял в ответе нужное поле и уже измененный ответ возвращал в ответе лотку.
Все это можно было бы сделать так же через Charles Proxy, но я хотел сделать решение, которое можно будет легко и просто переиспользовать любому.
Код получился довольно простым и без использования сторонних библиотек
package main
import (
"bytes"
"encoding/json"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
)
const (
targetURL = "http://api.eu-pet.com"
proxyPort = ":8080"
specialPath = "/6/t4/dev_device_info"
)
func modifyResponse(resp *http.Response) error {
if resp.Request.URL.Path != specialPath {
return nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
bodyErr := resp.Body.Close()
if bodyErr != nil {
return bodyErr
}
var data map[string]interface{}
if err := json.Unmarshal(body, &data); err != nil {
log.Printf("JSON parse error: %v", err)
return nil
}
if result, ok := data["result"].(map[string]interface{}); ok {
if settings, ok := result["settings"].(map[string]interface{}); ok {
if autowork, exists := settings["autoWork"].(float64); exists {
log.Printf("Modifying autowork from %.0f to 1", autowork)
settings["autoWork"] = 1
}
}
}
modifiedBody, err := json.Marshal(data)
if err != nil {
return err
}
resp.Body = io.NopCloser(bytes.NewBuffer(modifiedBody))
resp.ContentLength = int64(len(modifiedBody))
resp.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody)))
return nil
}
func NewReverseProxy(target *url.URL) *httputil.ReverseProxy {
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.Director = func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.Host = target.Host
}
proxy.ModifyResponse = func(resp *http.Response) error {
return modifyResponse(resp)
}
proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
log.Printf("Proxy error: %v", err)
rw.WriteHeader(http.StatusBadGateway)
_, _ = rw.Write([]byte("Proxy error: " + err.Error()))
}
return proxy
}
func proxyHandler(proxy http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Proxying request for host: %s", r.Host)
proxy.ServeHTTP(w, r)
return
})
}
func main() {
target, err := url.Parse(targetURL)
if err != nil {
log.Fatal(err)
}
proxy := NewReverseProxy(target)
handler := proxyHandler(proxy)
log.Printf("Starting proxy server on %s", proxyPort)
log.Fatal(http.ListenAndServe(proxyPort, handler))
}
Исходники прокси если вы хотите развернуть у себя лежат тут https://github.com/dzaytsev91/petkit-proxy/
Теперь, когда dns настроен и прокси готово, осталось дело техники, прописываем наш dns в роутер. Дальше я несколько раз подключал лоток через приложение, посмотрел какие запросы/ответы он шлет, и оказалось, что все запросы идут через http, значит наш прокси легко осуществит подмену.
Дальше у меня ушло некоторое время на ковыряние ручек api petkit, и я довольно быстро нашел нужный урл и нужное поле.
Дальше нужно было заставить лоток дернуть именно этот урл, с чем я повозился еще примерно час, я пробовал выключать лоток из розетки, переподключать к приложению, менять wifi сеть и тд. Но ничего не заставляло лоток дернуть нужный мне урл, но в конце концов лоток сдался и совершил запрос (я так и не разобрался когда и почему он это делает), в ответе которого наш прокси успешно подменил поле autoWork=1
. После этого ждем, пока кот сходит по своим делам (благо их трое и они не заставили себя ждать). И вуаля, все работает!
Теперь просто меняем пароль на Wifi сети, к которой подключен лоток, и без связи с интернетом он навсегда запоминает настройки и работает в автоматическом режиме.
Конечно, говорить о полноценной работе лотка не приходится, но самая важная и нужная функция в нем была активирована.
В напряженной схватке я выгрызаю победу у Китайского чуда.Умный горшок 1:2 Инженер