Форум самогонщиков Сайт Барахолка Магазин Помощь солдатам

Ненавязчивая автоматизация ректификационной установки

Форум самогонщиков Автоматика
1 ... 84 85 86 87 88 89 90 ... 132 87
BogAD Кандидат наук Красногорск - Белово 403 184
Отв.1720  04 Сент. 19, 20:43
о для диэлькометра (измерение проницаемости) обкладки конденсатора нужно изолировать от жидкости.OldBean, 03 Сент. 19, 10:00
Сергей, как к гуру лабораторных примочек вопрос: у диэлькометра нужно две обкладки изолировать, или достаточно одной?
OldBean Доцент Красноярск 1K 1.4K
Отв.1721  05 Сент. 19, 02:49
ZagAl, спасибо за пояснения.
Думаю над счетчиком вот по такому принципу.ZagAl, 04 Сент. 19, 17:18
Отличная идея! Пожалуй, понадежнее будет, чем сифон. А если не нужно высокое разрешение по расходу (с квантом на уровне миллилитров), то вполне можно обойтись и без такого "тяжелого" элемента, как весы
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

у диэлькометра нужно две обкладки изолировать, или достаточно однойBogAD, 04 Сент. 19, 20:43
Я бы изолировал обе. Лучше исключить всякую электрохимию на металлических электродах. Кто знает какие примеси могут прилететь...
Endi Магистр Beer Sheva 238 86
Отв.1722  08 Окт. 19, 21:56
Уважаемый OldBean. Нижайшая просьба. Сделайте что-нибудь, чтобы изображения, которые Вы вставили в описание своей работы, можно было открыть и просмотреть. Без этого, описание очень много теряет. 
makh Профессор Sаmara 2.1K 1.1K
Отв.1723  08 Окт. 19, 23:35
Endi, это глюки форумного движка, скоро само заработает -- [сообщение #13585816]
Endi Магистр Beer Sheva 238 86
Отв.1724  09 Окт. 19, 11:46
Будем ждать.
OldBean Доцент Красноярск 1K 1.4K
Отв.1725  25 Окт. 19, 02:35
Endi, все проблемы с картинками Андрей поправил. Они теперь отображаются правильно.
tonik38 Новичок Иркутск 6
Отв.1726  25 Окт. 19, 07:00
У меня схемы так и закрыты логотипом. Gif-ки

Добавлено через 3мин.:

У меня схемы так и закрыты логотипом. Gif-ки, и ТЭН, и RMS
BogAD Кандидат наук Красногорск - Белово 403 184
Отв.1727  25 Окт. 19, 07:53, через 53 мин
У меня схемы так и закрыты логотипом. Gif-ки, и ТЭН, и RMStonik38, 25 Окт. 19, 07:00
[сообщение #13230607]
[сообщение #13211335]
Проверил, схемы видны...
OldBean Доцент Красноярск 1K 1.4K
Отв.1728  25 Окт. 19, 11:56
У меня схемы так и закрыты логотипом. Gif-киtonik38, 25 Окт. 19, 07:00
Сотрите кэш броузера. Скорее всего броузер берет картинки оттуда.
tonik38 Новичок Иркутск 6
Отв.1729  25 Окт. 19, 17:01
Спасибо,  все видно сейчас. Даже кэш не пришлось чистить)))
OldBean Доцент Красноярск 1K 1.4K
Отв.1730  10 Нояб. 19, 18:14
17.7.5. lsync v.0.3.x.x

Закончил тестирование и отладку LITE версии 0.3.0.0. Отладка получилась небыстрой - много экспериментировал с синхронизатором, интерфейсами, многое переписывал. наконец получилась система с которой приятно работать. В последние длинные выходные (ноябрьские праздники) провел более чем двухсуточную вторичную ректификацию накопленного СР1, получил 12л "нежно" пахнущего СР2 и решил, что на этом варианте софта вполне можно остановиться. Конечно, остались еще недоделки, хотелки и некритические ошибки ("списочек" на семи листах А5). Ну это уже потом. Главное -  концепт, заложенный в софт, наконец-то "рафинировался" и полностью оправдал мои ожидания.

Основные особенности версии 0.3 следующие (пункты 5,6 и 7 впервые реализованы только в версии 0.3):

