Skip to content

Commit

Permalink
API cleanup (#3)
Browse files Browse the repository at this point in the history
* Removed unused internal messaging
* Cleaned up HomeAssistant lighting commands for effects-types
* Tweaked/cleaned up `Movements` for more consistency and removed "arbitrary" limit checks
* New "animation" for graphics to show an 8-ball and the classic 8-ball fortunes
  • Loading branch information
EAGrahamJr authored Sep 25, 2024
1 parent fb6e169 commit 4a54622
Show file tree
Hide file tree
Showing 21 changed files with 215 additions and 386 deletions.
22 changes: 0 additions & 22 deletions EventBus.md

This file was deleted.

Binary file removed EventBus.png
Binary file not shown.
32 changes: 0 additions & 32 deletions EventBus.puml

This file was deleted.

4 changes: 2 additions & 2 deletions Movements.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ Only a single actuator is currently defined:

## Sequence Executor

The [SequenceExecutor](src/main/kotlin/crackers/kobots/parts/SequenceExecutor.kt) provides one means of executing sequences and actions. It is primarily intended to be used with the [Event Bus](EventBus.md).
The [SequenceExecutor](src/main/kotlin/crackers/kobots/parts/SequenceExecutor.kt) provides one means of executing sequences and actions.

This class provides a means to execute sequences in a _background thread_. The actions are executed sequentially, invoking the _stopCheck_ functions prior to moving each component, as well as providing a mans of signalling an _immediate_ stop. The actions are executed in a way to provide pauses between invocations: this allows the physical systems to move incrementally, reducing stress (this speed can obviously be modified). The actual spped a step can execute will be limited by the hardware I/O.
This class provides functions to execute sequences in a _background thread_. The actions are executed sequentially, invoking the _stopCheck_ functions prior to moving each component, as well as providing a mans of signalling an _immediate_ stop. The actions are executed in a way to provide pauses between invocations: this allows the physical systems to move incrementally, reducing stress (this speed can obviously be modified). The actual spped a step can execute will be limited by the hardware I/O.

Implementations of this class **MUST** handle any device or I/O contention -- this class does not provide any "locking" of devices.

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ Contains basic application construction elements that are being used in my vario
- Generic application "junk"
- Abstractions to orchestrate physical device interactions
- Includes movements and the human stuff
- A simplified event-bus for in-process communication
- Wrappers around MQTT for external communications
- Wrappers around MQTT/HomeAssistant for external communications

There are three main sections.

- [Actuators and Movements](Movements.md)
- [Event Bus](EventBus.md)
- [Home Assistant](HomeAssistant.md)

:bangbang: The **EventBus** was removed due to non-usage: since most actions are now either constrainted to the `SequenceManager` in the movements or via HomeAssistant.

Javadocs are published at the [GitHub Pages](https://eagrahamjr.github.io/kobots-parts/) for this project.

## Acknowledgements
Expand Down
12 changes: 0 additions & 12 deletions src/main/kotlin/crackers/kobots/app/AppCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ package crackers.kobots.app
import com.typesafe.config.ConfigFactory
import crackers.hassk.HAssKClient
import crackers.kobots.mqtt.KobotsMQTT
import crackers.kobots.parts.app.KobotsEvent
import crackers.kobots.parts.app.publishToTopic
import org.slf4j.LoggerFactory
import java.net.InetAddress
import java.util.concurrent.CountDownLatch
Expand Down Expand Up @@ -114,16 +112,6 @@ object AppCommon {
KobotsMQTT(InetAddress.getLocalHost().hostName, applicationConfig.getString("mqtt.broker"))
}

/**
* Generic topic and event for sleep/wake events on the internal event bus.
*/
const val SLEEP_TOPIC = "System.Sleep"

class SleepEvent(val sleep: Boolean) : KobotsEvent

fun goToSleep() = publishToTopic(SLEEP_TOPIC, SleepEvent(true))
fun wakey() = publishToTopic(SLEEP_TOPIC, SleepEvent(false))

fun <F> ignoreErrors(executionBlock: () -> F?, logIt: Boolean = false): F? = try {
executionBlock()
} catch (t: Throwable) {
Expand Down
83 changes: 83 additions & 0 deletions src/main/kotlin/crackers/kobots/graphics/animation/EightBall.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2022-2024 by E. A. Graham, Jr.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package crackers.kobots.graphics.animation

import crackers.kobots.graphics.center
import crackers.kobots.graphics.loadImage
import crackers.kobots.graphics.middle
import java.awt.Color
import java.awt.Font
import java.awt.Graphics2D
import kotlin.math.roundToInt

/**
* 8-ball for small displays. Default size is for a "large" OLED (128x128).
*
* An image of the 8-ball is scaled to fit and the [next] function will print the fortune. Note that both will clear
* the designated region
*/
class EightBall(
private val graphics: Graphics2D,
private val x: Int = 0,
private val y: Int = 0,
private val width: Int = 128,
private val height: Int = 128
) {
private val eightBallFont: Font

// TODO use multi-line responses, line-breaks, and a bigger font!
private val eightBallList = listOf(
"It is certain", "Reply hazy, try again", "Don’t count on it", "Don’t count on it",
"Ask again later", "My reply is no", "Without a doubt", "Better not tell you now",
"My sources say no", "Yes definitely", "Cannot predict now", "Outlook not so good",
"You may rely on it", "Concentrate and ask again", "Very doubtful",
"As I see it, yes", "Most likely", "Outlook good", "Yes", "Signs point to yes"
)
.also { theList ->
var fitThis = ""
theList.forEach { if (it.length > fitThis.length) fitThis = it }
var fontSize = 18.0f
var font = Font(Font.SERIF, Font.PLAIN, fontSize.roundToInt())
while (font.size > 0f) {
if (graphics.getFontMetrics(font).stringWidth(fitThis) < width) break
fontSize = fontSize - .5f
font = font.deriveFont(fontSize)
}
if (font.size < 1f) throw IllegalStateException("Cannot get font size")
eightBallFont = font
}

private val eightBallImage by lazy { loadImage("/8-ball.png") }
private val imageX = (width - height) / 2

fun image() = with(graphics) {
clearRect(x, y, width, height)
drawImage(eightBallImage, imageX, 0, height, height, null)
}

fun next() = with(graphics) {
color = Color.WHITE
background = Color.BLACK
font = eightBallFont

clearRect(y, x, width, height)
val sayThis = eightBallList.random()
val textX = fontMetrics.center(sayThis, width)
val textY = fontMetrics.middle(height)
drawString(sayThis, textX + x, textY + y)
}
}
31 changes: 0 additions & 31 deletions src/main/kotlin/crackers/kobots/mqtt/KobotsMQTT.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*/
package crackers.kobots.mqtt

import crackers.kobots.parts.app.EmergencyStop
import crackers.kobots.parts.app.STOP_NOW
import org.eclipse.paho.mqttv5.client.*
import org.eclipse.paho.mqttv5.common.MqttException
import org.eclipse.paho.mqttv5.common.MqttMessage
Expand All @@ -31,7 +29,6 @@ import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.system.exitProcess

/**
* MQTT wrapper for Kobots.
Expand Down Expand Up @@ -282,29 +279,6 @@ class KobotsMQTT(private val clientName: String, broker: String) : AutoCloseable
operator fun set(topic: String, payload: String) = publish(topic, payload.toByteArray())
operator fun set(topic: String, payload: ByteArray) = publish(topic, payload)

/**
* Set up and allow [EmergencyStop] to forcibly terminate the application. This is intended to allow for a single
* "stop" to kill everything listening.
*
* **NOTE** This uses a separate topic from everything else and should be "private" to this function.
*/
fun allowEmergencyStop() {
subscribeJSON(KOBOTS_STOP) { event ->
if (event.optString("name") == STOP_NOW) {
logger.error("Emergency stop received")
close()
exitProcess(3)
}
}
}

/**
* Publish an [EmergencyStop] event. This should kill everything listening. See [allowEmergencyStop].
*/
fun emergencyStop() {
publish(KOBOTS_STOP, JSONObject(EmergencyStop()))
}

override fun close() {
mqttClient.close()
}
Expand All @@ -324,10 +298,5 @@ class KobotsMQTT(private val clientName: String, broker: String) : AutoCloseable
* Sequence events are published to this topic.
*/
const val KOBOTS_EVENTS = "kobots/events"

/**
* Emergency stops on a separate topic.
*/
private const val KOBOTS_STOP = "kobots/stop"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,47 @@
package crackers.kobots.mqtt.homeassistant

import com.diozero.api.PwmOutputDevice
import crackers.kobots.devices.set
import java.util.concurrent.CompletableFuture
import kotlin.math.roundToInt

/**
* Interface for controlling a light. This is used by [KobotLight] and [KobotLightStrip] to abstract the actual
* hardware implementation.
* Interface for controlling a light. This is used by [KobotLight] to abstract the actual hardware implementation.
*
* The [exec], [flash], and [transition] functions are specifically called out since they will _usually_ involve some
* background tasking to work with this system.
*/
interface LightController {
/**
* Set the state of the light. Node "0" represents either a single LED or a full LED strand. Non-zero indices are
* to address each LED individually, offset by 1.
* Set the state of the light.
*/
infix fun set(command: LightCommand)

/**
* Execute an effect in some sort of completable manner. Note this **must** be cancellable since commands can be
* compounded.
* Execute an effect in some sort of completable manner. Note this **must** be cancellable by any subsequent
* commands.
*/
infix fun exec(effect: String) {
// does nothing
}

/**
* Flash the light (on/off) using this period (seconds). Continues flashing until another command is received.
*
* **NOTE** HA is only currently sending either 10 or 2 to signal fast/slow.
*/
infix fun exec(effect: String): CompletableFuture<Void>
infix fun flash(flash: Int) {
// does nothing
}

/**
* Get the state of the light. Node "0" represents either a single LED or a full LED strand. Non-zero indices are
* to address each LED individually, offset by 1.
* Transition from the current state to a new state. Because this _can_ include color and brightness changes, the
* whole parsed command is necessary to complete this function.
*/
infix fun transition(command: LightCommand) {
// does nothing
}

/**
* Get the state of the light.
*/
fun current(): LightState

Expand All @@ -50,29 +67,20 @@ interface LightController {
}

/**
* Turns it on and off, adjust brightness. Supports a minimal set of effects.
* Turns it on and off, adjust brightness.
*
* TODO the effect should include (`n` is a duration)
*
* * `blink n` on/off; duration is how long the light stays in that state
* * `pulse n` gently fade in/out; duration is for a full cycle7
* TODO support a minimal set of effects? should support fade-in/out as HA settings?
*/
class BasicLightController(val device: PwmOutputDevice) : LightController {
private var currentEffect: String? = null
override val lightEffects: List<String>? = null

override fun set(command: LightCommand) = with(command) {
if (brightness != null) {
device.setValue(brightness / 100f)
} else {
device.set(state)
}
}

override fun exec(effect: String): CompletableFuture<Void> {
currentEffect = effect
return CompletableFuture.runAsync {
TODO("No effects yet")
// off over-rides everything
device.value = when {
!state -> 0f
brightness != null -> brightness / 100f
else -> 1f
}
}

Expand All @@ -81,5 +89,6 @@ class BasicLightController(val device: PwmOutputDevice) : LightController {
brightness = (device.value * 100f).roundToInt(),
effect = currentEffect
)

override val controllerIcon = "mdi:lamp"
}
Loading

0 comments on commit 4a54622

Please sign in to comment.