Русский
Русский
English
Статистика
Реклама

Промышленное программирование

Автоматизация и промышленная электроника когда одним Arduino сыт не будешь

19.05.2021 12:19:31 | Автор: admin
Если играться с контроллерами, то почему с маленькими?

Очень часто, когда речь заходит об автоматизации чего-либо, то в разговоре всплывает Arduino, его производные или же Raspberry PI и прочие одноплатники. Но есть отличие от домашних поделок, где можно пользоваться чем угодно ради экономии и потому, что это простое и доступное решение. В сфере автоматизации/модернизации объектов, связанных с промышленностью, речь идёт исключительно о специализированных промышленных контроллерах и системах визуализации, диспетчеризации/удалённого управления и все это исключительно с сертификатами соответствия и лицензиями.
Решений такого класса море и порой сложно в них разобраться. Разумеется, все возможные варианты разобрать невозможно, но мы с коллегами уже несколько лет работаем в этой сфере и потому какое-то количество опыта набралось. Мы поделимся своим и если вам есть, что сказать просим писать комментарии.

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

Цель выполнить задание и не потерять заложенную прибыль. А вот как это получается и получается ли вообще это вопрос опыта.

Самая маленькая команда должна включать в себя:

  • проектанта
  • опытного инженера в области промышленной электроники
  • электромонтеров/электромехаников
  • программиста

Если же компания постоянно развивается и работает в этой области, то она неуклонно растёт, так как несколько объектов делается одновременно и все они находятся на разной стадии.

Стадия подготовки к тендеру


О начале тендера, на выполнение чего-то, мы не всегда узнаем заранее. Иногда информация запаздывает, тендер ожидается на днях, или долго шли подготовительные работы, чтобы принять участие. Но это не меняет сути: нужно посчитать стоимость автоматизации объекта, к примеру. Сначала изучается планируемый объем и срок работ (если, конечно, сроки внятно прописаны) и принимается решение о выполнимости своими силами, либо с привлечением подрядчиков.
Часто в нагрузку к системам автоматики докидывают смежные (в понимании заказчика/инвестора) вещи. Нам постоянно навязывают обычную электрику, вплоть до внешнего освещения, локальные сети и телефонные линии в промышленных и административных помещениях, электрические сети низкого напряжения и понижающие трансформаторные подстанции. И вот хотелось бы иметь только автоматику в чистом виде, да нет её. Так как для инвесторов, что электроника, что электрика одного поля ягоды. Попутно нужно понимать, что компания, занимающаяся автоматизацией, заканчивает объект последней. Именно она будет связывать воедино разрозненные структуры пожарные системы, контроли доступа, видео наблюдение, связь с серверной и оптическими линиями, диспетчеризацию и удалённый доступ. Если же технологический процесс объекта содержит в себе независимые самоуправляемые системы или машины (автоматические линии, вентиляционные централи, сигнализацию, запасной генератор, солнечные панели и преобразователи к ним и т.д.), то задача усложняется ещё и необходимостью соединить между собой оборудование разных производителей, порой с совсем разными протоколами промышленной связи.
Вооружившись знаниями за все предыдущие годы и выполненные объекты, можно попробовать посчитать общую себестоимость работ (включая материалы, покупку оборудования, количество человеко-часов, стоимость всех выездов на объект для проведения монтажа, пуско-наладки и последующих сервисных выездов в течении гарантийных 5-7 лет).
Q: На основании чего производится расчёт?
Пожеланий инвестора и приблизительных догадок об общем бюджете объекта. Всегда можно воспользоваться открытой информацией. Оттуда можно выудить сколько процентов от общей суммы пойдёт на данную сферу. Грубо говоря, если объект будет стоить 10 млн евро, то системы автоматизации плюс электрика это около 1,5-2 млн.
Хорошо, когда есть возможность пообщаться с генеральным подрядчиком, который координирует весь ход работ, от выравнивания территории под фундаменты и дороги, до капитального строительства и озеленения территории. Всегда есть кто-то, кто пытается, не выходя за рамки первичного бюджета, построить объект и сдать его срок. Не завидуем им совсем, но пытаемся сотрудничать на всех этапах реализации. Это действительно упрощает жизнь в дальнейшем.
Итак, получаем информацию от генерального подрядчика, что общая квота от этого бюджета на автоматику и электрику не сможет превысить тех же 2 млн. Теперь начинается самое интересное в тендере участвует минимум 3 компании на каждую сферу работ. А значит мы встретимся ещё, как минимум, с двумя предложениями от других компаний, что претендуют выполнить этот же объем работ, что и мы. Нужно предложить на тендере сумму меньше них (а мы, разумеется не знаем сколько они заявят), ну и не остаться без прибыли, тем более не уйти в минус.
Q: Что мы используем и почему?
Зависит от возможностей оборудования и пожеланий заказчика. Из контроллеров это Siemens s1200-s1500 и его аналоги. Панели к ним Weintek и Astraada. Реле, пускатели, клеммные колодки Eaton и т.д. Частотных преобразователи Danfoss, Mitsubishi, LS IS. Коммуникация Modbus RTU, Modbus TCP, Ethernet, Profibus и оптика. Подробнее о подборе оборудования будет в следующей статье относительно проектирования.

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

Сначала изучается ТЗ.Как известно без внятного ТЗ получается непонятно что. Инвестор не будет пояснять расплывчатые формулировки и нужно готовиться ко всему. Подводные камни ждут в каждом предложении и нужно уметь их находить. К примеру, если пишется, что необходимо выполнить систему автоматизации и удалённого управления с возможностью работы при кратковременных пропаданиях питания от сети, то это означает, что всего лишь необходимо доложить по одному блоку бесперебойного питания либо аккумуляторы в каждых шкаф управления техпроцессом и обычный UPS, через который запитана серверная стойка или компьютер диспетчера с системой управления. Задача поддержать работу всех контролёров, во всех шкафах управления и всех роутеров и модемов, чтобы собрать все статусы ошибок различных устройств, автоматических линий и машин, записать все это в логи и передать на сервер/компьютер с системой удалённого управления, который через SMS-шлюз сообщит наружу подготовленному списку номеров, что пропало напряжение сети и все оборудование остановилось.
Как можно понять из требований, это достаточно недорогое удовольствие обойдётся в несколько сотен евро, и его легко учесть на стадии проектирования. Однако формулировка может быть несколько иной выполнить систему автоматизации с возможностью продолжения работы объекта при пропадании питания от сети, до момента её появления. И вот это уже совсем другое. Здесь хотят иметь либо вторую линию резервного питания со своим понижающим трансформатором и шкаф автоматического переключения нагрузки с одного источника питания на другой с анализаторами параметров сети. Либо, что встречается гораздо чаще заказчик хочет иметь резервный источник питания в виде дизельного генератора, который сможет поддержать работу объекта в течении нескольких часов. Подобные генераторы стоят десятки тысяч евро. И не забываем про шкаф переключения нагрузки, благо в большинстве случаев современные дизельные генераторы имеют свои неплохие контроллеры с внешним LCD экраном и возможностью отслеживания параметров сети сразу по нескольким параметрам и управлением силовыми контакторами, что переключают нагрузку от одного источника (сети) на другой (генератор) и наоборот при восстановлении нормальной работы первичного источника.
К примеру, генераторы фирмы FOGO, Elem Power или их аналоги, с контроллером, к примеру, datacom-d500-lite или inteli lite amf 25. Тут шкаф переключения нагрузки (AVR) будет состоять всего из 2х силовых контакторов с дополнительными контактами, чтобы снимать состояние, какой из них включён и сделать их одновременно включение взаимоисключающим.

Фото панели управления генератором

После прочтения ТЗ все обычные работы:прокладка кабельных трасс, монтаж кабельных лотков, монтаж наружного освещения, заземления, громоотвода и молниезащиты (если есть в требованиях), изготовление шкафов управления (их мы делаем сами), всё калькулируется по нормам. В нашем случае эти нормы европейские. Потом прикидывается общая стоимость всех возможных шкафов управления с их наполнением в виде контроллеров и их обвязки, частотных преобразователей или устройств плавного пуска, серверных стоек, компьютеров и телевизоров (это относительная новинка, так как стали отказываться от синоптических таблиц и предпочитают иметь выведенную систему визуализации техпроцесса на 4-6 телевизоров по 50-55", склеенных в мульти-экран), считается за сколько рабоче-часов будет изготовлено, смонтировано. Сюда же добавляется прибыль. Есть несколько быстрых шаблонов по подсчёту стоимости к примеру, в конечную стоимость одного шкафа управления, включена оплата работы проектанта, стоимость сборки шкафа и стоимость программирования контроллера и панели HMI. Чтобы быстро считать многие вещи упрощаются. Итоговая стоимость шкафа управления будет = себестоимость всех компонентов * 1,5(2). Разумеется, сам по себе шкаф ничего не делает. Его ещё нужно привезти на объект, смонтировать на своём месте, проложить к нему кабельные линии и трассы, уложить питающие и управляющие кабеля, подключить это все и запустить. Приблизительно можно подсчитать общую длину будущих кабельных линий, учесть проколы под дорогами (это дорого), посмотреть требования к силовым линиям (медные или алюминиевые), подумать на счёт трансформаторной подстанции и генератора. Многие вещи сразу умножаются на 2, чтобы избежать нехватки средств в будущем. К примеру, тот же генератор весит 3-5 тонн. Значит нужно организовать выгрузку с помощью крана и прочный фундамент, соответственно ещё и заземление. Внимательно изучаются расположения разных технологических зданий внутри объекта. По современным нормам уже никто не хочет связывать отдалённые здания витой парой хоть 5й, хоть 6й категории. Только оптика, только хардкор. Считаем и стоимость укладки, сварки оптики и оборудования для соединения между собой разных зданий. Внутри зданий и на малых расстояниях, для экономии, используем utp категории 5е/6e.

Готовый шкаф управления. Запуск ещё идёт, но большая часть техпроцесса уже налажена

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

Фото запущенного объекта на память)

Вот, пожалуй, и все, что можно в нескольких словах сказать о подготовке к тендеру и подводных камнях на этом этапе. Большие объекты, вроде Amazon, Volkswagen, АЭС (да, в нашей практике и такое было) строятся не быстро, но и бюджеты отнюдь не маленькие.
В одной статье описать все стадии автоматизации невозможно, далее планирую затронуть стадии проектирования, программирования и запуска оборудования. Пожалуй, больше всего интересного происходит во время запуска. Именно там встречается масса проблем и находятся решения.


Подробнее..

Продвинутые дженерики в TypeScript. Доклад Яндекса

03.05.2021 12:05:25 | Автор: admin
Дженерики, или параметризованные типы, позволяют писать более гибкие функции и интерфейсы. Чтобы зайти дальше, чем параметризация одним типом, нужно понять лишь несколько общих принципов составления дженериков и TypeScript раскроется перед вами, как шкатулка с секретом. AlexandrNikolaichev объяснил, как не бояться вкладывать дженерики друг в друга и использовать автоматический вывод типов в ваших проектах.

Всем привет, меня зовут Александр Николаичев. Я работаю в Yandex.Cloud фронтенд-разработчиком, занимаюсь внутренней инфраструктурой Яндекса. Сегодня расскажу об очень полезной вещи, без которой сложно представить современное приложение, особенно большого масштаба. Это TypeScript, типизация, более узкая тема дженерики, и то, почему они нужны.

Сначала ответим на вопрос, почему TypeScript и при чем тут инфраструктура. У нас главное свойство инфраструктуры ее надежность. Как это можно обеспечить? В первую очередь можно тестировать.


У нас есть юнит- и интеграционные тесты. Тестирование нужная стандартная практика.

Еще нужно использовать ревью кода. В довесок сбор ошибок. Если все-таки ошибка случилась, то специальный механизм ее отсылает, и мы можем оперативно что-то пофиксить.

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

Синтаксис


Чтобы провести базовый ликбез, сначала рассмотрим основы синтаксиса.

Дженерик в TypeScript это тип, который зависит от другого типа.

У нас есть простой тип, Page. Мы его параметризуем неким параметром <T>, записывается через угловые скобки. И мы видим, что есть какие-то строки, числа, а вот <T> у нас вариативный.

Кроме интерфейсов и типов мы можем тот же синтаксис применять и для функций. То есть тот же параметр <T> пробрасывается в аргумент функции, и в ответе мы переиспользуем тот же самый интерфейс, туда его тоже пробросим.

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

Для классов существует похожий синтаксис. Прокидываем параметр в приватные поля, и у нас есть некий геттер. Но там мы тип не записываем. Почему? Потому что TypeScript умеет выводить тип. Это очень полезная его фишка, и мы ее применим.

Посмотрим, что происходит при использовании этого класса. Мы создаем инстанс, и вместо нашего параметра <T> передаем один из элементов перечисления. Создаем перечисление русский, английский язык. TypeScript понимает, что мы передали элемент из перечисления, и выводит тип lang.

Но посмотрим, как работает вывод типа. Если мы вместо элементов перечисления передадим константу из этого перечисления, то TypeScript понимает, что это не всё перечисление, не все его элементы. И будет уже конкретное значение типа, то есть lang en, английский язык.

Если мы передаем что-то другое, допустим, строку, то, казалось бы, она имеет то же самое значение, что и у перечисления. Но это уже строка, другой тип в TypeScript, и мы его получим. И если мы передаем строку как константу, то вместо строки и будет константа, строковый литерал, это не все строки. В нашем случае будет конкретная строка en.

Теперь посмотрим, как можно это расширить.

У нас был один параметр. Ничто нам не мешает использовать несколько параметров. Все они записываются через запятую. В тех же угловых скобках, и мы их применяем по порядку от первого к третьему. Мы подставляем нужные значения при вызове.

Допустим, объединение числовых литералов, некий стандартный тип, объединение строковых литералов. Все они просто записываются по порядку.

Посмотрим, как это происходит в функциях. Мы создаем функцию random. Она рандомно дает либо первый аргумент, либо второй.

Первый аргумент типа A, второй типа B. Соответственно, возвращается их объединение: либо тот, либо этот. В первую очередь мы можем явно типизировать функцию. Мы указываем, что A это строка, B число. TypeScript посмотрит, что мы явно указали, и выведет тип.

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

Предположим, мы передаем аргумент, строковый литерал, и он должен соответствовать типу A, а второй аргумент, единичка, типу B. Минимально возможные для строкового литерала и единички литерал A и та же самая единичка. Нам TypeScript это и выведет. Получается такое сужение типов.

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

Отношение типов


Типы можно условно рассматривать как некие множества. Посмотрим на диаграммку, где показан кусок всего множества типов.

Мы видим, что типы в нем связаны некими отношениями. Но какими? Это отношения частичного порядка значит, для типа всегда указан его супертип, то есть тип выше его, который охватывает все возможные значения.

Если идти в обратную сторону, то у каждого типа может быть подтип, меньше его.

Какие супертипы у строки? Любые объединения, которые включают строку. Строка с числом, строка с массивом чисел, с чем угодно. Подтипы это все строковые литералы: a, b, c, или ac, или ab.

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

И в этом порядке есть тип, как бы самый верхний, unknown. И самый нижний, аналог пустого множества, never. Never подтип любого типа. А unknown супертип любого типа.

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

Посмотрим, что нам даст знание этого порядка.

Мы можем ограничивать параметры их супертипами. Ключевое слово extends. Мы определим тип, дженерик, у которого будет всего один параметр. Но мы скажем, что он может быть только подтипом строки либо самой строкой. Числа мы передавать не сможем, это вызовет ошибку типа. Если мы явно типизируем функцию, то в параметрах можем указать только подтипы строки или строку apple и orange. Обе строки это объединение строковых литералов. Проверка прошла.


Еще мы можем сами автоматически выводить типы, исходя из аргументов. Если мы передали строку литерал, то это тоже строка. Проверка сработала.

Посмотрим, как расширить эти ограничения.

Мы ограничились просто строкой. Но строка слишком простой тип. Хотелось бы работать с ключами объектов. Чтобы с ними работать, мы сначала поймем, как устроены сами ключи объектов и их типы.

У нас есть некий объектик. У него какие-то поля: строки, числа, булевы значения и ключи по именам. Чтобы получить ключи, используем ключевое слово keyof. Получаем объединение всех имен ключей.

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

Если мы хотим получить часть, то мы можем так и указать не все ключи, а какое-то подмножество. Мы ожидаемо получим лишь те поля, которым соответствуют указанным ключам. Если совсем всё редуцировать до одинарного случая, это одно поле, и один ключ дает одно значение. Так можно получать соответствующее поле.

Посмотрим, как использовать ключи объекта.

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

Посмотрим, как это работает с keyof. Мы определили тип CustomPick. На самом деле это почти полная копия библиотечного типа Pick из TypeScript. Что он делает?

У него есть два параметра. Второй это не просто какой-то параметр. Он должен быть ключами первого. Мы видим, что у нас он расширяет keyof от <T>. Значит, это должно быть какое-то подмножество ключей.

Далее мы для каждого ключа K из этого подмножества бегаем по объекту, кладем то же самое значение и специально убираем синтаксисом опциональность, минус знак вопроса. То есть все поля будут обязательными.

Смотрим на применение. У нас есть объект, в нем имена полей. Мы можем брать только их подмножество a, b или c, либо все сразу. Мы взяли a или c. Выводятся только соответствующие значения, но мы видим, что поле a стало обязательным, потому что мы, условно говоря, убрали знак вопроса. Мы определили такой тип, использовали его. Никто нам не мешает взять этот дженерик и засунуть его в еще один дженерик.

Как это происходит? Мы определили еще один тип Custom. Второй параметр расширяет не keyof, а результат применения дженерика, который мы привели справа. Как это работает, что мы вообще в него передаем?

Мы в этот дженерик передаем любой объект и все его ключи. Значит, на выходе будет копия объекта со всеми обязательными полями. Эту цепочку вкладывания дженерика в другой дженерик и так далее можно продолжать до бесконечности, в зависимости от задач, и структурировать код. Выносить в дженерики переиспользуемые конструкции и так далее.

Указанные аргументы не обязательно должны идти по порядку. Вроде как параметр P расширяет ключи T в дженерике CustomPick. Но никто нам не мешал указать его первым параметром, а T вторым. TypeScript не идет последовательно по параметрам. Он смотрит все параметры, что мы указали. Потом решает некую систему уравнений, и если он находит решение типов, которые удовлетворяют этой системе, то проверка типов прошла.

В связи с этим можно вывести такой забавный дженерик, у которого параметры расширяют ключи друг друга: a это ключи b, b ключи a. Казалось бы, как такое может быть, ключи ключей? Но мы знаем, что строки TypeScript это на самом деле строки JavaScript, а у JavaScript-строк есть свои методы. Соответственно, подойдет любое имя метода строки. Потому что имя у метода строки это тоже строка. И у нее оттуда есть свое имя.

Соответственно, мы можем получить такое ограничение, и система уравнений разрешится, если мы укажем нужные типы.

Посмотрим, как это можно использовать в реальности. Используем для API. Есть сайт, на котором деплоятся приложения Яндекса. Мы хотим вывести проект и сервис, который ему соответствует.

В примере я взял проект для запуска виртуальных машин qyp для разработчиков. Мы знаем, что у нас в бэкенде есть структура этого объекта, берем его из базы. Но помимо проекта есть и другие объекты: черновики, ресурсы. И у всех есть свои структуры.

Более того, мы хотим запрашивать не весь объект, а пару полей имя и имя сервиса. Такая возможность есть, бэкенд позволяет передавать пути и получить неполную структуру. Здесь описан DeepPartial. Мы его чуть попозже научимся конструировать. Но это значит, что передается не весь объект, а какая-то его часть.

Мы хотим написать некоторую функцию, которая бы запрашивала эти объекты. Напишем на JS. Но если присмотреться, видны опечатки. В типе какой-то Projeact, в путях тоже опечатка в сервисе. Нехорошо, ошибка будет в рантайме.

Вариант TS, казалось бы, не сильно отличается, кроме путей. Но мы покажем, что на самом деле в поле Type не может быть других значений, кроме тех, что есть у нас на бэкенде.

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

Посмотрим, как получить функцию.


У нас есть функция, ее использование. Есть вот эта структура. Сначала мы хотим получить все имена. Мы записываем такой тип, где имя соответствует структуре.

Допустим, для проекта мы где-то описываем его тип. В нашем проекте мы генерируем тайпинги из protobuf-файлов, которые доступны в общем репозитории. Далее мы смотрим, что у нас есть все используемые типы: Project, Draft, Resource.

Посмотрим на реализацию. Разберем по порядку.

Есть функция. Сначала смотрим, чем она параметризуется. Как раз этими уже ранее описанными именами. Посмотрим, что она возвращает. Она возвращает значения. Почему это так? Мы использовали синтаксис квадратных скобок. Но так как мы передаем в тип одну строку, объединение строковых литералов при использовании это всегда одна строка. Невозможно составить строку, которая одновременно была бы и проектом, и ресурсом. Она всегда одна, и значение тоже одно.

Обернем все в DeepPartial. Необязательный тип, необязательная структура. Самое интересное это параметры. Задаем их с помощью еще одного дженерика.

Тип, которым параметризуется дженерик параметров, также совпадает с ограничением на функцию. Он может принимать только тип имени Project, Resource, Draft. ID это, конечно, строка, она нам не интересна. Вот тип, который мы указали, один из трех. Интересно, как устроена функция для путей. Это еще один дженерик почему бы нам его не переиспользовать. На самом деле все, что он делает, просто создает функцию, которая возвращает массив из any, потому что в нашем объекте могут быть поля любых типов, мы не знаем каких. В такой реализации мы получаем контроль над типами.

Если кому-то это показалось простым, перейдем к управляющим конструкциям.

Управляющие конструкции


Мы рассмотрим всего две конструкции, но их хватит, чтобы покрывать практически все задачи, которые нам нужны.

Что такое условные типы? Они очень напоминают тернарки в JavaScript, только для типов. У нас есть условие, что тип a это подтип b. Если это так, то возврати c. Если это не так возврати d. То есть это обычный if, только для типов.

Смотрим, как это работает. Мы определим тип CustomExclude, который по сути копирует библиотечный Exclude. Он просто выкидывает нужные нам элементы из объединения типов. Если a это подтип b, то возврати пустоту, иначе возврати a. Это странно, если посмотреть, почему это работает с объединениями.

Нам пригодится специальный закон, который говорит: если есть объединение и мы проверяем с помощью extends условия, то мы проверяем каждый элемент отдельно и потом снова их объединяем. Это такой транзитивный закон, только для условных типов.

Когда мы применяем CustomExclude, то смотрим поочередно на каждый элемент наблюдения. a расширяет a, a это подтип, но верни пустоту; b это подтип a? Нет верни b. c это тоже не подтип a, верни c. Потом мы объединяем то, что осталось, все плюсики, получаем b и c. Мы выкинули a и добились того, что хотели.

Ту же технику можно использовать, чтобы получить все ключи кортежа. Мы знаем, что кортеж это тот же массив. То есть у него есть JS-методы, но нам это не нужно, а нужны только индексы. Соответственно, мы просто из всех ключей кортежа выкидываем имена всех методов и получаем только индексы.

Как нам определить наш ранее упомянутый тип DeepPartial? Тут впервые используется рекурсия. Мы пробегаемся по всем ключам объекта и смотрим. Значение это объект? Если да, применяем рекурсивно. Если нет и это строка или число оставляем и все поля делаем опциональными. Это все-таки Partial-тип.

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

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

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

Допустим, мы взяли ресурсы CPU, ядра. Всем нужны ядра. Я упростил пример, и тут всего лишь ресурсы, только ядра, и они представляют собой числа.

Мы хотим сделать функцию, которая их патчит, патчит значения. Добавить ядер или убавить. В том же JavaScript, как вы могли догадаться, возникают опечатки. Здесь же мы складываем число со строкой не очень хорошо.

В TypeScript почти ничего не изменилось, но на самом деле этот контроль еще на уровне IDE вам подскажет, что вы не можете передать ничего кроме этой строки или конкретного числа.

Посмотрим, как этого добиться. Нам надо получить такую функцию, и мы знаем, что у нас есть вот такого вида объект. Нужно понять, что мы патчим только число и поля. То есть надо получить имя только тех полей, где есть числа. У нас всего одно поле, и оно является числом.

Посмотрим, как это реализовывается в TypeScript.

Мы определили функцию. У нее как раз три аргумента, сам объект, который мы патчим, и имя поля. Но это не просто имя поля. Оно может быть только именем числовых полей. Мы сейчас узнаем, как это делается. И сам патчер, который представляет собой чистую функцию.

Есть некая обезличенная функция, патч. Нам интересна не ее реализация, а то, как получить такой интересный тип, получить ключи не только числовых, а любых полей по условию. Здесь у нас содержатся числа.

Вновь разберем по порядку, как это происходит.

Мы пробегаемся по всем ключам переданного объекта, потом делаем вот такую процедуру. Смотрим, что поле объекта это подтип нужного, то есть числовое поле. Если да, то важно, что мы записываем не значение поля, а имя поля, а иначе вообще, пустоту, never.

Но потом получился такой странный объект. Все числовые поля стали иметь свои имена в качестве значений, а все не числовые поля пустоту. Дальше мы берем все значения этого странного объекта.

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

В примере показано: есть простой объект, поле единичка. Это число? Да. Поле число, это число? Да. Последняя строка не число. Получаем только нужные, числовые поля.

С этим разобрались. Самый сложный я оставил напоследок. Это вывод типа Infer. Захват типа в условной конструкции.


Он неотделим от предыдущей темы, потому что работает только с условной конструкцией.

Как это выглядит? Допустим, мы хотим знать элементы массива. Пришел некий тип массива, нам бы хотелось узнать конкретный элемент. Мы смотрим: нам пришел какой-то массив. Это подтип массива из переменной x. Если да верни этот x, элемент массива. Если нет верни пустоту.

В этом условии вторая ветка никогда не будет выполнена, потому что мы параметризовали тип любым массивом. Конечно, это будет массив чего-то, потому что массив из any не может не иметь элементов.

Если мы передаем массив строк, то нам ожидаемо возвратится строка. И важно понимать, что у нас определяется не просто тип. Из массива строк визуально понятно: там строки. А вот с кортежем все не так просто. Нам важно знать, что определяется минимально возможный супертип. Понятно, что все массивы как бы являются подтипами массива с any или с unknown. Нам это знание ничего не дает. Нам важно знать минимально возможное.

Предположим, мы передаем кортеж. На самом деле кортежи это тоже массивы, но как нам сказать, что за элементы у этого массива? Если есть кортеж из строки числа, то на самом деле это массив. Но элемент должен иметь один тип. А если там есть и строка, и число значит, будет объединение.

TypeScript это и выведет, и мы получим для такого примера именно объединение строки и числа.

Можно использовать не только захват в одном месте, но и сколько угодно переменных. Допустим, мы определим тип, который просто меняет элементы кортежа местами: первый со вторым. Мы захватываем первый элемент, второй и меняем их местами.

Но на самом деле не рекомендуется слишком с этим заигрываться. Обычно для 90% задач хватает захвата всего лишь одного типа.


Посмотрим пример. Задача: нужно показать в зависимости от состояния запроса либо хороший вариант, либо плохой. Тут представлены скриншоты из нашего сервиса для деплоймента приложений. Некая сущность, ReplicaSet. Если запрос с бэкенда вернул ошибку, надо ее отрисовать. При этом есть API для бэкенда. Посмотрим, при чем тут Infer.

Мы знаем, что используем, во-первых, redux, а, во-вторых, redux thunk. И нам надо преобразовать библиотечный thunk, чтобы получить такую возможность. У нас есть плохой путь и хороший.

И мы знаем, что хороший путь в extraReducers в redux toolkit выглядит так. Знаем, что есть PayLoad, и хотим вытащить кастомные типы, которые нам пришли с бэкенда, но не только, а плюс еще информация про хороший или плохой запрос: есть там ошибка или нет. Нам нужен дженерик для этого вывода.

Про JavaScript я не привожу сравнение, потому что оно не имеет смысла. В JavaScript в принципе нельзя никак контролировать типы и полагаться только на память. Здесь нет плохого варианта, потому что его просто нет.

