MQTT_tinyClient.pb

Version: #1 [] 1  
Created: 24.06.2025 21:46:24
;tiny example MQTT Client

XIncludeFile "MQTT_Client.pbi"
EnableExplicit

CompilerIf #PB_Compiler_Version >= 620
	UseNetworkTLS()
CompilerEndIf

Enumeration #PB_Event_FirstCustomValue
	#MQTT_EventIncoming
EndEnumeration

Runtime Enumeration Gadgets
	#frame_settings
	#text_broker_url
	#string_broker_url
	#text_broker_port
	#string_broker_port
	#checkbox_TLS
	#text_username
	#string_username
	#text_password
	#string_password
	#button_connect
	#frame_subscriptions
	#editor_subscriptions
	#frame_publish
	#text_publish
	#string_topic
	#text_publish_content
	#string_publish_content
	#button_publish
	#frame_log
	#editor_log
EndEnumeration

Enumeration
	#Dialog_Main
EndEnumeration

	;not really needed, but the reply from a broker to a subscription request doesn't contain the topic we tried to subscribe to.
	;therefore it might make sense to store our topics we requested
	;in this case I only used it to create log messages, like "successfully subscribed to Topic"
Structure _MySubscriptions_
	PacketIdentifier.u
	List Topics.MQTT_Common::Filter() ;<- needed to store the topics we subscribed to
EndStructure

Global ClientID.i
Global ClientConf.MQTT_CLIENT::CLIENT_INIT
Global NewList MySubscriptions._MySubscriptions_()

Procedure.s GetXMLString()
	Protected XML$

	XML$ + "<?xml version='1.0' encoding='UTF-16'?>"
	XML$ + ""
	XML$ + "<dialogs><!--Created by Dialog Design0R V1.87 => get it from: https://hex0rs.coderbu.de/en/sdm_downloads/dialogdesign0r/-->"
	XML$ + "  <window flags='#PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget' text='MQTT tinyClient' minwidth='450' minheight='450' name='window_main' xpos='834' ypos='342'>"
	XML$ + "    <vbox expand='item:4'>"
	XML$ + "      <frame text='Connection Setup' id='#frame_settings'>"
	XML$ + "        <hbox>"
	XML$ + "          <gridbox columns='2' colexpand='item:2'>"
	XML$ + "            <text text='Broker URL/IP:' id='#text_broker_url'/>"
	XML$ + "            <string width='120' text='127.0.0.1' id='#string_broker_url'/>"
	XML$ + "            <text text='Broker port:' id='#text_broker_port'/>"
	XML$ + "            <string flags='#PB_String_Numeric' text='1883' id='#string_broker_port'/>"
	XML$ + "            <checkbox text='Use TLS' colspan='2' id='#checkbox_TLS' onevent='OnClick_UseTLS()'/>"
	XML$ + "          </gridbox>"
	XML$ + "          <gridbox columns='2' colexpand='item:2'>"
	XML$ + "            <text text='[Username]:' id='#text_username'/>"
	XML$ + "            <string width='100' id='#string_username'/>"
	XML$ + "            <text text='[Password]:' id='#text_password'/>"
	XML$ + "            <string flags='#PB_String_Password' id='#string_password'/>"
	XML$ + "            <button text='Connect' flags='#PB_Button_Default' id='#button_connect' onevent='OnClick_Connect()'/>"
	XML$ + "          </gridbox>"
	XML$ + "        </hbox>"
	XML$ + "      </frame>"
	XML$ + "      <frame text='Subscriptions (each line a topic to subscribe):' id='#frame_subscriptions'>"
	XML$ + "        <editor height='80' id='#editor_subscriptions'/>"
	XML$ + "      </frame>"
	XML$ + "      <frame text='Publish:' id='#frame_publish'>"
	XML$ + "        <hbox>"
	XML$ + "          <gridbox columns='2' colexpand='item:2'>"
	XML$ + "            <text text='to Topic:' id='#text_publish'/>"
	XML$ + "            <string width='100' id='#string_topic'/>"
	XML$ + "          </gridbox>"
	XML$ + "          <gridbox columns='3' colexpand='item:2'>"
	XML$ + "            <text text='Content:' id='#text_publish_content'/>"
	XML$ + "            <string width='100' id='#string_publish_content'/>"
	XML$ + "            <button text='Publish' id='#button_publish' onevent='OnClick_Publish()'/>"
	XML$ + "          </gridbox>"
	XML$ + "        </hbox>"
	XML$ + "      </frame>"
	XML$ + "      <frame text='Log:' id='#frame_log'>"
	XML$ + "        <editor flags='#PB_Editor_ReadOnly' height='100' id='#editor_log'/>"
	XML$ + "      </frame>"
	XML$ + "    </vbox>"
	XML$ + "  </window>"
	XML$ + "</dialogs><!--DDesign0R Definition: PureBasic|1|1|1|___|example_with_declares|1-->"
	XML$ + ""

	ProcedureReturn XML$