1. Автоматический анализ подключенного оборудования.
2. Автоматическое генерирование, именование и инициализация питоновских объектов, соответствующих подключенным датчикам и контроллерам.
3. Полное абстрагирование пользователя от особенностей реализации "железа" за счет создания виртуальной модели управляемой установки в БД типа "ключ-значение" redis.
4. Автоматическая двухсторонняя синхронизация состояния реального "железа" и его виртуальной модели. Такт синхронизации (в версии 0.3) - 2 секунды.
5. Реализация концепции конечного автомата для управляемой установки. Каждое состояние автомата определяется набором параметров исполнительных устройств. Переключение между состояниями производится на основе анализа показаний датчиков установки.
6. Реализована возможность загрузки в БД пользовательского скрипта на языке python, который может реализовывать специфическую логику управляемого процесса. На следующем (после загрузке) такте синхронизатора этот скрипт компилируется в байт-код с инициализацией всех его объектов. Затем этот байт-код будет выполняться на каждом такте синхронизатора в его пространстве имен.
7. Добавлен консольный пользовательский интерфейс (на базе библиотеки curses)  для работы с синхронизатором и мониторинга параметров. В рамках загружаемого пользовательского скрипта этот интерфейс может быть расширен до возможности проводить все процессы без использования специальных клиентских приложений. Практика показала, что такой вариант управления очень удобный и экономный в вопросах ресурсов. Последние ректификации я проводил именно в таком режиме. Мне понравилось.

Я не буду подробно описывать все детали реализации - все исходники прилагаются и подробно прокомментированы. Если кого заинтересуют детали - их можно посмотреть в исходниках. А ежели что конкретно непонятно - дайте знать - расскажу подробнее. А в данном топике я просто покажу пример реальной работы и прокомментирую действия.

Итак, запускаем на малинке в консоли синхронизатор lsync.py:
python3 lsync.py
Синхронизатор импортирует модуль lite.py. Поэтому он должен быть в этой же папке, что и lsync.py или в другой папке, известной питону. После запуска мы увидим в консоли базовый пользовательский интерфейс синхронизатора:
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

В левой колонке выводятся имена и показания датчиков, имена и состояние контроллеров исполнительных устройств. Все обнаруженные системой устройства будут показываться в этой колонке. В средней колонке показан всего один режим работы установки - мониторинг. В этот режим система переходит по умолчанию после инициализации. В правой колонке выводится время и справка по клавишам управления. Если нажмем клавишу 'h', то появится подробная информация об обнаруженном оборудовании:
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

В левой колонке - имя устройства, по которому можно обращаться к базе данных (имя - это ключа в БД). В следующей колонке - тип устройства, далее - его уникальный hard-ключ и в последней колонке - калибровочная информация. Нажав клавишу 'c' можно вернуться обратно в главное окно синхронизатора.

На этом этапе мы уже можем полностью управлять системой в ручном режиме. Для этого нужно соединится с БД redis и изменять соотвествующие поля БД. Проще всего это сделать при помощи утилиты api.py. Мы уже говорили про эту утилиту здесь. Там же (в архиве) есть соответствующий справочник. А можно и просто посмотреть комментарии в тексте модуля api.py. Там много примеров реальной работы с этой утилитой.

Ручной режим управления установкой хорош для отладки или каких-нибудь экспериментов. Но ректификацию лучше проводить в автоматическом режима. И нервы целее и качество продукта выше. Но для этого нам нужно объяснить синхронизатору как, что и когда нужно делать. Это можно сделать при помощи пользовательского скрипта, возможность загрузки и периодического выполнения которого в пространство имен синхронизатора реализована в версии 0.3.

Пользовательский скрипт - это по сути дела кусок кода на языке python при помощи которого можно делать всякие полезные вещи. Например, отправлять данные, получаемые синхронизатором от "железа", в облако. Или просто автоматически провести ректификацию. Рассмотрим второй вариант. Пример такого скрипта ниже:

Скрытый текст
'''-----------------------------------------------------------------------------
Пользовательский скрипт для простой ректификации спирта-сырца.
Регулировка скорости отбора - простой старт-стоп с гистерезисом +- 1 квант
датчика DS18B20
-----------------------------------------------------------------------------'''
'''Режимы работы установки'''
Mode('Разгон', w0 = 2000.0, q0 = 0.0)
Mode('Холостой ход', w0 = 600.0, q0 = 0.0)
Mode('Головы', w0 = 600.0, q0 = 50.0)
Mode('Подголовники', w0 = 600.0, q0 = 400.0)
Mode('Тело', w0 = 600.0, q0 = 400.0, ss = 1)
Mode('Хвосты', w0 = 600.0, q0 = 400.0)
Mode('Пропарка', w0 = 2000.0, q0 = 1200.0)
Mode('Откыть клапан', w0 = 0.0, q0 = 1200.0)
Mode('СР2', w0 = 600.0, q0 = 200.0)
'''Пользовательские переменные'''
ss = 0 # Флаг старт-стопа
Tset = 78.13 # Значение уставки старт-стопа
tsa = 0 # Флаг контроля температуры ТСА
water = 1 # Флаг контроля температуры воды охлаждения
'''Пользовательские константы'''
Trcr = 65.0 # Т-ра перехода с режима разгона на холостой ход
Ttcr = 50.0 # Критическая температура в ТСА
Twcr = 40.0 # Критическая температура воды охлаждения
Tdcr = 78.5 # Т-ра дефлегматора для окончания отбора хвостов
'''Пользовательские функции'''
def fss(): # Простейший старт-стоп. Гистерезис - 2 кванта DS18B20
 '''Если отбор ведется и т-ра превысила уставку, то выключим отбор'''
 if q0 > 0.0 and T1 >= Tset + 0.0625: q0 = 0.0
 '''Если отбор выключен и т-ра опустилась ниже уставки, то включим отбор'''
 if q0 == 0.0 and T1 <= Tset - 0.0625: q0 = 400.0
