├── Demo ├── Server2.dpr ├── Server2.dproj ├── Server2.exe ├── Server2.res ├── uBrokers.dfm ├── uBrokers.pas ├── uServer2.dfm └── uServer2.pas ├── README.md └── src ├── MQTTComponents.dpk ├── MQTTComponents.dproj ├── MQTTComponents.res ├── uMQTT.pas └── uMQTTComps.pas /Demo/Server2.dpr: -------------------------------------------------------------------------------- 1 | program Server2; 2 | 3 | uses 4 | Forms, 5 | uServer2 in 'uServer2.pas' {MainForm}, 6 | uMQTT in 'uMQTT.pas', 7 | uBrokers in 'uBrokers.pas' {BrokerForm}; 8 | 9 | {$R *.res} 10 | 11 | begin 12 | Application.Initialize; 13 | Application.MainFormOnTaskbar := True; 14 | Application.CreateForm(TMainForm, MainForm); 15 | Application.CreateForm(TBrokerForm, BrokerForm); 16 | Application.Run; 17 | end. 18 | -------------------------------------------------------------------------------- /Demo/Server2.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {EB00A936-B90C-47A8-888D-82C98B8DA42F} 4 | 12.2 5 | Server2.dpr 6 | True 7 | Debug 8 | Win32 9 | Application 10 | VCL 11 | DCC32 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | 00400000 28 | .\$(Config)\$(Platform) 29 | WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;$(DCC_UnitAlias) 30 | .\$(Config)\$(Platform) 31 | 32 | 33 | C:\Delphi Projects\Virtual Treeview\Resources;C:\Delphi Projects\Graphics32;C:\Delphi Projects\icsv816\Source;$(DCC_UnitSearchPath) 34 | DEBUG;$(DCC_Define) 35 | false 36 | true 37 | 38 | 39 | false 40 | RELEASE;$(DCC_Define) 41 | 0 42 | false 43 | 44 | 45 | 46 | MainSource 47 | 48 | 49 |
MainForm
50 |
51 | 52 | 53 |
BrokerForm
54 |
55 | 56 | Cfg_2 57 | Base 58 | 59 | 60 | Base 61 | 62 | 63 | Cfg_1 64 | Base 65 | 66 |
67 | 68 | 69 | 70 | Delphi.Personality.12 71 | 72 | 73 | 74 | 75 | False 76 | False 77 | 1 78 | 0 79 | 0 80 | 0 81 | False 82 | False 83 | False 84 | False 85 | False 86 | 3081 87 | 1252 88 | 89 | 90 | 91 | 92 | 1.0.0.0 93 | 94 | 95 | 96 | 97 | 98 | 1.0.0.0 99 | 100 | 101 | 102 | Microsoft Office 2000 Sample Automation Server Wrapper Components 103 | Microsoft Office XP Sample Automation Server Wrapper Components 104 | 105 | 106 | Server2.dpr 107 | 108 | 109 | 110 | True 111 | 112 | 113 | 12 114 | 115 |
116 | -------------------------------------------------------------------------------- /Demo/Server2.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjde/delphi-mqtt/65ef33f009f7b96d0e50f35d1591df08be79011f/Demo/Server2.exe -------------------------------------------------------------------------------- /Demo/Server2.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjde/delphi-mqtt/65ef33f009f7b96d0e50f35d1591df08be79011f/Demo/Server2.res -------------------------------------------------------------------------------- /Demo/uBrokers.dfm: -------------------------------------------------------------------------------- 1 | object BrokerForm: TBrokerForm 2 | Left = 0 3 | Top = 0 4 | BorderStyle = bsToolWindow 5 | Caption = ' Other Brokers' 6 | ClientHeight = 297 7 | ClientWidth = 310 8 | Color = clBtnFace 9 | Font.Charset = DEFAULT_CHARSET 10 | Font.Color = clWindowText 11 | Font.Height = -11 12 | Font.Name = 'Tahoma' 13 | Font.Style = [] 14 | OldCreateOrder = False 15 | OnCreate = FormCreate 16 | OnShow = FormShow 17 | PixelsPerInch = 96 18 | TextHeight = 13 19 | object Label1: TLabel 20 | Left = 4 21 | Top = 221 22 | Width = 52 23 | Height = 13 24 | Caption = 'IP Address' 25 | end 26 | object Label2: TLabel 27 | Left = 146 28 | Top = 221 29 | Width = 20 30 | Height = 13 31 | Caption = 'Port' 32 | end 33 | object MonTree: TVirtualStringTree 34 | Left = 4 35 | Top = 4 36 | Width = 299 37 | Height = 199 38 | Colors.UnfocusedSelectionColor = clHighlight 39 | Colors.UnfocusedSelectionBorderColor = clHighlight 40 | Header.AutoSizeIndex = 0 41 | Header.DefaultHeight = 17 42 | Header.Font.Charset = DEFAULT_CHARSET 43 | Header.Font.Color = clWindowText 44 | Header.Font.Height = -11 45 | Header.Font.Name = 'Tahoma' 46 | Header.Font.Style = [] 47 | Header.Options = [hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible] 48 | RootNodeCount = 3 49 | TabOrder = 0 50 | TreeOptions.PaintOptions = [toShowButtons, toShowDropmark, toShowHorzGridLines, toShowVertGridLines, toThemeAware, toUseBlendedImages, toFullVertGridLines] 51 | TreeOptions.SelectionOptions = [toFullRowSelect] 52 | OnChange = MonTreeChange 53 | OnGetText = MonTreeGetText 54 | OnGetNodeDataSize = MonTreeGetNodeDataSize 55 | Columns = < 56 | item 57 | Position = 0 58 | Width = 100 59 | WideText = 'IP Address' 60 | end 61 | item 62 | Position = 1 63 | Width = 60 64 | WideText = 'Port' 65 | end 66 | item 67 | Position = 2 68 | Width = 60 69 | WideText = 'Enabled' 70 | end 71 | item 72 | Position = 3 73 | WideText = 'Online' 74 | end> 75 | end 76 | object IPTxt: TEdit 77 | Left = 4 78 | Top = 235 79 | Width = 127 80 | Height = 21 81 | TabOrder = 1 82 | OnChange = IPTxtChange 83 | end 84 | object PortTxt: TEdit 85 | Left = 146 86 | Top = 235 87 | Width = 71 88 | Height = 21 89 | TabOrder = 2 90 | OnChange = IPTxtChange 91 | OnKeyPress = PortTxtKeyPress 92 | end 93 | object DelBtn: TButton 94 | Left = 231 95 | Top = 205 96 | Width = 71 97 | Height = 23 98 | Caption = 'Del' 99 | TabOrder = 3 100 | OnClick = DelBtnClick 101 | end 102 | object AddBtn: TButton 103 | Left = 232 104 | Top = 234 105 | Width = 71 106 | Height = 23 107 | Caption = 'Add' 108 | TabOrder = 4 109 | OnClick = AddBtnClick 110 | end 111 | object BitBtn1: TBitBtn 112 | Left = 211 113 | Top = 263 114 | Width = 91 115 | Height = 27 116 | Caption = 'Close' 117 | DoubleBuffered = True 118 | Kind = bkCancel 119 | NumGlyphs = 2 120 | ParentDoubleBuffered = False 121 | TabOrder = 5 122 | OnClick = BitBtn1Click 123 | end 124 | object StartBtn: TButton 125 | Left = 4 126 | Top = 271 127 | Width = 71 128 | Height = 23 129 | Caption = 'Start' 130 | TabOrder = 6 131 | OnClick = StartBtnClick 132 | end 133 | object StopBtn: TButton 134 | Left = 81 135 | Top = 271 136 | Width = 71 137 | Height = 23 138 | Caption = 'Stop' 139 | TabOrder = 7 140 | OnClick = StopBtnClick 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /Demo/uBrokers.pas: -------------------------------------------------------------------------------- 1 | unit uBrokers; 2 | 3 | interface 4 | 5 | uses 6 | Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 7 | Dialogs, uMQTTComps, VirtualTrees, Buttons, StdCtrls; 8 | 9 | type 10 | TDataRec = record 11 | Broker : TMQTTClient; 12 | end; 13 | PDataRec = ^TDataRec; 14 | 15 | TBrokerForm = class(TForm) 16 | MonTree: TVirtualStringTree; 17 | IPTxt: TEdit; 18 | PortTxt: TEdit; 19 | Label1: TLabel; 20 | Label2: TLabel; 21 | DelBtn: TButton; 22 | AddBtn: TButton; 23 | BitBtn1: TBitBtn; 24 | StartBtn: TButton; 25 | StopBtn: TButton; 26 | procedure FormCreate(Sender: TObject); 27 | procedure MonTreeGetNodeDataSize(Sender: TBaseVirtualTree; 28 | var NodeDataSize: Integer); 29 | procedure FormShow(Sender: TObject); 30 | procedure AddBtnClick(Sender: TObject); 31 | procedure MonTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; 32 | Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); 33 | procedure BitBtn1Click(Sender: TObject); 34 | procedure DelBtnClick(Sender: TObject); 35 | procedure PortTxtKeyPress(Sender: TObject; var Key: Char); 36 | procedure StartBtnClick(Sender: TObject); 37 | procedure StopBtnClick(Sender: TObject); 38 | procedure IPTxtChange(Sender: TObject); 39 | procedure MonTreeChange(Sender: TBaseVirtualTree; Node: PVirtualNode); 40 | private 41 | { Private declarations } 42 | procedure UpdateBtnStatus; 43 | 44 | public 45 | { Public declarations } 46 | FServer : TMQTTServer; 47 | procedure Sync; 48 | procedure RefreshTree; 49 | end; 50 | 51 | var 52 | BrokerForm: TBrokerForm; 53 | 54 | procedure ShowBrokerForm (anOwner : TComponent; aServer : TMQTTServer); 55 | 56 | implementation 57 | 58 | 59 | {$R *.dfm} 60 | 61 | procedure ShowBrokerForm (anOwner : TComponent; aServer : TMQTTServer); 62 | var 63 | aForm : TBrokerForm; 64 | begin 65 | aForm := TBrokerForm.Create (anOwner); 66 | aForm.FServer := aServer; 67 | aForm.Show; 68 | end; 69 | 70 | { TForm2 } 71 | 72 | procedure TBrokerForm.AddBtnClick(Sender: TObject); 73 | var 74 | aBroker : TMQTTClient; 75 | aNode : PVirtualNode; 76 | aData : PDataRec; 77 | begin 78 | if FServer = nil then exit; 79 | aBroker := FServer.AddBroker (IPTxt.Text, StrToIntDef (PortTxt.Text, 1883)); 80 | Sync; 81 | aNode := MonTree.GetFirst (false); 82 | while aNode <> nil do 83 | begin 84 | aData := MonTree.GetNodeData (aNode); 85 | if aData.Broker = aBroker then 86 | begin 87 | MonTree.Selected[aNode] := true; 88 | aNode := nil; 89 | end 90 | else 91 | aNode := MonTree.GetNext (aNode, false); 92 | end; 93 | end; 94 | 95 | procedure TBrokerForm.BitBtn1Click (Sender: TObject); 96 | begin 97 | hide; 98 | end; 99 | 100 | procedure TBrokerForm.DelBtnClick(Sender: TObject); 101 | var 102 | aNode : PVirtualNode; 103 | aData : PDataRec; 104 | begin 105 | if FServer = nil then exit; 106 | aNode := MonTree.GetFirstSelected (false); 107 | if aNode = nil then exit; 108 | aData := MonTree.GetNodeData (aNode); 109 | FServer.Brokers.Remove (aData.Broker); 110 | aData.Broker.Free; 111 | Sync; 112 | end; 113 | 114 | procedure TBrokerForm.FormCreate (Sender: TObject); 115 | begin 116 | FServer := nil; 117 | end; 118 | 119 | procedure TBrokerForm.FormShow (Sender: TObject); 120 | begin 121 | Sync; 122 | UpdateBtnStatus; 123 | end; 124 | 125 | procedure TBrokerForm.IPTxtChange(Sender: TObject); 126 | begin 127 | UpdateBtnStatus; 128 | end; 129 | 130 | procedure TBrokerForm.MonTreeChange(Sender: TBaseVirtualTree; 131 | Node: PVirtualNode); 132 | begin 133 | UpdateBtnStatus; 134 | end; 135 | 136 | procedure TBrokerForm.MonTreeGetNodeDataSize(Sender: TBaseVirtualTree; 137 | var NodeDataSize: Integer); 138 | begin 139 | NodeDataSize := SizeOf (TDataRec); 140 | end; 141 | 142 | procedure TBrokerForm.MonTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; 143 | Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); 144 | const 145 | ny : array [boolean] of string = ('NO', 'YES'); 146 | var 147 | aData : PDataRec; 148 | begin 149 | aData := Sender.GetNodeData (Node); 150 | if FServer = nil then 151 | CellText := '' 152 | else if FServer.Brokers.IndexOf (aData.Broker) >= 0 then 153 | begin 154 | case Column of 155 | 0 : CellText := aData.Broker.Host; 156 | 1 : CellText := IntToStr (aData.Broker.Port); 157 | 2 : Celltext := ny[aData.Broker.Enabled]; 158 | 3 : CellText := ny[aData.Broker.Online]; 159 | end; 160 | end 161 | else 162 | CellText := ''; 163 | end; 164 | 165 | procedure TBrokerForm.PortTxtKeyPress(Sender: TObject; var Key: Char); 166 | begin 167 | if not CharInSet (Key, ['0'..'9', #8]) then Key := #0; 168 | end; 169 | 170 | procedure TBrokerForm.RefreshTree; 171 | begin 172 | MonTree.Invalidate; 173 | end; 174 | 175 | procedure TBrokerForm.StartBtnClick(Sender: TObject); 176 | var 177 | aNode : PVirtualNode; 178 | aData : PDataRec; 179 | begin 180 | if FServer = nil then exit; 181 | aNode := MonTree.GetFirstSelected (false); 182 | if aNode = nil then exit; 183 | aData := MonTree.GetNodeData (aNode); 184 | aData.Broker.Activate (true); 185 | MonTree.Invalidate; 186 | end; 187 | 188 | procedure TBrokerForm.StopBtnClick(Sender: TObject); 189 | var 190 | aNode : PVirtualNode; 191 | aData : PDataRec; 192 | begin 193 | if FServer = nil then exit; 194 | aNode := MonTree.GetFirstSelected (false); 195 | if aNode = nil then exit; 196 | aData := MonTree.GetNodeData (aNode); 197 | aData.Broker.Activate (false); 198 | MonTree.Invalidate; 199 | end; 200 | 201 | procedure TBrokerForm.Sync; 202 | var 203 | i, x : integer; 204 | aData : PDataRec; 205 | aNode, bNode : PVirtualNode; 206 | begin 207 | if FServer = nil then 208 | begin 209 | MonTree.Clear; 210 | exit; 211 | end; 212 | MonTree.BeginUpdate; 213 | x := 0; 214 | aNode := MonTree.GetFirst (false); 215 | while (aNode <> nil) and (x < FServer.Brokers.Count) do 216 | begin 217 | aData := MonTree.GetNodeData (aNode); 218 | aData.Broker := FServer.Brokers[x]; 219 | x := x + 1; 220 | aNode := MonTree.GetNext (aNode, false); 221 | end; 222 | if aNode = nil then // ran out of existing 223 | begin 224 | for i := x to FServer.Brokers.Count - 1 do 225 | begin 226 | aNode := MonTree.AddChild (nil); 227 | aData := MonTree.GetNodeData (aNode); 228 | aData.Broker := FServer.Brokers[x]; 229 | end; 230 | end 231 | else // delete any extra 232 | begin 233 | while aNode <> nil do 234 | begin 235 | bNode := MonTree.GetNext (aNode, false); 236 | MonTree.DeleteNode (aNode, false); 237 | aNode := bNode; 238 | end; 239 | end; 240 | MonTree.EndUpdate; 241 | end; 242 | 243 | procedure TBrokerForm.UpdateBtnStatus; 244 | begin 245 | AddBtn.Enabled := (length (IPTxt.Text) > 0); 246 | DelBtn.Enabled := MonTree.GetFirstSelected (false) <> nil; 247 | StartBtn.Enabled := MonTree.GetFirstSelected (false) <> nil; 248 | StopBtn.Enabled := MonTree.GetFirstSelected (false) <> nil; 249 | end; 250 | 251 | end. 252 | -------------------------------------------------------------------------------- /Demo/uServer2.dfm: -------------------------------------------------------------------------------- 1 | object MainForm: TMainForm 2 | Left = 0 3 | Top = 0 4 | BorderStyle = bsToolWindow 5 | Caption = 'MQTT Server / Client Demo' 6 | ClientHeight = 426 7 | ClientWidth = 831 8 | Color = clBtnFace 9 | Font.Charset = DEFAULT_CHARSET 10 | Font.Color = clWindowText 11 | Font.Height = -11 12 | Font.Name = 'Tahoma' 13 | Font.Style = [] 14 | OldCreateOrder = False 15 | OnCreate = FormCreate 16 | OnDestroy = FormDestroy 17 | OnShow = FormShow 18 | PixelsPerInch = 96 19 | TextHeight = 13 20 | object Label2: TLabel 21 | Left = 8 22 | Top = 220 23 | Width = 27 24 | Height = 13 25 | Caption = 'Client' 26 | end 27 | object CIDTxt: TLabel 28 | Left = 370 29 | Top = 8 30 | Width = 3 31 | Height = 13 32 | AutoSize = False 33 | end 34 | object Label4: TLabel 35 | Left = 370 36 | Top = 220 37 | Width = 30 38 | Height = 13 39 | Caption = 'Online' 40 | end 41 | object COnlineTxt: TLabel 42 | Left = 406 43 | Top = 220 44 | Width = 15 45 | Height = 13 46 | Caption = 'NO' 47 | end 48 | object CEnableTxt: TLabel 49 | Left = 348 50 | Top = 220 51 | Width = 15 52 | Height = 13 53 | Caption = 'NO' 54 | end 55 | object Label3: TLabel 56 | Left = 304 57 | Top = 220 58 | Width = 38 59 | Height = 13 60 | Caption = 'Enabled' 61 | end 62 | object Label1: TLabel 63 | Left = 8 64 | Top = 6 65 | Width = 32 66 | Height = 13 67 | Caption = 'Server' 68 | end 69 | object Label5: TLabel 70 | Left = 431 71 | Top = 248 72 | Width = 91 73 | Height = 13 74 | Caption = 'Subscription Topics' 75 | end 76 | object Label6: TLabel 77 | Left = 582 78 | Top = 283 79 | Width = 42 80 | Height = 13 81 | Caption = 'Message' 82 | end 83 | object SClientsTxt: TLabel 84 | Left = 412 85 | Top = 6 86 | Width = 6 87 | Height = 13 88 | Caption = '0' 89 | end 90 | object Label9: TLabel 91 | Left = 370 92 | Top = 6 93 | Width = 32 94 | Height = 13 95 | Caption = 'Clients' 96 | end 97 | object Label7: TLabel 98 | Left = 737 99 | Top = 243 100 | Width = 85 101 | Height = 13 102 | Caption = 'Quality of Service' 103 | end 104 | object Label8: TLabel 105 | Left = 45 106 | Top = 220 107 | Width = 42 108 | Height = 13 109 | Caption = 'Last Msg' 110 | end 111 | object CMsgTxt: TLabel 112 | Left = 93 113 | Top = 220 114 | Width = 40 115 | Height = 13 116 | Caption = '' 117 | end 118 | object Label11: TLabel 119 | Left = 46 120 | Top = 6 121 | Width = 42 122 | Height = 13 123 | Caption = 'Last Msg' 124 | end 125 | object SMsgTxt: TLabel 126 | Left = 94 127 | Top = 6 128 | Width = 40 129 | Height = 13 130 | Caption = '' 131 | end 132 | object Label10: TLabel 133 | Left = 466 134 | Top = 155 135 | Width = 20 136 | Height = 13 137 | Caption = 'Port' 138 | end 139 | object Label13: TLabel 140 | Left = 653 141 | Top = 220 142 | Width = 20 143 | Height = 13 144 | Caption = 'Port' 145 | end 146 | object Label14: TLabel 147 | Left = 173 148 | Top = 6 149 | Width = 19 150 | Height = 13 151 | Caption = 'Qos' 152 | end 153 | object SQosTxt: TLabel 154 | Left = 198 155 | Top = 6 156 | Width = 3 157 | Height = 13 158 | Caption = ' ' 159 | end 160 | object Label16: TLabel 161 | Left = 173 162 | Top = 220 163 | Width = 19 164 | Height = 13 165 | Caption = 'Qos' 166 | end 167 | object CQosTxt: TLabel 168 | Left = 198 169 | Top = 220 170 | Width = 6 171 | Height = 13 172 | Caption = ' ' 173 | end 174 | object Label15: TLabel 175 | Left = 304 176 | Top = 6 177 | Width = 38 178 | Height = 13 179 | Caption = 'Enabled' 180 | end 181 | object SEnableTxt: TLabel 182 | Left = 348 183 | Top = 6 184 | Width = 15 185 | Height = 13 186 | Caption = 'NO' 187 | end 188 | object Label18: TLabel 189 | Left = 581 190 | Top = 247 191 | Width = 25 192 | Height = 13 193 | Caption = 'Topic' 194 | end 195 | object ClientIDTxt: TLabel 196 | Left = 683 197 | Top = 179 198 | Width = 38 199 | Height = 13 200 | Caption = 'ClientID' 201 | end 202 | object PixTxt: TLabel 203 | Left = 618 204 | Top = 2 205 | Width = 193 206 | Height = 13 207 | Alignment = taCenter 208 | AutoSize = False 209 | end 210 | object Label12: TLabel 211 | Left = 653 212 | Top = 196 213 | Width = 22 214 | Height = 13 215 | Caption = 'Host' 216 | end 217 | object Memo1: TMemo 218 | Left = 2 219 | Top = 20 220 | Width = 421 221 | Height = 183 222 | ScrollBars = ssVertical 223 | TabOrder = 0 224 | OnDblClick = Memo1DblClick 225 | end 226 | object Button15: TButton 227 | Left = 431 228 | Top = 15 229 | Width = 69 230 | Height = 27 231 | Caption = 'Start' 232 | TabOrder = 1 233 | OnClick = Button15Click 234 | end 235 | object Button16: TButton 236 | Left = 501 237 | Top = 15 238 | Width = 69 239 | Height = 27 240 | Caption = 'Stop' 241 | TabOrder = 2 242 | OnClick = Button16Click 243 | end 244 | object Memo2: TMemo 245 | Left = 7 246 | Top = 234 247 | Width = 279 248 | Height = 194 249 | ScrollBars = ssVertical 250 | TabOrder = 3 251 | OnDblClick = Memo2DblClick 252 | end 253 | object Button17: TButton 254 | Left = 501 255 | Top = 214 256 | Width = 69 257 | Height = 27 258 | Caption = 'Stop' 259 | TabOrder = 4 260 | OnClick = Button17Click 261 | end 262 | object Button18: TButton 263 | Left = 431 264 | Top = 214 265 | Width = 69 266 | Height = 27 267 | Caption = 'Start' 268 | TabOrder = 5 269 | OnClick = Button18Click 270 | end 271 | object Button22: TButton 272 | Left = 429 273 | Top = 48 274 | Width = 80 275 | Height = 27 276 | Caption = 'Show Clients' 277 | TabOrder = 6 278 | OnClick = Button22Click 279 | end 280 | object Button24: TButton 281 | Left = 581 282 | Top = 373 283 | Width = 69 284 | Height = 25 285 | Caption = 'Publish' 286 | TabOrder = 7 287 | OnClick = Button24Click 288 | end 289 | object rb1: TRadioButton 290 | Left = 735 291 | Top = 259 292 | Width = 87 293 | Height = 17 294 | Caption = 'At Most Once' 295 | TabOrder = 8 296 | OnClick = rb1Click 297 | end 298 | object rb2: TRadioButton 299 | Tag = 1 300 | Left = 735 301 | Top = 275 302 | Width = 91 303 | Height = 17 304 | Caption = 'At Least Once' 305 | Checked = True 306 | TabOrder = 9 307 | TabStop = True 308 | OnClick = rb1Click 309 | end 310 | object rb3: TRadioButton 311 | Tag = 2 312 | Left = 735 313 | Top = 291 314 | Width = 87 315 | Height = 17 316 | Caption = 'Exactly Once' 317 | TabOrder = 10 318 | OnClick = rb1Click 319 | end 320 | object Button1: TButton 321 | Left = 431 322 | Top = 399 323 | Width = 69 324 | Height = 25 325 | Caption = 'Show' 326 | TabOrder = 11 327 | OnClick = Button1Click 328 | end 329 | object PortTxt: TEdit 330 | Left = 497 331 | Top = 152 332 | Width = 85 333 | Height = 21 334 | TabOrder = 12 335 | Text = '1883' 336 | OnKeyPress = PortTxtKeyPress 337 | end 338 | object Button2: TButton 339 | Left = 429 340 | Top = 113 341 | Width = 80 342 | Height = 27 343 | Caption = 'Brokers' 344 | TabOrder = 13 345 | OnClick = Button2Click 346 | end 347 | object Button3: TButton 348 | Left = 573 349 | Top = 214 350 | Width = 69 351 | Height = 27 352 | Caption = 'Kill' 353 | TabOrder = 14 354 | OnClick = Button3Click 355 | end 356 | object CPortTxt: TEdit 357 | Left = 683 358 | Top = 217 359 | Width = 85 360 | Height = 21 361 | TabOrder = 15 362 | Text = '1883' 363 | OnKeyPress = PortTxtKeyPress 364 | end 365 | object BounceBox: TCheckBox 366 | Left = 517 367 | Top = 49 368 | Width = 97 369 | Height = 17 370 | Caption = ' Local Bounce' 371 | TabOrder = 16 372 | OnClick = BounceBoxClick 373 | end 374 | object TopicsTxt: TMemo 375 | Left = 431 376 | Top = 263 377 | Width = 144 378 | Height = 104 379 | Lines.Strings = ( 380 | 'update/memo' 381 | 'update/png/+' 382 | 'will/#') 383 | TabOrder = 17 384 | end 385 | object Button5: TButton 386 | Left = 431 387 | Top = 373 388 | Width = 69 389 | Height = 25 390 | Caption = 'Subscribe' 391 | TabOrder = 18 392 | OnClick = Button5Click 393 | end 394 | object Button6: TButton 395 | Left = 506 396 | Top = 373 397 | Width = 69 398 | Height = 25 399 | Caption = 'Unsubscribe' 400 | TabOrder = 19 401 | OnClick = Button6Click 402 | end 403 | object TopicTxt: TEdit 404 | Left = 581 405 | Top = 263 406 | Width = 146 407 | Height = 21 408 | TabOrder = 20 409 | Text = 'update/memo' 410 | end 411 | object MsgBox: TMemo 412 | Left = 582 413 | Top = 298 414 | Width = 145 415 | Height = 69 416 | Lines.Strings = ( 417 | 'WARNING' 418 | 'Coolant Leak.' 419 | 'Primary Cooling System.' 420 | 'Reactor 5.') 421 | TabOrder = 21 422 | end 423 | object CMBox: TCheckBox 424 | Left = 517 425 | Top = 65 426 | Width = 97 427 | Height = 17 428 | Caption = 'Client Monitor' 429 | Checked = True 430 | State = cbChecked 431 | TabOrder = 22 432 | end 433 | object CMBox2: TCheckBox 434 | Left = 735 435 | Top = 361 436 | Width = 97 437 | Height = 17 438 | Caption = 'Client Monitor' 439 | Checked = True 440 | State = cbChecked 441 | TabOrder = 23 442 | end 443 | object BounceBox2: TCheckBox 444 | Left = 735 445 | Top = 345 446 | Width = 97 447 | Height = 17 448 | Caption = ' Local Bounce' 449 | TabOrder = 24 450 | OnClick = BounceBox2Click 451 | end 452 | object CleanBox2: TCheckBox 453 | Left = 735 454 | Top = 312 455 | Width = 97 456 | Height = 17 457 | Caption = 'Clean' 458 | TabOrder = 25 459 | OnClick = CleanBox2Click 460 | end 461 | object Button4: TButton 462 | Left = 774 463 | Top = 216 464 | Width = 53 465 | Height = 23 466 | Caption = 'Update' 467 | TabOrder = 26 468 | OnClick = Button4Click 469 | end 470 | object Memo3: TMemo 471 | Left = 292 472 | Top = 234 473 | Width = 133 474 | Height = 189 475 | TabOrder = 27 476 | OnDblClick = Memo3DblClick 477 | end 478 | object RetainBox: TCheckBox 479 | Left = 735 480 | Top = 327 481 | Width = 97 482 | Height = 17 483 | Caption = 'Retain' 484 | TabOrder = 28 485 | OnClick = RetainBoxClick 486 | end 487 | object Button8: TButton 488 | Left = 556 489 | Top = 399 490 | Width = 50 491 | Height = 25 492 | Caption = 'Send' 493 | TabOrder = 29 494 | OnClick = Button8Click 495 | end 496 | object Button9: TButton 497 | Left = 769 498 | Top = 378 499 | Width = 50 500 | Height = 25 501 | Caption = 'Off' 502 | TabOrder = 30 503 | OnClick = Button9Click 504 | end 505 | object JTxt: TEdit 506 | Left = 674 507 | Top = 380 508 | Width = 33 509 | Height = 21 510 | TabOrder = 31 511 | Text = '3' 512 | OnKeyPress = JTxtKeyPress 513 | end 514 | object Edit1: TEdit 515 | Left = 618 516 | Top = 403 517 | Width = 89 518 | Height = 21 519 | TabOrder = 32 520 | Text = '3' 521 | end 522 | object Button10: TButton 523 | Left = 717 524 | Top = 378 525 | Width = 50 526 | Height = 25 527 | Caption = 'On' 528 | TabOrder = 33 529 | OnClick = Button10Click 530 | end 531 | object AddrTxt: TEdit 532 | Left = 683 533 | Top = 193 534 | Width = 129 535 | Height = 21 536 | TabOrder = 34 537 | Text = '10.0.0.2' 538 | end 539 | object TTServer: TMQTTServer 540 | MaxRetries = 4 541 | RetryTime = 60 542 | Port = 1883 543 | LocalBounce = False 544 | OnFailure = TTServerFailure 545 | OnStoreSession = TTServerStoreSession 546 | OnRestoreSession = TTServerRestoreSession 547 | OnDeleteSession = TTServerDeleteSession 548 | OnBrokerOnline = TTServerBrokerOnline 549 | OnBrokerOffline = TTServerBrokerOffline 550 | OnBrokerEnableChange = TTServerBrokerEnableChange 551 | OnEnableChange = TTServerEnableChange 552 | OnSubscription = TTServerSubscription 553 | OnClientsChange = TTServerClientsChange 554 | OnCheckUser = TTServerCheckUser 555 | OnObituary = TTServerObituary 556 | OnMon = TTServerMon 557 | Left = 526 558 | Top = 102 559 | end 560 | object TTClient: TMQTTClient 561 | KeepAlive = 10 562 | MaxRetries = 8 563 | RetryTime = 60 564 | Clean = True 565 | Broker = False 566 | AutoSubscribe = False 567 | Username = 'admin' 568 | Password = 'password' 569 | Host = 'localhost' 570 | Port = 1883 571 | LocalBounce = False 572 | OnClientID = TTClientClientID 573 | OnMon = TTClientMon 574 | OnOnline = TTClientOnline 575 | OnOffline = TTClientOffline 576 | OnEnableChange = TTClientEnableChange 577 | OnFailure = TTClientFailure 578 | OnMsg = TTClientMsg 579 | Left = 564 580 | Top = 102 581 | end 582 | end 583 | -------------------------------------------------------------------------------- /Demo/uServer2.pas: -------------------------------------------------------------------------------- 1 | unit uServer2; 2 | 3 | interface 4 | 5 | uses 6 | Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 7 | Dialogs, OverbyteIcsWndControl, OverbyteIcsWSocket, OverbyteIcsWSocketS, uMQTT, 8 | StdCtrls, uMQTTComps, ExtCtrls {, GR32_RangeBars}; 9 | 10 | type 11 | TMainForm = class (TForm) 12 | Memo1: TMemo; 13 | Button15: TButton; 14 | Button16: TButton; 15 | Memo2: TMemo; 16 | Button17: TButton; 17 | Button18: TButton; 18 | Button22: TButton; 19 | Button24: TButton; 20 | Label2: TLabel; 21 | CIDTxt: TLabel; 22 | Label4: TLabel; 23 | COnlineTxt: TLabel; 24 | CEnableTxt: TLabel; 25 | Label3: TLabel; 26 | Label1: TLabel; 27 | Label5: TLabel; 28 | Label6: TLabel; 29 | SClientsTxt: TLabel; 30 | Label9: TLabel; 31 | rb1: TRadioButton; 32 | rb2: TRadioButton; 33 | rb3: TRadioButton; 34 | Label7: TLabel; 35 | Button1: TButton; 36 | Label8: TLabel; 37 | CMsgTxt: TLabel; 38 | Label11: TLabel; 39 | SMsgTxt: TLabel; 40 | PortTxt: TEdit; 41 | Label10: TLabel; 42 | Button2: TButton; 43 | Button3: TButton; 44 | CPortTxt: TEdit; 45 | Label13: TLabel; 46 | Label14: TLabel; 47 | SQosTxt: TLabel; 48 | Label16: TLabel; 49 | CQosTxt: TLabel; 50 | Label15: TLabel; 51 | SEnableTxt: TLabel; 52 | BounceBox: TCheckBox; 53 | TopicsTxt: TMemo; 54 | Button5: TButton; 55 | Button6: TButton; 56 | TopicTxt: TEdit; 57 | Label18: TLabel; 58 | MsgBox: TMemo; 59 | CMBox: TCheckBox; 60 | CMBox2: TCheckBox; 61 | BounceBox2: TCheckBox; 62 | CleanBox2: TCheckBox; 63 | ClientIDTxt: TLabel; 64 | TTServer: TMQTTServer; 65 | TTClient: TMQTTClient; 66 | PixTxt: TLabel; 67 | Button4: TButton; 68 | Memo3: TMemo; 69 | RetainBox: TCheckBox; 70 | Button8: TButton; 71 | Button9: TButton; 72 | JTxt: TEdit; 73 | Edit1: TEdit; 74 | Button10: TButton; 75 | AddrTxt: TEdit; 76 | Label12: TLabel; 77 | 78 | procedure FormCreate (Sender : TObject); 79 | procedure FormDestroy(Sender: TObject); 80 | procedure Memo1DblClick(Sender: TObject); 81 | procedure Button15Click(Sender: TObject); 82 | procedure Button16Click(Sender: TObject); 83 | procedure Memo2DblClick(Sender: TObject); 84 | procedure Button18Click(Sender: TObject); 85 | procedure Button17Click(Sender: TObject); 86 | procedure Button22Click(Sender: TObject); 87 | procedure rb1Click(Sender: TObject); 88 | procedure Button1Click(Sender: TObject); 89 | procedure Button2Click(Sender: TObject); 90 | procedure FormShow(Sender: TObject); 91 | procedure PortTxtKeyPress(Sender: TObject; var Key: Char); 92 | procedure Button3Click(Sender: TObject); 93 | procedure BounceBoxClick(Sender: TObject); 94 | procedure Button5Click(Sender: TObject); 95 | procedure Button6Click(Sender: TObject); 96 | procedure Button24Click(Sender: TObject); 97 | procedure BounceBox2Click(Sender: TObject); 98 | procedure CleanBox2Click(Sender: TObject); 99 | procedure TTClientFailure(Sender: TObject; aReason: Integer; 100 | var CloseClient: Boolean); 101 | procedure TTClientMon(Sender: TObject; aStr: string); 102 | procedure TTClientEnableChange(Sender: TObject); 103 | procedure TTClientMsg (Sender: TObject; aTopic: UTF8String; 104 | aMessage: AnsiString; aQos : TMQTTQOSType; aRetained : Boolean); 105 | procedure TTClientOffline(Sender: TObject; Graceful: Boolean); 106 | procedure TTClientOnline(Sender: TObject); 107 | procedure TTServerCheckUser(Sender: TObject; aUser, aPass: UTF8String; 108 | var Allowed: Boolean); 109 | procedure TTServerClientsChange(Sender: TObject; anID: Word); 110 | procedure TTServerFailure(Sender: TObject; aReason: Integer; 111 | var CloseClient: Boolean); 112 | procedure TTServerEnableChange(Sender: TObject); 113 | procedure TTServerMon(Sender: TObject; aStr: string); 114 | procedure TTServerRestoreSession(Sender: TObject; aClientID: UTF8String); 115 | procedure TTServerStoreSession(Sender: TObject; aClientID: UTF8String); 116 | procedure TTServerDeleteSession(Sender: TObject; aClientID: UTF8String); 117 | procedure TTServerObituary(Sender: TObject; var aTopic, 118 | aMessage: UTF8String; var aQos: TMQTTQOSType); 119 | procedure TTServerSubscription(Sender: TObject; aTopic: UTF8String; 120 | var RequestedQos: TMQTTQOSType); 121 | procedure TTServerBrokerOffline(Sender: TObject; Graceful: Boolean); 122 | procedure TTServerBrokerOnline(Sender: TObject); 123 | procedure TTServerBrokerEnableChange(Sender: TObject); 124 | procedure Button4Click(Sender: TObject); 125 | procedure TTClientClientID(Sender: TObject; var aClientID: UTF8String); 126 | procedure Memo3DblClick(Sender: TObject); 127 | procedure RetainBoxClick(Sender: TObject); 128 | procedure Button8Click(Sender: TObject); 129 | procedure Button9Click(Sender: TObject); 130 | procedure JTxtKeyPress(Sender: TObject; var Key: Char); 131 | procedure GaugeBar1UserChange(Sender: TObject); 132 | procedure Button10Click(Sender: TObject); 133 | { Private declarations } 134 | public 135 | { Public declarations } 136 | aQos : TMQTTQOSType; 137 | aRetain : Boolean; 138 | procedure StoreSettings; 139 | procedure LoadSettings; 140 | procedure SMonHeader (Sender : TObject; aMsgType: TMQTTMessageType; aDup: Boolean; 141 | aQos: TMQTTQOSType; aRetain: Boolean); 142 | procedure CMonHeader (Sender : TObject; aMsgType: TMQTTMessageType; aDup: Boolean; 143 | aQos: TMQTTQOSType; aRetain: Boolean); 144 | end; 145 | 146 | var 147 | MainForm: TMainForm; 148 | 149 | implementation 150 | 151 | uses uBrokers, IniFiles; 152 | 153 | {$R *.dfm} 154 | { TForm1 } 155 | 156 | procedure TMainForm.BounceBox2Click(Sender: TObject); 157 | begin 158 | TTClient.LocalBounce := BounceBox2.Checked; 159 | end; 160 | 161 | procedure TMainForm.BounceBoxClick(Sender: TObject); 162 | begin 163 | TTServer.LocalBounce := BounceBox.Checked; 164 | end; 165 | 166 | procedure TMainForm.Button10Click(Sender: TObject); 167 | begin 168 | TTClient.Publish ('csi/pnl/set/state/' + UTF8String (JTxt.Text), '1', qtAT_MOST_ONCE, false); 169 | end; 170 | 171 | procedure TMainForm.Button15Click(Sender: TObject); 172 | begin 173 | TTServer.Port := StrToIntDef (PortTxt.Text, 1883); 174 | TTServer.Activate (true); 175 | end; 176 | 177 | procedure TMainForm.Button16Click (Sender: TObject); 178 | begin 179 | TTServer.Activate(false); 180 | end; 181 | 182 | procedure TMainForm.Button17Click (Sender: TObject); 183 | begin 184 | TTClient.Activate (false); 185 | end; 186 | 187 | procedure TMainForm.Button18Click (Sender: TObject); 188 | begin 189 | TTClient.Host := AddrTxt.Text; 190 | TTClient.Port := StrToIntDef (CPortTxt.Text, 1883); 191 | TTClient.Activate (true); 192 | end; 193 | 194 | procedure TMainForm.Button1Click (Sender: TObject); 195 | var 196 | j : integer; 197 | x : cardinal; 198 | begin 199 | memo2.Lines.Add (''); 200 | memo2.Lines.Add('------ Client ' + string (TTClient.Parser.ClientID) + ' -------'); 201 | memo2.Lines.Add (format ('Username "%s" Password "%s"', [TTClient.Parser.Username, TTClient.Parser.Password])); 202 | memo2.Lines.Add (format ('Keep Alive "%d" Retry Time "%d" Max Retries "%d"', [TTClient.Parser.KeepAlive, TTClient.Parser.RetryTime, TTClient.Parser.MaxRetries])); 203 | memo2.Lines.Add (format ('Will Topic "%s" Message "%s" @ %s', [TTClient.Parser.WillTopic, TTClient.Parser.WillMessage, QosNames [TTClient.Parser.WillQos]])); 204 | memo2.Lines.Add ('Subscriptions ----'); 205 | for j := 0 to TTClient.Subscriptions.Count - 1 do 206 | begin 207 | x := cardinal (TTClient.Subscriptions.Objects[j]) and $03; 208 | if (cardinal (TTClient.Subscriptions.Objects[j]) shr 8) and $ff = $ff then 209 | memo2.Lines.Add (' "' + TTClient.Subscriptions[j] + '" @ ' + QOSNames[TMQTTQOSType (x)] + ' Acked.') 210 | else 211 | memo2.Lines.Add (' "' + TTClient.Subscriptions[j] + '" @ ' + QOSNames[TMQTTQOSType (x)]); 212 | end; 213 | end; 214 | 215 | procedure TMainForm.Button22Click(Sender: TObject); 216 | var 217 | i, j : integer; 218 | aClient : TClient; 219 | x : cardinal; 220 | begin 221 | for i := 0 to TTServer.Server.ClientCount - 1 do 222 | begin 223 | aClient := TClient (TTServer.Server.Client[i]); 224 | memo1.Lines.Add (''); 225 | memo1.Lines.Add('------ Client ' + string (aClient.Parser.ClientID) + ' -------'); 226 | memo1.Lines.Add (format ('Username "%s" Password "%s"', [aClient.Parser.Username, aClient.Parser.Password])); 227 | memo1.Lines.Add (format ('Keep Alive "%d" Retry Time "%d" Max Retries "%d"', [aClient.Parser.KeepAlive, aClient.Parser.RetryTime, aClient.Parser.MaxRetries])); 228 | memo1.Lines.Add (format ('Will Topic "%s" Message "%s" @ %s', [aClient.Parser.WillTopic, aClient.Parser.WillMessage, QosNames [aClient.Parser.WillQos]])); 229 | memo1.Lines.Add ('Subscriptions ----'); 230 | for j := 0 to aClient.Subscriptions.Count - 1 do 231 | begin 232 | x := cardinal (aClient.Subscriptions.Objects[j]) and $03; 233 | memo1.Lines.Add (' "' + aClient.Subscriptions[j] + '" @ ' + QOSNames[TMQTTQOSType (x)]); 234 | end; 235 | end; 236 | end; 237 | 238 | procedure TMainForm.Button24Click (Sender: TObject); 239 | var 240 | i, x : integer; 241 | aStr : AnsiString; 242 | begin 243 | aStr := ''; 244 | for i := 0 to MsgBox.Lines.Count - 1 do 245 | begin 246 | x := length (MsgBox.Lines[i]); 247 | aStr := aStr + AnsiChar (x div $100) + AnsiChar (x mod $100) + AnsiString (MsgBox.Lines[i]); 248 | end; 249 | TTClient.Publish (UTF8String (TopicTxt.Text), aStr, aQos, aRetain); 250 | end; 251 | 252 | procedure TMainForm.Button2Click (Sender: TObject); 253 | begin 254 | BrokerForm.FServer := TTServer; 255 | BrokerForm.Show; 256 | end; 257 | 258 | procedure TMainForm.Button3Click (Sender: TObject); 259 | begin 260 | try 261 | TTClient.Link.Close; 262 | except 263 | end; 264 | end; 265 | 266 | procedure TMainForm.Button4Click(Sender: TObject); 267 | begin 268 | TTClient.Publish ('request/png/' + TTClient.ClientID, '?', qtEXACTLY_ONCE); 269 | end; 270 | 271 | procedure TMainForm.Button5Click (Sender: TObject); 272 | var 273 | s : TStringlist; 274 | i : integer; 275 | begin 276 | s := TStringList.Create; 277 | for i := 0 to TopicsTxt.Lines.Count - 1 do 278 | s.AddObject(TopicsTxt.Lines[i], TObject (aQos)); 279 | TTClient.Subscribe (s); 280 | s.Free; 281 | end; 282 | 283 | procedure TMainForm.Button6Click(Sender: TObject); 284 | var 285 | s : TStringlist; 286 | i : integer; 287 | begin 288 | s := TStringList.Create; 289 | for i := 0 to TopicsTxt.Lines.Count - 1 do 290 | s.Add (TopicsTxt.Lines[i]); 291 | TTClient.Unsubscribe (s); 292 | s.Free; 293 | end; 294 | 295 | procedure TMainForm.Button8Click(Sender: TObject); 296 | begin 297 | TTClient.Publish ('csi/pnl/set/text/' + UTF8String (JTxt.Text), AnsiString (Edit1.Text), qtAT_MOST_ONCE, false); 298 | end; 299 | 300 | procedure TMainForm.Button9Click(Sender: TObject); 301 | begin 302 | TTClient.Publish ('csi/pnl/set/state/' + UTF8String (JTxt.Text), '0', qtAT_MOST_ONCE, false); 303 | end; 304 | 305 | procedure TMainForm.CleanBox2Click(Sender: TObject); 306 | begin 307 | TTClient.Clean := CleanBox2.Checked; 308 | end; 309 | 310 | procedure TMainForm.CMonHeader(Sender: TObject; aMsgType: TMQTTMessageType; 311 | aDup: Boolean; aQos: TMQTTQOSType; aRetain: Boolean); 312 | begin 313 | CMsgTxt.Caption := MsgNames[aMsgType]; 314 | CQosTxt.Caption := QosNames[aQos]; 315 | end; 316 | 317 | procedure TMainForm.FormCreate (Sender: TObject); 318 | begin 319 | aQos := qtAT_LEAST_ONCE; 320 | aRetain := false; 321 | LoadSettings; 322 | // load retained messages 323 | end; 324 | 325 | procedure TMainForm.FormDestroy(Sender: TObject); 326 | begin 327 | // store retained messages 328 | StoreSettings; 329 | end; 330 | 331 | procedure TMainForm.FormShow (Sender: TObject); 332 | begin 333 | PortTxt.Text := IntToStr (TTServer.Port); 334 | CPortTxt.Text := IntToStr (TTClient.Port); 335 | AddrTxt.Text := TTClient.Host; 336 | BounceBox.Checked := TTServer.LocalBounce; 337 | BounceBox2.Checked := TTClient.LocalBounce; 338 | rb1.Checked := (aQos = qtAT_MOST_ONCE); 339 | rb2.Checked := (aQos = qtAT_LEAST_ONCE); 340 | rb3.Checked := (aQos = qtEXACTLY_ONCE); 341 | CleanBox2.Checked := TTClient.Clean; 342 | ClientIDTxt.Caption := ''; 343 | TTServer.FOnMonHdr := SMonHeader; 344 | TTClient.Parser.OnHeader := CMonHeader; 345 | RetainBox.Checked := aRetain; 346 | end; 347 | 348 | procedure TMainForm.GaugeBar1UserChange(Sender: TObject); 349 | begin 350 | // TTClient.Publish ('csi/pnl/set/amount/' + UTF8String (JTxt.Text), AnsiString (IntToStr (GaugeBar1.Position)), qtAT_MOST_ONCE, false); 351 | end; 352 | 353 | procedure TMainForm.JTxtKeyPress(Sender: TObject; var Key: Char); 354 | begin 355 | if not CharInSet (Key, ['0'..'9', #8]) then Key := #0; 356 | end; 357 | 358 | procedure TMainForm.LoadSettings; 359 | var 360 | anIniFile : string; 361 | begin 362 | anIniFile := ChangeFileExt (Application.ExeName, '.ini'); 363 | with TIniFile.Create (anIniFile) do 364 | begin 365 | TTServer.Port := ReadInteger ('SERVER', 'Port', 1883); 366 | TTServer.LocalBounce := ReadBool ('SERVER', 'Local Bounce', true); 367 | TTClient.Host := ReadString ('CLIENT', 'Host', 'localhost'); 368 | TTClient.Port := ReadInteger ('CLIENT', 'Port', 1883); 369 | TTClient.LocalBounce := ReadBool ('CLIENT', 'Local Bounce', false); 370 | aQos := TMQTTQOSType (ReadInteger ('CLIENT', 'Qos', 1)); 371 | CMBox.Checked := ReadBool ('SERVER', 'Monitor', true); 372 | CMBox2.Checked := ReadBool ('CLIENT', 'Monitor', true); 373 | Free; 374 | end; 375 | end; 376 | 377 | procedure TMainForm.StoreSettings; 378 | var 379 | anIniFile : string; 380 | begin 381 | anIniFile := ChangeFileExt (Application.ExeName, '.ini'); 382 | with TIniFile.Create (anIniFile) do 383 | begin 384 | WriteInteger ('SERVER', 'Port', TTServer.Port); 385 | WriteBool ('SERVER', 'Local Bounce', TTServer.LocalBounce); 386 | writeString ('CLIENT', 'Host', TTClient.Host); 387 | WriteInteger ('CLIENT', 'Port', TTClient.Port); 388 | WriteInteger ('CLIENT', 'Qos', ord (aQos)); 389 | WriteBool ('CLIENT', 'Local Bounce', TTClient.LocalBounce); 390 | WriteBool ('SERVER', 'Monitor', CMBox.Checked); 391 | WriteBool ('CLIENT', 'Monitor', CMBox2.Checked); 392 | Free; 393 | end; 394 | end; 395 | 396 | procedure TMainForm.TTClientClientID(Sender: TObject; 397 | var aClientID: UTF8String); 398 | begin 399 | TTClient.SetWill ('will/' + aClientID, 'I''ve had it folks..', qtEXACTLY_ONCE); 400 | end; 401 | 402 | procedure TMainForm.TTClientEnableChange(Sender: TObject); 403 | begin 404 | CEnableTxt.Caption := ny[TMQTTClient (Sender).Enabled]; 405 | if TMQTTClient (Sender).Enabled then 406 | memo2.Lines.Add ('Client is enabled.') 407 | else 408 | memo2.Lines.Add ('Client is disabled.'); 409 | end; 410 | 411 | procedure TMainForm.TTClientFailure(Sender: TObject; aReason: Integer; 412 | var CloseClient: Boolean); 413 | begin 414 | memo2.Lines.Add ('---- Failure Reported ' + FailureNames (aReason)); 415 | end; 416 | 417 | procedure TMainForm.TTClientMon (Sender: TObject; aStr: string); 418 | begin 419 | if CMBox2.Checked then memo2.Lines.Add (aStr); 420 | end; 421 | 422 | procedure TMainForm.TTClientMsg (Sender: TObject; aTopic: UTF8String; 423 | aMessage: AnsiString; aQos : TMQTTQOSType; aRetained : boolean); 424 | var 425 | i, x : integer; 426 | aStr : string; 427 | ForMe : boolean; 428 | t : TStringList; 429 | begin // Sender TMQTTClient 430 | memo2.Lines.Add ('MESSAGE "' + string (aTopic) + '".'); 431 | memo2.Lines.Add (IntToStr (length (aMessage)) + ' byte(s) @ ' + QOSNames[aQos]); 432 | if aRetained then 433 | memo2.Lines.Add('This is a Retained message.'); 434 | t := SubTopics (aTopic); 435 | if t[0] = 'will' then 436 | memo2.Lines.Add (string (aMessage)) 437 | else if t.Count >= 2 then 438 | begin 439 | if t[0] = 'update' then 440 | begin 441 | if t[1] = 'memo' then 442 | begin 443 | ForMe := true; 444 | if (t.Count > 2) then ForMe := (t[2] = string (TTClient.ClientID)); 445 | if ForMe then 446 | begin 447 | Memo3.Lines.Clear; 448 | i := 1; 449 | while (i + 1) <= length (aMessage) do 450 | begin 451 | aStr := ''; 452 | x := ord (aMessage[i]) * $100 + ord (aMessage[i + 1]); 453 | i := i + 2; 454 | if (x > 0) then 455 | begin 456 | aStr := Copy (string (aMessage), i, x); 457 | i := i + x; 458 | end; 459 | memo3.Lines.Add (aStr); 460 | end; // while 461 | if aRetained then memo3.Lines.Add ('(Retained)'); 462 | end; // forme 463 | end; // [t[1] 464 | end; // t[0] 465 | end; // t.Count >= 2 466 | memo2.Lines.Add ('MESSAGE END'); 467 | t.Free; 468 | end; 469 | 470 | procedure TMainForm.TTClientOffline(Sender: TObject; Graceful: Boolean); 471 | begin 472 | COnlineTxt.Caption := 'NO'; 473 | ClientIDTxt.Caption := ''; 474 | if Graceful then 475 | memo2.Lines.Add ('Client Gracefully Disconnected.') 476 | else 477 | memo2.Lines.Add ('Client Terminated Unexpectedly.'); 478 | end; 479 | 480 | procedure TMainForm.TTClientOnline(Sender: TObject); 481 | begin 482 | COnlineTxt.Caption := 'YES'; 483 | memo2.Lines.Add ('Client is online.'); 484 | ClientIDTxt.Caption := string (TTClient.Parser.ClientID); 485 | Button5Click (Button5); 486 | end; 487 | 488 | procedure TMainForm.TTServerBrokerEnableChange(Sender: TObject); 489 | begin 490 | if BrokerForm.Visible then BrokerForm.RefreshTree; 491 | end; 492 | 493 | procedure TMainForm.TTServerBrokerOffline(Sender: TObject; Graceful: Boolean); 494 | begin 495 | if BrokerForm.Visible then BrokerForm.RefreshTree; 496 | end; 497 | 498 | procedure TMainForm.TTServerBrokerOnline(Sender: TObject); 499 | begin 500 | if BrokerForm.Visible then BrokerForm.RefreshTree; 501 | end; 502 | 503 | procedure TMainForm.TTServerCheckUser(Sender: TObject; aUser, aPass: UTF8String; 504 | var Allowed: Boolean); 505 | begin 506 | memo1.Lines.Add ('Login Approval Username "' + string (aUser) + '" Password "' + string (aPass) + '".'); 507 | Allowed := true; 508 | end; 509 | 510 | procedure TMainForm.TTServerClientsChange (Sender: TObject; anID: Word); 511 | begin 512 | SClientsTxt.Caption := IntToStr (anID); 513 | end; 514 | 515 | procedure TMainForm.TTServerDeleteSession (Sender: TObject; 516 | aClientID: UTF8String); 517 | begin 518 | memo1.Lines.Add ('Delete Session for "' + string (aClientID) + '".'); 519 | end; 520 | 521 | procedure TMainForm.TTServerEnableChange (Sender: TObject); 522 | begin 523 | SEnableTxt.Caption := ny[TTServer.Enabled]; 524 | end; 525 | 526 | procedure TMainForm.TTServerFailure (Sender: TObject; aReason: Integer; 527 | var CloseClient: Boolean); 528 | begin 529 | memo1.Lines.Add ('---- Failure Reported ' + FailureNames (aReason)); 530 | end; 531 | 532 | procedure TMainForm.TTServerMon(Sender: TObject; aStr: string); 533 | begin 534 | if CMBox.Checked then memo1.Lines.Add (aStr); 535 | end; 536 | 537 | procedure TMainForm.TTServerObituary(Sender: TObject; var aTopic, 538 | aMessage: UTF8String; var aQos: TMQTTQOSType); 539 | begin 540 | if not (Sender is TClient) then exit; 541 | memo1.Lines.Add ('Obituary Approval "' + string (aTopic) + '" with message "' + string (aMessage) + '"'); 542 | with TClient (Sender) do 543 | begin 544 | aMessage := Parser.ClientID + ' failed at ' + UTF8String (TimeToStr (Now)) + ' - ' + aMessage; 545 | end; 546 | end; 547 | 548 | procedure TMainForm.TTServerRestoreSession(Sender: TObject; 549 | aClientID: UTF8String); 550 | begin 551 | memo1.Lines.Add ('Restore Session for "' + string (aClientID) + '".'); 552 | end; 553 | 554 | procedure TMainForm.TTServerStoreSession(Sender: TObject; 555 | aClientID: UTF8String); 556 | begin 557 | memo1.Lines.Add ('Store Session for "' + string (aClientID) + '".'); 558 | end; 559 | 560 | procedure TMainForm.TTServerSubscription(Sender: TObject; aTopic: UTF8String; 561 | var RequestedQos: TMQTTQOSType); 562 | begin 563 | memo1.Lines.Add ('Subscription Approval "' + string (aTopic) + '" @ ' + QOSNames [RequestedQOS]); 564 | end; 565 | 566 | procedure TMainForm.Memo1DblClick(Sender: TObject); 567 | begin 568 | Memo1.Lines.Clear; 569 | end; 570 | 571 | procedure TMainForm.Memo2DblClick(Sender: TObject); 572 | begin 573 | memo2.Lines.Clear; 574 | end; 575 | 576 | procedure TMainForm.Memo3DblClick(Sender: TObject); 577 | begin 578 | Memo3.Lines.Clear; 579 | end; 580 | 581 | procedure TMainForm.PortTxtKeyPress(Sender: TObject; var Key: Char); 582 | begin 583 | if not CharInSet (Key, ['0'..'9', #8]) then Key := #0; 584 | end; 585 | 586 | procedure TMainForm.rb1Click (Sender: TObject); 587 | begin 588 | aQos := TMQTTQOSType (TRadioButton (Sender).Tag); 589 | end; 590 | 591 | procedure TMainForm.RetainBoxClick(Sender: TObject); 592 | begin 593 | aRetain := RetainBox.Checked; 594 | end; 595 | 596 | procedure TMainForm.SMonHeader(Sender: TObject; aMsgType: TMQTTMessageType; 597 | aDup: Boolean; aQos: TMQTTQOSType; aRetain: Boolean); 598 | begin 599 | SMsgTxt.Caption := MsgNames[aMsgType]; 600 | SQosTxt.Caption := QosNames[aQos]; 601 | end; 602 | 603 | 604 | end. 605 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | delphi-mqtt 2 | =========== 3 | 4 | MQTT server and client components for Delphi -------------------------------------------------------------------------------- /src/MQTTComponents.dpk: -------------------------------------------------------------------------------- 1 | package MQTTComponents; 2 | 3 | {$R *.res} 4 | {$ALIGN 8} 5 | {$ASSERTIONS ON} 6 | {$BOOLEVAL OFF} 7 | {$DEBUGINFO ON} 8 | {$EXTENDEDSYNTAX ON} 9 | {$IMPORTEDDATA ON} 10 | {$IOCHECKS ON} 11 | {$LOCALSYMBOLS ON} 12 | {$LONGSTRINGS ON} 13 | {$OPENSTRINGS ON} 14 | {$OPTIMIZATION ON} 15 | {$OVERFLOWCHECKS OFF} 16 | {$RANGECHECKS OFF} 17 | {$REFERENCEINFO ON} 18 | {$SAFEDIVIDE OFF} 19 | {$STACKFRAMES OFF} 20 | {$TYPEDADDRESS OFF} 21 | {$VARSTRINGCHECKS ON} 22 | {$WRITEABLECONST OFF} 23 | {$MINENUMSIZE 1} 24 | {$IMAGEBASE $400000} 25 | {$IMPLICITBUILD ON} 26 | 27 | requires 28 | rtl, 29 | vcl, 30 | OverbyteIcsDXeRun; 31 | 32 | contains 33 | uMQTTComps in 'uMQTTComps.pas', 34 | uMQTT in 'uMQTT.pas'; 35 | 36 | end. 37 | -------------------------------------------------------------------------------- /src/MQTTComponents.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {BA2E8961-2769-4173-B5A1-093FF189B3EC} 4 | MQTTComponents.dpk 5 | 12.2 6 | True 7 | Debug 8 | Win32 9 | Package 10 | VCL 11 | DCC32 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | All 29 | true 30 | 00400000 31 | .\$(Config)\$(Platform) 32 | WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;$(DCC_UnitAlias) 33 | .\$(Config)\$(Platform) 34 | 35 | 36 | C:\Delphi Projects\ICS\Delphi\Vc32;$(DCC_UnitSearchPath) 37 | DEBUG;$(DCC_Define) 38 | false 39 | true 40 | 41 | 42 | false 43 | RELEASE;$(DCC_Define) 44 | 0 45 | false 46 | 47 | 48 | 49 | MainSource 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Cfg_2 58 | Base 59 | 60 | 61 | Base 62 | 63 | 64 | Cfg_1 65 | Base 66 | 67 | 68 | 69 | 70 | 71 | Delphi.Personality.12 72 | Package 73 | 74 | 75 | 76 | MQTTComponents.dpk 77 | 78 | 79 | True 80 | False 81 | 1 82 | 0 83 | 0 84 | 0 85 | False 86 | False 87 | False 88 | False 89 | False 90 | 3081 91 | 1252 92 | 93 | 94 | 95 | 96 | 1.0.0.0 97 | 98 | 99 | 100 | 101 | 102 | 1.0.0.0 103 | 104 | 105 | 106 | Microsoft Office 2000 Sample Automation Server Wrapper Components 107 | Microsoft Office XP Sample Automation Server Wrapper Components 108 | 109 | 110 | 111 | True 112 | 113 | 114 | 12 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/MQTTComponents.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjde/delphi-mqtt/65ef33f009f7b96d0e50f35d1591df08be79011f/src/MQTTComponents.res -------------------------------------------------------------------------------- /src/uMQTT.pas: -------------------------------------------------------------------------------- 1 | unit uMQTT; 2 | (* Web Sites 3 | http://www.alphaworks.ibm.com/tech/rsmb 4 | http://www.mqtt.org 5 | 6 | Permission to copy and display the MQ Telemetry Transport specification (the 7 | "Specification"), in any medium without fee or royalty is hereby granted by Eurotech 8 | and International Business Machines Corporation (IBM) (collectively, the "Authors"), 9 | provided that you include the following on ALL copies of the Specification, or portions 10 | thereof, that you make: 11 | A link or URL to the Specification at one of 12 | 1. the Authors' websites. 13 | 2. The copyright notice as shown in the Specification. 14 | 15 | The Authors each agree to grant you a royalty-free license, under reasonable, 16 | non-discriminatory terms and conditions to their respective patents that they deem 17 | necessary to implement the Specification. THE SPECIFICATION IS PROVIDED "AS IS," 18 | AND THE AUTHORS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 19 | IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE 21 | CONTENTS OF THE SPECIFICATION ARE SUITABLE FOR ANY PURPOSE; NOR THAT THE 22 | IMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY THIRD PARTY 23 | PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. THE AUTHORS WILL NOT 24 | BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL 25 | DAMAGES ARISING OUT OF OR RELATING TO ANY USE OR DISTRIBUTION OF THE 26 | SPECIFICATION *) 27 | 28 | interface 29 | uses 30 | Classes; 31 | 32 | const 33 | MQTT_PROTOCOL = 'MQIsdp'; 34 | MQTT_VERSION = 3; 35 | 36 | DefRetryTime = 60; // 6 seconds 37 | DefMaxRetries = 8; 38 | 39 | rsHdr = 0; 40 | rsLen = 1; 41 | rsVarHdr = 2; 42 | rsPayload = 3; 43 | 44 | frKEEPALIVE = 0; // keep alive exceeded 45 | frMAXRETRIES = 1; 46 | 47 | rcACCEPTED = 0; // Connection Accepted 48 | rcPROTOCOL = 1; // Connection Refused: unacceptable protocol version 49 | rcIDENTIFIER = 2; // Connection Refused: identifier rejected 50 | rcSERVER = 3; // Connection Refused: server unavailable 51 | rcUSER = 4; // Connection Refused: bad user name or password 52 | rcAUTHORISED = 5; // Connection Refused: not authorised 53 | // 6-255 Reserved for future use 54 | ny : array [boolean] of string = ('NO', 'YES'); 55 | 56 | type 57 | // Message type 58 | TMQTTMessageType = 59 | ( 60 | // mtReserved0, // 0 Reserved 61 | mtBROKERCONNECT, // 0 Broker request to connect to Broker 62 | mtCONNECT, // 1 Client request to connect to Broker 63 | mtCONNACK, // 2 Connect Acknowledgment 64 | mtPUBLISH, // 3 Publish message 65 | mtPUBACK, // 4 Publish Acknowledgment 66 | mtPUBREC, // 5 Publish Received (assured delivery part 1) 67 | mtPUBREL, // 6 Publish Release (assured delivery part 2) 68 | mtPUBCOMP, // 7 Publish Complete (assured delivery part 3) 69 | mtSUBSCRIBE, // 8 Client Subscribe request 70 | mtSUBACK, // 9 Subscribe Acknowledgment 71 | mtUNSUBSCRIBE, // 10 Client Unsubscribe request 72 | mtUNSUBACK, // 11 Unsubscribe Acknowledgment 73 | mtPINGREQ, // 12 PING Request 74 | mtPINGRESP, // 13 PING Response 75 | mtDISCONNECT, // 14 Client is Disconnecting 76 | mtReserved15 // 15 77 | ); 78 | 79 | TMQTTQOSType = 80 | ( 81 | qtAT_MOST_ONCE, // 0 At most once Fire and Forget <=1 82 | qtAT_LEAST_ONCE, // 1 At least once Acknowledged delivery >=1 83 | qtEXACTLY_ONCE, // 2 Exactly once Assured delivery =1 84 | qtReserved3 // 3 Reserved 85 | ); 86 | 87 | TMQTTStreamEvent = procedure (Sender : TObject; anID : Word; Retry : integer; aStream : TMemoryStream) of object; 88 | TMQTTMonEvent = procedure (Sender : TObject; aStr : string) of object; 89 | TMQTTCheckUserEvent = procedure (Sender : TObject; aUser, aPass : UTF8String; var Allowed : Boolean) of object; 90 | TMQTTPubResponseEvent = procedure (Sender : TObject; aMsg : TMQTTMessageType; anID : Word) of object; 91 | TMQTTIDEvent = procedure (Sender : TObject; anID : Word) of object; 92 | TMQTTAckEvent = procedure (Sender : TObject; aCode : Byte) of object; 93 | TMQTTDisconnectEvent = procedure (Sender : TObject; Graceful : Boolean) of object; 94 | TMQTTSubscriptionEvent = procedure (Sender : TObject; aTopic : UTF8String; var RequestedQos : TMQTTQOSType) of object; 95 | TMQTTSubscribeEvent = procedure (Sender : TObject; anID : Word; Topics : TStringList) of object; 96 | TMQTTUnsubscribeEvent = procedure (Sender : TObject; anID : Word; Topics : TStringList) of object; 97 | TMQTTSubAckEvent = procedure (Sender : TObject; anID : Word; Qoss : array of TMQTTQosType) of object; 98 | TMQTTFailureEvent = procedure (Sender : TObject; aReason : integer; var CloseClient : Boolean) of object; 99 | TMQTTMsgEvent = procedure (Sender : TObject; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; aRetained : boolean) of object; 100 | TMQTTRetainEvent = procedure (Sender : TObject; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType) of object; 101 | TMQTTRetainedEvent = procedure (Sender : TObject; Subscribed : UTF8String; var aTopic : UTF8String; var aMessage : AnsiString; var aQos : TMQTTQOSType) of object; 102 | TMQTTPublishEvent = procedure (Sender : TObject; anID : Word; aTopic : UTF8String; aMessage : AnsiString) of object; 103 | TMQTTClientIDEvent = procedure (Sender : TObject; var aClientID : UTF8String) of object; 104 | TMQTTConnectEvent = procedure (Sender : TObject; 105 | Protocol : UTF8String; 106 | Version : byte; 107 | ClientID, 108 | UserName, Password : UTF8String; 109 | KeepAlive : Word; Clean : Boolean) of object; 110 | TMQTTWillEvent = procedure (Sender : TObject; aTopic, aMessage : UTF8String; aQos : TMQTTQOSType; aRetain : boolean) of object; 111 | TMQTTObituaryEvent = procedure (Sender : TObject; var aTopic, aMessage : UTF8String; var aQos : TMQTTQOSType) of object; 112 | TMQTTHeaderEvent = procedure (Sender : TObject; MsgType: TMQTTMessageType; Dup: Boolean; Qos: TMQTTQOSType; Retain: Boolean) of object; 113 | TMQTTSessionEvent = procedure (Sender : TObject; aClientID : UTF8String) of object; 114 | 115 | TMQTTParser = class 116 | private 117 | FOnSend: TMQTTStreamEvent; 118 | FTxStream : TMemoryStream; 119 | FRxStream : TMemoryStream; 120 | FKeepAliveCount : cardinal; 121 | FKeepAlive : Word; 122 | FWillFlag : boolean; 123 | FRxState, FRxMult, FRxVal : integer; 124 | FOnConnAck: TMQTTAckEvent; 125 | FOnUnsubAck: TMQTTIDEvent; 126 | FOnSubscribe: TMQTTSubscribeEvent; 127 | FOnPing: TNotifyEvent; 128 | FOnDisconnect: TNotifyEvent; 129 | FOnPingResp: TNotifyEvent; 130 | FOnPublish: TMQTTPublishEvent; 131 | FOnConnect: TMQTTConnectEvent; 132 | FOnUnsubscribe: TMQTTUnsubscribeEvent; 133 | FOnSubAck: TMQTTSubAckEvent; 134 | FOnSetWill: TMQTTWillEvent; 135 | FOnHeader: TMQTTHeaderEvent; 136 | FOnMon: TMQTTMonEvent; 137 | FOnPubAck: TMQTTIDEvent; 138 | FOnPubRel: TMQTTIDEvent; 139 | FOnPubComp: TMQTTIDEvent; 140 | FOnPubRec: TMQTTIDEvent; 141 | FMaxRetries: Word; 142 | FRetryTime: Word; 143 | FOnBrokerConnect: TMQTTConnectEvent; 144 | procedure SetKeepAlive(const Value: Word); 145 | public 146 | NosRetries : integer; 147 | RxMsg : TMQTTMessageType; 148 | RxQos : TMQTTQOSType; 149 | RxDup, RxRetain : Boolean; 150 | UserName, Password, 151 | WillTopic: UTF8String; 152 | WillMessage : UTF8String; 153 | WillRetain : Boolean; 154 | WillQos : TMQTTQOSType; 155 | ClientID : UTF8String; 156 | Clean : Boolean; 157 | constructor Create; 158 | destructor Destroy; override; 159 | procedure Reset; 160 | procedure Parse (aStream : TStream); overload; 161 | procedure Parse (aStr : AnsiString); overload; 162 | procedure SetWill (aTopic, aMessage : UTF8String; aQos : TMQTTQOSType; aRetain : boolean); 163 | function CheckKeepAlive : boolean; 164 | procedure Mon (aStr : string); 165 | // client 166 | procedure SendBrokerConnect (aClientID, aUsername, aPassword : UTF8String; aKeepAlive : Word; aClean : Boolean); // non standard 167 | procedure SendConnect (aClientID, aUsername, aPassword : UTF8String; aKeepAlive : Word; aClean : Boolean); 168 | procedure SendPublish (anID : Word; aTopic: UTF8String; aMessage : AnsiString; aQOS : TMQTTQOSType; aDup : boolean = false; aRetain : boolean = false); 169 | procedure SendPing; 170 | procedure SendDisconnect; 171 | procedure SendSubscribe (anID : Word; aTopic : UTF8String; aQOS : TMQTTQOSType); overload; 172 | procedure SendSubscribe (anID : Word; Topics : TStringList); overload; 173 | procedure SendUnsubscribe (anID : Word; aTopic : UTF8String); overload; 174 | procedure SendUnsubscribe (anID : Word; Topics : TStringList); overload; 175 | // server 176 | procedure SendConnAck (aCode : byte); 177 | procedure SendPubAck (anID : Word); 178 | procedure SendPubRec (anID : Word); 179 | procedure SendPubRel (anID : Word; aDup : Boolean = false); 180 | procedure SendPubComp (anID : Word); 181 | procedure SendSubAck (anID : Word; Qoss : array of TMQTTQosType); 182 | procedure SendUnsubAck (anID : Word); 183 | procedure SendPingResp; 184 | property KeepAlive : Word read FKeepAlive write SetKeepAlive; 185 | property RetryTime : Word read FRetryTime write FRetryTime; 186 | property MaxRetries : Word read FMaxRetries write FMaxRetries; 187 | // client 188 | property OnConnAck : TMQTTAckEvent read FOnConnAck write FOnConnAck; 189 | property OnSubAck: TMQTTSubAckEvent read FOnSubAck write FOnSubAck; 190 | property OnPubAck : TMQTTIDEvent read FOnPubAck write FOnPubAck; 191 | property OnPubRel : TMQTTIDEvent read FOnPubRel write FOnPubRel; 192 | property OnPubRec : TMQTTIDEvent read FOnPubRec write FOnPubRec; 193 | property OnPubComp : TMQTTIDEvent read FOnPubComp write FOnPubComp; 194 | property OnUnsubAck : TMQTTIDEvent read FOnUnsubAck write FOnUnsubAck; 195 | property OnPingResp : TNotifyEvent read FOnPingResp write FOnPingResp; 196 | // server 197 | property OnBrokerConnect : TMQTTConnectEvent read FOnBrokerConnect write FOnBrokerConnect; // non standard 198 | property OnConnect : TMQTTConnectEvent read FOnConnect write FOnConnect; 199 | property OnPublish : TMQTTPublishEvent read FOnPublish write FOnPublish; 200 | property OnPing : TNotifyEvent read FOnPing write FOnPing; 201 | property OnDisconnect : TNotifyEvent read FOnDisconnect write FOnDisconnect; 202 | property OnSubscribe : TMQTTSubscribeEvent read FOnSubscribe write FOnSubscribe; 203 | property OnUnsubscribe : TMQTTUnsubscribeEvent read FOnUnsubscribe write FOnUnsubscribe; 204 | property OnSetWill : TMQTTWillEvent read FOnSetWill write FOnSetWill; 205 | property OnHeader : TMQTTHeaderEvent read FOnHeader write FOnHeader; 206 | property OnMon : TMQTTMonEvent read FOnMon write FOnMon; 207 | property OnSend : TMQTTStreamEvent read FOnSend write FOnSend; 208 | end; 209 | 210 | const 211 | MsgNames : array [TMQTTMessageType] of string = 212 | ( 213 | // 'Reserved', // 0 Reserved 214 | 'BROKERCONNECT', // 0 Broker request to connect to Broker 215 | 'CONNECT', // 1 Client request to connect to Broker 216 | 'CONNACK', // 2 Connect Acknowledgment 217 | 'PUBLISH', // 3 Publish message 218 | 'PUBACK', // 4 Publish Acknowledgment 219 | 'PUBREC', // 5 Publish Received (assured delivery part 1) 220 | 'PUBREL', // 6 Publish Release (assured delivery part 2) 221 | 'PUBCOMP', // 7 Publish Complete (assured delivery part 3) 222 | 'SUBSCRIBE', // 8 Client Subscribe request 223 | 'SUBACK', // 9 Subscribe Acknowledgment 224 | 'UNSUBSCRIBE', // 10 Client Unsubscribe request 225 | 'UNSUBACK', // 11 Unsubscribe Acknowledgment 226 | 'PINGREQ', // 12 PING Request 227 | 'PINGRESP', // 13 PING Response 228 | 'DISCONNECT', // 14 Client is Disconnecting 229 | 'Reserved15' // 15 230 | ); 231 | 232 | QOSNames : array [TMQTTQOSType] of string = 233 | ( 234 | 'AT_MOST_ONCE', // 0 At most once Fire and Forget <=1 235 | 'AT_LEAST_ONCE', // 1 At least once Acknowledged delivery >=1 236 | 'EXACTLY_ONCE', // 2 Exactly once Assured delivery =1 237 | 'RESERVED' // 3 Reserved 238 | ); 239 | 240 | function CodeNames (aCode : byte) : string; 241 | function ExtractFileNameOnly (FileName : string) : string; 242 | function FailureNames (aCode : byte) : string; 243 | procedure DebugStr (aStr : string); 244 | 245 | implementation 246 | 247 | uses Windows, SysUtils; 248 | 249 | function ExtractFileNameOnly (FileName : string) : string; 250 | begin 251 | Result := ExtractFileName (FileName); 252 | SetLength (Result, Length (Result) - Length (ExtractFileExt (FileName))); 253 | end; 254 | 255 | function CodeNames (aCode : byte) : string; 256 | begin 257 | case (aCode) of 258 | rcACCEPTED : Result := 'ACCEPTED'; // Connection Accepted 259 | rcPROTOCOL : Result := 'PROTOCOL UNACCEPTABLE'; // Connection Refused: unacceptable protocol version 260 | rcIDENTIFIER : Result := 'IDENTIFIER REJECTED'; // Connection Refused: identifier rejected 261 | rcSERVER : Result := 'SERVER UNAVILABLE'; // Connection Refused: server unavailable 262 | rcUSER : Result := 'BAD LOGIN'; // Connection Refused: bad user name or password 263 | rcAUTHORISED : Result := 'NOT AUTHORISED' 264 | else Result := 'RESERVED ' + IntToStr (aCode); 265 | end; 266 | end; 267 | 268 | function FailureNames (aCode : byte) : string; 269 | begin 270 | case (aCode) of 271 | frKEEPALIVE : Result := 'KEEP ALIVE TIMEOUT'; 272 | frMAXRETRIES : Result := 'MAX RETRIES EXCEEDED'; 273 | else Result := 'RESERVED ' + IntToStr (aCode); 274 | end; 275 | end; 276 | 277 | procedure DebugStr (aStr : string); 278 | begin 279 | OutputDebugString (PChar (aStr)); 280 | end; 281 | 282 | procedure AddByte (aStream : TStream; aByte: Byte); 283 | begin 284 | aStream.Write (aByte, 1); 285 | end; 286 | 287 | procedure AddHdr (aStream : TStream; MsgType: TMQTTMessageType; Dup: Boolean; 288 | Qos: TMQTTQOSType; Retain: Boolean); 289 | begin 290 | { Fixed Header Spec: 291 | bit |7 6 5 4 | |3 | |2 1 | | 0 | 292 | byte 1 |Message Type| |DUP flag| |QoS level| |RETAIN| } 293 | AddByte (aStream, (Ord (MsgType) shl 4) + (ord (Dup) shl 3) + (ord (Qos) shl 1) + ord (Retain)); 294 | end; 295 | 296 | procedure AddLength (aStream : TStream; aLen: integer); 297 | var 298 | x : integer; 299 | dig : byte; 300 | begin 301 | x := aLen; 302 | repeat 303 | dig := x mod 128; 304 | x := x div 128; 305 | if (x > 0) then 306 | dig := dig or $80; 307 | AddByte (aStream, dig); 308 | until (x = 0); 309 | end; 310 | 311 | procedure AddStr (aStream : TStream; aStr: UTF8String); 312 | var 313 | l : integer; 314 | begin 315 | l := length (aStr); 316 | AddByte (aStream, l div $100); 317 | AddByte (aStream, l mod $100); 318 | aStream.Write (aStr[1], length (aStr)); 319 | end; 320 | 321 | function ReadByte (aStream : TStream) : Byte; 322 | begin 323 | if aStream.Position = aStream.Size then 324 | Result := 0 325 | else 326 | aStream.Read (Result, 1); 327 | end; 328 | 329 | function ReadHdr (aStream : TStream; var MsgType: TMQTTMessageType; var Dup: Boolean; 330 | var Qos: TMQTTQOSType; var Retain: Boolean) : byte; 331 | begin 332 | Result := ReadByte (aStream); 333 | { Fixed Header Spec: 334 | bit |7 6 5 4 | |3 | |2 1 | | 0 | 335 | byte 1 |Message Type| |DUP flag| |QoS level| |RETAIN| } 336 | MsgType := TMQTTMessageType ((Result and $f0) shr 4); 337 | Dup := (Result and $08) > 0; 338 | Qos := TMQTTQOSType ((Result and $06) shr 1); 339 | Retain := (Result and $01) > 0; 340 | end; 341 | 342 | function ReadLength (aStream : TStream) : integer; 343 | var 344 | mult : integer; 345 | x : byte; 346 | begin 347 | mult := 0; 348 | Result := 0; 349 | repeat 350 | x := ReadByte (aStream); 351 | Result := Result + ((x and $7f) * mult); 352 | until (x and $80) <> 0; 353 | end; 354 | 355 | function ReadStr (aStream : TStream) : UTF8String; 356 | var 357 | l : integer; 358 | begin 359 | l := ReadByte (aStream) * $100 + ReadByte (aStream); 360 | if aStream.Position + l <= aStream.Size then 361 | begin 362 | SetLength (Result, l); 363 | aStream.Read (Result[1], l); 364 | end; 365 | end; 366 | 367 | { TMQTTParser } 368 | 369 | function TMQTTParser.CheckKeepAlive: boolean; 370 | begin 371 | Result := true; 372 | if FKeepAliveCount > 0 then 373 | begin 374 | FKeepAliveCount := FKeepAliveCount - 1; 375 | Result := (FKeepAliveCount > 0); 376 | end; 377 | end; 378 | 379 | constructor TMQTTParser.Create; 380 | begin 381 | KeepAlive := 10; 382 | FKeepAliveCount := 0; 383 | FMaxRetries := DefMaxRetries; 384 | FRetryTime := DefRetryTime; 385 | NosRetries := 0; 386 | ClientID := ''; 387 | WillTopic := ''; 388 | WillMessage := ''; 389 | FWillFlag := false; 390 | WillQos := qtAT_LEAST_ONCE; 391 | WillRetain := false; 392 | Username := ''; 393 | Password := ''; 394 | FRxState := rsHdr; 395 | FRxMult := 0; 396 | FRxVal := 0; 397 | RxMsg := mtReserved15; 398 | RxQos := qtAT_MOST_ONCE; 399 | RxDup := false; 400 | RxRetain := false; 401 | FTxStream := TMemoryStream.Create; 402 | FRxStream := TMemoryStream.Create; 403 | end; 404 | 405 | destructor TMQTTParser.Destroy; 406 | begin 407 | FTxStream.Free; 408 | FRxStream.Free; 409 | inherited; 410 | end; 411 | 412 | procedure TMQTTParser.Mon (aStr: string); 413 | begin 414 | if Assigned (FOnMon) then FOnMon (Self, 'P ' + aStr); 415 | end; 416 | 417 | procedure TMQTTParser.Parse (aStr: AnsiString); 418 | var 419 | aStream : TMemoryStream; 420 | begin 421 | aStream := TMemoryStream.Create; 422 | aStream.Write (aStr[1], length (aStr)); 423 | aStream.Seek (0, soFromBeginning); 424 | Parse (aStream); 425 | aStream.Free; 426 | end; 427 | 428 | procedure TMQTTParser.Reset; 429 | begin 430 | FRxState := rsHdr; 431 | FRxStream.Clear; 432 | FTxStream.Clear; 433 | RxMsg := mtReserved15; 434 | RxDup := false; 435 | RxQOs := qtAT_MOST_ONCE; 436 | RxRetain := false; 437 | end; 438 | 439 | procedure TMQTTParser.Parse (aStream: TStream); 440 | var 441 | x, fl, vr, wq : byte; 442 | id, ka : Word; 443 | wr, wf, un, ps, cl: Boolean; 444 | wt, wm, ci, pt : UTF8String; 445 | aStr, bStr : UTF8String; 446 | Str : AnsiString; 447 | Strs : TStringList; 448 | Qoss : array of TMQTTQOSType; 449 | begin 450 | while aStream.Position <> aStream.Size do 451 | begin 452 | case FRxState of 453 | rsHdr : 454 | begin 455 | ReadHdr (aStream, RxMsg, RxDup, RxQos, RxRetain); 456 | FRxState := rsLen; 457 | FRxMult := 1; 458 | FRxVal := 0; 459 | if Assigned (FOnHeader) then FOnHeader (Self, RxMsg, RxDup, RxQos, RxRetain); 460 | end; 461 | rsLen : 462 | begin 463 | x := ReadByte (aStream); 464 | FRxVal := FRxVal + ((x and $7f) * FRxMult); 465 | FRxMult := FRxMult * $80; 466 | if (x and $80) = 0 then 467 | begin 468 | FKeepAliveCount := KeepAlive * 10; 469 | FRxStream.Clear; 470 | if FRxVal = 0 then 471 | begin 472 | case RxMsg of 473 | mtPINGREQ : 474 | if Assigned (FOnPing) then FOnPing (Self); 475 | mtPINGRESP : 476 | if Assigned (FOnPingResp) then FOnPingResp (Self); 477 | mtDISCONNECT : 478 | if Assigned (FOnDisconnect) then FOnDisconnect (Self); 479 | end; 480 | FRxState := rsHdr; 481 | end 482 | else 483 | begin 484 | FRxState := rsVarHdr; 485 | end; 486 | end; 487 | end; 488 | rsVarHdr : 489 | begin 490 | x := ReadByte (aStream); 491 | FRxStream.Write (x, 1); 492 | FRxVal := FRxVal - 1; 493 | if FRxVal = 0 then 494 | begin 495 | FRxStream.Seek (0, soFromBeginning); 496 | case RxMsg of 497 | mtBROKERCONNECT, 498 | mtCONNECT : 499 | begin 500 | pt := ReadStr (FRxStream); // protocol 501 | vr := ReadByte (FRxStream); // version 502 | fl := ReadByte (FRxStream); 503 | ka := ReadByte (FRxStream) * $100 + ReadByte (FRxStream); 504 | ci := ReadStr (FRxStream); 505 | wf := (fl and $04) > 0; // will flag 506 | wr := (fl and $10) > 0; // will retain 507 | wq := (fl and $18) shr 3; // will qos 508 | un := (fl and $80) > 0; // user name 509 | ps := (fl and $40) > 0; // pass word 510 | cl := (fl and $02) > 0; // clean 511 | wt := ''; 512 | wm := ''; 513 | if wf then 514 | begin 515 | wt := ReadStr (FRxStream); // will topic 516 | wm := ReadStr (FRxStream); // will message 517 | if Assigned (FOnSetWill) then 518 | FOnSetWill (Self, wt, wm, TMQTTQOSType (wq), wr); 519 | end; 520 | aStr := ''; 521 | bStr := ''; 522 | if un then aStr := ReadStr (FRxStream); // username 523 | if ps then bStr := ReadStr (FRxStream); // password 524 | if RxMsg = mtCONNECT then 525 | begin 526 | if Assigned (FOnConnect) then 527 | FOnConnect (Self, pt, vr, ci, aStr, bStr, ka, cl); 528 | end 529 | else if RxMsg = mtBROKERCONNECT then 530 | begin 531 | if Assigned (FOnBrokerConnect) then 532 | FOnBrokerConnect (Self, pt, vr, ci, aStr, bStr, ka, cl); 533 | end; 534 | end; 535 | mtPUBLISH : 536 | if FRxStream.Size >= 4 then 537 | begin 538 | aStr := ReadStr (FRxStream); 539 | if RxQos in [qtAT_LEAST_ONCE, qtEXACTLY_ONCE] then 540 | id := ReadByte (FRxStream) * $100 + ReadByte (FRxStream) 541 | else 542 | id := 0; // no id when RxQos = 0 543 | SetLength (Str, FRxStream.Size - FRxStream.Position); 544 | if length (Str) > 0 then FRxStream.Read (Str[1], length (Str)); 545 | if Assigned (FOnPublish) then FOnPublish (Self, id, aStr, Str); 546 | end; 547 | mtPUBACK, 548 | mtPUBREC, 549 | mtPUBREL, 550 | mtPUBCOMP : 551 | if FRxStream.Size = 2 then 552 | begin 553 | id := ReadByte (FRxStream) * $100 + ReadByte (FRxStream); 554 | case RxMsg of 555 | mtPUBACK : if Assigned (FOnPubAck) then FonPubAck (Self, id); 556 | mtPUBREC : if Assigned (FOnPubRec) then FonPubRec (Self, id); 557 | mtPUBREL : if Assigned (FOnPubRel) then FonPubRel (Self, id); 558 | mtPUBCOMP : if Assigned (FOnPubComp) then FonPubComp (Self, id); 559 | end; 560 | end; 561 | mtCONNACK : 562 | if FRxStream.Size = 2 then 563 | begin 564 | ReadByte (FRxStream); 565 | id := ReadByte (FRxStream); 566 | if Assigned (FOnConnAck) then 567 | FOnConnAck (Self, id); 568 | end; 569 | mtSUBACK : 570 | if FRxStream.Size >= 2 then 571 | begin 572 | SetLength (Qoss, 0); 573 | id := ReadByte (FRxStream) * $100 + ReadByte (FRxStream); 574 | while FRxStream.Position < FRxStream.Size do 575 | begin 576 | SetLength (Qoss, Length (Qoss) + 1); 577 | Qoss[Length (Qoss) - 1] := TMQTTQOSType (ReadByte (FRxStream) and $03); 578 | end; 579 | if Assigned (FOnSubAck) then FOnSubAck (Self, id, Qoss); 580 | end; 581 | mtUNSUBACK : 582 | if FRxStream.Size = 2 then 583 | begin 584 | ReadByte (FRxStream); 585 | id := ReadByte (FRxStream); 586 | if Assigned (FOnUnsubAck) then 587 | FOnUnsubAck (Self, id); 588 | end; 589 | mtUNSUBSCRIBE : 590 | if FRxStream.Size >= 2 then 591 | begin 592 | id := ReadByte (FRxStream) * $100 + ReadByte (FRxStream); 593 | Strs := TStringList.Create; 594 | while FRxStream.Size >= FRxStream.Position + 2 do // len 595 | begin 596 | aStr := ReadStr (FRxStream); 597 | Strs.Add (string (aStr)); 598 | end; 599 | if Assigned (FOnUnsubscribe) then FOnUnsubscribe (Self, id, Strs); 600 | Strs.Free; 601 | end; 602 | mtSUBSCRIBE : 603 | if FRxStream.Size >= 2 then 604 | begin 605 | id := ReadByte (FRxStream) * $100 + ReadByte (FRxStream); 606 | Strs := TStringList.Create; 607 | while FRxStream.Size >= FRxStream.Position + 3 do // len + qos 608 | begin 609 | aStr := ReadStr (FRxStream); 610 | x := ReadByte (FRxStream) and $03; 611 | Strs.AddObject (string (aStr), TObject (x)); 612 | end; 613 | if Assigned (FOnSubscribe) then FOnSubscribe (Self, id, Strs); 614 | Strs.Free; 615 | end; 616 | end; 617 | FKeepAliveCount := KeepAlive * 10; 618 | FRxState := rsHdr; 619 | end; 620 | end; 621 | end; 622 | end; 623 | end; 624 | 625 | procedure TMQTTParser.SendConnect (aClientID, aUsername, aPassword : UTF8String; aKeepAlive : Word; aClean : Boolean); 626 | var 627 | s : TMemoryStream; 628 | x : byte; 629 | begin 630 | KeepAlive := aKeepAlive; 631 | 632 | FTxStream.Clear; // dup, qos, retain not used 633 | AddHdr (FTxStream, mtCONNECT, false, qtAT_LEAST_ONCE, false); 634 | s := TMemoryStream.Create; 635 | // generate payload 636 | AddStr (s, aClientID); 637 | if FWillFlag then 638 | begin 639 | AddStr (s, WillTopic); 640 | AddStr (s, WillMessage); 641 | end; 642 | if length (aUserName) > 0 then 643 | AddStr (s, aUserName); 644 | if length (aPassword) > 0 then 645 | AddStr (s, aPassword); 646 | // finish fixed header 647 | AddLength (FTxStream, 12 + s.Size); 648 | // variable header 649 | AddStr (FTxStream, MQTT_PROTOCOL); // 00 06 MQIsdp (8) 650 | AddByte (FTxStream, MQTT_VERSION); // 3 (1) 651 | x := 0; 652 | if length (aUserName) > 0 then 653 | x := x or $80; 654 | if length (aPassword) > 0 then 655 | x := x or $40; 656 | if FWillFlag then 657 | begin 658 | x := x or $04; 659 | if WillRetain then 660 | x := x or $10; 661 | x := x or (ord (WillQos) shl 3); 662 | end; 663 | if Clean then 664 | x := x or $02; 665 | AddByte (FTxStream, x); // (1) 666 | AddByte (FTxStream, aKeepAlive div $100); // (1) 667 | AddByte (FTxStream, aKeepAlive mod $100); // (1) 668 | // payload 669 | s.Seek (0, soFromBeginning); 670 | FTxStream.CopyFrom (s, s.Size); 671 | s.Free; 672 | if Assigned (FOnSend) then FOnSend (Self, 0, 0, FTxStream); 673 | end; 674 | 675 | procedure TMQTTParser.SendBrokerConnect(aClientID, aUsername, 676 | aPassword: UTF8String; aKeepAlive: Word; aClean: Boolean); 677 | var 678 | s : TMemoryStream; 679 | x : byte; 680 | begin 681 | KeepAlive := aKeepAlive; 682 | FTxStream.Clear; // dup, qos, retain not used 683 | AddHdr (FTxStream, mtBROKERCONNECT, false, qtAT_LEAST_ONCE, false); 684 | s := TMemoryStream.Create; 685 | // generate payload 686 | AddStr (s, aClientID); 687 | if FWillFlag then 688 | begin 689 | AddStr (s, WillTopic); 690 | AddStr (s, WillMessage); 691 | end; 692 | if length (aUserName) > 0 then 693 | AddStr (s, aUserName); 694 | if length (aPassword) > 0 then 695 | AddStr (s, aPassword); 696 | // finish fixed header 697 | AddLength (FTxStream, 12 + s.Size); 698 | // variable header 699 | AddStr (FTxStream, MQTT_PROTOCOL); // 00 06 MQIsdp (8) 700 | AddByte (FTxStream, MQTT_VERSION); // 3 (1) 701 | x := 0; 702 | if length (aUserName) > 0 then 703 | x := x or $80; 704 | if length (aPassword) > 0 then 705 | x := x or $40; 706 | if FWillFlag then 707 | begin 708 | x := x or $04; 709 | if WillRetain then 710 | x := x or $10; 711 | x := x or (ord (WillQos) shl 3); 712 | end; 713 | if Clean then 714 | x := x or $02; 715 | AddByte (FTxStream, x); // (1) 716 | AddByte (FTxStream, aKeepAlive div $100); // (1) 717 | AddByte (FTxStream, aKeepAlive mod $100); // (1) 718 | // payload 719 | s.Seek (0, soFromBeginning); 720 | FTxStream.CopyFrom (s, s.Size); 721 | s.Free; 722 | if Assigned (FOnSend) then FOnSend (Self, 0, 0, FTxStream); 723 | end; 724 | 725 | procedure TMQTTParser.SendConnAck (aCode: byte); 726 | begin 727 | FTxStream.Clear; // dup, qos, retain not used 728 | AddHdr (FTxStream, mtCONNACK, false, qtAT_MOST_ONCE, false); 729 | AddLength (FTxStream, 2); 730 | AddByte (FTxStream, 0); // reserved (1) 731 | AddByte (FTxStream, aCode); // (1) 732 | if Assigned (FOnSend) then FOnSend (Self, 0, 0, FTxStream); 733 | end; 734 | 735 | procedure TMQTTParser.SendPublish (anID : Word; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; aDup : boolean = false; aRetain : boolean = false); 736 | var 737 | s : TMemoryStream; 738 | begin 739 | FTxStream.Clear; // dup qos and retain used 740 | AddHdr (FTxStream, mtPUBLISH, aDup, aQos, aRetain); 741 | s := TMemoryStream.Create; 742 | AddStr (s, aTopic); 743 | if aQos in [qtAT_LEAST_ONCE, qtEXACTLY_ONCE] then 744 | begin 745 | AddByte (s, anID div $100); 746 | AddByte (s, anID mod $100); 747 | end; 748 | if length (aMessage) > 0 then s.Write (aMessage[1], length (aMessage)); 749 | // payload 750 | s.Seek (0, soFromBeginning); 751 | AddLength (FTxStream, s.Size); 752 | FTxStream.CopyFrom (s, s.Size); 753 | s.Free; 754 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 755 | end; 756 | 757 | procedure TMQTTParser.SendPubAck (anID: Word); 758 | begin 759 | FTxStream.Clear; // dup, qos, retain not used 760 | AddHdr (FTxStream, mtPUBACK, false, qtAT_MOST_ONCE, false); 761 | AddLength (FTxStream, 2); 762 | AddByte (FTxStream, anID div $100); 763 | AddByte (FTxStream, anID mod $100); 764 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 765 | end; 766 | 767 | procedure TMQTTParser.SendPubRec (anID: Word); 768 | begin 769 | FTxStream.Clear; // dup, qos, retain are used 770 | AddHdr (FTxStream, mtPUBREC, false, qtAT_MOST_ONCE, false); 771 | AddLength (FTxStream, 2); 772 | AddByte (FTxStream, anID div $100); 773 | AddByte (FTxStream, anID mod $100); 774 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 775 | end; 776 | 777 | procedure TMQTTParser.SendPubRel (anID: Word; aDup : Boolean = false); 778 | begin 779 | FTxStream.Clear; 780 | AddHdr (FTxStream, mtPUBREL, aDup, qtAT_LEAST_ONCE, false); 781 | AddLength (FTxStream, 2); 782 | AddByte (FTxStream, anID div $100); 783 | AddByte (FTxStream, anID mod $100); 784 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 785 | end; 786 | 787 | procedure TMQTTParser.SendPubComp (anID: Word); 788 | begin 789 | FTxStream.Clear; // dup, qos, retain not used 790 | AddHdr (FTxStream, mtPUBCOMP, false, qtAT_MOST_ONCE, false); 791 | AddLength (FTxStream, 2); 792 | AddByte (FTxStream, anID div $100); 793 | AddByte (FTxStream, anID mod $100); 794 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 795 | end; 796 | 797 | procedure TMQTTParser.SendSubscribe (anID : Word; aTopic : UTF8String; aQOS : TMQTTQOSType); 798 | begin 799 | FTxStream.Clear; // qos and dup used 800 | AddHdr (FTxStream, mtSUBSCRIBE, false, qtAT_LEAST_ONCE, false); 801 | AddLength (FTxStream, 5 + length (aTopic)); 802 | AddByte (FTxStream, anID div $100); 803 | AddByte (FTxStream, anID mod $100); 804 | AddStr (FTxStream, aTopic); 805 | AddByte (FTxStream, ord (aQos)); 806 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 807 | end; 808 | 809 | procedure TMQTTParser.SendSubscribe (anID: Word; Topics: TStringList); 810 | var 811 | i : integer; 812 | s : TMemoryStream; 813 | begin 814 | FTxStream.Clear; // dup qos and retain used 815 | AddHdr (FTxStream, mtSUBSCRIBE, false, qtAT_LEAST_ONCE, false); 816 | s := TMemoryStream.Create; 817 | AddByte (s, anID div $100); 818 | AddByte (s, anID mod $100); 819 | for i := 0 to Topics.Count - 1 do 820 | begin 821 | AddStr (s, UTF8String (Topics[i])); 822 | AddByte (s, byte (Topics.Objects[i]) and $03); 823 | end; 824 | // payload 825 | s.Seek (0, soFromBeginning); 826 | AddLength (FTxStream, s.Size); 827 | FTxStream.CopyFrom (s, s.Size); 828 | s.Free; 829 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 830 | end; 831 | 832 | procedure TMQTTParser.SendUnsubscribe (anID: Word; Topics: TStringList); 833 | var 834 | i : integer; 835 | s : TMemoryStream; 836 | begin 837 | FTxStream.Clear; // qos and dup used 838 | AddHdr (FTxStream, mtUNSUBSCRIBE, false, qtAT_LEAST_ONCE, false); 839 | s := TMemoryStream.Create; 840 | AddByte (s, anID div $100); 841 | AddByte (s, anID mod $100); 842 | for i := 0 to Topics.Count - 1 do 843 | AddStr (s, UTF8String (Topics[i])); 844 | // payload 845 | s.Seek (0, soFromBeginning); 846 | AddLength (FTxStream, s.Size); 847 | FTxStream.CopyFrom (s, s.Size); 848 | s.Free; 849 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 850 | end; 851 | 852 | 853 | procedure TMQTTParser.SendSubAck (anID: Word; Qoss : array of TMQTTQOSType); 854 | var 855 | i : integer; 856 | begin 857 | FTxStream.Clear; // dup, qos, retain not used 858 | AddHdr (FTxStream, mtSUBACK, false, qtAT_MOST_ONCE, false); 859 | AddLength (FTxStream, 2 + length (Qoss)); 860 | AddByte (FTxStream, anID div $100); 861 | AddByte (FTxStream, anID mod $100); 862 | for i := low (Qoss) to high (Qoss) do 863 | AddByte (FTxStream, ord (Qoss[i])); 864 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 865 | end; 866 | 867 | procedure TMQTTParser.SendUnsubscribe (anID: Word; aTopic: UTF8String); 868 | begin 869 | FTxStream.Clear; // qos and dup used 870 | AddHdr (FTxStream, mtUNSUBSCRIBE, false, qtAT_LEAST_ONCE, false); 871 | AddLength (FTxStream, 4 + length (aTopic)); 872 | AddByte (FTxStream, anID div $100); 873 | AddByte (FTxStream, anID mod $100); 874 | AddStr (FTxStream, aTopic); 875 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 876 | end; 877 | 878 | procedure TMQTTParser.SendUnsubAck (anID: Word); 879 | begin 880 | FTxStream.Clear; // dup, qos, retain not used 881 | AddHdr (FTxStream, mtUNSUBACK, false, qtAT_MOST_ONCE, false); 882 | AddLength (FTxStream, 2); 883 | AddByte (FTxStream, anID div $100); 884 | AddByte (FTxStream, anID mod $100); 885 | if Assigned (FOnSend) then FOnSend (Self, anID, 0, FTxStream); 886 | end; 887 | 888 | procedure TMQTTParser.SendPing; 889 | begin 890 | FTxStream.Clear; // dup, qos, retain not used 891 | AddHdr (FTxStream, mtPINGREQ, false, qtAT_MOST_ONCE, false); 892 | AddLength (FTxStream, 0); 893 | if Assigned (FOnSend) then FOnSend (Self, 0, 0, FTxStream); 894 | end; 895 | 896 | procedure TMQTTParser.SendPingResp; 897 | begin 898 | FTxStream.Clear; // dup, qos, retain not used 899 | AddHdr (FTxStream, mtPINGRESP, false, qtAT_MOST_ONCE, false); 900 | AddLength (FTxStream, 0); 901 | if Assigned (FOnSend) then FOnSend (Self, 0, 0, FTxStream); 902 | end; 903 | 904 | procedure TMQTTParser.SendDisconnect; 905 | begin 906 | FTxStream.Clear; 907 | AddHdr (FTxStream, mtDISCONNECT, false, qtAT_MOST_ONCE, false); 908 | AddLength (FTxStream, 0); 909 | if Assigned (FOnSend) then FOnSend (Self, 0, 0, FTxStream); 910 | end; 911 | 912 | procedure TMQTTParser.SetKeepAlive (const Value: Word); 913 | begin 914 | FKeepAlive := Value; 915 | FKeepAliveCount := Value * 10; 916 | end; 917 | 918 | procedure TMQTTParser.SetWill (aTopic, aMessage : UTF8String; aQos : TMQTTQOSType; aRetain : boolean); 919 | begin 920 | WillTopic := aTopic; 921 | WillMessage := aMessage; 922 | WillRetain := aRetain; 923 | WillQos := aQos; 924 | FWillFlag := (length (aTopic) > 0) and (length (aMessage) > 0); 925 | end; 926 | 927 | end. 928 | -------------------------------------------------------------------------------- /src/uMQTTComps.pas: -------------------------------------------------------------------------------- 1 | unit uMQTTComps; 2 | 3 | interface 4 | uses 5 | Classes, uMQTT, OverbyteIcsWndControl, OverbyteIcsWSocket, OverbyteIcsWSocketS, Windows, Messages; 6 | 7 | 8 | (* Todo 9 | Finish Retain 10 | *) 11 | (* Web Sites 12 | http://www.alphaworks.ibm.com/tech/rsmb 13 | http://www.mqtt.org 14 | 15 | Permission to copy and display the MQ Telemetry Transport specification (the 16 | "Specification"), in any medium without fee or royalty is hereby granted by Eurotech 17 | and International Business Machines Corporation (IBM) (collectively, the "Authors"), 18 | provided that you include the following on ALL copies of the Specification, or portions 19 | thereof, that you make: 20 | A link or URL to the Specification at one of 21 | 1. the Authors' websites. 22 | 2. The copyright notice as shown in the Specification. 23 | 24 | The Authors each agree to grant you a royalty-free license, under reasonable, 25 | non-discriminatory terms and conditions to their respective patents that they deem 26 | necessary to implement the Specification. THE SPECIFICATION IS PROVIDED "AS IS," 27 | AND THE AUTHORS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 28 | IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE 30 | CONTENTS OF THE SPECIFICATION ARE SUITABLE FOR ANY PURPOSE; NOR THAT THE 31 | IMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY THIRD PARTY 32 | PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. THE AUTHORS WILL NOT 33 | BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL 34 | DAMAGES ARISING OUT OF OR RELATING TO ANY USE OR DISTRIBUTION OF THE 35 | SPECIFICATION *) 36 | 37 | const 38 | MinVersion = 3; 39 | 40 | type 41 | TClient = class; 42 | TMQTTClient = class; 43 | TMQTTPacketStore = class; 44 | TMQTTMessageStore = class; 45 | 46 | TMQTTPacket = class 47 | ID : Word; 48 | Stamp : TDateTime; 49 | Counter : cardinal; 50 | Retries : integer; 51 | Publishing : Boolean; 52 | Msg : TMemoryStream; 53 | procedure Assign (From : TMQTTPacket); 54 | constructor Create; 55 | destructor Destroy; override; 56 | end; 57 | 58 | TMQTTMessage = class 59 | ID : Word; 60 | Stamp : TDateTime; 61 | LastUsed : TDateTime; 62 | Qos : TMQTTQOSType; 63 | Retained : boolean; 64 | Counter : cardinal; 65 | Retries : integer; 66 | Topic : UTF8String; 67 | Message : AnsiString; 68 | procedure Assign (From : TMQTTMessage); 69 | constructor Create; 70 | destructor Destroy; override; 71 | end; 72 | 73 | TMQTTSession = class 74 | ClientID : UTF8String; 75 | Stamp : TDateTime; 76 | InFlight : TMQTTPacketStore; 77 | Releasables : TMQTTMessageStore; 78 | constructor Create; 79 | destructor Destroy; override; 80 | end; 81 | 82 | TMQTTSessionStore = class 83 | List : TList; 84 | Stamp : TDateTime; 85 | function GetItem (Index: Integer): TMQTTSession; 86 | procedure SetItem (Index: Integer; const Value: TMQTTSession); 87 | property Items [Index: Integer]: TMQTTSession read GetItem write SetItem; default; 88 | function Count : integer; 89 | procedure Clear; 90 | function GetSession (ClientID : UTF8String) : TMQTTSession; 91 | procedure StoreSession (ClientID : UTF8String; aClient : TClient); overload; 92 | procedure StoreSession (ClientID : UTF8String; aClient : TMQTTClient); overload; 93 | procedure DeleteSession (ClientID : UTF8String); 94 | procedure RestoreSession (ClientID : UTF8String; aClient : TClient); overload; 95 | procedure RestoreSession (ClientID : UTF8String; aClient : TMQTTClient); overload; 96 | constructor Create; 97 | destructor Destroy; override; 98 | end; 99 | 100 | TMQTTPacketStore = class 101 | List : TList; 102 | Stamp : TDateTime; 103 | function GetItem (Index: Integer): TMQTTPacket; 104 | procedure SetItem (Index: Integer; const Value: TMQTTPacket); 105 | property Items [Index: Integer]: TMQTTPacket read GetItem write SetItem; default; 106 | function Count : integer; 107 | procedure Clear; 108 | procedure Assign (From : TMQTTPacketStore); 109 | function AddPacket (anID : Word; aMsg : TMemoryStream; aRetry : cardinal; aCount : cardinal) : TMQTTPacket; 110 | procedure DelPacket (anID : Word); 111 | function GetPacket (anID : Word) : TMQTTPacket; 112 | procedure Remove (aPacket : TMQTTPacket); 113 | constructor Create; 114 | destructor Destroy; override; 115 | end; 116 | 117 | TMQTTMessageStore = class 118 | List : TList; 119 | Stamp : TDateTime; 120 | function GetItem (Index: Integer): TMQTTMessage; 121 | procedure SetItem (Index: Integer; const Value: TMQTTMessage); 122 | property Items [Index: Integer]: TMQTTMessage read GetItem write SetItem; default; 123 | function Count : integer; 124 | procedure Clear; 125 | procedure Assign (From : TMQTTMessageStore); 126 | function AddMsg (anID : Word; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; aRetry : cardinal; aCount : cardinal; aRetained : Boolean = false) : TMQTTMessage; 127 | procedure DelMsg (anID : Word); 128 | function GetMsg (anID : Word) : TMQTTMessage; 129 | procedure Remove (aMsg : TMQTTMessage); 130 | constructor Create; 131 | destructor Destroy; override; 132 | end; 133 | 134 | TClient = class (TWSocketClient) 135 | private 136 | FOnMon : TMQTTMonEvent; 137 | FGraceful : boolean; 138 | FBroker : Boolean; // non standard 139 | FOnSubscriptionChange: TNotifyEvent; 140 | procedure DoSend (Sender : TObject; anID : Word; aRetry : integer; aStream : TMemoryStream); 141 | procedure RxSubscribe (Sender : TObject; anID : Word; Topics : TStringList); 142 | procedure RxUnsubscribe (Sender : TObject; anID : Word; Topics : TStringList); 143 | procedure RxPubAck (Sender : TObject; anID : Word); 144 | procedure RxPubRec (Sender : TObject; anID : Word); 145 | procedure RxPubRel (Sender : TObject; anID : Word); 146 | procedure RxPubComp (Sender : TObject; anID : Word); 147 | public 148 | Subscriptions : TStringList; 149 | Parser : TMQTTParser; 150 | InFlight : TMQTTPacketStore; 151 | Releasables : TMQTTMessageStore; 152 | procedure Mon (aStr : string); 153 | procedure DoData (Sender : TObject; ErrCode : Word); 154 | procedure DoSetWill (Sender : TObject; aTopic, aMessage : UTF8String; aQOS : TMQTTQOSType; aRetain : boolean); 155 | constructor Create (anOwner : TComponent); override; 156 | destructor Destroy; override; 157 | property OnSubscriptionChange : TNotifyEvent read FOnSubscriptionChange write FOnSubscriptionChange; 158 | property OnMon : TMQTTMonEvent read FOnMon write FOnMon; 159 | end; 160 | 161 | TMQTTClient = class (TComponent) 162 | private 163 | Timers : HWnd; 164 | FUsername, FPassword : UTF8String; 165 | FMessageID : Word; 166 | FHost : string; 167 | FPort : integer; 168 | FEnable, FOnline : Boolean; 169 | FGraceful : Boolean; 170 | FOnMon : TMQTTMonEvent; 171 | FOnOnline: TNotifyEvent; 172 | FOnOffline: TMQTTDisconnectEvent; 173 | FOnEnableChange: TNotifyEvent; 174 | FOnMsg: TMQTTMsgEvent; 175 | FOnFailure: TMQTTFailureEvent; 176 | FLocalBounce: Boolean; 177 | FAutoSubscribe: Boolean; 178 | FOnClientID : TMQTTClientIDEvent; 179 | FBroker: Boolean; // non standard 180 | procedure DoSend (Sender : TObject; anID : Word; aRetry : integer; aStream : TMemoryStream); 181 | procedure RxConnAck (Sender : TObject; aCode : byte); 182 | procedure RxSubAck (Sender : TObject; anID : Word; Qoss : array of TMQTTQosType); 183 | procedure RxPubAck (Sender : TObject; anID : Word); 184 | procedure RxPubRec (Sender : TObject; anID : Word); 185 | procedure RxPubRel (Sender : TObject; anID : Word); 186 | procedure RxPubComp (Sender : TObject; anID : Word); 187 | procedure RxPublish (Sender : TObject; anID : Word; aTopic : UTF8String; aMessage : AnsiString); 188 | procedure RxUnsubAck (Sender : TObject; anID : Word); 189 | procedure LinkConnected (Sender: TObject; ErrCode: Word); 190 | procedure LinkClosed (Sender: TObject; ErrCode: Word); 191 | procedure LinkData (Sender: TObject; ErrCode: Word); 192 | procedure TimerProc (var aMsg : TMessage); 193 | function GetClientID: UTF8String; 194 | procedure SetClientID (const Value: UTF8String); 195 | function GetKeepAlive: Word; 196 | procedure SetKeepAlive(const Value: Word); 197 | function GetMaxRetries : integer; 198 | procedure SetMaxRetries(const Value: integer); 199 | function GetRetryTime : cardinal; 200 | procedure SetRetryTime (const Value : cardinal); 201 | function GetClean: Boolean; 202 | procedure SetClean(const Value: Boolean); 203 | function GetPassword: UTF8String; 204 | function GetUsername: UTF8String; 205 | procedure SetPassword(const Value: UTF8String); 206 | procedure SetUsername(const Value: UTF8String); 207 | public 208 | Link : TWSocket; 209 | Parser : TMQTTParser; 210 | InFlight : TMQTTPacketStore; 211 | Releasables : TMQTTMessageStore; 212 | Subscriptions : TStringList; 213 | function Enabled : boolean; 214 | function Online : boolean; 215 | function NextMessageID : Word; 216 | procedure Subscribe (aTopic : UTF8String; aQos : TMQTTQOSType); overload; 217 | procedure Subscribe (Topics : TStringList); overload; 218 | procedure Unsubscribe (aTopic : UTF8String); overload; 219 | procedure Unsubscribe (Topics : TStringList); overload; 220 | procedure Ping; 221 | procedure Publish (aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; aRetain : Boolean = false); 222 | procedure SetWill (aTopic, aMessage : UTF8String; aQos : TMQTTQOSType; aRetain : Boolean = false); 223 | procedure Mon (aStr : string); 224 | procedure Activate (Enable : Boolean); 225 | constructor Create (anOwner : TComponent); override; 226 | destructor Destroy; override; 227 | published 228 | property ClientID : UTF8String read GetClientID write SetClientID; 229 | property KeepAlive : Word read GetKeepAlive write SetKeepAlive; 230 | property MaxRetries : integer read GetMaxRetries write SetMaxRetries; 231 | property RetryTime : cardinal read GetRetryTime write SetRetryTime; 232 | property Clean : Boolean read GetClean write SetClean; 233 | property Broker : Boolean read FBroker write FBroker; // no standard 234 | property AutoSubscribe : Boolean read FAutoSubscribe write FAutoSubscribe; 235 | property Username : UTF8String read GetUsername write SetUsername; 236 | property Password : UTF8String read GetPassword write SetPassword; 237 | property Host : string read FHost write FHost; 238 | property Port : integer read FPort write FPort; 239 | property LocalBounce : Boolean read FLocalBounce write FLocalBounce; 240 | property OnClientID : TMQTTClientIDEvent read FOnClientID write FOnClientID; 241 | property OnMon : TMQTTMonEvent read FOnMon write FOnMon; 242 | property OnOnline : TNotifyEvent read FOnOnline write FOnOnline; 243 | property OnOffline : TMQTTDisconnectEvent read FOnOffline write FOnOffline; 244 | property OnEnableChange : TNotifyEvent read FOnEnableChange write FOnEnableChange; 245 | property OnFailure : TMQTTFailureEvent read FOnFailure write FOnFailure; 246 | property OnMsg : TMQTTMsgEvent read FOnMsg write FOnMsg; 247 | end; 248 | 249 | TMQTTServer = class (TComponent) 250 | private 251 | FOnMon : TMQTTMonEvent; 252 | FOnClientsChange: TMQTTIDEvent; 253 | FOnCheckUser: TMQTTCheckUserEvent; 254 | Timers : HWnd; 255 | FPort : integer; 256 | FEnable : boolean; 257 | FOnBrokerOffline: TMQTTDisconnectEvent; 258 | FOnBrokerOnline: TNotifyEvent; 259 | FOnBrokerEnableChange: TNotifyEvent; 260 | FOnObituary: TMQTTObituaryEvent; 261 | FOnEnableChange: TNotifyEvent; 262 | FLocalBounce: Boolean; 263 | FOnSubscription: TMQTTSubscriptionEvent; 264 | FOnFailure: TMQTTFailureEvent; 265 | FMaxRetries: integer; 266 | FRetryTime: cardinal; 267 | FOnStoreSession: TMQTTSessionEvent; 268 | FOnRestoreSession: TMQTTSessionEvent; 269 | FOnDeleteSession: TMQTTSessionEvent; 270 | // FOnRetain: TMQTTRetainEvent; 271 | // FOnGetRetained: TMQTTRetainedEvent; 272 | procedure TimerProc (var aMsg : TMessage); 273 | procedure DoMon (Sender: TObject; aStr : string); 274 | // broker events 275 | procedure BkrOnline (Sender : TObject); 276 | procedure BkrOffline (Sender : TObject; Graceful : boolean); 277 | procedure BkrEnableChanged (Sender : TObject); 278 | procedure BkrSubscriptionChange (Sender : TObject); 279 | procedure BkrMsg (Sender : TObject; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; aRetained : boolean); 280 | // socket events 281 | procedure DoClientConnect (Sender: TObject; Client: TWSocketClient; Error: Word); 282 | procedure DoClientDisconnect (Sender: TObject; Client: TWSocketClient; Error: Word); 283 | procedure DoClientCreate (Sender: TObject; Client: TWSocketClient); 284 | // parser events 285 | procedure RxDisconnect (Sender : TObject); 286 | procedure RxPing (Sender : TObject); 287 | procedure RxPublish (Sender : TObject; anID : Word; aTopic : UTF8String; aMessage : AnsiString); 288 | procedure RxHeader (Sender : TObject; MsgType: TMQTTMessageType; Dup: Boolean; 289 | Qos: TMQTTQOSType; Retain: Boolean); 290 | procedure RxConnect (Sender : TObject; 291 | aProtocol : UTF8String; 292 | aVersion : byte; 293 | aClientID, 294 | aUserName, aPassword : UTF8String; 295 | aKeepAlive : Word; aClean : Boolean); 296 | procedure RxBrokerConnect (Sender : TObject; // non standard 297 | aProtocol : UTF8String; 298 | aVersion : byte; 299 | aClientID, 300 | aUserName, aPassword : UTF8String; 301 | aKeepAlive : Word; aClean : Boolean); 302 | procedure SetMaxRetries (const Value: integer); 303 | procedure SetRetryTime (const Value: cardinal); 304 | public 305 | FOnMonHdr : TMQTTHeaderEvent; 306 | Server : TWSocketServer; 307 | MessageID : Word; 308 | Brokers : TList; // of wsocket 309 | Sessions : TMQTTSessionStore; 310 | Retained : TMQTTMessageStore; 311 | function NextMessageID : Word; 312 | procedure Mon (aStr : string); 313 | procedure Activate (Enable : boolean); 314 | procedure LoadBrokers (anIniFile : string); 315 | procedure StoreBrokers (anIniFile : string); 316 | function GetClient (aParser : TMQTTParser) : TClient; overload; 317 | function GetClient (aClientID : UTF8String) : TClient; overload; 318 | 319 | procedure PublishToAll (From : TObject; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; wasRetained : boolean = false); 320 | function Enabled : boolean; 321 | function AddBroker (aHost : string; aPort : integer) : TMQTTClient; 322 | procedure SyncBrokerSubscriptions (aBroker : TMQTTClient); 323 | constructor Create (anOwner : TComponent); override; 324 | destructor Destroy; override; 325 | published 326 | property MaxRetries : integer read FMaxRetries write SetMaxRetries; 327 | property RetryTime : cardinal read FRetryTime write SetRetryTime; // in secs 328 | property Port : integer read FPort write FPort; 329 | property LocalBounce : Boolean read FLocalBounce write FLocalBounce; 330 | property OnFailure : TMQTTFailureEvent read FOnFailure write FOnFailure; 331 | property OnStoreSession : TMQTTSessionEvent read FOnStoreSession write FOnStoreSession; 332 | property OnRestoreSession : TMQTTSessionEvent read FOnRestoreSession write FOnRestoreSession; 333 | property OnDeleteSession : TMQTTSessionEvent read FOnDeleteSession write FOnDeleteSession; 334 | // property OnRetain : TMQTTRetainEvent read FOnRetain write FOnRetain; 335 | // property OnGetRetained : TMQTTRetainedEvent read FOnGetRetained write FOnGetRetained; 336 | property OnBrokerOnline : TNotifyEvent read FOnBrokerOnline write FOnBrokerOnline; 337 | property OnBrokerOffline : TMQTTDisconnectEvent read FOnBrokerOffline write FOnBrokerOffline; 338 | property OnBrokerEnableChange : TNotifyEvent read FOnBrokerEnableChange write FOnBrokerEnableChange; 339 | property OnEnableChange : TNotifyEvent read FOnEnableChange write FOnEnableChange; 340 | property OnSubscription : TMQTTSubscriptionEvent read FOnSubscription write FOnSubscription; 341 | property OnClientsChange : TMQTTIDEvent read FOnClientsChange write FOnClientsChange; 342 | property OnCheckUser : TMQTTCheckUserEvent read FOnCheckUser write FOnCheckUser; 343 | property OnObituary : TMQTTObituaryEvent read FOnObituary write FOnObituary; 344 | property OnMon : TMQTTMonEvent read FOnMon write FOnMon; 345 | end; 346 | 347 | procedure Register; 348 | function SubTopics (aTopic : UTF8String) : TStringList; 349 | function IsSubscribed (aSubscription, aTopic : UTF8String) : boolean; 350 | 351 | implementation 352 | 353 | uses 354 | SysUtils, IniFiles; 355 | 356 | procedure Register; 357 | begin 358 | RegisterComponents ('MQTT', [TMQTTServer, TMQTTClient]); 359 | end; 360 | 361 | 362 | function StateStr (aState: TSocketState): string; 363 | begin 364 | case aState of 365 | wsInvalidState : result := 'Invalid State'; 366 | wsOpened : result := 'Opened'; 367 | wsBound : result := 'Bound'; 368 | wsConnecting : Result := 'Connecting'; 369 | wsSocksConnected : Result := 'Sock Connected'; 370 | wsConnected : result := 'Connected'; 371 | wsAccepting : result := 'Accepting'; 372 | wsListening : result := 'Listening'; 373 | wsClosed : result := 'Closed'; 374 | end; 375 | end; 376 | 377 | function SubTopics (aTopic : UTF8String) : TStringList; 378 | var 379 | i : integer; 380 | begin 381 | Result := TStringList.Create; 382 | Result.Add (''); 383 | for i := 1 to length (aTopic) do 384 | begin 385 | if aTopic[i] = '/' then 386 | Result.Add('') 387 | else 388 | Result[Result.Count - 1] := Result[Result.Count - 1] + Char (aTopic[i]); 389 | end; 390 | end; 391 | 392 | function IsSubscribed (aSubscription, aTopic : UTF8String) : boolean; 393 | var 394 | s, t : TStringList; 395 | i : integer; 396 | MultiLevel : Boolean; 397 | begin 398 | s := SubTopics (aSubscription); 399 | t := SubTopics (aTopic); 400 | MultiLevel := (s[s.Count - 1] = '#'); // last field is # 401 | if not MultiLevel then 402 | Result := (s.Count = t.Count) 403 | else 404 | Result := (s.Count <= t.Count + 1); 405 | if Result then 406 | begin 407 | for i := 0 to s.Count - 1 do 408 | begin 409 | if (i >= t.Count) then Result := MultiLevel 410 | else if (i = s.Count - 1) and (s[i] = '#') then break 411 | else if s[i] = '+' then continue // they match 412 | else 413 | Result := Result and (s[i] = t[i]); 414 | if not Result then break; 415 | end; 416 | end; 417 | s.Free; 418 | t.Free; 419 | end; 420 | 421 | procedure SetDup (aStream : TMemoryStream; aState : boolean); 422 | var 423 | x : byte; 424 | begin 425 | if aStream.Size = 0 then exit; 426 | aStream.Seek (0, soFromBeginning); 427 | aStream.Read (x, 1); 428 | x := (x and $F7) or (ord (aState) * $08); 429 | aStream.Seek (0, soFromBeginning); 430 | aStream.Write (x, 1); 431 | end; 432 | 433 | { TClient } 434 | 435 | constructor TClient.Create (anOwner: TComponent); 436 | begin 437 | inherited; 438 | FBroker := false; // non standard 439 | Parser := TMQTTParser.Create; 440 | Parser.OnSend := DoSend; 441 | Parser.OnSetWill := DoSetWill; 442 | Parser.OnSubscribe := RxSubscribe; 443 | Parser.OnUnsubscribe := RxUnsubscribe; 444 | Parser.OnPubAck := RxPubAck; 445 | Parser.OnPubRel := RxPubRel; 446 | Parser.OnPubRec := RxPubRec; 447 | Parser.OnPubComp := RxPubComp; 448 | InFlight := TMQTTPacketStore.Create; 449 | Releasables := TMQTTMessageStore.Create; 450 | Subscriptions := TStringList.Create; 451 | OnDataAvailable := DoData; 452 | end; 453 | 454 | destructor TClient.Destroy; 455 | begin 456 | InFlight.Clear; 457 | InFlight.Free; 458 | Releasables.Clear; 459 | Releasables.Free; 460 | Parser.Free; 461 | Subscriptions.Free; 462 | inherited; 463 | end; 464 | 465 | procedure TClient.DoData (Sender: TObject; ErrCode: Word); 466 | begin 467 | if ErrCode = 0 then Parser.Parse (ReceiveStrA); 468 | end; 469 | 470 | procedure TClient.DoSend (Sender: TObject; anID : Word; aRetry : integer; aStream: TMemoryStream); 471 | var 472 | x : byte; 473 | begin 474 | if State = wsConnected then 475 | begin 476 | aStream.Seek (0, soFromBeginning); 477 | aStream.Read (x, 1); 478 | if (TMQTTQOSType ((x and $06) shr 1) in [qtAT_LEAST_ONCE, qtEXACTLY_ONCE]) and 479 | (TMQTTMessageType ((x and $f0) shr 4) in [{mtPUBREL,} mtPUBLISH, mtSUBSCRIBE, mtUNSUBSCRIBE]) and 480 | (anID > 0) then 481 | begin 482 | InFlight.AddPacket (anID, aStream, aRetry, Parser.RetryTime); // start disabled 483 | mon (string (Parser.ClientID) + ' Message ' + IntToStr (anID) + ' created.'); 484 | end; 485 | Send (aStream.Memory, aStream.Size); 486 | Sleep (0); 487 | end; 488 | end; 489 | 490 | procedure TClient.DoSetWill (Sender: TObject; aTopic, aMessage: UTF8String; 491 | aQos : TMQTTQOSType; aRetain: boolean); 492 | begin 493 | Parser.WillTopic := aTopic; 494 | Parser.WillMessage := aMessage; 495 | Parser.WillQos := aQos; 496 | Parser.WillRetain := aRetain; 497 | end; 498 | 499 | procedure TClient.Mon (aStr: string); 500 | begin 501 | if Assigned (FOnMon) then FOnMon (Self, aStr); 502 | end; 503 | 504 | procedure TClient.RxPubAck (Sender: TObject; anID: Word); 505 | begin 506 | InFlight.DelPacket (anID); 507 | Mon (string (Parser.ClientID) + ' ACK Message ' + IntToStr (anID) + ' disposed of.'); 508 | end; 509 | 510 | procedure TClient.RxPubComp (Sender: TObject; anID: Word); 511 | begin 512 | InFlight.DelPacket (anID); 513 | Mon (string (Parser.ClientID) + ' COMP Message ' + IntToStr (anID) + ' disposed of.'); 514 | end; 515 | 516 | procedure TClient.RxPubRec (Sender: TObject; anID: Word); 517 | var 518 | aPacket : TMQTTPacket; 519 | begin 520 | aPacket := InFlight.GetPacket (anID); 521 | if aPacket <> nil then 522 | begin 523 | aPacket.Counter := Parser.RetryTime; 524 | if aPacket.Publishing then 525 | begin 526 | aPacket.Publishing := false; 527 | Mon (string (Parser.ClientID) + ' REC Message ' + IntToStr (anID) + ' recorded.'); 528 | end 529 | else 530 | Mon (string (Parser.ClientID) + ' REC Message ' + IntToStr (anID) + ' already recorded.'); 531 | end 532 | else 533 | Mon (string (Parser.ClientID) + ' REC Message ' + IntToStr (anID) + ' not found.'); 534 | Parser.SendPubRel (anID); 535 | end; 536 | 537 | procedure TClient.RxPubRel (Sender: TObject; anID: Word); 538 | var 539 | aMsg : TMQTTMessage; 540 | begin 541 | aMsg := Releasables.GetMsg (anID); 542 | if (aMsg <> nil) and (Owner.Owner is TMQTTServer) then 543 | begin 544 | Mon (string (Parser.ClientID) + ' REL Message ' + IntToStr (anID) + ' publishing @ ' + QOSNames[aMsg.Qos]); 545 | TMQTTServer (Owner.Owner).PublishToAll (Self, aMsg.Topic, aMsg.Message, aMsg.Qos); 546 | Releasables.Remove (aMsg); 547 | aMsg.Free; 548 | Mon (string (Parser.ClientID) + ' REL Message ' + IntToStr (anID) + ' removed from storage.'); 549 | end 550 | else 551 | Mon (string (Parser.ClientID) + ' REL Message ' + IntToStr (anID) + ' has been already removed from storage.'); 552 | Parser.SendPubComp (anID); 553 | end; 554 | 555 | procedure TClient.RxSubscribe (Sender: TObject; anID: Word; Topics: TStringList); 556 | var 557 | x : cardinal; 558 | q : TMQTTQOSType; 559 | i, j : integer; 560 | found : boolean; 561 | Qoss : array of TMQTTQOSType; 562 | aServer : TMQTTServer; 563 | bMsg : TMQTTMessage; 564 | aQos : TMQTTQOSType; 565 | begin 566 | SetLength (Qoss, Topics.Count); 567 | aServer := nil; 568 | if Owner is TWSocketServer then 569 | if Owner.Owner is TMQTTServer then 570 | aServer := TMQTTServer (Owner.Owner); 571 | if aServer = nil then exit; 572 | for i := 0 to Topics.Count - 1 do 573 | begin 574 | found := false; 575 | x := cardinal (Topics.Objects[i]) and $03; 576 | q := TMQTTQOSType (x); 577 | if Assigned (aServer.FOnSubscription) then 578 | aServer.FOnSubscription (Self, UTF8String (Topics[i]), q); 579 | for j := 0 to Subscriptions.Count - 1 do 580 | if Subscriptions[j] = Topics[i] then 581 | begin 582 | found := true; 583 | Subscriptions.Objects[j] := TObject (q); 584 | break; 585 | end; 586 | if not found then 587 | begin 588 | Subscriptions.AddObject (Topics[i], TObject (q)); 589 | end; 590 | Qoss[i] := q; 591 | for j := 0 to aServer.Retained.Count - 1 do // set retained 592 | begin 593 | bMsg := aServer.Retained[j]; 594 | if IsSubscribed (UTF8String (Topics[i]), bMsg.Topic) then 595 | begin 596 | aQos := bMsg.Qos; 597 | if q < aQos then aQos := q; 598 | bMsg.LastUsed := Now; 599 | Parser.SendPublish (aServer.NextMessageID, bMsg.Topic, bMsg.Message, aQos, false, true); 600 | end; 601 | end; 602 | end; 603 | if Parser.RxQos = qtAT_LEAST_ONCE then Parser.SendSubAck (anID, Qoss); 604 | if Assigned (FOnSubscriptionChange) then FOnSubscriptionChange (Self); 605 | end; 606 | 607 | procedure TClient.RxUnsubscribe (Sender: TObject; anID: Word; Topics: TStringList); 608 | var 609 | i, j : integer; 610 | changed : boolean; 611 | begin 612 | changed := false; 613 | for i := 0 to Topics.Count - 1 do 614 | begin 615 | for j := Subscriptions.Count - 1 downto 0 do 616 | begin 617 | if Subscriptions[j] = Topics[i] then 618 | begin 619 | Subscriptions.Delete (j); 620 | changed := true; 621 | end; 622 | end; 623 | end; 624 | if changed and Assigned (FOnSubscriptionChange) then 625 | FOnSubscriptionChange (Self); 626 | if Parser.RxQos = qtAT_LEAST_ONCE then Parser.SendUnSubAck (anID); 627 | end; 628 | 629 | { TMQTTServer } 630 | 631 | procedure TMQTTServer.Activate (Enable: boolean); 632 | var 633 | i : integer; 634 | begin 635 | if FEnable = Enable then exit; 636 | if (Enable) then 637 | begin 638 | Server.Banner := ''; 639 | Server.Addr := '0.0.0.0'; 640 | Server.Port := IntToStr (FPort); 641 | Server.Proto := 'tcp'; 642 | Server.ClientClass := TClient; 643 | try 644 | Server.Listen; 645 | FEnable := true; 646 | except 647 | FEnable := false; 648 | end; 649 | if FEnable then SetTimer (Timers, 3, 100, nil); 650 | end 651 | else 652 | begin 653 | FEnable := false; 654 | for i := 0 to Server.ClientCount - 1 do 655 | try 656 | TClient (Server.Client[i]).Close; 657 | except 658 | end; 659 | try 660 | Server.Close; 661 | except 662 | end; 663 | KillTimer (Timers, 1); 664 | KillTimer (Timers, 2); 665 | KillTimer (Timers, 3); 666 | end; 667 | if Assigned (FOnEnableChange) then 668 | FOnEnableChange (Self); 669 | end; 670 | 671 | function TMQTTServer.AddBroker (aHost: string; aPort: integer): TMQTTClient; 672 | begin 673 | Result := TMQTTClient.Create (Self); 674 | Result.Host := aHost; 675 | Result.Port := aPort; 676 | Result.Broker := true; 677 | Result.LocalBounce := false; 678 | Result.OnOnline := BkrOnline; 679 | Result.OnOffline := BkrOffline; 680 | Result.OnEnableChange := BkrEnableChanged; 681 | Result.OnMsg := BkrMsg; 682 | Brokers.Add (Result); 683 | end; 684 | 685 | procedure TMQTTServer.BkrEnableChanged (Sender: TObject); 686 | begin 687 | if Assigned (FOnBrokerEnableChange) then 688 | FOnBrokerEnableChange (Sender); 689 | end; 690 | 691 | procedure TMQTTServer.BkrOffline (Sender: TObject; Graceful: boolean); 692 | begin 693 | TMQTTClient (Sender).Subscriptions.Clear; 694 | if Assigned (FOnBrokerOffline) then 695 | FOnBrokerOffline (Sender, Graceful); 696 | end; 697 | 698 | procedure TMQTTServer.BkrOnline(Sender: TObject); 699 | begin 700 | SyncBrokerSubscriptions (TMQTTClient (Sender)); 701 | if Assigned (FOnBrokerOnline) then 702 | FOnBrokerOnline (Sender); 703 | end; 704 | 705 | procedure TMQTTServer.BkrMsg (Sender: TObject; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; aRetained : boolean); 706 | var 707 | aBroker : TMQTTClient; 708 | i : integer; 709 | aMsg : TMQTTMessage; 710 | begin 711 | aBroker := TMQTTClient (Sender); 712 | mon ('Received Retained Message from a Broker - Retained ' + ny[aRetained]); 713 | if aRetained then 714 | begin 715 | mon ('Retaining "' + string (aTopic) + '" @ ' + QOSNames[aQos]); 716 | for i := Retained.Count - 1 downto 0 do 717 | begin 718 | aMsg := Retained[i]; 719 | if aMsg.Topic = aTopic then 720 | begin 721 | Retained.Remove (aMsg); 722 | aMsg.Free; 723 | break; 724 | end; 725 | end; 726 | Retained.AddMsg (0, aTopic, aMessage, aQos, 0, 0); 727 | end 728 | else 729 | mon ('Received Message from a Broker - Publishing..'); 730 | PublishToAll (Sender, aTopic, aMessage, aBroker.Parser.RxQos, aRetained); 731 | end; 732 | 733 | procedure TMQTTServer.BkrSubscriptionChange(Sender: TObject); 734 | var 735 | i : integer; 736 | begin 737 | mon ('Subscriptions changed...'); 738 | for i := 0 to Brokers.Count - 1 do 739 | SyncBrokerSubscriptions (TMQTTClient (Brokers[i])); 740 | end; 741 | 742 | constructor TMQTTServer.Create (anOwner: TComponent); 743 | begin 744 | inherited; 745 | Timers := AllocateHWnd (TimerProc); 746 | MessageID := 1000; 747 | FOnMonHdr := nil; 748 | FPort := 1883; 749 | FMaxRetries := DefMaxRetries; 750 | FRetryTime := DefRetryTime; 751 | Brokers := TList.Create; 752 | Sessions := TMQTTSessionStore.Create; 753 | Retained := TMQTTMessageStore.Create; 754 | Server := TWSocketServer.Create (Self); 755 | Server.OnClientCreate := DoClientCreate; 756 | Server.OnClientDisconnect := DoClientDisconnect; 757 | Server.OnClientConnect := DoClientConnect; 758 | end; 759 | 760 | destructor TMQTTServer.Destroy; 761 | var 762 | i : integer; 763 | begin 764 | DeallocateHWnd (Timers); 765 | for i := 0 to Brokers.Count - 1 do 766 | TMQTTClient (Brokers[i]).Free; 767 | Brokers.Free; 768 | Retained.Free; 769 | Sessions.Free; 770 | Activate (false); 771 | Server.Free; 772 | inherited; 773 | end; 774 | 775 | procedure TMQTTServer.RxPing (Sender: TObject); 776 | begin 777 | if not (Sender is TMQTTParser) then exit; 778 | TMQTTParser (Sender).SendPingResp; 779 | end; 780 | 781 | procedure TMQTTServer.RxPublish (Sender: TObject; anID: Word; aTopic : UTF8String; 782 | aMessage: AnsiString); 783 | var 784 | aParser : TMQTTParser; 785 | aClient : TClient; 786 | aMsg : TMQTTMessage; 787 | i : integer; 788 | 789 | begin 790 | if not (Sender is TMQTTParser) then exit; 791 | aParser := TMQTTParser (Sender); 792 | aClient := GetClient (aParser); 793 | if aClient = nil then exit; 794 | if aParser.RxRetain then 795 | begin 796 | mon ('Retaining "' + string (aTopic) + '" @ ' + QOSNames[aParser.RxQos]); 797 | for i := Retained.Count - 1 downto 0 do 798 | begin 799 | aMsg := Retained[i]; 800 | if aMsg.Topic = aTopic then 801 | begin 802 | Retained.Remove (aMsg); 803 | aMsg.Free; 804 | break; 805 | end; 806 | end; 807 | Retained.AddMsg (0, aTopic, aMessage, aParser.RxQos, 0, 0); 808 | end; 809 | case aParser.RxQos of 810 | qtAT_MOST_ONCE : 811 | PublishToAll (aClient, aTopic, aMessage, aParser.RxQos, aParser.RxRetain); 812 | qtAT_LEAST_ONCE : 813 | begin 814 | aParser.SendPubAck (anID); 815 | PublishToAll (aClient, aTopic, aMessage, aParser.RxQos, aParser.RxRetain); 816 | end; 817 | qtEXACTLY_ONCE : 818 | begin 819 | aMsg := aClient.Releasables.GetMsg (anID); 820 | if aMsg = nil then 821 | begin 822 | aClient.Releasables.AddMsg (anID, aTopic, aMessage, aParser.RxQos, 0, 0); 823 | mon (string (aClient.Parser.ClientID) + ' Message ' + IntToStr (anID) + ' stored and idle.'); 824 | end 825 | else 826 | mon (string (aClient.Parser.ClientID) + ' Message ' + IntToStr (anID) + ' already stored.'); 827 | aParser.SendPubRec (anID); 828 | end; 829 | end; 830 | end; 831 | 832 | procedure TMQTTServer.LoadBrokers (anIniFile: string); 833 | var 834 | i : integer; 835 | Sections : TStringList; 836 | aBroker : TMQTTClient; 837 | EnFlag : Boolean; 838 | begin 839 | for i := 0 to Brokers.Count - 1 do 840 | TMQTTClient (Brokers[i]).Free; 841 | Brokers.Clear; 842 | Sections := TStringList.Create; 843 | with TIniFile.Create (anIniFile) do 844 | begin 845 | ReadSections (Sections); 846 | for i := 0 to Sections.Count - 1 do 847 | begin 848 | if Copy (Sections[i], 1, 6) = 'BROKER' then 849 | begin 850 | aBroker := AddBroker ('', 0); 851 | aBroker.Host := ReadString (Sections[i], 'Prim Host', ''); 852 | aBroker.Port := ReadInteger (Sections[i], 'Port', 1883); 853 | EnFlag := ReadBool (Sections[i], 'Enabled', false); 854 | if EnFlag then aBroker.Activate (true); 855 | end; 856 | end; 857 | Free; 858 | end; 859 | Sections.Free; 860 | end; 861 | 862 | procedure TMQTTServer.SetMaxRetries (const Value: integer); 863 | var 864 | i : integer; 865 | begin 866 | FMaxRetries := Value; 867 | for i := 0 to Server.ClientCount - 1 do 868 | TClient (Server.Client[i]).Parser.MaxRetries := Value; 869 | for i := 0 to Brokers.Count - 1 do 870 | TMQTTClient (Brokers[i]).Parser.MaxRetries := Value; 871 | end; 872 | 873 | procedure TMQTTServer.SetRetryTime (const Value: cardinal); 874 | var 875 | i : integer; 876 | begin 877 | FRetryTime := Value; 878 | for i := 0 to Server.ClientCount - 1 do 879 | TClient (Server.Client[i]).Parser.KeepAlive := Value; 880 | for i := 0 to Brokers.Count - 1 do 881 | TMQTTClient (Brokers[i]).Parser.KeepAlive := Value; 882 | end; 883 | 884 | procedure TMQTTServer.StoreBrokers (anIniFile: string); 885 | var 886 | i : integer; 887 | aBroker : TMQTTClient; 888 | Sections : TStringList; 889 | begin 890 | Sections := TStringList.Create; 891 | with TIniFile.Create (anIniFile) do 892 | begin 893 | ReadSections (Sections); 894 | for i := 0 to Sections.Count - 1 do 895 | if Copy (Sections[i], 1, 6) = 'BROKER' then 896 | EraseSection (Sections[i]); 897 | for i := 0 to Brokers.Count - 1 do 898 | begin 899 | aBroker := Brokers[i]; 900 | WriteString (format ('BROKER%.3d', [i]), 'Prim Host', aBroker.Host); 901 | WriteInteger (format ('BROKER%.3d', [i]), 'Port', aBroker.Port); 902 | WriteBool (format ('BROKER%.3d', [i]), 'Enabled', aBroker.Enabled); 903 | end; 904 | Free; 905 | end; 906 | Sections.Free; 907 | end; 908 | 909 | procedure TMQTTServer.SyncBrokerSubscriptions (aBroker: TMQTTClient); 910 | var 911 | i, j, k : integer; 912 | x : cardinal; 913 | ToSub, ToUnsub : TStringList; 914 | aClient : TClient; 915 | found : boolean; 916 | begin 917 | ToSub := TStringList.Create; 918 | ToUnsub := TStringList.Create; 919 | for i := 0 to Server.ClientCount - 1 do 920 | begin 921 | aClient := TClient (Server.Client[i]); 922 | for j := 0 to aClient.Subscriptions.Count - 1 do 923 | begin 924 | found := false; 925 | for k := 0 to ToSub.Count - 1 do 926 | begin 927 | if aClient.Subscriptions[j] = ToSub[k] then 928 | begin 929 | found := true; 930 | break; 931 | end; 932 | end; 933 | if not found then ToSub.AddObject (aClient.Subscriptions[j], aClient.Subscriptions.Objects[j]); 934 | end; 935 | end; 936 | // add no longer used to unsubscribe 937 | for i := aBroker.Subscriptions.Count - 1 downto 0 do 938 | begin 939 | found := false; 940 | for j := 0 to ToSub.Count - 1 do 941 | begin 942 | if aBroker.Subscriptions[i] = ToSub[j] then 943 | begin 944 | x := cardinal (aBroker.Subscriptions.Objects[i]) and $03; // change to highest 945 | if x > (cardinal (ToSub.Objects[j]) and $03) then 946 | ToSub.Objects[j] := TObject (x); 947 | found := true; 948 | break; 949 | end; 950 | end; 951 | if not found then 952 | ToUnsub.AddObject (aBroker.Subscriptions[i], aBroker.Subscriptions.Objects[i]); 953 | end; 954 | // remove those already subscribed to 955 | for i := 0 to aBroker.Subscriptions.Count - 1 do 956 | begin 957 | for j := ToSub.Count - 1 downto 0 do 958 | begin 959 | if aBroker.Subscriptions[i] = ToSub[j] then 960 | ToSub.Delete (j); // already subscribed 961 | end; 962 | end; 963 | for i := 0 to ToSub.Count - 1 do 964 | aBroker.Subscribe (UTF8String (ToSub[i]), TMQTTQOSType (cardinal (ToSub.Objects[i]) and $03)); 965 | for i := 0 to ToUnsub.Count - 1 do 966 | aBroker.Unsubscribe (UTF8String (ToUnsub[i])); 967 | ToSub.Free; 968 | ToUnsub.Free; 969 | end; 970 | 971 | procedure TMQTTServer.Mon (aStr: string); 972 | begin 973 | if Assigned (FOnMon) then FOnMon (Self, aStr); 974 | end; 975 | 976 | function TMQTTServer.NextMessageID: Word; 977 | var 978 | i, j : integer; 979 | Unused : boolean; 980 | aMsg : TMQTTPacket; 981 | aClient : TClient; 982 | begin 983 | repeat 984 | Unused := true; 985 | MessageID := MessageID + 1; 986 | if MessageID = 0 then MessageID := 1; // exclude 0 987 | for i := 0 to Server.ClientCount - 1 do 988 | begin 989 | aClient := TClient (Server.Client[i]); 990 | for j := 0 to aClient.InFlight.Count - 1 do 991 | begin 992 | aMsg := aClient.InFlight[j]; 993 | if aMsg.ID = MessageID then 994 | begin 995 | Unused := false; 996 | break; 997 | end; 998 | end; 999 | if not Unused then break; 1000 | end; 1001 | until Unused; 1002 | Result := MessageID; 1003 | end; 1004 | 1005 | procedure TMQTTServer.PublishToAll (From : TObject; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; wasRetained : boolean); 1006 | var 1007 | i, j : integer; 1008 | sent : boolean; 1009 | aClient : TClient; 1010 | aBroker : TMQTTClient; 1011 | bQos : TMQTTQOSType; 1012 | begin 1013 | mon ('Publishing -- Was Retained ' + ny[wasRetained]); 1014 | for i := 0 to Server.ClientCount - 1 do 1015 | begin 1016 | aClient := TClient (Server.Client[i]); 1017 | if (aClient = From) and (aClient.FBroker) then continue; // don't send back to self if broker - non standard 1018 | //not LocalBounce then continue; 1019 | sent := false; 1020 | for j := 0 to aClient.Subscriptions.Count - 1 do 1021 | begin 1022 | if IsSubscribed (UTF8String (aClient.Subscriptions[j]), aTopic) then 1023 | begin 1024 | bQos := TMQTTQOSType (cardinal (aClient.Subscriptions.Objects[j]) and $03); 1025 | if aClient.FBroker then 1026 | mon ('Publishing to Broker ' + string (aClient.Parser.ClientID) + ' "' + string (aTopic) + '" Retained ' + ny[wasRetained and aClient.FBroker]) 1027 | 1028 | else 1029 | mon ('Publishing to Client ' + string (aClient.Parser.ClientID) + ' "' + string (aTopic) + '"'); 1030 | if bQos > aQos then bQos := aQos; 1031 | aClient.Parser.SendPublish (NextMessageID, aTopic, aMessage, bQos, false, wasRetained and aClient.FBroker); 1032 | sent := true; 1033 | break; // only do first 1034 | end; 1035 | end; 1036 | if (not sent) and (wasRetained) and (aClient.FBroker) then 1037 | begin 1038 | mon ('Forwarding Retained message to broker'); 1039 | aClient.Parser.SendPublish (NextMessageID, aTopic, aMessage, qtAT_LEAST_ONCE, false, true); 1040 | end; 1041 | end; 1042 | for i := 0 to Brokers.Count - 1 do // brokers get all messages -> downstream 1043 | begin 1044 | aBroker := TMQTTClient (Brokers[i]); 1045 | if aBroker = From then continue; 1046 | if not aBroker.Enabled then continue; 1047 | // if aBroker then 1048 | mon ('Publishing to Broker ' + string (aBroker.ClientID) + ' "' + string (aTopic) + '" @ ' + QOSNames[aQos] + ' Retained ' + ny[wasretained]); 1049 | aBroker.Publish (aTopic, aMessage, aQos, wasRetained); 1050 | end; 1051 | end; 1052 | 1053 | procedure TMQTTServer.TimerProc (var aMsg: TMessage); 1054 | var 1055 | i, j : integer; 1056 | bPacket : TMQTTPacket; 1057 | aClient : TClient; 1058 | WillClose : Boolean; 1059 | begin 1060 | if aMsg.Msg = WM_TIMER then 1061 | begin 1062 | KillTimer (Timers, aMsg.WParam); 1063 | // Mon ('Timer ' + IntToStr (aMsg.WParam) + ' triggered'); 1064 | case aMsg.WParam of 1065 | 3 : begin 1066 | for i := Server.ClientCount - 1 downto 0 do 1067 | begin 1068 | aClient := TClient (Server.Client[i]); 1069 | if not aClient.Parser.CheckKeepAlive then 1070 | begin 1071 | WillClose := true; 1072 | if Assigned (FOnFailure) then FOnFailure (aClient, frKEEPALIVE, WillClose); 1073 | if WillClose then aClient.CloseDelayed; 1074 | end 1075 | else 1076 | begin 1077 | for j := aClient.InFlight.Count - 1 downto 0 do 1078 | begin 1079 | bPacket := aClient.InFlight[j]; 1080 | if bPacket.Counter > 0 then 1081 | begin 1082 | bPacket.Counter := bPacket.Counter - 1; 1083 | if bPacket.Counter = 0 then 1084 | begin 1085 | bPacket.Retries := bPacket.Retries + 1; 1086 | if bPacket.Retries <= aClient.Parser.MaxRetries then 1087 | begin 1088 | if bPacket.Publishing then 1089 | begin 1090 | aClient.InFlight.List.Remove (bPacket); 1091 | mon ('Message ' + IntToStr (bPacket.ID) + ' disposed of..'); 1092 | mon ('Re-issuing Message ' + inttostr (bPacket.ID) + ' Retry ' + inttostr (bPacket.Retries)); 1093 | SetDup (bPacket.Msg, true); 1094 | aClient.DoSend (aClient.Parser, bPacket.ID, bPacket.Retries, bPacket.Msg); 1095 | bPacket.Free; 1096 | end 1097 | else 1098 | begin 1099 | mon ('Re-issuing PUBREL Message ' + inttostr (bPacket.ID) + ' Retry ' + inttostr (bPacket.Retries)); 1100 | aClient.Parser.SendPubRel (bPacket.ID, true); 1101 | bPacket.Counter := aClient.Parser.RetryTime; 1102 | end; 1103 | end 1104 | else 1105 | begin 1106 | WillClose := true; 1107 | if Assigned (FOnFailure) then FOnFailure (Self, frMAXRETRIES, WillClose); 1108 | if WillClose then aClient.CloseDelayed; 1109 | end; 1110 | end; 1111 | end; 1112 | end; 1113 | end; 1114 | end; 1115 | SetTimer (Timers, 3, 100, nil); 1116 | end; 1117 | end; 1118 | end; 1119 | end; 1120 | 1121 | procedure TMQTTServer.DoClientConnect (Sender: TObject; Client: TWSocketClient; 1122 | Error: Word); 1123 | begin 1124 | if Sender = Server then 1125 | Mon ('Client Connected...'); 1126 | end; 1127 | 1128 | procedure TMQTTServer.DoClientCreate (Sender: TObject; Client: TWSocketClient); 1129 | begin 1130 | with TClient (Client) do 1131 | begin 1132 | Parser.OnPing := RxPing; 1133 | Parser.OnDisconnect := RxDisconnect; 1134 | Parser.OnPublish := RxPublish; 1135 | Parser.OnPubRec := RxPubRec; 1136 | Parser.OnConnect := RxConnect; 1137 | Parser.OnBrokerConnect := RxBrokerConnect; // non standard 1138 | Parser.OnHeader := RxHeader; 1139 | Parser.MaxRetries := FMaxRetries; 1140 | Parser.RetryTime := FRetryTime; 1141 | OnMon := DoMon; 1142 | OnSubscriptionChange := BkrSubscriptionChange; 1143 | end; 1144 | end; 1145 | 1146 | procedure TMQTTServer.DoClientDisconnect (Sender: TObject; 1147 | Client: TWSocketClient; Error: Word); 1148 | var 1149 | aTopic, aMessage : UTF8String; 1150 | aQos : TMQTTQOSType; 1151 | begin 1152 | with TClient (Client) do 1153 | begin 1154 | Mon ('Client Disconnected. Graceful ' + ny[TClient (Client).FGraceful]); 1155 | if (InFlight.Count > 0) or (Releasables.Count > 0) then 1156 | begin 1157 | if Assigned (FOnStoreSession) then 1158 | FOnStoreSession (Client, Parser.ClientID) 1159 | else 1160 | Sessions.StoreSession (Parser.ClientID, TClient (Client)); 1161 | end; 1162 | if not FGraceful then 1163 | begin 1164 | aTopic := Parser.WillTopic; 1165 | aMessage := Parser.WillMessage; 1166 | aQos := Parser.WillQos; 1167 | if Assigned (FOnObituary) then 1168 | FOnObituary (Client, aTopic, aMessage, aQos); 1169 | PublishToAll (nil, aTopic, AnsiString (aMessage), aQos); 1170 | end; 1171 | end; 1172 | if Assigned (FOnClientsChange) then 1173 | FOnClientsChange (Server, Server.ClientCount - 1); 1174 | end; 1175 | 1176 | procedure TMQTTServer.DoMon (Sender: TObject; aStr: string); 1177 | begin 1178 | Mon (aStr); 1179 | end; 1180 | 1181 | function TMQTTServer.Enabled: boolean; 1182 | begin 1183 | Result := FEnable; 1184 | end; 1185 | 1186 | function TMQTTServer.GetClient (aClientID: UTF8String): TClient; 1187 | var 1188 | i : integer; 1189 | begin 1190 | for i := 0 to Server.ClientCount - 1 do 1191 | begin 1192 | Result := TClient (Server.Client[i]); 1193 | if Result.Parser.ClientID = aClientID then exit; 1194 | end; 1195 | (* for i := 0 to BrokerServer.ClientCount - 1 do 1196 | begin 1197 | Result := TClient (BrokerServer.Client[i]); 1198 | if Result.Parser.ClientID = aClientID then exit; 1199 | end; *) 1200 | Result := nil; 1201 | end; 1202 | 1203 | function TMQTTServer.GetClient (aParser: TMQTTParser): TClient; 1204 | var 1205 | i : integer; 1206 | begin 1207 | for i := 0 to Server.ClientCount - 1 do 1208 | begin 1209 | Result := TClient (Server.Client[i]); 1210 | if Result.Parser = aParser then exit; 1211 | end; 1212 | (* for i := 0 to BrokerServer.ClientCount - 1 do 1213 | begin 1214 | Result := TClient (BrokerServer.Client[i]); 1215 | if Result.Parser = aParser then exit; 1216 | end; *) 1217 | Result := nil; 1218 | end; 1219 | 1220 | 1221 | procedure TMQTTServer.RxBrokerConnect(Sender: TObject; aProtocol: UTF8String; 1222 | aVersion: byte; aClientID, aUserName, aPassword: UTF8String; aKeepAlive: Word; 1223 | aClean: Boolean); 1224 | var 1225 | aClient : TClient; 1226 | begin 1227 | if not (Sender is TMQTTParser) then exit; 1228 | aClient := GetClient (TMQTTParser (Sender)); 1229 | if aClient = nil then exit; 1230 | aClient.FBroker := true; 1231 | RxConnect (Sender, aProtocol, aVersion, aClientID, aUserName, aPassword, aKeepAlive, aClean); 1232 | end; 1233 | 1234 | procedure TMQTTServer.RxConnect (Sender: TObject; aProtocol: UTF8String; 1235 | aVersion: byte; aClientID, aUserName, aPassword: UTF8String; aKeepAlive: Word; 1236 | aClean: Boolean); 1237 | var 1238 | aClient : TClient; 1239 | aServer : TWSocketServer; 1240 | Allowed : Boolean; 1241 | begin 1242 | Allowed := false; 1243 | if not (Sender is TMQTTParser) then exit; 1244 | aClient := GetClient (TMQTTParser (Sender)); 1245 | if aClient = nil then exit; 1246 | aServer := TWSocketServer (aClient.Owner); 1247 | aClient.FGraceful := true; 1248 | if Assigned (FOnCheckUser) then 1249 | FOnCheckUser (aServer, aUserName, aPassword, Allowed); 1250 | if Allowed then 1251 | begin 1252 | if aVersion < MinVersion then 1253 | begin 1254 | aClient.Parser.SendConnAck (rcPROTOCOL); // identifier rejected 1255 | aClient.CloseDelayed; 1256 | end 1257 | else if (length (aClientID) < 1) or (length (aClientID) > 23) then 1258 | begin 1259 | aClient.Parser.SendConnAck (rcIDENTIFIER); // identifier rejected 1260 | aClient.CloseDelayed; 1261 | end 1262 | else if GetClient (aClientID) <> nil then 1263 | begin 1264 | aClient.Parser.SendConnAck (rcIDENTIFIER); // identifier rejected 1265 | aClient.CloseDelayed; 1266 | end 1267 | else 1268 | begin 1269 | // mon ('Client ID ' + ClientID + ' User ' + striUserName + ' Pass ' + PassWord); 1270 | aClient.Parser.Username := aUserName; 1271 | aClient.Parser.Password := aPassword; 1272 | aClient.Parser.ClientID := aClientID; 1273 | aClient.Parser.KeepAlive := aKeepAlive; 1274 | aClient.Parser.Clean := aClean; 1275 | mon ('Clean ' + ny[aClean]); 1276 | if not aClean then 1277 | begin 1278 | if Assigned (FOnRestoreSession) then 1279 | FOnRestoreSession (aClient, aClientID) 1280 | else 1281 | Sessions.RestoreSession (aClientID, aClient); 1282 | end; 1283 | if Assigned (FOnDeleteSession) then 1284 | FOnDeleteSession (aClient, aClientID) 1285 | else 1286 | Sessions.DeleteSession (aClientID); 1287 | aClient.Parser.SendConnAck (rcACCEPTED); 1288 | aClient.FGraceful := false; 1289 | mon ('Accepted. Is Broker ' + ny[aClient.FBroker]); 1290 | if Assigned (FOnClientsChange) then 1291 | FOnClientsChange (aServer, aServer.ClientCount); 1292 | end; 1293 | end 1294 | else 1295 | begin 1296 | aClient.Parser.SendConnAck (rcUSER); 1297 | aClient.CloseDelayed; 1298 | end; 1299 | end; 1300 | 1301 | procedure TMQTTServer.RxDisconnect (Sender: TObject); 1302 | var 1303 | aClient : TClient; 1304 | begin 1305 | if not (Sender is TMQTTParser) then exit; 1306 | aClient := GetClient (TMQTTParser (Sender)); 1307 | if aClient = nil then exit; 1308 | aClient.FGraceful := true; 1309 | end; 1310 | 1311 | procedure TMQTTServer.RxHeader (Sender: TObject; MsgType: TMQTTMessageType; 1312 | Dup: Boolean; Qos: TMQTTQOSType; Retain: Boolean); 1313 | begin 1314 | if Assigned (FOnMonHdr) then FOnMonHdr (Self, MsgType, Dup, Qos, Retain); 1315 | end; 1316 | 1317 | { TMQTTClient } 1318 | 1319 | procedure TMQTTClient.Activate (Enable: Boolean); 1320 | begin 1321 | if Enable = FEnable then exit; 1322 | FEnable := Enable; 1323 | try 1324 | if (Link.State = wsConnected) then 1325 | begin 1326 | Parser.SendDisconnect; 1327 | FGraceful := true; 1328 | end; 1329 | Link.CloseDelayed; 1330 | except 1331 | end; 1332 | if Enable then 1333 | SetTimer (Timers, 1, 100, nil) 1334 | else 1335 | begin 1336 | KillTimer (Timers, 1); 1337 | KillTimer (Timers, 2); 1338 | KillTimer (Timers, 3); 1339 | end; 1340 | if Assigned (FOnEnableChange) then FOnEnableChange (Self); 1341 | end; 1342 | 1343 | constructor TMQTTClient.Create (anOwner: TComponent); 1344 | begin 1345 | inherited; 1346 | FHost := ''; 1347 | FUsername := ''; 1348 | FPassword := ''; 1349 | FPort := 1883; 1350 | FEnable := false; 1351 | FGraceful := false; 1352 | FOnline := false; 1353 | FBroker := false; // non standard 1354 | FLocalBounce := false; 1355 | FAutoSubscribe := false; 1356 | FMessageID := 0; 1357 | Subscriptions := TStringList.Create; 1358 | Releasables := TMQTTMessageStore.Create; 1359 | Parser := TMQTTParser.Create; 1360 | Parser.OnSend := DoSend; 1361 | Parser.OnConnAck := RxConnAck; 1362 | Parser.OnPublish := RxPublish; 1363 | Parser.OnSubAck := RxSubAck; 1364 | Parser.OnUnsubAck := RxUnsubAck; 1365 | Parser.OnPubAck := RxPubAck; 1366 | Parser.OnPubRel := RxPubRel; 1367 | Parser.OnPubRec := RxPubRec; 1368 | Parser.OnPubComp := RxPubComp; 1369 | Parser.KeepAlive := 10; 1370 | Timers := AllocateHWnd (TimerProc); 1371 | InFlight := TMQTTPacketStore.Create; 1372 | Link := TWSocket.Create (Self); 1373 | Link.OnDataAvailable := LinkData; 1374 | Link.OnSessionConnected := LinkConnected; 1375 | Link.OnSessionClosed := LinkClosed; 1376 | end; 1377 | 1378 | destructor TMQTTClient.Destroy; 1379 | begin 1380 | Releasables.Clear; 1381 | Releasables.Free; 1382 | Subscriptions.Free; 1383 | InFlight.Clear; 1384 | InFlight.Free; 1385 | KillTimer (Timers, 1); 1386 | KillTimer (Timers, 2); 1387 | KillTimer (Timers, 3); 1388 | DeAllocateHWnd (Timers); 1389 | try 1390 | Link.Close; 1391 | finally 1392 | Link.Free; 1393 | end; 1394 | Parser.Free; 1395 | inherited; 1396 | end; 1397 | 1398 | procedure TMQTTClient.RxConnAck (Sender: TObject; aCode: byte); 1399 | var 1400 | i : integer; 1401 | x : cardinal; 1402 | begin 1403 | Mon ('Connection ' + codenames(aCode)); 1404 | if aCode = rcACCEPTED then 1405 | begin 1406 | FOnline := true; 1407 | FGraceful := false; 1408 | SetTimer (Timers, 3, 100, nil); // start retry counters 1409 | if Assigned (FOnOnline) then FOnOnline (Self); 1410 | if (FAutoSubscribe) and (Subscriptions.Count > 0) then 1411 | begin 1412 | for i := 0 to Subscriptions.Count - 1 do 1413 | begin 1414 | x := cardinal (Subscriptions.Objects[i]) and $03; 1415 | Parser.SendSubscribe (NextMessageID, UTF8String (Subscriptions[i]), TMQTTQOSType (x)); 1416 | end; 1417 | end; 1418 | end 1419 | else 1420 | Activate (false); // not going to connect 1421 | end; 1422 | // publishing 1423 | procedure TMQTTClient.RxPublish (Sender: TObject; anID: Word; aTopic : UTF8String; 1424 | aMessage : AnsiString); 1425 | var 1426 | aMsg : TMQTTMessage; 1427 | begin 1428 | case Parser.RxQos of 1429 | qtAT_MOST_ONCE : 1430 | if Assigned (FOnMsg) then FOnMsg (Self, aTopic, aMessage, Parser.RxQos, Parser.RxRetain); 1431 | qtAT_LEAST_ONCE : 1432 | begin 1433 | Parser.SendPubAck (anID); 1434 | if Assigned (FOnMsg) then FOnMsg (Self, aTopic, aMessage, Parser.RxQos, Parser.RxRetain); 1435 | end; 1436 | qtEXACTLY_ONCE : 1437 | begin 1438 | Parser.SendPubRec (anID); 1439 | aMsg := Releasables.GetMsg (anID); 1440 | if aMsg = nil then 1441 | begin 1442 | Releasables.AddMsg (anID, aTopic, aMessage, Parser.RxQos, 0, 0, Parser.RxRetain); 1443 | mon ('Message ' + IntToStr (anID) + ' stored and idle.'); 1444 | end 1445 | else 1446 | mon ('Message ' + IntToStr (anID) + ' already stored.'); 1447 | end; 1448 | end; 1449 | end; 1450 | 1451 | procedure TMQTTClient.RxPubAck (Sender: TObject; anID: Word); 1452 | begin 1453 | InFlight.DelPacket (anID); 1454 | Mon ('ACK Message ' + IntToStr (anID) + ' disposed of.'); 1455 | end; 1456 | 1457 | procedure TMQTTClient.RxPubComp (Sender: TObject; anID: Word); 1458 | begin 1459 | InFlight.DelPacket (anID); 1460 | Mon ('COMP Message ' + IntToStr (anID) + ' disposed of.'); 1461 | end; 1462 | 1463 | procedure TMQTTClient.RxPubRec (Sender: TObject; anID: Word); 1464 | var 1465 | aPacket : TMQTTPacket; 1466 | begin 1467 | aPacket := InFlight.GetPacket (anID); 1468 | if aPacket <> nil then 1469 | begin 1470 | aPacket.Counter := Parser.RetryTime; 1471 | if aPacket.Publishing then 1472 | begin 1473 | aPacket.Publishing := false; 1474 | Mon ('REC Message ' + IntToStr (anID) + ' recorded.'); 1475 | end 1476 | else 1477 | Mon ('REC Message ' + IntToStr (anID) + ' already recorded.'); 1478 | end 1479 | else 1480 | Mon ('REC Message ' + IntToStr (anID) + ' not found.'); 1481 | Parser.SendPubRel (anID); 1482 | end; 1483 | 1484 | procedure TMQTTClient.RxPubRel (Sender: TObject; anID: Word); 1485 | var 1486 | aMsg : TMQTTMessage; 1487 | begin 1488 | aMsg := Releasables.GetMsg (anID); 1489 | if aMsg <> nil then 1490 | begin 1491 | Mon ('REL Message ' + IntToStr (anID) + ' publishing @ ' + QOSNames[aMsg.Qos]); 1492 | if Assigned (FOnMsg) then FOnMsg (Self, aMsg.Topic, aMsg.Message, aMsg.Qos, aMsg.Retained); 1493 | Releasables.Remove (aMsg); 1494 | aMsg.Free; 1495 | Mon ('REL Message ' + IntToStr (anID) + ' removed from storage.'); 1496 | end 1497 | else 1498 | Mon ('REL Message ' + IntToStr (anID) + ' has been already removed from storage.'); 1499 | Parser.SendPubComp (anID); 1500 | end; 1501 | 1502 | procedure TMQTTClient.SetClean (const Value: Boolean); 1503 | begin 1504 | Parser.Clean := Value; 1505 | end; 1506 | 1507 | procedure TMQTTClient.SetClientID (const Value: UTF8String); 1508 | begin 1509 | Parser.ClientID := Value; 1510 | end; 1511 | 1512 | procedure TMQTTClient.SetKeepAlive (const Value: Word); 1513 | begin 1514 | Parser.KeepAlive := Value; 1515 | end; 1516 | 1517 | procedure TMQTTClient.SetMaxRetries (const Value: integer); 1518 | begin 1519 | Parser.MaxRetries := Value; 1520 | end; 1521 | 1522 | procedure TMQTTClient.SetPassword (const Value: UTF8String); 1523 | begin 1524 | Parser.Password := Value; 1525 | end; 1526 | 1527 | procedure TMQTTClient.SetRetryTime (const Value: cardinal); 1528 | begin 1529 | Parser.RetryTime := Value; 1530 | end; 1531 | 1532 | procedure TMQTTClient.SetUsername (const Value: UTF8String); 1533 | begin 1534 | Parser.UserName := Value; 1535 | end; 1536 | 1537 | procedure TMQTTClient.SetWill (aTopic, aMessage : UTF8String; aQos: TMQTTQOSType; 1538 | aRetain: Boolean); 1539 | begin 1540 | Parser.SetWill (aTopic, aMessage, aQos, aRetain); 1541 | end; 1542 | 1543 | procedure TMQTTClient.Subscribe (Topics: TStringList); 1544 | var 1545 | j : integer; 1546 | i, x : cardinal; 1547 | anID : Word; 1548 | found : boolean; 1549 | begin 1550 | if Topics = nil then exit; 1551 | anID := NextMessageID; 1552 | for i := 0 to Topics.Count - 1 do 1553 | begin 1554 | found := false; 1555 | // 255 denotes acked 1556 | if i > 254 then 1557 | x := (cardinal (Topics.Objects[i]) and $03) 1558 | else 1559 | x := (cardinal (Topics.Objects[i]) and $03) + (anID shl 16) + (i shl 8) ; 1560 | for j := 0 to Subscriptions.Count - 1 do 1561 | if Subscriptions[j] = Topics[i] then 1562 | begin 1563 | found := true; 1564 | Subscriptions.Objects[j] := TObject (x); 1565 | break; 1566 | end; 1567 | if not found then 1568 | Subscriptions.AddObject (Topics[i], TObject (x)); 1569 | end; 1570 | Parser.SendSubscribe (anID, Topics); 1571 | end; 1572 | 1573 | procedure TMQTTClient.Subscribe (aTopic: UTF8String; aQos: TMQTTQOSType); 1574 | var 1575 | i : integer; 1576 | x : cardinal; 1577 | found : boolean; 1578 | anID : Word; 1579 | begin 1580 | if aTopic = '' then exit; 1581 | found := false; 1582 | anID := NextMessageID; 1583 | x := ord (aQos) + (anID shl 16); 1584 | for i := 0 to Subscriptions.Count - 1 do 1585 | if Subscriptions[i] = string (aTopic) then 1586 | begin 1587 | found := true; 1588 | Subscriptions.Objects[i] := TObject (x); 1589 | break; 1590 | end; 1591 | if not found then 1592 | Subscriptions.AddObject (string (aTopic), TObject (x)); 1593 | Parser.SendSubscribe (anID, aTopic, aQos); 1594 | end; 1595 | 1596 | procedure TMQTTClient.DoSend (Sender: TObject; anID : Word; aRetry : integer; aStream: TMemoryStream); 1597 | var 1598 | x : byte; 1599 | begin 1600 | if Link.State = wsConnected then 1601 | begin 1602 | KillTimer (Timers, 2); // 75% of keep alive 1603 | if KeepAlive > 0 then SetTimer (Timers, 2, KeepAlive * 750, nil); 1604 | aStream.Seek (0, soFromBeginning); 1605 | aStream.Read (x, 1); 1606 | if (TMQTTQOSType ((x and $06) shr 1) in [qtAT_LEAST_ONCE, qtEXACTLY_ONCE]) and 1607 | (TMQTTMessageType ((x and $f0) shr 4) in [{mtPUBREL,} mtPUBLISH, mtSUBSCRIBE, mtUNSUBSCRIBE]) and 1608 | (anID > 0) then 1609 | begin 1610 | InFlight.AddPacket (anID, aStream, aRetry, Parser.RetryTime); 1611 | mon ('Message ' + IntToStr (anID) + ' created.'); 1612 | end; 1613 | Link.Send (aStream.Memory, aStream.Size); 1614 | Sleep (0); 1615 | end; 1616 | end; 1617 | 1618 | function TMQTTClient.Enabled: boolean; 1619 | begin 1620 | Result := FEnable; 1621 | end; 1622 | 1623 | function TMQTTClient.GetClean: Boolean; 1624 | begin 1625 | Result := Parser.Clean; 1626 | end; 1627 | 1628 | function TMQTTClient.GetClientID: UTF8String; 1629 | begin 1630 | Result := Parser.ClientID; 1631 | end; 1632 | 1633 | function TMQTTClient.GetKeepAlive: Word; 1634 | begin 1635 | Result := Parser.KeepAlive; 1636 | end; 1637 | 1638 | function TMQTTClient.GetMaxRetries: integer; 1639 | begin 1640 | Result := Parser.MaxRetries; 1641 | end; 1642 | 1643 | function TMQTTClient.GetPassword: UTF8String; 1644 | begin 1645 | Result := Parser.Password; 1646 | end; 1647 | 1648 | function TMQTTClient.GetRetryTime: cardinal; 1649 | begin 1650 | Result := Parser.RetryTime; 1651 | end; 1652 | 1653 | function TMQTTClient.GetUsername: UTF8String; 1654 | begin 1655 | Result := Parser.UserName; 1656 | end; 1657 | 1658 | procedure TMQTTClient.RxSubAck (Sender: TObject; anID: Word; Qoss : array of TMQTTQosType); 1659 | var 1660 | j : integer; 1661 | i, x : cardinal; 1662 | begin 1663 | InFlight.DelPacket (anID); 1664 | Mon ('Message ' + IntToStr (anID) + ' disposed of.'); 1665 | for i := low (Qoss) to high (Qoss) do 1666 | begin 1667 | if i > 254 then break; // only valid for first 254 1668 | for j := 0 to Subscriptions.Count - 1 do 1669 | begin 1670 | x := cardinal (Subscriptions.Objects[j]); 1671 | if (hiword (x) = anID) and ((x and $0000ff00) shr 8 = i) then 1672 | Subscriptions.Objects[j] := TObject ($ff00 + ord (Qoss[i])); 1673 | end; 1674 | end; 1675 | end; 1676 | 1677 | procedure TMQTTClient.RxUnsubAck (Sender: TObject; anID: Word); 1678 | begin 1679 | InFlight.DelPacket (anID); 1680 | Mon ('Message ' + IntToStr (anID) + ' disposed of.'); 1681 | end; 1682 | 1683 | procedure TMQTTClient.LinkConnected (Sender: TObject; ErrCode: Word); 1684 | var 1685 | aClientID : UTF8String; 1686 | 1687 | function TimeString : UTF8string; 1688 | begin 1689 | // 86400 secs 1690 | Result := UTF8String (IntToHex (Trunc (Date), 5) + IntToHex (Trunc (Frac (Time) * 864000), 7)); 1691 | end; 1692 | 1693 | begin 1694 | if ErrCode = 0 then 1695 | begin 1696 | FGraceful := false; // still haven't connected but expect to 1697 | Parser.Reset; 1698 | // mon ('Time String : ' + Timestring); 1699 | //= mon ('xaddr ' + Link.GetXAddr); 1700 | aClientID := ClientID; 1701 | if aClientID = '' then 1702 | aClientID := 'CID' + UTF8String (Link.GetXPort); // + TimeString; 1703 | if Assigned (FOnClientID) then 1704 | FOnClientID (Self, aClientID); 1705 | ClientID := aClientID; 1706 | if Parser.Clean then 1707 | begin 1708 | InFlight.Clear; 1709 | Releasables.Clear; 1710 | end; 1711 | if FBroker then 1712 | Parser.SendBrokerConnect (aClientID, Parser.UserName, Parser.Password, KeepAlive, Parser.Clean) 1713 | else 1714 | Parser.SendConnect (aClientID, Parser.UserName, Parser.Password, KeepAlive, Parser.Clean); 1715 | end; 1716 | end; 1717 | 1718 | procedure TMQTTClient.LinkData (Sender: TObject; ErrCode: Word); 1719 | begin 1720 | if ErrCode = 0 then Parser.Parse (Link.ReceiveStrA); 1721 | end; 1722 | 1723 | procedure TMQTTClient.Mon(aStr: string); 1724 | begin 1725 | if Assigned (FOnMon) then FOnMon (Self, aStr); 1726 | end; 1727 | 1728 | function TMQTTClient.NextMessageID: Word; 1729 | var 1730 | i : integer; 1731 | Unused : boolean; 1732 | aMsg : TMQTTPacket; 1733 | begin 1734 | repeat 1735 | Unused := true; 1736 | FMessageID := FMessageID + 1; 1737 | if FMessageID = 0 then FMessageID := 1; // exclude 0 1738 | for i := 0 to InFlight.Count - 1 do 1739 | begin 1740 | aMsg := InFlight.List[i]; 1741 | if aMsg.ID = FMessageID then 1742 | begin 1743 | Unused := false; 1744 | break; 1745 | end; 1746 | end; 1747 | until Unused; 1748 | Result := FMessageID; 1749 | end; 1750 | 1751 | function TMQTTClient.Online: boolean; 1752 | begin 1753 | Result := FOnline; 1754 | end; 1755 | 1756 | procedure TMQTTClient.Ping; 1757 | begin 1758 | Parser.SendPing; 1759 | end; 1760 | 1761 | procedure TMQTTClient.Publish (aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; aRetain : Boolean); 1762 | var 1763 | i : integer; 1764 | found : boolean; 1765 | begin 1766 | if FLocalBounce and Assigned (FOnMsg) then 1767 | begin 1768 | found := false; 1769 | for i := 0 to Subscriptions.Count - 1 do 1770 | if IsSubscribed (UTF8String (Subscriptions[i]), aTopic) then 1771 | begin 1772 | found := true; 1773 | break; 1774 | end; 1775 | if found then 1776 | begin 1777 | Parser.RxQos := aQos; 1778 | FOnMsg (Self, aTopic, aMessage, aQos, false); 1779 | end; 1780 | end; 1781 | Parser.SendPublish (NextMessageID, aTopic, aMessage, aQos, false, aRetain); 1782 | end; 1783 | 1784 | procedure TMQTTClient.TimerProc (var aMsg: TMessage); 1785 | var 1786 | i : integer; 1787 | bPacket : TMQTTPacket; 1788 | WillClose : Boolean; 1789 | begin 1790 | if aMsg.Msg = WM_TIMER then 1791 | begin 1792 | KillTimer (Timers, aMsg.WParam); 1793 | case aMsg.WParam of 1794 | 1 : begin 1795 | Mon ('Connecting to ' + Host + ' on Port ' + IntToStr (Port)); 1796 | Link.Addr := Host; 1797 | Link.Port := IntToStr (Port); 1798 | Link.Proto := 'tcp'; 1799 | try 1800 | Link.Connect; 1801 | except 1802 | end; 1803 | end; 1804 | 2 : Ping; 1805 | 3 : begin // send duplicates 1806 | for i := InFlight.Count - 1 downto 0 do 1807 | begin 1808 | bPacket := InFlight.List[i]; 1809 | if bPacket.Counter > 0 then 1810 | begin 1811 | bPacket.Counter := bPacket.Counter - 1; 1812 | if bPacket.Counter = 0 then 1813 | begin 1814 | bPacket.Retries := bPacket.Retries + 1; 1815 | if bPacket.Retries <= MaxRetries then 1816 | begin 1817 | if bPacket.Publishing then 1818 | begin 1819 | InFlight.List.Remove (bPacket); 1820 | mon ('Message ' + IntToStr (bPacket.ID) + ' disposed of..'); 1821 | mon ('Re-issuing Message ' + inttostr (bPacket.ID) + ' Retry ' + inttostr (bPacket.Retries)); 1822 | SetDup (bPacket.Msg, true); 1823 | DoSend (Parser, bPacket.ID, bPacket.Retries, bPacket.Msg); 1824 | bPacket.Free; 1825 | end 1826 | else 1827 | begin 1828 | mon ('Re-issuing PUBREL Message ' + inttostr (bPacket.ID) + ' Retry ' + inttostr (bPacket.Retries)); 1829 | Parser.SendPubRel (bPacket.ID, true); 1830 | bPacket.Counter := Parser.RetryTime; 1831 | end; 1832 | end 1833 | else 1834 | begin 1835 | WillClose := true; 1836 | if Assigned (FOnFailure) then FOnFailure (Self, frMAXRETRIES, WillClose); 1837 | if WillClose then Link.CloseDelayed; 1838 | end; 1839 | end; 1840 | end; 1841 | end; 1842 | SetTimer (Timers, 3, 100, nil); 1843 | end; 1844 | end; 1845 | end; 1846 | end; 1847 | 1848 | procedure TMQTTClient.Unsubscribe (Topics: TStringList); 1849 | var 1850 | i, J : integer; 1851 | begin 1852 | if Topics = nil then exit; 1853 | for i := 0 to Topics.Count - 1 do 1854 | begin 1855 | for j := Subscriptions.Count - 1 downto 0 do 1856 | if Subscriptions[j] = Topics[i] then 1857 | begin 1858 | Subscriptions.Delete (j); 1859 | break; 1860 | end; 1861 | end; 1862 | Parser.SendUnsubscribe (NextMessageID, Topics); 1863 | end; 1864 | 1865 | procedure TMQTTClient.Unsubscribe (aTopic: UTF8String); 1866 | var 1867 | i : integer; 1868 | begin 1869 | if aTopic = '' then exit; 1870 | for i := Subscriptions.Count - 1 downto 0 do 1871 | if Subscriptions[i] = string (aTopic) then 1872 | begin 1873 | Subscriptions.Delete (i); 1874 | break; 1875 | end; 1876 | Parser.SendUnsubscribe (NextMessageID, aTopic); 1877 | end; 1878 | 1879 | procedure TMQTTClient.LinkClosed (Sender: TObject; ErrCode: Word); 1880 | begin 1881 | // Mon ('Link Closed...'); 1882 | KillTimer (Timers, 2); 1883 | KillTimer (Timers, 3); 1884 | if Assigned (FOnOffline) and (FOnline) then 1885 | FOnOffline (Self, FGraceful); 1886 | FOnline := false; 1887 | if FEnable then SetTimer (Timers, 1, 6000, nil); 1888 | end; 1889 | 1890 | { TMQTTPacketStore } 1891 | 1892 | function TMQTTPacketStore.AddPacket (anID : Word; aMsg : TMemoryStream; aRetry : cardinal; aCount : cardinal) : TMQTTPacket; 1893 | begin 1894 | Result := TMQTTPacket.Create; 1895 | Result.ID := anID; 1896 | Result.Counter := aCount; 1897 | Result.Retries := aRetry; 1898 | aMsg.Seek (0, soFromBeginning); 1899 | Result.Msg.CopyFrom (aMsg, aMsg.Size); 1900 | List.Add (Result); 1901 | end; 1902 | 1903 | procedure TMQTTPacketStore.Assign (From : TMQTTPacketStore); 1904 | var 1905 | i : integer; 1906 | aPacket, bPacket : TMQTTPacket; 1907 | begin 1908 | Clear; 1909 | for i := 0 to From.Count - 1 do 1910 | begin 1911 | aPacket := From[i]; 1912 | bPacket := TMQTTPacket.Create; 1913 | bPacket.Assign (aPacket); 1914 | List.Add (bPacket); 1915 | end; 1916 | end; 1917 | 1918 | procedure TMQTTPacketStore.Clear; 1919 | var 1920 | i : integer; 1921 | begin 1922 | for i := 0 to List.Count - 1 do 1923 | TMQTTPacket (List[i]).Free; 1924 | List.Clear; 1925 | end; 1926 | 1927 | function TMQTTPacketStore.Count: integer; 1928 | begin 1929 | Result := List.Count; 1930 | end; 1931 | 1932 | constructor TMQTTPacketStore.Create; 1933 | begin 1934 | Stamp := Now; 1935 | List := TList.Create; 1936 | end; 1937 | 1938 | procedure TMQTTPacketStore.DelPacket (anID: Word); 1939 | var 1940 | i : integer; 1941 | aPacket : TMQTTPacket; 1942 | begin 1943 | for i := List.Count - 1 downto 0 do 1944 | begin 1945 | aPacket := List[i]; 1946 | if aPacket.ID = anID then 1947 | begin 1948 | List.Remove (aPacket); 1949 | aPacket.Free; 1950 | exit; 1951 | end; 1952 | end; 1953 | end; 1954 | 1955 | destructor TMQTTPacketStore.Destroy; 1956 | begin 1957 | Clear; 1958 | List.Free; 1959 | inherited; 1960 | end; 1961 | 1962 | function TMQTTPacketStore.GetItem (Index: Integer): TMQTTPacket; 1963 | begin 1964 | if (Index >= 0) and (Index < Count) then 1965 | Result := List[Index] 1966 | else 1967 | Result := nil; 1968 | end; 1969 | 1970 | function TMQTTPacketStore.GetPacket (anID: Word): TMQTTPacket; 1971 | var 1972 | i : integer; 1973 | begin 1974 | for i := 0 to List.Count - 1 do 1975 | begin 1976 | Result := List[i]; 1977 | if Result.ID = anID then exit; 1978 | end; 1979 | Result := nil; 1980 | end; 1981 | 1982 | procedure TMQTTPacketStore.Remove (aPacket : TMQTTPacket); 1983 | begin 1984 | List.Remove (aPacket); 1985 | end; 1986 | 1987 | procedure TMQTTPacketStore.SetItem (Index: Integer; const Value: TMQTTPacket); 1988 | begin 1989 | if (Index >= 0) and (Index < Count) then 1990 | List[Index] := Value; 1991 | end; 1992 | 1993 | { TMQTTPacket } 1994 | 1995 | procedure TMQTTPacket.Assign (From: TMQTTPacket); 1996 | begin 1997 | ID := From.ID; 1998 | Stamp := From.Stamp; 1999 | Counter := From.Counter; 2000 | Retries := From.Retries; 2001 | Msg.Clear; 2002 | From.Msg.Seek (0, soFromBeginning); 2003 | Msg.CopyFrom (From.Msg, From.Msg.Size); 2004 | Publishing := From.Publishing; 2005 | end; 2006 | 2007 | constructor TMQTTPacket.Create; 2008 | begin 2009 | ID := 0; 2010 | Stamp := Now; 2011 | Publishing := true; 2012 | Counter := 0; 2013 | Retries := 0; 2014 | Msg := TMemoryStream.Create; 2015 | end; 2016 | 2017 | destructor TMQTTPacket.Destroy; 2018 | begin 2019 | Msg.Free; 2020 | inherited; 2021 | end; 2022 | 2023 | { TMQTTMessage } 2024 | 2025 | procedure TMQTTMessage.Assign (From: TMQTTMessage); 2026 | begin 2027 | ID := From.ID; 2028 | Stamp := From.Stamp; 2029 | LastUsed := From.LastUsed; 2030 | Retained := From.Retained; 2031 | Counter := From.Counter; 2032 | Retries := From.Retries; 2033 | Topic := From.Topic; 2034 | Message := From.Message; 2035 | Qos := From.Qos; 2036 | end; 2037 | 2038 | constructor TMQTTMessage.Create; 2039 | begin 2040 | ID := 0; 2041 | Stamp := Now; 2042 | LastUsed := Stamp; 2043 | Retained := false; 2044 | Counter := 0; 2045 | Retries := 0; 2046 | Qos := qtAT_MOST_ONCE; 2047 | Topic := ''; 2048 | Message := ''; 2049 | end; 2050 | 2051 | destructor TMQTTMessage.Destroy; 2052 | begin 2053 | inherited; 2054 | end; 2055 | 2056 | { TMQTTMessageStore } 2057 | 2058 | function TMQTTMessageStore.AddMsg (anID: Word; aTopic : UTF8String; aMessage : AnsiString; aQos : TMQTTQOSType; 2059 | aRetry, aCount: cardinal; aRetained : Boolean) : TMQTTMessage; 2060 | begin 2061 | Result := TMQTTMessage.Create; 2062 | Result.ID := anID; 2063 | Result.Topic := aTopic; 2064 | Result.Message := aMessage; 2065 | Result.Qos := aQos; 2066 | Result.Counter := aCount; 2067 | Result.Retries := aRetry; 2068 | Result.Retained := aRetained; 2069 | List.Add (Result); 2070 | end; 2071 | 2072 | procedure TMQTTMessageStore.Assign (From: TMQTTMessageStore); 2073 | var 2074 | i : integer; 2075 | aMsg, bMsg : TMQTTMessage; 2076 | begin 2077 | Clear; 2078 | for i := 0 to From.Count - 1 do 2079 | begin 2080 | aMsg := From[i]; 2081 | bMsg := TMQTTMessage.Create; 2082 | bMsg.Assign (aMsg); 2083 | List.Add (bMsg); 2084 | end; 2085 | end; 2086 | 2087 | procedure TMQTTMessageStore.Clear; 2088 | var 2089 | i : integer; 2090 | begin 2091 | for i := 0 to List.Count - 1 do 2092 | TMQTTMessage (List[i]).Free; 2093 | List.Clear; 2094 | end; 2095 | 2096 | function TMQTTMessageStore.Count: integer; 2097 | begin 2098 | Result := List.Count; 2099 | end; 2100 | 2101 | constructor TMQTTMessageStore.Create; 2102 | begin 2103 | Stamp := Now; 2104 | List := TList.Create; 2105 | end; 2106 | 2107 | procedure TMQTTMessageStore.DelMsg (anID: Word); 2108 | var 2109 | i : integer; 2110 | aMsg : TMQTTMessage; 2111 | begin 2112 | for i := List.Count - 1 downto 0 do 2113 | begin 2114 | aMsg := List[i]; 2115 | if aMsg.ID = anID then 2116 | begin 2117 | List.Remove (aMsg); 2118 | aMsg.Free; 2119 | exit; 2120 | end; 2121 | end; 2122 | end; 2123 | 2124 | destructor TMQTTMessageStore.Destroy; 2125 | begin 2126 | Clear; 2127 | List.Free; 2128 | inherited; 2129 | end; 2130 | 2131 | function TMQTTMessageStore.GetItem (Index: Integer): TMQTTMessage; 2132 | begin 2133 | if (Index >= 0) and (Index < Count) then 2134 | Result := List[Index] 2135 | else 2136 | Result := nil; 2137 | end; 2138 | 2139 | function TMQTTMessageStore.GetMsg (anID: Word): TMQTTMessage; 2140 | var 2141 | i : integer; 2142 | begin 2143 | for i := 0 to List.Count - 1 do 2144 | begin 2145 | Result := List[i]; 2146 | if Result.ID = anID then exit; 2147 | end; 2148 | Result := nil; 2149 | end; 2150 | 2151 | procedure TMQTTMessageStore.Remove (aMsg: TMQTTMessage); 2152 | begin 2153 | List.Remove (aMsg); 2154 | end; 2155 | 2156 | procedure TMQTTMessageStore.SetItem (Index: Integer; const Value: TMQTTMessage); 2157 | begin 2158 | if (Index >= 0) and (Index < Count) then 2159 | List[Index] := Value; 2160 | end; 2161 | 2162 | { TMQTTSession } 2163 | 2164 | constructor TMQTTSession.Create; 2165 | begin 2166 | ClientID := ''; 2167 | Stamp := Now; 2168 | InFlight := TMQTTPacketStore.Create; 2169 | Releasables := TMQTTMessageStore.Create; 2170 | end; 2171 | 2172 | destructor TMQTTSession.Destroy; 2173 | begin 2174 | InFlight.Clear; 2175 | InFlight.Free; 2176 | Releasables.Clear; 2177 | Releasables.Free; 2178 | inherited; 2179 | end; 2180 | 2181 | { TMQTTSessionStore } 2182 | 2183 | procedure TMQTTSessionStore.Clear; 2184 | var 2185 | i : integer; 2186 | begin 2187 | for i := 0 to List.Count - 1 do 2188 | TMQTTSession (List[i]).Free; 2189 | List.Clear; 2190 | end; 2191 | 2192 | function TMQTTSessionStore.Count: integer; 2193 | begin 2194 | Result := List.Count; 2195 | end; 2196 | 2197 | constructor TMQTTSessionStore.Create; 2198 | begin 2199 | Stamp := Now; 2200 | List := TList.Create; 2201 | end; 2202 | 2203 | procedure TMQTTSessionStore.DeleteSession (ClientID: UTF8String); 2204 | var 2205 | aSession : TMQTTSession; 2206 | begin 2207 | aSession := GetSession (ClientID); 2208 | if aSession <> nil then 2209 | begin 2210 | List.Remove (aSession); 2211 | aSession.Free; 2212 | end; 2213 | end; 2214 | 2215 | destructor TMQTTSessionStore.Destroy; 2216 | begin 2217 | Clear; 2218 | List.Free; 2219 | inherited; 2220 | end; 2221 | 2222 | function TMQTTSessionStore.GetItem (Index: Integer): TMQTTSession; 2223 | begin 2224 | if (Index >= 0) and (Index < Count) then 2225 | Result := List[Index] 2226 | else 2227 | Result := nil; 2228 | end; 2229 | 2230 | function TMQTTSessionStore.GetSession (ClientID: UTF8String): TMQTTSession; 2231 | var 2232 | i : integer; 2233 | begin 2234 | for i := 0 to List.Count - 1 do 2235 | begin 2236 | Result := List[i]; 2237 | if Result.ClientID = ClientID then exit; 2238 | end; 2239 | Result := nil; 2240 | end; 2241 | 2242 | procedure TMQTTSessionStore.RestoreSession (ClientID: UTF8String; 2243 | aClient: TClient); 2244 | var 2245 | aSession : TMQTTSession; 2246 | begin 2247 | aClient.InFlight.Clear; 2248 | aClient.Releasables.Clear; 2249 | aSession := GetSession (ClientID); 2250 | if aSession <> nil then 2251 | begin 2252 | aClient.InFlight.Assign (aSession.InFlight); 2253 | aClient.Releasables.Assign (aSession.Releasables); 2254 | end; 2255 | end; 2256 | 2257 | procedure TMQTTSessionStore.RestoreSession (ClientID: UTF8String; 2258 | aClient: TMQTTClient); 2259 | var 2260 | aSession : TMQTTSession; 2261 | begin 2262 | aClient.InFlight.Clear; 2263 | aClient.Releasables.Clear; 2264 | aSession := GetSession (ClientID); 2265 | if aSession <> nil then 2266 | begin 2267 | aClient.InFlight.Assign (aSession.InFlight); 2268 | aClient.Releasables.Assign (aSession.Releasables); 2269 | end; 2270 | end; 2271 | 2272 | procedure TMQTTSessionStore.StoreSession (ClientID: UTF8String; 2273 | aClient: TMQTTClient); 2274 | var 2275 | aSession : TMQTTSession; 2276 | begin 2277 | aSession := GetSession (ClientID); 2278 | if aSession <> nil then 2279 | begin 2280 | aSession := TMQTTSession.Create; 2281 | aSession.ClientID := ClientID; 2282 | List.Add (aSession); 2283 | end; 2284 | 2285 | aSession.InFlight.Assign (aClient.InFlight); 2286 | aSession.Releasables.Assign (aClient.Releasables); 2287 | end; 2288 | 2289 | procedure TMQTTSessionStore.StoreSession (ClientID: UTF8String; 2290 | aClient: TClient); 2291 | var 2292 | aSession : TMQTTSession; 2293 | begin 2294 | aClient.InFlight.Clear; 2295 | aClient.Releasables.Clear; 2296 | aSession := GetSession (ClientID); 2297 | if aSession <> nil then 2298 | begin 2299 | aSession := TMQTTSession.Create; 2300 | aSession.ClientID := ClientID; 2301 | List.Add (aSession); 2302 | end; 2303 | aSession.InFlight.Assign (aClient.InFlight); 2304 | aSession.Releasables.Assign (aClient.Releasables); 2305 | end; 2306 | 2307 | procedure TMQTTSessionStore.SetItem (Index: Integer; const Value: TMQTTSession); 2308 | begin 2309 | if (Index >= 0) and (Index < Count) then 2310 | List[Index] := Value; 2311 | end; 2312 | 2313 | end. 2314 | --------------------------------------------------------------------------------