sdg-dev 2.7
master and slave over sdg_io
Requires Python: >=3.6


Cодержит классы **SdgDevMaster** и **SdgDevAsync** для обмена командами, через
СКБ-шный интерфейс ввода/вывода. https://pypi.org/project/sdg-io/

`SdgDevMaster` умеет только передавать команды, т.е. выполняет работу `Главного устройства`.

`SdgDevAsync` предназначен как для передачи так и для приема команд, т.е позволяет
организовать работу как `Главного` так и `Подчиненного устройства`.
Для работы SdgDevAsync создается поток, в котором выполняется переодическое
чтение данных из интерфейса ввода/вывода и их обработка на предмет получения команд.
Входящие команды должны быть заранее добавлены методом `add_incmd()` с указанием кода команды,
форматом ожидаемых данных и callback функции, которой будет передано управление при
поступлении команды с нужным кодом и форматом данных.
`Внимание` callback функция вызывается из внутреннего потока SdgDevAsync и не должна его
блокировать! Для безопасного обмена данными должны быть приняты меры!

`SdgDevMaster` и `SdgDevAsync` могут автоматически определять порт к которому подключено
подчиненное устройство. Для этого нужно:
1. Переопределить функцию `check_id()`, которая будет запрашивать `уникальный идентификатор`
у `подчиненного` и при получении адекватного ответа, возвращать True.
2. Использовать ф-ю `search_port()` для поиска `подчиненного` по всем доступным портам системы,
или search_port(portlist) для поиска по заранее подготовленному списоку портов.

При необходимости подключить какой-то другой интерфейс ввода/вывода нужно
использовать классы **DevMaster** и **DevAsync**.
Интерфейс ввода/вывода должен иметь методы `read/write` и вызывать
исключение `IOError` при ошибках чтения/записи.

Формат обмена:
-------------
`АА СС D0 D1 .. DX` - команда
`AA CA D0 D1 .. DX` - ответ
* `АА`: адрес устройства. Возможна работа без адреса (точка -точка).
* `СС`: код команды, любой в диапазоне 0x00 - 0x7F, кроме 0x70
* `CA`: код ответа = код команды с установленным старшим битом 0x80 - 0xFF, кроме 0xF0
* `D0-DX`: данные команды/ответа опционально.
На широковещательные команды с адресом `АА=0х00` ответ подчиненным(и) не выдается.
Возможен ответ подчиненного с кодом `0xF0` `Недопустимая команда` это означает,
что он не смог выполнить данныую команду. Формат такого ответа:
`AA 0xF0 CC EE`, где `EE` - код ошибки выполнения. (см. class DevBadCode)

Пример использования (example.py):
-------------
```python
from struct import pack
from sdg_utils import log_open
from sdg_dev import SdgDevMaster, DevException, SdgDevAsync

log = log_open()


class MyMaster(SdgDevMaster):
    def __init__(self):
        super().__init__(port='', log=log.getChild('mastr'))

    def check_id(self):
        return self.send(b'\x00', ackfrmt='H') == 0xAAAA

    def send_val(self, val):
        return self.send(b'\x31' + pack('I', val), ackfrmt='')

    def request_val(self):
        return self.send(b'\x32', ackfrmt='I', timeout=.1, remix=0)


class MySlave(SdgDevAsync):
    def __init__(self, port):
        super().__init__(port, log=log.getChild('slave'))
        self.val = 0x04030201
        self.ID = 0xAAAA
        self.add_incmd(b'\x00', '', self._getid)
        self.add_incmd(b'\x31', 'I', self._setval)
        self.add_incmd(b'\x32', '', self._getval)
        self.add_incmd(b'\x33', '', lambda: pack('BBBB', 5, 6, 7, 8))

    def _setval(self, val):
        self.val = val
        return b''

    def _getval(self):
        return pack('I', self.val)

    def _getid(self):
        return pack('H', self.ID)


if __name__ == "__main__":
    print('Порт для master-a будет определен автопоиском по всем доступным портам системы.')
    PORT = input("Ведите название порта для slave-a или нажмите Enter(по умочанию 'COM26'):")
    if not PORT:
        PORT = 'COM26'

    slave = MySlave(PORT)
    master = MyMaster()
    master.search_port()

    try:
        val = master.request_val()
        log.info(f"request_val 0x{val:08x}")

        log.info(f"send new val 0x01020304")
        master.send_val(0x01020304)

        val = master.request_val()
        log.info(f"request_val 0x{val:08x}")

        a, b, c, d = master.send(b'\x33', ackfrmt='BBBB')
        log.info(f"{a}, {b}, {c}, {d}")

    except DevException as e:
        log.error(e)

    master.close()
    slave.close()
```