'''Следующие три функции предназначены для поддержки специфической части
пользовтельского интерфейса. UI основан на библиотеке curses и функционирует в
консоли, в которой запущен синхронизатор'''
def ui(raw, col): # Элементы управления для пользовательских переменных
 raw += 1
 stdscr.addstr(raw, col, 'Старт/стоп    [ ]')
 if ss != 0: stdscr.addstr(raw, col + 15, 'x')
 raw += 1
 stdscr.addstr(raw, col, 'Уставка   % 7.3f'%(Tset)); raw += 1
 d = T1 - Tset
 if d > 0: c = 1
 else: c = 2
 stdscr.addstr(raw, col, 'Невязка   % 7.3f'%(d), curses.color_pair(c))
 raw += 2
 stdscr.addstr(raw, col, 'Контроль:'); raw += 1
 stdscr.addstr(raw, col, 'ТСА           [ ]')
 if tsa != 0: stdscr.addstr(raw, col + 15, 'x')
 raw += 1
 stdscr.addstr(raw, col, 'Вода          [ ]')
 if water != 0: stdscr.addstr(raw, col + 15, 'x')
 raw += 1
 return raw
def uicallback(ch): # Пользовательская callback-функция
 if ch == ord('s'): ss = 1 - int(ss)
 elif ch == ord('g'): Tset = float(rdb.lindex('T1', -1))
 elif ch in [ord('-'), ord('_')]: Tset -= 0.0625
 elif ch in [ord('+'), ord('=')]: Tset += 0.0625
 elif ch == ord('t'): tsa = 1 - int(tsa)
 elif ch == ord('w'): water = 1 - int(water)
def uihelp(raw, col): # Справка для пользовательских команд
 stdscr.addstr(raw, col, 's        - вкл/выкл старт/стоп'); raw += 1
 stdscr.addstr(raw, col, 'g,+,-    - модификация уставки'); raw += 1
 stdscr.addstr(raw, col, 't - вкл/выкл контроль т-ры ТСА'); raw += 1
 stdscr.addstr(raw, col, 'w - вкл/выкл контроль т-ры воды'); raw += 1
 return raw
'''Общие условия, не зависящие от режима работы установки'''
if tsa != 0: # Контроль температуры ТСА включен
 if T3 > Ttcr: # Температура в ТСА превысила критическое значение
   mode = 0
if water != 0: # Контроль температуры воды включен
 if T4 > Twcr: # Недостаточное охлаждение дефлегматора
   mode = 0
'''Условия, зависящие от режимов работы установки'''
if mode == 1: # Разгон
 if T1 > Trcr: # Закипело - переходим на холостой ход
   mode = 2
elif mode == 2: # Холостой ход; автоматического выхода из этого режима нет
 pass
elif mode == 3: # Головы; скорость отбора - 50 мл/час
 if Q0 > 200.0: # Переход в режим холостого хода после отбора 200 мл
   mode = 2
elif mode == 4: # Подголовники (оборотный спирт); скорость отбора 400 мл/час
 if ss != 0: fss() # Если включен режим старт-стопа - используем его
 if Q0 > 1000.0: # После отбора 1000 мл подголовников - переход на х/ход
   mode = 2
elif mode == 5: # Тело (основная фракция); скорость отбора 400 мл/час
 if ss != 0: fss() # Если включен режим старт-стопа - используем его
 '''Автоматического выхода из режима отбора тела пока не делаем'''
elif mode == 6: # Исправимые хвосты (оборотный спирт)
 if T2 > Tdcr: # Выход на х/ход, если т-ра в дефе превысит порог
   mode = 2

Это не самый простой скрипт, при помощи которого можно провести полный процесс ректификации. Просто это - тот скрипт, которым я сейчас реально пользуюсь при ректификации. Помимо чисто программного управления установкой, он содержит элементы UI, позволяющие проводить процесс ректификации не прибегая к дополнительному клиентскому приложению. Это совсем не означает, что я "привязан" к малинке и установке. Я могу запустить синхронизатор с любого компьютера в сети через SSH. Желательно - через screen, чтобы можно было бы спокойно разрывать соединение. Ну а если в доме ничего кроме телефона нет, то тоже не беда. Можно установить на телефон SSH-клиент (для Android их есть несколько штук, бесплатных) и работать "с дивана". Как некоторые любят :)

