Моя первая Ардуинка: переключатель USB

в 15:14, , рубрики: arduino, diy или сделай сам, usb, Компьютерное железо, Разработка под Arduino, хаб

Пару лет назад я обзавёлся топовым смартфоном одной южнокорейской компании. Среди его особенностей оказалась поддержка DeX - способа запуска на большом экране, подключаемом к док-станции через HDMI, отдельных приложений и даже целого Linux в контейнере (к сожалению, последнее ушло со свежей версией Android). Кроме того, порадовала поддержка периферийных устройств - так, внешняя звуковая карта Asus Xonar U7, с которой у меня пущен сигнал на ресивер с большими колонками, завелась без проблем. Отсюда возникло желание превратить телефон в мини-рабочее место, научив делить периферию с системником - например, чтобы вой кулеров не мешал слушать музыку или смотреть видео. По сути, требовалось KVM-решение, удовлетворяющее ряду хотелок. Так я познакомился с Arduino.


Требования

До этого у меня на столе "жил" простой четырёхпортовый USB-хаб с клавишей отключения. К нему были подключены проводные клавиатура и мышь, а также Web-камера. Камеру я обычно физически отключал от хаба вне времени использования (паранойя), а клавиша отключения на хабе оказалась удобна тем, что позволяла с комфортом чистить клавиатуру и мышь, не выдёргивая разъёмы и не выключая компьютер. Кроме того, в хаб периодически включался аппаратный менеджер паролей. Звуковая карта и принтер были подключены напрямую к разъёмам на системном блоке, и этим набор USB-периферии ограничивается. Все перечисленные устройства работают по стандарту USB 2.0. На док-станции присутствуют два порта USB той же версии.

Заводить отдельную клавиатуру для телефона, конечно же, не хотелось. Кроме того, я очень привык к удобной мыши Logitech G600 с двенадцатью хорошо прощупываемыми дополнительными кнопками под большим пальцем, обладающими тем преимуществом, что назначенные на них комбинации работают под любой операционной системой и даже в BIOS без установки дополнительного ПО. Поэтому я решил использовать существующие клавиатуру и мышь, но делить их между устройствами. Кроме того, должен существовать способ отключить их совсем. Звуковая карта тоже должна переключаться между хостами и отключаться (чтоб не жрала энергию в отсутствие питания от сети). Отключение камеры решено было внести в ту же систему. Принтер остался в стороне, поскольку смартфон не захотел с ним работать.

Таким образом был сформирован следующий список требований к "умному" USB-переключателю:

  1. Клавиатура и мышь должны совместно переключаться между телефоном и системным блоком, а также отключаться.

  2. Звуковая карта должна переключаться между устройствами и отключаться независимо от клавиатуры и мыши.

  3. Камера должна подключаться к системному блоку и отключаться независимо от других устройств. Факт того, что камера сейчас подключена (даже если на ней самой не горит лампочка), должен привлекать к себе внимание (например, миганием светодиода).

  4. Переключения могут производиться вручную (кнопками) или автоматически (логикой хаба, например, при отключении или подключении устройств).

  5. Монитор переключается независимо от остальных устройств. Поскольку видеокарта и док-станция включены на разные его входы, это делается кнопкой на самом мониторе.

  6. Под рукой должен быть хаб или USB-разъём на гибком проводе для подключения менеджера паролей к системному блоку. Это может быть простой удлинитель, уходящий под стол.

  7. Телефон может быть подключен к переключателю как через док-станцию с внешним питанием, так и напрямую (например, чтобы включать музыку во время мытья пола, когда на сетевом фильтре под столом и, соответственно, на док-станции нет питания). Желательно иметь для подключения через док-станцию и напрямую разные интерфейсы, чтобы не выдёргивать каждый раз разъём из док-станции. Вместо телефона может также быть подключен ноутбук (если HDMI-кабель с док-станции вручную переключить на него же, получится полноценное рабочее место, пока стационарный компьютер на обслуживании).

  8. Питание на переключателе и на подключенных к нему подчинённых устройствах должно присутствовать, если включено хоть одно хост-устройство (системный блок, док-станция или телефон).

  9. Должна присутствовать индикация, показывающая текущее состояние коммутации.

  10. Вся конструкция должна занимать не слишком много места на столе.

