Как обрабатывать множество тем подписки MQTT в TwinCAT с помощью Tc3_IoTBase

#mqtt #plc #twincat #structured-text

#mqtt #plc #twincat #структурированный текст

Вопрос:

Я могу читать несколько разделов подписки, используя этот код. Однако я подписан на множество разных тем, и длинный оператор if замедляет мой код. Мне уже приходилось увеличивать количество тактов цикла до 20 (это, вероятно, перебор, но 10 было недостаточно) в PlcTask. Я ищу более разумное решение, которое будет работать с меньшим количеством тактов цикла. В приведенном ниже коде ясно, как долго выполняется оператор IF, и это даже не весь оператор if (только для тем machine и motion1).

 IF fbMessageQueue.nQueuedMessages > 0 THEN
    IF fbMessageQueue.Dequeue(fbMessage:=fbMessage) THEN
        IF fbMessage.CompareTopic(sTopic:='machine/on') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Machine.bOnPB := STRING_TO_BOOL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='machine/off') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Machine.bOffPB := STRING_TO_BOOL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='motion1/position') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Motion.nMotion1Postion := STRING_TO_LREAL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='motion1/velocity') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Motion.nMotion1Velocity := STRING_TO_LREAL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='motion1/acceleration') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Motion.nMotion1Acceleration := STRING_TO_LREAL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='motion1/deceleration') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Motion.nMotion1Deceleration := STRING_TO_LREAL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='motion1/execute') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Motion.nMotion1Execute := STRING_TO_LREAL(sPayloadRcv);
        // same for motion2 and motion3
        END_IF
    END_IF
END_IF
 

Мои темы строятся так: «движение 1 / положение», «движение 1 / ускорение», «движение 2 / ускорение» и т. Д. И т. Д. (Надеюсь, вы поняли идею). Итак, я уже смог подписаться на все темы motion1, подписавшись на ‘motion1 / #’. Поэтому я попытался использовать fb.Message.Сравните Topic(sTopic:= ‘motion1 /#’), чтобы найти темы, принадлежащие motion1, а не оператор if, который распознает темы ‘motion1 / somethingsomething’. Однако fb.Message.CompareTopic(sTopic:= ‘motion1 /#’) не распознал темы motion1.

 IF fbMessageQueue.nQueuedMessages > 0 THEN
    IF fbMessageQueue.Dequeue(fbMessage:=fbMessage) THEN
        IF fbMessage.CompareTopic(sTopic:='machine/on') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Machine.bOnPB := STRING_TO_BOOL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='machine/off') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Machine.bOffPB := STRING_TO_BOOL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='motion1/#') THEN
            IF fbMessage.CompareTopic(sTopic:='motion1/position') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Postion := STRING_TO_LREAL(sPayloadRcv);
            ELSIF fbMessage.CompareTopic(sTopic:='motion1/velocity') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Velocity := STRING_TO_LREAL(sPayloadRcv);
            ELSIF fbMessage.CompareTopic(sTopic:='motion1/acceleration') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Acceleration := STRING_TO_LREAL(sPayloadRcv);
            ELSIF fbMessage.CompareTopic(sTopic:='motion1/deceleration') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Deceleration := STRING_TO_LREAL(sPayloadRcv);
            ELSIF fbMessage.CompareTopic(sTopic:='motion1/execute') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Execute := STRING_TO_LREAL(sPayloadRcv);
            END_IF
        // same for motion2 and motion3
        END_IF
    END_IF
END_IF
 

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

Комментарии:

1. Пожалуйста, не редактируйте вопросы, чтобы включать решения (и НЕ добавляйте «Решаемые» в заголовок). Добавьте ответ, используя поле под вопросом, а затем отметьте его как принятый, поскольку это поможет другим найти ответ.

Ответ №1:

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

 FUNCTION SPLIT : ARRAY[0..255] OF STRING(250)
VAR_INPUT
    STR: STRING(250);
    CHAR: STRING(1);
END_VAR
VAR
    iPos: INT;
    sTest: STRING(250);
    iIndex: INT;
    xFinish: BOOL;
END_VAR

    sTest := STR;
    REPEAT
        iPos := FIND(sTest, CHAR);

        IF iPos = 0 THEN
            SPLIT[iIndex] := sTest;
            xFinish := TRUE;
        ELSE
            SPLIT[iIndex] := LEFT(sTest, iPos - 1);
            sTest := RIGHT(sTest, LEN(sTest) - iPos);
        END_IF;
        iIndex := iIndex   1;
    UNTIL (xFinish = TRUE)
    END_REPEAT;

END_FUNCTION
 

Теперь, как вы можете узнать, связана ли текущая тема с ‘motion1’

 VAR
    arsTopic: ARRAY[0..255] OF STRING(250);
END_VAR

IF fbMessageQueue.nQueuedMessages > 0 THEN
    IF fbMessageQueue.Dequeue(fbMessage:=fbMessage) THEN
        fbMessage.GetTopic(pTopic:=ADR(sTopicRcv), nTopicSize := SIZEOF(sTopicRcv));
        arsTopic := SPLIT(sTopicRcv, '/');
        IF (arsTopic[0] = 'motion1') THEN
            // do your staff
        END_IF
    END_IF