Вернемся к скрипту. В первом блоке описаны несколько режимов работы установки (это - возможные состояния нашего "конечного автомата"). А по питоновской сути - это последовательное создание объектов класса Mode. В скобочках - название режима и конкретные значение параметров исполнительных устройств в используемых единицах измерения. Например, строчка
Mode('Тело', w0 = 600.0, q0 = 400.0, ss = 1)
означает режим отбора "тела" с мощностью нагрева 600 Вт, базовой скоростью отбора - 400 мл/час и включенным режимом старт-стопа. По умолчанию старт-стоп выключен. Если клапанов и нагревателей несколько, то список параметров конструктора, естественно, должен быть расширен.

Далее описаны пользовательские переменные/константы и пользовательские функции. Рассмотрим кратко функции. Первая fss как раз и описывает режим старт-стопа. Она подробно прокомментирована в тексте скрипта. Вторая (ui) дополняет базовый UI синхронизатора пользовательскими элементами. Следующая callback-функция (uicallback) предназначена для расширения базового обработчика клавиатуры (который в синхронизаторе) пользовательскими потребностями. Ну и последняя функция (uihelp) дополняет справку.

Ну и, наконец, последний блок скрипта - реализация пользовательской логики автоматического управления установкой. Этот блок тоже подробно прокомментирован. Не буду повторяться.

Итак, пользовательский скрипт пишется пользователем, исходя из решаемой задачи. Непосредственно это скрипт нельзя выполнить без некоторых преобразований. Эти преобразования делаются автоматически, но я не стал утяжелять синхронизатор и вынес преобразователь в отдельную утилиту conv.py. Основных причин для такого "выноса" две. Во-первых, загрузка скрипта производится сравнительно редко. Если режимы и логика процесса остается такой же, как и в предыдущем процессе, то загружать скрипт каждый раз не нужно. Он сохраняется в БД и автоматически "включается в работу". И, во-вторых, имея преобразованный скрипт в виде текстового файла гораздо легче найти ошибки в логике управления, если таковые имеются.

Итак, утилита conv.py преобразует исходный скрипт (типа приведенного выше) в адаптированный скрипт с именем uscr.py, который может быть загружен работающим синхронизатором при нажатии клавиши 'u'. Пользоваться преобразователем нужно так
python3 conv.py -f uscript.py
где uscript.py - файл нашего исходного скрипта. Имя файла может быть любым допустимым.

Ну что ж. Предположим, что мы написали свой скрипт (в моем случае - uscript.py), запустили преобразователь и получили на выходе файл uscr.py. Имя преобразованного файла всегда должно быть таким. Это тоже для того, чтобы не утяжелять всуе синхронизатор диалогами выбора файла и пр. Текст преобразованного скрипта для моего варианта скрипта приведен ниже:

