Added basic MQTT functionality

This commit is contained in:
Cameron Cordes
2023-10-02 13:30:45 -04:00
parent f0bb26098a
commit aa4cca4bc0
7 changed files with 272 additions and 19 deletions

1
.idea/gradle.xml generated
View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>

3
.idea/misc.xml generated
View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -20,10 +20,26 @@ android {
} }
} }
Properties mqtt_props = new Properties()
mqtt_props.load(new File('local.properties').newInputStream())
buildTypes { buildTypes {
debug {
buildConfigField("String", "MQTT_URL", mqtt_props["MQTT_URL"])
buildConfigField("Integer", "MQTT_PORT", mqtt_props["MQTT_PORT"])
buildConfigField("String", "MQTT_USER", mqtt_props["MQTT_USER"])
buildConfigField("String", "MQTT_PASSWORD", mqtt_props["MQTT_PASSWORD"])
}
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField("String", "MQTT_URL", mqtt_props["MQTT_URL"])
buildConfigField("Int", "MQTT_PORT", mqtt_props["MQTT_PORT"])
buildConfigField("String", "MQTT_USER", mqtt_props["MQTT_USER"])
buildConfigField("String", "MQTT_PASSWORD", mqtt_props["MQTT_PASSWORD"])
} }
} }
compileOptions { compileOptions {
@@ -35,6 +51,7 @@ android {
} }
buildFeatures { buildFeatures {
compose true compose true
buildConfig true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion '1.3.2' kotlinCompilerExtensionVersion '1.3.2'
@@ -48,15 +65,21 @@ android {
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.core:core-ktx:1.10.1'
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0') implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation 'androidx.activity:activity-compose:1.5.1' implementation 'androidx.activity:activity-compose:1.7.2'
implementation platform('androidx.compose:compose-bom:2022.10.00') implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3' implementation 'androidx.compose.material3:material3'
implementation 'io.insert-koin:koin-android:3.4.2'
implementation 'io.insert-koin:koin-androidx-compose:3.4.5'
implementation 'org.eclipse.paho:org.eclipse.paho.mqttv5.client:1.2.5'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

View File

@@ -2,7 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:name=".LEDMatrixControllerApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@@ -12,10 +15,10 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.LEDMatrixController" android:theme="@style/Theme.LEDMatrixController"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.LEDMatrixController"> android:theme="@style/Theme.LEDMatrixController">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@@ -0,0 +1,51 @@
package com.cameronc.dev.ledmatrixcontroller
import android.app.Application
import android.net.TrafficStats
import android.os.StrictMode
import org.eclipse.paho.mqttv5.client.MqttClient
import org.eclipse.paho.mqttv5.client.MqttConnectionOptionsBuilder
import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.module
import kotlin.concurrent.thread
class LEDMatrixControllerApplication : Application() {
private val matrixController: MatrixController by inject()
override fun onCreate() {
super.onCreate()
// StrictMode.enableDefaults()
startKoin {
androidContext(this@LEDMatrixControllerApplication)
modules(AppModule)
}
// Init to avoid slow reaction on first use
thread {
matrixController
}
}
}
private val AppModule = module {
factoryOf(::MatrixController)
single {
MqttClient(BuildConfig.MQTT_URL, "LED-Matrix-Controller-Android", MemoryPersistence()).apply {
connect(get())
}
}
single {
MqttConnectionOptionsBuilder()
.username(BuildConfig.MQTT_USER)
.password(BuildConfig.MQTT_PASSWORD.toByteArray())
.build()
}
}

View File

@@ -3,41 +3,138 @@ package com.cameronc.dev.ledmatrixcontroller
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.cameronc.dev.ledmatrixcontroller.ui.theme.LEDMatrixControllerTheme import com.cameronc.dev.ledmatrixcontroller.ui.theme.LEDMatrixControllerTheme
import org.koin.android.ext.android.inject
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val weatherMatrixController: MatrixController by inject()
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
LEDMatrixControllerTheme { LEDMatrixControllerTheme {
// A surface container using the 'background' color from the theme // A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Greeting("Android") val keyboardController: SoftwareKeyboardController = LocalSoftwareKeyboardController.current!!
val focusManager = LocalFocusManager.current
Column(
Modifier
.background(
Brush.verticalGradient(
listOf(
Color.Black,
MaterialTheme.colorScheme.primary,
Color.Black
)
)
)
.wrapContentSize()
.padding(16.dp),
Arrangement.SpaceBetween,
Alignment.CenterHorizontally,
) {
MatrixModeButton(Modifier.padding(vertical = 8.dp), text = "Weather", onClick = {
weatherMatrixController.weatherMode()
})
MatrixModeButton(Modifier.padding(vertical = 8.dp), text = "Time", onClick = {
weatherMatrixController.timeMode()
})
MatrixModeButton(
Modifier.padding(vertical = 8.dp),
text = "Rainbow",
onClick = { weatherMatrixController.rainbow() })
MatrixModeButton(Modifier.padding(vertical = 8.dp), text = "Off", onClick = { weatherMatrixController.off() })
Row(modifier = Modifier.padding(vertical = 8.dp), horizontalArrangement = Arrangement.SpaceBetween) {
var brightness by remember { mutableStateOf(weatherMatrixController.brightness.value ?: "10") }
TextField(
modifier = Modifier.defaultMinSize(minWidth = 128.dp),
value = brightness,
label = { Text("Brightness") },
onValueChange = { brightness = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Send),
keyboardActions = KeyboardActions(onSend = {
weatherMatrixController.updateBrightness(brightness)
focusManager.clearFocus()
keyboardController.hide()
})
)
Button(
modifier = Modifier.padding(start = 16.dp),
onClick = {
weatherMatrixController.updateBrightness(brightness)
focusManager.clearFocus()
keyboardController.hide()
}) {
Text("Set")
}
}
}
} }
} }
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Greeting(name: String, modifier: Modifier = Modifier) { fun MatrixModeButton(modifier: Modifier = Modifier, text: String, onClick: () -> Unit = {}) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true) ElevatedCard(
@Composable modifier = modifier
fun GreetingPreview() { .defaultMinSize(minWidth = 128.dp, minHeight = 64.dp)
LEDMatrixControllerTheme { .height(IntrinsicSize.Min)
Greeting("Android") .width(IntrinsicSize.Max),
onClick = onClick,
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(text = text, fontSize = 18.sp)
}
} }
} }