EndProcedure

Procedure LogIT(Text.s)
	If Text
		Text = FormatDate("%hh:%ii:%ss", Date()) + " " + Text
	EndIf
	AddGadgetItem(#editor_log, - 1, Text)
	CompilerSelect #PB_Compiler_OS
		CompilerCase #PB_OS_Windows
			Select GadgetType(#editor_log)
				Case #PB_GadgetType_ListView
					SendMessage_(GadgetID(#editor_log), #LB_SETTOPINDEX, CountGadgetItems(#editor_log) - 1, #Null)
				Case #PB_GadgetType_ListIcon
					SendMessage_(GadgetID(#editor_log), #LVM_ENSUREVISIBLE, CountGadgetItems(#editor_log) - 1, #False)
				Case #PB_GadgetType_Editor
					SendMessage_(GadgetID(#editor_log), #EM_SCROLLCARET, #SB_BOTTOM, 0)
			EndSelect
		CompilerCase #PB_OS_Linux
			Protected *Adjustment.GtkAdjustment
			*Adjustment       = gtk_scrolled_window_get_vadjustment_(gtk_widget_get_parent_(GadgetID(#editor_log)))
			*Adjustment\value = *Adjustment\upper
			gtk_adjustment_value_changed_(*Adjustment)
	CompilerEndSelect
	
EndProcedure

Runtime Procedure OnClick_UseTLS()
	If GetGadgetState(#checkbox_TLS)
		SetGadgetText(#string_broker_port, "8883")
	Else
		SetGadgetText(#string_broker_port, "1883")
	EndIf
EndProcedure

Runtime Procedure OnClick_Connect()
	
	If ClientID
		MQTT_CLIENT::DeInitClient(ClientID)
		ClientID = #Null
		SetGadgetText(#button_connect, "Connect")
		ProcedureReturn 
	EndIf
	
	;Config Client
	ClientConf\BrokerURL         = GetGadgetText(#string_broker_url)
	ClientConf\Port              = Val(GetGadgetText(#string_broker_port))
	ClientConf\Username          = GetGadgetText(#string_username)
	ClientConf\Password          = GetGadgetText(#string_password)
	ClientConf\ClientIdentifier  = "PBClient_{" + RSet(Str(Random(999999, 101)), 6, "0") + "}"
	ClientConf\Window            = DialogWindow(#Dialog_Main)
	ClientConf\WindowEvent       = #MQTT_EventIncoming
	ClientConf\Will\Topic        = "Booom/We/are/Dead"
	ClientConf\Will\Message      = "disconnected!"
	CompilerIf #PB_Compiler_Version
		If GetGadgetState(#checkbox_TLS)
			ClientConf\ExtraConnectFlags = #PB_Network_TLSv1_2
		Else
			ClientConf\ExtraConnectFlags = 0
		EndIf
	CompilerEndIf
	
	ClientID = MQTT_CLIENT::InitClient(@ClientConf, #True)
	If ClientID
		SetGadgetText(#button_connect, "Disconnect")
	EndIf

	ProcedureReturn ClientID
EndProcedure

Runtime Procedure OnClick_Publish()
	Protected Topic$, Content$
	
	Topic$ = GetGadgetText(#string_topic)
	Content$ = GetGadgetText(#string_publish_content)
	If Topic$ And Content$ And ClientID
		MQTT_CLIENT::PublishTopic(ClientID, Topic$, @Content$, StringByteLength(Content$))
	EndIf
	
EndProcedure

Procedure MQTT_EventIncoming()
	Protected *Values.MQTT_Common::MQTT_EVENTDATA, a$
	Protected *Buffer, i, no, Type, Payload.s, Topic.s, PacketIdentifier.i, ErrorText.s, Error
	Protected QoS, DUP, Retain
	
	*Values = EventData()
	If *Values
		a$               = PeekS(*Values + OffsetOf(MQTT_Common::MQTT_EVENTDATA\D), - 1, #PB_UTF8)
		Topic            = StringField(a$, 1, #ESC$)
		ErrorText        = ReplaceString(StringField(a$, 2, #ESC$), "{CLIENT}", "BROKER")
		Type             = *Values\Type
		PacketIdentifier = *Values\PacketIdentifier
		Error            = *Values\Error
		QoS              = *Values\QoS
		DUP              = *Values\DUP
		Retain           = *Values\Retain
		If *Values\PayLoad
			Payload = PeekS(*Values\PayLoad, *Values\PayLoadLength, #PB_UTF8 | #PB_ByteLength)
			FreeMemory(*Values\PayLoad)
		EndIf
		FreeMemory(*Values)
			;handle it...
		Select Type
			Case MQTT_Common::#MQTTEvent_SuccessfullyConnected
				LogIT("connection o.k.!")
				;subscribe now to topics which have been set before
				If ListSize(MySubscriptions()) > 0
					ForEach MySubscriptions()
						ClearList(MySubscriptions()\Topics())
					Next
					ClearList(MySubscriptions())
				EndIf
				AddElement(MySubscriptions())
				For i = 0 To CountGadgetItems(#editor_subscriptions) - 1
					AddElement(MySubscriptions()\Topics())
					MySubscriptions()\Topics()\Topic = GetGadgetItemText(#editor_subscriptions, i)
					MySubscriptions()\Topics()\QoS   = 0
				Next i
				MySubscriptions()\PacketIdentifier = MQTT_CLIENT::SubscribeToTopics(ClientID, MySubscriptions()\Topics())
			Case MQTT_Common::#MQTTEvent_SubscriptionSuccessfull
				ForEach MySubscriptions()
					If MySubscriptions()\PacketIdentifier = PacketIdentifier
						i = 1
						ForEach MySubscriptions()\Topics()
							If Val(StringField(Payload, i, ",")) <> $80
								LogIT("Subscribed successfully to '" + MySubscriptions()\Topics()\Topic + "'")
							Else
								LogIT("Unable to subscribe to '" + MySubscriptions()\Topics()\Topic + "'!")
							EndIf
							i + 1
						Next
						ClearList(MySubscriptions()\Topics())
						DeleteElement(MySubscriptions())
					EndIf
				Next
			Case MQTT_Common::#MQTTEvent_PublishIncoming
				LogIT("Published Topic: '" + Topic + "'->" + Payload + " (Q=" + Str(Qos) + ", D=" + Str(DUP) + ", R=" + Str(Retain) + ", M=" + Str(PacketIdentifier) + ")")
			Case MQTT_Common::#MQTTEvent_PublishingSuccessfull
				LogIT("Published successfully to topic '" + Topic + "'!")
			Case MQTT_Common::#MQTTEvent_Error
				LogIT(ErrorText)
				ClientID = #Null
				SetGadgetText(#button_connect, "Connect")
		EndSelect
	EndIf
EndProcedure


Procedure main()
	Protected a$
	
	a$ = GetXMLString()
	ParseXML(0, a$)
	CreateDialog(0)
	OpenXMLDialog(0, 0, "window_main")
	BindEvent(#MQTT_EventIncoming, @MQTT_EventIncoming(), DialogWindow(#Dialog_Main))

	Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
	MQTT_CLIENT::DeInitClient(ClientID)
EndProcedure

main()