Из имеющихся на рынке решений мне на глаза попались KVM-переключатели, а также специализированные переключатели USB вроде этого. Все встреченные мной модели работали по принципу "всё или ничего", коммутируя все подключенные к ним устройства совместно. Мне же хотелось иметь возможность, например, слушать музыку с телефона во время создания образа системного диска с использованием клавиатуры и мыши, а также отключать камеру независимо от остальных устройств. Кроме того, было желание поковыряться и попробовать собрать что-то самостоятельно. Друг посоветовал посмотреть в сторону Arduino, и я начал изучение.

Элементная база

Моё первое представление, составленное со слов друга, сводилось к тому, что для решения моей задачи достаточно будет подключить к контроллеру один или несколько шилдов с достаточным количеством USB-разъёмов, кнопок и индикаторов, а потом запрограммировать контроллер так, чтобы обеспечить коммутацию пакетов USB. Это оказалось неверным по нескольким причинам.

  1. Шилды с мастер-разъёмами USB предназначены для того, чтобы контроллер мог сам опрашивать подключенную к ним периферию, выступая ведущим устройством на шине. При этом непосредственно в формировании и разборе пакетов Arduino не участвует - это делает сторонняя микросхема.

  2. Слэйв-разъёмы предназначены почти исключительно для прошивки и отделки контроллера, а за коммуникацию по ним отвечает не сам контроллер, а отдельная микросхема, обеспечивающая конвертацию серийных протоколов и определяющаяся как COM-порт. Заставить её вместо этого представиться USB-хабом и начать пропускать пакеты от других устройств невозможно.

  3. Сами контроллеры не применяются для коммутации пакетов, и я не уверен, что мне с моим нулевым опытом на этой платформе удалось бы решить эту задачу, равно как и что она вообще имеет решение.

В этот момент я понял, что придётся паять. Опыта самоделок такого уровня у меня не было - тем выше был соблазн попробовать.

Идея состояла в том, чтобы взять два обычных хаба USB 2.0 (в сборе либо в виде микросхем) и под управлением Arduino коммутировать связи между ними и внешними устройствами, добиваясь того же эффекта, как если бы я вручную перетыкал разъёмы. Прежде всего это касается пары сигнальных проводов, поскольку подачу питания можно организовать отдельно. Вооружившись каталогом одного из магазинов радиодеталей, я приступил к поискам подходящей элементной базы.

Найти контроллеры хабов USB в таком виде, чтобы их можно было распаять на плате, мне не удалось - возможно, потому, что я неправильно искал. Поэтому я просто зашёл в компьютерный магазин рядом с домом и купил два самых простых и дешёвых хаба. Дома я вскрыл корпуса, извлёк из них платы и удалил с них разъёмы. Остались небольшие (примерно 2 на 8 сантиметров) панели, которые можно спрятать в корпус устройства, соединив с остальными компонентами монтажным проводом.

С коммутирующими элементами мне повезло больше - на глаза попался переключатель шины FST3125. Он представляет собой четыре ключа, каждый из которых замыкает либо размыкает два вывода:

FST3125
FST3125

Подключив через него мышь, я с удовольствием обнаружил, что всё работает.

Несложный подсчёт показывает, что одной такой микросхемы достаточно для переключения пары выводов D+ и D- одного устройства USB между двумя источниками. Стало быть, для управления одним каналом коммутации требуется два бита (или два вывода ардуинки):

Прямое управление ключами
Прямое управление ключами

Тем не менее, напрямую подавать управляющий сигнал от контроллера к коммутатору мне не хотелось. Дело в том, что, помимо трёх нужных мне состояний ("отключено", "подключено к первому источнику", "подключено ко второму источнику"), такая схема подключения даёт возможность открыть все ключи одновременно (например, из-за ошибки в программе или во время загрузки, когда значения выходных сигналов ещё не выставлены), что приведёт к соединению шин данных сразу трёх устройств, два из которых являются ведущими. Чтобы избежать такого сценария, я вставил между ардуинкой и коммутатором аппаратную логику, исключающую возможность одновременного открытия всех ключей:

Подключение FST3125, исключающее одновременное открытие всех ключей
Подключение FST3125, исключающее одновременное открытие всех ключей

Инвертор перед нижним элементом "или" позволяет сигналу "SIDE" выбирать группу ключей, которая будет открыта. В то же время, высокий сигнал на входе "ON" отключает все ключи сразу.

В качестве контроллера я взял Arduino Nano. Питание на него я подал через пин +5V, а вообще подачу питания организовал через пятивольтовые механические реле V23079A1001B301 (почему не через полупроводники - расскажу ниже). Светодиоды взял первые попавшиеся - убедившись, что через 220-омные резисторы ток и яркость получаются адекватными. Управление яркостью светодиодов взяли на себя встроенные ШИМы контроллера.

Поскольку ножек у Ардуинки не хватало, управление светодиодами (а также некоторой другой логикой на плате, см. ниже) я организовал через микросхемы серии AC32 (на макетке пробовал сначала DIP-исполнение, а потом перешёл на SOIC). Кроме того, в нескольких местах используются инверторы - модели LS04.

Дизайн

Теперь нужно было определиться с тем, как мой умный хаб должен выглядеть и управляться. Я начал с дизайна хаба, лежавшего у меня на столе, и мысленно дополнил его недостающими элементами. Этот процесс показан на рисунке ниже.

Готовый USB-хаб (слева) и проект "умного" хаба (справа)
Готовый USB-хаб (слева) и проект "умного" хаба (справа)

Работать такое устройство должно было следующим образом. Пять разъёмов USB-A, расположенных столбиком, по проекту были объединены в четыре группы и управлялись четырьмя кнопками (на рисунке - справа). Первая группа (два разъёма) предназначалась для клавиатуры и мыши, вторая (один разъём) - для звуковой карты, третья (один разъём) - для камеры, четвёртая (один разъём) была оставлена про запас.

Под крышкой находились те самые два хаба, подключенные к ведущим устройствам через три разъёма USB-B, расположенные в верхнем торце устройства. Правый разъём предназначен для подключения к компьютеру и всегда связан с "правым" хабом. К левому и среднему разъёмам подключались телефон и док-станция: когда телефон подключен, "левым" хабом владел он, в противном случае хаб доставался док-станции. Верхний ряд светодиодов отображал состояние "сторон"-хабов: зелёный - подключено к постоянному устройству, оранжевый - подключено к портативному устройству (телефону), красный - отключено.

Светодиоды с правой и левой стороны от каждого разъёма показывали, к какой стороне он сейчас подключен (а также к какой стороне он подключится при изменении внешних условий, например, возвращении питания на данную сторону). Первые две группы устройств (клавиатуру, мышь и звуковую карту) можно было подключать к любой из сторон и отключать. Камеру - подключать только к правой стороне или отключать. Последний разъём подключался к порту, оставшемуся свободным на левом хабе, и тоже мог отключаться.

Этот проект был затем пересмотрен по нескольким параметрам.

  1. Я решил, что все группы должны вести себя полностью одинаково - меньше будет путаницы. Это подразумевало, что камера сможет, как и прочие каналы, подключаться к каждой из сторон, а пятый разъём исчезнет.

  2. Иллюминация было урезана - остались по два светодиода на группу, загорающиеся при подключении её к правой и левой стороне, соответственно. Цвета этих диодов хватает, чтобы отслеживать также статус сторон.

  3. Поскольку вся схема с ардуинкой, реле, коммутаторами и деталями обвязки значительно превосходила по размерам старый хаб, я решил спрятать её под стол, оставив наверху лишь маленький пульт с кнопками и светодиодами.

  4. Я добавил два "аварийных" светодиода, тлеющих и иногда ярко вспыхивающих красным светом: первый напоминает, что камера в данный момент подключена, второй указывает, что звуковой карте может не хватать питания (об этом - ниже). Эти светодиоды органично вписались в соответствующие кнопки.

Фотографию пульта в работе, а также его относительного размера к основной плате я привожу ниже.

Пульт управления крупным планом и вместе с печатной платой в процессе распайки деталей
Пульт управления крупным планом и вместе с печатной платой в процессе распайки деталей

Кнопки работают наиболее очевидным способом: одиночные нажатия последовательно переключают соответствующий канал между состояниями "подключен к компьютеру", "подключен к телефону", "отключен". Длинные или множественные нажатия не используются.

Светодиоды расположены над каждой кнопкой попарно и отображают состояние данного канала. Левый светодиод горит, когда канал подключен к системному блоку, правый - когда он подключен к телефону. Зелёный сигнал означает, что устройство, к которому выполнено подключение, активно, красный - что напряжения на этом направлении нет.

Макетирование

Проект пережил две макетки, в сумме прожившие у меня на столе более полугода. Их фотографии я прикрепляю ниже.

Первая макетка - питание через диоды
Первая макетка - питание через диоды
Вторая макетка - добавились реле и дополнительные светодиоды в левом верхнем углу
Вторая макетка - добавились реле и дополнительные светодиоды в левом верхнем углу

Первый вопрос касался схемы подключения коммутирующих микросхем. Я использовал описанную выше аппаратную защиту от одновременного открытия ключей, причём клавиатура и мышь управлялись одной парой сигналов. Кроме того, я использовал эту же схему для переключения "левого" хаба между телефоном и док-станцией - правда, не по сигналу контроллера, а по наличию напряжения на разъёме подключения телефона.

Далее, нужно было выбрать схему подключения светодиодов. Поскольку 6 двуцветных светодиодов и 2 одноцветных - это 14 каналов, нечего было и думать подключить их напрямую к ардуинке - ног не хватило бы. Кроме того, мне хотелось поиграться с ШИМами, снизив яркость индикаторов каналов в простое, но зажигая их ярко при нажатии на любую кнопку. "Аварийные" светодиоды я решил заставить мигать - за 4.5 секундами тления должна была следовать яркая вспышка длительностью в полсекунды.

Arduino Nano предоставляет всего шесть ШИМов. Я распределил их следующим образом:

  • Управление яркостью красной компоненты "левых" светодиодов;

  • Управление яркостью зелёной компоненты "левых" светодиодов;

  • Управление яркостью красной компоненты "правых" светодиодов;

  • Управление яркостью зелёной компоненты "правых" светодиодов;

  • Управление светодиодом "камера включена";

  • Управление светодиодом "звуковой карте не хватает питания".

Таким образом, "аварийные" светодиоды подключены напрямую к контроллеру, а остальные - к выходам элементов "или", смешивающих сигнал яркости с сигналом подключения канала к соответствующей стороне.

Разобравшись с этим, я приступил к распайке первой макетки. При тестировании всплыл ряд проблем, которые были частично исправлены при переходе ко второй макетке, а частично - при разводке окончательной платы.

Первая проблема была тривиальной и сводилась к нехватке питания. Как показал эксперимент, просто взять и подключить все четыре устройства к одному хабу нельзя: клавиатура и мышь в сумме потребляют до 300 мА, камера - до 440, звуковая карта - до 540, и это не учитывая питания самой схемы. (Напоминаю, что я решил ограничиться стандартом USB 2.0 с 500 мА на порт, пусть даже материнские платы обычно и разрешают чуть более высокое энергопотребление.) В итоге я решил подключить компьютер через два кабеля вместо одного, предоставив звуковой карте как самому прожорливому потребителю отдельную шину питания - это показалось проще, чем поднимать версию USB. Если же компьютер не выдаёт напряжения на USB-разъёмы, то питание предоставляется оставшимся хостом (телефоном или док-станцией): совместное использование всех устройств в такой конфигурации невозможно, но и юзкейза для подключения всей периферии при обесточенном компьютере я не придумал. Соответственно, "аварийный" светодиод звуковой карты должен мигать, если при питании не от компьютера включены она и что-то ещё.

Кстати, это освободило один интерфейс USB A на хабе, подключенном к компьютеру. Я использовал его для подключения генератора паролей.

Вторая проблема тоже была связана с питанием. "Плюс" с трёх различных источников я изначально планировал свести через диоды с общим катодом, но испытания на макетке показали, что те диоды, которые попались мне под руку, под максимальной нагрузкой давали просадку напряжения почти на целый вольт, что я счёл неприемлемым (стандарт USB допускает минимум в 4.75В). Вместо диодов я организовал подачу питания через механические реле - 30 мА "лишнего" тока на источник выглядели разумной платой за гарантированно равные потенциалы, хотя предполагаю, что опытные радиолюбители нашли бы решение получше.

Переход на реле, впрочем, вызвал другую, неожиданную проблему. Поскольку логика работы контроллера (как минимум, по части управления светодиодами) зависит от того, на какие из разъёмов типа B подано напряжение, эта информация должна быть каким-то образом подана на него. Не мудрствуя лукаво, я завёл эти напряжения на аналоговые входы ардуинки, подтянув к нулю через резисторы номиналом 33k. Это нормально работало с диодами, однако реле добавили задержку, равную времени срабатывания механики, между появлением напряжения на аналоговом входе и на пине +5V. В итоге контроллер получал питание от аналогового пина и зависал на старте. Для исправления ситуации оказалось достаточно поставить между вводом питания и аналоговым пином ещё один резистор, на 18k - теперь до подачи питания на пин +5V Arduino не хватало тока, чтобы включиться, и старт проходил нормально.

Первая макетка управляла коммутацией лишь сигнальных линий USB. Быстро выяснилось, что этого недостаточно: хабы "не замечали" перекоммутации, и переключение мыши, клавиатуры и звуковой карты на другое устройство затягивалось на десятки секунд (а иногда и вовсе не происходило). Стало очевидно, что нужно также отключать питание управляемых устройств, для чего на схему были добавлены ещё четыре реле: по одному на каждый канал и одно для обесточивания "левого" хаба при переключении между телефоном и док-станцией. В итоге алгоритм переключения стал выглядеть следующим образом:

  1. Отключаем линии связи со "старым" ведущим устройством.

  2. Отключаем питание.

  3. Ждём две секунды.

  4. Включаем питание.

  5. Включаем линии связи с "новым" ведущим устройством.

В таком режиме переключение стало происходить чётко и быстро - как при ручном подключении соответствующих разъёмов. Правда, для управления реле пришлось выделить отдельные пины на контроллере, чтобы светодиод, отображающий состояние коммутации, не гас на время принудительного отключения - такое поведение несколько дизориентировало. Это привело к тому, что остальные компоненты пришлось "втискивать" в оставшиеся свободными выводы ардуинки - сменив клавиатуру на аналоговую и даже сведя входные напряжения от обоих кабелей, ведущих к системному блоку, на один пин.

Ещё одной проблемой макеток были небольшие потери пакетов высокоскоростных устройств - изредка выпадали кадры с камеры, а в колонках при работе с телефона раздавались щелчки. Эти проблемы удалось нивелировать, сократив длину монтажного провода и, где возможно, подключив разрезанные USB-кабели напрямую к коммутаторам вместо распайки разъёмов USB-A и USB-B на макетке.

Итоговый проект

Финальная структурная схема коммутации линий данных выглядит следующим образом:

Линии передачи данных USB
Линии передачи данных USB

Здесь все переключатели с K1 по K5 представлены микросхемами FST3125. K1 и K2 управляются совместно, остальные - независимо. K1..K4 переключаются сигналами с контроллера, K5 - наличием напряжения на разъёме USB B, к которому подключается телефон.

Разводка питания выполнена по следующей схеме:

Разводка питания
Разводка питания

Земля (на схеме опущена) на всех разъёмах и на плате общая. Переключатели с K6 по K11 - это реле V23079A1001B301. Легко видеть, что питание схемы и большей части периферии обеспечивается системным блоком, если он выключен - то док-станцией, а в крайнем случае - телефоном. Для этого реле K6 и K7 управляются напрямую наличием питания на соответствующих разъёмах.

Реле K8..K11 управляются контроллером и используются для временного отключения устройств при переключении (периферии - между ведущими устройствами, первого хаба - между телефоном и док-станцией). Нагрузка подключена на их нормально замкнутые контакты, чтобы не увеличивать пиковый ток за счёт обмоток. Звуковая карта имеет приоритетное питание напрямую от системного блока.

Для создания финальной схемы и разводки дорожек я использовал EasyEDA, расположив элементы таким образом, чтобы минимизировать длину дорожек, по которым идут данные USB. Размеры платы я подогнал под пластиковый корпус, купленный в местном радиомагазине. Пульт собрал в отдельном маленьком корпусе и соединил с основной схемой жгутом монтажного провода, затянутом в термоусадочную трубку.

Плату я заказал в Китае. В готовом виде она выглядит следующим образом:

Готовые платы
Готовые платы

Правда, тут вышла небольшая накладка: распределяя пины контроллера для упрощения разводки, я случайно поменял местами пин с ШИМом и пин без него. В итоге управление яркостью светодиодов на одном из каналов стало невозможным. К счастью, это была единственная крупная ошибка, и я решил проблему, перерезав пару дорожек и соединив их крест-накрест монтажным проводом. Кроме того, я несколько неверно разметил выводы для подключения светодиодов, но это решилось иной распайкой проводов с пульта заменой номера пинов в программе.