Мы знаем, что хотим получить этот тип. Но у нас же не просто так появляется action. Нам нужно вызвать dispatch с этим action. И нам нужен вот такой вид, где по ключу запроса нужно отображать ошибку. То есть нужно поверх redux thunk примешивать такую дополнительную функциональность с помощью метода withRequestKey.

У нас, конечно, есть этот метод, но у нас есть и исходный метод API getReplicaSet. Он где-то записан и нам надо оверрайдить redux thunk с помощью некоего адаптера. Посмотрим, как это сделать.

Надо получить функцию вот такого вида. Это thunk с такой дополнительной функциональностью. Звучит страшно, но не пугайтесь, сейчас разберем по полочкам, чтобы было понятно.

Есть адаптер, который расширяет исходный библиотечный тип. Мы к этому библиотечному типу просто примешиваем дополнительный метод withRequestKey и кастомный вызов. Посмотрим, в чем основная фишка дженерика, какие параметры используются.

Первое это просто наш API, объектик с методами. Мы можем делать getReplicaSet, получать проекты, ресурсы, неважно. Мы в текущем методе используем конкретный метод, а второй параметр это просто имя метода. Далее мы используем параметры функции, которую запрашиваем, используем библиотечный тип Parameters, это TypeScript-тип. И аналогично для ответа с бэкенда мы используем библиотечный тип ReturnType. Это для того, что вернула функция.

Дальше мы просто прокидываем свой кастомный вывод в AsyncThunk-тип, который нам предоставила библиотека. Но что это за вывод? Это еще один дженерик. На самом деле он выглядит просто. Мы сохраняем не только ответ с сервера, но и наши параметры, то, что мы передали. Просто чтобы в Reducer за ними следить. Дальше мы смотрим withRequestKey. Наш метод просто добавляет ключ. Что он возвращает? Тот же адаптер, потому что мы можем его переиспользовать. Мы вообще не обязаны писать withRequestKey. Это просто дополнительная функциональность. Она оборачивает и рекурсивно нам возвращает тот же самый адаптер, и мы прокидываем туда то же самое.

Наконец, посмотрим, как выводить в Reducer то, что нам этот thunk вернул.


У нас есть этот адаптер. Главное помнить, что там четыре параметра: API, метод API, параметры (вход) и выход. Нам надо получить выход. Но мы помним, что выход у нас кастомный: и ответ сервера, и параметр запроса.

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

TypeScript основывается на структурной типизации. Он эту структуру разбирает и понимает, что вход находится здесь, на третьем месте, а выход на четвертом. И мы возвращаем нужные типы.

Так мы добились вывода типов, у нас есть доступ к ним уже в самом Reducer. В JavaScript сделать такое в принципе невозможно.
Подробнее..

Микрофронтенды и виджеты в 2021-м. Доклад Яндекса

07.05.2021 12:20:51 | Автор: admin
Давайте поговорим о микрофронтендах и о встраиваемых виджетах, которые, по сути, были предшественниками концепции микрофронтендов. В докладе я рассказал о способах встраивать виджеты на страницу, об их плюсах и минусах с точки зрения изоляции и производительности кода, а также о способах применять виджеты в микрофронтендной архитектуре.

Всем привет! Меня зовут Леша. Я хочу с вами сегодня обсудить немного перехайпленную тему микрофронтенды.

Если кто-то не знаком с этой концепцией, она берет свое начало от микросервисов. Микросервисы это когда мы наш большой монолитный бэкенд делим на маленькие кусочки. Каждый кусочек независимо разрабатывается и деплоится собственной командой разработчиков.

Микрофронтенды дальнейшее развитие этой идеи. Мы наше монолитное фронтенд-приложение также разделяем на независимые куски, независимые сервисы. Каждый сервис делается выделенной командой. Это может быть команда, у которой собственный фронтенд и бэкенд.

Идея микросервисов и микрофронтендов не то чтобы очень новая. В Яндексе когда-то давно был проект Яндекс.Виджеты: можно было из библиотеки виджетов собрать себе персонализированную главную страничку Яндекса. В библиотечке были виджеты, которые делались как нами, так и сторонними компаниями.

Мне нравится думать, что микрофронтенды это такие виджеты, которые перепридумали зумеры. Разница на самом деле не очень большая. Виджет это выделенный кусочек интерфейса, который куда-то встраивается. А идея микрофронтендов в том, что мы всё наше приложение собираем из таких небольших виджетов.

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

Я за время работы в компании тоже успел поработать над несколькими проектами, которые в целом находятся в концепции микрофронтендов. (...) Cейчас я занимаюсь разработкой видеоплеера, который вы уже видели на слайде. Это виджет, который воспроизводит видео и трансляции.

Евангелистом идеи микрофронтендов я совершенно не являюсь. У этой идеи, как и у всех, есть свои плюсы и минусы. Давайте мы с вами о них поговорим.

Какие есть бонусы, которые микрофронтенды дают вашему приложению? У вас появляется разделение ответственности. Каждый виджет в микрофронтендах разрабатывается выделенной командой, она за него отвечает. Виджеты изолированы друг от друга, у них независимые релизные циклы и независимые мониторинги. У каждой команды, которая занимается выделенным виджетом, есть свобода в технологическом стеке, они не зависят друг от друга.

Какие у этой концепции проблемы? Она сильно усложняет код, дает дополнительные накладные расходы на интеграцию и взаимодействие виджетов в микрофронтендах между собой, накладывает требования по обратной совместимости чтобы API, через который виджеты общаются, при релизах не ломался и ваше приложение не развалилось. Свобода в технологическом стеке наверное, тоже своего рода минус. Думаю, вам не очень хотелось бы пользоваться сайтом, который для отрисовки одного кусочка грузит Angular, а для другого React, это будет работать не слишком быстро. Так что свобода это одновременно и плюс, и минус.

Зачем вам эту концепцию использовать? Я для себя на этот вопрос ответил двумя пунктами. Первый: у вас большое приложение и несколько независимых команд разработки.

Второй: вы делаете встраиваемые виджеты, которые работают не только в микрофронтенде, а еще встраиваются на ваши сайты или, например, на сайты партнеров.

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

Как технически реализовать микрофронтенды/виджеты?


declare const DoggyWidget: {    init: ({        container: HTMLElement,    }) => DoggyWidgetInstance;}declare interface DoggyWidgetInstance {    destroy(): void;    updateDoggy(): void;}

В качестве примера возьмем вот такой простенький виджет DoggyWidget, он лежит по ссылке на GitHub. Виджет рисует картинку и кнопочку. Картинка принимает размеры контейнера, куда вы виджет вставили, и показывает рандомную фотографию собаки. Кнопка при нажатии меняет эту фотографию на другую рандомную. У нашего виджета будет API, с помощью которого с ним можно будет как-то взаимодействовать.

Из чего он будет состоять? В первую очередь он будет декларировать глобальный namespace DoggyWidget, в котором будет фабрика и с помощью которого можно создать инстанс этого виджета. У инстанса будет два метода. Первый метод destroy, который при вызове удалит виджет со страницы и почистит всё, что он успел сделать с DOM-ом. Второй метод updateDoggy, который делает то же самое, что нажатие на кнопку, а именно меняет картинку.

Давайте подумаем, как такой виджет реализовать.

<script>


Первая идея в лоб: наш виджет будет отдельным скриптом.

class Widget {    constructor({ container }) {        this.container = container;        container.classList.add('doggy-widget');        this._renderImg();        this._renderBtn();        this.updateDoggy();    } }

Давайте инстанс виджета определим с помощью класса. У класса будет конструктор, который принимает конфиг. В конфиге есть контейнер, где виджет должен рисоваться. Мы на этот контейнер навесим className, вызовем два метода для отрисовки картинки и для отрисовки кнопки и вызовем updateDoggy, который поставит начальную картинку при инициализации виджета.

    _renderImg() {        this.img = document.createElement('img');        this.img.classList.add('doggy-widget__img');        this.img.alt = 'doggy';        this.container.appendChild(this.img);    }

Что будет делать renderImg? Он будет создавать тег img, навешивать на него className и аппендить его в контейнер.

    _renderBtn() {        this.btn = document.createElement('button');        this.btn.classList.add('doggy-widget__btn');        this.btn.addEventListener('click', () => this.updateDoggy());        this.container.appendChild(this.btn);        this.btn.innerText = 'New doggy!';    } 

renderBtn будет делать примерно то же самое, только он будет создавать не img, а кнопочку.

    updateDoggy() {        const { width, height } = this.container.getBoundingClientRect();        const src = `https://placedog.net/${width - 10}/${height - 10}?random=${Math.random()}`;        this.img.src = src;    }

И у нас еще есть публичный API. updateDoggy определяет параметры контейнера, куда мы вставили виджет, конструирует ссылку на изображение. Я здесь буду использовать сервис placedog.net, который подставляет рандомные плейсхолдеры с фотками собак. Метод src ставит тег img.

    destroy() {        this.container.innerHTML = '';        this.container.classList.remove('doggy-widget');    }

destroy будет очень простой он будет подчищать innerHTML у контейнера и снимать с него className, который мы поставили в конструкторе.

(() => {    class Widget {        ...    }    window.DoggyWidget = {        init(config) {            return new Widget(config);        }    }})();

Напишем код, с помощью которого виджет будет вставляться. Мы его содержимое обернем в IIFE, чтобы спрятать класс виджета в замыкание, и определим в нем глобальный namespace DoggyWidget, в namespace будет функция init фабрика, которая вернет нам инстанс виджета.

<script src="doggy-widget.js"></script><link rel="stylesheet" href="doggy-widget.css"><div id="widget-1"></div><div id="widget-2"></div><script>    const widget1 = DoggyWidget.init({         container: document.getElementById('widget-1'),    });    const widget2 = DoggyWidget.init({         container: document.getElementById('widget-2'),    });</script>

Как это все будет ставиться на страничку? Вот два файла: doggy-widget.js с JS-кодом, который мы разобрали, и doggy-wodget.css со стилями для виджета.

Мы заведем два div, и в каждый из них вставим виджет через DoggyWidget.init(), который мы тоже в doggy-widget.js описали.

Ссылка со слайда

Это все будет выглядеть так. У первого виджета будет updateDoggy.

Ссылка со слайда

Мы его вызовем. Он изменит нам фотографию.

Вспомним, какие бонусы нам обещают микрофронтенды. Первый бонус изоляция виджетов.

Ссылка со слайда

        * {            font-family:         Arial, Helvetica, sans-serif !important;            font-size: 10px !important;        }

Представим, что мы наш виджет встроили на страничку, где находится вот такой CSS-код.

Ссылка со слайда

Что произойдет, когда мы отрисуем виджет? Очевидно, у него поедет верстка, потому что у нас есть глобальный CSS selector, который для всех элементов переопределяет font-family и font-size. Так что виджет не очень хорошо изолирован от окружающего его CSS-кода.

Вы скажете, что это вредительство и такого CSS никто не пишет.


Ссылка со слайда

<link rel="stylesheet"       href="bootstrap.min.css">*, ::after, ::before {    box-sizing: border-box;}

Окей, рассмотрим чуть более реальный пример. Мы встраиваемся на страничку, на которой используется Bootstrap, например. В Bootstrap есть такой код, который всем элементам задает box-sizing.

Предположим, мы наш виджет отрисуем на такой страничке:

Тогда у него чуть-чуть поедут размеры, потому что border переехал из-за box-sizing внутрь элемента у нас меняются размеры виджета. Не очень классно. Получается, что наш виджет подвержен влиянию окружающего его кода, чего мы в микрофронтендах хотим избежать.

Как этого можно избежать? Первый вариант: есть достаточно старый проект cleanslate.css.

<body>  <div class="blah">      <!-- general content is not affected -->      <div class="myContainer cleanslate">          <!-- this content will be reset -->      </div>  </div></body>

Это специальный CSS reset, который перезагружает стили не на всей страничке, а только на том div, где стоит класс cleanslate. Всё, что находится внутри cleanslate, будет переопределено, у него будут дефолтные зарезеченные стили.

Либо есть более современное решение, которое использует часть спецификаций веб-компонентов, а именно Shadow DOM.

Shadow DOM это такой способ отрисовать часть DOM-дерева изолированно и скрыто от других элементов на страничке. С помощью Shadow DOM рисуются встроенные в браузер контролы, например, input range. Если вы посмотрите на него в dev tools, там внутри в shadow root находится верстка, стилизованная с помощью CSS, который зашит в движок браузера.

    constructor({ container }) {        this.shadowRoot = container.attachShadow(            { mode: 'open' }        );        this.innerContainer = document.createElement('div');        this.innerContainer.classList.add('doggy-widget');        this.shadowRoot.appendChild(this.innerContainer);            }

Окей, попробуем заюзать Shadow DOM для нашего виджета. Что нам для этого нужно? В конструкторе мы приаттачим в контейнер shadowRoot, создадим еще один div, назовем его innerContainer и зааппендим его внутрь нашего shadowRoot.

    _renderImg() {                this.innerContainer.appendChild(this.img);    }    _renderBtn() {                this.innerContainer.appendChild(this.btn);    }

И нам потребуется немного переделать методы renderImg(), renderBtn(). Теперь мы будем картинку и кнопку складывать не в контейнер, который нам пришел, а в innerContainer, который мы уже положили внутрь shadowRoot.

    destroy() {                this.shadowRoot.innerHTML = '';    } 

Осталось еще немного поправить destroy. В destroy будем shadowRoot просто подчищать за собой.

Класс! Кажется, мы использовали Shadow DOM и смогли нашу верстку изолировать от другого кода.


Ссылка со слайда

В этом случае мы получим что-то такое у нас пропали все стили.


Что именно произошло? Изоляция, которую обеспечивает Shadow DOM, работает в обе стороны: она блокирует как вредоносные стили, которые нам не нужны, так и наши собственные стили, которые мы хотим добавить. Смотрите, link с doggy widget CSS остался снаружи shadowRoot, а верстка виджета находится внутри. Соответственно, правила, которые описаны снаружи, не влияют на то, что находится внутри shadowRoot.

     constructor() {                const link = document.createElement('link');        link.rel = 'stylesheet';        link.href = 'doggy-widget.css';        this.shadowRoot.appendChild(link);            }

<script src="doggy-widget.js"></script>

<link rel="stylesheet" href="doggy-widget.css">

Чтобы это полечить, нам нужно тег link класть внутрь shadowRoot. Сделать это очень просто. Создаем элемент link, ставим ему href и аппендим его внутрь shadowRoot. В коде вставки виджета на страницу отдельный CSS-файл нам уже будет не нужен, он будет подключаться в конструкторе виджета.

Ссылка со слайда

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

Единственная проблема, которую вы можете заметить, если откроете dev tools: на каждую инициализацию виджета появился отдельный запрос за doggy-widget.css. Здесь вам нужно будет убедиться, что у вас корректно настроено кеширование, чтобы повторно не грузить этот файл вашим клиентам.

Вроде изоляцию мы полечили. Или не совсем? Давайте немножко поиграем в шарады.

Опытные разработчики поймут, что здесь зашифрован monkey-patching. Это техника, которая нам позволяет делать прототипное наследование JavaScript, а именно изменять стандартную библиотеку. Например, через это работают полифилы. Мы можем в старый браузер притащить метод, который появился в новой спецификации, чтобы писать код, используя новые спеки. Но monkey-patching позволяет как делать хорошие штуки, так и очень сильно всё ломать.

Расскажу пример, с которым мы столкнулись, когда я работал в рекламных технологиях.

var str = JSON.stringify(['haha'])> '["haha"]'JSON.parse(str)> ["haha"]

Был у нас примерно такой код. Мы стрингифаили массивчик. Очевидно, результат у этого выражения вот такой. И потом мы эту застрингифаенную строчку отправляли на наш бэкенд, где потом ее парсили.

Очевидно, если мы такую строку распарсим, то получим массив. Все хорошо.

var str = JSON.stringify(['haha'])> '"[\"haha\"]"'JSON.parse(str)> '["haha"]'

А вот на сайте одного из партнеров, куда мы этот виджет встраивали, мы видели такую картину.

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

Array.prototype.toJSON: () => Object

Стали разбираться, что происходит. JSON.stringify по спецификации, если у объекта определен метод toJSON, вызывает его. Метод должен вернуть сериализуемый объект, который дальше будет через JSON.stringify преобразован, чтобы получить строку.

Array.prototype.toJSON = function () {    var c = [];    this.each(function (a) {        var b = Object.toJSON(a);        if (!Object.isUndefined(b))            c.push(b)    });    return '[' + c.join(', ') + ']'}

И как раз на этом сайте мы обнаружили такой код, внимание на предпоследнюю строку. В прототип массива добавлялся метод toJSON, который возвращал не сериализуемый объект, как должно быть по спеке, а строчку.

Код этот прилетел из старой библиотеки prototype.js, это такая либа эпохи раннего jQuery, которая занимается тем, что расширяет стандартную библиотеку JavaScript для появления удобных в использовании методов.

Мы, кстати, как потом выяснилось, не единственные, кто с такой проблемой столкнулся. На Stack Overflow есть обсуждение, где предлагается эту проблему пролечить таким страшненьким кодом:

var _json_stringify = JSON.stringify;JSON.stringify = function(value) {    var _array_tojson = Array.prototype.toJSON;    delete Array.prototype.toJSON;    var r=_json_stringify(value);    Array.prototype.toJSON = _array_tojson;    return r;};

Строго говоря, предлагается полечить monkey-patching еще одним monkey-patching, что не кажется очень хорошим решением.

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

Что еще у нас было из бонусов? Независимые мониторинги. Когда я говорю о них, я в первую очередь имею в виду мониторинг клиентских ошибок. Есть достаточно много сервисов, которые вы можете подключить в свое приложение, и они будут мониторить и агрегировать эксепшены, которые случаются у вас в коде. Если вы это еще не делаете, то настоятельно рекомендую начать мониторить клиентские ошибки. Вы будете неприятно удивлены количеством багов, которые у вас случаются в проде и ломают поведение сайтов у ваших пользователей, а вы об этом ничего не знаете.

