TionAPI / tion_python

Python module for Tion
GNU Lesser General Public License v3.0
64 stars 9 forks source link

Проблемы с Lite. #7

Closed mozoh closed 4 years ago

mozoh commented 4 years ago
pi@raspberrypi:~ $ python3 ./tion_python/tests/lite.py fb:fa:3b:ae:82:0d
DEBUG:tion_btle.tion:Enabling notification
DEBUG:tion_btle.tion:Notify handler is 18
DEBUG:tion_btle.tion:Will write b'\x01\x00' to 19 handle
DEBUG:tion_btle.tion:Result is {'rsp': ['wr']}
Traceback (most recent call last):
File "./tion_python/tests/lite.py", line 15, in <module>
result = device.get()
File "/home/pi/.local/lib/python3.7/site-packages/tion_btle/tion.py", line 215, in get
response = self._get_data_from_breezer(keep_connection)
TypeError: _get_data_from_breezer() takes 1 positional argument but 2 were given

Если руками добавить лишний аргумент, то начинает работать, но как-то нестабильно: инфу получается достать примерно один из пяти раз, в остальных получаю:

pi@raspberrypi:~ $ python3 ./tion_python/tests/lite.py fb:fa:3b:ae:82:0d
DEBUG:tion_btle.tion:Enabling notification
DEBUG:tion_btle.tion:Notify handler is 18
DEBUG:tion_btle.tion:Will write b'\x01\x00' to 19 handle
DEBUG:tion_btle.tion:Result is {'rsp': ['wr']}
DEBUG:tion_btle.tion:Doing _try_write. Attempt 1/3
DEBUG:tion_btle.tion:Writing 8010003a0232120dd71f8f48d3c31abbaa to 98f00002-3788-83ea-453e-f52244709ddb
DEBUG:tion_btle.lite:Collecting data
DEBUG:tion_btle.tion:Got data in 18 response 0049003a6e31120dd71f8ff5bccd2ccdd8020a01
DEBUG:tion_btle.lite:Got 0049003a6e31120dd71f8ff5bccd2ccdd8020a01 from tion
DEBUG:tion_btle.tion:Got data in 18 response 40f90a1580e521009a791f0066d4cd0018e85200
DEBUG:tion_btle.lite:Got 40f90a1580e521009a791f0066d4cd0018e85200 from tion
DEBUG:tion_btle.tion:Got data in 18 response 4000000000000000000000000000000000000000
DEBUG:tion_btle.lite:Got 4000000000000000000000000000000000000000 from tion
DEBUG:tion_btle.lite:Waiting too long for data
DEBUG:tion_btle.tion:Got data in 18 response c000000000000a1419020406061c005a73
Traceback (most recent call last):
File "./tion_python/tests/lite.py", line 15, in <module>
result = device.get()
File "/home/pi/.local/lib/python3.7/site-packages/tion_btle/tion.py", line 215, in get
response = self._get_data_from_breezer(keep_connection)
File "/home/pi/.local/lib/python3.7/site-packages/tion_btle/lite.py", line 174, in _get_data_from_breezer
raise TionException("Lite _get_data_from_breezer", "Could not get breezer state")
tion_btle.tion.TionException: ('Lite _get_data_from_breezer', 'Could not get breezer state') 

или

pi@raspberrypi:~ $ python3 ./tion_python/tests/lite.py fb:fa:3b:ae:82:0d
WARNING:tion_btle.tion:Got Failed to connect to peripheral fb:fa:3b:ae:82:0d, addr type: random exception
Traceback (most recent call last):
File "./tion_python/tests/lite.py", line 15, in <module>
result = device.get()
File "/home/pi/.local/lib/python3.7/site-packages/tion_btle/tion.py", line 214, in get
self._connect()
File "/home/pi/.local/lib/python3.7/site-packages/tion_btle/tion.py", line 308, in _connect
raise e
File "/home/pi/.local/lib/python3.7/site-packages/tion_btle/tion.py", line 297, in _connect
self._btle.connect(self.mac, btle.ADDR_TYPE_RANDOM)
File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 445, in connect
self._connect(addr, addrType, iface)
File "/usr/local/lib/python3.7/dist-packages/bluepy/btle.py", line 439, in _connect
raise BTLEDisconnectError("Failed to connect to peripheral %s, addr type: %s" % (addr, addrType), rsp)
bluepy.btle.BTLEDisconnectError: Failed to connect to peripheral fb:fa:3b:ae:82:0d, addr type: random
IATkachenko commented 4 years ago

Версия 1.0.0, как понимаю. Про функцию понял, проверю и поправлю.

