Comments 95
Конечные автоматы - это прекрасно. Я и сам их очень люблю. Но это ни разу не панацея. Во-первых, у вас существуют физически уникальные ресурсы (например, I2C шина). Исчезает независимость состояний - теперь придется думать и отлаживать целые кусты переходов с учетом общих ресурсов. Аналогично если появляется remove state - например, датчик в который надо сначала записать адрес, потом читать результаты... Во-вторых, сама форма для сложных процессов малопрактична. Сделать общение, например, с дисплеем 1604 через I2C регистр-расширитель - можно. Будет ли такой код красивее и понятнее чем через примитивы параллельного исполнения - ой, не знаю!
Если строить систему на базе автоматов - то скорее всего придется делать очередь событий, и иерархические автоматы. Иначе для сколько-то сложной системы - таблица переходов становится необозримой.
Я помню, был какой-то фреймворк под MCU который продвигал идею автоматного программирования (и имел GUI для описания автоматов, и потом генерировал код). Чем кончилось - за давностью лет не помню.
В общем, проклятие сложности никуда не девается. С одной стороны, перейдя на автоматы мы избавляемся от головной боли с гонками - ибо теперь можем 100% контролировать потоки выполнения. Но получаем ту же сложность, вылезающую в описании автоматов и размере таблиц переходов...
Это приятно, что Вам нравятся автоматы. Но, судя по ответу, у Вас проблемы с их применением. И это понятно, т.к. автоматы они тоже разные. У Вас они явно другие. Например, какая бы ни была сложность системы, но «необозримая таблица переходов» - это не правильно. Наличие такого автомата – повод задуматься. Подобный автомат покрывается множеством (сетью) автоматов, каждый из которых имеет несравнимо меньшую таблицу переходов. Собственно так решается упомянутое Вами «проклятие сложности». Т.е. получаем не «ту же сложность», а гораздо меньшую. Правда, появляется другое «проклятие» - создать подобную сеть ;) Но это «проклятие», как правило, будет проще.
Библиотека позволяет легко решать именно подобные проблемы. И, что примечательно, без потоков, корутин и сопутствующих им «проклятий». Потоки и корутины сложность только множат, а предлагаемая библиотека ее уменьшает.
"Говорят, Рим строит дороги - да это ж мы, блдж, их строим!.." (C) Легионер с ютуба. Это я про "автомат покрывается сетью автоматов" - да это ж мы их покрываем!.. :-)
Покажите вариант решения задачи на автоматах: на общей I2C шине (которая управляется аппаратным автоматом AVR (TWI) сидит драйвер дисплея HD44780 через I2C адаптер PCF8574. И на ней же сидит датчик влажности, которому надо для начала изменений записать константу, а через таймаут вычитать по I2C пять байт значений. Дисплей надо инициализировать в 4-битный режим, и в дальнейшем обновлять раз в секунду. Если он перестанет штатно отвечать - инициализацию надо повторить. Влажность можно считывать реже - скажем, раз в 30 секунд...
Я очень хочу посмотреть на то, как вы этакую каракатицу легко опишите сетью автоматов. И насколько это будет легче читаемо чем решение через примитивы параллельного исполнения...
Да легко! (Я так думаю!) Но Вы в теме, а я нет. А потому - алаверды. Представьте схематично (I2C и остальное - это все условности) свое решение в форме "примитивов параллельного исполнения", а я их легко, если не сказать - изящно ;), превращу в автоматы. Используя, конечно, библиотеку. Тогда и сравним.
Нет, у меня это есть на автоматах - поэтому я и спрашиваю. Оно сделано на автоматах не потому что это лучшее решение, а потому что по-другому в условиях ограниченных ресурсов не влезет. Был бы у меня под эту задачу Linux - я бы запустил задачи в разных процессах, и синхронизировал через семафор.
Покажите на библиотеке достаточно сложный пример взаимодействия ? I2C - это очень распространенный стандарт с миллионом разных датчиков которые на нее вешаются. Интересно именно как вы собираетесь в ДКА синхронизировать работу с общим ресурсом, и как вы будете обслуживать транзакции на шине когда устройству надо послать несколько команд, проверить ответы, и т.д.
Потому что рассказывать о преимуществах автоматного подхода в задаче чтения состояния ножки - это каждый может. Покажите насколько выразительно реализуются сложные протоколы взаимодействия в такой парадигме. HD44780 и PCF8574 можете посмотреть в библиотеках того же ардуино. Это как грязь распространенные микросхемы (точнее, их текущие китайские no-name аналоги), и найти на них документацию и примеры - не должно быть проблемой...
Нет, у меня это есть на автоматах - поэтому я и спрашиваю.
Так это ж совсем другое дело! :)
Если Вы согласны, то мы сейчас и покажем/разберемся, что у Вас совсем другие автоматы:) Тогда и поймем, что и Linux , а семафоры так совсем зашквар :) По крайней мере у меня так. Когда есть библиотека VCPa, то не нужно ни то, ни другое.
Код совсем не важен, но изобразите хоть как-нибудь автоматы. Лучше в форме графов, но можно и таблицы переходов. Входы пусть будут обозначены x1... xn, выходы - y1, y2, ... yn. В идеале должно походить (по форме, конечно) на мой автомат из статьи.
Желательна и структурная схема. Она должна отражать количество автоматов и связи между ними. Что-то типа электронной схемы. Только здесь каждый автомат - это квадратик с входами и выходами, а между ними связи. Например (см. рис.2), в статье это один квадратик, имеющий один вход - x1 и два выхода - y1, y2, а внутри у него автомат.
Сможете такое сделать? Можно начать со структурной схемы. Принимается любая форма. Хоть от руки. Главное принцип.
Не надо мне возвращать обезьянку. Это вы утверждаете что автоматная форма лучше/удобнее чем синхронизационные примитивы. Я, как человек, поевший и то и другое - утверждаю что это сильно не так (по крайней мере, сильно не везде и не всегда). В качестве модельной задачи я вам предлагаю вещь совершенно обыденную и широко распространенную: I2C шину и символьный дисплей (и любое простое устройство на ней же). Не хотите эту задачу - покажите решение любой другой, но аналогичной сложности. Чтение ног микроконтроллера меня лично не впечатляет.
Дополнительно скажу, что вы не первый кто прибегает с идеей автоматного программирования. И почему-то все прибегают и показывают как она замечательно работает на простых примерах. К сожалению, на простых примерах вообще всё работает. Вы покажите на достаточно сложной реальной задаче!
Не будете защищать свою точку зрения - добро пожаловать, как я написал выше - не вы первый, не вы последний. А я ради вашего удобства дополнительный код писать не собираюсь... Вот когда я к вам прийду с дурной идеей и буду агитировать - тогда и просите...
...А я ради вашего удобства дополнительный код писать не собираюсь...
Так я ж просил не код...
Хорошо упростим задачу. Давайте оценим сложность Вашего проекта. У Вас проект на автоматах и это позволяет такую оценку сделать. А потом я приведу аналогичные цифры какого-нибудь из своих реальных проектов.
Итак, вопрос первый - сколько у Вас автоматов?
Вопрос второй - Сколько из них представляют параллельные процессы.
Вопрос третий - сколько состояний у каждого из этих автоматов.
Всего три цифры. Код писать не надо. Проект Ваш и он Вам понятен. Сделать, т.е. посчитать фактически, как мне кажется, не сложно.
Надеюсь, я Вас не сильно напряг? Я все сделал, чтобы Вам было как можно удобнее ;)
Не надо задавать мне вопросы. Покажите код. Ссылку на гитхаб, например. Я посмотрю и скажу что думаю по этому поводу.
А как мне оценить Ваш уровень? Вы не ответили ни на один мой вопрос. Вы не знаете сколько у Вас автоматов? Вы не знаете вообще сколько их у Вас. Вы не можете сказать сколько у них состояний. Что проще-то может быть, чтобы дать на них ответы? Да, может, Вы совсем не понимаете о каких автоматах идет речь? Может, и проекта такого (на автоматах) у Вас нет, а так ... одно только ля-ля? Давайте, наверное, как-то серьезнее относиться друг к другу... А просто "сотрясать воздух" смысла нет.
А ссылка на гит с библиотекой есть. В статье есть информация, как создавать автоматы и проекты на их базе. Что-то не ясно? Есть ссылка и на проект. Правда, в силу определенных причин его код не выложен пока на гит. Но это тоже будет сделано.
Может, и проекта такого (на автоматах) у Вас нет,
а что такое автомат (он же КА) с точки зрения программирования, это же простой SWITCH, правильно? То есть по сути вы предлагаете программирование на SWITCH-ах? Вот очень интересно посмотреть конструкцию на SWITCH-е которая заменяет семафор, хотя я в принципе, теоритически, могу себе это представить, но ведь практическая ценность такой конструкции будет абсолютно нулевая? Не нужно больших проектов! Просто попробуйте изобразить такую конструкцию в коде.
Нет такой "точки зрения программирования". Но есть некая алгоритмическая модель, есть ее реализация в той или иной форме (графическая, текстовая и т.п.). Так вот, надо сравнивать модели, их "точки зрения". С такой "точки" SWITCH - не автомат. SWITCH не заменяет семафор. Наверное, его поведение и работу с ним можно как-то смоделировать автоматом/автоматами, но ... зачем? Как я уже сказал: нужен - используете "как есть". Какие проблемы? И это будет правильно. Зачем изобретать велосипед?
Во-первых, вам не надо оценивать мой уровень. Это вы сюда пришли проповедовать автоматное программирование, и это мы будем оценивать полезность того, что вы предлагаете.
Во-вторых, моя гражданская специальность была 220100 - выч маш, системы и сети. Поэтому автоматы Мура/Мили, их перевод в переключательные функции, минимизация и синтез в базисе - это примерно наш хлеб с зимы первого по зиму третьего курса. Дальше начиналось микропрограммное управление и ассемблер...
В третьих, в последний раз говорю - покажите на примере своей распрекрасной библиотеки и распрекрасного автоматного программирования решение реальной задачи - общение с несколькими устройствами по I2C шине ? На ардуино у вас есть все необходимые исходники и протоколы - хотя, разумеется, не в виде конечных автоматов. Практика - критерий истины, короче...
А если вы только агитировать можете за коммунизм и автоматное программирование - ну тогда ой! С зелотами ругаться - только время терять...
Давайте жить дружно :) Но я не на экзамене, да и Вы тоже. Но мне хотелось бы оценить сложность Вашего существующего решения. Вы же оперируете понятием "сложного решения"? Для меня сложность оценивается количеством состояний автомата. Чем их больше - тем больше сложность. Какие системы Вы считаете сложными? С каким числом состояний? Зная это и бы мог предложить свое эквивалентное решение из уже существующих, чтобы Вы могли его заценить.
Реализовывать что-то другое? Зачем? У меня есть чем заняться. А как-то оценить проблему можно и абстрактно. Вам было предложено как это сделать, т.е. создать отдельны КА для шины и устройств на ней. Моя "замечательная библиотека" это позволяет сделать легко и просто. В чем проблема такого подхода? Можете ответить хоть в этот раз?
И я ни за что не агитирую. Я предложил конкретное решение. Оно в свободном доступе. У Вас достаточно (надеюсь) квалификации, чтобы его применить, Вы знаете "шину", Вы знаете "символьный дисплей", у Вас уже есть готовое решение, Вы даже знаете что такое автомат! Переложите свои автоматы на автоматы мои. Если Вас правильно учили то и мои и Ваши автоматы - это классические автоматы. Перевести одни в другие - тьфу! проблема. Ну и озвучьте, какие их проблем VCPa не решает? Ну, такой алгоритм должен быть общения между нормальными людьми. А если Вы помешаны на идеях коммунизма, круг Ваш общения - какое "золото", то - увольте. Мне бы хотелось больше про программирование. И лучше если с разных "точек зрения" на него. Или, что точнее, сравнивая разные его модели.
То есть решения сколько-то похожей на реальную задачи на своих КА вы предъявить не только не можете, но даже и не собираетесь. Дальнейшая дискуссия смысла не имеет. Это будет спор о том, сколько ангелов помещается на кончике иглы. Идите со своими идеями к теологам, а Хабр - пока все-же технический ресурс...
Спора между нами нет и не было. Поскольку нет предмета самого спора, а есть одни некие "предъявы". Вы достали меня своей тупизной и нежеланием ответить на элементарные вопросы, чтобы создался хоть какой-то предмет спора. А попросту, по-видимому, Вы и не знаете на них ответа.Т.е. оценить сложность своего проекта Вы не в силах.
Хорошо, скорее не для Вас, а дех кто по наивности своей предполагает, что Вы из себя что-то представляете. Привожу скрины одного из своих реальных проектов. Это установка водоподготовки размером с железнодорожный вагон. Забита всякой всячиной, но для управления достаточно одного ПЛК. Разработка велась не на ПЛК, а путем моделирования ее работы на ПК. Приведена структурная схема, где каждый квадрат - это автомат. На схеме отражены все связи процессов. Своего рода пример нынешнего бэкенда. Мнемосхема - каждый ее элемент - это, порой, даже не один автомат. Можно сказать пример фронтенда. После окончания тестирования модели, с участием заказчика и под его контролем, все автоматы бэкенда перенесены были на ПЛК за один день и заработали сразу и без какой-либо отладки. Вагон был поставлен на рельсы и уехал туда, куда было ему нужно. Да было составлено полное описание алгоритмов работы всех элементов, представленных на структурной схеме. Что-то порядка ста листов документации. Работа прошла дружно и без каких-либо претензий. Приятно вспомнить:)
Структурная схема и мнемосхема модели установки

