From 89d4b02ec6b1fcb41d695327cf3adf41029f03c0 Mon Sep 17 00:00:00 2001 From: Brendon Telman Date: Mon, 24 Feb 2020 19:54:35 -0600 Subject: [PATCH] MQTT integration. Not hooked up to settings yet --- .../controller/activities/MainActivity.kt | 2 + build.gradle | 3 + sdk/build.gradle | 2 + .../sdk/components/MqttComponent.kt | 144 ++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 sdk/src/main/java/tv/remo/android/controller/sdk/components/MqttComponent.kt diff --git a/app/src/main/java/tv/remo/android/controller/activities/MainActivity.kt b/app/src/main/java/tv/remo/android/controller/activities/MainActivity.kt index 8c3b4d3..d4f3e87 100644 --- a/app/src/main/java/tv/remo/android/controller/activities/MainActivity.kt +++ b/app/src/main/java/tv/remo/android/controller/activities/MainActivity.kt @@ -17,6 +17,7 @@ import org.btelman.controlsdk.services.ControlSDKServiceConnection import org.btelman.controlsdk.services.observeAutoCreate import tv.remo.android.controller.R import tv.remo.android.controller.sdk.RemoSettingsUtil +import tv.remo.android.controller.sdk.components.MqttComponent import tv.remo.android.controller.sdk.models.api.Message import tv.remo.android.controller.sdk.utils.ChatUtil import tv.remo.android.controller.sdk.utils.ComponentBuilderUtil @@ -150,6 +151,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { private fun createComponentHolders() { RemoSettingsUtil.with(this){ settings -> + arrayList.add(ComponentHolder(MqttComponent::class.java, null)) arrayList.add(ComponentBuilderUtil.createSocketComponent(settings)) arrayList.addAll(ComponentBuilderUtil.createTTSComponents(settings)) arrayList.addAll(ComponentBuilderUtil.createStreamingComponents(settings)) diff --git a/build.gradle b/build.gradle index 66af967..019ffad 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,9 @@ allprojects { maven { url "https://dl.bintray.com/btelman96/maven" } + maven { + url "https://repo.eclipse.org/content/repositories/paho-snapshots/" + } maven { url "https://jitpack.io" } } } diff --git a/sdk/build.gradle b/sdk/build.gradle index e5f19cc..d0a7810 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -42,6 +42,8 @@ dependencies { api 'com.google.code.gson:gson:2.8.5' api project(path: ':licensehelper') implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' + api 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' + api 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' } repositories { mavenCentral() diff --git a/sdk/src/main/java/tv/remo/android/controller/sdk/components/MqttComponent.kt b/sdk/src/main/java/tv/remo/android/controller/sdk/components/MqttComponent.kt new file mode 100644 index 0000000..78fae9a --- /dev/null +++ b/sdk/src/main/java/tv/remo/android/controller/sdk/components/MqttComponent.kt @@ -0,0 +1,144 @@ +package tv.remo.android.controller.sdk.components + +import android.content.Context +import android.os.Bundle +import android.util.Log +import org.btelman.controlsdk.enums.ComponentType +import org.btelman.controlsdk.models.Component +import org.btelman.controlsdk.models.ComponentEventObject +import org.eclipse.paho.client.mqttv3.* +import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence +import tv.remo.android.controller.sdk.RemoSettingsUtil +import tv.remo.android.controller.sdk.interfaces.RemoCommandSender +import tv.remo.android.controller.sdk.models.api.Channel +import java.util.* + +/** + * MQTT component + * + * Note: Do not instantiate in the activity! Must pass it to the ControlSDK Service + */ +class MqttComponent : Component() , RemoCommandSender, MqttCallback { + private var channel: Channel? = null + var client : MqttClient? = null + + override fun onInitializeComponent(applicationContext: Context, bundle: Bundle?) { + super.onInitializeComponent(applicationContext, bundle) + RemoSettingsUtil.with(applicationContext){ + + } + } + + override fun disableInternal() { + disconnectMQTT() + client?.close() + } + + override fun enableInternal() { + client = MqttClient("ssl://url:8883", UUID.randomUUID().toString(), + MqttDefaultFilePersistence(context!!.filesDir.absolutePath) + ) + } + + override fun getType(): ComponentType { + return ComponentType.CUSTOM + } + + override fun handleExternalMessage(message: ComponentEventObject) : Boolean{ + if(message.source is RemoCommandSender){ + when(message.data){ + is Channel -> { + resetMQTT(message.data as Channel) + } + is String -> { + //do something! + } + } + } + return false + } + + private fun resetMQTT(channel: Channel?) { + disconnectMQTT() + channel?.let { + connectMQTT(channel) + } + } + + private fun connectMQTT(channel: Channel){ + client?.let { + val connectOptions = MqttConnectOptions().also { options-> + options.setWill(TopicBuilder.buildTopic(JOIN_TOPIC, channel), + "null-from-will".toByteArray(), 1, false) + options.isCleanSession = true + options.userName = "user" + options.password = "pass".toCharArray() + } + it.setCallback(this) + it.connect(connectOptions) + this.channel = channel + TopicBuilder(it, JOIN_TOPIC, channel, data = Date().toString()).send() //register channel and persist + Log.d("AAA", "Connected") + } + } + + private fun disconnectMQTT(){ + client?.takeIf { channel != null }?.let { + TopicBuilder(it, JOIN_TOPIC, channel!!, data = null).send() //unregister channel + it.disconnect() + } + } + + override fun messageArrived(topic: String?, message: MqttMessage?) { + + } + + override fun connectionLost(cause: Throwable?) { + + } + + override fun deliveryComplete(token: IMqttDeliveryToken?) { + + } + + data class TopicBuilder( + val client : IMqttClient, + val topic : String, + val channel: Channel, + val qos : Int = 1, + var data : Any? = null + ){ + fun send(persist : Boolean = false){ + val dataToSend = parse(data) + client.publish(buildTopic(topic, channel), dataToSend, qos, persist) + } + + companion object{ + fun buildTopic(topic : String, channel: Channel) : String{ + return String.format(topic, channel.id) + } + + fun parse(data : Any?) : ByteArray{ + return when(data){ + is String -> { + data.toByteArray() + } + else -> { + "null".toByteArray() + } + } + } + } + } + + companion object{ + private const val CHANNEL = "%1\$s" + private const val ROOT_TOPIC = "remo" + private const val JOIN_TOPIC = "$ROOT_TOPIC/device/$CHANNEL" + private const val AVAILABLE_TOPICS_TOPIC = "$ROOT_TOPIC/$CHANNEL/topics" //acts like discovery, and should be retained + private const val SEND_BUTTON_COMMAND_TOPIC = "$ROOT_TOPIC/$CHANNEL/site/buttonCommand" + private const val SEND_CHAT_TOPIC = "$ROOT_TOPIC/$CHANNEL/site/chat" + private const val BATTERY_ROBOT_TOPIC = "$ROOT_TOPIC/$CHANNEL/robot/battery" + private const val BATTERY_PHONE_TOPIC = "$ROOT_TOPIC/$CHANNEL/phone/battery" + } +} \ No newline at end of file