Про стабильность: как далеко у вас то что опрашивает бризер от самого бризера? Многие жалуются на стабильность связи, если расстояние "большое". И, на всякий случай, уточню: в момент работы с бризером через модуль к нему не пытается подключаться приложение или пульт, или что-нибудь еще?

Хотя первый лог довольно странный: последний пакет он все же получил. Видимо это было не очень быстро.

IATkachenko commented 4 years ago

Первая часть исправлена в v1.0.1, А про стабильность еще вот тут можно почитать:

Компонент использует этот модуль и все что обсуждалось там применимо и к использованию модуля без HomeAssistant.

mozoh commented 4 years ago

Про pair еще вопрос: для Lite использовать pair.py? У меня 2 Lite бризера, для одного использовал, но он завершается с ошибкой (видимо, потому что код для s3), и с ним худо бедно удается связаться (на логах выше), для второго не вызывал, с ним только:

WARNING:tion_btle.tion:Got Failed to connect to peripheral d6:a7:26:09:47:21, addr type: random exception 
mozoh commented 4 years ago

Протестировал, максимально поднеся raspberry к тиону (50 сантиметров), при этом убив приложение на смартфоне и немного подождав: результаты те же.

IATkachenko commented 4 years ago

Нет, для lite специальный tion-pair не нужен, но нужен обычный BT pairing. Я пока не знаю как это оформить на уровне bluepy. И не уверен что вообще буду его решать: сейчас в приоритете переход на асинхронный режим работы, а bleak BT pairing умеет сам по себе.

На малине можно параллельно запустиь bluetoothctl -- будет видно как модуль подключается/отключается к/от бризера. "Waiting too long for data" означает что за 10 секунд бризер не прислал ничего по каналу уведомлений. Обычно это происходит если модуль отключается (на уровне BT) от бризера раньше, чем получает все данные. Но вот дальше у вас сразу идет "Got data in 18 response ..." -- это как раз маркер того, что данные пришли, те отключения не было.

У меня пока нет идей, на тему того что происходит, кроме как проблем с радиоканалом. Но ваш эксперимент, с поднесением малины близко к бризеру, эту догадку опровергает.

Можно попробовать добавить вот такую конструкцию

logging.basicConfig(
    format='%(asctime)s %(levelname)-8s %(message)s',
    level=logging.DEBUG,
    datefmt='%Y-%m-%d %H:%M:%S')

вместо logging.basicConfig(level=logging.DEBUG) в lite.py Тогда в логах будут видны timestamp и может быть будет видно что на самом деле он отваливается не по timeout'у.

mozoh commented 4 years ago

image По bluetoothctl так же видно, что он [CHG] Device FB:FA:3B:AE:82:0D Connected: no происходит после вылета из скрипта.

mozoh commented 4 years ago

Повторил все в "чистых" условиях со вторым Lite: запейрил через bluetoothctl, все успешно, положил в метре - скрипт валится точно так же, и ни разу инфу до конца получить не удалось. Причем в bluetoothctl все хорошо - пейрится, коннектится и т.д. без проблем. Возможно дело в usb-донгле, который я использую (c raspberry b+ первого поколения). Попробую раздобыть малинку поновее, zero w например. image

mozoh commented 4 years ago

Немного покопался в коде: image Если сделать так, то по прошествии 10 секундной паузы все парсится и выдается правильный json. Видимо все таки ошибка в алгоритме.

IATkachenko commented 4 years ago

_btle.waitForNotifications(1.0) -- штука которая секунду ждет, когда бризер пришлет данные по каналу уведомлений. Когда по каналу уведомлений приходят данные, то вызывается callback def handleNotification(self, handle: int, data: bytes):, который в _delegation.data кладет данные. И в этот момент waitForNotifications должна прерываться и возвращать true. И именно эта часть у вас не работает.

Ваш вариант не корректен, поскольку обращение к self._data, без успешного завершения _collect_message некорректен: в _data будет или предыдущий запрос или непойми что.

Я сейчас в dev залью альтернативный вариант работы с Notification, по аналогии с тем, как это было сделано для bleak. Попробуйте его, пожалуйста.

mozoh commented 4 years ago

Но я как раз добавил _collect_message в случай когда "Waiting too long for data" после notify.read(). Мне показалось, что по логу видно, что данные приходят после этих событий. Заранее извиняюсь, в теме не разбираюсь. Обязательно попробую обновление.

IATkachenko commented 4 years ago

Готово.

С collect_message за пределами цикла все сложно: lite посылает ответ из нескольких пакетов. Наиболее интересный -- первый и второй. Все остальное можно, в принципе, игнорировать. А collect_message собирает _data только если получен полный пакет: установлен маркер мультипакета и пришел последний пакет.

