PDA

Просмотр полной версии : Что почитать?



Stan_1
21.04.2021, 13:26
Здравствуйте!

Посоветуйте, чтобы почитать по совсем базовым вещам CodeSys. Я тут пытаюсь разобраться (есть бекграунд программирования), но упираюсь в непонимание вещей, которые скорее всего являются супербазовыми. Например:
1. Вижу примеры с объявлением структур TYPE. Но в какой файл его добавлять? В PRG_PLC или какой-то другой?
2. Какой жизненный цикл работы с переменными Modbus? Как сделать так, чтобы я вызывал FB только при изменении переменной?
3. Как на языке ST передать в FUN значения входов, и изменить в FUN значения нескольких выходов? Можно ли внутри FUN менять внешние переменные?
4. Как вызывать функции по изменению аналоговых входов? Например, хочу раз в 10 секунд передавать по MQTT значение температуры. Как мне сделать так, чтобы я раз в 10 секунд дергал функцию (FUN)? Делать ли это через счетчик циклов или есть более изящные способы?
5. Везде показано, как использовать MQTT визуально. А можно как-то работать с такими объектами в ST-формате?
6. Как настроить MQTT-клиент централизовано. Допустим, мне надо в множестве FUN передавать данные в MQTT-сервер. Не делать же вызов этого клиента в каждой функции? Иначе потом смена пароля будет в десятке мест. Или сделать функцию, куда поместить клиент и уже дергать функцию? Или нужно где-то настроить клиента, получить его инстанс, и уже дергать инстанс?

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

Где можно об этом почитать? :) Именно об азах.

Евгений Кислов
21.04.2021, 13:53
Добрый день.

Литературы не слишком много, из общеизвестных книг можно порекомендовать Игорь Петров: Программируемые контроллеры. Стандартные языки и приемы прикладного проектирования
Еще, возможно, подойдет Сергей Романов. Изучаем Structured Text МЭК 61131-3
И, естественно, есть справка: https://help.codesys.com/webapp/_cds_f_reference_programming;product=codesys;versi on=3.5.14.0

Во вопросам:


1. Вижу примеры с объявлением структур TYPE. Но в какой файл его добавлять? В PRG_PLC или какой-то другой?

ПКМ на узел Application - Добавление объекта - DUT


2. Какой жизненный цикл работы с переменными Modbus?

Вы "жизненным циклом" называете время жизни объекта (object lifetime)?
В CODESYS в большинстве случаев используется только статическое выделение памяти, так что этот вопрос не имеет смысла.
Или я вас неправильно понял?


Как сделать так, чтобы я вызывал FB только при изменении переменной?

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


3. Как на языке ST передать в FUN значения входов, и изменить в FUN значения нескольких выходов? Можно ли внутри FUN менять внешние переменные?

https://help.codesys.com/webapp/_cds_obj_function;product=codesys;version=3.5.14.0
Внутри функции можно менять глобальные переменные - но это плохой подход, который нарушает инкапсуляцию данных.


Как мне сделать так, чтобы я раз в 10 секунд дергал функцию (FUN)? Делать ли это через счетчик циклов или есть более изящные способы?

Классический вариант:


PROGRAM PLC_PRG
VAR
fbTon: TON;
END_VAR


