diff --git a/Assets/Generic Client.meta b/Assets/Generic Client.meta new file mode 100644 index 00000000..f526e27f --- /dev/null +++ b/Assets/Generic Client.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d6f1fedab44a1964e800b463d37b5039 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Generic Client/GenericClient.Logic.cs b/Assets/Generic Client/GenericClient.Logic.cs new file mode 100644 index 00000000..47ffe707 --- /dev/null +++ b/Assets/Generic Client/GenericClient.Logic.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Best.HTTP.Shared.PlatformSupport.Memory; + +using Best.MQTT; +using Best.MQTT.Examples; +using Best.MQTT.Examples.Helpers; +using Best.MQTT.Packets; +using Best.MQTT.Packets.Builders; + +using UnityEngine; + +namespace Best.MQTT.Examples +{ + public partial class GenericClient + { + private MQTTClient client; + + // UI instances of SubscriptionListItem + private List subscriptionListItems = new List(); + + public void OnConnectButton() + { +#if UNITY_WEBGL && !UNITY_EDITOR + if (this.transportDropdown.value == 0) + { + AddText("TCP transport isn't available under WebGL!"); + return; + } +#endif + + SetConnectingUI(); + + var host = this.hostInput.GetValue("broker.mqttdashboard.com"); + AddText($"[{host}] Connecting with client id: {SessionHelper.Get(host).ClientId}"); + + var options = new ConnectionOptions(); + options.Host = host; + options.Port = this.portInput.GetIntValue(1883); + options.Transport = (SupportedTransports)this.transportDropdown.value; + options.UseTLS = this.isSecureToggle.GetBoolValue(); + options.Path = this.pathInput.GetValue("/mqtt"); + options.ProtocolVersion = (SupportedProtocolVersions)this.protocolVersionDropdown.value; + + this.client = new MQTTClient(options); + + this.client.OnConnected += OnConnected; + this.client.OnError += OnError; + this.client.OnDisconnect += OnDisconnected; + this.client.OnStateChanged += OnStateChanged; + + this.client.BeginConnect(ConnectPacketBuilderCallback); + } + + private void OnConnected(MQTTClient client) + { + SetConnectedUI(); + } + + private void OnDisconnected(MQTTClient client, DisconnectReasonCodes code, string reason) + { + SetDisconnectedUI(); + + AddText($"[{client.Options.Host}] OnDisconnected - code: {code}, reason: {reason}"); + } + + private void OnError(MQTTClient client, string reason) + { + AddText($"[{client.Options.Host}] OnError reason: {reason}"); + } + + public void OnDisconnectButton() + { + this.connectButton.interactable = false; + this.client?.CreateDisconnectPacketBuilder().BeginDisconnect(); + } + + private void OnStateChanged(MQTTClient client, ClientStates oldState, ClientStates newState) + { + AddText($"[{client.Options.Host}] {oldState} => {newState}"); + } + + private ConnectPacketBuilder ConnectPacketBuilderCallback(MQTTClient client, ConnectPacketBuilder builder) + { + AddText($"[{client.Options.Host}] Creating connect packet."); + + var userName = this.userNameInput.GetValue(null); + var password = this.passwordInput.GetValue(null); + + var session = SessionHelper.HasAny(client.Options.Host) ? SessionHelper.Get(client.Options.Host) : SessionHelper.CreateNullSession(client.Options.Host); + builder.WithSession(session); + + if (!string.IsNullOrEmpty(userName)) + builder.WithUserName(userName); + + if (!string.IsNullOrEmpty(password)) + builder.WithPassword(password); + + builder.WithKeepAlive((ushort)this.keepAliveInput.GetIntValue(60)); + + // setup last-will + + var lastWillTopic = this.lastWill_TopicInput.GetValue(null); + var lastWillMessage = this.lastWill_MessageInput.GetValue(null); + var retain = this.lastWill_RetainToggle.GetBoolValue(); + + if (!string.IsNullOrEmpty(lastWillTopic) && !string.IsNullOrEmpty(lastWillMessage)) + builder.WithLastWill(new LastWillBuilder() + .WithTopic(lastWillTopic) + .WithContentType("text/utf-8") + .WithPayload(Encoding.UTF8.GetBytes(lastWillMessage)) + .WithQoS(this.lastWill_QoSDropdown.GetQoS()) + .WithRetain(retain)); + + return builder; + } + + public void OnPublishButtonClicked() + { + string topic = this.publish_TopicInput.GetValue("best_mqtt/test"); + QoSLevels qos = this.publish_QoSDropdown.GetQoS(); + bool retain = this.publish_RetainToggle.GetBoolValue(); + string message = this.publish_MessageInput.GetValue("Hello MQTT World..."); + + this.client.CreateApplicationMessageBuilder(topic) + .WithQoS(qos) + .WithRetain(retain) + .WithPayload(message) + .BeginPublish(); + } + + public void OnSubscribeButtonClicked() + { + var colorValue = this.subscribe_ColorInput.GetValue("000000"); + if (!ColorUtility.TryParseHtmlString("#" + colorValue, out var color)) + { + AddText($"[{client.Options.Host}] Couldn't parse '#{colorValue}'"); + return; + } + + var qos = this.subscribe_QoSDropdown.GetQoS(); + var topic = this.subscribe_TopicInput.GetValue("best_mqtt/#"); + + this.client?.CreateBulkSubscriptionBuilder() + .WithTopic(new SubscribeTopicBuilder(topic) + .WithMaximumQoS(qos) + .WithAcknowledgementCallback(OnSubscriptionAcknowledgement) + .WithMessageCallback(OnApplicationMessage)) + .BeginSubscribe(); + + AddText($"[{client.Options.Host}] Subscribe request for topic {topic} sent..."); + + AddSubscriptionUI(topic, colorValue); + } + + private void OnSubscriptionAcknowledgement(MQTTClient client, SubscriptionTopic topic, SubscribeAckReasonCodes reasonCode) + { + var subscription = FindSubscriptionItem(topic.Filter.OriginalFilter); + + string reasonColor = reasonCode <= SubscribeAckReasonCodes.GrantedQoS2 ? "green" : "red"; + AddText($"[{client.Options.Host}] Subscription request to topic {topic.Filter.OriginalFilter} returned with reason code: {reasonCode}"); + } + + private void AddSubscriptionUI(string topic, string color) + { + var item = Instantiate(this.subscription_ListItem, this.subscribe_ListItemRoot); + item.Set(this, topic, color); + + this.subscriptionListItems.Add(item); + } + + public void Unsubscribe(string topic) + { + this.client.CreateUnsubscribePacketBuilder(topic) + .WithAcknowledgementCallback(OnUnsubscribed) + .BeginUnsubscribe(); + + var subscription = FindSubscriptionItem(topic); + + AddText($"[{client.Options.Host}] Unsubscribe request for topic {topic} sent..."); + } + + private void OnUnsubscribed(MQTTClient client, string topic, Best.MQTT.Packets.UnsubscribeAckReasonCodes reason) + { + var instance = this.subscriptionListItems.FirstOrDefault(s => s.Topic.OriginalFilter == topic); + this.subscriptionListItems.Remove(instance); + Destroy(instance.gameObject); + + string reasonColor = reason == UnsubscribeAckReasonCodes.Success ? "green" : "red"; + + AddText($"[{client.Options.Host}] Unsubscription request to topic {topic} returned with reason code: {reason}"); + } + + private void OnApplicationMessage(MQTTClient client, SubscriptionTopic topic, string topicName, ApplicationMessage applicationMessage) + { + // find matching subscription for its color + var subscription = FindSubscriptionItem(topicName); + + string payload = string.Empty; + + // Here we going to try to convert the payload as an UTF-8 string. Note that it's not guaranteed that the payload is a string! + // While MQTT supports an additional Content-Type field in this demo we can't rely on its presense. + if (applicationMessage.Payload != BufferSegment.Empty) + { + payload = Encoding.UTF8.GetString(applicationMessage.Payload.Data, applicationMessage.Payload.Offset, applicationMessage.Payload.Count); + + const int MaxPayloadLength = 512; + if (payload.Length > MaxPayloadLength) + payload = payload?.Remove(MaxPayloadLength); + } + + // Display the Content-Type if present + string contentType = string.Empty; + if (applicationMessage.ContentType != null) + contentType = $" ({applicationMessage.ContentType}) "; + + // Add the final text to the demo's log view. + AddText($"[{client.Options.Host}] [{topicName}] {contentType}{payload}"); + } + + private SubscriptionListItem FindSubscriptionItem(string topicName) => this.subscriptionListItems.FirstOrDefault(s => s.Topic.IsMatching(topicName)); + + private void OnDestroy() + { + this.client?.CreateDisconnectPacketBuilder().BeginDisconnect(); + } + } +} diff --git a/Assets/Generic Client/GenericClient.Logic.cs.meta b/Assets/Generic Client/GenericClient.Logic.cs.meta new file mode 100644 index 00000000..27b24203 --- /dev/null +++ b/Assets/Generic Client/GenericClient.Logic.cs.meta @@ -0,0 +1,18 @@ +fileFormatVersion: 2 +guid: 80cb39f4371a29e4d8da5a85303b5ee9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: +AssetOrigin: + serializedVersion: 1 + productId: 268762 + packageName: Best MQTT + packageVersion: 3.0.4 + assetPath: Packages/com.tivadar.best.mqtt/Samples~/Generic Client/GenericClient.Logic.cs + uploadId: 737289 diff --git a/Assets/Generic Client/GenericClient.cs b/Assets/Generic Client/GenericClient.cs new file mode 100644 index 00000000..7fe682a5 --- /dev/null +++ b/Assets/Generic Client/GenericClient.cs @@ -0,0 +1,302 @@ +using System.Collections.Generic; + +using Best.HTTP.Shared; + +using Best.MQTT; +using Best.MQTT.Examples; +using Best.MQTT.Examples.Helpers; + +using UnityEngine; +using UnityEngine.UI; + +namespace Best.MQTT.Examples +{ + public partial class GenericClient : MonoBehaviour + { +#pragma warning disable 0649 + [Header("Connect")] + [SerializeField] + private Dropdown templatesDropdown; + + [SerializeField] + private InputField hostInput; + + [SerializeField] + private InputField portInput; + + [SerializeField] + private Dropdown transportDropdown; + + [SerializeField] + private InputField pathInput; + + [SerializeField] + private Toggle isSecureToggle; + + [SerializeField] + private InputField userNameInput; + + [SerializeField] + private InputField passwordInput; + + [SerializeField] + private InputField keepAliveInput; + + [SerializeField] + private Dropdown protocolVersionDropdown; + + [SerializeField] + private Button connectButton; + + [Header("Connect Last-Will")] + + [SerializeField] + private InputField lastWill_TopicInput; + + [SerializeField] + private Dropdown lastWill_QoSDropdown; + + [SerializeField] + private Toggle lastWill_RetainToggle; + + [SerializeField] + private InputField lastWill_MessageInput; + + + [Header("Publish")] + [SerializeField] + private InputField publish_TopicInput; + + [SerializeField] + private Dropdown publish_QoSDropdown; + + [SerializeField] + private Toggle publish_RetainToggle; + + [SerializeField] + private InputField publish_MessageInput; + + [Header("Subscribe")] + [SerializeField] + private InputField subscribe_ColorInput; + + [SerializeField] + private Dropdown subscribe_QoSDropdown; + + [SerializeField] + private InputField subscribe_TopicInput; + + [SerializeField] + private Transform subscribe_ListItemRoot; + + [SerializeField] + private SubscriptionListItem subscription_ListItem; + + [SerializeField] + private Transform publishSubscribePanel; + + [Header("Logs")] + [SerializeField] + private InputField logs_MaxEntriesInput; + + [SerializeField] + private Toggle logs_AutoScroll; + + [SerializeField] + private TextListItem textListItem; + + [SerializeField] + private ScrollRect log_view; + + [SerializeField] + private Transform logRoot; + +#pragma warning restore + + private void Awake() + { + InitUI(); + PopulateTemplates(); + } + + private void AddText(string text) + { + int maxEntries = this.logs_MaxEntriesInput.GetIntValue(100); + + if (this.logRoot.childCount >= maxEntries) + { + TrimLogEntries(maxEntries); + + var child = this.logRoot.GetChild(0); + child.GetComponent().SetText(text); + child.SetAsLastSibling(); + } + else + { + var item = Instantiate(this.textListItem, this.logRoot); + item.SetText(text); + } + + bool autoScroll = this.logs_AutoScroll.GetBoolValue(); + if (autoScroll) + { + this.log_view.normalizedPosition = new Vector2(0, 0); + } + } + + private void TrimLogEntries(int maxEntries) + { + while (this.logRoot.childCount > maxEntries) + { + var child = this.logRoot.GetChild(0); + child.transform.SetParent(this.transform); + + Destroy(child.gameObject); + } + } + + private void InitUI() + { + this.connectButton.GetComponentInChildren().text = "Connect"; + this.connectButton.interactable = true; + this.connectButton.onClick.RemoveAllListeners(); + this.connectButton.onClick.AddListener(OnConnectButton); + + foreach (var button in this.publishSubscribePanel.GetComponentsInChildren