ps. Извиняться не нужно: вы помогаете сделать модуль лучше, за что вам спасибо.

mozoh commented 4 years ago

image К сожалению это не исправило ситуацию, снова Waiting too long for data.

IATkachenko commented 4 years ago

Тут что-то не так пошло с новым алгоритмом: _collect_message должен при вызове сообщать что он от Tion'a что-то получил, а этих сообщений нет. Те в ветку if self._delegation.haveNewData: он не заходит... Сейчас поковыряюсь на тему того почему так происходит.

IATkachenko commented 4 years ago

Обновил dev. Код не обрабатывал callback'и пока находился в цикле. Поправил это. Сейчас должно все корректно обрабатывать.

mozoh commented 4 years ago

image Вернулись к началу, часть приходит нормально, часть после notify.read() вне цикла.

mozoh commented 4 years ago

image Если добавить вот сюда self.notify.read() все срабатывает без ошибок, и с верыми данными (совпадает с состоянием тиона).

mozoh commented 4 years ago

Решил заодно потестировать set() функцию, добавил `device.set({'fan_speed':2}) в тест. image

Похоже, что опечатка и должно быть state вместо status.

mozoh commented 4 years ago

В общем, с двумя этими фиксами, все работает замечательно. Я написал маленькую mqtt обвязку и счастлив, что могу наконец управлять этим добром из node-red.

IATkachenko commented 4 years ago

Выложите mqtt на git, в соседний репозиторий? Под TionAPI сделаем отдельный репозиторий, на подобии python-server'a. Я думаю многим это будет полезно.

Касательно read -- поправил основной код более корректным образом. Можете проверить что все работает правильно? На моем S3 полет нормальный, но он умещает всю информацию в один пакет.

mozoh commented 4 years ago

Проверил обновление - теперь все работает правильно, без проблем.

Про mqtt - я набросал максимально простой сервис с paho.mqtt и захадкожеными маками моих тионов. Нужно будет его допилить до удобоваримого состояния: неизвестные модели и кол-во устройств и т.д.

import json
import logging
import paho.mqtt.client as mqtt
from tion_btle.lite import Lite

logging.basicConfig(level=logging.DEBUG)
_LOGGER = logging.getLogger(__name__)
_LOGGER.setLevel("DEBUG")

mac1 = 'YOUR_DEVICE_1_MAC'
mac2 = 'YOUR_DEVICE_2_MAC'
deviceTopic1 = 'tion/'+mac1
deviceTopic2 = 'tion/'+mac2
refreshTopic1 = deviceTopic1+'/refresh'
refreshTopic2 = deviceTopic2+'/refresh'
setTopic1 = deviceTopic1+'/set'
setTopic2 = deviceTopic2+'/set'

maxTries = 3

device1 = Lite(mac1)
device2 = Lite(mac2)
client = mqtt.Client()

def on_connect(client, userdata, flags, rc):
    client.subscribe(refreshTopic1)
    client.subscribe(refreshTopic2)
    client.subscribe(setTopic1)
    client.subscribe(setTopic2)

def on_message(client, userdata, msg):
    success = False
    tryCount = 0

    while not success and tryCount < maxTries:
        if msg.topic == refreshTopic1:
            try:
                client.publish(deviceTopic1, json.dumps(device1.get(True)))
                success = True
            except Exception as e:
                print(e)

        elif msg.topic == refreshTopic2:
            try:
                client.publish(deviceTopic2, json.dumps(device2.get(True)))
                success = True
            except Exception as e:
                print(e)

        elif msg.topic == setTopic1:
            try:
                device1.set(json.loads(msg.payload))
                client.publish(deviceTopic1, json.dumps(device1.get(True)))
                success = True
            except Exception as e:
                print(e)

        elif msg.topic == setTopic2:
            try:
                device2.set(json.loads(msg.payload))
                client.publish(deviceTopic2, json.dumps(device2.get(True)))
                success = True
            except Exception as e:
                print(e)

        tryCount += 1

client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set('YOUR_MQTT_USERNAME', 'YOUR_MQTT_PASSWORD')
client.connect('YOUR_MQTT_HOST', YOUR_MQTT_PORT, 60)
client.loop_forever()
IATkachenko commented 4 years ago

Можно это оформить хоть в каком-то читаемом виде и закинуть в репозиторий, чтобы люди не начинали с чистого листа. Репозиторий сделал, права дал. Если будет желание и время -- действуйте! ;)

А это issue я закрываю. Релиз модуля будет в ближайшее время.