Скрытый текст
if float(rdb.get('usinit')) != 0:

 class R(object):
   fc = 0xFD
   @property
   def T0(self):
     if rdb.type('T0') == b'list':
       return float(rdb.lindex('T0', -1))
     else:
       return float(rdb.get('T0'))
   @property
   def T1(self):
     if rdb.type('T1') == b'list':
       return float(rdb.lindex('T1', -1))
     else:
       return float(rdb.get('T1'))
   @property
   def T2(self):
     if rdb.type('T2') == b'list':
       return float(rdb.lindex('T2', -1))
     else:
       return float(rdb.get('T2'))
   @property
   def T3(self):
     if rdb.type('T3') == b'list':
       return float(rdb.lindex('T3', -1))
     else:
       return float(rdb.get('T3'))
   @property
   def T4(self):
     if rdb.type('T4') == b'list':
       return float(rdb.lindex('T4', -1))
     else:
       return float(rdb.get('T4'))
   @property
   def Q0(self):
     if rdb.type('Q0') == b'list':
       return float(rdb.lindex('Q0', -1))
     else:
       return float(rdb.get('Q0'))
   @Q0.setter
   def Q0(self, val):
     if rdb.type('Q0') == b'list':
       rdb.rpush('must', 'Q0')
       rdb.rpush('must', val)
     else:
       rdb.set('Q0', val)
   @property
   def q0(self):
     if rdb.type('q0') == b'list':
       return float(rdb.lindex('q0', -1))
     else:
       return float(rdb.get('q0'))
   @q0.setter
   def q0(self, val):
     if rdb.type('q0') == b'list':
       rdb.rpush('must', 'q0')
       rdb.rpush('must', val)
     else:
       rdb.set('q0', val)
   @property
   def w0(self):
     if rdb.type('w0') == b'list':
       return float(rdb.lindex('w0', -1))
     else:
       return float(rdb.get('w0'))
   @w0.setter
   def w0(self, val):
     if rdb.type('w0') == b'list':
       rdb.rpush('must', 'w0')
       rdb.rpush('must', val)
     else:
       rdb.set('w0', val)
   @property
   def mtime(self):
     if rdb.type('mtime') == b'list':
       return float(rdb.lindex('mtime', -1))
     else:
       return float(rdb.get('mtime'))
   @mtime.setter
   def mtime(self, val):
     if rdb.type('mtime') == b'list':
       rdb.rpush('must', 'mtime')
       rdb.rpush('must', val)
     else:
       rdb.set('mtime', val)
   @property
   def mode(self):
     if rdb.type('mode') == b'list':
       return float(rdb.lindex('mode', -1))
     else:
       return float(rdb.get('mode'))
   @mode.setter
   def mode(self, val):
     if rdb.type('mode') == b'list':
       rdb.rpush('must', 'mode')
       rdb.rpush('must', val)
     else:
       rdb.set('mode', val)
   @property
   def ss(self):
     if rdb.type('ss') == b'list':
       return float(rdb.lindex('ss', -1))
     else:
       return float(rdb.get('ss'))
   @ss.setter
   def ss(self, val):
     if rdb.type('ss') == b'list':
       rdb.rpush('must', 'ss')
       rdb.rpush('must', val)
     else:
       rdb.set('ss', val)
   @property
   def Tset(self):
     if rdb.type('Tset') == b'list':
       return float(rdb.lindex('Tset', -1))
     else:
       return float(rdb.get('Tset'))
   @Tset.setter
   def Tset(self, val):
     if rdb.type('Tset') == b'list':
       rdb.rpush('must', 'Tset')
       rdb.rpush('must', val)
     else:
       rdb.set('Tset', val)
   @property
   def tsa(self):
     if rdb.type('tsa') == b'list':
       return float(rdb.lindex('tsa', -1))
     else:
       return float(rdb.get('tsa'))
   @tsa.setter
   def tsa(self, val):
     if rdb.type('tsa') == b'list':
       rdb.rpush('must', 'tsa')
       rdb.rpush('must', val)
     else:
       rdb.set('tsa', val)
   @property
   def water(self):
     if rdb.type('water') == b'list':
       return float(rdb.lindex('water', -1))
     else:
       return float(rdb.get('water'))
   @water.setter
   def water(self, val):
     if rdb.type('water') == b'list':
       rdb.rpush('must', 'water')
       rdb.rpush('must', val)
     else:
       rdb.set('water', val)
   @property
   def Trcr(self):
     if rdb.type('Trcr') == b'list':
       return float(rdb.lindex('Trcr', -1))
     else:
       return float(rdb.get('Trcr'))
   @Trcr.setter
   def Trcr(self, val):
     if rdb.type('Trcr') == b'list':
       rdb.rpush('must', 'Trcr')
       rdb.rpush('must', val)
     else:
       rdb.set('Trcr', val)
   @property
   def Ttcr(self):
     if rdb.type('Ttcr') == b'list':
       return float(rdb.lindex('Ttcr', -1))
     else:
       return float(rdb.get('Ttcr'))
   @Ttcr.setter
   def Ttcr(self, val):
     if rdb.type('Ttcr') == b'list':
       rdb.rpush('must', 'Ttcr')
       rdb.rpush('must', val)
     else:
       rdb.set('Ttcr', val)
   @property
   def Twcr(self):
     if rdb.type('Twcr') == b'list':
       return float(rdb.lindex('Twcr', -1))
     else:
       return float(rdb.get('Twcr'))
   @Twcr.setter
   def Twcr(self, val):
     if rdb.type('Twcr') == b'list':
       rdb.rpush('must', 'Twcr')
       rdb.rpush('must', val)
     else:
       rdb.set('Twcr', val)
   @property
   def Tdcr(self):
     if rdb.type('Tdcr') == b'list':
       return float(rdb.lindex('Tdcr', -1))
     else:
       return float(rdb.get('Tdcr'))
   @Tdcr.setter
   def Tdcr(self, val):
     if rdb.type('Tdcr') == b'list':
       rdb.rpush('must', 'Tdcr')
       rdb.rpush('must', val)
     else:
       rdb.set('Tdcr', val)
 r = R()
 modes.append(Mode('Разгон', w0 = 2000.0, q0 = 0.0))
 modes.append(Mode('Холостой ход', w0 = 600.0, q0 = 0.0))
 modes.append(Mode('Головы', w0 = 600.0, q0 = 50.0))
 modes.append(Mode('Подголовники', w0 = 600.0, q0 = 400.0))
 modes.append(Mode('Тело', w0 = 600.0, q0 = 400.0, ss = 1))
 modes.append(Mode('Хвосты', w0 = 600.0, q0 = 400.0))
 modes.append(Mode('Пропарка', w0 = 2000.0, q0 = 1200.0))
 modes.append(Mode('Откыть клапан', w0 = 0.0, q0 = 1200.0))
 modes.append(Mode('СР2', w0 = 600.0, q0 = 200.0))
 r.ss = 0
 r.Tset = 78.13
 r.tsa = 0
 r.water = 1
 r.Trcr = 65.0
 r.Ttcr = 50.0
 r.Twcr = 40.0
 r.Tdcr = 78.5
 def fss():
   if r.q0 > 0.0 and r.T1 >= r.Tset + 0.0625: r.q0 = 0.0
   if r.q0 == 0.0 and r.T1 <= r.Tset - 0.0625: r.q0 = 400.0
 def ui(raw, col):
   raw += 1
   stdscr.addstr(raw, col, 'Старт/стоп    [ ]')
   if r.ss != 0: stdscr.addstr(raw, col + 15, 'x')
   raw += 1
   stdscr.addstr(raw, col, 'Уставка   % 7.3f'%(r.Tset)); raw += 1
   d = r.T1 - r.Tset
   if d > 0: c = 1
   else: c = 2
   stdscr.addstr(raw, col, 'Невязка   % 7.3f'%(d), curses.color_pair(c))
   raw += 2
   stdscr.addstr(raw, col, 'Контроль:'); raw += 1
   stdscr.addstr(raw, col, 'ТСА           [ ]')
   if r.tsa != 0: stdscr.addstr(raw, col + 15, 'x')
   raw += 1
   stdscr.addstr(raw, col, 'Вода          [ ]')
   if r.water != 0: stdscr.addstr(raw, col + 15, 'x')
   raw += 1
   return raw
 def uicallback(ch):
   if ch == ord('s'): r.ss = 1 - int(r.ss)
   elif ch == ord('g'): r.Tset = float(rdb.lindex('T1', -1))
   elif ch in [ord('-'), ord('_')]: r.Tset -= 0.0625
   elif ch in [ord('+'), ord('=')]: r.Tset += 0.0625
   elif ch == ord('t'): r.tsa = 1 - int(r.tsa)
   elif ch == ord('w'): r.water = 1 - int(r.water)
 def uihelp(raw, col):
   stdscr.addstr(raw, col, 's        - вкл/выкл старт/стоп'); raw += 1
   stdscr.addstr(raw, col, 'g,+,-    - модификация уставки'); raw += 1
   stdscr.addstr(raw, col, 't - вкл/выкл контроль т-ры ТСА'); raw += 1
   stdscr.addstr(raw, col, 'w - вкл/выкл контроль т-ры воды'); raw += 1
   return raw
 rdb.set('usinit', 0)
