Skip to content

Commit

Permalink
Move a lot of stuff around.
Browse files Browse the repository at this point in the history
More deck-chair shuffling, as well as a failed attempt to use a stepper as a lift driver without limit switches (and noisy af, as well).
  • Loading branch information
EAGrahamJr committed Mar 13, 2024
1 parent cfb2dff commit 70477e2
Show file tree
Hide file tree
Showing 9 changed files with 434 additions and 218 deletions.
54 changes: 29 additions & 25 deletions armthing/src/main/kotlin/crackers/kobots/app/Thingie.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,29 @@ import crackers.kobots.app.AppCommon.executor
import crackers.kobots.app.arm.ArmMonitor
import crackers.kobots.app.arm.ManualController
import crackers.kobots.app.enviro.DieAufseherin
import crackers.kobots.app.enviro.DisplayDos
import crackers.kobots.devices.expander.CRICKITHat
import crackers.kobots.devices.expander.I2CMultiplexer
import org.slf4j.LoggerFactory
import kotlin.concurrent.thread
import kotlin.system.exitProcess

private val crickitDelegate = lazy { CRICKITHat() }
private val muxDelegate = lazy { I2CMultiplexer() }

// shared devices
internal val crickitHat by lazy { CRICKITHat() }
internal val crickitHat by crickitDelegate
internal val multiplexor by muxDelegate

private val logger = LoggerFactory.getLogger("BRAINZ")

internal interface Startable {
fun start()
fun stop()
}

private val startables = listOf(ArmMonitor, DisplayDos, ManualController, DieAufseherin)

/**
* Run this.
*/
Expand All @@ -38,33 +52,23 @@ fun main(args: Array<String>? = null) {
// NOTE: this requires a diozero daemon running on the remote pi and the diozero remote jar in the classpath
if (args?.isNotEmpty() == true) System.setProperty(REMOTE_PI, args[0])

// these do not require the CRICKIT
ArmMonitor.start()
// Segmenter.start()
ManualController.start()

crickitHat.use {
// start all the things that require the CRICKIT
// TheArm.start()
DieAufseherin.start()
// RosetteStatus.start()
// start all the things to be started
startables.forEach { it.start() }

AppCommon.awaitTermination()
logger.warn("Exiting")
// always "home" the Arm
// TheArm.request(homeSequence)
// and then we wait and stop
Runtime.getRuntime().addShutdownHook(thread(start = false) { stopAll() })
AppCommon.awaitTermination()
logger.warn("Exiting")

// stop all the things using the crickit
// RosetteStatus.stop()
// TheArm.stop()
DieAufseherin.stop()
}
ManualController.stop()
// Segmenter.stop()
ArmMonitor.stop()

logger.warn("Waiting 5 seconds for all background processes to clear...")
executor.shutdownNow()
stopAll()
logger.warn("Shutdown")
exitProcess(0)
}

private fun stopAll() {
startables.forEach { AppCommon.ignoreErrors { it::stop } }

if (muxDelegate.isInitialized()) multiplexor.close()
if (crickitDelegate.isInitialized()) crickitHat.close()
}
29 changes: 20 additions & 9 deletions armthing/src/main/kotlin/crackers/kobots/app/arm/ArmMonitor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@

package crackers.kobots.app.arm

import com.diozero.api.I2CDevice
import com.diozero.devices.oled.SH1106
import com.diozero.devices.oled.SSD1306
import com.diozero.devices.oled.SsdOledCommunicationChannel.I2cCommunicationChannel
import crackers.kobots.app.AppCommon
import crackers.kobots.app.AppCommon.whileRunning
import crackers.kobots.app.Startable
import crackers.kobots.app.enviro.DieAufseherin
import crackers.kobots.app.multiplexor
import crackers.kobots.graphics.animation.*
import crackers.kobots.parts.app.KobotSleep
import crackers.kobots.parts.app.io.StatusColumnDelegate
import crackers.kobots.parts.app.io.StatusColumnDisplay
import crackers.kobots.parts.app.io.graphics.*
import crackers.kobots.parts.elapsed
import crackers.kobots.parts.scheduleWithFixedDelay
import org.slf4j.LoggerFactory
Expand All @@ -47,7 +47,7 @@ private const val MAX_HT = 32
/**
* Shows where the arm is on a timed basis.
*/
object ArmMonitor : StatusColumnDisplay by StatusColumnDelegate(MAX_WD, MAX_HT), KobotRadar by SimpleRadar(
object ArmMonitor : Startable, StatusColumnDisplay by StatusColumnDelegate(MAX_WD, MAX_HT), KobotRadar by SimpleRadar(
Point(0, 0),
MAX_HT.toDouble()
) {
Expand Down Expand Up @@ -76,8 +76,9 @@ object ArmMonitor : StatusColumnDisplay by StatusColumnDelegate(MAX_WD, MAX_HT),
}

private val screen by lazy {
val i2CDevice = I2CDevice(1, SSD1306.DEFAULT_I2C_ADDRESS)
SH1106(I2cCommunicationChannel(i2CDevice)).apply { setContrast(0x20.toByte()) }
// val i2CDevice = I2CDevice(1, SSD1306.DEFAULT_I2C_ADDRESS)
val i2CDevice = multiplexor.getI2CDevice(0, SH1106.DEFAULT_I2C_ADDRESS)
SH1106(I2cCommunicationChannel(i2CDevice)).apply { setContrast(0f) }
}


Expand All @@ -94,7 +95,7 @@ object ArmMonitor : StatusColumnDisplay by StatusColumnDelegate(MAX_WD, MAX_HT),
pupilPosition = Pupil.Position.LEFT + Pupil.Position.CENTER
)