fbTon(IN := NOT(fbTon.Q), PT := T#10M);

IF fbTon.Q THEN
// код, размещенный здесь, будет выполняться раз в 10 минут
END_IF



5. Везде показано, как использовать MQTT визуально. А можно как-то работать с такими объектами в ST-формате?

Да, безусловно, ФБ можно вызывать на любом языке - в том числе, и на ST.
https://help.codesys.com/webapp/_cds_obj_function_block;product=codesys;version=3. 5.14.0#calling-a-function-block


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

Это вопрос не к CODESYS, а к архитектуре приложения. Про проектирование ПО написано уже достаточно книг.

murzik
21.04.2021, 14:04
Классический вариант:


PROGRAM PLC_PRG
VAR
fbTon: TON;
END_VAR


fbTon(IN := NOT(fbTon.Q), PT := T#10M);

IF fbTon.Q THEN
// код, размещенный здесь, будет выполняться раз в 10 минут
END_IF




вот, кстати, очень интересный момент: ТОН работает 10 минут, потом ждётся какое-то время до следующего цикла работы PLC_PRG. Потом в этом цикле работает функция NOT, и только на следующий цикл работы PLC_PRG произойдёт перезапуск ТОН. Итого 10 минут + 1...2 такта работы PLC_PRG. Уж лучше тогда BLINK+R_TRIG. Или вру я?

Stan_1
21.04.2021, 14:29
О! Отлично, спасибо большое! Пошел читать и учить :)

Евгений Кислов
21.04.2021, 16:34
Итого 10 минут + 1...2 такта работы PLC_PRG.

Я предполагаю, в рамках описанной задачи задержка в 1 такт вполне допустима.

murzik
23.04.2021, 20:16
Внутри функции можно менять глобальные переменные - но это плохой подход, который нарушает инкапсуляцию данных.





Будьте добры, поясните, а внутри ФБ тоже нежелательно глобальные переменные пересчитывать? Просто у меня весь проект на этом работает. Может переделать, пока не поздно?

Евгений Кислов
23.04.2021, 20:45
Будьте добры, поясните, а внутри ФБ тоже нежелательно глобальные переменные пересчитывать? Просто у меня весь проект на этом работает. Может переделать, пока не поздно?

https://habr.com/ru/company/mailru/blog/454946/

murzik
24.04.2021, 07:11
https://habr.com/ru/company/mailru/blog/454946/
Ознакомился, спасибо! Примеры правда там на непонятном языке. Но кое-что отложилось кое-где. Думаю попробовать сделать проект без ГВЛ. Вот только тогда наверное есть смысл кое-какие обсчёты сразу в плс-прг перенести из пары фб. Иначе будут какие-то кошмарные количества входных и выходных переменных у пары фб.
Но тогда сама плс-прг может неудобоваримый вид принять. В этой связи страшная мысль приходит в голову: а не написать-ли плс-прг на ст? Это вообще можно? Кто-нибудь пробовал? Вроде бы там особых таких ветвлений нет, чтоб от перехода на ст с сфс наглядность пропала.
И вот ещё вопрос, чтобы два раза не вставать: а можно ли в ст, при вызове фб передаваемые ему параметры в столбик написать, чтоб в онлайне значения переменных нагляднее смотрелись?

Евгений Кислов
24.04.2021, 07:29
а не написать-ли плс-прг на ст? Это вообще можно? Кто-нибудь пробовал?

Это крайне распространенный подход.


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

Конечно.

Stan_1
25.04.2021, 10:12
Ответы Евгения дали толчок к пониманию :) Многое стало понятнее и проще. Просьба помочь еще с одним вопросом. Хочу написать функциональный блок, который будет отрабатывать одинарный и двойной "клик" клавиши света. И хочу фиксировать время нажатия и отжатия клавиши в виде временной метки - timestamp в мс. Примерно так:

VAR
pressedAt: DWORD := 0; // Временная метка последнего нажатия клавиши "вниз"
sysTimer: SystemTimeDate; // Системный таймер
END_VAR

pressedAt := (sysTimer.dwLowMSecs - (sysTimer.dwLowMSecs/1000) * 1000);