if r.tsa != 0:
 if r.T3 > r.Ttcr:
   r.mode = 0
if r.water != 0:
 if r.T4 > r.Twcr:
   r.mode = 0
if r.mode == 1:
 if r.T1 > r.Trcr:
   r.mode = 2
elif r.mode == 2:
 pass
elif r.mode == 3:
 if r.Q0 > 200.0:
   r.mode = 2
elif r.mode == 4:
 if r.ss != 0: fss()
 if r.Q0 > 1000.0:
   r.mode = 2
elif r.mode == 5:
 if r.ss != 0: fss()
elif r.mode == 6:
 if r.T2 > r.Tdcr:
   r.mode = 2

Из текста преобразованного скрипта мы видим, что сгенерирован новый класс R, в котором реализованы сеттеры и геттеры всех переменных, используемых в скрипте. Сеттеры и геттеры "переопределяют" обращение к переменным пользовательского скрипта. Их значения берутся/помещаются в БД вместо памяти. Обращения к переменным заменены обращениями к атрибутам объекта r класса R. Ну и, естественно, из скрипта убраны все комментарии и сделаны некоторые преобразования, упрощающие синтаксис исходного скрипта.

Итак, скрипт преобразован. Теперь в работающем синхронизаторе мы можем нажать клавишу 'u'. Преобразованный скрипт будет загружен в БД, настроен на работу в пространстве имен синхронизатора и изменит вид пользовательского интерфейса:
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

Мы видим, что интерфейс уже "заточен" под текущую задачу. Если в кубе находится сырец, мы можем включить сеть и нажать клавишу '1' для включения режима разгона. Поехали:
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

После того, как температура в нижней части колонны достигнет 65° (см. условие в скрипте) система автоматически перейдет в режим холостого хода:
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

После этого нужно подождать, пока пар дойдет до дефлегматора, поток флегмы потечет вниз и в конце концов установится стационарное состояние, когда температура в нижней части колонны перестанет изменяться во времени. После этого, можно поставить приемную емкость для голов, нажать клавишу '3' и приступить к отбору голов. Отобрав положенное количество голов, система автоматически вернется в режим 2 (холостой ход). Мы ей так "предписали" в своем скрипте. Теперь можно сменить приемную емкость и перейти к отбору подголовников. И так далее, до самого конца ректификации...

PS1
Немного про интерфейсы

Клиент с графическим пользовательским интерфейсом для варианта LITE у меня, естественно, был написан. Вид его представлен на рисунке ниже.
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

