sensorylab-smeller 0.1.83
Python library for Neuroair device control
License: MIT
Requires Python: >=3.10

# Smeller: Python Library for Neuroair Device Control

[![PyPI version](https://badge.fury.io/py/smeller.svg)](https://badge.fury.io/py/smeller)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Documentation Status](https://readthedocs.org/projects/smeller-python-lib/badge/?version=latest)](https://smeller-python-lib.readthedocs.io/en/latest/?badge=latest)
[![Tests](https://github.com/yourusername/smeller-python-lib/actions/workflows/test.yml/badge.svg)](https://github.com/yourusername/smeller-python-lib/actions/workflows/test.yml)

## Описание

`Smeller` – это Python библиотека, разработанная для **управления устройствами Neuroair**, предлагая как **API для программного управления**, так и **графический интерфейс пользователя (GUI) для интерактивной работы с устройством**.  Она предоставляет полный набор инструментов для **взаимодействия с устройством через последовательный порт (Serial) или Bluetooth, позволяя разработчикам интегрировать управление Neuroair в свои приложения или использовать готовое GUI приложение.**


**Основные возможности:**

* **Комплексное управление устройством:**  Предлагает как программное управление через API, так и интуитивно понятный GUI.
* **Управление каналами (API и GUI):**  Настройка параметров каналов, включение/выключение, активация/деактивация. В GUI предусмотрена визуализация и удобное управление параметрами.
* **Арома контроль (API и GUI):** Управление ароматическими каналами, настройка параметров, включение/выключение, активация/деактивация. GUI обеспечивает визуальное представление настроек и состояний.
* **Управление генератором (API и GUI):** Контроль мощности и логики работы генератора. GUI предоставляет элементы управления для быстрого доступа к настройкам генератора.
* **Динамическое управление ароматами (GUI):** Пользователь может создавать сложные ароматические сценарии, используя вейпоинты и различные типы интерполяции (линейная, экспоненциальная, синусоидальная, ступенчатая) для плавного изменения интенсивности ароматов со временем.
* **Визуализация и редактирование (GUI):** Графическое отображение интенсивности каналов в реальном времени, возможность интерактивного редактирования вейпоинтов непосредственно на графике.
* **Сохранение и загрузка аромаблоков (GUI):** Пользователи могут сохранять созданные ароматические конфигурации в виде "аромаблоков" и загружать их для повторного использования или редактирования.
* **Интеграция с медиа (GUI):** Возможность синхронизации ароматических сценариев с медиа-контентом, таким как видео или аудио, для создания мультисенсорных впечатлений.
* **Разнообразные команды (API):** Поддержка широкого спектра команд для настройки и управления различными функциями устройства, включая watchdog, Bluetooth имя, режимы работы (MOD), вентилятор, и многое другое.
* **Гибкость подключения (API):** Поддержка подключения через Serial порт и Bluetooth для **управления устройством**.
* **Асинхронная архитектура (API и GUI):**  Использование асинхронности для обеспечения неблокирующей работы и отзывчивости вашего приложения, особенно при работе с GUI.
* **Событийно-ориентированная модель (API):**  Основана на событиях для удобного взаимодействия между компонентами вашего приложения и библиотекой `smeller`.
* **Надежная обработка ошибок (API и GUI):**  Комплексная система обработки ошибок и исключений для стабильной работы и информативных сообщений об ошибках как в API, так и в GUI.
* **Чистый и тестируемый код (API и GUI):**  Разработана в соответствии с лучшими практиками Python, с акцентом на читаемость, модульность и тестируемость.
* **Логирование (API и GUI):** Встроенная система логирования для отслеживания работы библиотеки и облегчения отладки.
* **Тёмная и светлая темы оформления (GUI):** GUI поддерживает смену тем оформления для комфортной работы в различных условиях освещения.

**Технологический стек:**

* **Python 3.10+:**
* **PyQt6 (GUI):** Современный и мощный фреймворк для создания кросс-платформенных GUI приложений.
* **PyQtGraph (GUI):**  Библиотека для визуализации данных и графиков в реальном времени, используется для отображения и редактирования вейпоинтов.
* **PySerial (API):** Обеспечивает надежное и эффективное взаимодействие с устройством через последовательный порт.
* **Bluetooth (bluetooth/pybluez) (API):**  (Опционально) Для беспроводного подключения (если поддерживается устройством).
* **threading и asyncio (API и GUI):** Используются для многопоточности и асинхронных операций, обеспечивая неблокирующий интерфейс.
* **logging (API и GUI):** Встроенная система логирования для отслеживания работы библиотеки.

## Структура проекта
```

📦 smeller
└── 📂 smeller  # Основной код приложения 
    ├── 📄 __init__.py # Инициализация основного пакета smeller. Определяет пакет.
    ├── 📂 commands # Реализации команд управления устройством
    │   ├── 📄 __init__.py # Инициализация пакета commands. Определяет пакет и задает пространство имен для команд управления устройством.
    │   ├── 📄 base.py # Базовые классы для команд управления устройством, такие как Command и CommunicationInterface. Определяет абстрактные интерфейсы для команд и коммуникации.
    │   ├── 📄 channel_control.py # Команды управления отдельными каналами устройства: включение, выключение, установка параметров. Реализует классы команд для управления каналами.
    │   ├── 📄 command_factory.py # Фабрика создания объектов команд на основе строковых идентификаторов команд. Отвечает за создание экземпляров команд по запросу.
    │   ├── 📄 get_help.py # Команда запроса справки или списка доступных команд от устройства. Реализует команду получения справки от устройства.
    │   ├── 📄 misc_commands.py # Различные команды общего назначения, не попадающие в другие категории: перезагрузка логов, watchdog, bluetooth имя и т.д. Содержит набор разнообразных управляющих команд.
    │   ├── 📄 restart_device.py # Команда для перезапуска устройства. Реализует команду перезагрузки устройства.
    │   ├── 📄 set_aroma_params.py # Команды для установки параметров, связанных с ароматическими картриджами или арома-генератором. Реализует команды для настройки ароматизации.
    │   ├── 📄 set_channel_params.py # Команды для установки параметров отдельных каналов, таких как tick_on, tick_off. Реализует команды для детальной настройки каналов.
    │   └── 📄 set_generator.py # Команды для управления общими параметрами генератора, например, мощность или логика работы. Реализует команды управления генератором воздушного потока/распыления.
    ├── 📂 communication # Модули для обмена данными с устройством
    │   ├── 📄 __init__.py # Инициализация пакета communication. Определяет пакет и задает пространство имен для модулей связи.
    │   ├── 📄 base.py # Базовые абстрактные классы и интерфейсы для различных протоколов коммуникации. Содержит базовые классы для коммуникационных интерфейсов.
    │   ├── 📄 bluetooth_com.py # Реализация коммуникационного интерфейса для Bluetooth соединения. Обеспечивает связь с устройством через Bluetooth.
    │   ├── 📄 factory.py # Фабрика для создания объектов коммуникационных интерфейсов (SerialCommunication, BluetoothCommunication). Отвечает за создание коммуникационных объектов.
    │   ├── 📄 mqtt_com.py # Реализация коммуникационного интерфейса для Bluetooth соединения. Обеспечивает связь с устройством через MQTT.
    │   └── 📄 serial_com.py # Реализация коммуникационного интерфейса для последовательного порта (Serial/COM). Обеспечивает связь с устройством через последовательный порт.
    ├── 📂 config # Конфигурация приложения
    │   ├── 📄 __init__.py # Инициализация пакета config. Определяет пакет и задает пространство имен для конфигурации приложения.
    │   ├── 📄 config.py # Data classes для хранения конфигурации приложения, включая DeviceConfig, SerialConfig, AppConfig и др. Определяет структуру конфигурационных данных приложения.
    │   ├── 📄 config_manager.py # Менеджер для загрузки, сохранения и применения конфигурации приложения из файла (JSON). Отвечает за управление конфигурацией приложения.
    │   └── 📄 constants.py # Объявление глобальных констант, используемых в приложении, таких как размеры графиков, лимиты каналов и типы интерполяции. Содержит статические константы приложения.
    ├── 📂 controllers # Контроллеры бизнес-логики, связующее звено между GUI/API и моделями/коммуникацией
    │   ├── 📄 __init__.py # Инициализация пакета controllers. Определяет пакет и задает пространство имен для контроллеров приложения.
    │   ├── 📄 aromablock_model_controller.py # Контроллер, управляющий логикой AromaBlock (загрузка, сохранение, удаление, применение конфигураций). Управляет моделями аромаблоков и взаимодействием с БД.
    │   ├── 📄 cartridge_manager.py # Контроллер для управления информацией о картриджах, их именами и ID, получаемыми с устройства и из базы данных. Управляет информацией о картриджах и их каталогом.
    │   └── 📄 device_controller.py # Основной контроллер для управления устройством, отправки команд и обработки ответов через communication package.  Ядро управления устройством и обработки команд.
    ├── 📂 database # Модули для работы с базой данных (если используется)
    │   ├── 📄 __init__.py # Инициализация пакета database. Определяет пакет и задает пространство имен для работы с базой данных.
    │   └── 📄 db_manager.py # Менеджер для взаимодействия с базой данных, выполняющий операции CRUD для моделей данных. Отвечает за операции с базой данных и ORM.
    ├── 📂 dynamic_control # Логика динамического управления ароматами (например, по временной шкале)
    │   ├── 📄 __init__.py # Инициализация пакета dynamic_control. Определяет пакет и задает пространство имен для динамического управления.
    │   ├── 📄 aroma_table_model.py # Модель таблицы PyQt для отображения списка AromaBlock в графическом интерфейсе. Обеспечивает представление данных AromaBlock в табличном виде.
    │   ├── 📄 dynamic_block_controller.py # Контроллер, реализующий логику динамического управления интенсивностью каналов по заданным временным точкам (вейпоинтам). Управляет динамическим изменением параметров каналов по времени.
    │   └── 📄 view_model.py # ViewModel для динамического управления, связывающий данные и логику динамического контроля с GUI. Обеспечивает связь между GUI и логикой динамического управления.
    ├── 📂 gui # Графический пользовательский интерфейс (PyQt6)
    │   ├── 📄 __init__.py # Инициализация основного пакета GUI. Определяет пакет и задает пространство имен для графического интерфейса.
    │   ├── 📂 aromablocks # Компоненты GUI, связанные с аромаблоками
    │   │   ├── 📄 __init__.py # Инициализация пакета gui.aromablocks. Определяет пакет и задает пространство имен для GUI компонентов аромаблоков.
    │   │   ├── 📄 aroma_block_delegate.py # Делегат для отображения элементов AromaBlock в списках или таблицах, настраивающий внешний вид элементов. Кастомизирует отображение элементов AromaBlock в GUI.
    │   │   ├── 📄 aroma_block_dialog.py # Диалоговое окно для создания и редактирования AromaBlock, включающее поля ввода имени, описания и параметров. Предоставляет интерфейс для создания/редактирования AromaBlock.
    │   │   └── 📄 aroma_block_list.py # Виджет для отображения списка AromaBlock, позволяющий пользователю выбирать, загружать и управлять аромаблоками. Отображает список AromaBlock в GUI.
    │   ├── 📂 cartridges # Компоненты GUI, связанные с картриджами
    │   │   ├── 📄 __init__.py # Инициализация пакета gui.cartridges. Определяет пакет и задает пространство имен для GUI компонентов картриджей.
    │   │   └── 📄 cartridge_info_dialog.py # Диалоговое окно для отображения информации о картридже и, возможно, для его настройки. Предоставляет интерфейс для просмотра информации о картриджах.
    │   ├── 📂 channels # Компоненты GUI, связанные с каналами устройства
    │   │   ├── 📄 __init__.py # Инициализация пакета gui.channels. Определяет пакет и задает пространство имен для GUI компонентов каналов.
    │   │   ├── 📄 channel_button.py # Кастомная кнопка для управления каналом, отображающая его статус и позволяющая переключать канал.  Интерактивный элемент GUI для управления каналом.
    │   │   └── 📄 channel_manager.py # Виджет, управляющий отображением и взаимодействием с набором ChannelButton, организуя их компоновку. Управляет группой ChannelButton и их представлением в GUI.
    │   ├── 📂 control_panel # Компоненты панели управления воспроизведением
    │   │   ├── 📄 __init__.py # Инициализация пакета gui.control_panel. Определяет пакет и задает пространство имен для GUI компонентов панели управления.
    │   │   ├── 📄 control_panel.py # Основная панель управления с кнопками плей/пауза/стоп и таймлайном для управления воспроизведением ароматов.  Содержит виджеты управления воспроизведением.
    │   │   ├── 📄 seconds_time_edit.py # Виджет для ввода и отображения времени в секундах, специализированный для установки общей длительности воспроизведения.  Виджет ввода времени в секундах.
    │   │   └── 📄 timeline_player.py # Виджет таймлайна (слайдер) для визуализации прогресса воспроизведения и управления текущим временем. Виджет для управления и отображения прогресса воспроизведения по времени.
    │   ├── 📂 mediacenter # Компоненты для интеграции с медиа (видео/аудио)
    │   │   ├── 📄 __init__.py # Инициализация пакета gui.mediacenter. Определяет пакет и задает пространство имен для GUI компонентов медиацентра.
    │   │   ├── 📄 aromablock_timeline_widget.py # Виджет временной шкалы для отображения и управления аромаблоками в контексте медиа-воспроизведения.  Виджет таймлайна для интеграции с медиа и аромаблоками.
    │   │   ├── 📄 integrated_player.py # Планировался, но отсутствует; возможно, интеграция медиаплеера и управления ароматами. (Файл отсутствует, описание placeholder).
    │   │   └── 📄 media_view.py # Виджет для отображения медиа-контента, например, видео, синхронизированного с воспроизведением ароматов. Отображает медиа контент в GUI.
    │   ├── 📂 menu_bar # Компоненты для строки меню
    │   │   ├── 📄 __init__.py # Инициализация пакета gui.menu_bar. Определяет пакет и задает пространство имен для GUI компонентов меню.
    │   │   └── 📄 menu_bar_manager.py # Менеджер для создания и управления меню приложения (Файл, Вид, Настройки и т.д.). Управляет строкой меню приложения.
    │   ├── 📂 waypoints # Компоненты для работы с точками на временной шкале/графике
    │   │   ├── 📄 __init__.py # Инициализация пакета gui.waypoints. Определяет пакет и задает пространство имен для GUI компонентов вейпоинтов.
    │   │   ├── 📄 waypoint.py # Класс или структура данных для представления вейпоинта на графике, содержащая время и интенсивность. Определяет структуру данных вейпоинта.
    │   │   └── 📄 waypoint_plot_widget.py # Виджет для отображения графика интенсивности и вейпоинтов, позволяющий пользователю редактировать вейпоинты. Визуализирует график интенсивности и обеспечивает интерактивное управление вейпоинтами.
    │   ├── 📄 connectors.py # Модуль, устанавливающий связи (сигналы и слоты) между различными компонентами GUI и ViewModel. Отвечает за установку соединений между компонентами GUI.
    │   ├── 📄 main_window.py # Главное окно приложения, объединяющее все компоненты GUI и ViewModel. Основное окно приложения, контейнер для всех GUI компонентов.
    │   ├── 📄 theme_manager.py # Менеджер для применения тем оформления к GUI приложения (светлая, темная и т.д.). Управляет темами оформления GUI.
    │   └── 📄 ui_manager.py # Главный менеджер UI, отвечающий за создание, компоновку и управление всеми виджетами GUI.  Координирует создание и управление всеми элементами GUI.
    ├── 📂 models # Модели данных приложения
    │   ├── 📄 __init__.py # Инициализация пакета models. Определяет пакет и задает пространство имен для моделей данных.
    │   ├── 📄 aroma_block.py # Модель данных для AromaBlock, представляющая собой контейнер конфигураций каналов для определенного арома-сценария. Определяет структуру данных AromaBlock.
    │   ├── 📄 aroma_event.py # Модель данных для единичного ароматического события, содержащая параметры команды и время срабатывания. Определяет структуру данных AromaEvent.
    │   ├── 📄 aroma_track.py # Модель данных для AromaTrack, представляющая собой последовательность AromaBlock, формирующую полный ароматический трек.  Определяет структуру данных AromaTrack.
    │   ├── 📄 base.py # Базовый класс для моделей данных, предоставляющий общую функциональность или поля. Содержит базовые классы для моделей данных.
    │   ├── 📄 cartridge.py # Модель данных для картриджа, содержащая информацию о типе аромата, ресурсе и идентификаторе. Определяет структуру данных Cartridge.
    │   ├── 📄 channel_control_config.py # Модель данных для конфигурации управления каналом, включая вейпоинты, тип интерполяции и другие параметры. Определяет структуру данных ChannelControlConfig.
    │   └── 📄 interpolation.py # Enum, определяющий типы интерполяции для динамического управления интенсивностью ароматов. Определяет перечисление InterpolationType.
    ├── 📂 services 
    │   └── 📄 mqtt_client.py.py # MQTT клиент
    ├── 📂 tests # Модульные и интеграционные тесты
    │   ├── 📄 __init__.py # Инициализация пакета tests. Определяет пакет и задает пространство имен для тестов.
    │   ├── 📄 test.py # Общий файл для запуска тестов или определения базовых тестовых классов.  Содержит общие тестовые функции или настройки.
    │   ├── 📄 test_db_aromablock.py # Тесты для проверки корректности работы с AromaBlock в базе данных (CRUD операции). Содержит тесты для модели AromaBlock и базы данных.
    │   ├── 📄 test_db_connection.py # Тесты для проверки соединения с базой данных и основных функций db_manager. Содержит тесты для проверки соединения с БД.
    │   ├── 📄 test_device_functionality.py # Интеграционные тесты для проверки функциональности устройства через DeviceController. Содержит интеграционные тесты для DeviceController и устройства.
    │   └── 📄 test_dynamic.py # Тесты для проверки логики динамического управления ароматами и DynamicBlockController. Содержит тесты для динамического управления.
    └── 📂 utils # Вспомогательные утилиты и функции
        ├── 📄 __init__.py # Инициализация пакета utils. Определяет пакет и задает пространство имен для утилитных функций.
        ├── 📄 comport_manager.py # Утилита для управления COM-портами, их обнаружения и кэширования списка портов.  Содержит утилиты для работы с COM-портами.
        ├── 📄 events.py # Реализация системы событий для асинхронного уведомления компонентов приложения о различных событиях. Реализует систему обработки событий в приложении.
        └── 📄 exceptions.py # Определение кастомных исключений, используемых в приложении для обработки ошибок и исключительных ситуаций. Определяет иерархию исключений приложения.

```

## Установка API

Для установки библиотеки `smeller` используйте `pip`:

```bash
pip install sensorylab-smeller
```

## Установка API и GUI

```bash
pip install sensorylab-smeller[gui]
```

**Предварительные требования:**

* **Python 3.10 или выше:** Убедитесь, что у вас установлена подходящая версия Python.
* **PySerial:** Устанавливается автоматически как зависимость при любой установке.
* ***PyQt6 и PyQtGraph (опционально, для GUI):*** Устанавливаются автоматически при установке с опцией `[gui]`.

## Интеграция в ваше приложение

### 1. Импорт необходимых модулей

В вашем Python скрипте импортируйте классы из библиотеки `smeller`:

```bash
from smeller.config import DeviceConfig
from smeller.controllers.device_controller import DeviceController
from smeller.communication.factory import create_communication
from smeller.utils.events import EventHandler
import asyncio
import logging
```
### 2. Настройка логирования (рекомендуется)

Настройте логирование для отслеживания работы библиотеки и отладки:
```bash
logging.basicConfig(level=logging.DEBUG) # Или logging.INFO для менее подробного вывода
```
### 3. Инициализация компонентов

Создайте экземпляры `DeviceConfig`, `EventHandler`, и `DeviceController`:
```bash
config = DeviceConfig() # Можно настроить параметры в DeviceConfig, если необходимо
event_handler = EventHandler()
communication = create_communication(config, 'serial') # Или 'bluetooth' для Bluetooth подключения
controller = DeviceController(communication, config, event_handler)
```


### 4. Подключение к устройству


### Перечисление доступных COM-port устройств
Вы можете использовать `COMPortManager` из библиотеки `smeller` для получения списка доступных Serial портов.

```bash
from smeller.utils.manager import COMPortManager

def list_serial_ports():
    port_manager = COMPortManager()
    available_ports = port_manager.get_com_ports()
    if available_ports:
        print("Доступные Serial порты:")
        for port_info in available_ports:
            print(f"  - Порт: {port_info.device}, Описание: {port_info.description}, HWID: {port_info.hwid}")
    else:
        print("Serial порты не найдены.")

# ... в вашем asyncio main() или в отдельной функции ...
list_serial_ports()
```

**Bluetooth подключение (если поддерживается устройством):**

Для Bluetooth подключения библиотека `smeller` предоставляет возможность поиска и перечисления доступных устройств Neuroair. Вы можете использовать `BluetoothDeviceController` для обнаружения устройств и получения информации о них.

Пример кода для поиска и списка Bluetooth устройств:

```bash
import asyncio
import logging
from smeller.communication.bluetooth_com import BluetoothDeviceController # Импорт контроллера Bluetooth

logging.basicConfig(level=logging.DEBUG)

async def list_bluetooth_devices():
    bt_controller = BluetoothDeviceController()
    print("Начинаем поиск Bluetooth устройств...")
    devices = await bt_controller.discover_devices()
    if devices:
        print("Найденные Bluetooth устройства:")
        for device_info in devices:
            print(f"  - Имя: {device_info.name}, MAC: {device_info.mac}, COM-порт: {device_info.com_port}, Сопряжено: {device_info.is_paired}")
        return devices
    else:
        print("Bluetooth устройства не найдены.")
        return []

async def main():
    available_devices = await list_bluetooth_devices() # Получаем список устройств

    if not available_devices:
        print("Невозможно подключиться: Bluetooth устройства не найдены.")
        return

    # Даем пользователю выбрать устройство (пример - можно сделать GUI выбор)
    selected_device = available_devices[0] # По умолчанию - первое устройство
    print(f"Попытка подключения к устройству: {selected_device.name} ({selected_device.mac}), COM-порт: {selected_device.com_port}")

    # ... дальнейший код подключения с использованием controller.connect(connection_type='bluetooth', device_info=selected_device) ...

if __name__ == "__main__":
    asyncio.run(main())
```
> для Bluetooth discovery требуется, чтобы Bluetooth на компьютере был включен и устройство Neuroair было в режиме обнаружения (если это необходимо для вашей модели устройства).
>Используйте асинхронный метод `controller.connect()` для установки соединения. Укажите COM-порт для Serial подключения или MAC-адрес для Bluetooth (если необходимо).

**Serial подключение:**
```bash
async def main():
    # ... инициализация компонентов как в шаге 3 ...

    com_port = "COM3" # Замените на ваш COM-порт
    if await controller.connect(com_port=com_port):
        print(f"Успешно подключено к {com_port}")
        # Дальнейшая работа с устройством
    else:
        print("Не удалось подключиться к устройству.")

if __name__ == "__main__":
    asyncio.run(main())
```
### 5. Отправка команд и обработка ответов

Используйте методы `DeviceController` для отправки команд.  Большинство методов соответствуют командам устройства и возвращают разобранный ответ в виде списка строк.

**Пример: Получение справки (команда 'h')**
```bash
async def main():
    # ... инициализация и подключение ...

    if controller.is_connected():
        help_response = await controller.get_help()
        if help_response:
            print("Ответ на команду 'help':")
            for line in help_response:
                print(line)
        else:
            print("Ошибка при выполнении команды 'help'.")

    await controller.disconnect() # Важно не забывать отключаться

if __name__ == "__main__":
    asyncio.run(main())
```
**Пример: Установка параметров канала 1 (команда 'p')**
```bash
async def main():
    # ... инициализация и подключение ...

    if controller.is_connected():
        channel_params_response = await controller.set_channel_parameters(channel=0, on_tick=100, off_tick=50)
        if channel_params_response:
            print("Ответ на команду 'set_channel_parameters':", channel_params_response)
        else:
            print("Ошибка при выполнении команды 'set_channel_parameters'.")

    await controller.disconnect()

if __name__ == "__main__":
    asyncio.run(main())
```
### 6. Подписка на события (опционально, но рекомендуется)

Библиот