Я полагаю, что публике хабра мало интересно сколько вагонов документации и схем вы нарисовали. Вообще как-то не по-детски вас шатает: от ПЛК и установок водоподготовки до ардуино... Но мы тут люди "от сохи" - поэтому покажите пожалуйста код - как на вашем автоматном подходе можно одновременно общаться с дисплеем и датчиком температуры по I2C шине ?
И еще раз - я не говорю о невозможности реализаци чего бы то ни было на конечных автоматах. Более того, что и как бы вы не написали в абстракциях высокого уровня - в конце концов на кремнии в системе ДКА и будет исполняться.
Я говорю о другом - логика развития инженерии идет от (!) ДКА на кремнии к примитивам более высокого уровня (потокам, корутинам, и т.д.). Потому что описание процесса через потоки и примитивы синхронизации является более удобным (и компактным!) для понимания и дальнейшей модификации. В некоторых (!) случаях автоматное представление имеет право на жизнь: либо для задачи ДКА является естественным представлением, либо просто нет ресурсов для более сложной формы.
А адепты, которые нашли что ДКА можно потенциально применить к любой задаче - и поэтому автоматное программирование сейчас вытеснит все другие форматы - на хабре появляются каждые 6-9 месяцев. Глушко еще вспомните с циклограммами Энергии и язык "Дракон" - а то давно еще по этому поводу не срались...
Хотя, конечно, я могу ошибаться - и вдруг вы действительно совершили прорыв в этой области... Но только сначала код покажите для сколько-то реальной задачи, а не автомат из двух состояний и трех ножек ардуины... Желательно не из области водоподготовки, а из того с чем мы хоббийно каждый день имеем дело. Общение с переферией по I2C шине - вполне подойдет...
Но мы тут люди "от сохи" - поэтому покажите пожалуйста код - как на вашем автоматном подходе можно одновременно общаться с дисплеем и датчиком температуры по I2C шине ?
опрос устройств по шине это естественный КА, даже если вы это так не воспринимаете)
отправка запроса, получение ответа либо таймаут, отправка запроса на следующее устройство
А вот дальше можно найти отличия - либо цикл опроса отдельный от алгоритма работы с этими данными, как в типичных ПЛК, либо же смешан с процедурой опроса, что часто наблюдается в эмбеде, и на мой взгляд плохой подход.
Это понятно. Более того - TWI в архитектуре AVR для упрощения жизни уже реализован как аппаратный конечный автомат - достаточно только дергать его флагами в регистрах. То есть, нижний уровень уже реализовали. НО! Эти регистры (и ноги с аппаратным I2C - они одни на весь микроконтроллер). Я хочу посмотреть - как конкретно этот автор предлагает синхронизировать между собой два КА (дисплей и термометр), которые во время своей работы хотят менять состояние разделяемого ресурса - шины I2C (и связанных с ней регистров МК). Я не говорю что это нельзя сделать - есть разные способы. Пусть автор покажет как это решается с его точки зрения правильным способом и на его библиотеке. Моя изначальная претензия была в том, что преимущества автоматного программирования показываются ровно на том простом примере, где они очевидны - а недостатки отсутствуют. А в реальности (в современной реальности!) вы не сможете выделить по одной ноге контроллера на каждый датчик и исполнительное устройство, придется мультиплексировать. И мой опыт показывает, что "простое и понятное" устройство программы как графа с дугами - чего-то перестает быть и простым и понятным. И выясняется что потоки и межпоточную синхронизацию люди не от нечего делать придумали...
Картинка выглядит достаточно подходящей для КА. На каждое устройство свой КА.
Параллельное исполнение и машина состояний понятия ортогональные.
Конечно, на конкурентую запись появятся семафоры, зато и точки распараллеливания удобны в точках ожидания изменения состояний.
Проблема в том, что у вас теряется независимость автоматов. В идеальном мире - у вас КА дисплея и КА термометра понятия не имеют о существовании друг-друга. В реальном мире - они пересекаются на физически неразделяемом устройстве - шине и управляющих ей регистрах микроконтроллера. Если вы в середине транзакции с дисплеем вставите обмен с термометром (не проведя корректного освобождения шины и новой инциализации посылки) - то у вас команды термометра поедут в дисплей, чем собьют ему протокол и вызовут артефакты. Если вы будете монопольно захватывать шину до окончания обмена с устройством - то в обмене с дисплеем есть минимальные промеждутки между командами - и вы будете занимать шину когда она вам не нужна.
Я не говорю, что это нерешаемая проблема - я хочу посмотреть, как конкретно этот адепт автоматного программирования хочет их решать. И насколько это решение хуже/лучше отдельных процессов/потоков на каждое из устройств с примитивами синхронизации...
я собственно про то, что теория КА не противоречит параллельности в любом виде.
Но как всегда, КА лучше привязывать к экземпляру устройства. У нас общая шина на 10 приборов, значит КА на шину, а приборы пойдут подчиненными КА или просто параметрами.
сихронизация зависимых КА дело муторное, надо думать над удобным разбиением и минимизацией количества
Можно КА и на каждый прибор. Все это будет сеть параллельных автоматов.Каждый из КА имеет доступ к текущим состояниям других КА и это позволяет синхронизировать их работу, как это требуется. Такой подход решает любые проблемы. Проверено и не раз.
Мсье решил выкинуть в мусор принцип инкапсюляции, и дает возможность каждому автомату бесконтрольно читать состояния других автоматов ? Мсье - тонкий извращенец... Как бы уже лет 40 известно что нужно различать интерфейс (внешний контракт) и внутреннее состояние программного модуля...
Я не говорю, что это нерешаемая проблема - я хочу посмотреть, как конкретно этот адепт автоматного программирования хочет их решать.
"Адепт" подобные задачи решает "по щелчку"! Имея доступ к состояниям, он знает кто и что делает, а потому, используя эту информацию, может синхронизировать процессы так, как того требуют протоколы и нюансы их работы. Ну нет тут с автоматами проблем от слова совсем.
Почти то, что надо. Вот только семафоры, думаю, ни к чему. Синхронизацию можно организовать через состояния. Это будет надежнее. Хотя - если хочется, то почему бы и нет. Автоматы семафоры не отрицают.
семафор это и есть атомарное состояние
Семафор - это, скорее, объект операции с которым атомарны. Вот, кстати, что интересно, в параллельной библиотеке VCPa фактически все действия ее автоматов в определенном смысле атомарны. В чем заключается такая атомарность? В том, что ни один другой автомат не может прервать/вмешаться в "атомарное действие" текущего автомата. Ну, также, как в корутинах/сопрограммах (см. рис.1)
вот такие шедевры я прям люблю:
level1 = digitalRead(gpioLevel1);
level2 = digitalRead(gpioLevel2);
level3 = digitalRead(gpioLevel3);
вот прям никак не возможно прочитать порт одной командой? Или это специально развели на разные порты, чтобы скучно не было? (причем и тем кто разводит и тем кто это программирует) Или это просто Ардуино-стиль такой, так просто модно?
если что вот это :
if (!level1&&level2&&level3) fLevel=30;
можно записать так:
if (levels == 0x3) fLevel=30;
но я наверно в другом мире живу, не одухотворен Ардуино.
Или это просто Ардуино-стиль такой, так просто модно?
Как я понял, да. Это стиль "для тупых". Типа, чтобы даже нуб понял. Ну, как мы в детстве на BASICe писали.
А сам по себе микроконтроллер - нормальный. Всё в нём можно, что положено микроконтроллеру. И в самой среде Ардуино можно писать нормально.
По поводу "нормально". Понятие растяжимое, хотя в статье прямо сказано - С++ и STL. Может, на Arduino это тоже уже есть? "Кому и кобыла невеста". Так, кажется, говаривал один персонаж из "12 стульев"?
Ну вообще-то кодогенерация для Ардуино - это обычный gcc/g++. Все что там есть - всё доступно... Другое дело что под какую-нибудь ATTINY13 писать на C++ с темплейтами и исключениями - довольно странно, да можно и в память не войти!
У Arduino серьезные ограничения с С++. Как бы это было именно так. И именно не так давно. Неужели что-то изменилось?
Не надо говорить "Arduino", надо говорить о конкретном семействе микроконтроллеров. Если мы имеем в виду классическое ардуино - то это AVR. Никаких ограничений по кодогенерации там нет. По стандартной библиотеке - да, есть (с учетом того что оригинальная libc была для POSIX-систем, чего же вы хотите от MCU?!). По архитектуре памяти (например, вам надо заранее решить - хотите ли вы чтобы константа лежала в .data и занимала RAM или в .flash - но тогда использовать специальные функции чтобы ее доставать) - тоже есть. Но никаких ограничений именно по стандарту языка - я не помню. Пока вы влазите в память и стек - творите что хотите!
Не придирайтесь :) Да, конечно, можно и подключить к портам и читать их как угодно. Вариантов тут множество. Но речь о другом. Дело не в реализации какого-то действия (чтения тех же портов), а в 1) в проектировании логики одного процесса и 2) в проектировании логики множества процессов.
Насколько я понял, основной движущей силой проекта было - реализовать параллельность и ни в коем случае не столкнуться с RTOS. Может я незаслуженно опримитивливаю проект этим выводом, но я правда не понимаю. RTOS - это не только накладные расходы. ESP - контроллеры двухядерные, а RTOS необходима для эффективной загрузки обоих ядер. Ваш контроллер автоматов работает на одном ядре?
Довести идею корутин на базе автоматов до рабочего состояние поможет объектно-ориентированное программирование (ООП)
Rust имеет встроенные конечные автоматы и обходится без ООП. Для Ардуино вполне вариант
А можете на Rust создать такой же автомат, как и в статье? Сравним, что и как.
Ну вот что-то близкое, но на раст и с конечным автоматом
use std::time::Instant;
// === Состояние сигнала на пине ===
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Level {
Low, // 0V — датчик не активен
High, // 3.3V/5V — датчик активен
}
impl Level {
// Удобный метод: true, если High
fn is_high(self) -> bool { self == Level::High }
fn is_low(self) -> bool { self == Level::Low }
}
// === Состояния конечного автомата ===
#[derive(Debug, Clone, PartialEq)]
enum State {
Ss,
S1,
Er,
}
// === Датчик уровня с конечным автоматом ===
struct SensorLevel {
name: String,
state: State,
level1: Level,
level2: Level,
level3: Level,
n_delay: i32, // задержка: >0 — норма, <0 — ошибка
f_level: f32, // текущий уровень жидкости: 0, 30, 60, 90
f_sav_level: f32, // предыдущее значение уровня
b_if_view_error: bool, // флаг: ошибка уже показана
delay_start: Option<Instant>, // начало отсчёта задержки
}
impl SensorLevel {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
state: State::Ss,
level1: Level::Low,
level2: Level::Low,
level3: Level::Low,
n_delay: 0,
f_level: -1.0,
f_sav_level: -1.0,
b_if_view_error: false,
delay_start: None,
}
}
// === ПРЕДИКАТЫ ===
fn x1(&self) -> bool { self.n_delay > 0 }
fn x2(&self) -> bool { self.n_delay < 0 }
// === ДЕЙСТВИЯ ===
fn y1(&mut self) {
self.level_view();
}
fn y2(&mut self) {
println!("{}: Creating delay of {} ms", self.name, self.n_delay);
self.delay_start = Some(Instant::now());
}
fn y3(&self) {
println!("{}(ss): error nDelay={}", self.name, self.n_delay);
}
fn y4(&mut self) {
if !self.b_if_view_error {
println!("{}(er)", self.name);
self.b_if_view_error = true;
}
}
// === Определение уровня жидкости по датчикам ===
fn level_view(&mut self) {
use Level::*;
let (l1, l2, l3) = (self.level1, self.level2, self.level3);
let f_level = match (l1, l2, l3) {
(Low, High, High) => 30.0,
(High, Low, _) => 60.0,
(_, _, Low) => 90.0,
(High, High, High) => 0.0,
_ => -1.0, // нестабильное состояние
};
if self.f_sav_level != f_level {
println!("{}: Level has changed its state: {:.0}", self.name, f_level);
self.f_sav_level = f_level;
}
self.f_level = f_level;
}
// === Обновление значений датчиков (имитация digitalRead) ===
pub fn update_sensors(&mut self, l1: Level, l2: Level, l3: Level) {
self.level1 = l1;
self.level2 = l2;
self.level3 = l3;
}
// === Основной шаг автомата ===
pub fn step(&mut self) {
// Если мы в состоянии S1 — запущена задержка
if let (State::S1, Some(start)) = (&self.state, self.delay_start) {
let elapsed = start.elapsed().as_millis() as i32;
if elapsed >= self.n_delay.abs() {
self.state = State::Ss;
self.delay_start = None;
}
return;
}
// Определяем входы
let input_x1 = self.x1();
let input_x2 = self.x2();
// === Таблица переходов (как в TBL_SENSORLEVEL) ===
match (&self.state, input_x1, input_x2) {
// ss --x1--> ss или s1, действие y1
(State::Ss, true, _) => {
self.y1();
// Например, переходим в s1
self.state = State::S1;
}
// ss --x2--> er, действие y3
(State::Ss, _, true) => {
self.y3();
self.state = State::Er;
}
// er -- любое --, действие y4 (один раз)
(State::Er, _, _) => {
self.y4();
}
// s1 -- без условия --> ждём, действие y2 (один раз при входе)
(State::S1, _, _) => {
// y2 выполняется при первом входе в S1
if self.delay_start.is_none() {
self.y2();
}
}
_ => {}
}
}
}
// === Пример использования ===
fn main() {
let mut sensor = SensorLevel::new("TankLevel");
// Устанавливаем нормальную задержку
sensor.n_delay = 1000; // 1 секунда
// Цикл, как в Arduino
for i in 0..15 {
println!("\n--- Step {} ---", i);
// Имитация изменения уровня жидкости
let l1 = if i > 2 { Level::High } else { Level::Low };
let l2 = if i > 5 { Level::High } else { Level::Low };
let l3 = if i > 8 { Level::High } else { Level::Low };
sensor.update_sensors(l1, l2, l3);
// Выполняем шаг автомата
sensor.step();
std::thread::sleep(std::time::Duration::from_millis(600));
}
// Тест ошибки: отрицательная задержка
println!("\n--- Test: Negative nDelay (Error) ---");
sensor.n_delay = -500;
sensor.state = State::Ss;
sensor.step();
}
Кода больше, но он понятный
Вот, разобрался с io
//! # Монитор уровня жидкости на ESP32
//! Простой, надёжный, легко читаемый код без излишеств.
use esp_idf_hal::gpio::{Gpio0, Gpio1, Gpio2, Input, Pin};
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::prelude::*;
use std::time::Instant;
fn main() -> anyhow::Result<()> {
// —————————————————————————————
// 1. Инициализация оборудования
// —————————————————————————————
let peripherals = Peripherals::take()?;
let pins = peripherals.pins;
// Настроим GPIO как входы
let sensor_low = pins.gpio0.into_input()?;
let sensor_mid = pins.gpio1.into_input()?;
let sensor_high = pins.gpio2.into_input()?;
// —————————————————————————————
// 2. Переменные состояния
// —————————————————————————————
let mut last_level: f32 = -1.0; // Предыдущий уровень
let mut delay_start: Option<Instant> = None; // Таймер задержки
let delay_ms: i32 = 1000; // Задержка: >0 — норма, <0 — ошибка
let mut error_shown = false; // Флаг: ошибка уже показана
// Приветствие
println!("[TankLevel] Система запущена");
// —————————————————————————————
// 3. Основной цикл
// —————————————————————————————
loop {
// —— Чтение датчиков ——
let l1 = read_gpio(&sensor_low);
let l2 = read_gpio(&sensor_mid);
let l3 = read_gpio(&sensor_high);
// —— Определение уровня жидкости ——
let current_level = match (l1, l2, l3) {
(false, true, true) => 30.0, // Только средний и верхний — 30%
(true, false, _) => 60.0, // Нижний и средний (но не верх) — 60%
(_, _, false) => 90.0, // Верхний не сработал — 90%+
(true, true, true) => 0.0, // Все сработали — пусто
_ => -1.0, // Некорректное состояние
};
// —— Уведомление при изменении уровня ——
if (last_level - current_level).abs() > f32::EPSILON {
match current_level {
0.0..=100.0 => println!("[Level] Уровень: {:.0}%", current_level),
_ => println!("[Level] ОШИБКА: некорректные показания датчиков"),
}
last_level = current_level;
}
// —— Обработка задержки (например, для активации насоса) ——
if delay_ms > 0 {
manage_delay(delay_ms, &mut delay_start);
} else if !error_shown {
println!("[ERROR] Некорректная задержка: {}", delay_ms);
error_shown = true;
}
// —— Пауза между опросами ——
std::thread::sleep(std::time::Duration::from_millis(500));
}
}
// —————————————————————————————
// Вспомогательные функции
// —————————————————————————————
/// Читает состояние GPIO, обрабатывая возможные ошибки.
fn read_gpio(pin: &impl InputPin) -> bool {
pin.is_high().unwrap_or(false)
}
/// Управление таймером задержки: запуск и завершение.
fn manage_delay(delay_ms: i32, delay_start: &mut Option<Instant>) {
if delay_start.is_none() {
println!("[Delay] Запуск: {} мс", delay_ms);
*delay_start = Some(Instant::now());
}
if let Some(start) = *delay_start {
if start.elapsed().as_millis() >= delay_ms as u128 {
println!("[Delay] Завершено");
*delay_start = None;
}
}
}
Должно быть
let current_level = match (bottom, mid, high) {
(false, false, false) => 0.0, // Все сухие → пусто
(true, false, false) => 30.0, // Только нижний в воде
(true, true, false) => 60.0, // Ниже верхнего
(true, true, true) => 100.0, // Все в воде → полон
_ => -1.0, // Ошибка: например, (false, true, true)
};
А у вас, пардон, фигня. Сразу и не заметил
Ни разу ни фигня :) Просто здесь я одним датчиком проверяю работу автомата. Для трех я сделаю правильно. А Вам спасибо и за код и за внимательное отношение к статье ;)
В любом случае через match читается лучше и нельзя потерять не валидный случай
Сложилось впечатление, что содержание статьи ни разу не соответствует заголовку. Тема параллелизма не раскрыта от слова совсем.
А у меня сложилось впечатление, что Вы не читали статью. Все же она не про параллелизм, как таковой, а про то, как избавиться от "параллелизма" потоков и корутин. Про библиотеку VCPa. Но, если Вас интересует теория АП, то пжалте пройтись по моим ссылкам и литературе (специально вставлены для таких как Вы). Там про автоматный параллелизм все очень подробно. Про потоки и корутины читайте статьи на Хабре других авторов. Они "коротко" или не очень :) тоже все рассказали.
Я дал конкретные рекомендации, как избавиться от того "кошмара", с которым сталкиваются, когда используют потоки и корутины. Да, и про сами кошмары тоже читайте упомянутые "короткие" статьи.
Вот так. Будьте внимательны. Успехов!
Все же она не про параллелизм, как таковой, а про то, как избавиться от "параллелизма" потоков и корутин.
Смотрим на название статьи: "Как избежать кошмара параллелизма в IoT: автоматы вместо потоков и корутин"
Т.е. в заголовке вы обозначаете наличие некого "кошмара параллелизма" и обещаете рассказать про некий способ его избежать. В самой же статье сперва очень много наукообразных слов о том, что считалось короутинами в 1970-х. И не менее наукообразные, но совершенно бесполезные рассуждения о КА из тех же самых, если не более ранних времен. Потом еще немного про какой-то стремный оторванный от жизни пример с сылками на еще более стремную в своей реализации библиотеку. И все.
Поэтому хочется спросить: о каком именно кошмаре параллелизма вы собирались нам, читателям, рассказать?
Или заголовок следует воспринимать как чистой воды кликбейт?
Перечитайте еще раз тот мой пост из которого Вы заимствовали цитату. Повторяю для непонятливых: про сами кошмары - читайте других авторов. Или Вы считаете, что их нет? Если так, то Вы - наивный программист, верящий в нечто, что (потоки, корутины), как минимум, многими признается "стремным". Еще раз - читайте, читайте и читайте... Ссылки Вам даны. Время, надеюсь, найдете. Иначе, если не было, то "кошмары" Вас обязательно настигнут :)
Когда захотите от них избавиться - купите микроконтроллер ESP32 (что совсем дешево) и на него установите библиотеку (еще дешевле - бесплатно), которая Вас от них избавит. Перечитайте статью, которая об этом повествует (как пользоваться библиотекой). Тоже затрат не требуется: по времени всего-то 17 мин (если верить Хабру) Разжевано и распихано по клювикам... ;). Поэтому при минимуме затрат сможете получить, возможно, максимум удовольствия :) Других вариантов избавиться от существующих "кошмаров", увы, нет. Еще раз - успехов!
Да и есть совсем практичный вариант (см. в статье про HotbedAgroControl). Чуток подороже правда, будет, но зато все в одном флаконе - и микропроцессор и практический пример (некоторым просто вынь да положь!) применения автоматов. Короче - осваивай - не хочу! Практично, экономично, фантастично и без кошмаров! :) "Как вам такое, Илон Маск!"
И, да, может, показаться, что кликбейт. Но совсем нет. Читайте, думайте, все открыто, все можно попробовать. Все или халявно или почти халявно. Но уж совсем наглеть не надо-то!? Так что, какой там кликбейт - упаси Бог! :)
Перечитайте еще раз тот мой пост из которого Вы заимствовали цитату.
Обычно за разгребание чужого говна мне платят. А вы хотите, чтобы я тратил на это время бесплатно? o_O
про сами кошмары - читайте других авторов
Тогда зачем ваша статья, если "кошмары" описывают другие люди, а вы предлагаете рецепты для лечения чего? Где связь между вашим наукообразным потоком сознания и "кошмарами", описанными в других статьях других авторов?
Пальцем ткните, пожалуйста. Мол, вот здесь проблема, вот мое решение.
Или Вы считаете, что их нет?
Сходу могу назвать один, главный -- разделяемое между параллельными задачами мутабельное состояние. Только вот в статье о нем что-то ничего не увидел. Может укажите на конкретный абзац или раздел?
С интересом прочитал про это самое состояние - https://ru.hexlet.io/blog/posts/which-skills-to-develop. Действительно, такие есть? Не зря его назвали "мутабельным" :)
Так вот, спешу Вас огорчить в АП (автоматное программирование) оно в принципе невозможно. По теории и практике АП. Я так понимаю основная его проблема не в том, что оно глобально, не в том, что его отслеживать как-то сложно, а в его неповторяемости. И это понятно. Нет детерминизма у потоков. Оттого и проблема. И у сетей автоматов есть глобальное состояние. Это текущее состояние все состояний его автоматов. И тут полный контроль и повторяемость. Как то так. Надеюсь, теперь понятно, как избавиться от "кошмара мутабельности"? :)
Да, если я порешал Вашу проблему, то, может, и Вы мне чего-нибудь подбросите. Как никак я, может, тоже помог Вам что-то "разгребсти". Кстати, и мне за это обычно платят. Вот совпадение-то!
С интересом прочитал про это самое состояние - https://ru.hexlet.io/blog/posts/which-skills-to-develop
Ваша ссылка ведет на статью под названием "Какие навыки необходимы на разных этапах карьеры". Вы ошиблись или реально искали там определение "разделяемое мутабельное состояние"?
Надеюсь, теперь понятно, как избавиться от "кошмара мутабельности"? :)
Нет. Не понято. Как и непонятно можете ли вы вообще связно излагать свои мысли на простом русском языке.
Если вы хотите поговорить здесь о разработке всерьез, то ответьте, пожалуйста, на заданные ранее вопросы. В частности, меня очень интересует вот что:
"Где связь между вашим наукообразным потоком сознания и "кошмарами", описанными в других статьях других авторов?
Пальцем ткните, пожалуйста. Мол, вот здесь проблема, вот мое решение."
Извиняйте. А вот так - https://ru.hexlet.io/blog/posts/global-mutable-state? Подходит? Но, с другой стороны, ссылка, конечно, не та, но чем Вас не устроил мой ответ. По сути я ответил или нет?
А вот так - https://ru.hexlet.io/blog/posts/global-mutable-state?
Так да. Но я не говорил про "глобальное", я говорил про разделяемое мутабельное состояние. Оно не обязательно должно быть глобальным.
но чем Вас не устроил мой ответ
Во-первых, левой ссылкой.
Во-вторых, отсутствием вменяемых пояснений по поводу того, как КА спасают от проблемы разделяемого мутабельного состояния при параллельном программировании.
В-третьих, отсутствием ответов на ранее заданные вопросы.
что такое сеть автоматов, что такое общее состояние всех автоматов?
Вот это вот нуждается в расшифровке. Особенно "общее состояние" для параллельно и независимо работающих КА.
Кроме того, у меня сложилось ощущение, что под "параллельностью" вы понимаете что-то свое, известное только вам.
И еще одно ощущение: вы не сможете объяснить что лично вы вкладываете в понятие "параллельность".
Решил поискать... Сразу наткнулся на автора - Модель Акторов и C++: что, зачем и как? Похоже Ваше. И вы с 2017 года так и не разобрались с мутабельностью? Разделяемой само-собой. Жуть и кошмар.
И на какие я вопросы не ответил? На глупые, наверное? Так и не собираюсь. Потому как хочешь уточнить - молчок...
По поводу параллельно и независимо. Так это совсем фигня. Интересны именно взаимозависимые. С ними самая беда. Только что тут уточнять? Все следует из теории. Есть такая - алгебра автоматов, где рассматриваются разные варианты соединения автоматов. Читаем букварь и начинаем понимать, если на пальцах не понятно.
По параллельность - все из той же теории автоматов. Читайте, вникайте, если на слух не получается понять. Я ж когда-то с этим разобрался, надеюсь, и Вы осилите. Но проще почитать мои статьи на тему теории АП. Там выжимки того, что надо. Ссылки есть. Нужно только пальчиком ткнуть. Конечно я что-то добавил свое. Применительно к программированию. Поэтому советую мои, чтобы не корячиться и идти моим путем, т.к. классическая теория автоматов - это одно, а применительно к программированию - есть нюансы. Будете повежливее - расскажу, нет - ковыряйтесь сами (с усами).
Т.е. читаем теорию автоматного программирования и - в путь. Без всякой мутабельности, но зато параллельно по-настоящему. Все Ваши с мутабельностью беды от "многия знаний". Причем без знания каких-либо основ теории - в программировании, в кибернетике, теории цифровых схем и .д. и т.п. Манагеры он такие... Думают, все можно взять нахрапом, наскоком, ... мутабельностью, так сказать. Сначала создадут проблему, а потом с ней борются всем кагалом. А нет, чтобы тормознуть и подумать, заглянуть в книжки. Может, даже старые...
"Мутабельное" - придумают же! В приличном обществе и сказать-то как-то неудобно :) Разберитесь с автоматами и выражаться не прийдется :)
И на какие я вопросы не ответил?
На все вышеперечисленные.
Но проще почитать мои статьи на тему теории АП
Ваши статьи -- редкостное графоманство, написанное нечитаемым наукообразным языком.
А в комментариях вы ведете себя как высокомерный чудак на букву "М", который не может ответить на прямые простые вопросы прикрываясь мантрой "идите читайте".
как КА спасают от проблемы разделяемого мутабельного состояния при параллельном программировании.
КА инкапсулируют разледеляемый ресурс - свое состояние.
Особенно "общее состояние"
Это множество текущих состояний всех КА.
В общем, сама идея интересная, но статья увы и ах. Параллелить КА можно как yield на состоянии ожидания, так и удобнее между потоками.
Как при этом у автора набегают мегабайты объектного кода, большая загадка
КА инкапсулируют разледеляемый ресурс - свое состояние.
Давайте попробуем более предметно и менее абстрактно.
Допустим, что у нас есть электронная табличка. По типу Google Sheets.
С одной таблицей могут одновременно работать несколько пользователей.
Допустим, работа каждого пользователя описывается своим КА.
Что с данными этой таблицы?
Два пользователя могут одновременно вводить в нее информацию. Причем, если они вносят новые значения в разные места таблицы, то делать они это могут реально в параллель.
При этом третий пользователь может в этот момент вести обсчет данных из таблицы. И в диапазон обсчета могут попасть как раз модифицируемые сейчас ячейки.
Или еще интереснее: один пользователь вводит данные в свою ячейку, которую сейчас не трогает пользователь №2. Но изменение ведет к пересчету значения в другой ячейки, которая сейчас как раз пользователю №2 нужна.
Если представлять данные таблицы в виде своего КА, то придется все запросы (на модификацию, на чтение и т.д.) сериализовать в последовательность запросов. Которая и обрабатываться будет последовательно. Что как бы не очень хорошо влияет на "параллельность".
Допустим, работа каждого пользователя описывается своим КА.
КА надо делать на ресурс. в данном случае ресурс это таблица.
Да, неудобный выбор КА приводит только к усложнению логики.
Проблема очереди изменений типичная для СУБД.
Все таки этот пример далековато от темы статьи - контроллеры и устройства, где КА уместнее
КА надо делать на ресурс.
Сущность, которая представляет собой пользователя внутри программы так же вполне может быть КА. Отсюда и вырисовывается взаимодействие различных КА, причем независимых друг от друга.
Все таки этот пример далековато от темы статьи - контроллеры и устройства, где КА уместнее
Я долго не мог понять о какой параллельности говорит автор, смутило то, что статья помещена в хаб "параллельное программирование". Для меня параллельное программирование -- это parallel computing. Тогда как автор, видимо, говорит о параллельной связанности КА, что вовсе не обязательно должно означать параллельное исполнение самих КА (запросто может быть и всего лишь квазипараллелизм).
не скажу за автора, но для меня интересно запустить каждый КА в своём треде и пусть уже библиотека раскидывает между ядрами и ожиданиями на не самом мощном эмбед железе
Еще раз. Модель, модель и еще раз модель. Какую модель параллельных вычислений реализует данный подход? Прежде чем что-то предлагать нужно дать ответ на вопрос - какая модель реализуется?
Использую этот подход (но не в embedded) много лет. Он хорош, но далеко не всегда, увы, в области concurrent computing. А в parallel computing, имхо, мало применим.
Для меня параллельное программирование -- это parallel computing.
Это для Вас и для тех, кто писал материал, на который указывает Ваша ссылка. А теоретически правильное определение: параллельное программирование - это проектирование параллельных алгоритмов в рамках параллельной модели. Ключевые термины - параллельный алгоритм и параллельная модель вычислений. Это "азбука", которую следует знать. Нет параллельной модели - нет параллельных вычислений. Люди рассуждают, не зная "азбуки". Из-за этого и все проблемы типа ... "мутабельности".
КА инкапсулируют разледеляемый ресурс - свое состояние.
Это прям шедевр наукообразия! Это как сказать мы намочим сухое, догадайтесь будет сухое мокрым или мокрое сухим?
Останется разделяемый ресурс разделяемым или разделяемый ресурс станет инкапсулированным? Это круче чем Шекспировское "быть или не быть", мне кажется!
Купил еще попкорна! Продолжайте!
я не упомянул очевидной вещи, что доступ на смену состояния КА только через интерфейс? ну извините
читать состояние кстати, обычно можно без ограничения
читать состояние кстати, обычно можно без ограничения
Смотря что понимать под состоянием. Если только "имя" текущего состояния, то это одна история. При этом еще вопрос: а насколько разумно это делать, если мы сейчас прочитатли "имя" текущего состояния, а оно поменялось на другое еще до того, как мы из операции чтения вышли.
Если же под состоянием понимается не только "имя", но и какое-то содержимое объекта КА, то тут сразу же вопросы...
Опять же, все это актуально только если КА физически работают параллельно. А не квазипараллельно в рамках одного треда, как это, подозреваю, происходит у автора статьи.
в реальности абсолютная синхронность состояния и связанных данных непринципиальна. чаще нужно знать только шаг КА (имя)
хочется атомарности - делаем
...а оно поменялось на другое еще до того, как мы из операции чтения вышли.
Чтобы такого не было в принципе процессы должны функционировать в рамках модели параллельных вычислений (как бы это не было кому-то противно). В противном случае мы будем иметь описанный "кошмар". "Кошмар мутабельности". И, что печально, он существует на самом деле! Не поможет даже FreeRTOS, т.к. она базируется на многопоточности. Т.е. мутабельность заложена в идеологию. Используйте VCPa и с подобным вы никогда не столкнетесь.
И о состояниях. Они не разделяемый ресурс. Да их читать может кто угодно, но изменять - только сам автомат. Но это, конечно, чистая теория. В VCPa есть возможность изменять состояние автомата извне, но злоупотреблять подобными фишками нужно осторожно.
Используйте VCPa и с подобным вы никогда не столкнетесь.
Я так понимаю, что это из-за того, что ваш VCPa все гоняет исключительно в одном рабочем треде.
Не правильно понимаете. VCPa реализует модель программирования, которая подобные "кошмары" не допускает.
Не правильно понимаете.
Тогда покажите в коде, где в вашем VCPa запускаются параллельно работающие треды. Или независимые процессы ОС, которые каким-то образом друг с другом могут обмениваться информацией, но работать реально параллельно.
Можно я с Вашего разрешения процитирую себя самого:
Но обязательно найдется тот, кто‑то возразит по поводу однопоточного параллелизма. Но в этом нет ничего необычного, т. к. вопрос параллелизма целиком лежит во власти модели вычислений, а не ее реализации. Если модель параллельна и корректно реализована, то параллельны будут и представленные ею процессы. Таков простой и краткий ответ, рассеивающий подобные сомнения.
Вы и есть "тот" ;) Вот только не знаю поймете ли Вы о чем здесь идет речь. "Квазипараллелизм" он в современных потоках, корутинах, многоядерных системах и т.д. (перечисляете, что хотите). В той или иной мере их можно использовать или попытаться использовать для реализации реального параллелизма. Но с разной степенью успеха. Например, на потоках мне не удалось создать ядро, хотя потоки я попробовал пристегнуть к модели и что-то даже получилось. Их "мутабельность" мне побороть не удалось :)
Но все же. Вы программируете в рамках модели и как она реализована, что у ней под капотом по большому счету не должно интересовать программиста. Только, если он сильно любопытный. Вот как Вы сейчас, пытаясь выяснить, как реализовано ядро и есть ли в нем потоки. Я уже сказал - нет. В реализации ядра - нет. На уровне модели есть. Есть вариант с их использованием. API библиотеки это отражает.
Если у вас нет утилизации параллельных ядер CPU, то у вас нет никакого параллельного программирования. Ваш К.О.
Вы можете до посинения повторять мантры о "модели параллелизма", но до тех пор, пока ваши сущности живут в рамках одного рабочего треда, параллельного программирования у вас нет. От слова совсем. Просто по определению.
Как я понял, "параллельность" в ваших статьях относится к каскадированию КА. Но ставить знак равенства между вашей "параллельностью" и параллельным программированием... Мощно с вашей стороны, скажем так.
"Квазипараллелизм" он в современных потоках, корутинах, многоядерных системах и т.д. (перечисляете, что хотите).
Вы заблуждаетесь.
Вы программируете в рамках модели и как она реализована, что у ней под капотом по большому счету не должно интересовать программиста.
Ну да, ну да. Когда на машине программиста 24 вычислительных ядра, задействовать из которых рекламируемый вами VCPa может только одно(!!!), то современного программиста в современных условиях это ну никак не должно интересовать. lavrov.jpg
Какое такое "каскадирование"? Охренеть до чего можно додуматься!
Вопрос на смекалку. Вы подходите к "черному ящику", как Вы определите параллельный он или нет? Ну, ответьте. Ну, напрягите свой (без обид) мозжечок. Как отличить параллельную систему от квазипараллельной, если Вам не дают заглянуть в ее нутро? Ну, как?
Какое такое "каскадирование"?
Как отличить параллельную систему от квазипараллельной, если Вам не дают заглянуть в ее нутро?
По показателям загрузки вычислительных ядер в top/htop или TaskManager. Например.
Ну, как?
Как можно быть, мягко говоря, настолько неумным человеком и еще что-то умудряться программировать? Ну, как?
А здесь - https://ru.hexlet.io/blog/posts/global-mutable-state? Но в любом случае я ответил. Или Вам нужно разжевывать, что такое автомат, что такое его состояние, что такое сеть автоматов, что такое общее состояние всех автоматов? Такой язык Вам заходит? Я бесплатным образованием не занимаюсь, однако.
Вот эта цитата вызывает сомнения
Кроме датчиков уровня жидкости он содержит датчики температуры, влажности, освещенности, реле и т. д. и т. п. Все процессы реализованы как автоматы, которые работают в жестком реальном времени (дискретность такта 10 мсек). Проект поддерживает подключение по WiFi, MQTT, Home Assistant и имеет свою страничку управления. При этом нет нужды во FreeRTOS фактически от слова совсем.
Весть стек ESP32 построен на FreeRTOS. У ESP32 нет Non-OS SDK
Пэтому утверждение выше вводит в заблуждение.
Если понимать буквально, то, конечно, Вы правы. Но в статье имеется в виду создании процессов с помощью FreeRTOS, управление ими и т.д. и т.п Этого не нужно, т.к. управление дискретным временем процессов, создание самих процессов реализует VCPa. А так, действительно, ESP32 содержит обязательную часть "по слухам" базирующуюся на FreeRTOS. Ведь, когда мы создаем даже пустой проект, то видим, что уже в пределах 18% занято. И мои попытки освободить хотя бы 8% превратились в пляски с бубном и пришлось от них отказаться. Так что в том большом проекте пришлось отказаться от VCPa и перейти аналог SWITCH и реализовывать автоматы в минимизированной форме.
Claude Sonnet идентифицировал ваш проект как операционку с кооперативной многозадачностью. Вот её API:
Проверьте
// Управление автоматами
void FStart()
void FStop()
bool FIfStop()
TASK* FLoad(TNetFsa *NetFsa, string strProc, int pri=1, bool bNot=false, CVarFSA *pVar=nullptr)
TASK* FGetTask()
TNetFsa* GetPointerToNet()
// Состояния и навигация
string FGetState(int nNum=-1)
bool FGoToState(string nam)
string FGetStateUp()
string FGetNameProcess() const
string FGetNameFsa() const
string FGetNameVarFSA() const
// Выполнение
void ExecuteThreadStep()
virtual void MooreAction()
virtual void ELSE()
virtual void MealyLoop()
void FResetAppl()
// Отладка и мониторинг
bool FDbgSetViewArc(bool b)
string FDbgViewArc(UINT id = 29003)
string FDbgViewState(UINT id = 29003)
// Переменные и параметры
CVarPrmProc* GreateParameters()
CVarPrmProc* GetPtrParameters()
void FUpdateVariable()
CLocVar* CreateLocVar(string namVar, unsigned int unType, string strComment, bool bSave=true, string strInit="", string strNameNetFsa="")
// Управление временем и задержками
void FSetSleep(long lS)
long FGetSleep()
void FCreateDelay(int nDelay)
void FCreateParDelay(int nDelay)
// Состояние и ошибки
void FClearOk()
void FClearError()
void FSetOk(int nOk=1, const char* pch=nullptr)
Такие в своё время под 8-и битники писали все кому не лень.
Что заставило взяться за старое?
Claude Sonnet идентифицировал ...
Он не все может, видимо, идентифицировать. Понять, какую алгоритмическую модель реализуют процессы библиотеки он, похоже, не может. А посему библиотеки, реализующие параллельную автоматную модель программирования мне неизвестны. Ни новых, ни, тем более, старых. Если что-то пропустил, то подскажите. Буду весьма благодарен ;)
Что заставило взяться за старое?
Для меня это не "старое", а то настоящие, что я использую, заменяя "старые-новые" потоки и корутины. Права, иногда экспериментирую с потоками (что, кстати, отражает API), но очень-очень редко.
Ну если вы не отрицаете аутентичность показанного API, то виден единственный примитив передачи управления другому потоку - задержка.
Т.е. здесь имеем самую примитивную реализацию кооперативной OS.
Вот ChatGPT 5 написал вам свою более мощную реализацию такой операционки уже портированную под ESP32 SDK:
Открывать осторожно. Реально большой и сложный код
/*
* CoopOS-ESP32 — минимальная кооперативная ОС в одном файле (MIT)
* Надстройка над сервисами ESP-IDF: esp_timer (тик), FreeRTOS (один таск-раннер),
* ESP_LOG и GPIO для демо. Задачи "корутинные": сами уступают управление.
*
* Как использовать (ESP-IDF):
* - Добавь этот файл в компонент/проект.
* - По желанию выставь #define COOP_TICK_HZ и LED_GPIO ниже.
* - В app_main() создай свои задачи через os_task_create(...).
* - Вызови coopos_start(...), чтобы запустить кооперативный рантайм.
*
* Важно: Все "задачи" CoopOS исполняются последовательно внутри ОДНОГО FreeRTOS-таска.
* Очереди/семафоры внизу — для внутреннего использования задач CoopOS (SPSC best-effort).
*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
/* ======== Настройки по умолчанию ======== */
#ifndef COOP_MAX_TASKS
# define COOP_MAX_TASKS 16
#endif
#ifndef COOP_TICK_HZ
# define COOP_TICK_HZ 1000u /* 1 ms тик */
#endif
#ifndef LED_GPIO
# define LED_GPIO 2 /* Пины зависят от платы; часто 2 или 8/19. Поменяй при необходимости. */
#endif
/* ======== ESP-IDF / FreeRTOS ======== */
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "driver/gpio.h"
/* ======== Внутренние утилиты/критические секции ======== */
static const char *TAG = "CoopOS";
#if CONFIG_FREERTOS_UNICORE
# define COOP_CORE tskNO_AFFINITY
#else
# define COOP_CORE tskNO_AFFINITY
#endif
static portMUX_TYPE g_mux = portMUX_INITIALIZER_UNLOCKED;
static inline void os_crit_enter(void){ taskENTER_CRITICAL(&g_mux); }
static inline void os_crit_exit (void){ taskEXIT_CRITICAL (&g_mux); }
/* ======== Типы CoopOS ======== */
typedef uint32_t os_tick_t;
struct os_task;
typedef int (*os_thread_t)(struct os_task *self, void *arg);
typedef enum {
OS_TASK_READY = 0,
OS_TASK_RUNNING,
OS_TASK_BLOCKED_DELAY,
OS_TASK_BLOCKED_WAIT,
OS_TASK_SUSPENDED,
OS_TASK_DEAD
} os_task_state_t;
/* TCB без отдельного стека: локальный "континуэйшен" как в корутинах */
typedef struct os_task {
const char *name;
os_thread_t entry;
void *arg;
uint8_t prio; /* 0 — самый высокий */
uint8_t id;
volatile uint8_t state; /* os_task_state_t */
uint32_t lc; /* local continuation для макросов OS_* */
os_tick_t wake_deadline;/* для задержек/таймаутов */
void *wait_obj;
} os_task_t;
/* Счётный семафор (минимальный) и мьютекс на его основе */
typedef struct { volatile int32_t count; } os_sem_t;
typedef os_sem_t os_mutex_t;
/* Неблокирующая очередь указателей (SPSC, best-effort) */
typedef struct {
void **buf;
uint16_t capacity;
volatile uint16_t head; /* producer */
volatile uint16_t tail; /* consumer */
} os_queue_t;
/* ======== API CoopOS ======== */
static inline os_tick_t os_ms_to_ticks(uint32_t ms){ return (os_tick_t)((ms * (uint32_t)COOP_TICK_HZ) / 1000u); }
void os_init(void);
void os_run(void); /* Бесконечный цикл рантайма CoopOS (внутри FreeRTOS-таска) */
os_task_t* os_task_create(const char *name, os_thread_t entry, void *arg, uint8_t prio);
void os_task_kill(os_task_t *t);
os_tick_t os_now(void);
void os_task_sleep_until(os_task_t *self, os_tick_t until_tick);
void os_task_sleep_ms(os_task_t *self, uint32_t ms);
static inline void os_sem_init(os_sem_t *s, int32_t initial){ s->count = initial; }
bool os_sem_try_take(os_sem_t *s);
void os_sem_give(os_sem_t *s);
static inline void os_mutex_init(os_mutex_t *m){ os_sem_init(m, 1); }
static inline bool os_mutex_try_lock(os_mutex_t *m){ return os_sem_try_take(m); }
static inline void os_mutex_unlock(os_mutex_t *m){ os_sem_give(m); }
void os_queue_init(os_queue_t *q, void **storage, uint16_t capacity);
bool os_queue_try_send(os_queue_t *q, void *item);
bool os_queue_try_recv(os_queue_t *q, void **out_item);
/* Макросы корутин */
#define OS_BEGIN(self) switch ((self)->lc) { case 0:
#define OS_YIELD(self) do { (self)->lc = __LINE__; (self)->state = OS_TASK_READY; return 0; case __LINE__:; } while (0)
#define OS_WAIT_UNTIL(self, cond) do { while (!(cond)) { OS_YIELD(self); } } while (0)
#define OS_DELAY(self, ms) do { os_task_sleep_ms((self), (ms)); OS_YIELD(self); } while (0)
#define OS_END(self) } (self)->state = OS_TASK_DEAD; return 1
/* ======== Тик и idle поверх ESP-IDF ======== */
static volatile os_tick_t g_ticks = 0;
static esp_timer_handle_t s_tick_timer = NULL;
static void coop_tick_cb(void *arg){
(void)arg;
os_crit_enter();
g_ticks++;
os_crit_exit();
}
static inline bool time_reached(os_tick_t now, os_tick_t deadline){
return (int32_t)(now - deadline) >= 0; /* корректно при переполнении 32-бит */
}
static void os_hw_tick_init(void){
if (s_tick_timer) return;
const esp_timer_create_args_t args = {
.callback = &coop_tick_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "coop_tick"
};
if (esp_timer_create(&args, &s_tick_timer) == ESP_OK){
/* Период в микросекундах */
uint64_t us = 1000000ULL / (uint64_t)COOP_TICK_HZ;
esp_timer_start_periodic(s_tick_timer, us);
} else {
ESP_LOGE(TAG, "esp_timer_create failed");
}
}
static inline void os_hw_idle_hook(void){
/* Когда нет готовых задач CoopOS — уступаем FreeRTOS на 1 тик */
vTaskDelay(1);
}
void os_tick_isr(void){ /* оставлено для совместимости; не используется напрямую */
os_crit_enter();
g_ticks++;
os_crit_exit();
}
/* ======== Реализация ядра CoopOS ======== */
static os_task_t g_tasks[COOP_MAX_TASKS];
static uint8_t g_task_count = 0;
os_tick_t os_now(void){ return g_ticks; }
void os_task_sleep_until(os_task_t *self, os_tick_t until_tick){
self->wake_deadline = until_tick;
self->state = OS_TASK_BLOCKED_DELAY;
}
void os_task_sleep_ms(os_task_t *self, uint32_t ms){
os_task_sleep_until(self, os_now() + os_ms_to_ticks(ms));
}
static inline void task_init(os_task_t *t, const char *name, os_thread_t entry, void *arg, uint8_t prio, uint8_t id){
t->name = name;
t->entry = entry;
t->arg = arg;
t->prio = prio;
t->id = id;
t->state = OS_TASK_READY;
t->lc = 0;
t->wake_deadline = 0;
t->wait_obj = NULL;
}
void os_init(void){
g_ticks = 0;
g_task_count = 0;
os_hw_tick_init();
}
os_task_t* os_task_create(const char *name, os_thread_t entry, void *arg, uint8_t prio){
if (!entry) return NULL;
os_crit_enter();
if (g_task_count >= COOP_MAX_TASKS){
os_crit_exit();
return NULL;
}
uint8_t id = g_task_count;
task_init(&g_tasks[id], name, entry, arg, prio, id);
g_task_count++;
os_crit_exit();
return &g_tasks[id];
}
void os_task_kill(os_task_t *t){
if (!t) return;
os_crit_enter();
t->state = OS_TASK_DEAD;
os_crit_exit();
}
/* Семафоры */
bool os_sem_try_take(os_sem_t *s){
bool ok = false;
os_crit_enter();
if (s->count > 0){ s->count--; ok = true; }
os_crit_exit();
return ok;
}
void os_sem_give(os_sem_t *s){
os_crit_enter();
s->count++;
os_crit_exit();
}
/* Очередь указателей (SPSC) */
static inline uint16_t wrap_inc(uint16_t v, uint16_t cap){ return (uint16_t)((v + 1u) % cap); }
void os_queue_init(os_queue_t *q, void **storage, uint16_t capacity){
q->buf = storage;
q->capacity = capacity;
q->head = 0;
q->tail = 0;
}
bool os_queue_try_send(os_queue_t *q, void *item){
uint16_t h = q->head;
uint16_t n = wrap_inc(h, q->capacity);
if (n == q->tail) return false; /* полна */
q->buf[h] = item;
q->head = n;
return true;
}
bool os_queue_try_recv(os_queue_t *q, void **out_item){
uint16_t t = q->tail;
if (t == q->head) return false; /* пуста */
*out_item = q->buf[t];
q->tail = wrap_inc(t, q->capacity);
return true;
}
/* Планировщик CoopOS внутри одного FreeRTOS-таска */
static int run_task_once(os_task_t *t){
t->state = OS_TASK_RUNNING;
int finished = t->entry(t, t->arg);
if (t->state == OS_TASK_RUNNING){
t->state = OS_TASK_READY; /* если задача сама не поменяла состояние */
}
return finished;
}
void os_run(void){
for (;;){
os_tick_t now = os_now();
/* Разбудить задачи с истекшей задержкой */
for (uint8_t i = 0; i < g_task_count; ++i){
os_task_t *t = &g_tasks[i];
if (t->state == OS_TASK_BLOCKED_DELAY && time_reached(now, t->wake_deadline)){
t->state = OS_TASK_READY;
}
}
/* Выполнить готовые задачи по возрастанию prio */
int ran_any = 0;
for (uint16_t pr = 0; pr <= 255; ++pr){
for (uint8_t i = 0; i < g_task_count; ++i){
os_task_t *t = &g_tasks[i];
if (t->state == OS_TASK_READY && t->prio == pr){
int done = run_task_once(t);
ran_any = 1;
if (done){ t->state = OS_TASK_DEAD; }
}
}
}
/* Удалить DEAD (уплотнить массив) */
for (uint8_t i = 0; i < g_task_count; ){
if (g_tasks[i].state == OS_TASK_DEAD){
g_task_count--;
if (i != g_task_count){
g_tasks[i] = g_tasks[g_task_count];
g_tasks[i].id = i;
}
} else {
++i;
}
}
if (!ran_any){
os_hw_idle_hook(); /* никого нет — уступить FreeRTOS */
}
}
}
/* ======== Запуск CoopOS как FreeRTOS-таска ======== */
static void coop_runner_task(void *arg){
(void)arg;
ESP_LOGI(TAG, "CoopOS runner started (tick=%u Hz)", (unsigned)COOP_TICK_HZ);
os_run(); /* не возвращается */
vTaskDelete(NULL);
}
/* Создать таск-раннер CoopOS
* prio — приоритет FreeRTOS таска-раннера (рекомендуется > tskIDLE_PRIORITY)
* stack — размер стека раннера (т.к. задачи без собственного стека, хватит 2..4К)
* core — ядро, либо tskNO_AFFINITY
*/
static TaskHandle_t s_coop_task = NULL;
static inline void coopos_start(UBaseType_t prio, uint32_t stack, BaseType_t core){
if (s_coop_task) return;
xTaskCreatePinnedToCore(coop_runner_task, "coop_runner", stack, NULL, prio, &s_coop_task, core);
}
/* ======== ДЕМО: мигание и prod/cons ======== */
static inline void board_led_init(void){
gpio_reset_pin(LED_GPIO);
gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT);
}
static inline void board_led_toggle(void){
static int s = 0;
s ^= 1;
gpio_set_level(LED_GPIO, s);
}
static inline void board_log(const char *s){ ESP_LOGI(TAG, "%s", s); }
/* Задача: мигает раз в 500 мс */
static int task_blink(os_task_t *self, void *arg){
(void)arg;
OS_BEGIN(self);
board_led_init();
for (;;){
board_led_toggle();
OS_DELAY(self, 500);
}
OS_END(self);
}
/* Очередь сообщений */
static void *q_storage[8];
static os_queue_t q;
/* Продюсер: пишет "tick" каждые 300 мс */
static int task_producer(os_task_t *self, void *arg){
(void)arg;
OS_BEGIN(self);
for (;;){
const char *msg = "tick";
OS_WAIT_UNTIL(self, os_queue_try_send(&q, (void*)msg));
OS_DELAY(self, 300);
}
OS_END(self);
}
/* Консюмер: читает из очереди и логирует */
static int task_consumer(os_task_t *self, void *arg){
(void)arg;
OS_BEGIN(self);
for (;;){
void *msg = NULL;
OS_WAIT_UNTIL(self, os_queue_try_recv(&q, &msg));
board_log((const char*)msg);
OS_DELAY(self, 50);
}
OS_END(self);
}
/* ======== Точка входа ESP-IDF ======== */
void app_main(void){
os_init();
os_queue_init(&q, q_storage, (uint16_t)(sizeof(q_storage)/sizeof(q_storage[0])));
/* Создаём три кооперативные задачи (prio: 0 — самый высокий) */
os_task_create("producer", task_producer, NULL, 0);
os_task_create("blink", task_blink, NULL, 1);
os_task_create("consumer", task_consumer, NULL, 2);
/* Запускаем рантайм CoopOS в отдельном FreeRTOS-таске */
coopos_start(/*prio*/ tskIDLE_PRIORITY+2, /*stack*/ 4096, /*core*/ COOP_CORE);
}
Нет, ну как тут не возмущаться? "Примитивная реализация", понимаешь? У меня почти любой файл в два раза больше "мощной реализации" ChatGPT. Как Вы оцениваете мощь? По каким критериям? Ну, да ладно. Все гениальное простое, как сапог... Поэтому вернемся к нашим "мощам"...
Итак, - задержка. Где Вы увидели другой поток. Ну, прогуляйтесь до моей статьи там про нее все расписано. Это вложенный автомат. Извиняйте, что не расписываю детали. Извиняйте, что ссылаюсь опять на свои статьи. Это надо же статью пересказать! Не проще ли прочитать, а?
Итак, в VCPa поток - это автомат. Задержка - это вложенный автомат, вызываемый автоматам. Аналог подпрограммы. А, кстати, вложенные потоки есть?
Далее. Разница между моей "операционкой" и ChatGPT в том, что она реализует АЛГОРИТМИЧЕСКУЮ МОДЕЛЬ!!! Последняя - только создает, видимо, некие потоки. Может, корутины. Короче - все что было, то и осталось. Вот, истинно "полная мутабельность". И Вы еще утверждаете, что она мощнее? Грустно все это. Народ не любит читать, народ не любит вникать. Может, конечно, элементарно нет на это время? Но время надо находить на чтение. Это должно быть частью профессии. Ну, как иначе, ребята? Так вы никогда не разберетесь в том, что даже обсуждаете. И во всем нужно искать основу, причины. Без этого не будет решений. Так и будем бороться с "мутабельностью" (красивое, блин, слово!) до скончания веков.
Но если критикуете, то хотя бы нужно понимать что, где и как. А то - мощнее и все!? "Вигвам", как говаривал кто-то, может, кот Матроскин.
Ну с кем вы тягаетесь!
ChatGPT потратил на этот исходник 3 секунды!
На загадки про козу и капусту он больше тратит времени.
Эти нейронки просто профи в создании таких операционок.
Я еще попросил уложиться как можно короче. И не привлекал Claude, чтобы не загрязнять свой текущий чат.
Просто хотел посоветовать открыть глаза. Революция уже произошла. Исходники в текстовой нотации ничего больше не значат. Нейронкам без разницы - автоматами писать или под RTOS или под bare metal. Дело только вкуса.
Сейчас осталась гибридная графическая нотация, которая что-то значит, поскольку ИИ еще ее не осиливает.
Пример:

Но написать такую диаграмму в примением вашей "библиотеки" я бы даже не пытался.
Как избежать кошмара параллелизма в IoT: автоматы вместо потоков и корутин