Отладку синхронизатора версии 0.3 я начал именно с этим клиентом. Но потом мне потребовалось "наладить более тесный контакт" :) с синхронизатором и я сделал простой текстовый оконный интерфейс на основе библиотеки curses. Этот вариант интерфейса оказался таким легким и удобным, что я "раздвинул" его (за счет дополнительных опций пользовательского скрипта) до состояния, позволяющего проводить весь процесс ректификации непосредственно в нем. Получилось хорошо. Вариант с GUI я забросил и дальнейшую работу проводил в текстовом оконном режиме. Честно говоря, только в одном случае, при ректификации, захотелось GUI. Точнее даже не самого GUI, а графика скорости отбора и температуры в нижней части колонны. Это - в конце отбора тела, когда старт-стоп начинает отрабатывать часто. График скорости отбора позволяет легко оценивать скважности импульсов "старта", а график температуры - сразу оценивать амплитуду "залетов". Но введение скользящего среднего скорости отбора (см. параметр <q> слева на картинках текстового UI) заметно снизило такие желания.

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

PS2
В приложении к данному топику находится архив, в котором находятся все необходимые для работы модули. В архиве также находятся прошивки для всех модулей. Часть из них корректировалась несколько раз. Часть нет. Не все фиксировалось в изменениях. Поэтому лучше перепрошить все модули свежими прошивками.

Предыдущий топик  Вернуться к оглавлению   Следующий топик
lite_0300.zip 105.6 Кб
U-M Магистр MSK 210 39
Отв.1731  14 Нояб. 19, 17:18
Может в архив добавить актуальные схемы модулей? - Взялся повторно изучать тему, немного путаюсь в аппаратных версиях...
OldBean Доцент Красноярск 1K 1.4K
Отв.1732  14 Нояб. 19, 18:51
Может в архив добавить актуальные схемы модулей? - Взялся повторно изучать тему, немного путаюсь в аппаратных версиях...U-M, 14 Нояб. 19, 17:18
Хорошо. Какой-то порядок, действительно, пора навести. У меня уже тоже не все детали держатся в голове. В ближайшие выходные (ну что бы не "на бегу") отревизирую все железо, соберу все актуальное в кучу (схемы, платы, прошивки). Подумаю как лучше сделать. Да и по малинкиному софту уже появились кое-какие изменения...
OldBean Доцент Красноярск 1K 1.4K
Отв.1733  15 Нояб. 19, 18:25
20.2. Простой датчик скорости отбора

Посмотрел как работает датчик скорости отбора с периодическим сбросом накопленного спирта. Наконец-то "дошли руки" до него. Сама идея датчика "нарисована" на картинке к этому посту. Реальное устройство и результат его работы показан на рисунке ниже.
Ненавязчивая автоматизация ректификационной установки
Ненавязчивая автоматизация ректификационной установки. Автоматика.

Небольшая пробирка с сифоном приклеена к одному концу 100-граммового тензодатчика капелькой суперклея. Сифон предназначен для периодического слива поступающего в пробирку спирта. А тензодатчик - для постоянного взвешивания пробирки вместе с находящимся в ней спиртом. Провода от тензодатчика идут к АЦП HX711. Далее - на ардуинку (Nano), а ардуинка через USB подключена к малинке. Можно сделать через 1-Wire или i2c. Но здесь это не принципиально. Просто USB уже есть в Arduino Nano и ничего дополнительно делать не нужно. Схема подключения и софт подробно описаны здесь.

Источник капель спирта - бюретка. Высота столба в бюретке изменяется. Поэтому скорость поступления капель все время падает. Но сейчас это тоже непринципиально. Главное - такой простой датчик скорости отбора действительно отлично работает. Красные кривые, на верхней диаграмме - вес с 10-секундным окном усреднения, на нижней - скорость отбора (т.е. производная веса по времени). Нарастание зубцов пилы - это этап наполнения пробирки (левый снимок), спад зубцов пилы - это сборос спирта через сифон (правй снимок).

На нижней диаграмме хорошо видно уменьшение "скорости обора" с 550 г/час до 150 г/час по мере опорожнения бюретки. Также хорошо видно, что кривые практически незашумлены. В периоды времени, когда вес пробирки больше 2 граммов и меньше 6 граммов (для данной конфигурации) скорость отбора четко фиксируется. Первый зубец - не считается. Я в это время регулировал кран бюретки. На последний зубец тоже можно не обращать внимания - бюретка опустела.

Вернуться к оглавлению