View File

@@ -0,0 +1,79 @@
package com.cameronc.dev.ledmatrixcontroller
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import org.eclipse.paho.mqttv5.client.MqttClient
import org.eclipse.paho.mqttv5.client.MqttConnectionOptions
import org.eclipse.paho.mqttv5.common.MqttMessage
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class MatrixController(
private val mqttClient: MqttClient,
private val mqttConnectionOptions: MqttConnectionOptions
) {
companion object {
private const val MODE_TOPIC = "/weather/mode"
private val OFF = "0".toMqttMessage()
private val WEATHER = "2".toMqttMessage()
private val TIME = "3".toMqttMessage()
private val RAINBOW = "4".toMqttMessage()
private const val BRIGHTNESS = "/weather/brightness"
}
val brightness: LiveData<String> get() = brightnessLiveData
private val brightnessLiveData: MutableLiveData<String> = MutableLiveData()
private val threadPool: ExecutorService = Executors.newFixedThreadPool(1)
init {
/*
mqttClient.subscribe(
arrayOf("/weather/brightness"),
IntArray(1) { 1 },
arrayOf<IMqttMessageListener>(object : IMqttMessageListener {
override fun messageArrived(topic: String?, message: MqttMessage?) {
brightnessLiveData.value = message.toString()
}
})
)
*/
}
fun weatherMode() {
queueMessage(MODE_TOPIC, WEATHER)
}
private fun queueMessage(topic: String, message: MqttMessage) {
threadPool.execute {
if (mqttClient.isConnected) {
mqttClient.publish(topic, message)
} else {
mqttClient.connect(mqttConnectionOptions)
mqttClient.publish(topic, message)
}
}
}
fun timeMode() {
queueMessage(MODE_TOPIC, TIME)
}
fun off() {
queueMessage(MODE_TOPIC, OFF)
}
fun rainbow() {
queueMessage(MODE_TOPIC, RAINBOW)
}
fun updateBrightness(brightness: String) {
queueMessage(BRIGHTNESS, brightness.toMqttMessage())
}
}
fun String.toMqttMessage(): MqttMessage {
return MqttMessage(this.toByteArray())
}