Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
deskscreen
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
1
Issues
1
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Packages & Registries
Packages & Registries
Package Registry
Container Registry
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
kowis-projects
deskscreen
Commits
79bb5f6d
Commit
79bb5f6d
authored
Apr 03, 2019
by
David
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6 from dkowis/camera
Camera!
parents
26c0e882
22a24091
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
467 additions
and
5 deletions
+467
-5
build.gradle
build.gradle
+11
-0
src/main/kotlin/is/kow/deskscreen/RootView.kt
src/main/kotlin/is/kow/deskscreen/RootView.kt
+3
-0
src/main/kotlin/is/kow/deskscreen/TransitionsController.kt
src/main/kotlin/is/kow/deskscreen/TransitionsController.kt
+8
-1
src/main/kotlin/is/kow/deskscreen/camera/CameraController.kt
src/main/kotlin/is/kow/deskscreen/camera/CameraController.kt
+138
-0
src/main/kotlin/is/kow/deskscreen/camera/CameraView.kt
src/main/kotlin/is/kow/deskscreen/camera/CameraView.kt
+203
-0
src/main/kotlin/is/kow/deskscreen/utils/Util.kt
src/main/kotlin/is/kow/deskscreen/utils/Util.kt
+80
-0
src/main/kotlin/is/kow/deskscreen/views/NamePlateView.kt
src/main/kotlin/is/kow/deskscreen/views/NamePlateView.kt
+18
-2
src/main/kotlin/is/kow/deskscreen/wx/WeatherController.kt
src/main/kotlin/is/kow/deskscreen/wx/WeatherController.kt
+6
-2
src/main/resources/imagery/MaineCoon-cropped.jpeg
src/main/resources/imagery/MaineCoon-cropped.jpeg
+0
-0
No files found.
build.gradle
View file @
79bb5f6d
...
...
@@ -35,6 +35,7 @@ repositories {
jcenter
()
maven
{
url
"https://dl.bintray.com/jerady/maven"
}
maven
{
url
"http://dl.bintray.com/jetbrains/spek"
}
maven
{
url
'https://jitpack.io'
}
maven
{
url
'https://oss.sonatype.org/content/repositories/snapshots/'
}
}
...
...
@@ -73,6 +74,16 @@ dependencies {
"io.github.microutils:kotlin-logging:1.5.6"
,
'io.github.config4k:config4k:0.4.0'
//V4l4j for the raspberry pi, to get video directly from v4l
//Seems to be reasonably effective. OpenCV would be cool, but it's icky.
// The other option is fork out to rpi commands directly
//This requires the bcm2835_v4l2 module to be loaded
// Need to figure out how to get this to build, it's a huge PITA
//implementation 'com.github.sarxos:v4l4j:0.9.1-r507'
implementation
'com.github.Hopding:JRPiCam:1.1.1'
//Gonna use the DarkSky forecast info
implementation
'tk.plogitech:darksky-forecast-api-jackson:1.3.1'
,
'org.slf4j:jul-to-slf4j:1.7.25'
...
...
src/main/kotlin/is/kow/deskscreen/RootView.kt
View file @
79bb5f6d
package
`is`.kow.deskscreen
import
`is`.kow.deskscreen.camera.CameraController
import
`is`.kow.deskscreen.views.DefaultView
import
javafx.scene.input.MouseEvent
import
javafx.scene.input.TouchEvent
...
...
@@ -11,6 +12,8 @@ class RootView : View() {
val
displayController
:
DisplayController
by
inject
()
val
transitionsController
:
TransitionsController
by
inject
()
val
cameraController
:
CameraController
by
inject
()
override
val
root
=
stackpane
{
transitionsController
.
setStartingView
(
find
(
DefaultView
::
class
))
...
...
src/main/kotlin/is/kow/deskscreen/TransitionsController.kt
View file @
79bb5f6d
package
`is`.kow.deskscreen
import
`is`.kow.deskscreen.
views.Default
View
import
`is`.kow.deskscreen.
camera.Camera
View
import
`is`.kow.deskscreen.system.SystemView
import
`is`.kow.deskscreen.views.DefaultView
import
`is`.kow.deskscreen.wx.LoadingView
import
`is`.kow.deskscreen.wx.RadarView
import
mu.KotlinLogging
...
...
@@ -20,6 +21,7 @@ class TransitionsController : Controller() {
private
val
loadingView
:
LoadingView
by
inject
()
private
val
radarView
:
RadarView
by
inject
()
private
val
infoView
:
SystemView
by
inject
()
private
val
cameraView
:
CameraView
by
inject
()
private
val
currentView
=
AtomicReference
<
View
>()
...
...
@@ -46,6 +48,11 @@ class TransitionsController : Controller() {
setView
(
infoView
)
}
fun
showCameraView
()
{
logger
.
debug
(
"Switching to camera view"
)
setView
(
cameraView
)
}
private
fun
setView
(
thing
:
View
)
{
//TODO: there's something wrong here, I haven't done it right...
currentView
.
getAndSet
(
thing
).
replaceWith
(
thing
)
...
...
src/main/kotlin/is/kow/deskscreen/camera/CameraController.kt
0 → 100644
View file @
79bb5f6d
package
`is`.kow.deskscreen.camera
import
`is`.kow.deskscreen.MainController
import
`is`.kow.deskscreen.TransitionsController
import
`is`.kow.deskscreen.utils.Util
import
com.github.thomasnield.rxkotlinfx.doOnNextFx
import
com.github.thomasnield.rxkotlinfx.observeOnFx
import
com.hopding.jrpicam.RPiCamera
import
io.reactivex.Observable
import
io.reactivex.schedulers.Schedulers
import
javafx.event.ActionEvent
import
javafx.scene.image.Image
import
mu.KotlinLogging
import
tornadofx.*
import
java.nio.file.Paths
import
java.util.concurrent.TimeUnit
class
CameraController
:
Controller
()
{
val
logger
=
KotlinLogging
.
logger
{}
private
val
camera
:
RPiCamera
?
private
val
mainController
:
MainController
by
inject
()
private
val
cameraStateModel
:
CameraStateModel
by
inject
()
private
val
transitionsController
:
TransitionsController
by
inject
()
init
{
//Only care that the folders exist
//TODO: add a cleanup mechanism that only keeps some number of pictures, or some amount of space or something
//Or just always use toBufferedStill
Paths
.
get
(
"/home/pi/Pictures"
).
toFile
().
mkdirs
()
camera
=
if
(
mainController
.
isPi
)
{
try
{
RPiCamera
(
"/home/pi/Pictures"
)
}
catch
(
e
:
Exception
)
{
//Crapopolis
logger
.
error
(
"Unable to initialize camera"
,
e
)
null
}
}
else
{
null
}
}
fun
takePicture
(
actionEvents
:
Observable
<
ActionEvent
>)
{
actionEvents
.
observeOn
(
Schedulers
.
io
())
.
doOnNextFx
{
//UPdate state
cameraStateModel
.
state
.
set
(
PictureTakingState
.
TAKING
)
}
.
map
{
val
timeout
=
5
//gimme a timer countdown
logger
.
debug
(
"Right before starting range timeout"
)
Observable
.
intervalRange
(
0
,
timeout
.
toLong
()
+
1
,
0
,
1
,
TimeUnit
.
SECONDS
)
.
map
{
timeout
-
it
}
.
observeOnFx
()
.
map
{
logger
.
debug
(
"COUNTING: $it"
)
cameraStateModel
.
countdownPercentage
.
value
=
it
/
timeout
.
toDouble
()
}
.
subscribe
()
//The camera blocks things. need to also start the countdown observable for the thing.
if
(
mainController
.
isPi
)
{
//Camera won't be null if it's pi!
val
bf
=
camera
?.
setRotation
(
270
)
?.
setDateTimeOn
()
?.
setTimeout
(
timeout
*
1000
)
//This timeout is in milliseconds
?.
setFullPreviewOn
()
?.
takeBufferedStill
()
!!
//lets just convert it directly
Util
.
toFXImage
(
bf
,
null
)
}
else
{
//load the cat picture WHY WON'T YOU LOAD MY KITTY
val
stream
=
this
.
javaClass
.
getResourceAsStream
(
"/imagery/MaineCoon-cropped.jpeg"
)
val
img
=
Image
(
stream
)
Thread
.
sleep
((
timeout
*
1000
).
toLong
())
img
}
}
.
doOnNextFx
{
logger
.
debug
(
"SETTING IMAGE IN THE MODEL AND CHANGING STATE"
)
cameraStateModel
.
image
.
set
(
it
)
cameraStateModel
.
state
.
set
(
PictureTakingState
.
PREVIEWING
)
}
.
doOnError
{
logger
.
error
(
"Error during taking picture!"
,
it
)
}
.
subscribe
()
}
fun
keepPicture
(
actionEvents
:
Observable
<
ActionEvent
>)
{
actionEvents
.
doOnNext
{
logger
.
error
(
"SHARE PICTURE OR SOMETHING!"
)
}
.
observeOnFx
()
.
map
{
cameraStateModel
.
image
.
value
=
null
cameraStateModel
.
state
.
set
(
PictureTakingState
.
READY
)
//TODO: would set state to sharing or something to display what to do with the picture
//Until then, just return them to the main frame
transitionsController
.
showDefaultView
()
}
.
subscribe
()
}
fun
rejectPicture
(
actionEvents
:
Observable
<
ActionEvent
>)
{
actionEvents
.
observeOnFx
()
.
map
{
cameraStateModel
.
image
.
value
=
null
cameraStateModel
.
state
.
set
(
PictureTakingState
.
READY
)
}
.
subscribe
()
}
fun
cancelPictureTaking
(
actionEvents
:
Observable
<
ActionEvent
>)
{
actionEvents
.
observeOnFx
()
.
doOnNext
{
cameraStateModel
.
image
.
value
=
null
cameraStateModel
.
state
.
set
(
PictureTakingState
.
READY
)
transitionsController
.
showDefaultView
()
}
.
subscribe
()
}
}
\ No newline at end of file
src/main/kotlin/is/kow/deskscreen/camera/CameraView.kt
0 → 100644
View file @
79bb5f6d
package
`is`.kow.deskscreen.camera
import
com.github.thomasnield.rxkotlinfx.actionEvents
import
com.github.thomasnield.rxkotlinfx.toObservableChanges
import
com.github.thomasnield.rxkotlinfx.toObservableChangesNonNull
import
javafx.beans.binding.Bindings
import
javafx.beans.binding.BooleanBinding
import
javafx.beans.property.SimpleDoubleProperty
import
javafx.beans.property.SimpleObjectProperty
import
javafx.geometry.Pos
import
javafx.scene.control.ProgressBar
import
javafx.scene.image.Image
import
javafx.scene.layout.Priority
import
mu.KotlinLogging
import
org.kordamp.ikonli.fontawesome5.FontAwesomeSolid
import
org.kordamp.ikonli.javafx.FontIcon
import
tornadofx.*
import
java.util.concurrent.atomic.AtomicReference
enum
class
PictureTakingState
{
READY
,
TAKING
,
PREVIEWING
,
GOOD
}
class
CameraState
{
val
imageProperty
=
SimpleObjectProperty
<
Image
>(
this
,
"image"
,
null
)
var
image
by
imageProperty
val
stateProperty
=
SimpleObjectProperty
<
PictureTakingState
>(
this
,
"state"
,
PictureTakingState
.
READY
)
var
state
by
stateProperty
val
countdownPercentageProperty
=
SimpleDoubleProperty
(
this
,
"countdown"
,
5.0
)
var
countdownPercentage
by
countdownPercentageProperty
}
class
CameraStateModel
:
ItemViewModel
<
CameraState
>()
{
private
val
logger
=
KotlinLogging
.
logger
{}
val
image
:
SimpleObjectProperty
<
Image
>
=
bind
(
CameraState
::
imageProperty
)
val
state
:
SimpleObjectProperty
<
PictureTakingState
>
=
bind
(
CameraState
::
stateProperty
)
val
countdownPercentage
=
bind
(
CameraState
::
countdownPercentageProperty
)
val
previewing
:
BooleanBinding
=
Bindings
.
equal
(
PictureTakingState
.
PREVIEWING
,
state
)
val
readyToTake
:
BooleanBinding
=
Bindings
.
equal
(
PictureTakingState
.
READY
,
state
)
val
taking
:
BooleanBinding
=
Bindings
.
equal
(
PictureTakingState
.
TAKING
,
state
)
}
class
CameraView
:
View
()
{
private
val
logger
=
KotlinLogging
.
logger
{}
private
val
cameraLoadingView
:
CameraLoadingView
by
inject
()
private
val
cameraImagePreviewView
:
CameraImagePreviewView
by
inject
()
private
val
cameraStateModel
:
CameraStateModel
by
inject
()
private
val
cameraController
:
CameraController
by
inject
()
private
val
currentView
=
AtomicReference
<
View
>()
init
{
//set up the state, it's weird that I even have to do this, the initial value doesn't work
cameraStateModel
.
state
.
set
(
PictureTakingState
.
READY
)
currentView
.
set
(
cameraLoadingView
)
}
override
fun
onDock
()
{
//Hook up an observable to the state to have it swap views when ready
cameraStateModel
.
state
.
toObservableChangesNonNull
()
.
map
{
change
->
val
to
=
change
.
newVal
to
}
.
map
{
when
(
it
)
{
PictureTakingState
.
PREVIEWING
->
currentView
.
getAndSet
(
cameraImagePreviewView
).
replaceWith
(
cameraImagePreviewView
)
else
->
currentView
.
getAndSet
(
cameraLoadingView
).
replaceWith
(
cameraLoadingView
)
}
}
.
subscribe
()
}
override
val
root
=
borderpane
{
//Left and right can be 160px max
center
=
currentView
.
get
().
root
right
=
vbox
{
prefWidth
=
160.0
hbox
{
alignment
=
Pos
.
CENTER
button
(
""
,
FontIcon
.
of
(
FontAwesomeSolid
.
CHECK_SQUARE
,
60
))
{
style
{
baseColor
=
c
(
"black"
)
backgroundColor
+=
c
(
"green"
)
}
enableWhen
{
cameraStateModel
.
previewing
}
cameraController
.
keepPicture
(
actionEvents
())
//Picture is good, enabled only after a capture
//Make it green
}
}
//Kinda want a space in-between these two
hbox
{
alignment
=
Pos
.
CENTER
style
{
paddingTop
=
10.0
}
button
(
"Take Picture!"
)
{
style
{
textFill
=
c
(
"black"
)
}
//Call the controller action that updates the model and starts the count down
enableWhen
(
cameraStateModel
.
readyToTake
)
cameraController
.
takePicture
(
actionEvents
())
}
}
label
(
"Delay to take picture"
)
hbox
{
hgrow
=
Priority
.
ALWAYS
progressbar
(
cameraStateModel
.
countdownPercentage
)
{
useMaxWidth
=
true
prefWidthProperty
().
bind
(
this
@hbox
.
widthProperty
())
}
}
}
left
=
borderpane
{
prefWidth
=
160.0
top
=
hbox
{
alignment
=
Pos
.
CENTER
button
(
""
,
FontIcon
.
of
(
FontAwesomeSolid
.
WINDOW_CLOSE
,
60
))
{
cameraController
.
rejectPicture
(
actionEvents
())
style
{
baseColor
=
c
(
"black"
)
backgroundColor
+=
c
(
"red"
)
}
//Picture is no good, don't keep it
enableWhen
{
cameraStateModel
.
previewing
}
}
}
bottom
=
hbox
{
alignment
=
Pos
.
CENTER
button
(
""
,
FontIcon
.
of
(
FontAwesomeSolid
.
EJECT
,
60
))
{
cameraController
.
cancelPictureTaking
(
actionEvents
())
style
{
baseColor
=
c
(
"black"
)
}
disableWhen
{
cameraStateModel
.
taking
}
}
}
}
}
}
class
CameraLoadingView
:
View
()
{
override
val
root
=
hbox
{
vgrow
=
Priority
.
ALWAYS
hgrow
=
Priority
.
ALWAYS
alignment
=
Pos
.
CENTER
progressindicator
{
progress
=
ProgressBar
.
INDETERMINATE_PROGRESS
}
}
}
class
CameraImagePreviewView
:
View
()
{
private
val
cameraStateModel
:
CameraStateModel
by
inject
()
private
val
logger
=
KotlinLogging
.
logger
{}
override
val
root
=
hbox
{
//Put teh image here
imageview
{
this
.
fitHeight
=
480.0
this
.
fitWidth
=
480.0
this
.
image
=
cameraStateModel
.
image
.
get
()
//Why doesn't this get triggered, because first time through it's a race condition
cameraStateModel
.
image
.
toObservableChanges
()
.
map
{
if
(
it
.
newVal
!=
null
)
{
this
.
image
=
it
.
newVal
}
else
{
//Can this even work?
logger
.
debug
(
"NO IMAGE"
)
this
.
image
=
null
}
}
.
subscribe
()
}
}
}
src/main/kotlin/is/kow/deskscreen/utils/Util.kt
View file @
79bb5f6d
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
...
...
@@ -31,4 +36,79 @@ 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
src/main/kotlin/is/kow/deskscreen/views/NamePlateView.kt
View file @
79bb5f6d
package
`is`.kow.deskscreen.views
import
`is`.kow.deskscreen.Styles
import
`is`.kow.deskscreen.TransitionsController
import
`is`.kow.deskscreen.temperature.TemperatureView
import
javafx.geometry.Pos
import
javafx.scene.paint.CycleMethod
import
javafx.scene.paint.LinearGradient
import
javafx.scene.paint.Stop
import
mu.KotlinLogging
import
org.kordamp.ikonli.fontawesome5.FontAwesomeSolid
import
org.kordamp.ikonli.javafx.FontIcon
import
tornadofx.*
//TODO: need to ensure a max width
class
NamePlateView
:
View
()
{
val
temperatureView
:
TemperatureView
by
inject
()
private
val
logger
=
KotlinLogging
.
logger
{}
private
val
temperatureView
:
TemperatureView
by
inject
()
private
val
transitionsController
:
TransitionsController
by
inject
()
override
val
root
=
borderpane
{
style
{
...
...
@@ -48,7 +54,17 @@ class NamePlateView : View() {
}
}
right
=
temperatureView
.
root
right
=
borderpane
{
left
=
button
(
""
,
FontIcon
.
of
(
FontAwesomeSolid
.
CAMERA
,
30
))
{
style
{
baseColor
=
c
(
"black"
)
}
action
{
transitionsController
.
showCameraView
()
}
}
right
=
temperatureView
.
root
}
bottom
=
hbox
{
style
{
...
...
src/main/kotlin/is/kow/deskscreen/wx/WeatherController.kt
View file @
79bb5f6d
...
...
@@ -151,7 +151,9 @@ class WeatherController : Controller() {
//TODO: emit to the ticker the error to display it on the screen
logger
.
error
(
"Failed to acquire station identifier!"
,
throwable
)
}
.
retry
()
.
retryWhen
{
Observable
.
timer
(
1
,
TimeUnit
.
SECONDS
)
}
.
subscribe
()
//Acquire the hourly forecast url too
...
...
@@ -176,7 +178,9 @@ class WeatherController : Controller() {
//TODO: emit to the ticker the error to display it on the screen
logger
.
error
(
"Failed to acquire hourly forecast url"
,
throwable
)
}
.
retry
()
//TODO: should have some kind of backoff or whatever
.
retryWhen
{
Observable
.
timer
(
1
,
TimeUnit
.
SECONDS
)
}
.
subscribe
()
//EMIT!
...
...
src/main/resources/imagery/MaineCoon-cropped.jpeg
0 → 100644
View file @
79bb5f6d
92.9 KB
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment