├── LICENSE ├── Net └── MQTT │ ├── MessageHandler.xml │ ├── Auxiliary │ ├── SubscriptionTopic.xml │ ├── MessageCounter.xml │ ├── Subscription.xml │ ├── MessageStatus.xml │ └── TaskList.xml │ ├── Message.xml │ ├── MessageStore.xml │ ├── Client.xml │ └── Agent.xml ├── README.md └── MQTTOptions.xml /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 InterSystems Corp. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Net/MQTT/MessageHandler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 64138,59002.145789 6 | 64131,56764.693636 7 | 8 | 9 | Net.MQTT.Agent everytime, when it receives 11 | a new incoming message. 12 | 13 | The name of this class and method have to be set in the OnMessage property of the Net.MQTT.Client object.]]> 14 | 1 15 | 16 | %Status 17 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Net/MQTT/Auxiliary/SubscriptionTopic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Net.MQTT.Client and its Net.MQTT.Agent. 6 |

Provides the topic list, which the primary content of SUBSCRIBE and UNSUBSCRIBE messages.

]]> 7 | %Persistent 8 | 64152,52574.606659 9 | 64050,80453.857656 10 | 11 | 12 | Subscription,Topic 13 | 1 14 | 15 | 16 | 17 | Net.MQTT.Auxiliary.Subscription 18 | parent 19 | Topics 20 | 1 21 | 22 | 23 | 24 | %String 25 | 1 26 | 27 | 28 | 29 | 30 | %Integer 31 | 0 32 | 33 | 34 | 35 | 36 | %Integer 37 | 38 | 39 | 40 | 41 | %Library.CacheStorage 42 | {%%PARENT}("Topics") 43 | SubscriptionTopicDefaultData 44 | ^Net.MQTT.Auxil93C.SubscriptionC("Topics") 45 | ^Net.MQTT.Au93C.Subscriptio1311I 46 | ^Net.MQTT.Au93C.Subscriptio1311S 47 | 48 | 49 | %%CLASSNAME 50 | 51 | 52 | Topic 53 | 54 | 55 | QoSLevel 56 | 57 | 58 | GrantedQoS 59 | 60 | 61 | 62 | 63 |
64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # COS-MQTTClient 2 | MQTT Client implemented in Caché Object Script 3 | 4 | Fully functional sample implementation to demonstrate all kind of MQTT Client activities (publish, subscribe). 5 | For more information regarding the MQTT protocol visit the http://mqtt.org website, 6 | or examine the OASIS standard specification: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html 7 | 8 | Questions, bug reports, recommendations are highly welcome to Attila.Toth@InterSystems.com 9 | 10 | Installation 11 | - Download and extract the project to a directory (\). 12 | - Import and compile all XML files onto one of your Caché / Ensemble namespaces, with the following command: 13 | Do $System.OBJ.LoadDir("\","c",.err,1,.list) 14 | 15 | Usage 16 | - For detailed information- including usage examples- look into the class documentation of Net.MQTT.Client 17 | 18 | There are a few known (and maybe some unknown) limitations: 19 | - It's not prepared for SSL / TLS communication. 20 | - TCP/IP based only, cannot be used with web-socket based MQTT Brokers. 21 | - Communication of the Net.MQTT.Client and background Net.MQTT.Agent instances is limited to the tasks to be done / completed. 22 | This could be enriched with for example status information of the Agent, which would make it easier to monitor the background job from the Client interface 23 | and check its health status in certain scenarios. 24 | - Incoming and outgoing messages are simply logged into the Net.MQTT.MessageStore class. No any checks for duplicate messages are done 25 | (e.g.: when an incoming message is repeated by the Broker, due to a lost acknowledge). 26 | - For incoming QoS Level 2 messages no retry mechanism is implemented: if no PUBREL message would arrive for the PUBREC acknowledge, 27 | the message simply remains in the Net.MQTT.Aux.MessageStatus table (registered with a "waiting for PUBREL" status). 28 | 29 | Version history 30 | - 0.9: Initial version 31 | - 0.92: Adding SSL support and OnMessage callback 32 | - 0.95: 33 | * Adding trace levels to better control the traced content. 34 | * Net.MQTT.Client is now persistent. This is useful, when- for example- a REST interface is built on the top of the MQTT client. 35 | * Agent automatically tries to re-connect (once only), if the TCP connection to the broker seems to be broken. 36 | -------------------------------------------------------------------------------- /Net/MQTT/Message.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Net.MQTT.Client methods.]]> 6 | %RegisteredObject,%XML.Adaptor 7 | 64131,57977.885311 8 | 64043,42166.491302 9 | 10 | 11 | Net.MQTT.Client and Net.MQTT.Agent objects 13 | is also recorded in the Message object. Usually this property remains empty though. ]]> 14 | %String 15 | 16 | 17 | 18 | 19 | 20 | Message Identifier. Usually it doesn't have to be set, when used as an input parameter. 21 | %Integer 22 | 23 | 24 | 25 | 26 | 27 | %String 28 | 29 | 30 | 31 | %String 32 | 33 | 34 | 35 | 36 | 38 |
  • 0 - no guaranteed message delivery.
  • 39 |
  • 1 - at least once delivery (simple acknowledge).
  • 40 |
  • 2 - exactly once delivery (two level acknowledges).
  • 41 | ]]>
    42 | %Integer 43 | 44 |
    45 | 46 | 47 | For details see QoSLevel]]> 50 | %Integer 51 | 52 | 53 | 54 | 55 | 56 | In some message flows (publish, subscribe) may show, whether the server is expected to keep the message, 57 | even after the client disconnects. 58 | %Boolean 59 | 60 | 61 |
    62 | -------------------------------------------------------------------------------- /MQTTOptions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0) 11 | #define MQTTTraceWarnings (..traceLevel > 1) 12 | #define MQTTTraceInfo (..traceLevel > 2) 13 | #define MQTTTraceDebug (..traceLevel > 3) 14 | 15 | #define MQTTTrace(%type,%dir,%msg) Set:(..trace) var=..traceTarget,@var@($I(@var),$ZDT($ZU(188),3,1,3),%dir,$Case(%type,"":"UNKNOWN",:%type))=%msg 16 | #define MQTTTraceCond(%type,%dir,%msg,%cond) Set:(..trace && (%cond)) var=..traceTarget,@var@($I(@var),$ZDT($ZU(188),3,1,3),%dir,$Case(%type,"":"UNKNOWN",:%type))=%msg 17 | #define MQTTTraceIN(%type,%msg,%level) $$$MQTTTraceCond(%type,"I",%msg,%level) 18 | #define MQTTTraceOUT(%type,%msg,%level) $$$MQTTTraceCond(%type,"O",%msg,%level) 19 | #define MQTTTraceWRN(%msg) $$$MQTTTraceCond("WARNING","S",%msg,$$$MQTTTraceWarnings) 20 | #define MQTTTraceERR(%msg) $$$MQTTTraceCond("ERROR","S",%msg,$$$MQTTTraceErrors) 21 | #define MQTTTraceINF(%msg) $$$MQTTTraceCond("INFO","S",%msg,$$$MQTTTraceInfo) 22 | #define MQTTTraceDEB(%msg) $$$MQTTTraceCond("DEBUG","S",%msg,$$$MQTTTraceDebug) 23 | 24 | #; Fixed header 25 | #define MQTTRetain 1 26 | #define MQTTQoS1 2 27 | #define MQTTQoS2 4 28 | #define MQTTDup 8 29 | 30 | #; Message types 31 | #define MQTTCONNECT 16 32 | #define MQTTCONNACK 32 33 | #define MQTTPUBLISH 48 34 | #define MQTTPUBACK 64 35 | #define MQTTPUBREC 80 36 | #define MQTTPUBREL 96 37 | #define MQTTPUBCOMP 112 38 | #define MQTTSUBSCRIBE 128 39 | #define MQTTSUBACK 144 40 | #define MQTTUNSUBSCRIBE 160 41 | #define MQTTUNSUBACK 176 42 | #define MQTTPINGREQ 192 43 | #define MQTTPINGRESP 208 44 | #define MQTTDISCONNECT 224 45 | #define MQTTMsgType(%code) $Case(%code,$$$MQTTCONNECT:"CONNECT",$$$MQTTCONNACK:"CONNACK",$$$MQTTPUBLISH:"PUBLISH",$$$MQTTPUBACK:"PUBACK",$$$MQTTPUBREC:"PUBREC",$$$MQTTPUBREL:"PUBREL",$$$MQTTPUBCOMP:"PUBCOMP",$$$MQTTSUBSCRIBE:"SUBSCRIBE",$$$MQTTSUBACK:"SUBACK",$$$MQTTUNSUBSCRIBE:"UNSUBSCRIBE",$$$MQTTUNSUBACK:"UNSUBACK",$$$MQTTPINGREQ:"PINGREQ",$$$MQTTPINGRESP:"PINGRESP",$$$MQTTDISCONNECT:"DISCONNECT",:"Unkown") 46 | 47 | #; CONNECT 48 | #define MQTTCleanSession 2 49 | #define MQTTWillFlag 4 50 | #define MQTTWillQoS1 8 51 | #define MQTTWillQoS2 16 52 | #define MQTTWillRetain 32 53 | #define MQTTPasswordFlag 64 54 | #define MQTTUsernameFlag 128 55 | 56 | #; SUBSCRIBE 57 | #define MQTTSubQoS1 1 58 | #define MQTTSubQoS2 2 59 | 60 | ]]> 61 | 62 | -------------------------------------------------------------------------------- /Net/MQTT/MessageStore.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Net.MQTT.Client and Net.MQTT.Agent instances. 6 |

    This could be implemented different ways. For example: this implementation does not care about duplicate messages.

    7 |

    In this sample MQTT Client implementation this class just stores all the incoming and outgoing messages unconditionally.

    8 |

    For more documentation of message properties see: Net.MQTT.Message.

    ]]> 9 | %Persistent 10 | 64072,57252.367978 11 | 64050,80453.857656 12 | 13 | 14 | ClientId,Topic 15 | 16 | 17 | 18 | %String 19 | 1 20 | 21 | 22 | 23 | 24 | %String 25 | 1 26 | 27 | 28 | 29 | 30 | I is for input (subscribe), O is for output (publish). ]]> 32 | %String 33 | "I" 34 | 1 35 | 36 | 37 | 38 | 39 | %Integer 40 | 41 | 42 | 43 | 44 | 45 | %String 46 | 47 | 48 | 49 | 50 | %Integer 51 | 52 | 53 | 54 | 55 | %Integer 56 | 57 | 58 | 59 | 60 | %Boolean 61 | 62 | 63 | 64 | %TimeStamp 65 | $ZDateTime($ZTimestamp, 3) 66 | 1 67 | 68 | 69 | 70 | %Library.CacheStorage 71 | ^Net.MQTT.MessageStoreD 72 | MessageStoreDefaultData 73 | ^Net.MQTT.MessageStoreD 74 | ^Net.MQTT.MessageStoreI 75 | ^Net.MQTT.MessageStoreS 76 | 77 | 78 | %%CLASSNAME 79 | 80 | 81 | ClientId 82 | 83 | 84 | Topic 85 | 86 | 87 | MessageId 88 | 89 | 90 | Content 91 | 92 | 93 | QoSLevel 94 | 95 | 96 | GrantedQoS 97 | 98 | 99 | Retain 100 | 101 | 102 | CreatedAt 103 | 104 | 105 | Direction 106 | 107 | 108 | 109 | 110 |
    111 | -------------------------------------------------------------------------------- /Net/MQTT/Auxiliary/MessageCounter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MQTT standard only specifies that Message Identifiers must be unique among the currently in-flight messages. 7 | This class helps to initialize a new message sequence for each Client (Net.MQTT.Client) / Agent (Net.MQTT.Agent) pairs, 8 | based on the ClientId. This also means that ClientId must be unique among parallel 9 | Client instances currently running in the same database.

    ]]>
    10 | %Persistent 11 | 64138,82668.950865 12 | 64070,61478.847771 13 | 14 | 15 | 1 16 | 1 17 | ClientId 18 | 19 | 20 | 21 | %String 22 | 1 23 | 24 | 25 | 26 | %Integer 27 | 0 28 | 29 | 30 | 31 | 32 | 33 | Net.MQTT.Client
    instances to create a new Message Identifier sequence. 35 | for a ClientId (which must be unqique among the currently running Clients). ]]> 36 | 1 37 | pClientId:%String 38 | %Status 39 | 57 | 58 | 59 | 60 | Net.MQTT.Client instances. 62 |

    Returns the next available Message Idenfier for a specific ClientId.

    63 |

    Currently MQTT Message Identifiers must fall between 1 and 65535. After 65535 the counter simply turns into 1 again, 64 | simply assuming that this cannot lead to a collision among current in-flight messages.

    ]]>
    65 | 1 66 | 67 | %Integer 68 | 76 |
    77 | 78 | 79 | Net.MQTT.Client instances. 81 |

    Deletes the corresponding counter, when a Client is about to stop (more specifically: when its Net.MQTT.Agent is stopped).

    ]]>
    82 | 1 83 | expression 84 | pClientId:%String 85 | %Status 86 | 88 |
    89 | 90 | 91 | %Library.CacheStorage 92 | ^Net.MQTT.Aux93C.MessageCounterD 93 | MessageCounterDefaultData 94 | ^Net.MQTT.Aux93C.MessageCounterD 95 | ^Net.MQTT.Aux93C.MessageCounterI 96 | ^Net.MQTT.Aux93C.MessageCounterS 97 | 98 | listnode 99 | 100 | 101 | %%CLASSNAME 102 | 103 | 104 | LastMessageId 105 | 106 | 107 | 108 | 109 |
    110 | -------------------------------------------------------------------------------- /Net/MQTT/Auxiliary/Subscription.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Net.MQTT.Client and its Net.MQTT.Agent to keep track of 6 | SUBSCRIBE and UNSUBSCRIBE message contents. 7 |

    These messages must have a unique Message Idenfier among current in-flight messages (per Client), 8 | therefore all instances are dropped as soon as the corresponding message flow is completed.

    ]]> 9 | MQTTOptions 10 | %Persistent 11 | 64138,82815.465679 12 | 64050,80453.857656 13 | 14 | 15 | ClientId,MessageId 16 | 1 17 | 18 | 19 | 20 | %String 21 | 1 22 | 23 | 24 | 25 | 26 | %Integer 27 | 1 28 | 29 | 30 | 31 | 32 | 33 | Net.MQTT.Auxiliary.SubscriptionTopic 34 | children 35 | Subscription 36 | 1 37 | 38 | 39 | 40 | 1 41 | 42 | Net.MQTT.Auxiliary.Subscription 43 | 51 | 52 | 53 | 54 | 1 55 | expression 56 | pId:%String 57 | %Status 58 | 60 | 61 | 62 | 63 | pTopic:Net.MQTT.Message 64 | %Status 65 | 74 | 75 | 76 | 77 | 1 78 | 79 | %Integer 80 | 89 | 90 | 91 | 92 | 1 93 | 94 | %ListOfObjects 95 | ELEMENTTYPE="Net.MQTT.Message" 96 | 113 | 114 | 115 | 116 | 1 117 | pClientId:%String,pMessageId:%String,pTopicNr:%Integer,pGrantedQoS:%Integer 118 | %Status 119 | obj.Topics.Count()) { 123 | Set tSC = $$$ERROR($$$GeneralError, "Inavlid topic number (" _ pTopicNr _ " is not between 1 and " _ obj.Topics.Count() _ ")") 124 | } 125 | Else { 126 | Set topic = obj.Topics.GetAt(pTopicNr) 127 | Set topic.GrantedQoS = pGrantedQoS 128 | Set tSC = topic.%Save() 129 | } 130 | } 131 | 132 | Quit tSC 133 | ]]> 134 | 135 | 136 | 137 | 138 | %Status 139 | " _ ..Topics.Count() _ ")") 144 | } 145 | Else { 146 | For i = 1: 1: pTopics.Count() { 147 | Set pTopics.GetAt(i).GrantedQoS = ..Topics.GetAt(i).GrantedQoS 148 | } 149 | } 150 | } 151 | 152 | Quit tSC 153 | ]]> 154 | 155 | 156 | 157 | %Library.CacheStorage 158 | ^Net.MQTT.Auxil93C.SubscriptionD 159 | SubscriptionDefaultData 160 | ^Net.MQTT.Auxil93C.SubscriptionD 161 | ^Net.MQTT.Auxil93C.SubscriptionI 162 | ^Net.MQTT.Auxil93C.SubscriptionS 163 | 164 | listnode 165 | 166 | 167 | %%CLASSNAME 168 | 169 | 170 | ClientId 171 | 172 | 173 | MessageId 174 | 175 | 176 | 177 | 178 |
    179 | -------------------------------------------------------------------------------- /Net/MQTT/Auxiliary/MessageStatus.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This class is used only internally by the Net.MQTT.Client and Net.MQTT.Agent classes. 7 |

    Messages are delete from this class, as soon as the corresponding message flow is completed.

    ]]>
    8 | %Persistent 9 | 64138,82698.167565 10 | 64050,79985.782311 11 | 12 | 13 | ClientId,Direction,MessageId 14 | 1 15 | 16 | 17 | 18 | %String 19 | 1 20 | 21 | 22 | 23 | 24 | %String 25 | 1 26 | 27 | 28 | 29 | 30 | %Integer 31 | 0 32 | 33 | 34 | 35 | 36 | 37 | %String 38 | 1 39 | 40 | 41 | 42 | 43 | 1 44 | expression 45 | pClientId:%String,pMessageId:%String,pQoSLevel:%Integer 46 | %Status 47 | 49 | 50 | 51 | 52 | 1 53 | expression 54 | pClientId:%String,pMessageId:%String 55 | %Boolean 56 | 58 | 59 | 60 | 61 | 1 62 | expression 63 | pClientId:%String,pMessageId:%String 64 | %Status 65 | 67 | 68 | 69 | 70 | 1 71 | expression 72 | pClientId:%String,pMessageId:%String 73 | %Status 74 | 76 | 77 | 78 | 79 | 1 80 | expression 81 | pClientId:%String,pMessageId:%String 82 | %Status 83 | 85 | 86 | 87 | 88 | 1 89 | expression 90 | pClientId:%String,pMessageId:%String 91 | %Status 92 | 94 | 95 | 96 | 97 | 1 98 | expression 99 | pClientId:%String,pMessageId:%String 100 | %Status 101 | 103 | 104 | 105 | 106 | 1 107 | expression 108 | pClientId:%String,pMessageId:%String 109 | %Status 110 | 112 | 113 | 114 | 115 | 1 116 | expression 117 | pClientId:%String,pMessageId:%String 118 | %Status 119 | 121 | 122 | 123 | 124 | 1 125 | expression 126 | pClientId:%String,pMessageId:%String 127 | %Status 128 | 130 | 131 | 132 | 133 | 1 134 | expression 135 | pClientId:%String,pMessageId:%String 136 | %Status 137 | 139 | 140 | 141 | 142 | 1 143 | 1 144 | pClientId:%String,pMessageId:%String,pDirection:%String,pStatus:%String 145 | 1 146 | %Status 147 | 167 | 168 | 169 | 170 | 1 171 | 1 172 | pClientId:%String,pDirection:%String,pMessageId:%String,pStatus:%String 173 | 1 174 | %Status 175 | 196 | 197 | 198 | 199 | 1 200 | 1 201 | pClientId:%String,pDirection:%String,pMessageId:%String 202 | 1 203 | %Status 204 | 224 | 225 | 226 | 227 | %Library.CacheStorage 228 | ^Net.MQTT.Auxi93C.MessageStatusD 229 | MessageStatusDefaultData 230 | ^Net.MQTT.Auxi93C.MessageStatusD 231 | ^Net.MQTT.Auxi93C.MessageStatusI 232 | ^Net.MQTT.Auxi93C.MessageStatusS 233 | 234 | listnode 235 | 236 | 237 | %%CLASSNAME 238 | 239 | 240 | ClientId 241 | 242 | 243 | Direction 244 | 245 | 246 | MessageId 247 | 248 | 249 | Status 250 | 251 | 252 | 253 |
    254 |
    255 | -------------------------------------------------------------------------------- /Net/MQTT/Auxiliary/TaskList.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Net.MQTT.Client and its Net.MQTT.Agent.]]> 6 | %Persistent 7 | 64138,82875.607597 8 | 64051,66904.675697 9 | 10 | 11 | connectionId property of the corresponding Net.MQTT.Client and Net.MQTT.Agent objects.]]> 13 | %String 14 | 1 15 | 16 | 17 | 18 | 19 | Task-dependent context identifier. The cretor and processor of the task must agree on the meaning of this string. 20 | %String 21 | 1 22 | 23 | 24 | 25 | 26 | 27 | List of actions, which require an event-driven interface. 28 | %String 29 | 1 30 | 31 | 32 | 33 | 34 | 36 |
  • 0 - Pending, waiting for being processed.
  • 37 |
  • 1 - In progress, the Net.MQTT.Agent already picked up the task.
  • 38 |
  • 2 - Done, and kept for debugging purposes.
  • 39 | ]]>
    40 | %Integer 41 | 0 42 | 1 43 | 44 | 45 |
    46 | 47 | 48 | pMessageId) of a client connection is waiting for an event. 50 |

    The %SYSTEM.Event API is used to implement this, for detailed explanation of the return value, see that class documentation!

    ]]>
    51 | 1 52 | pConnectionId:%String,pMessageId:%String,pStatus:%String,pTimeout:%Integer=-1 53 | %List 54 | 61 |
    62 | 63 | 64 | pMessageId) of a client connection. 66 |

    The %SYSTEM.Event API is used to implement this, for detailed explanation of the return value, see that class documentation!

    ]]>
    67 | 1 68 | pConnectionId:%String,pMessageId:%String,pStatus:%String,pContent:%String="" 69 | %Integer 70 | 74 |
    75 | 76 | 77 | The %SYSTEM.Event API is used to implement this, for detailed explanation of the return value, see that class documentation!

    ]]>
    80 | 1 81 | 82 | %List 83 | 93 |
    94 | 95 | 96 | pStatusCode

    is the return status of the method, which processed the task.

    99 |

    pDebugMode

    shows, whether the Net.MQTT.Auxiliary.TaskList object has to be kept for debugging purposes, 100 | or can be deleted.

    ]]>
    101 | 1 102 | pConnectionId:%String,pTaskId:%String,pStatusCode:%Status,pDebugMode:%Boolean=0 103 | %Status 104 | 110 |
    111 | 112 | 113 | 1 114 | 115 | %String 116 | 124 | 125 | 126 | 127 | 1 128 | pConnectionId:%String,*pTaskId:%String,*pContextId:%String,*pAction:%String 129 | %Status 130 | 160 | 161 | 162 | 163 | 1 164 | pTaskId:%String 165 | %Status 166 | 178 | 179 | 180 | 181 | 1 182 | pTaskId:%String,pKeepIt:%Boolean=0 183 | %Status 184 | 204 | 205 | 206 | 207 | %Library.CacheStorage 208 | ^Net.MQTT.Auxiliary.TaskListD 209 | TaskListDefaultData 210 | ^Net.MQTT.Auxiliary.TaskListD 211 | ^Net.MQTT.Auxiliary.TaskListI 212 | ^Net.MQTT.Auxiliary.TaskListS 213 | 214 | 215 | %%CLASSNAME 216 | 217 | 218 | ConnectionId 219 | 220 | 221 | ContextId 222 | 223 | 224 | Action 225 | 226 | 227 | Status 228 | 229 | 230 | 231 | 232 |
    233 | -------------------------------------------------------------------------------- /Net/MQTT/Client.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Caché Object Script MQTT Client sample implementation (version: 0.9). 6 | 7 |

    For more information regarding the MQTT protocol visit the MQTT Org. website, 8 | or examine the OASIS standard specification.

    9 | 10 |

    Fully functional to demonstrate all kind of MQTT Client activities, but not a fully-featured client.

    11 | 12 |

    There are a few known (and maybe some unknown) limitations:

      13 |
    • It's not prepared for SSL / TLS communication.
    • 14 |
    • TCP/IP based only, cannot be used with web-socket based MQTT Brokers.
    • 15 |
    • Communication of the Net.MQTT.Client and background Net.MQTT.Agent instances is limited to the 16 | tasks to be done / completed. This could be enriched with for example status information of the Agent, which would make 17 | it easier to monitor the background job from the Client interface and check its health status in certain scenarios.
    • 18 |
    • Incoming and outgoing messages are simply logged into the Net.MQTT.MessageStore class. No any checks for 19 | duplicate messages are done (when an incoming message is repeated by the Broker, due to a lost acknowledge).
    • 20 |
    • For incoming QoS Level 2 messages no retry mechanism is implemented: if no PUBREL message would arrive 21 | for the PUBREC acknowledge, the Agent will simply wait for it endless and the message remains in the 22 | Net.MQTT.Auxiliary.MessageStatus, registered with a waiting for PUBREL status.
    • 23 |

    24 |

    Questions, bug reports, recommendations are highly welcome to my Inbox.

    ]]>
    25 | MQTTOptions 26 | %Persistent,%XML.Adaptor 27 | 64152,50908.223153 28 | 64041,57924.914604 29 | 30 | 31 | 32 | Host name of the MQTT Broker. 33 | %String 34 | "localhost" 35 | 36 | 37 | 38 | 39 | Port number of the MQTT Broker. 40 | %String 41 | 1883 42 | 43 | 44 | 45 | 46 | SSL/TLS Configuration to be used to build a secured TCP Connection to the MQTT Broker. 47 | %String 48 | 49 | 50 | 51 | 52 | Timeout (in seconds) of building the TCP Connection to the MQTT Broker. 53 | %Integer 54 | 10 55 | 56 | 57 | 58 | Net.MQTT.Agent
    to complete a task.]]> 61 | %Integer 62 | 10 63 | 64 | 65 | 66 | It's the responsibility of the Net.MQTT.Agent to send a PING request at least in each intervals.

    ]]>
    69 | %Integer 70 | 60 71 |
    72 | 73 | 74 | This implementation is restricted multiple places to use unique Client IDs among the co-existing Net.MQTT.Client 77 | instances within the same database. This restriction is acceptable, as the MQTT Brokers may reject multiple TCP connections 78 | with the same Client ID.

    ]]>
    79 | %String 80 | 81 |
    82 | 83 | 84 | CONNECT message to the MQTT Broker. 86 |

    For details see the MQTT standard specification.

    ]]>
    87 | %Boolean 88 | 1 89 |
    90 | 91 | 92 | 93 | This object contains details of the "Last will and testament" of this Client - the message to be sent to subscribed Clients 94 | from the MQTT Broker in case, when the Broker's connection to this Client would be closed / lost. 95 | Net.MQTT.Message 96 | 97 | 98 | 99 | PUBLISH, PUBREL, SUBSCRIBE, UNSUBSCIBE) 101 | might be re-sent by the Client if no appropriate acknowledge can be received from the MQTT Broker within the specified timout intervals. 102 |
    This property controls the maximum number of repeated attempts.]]>
    103 | %Integer 104 | 2 105 |
    106 | 107 | 108 | PUBLISH, PUBREL, SUBSCRIBE, UNSUBSCIBE) 110 | might be re-sent by the Client if no appropriate acknowledge can be received from the MQTT Broker within the specified timout intervals. 111 |
    This property controls the wait interval (in seconds) within each repeated attempts.]]>
    112 | %Integer 113 | 2 114 |
    115 | 116 | 117 | Net.MQTT.Agent receives an incoming message. 119 |

    The required format of the OnMessage property is: Classname:MethodName. 120 | A sample implementation is provided in the Net.MQTT.MessageHandler.

    121 | 122 |

    If this property is empty, doesn't contain a valid classname/method name or the message handler method throws a runtime error, 123 | that event is logged in the traceTarget (if it's set).

    ]]>
    124 | %String 125 | "Net.MQTT.MessageHandler:OnMessage" 126 |
    127 | 128 | 129 | 130 | For debugging purposes this property can be set to any global variable names (e.g.: ^MQTT.Trace), 131 | in which case detailed trace information is logged into the specified global about the actions taken and also 132 | the incoming and outgoing TCP communication. 133 | %String 134 | 135 | 136 | 137 | %Boolean 138 | 1 139 | 140 | 141 | 142 | expression 143 | %Boolean 144 | 146 | 147 | 148 | 149 | traceTarget:
      151 |
    • 0 - no trace information
    • 152 |
    • 1 - errors only
    • 153 |
    • 2 - errors and warnings
    • 154 |
    • 3 - beyond errors and warnings, information about the actions made by the MQTT Client and Agent
    • 155 |
    • 4 - in this case even some originally temporary information is not deleted from auxiliary tables (e.g.: Net.MQTT.Auxiliary.TaskList
    • 156 |
    ]]> 157 | %Integer 158 | 3 159 | 1 160 | 161 | 162 | 163 | 164 | 165 | *** DEPRECATED ***: Use traceLevel instead. 167 | For debugging purposes this property can be set to 1, in which case some originally temporary information is not deleted from 168 | auxiliary tables (for example: Net.MQTT.Auxiliary.TaskList). ]]> 169 | %Integer 170 | 0 171 | 172 | 173 | 174 | Net.MQTT.Client and the related Net.MQTT.Agent instance, 176 | and it is used in event-driven communication of the foreground Client and the background Agent processes. 177 |

    This is meant to be set automatically by the Client, do not set it directly!

    ]]>
    178 | %String 179 | $System.Util.CreateGUID() 180 |
    181 | 182 | 183 | Net.MQTT.Agent.]]> 185 | %String 186 | 187 | 188 | 189 | Starts a Net.MQTT.Agent instance, which is responsible for building the TCP connection and 192 | managing the communication with the broker.

    ]]>
    193 | pUsername:%String="",pPassword:%String="" 194 | %Status 195 | has already been started") 199 | } 200 | Else { 201 | Set tSC = ##class(Net.MQTT.Auxiliary.MessageCounter).CreateCounter(..ClientId) 202 | Set:$$$ISOK(tSC) tSC = ..XMLExportToString(.data, "root") 203 | If $$$ISOK(tSC) { 204 | Do $System.Event.Create("^MQTT.Connect(""" _ ..connectionId _ """)") 205 | JOB ##class(Net.MQTT.Agent).StartListening(data, pUsername, pPassword) 206 | Set ..agent = $ZChild 207 | Set ret = $System.Event.WaitMsg("^MQTT.Connect(""" _ ..connectionId _ """)", (..ConnectTimeout * 1.5)) 208 | If $ListGet(ret, 1) < 1 { 209 | Set tSC = $$$ERROR($$$GeneralError, "MQTT Agent has not connected within the timeout period") 210 | } 211 | ElseIf $ListGet(ret, 2) '= "" { 212 | Set tSC = $$$ERROR($$$GeneralError, $ListGet(ret, 2)) 213 | } 214 | 215 | If $$$ISERR(tSC) { 216 | Do ..KillAgent() 217 | } 218 | } 219 | } 220 | Quit tSC 221 | ]]> 222 |
    223 | 224 | 225 | DISCONNECT message and closing the TCP connection to the MQTT Broker. 227 |

    If this method completes successfully, the Net.MQTT.Client instance is re-usable and 228 | its StartAgent method can be called again.

    ]]>
    229 | %Status 230 | 248 |
    249 | 250 | 251 | StopAgent method. 253 |

    In some rare cases, when the Agent cannot be stopped normally (by sending a DISCONNECT message), 254 | this can still be used to terminate the Agent's background job and perform important maintenance steps.

    ]]> 255 | %Status 256 | 272 | 273 | 274 | 275 | 278 | Sends the SUBSCRIBE message and if no SUBACK has been received in the timeout period, 279 | it can retry the SUBSCRIBE according to the Retry and RetryInterval settings, 280 | before giving up and returning with an error. 281 | 282 |
    283 | If the acknowledge is received, the GrantedQoS of each elements in the pTopics list should contain 284 | the QoS Level assured by the MQTT broker for the corresponding topic. 285 |
    286 |

    Usage example:

    287 | 288 | Set tpc = ##class(Net.MQTT.Message).%New() 289 | Set tpc.Topic = "$SYS/broker/load/bytes/#" 290 | Set tpc.QoSLevel = 2 291 | Set topics = ##class(%ListOfObjects).%New() 292 | Do topics.Insert(tpc) 293 | 294 | Set client = ##class(Net.MQTT.Client).%New() 295 | Set client.Host = "test.mosquitto.org" 296 | Set client.ClientId = "My1stMQTTClient" 297 | Set client.traceTarget = "^MQTT.Trace" 298 | Set sc = client.StartAgent() If ('sc) { Do $System.OBJ.DisplayError(sc) } 299 | Set sc = client.Subscribe(.topics) If ('sc) { Do $System.OBJ.DisplayError(sc) } 300 | If (''sc) { Set t=topics.GetAt(1) Write "Topic:'"_t.Topic_"'; Expected QoS:"_t.QoSLevel_"; Granted QoS:"_t.GrantedQoS_";", ! } 301 | ; wait a few seconds to receive some messages from the broker 302 | Set sc = client.Unsubscribe(.topics) If ('sc) { Do $System.OBJ.DisplayError(sc) } 303 | Set sc = client.StopAgent() If ('sc) { Do $System.OBJ.DisplayError(sc) } 304 | ZWrite ^MQTT.Trace 305 | ]]>
    306 | 307 | %Status 308 | has not been started yet") 312 | } 313 | If '$IsObject(pTopics) || (pTopics.Count() < 1) { 314 | Quit $$$ERROR($$$GeneralError, "At least one topic must be specified to subscribe for") 315 | } 316 | 317 | Set msgId = ##class(Net.MQTT.Auxiliary.MessageCounter).NextMessageId(..ClientId, .tSC) 318 | If $$$ISERR(tSC) { 319 | $$$MQTTTraceERR($System.Status.GetErrorText(tSC)) 320 | Quit $$$ERROR($$$GeneralError, "Message ID for SUBSCRIBE message cannot be created") 321 | } 322 | 323 | TStart 324 | TRY { 325 | Set key = "", cnt = 0 326 | Set sub = ##class(Net.MQTT.Auxiliary.Subscription).CreateSubscription(..ClientId, msgId, .tSC) 327 | While $$$ISOK(tSC) { 328 | Set topic = pTopics.GetNext(.key) Quit:(key = "") 329 | Set tSC = sub.AddTopic(topic) 330 | } 331 | } 332 | CATCH ex { 333 | Set tSC = ex.AsStatus() 334 | } 335 | If $$$ISOK(tSC) { TCommit } 336 | Else { TRollback } 337 | 338 | Set retry = ..Retry, retcnt = 0, success = 0 339 | While $$$ISOK(tSC) && (success '= 1) { 340 | Set ret = ##class(Net.MQTT.Auxiliary.TaskList).WaitTask(..connectionId, msgId _ $Select(retcnt: ":" _ retcnt, 1: ""), "SUBSCRIBE", ..ReadTimeout, .tSC) 341 | If $$$ISOK(tSC) { 342 | If $ListGet(ret, 1) < 1 { 343 | Set tSC = $$$ERROR($$$GeneralError, "MQTT Agent has not succeeded within the timeout period (" _ $ListGet(ret, 1) _ ")") 344 | } 345 | ElseIf $ListGet(ret, 2) '= "" { 346 | Set tSC = $$$ERROR($$$GeneralError, $ListGet(ret, 2)) 347 | } 348 | Else { 349 | Set ack = ##class(Net.MQTT.Auxiliary.TaskList).WaitAck(..connectionId, msgId, "SUBACK", ..ReadTimeout) 350 | Set success = $ListGet(ack, 1) 351 | If (success = 0) && (retry > 0) { 352 | $$$MQTTTraceERR("MQTT Agent has not received acknowledge within the timeout period (remaining reties: " _ retry _ ")") 353 | Hang ..RetryInterval 354 | Set retry = retry - 1, retcnt = retcnt + 1 355 | } 356 | ElseIf success < 1 { 357 | Set tSC = $$$ERROR($$$GeneralError, "MQTT Agent has not received acknowledge within the timeout period (" _ success _ ")") 358 | } 359 | ElseIf $ListGet(ack, 2) '= "" { 360 | Set tSC = $$$ERROR($$$GeneralError, $ListGet(ack, 2)) 361 | } 362 | Else { 363 | Set tSC = sub.RetrieveAck(.pTopics) 364 | } 365 | } 366 | } 367 | } 368 | 369 | If $IsObject(sub) { 370 | Set subid = sub.%Id(), sub = "" 371 | Set tSC = $System.Status.AppendStatus(tSC, ##class(Net.MQTT.Auxiliary.Subscription).DropSubscription(subid)) 372 | } 373 | 374 | Quit tSC 375 | ]]> 376 |
    377 | 378 | 379 | 382 | Sends the UNSUBSCRIBE message and if no UNSUBACK has been received in the timeout period, 383 | it can retry the UNSUBSCRIBE according to the Retry and RetryInterval settings, 384 | before giving up and returning with an error. 385 | 386 |

    For usage example see the Subscribe method.

    ]]>
    387 | 388 | %Status 389 | has not been started yet") 393 | } 394 | If '$IsObject(pTopics) || (pTopics.Count() < 1) { 395 | Quit $$$ERROR($$$GeneralError, "At least one topic must be specified to unsubscribe from") 396 | } 397 | 398 | Set msgId = ##class(Net.MQTT.Auxiliary.MessageCounter).NextMessageId(..ClientId, .tSC) 399 | If $$$ISERR(tSC) { 400 | $$$MQTTTraceERR($System.Status.GetErrorText(tSC)) 401 | Quit $$$ERROR($$$GeneralError, "Message ID for UNSUBSCRIBE message cannot be created") 402 | } 403 | 404 | TStart 405 | TRY { 406 | Set key = "", cnt = 0 407 | Set sub = ##class(Net.MQTT.Auxiliary.Subscription).CreateSubscription(..ClientId, msgId, .tSC) 408 | While $$$ISOK(tSC) { 409 | Set topic = pTopics.GetNext(.key) Quit:(key = "") 410 | Set tSC = sub.AddTopic(topic) 411 | } 412 | } 413 | CATCH ex { 414 | Set tSC = ex.AsStatus() 415 | } 416 | If $$$ISOK(tSC) { TCommit } 417 | Else { TRollback } 418 | 419 | Set retry = ..Retry, retcnt = 0, success = 0 420 | While $$$ISOK(tSC) && (success '= 1) { 421 | Set ret = ##class(Net.MQTT.Auxiliary.TaskList).WaitTask(..connectionId, msgId _ $Select(retcnt: ":" _ retcnt, 1: ""), "UNSUBSCRIBE", ..ReadTimeout, .tSC) 422 | If $$$ISOK(tSC) { 423 | If $ListGet(ret, 1) < 1 { 424 | Set tSC = $$$ERROR($$$GeneralError, "MQTT Agent has not succeeded within the timeout period (" _ $ListGet(ret, 1) _ ")") 425 | } 426 | ElseIf $ListGet(ret, 2) '= "" { 427 | Set tSC = $$$ERROR($$$GeneralError, $ListGet(ret, 2)) 428 | } 429 | Else { 430 | Set ack = ##class(Net.MQTT.Auxiliary.TaskList).WaitAck(..connectionId, msgId, "UNSUBACK", ..ReadTimeout) 431 | Set success = $ListGet(ack, 1) 432 | If (success = 0) && (retry > 0) { 433 | $$$MQTTTraceERR("MQTT Agent has not received acknowledge within the timeout period (remaining reties: " _ retry _ ")") 434 | Hang ..RetryInterval 435 | Set retry = retry - 1, retcnt = retcnt + 1 436 | } 437 | If success < 1 { 438 | Set tSC = $$$ERROR($$$GeneralError, "MQTT Agent has not received acknowledge within the timeout period (" _ success _ ")") 439 | } 440 | ElseIf $ListGet(ack, 2) '= "" { 441 | Set tSC = $$$ERROR($$$GeneralError, $ListGet(ack, 2)) 442 | } 443 | } 444 | } 445 | } 446 | 447 | If $IsObject(sub) { 448 | Set subid = sub.%Id(), sub = "" 449 | Set tSC = $System.Status.AppendStatus(tSC, ##class(Net.MQTT.Auxiliary.Subscription).DropSubscription(subid)) 450 | } 451 | 452 | Quit tSC 453 | ]]> 454 |
    455 | 456 | 457 | 0) controls the message flow. 459 |

    In case of QoS = 1

    460 |
    461 | Sends the PUBLISH message and if no PUBACK has been received in the timeout period, 462 | it can retry the PUBLISH according to the Retry and RetryInterval settings, 463 | before giving up and returning with an error. 464 |
    465 |

    In case of QoS = 2

    466 |
    467 | Sends the PUBLISH message and if no PUBREC has been received in the timeout period, 468 | it can retry the PUBLISH according to the Retry and RetryInterval settings, 469 | before giving up and returning with an error. 470 |
    471 |
    472 | After the PUBREC message has been received, it sends the PUBREL message 473 | and if no PUBCOMP has been received in the timeout period, 474 | it can retry the PUBREL according to the Retry and RetryInterval settings, 475 | before giving up and returning with an error. 476 |
    477 |

    Usage example:

    478 | 479 | Set message = ##class(Net.MQTT.Message).%New() 480 | Set message.Topic = "/isctest/client/sent" 481 | Set message.Content = 1 482 | Set message.QoSLevel = 2 483 | 484 | Set client = ##class(Net.MQTT.Client).%New() 485 | Set client.Host = "test.mosquitto.org" 486 | Set client.ClientId = "My1stMQTTClient" 487 | Set client.traceTarget = "^MQTT.Trace" 488 | Set sc = client.StartAgent() If ('sc) { Do $System.OBJ.DisplayError(sc) } 489 | Set sc = client.Publish(message) If ('sc) { Do $System.OBJ.DisplayError(sc) } 490 | Set sc = client.StopAgent() If ('sc) { Do $System.OBJ.DisplayError(sc) } 491 | ZWrite ^MQTT.Trace 492 | ]]>
    493 | pMessage:Net.MQTT.Message 494 | %Status 495 | has not been started yet") 498 | } 499 | If '$IsObject(pMessage) { 500 | Quit $$$ERROR($$$GeneralError, "A message must be specified to publish") 501 | } 502 | 503 | Set tSC = $$$OK 504 | If pMessage.QoSLevel > 0 { 505 | Set pMessage.MessageId = ##class(Net.MQTT.Auxiliary.MessageCounter).NextMessageId(..ClientId, .tSC) 506 | Set:$$$ISOK(tSC) tSC = ##class(Net.MQTT.Auxiliary.MessageStatus).RegisterMessageOut(..ClientId, pMessage.MessageId) 507 | } 508 | If $$$ISERR(tSC) { 509 | $$$MQTTTraceERR($System.Status.GetErrorText(tSC)) 510 | Quit $$$ERROR($$$GeneralError, "Message ID for PUBLISH message cannot be created") 511 | } 512 | 513 | Set msg = ##class(Net.MQTT.MessageStore).%New() 514 | Set msg.ClientId = ..ClientId 515 | Set msg.Direction = "O" 516 | Set msg.MessageId = pMessage.MessageId 517 | Set msg.QoSLevel = pMessage.QoSLevel 518 | Set msg.Retain = pMessage.Retain 519 | Set msg.Topic = pMessage.Topic 520 | Set msg.Content = pMessage.Content 521 | Set tSC = msg.%Save() 522 | 523 | Set retry = ..Retry, retcnt = 0, success = 0 524 | Set acktype = $Case(pMessage.QoSLevel, 2: "PUBREC", 1: "PUBACK", : "") 525 | While $$$ISOK(tSC) && (success '= 1) { 526 | Set ret = ##class(Net.MQTT.Auxiliary.TaskList).WaitTask(..connectionId, msg.%Id() _ $Select(retcnt: ":" _ retcnt, 1: ""), "PUBLISH", ..ReadTimeout, .tSC) 527 | If $$$ISOK(tSC) { 528 | If $ListGet(ret, 1) < 1 { 529 | Set tSC = $$$ERROR($$$GeneralError, "MQTT Agent has not succeeded within the timeout period (" _ $ListGet(ret, 1) _ ")") 530 | } 531 | ElseIf $ListGet(ret, 2) '= "" { 532 | Set tSC = $$$ERROR($$$GeneralError, $ListGet(ret, 2)) 533 | } 534 | ElseIf pMessage.QoSLevel = 0 { 535 | Set success = 1 536 | } 537 | ElseIf pMessage.QoSLevel > 0 { 538 | Set ack = ##class(Net.MQTT.Auxiliary.TaskList).WaitAck(..connectionId, pMessage.MessageId, acktype, ..ReadTimeout) 539 | Set success = $ListGet(ack, 1) 540 | If (success = 0) && (retry > 0) { 541 | $$$MQTTTraceERR("MQTT Agent has not received acknowledge within the timeout period (remaining reties: " _ retry _ ")") 542 | Hang ..RetryInterval 543 | Set retry = retry - 1, retcnt = retcnt + 1 544 | } 545 | If success < 1 { 546 | Set tSC = $$$ERROR($$$GeneralError, "MQTT Agent has not received acknowledge within the timeout period (" _ success _ ")") 547 | } 548 | ElseIf $ListGet(ack, 2) '= "" { 549 | Set tSC = $$$ERROR($$$GeneralError, $ListGet(ack, 2)) 550 | } 551 | ElseIf pMessage.QoSLevel = 2 { 552 | Set tSC = ..PublishQoS2(pMessage.MessageId) 553 | } 554 | } 555 | } 556 | } 557 | 558 | Quit tSC 559 | ]]> 560 |
    561 | 562 | 563 | Publish to manage the second pahse of QoS Level 2 message flow 565 | (PUBREL message is sent and PUBCOMP is expected).]]> 566 | 1 567 | pMessageId:%String 568 | 1 569 | %Status 570 | 0) { 587 | $$$MQTTTraceERR("MQTT Agent has not received QOS=2 acknowledge within the timeout period (remaining reties: " _ retry _ ")") 588 | Hang ..RetryInterval 589 | Set retry = retry - 1, retcnt = retcnt + 1 590 | } 591 | If success < 1 { 592 | Set tSC = $$$ERROR($$$GeneralError, "MQTT Agent has not received QOS=2 acknowledge within the timeout period (" _ success _ ")") 593 | } 594 | ElseIf $ListGet(ack, 2) '= "" { 595 | Set tSC = $$$ERROR($$$GeneralError, $ListGet(ack, 2)) 596 | } 597 | } 598 | } 599 | } 600 | 601 | Quit tSC 602 | ]]> 603 | 604 | 605 | 606 | expression 607 | %String 608 | 610 | 611 | 612 | 613 | expression 614 | %String 615 | 617 | 618 | 619 | 620 | pString:%String 621 | %String 622 | 624 | 625 | 626 | 627 | USE WITH CARE! 629 |

    If one of the message flows would break with unexpected error, the Net.MQTT.Client may leave 630 | records behind in some auxiliary classes which can prevent the same ClientId to be used in a subsequent 631 | execution of a Net.MQTT.Client instance.

    632 |

    This auxiliary method is responsible for clearing those records when needed.

    633 |

    WARNING: if this is called while a Net.MQTT.Agent with the same Client ID is still active, 634 | this may cause errors in the Agent, too. And stopping the Agent in a clear way (with sending a DISCONNECT message) 635 | won't be possible any more. Use KillAgent in this case!

    ]]>
    636 | 1 637 | pClientId:%String 638 | 643 |
    644 | 645 | 646 | %Library.CacheStorage 647 | ^Net.MQTT.ClientD 648 | ClientDefaultData 649 | ^Net.MQTT.ClientD 650 | ^Net.MQTT.ClientI 651 | ^Net.MQTT.ClientS 652 | 653 | 654 | %%CLASSNAME 655 | 656 | 657 | Host 658 | 659 | 660 | Port 661 | 662 | 663 | SSLConfig 664 | 665 | 666 | ConnectTimeout 667 | 668 | 669 | ReadTimeout 670 | 671 | 672 | KeepAliveInterval 673 | 674 | 675 | ClientId 676 | 677 | 678 | CleanSession 679 | 680 | 681 | LastWill 682 | 683 | 684 | Retry 685 | 686 | 687 | RetryInterval 688 | 689 | 690 | OnMessage 691 | 692 | 693 | traceTarget 694 | 695 | 696 | debugMode 697 | 698 | 699 | connectionId 700 | 701 | 702 | agent 703 | 704 | 705 | traceLevel 706 | 707 | 708 | 709 | 710 |
    711 | -------------------------------------------------------------------------------- /Net/MQTT/Agent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Net.MQTT.Client in the background to manage the real communication with the broker. 6 |

    The agent inherits its settings from the Client, which started it and it is responsible for:

      7 |
    • Building the TCP connection to the broker.
    • 8 |
    • Executing the tasks created by the Client (and the Agent itself) based on the Net.MQTT.Auxiliary.TaskList records. 9 | E.g.: sending various messages to the broker.
    • 10 |
    • Keeping the connection alive by sending PING requests to the broker, when no other messages has been sent for a certain amount of time.
    • 11 |
    • Listening for incoming messages from the broker and triggering appropriate actions based on the message type.
    • 12 |

    ]]> 13 | Net.MQTT.Client 14 | 64152,49723.657073 15 | 64051,64922.792357 16 | 17 | 18 | %Boolean 19 | 0 20 | 21 | 22 | 23 | %String 24 | 25 | 26 | 27 | %String 28 | 29 | 30 | 31 | %String 32 | ##class(%IO.I.TranslationDevice).GetCharEncodingTable("UTF-8") 33 | 34 | 35 | 36 | %TimeStamp 37 | $ZDateTime($ZTimeStamp, 3, 1) 38 | 39 | 40 | 41 | %TimeStamp 42 | 43 | 44 | 45 | -1. 48 |

    If automatic re-connect is not possible, fatalError is set to 1, 49 | and the Agent stops itself.

    ]]>
    50 | %Integer 51 | 0 52 | 53 | 54 |
    55 | 56 | 57 | %String 58 | 1 59 | 60 | 61 | 62 | %String 63 | 1 64 | 65 | 66 | 67 | Net.MQTT.Client to start the Agent. 69 |

    pInitialState is the XML serialized content of the calling Client object, from which the agent can populate its own properties.

    70 |

    pUsername and pPassword are the optional credentials to be sent to the broker on connecting.

    71 |

    The Agent builds the TCP connection to the broker, sends a CONNECT message and then starts to communicate with the broker.

    ]]>
    72 | 1 73 | pInitialState:%String,pUsername:%String="",pPassword:%String="" 74 | 95 |
    96 | 97 | 98 | 100 |
  • If a pending Net.MQTT.Auxiliary.TaskList object can be found for the Agent's unique connectionId, 101 | it starts to execute the corresponding task, then signals the initiator of the task via the %SYSTEM.Event API.
  • 102 |
  • If no response has arrived from the broker for the last PING request within the KeepAliveInterval 103 | it treats the connection to the broker broken and stops.
  • 104 |
  • If no other messages has been sent to the broker for a certain amount of time (~ 80% of the ) KeepAliveInterval, 105 | it sends a PING request to keep the connection alive.
  • 106 |
  • Listens for incoming messages.
  • 107 | ]]>
    108 | 1 109 | 1 110 | starts listening") 118 | Do SetIO^%NLS("RAW") 119 | While 1 { 120 | If ..fatalError > 0 { 121 | Quit 122 | } 123 | 124 | TRY { 125 | Set tSC = ##class(Net.MQTT.Auxiliary.TaskList).AcquireNext(..connectionId, .taskId, .contextId, .action) 126 | If $$$ISOK(tSC) { 127 | If taskId '= "" { 128 | If $$$MQTTTraceDebug { 129 | $$$MQTTTraceDEB("Agent for " _ ..ClientId _ " to <" _ ..Host _ ":" _ ..Port _ "> processes task (" _taskId _ ")") 130 | } 131 | Else { 132 | $$$MQTTTraceINF("Agent for " _ ..ClientId _ " to <" _ ..Host _ ":" _ ..Port _ "> processes '" _ action _ "' action") 133 | } 134 | Set tSC = $METHOD($this, "Do" _ action, taskId, contextId) 135 | Set tSC = ##class(Net.MQTT.Auxiliary.TaskList).SignalTask(..connectionId, taskId, tSC, $$$MQTTTraceDebug) 136 | } 137 | ElseIf (..lastPing '= "") && ($System.SQL.DATEDIFF("s", ..lastPing, $ZDateTime($ZTimeStamp, 3, 1)) > ..KeepAliveInterval) { 138 | $$$MQTTTraceERR("PINGRESP message has not arrived from <" _ ..Host _ ":" _ ..Port _ "> within the timeout interval") 139 | Set ..fatalError = 1 140 | } 141 | ElseIf $System.SQL.DATEDIFF("s", ..lastMessage, $ZDateTime($ZTimeStamp, 3, 1)) > (..KeepAliveInterval * .8) { 142 | $$$MQTTTraceINF("Agent for " _ ..ClientId _ " to <" _ ..Host _ ":" _ ..Port _ "> sends ping") 143 | Set tSC = ..SendPINGREQ() 144 | If $$$ISERR(tSC) { 145 | $$$MQTTTraceERR("PINGREQ message cannot be sent to <" _ ..Host _ ":" _ ..Port _ ">") 146 | Set ..fatalError = 1 147 | } 148 | } 149 | Else { 150 | Set tSC = ..RecvMessage() 151 | } 152 | } 153 | } 154 | CATCH ex { 155 | Set tSC = ex.AsStatus() 156 | } 157 | 158 | If $$$ISERR(tSC) { 159 | $$$MQTTTraceERR($System.Status.GetErrorText(tSC)) 160 | } 161 | } 162 | 163 | Do ..CloseDev() 164 | Use ..saveIODev 165 | 166 | Quit 167 | ]]> 168 |
    169 | 170 | 171 | First takes a 1 byte MQTT Header. The takes the following 1 to 4 bytes to define the remaining length of the message. 174 | Finally reads the remaining part of the message and triggers appropriate action based on the message type (extracted from the Header).

    ]]>
    175 | %Status 176 | (missing Remaining Length)") 193 | Quit 194 | } 195 | Set rl = rl _ next 196 | Set next = $Ascii(next) 197 | Set length = length + ((next # 128) * multi) 198 | If next < 128 { Quit } 199 | Set multi = multi * 128 200 | } 201 | 202 | Set content = "" 203 | $$$MQTTTraceIN("Length", rl, $$$MQTTTraceDebug) 204 | If $$$ISOK(tSC) && (length > 0) { 205 | Read content#length:..ReadTimeout Set timeout = ('$Test) 206 | If timeout || ($Length(content) '= length) { 207 | Set tSC = $$$ERROR($$$GeneralError, "Invalid " _ typeT _ " message has arrived from <" _ ..Host _ ":" _ ..Port _ "> (missing content)") 208 | } 209 | } 210 | If $$$ISOK(tSC) { 211 | $$$MQTTTraceIN(typeT, header _ rl _ content, $$$MQTTTraceInfo) 212 | } 213 | Set tSC = $METHOD($this, "Recv" _ typeT, dup, qos, retain, content) 214 | } 215 | } 216 | CATCH ex { 217 | If ex.%IsA("%Exception.SystemException") && (ex.Name = "") { 218 | $$$MQTTTraceWRN("MQTT broker probably closed TCP connection to <" _ ..Host _ ":" _ ..Port _ "> (READ error)") 219 | // Try reconnect 220 | Set sc = $$$OK 221 | If ('..fatalError) { 222 | Set ..fatalError = -1 223 | Set sc = ..DoCONNECT(..username, ..password) 224 | } 225 | If $$$ISERR(sc) { 226 | $$$MQTTTraceERR("Agent couldn't re-connect automatically to <" _ ..Host _ ":" _ ..Port _ ">") 227 | Set ..fatalError = 1 228 | } 229 | } 230 | Set tSC = ex.AsStatus() 231 | } 232 | Quit tSC 233 | ]]> 234 |
    235 | 236 | 237 | CONNECT message.]]> 239 | 1 240 | pUsername:%String="",pPassword:%String="" 241 | 1 242 | %Status 243 | is starting") 246 | 247 | Set ..saveIODev = $IO 248 | If ('..connected) { 249 | Set ..device = "|TCP|" _ ..Port _ "|" _ $P($Job, ":") 250 | Open ..device:(..Host:..Port::::::$Case(..SSLConfig, "": "", :"/SSL="_..SSLConfig)):..ConnectTimeout Set timeout=('$Test) 251 | 252 | If timeout { 253 | $$$MQTTTraceERR("Agent for " _ ..ClientId _ " to <" _ ..Host _ ":" _ ..Port _ "> failed to start TCP connection") 254 | Quit $$$ERROR($$$GeneralError, "TCP Connection to <" _ ..Host _ ":" _ ..Port _ "> has not succeeded within the timeout interval") 255 | } 256 | Else { 257 | $$$MQTTTraceINF("Agent for " _ ..ClientId _ " to <" _ ..Host _ ":" _ ..Port _ "> started TCP connection") 258 | Set ..connected = 1 259 | } 260 | } 261 | 262 | If ..SSLConfig = "" { 263 | Use ..device 264 | } 265 | Else { 266 | Use ..device:(:::::::/SSL=..SSLConfig) 267 | } 268 | Do SetIO^%NLS("RAW") 269 | Set tSC = ..SendCONNECT(pUsername, pPassword) 270 | If $$$ISERR(tSC) { 271 | Do ..CloseDev() 272 | } 273 | Use ..saveIODev 274 | 275 | Quit tSC 276 | ]]> 277 | 278 | 279 | 280 | DISCONNECT message to the MQTT broker and closes the TCP connection.]]> 282 | 1 283 | pTaskId:%String,pConnectionId:%String 284 | 1 285 | %Status 286 | 293 | 294 | 295 | 296 | SUBSCRIBE message to the MQTT broker. 298 |

    The ContextId of the corresponding Net.MQTT.Auxiliary.TaskList object is a Message Idenifier. 299 | If it contains a colon, this is a repeated attempt, because no acknowledge has been received from the broker within the defined timout period.

    300 |

    The details of the SUBSCRIBE message must be stored in a Net.MQTT.Auxiliary.Subscription object.

    ]]>
    301 | 1 302 | pTaskId:%String,pMessageId:%String 303 | 1 304 | %Status 305 | 1 { 309 | Set msgid = $Piece(pMessageId, ":", 1) 310 | Set dup = (''$Piece(pMessageId, ":", 2)) 311 | } 312 | Set topics = ##class(Net.MQTT.Auxiliary.Subscription).GetTopicList(..ClientId, msgid, .tSC) 313 | Set:$$$ISOK(tSC) tSC = ..SendSUBSCRIBE(msgid, topics, dup) 314 | 315 | Quit tSC 316 | ]]> 317 |
    318 | 319 | 320 | UNSUBSCRIBE message to the MQTT broker. 322 |

    The ContextId of the corresponding Net.MQTT.Auxiliary.TaskList object is a Message Idenifier. 323 | If it contains a colon, this is a repeated attempt, because no acknowledge has been received from the broker within the defined timout period.

    324 |

    The details of the UNSUBSCRIBE message must be stored in a Net.MQTT.Auxiliary.Subscription object.

    ]]>
    325 | 1 326 | pTaskId:%String,pMessageId:%String 327 | 1 328 | %Status 329 | 1 { 333 | Set msgid = $Piece(pMessageId, ":", 1) 334 | Set dup = (''$Piece(pMessageId, ":", 2)) 335 | } 336 | Set topics = ##class(Net.MQTT.Auxiliary.Subscription).GetTopicList(..ClientId, msgid, .tSC) 337 | Set:$$$ISOK(tSC) tSC = ..SendUNSUBSCRIBE(msgid, topics, dup) 338 | 339 | Quit tSC 340 | ]]> 341 |
    342 | 343 | 344 | PUBLISH message to the MQTT broker. 346 |

    The ContextId of the corresponding Net.MQTT.Auxiliary.TaskList object is a Net.MQTT.Auxiliary.MessageStore object ID. 347 | If it contains a colon, this is a repeated attempt, because no acknowledge has been received from the broker within the defined timout period.

    348 |

    The details of the PUBLISH message are stored in the referenced Net.MQTT.Auxiliary.MessageStore object.

    349 |

    On QoS levels > 0 it also pushes the Net.MQTT.Auxiliary.MessageStatus to the next state 350 | (either waiting for a PUBACK or PUBREC message).

    ]]>
    351 | 1 352 | pTaskId:%String,pMessageStoreId:%String 353 | 1 354 | %Status 355 | 1 { 359 | Set msgid = $Piece(pMessageStoreId, ":", 1) 360 | Set dup = (''$Piece(pMessageStoreId, ":", 2)) 361 | } 362 | Set message = ##class(Net.MQTT.MessageStore).%OpenId(msgid, -1, .tSC) 363 | Set:$$$ISOK(tSC) tSC = ..SendPUBLISH(message, dup) 364 | If $$$ISOK(tSC) { 365 | If message.QoSLevel = 1 { 366 | Set tSC = ##class(Net.MQTT.Auxiliary.MessageStatus).AcknowledgeMessageOut(..ClientId, message.MessageId) 367 | } 368 | ElseIf message.QoSLevel = 2 { 369 | Set tSC = ##class(Net.MQTT.Auxiliary.MessageStatus).ReceiveMessageOut(..ClientId, message.MessageId) 370 | } 371 | } 372 | 373 | Quit tSC 374 | ]]> 375 |
    376 | 377 | 378 | PUBACK message to the MQTT broker for an incoming, QoS Level 1 message. 380 |

    The ContextId of the corresponding Net.MQTT.Auxiliary.TaskList object is a Message Identifier.

    381 |

    This is the end of the message flow of the incoming message.

    ]]>
    382 | 1 383 | pTaskId:%String,pMessageId:%String 384 | 1 385 | %Status 386 | 392 |
    393 | 394 | 395 | PUBREC message to the MQTT broker for an incoming, QoS Level 2 message. 397 |

    The ContextId of the corresponding Net.MQTT.Auxiliary.TaskList object is a Message Identifier.

    398 |

    It also pushes the Net.MQTT.Auxiliary.MessageStatus to the next state (waiting for a PUBREL message).

    ]]>
    399 | 1 400 | pTaskId:%String,pMessageId:%String 401 | 1 402 | %Status 403 | 409 |
    410 | 411 | 412 | PUBREL message to the MQTT broker for an outgoing, QoS Level 2 message. 414 |

    The ContextId of the corresponding Net.MQTT.Auxiliary.TaskList object is a Message Identifier. 415 | If it contains a colon, this is a repeated attempt, because no acknowledge has been received for the original PUBLISH message 416 | from the broker within the defined timout period.

    417 |

    It also pushes the Net.MQTT.Auxiliary.MessageStatus to the next state (waiting for a PUBCOMP message).

    ]]>
    418 | 1 419 | pTaskId:%String,pMessageId:%String 420 | 1 421 | %Status 422 | 1 { 426 | Set msgid = $Piece(pMessageId, ":", 1) 427 | Set dup = (''$Piece(pMessageId, ":", 2)) 428 | } 429 | Set tSC = ..SendPUBREL(msgid, dup) 430 | Set:$$$ISOK(tSC) tSC = ##class(Net.MQTT.Auxiliary.MessageStatus).CompleteMessageOut(..ClientId, pMessageId) 431 | 432 | Quit tSC 433 | ]]> 434 |
    435 | 436 | 437 | PUBCOMP message to the MQTT broker for an incoming, QoS Level 2 message. 439 |

    The ContextId of the corresponding Net.MQTT.Auxiliary.TaskList object is a Message Identifier.

    440 |

    This is the end of the message flow of the incoming message.

    ]]>
    441 | 1 442 | pTaskId:%String,pMessageId:%String 443 | 1 444 | %Status 445 | 451 |
    452 | 453 | 454 | CONNECT message and sends the package to the MQTT broker.]]> 456 | 1 457 | pUsername:%String="",pPassword:%String="" 458 | 1 459 | %Status 460 | 488 | 489 | 490 | 491 | DISCONNECT message and sends the package to the MQTT broker.]]> 493 | 1 494 | 1 495 | %Status 496 | 498 | 499 | 500 | 501 | PINGREQ message and sends the package to the MQTT broker.]]> 503 | 1 504 | 1 505 | %Status 506 | 512 | 513 | 514 | 515 | SUBSCRIBE message and sends the package to the MQTT broker.]]> 517 | 1 518 | pMessageId:%Integer,pTopics:%ListOfObjects,pDup:%Boolean=0 519 | 1 520 | %Status 521 | 535 | 536 | 537 | 538 | UNSUBSCRIBE message and sends the package to the MQTT broker.]]> 540 | 1 541 | pMessageId:%Integer,pTopics:%ListOfObjects,pDup:%Boolean=0 542 | 1 543 | %Status 544 | 558 | 559 | 560 | 561 | PUBLISH message and sends the package to the MQTT broker.]]> 563 | 1 564 | pMessage:Net.MQTT.MessageStore,pDup:%Boolean=0 565 | 1 566 | %Status 567 | 0: $$$MQTTEncodeNumber(+pMessage.MessageId), 1: "") 569 | 570 | Set tSC = ..PackSendMsg(varhdr_pMessage.Content, $$$MQTTPUBLISH, pDup, pMessage.QoSLevel, pMessage.Retain) 571 | Quit tSC 572 | ]]> 573 | 574 | 575 | 576 | PUBACK message and sends the package to the MQTT broker.]]> 578 | 1 579 | pMessageId:%Integer 580 | 1 581 | %Status 582 | 588 | 589 | 590 | 591 | PUBREC message and sends the package to the MQTT broker.]]> 593 | 1 594 | pMessageId:%Integer 595 | 1 596 | %Status 597 | 603 | 604 | 605 | 606 | PUBREL message and sends the package to the MQTT broker.]]> 608 | 1 609 | pMessageId:%Integer,pDup:%Boolean=0 610 | 1 611 | %Status 612 | 618 | 619 | 620 | 621 | PUBCOMP message and sends the package to the MQTT broker.]]> 623 | 1 624 | pMessageId:%Integer 625 | 1 626 | %Status 627 | 633 | 634 | 635 | 636 | CONNACK message, received from the MQTT Broker and signals the initiator (Net.MQTT.Client) 638 | via the %SYSTEM.Event API about the success or failure of the connection attempt.]]> 639 | 1 640 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 641 | 1 642 | %Status 643 | (length: " _ $Length(pContent) _ " <> 2)") 649 | } 650 | Else { 651 | Set ret = +$Ascii($Extract(pContent, 2)) 652 | If ret = 1 { Set tSC = $$$ERROR($$$GeneralError, "Connection to <" _ ..Host _ ":" _ ..Port _ "> failed (unacceptable protocol version)") } 653 | ElseIf ret = 2 { Set tSC = $$$ERROR($$$GeneralError, "Connection to <" _ ..Host _ ":" _ ..Port _ "> failed (identifier rejected)") } 654 | ElseIf ret = 3 { Set tSC = $$$ERROR($$$GeneralError, "Connection to <" _ ..Host _ ":" _ ..Port _ "> failed (server unavailable)") } 655 | ElseIf ret = 4 { Set tSC = $$$ERROR($$$GeneralError, "Connection to <" _ ..Host _ ":" _ ..Port _ "> failed (bad username or password)") } 656 | ElseIf ret = 5 { Set tSC = $$$ERROR($$$GeneralError, "Connection to <" _ ..Host _ ":" _ ..Port _ "> failed (not authorized)") } 657 | } 658 | 659 | Do $System.Event.Signal("^MQTT.Connect(""" _ ..connectionId _ """)", $Select($$$ISOK(tSC): "", 1: $System.Status.GetErrorText(tSC))) 660 | 661 | Quit tSC 662 | ]]> 663 | 664 | 665 | 666 | PINGRESP message, received from the MQTT Broker and clears the lastPing property, 668 | so the Agent can know that the broker is still responsive and the connection is successfully kept alive.]]> 669 | 1 670 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 671 | 1 672 | %Status 673 | ") 678 | } 679 | Else { 680 | Set ..lastPing = "" 681 | } 682 | 683 | Quit tSC 684 | ]]> 685 | 686 | 687 | 688 | UNSUBACK message, received from the MQTT Broker and 690 | signals the Net.MQTT.Client waiting for this acknowledge of a previously sent UNSUBSCRIBE message.]]> 691 | 1 692 | expression 693 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 694 | 1 695 | %Status 696 | 698 | 699 | 700 | 701 | PUBACK message, received from the MQTT Broker and 703 | signals the Net.MQTT.Client waiting for this acknowledge of a previously published QoS Level 1 message. 704 |

    This is the end of the message flow of the outgoing message.

    ]]>
    705 | 1 706 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 707 | 1 708 | %Status 709 | 715 |
    716 | 717 | 718 | PUBREC message, received from the MQTT Broker and 720 | signals the Net.MQTT.Client waiting for this acknowledge of a previously published QoS Level 2 message. 721 |

    It also pushes the Net.MQTT.Auxiliary.MessageStatus to the next state (a PUBREL message has to be sent).

    ]]>
    722 | 1 723 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 724 | 1 725 | %Status 726 | 732 |
    733 | 734 | 735 | PUBREL message, received from the MQTT Broker. 737 |

    It pushes the Net.MQTT.Auxiliary.MessageStatus to the next state (a PUBCOMP message has to be sent), 738 | and cretes a new task (Net.MQTT.Auxiliary.TaskList) to complete the next step of the QoS Level 2 message flow.

    ]]>
    739 | 1 740 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 741 | 1 742 | %Status 743 | 750 |
    751 | 752 | 753 | PUBCOMP message, received from the MQTT Broker and 755 | signals the Net.MQTT.Client waiting for this acknowledge of a previously published QoS Level 2 message. 756 |

    This is the end of the message flow of the outgoing message.

    ]]>
    757 | 1 758 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 759 | 1 760 | %Status 761 | 767 |
    768 | 769 | 770 | %SYSTEM.Event API 772 | waiting for this acknowledge.]]> 773 | 1 774 | pType:%String,pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String,*pMsgId:%Integer 775 | 1 776 | %Status 777 | (length: " _ $Length(pContent) _ " <> 2)") 782 | } 783 | Else { 784 | Set pMsgId = $$$MQTTDecodeNumber(pContent) 785 | Do ##class(Net.MQTT.Auxiliary.TaskList).SignalAck(..connectionId, pMsgId, pType, $Select($$$ISOK(tSC): "", 1: $System.Status.GetErrorText(tSC))) 786 | } 787 | 788 | Quit tSC 789 | ]]> 790 | 791 | 792 | 793 | SUBACK message, received from the MQTT Broker, 795 | stores the QoS levels granted by the broker on the various items of the subscription message (see: Net.MQTT.Auxiliary.Subscription) 796 | and signals the Net.MQTT.Client waiting for this acknowledge of the previously sent SUBSCRIBE message.]]> 797 | 1 798 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 799 | 1 800 | %Status 801 | (length < 2)") 806 | } 807 | Else { 808 | Set msgid = $$$MQTTDecodeNumber($Extract(pContent, 1, 2)) 809 | Set cnt = ##class(Net.MQTT.Auxiliary.Subscription).GetTopicCount(..ClientId, msgid, .tSC) 810 | If $$$ISOK(tSC) { 811 | If $Length(pContent) '= (2 + cnt) { 812 | Set tSC = $$$ERROR($$$GeneralError, "Invalid SUBACK message has arrived from <" _ ..Host _ ":" _ ..Port _ "> (# of topics)") 813 | } 814 | Else { 815 | For i = 1: 1: cnt { 816 | Set tSC = ##class(Net.MQTT.Auxiliary.Subscription).AckTopic(..ClientId, msgid, i, $Case($Ascii($Extract(pContent, 2 + i)), $$$MQTTSubQoS2: 2, $$$MQTTSubQoS1: 1, : 0)) 817 | Quit:$$$ISERR(tSC) 818 | } 819 | } 820 | } 821 | Do ##class(Net.MQTT.Auxiliary.TaskList).SignalAck(..connectionId, msgid, "SUBACK", $Select($$$ISOK(tSC): "", 1: $System.Status.GetErrorText(tSC))) 822 | } 823 | 824 | Quit tSC 825 | ]]> 826 | 827 | 828 | 829 | PUBLISH message, received from the MQTT Broker and 831 | sends the incoming message to the OnMessage callback method, which is responsible for 832 | optionally filtering, processing and storing of the message. 833 |

    If the QoS Level > 0, it also pushes the Net.MQTT.Auxiliary.MessageStatus to the next state 834 | (either a PUBACK or PUBREC message has to be sent), 835 | and cretes a new task (Net.MQTT.Auxiliary.TaskList) to complete the next step of the message flow.

    ]]>
    836 | 1 837 | pDup:%Boolean,pQoS:%Integer,pRetain:%Boolean,pContent:%String 838 | 1 839 | %Status 840 | 0 { 847 | Set msgid = $$$MQTTDecodeNumber($Extract(pContent, pos, pos + 1)) 848 | Set pos = pos + 2 849 | } 850 | Else { 851 | Set msgid = "" 852 | } 853 | 854 | TStart 855 | Set tSC = ##class(Net.MQTT.Auxiliary.MessageStatus).%LockExtent(0) 856 | TRY { 857 | Set dup = $Select(pQoS > 0: pDup, 1: 0) 858 | If pQoS > 0 { 859 | If dup { 860 | Set dup = ##class(Net.MQTT.Auxiliary.MessageStatus).IsRegisteredMessageIn(..ClientId, msgid) 861 | } 862 | If 'dup { 863 | Set tSC = ##class(Net.MQTT.Auxiliary.MessageStatus).RegisterMessageIn(..ClientId, msgid, pQoS) 864 | } 865 | 866 | // The acknowledgement mechanism is independent from the fact, 867 | // whether the application message is a duplicate or not, or it is successfully processed by the OnMessage handler or not. 868 | Set:$$$ISOK(tSC) taskid = ##class(Net.MQTT.Auxiliary.TaskList).CreateNewTask(..connectionId, msgid, $Case(pQoS, 2: "PUBREC", : "PUBACK"), .tSC) 869 | } 870 | } 871 | CATCH ex { 872 | Set tSC = ex.AsStatus() 873 | } 874 | Do ##class(Net.MQTT.Auxiliary.MessageStatus).%UnlockExtent() 875 | If $$$ISOK(tSC) { TCommit } 876 | Else { TRollback } 877 | Quit:$$$ISERR(tSC) tSC 878 | 879 | Do ..ProcessMessage(msgid, topic, $Extract(pContent, pos, *), pDup, pQoS, pRetain) 880 | 881 | Quit $$$OK 882 | ]]> 883 |
    884 | 885 | 886 | OnMessage setting, calls the appropriate callback method and handles potential errors.]]> 888 | pMsgId:%String,pTopic:%String,pContent:%String,pDuplicate:%Boolean,pQoS:%Integer,pRetain:%Boolean 889 | 912 | 913 | 914 | 915 | 1 916 | 1 917 | is stopping TCP connection") 920 | Close ..device 921 | Use ..saveIODev 922 | Set ..connected = 0, ..device = "" 923 | } 924 | ]]> 925 | 926 | 927 | 928 | 1 929 | pMessage:%String,pMessageType:%Integer,pDup:%Boolean=0,pQoS:%Integer=0,pRetain:%Boolean=0 930 | 1 931 | %Status 932 | 0 { 944 | Set nxt = nxt + 128 945 | } 946 | Set header = header _ $Char(nxt) 947 | Quit:(lng '> 0) 948 | } 949 | 950 | Set msg = header_pMessage 951 | Write msg, ! 952 | Set ..lastMessage = $ZDateTime($ZTimeStamp, 3, 1) 953 | $$$MQTTTraceOUT($$$MQTTMsgType(pMessageType), msg, $$$MQTTTraceInfo) 954 | 955 | Quit $$$OK 956 | ]]> 957 | 958 | 959 | 960 | 1 961 | pHeader:%String 962 | %Integer 963 | 972 | 973 | 974 | 975 | 1 976 | pHeader:%String 977 | %Boolean 978 | 983 | 984 | 985 | 986 | 1 987 | pHeader:%String 988 | %Integer 989 | 995 | 996 | 997 | 998 | 1 999 | pHeader:%String 1000 | %Boolean 1001 | 1006 | 1007 | 1008 | 1009 | pString:%String 1010 | %String 1011 | 1017 | 1018 | 1019 | 1020 | %Library.CacheStorage 1021 | AgentDefaultData 1022 | 1023 | "Agent" 1024 | 1025 | connected 1026 | 1027 | 1028 | device 1029 | 1030 | 1031 | saveIODev 1032 | 1033 | 1034 | transTable 1035 | 1036 | 1037 | lastMessage 1038 | 1039 | 1040 | lastPing 1041 | 1042 | 1043 | fatalError 1044 | 1045 | 1046 | 1047 | 1048 |
    1049 | --------------------------------------------------------------------------------