Неприятным сюрпризом стали проблемы с потерей пакетов с камеры. Вскоре выяснилось, что пакеты теряются, только если камера подключена к хабу, соединённому с компьютером без промежуточной логики. При подключении через второй хаб, соединённый с ведущими устройствами через коммутатор FST3125, потерь не было. Не было их также и при переключении камеры на разъём, предназначенный для звуковой карты и подключенный к системному блоку в обход хабов. Наконец, подключив хаб напрямую к компьютеру обрезком USB-кабеля (а не через разъём USB-B, распаянный на плате), я снова добился отсутствия потерь.

Таблица ниже содержит информацию о том, в каких случаях наблюдались потери пакетов.

Состав тракта

Потери пакетов

Компьютер - кабель - разъём USB B - дорожки на плате - провода между платой и хабом - хаб - провода между хабом и платой - дорожки на плате - микросхема-коммутатор - дорожки на плате - разъём USB A - кабель - камера

Да

Компьютер - кабель - хаб - провода между хабом и платой - дорожки на плате - микросхема-коммутатор - дорожки на плате - разъём USB A - кабель - камера

Нет

Компьютер - кабель - разъём USB B - дорожки на плате - микросхема-коммутатор - дорожки на плате - разъём USB A - кабель - камера

Нет

Компьютер - кабель - разъём USB B - дорожки на плате - микросхема-коммутатор - дорожки на плате - провода между платой и хабом - хаб - провода между хабом и платой - дорожки на плате - микросхема-коммутатор - дорожки на плате - разъём USB A - кабель - камера

Нет

Из этой таблицы становится ясно, что потери пакетов наблюдаются только в том случае, если после разъёма USB B, в который включен кабель, пришедший с компьютера, следует отрезок провода, ведущий на плату хаба, причём между двумя этими отрезками нет микросхемы-коммутатора. По-видимому, кабель USB и перемычка из монтажного провода обладают разными волновыми сопротивлениями, что ведёт к появлению отражений на их границе и, соответственно, к потере пакетов. Микросхема FST3125, включенная между этими отрезками, эффективно гасит подобные отражения.

Чтобы проверить эту теорию, я соединил хаб с основной платой не монтажным проводом, а отрезком того самого кабеля, которым он далее подключен к компьютеру через разъём USB B. Результат превзошёл все ожидания: потери кадров полностью прекратились, и всё заработало как часы.

Плату я разместил в пластиковом корпусе, закрепив двумя гайками и прорезав окошки для разъёмов. Контроллер поставил в колодки, чтобы иметь возможность извлечь его для обновления прошивки. Хабы подвесил на проводах - они прижаты к верхней крышке корпуса их жёсткостью, что не должно создавать проблем. Сам корпус я подвесил на шнурке к креплению настольной лампы, а пульт положил на стол. Разъёмы подписал белым маркером.

Основной модуль в сборе
Основной модуль в сборе

Программа написана на C++ и доступна по ссылке. Я постарался разделить сущности, отвечающие за взаимодействие с различными компонентами схемы, хотя и поленился выделить код методов в файлы cpp, рассудив, что система сборки Arduino Studio всё равно не извлечёт из этого какой-либо выгоды. За работу с кнопками (прежде всего, антидребезг) отвечает библиотека GyverButton. За тайминг (отключение устройств, мигание и снижение яркости светодиодов) - GyverTimer. И то, и другое доступно в пакете GyverLibs - пользуясь случаем, выражаю его автору свою благодарность.

Резюме

Задача оказалась хорошей в учебном плане - благодаря работе над проектом я обрёл новые навыки и знания. Это касается как работы с железом (раздача питания, коммутация потоков данных, управление индикацией, пайка микросхем в форм-факторе SOIC, отражения в высокочастотном тракте), так и представлений о современных контроллерах и способах их использования. Получившееся изделие прекрасно справляется со своей задачей - использование телефона в качестве рабочего места или источника музыки происходит теперь весьма просто.

В то же время, существующее железо для Arduino не позволило пойти по пути быстрого прототипирования, поскольку многое пришлось изобретать самому, что сильно затянуло проект. Надеюсь, следующую задумку - управление работой дистиллятора - удастся реализовать более прямым путём.

Автор: Дмитрий Баскаков

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js