Давайте попробуем сломать наш виджет, посмотреть, как такой мониторинг будет работать и что он нам даст.

    _renderImg() {        const img = document.createElement(img');        this.img = img;        img.classList.add('doggy-widget__img');        img.alt = 'doggy';        this.container.appendChild(this.img);         this.updateDoggy(img);    }

Если помните, у нас был метод renderImg, который отрисовывал картинку. Давайте мы его сломаем, а именно удалим третью строчку, которая img кладет в поле нашего класса.

Что произойдет? Начальная отрисовка у нас отработает.

Ссылка со слайда

А вот если мы нажмем на кнопочку, то увидим exception.

window.addEventListener('error', (e) => {    console.log('got error:', e.error);    e.preventDefault();});


Как этот exception можно поймать, обработать и залогировать? Что делают те сервисы, которые я показывал несколько слайдов назад? Есть глобальный ивент 'error', который срабатывает на объекте window. На него можно подписаться и получить из этого ивента объект ошибки, которая произошла и которую вы не отловили через try-catch. У ивента можно вызвать preventDefault, чтобы также скрыть красную ошибку в консольке и не пугать ваших пользователей, которые внезапно решили открыть devtools.

В нашем виджете это будет выглядеть так. При нажатии на кнопку будет срабатывать обработчик.

window.addEventListener('unhandledrejection', (e) => {    console.log('got promise reject:', e.reason);    e.preventDefault();});

Маленький бонус. Почти так можно ловить зареджекченные цепочки промисов. Для этого используется отдельный ивент unhandledrejection. В поле reason у этого ивента будет находиться тот объект, с которым был зареджекчен ваш необработанный промис:

Promise.reject(new Error('bla'))

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


window.addEventListener('error', (e) => {    console.log('got error:', e.error);    e.preventDefault();});

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

Так что независимые мониторинги при таком подходе мы не получаем.

Давайте подведем промежуточные итоги. Что нам дает использование независимых скриптов?

У нас есть минимум накладных расходов. Каждый отдельный элемент микрофронтенда, каждый виджет, это просто лишний скрипт. Кажется, менее накладно это сделать нельзя. Но при этом у нас плохая изоляция, мы научились изолировать верстку с помощью Shadow DOM, а JS мы изолировать не можем. И у нас нет хороших независимых мониторингов ошибок.

Для чего такая идея может пригодиться? Она в целом нормально работает в микрофронтендах. Но если мы виджет делаем как независимую библиотечку, которая встраивается не только в приложение с микрофронтендами, но и на какие-то сторонние странички, то эта идея подходит не очень хорошо, потому что мы не защищены от monkey-patching на внешних сайтах и не можем хорошо отслеживать ошибки, которые происходят с нашим виджетом.

Тем не менее, эта идея активно используется. Например, один из популярных фреймворков для построения микрофронтендов single-spa как раз на ней, в общем-то, и построен.

Что делать, если нам это все не подходит и хочется больше изоляции? Здесь поможет старая технология iframe.

<iframe>


Это тег, который позволяет на внутри вашей странички отрисовать еще один независимый документ.

(() => {    window.DoggyWidget = {        init({ container }) {            const iframe = document.createElement('iframe');        }    }})();

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

(() => {    window.DoggyWidget = {        init({ container }) {            const iframe = document.createElement('iframe');            iframe.style.width = '100%';            iframe.style.height = '100%';            iframe.style.borderWidth = 0;            iframe.style.display = 'block';            iframe.src = 'https://some-url/doggy-widget.html';                    }    }})();

В фабрике init нашего виджета нам нужно будет создать iframe и повесить на него стили. Мы поставим width и height 100%, чтобы он полностью растягивался до размеров контейнера, куда его вставили. Мы переопределим ему display и поставим границу 0, потому что по дефолту браузеры рисуют border.

Внутри iframe загрузим документ, в котором будет рендериться наш виджет.

(() => {    window.DoggyWidget = {        init({ container }) {            const iframe = document.createElement(iframe');            iframe.style.width = '100%';            iframe.style.height = '100%';            iframe.style.borderWidth = 0;            iframe.style.display = 'block';            iframe.src = 'https://some-url/doggy-widget.html';            container.appendChild(iframe);                        ...        }    }})();

Осталось зааппендить этот iframe внутрь контейнера.


Ссылка со слайда

Все будет работать, виджет будет отрисовываться.

Вроде здорово. Мы использовали iframe, виджет в нем работает, не подвержен влиянию другого кода на страничке. У нас есть полная изоляция, независимые мониторинги внутри iframe.

declare const DoggyWidget: {    init: ({        container: HTMLElement,    }) => DoggyWidgetInstance;}declare interface DoggyWidgetInstance {    destroy(): void;    updateDoggy(): void;}

Но мы кое о чем забыли. У нашего виджета есть API. У инстанса есть destroy и updateDoggy. Давайте попробуем их реализовать.

destroy() {    this.container.innerHTML = '';}

destroy будет суперпростой. Нам нужно будет просто почистить контейнер, если вы не используете этого парня. В IE 11 и legacy Edge есть неприятный баг, связанный с тем, что контекст JS, который работает внутри фрейма, продолжает частично жить после удаления iframe из DOM. Что значит частично? В нем ломается стандартная библиотека, перестают, например, быть доступны объекты Date, Object, Array и прочее. Но асинхронный код, сет таймауты, сет интервалы, реакция на ивенты, которая там была, продолжают работать, и вы можете в ваших мониторингах в таком случае увидеть очень странные эксепшены из IE и legacy Edge о том, что у вас вдруг пропал Date, он стал undefined.

Чтобы это обойти, нам наш iframe предварительно перед удалением его из DOM нужно будет вот таким образом почистить. Тогда IE 11 и старый Edge корректно его задестроят и остановят весь JS-код, который внутри него выполнялся.

destroy() {    // чистим iframe для ie11 и legacy edge     this.iframe.src = '';    this.container.innerHTML = '';}


Ссылка со слайдов

Proof of concept destroy работает.

Что еще? У нас остался updateDoggy, для него нам нужно обновить картинку, которая рисуется внутри фрейма. Соответственно, сделать какое-то действие между нашим основным документом, отправить команду внутрь iframe. Здесь есть проблема. Если iframe загружается с другого хоста, браузер заблокирует любое взаимодействие с window внутри фрейма и вы получите примерно такую ошибку.

Как же все-таки можно взаимодействовать? Для взаимодействия нужно использовать postMessage. Это API, который позволяет отправить сериализуемую команду внутрь другого window, и внутри этого window подписаться на объект message, прочитать то, что было в команде. И отреагировать на нее.

updateDoggy() {    this.iframe.contentWindow        .postMessage({ command: 'updateDoggy' });}

Давайте реализуем updateDoggy через postMessage. В родительском документе у нас будет отправляться сообщение с командой updateDoggy внутрь iframe.

window.addEventListener('message', (e) => {    if (e.data.command === 'updateDoggy') {        widget.updateDoggy();    }})

И внутри iframe нам нужно будет написать вот такой код, который подписывается на события message, а если там updateDoggy, то дергает updateDoggy у виджета, который перерисует нам картинку.


Ссылка со слайдов

Посмотрим, что нам дает использование iframe. В первую очередь все взаимодействие с виджетом, который рисуется внутри iframe, становится асинхронным. postMessage асинхронный API. До этого мы могли синхронно вызывать методы, а сейчас мы этого делать не можем.

События, которые происходят внутри iframe, наружу не всплывают. Если вы хотите реагировать, например, снаружи на то, что пользователь кликнул внутри виджета, то вам нужно отправлять postMessage наверх. Использовать addEventListener напрямую у вас не получится событие через iframe не всплывет.

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

И еще: iframe нельзя передвигать по DOM. Когда вы iframe детачите и аттачите обратно, он перезагружается, виджет будет перерисовываться, все запросы, которые он выполняет для инициализации, будут исполнены заново. В общем, не очень оптимально.

Что мы в итоге получаем? У нас сильно усложняется код. И еще появляются накладные расходы.

Если мы вспомним, как рисовался наш виджет, вставляемый через скрипт, это это выглядело бы так. У нас бы загружалась страничка, загружался CSS, JS. Дальше, когда виджет рисовался бы, каждый виджет запрашивал бы для себя картинку.

Если мы рассмотрим наш новый вариант с iframes, мы увидим такое. Внутри каждого виджета загрузится документ, у нас загрузится CSS, который там нужен, и JS, который внутри этого документа исполняется.

Для первого виджета, для второго. Сколько у вас их будет на странице, столько будет загрузок этих файлов?

Ссылка со слайда

Здесь могло бы помочь кеширование, но недавно браузеры сделали так, чтобы изолировать кеши друг от друга между различными сайтами. Это нужно, чтобы предотвратить трекинг посещения пользователем одного сайта с другого. То есть если на сайте номер 1 используется какая-то библиотечка, сайт номер 2 тоже может ее подключить и посмотреть через Performance API, была они ла загружена из кеша. Если да, то пользователь, скорее всего, до этого посещал сайт 1 и это можно как-то использовать. Браузеры сейчас от такого поведения стараются пользователей защищать.

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

К чему это приводит, если мы пытаемся строить микрофронтенды на виджетах, которые загружаются каждый в независимом iframe?

https://website.ru/    https://yastatic.net/react/16.8.4/react-with-dom.min.js    Widget #1        <iframe> https://widget-1.ru/            https://yastatic.net/react/16.8.4/react-with-dom.min.js    Widget #2        <iframe> https://widget-2.ru/            https://yastatic.net/react/16.8.4/react-with-dom.min.js

Допустим, у нас есть наш основной сайт, на котором подключен React. Есть виджет номер 1, на котором подключен React допустим, даже тот же самый bundle. И виджет номер 2 с еще одного хоста, на нем тоже подключен React.

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

Итак, что мы получаем с iframe? У нас есть полная изоляция виджетов в CSS. Есть полная изоляция JS, потому что документы не зависят друг от друга. Есть независимые мониторинги, потому что внутри каждого iframe свой собственный window, на котором мы можем ловить ошибки.

Но при этом сильно усложнился код, поскольку появилась асинхронность.

Появились накладные расходы за дополнительными запросами, а именно за документом, который грузится внутри iframe, за HTML. И появились сложности с кешированием. iframes также требуют дополнительные браузерные ресурсы на работу, дополнительную память и дополнительное процессорное время.

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

Выглядит так, будто у нас есть два варианта: один с использованием скрипта, один с использованием iframe.

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

Здесь поможет так называемый friendly <iframe>. Вы еще можете встретить название same-origin <iframe>, или anonymous <iframe>.

const globalOne = window;let iframe = document.createElement('iframe');document.body.appendChild(iframe);const globalTwo = iframe.contentWindow;

В чем идея? Есть глобальная область наш текущий window. Можно создать через createElement новый iframe и зааппендить его на страничку. При этом заметьте, что я внутри этого фрейма никакой документ не загружаю, дополнительного запроса за HTML здесь не будет и внутри документа окажется пустая страничка, которую туда автоматически подложит браузер.

Теперь contentWindow этого iframe можно рассматривать как еще один независимый контекст, который мы можем использовать.

foobar.js:
window.someMethod = () => {...}

Давайте подумаем, зачем. Мы можем в этом контексте исполнять скрипты.

Вот наш скрипт foobar.js, который в глобальную область добавляет метод. Как подключить его внутрь нашего нового контекста? Создаем скрипт, ставим ему src и аппендим внутрь head нашего iframe.

const script = document.createElement(script);script.src = 'foobar.js';globalTwo.head.appendChild(script);

Теперь, чтобы взаимодействовать с кодом внутри этого скрипта, нам больше не нужно использовать postMessage, потому что контекст у нас same-origin:

globalTwo.postMessage();

globalTwo.someMethod();

Можно просто напрямую вызвать сам метод, и это будет работать, браузер не будет это блокировать.

Давайте попробуем, используя эту идею, переписать наш виджет еще раз.

Единственное, нам надо понять, где у нас будет находиться верстка. Если помните, я сказал, что внутрь friendly frame документ мы не загружаем, но верстку надо где-то отрисовать.

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

Как теперь будет выглядеть фабрика нашего виджета?

const iframe = document.createElement('iframe');document.head.appendChild(iframe);const script = document.createElement('script');script.src = 'doggy-widget-inner.js';const loaded = new Promise((resolve) => {    script.onload = resolve;});loaded.then(() => {    iframe.contentWindow.init(config);})iframe.contentDocument.head.appendChild(script);

Создаем в нем iframe, загружаем внутрь этого iframe скрипт и сохраняем promise, который зарезолвится, когда этот скрипт загрузится.

После того, как он прогрузился, мы вызовем внутри нашего виджета init и передадим его config, который отрисует виджет внутри. Нам осталось зааппендить скрипт в head нашего iframe.

Как теперь преобразуется doggy-widget-inner.js, код, который работает внутри фрейма?

window.init = (config) => {    const widget = new Widget(config);    window.widget = widget;};

В нем будет реализация класса widget, точно такая же, как мы использовали, когда рассматривали подход со скриптом и применением Shadow DOM. В нем появится глобальный метод init, который мы вызывали на предыдущем слайде и который будет создавать виджет и класть инстанс виджета прямо в глобальную область.

Как в итоге все будет работать? Если мы отрисуем таким способом два виджета на страничке, то получим примерно такое DOM-дерево.



Ссылка со слайдов

Для каждого виджета у нас будет в хэде скрытый friendly iframe, который пользователь не видит, но при этом код внутри него исполняется и с ним можно работать. Для каждого виджета в контейнере, который мы передали, будет использоваться shadow root, внутри которого будет находиться верстка этого конкретного виджета. Вот для первого виджета, а вот для второго.

Код целиком:

<head>    <iframe>        #document            <html>                <head>                    <script src="doggy-widget-inner.js"></script>                </head>                <body></body>            </html>    </iframe>    <iframe>        #document            <html>                <head>                    <script src="doggy-widget-inner.js"></script>                </head>                <body></body>            </html>    </iframe></head><body>    <div id="widget-1">        #shadow-root            <link rel="stylesheet" href="doggy-widget.css">            <div class="doggy-widget">                <img class="doggy-widget__img"/>                <button class="doggy-widget__btn"/>            </div>    </div>    <div id="widget-2">        #shadow-root            <link rel="stylesheet" href="doggy-widget.css">            <div class="doggy-widget">                <img class="doggy-widget__img"/>                <button class="doggy-widget__btn"/>            </div>    </div>    <script src="doggy-widget.js"></script></body>

Что этот подход нам дает? Мы получаем:

  • Полную изоляцию наших виджетов в CSS, потому что используем Shadow DOM.
  • Полную изоляцию в JS, потому что код работает внутри выделенного iframe, и какой-либо monkey-patching в родительском документе на него никак не влияет.
  • Независимые мониторинги, потому что код виджета работает, опять-таки, в независимом window, где мы можем слушать эксепшены.
  • Работающее кеширование, так как контекст same-origin в браузере больше не изолирует кеши между виджетами.

При этом все еще есть:

  • Некоторое усложнение кода. Загрузка становится асинхронной, но гораздо лучше использовать асинхронное взаимодействие, в отличие от секьюрного фрейма, где мы использовали постмесседжи.
  • Небольшие накладные расходы запрос за дополнительным js-файлом, который загружается внутрь iframe.
  • Необходимость в дополнительных ресурсах. Требуются дополнительные ресурсы браузера и устройства пользователя на работу отдельного контекста iframe, но они сильно меньше, чем у секьюрного фрейма.

Такая концепция хорошо подходит и когда вы строите отдельный виджет, и когда собираете из виджетов приложение с микрофронтендами.

Немного поговорим о том, что ждет нас в светлом будущем. Там нас ждет спецификация Realms API. Она сейчас находится в TC39 на Stage 2, это draft. Активно идет написание стандарта. Спецификация развивается. Надеемся, что скоро она перейдет на stage 3.

Что она позволяет делать? Вспомним, как мы создавали friendly frame. У нас был глобальный контекст globalOne. Мы создавали элемент iframe, аппендили его в документ и получали globalTwo еще один независимый контекст внутри этого фрейма.

const globalOne = window;let iframe = document.createElement('iframe');document.body.appendChild(iframe);const globalTwo = iframe.contentWindow;

const globalOne = window;const globalTwo = new Realm().globalThis;

Realms позволяет это заменить на такую конструкцию. Появляется новый глобальный объект Realm. Создав инстанс Realm, вы получаете внутри него globalThis, который является как раз тем самым независимым контекстом, который при этом работает оптимальнее, чем отдельный iframe.

Как внутри Realm можно будет исполнить код? Через вызов импорта.

const realm = new Realm();const { doSomething } = await realm.import(    ./file.js');doSomething();

Заимпортируем какой-нибудь JS-файл, который экспортирует метод doSomething. Его сразу можно будет вызвать, он будет работать в контексте Realm независимо от основной странички.

У этого API даже есть полифильчик, который построен как раз с использованием friendly frame. Правда, полифил, похоже, стал deprecated. Там года два уже не было коммитов, и он сильно отстал от текущего драфта спеки. Но надеюсь, с развитием спецификации его оживят.

Итоги


Используйте микрофронтендный подход с умом, только если вам это действительно нужно. Выбирайте подходящую вам техническую реализацию. И следите за спецификациями. Эта тематика активно развивается, в будущем у нас появятся более удобные способы строить микрофронтенды. Спасибо!
Подробнее..

ESP32 LVGL и круглый дисплей

25.03.2021 16:19:55 | Автор: admin

В прошлом году, после выхода видео про дисплей GC9A01 на канале "Электроника в объективе", я решил, что обязательно должен что-то на нем собрать, да еще и с использованием графической библиотеки LVGL. Заказал 2 таких дисплея, один на отладочной плате, второй отдельно только дисплей со шлейфом.

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

Основные требования по устройству были: чип ESP32 так же для перспективы заложил драйвер RS-485 и слот для microSD карточки, так же хотелось иметь возможность питать все от usb и в перспективе иметь возможность подать внешнее питание. Так как разъем все равно ставить, хотелось сразу иметь возможность и программировать esp32 без дополнительных подключений. Но количество свободного места оказалось крайне мало, ставить CP2102 было дороговато, а CH340 с ее габаритами и кварцем не хотелось. Случайно увидел что есть компактная микросхема CH340N SOIC-8, преобразователь USB-UART с минимальной обвязкой в корпусе SOP-8. Ее и решено было использовать.

Так как на одной плате все не помещалось, разделил на две, верхняя ESP32 и дисплей, снизу питание, microSD слот, драйвер RS485 и USB-UART преобразователь. Для связи между нижней и верхней платой решил использовать гибкий плоский шлейф FFC и коннекторы FPC с шагом 0.5. Готовых шлейфов под размер и количество линий в наличии не было, решил делать из широкого и длинного шлейфа, думал что не получится разделить, но оказалось что это возможно. Для удешевления заказа объединил две платы в одну, с последующим удалением перемычки между ними, такая компоновка позволяет использовать верхнюю плату и под другие проекты с выводом информации на дисплей.

Общий вид печатной платы

Прикинув все размеры компонентов, решил уменьшить толщину платы до минимально возможного размера без увеличения стоимости, заказал с толщиной 0,6мм с зеленой маской. При такой толщине их можно резать чуть ли не ножницами, разделение на 2 платы заняло пару минут. Как оказалось не обошлось и без "косяков", перепутал выводы micro-USB разъема, разместил зеркально, пришлось резать дорожки и переделывать, после залил лаком. Так же вывод Reset дисплея соединил не с портом общего назначения, а с сигналом разрешения работы EN, в принципе на работе это не отразилось, но нет возможности сброса экрана из программы. Вся остальная схема запустилась без проблем, прошивается через micro-USB, через этот же разъем можно подключаться по Modbus например используя Modbus poll/mbslave, задействовал линии rx и tx которые идут на драйвер ADM3485 и они же используются при программировании.

Для создания проекта использовал ESP-IDF и порт LVGL. Данный пример содержит множество готовых драйверов дисплеев и примеры большинства виджетов библиотеки LVGL. Так как дисплей не имеет тачскрина, то мне были интересны примеры вывода графики в виде различных манометров и других, заточенных под круглые дисплеи, виджетов. Пример из библиотеки мне не очень понравился, хотелось использовать что-то более реалистичное, так как сам дисплей позволяет выводить довольно качественную картинку, решил использовать готовые подложки, их необходимо подготовить под разрешение 240x240, в принципе можно использовать любые подходящие, единственное перед конвертацией в графическом редакторе необходимо убрать стрелки, так как стрелка будет управляться отдельно из программы. Варианты стрелок так же можно подготовить в разном стиле и цвете, для разных подложек. Стрелка должна быть симметрична, так как из программы она будет вращаться относительно центра дисплея. Вся подготовленная под размер экрана графика конвертируется через сервис imageconverter в .с файл, который затем подключается в программе. Самый простой пример для манометра содержит два объекта, сама подложка она статическая и стрелка- изменяющийся объект.

img0 = lv_img_create(lv_scr_act(), NULL);//фон манометраlv_img_set_src(img0, &man5);lv_obj_align(img0, NULL, LV_ALIGN_CENTER, 0, 0);//стрелка, угол 60lv_img_set_angle(s6, 600);

Задание для стрелки выдается в отдельной задаче вызываемой с требуемым периодом:

static void update_time(void *arg){//    get_time(); //получить значение часов, минут, секундcur_time_s++;//    lv_img_set_angle(  lvHour, cur_time_h*30*10);//    lv_img_set_angle(  lvMinute, cur_time_m*6*10);    lv_img_set_angle(  lvSecond, cur_time_s*6*10);//задание угла для секундной стрелки}

Минимальный шаг задания 0,1, окружность разделена на 3600 частей. В зависимости от выбранной подложки можно рассчитать min и max задание в пределах шкалы. Остается преобразовать полученное значение от датчика в угол задания. Для демонстрации часов происходит управление тремя стрелками, достаточно поднять на ESP32 NTP сервер и получить текущее значение часов. минут, секунд (в моем примере NTP сервис не запущен) Для того, чтобы каждый раз не корректировать файлы стрелок под выбранный циферблат их можно масштабировать:

//удлинить минутную стрелкуlv_img_set_zoom(lvMinute, 340);

В примере продемонстрирован минимальный набор для запуска на дисплее, в самой библиотеке представлены примеры всех виджетов, большинство из которых имеет смысл на дисплеях с тачскрином. Ранее я проверил данную библиотеку на 7" дисплее с сенсорным тач контроллером.

Из недостатков можно отметить "тормознутость" на больших диагоналях из-за невозможности выделить полноценный буфер под полное разрешение экрана, особенно это проявляется при обновлении всего экрана, когда происходит скроллинг или открытие другого таба. Для простых кнопочных интерфейсов эта проблема не так актуальна, так как обновляется только область кнопки. Для представленного дисплея с разрешением 240x240 проблема не так заметна, но при использовании нескольких подложек сильно увеличивается размер прошивки. Пока вижу вариант загрузки графики с SD карты или использования ESP32 с памятью psram и размещением в ней графики или буфера. На моих платах интерфейс spi дисплея и sd карты разведены на одних и тех же пинах и, насколько я знаю, пока не удалось заставить их работать в тандеме, есть отдельные упоминания о работе но я пока не проверял. Если у кого-то есть работающий пример напишите в комментариях. Так же я пока не пробовал использовать память psram для размещения буфера дисплея, без этого памяти остается не так много. В чипе который используется ее нет, но она есть в чипе ESP32-WROVER на второй плате к которой можно подключить дисплей от Adafruit 4,3" , Waveshare 7" и данный круглый через переходник со шлейфом.

Общий вид отладочной платы

Дальше планирую проверить подключение по Modbus и можно использовать как небольшую панель для чтения параметров из внешних устройств. Так же на самой плате возле выводов модуля сделаны полигоны для возможности подключения датчиков, например ds18b20 или влажности/давления, внутри их размещать смысла нет, так как при закрытом корпусе там довольно жарко, но можно вывести наружу через отверстие. Так же можно организовать mqtt и получать данные с удаленного датчика. Наконец, есть еще bluetooth который я даже не включал, но думаю и там есть множество применений. Далее видео получившегося устройства с демонстрацией разных вариантов экранов.

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

Схему не привожу, так как ее у меня нет, плата разводилась "на лету" за чашкой чая вечером, обвязку для микросхем можно взять в pdf, esp32 так же позволяет переназначать все пины используемые в проекте. Для запуска примеров достаточно взять ESP32-DevKitC и плату с экраном GC9A01. Пины используемые в моем проекте: MOSI-12 CLK-14 CS-23 DC-22. SPI лучше использовать на пинах по умолчанию, тогда его можно будет запускать на "максималках", у меня первая плата была разведена не так и эту я оставил для совместимости проектов.

Перечень основных элементов:

  • ESP-WROOM-32

  • ADM3485EARZ-REEL7

  • CH340N

  • AMS1117

  • FPC SC 0.5MM 14P CB

  • 12PIN SPI TFT LCD GC9A01

Мой демо проект на github в папке components/image конвертированные файлы подложек для экранов.

Тут более свежие версии библиотеки с примерами.

Подробнее..

Сетевой интерфейс для программируемого реле с поддержкой Telegram Bot и HomeKit

07.05.2021 14:19:42 | Автор: admin

Как я реализовал удаленное управление и мониторинг, для программируемого реле ПР200, используя разные сервисы (Telegram Bot, HomeKit) и протоколы (Modbus RTU, Modbus TCP, mqtt) и ESP32.

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

Долгое время я использовал сетевой интерфейс на основе модуля esp8266, на сегодня более перспективным вижу использование модуля esp32, долгое время я не рассматривал его из-за размеров, но впоследствии, проработав разные варианты, удалось не только вместить все на одной плате, но и сделать решение более универсальным и удобным.

Первая версия платы на основе ESP32

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

В обновленном варианте добавил ещё и кнопки сброса и загрузки при прошивке, а так же добавил поддержку модулей ESP32-WROVER с PSRAM, это позволит использовать больше памяти и расширит возможности.

В общем, структура взаимодействия сетевой платы с программируемым реле основана на протоколе modbus rtu, а с внешним миром варианты могут быть самые разнообразные от bluetooth до TelegramBot.

TelegramBot

Именно с поддержки бота я и начал опыты, на раннем этапе получилось вот такая реализация:

Для использования бота достаточно получить токен и ввести его в поле ввода и сохранить, значение сохраняется в энергонезависимой памяти, после перезагрузки бот запустится автоматически.

Для универсальности взаимодействие бота с алгоритмом в приборе, использован режим чтения/записи сетевых регистров Modbus а разных форматах представления:

/R- целое 16 битное значение

/I-целое число занимающее 2 регистра

/F- число в формате float тоже 2 регистра.

После символа адрес в диапазоне 512-576, эти регистры можно читать и записывать, формат для записи /Xzzz=nnnn, для чтения достаточно отправить номер регистра в требуемом формате.

Для представления состояний регистра в битовых полях, можно отправить адрес в формате /Bzzz, ответ будет в виде 16 значения в булевом формате.

Apple HomeKit

Следующим этапом был сервис Apple HomeKit и приложение дом, как раз для управления освещением и другими точечными нагрузками он подходит лучше всего, я начал с 16 каналов, по количеству бит в регистре модбас.

После выхода обзора по такому применению,

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

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

Для быстрого добавления платы в приложение Дом, на web страницу добавлен QR код, второй вариант ввести код настройки, индивидуальный для каждой платы. Постарался упростить и минимизировать все настройки для быстрого старта.

Так же протестировал mqtt, идея задания топиков взята из версии платы для esp8266. Проверил поддержку датчиков 1-wire ds18b20, для их подключения к плате предусмотрены посадочные места под разъем, и сигнальные линии с резисторами, такой-же использовался в плате prsd на esp8266.

4 пина, два из которых +3.3v и gnd, позволяют задействовать 2 порта в качестве интерфейса 1-wire или i2c. I2C позволяет подключать всякую экзотику, которую практически невозможно состыковать в базовой поставке прибора. Например, датчик влажности/давления с I2C или RFID ридер.

Для быстрого просмотра значений регистров используется протокол Modbus TCP, запустив Modbus Poll на ПК или Virtuino/Kascada и другие приложения на Android, можно быстро организовать доступ и управление устройством с помощью телефона или планшета.

Остальные настройки WEB интерфейса представлены ниже:

WEB настройки

Для смены прошивки платы, когда она уже установлена в устройство, предусмотрен режим обновления по воздуху (OTA), для этого достаточно выбрать bin файл, после загрузки прошивки устройство перезагрузится и запустится обновленная версия. Так же можно перезагрузить плату в ручном режиме через web кнопку.

При первом старте, когда устройство не имеет настроек точки доступа и пароля и не может подключиться к сети wi-fi, плата включает режим точки доступа для подключения и ввода ssid и pass, после сохранения значений и перезагрузки если подключение к сети успешно, точка доступа выключается. Если токен Telegram bot введен, то после подключения и выхода в интернет, узнать IP адрес платы можно введя команду. Через бот можно получить и другую информацию.

Основные моменты по работе представлены в видео.

На данный момент прошивка находится в режиме доработки, демонстрационные версии будут доступны позже.

Для экономии ресурсов ESP32 в перспективе думаю разделить на версии с поддержкой того или иного функционала, памяти в esp32 хоть и больше в сравнении с esp8266, но одновременная работа всех сервисов может снизить ресурсы до критического уровня.

В процессе тестирования нескольких подключенных устройств одновременно, заметил особенность.

В меню бота есть возможность смотреть время бесперебойной работы в секундах, текущий и минимальный объем доступной Heap памяти, используя один и тот же токен для TelegramBot, можно читать параметры сразу с нескольких устройств, аналогично и на запись параметров. Т.е. любая команда, отправленная с телефона боту, отправляется всем устройствам с токеном, это происходит не всегда, иногда отвечает только часть устройств, и приходится делать запросы повторно. Сейчас все они не имеют привязки к имени, думаю сделать уникальные имена для отличия при запросах/ответах.

Используя несложный сетевой интерфейс с чипом ESP32, можно значительно расширить функционал программируемого реле ПР200 и в перспективе ПР103, куда можно установить сетевой интерфейс, другие модели ПР100/ПР102 потребуют внешний драйвер RS-485 для подключения снаружи, так как сетевые интерфейсы в них не съемные.

Наличие программатора на борту платы, позволяет создать собственные алгоритмы с быстрой загрузкой в устройство, необходимо лишь обеспечить обмен по протоколу Modbus на стандартных выводах UART esp32.

Подробнее..

Конвейер уникальная система мониторинга и управления для конвейерного производства

31.03.2021 14:23:38 | Автор: admin

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

Компания Сибирь Телематика с 2017 года ведет научно-исследовательские и опытно-конструкторские работы по созданию технологической платформы программно-аппаратного комплекса Конвейер. Решение призвано эффективного управлять непрерывными конвейерными производствами, используя технологии Индустрия 4.0.

В 2019 году разработку поддержал Фонд содействия инновациям в рамках конкурса Развитие-ЦТ. Благодаря этому команда создала и запустила платформу, объединяющую автономно управляемые технологические участки конвейерного производства в единую систему. Кроме того, грантовая поддержка позволила существенно расширить штат разработчиков и создать уникальное, с точки зрения технической архитектуры, решение, не имеющие аналогов в мире.

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

На сегодняшний день все большую значимость приобретают решения в области цифровизации производственных процессов в различных отраслях промышленности, в первую очередь в сфере обрабатывающих производств. Особенно актуальными являются задачи по снижению себестоимости выпускаемой продукции, уменьшению объема потерь и брака при производстве. Мы, как компания, которая изначально занималась решением индивидуальных заказных задач, направленных на оптимизацию производственных процессов, искали возможности для масштабирования создания коробочного продукта. Благодаря Фонду содействия инновациям и его представительству в Академпарке, у нас это получилось, отметил Иван Корсуков, директор компании Сибирь Телематика.

Экономическую эффективность разработанного решения команда подтвердила, внедрив технологию на крупнейшем в России заводе по производству полых стеклянных изделий ООО Сибирское стекло.

За счет внедрения новых технических решений, в том числе, программного-аппаратного комплекса Конвейер от ООО Сибирь Телематика, производство стеклотары в сравнении с аналогичным периодом прошлого года выросло: на 6% в тоннах со 133,2 тысяч до 141,7 тысяч, и на 10% в штуках с 437 млн до 480 млн, отметил исполняющий обязанности генерального директора ООО Сибирское стекло Антон Мор.

Кроме того, Сибирь Телематика стала профильной организацией в ассоциации заводов-производителей стекла СтеклоСоюз России и в течении ближайших трех лет планирует внедрить ПАК Конвейер на шести крупных стекольных заводах России, Белоруссии и Казахстана. Общая сумма будущих контрактов составит около 350 млн рублей.

На сегодняшний день наша разработка показала высокий спрос в сфере обрабатывающих производств, поэтому мы планируем развивать производственные мощности для серийного выпуска аппаратной части ПАК Конвейер. Также важным аспектом является правовая охрана созданного решения на ключевых зарубежных рынках: странах Евросоюза, Китая и США. Поэтому мы решили снова принять участие в конкурсе Фонда содействия инновациям по программе Коммерциализация, который дает возможность в сжатые сроки решить задачи дальнейшего динамичного развития проекта, добавил Иван Корсуков.

Справка:

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

Подробнее..

Как катать релизы несколько раз в день и спать спокойно. Доклад Яндекса

26.02.2021 12:14:08 | Автор: admin
Высокие темпы разработки сопряжены с рисками, влияющими на отказоустойчивость и стабильность особенно если хочется экспериментировать и пробовать разное. Разработчик Маркета Мария Кузнецова рассказала о релизном цикле своей команды от и до, а также о мониторингах и других вещах, позволяющих обновлять сервис со скоростью три релиза в день.



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

(...) Сейчас у нас в среднем выкатывается по три релиза в день.



Стек технологий, которые мы используем типичен для Java-приложений Маркета. Мы используем 11-ю Java, Spring, PostgreSQL для хранения данных, Liquibase для накатывания миграций и Quartz для регулярных Cron-задач. Конечно, у нас реализовано много интеграций с внутренними сервисами.

Начать я хочу с того, как у нас устроен процесс релиза.

1. Релизы


С самого начала проекта мы живем в парадигме trunk-based development. Чтобы код попал в продакшен, нужно поставить пул-реквест и пройти код-ревью. Причем в пул-реквесте запускаются также и прикоммитные проверки, в первую очередь это юнит-тесты и функциональные тесты.

Среды у нас сейчас только две продакшен и тестинг. Когда код-ревью пройдено и код попал в trunk, запускается вот такой релизный пайплайн.



Дальше я покажу все шаги подробнее.



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



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

Всё, релиз раскатили. Идёт шаг проверки. По сути мы заменили ручной регресс тестирования на интеграционные тесты, но также добавили нагрузочные тесты. Самое главное: на каждом шаге мы пытаемся выявить проблему и не допустить, чтобы плохой код попал в продакшен.

Конечно, у нас есть ограничения при выкатке релиза. Парадигма trunk-based development диктует то, что не должно быть долго живущих feature branches, и получается так, что в trunk может оказаться незаконченная функциональность.

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

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

Но такой подход звучит дорого.

2. Feature toggles


Поэтому мы пришли к тому, что стали использовать feature toggles. Toggles с английского переводится как переключатель, и это в точности описывает его предназначение.

if (configurationService.isBooleanEnabled(NEW_FEATURE_ENABLED)) {    //new feature here} else {        //old logic}

Можно выкатить код и пока не использовать его в продашкене, например, ждать поддержки фронтенда или же дальше его реализовывать.

Нам очень важно уметь включать-выключать функциональность по отмашке от коллег. Поэтому свой toggles мы сложили в базу.

public class User {    private Map<String, UserProperty> properties = new HashMap<>();    String getPropertyValue(String key) {        UserPropertyEntity userProperty = properties.get(key);        return userProperty == null ? null : userProperty.getValue();    }}

Поскольку функциональность не всегда нужно сразу включать на всех пользователей, сделали feature toggles на пользователя и тоже положили их в базу. Так мы можем точечно включать функциональность, и это дает возможность провести эксперименты. Например, мы делали новую функциональность под кодовым названием Звонки. Это напоминание курьеру о том, что у него скоро доставка. (...) Такая функциональность сильно перестраивала процесс работы курьера, и нам было важно проверить, как процесс летит в реальном времени, в реальной жизни.

По первым результатам эксперимента мы поняли, что после звонка курьеру некуда записать комментарии от пользователя. Поэтому срочно добавили комментарии и возможность работы с ними. И дальше уже пользователей в эксперимент добавляли только с этой функциональностью. С помощью feature toggles у нас сделана выкатка на процент пользователей. Мы храним версию и требуем от курьера, чтобы у него была установлена именно она. В противном случае просим его обновиться.



Конечно, у такого подхода есть минусы. Toggles скапливаются, и здесь ничего не остается, кроме как их убирать. Также увеличивается сложность тестов, потому что проверять свой код нужно во всех режимах работы toggles. Кроме того, toggles лежат в базе, поэтому получается лишний поход в БД. Здесь ничего не остается, кроме как поставить кэш. Какие плюсы мы за это получаем?



Мы получаем возможность спокойно жить в парадигме trunk based development. Также можем проводить точечные эксперименты на пользователях.

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

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

Остался вопрос про деградацию. Мы обслуживаем офлайн-процесс, и в случае любых проблем с ПО нам нужно сделать так, чтобы самая важная часть функциональности работала. Чтобы курьеры могли выдать посылку, выбить чек и понять, куда ехать дальше.

А вот если, например, у нас возникли проблемы с фото посылки, можно отключить эту функциональность, разобраться с проблемой, устранить ее и включить обратно.

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

3. Метрики и мониторинги


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

Какую информацию мы собираем? На слайд я выписала формальное определение метрик и мониторинга.



Но предлагаю рассмотреть, что это такое, на примере.



Метрика обычно представляется как график, он описывает какой-то параметр нашей системы. А мониторинг это событие, превышение заданного ранее порога и какое-то действие, которое необходимо предпринять при превышении порога.

Какие могут быть действия? Конечно, самое важное уведомить ответственных: написать в мессенджер или позвонить. Реже запустить процесс откатки последнего релиза. У себя в проекте мы такой вариант не используем. Еще есть вариант мониторинга, который мы называем загорается лампочка. Произошло интересующее нас событие, и оно отобразилось в интерфейсе мониторинга. Дальше тоже происходит действие уведомить ответственных или еще что-то.



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

На слайде пример, как нам в базе перестало хватать CPU.

Окей, у нас Java-приложение, и, конечно, стоит собирать информацию о состоянии JVM.



Мы используем Spring, поэтому для решения такой задачи хорошо подходит библиотека Spring Boot Actuator. Она добавляет endpoint в ваше приложение, и к этому endpoint можно обратиться по http и получить необходимую информацию о памяти или что-то еще. Окей, приложение запущено. Дальше оно вообще работает или нет? Что с ним происходит? Можно отправлять запросы в это приложение или нет?

@RestController@RequiredArgsConstructorpublic class MonitoringController {    private final ComplexMonitoring generalMonitoring;    private final ComplexMonitoring pingMonitoring;    @RequestMapping(value = "/ping")    public String ping() {        return pingMonitoring.getResult();    }    @RequestMapping(value = "/monitoring")    public String monitoring() {        return generalMonitoring.getResult();    }}

Такие вещи нужно понимать не только нам, но и балансеру. Для этого мы добавляем в приложение контроллер с двумя методами Ping и Monitoring. Рассмотрим вначале Ping. Он отвечает на вопросы, живо ли приложение, можно ли отправлять на него запросы. И отвечает он это в первую очередь не нам, но балансеру. Но мы же используем этот метод для мониторинга того, живо приложение или нет.

Метод Monitoring дает нам возможность получать сообщения о неполадках в системе. Например, не удалось загрузить знания, с которыми работает приложение, или не удалось их обновить. Или же процесс по распределению заказов между курьерами закончился ошибкой.

public enum MonitoringStatus { OK, WARNING, CRITICAL;}@RequiredArgsConstructorpublic class MonitoringEvent { private final String name; private volatile long validTill; private volatile MonitoringStatus status;}

Мониторинги строятся на системе статусов и приоритетов между ними. Событие мониторинга в минимальном варианте описывается именем, временем, до которого действует мониторинг, но и статусом. По истечении этого времени мониторинг считается пройденным, погашенным и переходит в статус ok.

public interface ComplexMonitoring { void addTemporary(String name, MonitoringStatus status, long validTillMilis); Result getResult(); //тут можно делать проверку для статусов в MonitoringEvent} 

Получается такой простейший интерфейс мониторинга. Добавить или обновить событие и вернуть текущее состояние мониторинга в оговоренном формате.

У нас есть клиент мобильное приложение, нужно понимать, как качественно мы отвечаем клиенту и какая вообще нагрузка на нас идет. Поэтому стоит собирать RPS, тайминги и пятисотки.



Как мерить RPS, понятно из названия. Стоит отметить, что хорошо смотреть как общий RPS по приложению, так и по отдельным методам API. А вот с двумя остальными с таймингами и пятисотками все не так понятно. В каждый момент времени может быть много запросов и, соответственно, много времен ответов. Всю эту информацию нужно агрегировать. Для этого хорошо подходят перцентили. Этот термин пришел к нам из статистики.

Чтобы рассказать, что это такое, на слайде есть нижний график, на нем выделена точка в 400 мс. Это график 99 перцентиля какого-то метода из нашего API. Что значат эти 400 мс? Что в этот момент 99% запросов отрабатывали не хуже 400 мс.

Остались пятисотки. Не стоит мерить просто количество пятисоток, потому что не будет понятен масштаб. Стоит мерить долю или процент. Но собирать можно не только пятисотые коды ответа, но и любые другие коды, которые вам могут быть интересны.

Как мы собираем RPS, тайминги и пятисотки? Когда запрос оказался у нас в инфраструктуру, он попадает на L7 balancer. А дальше он не сразу попадает в приложение, перед этим есть nginx.



А вот уже из nginx он попадает в приложение. У nginx есть access.log, в который можно собирать всю необходимую информацию. Это коды ответа, время ответа и сам запрос.



Часто, когда возникают проблемы с временем ответа или пятисотки, стоит посмотреть, что в это время было с хранилищем.

У нас PostgreSQL, поэтому мы именно его мониторим и смотрим. Мы строим все эти графики не сами они нам предоставляются облаком, в котором развернута наша база. Что у нас есть? Количество запросов, транзакций, лаг репликации, среднее время транзакции и так далее. На самом деле на этих графиках представлен факап, который у нас произошел.



Вообще-то мы работаем с реальным процессом, и он нам тоже диктует, на что нам нужно посмотреть. Регистрацию нежелательных событий мы уже рассматривали, для этого хорошо подходит метод Monitoring, он был ранее.

Осталась статистика по хранилищу. Хранилище вообще может многое рассказать о состоянии нашей системы. Можно считать показатели: как, например, количество заказов за день, так и просто попадание в интервал доставки. Можно искать противоречия статусной модели. Так мы отслеживаем чеки, с которыми возникли проблемы.

public class MetricQueryRealJobExecutor { private static final RowMapper<Metric> METRIC_ROW_MAPPER = BeanPropertyRowMapper.newInstance(Metric.class); private final JdbcTemplate jdbcTemplate; public void doJob(MetricQuery task) { List<Metric> metrics = jdbcTemplate.query(task.getQuery(), METRIC_ROW_MAPPER); metrics.forEach(metric -> KEY_VALUE_LOG.info(KeyValueLogFormat.format(metric)) ); } @Data private static class Metric { private String key; private double value; }} 

Но далеко не всегда на такие мониторинги должна реагировать разработка. Очень часто на них вначале нужна реакция бизнеса. Вообще задача получения статистики по хранилищу выходит однотипной. Нужно сходить в базу, рассчитать статистику, а результат записать в key-value-лог. Поэтому мы сделали обертку для таких задач.

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

Итого, что мы получаем?

  • Плохой код не должен попадать в продакшен, для этого мы ставим всевозможные преграды: тесты, стрельбы, мониторинги. Задача без тестов не считается сделанной. Лучше заниматься оптимизацией работы тестов, чем получить проблему в продакшене.
  • Feature toggles помогают нам управлять логикой работы бэкенда, а мы, в свою очередь, можем легко управлять toggles, и их плюсы все-таки перевешивают минусы.
  • Мы должны уметь быстро обнаруживать проблему, в идеале раньше, чем ее заметят наши пользователи. Но ее мало обнаружить, нужно еще ее интерпретировать. Поэтому собирайте много метрик о состоянии системы. Это помогает в поиске проблемы.

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

Добавляем modbus в Embox RTOS и используем на STM32 и не только

17.03.2021 18:23:31 | Автор: admin
image
Нас часто спрашивают, чем Embox отличается от других ОС для микроконтроллеров, например, FreeRTOS? Сравнивать проекты между собой, конечно, правильно. Но параметры, по которым порой предлагают сравнение, лично меня повергают в легкое недоумение. Например, сколько нужно памяти для работы Embox? А какое время переключения между задачами? А в Embox поддерживается modbus? В данной статье на примере вопроса про modbus мы хотим показать, что отличием Embox является другой подход к процессу разработки.

Давайте разработаем устройство, в составе которого будет работать в том числе modbus server. Наше устройство будет простым. Ведь оно предназначено только для демонстрации modbus, Данное устройство будет позволять управлять светодиодами по протоколу Modbus. Для связи с устройством будем использовать ethernet соединение.

Modbus открытый коммуникационный протокол. Широко применяется в промышленности для организации связи между электронными устройствами. Может использоваться для передачи данных через последовательные линии связи RS-485, RS-422, RS-232 и сети TCP/IP (Modbus TCP).

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

Одной из самых популярных реализаций протокола modbus является открытый проект libmodbus. Его и будем использовать. Это позволит сократить время разработки и уменьшить количество ошибок. При этом мы сможем сосредоточиться на реализации бизнес логики, а не на изучении протокола.

Наш проект будем вести в отдельном репозитории. При желании все можно скачать и воспроизвести самостоятельно.

Разработка прототипа на Linux


Начнем с разработки прототипа на хосте. Для того чтобы можно было использовать libmodbus в качестве библиотеки его нужно скачать, сконфигурировать и собрать.
Для этих целей я набросал Makefile

libmodbus-$(LIBMODBUS_VER).tar.gz:    wget http://libmodbus.org/releases/libmodbus-$(LIBMODBUS_VER).tar.gz$(BUILD_BASE)/libmodbus/lib/pkgconfig/libmodbus.pc : libmodbus-$(LIBMODBUS_VER).tar.gz    tar -xf libmodbus-$(LIBMODBUS_VER).tar.gz    cd libmodbus-$(LIBMODBUS_VER); \    ./configure --prefix=$(BUILD_BASE)/libmodbus --enable-static --disable-shared; \    make install; cd ..;


Собственно из параметров конфигурации мы используем только prefix чтобы собрать библиотеку локально. А поскольку мы хотим использовать библиотеку не только на хосте, соберем ее статическую версию.

Теперь нам нужен modbus сервер. В проекте libmodbus есть примеры, давайте на основе какого-нибудь простого сервера сделаем свою реализацию.

    ctx = modbus_new_tcp(ip, port);    header_len = modbus_get_header_length(ctx);    query = malloc(MODBUS_TCP_MAX_ADU_LENGTH);    modbus_set_debug(ctx, TRUE);    mb_mapping = mb_mapping_wrapper_new();    if (mb_mapping == NULL) {        fprintf(stderr, "Failed to allocate the mapping: %s\n",                modbus_strerror(errno));        modbus_free(ctx);        return -1;    }    listen_socket = modbus_tcp_listen(ctx, 1);    for (;;) {        client_socket = modbus_tcp_accept(ctx, &listen_socket);        if (-1 == client_socket) {            break;        }        for (;;) {            int query_len;            query_len = modbus_receive(ctx, query);            if (-1 == query_len) {                /* Connection closed by the client or error */                break;            }            if (query[header_len - 1] != MODBUS_TCP_SLAVE) {                continue;            }            mb_mapping_getstates(mb_mapping);            if (-1 == modbus_reply(ctx, query, query_len, mb_mapping)) {                break;            }            leddrv_updatestates(mb_mapping->tab_bits);        }        close(client_socket);    }    printf("exiting: %s\n", modbus_strerror(errno));    close(listen_socket);    mb_mapping_wrapper_free(mb_mapping);    free(query);    modbus_free(ctx);


Здесь все стандартно. Пара мест, которые представляют интерес, это функции mb_mapping_getstates и leddrv_updatestates. Это как раз функционал, который и реализует наше устройство.

static modbus_mapping_t *mb_mapping_wrapper_new(void) {    modbus_mapping_t *mb_mapping;    mb_mapping = modbus_mapping_new(LEDDRV_LED_N, 0, 0, 0);    return mb_mapping;}static void mb_mapping_wrapper_free(modbus_mapping_t *mb_mapping) {    modbus_mapping_free(mb_mapping);}static void mb_mapping_getstates(modbus_mapping_t *mb_mapping) {    int i;    leddrv_getstates(mb_mapping->tab_bits);    for (i = 0; i < mb_mapping->nb_bits; i++) {        mb_mapping->tab_bits[i] = mb_mapping->tab_bits[i] ? ON : OFF;    }}


Таким образом, нам нужны leddrv_updatestates, которая задает состояние светодиодов, и leddrv_getstates, которая получает состояние светодиодов.
static unsigned char leddrv_leds_state[LEDDRV_LED_N];int leddrv_init(void) {    static int inited = 0;    if (inited) {        return 0;    }    inited = 1;    leddrv_ll_init();    leddrv_load_state(leddrv_leds_state);    leddrv_ll_update(leddrv_leds_state);    return 0;}...int leddrv_getstates(unsigned char leds_state[LEDDRV_LED_N]) {    memcpy(leds_state, leddrv_leds_state, sizeof(leddrv_leds_state));    return 0;}int leddrv_updatestates(unsigned char new_leds_state[LEDDRV_LED_N]) {    memcpy(leddrv_leds_state, new_leds_state, sizeof(leddrv_leds_state));    leddrv_ll_update(leddrv_leds_state);    return 0;}


Так как мы хотим чтобы наше ПО работало и на плате и на хосте, нам понадобятся разные реализации функций установки и получения состояния светодиодов. Давайте для хоста хранить состояние в обычном файле. Это позволит получать состояние светодиодов в других процессах.

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

void leddrv_ll_update(unsigned char leds_state[LEDDRV_LED_N]) {    int i;    int idx;    char buff[LEDDRV_LED_N * 2];        for (i = 0; i < LEDDRV_LED_N; i++) {        char state = !!leds_state[i];        fprintf(stderr, "led(%03d)=%d\n", i, state);        buff[i * 2] = state + '0';        buff[i * 2 + 1] = ',';    }    idx = open(LED_FILE_NAME, O_RDWR);    if (idx < 0) {        return;    }    write(idx, buff, (LEDDRV_LED_N * 2) - 1);    close(idx);}...void leddrv_load_state(unsigned char leds_state[LEDDRV_LED_N]) {    int i;    int idx;    char buff[LEDDRV_LED_N * 2];    idx = open(LED_FILE_NAME, O_RDWR);    if (idx < 0) {        return;    }    read(idx, buff, (LEDDRV_LED_N * 2));    close(idx);        for (i = 0; i < LEDDRV_LED_N; i++) {        leds_state[i] = buff[i * 2] - '0';    }}


Нам нужно указать файл где будет сохранено начальное состояние светодиодов. Формат файла простой. Через запятую перечисляются состояние светодиодов, 1 светодиод включен, а 0 -выключен. В нашем устройстве 80 светодиодов, точнее 40 пар светодиодов. Давайте предположим, что по умолчанию четные светодиоды будут выключены а нечетные включены. Содержимое файла

0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1


Запускаем сервер
./led-serverled(000)=0led(001)=1...led(078)=0led(079)=1


Теперь нам нужен клиент для управления нашим устройством. Его тоже очень просто разрабатываем на базе примера из libmodbus

ctx = modbus_new_tcp(ip, port);    if (ctx == NULL) {        fprintf(stderr, "Unable to allocate libmodbus context\n");        return -1;    }    modbus_set_debug(ctx, TRUE);    modbus_set_error_recovery(ctx,            MODBUS_ERROR_RECOVERY_LINK |            MODBUS_ERROR_RECOVERY_PROTOCOL);    if (modbus_connect(ctx) == -1) {        fprintf(stderr, "Connection failed: %s\n",                modbus_strerror(errno));        modbus_free(ctx);        return -1;    }    if (1 == modbus_write_bit(ctx, bit_n, bit_value)) {        printf("OK\n");    } else {        printf("FAILED\n");    }    /* Close the connection */    modbus_close(ctx);    modbus_free(ctx);


Запускаем клиент. Установим 78 светодиод, который по умолчанию выключен

./led-client set 78Connecting to 127.0.0.1:1502[00][01][00][00][00][06][FF][05][00][4E][FF][00]Waiting for a confirmation...<00><01><00><00><00><06><FF><05><00><4E><FF><00>OK


На сервере увидим
...led(076)=0led(077)=1led(078)=1led(079)=1Waiting for an indication...ERROR Connection reset by peer: read


То есть светодиод установлен. Давайте выключим его.
./led-client clr 78Connecting to 127.0.0.1:1502[00][01][00][00][00][06][FF][05][00][4E][00][00]Waiting for a confirmation...<00><01><00><00><00><06><FF><05><00><4E><00><00>OK


На сервере увидим сообщение об изменении
...led(076)=0led(077)=1led(078)=0led(079)=1Waiting for an indication...ERROR Connection reset by peer: read


Запустим http сервер. О разработке веб-сайтов мы рассказывали в статье. К тому же веб-сайт нам нужен только для более удобной демонстрации работы modbus. Поэтому не буду сильно вдаваться в подробности. Сразу приведу cgi скрипт

#!/bin/bashecho -ne "HTTP/1.1 200 OK\r\n"echo -ne "Content-Type: application/json\r\n"echo -ne "Connection: close\r\n"echo -ne "\r\n"if [ $REQUEST_METHOD = "GET" ]; then    echo "Query: $QUERY_STRING" >&2    case "$QUERY_STRING" in        "c=led_driver&a1=serialize_states")            echo [ $(cat ../emulate/conf/leds.txt) ]            ;;        "c=led_driver&a1=serialize_errors")            echo [ $(printf "0, %.0s" {1..79}) 1 ]            ;;        "c=led_names&a1=serialize")            echo '[ "one", "two", "WWWWWWWWWWWWWWWW", "W W W W W W W W " ]'            ;;    esacelif [ $REQUEST_METHOD = "POST" ]; then    read -n $CONTENT_LENGTH POST_DATA    echo "Posted: $POST_DATA" >&2fi


И напомню что запустить можно с помощью любого http сервера с поддержкой CGI. Мы используем встроенный в python сервер. Запускаем следующей командой
python3 -m http.server --cgi -d .


Откроем наш сайт в браузере


Установим 78 светодиод с помощью клиента
./led-client -a 127.0.0.1 set 78Connecting to 127.0.0.1:1502[00][01][00][00][00][06][FF][05][00][4E][FF][00]Waiting for a confirmation...<00><01><00><00><00><06><FF><05><00><4E><FF><00>OK


сбросим 79 светодиод
./led-client -a 127.0.0.1 clr 79Connecting to 127.0.0.1:1502[00][01][00][00][00][06][FF][05][00][4F][00][00]Waiting for a confirmation...<00><01><00><00><00><06><FF><05><00><4F><00><00>OK


На сайте увидим разницу


Собственно все, на Linux наша библиотека прекрасно работает.

Адаптация к Embox и запуск на эмуляторе


Библиотека libmodbus


Теперь нам нужно перенести код в Embox. начнем с самого проекта libmodbus.
Все просто. Нам нужно описание модуля (Mybuild)
package third_party.lib@Build(script="$(EXTERNAL_MAKE)")@BuildArtifactPath(cppflags="-I$(ROOT_DIR)/build/extbld/third_party/lib/libmodbus/install/include/modbus")module libmodbus {    @AddPrefix("^BUILD/extbld/^MOD_PATH/install/lib")    source "libmodbus.a"    @NoRuntime depends embox.compat.posix.util.nanosleep}


Мы с помощью аннотации Build(script="$(EXTERNAL_MAKE)") указываем что используем Makefile для работы с внешними проектами.

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

И говорим что нам нужна библиотека source libmodbus.a

PKG_NAME := libmodbusPKG_VER  := 3.1.6PKG_SOURCES := http://libmodbus.org/releases/$(PKG_NAME)-$(PKG_VER).tar.gzPKG_MD5     := 15c84c1f7fb49502b3efaaa668cfd25ePKG_PATCHES := accept4_disable.patchinclude $(EXTBLD_LIB)libmodbus_cflags = -UHAVE_ACCEPT4$(CONFIGURE) :    export EMBOX_GCC_LINK=full; \    cd $(PKG_SOURCE_DIR) && ( \        CC=$(EMBOX_GCC) ./configure --host=$(AUTOCONF_TARGET_TRIPLET) \        prefix=$(PKG_INSTALL_DIR) \        CFLAGS=$(libmodbus_cflags) \    )    touch $@$(BUILD) :    cd $(PKG_SOURCE_DIR) && ( \        $(MAKE) install MAKEFLAGS='$(EMBOX_IMPORTED_MAKEFLAGS)'; \    )    touch $@


Makefile для сборки тоже простой и очевидный. Единственное, отмечу что используем внутренний компилятор ($(EMBOX_GCC) ) Embox и в качестве платформы (--host) передаем ту, которая задана в Embox ($(AUTOCONF_TARGET_TRIPLET)).

Подключаем проект к Embox


Напомню, что для удобства разработки мы создали отдельный репозиторий. Для того чтобы подключить его к Embox достаточно указать Embox где лежит внешний проект.
Делается это с помощью команды
make ext_conf EXT_PROJECT_PATH=<path to project> 

в корне Embox. Например,
 make ext_conf EXT_PROJECT_PATH=~/git/embox_project_modbus_iocontrol


modbus-server


Исходный код modbus сервера не требует изменений. То есть мы используем тот же код, который разработали на хосте. Нам нужно добавить Mybuild
package iocontrol.modbus.cmd@AutoCmd@Build(script="true")@BuildDepends(third_party.lib.libmodbus)@Cmd(name="modbus_server")module modbus_server {    source "modbus_server.c"    @NoRuntime depends third_party.lib.libmodbus}


В котором с помощью аннотаций мы укажем что это у нас команда, а также что она зависит от библиотеки libmodbus.

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

Нам также нужно собрать нашу систему вместе с modbus сервером
Добавляем наши модули в mods.conf
    include iocontrol.modbus.http_admin    include iocontrol.modbus.cmd.flash_settings    include iocontrol.modbus.cmd.led_names    include third_party.lib.libmodbus    include iocontrol.modbus.cmd.modbus_server    include iocontrol.modbus.cmd.led_driver    include embox.service.cgi_cmd_wrapper(cmds_check=true, allowed_cmds="led_driver led_names flash_settings")    include iocontrol.modbus.lib.libleddrv_ll_stub


А наш файл leds.txt со статусами светодиодов кладем в корневую файловую систему. Но так как нам нужен изменяемый файл, давайте добавим RAM disk и скопируем наш файл на этот диск. Содержимое system_start.inc
"export PWD=/","export HOME=/","netmanager","service telnetd","service httpd http_admin","ntpdate 0.europe.pool.ntp.org","mkdir -v /conf","mount -t ramfs /dev/static_ramdisk /conf","cp leds.txt /conf/leds.txt","led_driver init","service modbus_server","tish",


Этого достаточно запустим Embox на qemu
./scripts/qemu/auto_qemu

modbus и httpd сервера запускаются автоматически при старте. Установим такие же значения с помощью modbus клиента, только указав адрес нашего QEMU (10.0.2.16)
./led-client -a 10.0.2.16 set 78Connecting to 10.0.2.16:1502[00][01][00][00][00][06][FF][05][00][4E][FF][00]Waiting for a confirmation...<00><01><00><00><00><06><FF><05><00><4E><FF><00>OK


и соответственно
./led-client -a 10.0.2.16 clr 79Connecting to 10.0.2.16:1502[00][01][00][00][00][06][FF][05][00][4F][00][00]Waiting for a confirmation...<00><01><00><00><00><06><FF><05><00><4F><00><00>


Откроем браузер


Как и ожидалось все тоже самое. Мы можем управлять устройством через modbus протокол уже на Embox.

Запуск на микроконтроллере


Для запуска на микроконтроллере будем использовать STM32F4-discovery. На вышеприведенных скриншотах страниц браузера, видно что используется 80 ног вывода, объединенные в пары, и еще можно заметить что у этих пар есть другие свойства, например можно задать имя, или пара может подсвечиваться. На самом деле, код был взят из реального проекта и из него для упрощения были убраны лишние части. 80 выходных пинов было получено с помощью дополнительных микросхем сдвиговых регистров.

Но на плате STM32F4-discovery всего 4 светодиода. Было бы удобно задавать количество светодиодов, чтобы не модифицировать исходный код В Embox есть механизм позволяющий параметризировать модули. Нужно в описании модуля (Mybuild) добавить опцию
package iocontrol.modbus.libstatic module libleddrv {    option number leds_quantity = 80...}


И можно будет использовать в коде
#ifdef __EMBOX__#include <framework/mod/options.h>#include <module/iocontrol/modbus/lib/libleddrv.h>#define LEDDRV_LED_N OPTION_MODULE_GET(iocontrol__modbus__lib__libleddrv,NUMBER,leds_quantity)#else#define LEDDRV_LED_N 80#endif


При этом менять этот параметр можно будет указав его в файле mods.conf
    include  iocontrol.modbus.lib.libleddrv(leds_quantity=4)


если параметр не указывается, то используется тот который задан в модуле по умолчанию, то есть 80.

Нам нужно еще управлять реальными линиями вывода. Код следующий
struct leddrv_pin_desc {    int gpio; /**< port */    int pin; /**< pin mask */};static const struct leddrv_pin_desc leds[] = {    #include <leds_config.inc>};void leddrv_ll_init(void) {    int i;    for (i = 0; i < LEDDRV_LED_N; i++) {        gpio_setup_mode(leds[i].gpio, leds[i].pin, GPIO_MODE_OUTPUT);    }}void leddrv_ll_update(unsigned char leds_state[LEDDRV_LED_N]) {    int i;    for (i = 0; i < LEDDRV_LED_N; i++) {        gpio_set(leds[i].gpio, leds[i].pin,                leds_state[i] ? GPIO_PIN_HIGH : GPIO_PIN_LOW);    }}


В файле mods.conf нам нужна конфигурация для нашей платы. К ней добавляем наши модули
    include iocontrol.modbus.http_admin    include iocontrol.modbus.cmd.flash_settings    include iocontrol.modbus.cmd.led_names    include third_party.lib.libmodbus    include iocontrol.modbus.cmd.modbus_server    include iocontrol.modbus.cmd.led_driver    include embox.service.cgi_cmd_wrapper(cmds_check=true, allowed_cmds="led_driver led_names flash_settings")    include iocontrol.modbus.lib.libleddrv(leds_quantity=4)    include iocontrol.modbus.lib.libleddrv_ll_stm32_f4_demo


По сути дела, те же модули как и для ARM QEMU, за исключением конечно драйвера.

Собираем, прошиваем, запускаем. И с помощью того же modbus клиента управляем светодиодами. Нужно только поставить правильный адрес, и не забыть что у нас всего 4 светодиода на плате.

Работу на плате stm32f4-discovery можно увидеть на этом коротком видео


Выводы


На этом простом примере мы постарались показать, в чем же основное отличие Embox от других ОС для микроконтроллеров. В том числе которые имеют POSIX совместимость. Ведь мы по сути дела, взяли готовый модуль, разработали бизнес логику на Linux используя при этом несколько приложений. И запустили все это на нашей целевой платформе. Тем самым существенно упростив и ускорив саму разработку.

Да, конечно, приложение демонстрационное и не сложное. Сам протокол modbus так же можно было бы реализовать самостоятельно. Но в этом случае нам нужно было бы разбираться в протоколе modbus. А наш подход позволяет сосредоточится каждому специалисту на своей части. Ну и конечно, большинство проблем решаются на хосте, что гораздо более удобно чем разрабатывать напрямую на плате.
Подробнее..

Computer Science Center открыл приём заявок на новый учебный год

24.03.2021 16:21:32 | Автор: admin

До 10 апреля продолжается набор на вечерние курсы по математике и программированию в CS центре. Computer Science Center это совместный проект Школы анализа данных Яндекса, JetBrains и Computer Science клуба при ПОМИ РАН. Курсы проходят очно в Санкт-Петербурге и Новосибирске, жители других городов могут заниматься дистанционно. Обучение в Computer Science Center бесплатное.

На какие направления открыт набор, как поступить, и зачем вообще оно вам надо, подробно рассказываем под катом.

Большинство наших студентов учатся в вузах или работают, поэтому расписание составлено так, чтобы занятия можно было совмещать с основной деятельностью. Однако будьте осторожны: на учёбу в Computer Science Center придется тратить не менее 15 часов в неделю. Если у вас недостаточно времени (или мотивации), советуем начать с онлайн-курсов на платформе Stepik.

Длительность обучения два или три года по выбору студента. За это время нужно пройти не менее 12 курсов: часть из них обязательная, часть вы выбираете сами. Обязательные курсы зависят от направления обучения. Для поступающих в 2021 году будут открыты три направления (позже вы сможете выбрать одно из них или даже несколько сразу):

  • Computer Science,

  • Data Science,

  • Software Engineering.

Подробнее обо всех направлениях на сайте. Для выпуска студенты проходят три семестра практики или научно-исследовательской работы (примеры есть на этой странице).

Чтобы стать студентом или студенткой CS центра, нужно справиться с вступительными испытаниями:

  • заполните анкету на сайте до 10 апреля,

  • решите задания онлайн-теста до 11 апреля,

  • участвуйте в онлайн-экзамене в конце апреля-начале мая,

  • пройдите собеседование в мае-июне.

Для кандидатов, которые успешно прошли отбор, занятия начнутся в сентябре 2021 года.

Если вы еще не решили, стоит ли ввязываться в эту авантюру, посмотрите на отзывы выпускников:

Станислав Гордеев, разработчик игрового движка в People Can Fly (Варшава), выпускник 2018 года направления Software Engineering:

Когда-то давно, в сентябре 2014, будучи ещё студентом-физиком, я случайно наткнулся на хабро-статью про онлайн-курсы на Stepik, заинтересовался и прошёл курсы по C++ и алгоритмам, кроме того, узнал о CSC. На тот момент у меня помимо интереса появилась довольно амбициозная цель попасть в большой gamedev. Поступление оказалось совсем не лёгким и до последнего дня с письмом о зачислении я не верил, что всё получится. Моё обучение началось в 2016, и с тех пор я семимильными шагами приближался к своей цели. А все из-за неповторимой атмосферы знаний, амбиций, дружелюбия, которая не позволяет сидеть на месте, которая заставляет поверить в себя. Сейчас я с уверенностью могу сказать, что моя цель осуществилась в первую очередь благодаря CS центру, потому что это больше, чем просто курсы. Старайтесь, и у вас всё получится.

Анна Атаманова, разработчица в Яндексе, выпускница 2016 года направления Data Science:

Почему СS центр это дико круто:

курсы! (всё новое и интересное по основным направлением центра тут-тут-тут);

увлечённые своим делом преподаватели (таких людей в любом случае приятно слушать);

мотивированные, заинтересованные студенты (да у них можно узнать чуть ли не больше, чем на паре);

проекты! стажировки! (опыт, реальные задачи);

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

Артемий Пестрецов, разработчик в JetBrains, преподаватель CS центра, выпускник 2019 года направлений Data Science и Software Engineering:

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

Все отзывы можно посмотреть на этой странице.


Для тех, кто нашел в себе силы дочитать до этого места, бонус! Перед тестом вы можете потренироваться и порешать задачи прошлых лет. Но помните, что 10 апреля тренажёр превратится в тыкву, так что лучше не откладывать.

Ну и самое последнее: 31 марта состоится День открытых дверей для поступающих в CS центр. Кураторы подробно расскажут про направления и формат обучения и ответят на все ваши вопросы. Зарегистрироваться можно здесь.

Ждем будущих студентов Computer Science Center!

Подробнее..

Перевод Современный C нас не спасет

26.04.2021 12:16:37 | Автор: admin

Я часто критикую небезопасные при работе с памятью языки, в основном C и C++, и то, как они провоцируют необычайное количество уязвимостей безопасности. Моё резюме, основанное на изучении доказательств из многочисленных крупных программных проектов на С и С++, заключается в том, что нам необходимо мигрировать нашу индустрию на безопасные для памяти языки по умолчанию (такие как Rust и Swift). Один из ответов, который я часто получаю, заключается в том, что проблема не в самих С и С++, разработчики просто неправильно их "готовят". В частности, я часто получаю в защиту C++ ответ типа: "C++ безопасен, если вы не используете унаследованную от C функциональность" [1] или аналогичный ему, что если вы используете типы и идиомы современного C++, то вы будете застрахованы от уязвимостей типа повреждения памяти, от которых страдают другие проекты.

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


Скрытая ссылка и use-after-free

#include <iostream>#include <string>#include <string_view>int main() {  std::string s = "Hellooooooooooooooo ";  std::string_view sv = s + "World\n";  std::cout << sv;}

Вот что здесь происходит, s + "World\n" создает новую строку std::string, а затем преобразует ее в std::string_view. На этом этапе временная std::string освобождается, но sv все еще указывает на память, которая ранее ей принадлежала. Любое последующее использование sv является use-after-free уязвимостью. Упс! В С++ не хватает средств, чтобы компилятор знал, что sv захватывает ссылку на что-то, где ссылка живет дольше, чем донор. Эта же проблема затрагивает std::span, также чрезвычайно современный тип С++.

Другой забавный вариант включает в себя использование лямбда в С++ для сокрытия ссылки:

#include <memory>#include <iostream>#include <functional>std::function<int(void)> f(std::shared_ptr<int> x) {    return [&]() { return *x; };}int main() {    std::function<int(void)> y(nullptr);    {        std::shared_ptr<int> x(std::make_shared<int>(4));        y = f(x);    }    std::cout << y() << std::endl;}

Здесь [&] в f лямбда захватывает значение по ссылке. Затем в main, x выходит за пределы области видимости, уничтожая последнюю ссылку на данные и освобождая их. В этот момент y содержит висячий указатель. Это происходит, несмотря на наше тщательное использование умных указателей. И да, люди действительно пишут код, использующий std::shared_ptr&, часто как попытку избежать дополнительного приращения и уменьшения количеств в подсчитывающих ссылках.

Разыменование std::optional

std::optional представляет собой значение, которое может присутствовать, а может и не присутствовать, часто заменяя магические значения (например, -1 или nullptr). Он предлагает такие методы, как value(), которые извлекают T, которое он содержит, и вызывает исключение, если optional пуст. Однако, он также определяет operator* и operator->. Эти методы также обеспечивают доступ к хранимому T, однако они не проверяют, содержит ли optional значение или нет.

Следующий код, например, просто возвращает неинициализированное значение:

#include <optional>int f() {    std::optional<int> x(std::nullopt);    return *x;}

Если вы используете std::optional в качестве замены nullptr, это может привести к еще более серьезным проблемам! Разыменование nullptr дает segfault (что не является проблемой безопасности, кроме как в старых ядрах). Однако, разыменование nullopt дает вам неинициализированное значение в качестве указателя, что может быть серьезной проблемой с точки зрения безопасности. Хотя T* также бывает с неинициализированным значением, это гораздо менее распространено, чем разыменование указателя, который был правильно инициализирован nullptr.

И нет, это не требует использования сырых указателей. Вы можете получить неинициализированные/дикие указатели и с помощью умных указателей:

#include <optional>#include <memory>std::unique_ptr<int> f() {    std::optional<std::unique_ptr<int>> x(std::nullopt);    return std::move(*x);}

Индексация std::span

std::span обеспечивает эргономичный способ передачи ссылки на непрерывный кусок памяти вместе с длиной. Это позволяет легко писать код, который работает с несколькими различными типами; std::span может указывать на память, принадлежащую std::vector, std::array<uint8_t, N> или даже на сырой указатель. Некорректная проверка границ - частый источник уязвимостей безопасности, и во многих смыслах span помогает, гарантируя, что у вас всегда будет под рукой длина.

Как и все структуры данных STL, метод span::operator[] не выполняет проверку границ. Это печально, так как operator[] является наиболее эргономичным и стандартным способом использования структур данных. std::vector и std::array можно, по крайней мере, теоретически безопасно использовать, так как они предлагают метод at(), который проверяет границы (на практике я этого никогда не видел, но можно представить себе проект, использующий инструмент статического анализа, который просто запрещает вызовы std::vector::operator[]). span не предлагает метод at(), или любой другой подобный метод, который выполняет проверку границ.

Интересно, что как Firefox, так и Chromium в бэкпортах std::span выполняют проверку границ в operator[], и, следовательно, никогда не смогут безопасно мигрировать на std::span.

Заключение

Идиомы современного C++ вводят много изменений, которые могут улучшить безопасность: умные указатели лучше выражают ожидаемое время жизни, std::span гарантирует, что у вас всегда под рукой правильная длина, std::variant обеспечивает более безопасную абстракцию для union. Однако современный C++ также вводит новые невообразимые источники уязвимостей: захват лямбд с эффектом use-after-free, неинициализированные optional и не проверяющие границы span.

Мой профессиональный опыт написания относительно современного С++ кода и аудита Rust-кода (включая Rust-код, который существенно использует unsafe) заключается в том, что безопасность современного С++ просто не сравнится с языками, в который безопасностью памяти включена по умолчанию, такими как Rust и Swift (или Python и Javascript, хотя я в реальности редко встречаю программы, для который имеет смысл выбора - писать их на Python, либо на C++).

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

[1] Это надо понимать, что речь идет о сырых указателях, массивах-как-указателях, ручном malloc/free и другом подобном функционале Си. Однако, думаю, стоит признать, что, учитывая, что Си++ явно включил в свою спецификацию Си, на практике большинство Си++-кода содержит некоторые из этих "фич".

Подробнее..

Как подружить Redis Cluster c Testcontainers?

20.06.2021 12:09:43 | Автор: admin
В 26-м выпуске NP-полного подкаста я рассказывал, что начал переводить один из своих сервисов из Redis Sentinel на Redis Cluster. На этой неделе я захотел потестировать данный код, и, конечно же, выбрал Testcontainers для этого. К сожалению, Redis Cluster в тестовых контейнерах не завелся из коробки, и мне пришлось вставить несколько костылей. О них и пойдет речь далее.



Вводные


Сначала я бы хотел описать все вводные, а потом рассказать про костыли. Мой проект построен на Spring Boot. Для взаимодействия с редисом используется Lettuce клиент. Для тестирования testcontainers-java с JUnit. Версия обоих редисов 6. В общем, всё типичное, нет ничего особенного с точки зрения стека.

Если кто-то еще не знаком с testcontainers, то пара слов о них. Это библиотека для интеграционного тестирования. Она построена на другой библиотеке https://github.com/docker-java/docker-java. Тестконтейнеры, по сути говоря, помогают быстро и просто запускать контейнеры с разными зависимостями в ваших интеграционных тестах. Обычно это базы данных, очереди и другие сложные системы. Некоторые люди используют testcontainers и для запуска своих сервисов, от которых зависит тестируемое приложение (чтобы тестировать микросервисное взаимодействие).

Про Redis Cluster


Redis Cluster это одна из нескольких реализаций распределнного режима Редиса. К сожалению, в Редисе нет единого правильного способа, как масштабировать базу. Есть Sentinel, есть Redis Cluster, а еще ребята активно разрабатывают RedisRaft распредеделенный редис на базе протокола консенсуса Raft (у них там своя реализация, которая, как они сами заявляют, не совсем каноничный Рафт, но конкретно для Redis самое то).

В целом, про Redis Cluster есть две замечательных статьи на официальном сайте https://redis.io/topics/cluster-tutorial и https://redis.io/topics/cluster-spec. Большинство деталей описано там.

Для использования Redis Cluster в testcontainers важно знать несколько вещей из документации. Во-первых, Redis Cluster использует gossip протокол поэтому каждый узел кластера имеет TCP-соединение со всеми другими узлами. Поэтому, между нодами должна быть сетевая связность, даже в тестах.

Вторая важная штука, которую надо знать при тестировании это наличие в Redis Cluster bootstrap узлов для конфигурации. То есть, вы в настройках можете задать лишь подмножество узлов, которые будут использоваться для старта приложения. В последствие, Redis клиент сам получит Топологию кластера через взаимодействие с Редисом. Исходя из этого, получается вторая особенность тестируемое приложение должно иметь сетевую связность с теми Redis URI, которые будут аннонсированы со стороны редис кластера (кстати, эти адреса можно сконфигурировать через cluster-announce-port и cluster-announce-ip).

Про костыли с Redis Cluster и testcontainers


Для тестирования я выбрал довольно популярный docker-образ https://github.com/Grokzen/docker-redis-cluster. Он не подходит для продакшена, но очень прост в использовании в тестах. Особенность этого образа все Редисы (а их 6 штук, по умолчанию 3 мастера и 3 слейва) будут подняты в рамках одного контейнера. Поэтому, мы автоматически получаем сетевую связность между узлами кластера из коробки. Осталось решить вторую из двух проблем, связанную с получением приложением топологии кластера.

Я не хотел собирать свой docker-образ, а выбранный мной image не предоставляет возможности задавать настройки cluster-announce-port и cluster-announce-ip. Поэтому, если ничего не делать дополнительно, при запуске тестов вы увидите примерно такие ошибки:

Unable to connect to [172.17.0.3/<unresolved>:7003]: connection timed out: /172.17.0.3:7003


Ошибка означает, что мы со стороны приложения пытаеся приконнектится к Узлу редис кластера, используя IP докер контейнера и внутренний порт (порт 7003 используется данным узлом, но наружу он отображается на какой-то случайный порт, который мы и должны использовать в нашем приложении; внутренний порт, по понятным причинам, не доступен из вне). Что касается данного IP-адреса он доступен для приложения, если это Linux, и он не доступен для приложения, если это MacOs/Windows (из-за особенностей реализации докера на этих ОС).

Решение проблемы (а-ка костыль) я собрал по частичкам из разных статей. А давайте сделаем NAT RedisURI на стороне приложения. Ведь это нужно именно для тестов, и тут не так страшно вставлять такой ужас. Решение, на самом деле, состоит из пары строк (огромное спасибо Спрингу и Lettuce, где можно сконфигурировать практически всё, только и успевай, как переопределять бины).

public SocketAddress resolve(RedisURI redisURI) {    Integer mappedPort = redisClusterNatPortMapping.get(redisURI.getPort());    if (mappedPort != null) {        SocketAddress socketAddress = redisClusterSocketAddresses.get(mappedPort);        if (socketAddress != null) {            return socketAddress;        }        redisURI.setPort(mappedPort);    }    redisURI.setHost(DockerClientFactory.instance().dockerHostIpAddress());    SocketAddress socketAddress = super.resolve(redisURI);    redisClusterSocketAddresses.putIfAbsent(redisURI.getPort(), socketAddress);    return socketAddress;}


Полный код выложен на гитхаб https://github.com/Hixon10/spring-redis-cluster-testcontainers.

Идея кода супер простая. Будем хранить две Map. В первой маппинг между внутренними портами редиса (7000..7005) и теми, что доступны для приложения (они могут быть чем-то типа 51343, 51344 и тд). Во-второй внешние порты (типа, 51343) и SocketAddress, полученный для них. Теперь, когда мы получаем от Редиса при обновлении топологии что-то типа 172.17.0.3:7003, мы сможем легко найти нужный внешний порт, по которому сможем найти SocketAddress и переиспользовать его. То есть, с портами проблема решена. А что с IP?

С IP-адресом всё просто. Тут нам на помощь приходят Тест контейнеры в которых есть утилитный метод DockerClientFactory.instance().dockerHostIpAddress(). Для MacOs/Windows он будет отдавать localhost, а для linux IP-адрес контейнера.

Выводы


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

Хочу больше годных профстатей, Хабр

21.06.2021 10:15:25 | Автор: admin

Листая страницы Хабра, поймал себя на мысли, что я воспринимаю Хабр как новостную ленту в социальной сети. То есть как нечто, что прямого отношения лично ко мне не имеет и касается меня очень косвенным путем. Нечто полуразвлекательное-полупознавательное.

Ну, судите сами. Вот примерный список тем, которые превалируют на Хабре.

  1. Что там новенького у Илона Петровича Маска.

  2. Как с помощью Arduino, говна и палок сделать годный фаллоимитатор радиоприемник.

  3. Как я ушел с прошлой работы, и как мне было там плохо.

  4. Как я нашел свою текущую работу, и какая она крутая.

  5. Как живется специалисту X в стране Y.

  6. Какой путь нужно проделать фельдшеру из Ангарска, чтобы стать тестировщиком мобильных приложений в Ирландии.

  7. Обсуждение новомодной платформы для веб-разработки, которая через 3 года станет старомодной.

  8. Промываем косточки крупным компаниям.

  9. Исторические экскурсы в IT/технологии/медицину.

  10. Реклама компаний.

  11. Мнения обо всем отвлеченном на свете.

  12. И т. д.

Все эти темы и все статьи неплохие, интересные. Но я хотел бы другого.

Я давно программирую на С++/qt. Думаю, что могу создать с нуля любой программный продукт (desktop) . Могу набирать команды, могу выстраивать отношения с заказчиками и т.д. Периодически приходится запускать (создавать) новые направления/программные продукты. Однако время, затрачиваемое мной и командой на каждый новый продукт, остается примерно постоянным. Вернее, не совсем так. Время суммарной работы оказывается в прямой пропорциональной зависимости от сложности продукта (объема кодовой базы). То есть постоянной величиной оказывается производительность работ, или эффективность труда. На всякий случай оговорюсь, что речь не идет о новичках, в команде только опытные толковые сотрудники.

Так вот, эту самую производительность труда очень хотелось бы увеличить. Как и в чем мне может помочь Хабр? Или я зря надеюсь?

Мне хотелось бы поднимать свой профессиональный уровень серьезными профессиональными статьями по проектированию/созданию/ведению больших продуктов. Статьями, которые по легкости восприятия были бы такого же классного уровня, как и сейчас большинство статей на Хабре. Но статьями, которые по глубине и полезности были бы как классические книги Скотта Майерса, банды четырех, Алана Купера, Роберта Мартина и др. Знаете, читая эти книги, я прибавлял каждый раз в квалификации. К сожалению, читая статьи на Хабре, я этого не чувствую. Даже более того: не могу припомнить случая, когда я хотел изучить какой-то новый для меня (и обычно нетривиальный) нюанс и находил бы его на Хабре. Я находил его где угодно, но только не на Хабре. Или вообще не находил.

Посему я очень жду и буду приветствовать появление на Хабре статей по следующим направлениям.

Новые шаблоны проектирования (С++)

Да, я знаю, что шаблоны не догма и не панацея, и всё всегда можно придумать и самому. Но я также знаю, что это проверенная годами экономия времени архитекторов и программистов. Это кругозор, который (при его наличии) позволяет делать сложную работу быстрее, а то и вообще моментально. У меня сложилось ощущение, что в мире С++ развитие шаблонов практически остановилось на известной книге банды четырех. Там описано 23 шаблона, и еще примерно столько же можно накопать в интернете. Я хочу больше! В моей практике были случаи, когда приходилось создавать шаблоны, разительно непохожие на известные. И приходилось тратить довольно много времени на приведение их к товарному использованию, хотя бы даже на продумывание такой терминологии, которая бы естественно бы воспринималась коллегами. Уверен, что если бы мы имели возможность в нужный момент найти и прочитать описание свежего шаблончика, наша работа местами была бы намного быстрее.

Думаю, на Хабре замечательно бы смотрелись статьи в классическом стиле банды четырех: описание и идеология шаблона, пример реальной задачи, код С++, границы применения.

Кстати, по шаблонам есть фундаментальный труд POSA: 5-томник на 2000+ страниц, перевода на русский язык до сих пор нет. Чем не непаханное поле?

Шаблоны ведения проектов в Git

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

  1. Я веду маленький проект на 20 тысяч строк кода, в нем задействовано 5 человек, с системой контроля версий мы работаем вот так (и описать конкретно, вплоть до команд командной строки).

  2. Я веду неплохой проект на 100 тысяч строк кода, в нем задействовано 10 человек, и мы работаем вот так: схемы, команды git.

  3. Мы с тремя командами по 10 человек развиваем проект на 1 миллион строк кода, продаем продукт разным клиентам в разных конфигурациях, и всё свое хозяйство мы покрыли регрессионным тестированием. И для этого мы делаем вот это: схемы, команды git.

  4. У нас работает 200 человек, и у нас 10 миллионов строк кода в монорепе на 5 продуктов, и каждый продукт ещё поставляется в трех разных версиях. Мы опытным путем пришли, что только так: схемы, команды git.

  5. А у нас очень много кода и много микросервисов, каждый в своем репозитории, впрочем, есть и подрепозитории (подмодули). Часть кода покрыта тестами, часть нет. Раньше мы работали вот так, а теперь перешли на вот это (схемы, команды), но по эффективности труда одинаково приемлемо.

GUI

Дизайн GUI, новые подходы, примеры удачных и неудачных виджетов, примеры плохих и хороших интерфейсов/решений. Эта область очень динамично развивается в плане того, что мы видим на экранах своих смартфонов и мониторов (причем на смартфонах более динамично, чем на мониторах). Однако в плане научного описания, осмысления и систематизации всё как-то вяло. То есть решений полно, но ими никто не делится. А если и делится, то как-то не очень доходит до конкретных исполнителей, и те пишут с нуля. Думаю, ситуация бы улучшилась, если бы появилась серия статей в таком духе:

  1. Камрады, я внедрил к себе гамбургер-меню на 500 строк (qt) вот отсюда (ссылка). Вот, смотрите: скриншот, gif-анимация. Работает чётко! Лицензия LGPL. Короче, пользуйтесь все.

  2. Я создал свой виджет ввода паролей. Он круче, чем другие по таким-то причинам. Делюсь! Ссылка на репозиторий, скриншоты.

  3. Я смог объединить панели меню и тулбаров в одной панели. Получился принципиально новый виджет, похожий одновременно и на меню, и на тулбары. Область применения вот такая, описываю. Скриншоты, описание даю, а вот код нет!

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

  5. Раньше у нас был такой интерфейс, и такие задачи. А потом добавилась еще одна, и мы в корне переделали интерфейс. Рассказываю, почему прошлый интерфейс был оптимален, а текущий супероптимален.

  6. Я создал свою библиотеку виджетов, да еще с библиотекой стилей. Ссылка. Область применения такая-то. Могу продать хорошим людям (или подарить).

  7. Я супермегадизайнер, и на примере 30 известных приложений за последний год объясню вам, что попало в тренд, что не попало, а что создает будущий тренд.

Успешный опыт решения важных и нетривиальных задач из самой близкой действительности

Знаете, меня не сильно цепляют новости, о том, какие планы у Джеффа Безоса на космос или как Boston Dynamics обучает своего пса. Это не увеличивает мою зарплату. Я хочу чего-то более близкого и понятного мне, но самое главное применимого в моей работе.

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

Сложные ли это задачи? Непростые, но решаемые. И некоторые из них решены многократно самыми разными производителями софта.

Давайте еще пример. Я запускаю приложение X и параллельно еще парочку для полного занятия вычислительных ресурсов (например, конвертор видео). И вот в приложении X вижу слайд-анимацию, который замечательно себя ведет (нисколько не тормозит) при том, что соседние приложения на 100% заняли мой процессор. Это неплохой результат для X, и, черт возьми, я хочу знать, как они этого добились.

Да, я знаю, что есть такие понятия, как коммерческие секреты, патенты и всякое такое. Но ведь несложно же из груды решенных задач вытащить что-то такое, что давно уже не представляет коммерческой ценности и поделиться этим с программистским сообществом. Подозреваю, что в команде разработки, к примеру, Adobe Photoshop найдется немалая кипа примеров изящно решенных нетривиальных задач, которые незаслуженно скрыты от мира. И если бы их открыть в виде конкретных формул, диаграмм (а в идеале исходного кода) и разместить в удобочитаемом виде на Хабре, это было бы очень многим полезно.

Вместо послесловия

Изложенные мной хотелки уже присутствуют в некотором небольшом количестве на Хабре, а помимо этого, подобную информацию по крупинкам можно собирать на конференциях, искать в книгах и некоторых научных журналах. Но трудозатраты слишком велики! Бывает, читаешь книгу в 500 страниц, чтобы найти хоть намек на решение, а его там не оказывается. Или, того хуже, оказывается, но такой, что лучше бы его не было Статьи в научных журналах часто оказываются написанными не с целью поделиться со всем миром своими достижениями, а просто формально отчитаться за очередной грант. Да и вообще, кажется, что печатные научные журналы по программированию эволюционно проигрывают интернет-платформам.

В общем, хочется надеяться, что Хабр сможет подобрать это непростое знамя нести серьезную, неразвлекательную науку в профессиональные сообщества, но делать это легко и изящно. И создавать условия для современных Скоттов Майерсов, готовых писать статьи для русскоязычной аудитории и потом компилировать их них свои бестселлеры. Надеюсь, жду, верю И всем не болеть!

Подробнее..

Автозаказ как сделать так, чтобы нужные продукты сами попадали на полки 17000 магазинов по всей стране

17.06.2021 18:06:38 | Автор: admin

В школе все мы решали задачки вида едет из пункта А в пункт Б. Речь преимущественно шла о скорости и времени как быстро доберётся транспортное средство? Реальность, однако, подбрасывает задачки значительно интереснее: Существует масштабная ритейл-сеть по продаже товаров, которой необходимо, чтобы огромное количество номенклатурных позиций доезжало в каждый из 17000 магазинов, расположенных на половине площади самой большой страны в мире, вовремя и в нужном количестве. Для решения такой задачи в X5 Group существует ряд реализованных решений, и одним из самых важных является процесс автозаказа товаров.

Техническую поддержку этого направления в X5 Group обеспечивает команда 2-SAP Логистики.

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

Автозаказ это комплекс процессов управления запасами и заказами

Поддержание объемов товарных запасов на установленном уровне, который обеспечивает постоянное наличие товара при оптимальных запасах в системе. Это достигается путем автоматического выполнения процессов:

Планирование заказа.

Формирование заказа.

Отправка заказа.

Экономическое обоснование поддерживаемого уровня.

Контроль за состоянием запасов.

Управление ассортиментом.

Для этого используются разные механизмы прогнозирования в зависимости от формата магазина и гибких настроек в системе.

Прогноз продаж, построенный SAP это прогноз спроса на период 42 дня, который строится на исторических данных по продажам. Период анализа продаж и модель прогноза определяется из настройки Профиля прогноза.

В периоде анализа продажи могут быть Плановые регулярные продажи и Внеплановые промо продажи.

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

Модель прогноза определенный алгоритм расчета прогноза, который устанавливается для каждого товара в магазине.

Для больших форматов прогнозирование и создание рекомендаций для Автозаказа производится в JDA, алгоритмы и логика расширены в данной системе и позволяют учитывать огромное количество параметров для расчета.

Около 04:30 эти данные по потребности из JDA поступают в SAP, где обрабатываются фоновым заданием с интервалом запуска каждые 15 минут, в результате чего создаются Автозаказы.

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

Большую роль этот функционал играет для персонала магазинов магазинах, так как он исключает необходимость формировать заказы вручную. За исключением отдельных случаев всё происходит в автоматическом режиме с учетом большого количества факторов и сложных расчетов. Автозаказ облегчает ежедневный труд тысяч сотрудников по всей стране от Калининграда до Новосибирска.

Через автозаказ пополняется до 80% основного ассортимента магазинов, а ручное пополнение выполняется для товаров in-out.

Схема реализации товара:

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

Так выглядит децентрализованная цепочка:

- в 00:00 стартует задание по АЗ, в период его работы по нашему товару рассчиталась потребность, сформировался автозаказ, и он бы отправлен из SAP в программу магазина GK(Пятёрочка), там у сотрудников магазина есть время отредактировать рассчитанное кол-во (в большую или меньшую сторону) до наступления времени автосогласования (например, до 11 утра, время зависит от тайминга поставщика, т.е. до какого момента он должен получить заказ, чтобы собрать и вовремя привезти заказ в магазин);

- после внесения корректировок эти изменения отправляются в SAP, и дальше происходит процесс автосогласования заказ согласовывается и автоматически отправляется поставщику по почте или его провайдеру (в зависимости от того, по какой схеме поставщик работает). Например, если поставщик работает по EDI (через электронный документооборот), то после доставки заказа в его систему, нам провайдер передает информацию о размещении заказа в своем ПО и в системе ER2 заказ переходит в статус Размещен;

- далее нам поступает буфер приемки, который мы передаем в ПО магазина. По этому буферу магазин осуществит приемку товара от поставщика. После осуществления приемки поставщику отправляется информация об этом. На этом путь закончен, сотрудникам магазина остается выставить на полки товар и продать его счастливым покупателям.

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

На ежедневной основе автозаказ формирует более 400 000 тысяч заказов для магазинов, как на распределительные центры, так и внешним поставщикам.

  • Продажи за период в прошлом

  • Прогноз на период в будущее (на основании продаж строится прогноз SAP)

  • Остатки на объекте получателе (остаток в SAP на момент расчёта)

  • Открытые заказы (заказы на поставку и возвраты)

Продажи приходят из магазинов каждый вечер до расчёта фонового задания Автозаказа.

Факт получения продаж запускает расчет Автозаказа. Если до 03:00 продажи не получены SAP ERP, то происходит безусловный запуск задания Автозаказа.

Автозаказ формируется строго по графику заказа/поставки согласованному с поставщиком, так категории FRESH и ULTRA FRESH, как правило, заказываются и поставляются в магазины ежедневно.

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

Для того, чтобы заказать оптимальное кол-во товара, учитывается прогноз, который строится в SAP в момент расчета Автозаказа.

Если по этому товару уже есть запас или размещенный заказ, который еще не закрыт, то система также учтёт это при определении размера финального заказа.

Автозаказ определяет цепочку и поставщика по каждому товару для каждого магазина, согласно записей книги источников поставок (смежный функционал, автоматизирован, и ведется также в SAP) и формирует заказ.

Запускается задание ежедневно в 00:00. Первыми выполняются расчеты по регионам Сибирь и Урал, далее по регионам Волга, ЮГ, Москва, Центральные регионы и Северо-запад. Последовательность выполнения крайне важна, т.к. наши магазины расположены в разных часовых поясах, и пока в Москве все сладко спят, в Сибири уже в разгаре рабочий день.

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

Для этого мы реализовали различные системы мониторинга, инструменты для анализа, а в случае необходимости и возможность отправки в магазины резервных шаблонов заказа.<o:p>

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

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

Подробнее..

3. Частотные характеристики звеньев и систем автоматического управления. ч. 3.4 Апериодическое звено 2го порядка

24.02.2021 02:21:58 | Автор: admin

Предыдущая часть Апереодическое звено первого порядка.

3.4 Апереодическое звено второго порядка

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

У нас есть модель механического демпфера. Это поршень на пружине, он движется внутри цилиндра, может перемещается вверх-вниз. Его положение это интересующая нас функция Y(t), сверху на него воздействует возмущающая сила (U(t)), на стенках поршня действует сила вязкого трения. (См. рис. 3.4.1)

Рисунок 3.4.1. Расчетная схема амортизатора. Рисунок 3.4.1. Расчетная схема амортизатора.

Выведем передаточную функцию для этого звена. Согласно 2-му закону Ньютона ускорение тела пропорционально сумме сил, действующих на тело:

m \cdot \frac{d^2Y(t)}{dt} = \sum F_j = P+ U(t) +F_{пр}+F_{тр}
  • где:

    m - масса поршня;

    Y(t)- положение поршня (выходная переменная);

    U(t) = X(t)- приложенная сила (входное воздействие);

    P = m \cdot g- сила тяжести;

    F_{пр} = k \cdot Y(t) сила сопротивления пружины;

    F_{тр} = с \cdot \frac{dY}{dt} сила вязкого трения (пропорциональная скорости движения поршня).

Считаем, что в нулевой момент времени поршень находится в равновесии. Тогда начальное положение поршня y0в равновесии, где скорость и ускорения равны 0, можно посчитать из уравнения 2.

0 = m \cdot g + u_0-k \cdot y_0 \Rightarrow y_0= \frac{1}{k} [m\cdot g+u_0]

Перепишем уравнение равновесия в отклонениях от нулевого состояния:

Y(t) =y_0+y(t); \ \ \ U(t) = u_0+u(t);

m \cdot \frac{d^2 y(t)}{dt^2} = \underbrace { m \cdot g+ u_0}+u(t)-\underbrace {k \cdot y_0} -k\cdot y(t) - c \cdot\frac{dy(t)}{dt}

Поскольку мы приняли, что в начальный момент у нас состояние равновесия, а сумма трех сил в состоянии равновесия равна нулю, их можно убрать из уравнения, и в итоге получим:

m \cdot y''(t)+c \cdot y'(t)+k \cdot y(t) = u(t) = x(t)

Приведем данное уравнение к классическому виду:

 \underbrace{\frac{m}{k}}_{T_2^2}\cdot y''(t)+\underbrace{\frac{c}{k}}_{T_1}\cdot y'(t)+y(t)=\underbrace{\frac{1}{k}}_K \cdot x(t)

Уравнение динамики апериодического звена 2го порядка имеет следующий вид:

T_2^2 \cdot y''(t)+T_1 \cdot y'(t)+y(t)=K \cdot x(t) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.1)}

при этом:

D = T_1^2-4 \cdot T_2^2 \ge 0 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.2)}

ЕслиD<0, то звено становится колебательным (см. раздел 3.5)

Переходя к изображениямx(t) \rightarrow X(s); \ \ \ y(t) \rightarrow Y(s)получаем уравнение динамики звена в изображениях:

(T_2^2\cdot s^2+ T_1 \cdot s+1) \cdot Y(s) = K \cdot X(s) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.3)}

Передаточная функция звена может быть представлена в двух видах:

W(s) = \frac{Y(s)}{X(s)}= \frac{K}{T_2^2 \cdot s^2+T_1 \cdot s+1} \iff \frac{K}{(T_3 \cdot s+1)(T_4 \cdot s+1)}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.4)}

где:

T_3 = \frac{T_1-\sqrt{D} }{2} ;\ \ T_4 = \frac{T_1+\sqrt{D}}{2}Рисунок 3.4.2 Апериодическое звено 2-го порядка (два варианта) Рисунок 3.4.2 Апериодическое звено 2-го порядка (два варианта)

Амплитудно-фазовая частотная характеристика (АФЧХ):

W(i \cdot \omega) = W(s)|_{s =i \cdot \omega} =\frac{K}{(1+i \cdot T_3 \cdot \omega)(1+ i \cdot T_4 \cdot \omega)} \Leftrightarrow \frac{K}{(1-T_2^2 \cdot \omega^2)+i \cdot T_1 \cdot \omega} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.5)}

Домножив числитель и знаменатель формулы (3.4.5) на комплексно-сопряженные скобки (1-i \cdot T_3 \cdot \omega) и (1-i \cdot T_4 \cdot \omega) , получаем:

W(i\cdot\omega) = \frac{K(1- i \cdot T_3 \cdot \omega)(1- i \cdot T_4 \cdot \omega)}{(1 + T_3^2 \cdot \omega^2)(1+T_4^2\cdot \omega^2)} == \underbrace {\frac{K (1 -T_4\cdot T_3 \cdot \omega^2)}{(1+T^2_3 \cdot \omega^2)(1+T_4^2 \cdot \omega^2)}}_{u(\omega)}- i \cdot \underbrace {\frac{K(T_4+T_3)\omega}{(1+T_3^2\cdot \omega^2)(1+ T_4^2\cdot \omega^2)}}_{v(\omega)}

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

u(\omega) = \frac{K(1- T_3 \cdot T_4 \cdot \omega^2)}{(1+T_3^2 \cdot \omega^2)(1+ T_4^2 \cdot \omega^2)}; \ \ \ \ \ v(\omega) = -\frac{K(T_4+ T_3)\omega}{(1+T_3^2 \cdot \omega^2)(1+T_4^2 \cdot \omega^2)} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.6)}

Анализируя поведениеu()иv()при\omega \rightarrow 0 и при\omega \rightarrow \infty , получаем:

\lim_{\omega \to 0} u(\omega) = K; \ \ \ \ \ \lim_{\omega \to \infty}u(\omega) = 0\omega \rightarrow 0 \Rightarrow \left \{ \begin{gathered} u(\omega) \rightarrow K; \\ v(\omega) \rightarrow 0; \end{gathered} \right. \ \ \ \ \ \ \ \omega \rightarrow \infty \Rightarrow \left \{ \begin{gathered} u(\omega) \rightarrow 0; \\ v(\omega) \rightarrow 0; \end{gathered} \right.

Модуль АФЧХ (амплитуда), то естьmod(W(i)) = |W(i)| из формулы 3.4.5:

A(\omega) = |W(i\cdot \omega) | = \left | \frac{K}{(1+i\cdot T_3 \cdot \omega)(1+i \cdot T_4\cdot \omega)} \right | = \frac{K}{\sqrt{1+ T_3^2 \cdot \omega^2}\cdot \sqrt{1+ T_4^2\cdot \omega^2}}\ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.7)}

Подставляя в формулы (3.4.6) или в формулу (3.4.5) различные значения можно построить векторы, соответствующие различным значениям :

Рисунок 3.4.3 Годограф АФЧХ апериодического звена 2-го порядкаРисунок 3.4.3 Годограф АФЧХ апериодического звена 2-го порядка

Из формул 3.4.6 очевидно, что на рисунке годографа 3.4.3 :

1) \ \omega_6>\omega_5>\omega_4>\omega_3>\omega_2>\omega_1>0\\ 2) \ \ 0 >\varphi_1>\varphi_2>\varphi_3>\varphi_4>\varphi_5>\varphi_6

Используя формулу 3.4.6 можно показать что u(w_3)=0 при \omega_3 = \frac{1}{\sqrt{T_3\cdot T_4}}

Из рисунка видно, что \varphi(\omega) \in [-\pi;0] .

Формула фазового сдвига:

\varphi(\omega) = - \pi \cdot j+ arctg \frac{v(\omega)}{u(\omega)} \omega\leq \omega_3 \Rightarrow j = 0;\\ \omega>\omega_3 \Rightarrow j=1.

Для фазового сдвига удобно представить апереодическое звено в виде последоваетельного соединения двух звеньев (см. рис. 3.4.2). Известно, что при последовательном соединении звеньев общий сдвиг фазы равен сумме фазовых сдвигов:

 \varphi(\omega)=\varphi_1(\omega)+\varphi_2(\omega) = -arctg (T_3\cdot \omega)-arctg(T_4 \cdot \omega)\ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.8)}

