Compare commits
17 Commits
7575193024
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
f71c15b4d2 | ||
|
d3b5b7d9ba | ||
|
d26f92a0f1 | ||
|
5822328d9d | ||
|
59cb6d1380 | ||
|
149a6ddefe | ||
0a2b75f4b5 | |||
ca7ff8e154 | |||
7e36c36ec0 | |||
2a1925dba9 | |||
bd7af78cba | |||
|
6c6e405c92 | ||
|
99d0cb478a | ||
|
52dbdb0b65 | ||
|
137b342629 | ||
|
4eae02cd0c | ||
|
84d84b4313 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
/build/
|
/build/
|
||||||
|
/vendor/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
21
go.mod
21
go.mod
@@ -1,11 +1,20 @@
|
|||||||
module webserver
|
module webserver
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require github.com/eclipse/paho.mqtt.golang v1.4.2
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
git.blackfinn.de/apiservice/bicycle v0.0.2
|
||||||
golang.org/x/net v0.4.0 // indirect
|
git.blackfinn.de/apiservice/sauna v1.1.0
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/eclipse/paho.mqtt.golang v1.4.3 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
|
github.com/influxdata/influxdb-client-go/v2 v2.13.0 // indirect
|
||||||
|
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect
|
||||||
|
github.com/oapi-codegen/runtime v1.0.0 // indirect
|
||||||
|
golang.org/x/net v0.22.0 // indirect
|
||||||
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
47
go.sum
47
go.sum
@@ -1,13 +1,34 @@
|
|||||||
github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4=
|
git.blackfinn.de/apiservice/bicycle v0.0.2 h1:q+DMnWzxa4ZWxwMf03OPhQyDGRii7l7YmrEKYLgwn2A=
|
||||||
github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
|
git.blackfinn.de/apiservice/bicycle v0.0.2/go.mod h1:ygL9Ax8JreS+taIsqNq0NNlmhGnrHE+v1NE8Solihls=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
git.blackfinn.de/apiservice/sauna v1.1.0 h1:F2qysKjOld5lwTT7VJCY1YIEz6rXtE8CLpEfkGmeM3o=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
git.blackfinn.de/apiservice/sauna v1.1.0/go.mod h1:yM5lSlCApQrrZ7a+Y8ZxzBkLV2Frx6k1cw219XeSEG4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
|
github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM=
|
||||||
|
github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4=
|
||||||
|
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU=
|
||||||
|
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||||
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
|
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
|
||||||
|
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||||
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
80
main.go
80
main.go
@@ -1,75 +1,22 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
|
|
||||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
bicycle "git.blackfinn.de/apiservice/bicycle"
|
||||||
|
sauna "git.blackfinn.de/apiservice/sauna"
|
||||||
)
|
)
|
||||||
|
|
||||||
type temperature struct {
|
|
||||||
Value float64 `json:"value"`
|
|
||||||
Unit string `json:"unit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logger log.Logger = *log.Default()
|
logger log.Logger = *log.Default()
|
||||||
|
|
||||||
sauna_mutex sync.Mutex
|
|
||||||
sauna_temperature = temperature{
|
|
||||||
Value: 0.0,
|
|
||||||
Unit: "°C",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
|
|
||||||
logger.Printf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
|
|
||||||
}
|
|
||||||
|
|
||||||
var saunaHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
|
|
||||||
log.Printf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
|
|
||||||
sauna_mutex.Lock()
|
|
||||||
err := json.Unmarshal(msg.Payload(), &sauna_temperature)
|
|
||||||
sauna_mutex.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
logger.Print(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
|
|
||||||
logger.Println("Connected")
|
|
||||||
}
|
|
||||||
|
|
||||||
var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
|
|
||||||
logger.Printf("Connect lost: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logger.SetPrefix("Homeservice: ")
|
logger.SetPrefix("Homeservice: ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func http_endpoint_sauna(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-type", "application/json; charset=utf-8;")
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
sauna_mutex.Lock()
|
|
||||||
data, err := json.Marshal(sauna_temperature)
|
|
||||||
sauna_mutex.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write(json.RawMessage(`{"error": "cannot marshal object to json"}`))
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write(data)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logger.Println("starting")
|
logger.Println("starting")
|
||||||
|
|
||||||
@@ -77,30 +24,13 @@ func main() {
|
|||||||
flag.StringVar(&webui_path, "d", "./build/webui", "Specify path to serve the web ui. Default is ./static")
|
flag.StringVar(&webui_path, "d", "./build/webui", "Specify path to serve the web ui. Default is ./static")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// MQTT connection
|
// Start apis
|
||||||
opts := mqtt.NewClientOptions()
|
bicycle.Start()
|
||||||
opts.AddBroker("tcp://nuc:1883")
|
sauna.Start()
|
||||||
opts.SetClientID("homeservice")
|
|
||||||
opts.SetDefaultPublishHandler(messagePubHandler)
|
|
||||||
opts.OnConnect = connectHandler
|
|
||||||
opts.OnConnectionLost = connectLostHandler
|
|
||||||
client := mqtt.NewClient(opts)
|
|
||||||
if token := client.Connect(); token.Wait() && token.Error() != nil {
|
|
||||||
panic(token.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// MQTT subscribtion
|
|
||||||
topic := "sauna/temperature"
|
|
||||||
token := client.Subscribe(topic, 1, saunaHandler)
|
|
||||||
token.Wait()
|
|
||||||
logger.Printf("Subscribed to topic %s", topic)
|
|
||||||
|
|
||||||
// API routes
|
|
||||||
// Serve files from static folder
|
// Serve files from static folder
|
||||||
http.Handle("/", http.FileServer(http.Dir(webui_path)))
|
http.Handle("/", http.FileServer(http.Dir(webui_path)))
|
||||||
|
|
||||||
http.HandleFunc("/sauna/sample", http_endpoint_sauna)
|
|
||||||
|
|
||||||
port := ":5000"
|
port := ":5000"
|
||||||
logger.Println("Server is running on port" + port)
|
logger.Println("Server is running on port" + port)
|
||||||
|
|
||||||
|
925
webui/package-lock.json
generated
925
webui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -32,5 +32,9 @@
|
|||||||
"vite": "^4.0.4",
|
"vite": "^4.0.4",
|
||||||
"vitest": "^0.25.3"
|
"vitest": "^0.25.3"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"hamburger-menu-svelte": "^2.2.0",
|
||||||
|
"uplot": "^1.6.30"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -141,175 +141,20 @@ textarea {
|
|||||||
margin-left: 2%;
|
margin-left: 2%;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #282929;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*logo placement*/
|
|
||||||
|
|
||||||
header figure {
|
|
||||||
float: left;
|
|
||||||
color: #b6b6b6;
|
|
||||||
}
|
|
||||||
|
|
||||||
header figure.logo {
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
top: -15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*Navigation*/
|
/*Navigation*/
|
||||||
|
|
||||||
.header {
|
header {
|
||||||
background-color: #282929;
|
height: 40px;
|
||||||
/*position: fixed;*/
|
display: flex;
|
||||||
width: 100%;
|
align-items: center;
|
||||||
z-index: 510;
|
padding:10px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header ul {
|
@media (min-width: 768px) {
|
||||||
margin: 0;
|
header {
|
||||||
padding: 0;
|
justify-content: space-between;
|
||||||
list-style: none;
|
|
||||||
overflow: hidden;
|
|
||||||
/* background-color: #282929; */
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header li a {
|
|
||||||
display: block;
|
|
||||||
padding: 30px 5px 10px 5px;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
border-right: 1px solid #282929;
|
|
||||||
text-decoration: none;
|
|
||||||
/* background: rgba(255, 255, 255, 0.9) */
|
|
||||||
background-color: #282929;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header li a:hover,
|
|
||||||
.header .menu-btn:hover {
|
|
||||||
background-color: #282929;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .logo {
|
|
||||||
display: block;
|
|
||||||
float: left;
|
|
||||||
font-size: 2em;
|
|
||||||
padding: 5px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* menu */
|
|
||||||
|
|
||||||
.header .menu {
|
|
||||||
clear: both;
|
|
||||||
max-height: 0;
|
|
||||||
transition: max-height .2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* menu icon */
|
|
||||||
|
|
||||||
.header .menu-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
margin: 2%;
|
|
||||||
padding: 10px 20px;
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .menu-icon .nav-icon {
|
|
||||||
background: #000;
|
|
||||||
display: block;
|
|
||||||
height: 5px;
|
|
||||||
position: relative;
|
|
||||||
transition: background .2s ease-out;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .menu-icon .nav-icon:before,
|
|
||||||
.header .menu-icon .nav-icon:after {
|
|
||||||
background: #000;
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
transition: all .2s ease-out;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .menu-icon .nav-icon:before {
|
|
||||||
top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .menu-icon .nav-icon:after {
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* menu btn */
|
|
||||||
|
|
||||||
.header .menu-btn {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .menu-btn:checked~.menu {
|
|
||||||
max-height: 1200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .menu-btn:checked~.menu-icon .nav-icon {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .menu-btn:checked~.menu-icon .nav-icon:before {
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .menu-btn:checked~.menu-icon .nav-icon:after {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* menu content - larger screen*/
|
|
||||||
|
|
||||||
@media (min-width: 1132px) {
|
|
||||||
.header li {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.header li a {
|
|
||||||
padding: 20px 15px 5px 0px;
|
|
||||||
font-size: 1rem;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.header .menu {
|
|
||||||
clear: none;
|
|
||||||
float: right;
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
.header .menu-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* smaller screen*/
|
|
||||||
|
|
||||||
@media (max-width: 924px) {
|
|
||||||
.header li {
|
|
||||||
padding: 30px 5px 10px 5px;
|
|
||||||
}
|
|
||||||
.header li a {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,21 +1,27 @@
|
|||||||
<script>
|
<script>
|
||||||
import favicon from '$lib/images/haus.svg'
|
import Hamburger from "hamburger-menu-svelte";
|
||||||
|
const menu_list = [
|
||||||
|
{ name: "Home", url: "/" },
|
||||||
|
{ name: "Bicycle", url: "/bicycle" },
|
||||||
|
{ name: "Sauna", url: "/sauna" }
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<!-- <a href="https://www.perinet.io" target="_blank" class="logo">
|
<Hamburger {menu_list}/>
|
||||||
<img src={perinetLogo} alt="Perinet Logo" width="180">
|
|
||||||
</a> -->
|
|
||||||
|
|
||||||
<link rel="shortcut icon" type="image/svg" href={favicon} />
|
|
||||||
|
|
||||||
<input class="menu-btn" type="checkbox" id="menu-btn" />
|
|
||||||
|
|
||||||
<label class="menu-icon" for="menu-btn"><span class="nav-icon"></span></label>
|
|
||||||
|
|
||||||
<ul class="menu">
|
|
||||||
<li><a href="/">Home</a></li>
|
|
||||||
<li><a href="/sauna">Sauna</a></li>
|
|
||||||
<!-- <li><a href="/chicken">Chicken</a></li> -->
|
|
||||||
</ul>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* header {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding:10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
header {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
</style>
|
||||||
|
110
webui/src/lib/Uplot.svelte
Normal file
110
webui/src/lib/Uplot.svelte
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import 'uplot/dist/uPlot.min.css';
|
||||||
|
|
||||||
|
export let data_label = ""; // data label in legend
|
||||||
|
export let unit = ""; // unit in legend
|
||||||
|
|
||||||
|
export let last_for_seconds = 20; // Period of time [s] the data are shifted out on the left
|
||||||
|
let plotContainer;
|
||||||
|
let data = [];
|
||||||
|
data[0] = [Date.now()];
|
||||||
|
data[1] = [null];
|
||||||
|
|
||||||
|
const PRECISION = 1;
|
||||||
|
|
||||||
|
export async function create(legend_label, legend_unit, duration) {
|
||||||
|
data_label = legend_label;
|
||||||
|
unit = legend_unit;
|
||||||
|
last_for_seconds = duration;
|
||||||
|
const uplotModule = await import('uplot');
|
||||||
|
const uPlot = uplotModule.default;
|
||||||
|
redraw(uPlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function add_data(new_data) {
|
||||||
|
data[0].push(Date.now());
|
||||||
|
data[1].push(new_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redraw(uPlot) {
|
||||||
|
function getSize() {
|
||||||
|
let max_height = window.innerHeight;
|
||||||
|
let max_width = window.innerWidth;
|
||||||
|
let current_posY = plotContainer.getBoundingClientRect().top;
|
||||||
|
let height = Math.max(max_height - current_posY - 200, 250);
|
||||||
|
let width = Math.max(max_width - (2 * 50), 600);
|
||||||
|
return {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const opts = {
|
||||||
|
...getSize(),
|
||||||
|
ms: 1,
|
||||||
|
cursor: {drag: {setScale: false}},
|
||||||
|
points: {show: false},
|
||||||
|
pxSnap: false,
|
||||||
|
pxAlign: 0,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
label: "time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: data_label,
|
||||||
|
stroke: "#b6b6b6",
|
||||||
|
paths: uPlot.paths.spline(),
|
||||||
|
value: (u, v, sidx, didx) => {
|
||||||
|
if (didx == null) {
|
||||||
|
let d = u.data[sidx];
|
||||||
|
v = d[d.length - 1];
|
||||||
|
}
|
||||||
|
let value = v;
|
||||||
|
if ( value != null) {
|
||||||
|
value = value.toFixed(PRECISION) + unit;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
pxAlign: 0,
|
||||||
|
points: { show: false },
|
||||||
|
}
|
||||||
|
],
|
||||||
|
axes: [{grid: { show: false}}, {grid: { show: false}}],
|
||||||
|
// hooks are used here to filter out the time series from the legend
|
||||||
|
hooks: {
|
||||||
|
init: [
|
||||||
|
u => {
|
||||||
|
[...u.root.querySelectorAll('.u-legend .u-series')].forEach((el, i) => {
|
||||||
|
if (u.series[i].label ==="time") {
|
||||||
|
el.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let uplot = new uPlot(opts, data, plotContainer);
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
const now = Date.now();
|
||||||
|
const scale = { min: now - (last_for_seconds * 1000), max: now };
|
||||||
|
|
||||||
|
// shift out data earlier than chart origin
|
||||||
|
let last = Date.now();
|
||||||
|
let first = last - (last_for_seconds * 1000);
|
||||||
|
while(data[0][0] < first) {
|
||||||
|
for(let i = 0; i < data.length; i++) {
|
||||||
|
data[i].shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uplot.setData(data, false);
|
||||||
|
uplot.setScale('x', scale);
|
||||||
|
uplot.setLegend({idx: data[0].length - 1}, false);
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={plotContainer}></div>
|
53
webui/src/lib/images/wheels.svg
Normal file
53
webui/src/lib/images/wheels.svg
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 397.6 397.6" style="enable-background:new 0 0 397.6 397.6;" xml:space="preserve">
|
||||||
|
<path d="M313.1,147.875c-6.057,0-11.962,0.654-17.661,1.871l-15.805-53.435l37.842-2.565c4.447-0.301,8.487,2.613,9.602,6.932
|
||||||
|
c0.689,2.671,0.161,5.488-1.45,7.728c-1.611,2.24-4.113,3.638-6.865,3.834l-9.126,0.652c-3.581,0.256-6.276,3.366-6.02,6.947
|
||||||
|
s3.361,6.289,6.947,6.02l9.126-0.652c6.611-0.472,12.623-3.829,16.493-9.21c3.87-5.382,5.14-12.148,3.484-18.566
|
||||||
|
c-2.677-10.375-12.376-17.364-23.069-16.654l-45.936,3.114c-1.969,0.134-3.77,1.154-4.896,2.773
|
||||||
|
c-1.127,1.62-1.457,3.663-0.897,5.556l6.405,21.656H145.477l-6.316-12H155.5c3.59,0,6.5-2.91,6.5-6.5s-2.91-6.5-6.5-6.5h-47
|
||||||
|
c-3.59,0-6.5,2.91-6.5,6.5s2.91,6.5,6.5,6.5h16.086l9.542,18.349l-18.836,33.485c-9.549-3.751-19.929-5.834-30.792-5.834
|
||||||
|
c-46.593,0-84.5,37.906-84.5,84.5s37.907,84.5,84.5,84.5c44.404,0,80.892-34.436,84.225-78c0,0,31.695,0,31.776,0
|
||||||
|
c2.235,0,4.32-1.15,5.511-3.055l68.779-110.047l8.185,27.672c-31.758,12.162-54.376,42.945-54.376,78.93
|
||||||
|
c0,46.594,37.907,84.5,84.5,84.5s84.5-37.906,84.5-84.5S359.693,147.875,313.1,147.875z M84.5,303.875
|
||||||
|
c-39.425,0-71.5-32.075-71.5-71.5s32.075-71.5,71.5-71.5c8.549,0,16.75,1.513,24.355,4.276l-31.482,55.968
|
||||||
|
c-3.726,2.365-6.206,6.516-6.206,11.256c0,7.363,5.969,13.333,13.333,13.333c5.002,0,9.354-2.759,11.636-6.833h59.556
|
||||||
|
C152.395,275.263,121.733,303.875,84.5,303.875z M96.136,225.875c-0.99-1.769-2.37-3.285-4.025-4.439l28.528-50.717
|
||||||
|
c19.37,11.397,32.922,31.647,35.052,55.156H96.136z M168.725,225.875c-2.17-28.365-18.393-52.845-41.715-66.482l14.327-25.471
|
||||||
|
l48.396,91.953H168.725z M200.983,219.337l-48.665-92.462h106.454L200.983,219.337z M313.1,303.875c-39.425,0-71.5-32.075-71.5-71.5
|
||||||
|
c0-30.093,18.697-55.885,45.077-66.418l16.89,57.105c-2.348,2.403-3.8,5.687-3.8,9.313c0,7.363,5.969,13.333,13.333,13.333
|
||||||
|
s13.333-5.97,13.333-13.333c0-6.354-4.449-11.661-10.399-12.999l-16.895-57.123c4.518-0.897,9.184-1.378,13.962-1.378
|
||||||
|
c39.425,0,71.5,32.075,71.5,71.5S352.525,303.875,313.1,303.875z"/>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
2
webui/src/routes/bicycle/+page.js
Normal file
2
webui/src/routes/bicycle/+page.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const prerender = true;
|
||||||
|
export const trailingSlash = 'always';
|
49
webui/src/routes/bicycle/+page.svelte
Normal file
49
webui/src/routes/bicycle/+page.svelte
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "../../../node_modules/svelte/internal";
|
||||||
|
import icon from "$lib/images/wheels.svg"
|
||||||
|
|
||||||
|
let backend_url = "https://home.blackfinn.de/api/bicycle";
|
||||||
|
// let backend_url = "http://localhost:5000/api/bicycle";
|
||||||
|
|
||||||
|
let year = "2024";
|
||||||
|
let total_distance_value = 0.0;
|
||||||
|
let total_distance_unit = "m"
|
||||||
|
let year_result_value = 0.0;
|
||||||
|
let year_result_unit = "m"
|
||||||
|
|
||||||
|
function get_api() {
|
||||||
|
fetch(backend_url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
total_distance_value = data.Years[year].total_distance.value
|
||||||
|
total_distance_unit = data.Years[year].total_distance.unit
|
||||||
|
year_result_value = data.Years[year].year_result.value
|
||||||
|
year_result_unit = data.Years[year].year_result.unit
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
get_api();
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
get_api();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Bicycle</title>
|
||||||
|
<meta name="description" content="Bicycle"/>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<section id='content_id' class='content'>
|
||||||
|
<h1>Bicycle</h1>
|
||||||
|
<figure>
|
||||||
|
<img src={icon} alt="Bicycle" width=150/>
|
||||||
|
</figure>
|
||||||
|
<h1>Total distance: {total_distance_value} {total_distance_unit}</h1>
|
||||||
|
<h1>Year result: {year_result_value} {year_result_unit}</h1>
|
||||||
|
</section>
|
@@ -1,11 +1,15 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "../../../node_modules/svelte/internal";
|
import { onMount } from "../../../node_modules/svelte/internal";
|
||||||
|
// import Plot from "$lib/Uplot.svelte"
|
||||||
import icon from "$lib/images/sauna.svg"
|
import icon from "$lib/images/sauna.svg"
|
||||||
|
|
||||||
let backend_url = "https://home.blackfinn.de/sauna/sample";
|
let backend_url = "https://home.blackfinn.de/api/sauna";
|
||||||
|
|
||||||
let temperature_value = 0.0
|
let temperature_value = 0.0
|
||||||
let temperature_unit = "°C"
|
let temperature_unit = "°C"
|
||||||
|
let time = "00:00:00";
|
||||||
|
let updated = false;
|
||||||
|
let plot;
|
||||||
|
|
||||||
function get_temperature() {
|
function get_temperature() {
|
||||||
fetch(backend_url)
|
fetch(backend_url)
|
||||||
@@ -13,6 +17,11 @@
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
temperature_unit = data.unit
|
temperature_unit = data.unit
|
||||||
temperature_value = data.value
|
temperature_value = data.value
|
||||||
|
time = data.time.slice(11, 19)
|
||||||
|
// if(plot) {
|
||||||
|
// plot.add_data(temperature_value);
|
||||||
|
// }
|
||||||
|
updated = JSON.parse(data.valid);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return [];
|
return [];
|
||||||
@@ -24,6 +33,9 @@
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
// if(plot) {
|
||||||
|
// plot.create("Temperature", "°C", 4 * 60 * 60 /* 4h */);
|
||||||
|
// }
|
||||||
get_temperature();
|
get_temperature();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -38,5 +50,11 @@
|
|||||||
<figure>
|
<figure>
|
||||||
<img src={icon} alt="Sauna" width=150/>
|
<img src={icon} alt="Sauna" width=150/>
|
||||||
</figure>
|
</figure>
|
||||||
<h1>{temperature_value} {temperature_unit}</h1>
|
{#if updated}
|
||||||
|
<div>{time} Uhr:</div>
|
||||||
|
<h1>{temperature_value} {temperature_unit}</h1>
|
||||||
|
<!-- <div><Plot/></div> -->
|
||||||
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- <Plot bind:this={plot}/> -->
|
||||||
|
Reference in New Issue
Block a user