Терминал сбора данных на ардуино
- Войдите на сайт для отправки комментариев
Пнд, 10/11/2014 - 14:06
В каждой торговой организации регулярно случается процедура инвентаризаций товара. Современное торгово-розничное предприятие содержит в базе до 15000 наименований номенклатуры и у каждого наименования может быть по несколько штриховых кодов на упаковке если такой товар производят на разных заводах. А бывает что на упаковке штрихкод отсутствует или не читается. Да и количество товара часто не единичное, но очень важно абсолютно точно счесть какое. Более того современная торговля тяготеет к круглосуточному режиму работы даже в праздничные дни. По-этому закрытие на переучёт это букет головной боли не только для предприятия, но и для покупателей.
Обычно для подсчёта остатков используются сканеры штрихкода в связке с ноутбуком где открыта учётная программа, или терминалы сбора данных которые накапливают в себе таблицу записей и при подключении к учётной программе формируют документ инвентаризации. Оба имеют недостатки: в первом мобильность весьма условна, во втором нет онлайн соединения при вводе. Моя идея в устройстве которое может работать по такому алгоритму:
0. при включении устанавливаем беспроводное соединение с сервером (индикатор сеть и заряд акб)
1. вводим код товара с клавиатуры или сканером при нажатии клавиши СканШК получаем штрихкод
2. если код начинаестся с 29... - этикетка стеллажа - сохраняем в переменную номер витрины и переходим к пп1
3. выводим на дисплее код стеллажа (без кода стеллажа переходим к пп1)
4. выводим на дисплее код товара
5. отправляем запрос наименования по коду товара на сервер
6. получение обратно наименования на терминал
7. выводим на дисплее наименование
8. вводим с клавиатуры количество товара
9. отправляем количество на сервер
10. принимаем и выводим подтверждение операции
Сетевые параметры ТСД получает по DHCP. По скольку терминал будет беспроводным в качестве канала передачи данных хорошо подойдёт WiFi. С одной стороны роутер решающий задачи маршрутизации и аренды адресов, с другой модули esp8266 для тсд.
Основой торговой системы в моём случае выбрана открытая СУБД Firebird. В других случаях может использоваться любая ODBC-совместимая система. При включении терминал читает параметры из таблицы config по id в БД учёта.
Ну и немного кода для общего представления системы.
Пока без алгоритма и с примитивными функциями, но что бы начать:
/* Будем принимать по com-порту строку, выводим ее на экран и передавать ее на web-сервер Оборудование: - дисплей 1602, потенциометр для подсветки дисплея - Ethernet W5100 Shield Network Expansion Board w/ Micro SD Card Slot - web server Подключение дисплея 1602: Контакт дисплея Контакт Arduino Значение 1 GND Vss 2 5V Vdd 3 GND Vo (контрастность) 4 digital 9 RS 5 digital 8 R/W 6 digital 7 ENABLE 11 digital 6 DB4 12 digital 5 DB5 13 digital 3 DB6 14 digital 2 DB7 15 5V LED+ 16 GND LED- Подключение W5100: Обмен данными между Ардуиной и Шилдом происходит по SPI. Низким уровнем на digital выводе выбирает ведомое устройство на шине SPI: выбор SD - digital 4 выбор ethernet - digital 10 * SD card attached to SPI bus as follows: ** MOSI - pin 11 ** MISO - pin 12 ** CLK - pin 13 ** CS - pin 4 */ // подключаем библиотеки #include <LiquidCrystal.h> // для работы с LCD #include <SPI.h> // для работы с SPI // #include <SD.h> // для работы с SD #include <Ethernet.h> // для работы с Ethernet // инициализируем LCD, указывая контакты данных LiquidCrystal lcd(9, 8, 7, 6, 5, 3, 2); // задаем переменные boolean receiveFlag = false; // флаг что было что-то принято через com-порт String myString; // строка int Count = 1; // счетчик int selectEthernet = 10; // выбор ведомого на шилде - Ethernet int selectSd = 4; // выбор ведомого на шилде - SD byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; // mac адрес byte ip[] = { 192, 168, 1, 200}; // ip если не получим из DHCP byte gateway[] = { 192, 168, 1, 101}; // gateway byte subnet[] = { 255, 255, 255, 0}; // маска сети byte remote_ip[] = { 192, 168, 1, 100}; // адрес сервера char server[] = "vpn"; // name address server // включим Ethernet #define SWITCH_TO_W5100 digitalWrite(selectSd,HIGH); digitalWrite(selectEthernet,LOW); // включим SD #define SWITCH_TO_SD digitalWrite(selectEthernet,HIGH); digitalWrite(selectSd,LOW); // выключим и SD и Ethernet #define ALL_OFF digitalWrite(selectEthernet,HIGH); digitalWrite(selectSd,HIGH); // Initialize the Ethernet client library // with the IP address and port of the server // that you want to connect to (port 80 is default for HTTP) EthernetClient client; void setup() { // красивая "загрузка" программы при старте :) lcd.begin(16, 2); lcd.print("Loading"); delay(300); // ждем 0.5 секунды lcd.print("."); delay(300); // ждем 0.5 секунды lcd.print("."); delay(500); // ждем 1 секунду Serial.begin(9600); // инициализируем передачу данных по COM-порту // Задаем режим работы портов pinMode(selectEthernet, OUTPUT); pinMode(selectSd, OUTPUT); // высокий уровень на обоих - не выбран ведомый на SPI digitalWrite(selectEthernet, HIGH); digitalWrite(selectSd, HIGH); // старт Ethernet подключения if (Ethernet.begin(mac) == 0) { lcd.print("Failed DHCP"); // инициализируем без использования DHCP Ethernet.begin(mac, ip, gateway, subnet); // пауза 1 секунда - чтобы shield успел инициализироваться delay(1000); } // выведем локальный IP адрес lcd.clear(); //очистка экрана lcd.print("My IP address: "); lcd.setCursor(0, 1); // устанавливаем курсор в 0-ом столбце, 2 строке for (byte thisByte = 0; thisByte < 4; thisByte++) { // печатаем каждый байт IP адреса lcd.print(Ethernet.localIP()[thisByte], DEC); lcd.print("."); } delay(1000); lcd.clear(); //очистка экрана lcd.print("Please scan..."); } void loop() { // проверяем буфер com-порта - было ли что-то принято if (Serial.available() > 0) //если есть доступные данные считываем строку { while (Serial.available() > 0) { int inChar = Serial.read(); // считываем символ myString += (char)inChar; // формируем строку } // устанавливаем флаг что нужно обработать принятые данные receiveFlag = true; Serial.flush(); // очистим буфер } // если флаг receiveFlag установлен - значит что-то приняли. Обработаем if (receiveFlag == true) { // выводим принятую переменную на экран lcd.clear(); //очистка экрана lcd.setCursor(13, 0); // устанавливаем курсор в 12-ом столбце, 1 строке lcd.print(Count); // выводим кол-во итераций lcd.setCursor(0, 1); // устанавливаем курсор в 0-ом столбце, 2 строке lcd.print(myString); // выводим myString на экран // отправим на сервер remote_ip GET запрос // выберем ведомого SPI = включим ethernet SWITCH_TO_W5100; // если удалось подключиться if (client.connect(remote_ip, 80)) { String sid = "tsd1"; String vitrina = "v01"; String postsend = "POST /tsd/inv.php?s="+sid+"&v="+vitrina+"&q="+myString+" HTTP/1.1"; //Serial.println("connected. "+postsend); client.println(postsend); client.println("Host: vpn"); client.println("Connection: close"); client.println(); // рвем соединение client.stop(); } else { // почему то не удалось подключиться к серверу lcd.print("connection failed"); } // отключим шилд от шины SPI (отключим все) ALL_OFF; // подотрем за собой :) receiveFlag = false; // сбрасываем флаг myString = ""; // очистка строки Count++; // инкремент счетчика } delay(1000); // задержка перед повторением }Запрос наим.товара из учётной БД:
К слову сказать web-сервер поднимать в каждой торговой точке я не собирался. Использовать публичный - рискованно (во время инвентаризации пропадёт интернет и что тогда?). Логично написать некого демона. Попробовал processing - не нашёл механизма работы с БД firebird. Написал udp клиент-сервер на Delphi (используя Interbase компоненту), но пока есть некоторые сложности с двусторонней диалоговой связью. Вероятно нарисую обработчик вроде http-odbc или http-FB. Режим связи в моем случае сильно похож на http клиента:
<запрос-{приём-передача-[выполнение-возврат]-передача}-приём>. []-sql, {}-демон, <>-терминал
Источник вдохновения, используемый на данный момент:
Из беспроводных интерфесов ИК. Если бы можно было заменить на wifi и перешить ?)
Ну и что есть на текущий момент в железе:
Ethernet-shield собираюсь заменить на esp8266.
Однако он юзает rx/tx. Сканер ШК читает код по rx. Потребуется как то их переключать?
Читал про программное управление сом-портом, и про мегу-core. Что сможете посоветовать?
Тут можно собрать загрузчик с програмным UART, он (програмный UART) конечно сильно ограничен, но сканеру должно хватать.
Рабочая версия обработчика php-сервера:
<? //Глобальные переменные: $database = "web:C:/Database/Database.gdb"; $table = "INV"; $user = "SYSDBA"; $password = "masterkey"; $charset = "win1251"; //разбираем входные параметры $TSD = $_REQUEST['tsd']; $BARCODE = $_REQUEST['barcode']; $QTY = $_REQUEST['qty']; $PRODUCT = $_REQUEST['product']; $MX = $_REQUEST['mx']; // подключаемся к базе если это терминал if ($TSD > 0) $db = ibase_connect($database, $user, $password, $charset) or die; //спросим наименование если есть штрихкод товара if ($BARCODE !='') { if ($QTY =='') { $stn = "SELECT FIRST 1 PRODUCTS.PROD_NAME AS PRODUCT FROM BARCODES INNER JOIN PRODUCTS ON (BARCODES.BC_PRODUCT_ID = PRODUCTS.PROD_PRODUCT_ID) WHERE (BARCODES.BC_BARCODE like $barcode)"; $resultn = ibase_query($stn, $db) or die("Error_net_stn"); while ($rown = ibase_fetch_assoc($resultn)) echo $rown['PRODUCT']; } } //запишем количество if ($BARCODE !='') { if ($QTY !='') { $inst = "INSERT INTO $table (ID, TSD, DATETIME, BARCODE, PRODUCT, QTY, MX) VALUES (GEN_ID (GEN_INV_ID,1), '$TSD', 'NOW', '$BARCODE', '$PRODUCT', '$QTY', '$MX')"; $result = ibase_query($inst, $db) or die("Error_net_inst"); } } @ibase_close($db); ?>Формат БД Firebird:
/******************************************************************************/ /*** Generated by IBExpert 14.11.2014 13:10:50 ***/ /******************************************************************************/ SET SQL DIALECT 3; SET NAMES WIN1251; /******************************************************************************/ /*** Tables ***/ /******************************************************************************/ CREATE TABLE INV ( ID D_ID /* INTEGER NOT NULL; GEN_ID (GEN_INV_ID,1) */, TSD D_INT /* D_INT = INTEGER */, DATETIME D_DATETIME /* D_DATETIME = TIMESTAMP */, BARCODE D_BARCODE /* D_BARCODE = NUMERIC(14,0) */, PRODUCT D_STR_80 /* D_STR_80 = VARCHAR(80) */, QTY D_NUM_10_3 /* D_NUM_10_3 = NUMERIC(10,3) */, MX D_BARCODE /* D_BARCODE = NUMERIC(14,0) */, INVID D_INT /* D_INT = INTEGER */ ); /******************************************************************************/ /*** Primary Keys ***/ /******************************************************************************/ ALTER TABLE INV ADD CONSTRAINT PK_INV PRIMARY KEY (ID); /******************************************************************************/ /*** Fields descriptions ***/ /******************************************************************************/ DESCRIBE FIELD ID TABLE INV 'Порядковый номер записи'; DESCRIBE FIELD TSD TABLE INV 'Номер терминала зашитый при производстве'; DESCRIBE FIELD DATETIME TABLE INV 'Время операции ввода данных'; DESCRIBE FIELD BARCODE TABLE INV 'Код по которому ищем товар'; DESCRIBE FIELD PRODUCT TABLE INV 'Наименование товара'; DESCRIBE FIELD QTY TABLE INV 'Подсчитанное количество'; DESCRIBE FIELD MX TABLE INV 'Код места хранения товара'; DESCRIBE FIELD INVID TABLE INV 'Порядковый номер инвентаризации (заполняется при выгрузке в учётную программу, может быть использован для определения записей последней инвентаризации'; /******************************************************************************/ /*** Generators ***/ /******************************************************************************/ CREATE GENERATOR GEN_INV_ID; SET GENERATOR GEN_INV_ID TO 0;Обращение к серверу:
Заказал экран, несколько wifi модулей и mega-core-mini для проекта. Жду в декабре.
84*48 LCD Module White backlight adapter pcb for Nokia 5110
esp8266 esp-03 дистанционный последовательный порт WiFi трансивер беспроводной модуль ap+sta C
мини 2560 mega2560-core 3.3 V, 5V BUONO usb2serial USB UART кабель для Arduino
Ардуино код снова не даёт покоя.
Решил на терминале проверить штрихкод на принадлежность товару или услуге.
Из строки EAN-13 нужно прочитать первые два символа, и если они 29 то это этикетка шкафа, иначе товар.
//проверим штрихкод: 29..-витрина, остальные-товар char vala = BARCODE.charAt(0); //читаем 0 символ char valb = BARCODE.charAt(1); //читаем 1 символ int len = BARCODE.length(); //длина стоки if (len = 14) { if (vala='2') { if (valb='9') { MX = BARCODE; BARCODE =''; } } }Результат: empty character constant!
Решено: строка String "abc", первый символ строки Char 'a'. Дело в кавычках.
Полный код с алгоритмом работы, но пока ещё с delay() и прочими подпорками:
/* Будем принимать по com-порту строку, выводим ее на экран и передавать ее на web-сервер Оборудование: - дисплей 1602, потенциометр для подсветки дисплея - Ethernet W5100 Shield Network Expansion Board w/ Micro SD Card Slot - web server Подключение дисплея 1602: Контакт дисплея Контакт Arduino Значение 1 GND Vss 2 5V Vdd 3 GND Vo (контрастность) 4 digital 9 RS 5 digital 8 R/W 6 digital 7 ENABLE 11 digital 6 DB4 12 digital 5 DB5 13 digital 3 DB6 14 digital 2 DB7 15 5V LED+ 16 GND LED- Подключение W5100: Обмен данными между Ардуиной и Шилдом происходит по SPI. Низким уровнем на digital выводе выбирает ведомое устройство на шине SPI: выбор SD - digital 4 выбор ethernet - digital 10 * SD card attached to SPI bus as follows: ** MOSI - pin 11 ** MISO - pin 12 ** CLK - pin 13 ** CS - pin 4 */ // подключаем библиотеки #include <LiquidCrystal.h> // для работы с LCD #include <SPI.h> // для работы с SPI #include <Ethernet.h> // для работы с Ethernet // #include <SD.h> // для работы с SD // инициализируем LCD, указывая контакты данных LiquidCrystal lcd(9, 8, 7, 6, 5, 3, 2); // задаем переменные boolean receiveFlag = false; // флаг что было что-то принято через com-порт boolean SendFlag = false; // флаг что готовности отправки данных на сервер char vala = '13'; char valb = '13'; String BARCODE = ""; // штрихкод String TSD = "1"; // ID-терминала (для каждого нового зашить свой) String QTY = ""; // количество товара String PRODUCT = ""; // наименование товара String MX = ""; // место хранения int selectEthernet = 10; // выбор ведомого на шилде - Ethernet int selectSd = 4; // выбор ведомого на шилде - SD byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; // mac адрес byte ip[] = {192, 168, 1, 200}; // ip если не получим из DHCP byte gateway[] = {192, 168, 1, 101}; // gateway byte subnet[] = {255, 255, 255, 0}; // маска сети byte remote_ip[] = {192, 168, 1, 100}; // адрес сервера char server[] = "web"; // name address server #define SWITCH_TO_W5100 digitalWrite(selectSd,HIGH); digitalWrite(selectEthernet,LOW); // включим Ethernet #define SWITCH_TO_SD digitalWrite(selectEthernet,HIGH); digitalWrite(selectSd,LOW); // включим SD #define ALL_OFF digitalWrite(selectEthernet,HIGH); digitalWrite(selectSd,HIGH); // выключим и SD и Ethernet // Initialize the Ethernet client library // with the IP address and port of the server // that you want to connect to (port 80 is default for HTTP) EthernetClient client; void setup() { // красивая "загрузка" программы при старте lcd.begin(16, 2); lcd.print("Loading"); delay(300); // ждем 0.5 секунды lcd.print("."); delay(300); // ждем 0.5 секунды lcd.print("."); delay(500); // ждем 1 секунду Serial.begin(9600); // инициализируем передачу данных по COM-порту // Задаем режим работы портов pinMode(selectEthernet, OUTPUT); pinMode(selectSd, OUTPUT); // высокий уровень на обоих - не выбран ведомый на SPI digitalWrite(selectEthernet, HIGH); digitalWrite(selectSd, HIGH); // старт Ethernet подключения if (Ethernet.begin(mac) == 0) { lcd.print("Failed DHCP"); // инициализируем без использования DHCP Ethernet.begin(mac, ip, gateway, subnet); // пауза 1 секунда - чтобы shield успел инициализироваться delay(1000); } // выведем локальный IP адрес lcd.clear(); //очистка экрана lcd.print("My IP address: "); lcd.setCursor(0, 1); // устанавливаем курсор в 0-ом столбце, 2 строке for (byte thisByte = 0; thisByte < 4; thisByte++) { lcd.print(Ethernet.localIP()[thisByte], DEC); // печатаем каждый байт IP адреса lcd.print("."); } delay(1000); lcd.clear(); lcd.print("Please scan..."); } void loop() { // проверяем буфер com-порта - было ли что-то принято if (Serial.available() > 0) { //если есть доступные данные считываем строку while (Serial.available() > 0) { int inChar = Serial.read(); // считываем символ BARCODE += (char)inChar; // формируем строку } receiveFlag = true; // устанавливаем флаг что нужно обработать принятые данные Serial.flush(); // очистим буфер } if (receiveFlag == true) { // если флаг receiveFlag установлен - значит что-то приняли. Обработаем //проверим штрихкод: 29..-витрина, остальные-товар char vala = BARCODE.charAt(0); char valb = BARCODE.charAt(1); int len = BARCODE.length(); if (len = 14) { if (vala='2') { if (valb='9') { MX = BARCODE; BARCODE =''; } } } receiveFlag = false; // сбрасываем флаг } // проверим что у нас есть из параметров до сеанса связи с сервером // если не знаем места хранения, ждём сканирования витрины if (MX != "") { // если знаем штрихкод товара, но не знаем наименования - делаем запрос наименования с сервера в PRODUCT if (BARCODE != "") { if (PRODUCT == "") { SWITCH_TO_W5100; // выберем ведомого SPI = включим ethernet if (client.connect(remote_ip, 80)) { // если удалось подключиться String postsend = "POST /tsd/inv.php?tsd="+TSD+"&barcode="+BARCODE+" HTTP/1.1"; // строка запроса:tsd=1&barcode=1251 client.println(postsend); client.println("Host: web"); client.println("Connection: close"); client.println(); if (client.available()) PRODUCT = client.read(); //принимаем в ответ наименование товара client.stop(); // рвем соединение } else lcd.print("connection failed"); // почему то не удалось подключиться к серверу ALL_OFF; // отключим шилд от шины SPI (отключим все) } } } // если знаем штрихкод товара, знаем наименование, но не знаем количества - ждём ввода количества QTY if (BARCODE != "") { if (PRODUCT != "") { if (QTY == "") { // выводим на экран lcd.clear(); //очистка экрана lcd.setCursor(13, 0); // устанавливаем курсор в 12-ом столбце, 1 строке //lcd.print(Count); // выводим кол-во итераций lcd.setCursor(0, 1); // устанавливаем курсор в 0-ом столбце, 2 строке lcd.print(BARCODE); // выводим на экран delay(1000); // задержка перед повторением // проверяем буфер клавиатуры - было ли что-то набрано // ждём ввод числа } } } // если знаем штрихкод товара, знаем наименование, знаем количество - ждём "ОК" или "Отмена" if (BARCODE != "") { if (PRODUCT != "") { if (QTY != "") { // выводим на экран lcd.clear(); //очистка экрана lcd.setCursor(13, 0); // устанавливаем курсор в 12-ом столбце, 1 строке //lcd.print(Count); // выводим кол-во итераций lcd.setCursor(0, 1); // устанавливаем курсор в 0-ом столбце, 2 строке lcd.print(BARCODE); // выводим на экран delay(1000); // задержка перед повторением // ждём подтверждения ввода //if (OK = 1) SendFlag = true; } } } // если есть флаг - добавляем запись на сервере TSD+BARCODE+PRODUCT+QTY+MX if (SendFlag == true) { SWITCH_TO_W5100; // выберем ведомого SPI = включим ethernet if (client.connect(remote_ip, 80)) { // если удалось подключиться String postsend = "POST /tsd/inv.php?tsd="+TSD+"&barcode="+BARCODE+"&PRODUCT="+PRODUCT+"&QTY="+QTY+"&MX="+MX+" HTTP/1.1"; // /tsd/inv.php?tsd=1&barcode=1251&product=уп Аллохол тб N24&qty=2&mx=2978945612375 client.println(postsend); client.println("Host: vpn"); client.println("Connection: close"); client.println(); client.stop(); // рвем соединение // подотрём за собой BARCODE = ""; PRODUCT = ""; QTY = ""; SendFlag = false; } else lcd.print("connection failed"); // почему то не удалось подключиться к серверу ALL_OFF; // отключим шилд от шины SPI (отключим все) } delay(1000); // задержка перед повторением // в любой момент нажали на "Отмена" - очистили переменные BARCODE, PRODUCT, QTY // BARCODE = ""; // PRODUCT = ""; // QTY = ""; }Что можно оптимизировать по существу?
Предстоит вывод кириллицы на экран Nokia5110 всместо 1602A, сетевой обмен esp8266 вместо w5100.
Работу сканера и чтение клавиатуры стоит обрабатывать по прерываниям? delay() заменить millis()
Пришли посылки:
Экран : (Ну что же посмотрим на что он годится)
1. 4-board LED, backlight, light uniformity 2. PCB Size: 45 (mm) x 45 (mm) 3. the corners were positioning holes, along with two rows of the upper and lower connection port, ranked as follows: VCC - power input ( 3.3v ) GND - Ground SCE - Chip Select RST - Reset D \ C - data / instruction selection DN - Serial Data Line CLK - Serial Clock Line LED - backlit consoleWiFi: (Совсем микроскопический. как то даже в голове не укладывается.)
mini 2560 Mega2560-CORE (чувствую основной вес и габарит составляют разъёмы. Нужно будет их аккуратно выпаять вместе с кнопкой. Вопрос как минимизировать соединения?
Добрый день. Очень интересно чем дело закончилось? Уже наверное серийное производство наладили?:)
Дело закончилось иначе. Алгоритм работы сильно превзошел возможности железа. При расширении функционала надёжность сильно регрессировала. В результате база данных на firebird и php-обработчик сохранились, а терминал сваял на Android-смартфоне за рубль и получил достаточно мощный аппарат с надёжной связью и асинхронным интерфейсом взаимодействия, большим сенсорным экраном, русским интерфейсом. Изначально упор делался на аппаратный сканер штрихкода, который должен считать и передать запрос, а остальная часть была как бы вспомогательной, однако тесты показали важность как раз стадии контроля и принятия решения о записи, а так же корректировки. Сканирующий модуль был заменен фотокамерой, которую пришлось модернизировать для макросъёмки. Но в результате второй год в нашей торговой сети инвентаризации товара проходят без остановки торговли. Выборочные инвентаризации не новость, но при раздельном учёте по отделам (закрывается и считается отдел), а инвентаризации всех остатков сразу во время торговли я не встречал.
Производство не налаживал так как в первую очередь решалась частная задача. Конечно можно построить универсальное решение, но обсуждением темы мало кто заинтересовался. Разные торговые оргаинизации используют разные учётные системы. Одних версий, редакций и конфигураций 1С великое множество, не считая собственных и малоизвестных программ. Терминалы сбора данных продаются по той причине что они как чёрный ящик с формальным входом и выходом. Есть и программные терминалы как у меня и даже 1С нарисовала такой (но они все оффлайн, т.е. выгрузили таблицу кодов/наименований в ТСД - занесли отстатки- выгрузили ведомость обратно в БД). Я же решал задачу интегрированную в схему товародвижения. Изначально хотел расширить функционал ТСД при помощи ардуино, но в итоге вышел за рамки для завершения проекта. Если кому интересно - готов показать/настроить/сопроводить.