Логарифмическая амплитудная характеристика (ЛАХ)

Lm(\omega) = 20 \cdot lg \ A(\omega) = 20\cdot lg \ K - 20 \cdot lg \sqrt{1+T_3^2 \cdot \omega^2} - 20 \cdot lg \sqrt{1+T_4^2\cdot \omega^2}\ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.9)}

Графики А(), (),Lm() имеют вид:

Рисунок 3.4.4 АЧХ и ФЧХ апериодического звена 2-го порядкаРисунок 3.4.4 АЧХ и ФЧХ апериодического звена 2-го порядкаРисунок 3.4.5 ЛАХ и ЛФЧХ апериодического звена 2-го порядкаРисунок 3.4.5 ЛАХ и ЛФЧХ апериодического звена 2-го порядка

В инженерных расчетах часто графикLm() представляют виде отрезков ломаных, тогда:

при \omega < 1 /T_4 - звено близко к идеальному усилительному звену  W(s) \approx K

при  1/T_4 < \omega < 1/T_3 - звено близко к идеальному интегрирующему звену W(s) \approx K/(T\cdot s)

при \omega>1/T_3 - звено близко к дважды интегрирующему звену W(s)\approx K/(T^2 \cdot \omega^2)

В граничном случае (D=0 или T_1 = 2 \cdot T_2) \Rightarrow T_3 = T_4 отмеченные на графикеLm() (см. рис. 3.4.5 выше) точки излома совпадают:

