Commit e7b61e6a authored by David's avatar David
Browse files

Works on java 11 and openjfx 11

parent 815d20ca
Pipeline #658 failed with stage
in 27 seconds
oracle64-1.8.0.221
openjdk64-11.0.9.1
......@@ -8,17 +8,23 @@ buildscript {
}
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.21'
id "io.spring.dependency-management" version "1.0.7.RELEASE"
id 'org.jetbrains.kotlin.jvm' version '1.4.21'
id "io.spring.dependency-management" version "1.0.7.RELEASE" //Bet I don't need this...
id 'com.github.johnrengelman.shadow' version '5.0.0'
id "org.hidetake.ssh" version "2.9.0"
id "application"
id 'org.openjfx.javafxplugin' version '0.0.9'
}
apply plugin: 'org.junit.platform.gradle.plugin'
mainClassName = "is.kow.deskscreen.DeskScreenKt"
javafx {
version = "11.0.2"
modules = [ 'javafx.controls', 'javafx.swing' ]
}
wrapper {
distributionType = Wrapper.DistributionType.ALL
}
......@@ -66,16 +72,16 @@ shadowJar {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8",
"org.jetbrains.kotlin:kotlin-reflect",
"no.tornado:tornadofx:1.7.16",
'io.reactivex.rxjava2:rxkotlin:2.2.0',
"no.tornado:tornadofx:1.7.20",
'io.reactivex.rxjava2:rxkotlin:2.4.0',
"com.github.thomasnield:rxkotlinfx:2.2.2",
'io.github.microutils:kotlin-logging:1.5.4',
"io.reactivex.rxjava2:rxjava:2.2.6", //have to include it explicitly
"io.reactivex.rxjava2:rxjava:2.2.20", //have to include it explicitly
"org.apache.logging.log4j:log4j-api",
"org.apache.logging.log4j:log4j-core",
"org.apache.logging.log4j:log4j-slf4j-impl",
"io.github.microutils:kotlin-logging:1.5.6",
"io.github.microutils:kotlin-logging:2.0.4",
'io.github.config4k:config4k:0.4.0'
//V4l4j for the raspberry pi, to get video directly from v4l
......@@ -92,9 +98,10 @@ dependencies {
//Some extra java time classes that are potentially useful
implementation 'org.threeten:threeten-extra:1.5.0'
//Gonna use the DarkSky forecast info
implementation 'tk.plogitech:darksky-forecast-api-jackson:1.3.1',
'org.slf4j:jul-to-slf4j:1.7.25'
// Fuckin apple stoleDark sky, so that's dead.
// //Gonna use the DarkSky forecast info
// implementation 'tk.plogitech:darksky-forecast-api-jackson:1.3.1',
// 'org.slf4j:jul-to-slf4j:1.7.25'
implementation group: "org.http4k", name: "http4k-client-apache", version: "3.38.1"
implementation group: "org.http4k", name: "http4k-format-jackson", version: "3.38.1"
......@@ -108,12 +115,14 @@ dependencies {
// 'org.glassfish.jaxb:jaxb-runtime:2.3.0.1'
//Using the snapshot version of pi4j, because it's the best
implementation 'com.pi4j:pi4j-core:1.2-SNAPSHOT'
//Maybe I can get away without using the snapshot....
implementation 'com.pi4j:pi4j-core:1.2'
//Trying ikonili, because FontawesomeFX is being a pain
implementation 'org.kordamp.ikonli:ikonli-javafx:2.4.0'
implementation 'org.kordamp.ikonli:ikonli-fontawesome5-pack:2.4.0'
implementation 'org.kordamp.ikonli:ikonli-weathericons-pack:2.4.0'
//TODO: update this to use a version variable
implementation 'org.kordamp.ikonli:ikonli-javafx:12.0.0'
implementation 'org.kordamp.ikonli:ikonli-fontawesome5-pack:12.0.0'
implementation 'org.kordamp.ikonli:ikonli-weathericons-pack:12.0.0'
// implementation 'com.hierynomus:sshj:0.26.0',
// 'com.jcraft:jzlib:1.1.3'
......@@ -156,6 +165,8 @@ task deploy {
}
task runDesktop(type: JavaExec) {
group = "Application"
description = "Run it on the desktop lol"
classpath = sourceSets.main.runtimeClasspath
environment += [
......@@ -167,6 +178,7 @@ task runDesktop(type: JavaExec) {
"REDDIT_CLIENT_ID": redditClientId,
"REDDIT_SECRET": redditSecret
]
//jvmArgs = [""]
main = "is.kow.deskscreen.DesktopScreen"
}
......@@ -8,6 +8,7 @@ import com.github.thomasnield.rxkotlinfx.observeOnFx
import com.hopding.jrpicam.RPiCamera
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import javafx.embed.swing.SwingFXUtils
import javafx.event.ActionEvent
import javafx.scene.image.Image
import mu.KotlinLogging
......@@ -76,8 +77,7 @@ class CameraController : Controller() {
?.setTimeout(timeout * 1000) //This timeout is in milliseconds
?.setFullPreviewOn()
?.takeBufferedStill()!!
//lets just convert it directly
Util.toFXImage(bf, null)
SwingFXUtils.toFXImage(bf, null)
} else {
//load the cat picture WHY WON'T YOU LOAD MY KITTY
val stream = this.javaClass.getResourceAsStream("/imagery/MaineCoon-cropped.jpeg")
......@@ -135,4 +135,4 @@ class CameraController : Controller() {
.subscribe()
}
}
\ No newline at end of file
}
package `is`.kow.deskscreen.utils
import javafx.scene.image.Image
import javafx.scene.image.PixelFormat
import javafx.scene.image.WritableImage
import sun.awt.image.IntegerComponentRaster
import java.awt.image.BufferedImage
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Path
......@@ -36,79 +31,4 @@ object Util {
fun md5File(path: Path): String {
return md5Stuff(Files.newInputStream(path))
}
/**
* Stolen from the JDK, because it's apparently not in the javafx on the rpi. Maybe different versions?
* Snapshots the specified [BufferedImage] and stores a copy of
* its pixels into a JavaFX [Image] object, creating a new
* object if needed.
* The returned `Image` will be a static snapshot of the state
* of the pixels in the `BufferedImage` at the time the method
* completes. Further changes to the `BufferedImage` will not
* be reflected in the `Image`.
*
*
* The optional JavaFX [WritableImage] parameter may be reused
* to store the copy of the pixels.
* A new `Image` will be created if the supplied object is null,
* is too small or of a type which the image pixels cannot be easily
* converted into.
*
* @param bimg the `BufferedImage` object to be converted
* @param wimg an optional `WritableImage` object that can be
* used to store the returned pixel data
* @return an `Image` object representing a snapshot of the
* current pixels in the `BufferedImage`.
* @since JavaFX 2.2
*/
fun toFXImage(bimg: BufferedImage, wimg: WritableImage?): WritableImage {
var bimg = bimg
var wimg = wimg
val bw = bimg.width
val bh = bimg.height
when (bimg.type) {
BufferedImage.TYPE_INT_ARGB, BufferedImage.TYPE_INT_ARGB_PRE -> {
}
else -> {
val converted = BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE)
val g2d = converted.createGraphics()
g2d.drawImage(bimg, 0, 0, null)
g2d.dispose()
bimg = converted
}
}
// assert(bimg.getType == TYPE_INT_ARGB[_PRE]);
if (wimg != null) {
val iw = wimg.width.toInt()
val ih = wimg.height.toInt()
if (iw < bw || ih < bh) {
wimg = null
} else if (bw < iw || bh < ih) {
val empty = IntArray(iw)
val pw = wimg.pixelWriter
val pf = PixelFormat.getIntArgbPreInstance()
if (bw < iw) {
pw.setPixels(bw, 0, iw - bw, bh, pf, empty, 0, 0)
}
if (bh < ih) {
pw.setPixels(0, bh, iw, ih - bh, pf, empty, 0, 0)
}
}
}
if (wimg == null) {
wimg = WritableImage(bw, bh)
}
val pw = wimg.pixelWriter
val icr = bimg.raster as IntegerComponentRaster
val data = icr.dataStorage
val offset = icr.getDataOffset(0)
val scan = icr.scanlineStride
val pf = if (bimg.isAlphaPremultiplied)
PixelFormat.getIntArgbPreInstance()
else
PixelFormat.getIntArgbInstance()
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan)
return wimg
}
}
\ No newline at end of file
}
......@@ -130,7 +130,7 @@ class TimeTempView : View() {
.observeOnFx()
.doOnNext {
//Update the label (or the model?!?)
model.temp.value = it
model.temp.value = it.toLong()
}
.subscribe()
ticks.connect()
......@@ -139,4 +139,4 @@ class TimeTempView : View() {
private fun readTemp(): Int {
return temperatureFile.readText().trim().toInt()
}
}
\ No newline at end of file
}
package `is`.kow.deskscreen.wx
import `is`.kow.deskscreen.TransitionsController
import com.github.thomasnield.rxkotlinfx.events
import com.github.thomasnield.rxkotlinfx.toBinding
import com.github.thomasnield.rxkotlinfx.toObservable
import javafx.beans.property.*
import javafx.geometry.Pos
import javafx.scene.layout.Priority
import javafx.util.StringConverter
import mu.KotlinLogging
import org.kordamp.ikonli.javafx.FontIcon
import org.kordamp.ikonli.weathericons.WeatherIcons
import tornadofx.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
/*
Add more METAR fields to this, to display the current weather outside.
*/
class DarkSkyCurrentConditions(
time: LocalDateTime = LocalDateTime.now(),
temp: Double = 0.0) {
val timeProperty = SimpleObjectProperty(this, "time", time)
val tempProperty = SimpleDoubleProperty(this, "temp", temp)
val feelsLikeTempProperty = SimpleDoubleProperty(this, "feelsLikeTemp", 0.0)
val lowTempProperty = SimpleDoubleProperty(this, "lowTemp", 0.0)
val highTempProperty = SimpleDoubleProperty(this, "highTemp", 0.0)
val popsProperty = SimpleDoubleProperty(this, "pops", 0.0)
val precipitationIntensityProperty = SimpleDoubleProperty(this, "precipitationIntensity", 0.0)
val ikonProperty = SimpleObjectProperty(this, "ikon", WeatherIcons.NA)
val pressureProperty = SimpleDoubleProperty(this, "pressure", 0.0)
val windDirectionProperty = SimpleIntegerProperty(this, "windDirection", 0)
val windSpeedProperty = SimpleDoubleProperty(this, "windSpeed", 0.0)
val windGustProperty = SimpleDoubleProperty(this, "windGust", 0.0)
val humidityProperty = SimpleIntegerProperty(this, "humidity", 0)
val stormDistanceProperty = SimpleDoubleProperty(this, "stormDistance", 0.0)
val stormBearingProperty = SimpleDoubleProperty(this, "stormBearing", 0.0)
val problemProperty = SimpleStringProperty(this, "problem", null)
val hasProblemProperty = SimpleBooleanProperty(this, "hasProblemProperty", false)
val precipTypeProperty = SimpleStringProperty(this, "precipTypeProperty", "")
}
class DarkSkyCurrentConditionsModel : ItemViewModel<DarkSkyCurrentConditions>() {
val time = bind(DarkSkyCurrentConditions::timeProperty)
val temp = bind(DarkSkyCurrentConditions::tempProperty)
val feelsLikeTemp = bind(DarkSkyCurrentConditions::feelsLikeTempProperty)
val lowTemp = bind(DarkSkyCurrentConditions::lowTempProperty)
val highTemp = bind(DarkSkyCurrentConditions::highTempProperty)
val pops = bind(DarkSkyCurrentConditions::popsProperty)
val precipitationIntensity = bind(DarkSkyCurrentConditions::precipitationIntensityProperty)
val ikon = bind(DarkSkyCurrentConditions::ikonProperty)
val pressure = bind(DarkSkyCurrentConditions::pressureProperty)
val windDirection = bind(DarkSkyCurrentConditions::windDirectionProperty)
val windSpeed = bind(DarkSkyCurrentConditions::windSpeedProperty)
val windGust = bind(DarkSkyCurrentConditions::windGustProperty)
val humidity = bind(DarkSkyCurrentConditions::humidityProperty)
val stormDistance = bind(DarkSkyCurrentConditions::stormDistanceProperty)
val stormBearing = bind(DarkSkyCurrentConditions::stormBearingProperty)
val problem = bind(DarkSkyCurrentConditions::problemProperty)
val hasProblem = bind(DarkSkyCurrentConditions::hasProblemProperty)
val precipType = bind(DarkSkyCurrentConditions::precipTypeProperty)
}
class WxTimeConverter : StringConverter<LocalDateTime>() {
val formatter = DateTimeFormatter.ofPattern("HH:mm")
override fun toString(time: LocalDateTime?): String {
if (time != null) {
return time.format(formatter)
} else {
return ""
}
}
override fun fromString(string: String?): LocalDateTime {
return LocalDateTime.now()
}
}
class RoundedConverter : StringConverter<Number>() {
override fun toString(temp: Number?): String {
return if (temp != null) {
"%d".format(Math.round(temp.toDouble()))
} else {
""
}
}
override fun fromString(string: String?): Number {
return if (string != null) {
string.toDouble()
} else {
0.0
}
}
}
class WxDateConverter : StringConverter<LocalDateTime>() {
val formatter = DateTimeFormatter.ofPattern("M/dd/yy")
override fun toString(time: LocalDateTime?): String {
if (time != null) {
return time.format(formatter)
} else {
return ""
}
}
override fun fromString(string: String?): LocalDateTime {
return LocalDateTime.now()
}
}
class DarkSkyCurrentConditionsView : View() {
private val logger = KotlinLogging.logger {}
private val weatherController: WeatherController by inject()
private val transitionsController: TransitionsController by inject()
private val modelDarkSky: DarkSkyCurrentConditionsModel by inject()
override val root = borderpane {
style {
fontSize = 14.px
}
top = label("Current Conditions")
center = borderpane {
top = borderpane {
//Updated at and Barometric Pressure
hgrow = Priority.ALWAYS
left = vbox {
label {
this.bind(modelDarkSky.time, converter = WxDateConverter())
alignment = Pos.BASELINE_CENTER
hgrow = Priority.ALWAYS
useMaxWidth = true
}
label {
this.bind(modelDarkSky.time, converter = WxTimeConverter())
alignment = Pos.BASELINE_CENTER
hgrow = Priority.ALWAYS
useMaxWidth = true
}
}
right = hbox {
alignment = Pos.BASELINE_RIGHT
label {
bind(modelDarkSky.pressure)
}
label("in")
}
}
left = vbox {
//Probability of Precipitation and Intensity
vgrow = Priority.ALWAYS
alignment = Pos.CENTER
hbox {
alignment = Pos.CENTER
label {
bind(modelDarkSky.pops)
}
label {
text = "%"
}
}
hbox {
label {
bind(modelDarkSky.precipitationIntensity)
}
label("in/hr")
}
}
right = vbox {
//Wind Direction and Wind Speed
vgrow = Priority.ALWAYS
alignment = Pos.CENTER
hbox {
alignment = Pos.CENTER
label {
graphic = cache {
val icon = FontIcon.of(WeatherIcons.DIRECTION_DOWN)
icon.iconSize = 22
icon
}
rotateProperty().bind(modelDarkSky.windDirection)
}
}
hbox {
alignment = Pos.BASELINE_CENTER
label {
bind(modelDarkSky.windSpeed)
}
label("mph")
}
hbox {
alignment = Pos.BASELINE_CENTER
label {
bind(modelDarkSky.humidity)
}
label {
graphic = cache {
val icon = FontIcon.of(WeatherIcons.HUMIDITY)
icon.iconSize = 14
icon
}
}
}
}
center = borderpane {
//TODO: also needs to be touch evented -- Handled via mouse emulation, it'll be fine
weatherController.handleDisplayRadar(this.events(javafx.scene.input.MouseEvent.MOUSE_RELEASED))
style {
fontSize = 16.px
padding = box(3.px)
}
center = label {
//Weather Icon!
val convertToIcon = modelDarkSky.ikon.toObservable()
.map {
val icon = FontIcon.of(it)
icon.iconSize = 70
icon
}
graphicProperty().bind(convertToIcon.toBinding())
}
bottom = hbox {
//Current temp, and low/high
useMaxWidth = true
hgrow = Priority.ALWAYS
alignment = Pos.BASELINE_CENTER
vbox {
hbox {
alignment = Pos.BASELINE_CENTER
label {
bind(modelDarkSky.temp)
}
label {
graphic = cache {
val icon = FontIcon.of(WeatherIcons.FAHRENHEIT)
icon.iconSize = 18
icon
}
}
}
hbox {
alignment = Pos.BASELINE_CENTER
style {
fontSize = 14.px
}
label {
bind(modelDarkSky.lowTemp, converter = RoundedConverter())
}
label("/")
label {
bind(modelDarkSky.highTemp, converter = RoundedConverter())
}
}
}
}
}
}
}
override fun onDock() {
logger.debug("Current Conditions Polling started")
weatherController.darkSkyStream()
}
}
\ No newline at end of file
......@@ -20,12 +20,6 @@ import org.http4k.client.ApacheClient
import org.http4k.core.Method
import org.http4k.core.Request
import org.kordamp.ikonli.weathericons.WeatherIcons
import tk.plogitech.darksky.api.jackson.DarkSkyJacksonClient
import tk.plogitech.darksky.forecast.APIKey
import tk.plogitech.darksky.forecast.ForecastRequestBuilder
import tk.plogitech.darksky.forecast.GeoCoordinates
import tk.plogitech.darksky.forecast.model.Latitude
import tk.plogitech.darksky.forecast.model.Longitude
import tornadofx.*
import java.net.URI
import java.nio.file.Files
......@@ -46,7 +40,6 @@ class WeatherController : Controller() {
private val displayController: DisplayController by inject()
private val transitionsController: TransitionsController by inject()
private val darkSkyCurrentConditionsModel: DarkSkyCurrentConditionsModel by inject()
private val radarModel: RadarModel by inject()
private val radarDisplayedSubject = PublishSubject.create<Boolean>()
......@@ -399,92 +392,6 @@ class WeatherController : Controller() {
}
fun darkSkyStream() {
val config = mainController.appConfig
val darkSkyJacksonClient = DarkSkyJacksonClient()
val timeZoneId = ZoneId.of("America/Chicago")
//Use a switchMap to determine if we output intervals, or if we don't
displayController.displayStateObservable
.switchMap {
if (it) {
logger.debug("Display is on!")
Observable.interval(0, 30, TimeUnit.MINUTES)
} else {
logger.debug("display is not on!")
Observable.empty()
}
}
.subscribeOn(Schedulers.io())
.map {
//A request only for the KDAL station
val request = ForecastRequestBuilder()
.key(APIKey(config.getString("weather.darkSkyApiKey")))
.units(ForecastRequestBuilder.Units.us)
.location(GeoCoordinates(Longitude(config.getDouble("weather.longitude")), Latitude(config.getDouble("weather.latitude")))).build()
darkSkyJacksonClient.forecast(request)
}
//TODO: handle an error state
.doOnNext {
logger.debug("Forecast: ${it}")
}
.retryWhen {
it
.observeOnFx()
.doOnError { fail ->
//Update the model to indicate a prolem
darkSkyCurrentConditionsModel.problem.value = fail.message
darkSkyCurrentConditionsModel.hasProblem.value = true
}
.observeOn(Schedulers.io())
.flatMap {
Observable.timer(config.getLong("weather.retryDelaySeconds"), TimeUnit.SECONDS)
}
}
.observeOnFx()
.doOnNext { fcst ->
//No problem detected!
darkSkyCurrentConditionsModel.problem.value = null
darkSkyCurrentConditionsModel.hasProblem.value = false
//Updoot the model
val model = darkSkyCurrentConditionsModel
val currently = fcst.currently
model.temp.value = currently.temperature
model.feelsLikeTemp.value = currently.apparentTemperature
model.time.value = LocalDateTime.ofInstant(fcst.currently.time, timeZoneId)
model.pressure.value = currently.pressure * 0.0295301 //Converted to Inches
model.windDirection.value = currently.windBearing
model.windSpeed.value = currently.windSpeed
//No wind gust in currently
model.humidity.value = currently.humidity * 100