fun start() {
override fun start() {
screenOnAt = Instant.now()
var screenOn = true
future = AppCommon.executor.scheduleWithFixedDelay(10.milliseconds, 10.milliseconds) {
Expand All @@ -103,15 +104,23 @@ object ArmMonitor : StatusColumnDisplay by StatusColumnDelegate(MAX_WD, MAX_HT),
var drawStats = false
val screenDirty = when (DieAufseherin.currentMode) {
DieAufseherin.SystemMode.IDLE -> {
LiftStatus.sleep()
val random = (CannedExpressions.entries - CannedExpressions.CLOSED).random().expression
showEyes(random)
}

DieAufseherin.SystemMode.IN_MOTION -> TODO()
DieAufseherin.SystemMode.IN_MOTION -> {
drawStats = true
statsGraphics.displayStatuses(TheArm.state.position.mapped())
// LiftStatus.update(TheArm.elevator.current())
true
}

DieAufseherin.SystemMode.MANUAL -> {
// screen is always dirty amd enabled
screenOnAt = Instant.now()
statsGraphics.displayStatuses(ManualController.statuses())
// LiftStatus.update(TheArm.elevator.current())
showEyes(LOOK_LEFT)
drawStats = true
true
Expand Down Expand Up @@ -200,7 +209,9 @@ object ArmMonitor : StatusColumnDisplay by StatusColumnDelegate(MAX_WD, MAX_HT),
screen.display(screenImage)
}

fun stop() {
override fun stop() {
// LiftStatus.close()

if (::future.isInitialized) future.cancel(true)
screen.close()
}
Expand Down
87 changes: 87 additions & 0 deletions armthing/src/main/kotlin/crackers/kobots/app/arm/LiftStatus.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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.app.arm

import com.diozero.devices.oled.MonochromeSsdOled
import com.diozero.devices.oled.SH1106
import com.diozero.devices.oled.SSD1306
import com.diozero.devices.oled.SsdOledCommunicationChannel
import crackers.kobots.app.multiplexor
import crackers.kobots.graphics.toRadians
import crackers.kobots.graphics.widgets.VerticalPercentageIndicator
import java.awt.Font
import java.awt.Graphics2D
import java.awt.geom.AffineTransform
import java.awt.image.BufferedImage

/**
* Displays the lift status (0-100 percent) on a rotated 132x32 display.
*/
object LiftStatus {
private val DISPLAY_HEIGHT = MonochromeSsdOled.Height.SHORT.lines

private val screen: SSD1306 by lazy {
val i2CDevice = multiplexor.getI2CDevice(2, SH1106.DEFAULT_I2C_ADDRESS)
val channel = SsdOledCommunicationChannel.I2cCommunicationChannel(i2CDevice)
SSD1306(channel, MonochromeSsdOled.Height.SHORT).apply { setContrast(0f) }
}
private var ready = false
private var screenOn = false

// set up the vertical thingie and then rotate it to display
val rotatedGraphics: Graphics2D
val rotatedImage =
BufferedImage(MonochromeSsdOled.DEFAULT_WIDTH, DISPLAY_HEIGHT, BufferedImage.TYPE_BYTE_BINARY).also {
rotatedGraphics = (it.graphics as Graphics2D).apply {
val theRotation = AffineTransform().apply {
rotate(-90.toRadians())
translate(-DISPLAY_HEIGHT.toDouble(), 0.0)
}
transform(theRotation)
}
}
val vpd = VerticalPercentageIndicator(
rotatedGraphics,
Font(Font.SANS_SERIF, Font.BOLD, 10),
MonochromeSsdOled.DEFAULT_WIDTH,
label = "Lift"
)

fun update(percent: Int) {
if (!ready) {
vpd.drawStatic()
ready = true
}
if (!screenOn) {
screen.setDisplayOn(true)
screenOn = true
}
vpd.updateValue(percent)
screen.display(rotatedImage)
}

fun sleep() {
screenOn = false
screen.setDisplayOn(false)
screen.clear()
}

fun close() {
sleep()
screen.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,37 @@ package crackers.kobots.app.arm

import crackers.kobots.app.AppCommon
import crackers.kobots.app.AppCommon.whileRunning
import crackers.kobots.app.Startable
import crackers.kobots.app.enviro.DieAufseherin
import crackers.kobots.app.enviro.DieAufseherin.GripperActions
import crackers.kobots.app.enviro.DieAufseherin.SystemMode
import crackers.kobots.app.enviro.VeryDumbThermometer.thermoStepper
import crackers.kobots.app.multiplexor
import crackers.kobots.devices.io.GamepadQT
import crackers.kobots.parts.scheduleWithFixedDelay
import org.slf4j.LoggerFactory
import java.util.concurrent.Future
import kotlin.time.Duration.Companion.milliseconds

/**
* TODO fill this in
*/
object ManualController {
private val gamepad by lazy { GamepadQT() }
object ManualController : Startable {
private lateinit var gamepad: GamepadQT
private var gpZeroX: Float? = null
private var gpZeroY: Float? = null
private lateinit var joyRideTFuture: Future<*>
private var wasPressed = false

fun start() {
override fun start() {
try {
gamepad = GamepadQT(multiplexor.getI2CDevice(7, GamepadQT.DEFAULT_I2C_ADDRESS))
} catch (t: Throwable) {
LoggerFactory.getLogger("ManualController").error("Unable to init manual controller", t)
return
}

joyRideTFuture = AppCommon.executor.scheduleWithFixedDelay(20.milliseconds, 20.milliseconds) {
// looking for button _release_
whileRunning {
val manualMode = DieAufseherin.currentMode == SystemMode.MANUAL

Expand All @@ -57,9 +66,11 @@ object ManualController {
}
}

fun stop() {
joyRideTFuture.cancel(true)
gamepad.close()
override fun stop() {
if (::gamepad.isInitialized) {
joyRideTFuture.cancel(true)
gamepad.close()
}
}

private var armSelected = false
Expand All @@ -69,55 +80,47 @@ object ManualController {
* Run a thing with the Gamepad
*/
private fun joyRide() {
// select button changes the "mode" - TODO right now it's binary
// select button changes the "mode"
selectDebounce = gamepad.selectButton.also {
if (!it && selectDebounce) armSelected = !armSelected
if (it) return
}
if (selectDebounce) return

if (armSelected) armThing()
else otherThing()
}

private fun otherThing() = with(gamepad) {
if (xButton) thermoStepper += 5
else if (bButton) thermoStepper -= 5
else if (yButton) thermoStepper.reset()
if (xButton) +thermoStepper
else if (bButton) -thermoStepper
else if (aButton) thermoStepper.reset()
}

private fun armThing() {
// with(TheArm) {
with(TheArm) {
// val xAxis = gamepad.xAxis
// if (gpZeroX == null) gpZeroX = xAxis
// else {
// val diff = gpZeroX!! - xAxis
// if (diff > 45f) waist -= 2
// if (diff < -45f) waist += 2
// }
// val yAxis = gamepad.yAxis
// if (gpZeroY == null) gpZeroY = yAxis
// else {
// val diff = gpZeroY!! - yAxis
// if (diff > 45f) {
// val current = elbow.current()
//// println ("elbow $current")
// elbow.rotateTo(current - 3)
// }
// if (diff < -45f) {
// val current = elbow.current()
//// println ("elbow $current")
// elbow.rotateTo(current + 3)
// }
// }
//
val yAxis = gamepad.yAxis
if (gpZeroY == null) gpZeroY = yAxis
else {
val diff = gpZeroY!! - yAxis
// if (diff > 45f) elevator.extendTo(100)
// if (diff < -45f) elevator.extendTo(0)
// elevator.release()
}
//
// if (gamepad.aButton) +extender
// if (gamepad.yButton) -extender
// if (gamepad.xButton) -gripper
// if (gamepad.bButton) +gripper
//
// updateCurrentState()
// }
}
}

fun statuses(): Map<String, Any> {
Expand Down
Loading

0 comments on commit 70477e2

Please sign in to comment.