commit
8b8d90befc
7 changed files with 236 additions and 0 deletions
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
2025-11-08 11:57:54 100 |
||||
2025-11-08 11:58:34 100 |
||||
2025-11-08 11:58:54 100 |
||||
2025-11-08 11:59:14 100 |
||||
2025-11-08 11:59:34 100 |
||||
2025-11-08 11:59:54 100 |
||||
2025-11-08 12:00:33 100 |
||||
2025-11-08 12:00:53 100 |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
2025-11-08 11:57:54 46.10 |
||||
2025-11-08 11:58:34 46.09 |
||||
2025-11-08 11:58:54 46.08 |
||||
2025-11-08 11:59:14 46.10 |
||||
2025-11-08 11:59:34 46.12 |
||||
2025-11-08 11:59:54 46.20 |
||||
2025-11-08 12:00:33 46.15 |
||||
2025-11-08 12:00:53 46.20 |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
2025-11-08 11:57:54 23.00 |
||||
2025-11-08 11:58:34 23.03 |
||||
2025-11-08 11:58:54 23.03 |
||||
2025-11-08 11:59:14 23.06 |
||||
2025-11-08 11:59:34 23.07 |
||||
2025-11-08 11:59:54 23.05 |
||||
2025-11-08 12:00:33 23.06 |
||||
2025-11-08 12:00:53 23.10 |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
2025-11-08 11:58:24 3.051 |
||||
2025-11-08 11:58:44 3.051 |
||||
2025-11-08 12:00:03 3.051 |
||||
2025-11-08 12:00:23 3.051 |
||||
2025-11-08 12:01:03 3.051 |
||||
2025-11-08 12:01:23 3.051 |
||||
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
import asyncio |
||||
import signal |
||||
import argparse |
||||
from pathlib import Path |
||||
from bleak import BleakScanner |
||||
from datetime import datetime |
||||
|
||||
DEBUG_MODE = False |
||||
|
||||
# Переопределение print для работы с флагом --debug |
||||
_original_print = print |
||||
|
||||
def debug_print(*args, **kwargs): |
||||
"""Выводит сообщения только в режиме отладки""" |
||||
if DEBUG_MODE: |
||||
_original_print(*args, **kwargs) |
||||
|
||||
# Переопределяем глобальный print |
||||
print = debug_print |
||||
|
||||
def log_data(device_id, data_type, value): |
||||
|
||||
now = datetime.now() |
||||
year_month = now.strftime("%Y_%m") |
||||
day = now.strftime("%d") |
||||
|
||||
log_dir = Path(device_id) / data_type / year_month / day |
||||
log_dir.mkdir(parents=True, exist_ok=True) |
||||
|
||||
filename = log_dir / f"{device_id}_{data_type}_{year_month}_{day}.log" |
||||
|
||||
timestamp = now.strftime("%Y-%m-%d %H:%M:%S") |
||||
with open(filename, "a", encoding="utf-8") as f: |
||||
f.write(f"{timestamp} {value}\n") |
||||
|
||||
async def scan_bthome(): |
||||
def callback(device, adv_data): |
||||
if device.name and device.name.startswith("ATC_"): |
||||
device_id = device.name |
||||
|
||||
srv_data = adv_data.service_data.get("0000fcd2-0000-1000-8000-00805f9b34fb") |
||||
if not srv_data: |
||||
return |
||||
data = bytes(srv_data) |
||||
if len(data) < 3: |
||||
return |
||||
|
||||
# Парсинг формата BTHome v2 |
||||
# Структура зависит от длины пакета: |
||||
# - Пакет 10 байт: только напряжение (байты 4-5) |
||||
# - Пакет 11 байт: батарея (байт 4), температура (байты 6-7), влажность (байты 9-10) |
||||
|
||||
temp = None |
||||
hum = None |
||||
batt_percent = None |
||||
volt = None |
||||
|
||||
# Проверяем формат BTHome (начинается с 0x40) |
||||
if len(data) > 0 and data[0] == 0x40: |
||||
if len(data) == 10: |
||||
# Пакет только с напряжением |
||||
# Байты 4-5: напряжение (little endian, /1000) |
||||
if len(data) >= 6: |
||||
volt_raw = int.from_bytes(data[4:6], "little", signed=False) |
||||
volt = volt_raw / 1000.0 |
||||
elif len(data) >= 11: |
||||
# Пакет с температурой, влажностью и батареей |
||||
# Байт 4: батарея |
||||
if len(data) >= 5: |
||||
batt_percent = data[4] |
||||
# Байты 6-7: температура (little endian, /100) |
||||
if len(data) >= 8: |
||||
temp_raw = int.from_bytes(data[6:8], "little", signed=False) |
||||
temp = temp_raw / 100.0 |
||||
# Байты 9-10: влажность (little endian, /100) |
||||
if len(data) >= 11: |
||||
hum_raw = int.from_bytes(data[9:11], "little", signed=False) |
||||
hum = hum_raw / 100.0 |
||||
|
||||
# Логирование |
||||
if temp is not None: |
||||
log_data(device_id, "temperature", f"{temp:.2f}") |
||||
if hum is not None: |
||||
log_data(device_id, "humidity", f"{hum:.2f}") |
||||
if batt_percent is not None: |
||||
log_data(device_id, "battery", f"{batt_percent}") |
||||
if volt is not None: |
||||
log_data(device_id, "voltage", f"{volt:.3f}") |
||||
|
||||
# Выводим только те значения, которые удалось прочитать |
||||
values = [] |
||||
if temp is not None: |
||||
values.append(f"Температура: {temp:.2f} °C") |
||||
if hum is not None: |
||||
values.append(f"Влажность: {hum:.2f} %") |
||||
if batt_percent is not None: |
||||
values.append(f"Батарея: {batt_percent} %") |
||||
if volt is not None: |
||||
values.append(f"Напряжение: {volt:.3f} В") |
||||
|
||||
if values: |
||||
print(f"{device.name} ({device.address})") |
||||
print(", ".join(values)) |
||||
print("-" * 50) |
||||
|
||||
print("Сканирование BLE...") |
||||
scanner = BleakScanner(callback) |
||||
await scanner.start() |
||||
|
||||
# Создаем событие для остановки |
||||
stop_event = asyncio.Event() |
||||
|
||||
def signal_handler(): |
||||
"""Обработчик сигналов для корректной остановки""" |
||||
stop_event.set() |
||||
|
||||
# Регистрируем обработчики сигналов через asyncio |
||||
loop = asyncio.get_event_loop() |
||||
if hasattr(loop, 'add_signal_handler'): |
||||
# Linux/Unix |
||||
try: |
||||
loop.add_signal_handler(signal.SIGINT, signal_handler) |
||||
loop.add_signal_handler(signal.SIGTERM, signal_handler) |
||||
except NotImplementedError: |
||||
# Если add_signal_handler не поддерживается, используем обычный способ |
||||
signal.signal(signal.SIGINT, lambda s, f: signal_handler()) |
||||
signal.signal(signal.SIGTERM, lambda s, f: signal_handler()) |
||||
else: |
||||
# Windows |
||||
signal.signal(signal.SIGINT, lambda s, f: signal_handler()) |
||||
signal.signal(signal.SIGTERM, lambda s, f: signal_handler()) |
||||
|
||||
# Ждем сигнала завершения |
||||
await stop_event.wait() |
||||
|
||||
await scanner.stop() |
||||
print("Сканирование остановлено.") |
||||
|
||||
def main(): |
||||
"""Главная функция с парсингом аргументов командной строки""" |
||||
global DEBUG_MODE |
||||
|
||||
parser = argparse.ArgumentParser(description="Сканер BLE устройств BTHome") |
||||
parser.add_argument("--debug", action="store_true", help="Включить отладочный вывод") |
||||
args = parser.parse_args() |
||||
|
||||
DEBUG_MODE = args.debug |
||||
|
||||
asyncio.run(scan_bthome()) |
||||
|
||||
if __name__ == "__main__": |
||||
main() |
||||
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
# ATC Logger |
||||
|
||||
Python скрипт для сканирования и логгирования данных с BLE датчиков температуры и влажности LYWSD03MMC с прошивкой ATC. Внутри LYWSD03MMC используется SoC TLSR825x, чип для Bluetooth Low Energy с потреблением порядка 0.5-1 мкА в глубоком сне и 5 мА в момент передачи данных. |
||||
|
||||
## Требования |
||||
|
||||
- Python 3.7+ |
||||
- BLE адаптер |
||||
|
||||
## Установка |
||||
|
||||
```bash |
||||
pip install -r requirements.txt |
||||
``` |
||||
|
||||
## Запуск |
||||
|
||||
```bash |
||||
python ATC_logger.py [--debug] |
||||
``` |
||||
|
||||
## Прошивка датчиков LYWSD03MMC |
||||
|
||||
### Требования к прошивке |
||||
|
||||
Для корректной работы требуется прошивка **ATC_v5.5 или новее**. Рекомендуется использовать последнюю версию (https://github.com/pvvx/ATC_MiThermometer). |
||||
|
||||
### Прошивка по UART в Chromium |
||||
|
||||
- Нужен Chromium или любой Chromium-based браузер. Firefox не поддерживает WebSerial, увы. Сhromium должен быть установлен не из snap/flatpak, а из репозитория - иначе AppArmor не даст работать WebSerial. |
||||
- Прошиватор: https://pvvx.github.io/ATC_MiThermometer/USBCOMFlashTx.html |
||||
- Выбрать COM порт с подключенным датчиком. Пины подключения погуглить, меняются в зависимости от версии платы. Связь обычно односторонняя, только RX, протокол не настоящий UART, но поверх UART эмулируется. |
||||
- Выберите файл прошивки (.bin) |
||||
- Нажать "Erase all flash" |
||||
- Нажать "Start flashing" |
||||
|
||||
|
||||
### Настройка прошивки с сохранением настроек в Flash |
||||
- Убедиться что в настройках прошивки установлены максимальные интервалы измерения и передачи . Для этого использовать веб-интерфейс https://pvvx.github.io/ATC_MiThermometer/TelinkMiFlasher.html |
||||
|
||||
## Формат логов |
||||
|
||||
Логи сохраняются в структуру папок: `DeviceID/Type/YYYY_MM/DD/DeviceID_Type_YYYY_MM_DD.log` |
||||
|
||||
Пример: |
||||
``` |
||||
ATC_233FCD/temperature/2025_11/08/ATC_233FCD_temperature_2025_11_08.log |
||||
``` |
||||
|
||||
Каждая строка содержит timestamp и значение: |
||||
``` |
||||
2025-11-08 12:30:15 23.45 |
||||
``` |
||||
Loading…
Reference in new issue