PS
Показанный на втором рисунке в приложении к топику "сверхбюджетный" :) вариант датчика из 5 мл шприца и силиконовой трубки тоже работает. Но похуже. Смачиваемость пластмассы похуже - в трубке образуется много пузырей.
01_simplest_device.JPG
01_simplest_device.JPG Ненавязчивая автоматизация ректификационной установки. Автоматика.
ZagAl Доцент Прибалтика 1.9K 916
Отв.1734  16 Нояб. 19, 14:54
вариант датчика из 5 мл шприца и силиконовой трубки тоже работает. Но похуже.OldBean, 15 Нояб. 19, 18:25
Такой вариант я  тоже пробовал на 2х мл. шприце, только без тензодатчика. Подтверждаю, что воздушные пустоты, образовывающиеся в трубке мешают стабильной работе устройства. А вот о существовании таких пробирок даже не подумал. Теперь возник вопрос: где такую пробирку можно раздобыть?
OldBean Доцент Красноярск 1K 1.4K
Отв.1735  16 Нояб. 19, 16:59
воздушные пустоты, образовывающиеся в трубке мешают стабильной работе устройства. А вот о существовании таких пробирок даже не подумал. Теперь возник вопрос: где такую пробирку можно раздобыть?ZagAl, 16 Нояб. 19, 14:54
В стеклянной конструкции пузырьки тоже подсасываются. Но, за счет отличной смачиваемости стекла спиртом, они быстро "рассасываются" и практически не мешают измерениям.

Я не встречал такие маленькие пробирки с сифоном в готовом виде. Эту пробирку, по моей просьбе и эскизу, мне спаял наш институтский стеклодув Сергей. За что ему огромное спасибо! Но если заменить стеклянное дно пробирки на силиконовую пробку, то такую конструкцию несложно сделать и самому. Хороший спай без тренировки самому сделать непросто, но вот аккуратно согнуть тонкую стеклянную трубку в пламени горелки под силу любому. Поэтому вариант с силиконовой пробкой - вполне реален для самостоятельного изготовления. Она будет работать не хуже цельностеклянной.
tonik38 Новичок Иркутск 6
Отв.1736  17 Нояб. 19, 09:33
Добрый день всем!
Повторяю конструкцию уважаемого OldBean. Ранее ничего подобного(и программирование и электроника) не делал, оказывается, очень интересное дело)))

Спаял все модули, прошил ардуино. Малинка работает. Скрипты версии 36.
Есть такой вопрос скрипт sens.py читает правильное напряжение сети (230В), на индикаторе тоже 230
Модуль contr.py ругает за низкое напряжение (менее 102в) и отказывается работать.
Скорость шины понизил до 9600.

Не могу разобраться с этим вопросом, прошу подсказать.
Пока не могу вставлять в сообщение фото.

С Уважением, Николай



Добавлено через 2мин.:

Версия прошивки RMS 17.04.10
U-M Магистр MSK 210 39
Отв.1737  17 Нояб. 19, 10:17, через 45 мин
Я не встречал такие маленькие пробирки с сифоном в готовом виде. Эту пробирку, по моей просьбе и эскизу, мне спаял наш институтский стеклодув Сергей.OldBean, 16 Нояб. 19, 16:59

По идее, в ассортименте хим.посуды существует стеклянный экстрактор Сокслета. У него как раз сформирован сифон в нужном месте. Останется только отрезать лишнее, при условии, что его габарит изначально подойдет для переделки?
OldBean Доцент Красноярск 1K 1.4K
Отв.1738  17 Нояб. 19, 17:26
Модуль contr.py ругает за низкое напряжение (менее 102в) и отказывается работать.
...
Не могу разобраться с этим вопросом, прошу подсказать.tonik38, 17 Нояб. 19, 09:33
Николай, попробуйте вариант, который я прикрепил к данному топику (файл 180607_nna38.zip). Это последняя ректификация,которую я проводил при помощи первого варианта системы. Комплект модулей вполне рабочий (лог этой ректификации в виде графиков в этом же архиве). После этого все ректификации я делал уже на новом варианте системы (LITE), а старый (первый) вариант автоматики уже не развивался. Если этот комплект модулей не заработает, то проблема, скорее всего, уже в прошивках ардуинок. Давайте тогда с ними будем разбираться.

существует стеклянный экстрактор СокслетаU-M, 17 Нояб. 19, 10:17
Да. Топология у него подходящая, но размеры обычно великоваты. Даже для небольших рабочих объемов. Снизу шлиф (меньше чем на 29 не встречал), сверху (под холодильник) шлиф еще больше. Обводная трубка для пара. Слишком много отрезать придется. Вариант с силиконовой пробкой выглядит попроще - две трубки, толстая и тонкая. Тонкую дважды согнуть. Отлить по месту силикон. И все.

PS
Кстати, в качестве внешней трубки можно корпус от того же шприца взять. Главное, чтобы сифонная трубка была стеклянной.
пробирка.png
пробирка.png Ненавязчивая автоматизация ректификационной установки. Автоматика.

180607_nna38.zip 345.6 Кб
tonik38 Новичок Иркутск 6
Отв.1739  17 Нояб. 19, 19:23
Установил скрипты 38 версии. Все также осталось. Sens показывает среднеквадратичное 229в, nna_38 выдает 101в