END_IF
 

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

 TYPE SR_MOTION:
    STRUCT
        lrPostion: LREAL; 
        lrVelocity: LREAL; 
        lrAcceleration: LREAL; 
        lrDeceleration: LREAL; 
        lrExecute: LREAL; 
    END_STRUCT
END_TYPE
 

SR означает Сергей Романов, вы можете использовать любой префикс

Теперь давайте создадим массив ваших движений. Допустим, у вас есть 3. Таким образом, мы разделяем номер движения в отдельную переменную. Обратите внимание, что для того, чтобы ваш код работал плавно, не создавая много ненужного кода, ваши темы должны быть не motion1/velocity «но motion/1/velosity » .

 VAR
    astMotions: ARRAY[1..3] OF SR_MOTION;
    arsTopic: ARRAY[0..255] OF STRING(250);
    i: INT;
END_VAR

IF fbMessageQueue.nQueuedMessages > 0 THEN
    IF fbMessageQueue.Dequeue(fbMessage:=fbMessage) THEN
        fbMessage.GetTopic(pTopic:=ADR(sTopicRcv), nTopicSize := SIZEOF(sTopicRcv));
        fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);

        arsTopic := SPLIT(sTopicRcv, '/');
        IF arsTopic[0] = 'machine' THEN
            IF arsTopic[1] = 'on' THEN
                Machine.bOnPB := STRING_TO_BOOL(sPayloadRcv);
            ELSIF arsTopic[1] = 'off' THEN
                Machine.bOffPB := STRING_TO_BOOL(sPayloadRcv);
            END_IF
        ELSIF arsTopic[0] = 'motion' THEN
            i := STRING_TO_INT(arsTopic[1]); // arsTopic[1] has motion number in `motion/1/velosity`
            IF arsTopic[2] = 'position' THEN
                astMotions[i].lrPosition := STRING_TO_LREAL(sPayloadRcv);
            ELSIF arsTopic[2] = 'velosity' THEN
                astMotions[i].lrVelosity := STRING_TO_LREAL(sPayloadRcv);
            ELSIF arsTopic[2] = 'acceleration' THEN
                astMotions[i].lrAcceleration := STRING_TO_LREAL(sPayloadRcv);
            ELSIF arsTopic[2] = 'Deceleration' THEN
                astMotions[i].lrDeceleration := STRING_TO_LREAL(sPayloadRcv);
            ELSIF arsTopic[2] = 'execute' THEN
                astMotions[i].lrExecute := STRING_TO_LREAL(sPayloadRcv);
            END_IF
        END_IF
    END_IF
END_IF
 

Вот весь ваш код для всех 3 движений.

Редактировать: добавить исключение числа из темы

Если вы не хотите менять темы на motion/1/velosity then, вы можете использовать эту функцию, которая извлекает последний символ и преобразует в int

 FUNCTION TOPIC_TO_INT: INT
VAR_INPUT
    str: STRING;
END_VAR
VAR
    ps: POINTER TO ARRAY[0..200] OF BYTE;
END_VAR
    ps := ADR(str);
    TOPIC_TO_INT := BYTE_TO_INT(ps^[LEN(str) - 1]) - 48;
END_FUNCTION
 

И тогда в коде вместо i := STRING_TO_INT(arsTopic[1]); you можно i := TOPIC_TO_INT(arsTopic[0]) и остальной код немного изменить.

Поэтому, если вы используете TOPIC_TO_INT(STRING#'message2') эту функцию, она вернет INT#2

Ответ №2:

Я решил свою проблему! Вот мой код:

 IF fbMessageQueue.nQueuedMessages > 0 THEN
    IF fbMessageQueue.Dequeue(fbMessage:=fbMessage) THEN
        fbMessage.GetTopic(pTopic:=ADR(sTopicRcv), nTopicSize := SIZEOF(sTopicRcv));
        IF fbMessage.CompareTopic(sTopic:='machine/on') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Machine.bOnPB := STRING_TO_BOOL(sPayloadRcv);
        ELSIF fbMessage.CompareTopic(sTopic:='machine/off') THEN
            fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
            Machine.bOffPB := STRING_TO_BOOL(sPayloadRcv);
        ELSIF INT_TO_BOOL(FIND(sTopicRcv,'motion1')) THEN
            IF fbMessage.CompareTopic(sTopic:='motion1/position') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Postion := STRING_TO_LREAL(sPayloadRcv);
            ELSIF fbMessage.CompareTopic(sTopic:='motion1/velocity') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Velocity := STRING_TO_LREAL(sPayloadRcv);
            ELSIF fbMessage.CompareTopic(sTopic:='motion1/acceleration') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Acceleration := STRING_TO_LREAL(sPayloadRcv);
            ELSIF fbMessage.CompareTopic(sTopic:='motion1/deceleration') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Deceleration := STRING_TO_LREAL(sPayloadRcv);
            ELSIF fbMessage.CompareTopic(sTopic:='motion1/execute') THEN
                fbMessage.GetPayload(pPayload:=ADR(sPayloadRcv), nPayloadSize:=SIZEOF(sPayloadRcv), bSetNullTermination:=TRUE);
                Motion.nMotion1Execute := STRING_TO_LREAL(sPayloadRcv);
            END_IF
        // same for motion2 and motion3
        END_IF
    END_IF
END_IF
 

Комментарии:

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