Рисунок 3.4.6 ЛАХ и ЛФЧХ апериодического звена 2-го порядка в граничном случаеРисунок 3.4.6 ЛАХ и ЛФЧХ апериодического звена 2-го порядка в граничном случае

ЕслиD<0 \ (T_1 = 2T_2)звено переходит в разряд колебательных звеньев. Поэтому постоянная Т1в уравнении динамики (3.4.1) играет роль демпфирующего фактора, увеличение Т1(в колебательном звене) приводит к уменьшению или к полному исчезновению колебаний.

Найдем переходную функцию звена  h(t) - реакцию на воздействие единичное воздействие 1(t).

h(t) = L^{-1}[H(s)]=L^{-1} \left[ \frac{W(s)}{s} \right] =L^{-1} \left[ \frac{K}{s(T_3 \cdot s +1)(T_4 \cdot s+1)} \right]

Для нахождения функции по формуле Хэвисайда (см. раздел 2.8 Некоторые способы нахождения оригинала по известному изображению), запишем корни полюса изображения, т.е. те значения s при которых D_0(s) = s(T_3 \cdot s +1)(T_4 \cdot s +1) обращается в ноль:

s_1 = 0; \ \ s_2 =-\frac{1}{T_3}; \ \ s_3 = -\frac{1}{T_4}