В режиме симуляции sysTimer всегда равен нулю. :( Как правильно делать пошаговый debug в режиме симуляции?

Евгений Кислов
25.04.2021, 10:34
Давайте вы сначала ответите на самый главный вопрос.


И хочу фиксировать время нажатия и отжатия клавиши в виде временной метки - timestamp в мс.

Зачем?

Stan_1
25.04.2021, 10:56
Давайте вы сначала ответите на самый главный вопрос.
Зачем?

А какие варианты? Я смотрю статус клавиши. Изменилась с отжатой на нажатую - фиксирую время нажатия. Изменилась с нажатой на отжатую - фиксирую время отжатия. Затем смотрю разницу. Если она меньше 250 мс - это короткий клик, увеличиваю счетчик кликов (потом через CASE пееключу нужный выход). Если 1500 мс - это длинный клик, гашу все выходы. Если после последнего короткого отжатия прошло более 500 мс - переключаю нужный выход.

Все базируется на трех timestamp: текущее время, время нажатия и время отжатия.

Евгений Кислов
25.04.2021, 11:12
А какие варианты? Я смотрю статус клавиши. Изменилась с отжатой на нажатую - фиксирую время нажатия. Изменилась с нажатой на отжатую - фиксирую время отжатия. Затем смотрю разницу. Если она меньше 250 мс - это короткий клик, увеличиваю счетчик кликов (потом через CASE пееключу нужный выход). Если 1500 мс - это длинный клик, гашу все выходы. Если после последнего короткого отжатия прошло более 500 мс - переключаю нужный выход.

Все базируется на трех timestamp: текущее время, время нажатия и время отжатия.

Ну, например, первый вариант - просто взять готовый блок CLICK_MODE из библиотеки Oscat Building.
https://ftp.owen.ru/CoDeSys3/04_Library/05_3.5.11.5/02_Libraries/OSCATBuilding.package

Еще пользователь Cs-Cs (https://owen.ru/forum/member.php?u=74642) когда-то выкладывал свои ФБ для обработки нажатий - я думаю, если он появится в этой теме, то предоставит ссылки.

Stan_1
25.04.2021, 11:35
Ну, например, первый вариант - просто взять готовый блок CLICK_MODE из библиотеки Oscat Building.
https://ftp.owen.ru/CoDeSys3/04_Library/05_3.5.11.5/02_Libraries/OSCATBuilding.package


Прикольная библиотека. Спасибо. Там есть нужные и интересные вещи, но конкретно CLICK_MODE не подходит, увы. Она не позволяет делать тройной клик (а у меня в планах он есть). Плюс мне еще нужна будет возможность управлять от Home Assistant. Подобную логику, которая мне нужна - я уже делал, но на компе, с имитацией нажатия мышкой. А теперь хочу ее перенести на PLC. Поэтому интересно понять именно возможность действовать через измерение времени. Нет ли все-таки возможность получать текущий таймстамп? Причем в режиме симуляции тоже.

ASo
25.04.2021, 11:47
Так откройте эту библиотеку, для версии 2 она открытая, и посмотрите, как реализован этот ФБ.

Stan_1
25.04.2021, 11:52
Так откройте эту библиотеку, для версии 2 она открытая, и посмотрите, как реализован этот ФБ.

Я могу это сделать. Но у меня уже написана собственная реализация, моделирование которой меня устроило. Если я пойму - что на ПЛК таймстампы невозможны/ограничены, конечно я буду изучать альтернативу. Но пока хотелось бы понять про таймстампы. Пока я в этой теме четкого "нет, таких в ПЛК нет" - не услышал :) Возможно, я просто использую не те/не то.

ASo
25.04.2021, 11:56
Так пишите в переменную текущее время по фронту или спаду.

Stan_1
25.04.2021, 12:07
Так пишите в переменную текущее время по фронту или спаду.

Так я так и делаю. Но мой изначальный вопрос - как в режиме симуляции получать значение таймера sysTime - не ноль.

VAR
pressedAt: DWORD := 0; // Временная метка последнего нажатия клавиши "вниз"
sysTimer: SystemTimeDate; // Системный таймер
END_VAR

pressedAt := (sysTimer.dwLowMSecs - (sysTimer.dwLowMSecs/1000) * 1000);

Евгений Кислов
25.04.2021, 12:23
Так откройте эту библиотеку, для версии 2 она открытая, и посмотрите, как реализован этот ФБ.

Для V3.5 тоже открытая, кстати.


Так я так и делаю. Но мой изначальный вопрос - как в режиме симуляции получать значение таймера sysTime - не ноль.

VAR
pressedAt: DWORD := 0; // Временная метка последнего нажатия клавиши "вниз"
sysTimer: SystemTimeDate; // Системный таймер
END_VAR

pressedAt := (sysTimer.dwLowMSecs - (sysTimer.dwLowMSecs/1000) * 1000);

Потому что SystemTimeDate - это не системный таймер, а тип данных, который возвращает ФБ для получения системного времени из очень старой библиотеки, которая оставлена для совместимости с проектами, импортированными из CoDeSys V2.3.
Это можно понять, посмотрев на библиотеку, в которой он объявлен.

Можно так:



VAR
pressedAt: TIME; // Временная метка последнего нажатия клавиши "вниз" в миллисекундах
END_VAR

// тут какие-то условия, по которым выполняется этот код в нужный момент времени
pressedAt := TIME();

И лучше использовать не симуляцию, а виртуальный контроллер:
https://owen.ru/forum/showthread.php?t=28167&page=5&p=296706&viewfull=1#post296706

Stan_1
25.04.2021, 15:32
Да, TIME() - то, что надо. Спасибо! А не подскажите еще - как выводить что-то в консоль? И есть ли такое понятие? Я нашел вроде пару способов описанных здесь на форуме, но не сработали. :(

По поводу виртуального контроллера - я пробовал его несколько дней назад. Но он при update device скинул все настройки PLC :( Поэтому боюсь делать это опять.

Евгений Кислов
25.04.2021, 15:39
А не подскажите еще - как выводить что-то в консоль?

Что именно вы хотите сделать и с какой целью?

Stan_1
25.04.2021, 15:43
Что именно вы хотите сделать и с какой целью?

Просто писать собщения, или сообщения с парметрами. Например, "Клавиша нажата", "Клавиша отпущена через 300 мс". И видеть это где-то в Codesys

Евгений Кислов
25.04.2021, 15:53
Просто писать собщения, или сообщения с парметрами. Например, "Клавиша нажата", "Клавиша отпущена через 300 мс". И видеть это где-то в Codesys

Для работы со строками можно использовать переменные типа STRING и WSTRING.
https://help.codesys.com/webapp/_cds_datatype_string;product=codesys;version=3.5.1 6.0

Stan_1
25.04.2021, 17:10
Для работы со строками можно использовать переменные типа STRING и WSTRING.
https://help.codesys.com/webapp/_cds_datatype_string;product=codesys;version=3.5.1 6.0

Ага, понял :) Я думал - есть какой-то аналог console.log в JavaScript. Но лога в строковую переменную оказалось достаточно. В общем, задумка получилась. Вроде все работает норм. Одинарный, двойной, тройной клик для включения/выключения до 3-х выходов, и длительный для сброса всех выходов. Спасибо большое!

Stan_1
28.04.2021, 21:54
Евгений, добрый вечер! А подскажите, плз, в режиме эмулятора должен ли работать MQTT Client, то есть отсылать сообщения в брокер? Или он работает в режиме эмуляции без реальной отправки сообщений? Просто делаю

mqtt.i_sPayload := json;
mqtt.i_xPublish := TRUE;
sError := mqtt.q_sDiagMsg;

Но не вижу ни отправки, ни сообщений об ошибках.

Евгений Кислов
29.04.2021, 06:21
Евгений, добрый вечер! А подскажите, плз, в режиме эмулятора должен ли работать MQTT Client, то есть отсылать сообщения в брокер? Или он работает в режиме эмуляции без реальной отправки сообщений? Просто делаю

mqtt.i_sPayload := json;
mqtt.i_xPublish := TRUE;
sError := mqtt.q_sDiagMsg;

Но не вижу ни отправки, ни сообщений об ошибках.

Нет, в режиме эмулятора он работать не должен.
Используйте виртуальный контроллер.
https://owen.ru/forum/showthread.php?t=28167&page=5&p=296706&viewfull=1#post296706

Stan_1
30.04.2021, 21:48
Нет, в режиме эмулятора он работать не должен.
Используйте виртуальный контроллер.
https://owen.ru/forum/showthread.php?t=28167&page=5&p=296706&viewfull=1#post296706

Я наконец нашел попробовать. Смог настроить виртуальный контролер, запустил, вижу зеленый RUN. Вижу в логе виртуального устройства, что все ОК. Но обращений к mqtt-бокеру не вижу :( Подскажите, плз, как можно это проверить, коме установки Wireshark и сканирования сети?

Евгений Кислов
30.04.2021, 21:53
Попробуйте для начала просто по видео повторить:
https://youtu.be/3AGJbDp0aaU

Stan_1
30.04.2021, 22:30
Попробуйте для начала просто по видео повторить:
https://youtu.be/3AGJbDp0aaU

Я посмотрел внимательно видео. Вроде бы так и делаю. Попробовал максимально упростить задачу. И прямо в PLC_PRG написал так:
mqtt.i_xEnable := TRUE;
mqtt.i_sBrokerAddress := 'subdomain.domain.com';
mqtt.i_uiPort := 1883;
mqtt.i_sUsername := 'user';
mqtt.i_sPassword := 'password';
mqtt.i_sPayload := 'starting';
mqtt.i_sTopicPublish := '/topic11/plc';
mqtt.i_xPublish := TRUE;

И после запуска ничего не происходит :( Я указываю те же данные в приложении MQTT Explorer, и все нормально подключается. То есть коннективити есть, логин/пароль верные.

По netstat вижу подключения к Gateaway (localhost:1217). Но не вижу обращений к адресу mqtt-брокера.

capzap
30.04.2021, 22:46
Я посмотрел внимательно видео. Вроде бы так и делаю. Попробовал максимально упростить задачу. И прямо в PLC_PRG написал так:
mqtt.i_xEnable := TRUE;
mqtt.i_sBrokerAddress := 'subdomain.domain.com';
mqtt.i_uiPort := 1883;
mqtt.i_sUsername := 'user';
mqtt.i_sPassword := 'password';
mqtt.i_sPayload := 'starting';
mqtt.i_sTopicPublish := '/topic11/plc';
mqtt.i_xPublish := TRUE;

И после запуска ничего не происходит :( Я указываю те же данные в приложении MQTT Explorer, и все нормально подключается. То есть коннективити есть, логин/пароль верные.

По netstat вижу подключения к Gateaway (localhost:1217). Но не вижу обращений к адресу mqtt-брокера.

так на видео же CFC там сам квадрат ПОУ в ST обозначает что после такого кода должно стоять mqtt();

Евгений Кислов
01.05.2021, 08:39
Я посмотрел внимательно видео. Вроде бы так и делаю. Попробовал максимально упростить задачу. И прямо в PLC_PRG написал так:
mqtt.i_xEnable := TRUE;
mqtt.i_sBrokerAddress := 'subdomain.domain.com';
mqtt.i_uiPort := 1883;
mqtt.i_sUsername := 'user';
mqtt.i_sPassword := 'password';
mqtt.i_sPayload := 'starting';
mqtt.i_sTopicPublish := '/topic11/plc';
mqtt.i_xPublish := TRUE;

И после запуска ничего не происходит :( Я указываю те же данные в приложении MQTT Explorer, и все нормально подключается. То есть коннективити есть, логин/пароль верные.

По netstat вижу подключения к Gateaway (localhost:1217). Но не вижу обращений к адресу mqtt-брокера.

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

- используйте CFC
- используйте в качестве брокера www.mqtt-dashboard.com
- не используйте логин и пароль
- воздействуйте на переменные, привязанные к входам i_xPublish и i_xSubscribe "вручную", а не с помощью кода

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

54923

Stan_1
01.05.2021, 10:23
Попробуйте для начала просто по видео повторить.

- используйте CFC
- используйте в качестве брокера www.mqtt-dashboard.com
- не используйте логин и пароль
- воздействуйте на переменные, привязанные к входам i_xPublish и i_xSubscribe "вручную", а не с помощью кода

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

54923

Да, спасибо! Вчера весь вечер возился, и как-то оно заработало. Но очень нечетко. И есть нюансы, смысла которых я не понимаю, а описание очень скудное :(

Первое, что выяснил - нельзя вызывать mqtt() внутри FB :( Изначально была логика иметь в PLC_PRG настроеный экземпляр, передавать его в FB, где указывать топик, payload, устанавливать i_xPublish в TRUE, и вызывать mqtt(). Это не работало. Как только я вынес все указанное за пределы FB в PLC_PRG - я увидел факт передачи. Уже успех. Но почему это не работает внутри FB - не понимаю. Видимо, аналогия из моего пограммисткого опыта, что FB - это просто функция, неверна. У FB есть какие-то ограничения, на описание которых я не натыкался.
Долго не мог понять, почему MQTT_Client режет json, который я подготовил. Потом понял - передаю строку более 80 символов. Как указать STRING(250) для i_sPayload - не нашел :( Это вообще возможно, или начинать смотреть в сторону платных бибилиотек? Там у библиотеки 3S длина 1024 символа.
Хотя я вызываю функцию mqtt() раз в 2 секунды, отправка данных идет ОДИН раз за запуск. Видимо, есть опят какие-то базовые нюансы, которые нужно просто знать :( Я пробовал по разному: пробовал после вызова mqtt() вызывать i_xPublis = FALSE. Пробовал менять Payload (была версия, что отправляются только измененные данные, хотя в исходниках вроде не так). Но ничего не помогло. И здесь мыслей вообще нет. Видимо, есть какие-то особенности именно работы PLC, которые я фундаментально не понимаю :(

Евгений Кислов
01.05.2021, 10:32
1. В описании ситуации не содержится вопроса, но для начала я бы вам рекомендовал для самого себя написать ТЗ на модуль работы с MQTT и нарисовать схему взаимодействия его элементов.
2. Библиотека доступна в исходниках - можно подправить максимальную длину строк под себя.
3. Это не функция, а ФБ. Разумный подход - вызывать этот ФБ в цикле ПЛК (без каких-либо условий) и по нужным событиям генерировать передний фронт в переменной, привязанной ко входу i_xPublish.

Stan_1
01.05.2021, 12:31
Фух. Кажется, получилось. Спасибо большое! Даже удалось перенести все в FB. Не могу сказать, что понимаю четко почему заработало, скорее интуитивно. :) Но главное - что теперь можно уже экспериментировать, как Вы и советовали.

Stan_1
02.05.2021, 21:35
Эх. Натолкнулся опять на моменты, которые пока не могу осознать. Теперь ситуация такова. Есть FB, от которого созданы два инстанса. В каждом инстансе свой MQTT Client, который настроен на разные Subscribe topic. Инстансы вызываются один за другим, в каждом раз в 3 секунды выставляется i_xSubsribe := TRUE; и затем в каждом инстансе на каждом цикле выполнения программы вызывается mqtt(); Что я наблюдаю в итоге?
1. Допустим, в подписанных в MQTT_Client топиках установлены одинаковые значения (строка "ON"). В брокере я меняю один из топиков в "OFF". И вижу, что оба инстанса получили этот OFF, хотя ожидал, что только один инстанс должен был его получить. Если опять значение топика в брокере установить в ON - эту строку опять получат оба MQTT_Client.
2. Через некоторое время (3-4 минуты) MQTT Client выбрасывает Exception. Какое - не могу понять (где вообще это увидеть?). При этом q_sDiagMsg принимает значение "$04$10", а в q_sLastReceivedMessage выводится полная абракадабра.



Может ли такое произойти из-за одновременного запроса от двух клиентов к брокеру (вызов идет в одном цикле)? Как понять, в чем причина exception?

Евгений Кислов
03.05.2021, 08:39
Как понять, в чем причина exception?

1. См. Device - Журнал.

2.


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

3. См. эту статью: https://oscat.ru/?p=382

Stan_1
05.05.2021, 13:48
поэтапно начинайте адаптировать к своей задаче, после каждого этапа проверяя, сохранилась ли работоспособность. Если после очередного изменения связь пропадет - надо приступать к анализу изменений. Так обычно происходит отладка проектов.

В общем, есть какой-то баг библиотеки, который то-ли появляется только в симуляторе, то-ли имеет какие-то фундаментальные причины. Связан с тем, что два инстанса MQTT_Client, видимо, за счет использования шаринга сокета - читают данные друг друга, и когда это еще и приходится на совпадения циклов - вылетают с AccessViolation. Такой вопрос задавали автору в 2018 году, но вся помощь от него была в стиле "такого не может быть". Но описанный кейс - точно мой. Вот ссылка (https://sourceforge.net/p/codesys-mqtt-library/discussion/general/thread/fc5d0490/).

Пока не понимаю, что делать. Для начала хочу попробовать две вещи:
1. Понять, как игнорировать AccessViolation (если это вообще возможно)
2. Попробовать через глобальные переменные разводить циклы чтения из разных топиков.

Евгений Кислов
05.05.2021, 14:05
1. Понять, как игнорировать AccessViolation (если это вообще возможно)

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


Связан с тем, что два инстанса...

Есть FB, от которого созданы два инстанса. В каждом инстансе свой MQTT Client, который настроен на разные Subscribe topic.

А зачем два инстанса?.. Что мешает подписать один инстанс на оба топика?

Stan_1
05.05.2021, 14:14
А зачем два инстанса?.. Что мешает подписать один инстанс на оба топика?

Возможно, архитектурно делаю неправильно. Но опять же, иду из опыта обычного программирования.:) Есть функциональный блок. Допустим - обрабатывающий выключатель света, скажем, FB_LIGHT_SWITCH. Соответственно, из логики ООП я делаю его максимально самодостаточным, то есть в
каждый из них добавляю mqtt: MQTT_Client.FB_MQTTClient. И затем делаю:
switchKuhnya: FB_LIGHT_SWITCH;
switchKoridor: FB_LIGHT_SWITCH;
switchKabinet: FB_LIGHT_SWITCH;

И потом,

switchKuhnya(i_sTopic := '/rooms/kuhnya');
switchKoridor(i_sTopic := '/rooms/koridor');
switchKabinet(i_sTopic := '/rooms/kabinet');

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

Каких-то примером, и best practics по такому варианту я не нашел. Если прямо совсем не прав - то просьба поправить фокус. :)

Евгений Кислов
05.05.2021, 14:29
Соответственно, из логики ООП я делаю его максимально самодостаточным, то есть в каждый из них добавляю mqtt: MQTT_Client.FB_MQTTClient

Я не знаю, что вы называете "логикой ООП", но ваша фраза не соответствует моим личным представлениям об ООП.
https://ru.wikipedia.org/wiki/%D0%91%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D 0%BD%D0%BD%D1%8B%D0%B9_%D0%BE%D0%B1%D1%8A%D0%B5%D0 %BA%D1%82


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


Если прямо совсем не прав - то просьба поправить фокус.

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

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

melky
05.05.2021, 14:40
FB используют общую память, насколько помню всегда и везде, не только в Овен ПЛК.
А вот изолированы как раз FUNC (Функции)...

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

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

Stan_1
05.05.2021, 17:48
Уфффф. Кажется методом проб и ошибок понял, что не так. Есть разное поведение при публикации и подписке. Смысл в том, что сначала я писал код с публикацией. И она долго не шла. Поэтому в общем случае я делал такую логику:


mqtt.i_xPublish := FALSE;
tonInterval(IN := NOT(tonInterval.Q), PT := T#3S);
IF tonInterval.Q THEN
// Формируем json
mqtt.i_sPayload := json;
mqtt.i_xPublish := TRUE;
END_IF
mqtt();

И такая логика чудесно работает. Примерно по такой же логике я пытался сделать и i_xSubscribe.


mqtt.i_xSubscribe := FALSE;
tonInterval(IN := NOT(tonInterval.Q), PT := T#3S);
IF tonInterval.Q THEN
result := mqtt.q_sLastReceivedMessage;
mqtt.i_xSubscribe := TRUE;
END_IF
mqtt();
И на этом он падал в Access Violation. В общем, строчка: mqtt.i_xSubscribe := FALSE; - является лишней. Как только ее убрал - все стало отлично.

Stan_1
07.05.2021, 10:01
Нет, все-таки все оказывается не так просто. Сейчас закомментировал абсолютно все. Оставил только такой код в PLC_PRG:



PROGRAM PLC_PRG
VAR
mqtt: MQTT_Client.FB_MQTTClient;
iTest: INT := 0;
END_VAR

mqtt.i_xEnable := TRUE;
mqtt.i_sBrokerAddress := sHost;
mqtt.i_uiPort := iPort;
mqtt.i_sUsername := sUsername;
mqtt.i_sPassword := sPassword;

mqtt.i_sPayload := INT_TO_STRING(iTest);
mqtt.i_sTopicPublish := 'broker/test/step1';
IF (mqtt.q_udiState = 60) THEN
mqtt.i_xPublish := TRUE;
ELSE
mqtt.i_xPublish := FALSE;
END_IF
mqtt();

iTest := iTest + 1;


Итог - после примерно 1300-1500 итераций, падение в AccessViolation. В чем может быть проблема?

55011
55012

Евгений Кислов
07.05.2021, 10:20
У меня с вашим кодом и облачным брокером проблема не воспроизводится.

55013

Попробуйте тоже с www.mqtt-dashboard.com ваш эксперимент повторить.

Stan_1
07.05.2021, 11:35
Да, там стабильно работает. Буду выяснять в чем разница.

Stan_1
08.05.2021, 11:39
Вчера экспериментально выяснили, что замена MQTT-брокера с mosquitto на hivemq проблему AccessViolation снимает. Но в чем root cause - осталось непонятным. В логах mosquitto (полных) все нормально, ничего не видим. Продолжу работу с hivemq.