├── 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 |
--------------------------------------------------------------------------------