Closed mozoh closed 4 years ago
Версия 1.0.0, как понимаю. Про функцию понял, проверю и поправлю.
Про стабильность: как далеко у вас то что опрашивает бризер от самого бризера? Многие жалуются на стабильность связи, если расстояние "большое". И, на всякий случай, уточню: в момент работы с бризером через модуль к нему не пытается подключаться приложение или пульт, или что-нибудь еще?
Хотя первый лог довольно странный: последний пакет он все же получил. Видимо это было не очень быстро.
Первая часть исправлена в v1.0.1, А про стабильность еще вот тут можно почитать:
Компонент использует этот модуль и все что обсуждалось там применимо и к использованию модуля без HomeAssistant.
Про 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
Протестировал, максимально поднеся raspberry к тиону (50 сантиметров), при этом убив приложение на смартфоне и немного подождав: результаты те же.
Нет, для 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'у.
По bluetoothctl так же видно, что он [CHG] Device FB:FA:3B:AE:82:0D Connected: no
происходит после вылета из скрипта.
Повторил все в "чистых" условиях со вторым Lite: запейрил через bluetoothctl, все успешно, положил в метре - скрипт валится точно так же, и ни разу инфу до конца получить не удалось. Причем в bluetoothctl все хорошо - пейрится, коннектится и т.д. без проблем. Возможно дело в usb-донгле, который я использую (c raspberry b+ первого поколения). Попробую раздобыть малинку поновее, zero w например.
Немного покопался в коде: Если сделать так, то по прошествии 10 секундной паузы все парсится и выдается правильный json. Видимо все таки ошибка в алгоритме.
_btle.waitForNotifications(1.0) -- штука которая секунду ждет, когда бризер пришлет данные по каналу уведомлений. Когда по каналу уведомлений приходят данные, то вызывается callback def handleNotification(self, handle: int, data: bytes):, который в _delegation.data кладет данные. И в этот момент waitForNotifications должна прерываться и возвращать true. И именно эта часть у вас не работает.
Ваш вариант не корректен, поскольку обращение к self._data, без успешного завершения _collect_message некорректен: в _data будет или предыдущий запрос или непойми что.
Я сейчас в dev залью альтернативный вариант работы с Notification, по аналогии с тем, как это было сделано для bleak. Попробуйте его, пожалуйста.
Но я как раз добавил _collect_message в случай когда "Waiting too long for data" после notify.read(). Мне показалось, что по логу видно, что данные приходят после этих событий. Заранее извиняюсь, в теме не разбираюсь. Обязательно попробую обновление.
Готово.
С collect_message
за пределами цикла все сложно: lite посылает ответ из нескольких пакетов. Наиболее интересный -- первый и второй. Все остальное можно, в принципе, игнорировать. А collect_message
собирает _data только если получен полный пакет: установлен маркер мультипакета и пришел последний пакет.
ps. Извиняться не нужно: вы помогаете сделать модуль лучше, за что вам спасибо.
К сожалению это не исправило ситуацию, снова Waiting too long for data
.
Тут что-то не так пошло с новым алгоритмом: _collect_message
должен при вызове сообщать что он от Tion'a что-то получил, а этих сообщений нет. Те в ветку if self._delegation.haveNewData:
он не заходит...
Сейчас поковыряюсь на тему того почему так происходит.
Обновил dev. Код не обрабатывал callback'и пока находился в цикле. Поправил это. Сейчас должно все корректно обрабатывать.
Вернулись к началу, часть приходит нормально, часть после notify.read() вне цикла.
Если добавить вот сюда self.notify.read() все срабатывает без ошибок, и с верыми данными (совпадает с состоянием тиона).
Решил заодно потестировать set() функцию, добавил `device.set({'fan_speed':2}) в тест.
Похоже, что опечатка и должно быть state
вместо status
.
В общем, с двумя этими фиксами, все работает замечательно. Я написал маленькую mqtt обвязку и счастлив, что могу наконец управлять этим добром из node-red.
Выложите mqtt на git, в соседний репозиторий? Под TionAPI сделаем отдельный репозиторий, на подобии python-server'a. Я думаю многим это будет полезно.
Касательно read -- поправил основной код более корректным образом. Можете проверить что все работает правильно? На моем S3 полет нормальный, но он умещает всю информацию в один пакет.
Проверил обновление - теперь все работает правильно, без проблем.
Про 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()
Можно это оформить хоть в каком-то читаемом виде и закинуть в репозиторий, чтобы люди не начинали с чистого листа. Репозиторий сделал, права дал. Если будет желание и время -- действуйте! ;)
А это issue я закрываю. Релиз модуля будет в ближайшее время.
Если руками добавить лишний аргумент, то начинает работать, но как-то нестабильно: инфу получается достать примерно один из пяти раз, в остальных получаю:
или