├── CoDeSys MQTT lib states.xlsx ├── README.md ├── mqtt.EXP └── mqtt.lib /CoDeSys MQTT lib states.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lugaresi/codesys2-mqtt-library/423c738789d1d5704c472f04592b37ca085455fd/CoDeSys MQTT lib states.xlsx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoDeSys2 MQTT Library 2 | This project initially started from backporting of http://codesys-mqtt-library.sourceforge.net/ version 3.5.0.0. 3 | This Client follows [MQTT Specifications v3.1.1](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html). 4 | The only dependencies are Standard, SysLibSockets and SysLibMem libraries. 5 | 6 | The following MQTT features are supported: 7 | - CONNECT (incl. randomized or defined ClientId, User/Password authentication (max-length: 80), Last Will QoS 0, auto. reconnection) / DISCONNECT 8 | - Keep Alive (PINGREQ / PINGRESP) 9 | - PUBLISH QoS 0 (incl. Retain, topic max-length: 80) 10 | - SUBSCRIBE QoS 0 (SUBACK) / UNSUBSCRIBE (UNSUBACK) 11 | -------------------------------------------------------------------------------- /mqtt.EXP: -------------------------------------------------------------------------------- 1 | 2 | 3 | (* @NESTEDCOMMENTS := 'Yes' *) 4 | (* @PATH := '\/MQTT_Client' *) 5 | (* @OBJECTFLAGS := '0, 8' *) 6 | (* @SYMFILEFLAGS := '2048' *) 7 | FUNCTION_BLOCK FB_CalculateRemainingLength 8 | VAR_INPUT 9 | wNumberOfBytes: WORD; 10 | END_VAR 11 | VAR_OUTPUT 12 | iRemainingLengthLength : INT; 13 | abRemainingLengthBytes : ARRAY[0..3] OF BYTE; 14 | END_VAR 15 | VAR 16 | bEncodedByte: BYTE; 17 | wValue: WORD; 18 | iCount: INT; 19 | END_VAR 20 | (* @END_DECLARATION := '0' *) 21 | (* 22 | Notes: 23 | - The Remaining Length is the number of bytes remaining within the current packet, including data in the variable header and the payload. The Remaining Length does not include the bytes used to encode the Remaining Length. 24 | - The Remaining Length is encoded using a variable length encoding scheme which uses a single byte for values up to 127. Larger values are handled as follows. 25 | The least significant seven bits of each byte encode the data, and the most significant bit is used to indicate that there are following bytes in the representation. 26 | Thus each byte encodes 128 values and a "continuation bit". The maximum number of bytes in the Remaining Length field is four. 27 | *) 28 | 29 | wValue := wNumberOfBytes; 30 | iCount := 0; 31 | REPEAT 32 | bEncodedByte := UINT_TO_BYTE(wValue MOD 128); 33 | wValue := wVALUE / 128; 34 | 35 | (* if there are more data to encode, set the top bit of this byte *) 36 | IF (wVALUE > 0) THEN 37 | bEncodedByte := (bEncodedByte OR 128); 38 | END_IF 39 | abRemainingLengthBytes[iCount] := bEncodedByte; 40 | iCount := iCount + 1; 41 | UNTIL (wValue <= 0) 42 | END_REPEAT; 43 | iRemainingLengthLength := iCount; 44 | END_FUNCTION_BLOCK 45 | 46 | 47 | (* @NESTEDCOMMENTS := 'Yes' *) 48 | (* @PATH := '\/MQTT_Client' *) 49 | (* @OBJECTFLAGS := '0, 8' *) 50 | (* @SYMFILEFLAGS := '2048' *) 51 | FUNCTION_BLOCK FB_DecodeRemainingLength 52 | VAR_INPUT 53 | pabRemainingLengthBytes: POINTER TO ARRAY[0..3] OF BYTE; 54 | END_VAR 55 | VAR_OUTPUT 56 | uiRemainingLength: UINT; 57 | iRemainingLengthLength: INT; 58 | xError : BOOL; 59 | END_VAR 60 | VAR 61 | multiplier: UINT; 62 | value: UINT; 63 | iCount: INT; 64 | END_VAR 65 | (* @END_DECLARATION := '0' *) 66 | (* 67 | Notes: 68 | - The Remaining Length is the number of bytes remaining within the current packet, including data in the variable header and the payload. The Remaining Length does not include the bytes used to encode the Remaining Length. 69 | - The Remaining Length is encoded using a variable length encoding scheme which uses a single byte for values up to 127. Larger values are handled as follows. 70 | The least significant seven bits of each byte encode the data, and the most significant bit is used to indicate that there are following bytes in the representation. 71 | Thus each byte encodes 128 values and a "continuation bit". The maximum number of bytes in the Remaining Length field is four. 72 | *) 73 | 74 | multiplier := 1; 75 | value := 0; 76 | iCount :=-1; 77 | xError := FALSE; 78 | REPEAT 79 | iCount := iCount + 1; 80 | IF iCount >= 4 THEN iCount := 0; xError := TRUE; EXIT; END_IF (* security *) 81 | 82 | value := value + (pabRemainingLengthBytes^[iCount] AND 127) * multiplier; 83 | multiplier := multiplier * 128; 84 | UNTIL ((pabRemainingLengthBytes^[iCount] AND 128) = 0) 85 | END_REPEAT; 86 | uiRemainingLength := value; 87 | iRemainingLengthLength := iCount + 1; 88 | END_FUNCTION_BLOCK 89 | 90 | 91 | (* @NESTEDCOMMENTS := 'Yes' *) 92 | (* @PATH := '\/MQTT_Client' *) 93 | (* @OBJECTFLAGS := '0, 8' *) 94 | (* @SYMFILEFLAGS := '2048' *) 95 | (* This Function Block implements an MQTT Client 96 | Version: 2.9 97 | Supported: 98 | CONNECT / DISCONNECT / PING (Keep Alive) 99 | SUBSCRIBE / UNSUBSCRIBE (single topic) 100 | PUBLISH (send / receive, QoS0: at most once) 101 | Retain Messages 102 | Optional Last Will and Testament 103 | Optional ClientId and Username, Password authentication 104 | 105 | The only dependencies are Standard, SysLibSockets and SysLibMem libraries. 106 | 107 | MQTT v3.1.1 Specification: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html 108 | *) 109 | FUNCTION_BLOCK FB_MQTTClient 110 | 111 | VAR CONSTANT 112 | BUFFER_SIZE : INT := 535; (* = 536 since it starts at 0. CONNECT packet might be the biggest packet at 528 bytes, so round up to next ^2. *) 113 | MQTT_TOPIC_MAX_LENGTH : UINT := 80; 114 | MQTT_MESSAGE_MAX_LENGTH : UINT := 255; 115 | MQTT_USERNAME_MAX_LENGTH : UINT := 80; 116 | MQTT_PASSWORD_MAX_LENGTH : UINT := 80; 117 | MQTT_KEEPALIVE_MAX_VALUE : TIME := t#65535s; 118 | END_VAR 119 | 120 | VAR_INPUT 121 | i_xEnable : BOOL := TRUE; (* Enables the Function block, The rising Edge of this Input automatically connects to the MQTT Broker - Standard: TRUE *) 122 | i_sBrokerAddress : STRING := 'www.mqtt-dashboard.com'; (* IP-Address (example: 192.168.178.101) or Webaddress (www.mqtt-dashboard.com) of the MQTT Broker *) 123 | i_uiPort : UINT := 1883; (* Port of the MQTT Broker - Standard: 1883 *) 124 | i_sUsername : STRING(MQTT_USERNAME_MAX_LENGTH) := ''; (* Optional Username if required *) 125 | i_sPassword : STRING(MQTT_PASSWORD_MAX_LENGTH) := ''; (* Optional Password if required *) 126 | i_sClientIdentifier : STRING(23) := ''; (* Optional Client Identifier. A ramdom generated name will be used if empty. *) 127 | i_xCleanSession : BOOL; (* Clean Session *) 128 | i_sWillTopic : STRING(MQTT_TOPIC_MAX_LENGTH) := ''; (* Last Will Topic if required (Automatically enables Will Message IF changed) - otherwise leave untouched *) 129 | i_sWillMessage : STRING(MQTT_MESSAGE_MAX_LENGTH) := ''; (* Last Will Message if required - otherwise leave untouched *) 130 | i_xWillRetain : BOOL := FALSE; (* Retain Last Will Message, if required Standard: FALSE - otherwise leave untouched *) 131 | i_xAutoReconnect : BOOL := TRUE; (* Automatically try to reconnect after an Exception - Standard: TRUE. A Broker is permitted to disconnect a Client that it determines to be inactive or non-responsive at any time, regardless of the Keep Alive value provided by that Client. *) 132 | i_sPublishMessage : STRING(MQTT_MESSAGE_MAX_LENGTH) := 'Hello MQTT Broker from CoDeSys'; (* PayLoad to Publish *) 133 | i_sPublishTopic : STRING(MQTT_TOPIC_MAX_LENGTH) := 'CoDeSys'; (* Topic to Publish to *) 134 | i_sSubscribeTopic : STRING(MQTT_TOPIC_MAX_LENGTH) := 'CoDeSys'; (* Topic to Subscribe to *) 135 | i_sUnsubscribeTopic : STRING(MQTT_TOPIC_MAX_LENGTH) := 'CoDeSys'; (* Topic to Unsubscribe from *) 136 | i_xRetain : BOOL := TRUE; (* Retain Flag Forces the Broker to Retain the Last Mesage - Standard: TRUE *) 137 | i_xPublish : BOOL := FALSE; (* Publish the Payload to the Topic of the MQTT Broker *) 138 | i_xSubscribe : BOOL := FALSE; (* Subscribe to the Topic given to the Input i_sTopicSubscribe *) 139 | i_xUnsubscribe : BOOL := FALSE; (* Unsubscribe to the Topic given to the Input i_sTopicSubscribe *) 140 | i_tKeepAliveTimeInterval : TIME := t#1m; (* Keep Alive time interval (max = 18 hours 12 minutes and 15 seconds), default = 1 minute. *) 141 | i_tCommTimeout : TIME := t#1s; (* General communication timeout *) 142 | END_VAR 143 | 144 | VAR_OUTPUT 145 | q_sLastReceivedTopic : STRING(MQTT_TOPIC_MAX_LENGTH); (* Topic of Message Received *) 146 | q_sLastReceivedMessage : STRING(MQTT_MESSAGE_MAX_LENGTH); (* Message Received from a subsribed Topic *) 147 | q_xReceivedMessageNotification : BOOL; (* Message Received Notification *) 148 | q_sLog : STRING(89); (* Log Message. 89 should be enough for the currently generated messages. Increase if needed in future version *) 149 | q_wState : WORD; (* Current State of the Function Block: bit10:1=error; bit9:0=receive,1=send; MSB=MQTT Control Packet Type; LSB=step or error number*) 150 | q_xError : BOOL; (* Error Flag *) 151 | q_xConnected : BOOL; (* Current connection state *) 152 | q_xIDLE : BOOL; (* TRUE when idle. Means waiting for incoming packet or any send command *) 153 | q_sLastError : STRING(89); (* Contains the last error *) 154 | END_VAR 155 | 156 | VAR 157 | R_TRIG_Enable : R_TRIG; 158 | 159 | diSocket : DINT ; 160 | IoCtlParameter : DINT := 1; (* Set Push-Bit - HACK for Wago 841 - 881 according to OSCAT *) 161 | sockAddr : SOCKADDRESS; 162 | xResult : BOOL; 163 | diRecvResult : DINT; 164 | diSendResult : DINT; 165 | diInBufferOffset : DINT; 166 | abInBuffer : ARRAY[0..BUFFER_SIZE] OF BYTE; (* PUBLISH is the biggest packet. Its size should not exceed 9+MESSAGE_MAX_LENGHT+TOPIC_MAX_LENGHT, but as this is not possible in CodeSys, let start with 512 sized Input Buffer *) 167 | abOutBuffer : ARRAY[0..BUFFER_SIZE] OF BYTE; 168 | 169 | fbRandom : ARRAY[1..8] OF FB_Random; 170 | dwTime : DWORD; 171 | 172 | fbKeepAliveTimer : TON; 173 | xResetKeepAliveTimer : BOOL; 174 | 175 | fbCOMtimeout : TON; 176 | tCOMtimeoutValue : TIME; 177 | udiLastState : UDINT; 178 | 179 | fbTimeoutPINGREQ : TON; xAwaitingPINGRESP : BOOL; 180 | fbTimeoutCONNECT : TON; xAwaitingCONNACK : BOOL; 181 | fbTimeoutSUBSCRIBE : TON; xAwaitingSUBACK : BOOL; 182 | fbTimeoutUNSUBSCRIBE : TON; xAwaitingUNSUBACK : BOOL; 183 | 184 | fbCalculateRemainingLength : FB_CalculateRemainingLength; 185 | fbDecodeRemainingLength : FB_DecodeRemainingLength; 186 | 187 | iSendIndexCount : INT; 188 | 189 | bRecvCtrl : BYTE; 190 | xRecvDup : BOOL; 191 | bRecvQoS : BYTE; 192 | xRecvRetain : BOOL; 193 | wRecvPacketIdentifier : WORD; 194 | wSendPacketIdentifier : WORD := 0; 195 | uiRecvTopicLength : UINT; 196 | uiRecvPayloadLength : UINT; 197 | sPublishMessage : STRING(MQTT_MESSAGE_MAX_LENGTH); 198 | sPublishTopic : STRING(MQTT_TOPIC_MAX_LENGTH); 199 | sSubscribeTopic : STRING(MQTT_TOPIC_MAX_LENGTH); 200 | sUnsubscribeTopic : STRING(MQTT_TOPIC_MAX_LENGTH); 201 | 202 | R_TRIG_Publish : R_TRIG; 203 | R_TRIG_Subscribe : R_TRIG; 204 | R_TRIG_Unsubscribe : R_TRIG; 205 | END_VAR 206 | 207 | (* @END_DECLARATION := '0' *) 208 | (* Handle Connection / Disconnection *) 209 | R_TRIG_Enable(CLK := i_xEnable); (* Disconnect by setting Enable = FALSE *) 210 | CASE q_wState OF 211 | 0, (* Waiting for Enable = TRUE *) 212 | 2#1100010010, (* TCP Socket Creation Timeout, retry if AutoReconnect or wait for Enable *) 213 | 2#0111100101, (* TCP Socket closed, wait for Enable *) 214 | 2#1111100110: (* Closing TCP Socket Timeout, wait for Enable *) 215 | tCOMtimeoutValue := t#0ms; (* Reset COM Timeout *) 216 | q_sLog := 'Waiting for Enable = TRUE'; 217 | 218 | IF (R_TRIG_Enable.Q OR (i_xEnable AND i_xAutoReconnect AND NOT q_xConnected)) THEN 219 | q_xError := FALSE; q_sLog := ' '; q_sLastError := ''; q_sLastReceivedTopic := ''; q_sLastReceivedMessage := ''; 220 | AC_CreateSocket(); 221 | END_IF 222 | 223 | (* Create Socket *) 224 | 2#1100010001: (* Creating TCP Socket failed, retry until timeout *) 225 | AC_CreateSocket(); 226 | 227 | (* Connect to TCP Server *) 228 | 2#0100010001, (* Connect to TCP Server when creating Socket is successful *) 229 | 2#1100010101: (* Connecting to TCP Server failed, retry until succeed or timeout *) 230 | AC_ConnectToTCPserver(); 231 | 232 | (* Send MQTT CONNECT *) 233 | 2#0100010101, (* Send MQTT CONNECT when TCP Socket is connected *) 234 | 2#1100011001: (* Send MQTT CONNECT failed, retry until succeed or timeout *) 235 | AC_SendConnect(); 236 | 237 | (* Send DISCONNECT *) 238 | 2#1110000010, (* Send MQTT DISCONNECT on SUBSCRIBE Timeout, SUBACK not received *) 239 | 2#1110100010, (* Send MQTT DISCONNECT on UNSUBSCRIBE Timeout, UNSUBACK not received *) 240 | 2#1111100001: (* Send MQTT DISCONNECT failed, retry until succeed or timeout *) 241 | AC_SendDISCONNECT(); 242 | 243 | (* Close Socket *) 244 | 2#1100010111, (* Close Socket on 'CONNECT Timeout, CONNACK not received *) 245 | 2#1000100001, (* Close Socket on CONNACK Connection Refused *) 246 | 2#1100010110, (* Close Socket on TCP Server Connection Timeout *) 247 | 2#1000110001, (* Close Socket if received PUBLISH from Broker with QoS 0b11 (protocol violation) *) 248 | 2#1010010001, (* Close Socket if received invalid SUBACK Packet Identifier or RC 0x80 *) 249 | 2#1010110001, (* Close Socket if received invalid UNSUBACK Packet Identifier *) 250 | 2#1011010001, (* Close Socket if received invalid PINGRESP (protocol violation) *) 251 | 2#1111000010, (* Close Socket on PINGREQ Timeout *) 252 | 2#0111100001, (* Close Socket after sending DISCONNECT successfully *) 253 | 2#1111100010, (* Close Socket on DISCONNECT Timeout *) 254 | 2#1111100101: (* Close Socket failed, retry until Timeout *) 255 | AC_CloseSocket(); 256 | 257 | (* IDLE or BUSY *) 258 | ELSE 259 | IF (NOT i_xEnable) THEN (* Disconnect by setting Enable = FALSE *) 260 | AC_SendDISCONNECT(); 261 | END_IF 262 | END_CASE 263 | 264 | IF q_xConnected THEN 265 | (* MQTT Keep Alive *) 266 | AC_KeepAlive(); 267 | 268 | (* Handle Client Requests (copy input topic/message in local var in case more than one cycle is needed) *) 269 | R_TRIG_Publish(CLK := i_xPublish AND q_xIDLE); IF R_TRIG_Publish.Q THEN sPublishTopic := i_sPublishTopic; sPublishMessage := i_sPublishMessage; AC_SendPUBLISH(); END_IF 270 | R_TRIG_Subscribe(CLK := i_xSubscribe AND q_xIDLE); IF R_TRIG_Subscribe.Q THEN sSubscribeTopic := i_sSubscribeTopic; AC_SendSUBSCRIBE(); END_IF 271 | R_TRIG_Unsubscribe(CLK := i_xUnsubscribe AND q_xIDLE); IF R_TRIG_Unsubscribe.Q THEN sUnsubscribeTopic := i_sUnsubscribeTopic; AC_SendUNSUBSCRIBE(); END_IF 272 | 273 | (* Handle any incoming MQTT packet *) 274 | IF diRecvResult <= 0 THEN 275 | diRecvResult := SysSockRecv(diSocket := diSocket, pbyBuffer := ADR(abInBuffer), diBufferSize := BUFFER_SIZE+1, diFlags := 0); 276 | q_xReceivedMessageNotification := FALSE; 277 | diInBufferOffset := 0; 278 | END_IF 279 | IF diRecvResult > 0 THEN (* diRecvResult is > 0 as long as not all playloads have been read *) 280 | bRecvCtrl := SHR(abInBuffer[diInBufferOffset+0], 4); (* Check Control Packet Type *) 281 | CASE bRecvCtrl OF 282 | 00: AC_HandleReserved(); (* Reserved *) 283 | 01: AC_HandleCONNECT(); (* CONNECT *) 284 | 02: AC_HandleCONNACK(); (* CONNACK *) 285 | 03: AC_HandlePUBLISH(); (* PUBLISH *) 286 | 04: AC_HandlePUBACK(); (* PUBACK *) 287 | 05: AC_HandlePUBREC(); (* PUBREC *) 288 | 06: AC_HandlePUBREL(); (* PUBREL *) 289 | 07: AC_HandlePUBCOMP(); (* PUBCOMP *) 290 | 08: AC_HandleSUBSCRIBE(); (* SUBSCRIBE *) 291 | 09: AC_HandleSUBACK(); (* SUBACK *) 292 | 10: AC_HandleUNSUBSCRIBE(); (* UNSUBSCRIBE *) 293 | 11: AC_HandleUNSUBACK(); (* UNSUBACK *) 294 | 12: AC_HandlePINGREQ(); (* PINGREQ *) 295 | 13: AC_HandlePINGRESP(); (* PINGRESP *) 296 | 14: AC_HandleDISCONNECT(); (* DISCONNECT *) 297 | 15: AC_HandleRESERVED(); (* Reserved *) 298 | END_CASE 299 | END_IF 300 | 301 | (* Handle MQTT Timeouts *) 302 | AC_TimeoutCONNECT(); 303 | AC_TimeoutPINGREQ(); 304 | AC_TimeoutSUBSCRIBE(); 305 | AC_TimeoutUNSUBSCRIBE(); 306 | 307 | (* Purge MQTT Control Packet Type from In Buffer on Error *) 308 | IF q_xError THEN abInBuffer[diInBufferOffset+0] := 0; END_IF 309 | END_IF 310 | 311 | (* Handle COM Timeouts *) 312 | fbCOMtimeout(IN := (udiLastState = q_wState) AND (tCOMtimeoutValue > t#0ms), PT := tCOMtimeoutValue); 313 | udiLastState := q_wState; 314 | 315 | (* IDLE state *) 316 | q_xIDLE := diSendResult <= 0 AND diRecvResult <= 0; 317 | 318 | (* Last Error *) 319 | IF q_xError THEN q_sLastError := q_sLog; END_IF 320 | END_FUNCTION_BLOCK 321 | ACTION AC_CloseSocket: 322 | (* Close Socket (MQTT Broker Socket) 323 | --------------------------------------------------- 324 | *) 325 | 326 | q_sLog := 'Closing TCP Socket'; 327 | q_wState := 2#0111100100; 328 | 329 | (* Close Socket *) 330 | xResult := SysSockClose(diSocket); 331 | tCOMtimeoutValue := i_tCommTimeout; (* Start Timeout *) 332 | 333 | (* Success *) 334 | IF xResult THEN 335 | q_xConnected := FALSE; 336 | q_sLog := 'TCP Socket closed'; 337 | q_xError := FALSE; 338 | q_wState := 2#0111100101; (* Next step: Wait for Enable *) 339 | ELSE 340 | q_sLog := 'Closing TCP Socket failed'; 341 | q_wState := 2#1111100101; (* Next step: Retry until Timeout *) 342 | q_xError := TRUE; 343 | END_IF 344 | 345 | (* Reset MQTT Request Timers (again here in case we close the socket directly without sending MQTT DISCONNECT first) *) 346 | xAwaitingCONNACK := FALSE; 347 | xAwaitingPINGRESP := FALSE; 348 | xAwaitingSUBACK := FALSE; 349 | xAwaitingUNSUBACK := FALSE; 350 | 351 | (* Timeout *) 352 | IF fbCOMtimeout.Q THEN 353 | q_xConnected := FALSE; 354 | q_sLog := 'Closing TCP Socket Timeout'; 355 | q_xError := TRUE; 356 | q_wState := 2#1111100110; (* Next step: Wait for Enable *) 357 | END_IF 358 | END_ACTION 359 | 360 | ACTION AC_ConnectToTCPserver: 361 | (* Connect to TCP Server (MQTT Broker Socket) 362 | ---------------------------------------------------------------- 363 | *) 364 | 365 | q_sLog := 'Connecting to TCP Server'; 366 | q_wState := 2#0100010100; 367 | 368 | IF (sockAddr.sin_addr = 0) THEN 369 | sockAddr.sin_family := SOCKET_AF_INET; 370 | IF (RIGHT(i_sBrokerAddress,1) = '0' OR RIGHT(i_sBrokerAddress,1) = '1' OR RIGHT(i_sBrokerAddress,1) = '2' OR RIGHT(i_sBrokerAddress,1) = '3' OR RIGHT(i_sBrokerAddress,1) = '4' 371 | OR RIGHT(i_sBrokerAddress,1) = '5' OR RIGHT(i_sBrokerAddress,1) = '6' OR RIGHT(i_sBrokerAddress,1) = '7' OR RIGHT(i_sBrokerAddress,1) = '8' OR RIGHT(i_sBrokerAddress,1) = '9') THEN 372 | sockAddr.sin_addr := SysSockInetAddr(i_sBrokerAddress); 373 | ELSE 374 | (* SysSockInetNtoa(SysSockGetHostByName(ADR(i_sBrokerAddress))., i_sBrokerAddress, 80); 375 | sockAddr.sin_addr := SysSockInetAddr(i_sBrokerAddress); *) 376 | sockAddr.sin_addr := SysSockNtohl(SysSockGetHostByName(ADR(i_sBrokerAddress))); 377 | END_IF 378 | END_IF 379 | 380 | sockAddr.sin_port := SysSockHtons(i_uiPort); 381 | 382 | (* Connect to Socket *) 383 | xResult := SysSockConnect(diSocket, ADR(sockAddr), SIZEOF(sockAddr)); 384 | tCOMtimeoutValue := i_tCommTimeout; (* Start Timeout *) 385 | 386 | (* Success *) 387 | IF xResult THEN 388 | iSendIndexCount := 0; (* Temp. use iSendIndexCount as retry counter *) 389 | q_sLog := 'TCP Socket connected'; 390 | q_xError := FALSE; 391 | q_wState := 2#0100010101; (* Next step: Send CONNECT *) 392 | 393 | ELSE 394 | q_sLog := 'Connecting to TCP Server failed'; 395 | iSendIndexCount := iSendIndexCount + 1; 396 | IF iSendIndexCount > 3 THEN (* Trigger error only after 3 failures (connection often fails the first time...) *) 397 | q_xError := TRUE; 398 | END_IF 399 | q_wState := 2#1100010101; (* Next step: Retry until Succeed or Timeout *) 400 | END_IF 401 | 402 | (* Timeout *) 403 | IF fbCOMtimeout.Q THEN 404 | q_xError := TRUE; 405 | q_sLog := 'TCP Server Connection Timeout'; 406 | tCOMtimeoutValue := t#0ms; (* Reset COM Timeout *) 407 | q_wState := 2#1100010110; (* Next step: Close Socket *) 408 | END_IF 409 | END_ACTION 410 | 411 | ACTION AC_CreateSocket: 412 | (* Create Socket to (MQTT Broker Socket) 413 | -------------------------------------------------------- 414 | *) 415 | 416 | q_sLog := 'Creating TCP Socket'; 417 | q_wState := 2#0100010000; 418 | 419 | (* Create Socket *) 420 | diSocket := SysSockCreate(SOCKET_AF_INET, SOCKET_STREAM, SOCKET_IPPROTO_TCP); 421 | tCOMtimeoutValue := i_tCommTimeout; (* Start Timeout *) 422 | 423 | SysSockSetOption(diSocket, SOCKET_IPPROTO_TCP, SOCKET_TCP_NODELAY, ADR(IoCtlParameter), SIZEOF(IoCtlParameter)); (* Set Push-Bit - HACK for Wago 841 - 881 according to OSCAT *) 424 | SysSockIoctl(diSocket, SOCKET_FIONBIO, ADR(IoCtlParameter)); (* Send socket to non blocking *) 425 | 426 | (* Create Socket successful *) 427 | IF diSocket > 0 THEN 428 | sockAddr.sin_addr := 0; 429 | iSendIndexCount := 0; (* Temp. use iSendIndexCount as 'TCP Socket creation retry counter *) 430 | q_sLog := 'TCP Socket created'; 431 | q_xError := FALSE; 432 | q_wState := 2#0100010001; (* Next step: Connect to TCP Server *) 433 | 434 | ELSE 435 | q_sLog := 'Creating TCP Socket failed'; 436 | q_xError := TRUE; 437 | q_wState := 2#1100010001; (* Next step: Retry until Succeed or Timeout *) 438 | END_IF 439 | 440 | (* Timeout *) 441 | IF fbCOMtimeout.Q THEN 442 | q_sLog := 'TCP Socket Creation Timeout'; 443 | q_xError := TRUE; 444 | q_wState := 2#1100010010; (* Next step: Wait for Enable or Auto Reconnect *) 445 | END_IF 446 | END_ACTION 447 | 448 | ACTION AC_HandleCONNACK: 449 | (* Handle CONNACK Message from Broker 450 | ----------------------------------------------------------- 451 | 452 | Fixed Header | Variable Header | 453 | 1 + 1 bytes | 2 bytes | 454 | Control Packet TYPE | Flags | Remaining Length | Flags | return code | 455 | 1 byte | 1 bytes | 1 byte | 1 byte | 456 | | Reserved | | Res | SP | | 457 | 4 bits | 4 bits | | 7 bits | 1 bit | | 458 | value: 2 |bin 0 0 0 0 | 2 | 0 | 0/1 | 1-5 | 459 | 460 | Notes: 461 | - Remaining Length field: length of the variable header. For the CONNACK Packet this has the value 2. 462 | - Flags: Bits 7-1 are reserved AND MUST be set TO 0. Bit 0 (SP) is the Session Present Flag. 463 | *) 464 | 465 | q_sLog := 'Received CONNACK from Broker'; 466 | q_wState := 2#0000100000; 467 | 468 | (* Remaining length: Number of bytes left within current packet, including variable header plus payload. *) 469 | fbDecodeRemainingLength(pabRemainingLengthBytes := ADR(abInBuffer[diInBufferOffset + 1])); 470 | 471 | (* Check CONNACK return code in Variable Header 2nd Byte*) 472 | IF abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 1] = 0 THEN (* Success *) 473 | xAwaitingCONNACK := FALSE; 474 | q_xError := FALSE; 475 | q_wState := 2#0000100001; (* Next step: IDLE *) 476 | 477 | ELSE (* Error *) 478 | q_sLog := 'Connection Refused'; 479 | CASE abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 1] OF 480 | (* 0: Connection Accepted *) 481 | 1: CONCAT(q_sLog, ', Unacceptable Protocol Version'); (* 1: Connection Refused, unacceptable protocol version *) 482 | 2: CONCAT(q_sLog, ', Identifier Rejected'); (* 2: Connection Refused, identifier rejected *) 483 | 3: CONCAT(q_sLog, ', Server Unavailable'); (* 3: Connection Refused, Server unavailable *) 484 | 4: CONCAT(q_sLog, ', Bad Username or Password'); (* 4: Connection Refused, bad Username or passworrd *) 485 | 5: CONCAT(q_sLog, ', Not Autorized'); (* 5: Connection Refused, not autorized *) 486 | (* 6-255: Reserved for future use *) 487 | END_CASE 488 | q_xError := TRUE; 489 | q_wState := 2#1000100001; (* Next step: Close Socket *) 490 | END_IF 491 | diSendResult := 0; 492 | 493 | diInBufferOffset := diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + fbDecodeRemainingLength.uiRemainingLength; 494 | IF diRecvResult - diInBufferOffset <= 0 THEN 495 | diRecvResult := 0; 496 | END_IF 497 | END_ACTION 498 | 499 | ACTION AC_HandleCONNECT: 500 | (* Handle CONNECT Message from Broker 501 | ----------------------------------------------------------- 502 | *) 503 | 504 | (* CONNECT unsupported since it is an MQTT Client only (not a Broker) *) 505 | q_sLog := 'Received CONNECT from Broker: unsupported since I`m only an MQTT Client'; 506 | q_xError := TRUE; 507 | q_wState := 2#1000011000; (* Next Step: IDLE *) 508 | diRecvResult := 0; 509 | END_ACTION 510 | 511 | ACTION AC_HandleDISCONNECT: 512 | (* Handle DISCONNECT Message from Broker 513 | ---------------------------------------------------------------- 514 | *) 515 | 516 | (* DISCONNECT unsupported since it is an MQTT Client only (not a Broker) *) 517 | q_sLog := 'Received DISCONNECT from Broker: unsupported since I`m only an MQTT Client'; 518 | q_xError := TRUE; 519 | q_wState := 2#1011100000; (* Next Step: IDLE *) 520 | diRecvResult := 0; 521 | END_ACTION 522 | 523 | ACTION AC_HandlePINGREQ: 524 | (* Handle PINGREQ Message from Broker 525 | ---------------------------------------------------------- 526 | *) 527 | 528 | (* PINGREQ unsupported since it is an MQTT Client only (not a Broker) *) 529 | q_sLog := 'Received PINGREQ from Broker: unsupported since I`m only an MQTT Client'; 530 | q_xError := TRUE; 531 | q_wState := 2#1011000010; (* Next Step: IDLE *) 532 | diRecvResult := 0; 533 | END_ACTION 534 | 535 | ACTION AC_HandlePINGRESP: 536 | (* Handle PINGRESP Message from Broker 537 | ------------------------------------------------------------ 538 | 539 | Fixed Header | 540 | 2 bytes | 541 | Control Packet Type | Flags | Remaining Length | 542 | 1 byte | 1 byte | 543 | | Reserved | | 544 | 4 bits | 4 bits | | 545 | value: 13 |bin 0 0 0 0 | 0 | 546 | 547 | Notes: 548 | - A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet. It indicates that the Server is alive. 549 | - The Server MUST send a PINGRESP Packet in response to a PINGREQ Packet [MQTT-3.12.4-1]. 550 | - This Packet is used in Keep Alive processing. 551 | - Keep Alive: It is the maximum time interval that is permitted to elapse between the point at which the Client finishes transmitting one Control Packet and the point it starts sending the next. 552 | It is the responsibility of the Client to ensure that the interval between Control Packets being sent does not exceed the Keep Alive value. In the absence of sending any other Control Packets, the Client MUST send a PINGREQ Packet [MQTT-3.1.2-23]. 553 | - If a Client does not receive a PINGRESP Packet within a reasonable amount of time after it has sent a PINGREQ, it SHOULD close the Network Connection to the Server. 554 | *) 555 | 556 | q_sLog := 'Received PINGRESP from Broker'; 557 | q_wState := 2#0011010000; 558 | 559 | (* PINGRESP OK *) 560 | IF abInBuffer[diInBufferOffset + 1] = 0 THEN 561 | xAwaitingPINGRESP := FALSE; 562 | xResetKeepAliveTimer := TRUE; 563 | q_xError := FALSE; 564 | q_wState := 2#0011010001; (* Next Step: IDLE *) 565 | 566 | ELSE 567 | q_sLog := 'Received invalid PINGRESP from Broker'; 568 | q_xError := TRUE; 569 | q_wState := 2#1011010001; (* Next Step: Close Soket since this is a protocol violation *) 570 | END_IF 571 | diSendResult := 0; 572 | 573 | diInBufferOffset := diInBufferOffset + 1 + 1; 574 | IF diRecvResult - diInBufferOffset <= 0 THEN 575 | diRecvResult := 0; 576 | END_IF 577 | END_ACTION 578 | 579 | ACTION AC_HandlePUBACK: 580 | (* Handle PUBACK Message from Broker 581 | -------------------------------------------------------- 582 | *) 583 | 584 | (* PUBACK unsupported since only QoS 0 is supported *) 585 | q_sLog := 'Received PUBACK from Broker: rejected since QoS 0 only is supported yet'; 586 | q_xError := TRUE; 587 | q_wState := 2#1001000001; (* Next Step: IDLE *) 588 | diRecvResult := 0; 589 | END_ACTION 590 | 591 | ACTION AC_HandlePUBCOMP: 592 | (* Handle PUBCOMP Message from Broker 593 | ----------------------------------------------------------- 594 | *) 595 | 596 | (* PUBCOMP unsupported since only QoS 0 is supported *) 597 | q_sLog := 'Received PUBCOMP from Broker: rejected since QoS 0 only is supported yet'; 598 | q_xError := TRUE; 599 | q_wState := 2#1001110001; (* Next Step: IDLE *) 600 | diRecvResult := 0; 601 | END_ACTION 602 | 603 | ACTION AC_HandlePUBLISH: 604 | (* Handle PUBLISH Message from Broker 605 | --------------------------------------------------------- 606 | 607 | Fixed Header | Variable Header | Payload | 608 | 1 + 1-4 bytes | variable size | variable size | 609 | Control Packet Type | Flags | Remaining Length | Topic Length | Topic Name | Packet Identifier | | 610 | 1 byte | 1-4 bytes | 2 bytes | | 0 | 2 bytes (if QoS > 0) | | 611 | | DUP | QoS |Retain| | MSB | LSB | | MSB | LSB | | 612 | 4 bits | 1 bit | 2 bits | 1 bit | | 1 byte | 1 byte | | 1 byte | 1 byte | | 613 | value: 3 | 0/1 | 0,1,2 | 0/1 |length VarHead+Payload | 1-65536 | UTF8 | 0-65536 | UTF8 | 614 | 615 | Notes: 616 | - The DUP flag MUST be set to 1 by the Client or Server when it attempts to re-deliver a PUBLISH Packet [MQTT-3.3.1.-1]. The DUP flag MUST be set to 0 for all QoS 0 messages [MQTT-3.3.1-2]. 617 | - The value of the DUP flag from an incoming PUBLISH packet is not propagated when the PUBLISH Packet is sent to subscribers by the Server. 618 | The DUP flag in the outgoing PUBLISH packet is set independently to the incoming PUBLISH packet, its value MUST be determined solely by whether the outgoing PUBLISH packet is a retransmission [MQTT-3.3.1-3]. 619 | - The receiver of a PUBLISH Packet MUST respond according to QoS. 620 | - QoS 0 => None, QoS 1 => PUBACK Packet, QoS 2 => PUBREC Packet. 621 | - When sending a PUBLISH Packet to a Client the Server MUST set the RETAIN flag to 1 if a message is sent as a result of a new subscription being made by a Client [MQTT-3.3.1-8]. 622 | It MUST set the RETAIN flag to 0 when a PUBLISH Packet is sent to a Client because it matches an established subscription regardless of how the flag was set in the message it received [MQTT-3.3.1-9]. 623 | - The Payload contains the Application Message that is being published. It is valid for a PUBLISH Packet to contain a zero length payload. 624 | - A PUBLISH Packet MUST NOT have both QoS bits set to 1. If a Server or Client receives a PUBLISH Packet which has both QoS bits set to 1 it MUST close the Network Connection [MQTT-3.3.1-4]. 625 | *) 626 | 627 | (* for future use *) 628 | xRecvDup := (SHR(abInBuffer[diInBufferOffset + 0], 3) AND 2#1) > 0; (* Received SUP: The DUP flag MUST be set to 0 for all QoS 0 messages *) 629 | bRecvQoS := SHR(abInBuffer[diInBufferOffset + 0], 1) AND 2#11; (* Received QoS: should be 0 *) 630 | xRecvRetain := (abInBuffer[diInBufferOffset + 0] AND 2#1) > 0; (* Received Reatain flag *) 631 | 632 | q_sLog := CONCAT('Received PUBLISH from Broker (d', BYTE_TO_STRING(BOOL_TO_BYTE(xRecvDup))); 633 | q_sLog := CONCAT(CONCAT(q_sLog, ', q'), BYTE_TO_STRING(bRecvQoS)); 634 | q_sLog := CONCAT(CONCAT(CONCAT(q_sLog, ', r'), BYTE_TO_STRING(BOOL_TO_BYTE(xRecvRetain))), ')'); 635 | q_wState := 2#0000110000; 636 | 637 | IF bRecvQoS = 0 (* AND xDup = FALSE *) THEN (* Ensure QoS = 0 *) 638 | (* Remaining length: Number of bytes left within current packet, including variable header plus payload. *) 639 | fbDecodeRemainingLength(pabRemainingLengthBytes := ADR(abInBuffer[diInBufferOffset + 1])); 640 | 641 | uiRecvTopicLength := (* Fixed Header Length = 1 + fbDecodeRemainingLength.iRemainingLengthLength;*) 642 | SHL( BYTE_TO_UINT(abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength]), 8) (* Topic Length MSB *) 643 | + BYTE_TO_UINT(abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 1]); (* Topic Length LSB *) 644 | 645 | (* The length of the payload can be calculated by subtracting the length of the variable header from the Remaining Length field that is in the Fixed Header. *) 646 | uiRecvPayloadLength := fbDecodeRemainingLength.uiRemainingLength - (2 + uiRecvTopicLength + 0); (* Variable Header Length = 2 + Topic Length + 0-2 (depending on QoS)*) 647 | 648 | (* Topic *) 649 | SysMemSet(ADR(q_sLastReceivedTopic), 0, MQTT_TOPIC_MAX_LENGTH); 650 | SysMemMove(ADR(q_sLastReceivedTopic), ADR(abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 2]), MIN(uiRecvTopicLength, MQTT_TOPIC_MAX_LENGTH)); (* Security: MQTT_TOPIC_MAX_LENGTH *) 651 | 652 | (* Payload *) 653 | SysMemSet(ADR(q_sLastReceivedMessage), 0, MQTT_MESSAGE_MAX_LENGTH); 654 | SysMemMove(ADR(q_sLastReceivedMessage), ADR(abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 2 + uiRecvTopicLength]), MIN(uiRecvPayloadLength, MQTT_MESSAGE_MAX_LENGTH)); (* Security: MQTT_MESSAGE_MAX_LENGTH *) 655 | 656 | q_xReceivedMessageNotification := TRUE; 657 | q_xError := FALSE; 658 | q_wState := 2#0000110001; (* Next Step: IDLE *) 659 | 660 | (* A PUBLISH Packet MUST NOT have both QoS bits set to 1. If a Server or Client receives a PUBLISH Packet which has both QoS bits set to 1 it MUST close the Network Connection [MQTT-3.3.1-4]. *) 661 | ELSIF bRecvQoS = 2#11 THEN 662 | q_sLog := CONCAT(q_sLog, ': QoS protocol violation'); 663 | q_xError := TRUE; 664 | q_wState := 2#1000110001; (* Next Step: Close Socket *) 665 | 666 | ELSE 667 | q_sLog := CONCAT(q_sLog, ': rejected since QoS 0 only is supported yet'); 668 | q_xError := TRUE; 669 | q_wState := 2#1000110000; (* Next Step: IDLE *) 670 | END_IF 671 | 672 | diInBufferOffset := diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + fbDecodeRemainingLength.uiRemainingLength; 673 | IF diRecvResult - diInBufferOffset <= 0 THEN 674 | diRecvResult := 0; 675 | END_IF 676 | END_ACTION 677 | 678 | ACTION AC_HandlePUBREC: 679 | (* Handle PUBREC Message from Broker 680 | --------------------------------------------------------- 681 | *) 682 | 683 | (* PUBREC unsupported since only QoS 0 is supported *) 684 | q_sLog := 'Received PUBREC from Broker: rejected since QoS 0 only is supported yet'; 685 | q_xError := TRUE; 686 | q_wState := 2#1001010001; (* Next Step: IDLE *) 687 | diRecvResult := 0; 688 | END_ACTION 689 | 690 | ACTION AC_HandlePUBREL: 691 | (* Handle PUBREL Message from Broker 692 | -------------------------------------------------------- 693 | *) 694 | 695 | (* PUBREL unsupported since only QoS 0 is supported *) 696 | q_sLog := 'Received PUBREL from Broker: rejected since QoS 0 only is supported yet'; 697 | q_xError := TRUE; 698 | q_wState := 2#1001100001; (* Next Step: IDLE *) 699 | diRecvResult := 0; 700 | END_ACTION 701 | 702 | ACTION AC_HandleRESERVED: 703 | (* Handle reserved or undefined MQTT Control Packet Type Message from Broker 704 | --------------------------------------------------------------------------------------------------------------- 705 | *) 706 | 707 | (* reject any reserved or undefined MQTT Control Packet Type *) 708 | q_sLog := 'Received RESERVED MQTT Control Packet Type from Broker'; 709 | q_xError := TRUE; 710 | q_wState := 2#1000000001; 711 | q_wState := q_wState + SHL(bRecvCtrl, 4); (* Next Step: IDLE *) 712 | diRecvResult := 0; 713 | END_ACTION 714 | 715 | ACTION AC_HandleSUBACK: 716 | (* Handle SUBACK Message from Broker 717 | -------------------------------------------------------- 718 | 719 | Fixed Header | Variable Header | Payload | 720 | 1 + 1 bytes | 2 bytes | 1-X bytes | 721 | Control Packet Type | Flags | Remaining Length | Packet Identifier | return codes | 722 | 1 byte | 1 byte | 1 byte | 1 byte | | 723 | | Reserved | | MSB | LSB | | 724 | 4 bits | 4 bits | | | | | 725 | value: 9 |bin 0 0 0 0 | length VarHead+Payload(=3?) | 0-255 | 0-255 | 0x00, 0x01, 0x02 or 0x80 | 726 | 727 | Notes: 728 | - The variable header contains the Packet Identifier from the SUBSCRIBE Packet that is being acknowledged. 729 | - The SUBACK Packet sent by the Server to the Client MUST contain a return code for each Topic Filter/QoS pair. This return code MUST either show the maximum QoS that was granted for that Subscription or indicate that the subscription failed [MQTT-3.8.4-5]. 730 | - The Server might grant a lower maximum QoS than the subscriber requested. The QoS of Payload Messages sent in response to a Subscription MUST be the minimum of the QoS of the originally published message and the maximum QoS granted by the Server. 731 | - The payload contains a list of return codes. Each return code corresponds to a Topic Filter in the SUBSCRIBE Packet being acknowledged. SUBACK return codes other than 0x00, 0x01, 0x02 and 0x80 are reserved and MUST NOT be used. 732 | - The Server is permitted to start sending PUBLISH packets matching the Subscription before the Server sends the SUBACK Packet. 733 | *) 734 | 735 | q_sLog := 'Received SUBACK from Broker'; 736 | q_wState := 2#0010010000; 737 | 738 | (* Remaining length: Number of bytes left within current packet, including variable header plus payload. *) 739 | fbDecodeRemainingLength(pabRemainingLengthBytes := ADR(abInBuffer[diInBufferOffset + 1])); 740 | 741 | wRecvPacketIdentifier := SHL( BYTE_TO_UINT(abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength]), 8) (* Packet Identifier MSB *) 742 | + BYTE_TO_UINT(abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 1]); (* Packet Identifier LSB *) 743 | 744 | (* Success *) 745 | IF wRecvPacketIdentifier = wSendPacketIdentifier (* The variable header contains the Packet Identifier from the SUBSCRIBE Packet that is being acknowledged. *) 746 | AND abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 2] < 16#80 THEN (* should = 0x00 for QoS 0, 0x80 = error *) 747 | xAwaitingSUBACK := FALSE; 748 | xResetKeepAliveTimer := TRUE; 749 | q_xError := FALSE; 750 | q_wState := 2#0010010001; (* Next Step: IDLE *) 751 | 752 | (* Error *) 753 | ELSE 754 | IF abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 2] = 16#80 THEN 755 | q_sLog := 'SUBACK message reported failure (RC=0x80)'; 756 | ELSE 757 | q_sLog := 'Received invalid SUBACK message'; 758 | END_IF 759 | q_xError := TRUE; 760 | q_wState := 2#1010010001; (* Close Socket *) 761 | END_IF 762 | diSendResult := 0; 763 | 764 | diInBufferOffset := diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + fbDecodeRemainingLength.uiRemainingLength; 765 | IF diRecvResult - diInBufferOffset <= 0 THEN 766 | diRecvResult := 0; 767 | END_IF 768 | END_ACTION 769 | 770 | ACTION AC_HandleSUBSCRIBE: 771 | (* Handle SUBSCRIBE Message from Broker 772 | -------------------------------------------------------------- 773 | *) 774 | 775 | (* SUBSCRIBE unsupported since it is an MQTT Client only (not a Broker) *) 776 | q_sLog := 'Received SUBSCRIBE from Broker: unsupported since I`m only an MQTT Client'; 777 | q_xError := TRUE; 778 | q_wState := 2#1010000000; (* Next Step: IDLE *) 779 | diRecvResult := 0; 780 | END_ACTION 781 | 782 | ACTION AC_HandleUNSUBACK: 783 | (* Handle UNSUBACK Message from Broker 784 | ------------------------------------------------------------- 785 | 786 | Fixed Header | Variable Header | 787 | 1 + 1 bytes | 2 bytes | 788 | Control Packet Type | Flags | Remaining Length | Packet Identifier | 789 | 1 byte | 1 byte | 1 byte | 1 byte | 790 | | Reserved | | MSB | LSB | 791 | 4 bits | 4 bits | | | | 792 | value: 11 |bin 0 0 0 0 | length VarHead(=2) | 0-255 | 0-255 | 793 | 794 | Notes: 795 | - The variable header contains the Packet Identifier of the UNSUBSCRIBE Packet that is being acknowledged. 796 | - The UNSUBACK Packet has no payload. 797 | - A Client could also receive messages while an UNSUBSCRIBE operation is in progress. 798 | *) 799 | 800 | q_sLog := 'Received UNSUBACK from Broker'; 801 | q_wState := 2#0010110000; 802 | 803 | (* Remaining length: Number of bytes left within current packet, including variable header plus payload. *) 804 | fbDecodeRemainingLength(pabRemainingLengthBytes := ADR(abInBuffer[diInBufferOffset + 1])); 805 | 806 | wRecvPacketIdentifier := SHL( BYTE_TO_UINT(abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength]), 8) (* Packet Identifier MSB *) 807 | + BYTE_TO_UINT(abInBuffer[diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + 1]); (* Packet Identifier LSB *) 808 | 809 | (* Success *) 810 | IF wRecvPacketIdentifier = wSendPacketIdentifier THEN (* The variable header contains the Packet Identifier of the UNSUBSCRIBE Packet that is being acknowledged. *) 811 | xAwaitingUNSUBACK := FALSE; 812 | xResetKeepAliveTimer := TRUE; 813 | q_xError := FALSE; 814 | q_wState := 2#0010110001; (* Next Step: IDLE *) 815 | 816 | (* Error *) 817 | ELSE 818 | q_sLog := 'Received invalid UNSUBACK Packet Identifier'; 819 | q_xError := TRUE; 820 | q_wState := 2#1010110001; (* Close Socket *) 821 | END_IF 822 | diSendResult := 0; 823 | 824 | diInBufferOffset := diInBufferOffset + 1 + fbDecodeRemainingLength.iRemainingLengthLength + fbDecodeRemainingLength.uiRemainingLength; 825 | IF diRecvResult - diInBufferOffset <= 0 THEN 826 | diRecvResult := 0; 827 | END_IF 828 | END_ACTION 829 | 830 | ACTION AC_HandleUNSUBSCRIBE: 831 | (* Handle UNSUBSCRIBE Message from Broker 832 | ------------------------------------------------------------------ 833 | *) 834 | 835 | (* UNSUBSCRIBE unsupported since it is an MQTT Client only (not a Broker) *) 836 | q_sLog := 'Received UNSUBSCRIBE from Broker: unsupported since I`m only an MQTT Client'; 837 | q_xError := TRUE; 838 | q_wState := 2#1010100000; (* Next Step: IDLE *) 839 | diRecvResult := 0; 840 | END_ACTION 841 | 842 | ACTION AC_KeepAlive: 843 | (* Keep Alive 844 | ----------------- 845 | 846 | - A Keep Alive value of zero (0) has the effect of turning off the keep alive mechanism. This means that, in this case, the Server is not required to disconnect the Client on the grounds of inactivity. 847 | - Note that a Server is permitted to disconnect a Client that it determines to be inactive or non-responsive at any time, regardless of the Keep Alive value provided by that Client. 848 | - If the Keep Alive value is non-zero and the Server does not receive a Control Packet from the Client within one and a half times the Keep Alive time period, it MUST disconnect the Network Connection to the Client as if the network had failed [MQTT-3.1.2-24]. 849 | - Keep Alive maximum time interval is 65535 seconds, resulting in 18 hours 12 minutes and 15 seconds. 850 | *) 851 | 852 | fbKeepAliveTimer(IN := NOT xResetKeepAliveTimer AND (i_tKeepAliveTimeInterval > t#0s), PT := MIN(i_tKeepAliveTimeInterval, MQTT_KEEPALIVE_MAX_VALUE)); 853 | xResetKeepAliveTimer := FALSE; (* Keep Alive timer should run when IDLE *) 854 | 855 | (* Keep Alive timeout reached and no ongoing send request *) 856 | IF fbKeepAliveTimer.Q AND diSendResult <= 0 THEN 857 | AC_SendPINGREQ(); (* Send PINGREQ to Broker *) 858 | END_IF 859 | END_ACTION 860 | 861 | ACTION AC_SendCONNECT: 862 | (* Send CONNECT to Broker 863 | --------------------------------------- 864 | 865 | Fixed Header | Variable Header | Payload | 866 | 1 + 1-4 bytes | 10 bytes | 3-X bytes | 867 | Control Packet Type | Flags | Remaining Length | Length | Protocol Name | Level | Connect Flags | Keep Alive | Client Identifier | Will Topic | Will Message | User Name | Password | 868 | 1 byte | 1-4 bytes | 2 bytes | 4 bytes | 1 byte | 1 byte | 2 bytes |2 bytes length | 1-23* bytes | 0/2b length | 0-X bytes | 0/2b length | 0-X bytes | 0-X bytes | 0/2b length | 0-65535 bytes | 869 | | Reserved | | MSB | LSB | M | Q | T | T | | UF | PF | WR | Will QoS | WF | CS | Res | MSB | LSB | MSB | LSB | UTF8 | MSB | LSB | UTF8 | MSB | LSB | UTF8? | UTF8 | MSB | LSB | binary | 870 | 4 bits | 4 bits | | 1 byte | 1 byte | 1 byte | 1 byte | 1 byte | 1 byte | | 1 bit | 1 bit | 1 bit | 2 bits | 1 bit | 1 bit | 1 bit | 1 byte | 1 byte | 1 byte | 1 byte | | 1 byte | 1 byte | | 1 byte | 1 byte | | | 1 byte | 1 byte | | 871 | value: 1 |bin 0 0 0 0 | length VarHead+Payload | 0 | 4 | 77 | 81 | 84 | 84 | 4 | 0 | | | | | | | | | | | | | | | | 872 | 873 | Notes: 874 | - After a Network Connection is established by a Client to a Server, the first Packet sent from the Client to the Server MUST be a CONNECT Packet. 875 | - A Client can only send the CONNECT Packet once over a Network Connection. The Server MUST process a second CONNECT Packet sent from a Client as a protocol violation and disconnect the Client [MQTT-3.1.0-2]. 876 | - Remaining Length is the length of the variable header (10 bytes) plus the length of the Payload. 877 | - The variable header FOR the CONNECT Packet consists OF 4 fields in the following order: Protocol Name, Protocol Level, Connect Flags, AND Keep Alive. 878 | - Protocol Level: The 8 bit unsigned value that represents the revision level of the protocol used by the Client. The value of the Protocol Level field for the version 3.1.1 of the protocol is 4 (0x04). 879 | - Connect Flags: The Connect Flags byte contains a number of parameters specifying the behavior of the MQTT connection. It also indicates the presence or absence of fields in the payload. 880 | - Clean Session (CS): If CleanSession is set to 0, the Server MUST resume communications with the Client based on state from the current Session (as identified by the Client identifier). 881 | If there is no Session associated with the Client identifier the Server MUST create a new Session. If CleanSession is set to 1, the Client and Server MUST discard any previous Session and start a new one. This Session lasts as long as the Network Connection. 882 | - Will Flag (WF): If the Will Flag is set to 1 this indicates that, if the Connect request is accepted, a Will Message MUST be stored on the Server and associated with the Network Connection. 883 | If the Will Flag is set to 1, the Will QoS and Will Retain fields in the Connect Flags will be used by the Server, and the Will Topic and Will Message fields MUST be present in the payload. 884 | If the Will Flag is set to 0 the Will QoS and Will Retain fields in the Connect Flags MUST be set to zero and the Will Topic and Will Message fields MUST NOT be present in the payload. 885 | - Will QoS: These two bits specify the QoS level to be used when publishing the Will Message. If the Will Flag is set to 0, then the Will QoS MUST be set to 0 (0x00). If the Will Flag is set to 1, the value of Will QoS can be 0 (0x00), 1 (0x01), or 2 (0x02). It MUST NOT be 3 (0x03). 886 | - Will Retain (WR): This bit specifies if the Will Message is to be Retained when it is published. If the Will Flag is set to 0, then the Will Retain Flag MUST be set to 0. 887 | - Password Flag (PF): If the Password Flag is set to 0, a password MUST NOT be present in the payload. If the Password Flag is set to 1, a password MUST be present in the payload. If the User Name Flag is set to 0, the Password Flag MUST be set to 0. 888 | - User Name Flag (UF): If the User Name Flag is set to 0, a user name MUST NOT be present in the payload. If the User Name Flag is set to 1, a user name MUST be present in the payload. 889 | . Keep Alive: the Keep Alive is a time interval measured in seconds. The maximum value is 18 hours 12 minutes and 15 seconds. 890 | Expressed as a 16-bit word, it is the maximum time interval that is permitted to elapse between the point at which the Client finishes transmitting one Control Packet and the point it starts sending the next. 891 | It is the responsibility of the Client to ensure that the interval between Control Packets being sent does not exceed the Keep Alive value. In the absence of sending any other Control Packets, the Client MUST send a PINGREQ Packet. 892 | A Keep Alive value of 0 has the effect of turning off the keep alive mechanism. This means that, in this case, the Server is not required to disconnect the Client on the grounds of inactivity. 893 | Note that a Server is permitted to disconnect a Client that it determines to be inactive or non-responsive at any time, regardless of the Keep Alive value provided by that Client. 894 | - Payload: it contains one or more length-prefixed fields, whose presence is determined by the flags in the variable header. These fields, if present, MUST appear in the order Client Identifier, Will Topic, Will Message, User Name, Password 895 | - Client Identifier: each Client connecting to the Server has a unique ClientId. The ClientId MUST be present and MUST be the first field in the CONNECT packet payload. 896 | The Server MUST allow ClientIds which are between 1 and 23 UTF-8 encoded bytes in length, and that contain only the characters "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". 897 | * The Server MAY allow ClientId’s that contain more than 23 encoded bytes. The Server MAY allow ClientId’s that contain characters not included in the list given above. 898 | If the Client supplies a zero-byte ClientId, the Client MUST also set CleanSession to 1. 899 | If the Client supplies a zero-byte ClientId with CleanSession set to 0, the Server MUST respond to the CONNECT Packet with a CONNACK return code 0x02 (Identifier rejected) and then close the Network Connection. 900 | If the Server rejects the ClientId it MUST respond to the CONNECT Packet with a CONNACK return code 0x02 (Identifier rejected) and then close the Network Connection. 901 | *) 902 | 903 | q_sLog := 'Sending CONNECT to MQTT Broker'; 904 | q_wState := 2#0100011000; 905 | 906 | (* Purge Out Buffer *) 907 | FOR iSendIndexCount := 0 TO BUFFER_SIZE DO abOutBuffer[iSendIndexCount] := 0; END_FOR 908 | 909 | (* Fixed Header *) 910 | (* MQTT Control Packet Type *) 911 | abOutBuffer[0] := 2#00010000; (* 1 = CONNECT *) 912 | 913 | (* Calculate the Remaining Length: Length Variable Header + Length Payload *) 914 | fbCalculateRemainingLength(wNumberOfBytes := 10 + (* Variable Header length (always 10) *) 915 | (2 + 8) * BOOL_TO_INT(i_sClientIdentifier = '') + (* Client Identifier length in case of random generated *) 916 | (2 + LEN(i_sClientIdentifier)) * BOOL_TO_INT(i_sClientIdentifier <> '') + (* Client Identifier length in case defined as input *) 917 | (2 + LEN(i_sUsername)) * BOOL_TO_INT(i_sUsername <> '') + (* User Name length *) 918 | (2 + LEN(i_sPassword)) * BOOL_TO_INT(i_sPassword <> '') + (* Password length *) 919 | (2 + LEN(i_sWillTopic)) * BOOL_TO_INT(i_sWillTopic <> '') + (* Will Topic length *) 920 | (2 + LEN(i_sWillMessage)) * BOOL_TO_INT(i_sWillMessage <> '')); (* Will Message length *) 921 | 922 | FOR iSendIndexCount := 1 TO fbCalculateRemainingLength.iRemainingLengthLength DO 923 | abOutBuffer[iSendIndexCount] := fbCalculateRemainingLength.abRemainingLengthBytes[iSendIndexCount - 1]; 924 | END_FOR 925 | 926 | (* Variable Header *) 927 | abOutBuffer[iSendIndexCount] := 0; (* Variable Header Length MSB (0) *) 928 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 4; (* Variable Header Length LSB (1) *) 929 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 77; (* 'M' *) 930 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 81; (* 'Q' *) 931 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 84; (* 'T' *) 932 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 84; (* 'T' *) 933 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 4; (* The value of the Protocol Level field for the version 3.1.1 of the protocol is 4 (0x04). *) 934 | 935 | (* Connect Flags *) 936 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 0 937 | OR (BOOL_TO_BYTE(i_sUsername <> '') * 2#10000000) (* User Name Flag *) 938 | OR (BOOL_TO_BYTE(i_sPassword <> '') * 2#01000000) (* Password Flag *) 939 | OR (BOOL_TO_BYTE(i_xWillRetain) * 2#00100000) (* Will Retain *) 940 | (* 2#000XX000*) (* Will QoS *) 941 | OR (BOOL_TO_BYTE(i_sWillTopic <> '') * 2#00000100) (* Will Flag *) 942 | OR (BOOL_TO_BYTE(i_xCleanSession) * 2#00000010); (* Clean Session *) 943 | (* 2#00000000*) (* Reserved *) 944 | (* Keep Alive*) 945 | (* Valid Keep Alive is an integer from 0 to 65535, representing the maximum time in seconds allowed to elapse between MQTT protocol packets sent by the client. *) 946 | (* Datatype TIME is internally based on milliseconds stored on a DWORD, so TIME_TO_DWORD gets milliseconds. Divide by 1000 to provide a valid protocoll value based on seconds. *) 947 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(SHR((TIME_TO_DWORD(MIN(i_tKeepAliveTimeInterval, MQTT_KEEPALIVE_MAX_VALUE)) / 1000), 8)); (* Keep Alive MSB *) 948 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(TIME_TO_DWORD(MIN(i_tKeepAliveTimeInterval, MQTT_KEEPALIVE_MAX_VALUE)) / 1000); (* Keep Alive LSB *) 949 | 950 | 951 | (* Client Identifier *) 952 | IF i_sClientIdentifier = '' THEN 953 | (* A Client implementation could provide a convenience method to generate a random ClientId. Use of such a method should be actively discouraged when the CleanSession is set to 0. *) 954 | IF i_xCleanSession = FALSE THEN 955 | q_sLog := 'Random Client Id should be actively discouraged when the CleanSession is set to 0'; 956 | q_xError := TRUE; 957 | q_wState := 2#1100010100; (* just continue here *) 958 | END_IF 959 | 960 | (* Random Client Identifier in case not defined *) 961 | dwTime := TIME_TO_DWORD(TIME()); 962 | fbRandom[1](A := 21345,B := 36215,V := dwTime); 963 | fbRandom[2](A := 42784,B := 926432,V := dwTime); 964 | fbRandom[3](A := 87654,B := 23456,V := dwTime); 965 | fbRandom[4](A := 76543,B := 12345,V := dwTime); 966 | fbRandom[5](A := 3456543234,B := 54321234,V := dwTime); 967 | fbRandom[6](A := 763435,B := 121234,V := dwTime); 968 | fbRandom[7](A := 897,B := 434321,V := dwTime); 969 | fbRandom[8](A := 345654332,B := 77765,V := dwTime); 970 | 971 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 0; (* Random ClientId length MSB *) 972 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 8; (* Random ClientId length LSB *) 973 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(fbRandom[1].dwRandom); 974 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(fbRandom[2].dwRandom); 975 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(fbRandom[3].dwRandom); 976 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(fbRandom[4].dwRandom); 977 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(fbRandom[5].dwRandom); 978 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(fbRandom[6].dwRandom); 979 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(fbRandom[7].dwRandom); 980 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := DWORD_TO_BYTE(fbRandom[8].dwRandom); 981 | ELSE 982 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 0; (* Given ClientId length MSB (always 0 since i_sClientIdentifier is always less than 23 chars length) *) 983 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(LEN(i_sClientIdentifier)); (* Given ClientId length LSB *) 984 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(i_sClientIdentifier), LEN(i_sClientIdentifier)); 985 | iSendIndexCount := iSendIndexCount + LEN(i_sClientIdentifier) - 1; 986 | END_IF 987 | 988 | (* Will Topic *) 989 | IF i_sWillTopic <> '' THEN 990 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(SHR(LEN(i_sWillTopic), 8)); (* Will Topic length MSB *) 991 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(LEN(i_sWillTopic)); (* Will Topic length LSB *) 992 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(i_sWillTopic), LEN(i_sWillTopic)); 993 | iSendIndexCount := iSendIndexCount + LEN(i_sWillTopic) - 1; 994 | END_IF 995 | 996 | (* Will Message *) 997 | IF i_sWillMessage <> '' THEN 998 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(SHR(LEN(i_sWillMessage), 8)); (* Will Message length MSB *) 999 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(LEN(i_sWillMessage)); (* Will Message length LSB *) 1000 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(i_sWillMessage), LEN(i_sWillMessage)); 1001 | iSendIndexCount := iSendIndexCount + LEN(i_sWillMessage) - 1; 1002 | END_IF 1003 | 1004 | (* User Name *) 1005 | IF i_sUsername <> '' THEN 1006 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(SHR(LEN(i_sUsername), 8)); (* User Name length MSB *) 1007 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(LEN(i_sUsername)); (* User Name length LSB *) 1008 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(i_sUsername), LEN(i_sUsername)); 1009 | iSendIndexCount := iSendIndexCount + LEN(i_sUsername) - 1; 1010 | END_IF 1011 | 1012 | (* Password *) 1013 | IF i_sPassword <> '' THEN 1014 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(SHR(LEN(i_sPassword), 8)); (* Password length MSB *) 1015 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(LEN(i_sPassword)); (* Password length LSB *) 1016 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(i_sPassword), LEN(i_sPassword)); 1017 | iSendIndexCount := iSendIndexCount + LEN(i_sPassword) - 1; 1018 | END_IF 1019 | 1020 | (* Send CONNECT *) 1021 | iSendIndexCount := iSendIndexCount + 1; (* +1 because abOutBuffer is 0 based *) 1022 | diSendResult := SysSockSend(diSocket, ADR(abOutBuffer), iSendIndexCount, 0); 1023 | xAwaitingCONNACK := TRUE; (* This starts the dedicated CONNECT timer, awaiting for CONNACK response *) 1024 | 1025 | (* Send CONNECT successful *) 1026 | IF diSendResult = iSendIndexCount THEN 1027 | q_xConnected := TRUE; (* If CONNECT request sent successfully, we can expect to be connected. If CONNACK is unsuccessfull, q_xConnected will be set to false again after timeout is reached. It is allowed to send PUBLISH before getting CONNACK. *) 1028 | tCOMtimeoutValue := t#0ms; (* Stop any COM Timeout if connected, CONNECT timeout is running now. *) 1029 | FOR iSendIndexCount := 0 TO BUFFER_SIZE DO abInBuffer[iSendIndexCount] := 0; END_FOR (* Purge In Buffer on connection *) 1030 | q_sLog := 'Sending CONNECT successfully, waiting for CONNACK from Broker'; 1031 | q_xError := FALSE; 1032 | q_wState := 2#0100011001; (* Next Step: Wait for CONNACK from Broker. COM timeout is reset when IDLE. CONNACK Timeout handled by dedicated CONNACK timeout. *) 1033 | 1034 | (* Send CONNECT failed *) 1035 | ELSE 1036 | q_sLog := 'Sending CONNECT failed'; 1037 | q_xError := TRUE; 1038 | q_wState := 2#1100011001; (* Next Step: Do not close the connection yet. It will be retried. And if Timeout is reached by not receiving CONNACK, the connection will be closed. *) 1039 | diSendResult := 0; 1040 | END_IF 1041 | END_ACTION 1042 | 1043 | ACTION AC_SendDISCONNECT: 1044 | (* Send DISCONNECT to Broker 1045 | -------------------------------------------- 1046 | 1047 | Fixed Header | 1048 | 2 bytes | 1049 | Control Packet Type | Flags | Remaining Length | 1050 | 1 byte | 1 byte | 1051 | | Reserved | | 1052 | 4 bits | 4 bits | | 1053 | value: 14 |bin 0 0 0 0 | 0 | 1054 | 1055 | Notes: 1056 | - The DISCONNECT Packet is the final Control Packet sent from the Client to the Server. It indicates that the Client is disconnecting cleanly. 1057 | - After sending a DISCONNECT Packet the Client: 1058 | MUST close the Network Connection [MQTT-3.14.4-1]. 1059 | MUST NOT send any more Control Packets on that Network Connection [MQTT-3.14.4-2]. 1060 | *) 1061 | 1062 | q_sLog := 'Sending DISCONNECT to MQTT Broker'; 1063 | q_wState := 2#0111100000; 1064 | 1065 | (* Purge Out Buffer *) 1066 | FOR iSendIndexCount := 0 TO BUFFER_SIZE DO abOutBuffer[iSendIndexCount] := 0; END_FOR 1067 | 1068 | (* Fixed Header *) 1069 | (* MQTT Control Packet Type *) 1070 | abOutBuffer[0] := 2#11100000; (* 14 = DISCONNECT *) 1071 | abOutBuffer[1] := 0; (* Remaining Length always 0 for DISCONNECT *) 1072 | 1073 | (* Send DISCONNECT *) 1074 | diSendResult := SysSockSend(diSocket, ADR(abOutBuffer), 2, 0); 1075 | tCOMtimeoutValue := i_tCommTimeout; (* Start Timeout *) 1076 | 1077 | (* Reset MQTT Request Timers *) 1078 | xAwaitingCONNACK := FALSE; 1079 | xAwaitingPINGRESP := FALSE; 1080 | xAwaitingSUBACK := FALSE; 1081 | xAwaitingUNSUBACK := FALSE; 1082 | 1083 | (* Send DISCONNECT successful *) 1084 | IF diSendResult = 2 THEN 1085 | q_sLog := 'Sending DISCONNECT successfully'; 1086 | q_xError := FALSE; 1087 | q_wState := 2#0111100001; (* Next Step: Close Socket *) 1088 | 1089 | (* Send DISCONNECT failed *) 1090 | ELSE 1091 | q_sLog := 'Sending DISCONNECT failed'; 1092 | q_xError := TRUE; 1093 | q_wState := 2#1111100001; (* Next Step: Retry until succeed or timeout *) 1094 | END_IF 1095 | 1096 | (* DISCONNECT Timeout *) 1097 | IF fbCOMtimeout.Q THEN 1098 | q_sLog := 'Disconnection from MQTT Broker Timeout'; 1099 | q_xError := TRUE; 1100 | tCOMtimeoutValue := t#0ms; (* Reset COM Timeout *) 1101 | q_wState := 2#1111100010; (* Next Step: Close Socket *) 1102 | END_IF 1103 | diSendResult := 0; 1104 | END_ACTION 1105 | 1106 | ACTION AC_SendPINGREQ: 1107 | (* Send PINGREQ to Broker 1108 | -------------------------------------- 1109 | 1110 | Fixed Header | 1111 | 2 bytes | 1112 | Control Packet Type | Flags | Remaining Length | 1113 | 1 byte | 1 byte | 1114 | | Reserved | | 1115 | 4 bits | 4 bits | | 1116 | value: 12 |bin 0 0 0 0 | 0 | 1117 | 1118 | Notes: 1119 | - The Server MUST send a PINGRESP Packet in response to a PINGREQ Packet [MQTT-3.12.4-1]. 1120 | - This Packet is used in Keep Alive processing. 1121 | - Keep Alive: It is the maximum time interval that is permitted to elapse between the point at which the Client finishes transmitting one Control Packet and the point it starts sending the next. 1122 | It is the responsibility of the Client to ensure that the interval between Control Packets being sent does not exceed the Keep Alive value. In the absence of sending any other Control Packets, the Client MUST send a PINGREQ Packet [MQTT-3.1.2-23]. 1123 | *) 1124 | 1125 | IF NOT q_xIDLE THEN RETURN; END_IF (* do not send anything while awaiting an ACK packet *) 1126 | 1127 | q_sLog := 'Sending PINGREQ to Broker'; 1128 | q_wState := 2#0111000000; 1129 | 1130 | (* Purge Out Buffer *) 1131 | FOR iSendIndexCount := 0 TO BUFFER_SIZE DO abOutBuffer[iSendIndexCount] := 0; END_FOR 1132 | 1133 | (* Fixed Header *) 1134 | (* MQTT Control Packet Type *) 1135 | abOutBuffer[0] := 2#11000000; (* 12 = PINGREQ *) 1136 | abOutBuffer[1] := 0; (* Remaining Length always 0 for PINGREQ. The PINGREQ Packet has no variable header. *) 1137 | 1138 | (* Send PINGREQ *) 1139 | diSendResult := SysSockSend(diSocket, ADR(abOutBuffer), 2, 0); 1140 | xAwaitingPINGRESP := TRUE; 1141 | 1142 | (* Send PINGREQ successful *) 1143 | IF diSendResult = 2 THEN 1144 | q_sLog := 'Sending PINGREQ successfully'; 1145 | q_xError := FALSE; 1146 | q_wState := 2#0111000001; (* Next Step: IDLE, wait for PINGRESP *) 1147 | 1148 | (* Send PINGREQ failed *) 1149 | ELSE 1150 | q_sLog := 'Sending PINGREQ failed'; 1151 | q_xError := TRUE; 1152 | q_wState := 2#1111000001; (* Next Step: retry until timeout *) 1153 | diSendResult := 0; 1154 | END_IF 1155 | END_ACTION 1156 | 1157 | ACTION AC_SendPUBLISH: 1158 | (* Send PUBLISH to Broker 1159 | ------------------------------------- 1160 | 1161 | Fixed Header | Variable Header | Payload | 1162 | 1 + 1-4 bytes | variable size | variable size | 1163 | Control Packet Type | Flags | Remaining Length | Topic Length | Topic Name | Packet Identifier | | 1164 | 1 byte | 1-4 bytes | 2 bytes | | 0 | 2 bytes (if QoS > 0) | | 1165 | | DUP | QoS |Retain| | MSB | LSB | | MSB | LSB | | 1166 | 4 bits | 1 bit | 2 bits | 1 bit | | 1 byte | 1 byte | | 1 byte | 1 byte | | 1167 | value: 3 | 0/1 | 0,1,2 | 0/1 |length VarHead+Payload | 1-65536 | UTF8 | 0-65536 | UTF8 | 1168 | 1169 | Notes: 1170 | - A PUBLISH Packet MUST NOT contain a Packet Identifier if its QoS value is set to 0 [MQTT-2.3.1-5]. 1171 | - Topic MUST be present as the first field in the PUBLISH Packet Variable header (after topic length). 1172 | - The DUP flag MUST be set to 1 by the Client or Server when it attempts to re-deliver a PUBLISH Packet [MQTT-3.3.1.-1]. The DUP flag MUST be set to 0 for all QoS 0 messages [MQTT-3.3.1-2]. 1173 | - If the RETAIN flag is set to 1, in a PUBLISH Packet sent by a Client to a Server, the Server MUST store the Application Message and its QoS, so that it can be delivered to future subscribers whose subscriptions match its topic name [MQTT-3.3.1-5]. 1174 | When a new subscription is established, the last retained message, if any, on each matching topic name MUST be sent to the subscriber [MQTT-3.3.1-6]. 1175 | - The Topic Name in the PUBLISH Packet MUST NOT contain wildcard characters [MQTT-3.3.2-2]. 1176 | *) 1177 | 1178 | IF NOT q_xIDLE THEN RETURN; END_IF (* do not send anything while awaiting an ACK packet *) 1179 | 1180 | q_sLog := CONCAT(CONCAT('Sending PUBLISH to Broker (d0, q0, r', BYTE_TO_STRING(BOOL_TO_BYTE(i_xRetain))), ', m0)'); 1181 | q_wState := 2#0100110000; 1182 | 1183 | (* All Topic Names and Topic Filters MUST be at least one character long [MQTT-4.7.3-1] *) 1184 | IF LEN(sPublishTopic) <= 0 (*OR LEN(sPublishTopic) > 65535*) THEN (* TODO: check for wildcard characters *) 1185 | q_sLog := 'Send PUBLISH canceled: invalid topic length'; 1186 | q_xError := TRUE; 1187 | q_wState := 2#1100110000; (* Next Step: IDLE *) 1188 | RETURN; 1189 | END_IF 1190 | 1191 | (* Purge Out Buffer *) 1192 | FOR iSendIndexCount := 0 TO BUFFER_SIZE DO abOutBuffer[iSendIndexCount] := 0; END_FOR 1193 | 1194 | (* Fixed Header *) 1195 | (* MQTT Control Packet Type *) 1196 | abOutBuffer[0] := 2#00110000 + BOOL_TO_BYTE(i_xRetain); (* 3 = PUBLISH. Control Packet Flags bit 0 for RETAIN, DUP flag = 0 for QoS 0. *) 1197 | 1198 | (* Calculate the Remaining Length - Length of Variable Header + Length of Payload. *) 1199 | fbCalculateRemainingLength(wNumberOfBytes := 2 + LEN(sPublishTopic) + 0 + LEN(sPublishMessage)); (* Variable header = 2 (topic length) + Topic Name + Packet Identifier (0-2, here 0 because QoS=0) *) 1200 | FOR iSendIndexCount := 1 TO fbCalculateRemainingLength.iRemainingLengthLength DO 1201 | abOutBuffer[iSendIndexCount] := fbCalculateRemainingLength.abRemainingLengthBytes[iSendIndexCount - 1]; 1202 | END_FOR 1203 | 1204 | (* Variable Header *) 1205 | abOutBuffer[iSendIndexCount] := INT_TO_BYTE(SHR(LEN(sPublishTopic), 8)); (* Topic Length MSB *) 1206 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(LEN(sPublishTopic)); (* Topic Length LSB *) 1207 | 1208 | (* Topic MUST be present as the first field in the PUBLISH Packet Variable header (after topic length) *) 1209 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(sPublishTopic), LEN(sPublishTopic)); 1210 | iSendIndexCount := iSendIndexCount + LEN(sPublishTopic) - 1; 1211 | 1212 | (* Packet Identifier: !!! A PUBLISH Packet MUST NOT contain a Packet Identifier if its QoS value is set to 0 !!! *) 1213 | 1214 | (* Payload *) 1215 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(sPublishMessage), LEN(sPublishMessage)); 1216 | iSendIndexCount := iSendIndexCount + LEN(sPublishMessage) - 1; 1217 | 1218 | (* Send PUBLISH *) 1219 | iSendIndexCount := iSendIndexCount + 1; (* +1 because abOutBuffer is 0 based *) 1220 | diSendResult := SysSockSend(diSocket, ADR(abOutBuffer), iSendIndexCount, 0); 1221 | 1222 | (* Send PUBLISH successful *) 1223 | IF diSendResult = iSendIndexCount THEN 1224 | q_sLog := CONCAT(CONCAT('Sending PUBLISH to Broker (d0, q0, r', BYTE_TO_STRING(BOOL_TO_BYTE(i_xRetain))), ', m0) successfully'); 1225 | q_xError := FALSE; 1226 | q_wState := 2#0100110001; (* Next Step: IDLE *) 1227 | 1228 | (* Send PUBLISH failed *) 1229 | ELSE 1230 | q_sLog := CONCAT(CONCAT('Sending PUBLISH to Broker (d0, q0, r', BYTE_TO_STRING(BOOL_TO_BYTE(i_xRetain))), ', m0) failed'); 1231 | q_xError := TRUE; 1232 | q_wState := 2#1100110001; (* Next Step: IDLE (QoS0, no retry) *) 1233 | END_IF 1234 | diSendResult := 0; 1235 | END_ACTION 1236 | 1237 | ACTION AC_SendSUBSCRIBE: 1238 | (* Send SUBSCRIBE to Broker 1239 | ----------------------------------------- 1240 | 1241 | Fixed Header | Variable Header | Payload | 1242 | 1 + 1-4 bytes | 2 bytes | variable size | 1243 | Control Packet Type | Flags | Remaining Length | Packet Identifier | Topic1 length | Topic Filter / QoS pair 1 |Topic n length | Topic Filter / QoS pair n | ... | 1244 | 1 byte | 1-4 bytes | 2 bytes | 2 bytes | x bytes | 1 byte | 2 bytes | x bytes | 1 byte | ... | 1245 | | | | MSB | LSB | MSB | LSB | Topic Filter | QoS | MSB | LSB | Topic Filter | QoS | ... | 1246 | 4 bits | 4 bits | | 1 byte | 1 byte | 1 byte | 1 byte | | 2 bits | 1 byte | 1 byte | | 2 bits | ... | 1247 | value: 8 |bin 0 0 1 0 |length VarHead+Payload | 1-65536 | 1-65536 | UTF8 | 0.1.2 | 1-65536 | UTF8 | 0,1,2 | ... | 1248 | 1249 | Notes: 1250 | - Bits 3,2,1 and 0 of the fixed header of the SUBSCRIBE Control Packet are reserved and MUST be set to 0,0,1 and 0 respectively. The Server MUST treat any other value as malformed and close the Network Connection [MQTT-3.8.1-1]. 1251 | - The payload of a SUBSCRIBE packet MUST contain at least one Topic Filter / QoS pair. A SUBSCRIBE packet with no payload is a protocol violation [MQTT-3.8.3-3]. 1252 | - The payload of a SUBSCRIBE packet MUST contain at least one Topic Filter / QoS pair. A SUBSCRIBE packet with no payload is a protocol violation [MQTT-3.8.3-3]. 1253 | - Topic Names and Topic Filters are UTF-8 encoded strings, they MUST NOT encode to more than 65535 bytes [MQTT-4.7.3-3]. 1254 | 1255 | TODO: multiple topics subscription? 1256 | *) 1257 | 1258 | IF NOT q_xIDLE THEN RETURN; END_IF (* do not send anything while awaiting an ACK packet *) 1259 | 1260 | q_sLog := 'Sending SUBSCRIBE to Broker'; 1261 | q_wState := 2#0110000000; 1262 | 1263 | (* All Topic Names and Topic Filters MUST be at least one character long [MQTT-4.7.3-1] *) 1264 | IF LEN(sSubscribeTopic) <= 0 (*OR LEN(sSubscribeTopic) > 65535*) THEN 1265 | q_sLog := 'Send SUBSCRIBE canceled: invalid topic filter length'; 1266 | q_xError := TRUE; 1267 | q_wState := 2#1110000000; (* Next Step: IDLE *) 1268 | RETURN; 1269 | END_IF 1270 | 1271 | (* Purge Out Buffer *) 1272 | FOR iSendIndexCount := 0 TO BUFFER_SIZE DO abOutBuffer[iSendIndexCount] := 0; END_FOR 1273 | 1274 | (* Fixed Header *) 1275 | (* MQTT Control Packet Type *) 1276 | abOutBuffer[0] := 2#10000010; (* 8 = SUBSCRIBE. Bits 3,2,1 and 0 of the fixed header of the SUBSCRIBE Control Packet are reserved and MUST be set to 0,0,1 and 0 respectively. The Server MUST treat any other value as malformed and close the Network Connection. *) 1277 | 1278 | (* Remaining Length: length of Variable Header (2 bytes) + length of Payload: (topic length field = 2 + topic length = N + QoS = 1) *) 1279 | fbCalculateRemainingLength(wNumberOfBytes := 2 + 2 + LEN(sSubscribeTopic) + 1); 1280 | FOR iSendIndexCount := 1 TO fbCalculateRemainingLength.iRemainingLengthLength DO 1281 | abOutBuffer[iSendIndexCount] := fbCalculateRemainingLength.abRemainingLengthBytes[iSendIndexCount - 1]; 1282 | END_FOR 1283 | 1284 | (* Packet Identifier *) 1285 | wSendPacketIdentifier := wSendPacketIdentifier + 1; 1286 | IF (wSendPacketIdentifier = 0) THEN wSendPacketIdentifier := 1; END_IF (* Security when max value of WORD is reached *) 1287 | abOutBuffer[iSendIndexCount] := WORD_TO_BYTE(SHR(wSendPacketIdentifier, 8)); (* Variable Header: Packet Identifier MSB *) 1288 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := WORD_TO_BYTE(wSendPacketIdentifier); (* Variable Header: Packet Identifier LSB *) 1289 | 1290 | (* Payload *) 1291 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(SHR(LEN(sSubscribeTopic), 8)); (* Payload: Topic Length MSB *) 1292 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(LEN(sSubscribeTopic)); (* Payload: Topic Length LSB *) 1293 | 1294 | (* Topic Filter *) 1295 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(sSubscribeTopic), LEN(sSubscribeTopic)); 1296 | iSendIndexCount := iSendIndexCount + LEN(sSubscribeTopic) - 1; 1297 | 1298 | (* Topic Requested QoS *) 1299 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := 0; (* QoS = 0 *) 1300 | 1301 | (* Send SUBSCRIBE *) 1302 | iSendIndexCount := iSendIndexCount + 1; (* +1 because abOutBuffer is 0 based *) 1303 | diSendResult := SysSockSend(diSocket, ADR(abOutBuffer), iSendIndexCount, 0); 1304 | xAwaitingSUBACK := TRUE; 1305 | 1306 | (* Send SUBSCRIBE successful *) 1307 | IF diSendResult = iSendIndexCount THEN 1308 | q_sLog := 'Sending SUBSCRIBE successfully'; 1309 | q_xError := FALSE; 1310 | q_wState := 2#0110000001; (* Next Step: IDLE (Wait for SUBACK) *) 1311 | 1312 | (* Send SUBSCRIBE failed *) 1313 | ELSE 1314 | q_sLog := 'Sending SUBSCRIBE failed'; 1315 | q_xError := TRUE; 1316 | q_wState := 2#1110000001; (* Next Step: no retry for QoS0 (IDLE) *) 1317 | diSendResult := 0; 1318 | END_IF 1319 | END_ACTION 1320 | 1321 | ACTION AC_SendUNSUBSCRIBE: 1322 | (* Send UNSUBSCRIBE to Broker 1323 | ---------------------------------------------- 1324 | 1325 | Fixed Header | Variable Header | Payload | 1326 | 1 + 1-4 bytes | 2 bytes | variable size | 1327 | Control Packet Type | Flags | Remaining Length | Packet Identifier | Topic1 length | Topic Filter |Topic n length | Topic Filter | ... | 1328 | 1 byte | 1-4 bytes | 2 bytes | 2 bytes | x bytes | 2 bytes | x bytes | ... | 1329 | | | | MSB | LSB | MSB | LSB | Topic Filter | MSB | LSB | Topic Filter | ... | 1330 | 4 bits | 4 bits | | 1 byte | 1 byte | 1 byte | 1 byte | | 1 byte | 1 byte | | ... | 1331 | value: 10 |bin 0 0 1 0 |length VarHead+Payload | 1-65536 | 1-65536 | UTF8 | 1-65536 | UTF8 | ... | 1332 | 1333 | Notes: 1334 | - Bits 3,2,1 and 0 of the fixed header of the UNSUBSCRIBE Control Packet are reserved and MUST be set to 0,0,1 and 0 respectively. The Server MUST treat any other value as malformed and close the Network Connection [MQTT-3.10.1-1]. 1335 | - The payload of an UNSUBSCRIBE packet MUST contain at least one Topic Filter. An UNSUBSCRIBE packet with no payload is a protocol violation [MQTT-3.10.3-2]. 1336 | - The Server MUST respond to an UNSUBSUBCRIBE request by sending an UNSUBACK packet. The UNSUBACK Packet MUST have the same Packet Identifier as the UNSUBSCRIBE Packet [MQTT-3.10.4-4]. Even where no Topic Subscriptions are deleted, the Server MUST respond with an UNSUBACK [MQTT-3.10.4-5]. 1337 | - A Client could also receive messages while an UNSUBSCRIBE operation is in progress. 1338 | 1339 | TODO: multiple topics unsubscription? 1340 | *) 1341 | 1342 | IF NOT q_xIDLE THEN RETURN; END_IF (* do not send anything while awaiting an ACK packet *) 1343 | 1344 | q_sLog := 'Sending UNSUBSCRIBE to Broker'; 1345 | q_wState := 2#0110100000; 1346 | 1347 | (* All Topic Names and Topic Filters MUST be at least one character long [MQTT-4.7.3-1] *) 1348 | IF LEN(sUnsubscribeTopic) <= 0 (*OR LEN(sUnsubscribeTopic) > 65535*) THEN 1349 | q_sLog := 'Send UNSUBSCRIBE canceled: at least one topic filter is required'; 1350 | q_xError := TRUE; 1351 | q_wState := 2#1110100000; (* Next Step: IDLE *) 1352 | RETURN; 1353 | END_IF 1354 | 1355 | (* Purge Out Buffer *) 1356 | FOR iSendIndexCount := 0 TO BUFFER_SIZE DO abOutBuffer[iSendIndexCount] := 0; END_FOR 1357 | 1358 | (* Fixed Header *) 1359 | (* MQTT Control Packet Type *) 1360 | abOutBuffer[0] := 2#10100010; (* 10 = UNSUBSCRIBE. Bits 3,2,1 and 0 of the fixed header of the UNSUBSCRIBE Control Packet are reserved and MUST be set to 0,0,1 and 0 respectively. The Server MUST treat any other value as malformed and close the Network Connection. *) 1361 | 1362 | (* Remaining Length: length of Variable Header (2 bytes) + length of Payload: (topic length field = 2 + topic filter length = N) *) 1363 | fbCalculateRemainingLength(wNumberOfBytes := 2 + 2 + LEN(sUnsubscribeTopic)); 1364 | FOR iSendIndexCount := 1 TO fbCalculateRemainingLength.iRemainingLengthLength DO 1365 | abOutBuffer[iSendIndexCount] := fbCalculateRemainingLength.abRemainingLengthBytes[iSendIndexCount - 1]; 1366 | END_FOR 1367 | 1368 | (* Packet Identifier *) 1369 | wSendPacketIdentifier := wSendPacketIdentifier + 1; 1370 | IF (wSendPacketIdentifier = 0) THEN wSendPacketIdentifier := 1; END_IF (* Security when max value of WORD is reached *) 1371 | abOutBuffer[iSendIndexCount] := WORD_TO_BYTE(SHR(wSendPacketIdentifier, 8)); (* Variable Header: Packet Identifier MSB *) 1372 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := WORD_TO_BYTE(wSendPacketIdentifier); (* Variable Header: Packet Identifier LSB *) 1373 | 1374 | (* Payload *) 1375 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(SHR(LEN(sUnsubscribeTopic), 8)); (* Payload: Topic Length MSB *) 1376 | iSendIndexCount := iSendIndexCount + 1; abOutBuffer[iSendIndexCount] := INT_TO_BYTE(LEN(sUnsubscribeTopic)); (* Payload: Topic Length LSB *) 1377 | 1378 | (* Topic Filter *) 1379 | iSendIndexCount := iSendIndexCount + 1; SysMemMove(ADR(abOutBuffer[iSendIndexCount]), ADR(sUnsubscribeTopic), LEN(sUnsubscribeTopic)); 1380 | iSendIndexCount := iSendIndexCount + LEN(sUnsubscribeTopic) - 1; 1381 | 1382 | (* Send UNSUBSCRIBE *) 1383 | iSendIndexCount := iSendIndexCount + 1; (* +1 because abOutBuffer is 0 based *) 1384 | diSendResult := SysSockSend(diSocket, ADR(abOutBuffer), iSendIndexCount, 0); 1385 | xAwaitingUNSUBACK := TRUE; 1386 | 1387 | (* Send UNSUBSCRIBE successful *) 1388 | IF diSendResult = iSendIndexCount THEN 1389 | q_sLog := 'Sending UNSUBSCRIBE successfully'; 1390 | q_xError := FALSE; 1391 | q_wState := 2#0110100001; (* Next Step: IDLE (Wait for UNSUBACK) *) 1392 | 1393 | (* Send UNSUBSCRIBE failed *) 1394 | ELSE 1395 | q_sLog := 'Sending UNSUBSCRIBE failed'; 1396 | q_xError := TRUE; 1397 | q_wState := 2#1110100001; (* Next Step: no retry for QoS0 (IDLE) *) 1398 | diSendResult := 0; 1399 | END_IF 1400 | END_ACTION 1401 | 1402 | ACTION AC_TimeoutCONNECT: 1403 | (* Handle CONNECT Timeout 1404 | ----------------------------------------- 1405 | 1406 | Notes: 1407 | - If the Client does not receive a CONNACK Packet from the Server within a reasonable amount of time, the Client SHOULD close the Network Connection. A "reasonable" amount of time depends on the type of application and the communications infrastructure. 1408 | *) 1409 | 1410 | fbTimeoutCONNECT(IN := xAwaitingCONNACK, PT := i_tCommTimeout); 1411 | IF fbTimeoutCONNECT.Q THEN 1412 | diSendResult := 0; 1413 | q_sLog := 'CONNECT Timeout, CONNACK not received'; 1414 | q_xError := TRUE; 1415 | q_wState := 2#1100010111; (* Next Step: Close Socket *) 1416 | END_IF 1417 | END_ACTION 1418 | 1419 | ACTION AC_TimeoutPINGREQ: 1420 | (* Handle PINGREQ Timeout 1421 | ---------------------------------------- 1422 | 1423 | Notes: 1424 | - If a Client does not receive a PINGRESP Packet within a reasonable amount of time after it has sent a PINGREQ, it SHOULD close the Network Connection to the Server. 1425 | *) 1426 | 1427 | fbTimeoutPINGREQ(IN := xAwaitingPINGRESP, PT := i_tCommTimeout); 1428 | 1429 | (* PINGREQ Timeout *) 1430 | IF fbTimeoutPINGREQ.Q THEN 1431 | diSendResult := 0; 1432 | q_sLog := 'PINGREQ Timeout, PINGRESP not received'; 1433 | q_xError := TRUE; 1434 | q_wState := 2#1111000010; (* Next Step: Close Socket *) 1435 | END_IF 1436 | END_ACTION 1437 | 1438 | ACTION AC_TimeoutSUBSCRIBE: 1439 | (* Handle SUBSCRIBE Timeout 1440 | ------------------------------------------- 1441 | *) 1442 | 1443 | fbTimeoutSUBSCRIBE(IN := xAwaitingSUBACK, PT := i_tCommTimeout); 1444 | IF fbTimeoutSUBSCRIBE.Q THEN 1445 | diSendResult := 0; 1446 | q_sLog := 'SUBSCRIBE Timeout, SUBACK not received'; 1447 | q_xError := TRUE; 1448 | q_wState := 2#1110000010; (* Next Step: Disconnect *) 1449 | END_IF 1450 | END_ACTION 1451 | 1452 | ACTION AC_TimeoutUNSUBSCRIBE: 1453 | (* Handle UNSUBSCRIBE Timeout 1454 | ------------------------------------------------ 1455 | 1456 | Notes: 1457 | - The Server MUST respond to an UNSUBSUBCRIBE request by sending an UNSUBACK packet. 1458 | - Even where no Topic Subscriptions are deleted, the Server MUST respond with an UNSUBACK [MQTT-3.10.4-5]. 1459 | *) 1460 | 1461 | fbTimeoutUNSUBSCRIBE(IN := xAwaitingUNSUBACK, PT := i_tCommTimeout); 1462 | IF fbTimeoutUNSUBSCRIBE.Q THEN 1463 | diSendResult := 0; 1464 | q_sLog := 'UNSUBSCRIBE Timeout, UNSUBACK not received'; 1465 | q_xError := TRUE; 1466 | q_wState := 2#1110100010; (* Next Step: Disconnect *) 1467 | END_IF 1468 | END_ACTION 1469 | 1470 | 1471 | 1472 | 1473 | 1474 | 1475 | 1476 | 1477 | 1478 | 1479 | 1480 | 1481 | 1482 | 1483 | 1484 | 1485 | 1486 | 1487 | 1488 | 1489 | 1490 | 1491 | 1492 | 1493 | 1494 | 1495 | 1496 | 1497 | 1498 | 1499 | 1500 | 1501 | (* @NESTEDCOMMENTS := 'Yes' *) 1502 | (* @PATH := '\/MQTT_Client' *) 1503 | (* @OBJECTFLAGS := '0, 8' *) 1504 | (* @SYMFILEFLAGS := '2048' *) 1505 | FUNCTION_BLOCK FB_Random 1506 | VAR_INPUT 1507 | A: DWORD; 1508 | B: DWORD; 1509 | V: DWORD; 1510 | END_VAR 1511 | VAR_OUTPUT 1512 | dwRandom: DWORD; 1513 | END_VAR 1514 | VAR 1515 | M: DWORD; 1516 | Q: DWORD; 1517 | END_VAR 1518 | (* @END_DECLARATION := '0' *) 1519 | Q := Q + 2; 1520 | 1521 | M := 25; 1522 | dwRandom := (A+V+B+Q) MOD M + 65; 1523 | END_FUNCTION_BLOCK 1524 | -------------------------------------------------------------------------------- /mqtt.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lugaresi/codesys2-mqtt-library/423c738789d1d5704c472f04592b37ca085455fd/mqtt.lib --------------------------------------------------------------------------------