Added basic MQTT functionality
This commit is contained in:
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -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
3
.idea/misc.xml
generated
@@ -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">
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user