Тогда по формуле Хэвисайда:

h(t) = \lim_{s \to 0} \left[ (s + 0) \frac{K}{s(T_3 \cdot s+1)(T_4 \cdot s+1)} \cdot e^{st}\right] \\+ \lim_{s \to -\frac{1}{T_3}} \left[ (s+\frac{1}{T_3}) \frac{K}{s(T_3 \cdot s+1)(T_4 \cdot s +1)}\cdot e^{st} \right] +\\+ \lim_{s \to -\frac{1}{T_4}} \left[ (s+\frac{1}{T_4}) \frac{K}{s(T_3 \cdot s+1)(T_4 \cdot s +1)}\cdot e^{st} \right]

Вычисляя пределы получим формулу для переходной функции звена:

h(t) = K \left[1+ \frac{T_3}{T_4-T3} \cdot e^{- \frac{t}{T_3}}-\frac{T_4}{T_4 -T_3} \cdot e^{-\frac{t}{T_4}} \right] \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.10)}

Весовая функция получается дифференцированием w(t) =h'(t) :

w(t) = \frac{K}{T_4 - T_3} \cdot \left[ e^{-\frac{t}{T_4}} - e^{- \frac{t}{T_3}} \right] \ \ \ \ \ \ \ \ \ \ \mathbf{(3.4.11)}Рисунок 3.4.7 Переходная функция апериодического звена 2-го порядкаРисунок 3.4.7 Переходная функция апериодического звена 2-го порядка

Примерами апериодического звена 2-го порядка являются:

1) двигатель постоянного тока при учете инерционности самого якоря (механической) и цепи якоря (электрической);

2) электрический усилитель с учетом инерционности (механической и электрической) ротора;

3) двойныеRCилиRLцепочки

Рисунок 3.4.9 Пример апериодического звена 2-го порядкаРисунок 3.4.9 Пример апериодического звена 2-го порядка

Если звено представлено в переменных состояния в матричной форме таким образом:

x' = A \cdot x + B \cdot u; \ \ \ A = \begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ \end{pmatrix} \Rightarrow \left \{ \begin{gathered} x_1' = a_{11} \cdot x_1+a_{12}\cdot x_2+ B \cdot u \\x_2'= a_{21} \cdot x_1+a_{22}\cdot x_2+ B \cdot u \end{gathered} \right.

то звено будет апериодическим 2-го порядка, если:

 D = (a_{11}+ a_{22})^2 - 4(a_{11}\cdot a_{22} -a_{12}\cdot a_{21}) \ge 0

Пример

В качестве примера возьмём модель демпфера, которую мы уже использовали в лекциях. (см. Рисунок 3.4.10) Структурная схема модели описывает уравнения динамики, описанные в начале статьи. Свойства системы заданы в списке общих сигналов проекта (см. рис. 3.4.11). Для получения из демпфера апериодического звена 2-го порядка необходимо увеличить силу трения таким образом, чтобы (как показано выше) коэффициентT1 был больше, чем 2 хT2. В этом случае D>0 и из колебательного звена мы получим апериодическое 2-го порядка.

Рисунок 3.4.10 Структурная схема модели демпфера.Рисунок 3.4.10 Структурная схема модели демпфера.Рисунок 3.4.11 Параметры моделиРисунок 3.4.11 Параметры модели

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

Рисунок 3.4.12. Параметры для модели демпфера в виде звенаРисунок 3.4.12. Параметры для модели демпфера в виде звена

Выполним моделирование переходного процесса при ступенчатом изменении приложенной силы и сравним переходные процессы в двух вариантах модели демпфера. График переходного процесса (см. рис. 3.4.13) показывает, что переходные процессы в двух моделях полностью идентичны:

Рисунок 3.4.13 Переходные процессы в двух моделях.Рисунок 3.4.13 Переходные процессы в двух моделях.

График частотных характеристик звена (ЛАХ и ФЧХ) представлен на рисунке 3.4.14На графике видно две точки излома характеристики ЛАХ в которых наклон последовательно меняетсяс 0, до 20дБ/дек и с 20дБ/дек до 40 дБ/дек.

Рисунок 3.4.14 Частотные характеристика ЛАХ и ФЧХРисунок 3.4.14 Частотные характеристика ЛАХ и ФЧХ

Для демонстрации влияния изменения Т1 на свойства звена выполним моделирование, в котором структурная схема является эталонной, а в модели звена будем уменьшать коэффициент силы трения (коэффициентT1).

Источником воздействия будет меандр, с периодом 3 секунды.

Для изменения свойств звена создадим блок на языке программирования. Данный блок, в процессе моделирования, постепенно уменьшает коэффициент Т1 для модели в виде звена. Этот же блокготовит данные для отображения на 3Dграфике переходного процесса.

Общая схема модели приведена на рисунке 3.4.15.

Рисунок 3.4.15 Схема демпфера с изменения свойств блокаРисунок 3.4.15 Схема демпфера с изменения свойств блока

Меандр задает изменение приложенной силы 0 30 Н (входного воздействия) с полупериодом 1.5 сек. График изменения положения приведен на рисунке 3.4.16 Видно, что на первом изменении графики совпадают, но потом по мере накопления отличий в параметрах динамика изменения положения начинает меняться.

Рисунок 3.4.16 Графики положения демпферов.Рисунок 3.4.16 Графики положения демпферов.

Первая часть процесса изображена на рисунке 3.4.17 Видно, что снижение силы трения обеспечивает более быстрое изменении положения демпфера.

Рисунок 3.4.17 Начальная часть графикаРисунок 3.4.17 Начальная часть графика

Конечная часть графика представлена на рисунке 3.4.19. Дальнейшее снижение силы трения приводит к тому, что процесс перехода при ступенчатом изменении воздействия становится колебательным.

Рисунок 3.4.18 Конечная часть моделированиРисунок 3.4.18 Конечная часть моделировани

ЗDповерхность отображает переходный процесс при ступенчатом увеличении воздействия в блоке меандр.По осиZ отражается положение демпфера, по осиY время после увеличения входного воздействия в блоки меандр, по осиX измененийT1 (уменьшение силы трения).

Рисунок 3.4.19 Поверхность переходного процесса при снижении тренияРисунок 3.4.19 Поверхность переходного процесса при снижении трения

В заключение, сравним переходные процессы для разных параметровT1 (разных коэффициентов трения). Поскольку все основные блоки вSimInTechявляются векторными, создадим модели 7-ми демпферов из одного звена. Для этого в главном окне программы подготовим 7 векторов значений с разными коэффициентами трения. Скрипт приведен на рисунке 3.4.20.

Рисунок 3.4.20 Скрипт модели для задания параметров 7 демпферовРисунок 3.4.20 Скрипт модели для задания параметров 7 демпферов

Четвертый вектор содержит переходное значение T1. Как было показано выше, переходное значениеT1, при котором апереодическое звено второго порядка превращается в колебательное расчитывается по формуле T1 = 2хT2.

В модели, в свойствах блока указываем эти векторы в столбце формулы, и теперь блок может рассчитывать одновременно7демпферов одним блоком. (см. рис. 3.4.21)

Рисунок 3.4.21 Настройка параметров блока для векторного расчетаРисунок 3.4.21 Настройка параметров блока для векторного расчета

Общая схема модели в этом случае будет выглядеть как показано на рисунке 3.4.22 Ступенчатое изменение силы передается в блок Размножитель, где преобразуется в вектор из 7 воздействий. Данный вектор передается в блок, где и происходит расчёт семи вариантов демпфера.

Рисунок 3.4.22 Схема модели 7-и демпферовРисунок 3.4.22 Схема модели 7-и демпферов

Результат переходного процесса представленна рисунке 3.4.23. Видно, что 3 демпфера ведут себя как апериодическое звено второго порядка, 3 демпфера явно превратились в колебательные.

Рисунок 3.4.23 Перемещение 7 демпферов при ступенчатом воздействииРисунок 3.4.23 Перемещение 7 демпферов при ступенчатом воздействии

Характеристики ЛАХ и ФХЧ представлены на рисунке 3.4.24. Наглядно видно, как постепенно, при снижении коэффициента трения исчезают два излома на графике ЛАХ, и звено превращается в колебательное, о котором будем говорить в следующей части.

Рисунок 3.4.25 Частотные характеристики 7-и демпферовРисунок 3.4.25 Частотные характеристики 7-и демпферов

Модели с примерами для самостоятельного изучения можно взять по ссылке.

Предыдущая лекция Апереодическое звено первого порядка.

Подробнее..

3. Частотные характеристики звеньев и систем автоматического регулирования. 3.5 Колебательное звено

07.04.2021 08:12:05 | Автор: admin

Колебательное звено является наиболее интересным случаем из всех типовых звеньев, во-первых,за счет сильной похожести по своим динамическим свойствам на более сложные реальные САУ (САР), во-вторых, близкой идентичности переходных процессов в звене к аналогичным в реальных САР, и, в-третьих, существенной зависимости динамических свойств от величины параметра звена.

Выведем формулу колебательного звена на примере электрического колебательного контура, который изучают в курсе школьной физики. Пример такого контура приведен на рисунке 3.5.1

Рисунок 3.5.1 Модель электического колебательного контураРисунок 3.5.1 Модель электического колебательного контура

Электрическая цепь содержит источник напряжения и последовательно соединённые индуктивность, сопротивление, конденсатор.

Входное ступенчатое воздействиеx(t), формирующее внешнюю Э.Д.С в цепи, подключено к блоку источнику напряжения х(t) =Uвх(t).

Результирующий отклик звена - напряжение на конденсатореy(t) =Uс(t) =Uвых(t).

Согласно второму закону Кирхгофа для замкнутого контура, сумма Э.Д.С равна сумме напряжения на резистивных элементах контура.

U_R+U_C =U_{вх} +\xi_L \Rightarrow \\ \Rightarrow -\xi_L+U_R+U_C= U_{вх}

где:

\xi_L = -L \cdot \frac{dI}{dt}- ЭДС индукции на катушке, (направлено против изменения тока);

U_R=R \cdot I- падение напряжении на сопротивлении.

Поскольку в замкнутом контуре сила тока одинакова на всех элементах, перепишем уравнения, выразив силу тока через напряжение на конденсаторе. Сила тока в цепи равна изменению заряда конденсатора:

I =\frac{dq}{dt} где:

q=C\cdot U_C- заряд кондесатора.

Тогда сила тока в цепи связана с напряжение на конденсаторе соотношением:

I = C \cdot \frac{ dU_c}{dt}

После замены силы тока, ее выражением через U_C получим следующие выражение:

L \cdot C \cdot \frac{d^2U_c}{dt^2} +R\cdot C \cdot \frac{d U_c}{dt}+U_c = U_{вх}

Заменив U_C=y(t) и U_{ВХ} = x(t) получим уравнение колебательного звена:

\underbrace{L \cdot C}_{T_2^2} \cdot y''(t)+\underbrace{R \cdot C}_{T_1} \cdot y'(t) +y(t) =\underbrace{1 \cdot }_Kx(t)

Уравнение динамики звена описывается уравнением, аналогичным рассмотренном в предыдущем разделе (апериодическое звено второго порядка):

T^2_2 \cdot y''(t)+T_1 \cdot y'(t)+ y(t) =K\cdot x(t) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.1)}

причем T_1<T_2 , т.е. D= T_1^2-4 \cdot T_2^2 \leq 0

Учитывая, что D \leq0 , удобнее представить уравнение динамики в другой форме, а именно:

Введем новые параметры: T\equiv T_2 и \beta = \frac{T_1}{2 \cdot T_2} , где \beta - параметр (коэффициент) затухания (демпфирования).

Подставляя новые параметры в (3.5.1):

T^2 \cdot y''(t)+2 \cdot \beta \cdot T\cdot y'(t)+y(t) = K \cdot x(t) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.2)}

Уравнение 3.5.2 - наиболее удобная форма представления уравнения динамики.

Перейдем к изображениям: x(t) \rightarrow X(s) и y(t) \rightarrow Y(s) уравнение динамики в изображениях Лапласа:

(T^2_2 \cdot s^2+2 \cdot \beta \cdot T \cdot s+1) \cdot Y(s)=K \cdot X(s)

Передаточная функции колебательного звена:

W(s) =\frac{Y(s)}{X(s)}= \frac{ K}{ T^2 \cdot s^2+2 \cdot \beta \cdot T \cdot s + 1} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.3)}

Еще раз подчеркнем, что параметр (коэффициент) затухания (демпфирования) 0 \le \beta \le 1 , причем при \beta > 1 свойства колебательного звена совпадают с аналогичными свойствами соответствующего апериодического звена 2-го порядка, а при \beta = 0 звено выражается вконсервативное, в котором могут существовать незатухающие гармонические колебания.

Выражение для АФЧХ получается после подстановки в (3.5.3) значения s=i\cdot \omega :

W(i \cdot \omega)=\frac{K}{T^2 \cdot (i \cdot \omega)^2+2 \cdot \beta \cdot T \cdot i \cdot \omega+1}=\\= \frac{K}{(1-T^2\cdot \omega^2)+2 \cdot \beta \cdot T \cdot i \cdot \omega} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.4)}

Домножим числитель и знаменатель формулы 3.5.4 на компексно сопряженное выражения для знаменателя (1-T^2\cdot \omega^2) - 2 \cdot \beta \cdot T \cdot i \cdot \omega :

W(i \cdot \omega) = \frac{K(1-T^2\cdot \omega^2) - K \cdot 2 \cdot \beta \cdot T \cdot \omega \cdot i}{(1-T^2\cdot\omega^2)^2+4 \cdot \beta^2 \cdot T^2 \cdot \omega^2}

Выражения для вещественной и мнимой частей принимают вид:

u( \omega) = \frac{K(1-T^2\cdot \omega^2) }{(1-T^2\cdot\omega^2)^2+4 \cdot \beta^2 \cdot T^2 \cdot \omega^2} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.5)}v(\omega) = \frac{ - 2 \cdot K \cdot \beta \cdot T \cdot \omega }{(1-T^2\cdot\omega^2)^2+4 \cdot \beta^2 \cdot T^2 \ \cdot \omega^2} \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.6)}

Амплитуда АФЧХ

A(\omega) = \sqrt{u(\omega)^2+v(\omega)^2} =\sqrt{\frac{K^2 \left( (1-T^2\cdot \omega^2)+4\cdot K^2 \cdot \beta^2 \cdot T^2 \cdot \omega^2 \right)}{((1-T^2\cdot \omega^2)^2+4 \cdot \beta^2 \cdot T^2 \cdot \omega^2)^2}}A(\omega) = \frac{K }{\sqrt{(1-T^2\cdot \omega^2)^2+4 \cdot \beta^2 \cdot T^2 \cdot \omega^2}} \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.7)}

Сдвиг фазы

\varphi (\omega) = \left \{ \begin{gathered} -arctg \frac{2 \cdot \beta \cdot T \cdot \omega}{1- T^2 \cdot \omega^2}, \ если \ \omega \le \frac{1}{T}; \\ -\pi- arctg \frac{2 \cdot \beta \cdot T \cdot \omega}{1- T^2 \cdot \omega^2}, \ \ если \ \ \omega > \frac{1}{T}. \ \end{gathered} \right. \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.8)}

Анализ формул (3.5.5 3.5.8) показывает, что:

\omega \rightarrow 0 \Rightarrow \left \{ \begin{gathered} u(\omega) \rightarrow K; \\ v(\omega) \rightarrow 0; \\ A(\omega) \rightarrow K; \\ \varphi(\omega) \rightarrow 0; \end{gathered} \right. \ \ \ \ \ \ \ \omega \rightarrow \infty \Rightarrow \left \{ \begin{gathered} u(\omega) \rightarrow 0; \\ v(\omega) \rightarrow 0; \\ A(\omega) \rightarrow 0; \\ \varphi(\omega) \rightarrow - \pi; \end{gathered} \right. \ \ \ \ \ \ \ \mathbf{(3.5.9)}

Одной из главных особенностей АФЧХ является возможность существования экстремума в зависимостиA(). Выполним исследование на экстремум:

\frac{dA(\omega)}{d\omega}=\frac{d}{d\omega} \left( \frac{K}{\sqrt{(1-T^2\cdot\omega^2)^2+4\cdot\beta^2\cdot T^2\cdot \omega^2}}\right)=0\frac{\frac{d}{d\omega}K\cdot\sqrt{(1-T^2\cdot\omega^2)^2+4\cdot\beta^2\cdot\omega^2}-K \cdot \frac{d}{d\omega}\sqrt{(1-T^2\cdot\omega^2)^2+4\cdot\beta^2\cdot\omega^2}}{(1-T^2\cdot\omega^2)^2+4\cdot\beta^2\cdot\omega^2} = \\ =\frac{-0.5\cdot K\cdot((1-T^2\cdot\omega^2)^2+4\cdot\beta^2\cdot\omega^2)^{-1.5}\cdot[2\cdot(1-T^2\cdot\omega^2)\cdot(-2)\cdot T^2\cdot \omega+8 \cdot \beta^2\cdot\omega]}{(1-T^2\cdot\omega^2)^2+4\cdot\beta^2\cdot\omega^2}

Очевидно что, для того, что бы выражение равнялось нулю необходиом равенство нлую следующего выражения:

-4\cdot(1-T^2\cdot \omega^2)\cdot T^2 \cdot \omega+8 \cdot \beta^2\cdot T^2 \cdot \omega = 0

Отсюда вырражение для экстермума:

\omega_m=\frac{1}{T}\sqrt{1-2\cdot \beta^2} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.10)}

Очевидно, что \omega_m существует если (1- 2 \cdot \beta^2)\ge 0 \Rightarrow \beta \le\frac{\sqrt2}{2}

Если \beta < \frac{\sqrt{2}}{2} , то заивисмость A(\omega) имеет экстремум.

Если \beta >\frac{\sqrt{2}}{2} , экстремума в заивсимости A(\omega) нет.

Вычислим максимальное значение A(\omega) , под ставим выражение для \omega_m 3.5.10 в формулу 3.5.7, получим:

A(\omega_m) =\frac{K}{\sqrt{\left [1 -T^2 \frac{1}{T^2}(1-2\beta^2) \right ]^2+4\beta^2T^2\frac{1}{T^2}(1- 2\beta^2)}} \RightarrowA(\omega_m) = \frac{K}{2 \cdot\beta \sqrt(1- \beta^2)} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.11)}

Анализ вышеприведенных соотношений показывает, что при \beta < \frac{\sqrt{2}}{2} графикA(\omega)имеет горб, который при уменьшении \beta растет и при \beta \rightarrow 0 \ \ \ \ \ \ A(\omega) \rightarrow \infty , что означает разрыв в зависимостиA(\omega).

Частоту мбудем отождествлять с тем значением частоты входного гармонического воздействия, при которой имеет местомаксимальноезначение амплитуды выходного сигнала.

Поскольку \beta = \frac{T_1}{T_2} , то очевидна роль постоянных времени :

T_2 раскачивает колебания, а T_1 демпфирует их. Рассмотрим соответствующие графики:

Рисунок 3.5.2 АЧХ колебательного звенаРисунок 3.5.2 АЧХ колебательного звенаРисунок 3.5.3 ФЧХ колебательного звенаРисунок 3.5.3 ФЧХ колебательного звена

Данные графики аналогичны для случаев резонансов в теоретической механике, физике, электротехнике и т.д.

Величину \omega = \frac{1}{T} принято называть частотой свободных колебаний и обозначать 0.

Рассмотрим колебательное звено в котором = 0. Очевидно, что в данном звене при ступечатом воздействии устанавливаются незатухающие колебания, а само звено вырождается в консервативное. При этом согласно формуле 3.5.10 выражение экстремума для такого звена:

\omega_m=\frac{1}{T}\sqrt{1-2\cdot 0^2} = \frac{1}{T}

Величину \omega = \frac{1}{T} принято называть частотой свободных колебаний и обозначать 0.

Подставляя различные значения в формулу (3.5.5) или (3.5.6) построим годограф АФЧХ на комплексной плоскости:

Рисунок 3.5.4 АФЧХ колебательного звенаРисунок 3.5.4 АФЧХ колебательного звенаРисунок 3.5.5 Годограф АФЧХ консервативного звенаРисунок 3.5.5 Годограф АФЧХ консервативного звена

Построение ЛАХ Lm() не может быть сделано так же просто, как для предыдущих позиционных звеньев, т.е. она не сводится к комбинации отрезков прямых.

Будем использовать для построения графика ЛАХнормированную(безразмерную) частоту\tilde{\omega} = \frac{\omega}{\omega_0}, где \omega_0 - частота свободных колебаний, имеющим место в консервативном звене со следующим уравнением динамики:

T^2 \cdot y''(t)+y(t) = K \cdot x(t)

Решим данное уравнение динамики, используя корни характеристического уравнения L(\lambda )=0 :

T^2\cdot \lambda^2+1=0 \Rightarrow \lambda_{1,2} = \pm i\cdot\frac{1}{T} = \pm i \cdot \omega_0y_{собств} = С_1\cdot e^{i \cdot \omega_0\cdot t}+C_2\cdot e^{-i\cdot \omega_0 \cdot t} \approx sin(\omega_0\cdot t)

На этом месте у меня всегда выносится мозг, как могут прыгающие на пружинке шарике, и электроны в электрическом контуре, описаны с помощью одиникового выражения, формулы синуса - соотношения стороно в прямоугольном треуголнике. Как это работает?!

Введя новую переменную\tilde{\omega}в выражение дляLm() = 20 lg (А()):

Lm(\omega) =20\cdot lg(K) - 20 \cdot lg(\sqrt{(1-T^2\cdot \omega^2)^2+4 \cdot \beta^2\cdot T^2\cdot \omega^2}) = =20\cdot lg(K) - 20 \cdot lg(\sqrt{\left (1-\frac{\omega^2}{\omega_0^2} \right)^2+4 \cdot \beta^2\cdot \frac{\omega_2}{\omega_0^2}} \RightarrowLm(\omega) =20\cdot lg(K) - 20 \cdot lg(\sqrt{\left (1-\tilde{\omega}^2 \right)^2+4 \cdot \beta^2\cdot \tilde {\omega}^2} \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.12)}

Таким образом мы получаем выражение, которое не зависит от Т. Такая форма представления позволяет свести различные ЛАХ при различныхТк автомодельному (универсальному) виду графиков.

На рисунке ниже представлен графикLm() в форме (3.5.12), построенный фактически в логарифмических координатах, причем коэффициент усиленияK=1.

Рисунок 3.5.6 ЛАХ колебательного звенаРисунок 3.5.6 ЛАХ колебательного звена

Подчеркнем, что при такой форме представления все ЛАХ при различныхT1иT2можно собирать вместе.

ВеличинаHm(см. рис. 3.5.6) называетсяпревышением:

H_m=20\cdot lg \frac{1}{2\cdot\beta\cdot \sqrt{1-\beta^2}} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.13)}

Если, \beta <<1, \ \ \beta \approx 0 то в упрощенных расчетах величину превышенияHmможно оценить, как:

H_m = 20 \cdot lg \frac{1}{2\cdot \beta}=-20lg(2\cdot \beta) \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.14)}

при =m(эта формула работает для ярко выраженных горбов).

Вычислим переходную функцию звенаh(t):

h(t)= L^{-1}[H(s)] =L^{-1}\left[\frac{W(s)}{s}\right] = L^{-1}\left[ \frac{K}{s(T^2\cdot s^2+2 \cdot \beta \cdot s+1)} \right] \Rightarrowh(t) =\frac{K}{T^2}L^{-1} \left[ \frac{1}{s(s^2+\frac{2\cdot \beta}{T}\cdot s)+\frac{1}{T^2}} \right]

Для вычисления переходной функции воспользуемся формулой Хэвисайда сначала найдем полюса s_1,s_2,s_3:

s \cdot \left(s^2+\frac{2 \cdot \beta}{T}+ \frac{1}{T^2} \right) =0 \Rightarrow \\ s_1 =0;\\s_2 = -\frac{\beta}{T}+i \cdot \frac{1}{T}\sqrt{1- \beta^2} \\s_3 = -\frac{\beta}{T}-i \cdot \frac{1}{T}\sqrt{1- \beta^2}

По формуле Хэвисайда

h(t)= \frac{K}{T^2} \sum_1^3 \lim_{s \to s_j } \left[ \frac{(s-s_j)}{s \cdot(s^2+\frac{2 \cdot \beta}{T}\cdot s+\frac{1}{T^2})} \cdot e^{st} \right]\ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.15)}

Разберем отдельно каждый предел:

\lim_{s \to 0} \left [ \frac{(s-0)}{s \cdot(s^2+\frac{2 \cdot \beta}{T}\cdot s+ \frac{1}{T^2})} \cdot e^{st}\right]=\frac{1}{0+0+\frac{1}{T^2}}\cdot1=T^2

Для вычисления 2-го и 3-го предела в формуле Хэвисайда более удобно использовать новые переменные m и n:

m=-\frac{\beta}{T}; \ \ \ \ \ \ \ n = \frac{1}{T}\sqrt{1-\beta^2}

Тогда корни s_1, s_2 выраженные через переменные m и n будут записаны как:

s_2 =m+i\cdot n; \ \ \ \ \ \ \ \ s_3=m-i\cdot n

Разложим квадратный трех член в скобках в занаментели на множетели и использованием корней s_2, s_3 :

s^2+\frac{2\cdot\beta}{T} \cdot s+\frac{1}{T^2} =(s-s_2)\cdot(s-s_3)

тогда 2-й предел в фомуле Хевисайда можно записать как:

\lim_{s \to (m+i \cdot n)} \left [ \frac{s -m-i\cdot n}{s (s - m-i\cdot n)(s-m+i \cdot n)} \cdot e^{s\cdot t}\right] =\\ = \frac{1}{(m+i\cdot n)(m+i\cdot n-m+i \cdot n)} \cdot e^{(m+i \cdot n)\cdot t} = \\ = \frac{1}{(m +i\cdot n)\cdot 2 \cdot i\cdot n} \cdot e^{m\cdot t}\cdot e^{i\cdot n\cdot t}

домножая на комплексно сопряженное число (m-i \cdot n)\cdot i числитель и знаменатель получим значение второго предела:

-\frac{n+m \cdot i}{(m^2+n^2)\cdot 2 \cdot n}\cdot e^{m\cdot t}\cdot e^{i\cdot n \cdot t }

Анологично 3-й предел в формуле Хевисайда можно записать как:

\lim_{s \to (m-i \cdot n)} \left [ \frac{s -m+i\cdot n}{s (s - m-i\cdot n)(s-m+i \cdot n)} \cdot e^{s\cdot t}\right] =\\ = \frac{1}{(m-i\cdot n)(m-i\cdot n-m-i \cdot n)} \cdot e^{(m-i \cdot n)\cdot t} = \\ = -\frac{1}{(m -i\cdot n)\cdot (-2) \cdot i\cdot n} \cdot e^{m\cdot t}\cdot e^{i\cdot n\cdot t}

домножая на комплексно сопряженное число (m+i \cdot n)\cdot i , числитель и знаменатель получим значение третьего предела:

\frac{-n+m\cdot i}{(m^2+n^2)\cdot 2\cdot n}e^{m\cdot t}\cdot e^{-i\cdot n\cdot t}

Отдельно сложим второе и третье слогаемое в формуле Хевисайда:

\sum_2^3 =-\frac{e^{m\cdot t}}{2 \cdot n \cdot (m^2+n^2)} \left [ (n+i \cdot m)\cdot e^{i \cdot n \cdot t}+(n-i \cdot m)\cdot e^{-i \cdot n \cdot t} \right ]==-\frac{e^{m\cdot t}}{2 \cdot n \cdot(m^2+n^2)}\left[ n \cdot e^{i \cdot n \cdot t} +i \cdot m \cdot e^{i \cdot n \cdot t}+n \cdot e^{-i \cdot n \cdot t}-i \cdot m \cdot e^{-i \cdot n \cdot t} \right] == -\frac{e^{m \cdot t}}{2 \cdot n \cdot (m^2+n^2)} \left [ n\cdot(\underbrace{e^{i \cdot n \cdot t}+ e^{-i\cdot n \cdot t}}_{2 \cdot cos(n \cdot t)})+ i \cdot m \cdot(\underbrace{e^{i \cdot n \cdot t}-e^{-i \cdot n \cdot t}}_{2\cdot i \cdot sin(n \cdot t)})\right ]== -\frac{e^{m \cdot t}}{2 \cdot n \cdot(m^2+n^2)}\left [n \cdot cos (n \cdot t)- m \cdot sin(n \cdot t)\right ] == -\frac{e^{m \cdot t}}{2 \cdot n \cdot(m^2+n^2)}\left [cos (n \cdot t)- \frac{m}{n} \cdot sin(n \cdot t)\right ]

подставляя значения n и m:

(m^2+n^2)=\frac{\beta^2}{T^2}+\frac{1-\beta^2}{T^2}=\frac{1}{T^2}\\ \frac{m}{n}=-\frac{\beta}{T}\cdot \frac{T}{\sqrt{1-\beta^2}}

и собирая все слагаемые формулы 3.5.15 получаем:

h(t)=\frac{K}{T^2}\left [T^2 - T^2 \cdot e^{m \cdot t} (cos(n \cdot t)+\frac{\beta}{\sqrt{1-\beta^2}}\cdot sin(n \cdot t)) \right] \Rightarrow h(t) = K \left [ 1 -e^{-\frac{\beta}{T}\cdot t} \left(cos \frac{\sqrt{1-\beta^2}}{T}\cdot t+\frac{\beta}{\sqrt{1-\beta^2}}sin\frac{\sqrt{1-\beta^2}}{T} \cdot t \right) \right ] \ \ \ \ \ \mathbf{(3.5.16)}

Введем новую переменную \omega_c = \frac{1}{T}\sqrt{1-\beta^2} и перепишем формулу для переходной функции:

h(t) = K \left [1 -e^{-\frac{\beta}{T} \cdot t} \left( cos(\omega_c \cdot t)+\frac{\beta}{\sqrt{1 -\beta^2}}sin(\omega_c \cdot t)\right) \right ] \ \ \ \ \ \ \ \mathbf{(3.5.16.a)}

Величина \omega_c = \frac{1}{T}\sqrt{1-\beta^2} называется частотой собственной колебаний при 0<\beta< 1 .

Таким образом в описании колебательного звена появилосьтриновых частоты \omega_m < \omega_m <\omega_c

  • \omega_0 - частота свободных колебаний;

  • \omega_m- частота, соответствующая максимальной амплитуде;

  • \omega_c- частота собственных колебаний.

Причем \omega_m < \omega_m <\omega_c

Рассмотрим предельные случаи для (т.е. = 1 и = 0):

Если \beta \to 0 , то \omega_c \to \omega_0=\frac{1}{T} :

h(t) = K \left [1 -e^{0\cdot t} \left ( cos \frac{t}{T} +0 \cdot sin \frac{t}{T} \right ) \right]h(t) = K \left [ 1 - cos \frac{t}{T}\right ] \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.17)}

3.5.17 - переходная функция консервативного звена.

Рисунок 3.5.6 Переходная функция консервативного звенаРисунок 3.5.6 Переходная функция консервативного звена

Если \beta \to 1 , то \omega_c \to 0 , т.е. собственных колебаний в звененет, процесс без колебательный.В этом случае возникают трудности со вторым слагаемым в круглых скобках формулы (3.5.16).

Раскрываем неопределенность типа\frac{0}{0}:

\lim_{\beta \to 1 } \left[ \frac{\beta}{\sqrt{1-\beta^2}} \cdot sin \left (\frac{\sqrt{1-\beta^2}}{T} \cdot t \right ) \right ] = \lim_{\beta \to 1} \left [ \frac{\beta \cdot t}{T} \cdot \frac{sin(\frac{\sqrt{1-\beta^2}}{T}\cdot t)}{\underbrace{\frac{\sqrt{1-\beta^2}}{T}}_{\approx \frac{sin x}{x}}} \right ]=\frac{t}{T}h(t)_{\beta=1} = K \left [ 1 - e^{-\frac{t}{T}}\cdot \left (1 +\frac{t}{T} \right)\right] \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.18)}

эта формула соответствует также аналогичной формуле для апериодического звена 2-го порядка приD= 0 (совпадающиеполюса).

Рисунок 3.5.8 Переходная функция колебательного звена (при = 1)Рисунок 3.5.8 Переходная функция колебательного звена (при = 1)Рисунок 3.5.9 Переходная функция колебательного звена (при 0 < < 1)Рисунок 3.5.9 Переходная функция колебательного звена (при 0 < < 1)

Если 0<\beta<1 , то \beta =T\cdot \frac{\omega_c}{\pi} \cdot \ln \frac{A_1}{A_2}

Дифференцируя во времени формулы (3.5.16 3.5.18), найдем соответствующие весовые функции для крайних значений \beta (w(t)):

Если \beta =0 \Rightarrow

 w(h)_{\beta =0} = h'(t)= \frac{K}{T} sin \left ( \frac{t}{T} \right ) \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.19)}Рисунок 3.5.10 Весовая функция колебательного звена при = 0.Рисунок 3.5.10 Весовая функция колебательного звена при = 0.

Если \beta =1 \Rightarrow

 w(h)_{\beta =1} = h'(t)= K \left [ \frac{1}{T} \cdot e^{-\frac{t}{T}}\cdot \left( 1+ \frac{t}{T} \right) - e^{-\frac{t}{T}} \cdot \frac{t}{T} \right ]w(h)_{\beta =1} = \frac{K}{T^2} \cdot t \cdot e ^{-\frac{t}{T}} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.20)}Рисунок 3.5.11 Весовая функция колебательного звена при = 1.Рисунок 3.5.11 Весовая функция колебательного звена при = 1.

Если 0<\beta <1 \Rightarrow

w(t) = h'(t) = \frac{K}{T\cdot \sqrt{1 -\beta^2}}\cdot e^{-\frac{\beta \cdot t}{T}} \sin \left ( \frac{\sqrt{1-\beta^2}}{T} \cdot t \right) \ \ \ \ \ \ \ \ \ \ \mathbf{(3.5.21)}\beta = T\cdot \frac{\omega_c}{\pi}\cdot \ln\frac{B_1}{B_2}Рисунок 3.5.12 Весовая функция колебательного звена при 0 < < 1.Рисунок 3.5.12 Весовая функция колебательного звена при 0 < < 1.

Примерами колебательного звена можно считать:

  1. RCL цепь см. начало статьи;

  2. Упругиемеханические передачи;

  3. Гироскопический маятник;

  4. Управляемый двигатель постоянного тока (при некоторых условиях).

Пример

В качестве примера для исследования колебательного звена возьмем электрический колебательный контур, который был рассмотрен в начале статьи и сравним его с моделью колебательного звена.Модель контура представлена на рисунке 3.5.13:

Рисунок 3.5.13 Модель колебательного контураРисунок 3.5.13 Модель колебательного контура

Схема модели содержит в себе:

  1. модель электрического контура в виде электрической схемы;

  2. модель контура в виде колебательного звена.

Параметры электрической схемы задаются в виде общих сигналов проекта. См. рис. 3.5.14:

Рисунок 3.5.14 Общие сигналы проекта.Рисунок 3.5.14 Общие сигналы проекта.Рисунок 3.5.15. Вычисление параметров для колебательного звена.Рисунок 3.5.15. Вычисление параметров для колебательного звена.

В общем скрипте проекта выполняется вычисление постоянной времениTи коэффициента демпфирования \beta

Для сравнения модели в виде электрической схемы и модели в виде колебательного звена, выполним моделирование ступенчатого возрастания напряжения, с 0 до 1 В.

Рисунок 3.5.16. Графики напряжений источника и на конденсаторе.Рисунок 3.5.16. Графики напряжений источника и на конденсаторе.

Выполним гармонический анализ данной модели, аналогично тому, как мы это делали для модели демпфера и камеры смешения реактора демпфера.

Рисунок 3.5.17 Сравнение модели контура и колебательного звенаРисунок 3.5.17 Сравнение модели контура и колебательного звена

На графике рис. 3.5.16 видно возникновение колебательного процесса и его затухание с течением времени. График на рис. 3.5.17 показывает практически полное совпадение модели в виде электрической схемы и модели в виде колебательного звена:

Выполним гармонический анализ данной модели, аналогично тому, как мы это делали для модели демпфера и камеры смешения реактора демпфера (см. разделы 3.3 Апериодическое звено 1-го порядка. и 3.1 Амплитудно-фазовая частотная характеристика). Расчетная схема для такого анализа приведена на рисунке 3.5.18.

Рисунок 3.5.18. Частотный анализ электрического контураРисунок 3.5.18. Частотный анализ электрического контура

Амплитуда входного тестового сигнала - 1 В, аналогична амплитуде ступенчатого воздействия из предыдущего численного эксперимента.

Результаты анализа представлены на рисунке 3.5.19

Рисунок 3.5.19 Результаты гармонического анализа.Рисунок 3.5.19 Результаты гармонического анализа.

Результаты моделирования показывают практическое совпадение теоретических значений частоты, при которой достигается максимальная амплитуда сигнала, и значений, полученных в результате моделирования электрической схемы: Теоретическое значение = 111,75 Гц Полученное моделированием = 112,2 Гц

Для исследования влияния параметров модели добавим на схему управляющие элементы, которые буду менять сопротивление резистора и емкость конденсатора во время расчёта.

 Рисунок 3.5.20 Модель с изменяемыми параметрами контура. Рисунок 3.5.20 Модель с изменяемыми параметрами контура.

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

Чтобы данная конфигурация заработала, необходимо добавить в скрипт программы код, который заберёт значения с ползунков и передаст их в параметры модели (см. рис 3.5.21)

Рисунок 3.5.21. Скрипт изменения параметров моделиРисунок 3.5.21. Скрипт изменения параметров модели

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

Например, при положении ползунков, изображенном на рисунке 3.5.22, колебательный контур превращается в апериодическое звено второго порядка (см. рис. 3.5.23.)

Рисунок 3.5.22. Настройки контура для устранения колебанийРисунок 3.5.22. Настройки контура для устранения колебанийРисунок 3.5.23. Графики изменения переходных процессов в контуре при изменении R и С.Рисунок 3.5.23. Графики изменения переходных процессов в контуре при изменении R и С.

При увеличении сопротивления резистора и емкости кондесатора происходит увеличение коэффициента демпфирования, и когда Если \beta >1 \Rightarrow колебательное звено превращается в апериодическое 2-го порядка. (см. график на рис 3.5.23.

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

Заменим в качестве источника блок меандр, на блок синусоида и добавим ползунок, изменяющий частоту этого источника, а также добавим на схему текстовые надписи, отображающие частоты максимальной амплитуды, частоты собственных колебаний и частоты свободных колебаний. Расчетная схема будет выглядеть как на рисунке 3.5.25

Рисунок 3.5.24 Схема колебательного контура с настройками частоты источника.Рисунок 3.5.24 Схема колебательного контура с настройками частоты источника.

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

Рисунок 3.5.24 Скрипт для управления и отображения частоты.Рисунок 3.5.24 Скрипт для управления и отображения частоты.

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

В частности, можно убедится, что при различных настройках колебательного контура максимальная амплитуда колебаний напряжения достигается тогда, когда частота источника совпадает с частотой максимальной амплитуды, рассчитанной по формуле 3.5.10 см.скрипт на рис. 3.5.24.

Видео с управлением данным контуром можно посмотреть по ссылке.

А, например, на следующем графике изображено изменение напряжения на конденсаторе при повышении частоты источника от 0 до 300 Гц с шагом 1 Гц 1 сек.

График построен путем давления в скрипте строки, передвигающей ползунок каждую секунду на 1 единицу (Гц) BarW.Value=Round(time) .

Как видим результат ручного управления совпал с результатом гармонического анализа максиму амплитуды теоретической частоте максимума - 112 Гц.

Примеры проектов для самостоятельного изучения можно взять по ссылке здесь.

Предыдущая лекция. 3.4 Апериодическое звено 2го порядка.

Подробнее..

3. Частотные характеристики звеньев и систем автоматического регулирования. 3.7 Форсирующее звено

01.06.2021 02:22:59 | Автор: admin

Лекции по курсу Управление Техническими Системами читает Козлов Олег Степанович на кафедре Ядерные реакторы и энергетические установки факультета Энергомашиностроения МГТУ им. Н.Э. Баумана. За что ему огромная благодарность!

Данные лекции готовятся к публикации в виде книги, а поскольку здесь есть специалисты по ТАУ, студенты и просто интересующиеся предметом, то любая критика приветствуется. В предыдущих сериях:

1. Введение в теорию автоматического управления.
2. Математическое описание систем автоматического управления 2.1 2.3,2.3 2.8,2.9 2.13.
3. ЧАСТОТНЕ ХАРАКТЕРИСТИКИ ЗВЕНЬЕВ И СИСТЕМ АВТОМАТИЧЕСКОГО УПРАВЛЕНИЯ (РЕГУЛИРОВАНИЯ).
3.1. Амплитудно-фазовая частотная характеристика: годограф, АФЧХ, ЛАХ, ФЧХ.
3.2. Типовые звенья систем автоматического управления (регулирования). Классификация типовых звеньев. Простейшие типовые звенья.
3.3. Апериодическое звено 1го порядка (инерционное звено). На примере входной камеры ядерного реактора.
3.4. Апериодическое звено 2-го порядка.
3.5. Колебательное звено.3.3. Апериодическое звено 1го порядка (инерционное звено). На примере входной камеры ядерного реактора.
3.6. Инерционно-дифференцирующее звено.

Тем сегодняшней статьи: 3.7 Форсирующее звено (идеальное звено с введением производной)

Уравнение динамики форсирующего звена:

y(t) = k \cdot[x(t)+\tau \cdot x'(t)] \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.7.1)}

Уравнение динамики в изображениях Лапласа:

Y(s) = k \cdot [\tau\cdot s+1]\cdot X(s)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.7.2)}

В общем, данное звено формально можно отнести к позиционным, т.к.a_0=1; b_0 = 0или статическая характеристика имеет вид: y(0)= k \cdot x(0) .

Передаточная функция форсирующего звена:

W(s) =\frac{Y(s)}{X(s)}= k \cdot [\tau \cdot s+1] =k\cdot \tau\cdot s+k \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.7.3)}Рисунок 3.7.1 Эквивалентная структурная схема форсирующего звенаРисунок 3.7.1 Эквивалентная структурная схема форсирующего звена

АФЧХ форсирующего звена, получается путем замены s= i \cdot \omega:

W(i\cdot \omega) = k \cdot[1+i\cdot \tau\cdot \omega ] = \underbrace{k}_{Re}+i\cdot\underbrace{k\cdot \tau\cdot\omega}_{Im} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.7.4)}

Модуль АФЧХ:

u(\omega) = k\\ v(\omega)= k \cdot \tau \cdot \omega \left \{ \begin{gathered} U(\omega) = k \\ V(\omega) = k \cdot \tau\cdot \omega\ \end{gathered} \right. \Rightarrow A(\omega) = |W(i\cdot \omega) | = k \cdot \sqrt{1+\tau^2\cdot \omega^2} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.7.5)}

Подставляя в формулы (3.7.4) и (3.7.5) различные значениястроим соответствующие графики:

Рисунок 3.7.2 АФЧХ форсирующего звенаРисунок 3.7.2 АФЧХ форсирующего звенаРисунок 3.7.3 АЧХ и ФЧХ форсирующего звенаРисунок 3.7.3 АЧХ и ФЧХ форсирующего звена

Логарифмическая амплитудная характеристика (ЛАХ):

Lm(\omega) = 20 \cdot lg (A(\omega))=20 \cdot lg (k)+ 20 \cdot lg \sqrt{1+\tau^2\cdot \omega^2} \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.7.6)}Рисунок 3.7.4 ЛАХ и ЛФЧХ форсирующего звенаРисунок 3.7.4 ЛАХ и ЛФЧХ форсирующего звена

Если \omega_{сопр} <<\frac{1}{\tau} звено приблизительно совпадает с идеальным усилительным звеном - \omega(s)\approx k .

Если \omega_{сопр} >> \frac{1}{\tau} - звено приблизительно совпадает с идеальным дифференцирующим звеном -\omega(s) \approx k \cdot \tau \cdot s

Переходная функция:

h(s) = L^{-1} \left[ H(s) \right] = L^{-1} \left[ \frac{W(s)}{s}\right] = L^{-1}\left[ \frac{k}{s}+\frac{k \cdot \tau \cdot s}{s}\right] = k \cdot Z^{-1}\left[\frac{1}{s}+\tau \right] \Rightarrowh(t) = k \cdot 1(t)+k \cdot \tau \cdot \delta(t)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.7.7)}

Весовая функция получается диффернцированием h(t) поt:

w(t) = k \cdot \left[\delta(t)+ \tau\cdot \delta'(t) \right]\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \mathbf{(3.7.8)}

Построим соответствующие графики:

Рисунок 3.7.5 Переходная функция форсирующего звенаРисунок 3.7.5 Переходная функция форсирующего звенаРисунок 3.7.6 Весовая функция форсирующего звенаРисунок 3.7.6 Весовая функция форсирующего звена

Примечание: Данное звено реализуется в ПД-регуляторах, обеспечивающих введение производных в закон управления. ПД-регулятор увеличивает быстродействие замкнутых САР, т.к. управление ведется по рассогласованию и по производной от рассогласования.

Пример

Мы уже запилили 10 лекций по УТС Управление в технических устройствах, но пока не рассмотрели ни одного примера, где было представлено собственно управление. Поскольку рассматриваемое в этой лекции звено используется в виде регулятора, разберем, наконец, в примере непосредственно модель управления техническим устройством, чтобы еще немного переместиться от теории к практической реализации.

Создадим комплексный в проект, в котором будут модель технического объекта (файл проектаnode_НS_2.prt) и модель системы управления (файлpd.prt), объединенные в пакет (файлnode_НS_2.pak)

В качестве технического объекта возьмём модель камеры смешения, используемую как иллюстрацию лекции Апериодическое звено первого порядка, и добавим к модели Узел регулирования температуры (см. рис. 3.7.7)

Рисунок 3.7.7 Модель камеры смешения с узлом регулирования температурыРисунок 3.7.7 Модель камеры смешения с узлом регулирования температуры

Узел регулирования температуры представляет собой дополнительный трубопровод с регулирующим клапаном (Valve_1 см. рис. 3.7.7). С одной стороны трубопровод подключён к узлу камеры смешения, с другой стороны задается граничное условие (ГУ) по давлению и температуре.Давление в ГУ больше давления в камере смешения, и температура то же больше чем на входе в камеру смешения.

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

В системе также установлен датчик температуры в узле камеры смешения. База сигналов проекта содержит два сигнала:

  • Температура в камере смешения (берется из датчика);

  • Положение клапанаValve_1.

    Модель системы управления представлена на рисунке 3.7.8

Рисунок 3.7.8 Модель системы управленияРисунок 3.7.8 Модель системы управления

В системе управления показания датчика, полученные из базы данных, сравниваются с уставкой по температуре (с требуемой температурой). Отклонения передаются на два регулятора: один из нихпропорциональный, другой пропорционально дифференцирующий.

Настройки регуляторов взяты по умолчанию, все коэффициенты равны 1.

Модель позволяет переключаться между регуляторами с помощьюблокаключа.

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

Рисунок 3.7.9 Выбор типа регулятора в настройкахРисунок 3.7.9 Выбор типа регулятора в настройках

Для демонстрации работы П и ПД регуляторов используется один и тот же готовый блок ПИД регулятор. Тип регулятора задается в свойствах блока (см. рис. 3.7.9)

Рисунок 3.7.10. Скрипт изменения заданной температурыРисунок 3.7.10. Скрипт изменения заданной температуры

Для демонстрации режима управления в общем скрипте программы управления задается последовательное изменение требуемой температуры. (см. рис. 3.7.10)

В начальный момент времени заданная температура соответствует установившейся в системе температуре при 50% открытии клапана. На 10 секунде заданная температура меняется на 22 градуса С, на 50 секунде заданная температура меняется на 23.5 градусов С.

Чтобы можно было сравнить два варианта управления на одном графике, добавим еще один проект в пакет (файлdata.prt).

В данном файле расчетная схема содержит график, на котором выводятся текущее значение температуры (из базы данных сигналов) и значения из файла с предыдущим расчётом (temp_old.dat).

3.7.11 Проект и скрипт для сравнения двух рассчетов3.7.11 Проект и скрипт для сравнения двух рассчетов

Во время расчёта мы сохраняем текущее значение в файл (temp_cur.dat). По завершению расчёта (секцияfinalizationскрипта) мы копируем данные из текущего файла в сохраненный ранее с помощью глобального скрипта программы. (см. рис. 3.7.11).

Результаты моделирования представлены на рисунке 3.7.12

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

Рисунок 3.7.12 Сравнение П и ПД регуляторовРисунок 3.7.12 Сравнение П и ПД регуляторов

Пример для самостоятельного изучения можно взять здесь.

Предыдущая лекция из главы 3, Частотные характеристики звеньев и систем автоматического регулирования: 3.6 Инерционно-дифференцирующее звено.

Подробнее..

Разработка универсального счетчика импульсов

09.02.2021 14:04:30 | Автор: admin

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

Оптические датчики и их особенности

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

Рисунок 1. Типы датчиков (И - источник, П - приемник, О - объект).Рисунок 1. Типы датчиков (И - источник, П - приемник, О - объект).

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

Первый прототип датчика

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

За отправную точку было решено взять аналоговую часть универсального тахометра, который работает на отраженном свете. В этом случае на движущийся механизм (например, вал) крепится яркая светоотражающая метка, наводится луч света, и прибор показывает частоту вращения. Источником света может выступать как лазер, так и обычный светодиод. В первом прототипе нашего счетчика импульсов за источник света был взят светодиод, использовалась линза с фокусным расстоянием 35 мм. На одном операционном усилителе был собран компаратор, на другом буферный элемент. Также была собрана цепочка, обеспечивающая плавающий порог срабатывания компаратора.

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

Испытания показали работоспособность в диапазоне 10-30 см с очень контрастными метками (использовали световозвращающий скотч). Но такой результат нас еще не устраивает, так так датчик на просвет работает на значительно больших расстояниях. Также система оказалась чувствительна к включению/выключению освещения в помещении, и при использовании подобного подхода от этого недостатка избавиться не получится.

Второй прототип датчика

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

Принципиальная схема
Рисунок 3. Второй прототип датчика - принципиальная схема.Рисунок 3. Второй прототип датчика - принципиальная схема.

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

Третий прототип датчика

В результате проведенных опытов стало понятно, что нельзя обойтись без дополнительной настройки системы, которую можно осуществить только с помощью микроконтроллера. Также для исключения влияния помех от фонового освещения решили добавить модуляцию опорного сигнала и преобразование Фурье на приемнике. Корпус уже был разработан и изготовлен на предыдущих этапах, и нам хотелось вписаться в его габариты. Так выбор пал на практически единственный вариант - STM32G030J6M6 Cortex - M0+ c ADC 2.5Msps в корпусе SOIC-8. Отличное решение для непрерывной обработки данных от АЦП. Общение с микроконтроллером осуществляется по шине I2C.

Принципиальная схема
Рисунок 4. Третий прототип датчика - принципиальная схема.Рисунок 4. Третий прототип датчика - принципиальная схема.

На операционном усилителе собран трансимпедансный усилитель тока фотодиода. Лазер модулируется дискретным сигналом от таймера, потому что в данном случае нет необходимости получать чистый синус. Для совместимости с предыдущими решениями был сделан дискретный вывод для использования аппаратного счетчика событий (1й пин разъема P1), а конфигурация осуществляется один раз при старте системы. Таким образом, сохраняется полная преемственность с уже написанным ПО.

В микроконтроллере реализованы генерация сигнала ШИМ, обработка оцифрованных данных и общение по I2C. За генерацию ШИМ отвечает таймер, синхронизированный с АЦП. Данные передаются в память по DMA и обрабатываются по половинам - пока заполняется первая половина буфера, вторая анализируется. Сам алгоритм обработки данных получится следующий:

Рисунок 5. Алгоритм обработки данныхРисунок 5. Алгоритм обработки данных

Микрокомпьютер

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

  • возможность запускать программу на Python 3,

  • место для пары сетевых библиотек,

  • интерфейсы Ethernet и Wi-Fi для связи с сервером,

  • питание по micro USB или PoE,

  • производительность - не критично,

  • время включения - не более 2 минут,

  • хранилище данных не требуется, так как мы хотим передавать их на сервер, и буфера в оперативной памяти будет достаточно.

Сначала мы использовали Orange Pi zero, однако, учитывая их немалые габариты и невозможность нормально сделать PoE, решено было поискать другие варианты. Так взгляд пал на одноплатный компьютер VoCore, характеристики которого полностью подходили под задачу. Изучив предложения на китайском рынке, был найден очень похожий вариант выпускаемый массово - процессор RT5350, 32Mb RAM, 8/16Mb Flash.

Рисунок 6. Одноплатный компьютер VoCore.Рисунок 6. Одноплатный компьютер VoCore.

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

Конструктив

Рисунок 7. 3Д модель счетчика.Рисунок 7. 3Д модель счетчика.

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

Рисунок 8. Поворотная часть корпуса.Рисунок 8. Поворотная часть корпуса.

От люфта и случайного поворота защищает пружина и фрикционная шайба. Для большинства задач этого оказывалось достаточно. Материнская плата, модуль PoE и сам компьютер расположены максимально компактно в основной части корпуса.

Рисунок 9. Основная часть корпуса.Рисунок 9. Основная часть корпуса.

Так как партии пока относительно небольшие корпус изготавливается методом SLS печати.

Итак, в итоге у нас получилась следующая архитектура устройства:

  • вычислительный модуль (одноплатный компьютер),

  • основная плата, на которой расположены разъемы Ethernet, USB, I2C, светодиоды и кнопка,

  • плата питания (устройство может питаться как от microUSB так и от PoE).

Подсчет срабатываний

Теперь пара слов о том, как реализован подсчет срабатываний датчика. Независимо от типа датчика, алгоритм подсчета импульсов остается одинаковым. Выход датчика подключается к GPIO процессора. Количество импульсов подсчитывалось через GPIO interrupt. Для этого требуется настроить GPIO на вход и включить прерывания. Об этом хорошо написано, например, тут. Число срабатываний можно посмотреть командой cat /proc/interrupts | grep gpiolib. Если же требуется реагировать на каждое событие или записывать время его срабатывания, то уже придется написать простую программу. Данный подход хорошо себя зарекомендовал и является необходимым и достаточным источником данных для подобного класса датчиков. В случае датчика с микроконтроллером, нужно перед началом работы загрузить необходимые параметры по I2C.

Заключение

Итак, что мы имеем на выходе? Компактное устройство для подсчета импульсов с оптическим датчиком и готовой реализацией отправки данных на сервер по Ethernet или WiFi. Была реализована передача данных по MQTT. Адаптивная архитектура также позволяет легко подключать практически любой другой датчик по I2C или SPI через переходник. На данный момент имеются такие варианты счетчиков: лазерный с аналоговой обработкой сигналов, лазерный с цифровой обработкой сигналов, а также индукционный счетчик для подключения внешнего промышленного индукционного датчика. Разработанный корпус позволил осуществлять поворот оптического модуля, а также его замену на другой тип датчика. В ближайших планах хотим подключить тепловизионный датчик для мониторинга нагруженных узлов в производстве.

Подробнее..

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

09.04.2021 14:13:02 | Автор: admin

Майкл Джордан объясняет, почему современные системы искусственного интеллекта на самом деле не являются интеллектуальными

Автор: Кэти Претц

Майкл И.ДжорданМайкл И.Джордан

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

Он отмечает, что имитация человеческого мышления не является единственной целью машинного обучения инженерной области, которая лежит в основе недавнего прогресса в области ИИ или даже лучшей целью.Вместо этого машинное обучение может способствовать развитию человеческого интеллекта посредством кропотливого анализа больших массивов данных, подобно тому как поисковая система расширяет человеческие знания, организуя сеть.Машинное обучение также может предоставлять новые услуги людям в таких областях, как здравоохранение, коммерция и транспорт, объединяя информацию, найденную в различных наборах данных, находя закономерности и предлагая новые направления действий.

Люди путаются в значении ИИ в дискуссиях о технологических тенденциях, что в компьютерах есть какая-то интеллектуальная мысль, которая отвечает за прогресс и конкурирует с людьми, - говорит он. У нас этого нет, но люди говорят так, будто у нас есть.

В конце концов, Джордан должен знать разницу.Стипендиат IEEE является одним из ведущих мировых авторитетов в области машинного обучения.В 2016 году он был признансамым влиятельным компьютерным ученымпо версии программы, которая анализировала научные публикации,сообщает Science.Джордан помог преобразовать неконтролируемое машинное обучение, которое может находить структуру в данных без заранее существующих меток, из набора несвязанных алгоритмов в интеллектуально согласованную область,Вики-историю инженерии и технологийобъясняет.Неконтролируемое обучение играет важную роль в научных приложениях, где отсутствует устоявшаяся теория, которая могла бы обеспечить маркированные обучающие данные.

Вклад Джордана принес ему множество наград, в том числе премиюУльфа Гренандера в области стохастической теории и моделированияотАмериканского математического общества.В прошлом году он получилмедаль IEEE Джона фон Нейманаза вклад в машинное обучение и науку о данных.

В последние годы он занимался тем, чтобы помочь ученым, инженерам и другим понять весь объем машинного обучения.Он говорит, что считает, что развитие машинного обучения отражает появление новой области техники.Он проводит параллели с появлением химической инженерии в начале 1900-х годов из основ химии и механики жидкости, отмечая, что машинное обучение строится на десятилетиях прогресса в области компьютерных наук, статистики и теории управления.Более того, по его словам, это первая инженерная область, которая является гуманоцентрической, ориентированной на взаимодействие между людьми и технологиями.

Хотя научно-фантастические дискуссии об ИИ и суперинтеллекте забавны, они отвлекают внимание, говорит он. Не было достаточного внимания к реальной проблеме, которая заключается в создании систем машинного обучения планетарного масштаба, которые действительно работают, приносят пользу людям и не усиливают неравенство.

ПРИСОЕДИНЕНИЕ К ДВИЖЕНИЮ

Будучи ребенком 60-х годов, Джордан интересовался философскими и культурными взглядами на то, как работает ум.Он вдохновился изучением психологии и статистики после прочтения автобиографии британского логикаБертрана Рассела.Рассел исследовал мышление как логический математический процесс.

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

Джордан изучал психологию в Университете штата Луизиана в Батон-Руже, где в 1978 году получил степень бакалавра по этому предмету. В 1980 году он получил степень магистра математики в Университете штата Аризона в Темпе, а в 1985 году-степень доктора когнитивных наук в Калифорнийском университете в Сан-Диего.

Когда он поступил в колледж, область машинного обучения еще не существовала. Это только начало проявляться, когда он закончил школу.

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

В 2003 году он и его ученики разработали латентное распределение Дирихле, вероятностную структуру для изучения тематической структуры документов и других коллекций данных без надзора, согласно Вики. Этот метод позволяет компьютеру, а не пользователю, самостоятельно находить закономерности и информацию из документов. Фреймворк является одним из самых популярных методов моделирования тем, используемых для обнаружения скрытых тем и классификации документов по категориям.

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

УТОЧНЯЮЩИЙ ИИ

В 2019 году Джордан написал книгу Искусственный интеллектреволюция еще не произошла, опубликованную в журнале Harvard Data Science Review. Он объясняет в статье, что термин ИИ неправильно понимают не только люди, но и технологи. Еще в 1950-х годах, когда был придуман этот термин, пишет он, люди стремились создать вычислительные машины, которые обладали интеллектом человеческого уровня. Это стремление все еще существует, говорит он, но то, что произошло за прошедшие десятилетия, - это нечто иное. Компьютеры не стали интеллектуальными сами по себе, но они предоставили возможности, которые увеличивают человеческий интеллект, пишет он. Более того, они преуспели в низкоуровневых способностях распознавания образов, которые в принципе могли бы быть выполнены людьми, но с большими затратами. Системы, основанные на машинном обучении, способны обнаруживать мошенничество в финансовых операциях в массовом масштабе, например, тем самым катализируя электронную торговлю. Они играют важную роль в моделировании и контроле цепочек поставок в производстве и здравоохранении. Они также помогают страховым агентам, врачам, педагогам и кинематографистам.

Несмотря на то, что такие разработки называют технологией искусственного интеллекта, пишет он, лежащие в их основе системы не включают в себя высокоуровневые рассуждения или мышление. Системы не формируют тех семантических представлений и умозаключений, на которые способны люди. Они не формулируют и не преследуют долгосрочных целей.

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

Более того, подчеркивает он, человеческое счастье не должно быть запоздалой мыслью при разработке технологий. У нас есть реальная возможность постичь нечто исторически новое: гуманоцентрическую инженерную дисциплину, пишет он.

Перспектива Джордана включает в себя оживленное обсуждение роли инженерии в государственной политике и академических исследованиях. Он отмечает, что когда люди говорят о социальных науках, это звучит привлекательно, но термин "социальная инженерия" звучит непривлекательно. То же самое справедливо и для науки о геноме в сравнении с геномной инженерией.

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

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

ПОСТРОЕНИЕ СООБЩЕСТВА

Джордан говорит, что он особенно ценит IEEE за ее инвестиции в создание механизмов, с помощью которых сообщества могут общаться друг с другом через конференции и другие форумы.

Он также высоко ценит продуманную издательскую политику IEEE.Многие из его работ доступны вцифровой библиотеке IEEE Xplore.

Я думаю, что коммерческие издательства построили бизнес-модель, которая сейчас неэффективна и фактически блокирует поток информации, говорит он. Через журнал открытого доступаIEEE Access организация позволяет и помогает потоку информации.

Членство в IEEE предлагает широкий спектрпреимуществ и возможностейдля тех, кто разделяет общие интересы в области технологий.Если вы еще не являетесь членом IEEE, подумайте о том, чтобы присоединиться к ней и стать частью всемирной сети, насчитывающей более 400 000 студентов и специалистов.

Подробнее..

Система автоматического сброса на промышленном программируемом реле

23.02.2021 16:14:33 | Автор: admin

Имеется транспортёр, на котором установлен датчик дефектов и исполнительное устройство - сбрасыватель. По транспортёру движутся объекты, задача - производить сброс в случае обнаружения дефекта. Длина объекта от 1,5 до 7 метров, во избежание различных интересных эффектов сброс необходимо производить в момент прохождения центра объекта мимо сбрасывателя. Пройденное объектом расстояние измеряется при помощи датчика перемещения (инкрементального энкодера), наличие объекта определяется датчиком наличия (фотодатчиками). Обработав данные с этих датчиков, можно измерить длину объекта и вычислить необходимое перемещение до точки сброса.

Это история об использовании простого программируемого реле в реальной задаче промышленной автоматизации. Но такое ли оно простое? Добро пожаловать в программирование без единой строчки кода!

Для решения этой задачи используем имеющееся в наличии программируемое реле Siemens LOGO 6 серии. Подобные реле есть у многих производителей, они как правило имеют небольшие размеры и размещаются на стандартной DIN-рейке. Я попытаюсь на примере LOGO рассказать, что они из себя представляют и чем отличаются от ПЛК.
Базовый модуль имеет несколько входов, выходов, разъём для программирования, дисплей и кнопки управления (есть более дешёвые модели без кнопок и дисплея). Более новые серии (начиная с 7) имеют слот для SD карты и Ethernet. Питание бывает как низковольтное, так и 230 Вольт. К базовому модулю могут подключаться модули расширения - входные, выходные, коммуникационные. Максимальная конфигурация - 24 входа и 16 выходов, программа может содержать до 200 элементов (до 400 начиная с 7 серии).
Отличие от ПЛК: входы и выходы подключаются на неразъёмный клеммник, нет индикации их состояния, программа не может меняться во время исполнения и перезаписывается только целиком. Для программирования могут использоваться только два языка из стандарта МЭК 61131-3: LAD (релейно-контактная логика) и FBD (функциональные блоки).

Это одна и та же программа, слева FBD, справа LADЭто одна и та же программа, слева FBD, справа LAD

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

Это первый набросок, когда казалось что главная проблема это определить точку сбросаЭто первый набросок, когда казалось что главная проблема это определить точку сброса

Как оказалось, датчик дефектов - довольно сложное устройство и имеет свой ленточный транспортёр, в связи с этим разместить фотодатчики наличия объекта удалось только на расстоянии 3 метров от него. Как следствие - во время его срабатывания датчики наличия могут видеть совсем другой объект. Расстояние от датчиков наличия до сбрасывателя составило 9 метров, на таком расстоянии могут разместиться до 6 объектов одновременно - их все придётся отслеживать. Сбрасыватель тоже непрост, его исходное положение необходимо контролировать для подтверждения удачного сброса и чтобы исключить ситуацию когда он завис поперёк транспортёра. Сбрасыватель установлен на выходном транспортёре, который управляется из другого места - от нас нужен сигнал разрешения его работы. Инкрементальный энкодер также установлен на выходном транспортёре. Ну и для удобства отладки и диагностики я решил добавить возможность настройки расстояний через дисплей и индикацию типа нештатной ситуации с помощью количества миганий лампы "Помеха". Уже в процессе разработки пришла идея добавить на дисплей текущее значение скорости транспортера, измеренной длины объекта и счетчики нештатных ситуаций. Также добавил мигание лампы в момент прохождения точки сброса каждым объектом - это в дальнейшем сильно упростило отладку.

РекурсияРекурсия

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

Программный счётчик имеет функцию включения/выключения выходного сигнала при достижении заданных пороговПрограммный счётчик имеет функцию включения/выключения выходного сигнала при достижении заданных порогов

В качестве порогов могут выступать значения других счётчиков или результаты вычислений.

Математика простая - 4 действияМатематика простая - 4 действия

Алгоритм работы системы: при поступлении сигнала от датчика дефектов он проходит линию задержки на одном из счётчиков расстояний ДД и привязывается к имеющемуся объекту, при этом горит лампа "Обнаружен дефект". Затем начинается отслеживаемое одним из счётчиков сброса движение объекта к точке сброса, лампа при этом мигает. Измерение длины объекта производится счётчиком длины, измеренное значение фиксируется в блоке вычислений соответствующего канала сброса и используется для вычисления точки сброса. При её достижении транспортёры останавливаются и производится сброс объекта. После возврата сбрасывателя в исходное положение транспортёры запускаются. Входной транспортёр имеет задержку запуска 4 секунды для компенсации времени разгона остальных транспортёров, которые управляются частотными преобразователями. Возможные нештатные ситуации - срабатывание датчика без объекта, отсутствие исходного положения сбрасывателя, отсутствие реакции сбрасывателя на сигал сброса, превышение габаритов объекта (датчик на входе), превышение длины объекта в результате нахлёста или неисправности датчика наличия. Также контролируется готовность датчика дефектов и частотных преобразователей привода движения.

Больше всего при разработке программы напрягали логические элементы без возможности расширения количества входов, прямо как старые добрые ЛА3. В старших контроллерах входы элемента можно наращивать по мере необходимости. Ещё если к выходу элемента подключен десяток входов и вдруг понадобилось добавить в разрыв ещё элемент - перерисовывать придётся все 10 линий. Особенно это доставляет, когда они идут на другие страницы - не помешало бы сделать повторители. Все выходы элементов нужно куда-то подключать, даже если они не нужны - придётся использовать открытый коннектор, без этого можно запустить симуляцию, но нельзя загрузить программу в контроллер. Этих коннекторов ограниченное количество, если не хватает - нужны логические элементы для объединения бесполезных сигналов. Ну и пользовательские функции для повторяющихся блоков - у меня их не было, но даже если бы я взял более новую модель LOGO - это бы не помогло, не влез в ограничения. Ну ладно, я знал на что иду - это же просто программируемое реле, и подобное его использование скорее исключение.

Программа в графическом виде разместилась на 8 листах А4, тут я привожу её в виде двух картинок (осторожно, они большие).

Картинка 1
Картинка 2

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

Подробнее..

Мобильная Установка Доказательства Актуальности Контроля Измерений. Часть 2. Нам срочно нужна связь!

01.03.2021 10:14:41 | Автор: admin

-Нам срочно нужна связь

-Половую предложить не могу!

(с) Звездные Войны. Буря в стакане. Студия "Божья Искра" (ст. о/у Goblin)

Собранная и запрограммированная в конце прошлого года Мобильная Установка Доказательства Контроля Измерений (аббревиатуру сотавите сами) периодически включалась, строила графики давления и снова выключалась. Все бы хорошо, да применяемая там панель серии Basic позволяет хранить треды, в лучшем случае, дней за пять, чего маловато. Как я уже говорил ранее, применять панель Unified Comfort, в которой таких ограничений нет, совершенно не с руки. Панель Unified не является мобильной, вся установка - это набор модулей на дин-рейке (корпуса нет, да уже и не будет), в общем - рассыпуха на коленке. Сейчас она "живет" в ванной комнате, заводить кабель в ванную - тоже не с руки... это либо оставлять дверь открытой, либо долбить стену. А ради чего?

В общем, единственный выход - это беспроводные сети, благо, WiFi есть у каждого или почти у каждого.

Правда, я уже с трудом понимаю, зачем я все это продолжаю, так как за последние два месяца я провел собрание собстенников в доме, запустил голосование, по результатам которого уже скоро тут начнет работать другая управляющая комнания, а не эти дерзские бездельники из "Жилсервисуюта" (а в Госжилинспеции - тоже наглые бездельники... в общем, да покарает их всех Аллах, а Кришна - добавит еще люлей!).

Тем не менее, хоть как-то проверить, как настраивается и работает беспроводная связь от Siemens, тоже интересно. И вот у меня на руках точка доступа Scalance W774-1 RJ45. Этот модуль беспроводной связи может работать в двух режимах: и как точка доступа, и как клиентский модуль. Дома у меня уже есть бытовая сеть, поэтому модуль сразу настрою в клиентский режим.

Для небольшого ускорения процесса настройки модуля рекомендую скачать маленькую программу Proneta (в базовой редакции) по ссылке: https://new.siemens.com/global/en/products/automation/industrial-communication/profinet/proneta.html

Далее запитываем модуль, накручиваем на него антены, подключаем его кабелем (я подключил первым портом) к местному маршрутизатору и запускаем Proneta на компе. Программа Proneta должна обнаружить точку доступа, сама точка - получить IP адрес по DHCP (именно поэтому я для упрощения жизни подключаю модуль сразу в свою сеть).

Далее следует кликнуть правой кнопкой и выбрать пункт меню Set Network Parameters

Задать статические настройки IP

После этого программа Proneta не нужна, ее можно закрыть. Все дальнейшие настройки выполняем в браузере.

По умолчанию логин admin, пароль admin. Во время первого входа сразу предлагается сменить пароль. Воспользовавшись своим мощным интеллектом выставил новый пароль - admin1

Следующий шаг - это открыть визард и перевести модуль в клиентский режим. Данная операция потребует перезагрузки.

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

Следующие две вкладки я пропускаю - адрес уже задан, включение/отключение SSH и т.д. не особо важно для моей локальной задачи. А вот тип антен надо задать обязательно.

Следующая вкладка - включаем беспроводной интерфейс, выбираем диапазоны и т.д. Самое главное - это первое, ВКЛЮЧИТЬ радиоинтерфейс. А то я дважды уже этого клиента настраивал и дважды забывал про эту галочку.

Настройки клиента. Тут я снимаю галочку Any SSID и явно указываю свою сеть.

Каналы. Оставляю, как есть - клиент использует любой доступный канал автоматически.

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

На закладке "итоги" жму кнопу Set Values.

Настройки сохранятся автоматически в течении 1 минуты. Хотя, процесс можно и ускорить.

Далее я выдергиваю кабель "домовой" сети из клиента, соединию клиента и контроллер другим кабелем. Через несколько секунд я уже могу пинговать ПЛК "по воздуху".

И, напоследок, проверяем, можно ли достучаться до ПЛК по воздуху из TIA Portal. Связь устанавливается, можно зайти в online CPU.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru