Compare commits

...

113 commits

Author SHA1 Message Date
RenovateBot 2b662d4fbd Update Rust crate actix-web to 4.8.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 43m4s
2024-06-20 03:02:21 +00:00
RenovateBot 0bad5f9947 Update dependency com.android.tools.build:gradle to v8.5.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 29m25s
2024-06-13 18:03:08 +00:00
RenovateBot 0440cc0b40 Update dependency com.android.tools.build:gradle to v8.4.2
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 27m17s
2024-06-10 18:03:10 +00:00
RenovateBot 27801d275f Update Rust crate actix-web to 4.7.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 33m6s
2024-06-09 00:04:04 +00:00
RenovateBot e27f807fe5 Update dependency gradle to v8.8
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 23m40s
2024-06-01 00:03:57 +00:00
RenovateBot 938205c02e Update Rust crate tokio to 1.38.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 36m33s
2024-05-30 21:20:48 +00:00
hodasemi 2012c25fdd Merge pull request 'Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v2' (#70) from renovate/major-kotlin-monorepo into master
Reviewed-on: #70
2024-05-22 13:12:23 +00:00
RenovateBot 753145ec60 Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v2
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 41m58s
2024-05-21 09:02:39 +00:00
RenovateBot 55ec813ad6 Update dependency com.android.tools.build:gradle to v8.4.1
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 24m46s
2024-05-20 18:02:39 +00:00
RenovateBot ce13fe5f20 Update Rust crate actix-web to 4.6.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 26m43s
2024-05-19 12:02:39 +00:00
RenovateBot 9355307b1d Update Rust crate anyhow to 1.0.86
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 30m15s
2024-05-18 12:03:20 +00:00
RenovateBot 1c9d50b5d7 Update Rust crate anyhow to 1.0.85
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 28m30s
2024-05-18 00:03:27 +00:00
RenovateBot aaa93cd651 Update Rust crate anyhow to 1.0.84
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 29m11s
2024-05-17 18:03:12 +00:00
hodasemi 1a7b4dacfa Merge pull request 'Update dependency flutter_lints to v4' (#64) from renovate/flutter_lints-4.x into master
Reviewed-on: #64
2024-05-16 07:32:29 +00:00
RenovateBot c11b89b95d Update dependency flutter_lints to v4
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 23m17s
2024-05-09 18:02:33 +00:00
RenovateBot a534a03c69 Update dependency fl_chart to ^0.68.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 25m52s
2024-05-09 03:02:38 +00:00
RenovateBot 5167a0483e Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.24
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 28m30s
2024-05-07 09:02:47 +00:00
RenovateBot 68442fdc74 Update Rust crate anyhow to 1.0.83
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 26m58s
2024-05-06 21:03:19 +00:00
RenovateBot 204da7ff9b Update Rust crate anyhow to 1.0.82
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 24m8s
2024-04-30 21:03:34 +00:00
RenovateBot e83cae6c10 Update dependency com.android.tools.build:gradle to v8.4.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 24m29s
2024-04-30 18:02:05 +00:00
RenovateBot b40dfe9db9 Update Rust crate chrono to 0.4.38
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 14m58s
2024-04-15 12:04:56 +00:00
RenovateBot cadcc4fe9a Update dependency com.android.tools.build:gradle to v8.3.2
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 14m40s
2024-04-10 03:03:16 +00:00
RenovateBot 877c7ead31 Update dependency fl_chart to ^0.67.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 14m36s
2024-03-29 00:02:26 +00:00
RenovateBot f5d2cb1a26 Update Rust crate tokio to 1.37.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m38s
2024-03-28 18:02:10 +00:00
RenovateBot 27a79e1e51 Update Rust crate chrono to 0.4.37
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 14m24s
2024-03-27 12:02:33 +00:00
RenovateBot b615f416d1 Update dependency gradle to v8.7
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 14m3s
2024-03-22 18:02:48 +00:00
RenovateBot 4cf4085933 Update Rust crate reqwest to 0.11.27
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m55s
2024-03-19 21:02:18 +00:00
RenovateBot b4fdb45214 Update dependency com.android.tools.build:gradle to v8.3.1
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 12m51s
2024-03-18 21:01:59 +00:00
RenovateBot cc3bf31823 Update Rust crate reqwest to 0.11.26
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m34s
2024-03-12 18:03:58 +00:00
RenovateBot 9068d847ca Update Rust crate anyhow to 1.0.81
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 12m47s
2024-03-12 03:03:42 +00:00
RenovateBot ded7b345ed Update Rust crate reqwest to 0.11.25
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m13s
2024-03-09 00:02:47 +00:00
RenovateBot 6b6747e9cc Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.23
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 12m58s
2024-03-08 18:03:20 +00:00
RenovateBot 23478ca1ed Update Rust crate chrono to 0.4.35
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m43s
2024-03-08 12:03:39 +00:00
hodasemi ec033a4be0 Merge pull request 'Update dependency com.android.tools.build:gradle to v8.3.0' (#44) from renovate/com.android.tools.build-gradle-8.x into master
Reviewed-on: #44
2024-03-01 11:42:24 +00:00
hodasemi 63fa933ad9 Comment tasmota and tibber tests 2024-03-01 12:41:59 +01:00
RenovateBot d2ccc21176 Update dependency com.android.tools.build:gradle to v8.3.0
Some checks failed
Home Server Merge / Serial Reader (pull_request) Failing after 13m33s
2024-03-01 00:03:50 +00:00
hodasemi 5ac8f22c2d Merge pull request 'Update Rust crate chrono to 0.4.34' (#41) from renovate/chrono-0.x into master
Reviewed-on: #41
2024-02-26 09:46:27 +00:00
hodasemi 540c1df8fc Merge pull request 'Update Rust crate rusqlite to 0.31.0' (#42) from renovate/rusqlite-0.x into master
Reviewed-on: #42
2024-02-26 09:45:55 +00:00
RenovateBot e63b3013e1 Update Rust crate rusqlite to 0.31.0
Some checks failed
Home Server Merge / Serial Reader (pull_request) Failing after 14m8s
2024-02-22 02:03:09 +00:00
RenovateBot 5ae92776c5 Update Rust crate chrono to 0.4.34
Some checks failed
Home Server Merge / Serial Reader (pull_request) Failing after 14m0s
2024-02-22 02:02:58 +00:00
hodasemi 2d1765591e Merge pull request 'Update Rust crate anyhow to 1.0.80' (#43) from renovate/anyhow-1.x into master
Reviewed-on: #43
2024-02-21 11:48:23 +00:00
RenovateBot 08a1c9f398 Update Rust crate anyhow to 1.0.80
Some checks failed
Home Server Merge / Serial Reader (pull_request) Failing after 13m41s
2024-02-20 02:03:33 +00:00
hodasemi 1198263110 Merge pull request 'Update dependency gradle to v8.6' (#40) from renovate/gradle-8.x into master
Reviewed-on: #40
2024-02-07 12:03:33 +00:00
hodasemi 4cf60d9d37 Merge pull request 'Update Rust crate tokio to 1.36.0' (#39) from renovate/tokio-1.x into master
Reviewed-on: #39
2024-02-07 12:03:22 +00:00
hodasemi e6969c4a25 Merge pull request 'Update Rust crate actix-web to 4.5.1' (#38) from renovate/actix-web-4.x into master
Reviewed-on: #38
2024-02-07 12:03:00 +00:00
RenovateBot b80e58d127 Update dependency gradle to v8.6
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 14m5s
2024-02-07 02:02:43 +00:00
RenovateBot b5435a474a Update Rust crate tokio to 1.36.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m59s
2024-02-07 02:02:35 +00:00
RenovateBot b3ba9913b2 Update Rust crate actix-web to 4.5.1
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m46s
2024-02-07 02:02:27 +00:00
hodasemi 1947939d33 Merge pull request 'Update Rust crate reqwest to 0.11.24' (#37) from renovate/reqwest-0.x into master
Reviewed-on: #37
2024-02-01 08:11:28 +01:00
hodasemi c42d91cdba Merge pull request 'Update dependency com.android.tools.build:gradle to v8.2.2' (#36) from renovate/com.android.tools.build-gradle-8.x into master
Reviewed-on: #36
2024-02-01 08:11:18 +01:00
RenovateBot 36e3c64ff1 Update dependency com.android.tools.build:gradle to v8.2.2
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m54s
2024-02-01 02:03:54 +01:00
RenovateBot 6fe8df31cb Update Rust crate reqwest to 0.11.24
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m54s
2024-02-01 02:03:44 +01:00
RenovateBot a1af01cdfe Update Rust crate chrono to 0.4.33
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 14m16s
2024-01-26 02:02:52 +01:00
hodasemi 5b8866cdd7 Merge pull request 'Update Rust crate actix-cors to 0.7.0' (#34) from renovate/actix-cors-0.x into master
Reviewed-on: #34
2024-01-14 19:52:31 +01:00
hodasemi 7642856128 Merge pull request 'Update dependency fl_chart to ^0.66.0' (#32) from renovate/fl_chart-0.x into master
Reviewed-on: #32
2024-01-14 19:52:18 +01:00
hodasemi 9915c8627a Merge pull request 'Update dependency com.android.tools.build:gradle to v8.2.1' (#33) from renovate/com.android.tools.build-gradle-8.x into master
Reviewed-on: #33
2024-01-14 18:34:40 +01:00
hodasemi d8a75fec02 Merge pull request 'Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.22' (#29) from renovate/kotlin-monorepo into master
Reviewed-on: #29
2024-01-14 18:21:03 +01:00
hodasemi 1f64c28d5a Merge pull request 'Update Rust crate tokio to 1.35.1' (#27) from renovate/tokio-1.x into master
Reviewed-on: #27
2024-01-14 17:58:24 +01:00
hodasemi 8be32763fb Remove static warning
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m34s
2024-01-14 17:36:41 +01:00
RenovateBot 7928fc3d6b Update dependency fl_chart to ^0.66.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m47s
2024-01-14 02:03:37 +01:00
RenovateBot bea7ae3c6a Update Rust crate actix-cors to 0.7.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m15s
2024-01-14 02:03:30 +01:00
RenovateBot 7d0c5a32bf Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.22
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m36s
2024-01-14 02:03:24 +01:00
RenovateBot 2effa730a7 Update dependency com.android.tools.build:gradle to v8.2.1
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m26s
2024-01-14 02:03:18 +01:00
RenovateBot 08913a6cfc Update Rust crate tokio to 1.35.1
Some checks failed
Home Server Merge / Serial Reader (pull_request) Failing after 14m11s
2024-01-14 02:03:14 +01:00
RenovateBot 74769b720a Update Rust crate reqwest to 0.11.23
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m40s
2024-01-13 02:03:25 +01:00
RenovateBot 94fa1169fe Update Rust crate futures to 0.3.30
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m41s
2024-01-12 02:04:51 +01:00
RenovateBot 34cc7c508d Update Rust crate anyhow to 1.0.79
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m11s
2024-01-11 02:03:28 +01:00
RenovateBot 2e47e84435 Update Rust crate actix-web to 4.4.1
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m19s
2023-12-25 02:02:13 +01:00
hodasemi 2271673245 Merge pull request 'Update Rust crate tokio to 1.35.0' (#25) from renovate/tokio-1.x into master
Reviewed-on: #25
2023-12-13 18:24:56 +01:00
RenovateBot 95af3265c9 Update Rust crate tokio to 1.35.0
Some checks failed
Home Server Merge / Serial Reader (pull_request) Failing after 13m39s
2023-12-09 02:01:57 +01:00
hodasemi dfab237b9e Merge pull request 'Update dependency intl to ^0.19.0' (#24) from renovate/intl-0.x into master
Reviewed-on: #24
2023-12-08 07:08:37 +01:00
RenovateBot 64c70484f6 Update dependency intl to ^0.19.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m17s
2023-12-08 02:02:09 +01:00
hodasemi 960a1e2159 Merge pull request 'Update Rust crate actix-cors to 0.6.5' (#23) from renovate/actix-cors-0.x into master
Reviewed-on: #23
2023-12-07 11:12:18 +01:00
RenovateBot e15f9dce83 Update Rust crate actix-cors to 0.6.5
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 14m55s
2023-12-07 02:01:56 +01:00
hodasemi b9aedfadda Merge pull request 'Update dependency com.android.tools.build:gradle to v8.2.0' (#22) from renovate/com.android.tools.build-gradle-8.x into master
Reviewed-on: #22
2023-12-01 10:57:01 +01:00
RenovateBot 0c53ab7b6e Update dependency com.android.tools.build:gradle to v8.2.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m4s
2023-12-01 02:03:54 +01:00
hodasemi 210ca5012d Merge pull request 'Update dependency gradle to v8.5' (#21) from renovate/gradle-8.x into master
Reviewed-on: #21
2023-11-30 15:49:31 +01:00
RenovateBot 307ae067a0 Update dependency gradle to v8.5
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m20s
2023-11-30 02:02:39 +01:00
RenovateBot 61ba2d32a0 Update dependency fl_chart to ^0.65.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m37s
2023-11-25 02:07:54 +01:00
RenovateBot af0dc6f7fa Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.21
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m45s
2023-11-24 02:05:36 +01:00
RenovateBot 593dc8e8e5 Update dependency com.android.tools.build:gradle to v8.1.4
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 13m21s
2023-11-17 02:02:54 +01:00
RenovateBot 3611ef0de8 Update Rust crate rusqlite to 0.30.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 18m45s
2023-11-13 02:03:01 +01:00
RenovateBot cab496f039 Update Rust crate tokio to 1.34.0
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 19m43s
2023-11-10 02:01:55 +01:00
RenovateBot 45effe3339 Update dependency com.android.tools.build:gradle to v8.1.3
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 20m55s
2023-11-08 02:02:24 +01:00
hodasemi aac0365a54 Merge pull request 'Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.20' (#14) from renovate/kotlin-monorepo into master
Reviewed-on: #14
2023-11-01 10:51:35 +01:00
RenovateBot 238930a651 Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.20
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 19m53s
2023-11-01 02:02:18 +01:00
hodasemi 4c0f6ea0f3 Merge pull request 'Update Rust crate futures to 0.3.29' (#13) from renovate/rust-futures-monorepo into master
Reviewed-on: #13
2023-10-31 11:17:47 +01:00
hodasemi 47aab2fc75 Fix equality check
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 19m56s
2023-10-30 06:49:59 +01:00
RenovateBot b21c89c81c Update Rust crate futures to 0.3.29
Some checks failed
Home Server Merge / Serial Reader (pull_request) Failing after 20m36s
2023-10-27 02:02:18 +02:00
hodasemi 932958657d Query actions from server 2023-10-23 13:30:45 +02:00
hodasemi c71e5c63ee Impl rest interface to post push-actions 2023-10-23 11:07:37 +02:00
hodasemi 184011c232 Implement temperature push 2023-10-23 10:32:52 +02:00
hodasemi 0650270b22 Implement actions and tasks 2023-10-23 10:03:16 +02:00
hodasemi e77e0275fd Also copy tibber token 2023-10-19 17:11:31 +02:00
hodasemi d69610fc01 Set up thermometer 2023-10-19 16:26:09 +02:00
hodasemi 11e4a22307 renovate.json aktualisiert 2023-10-19 06:48:22 +02:00
hodasemi 1987131208 Merge pull request 'Update dependency gradle to v8' (#12) from renovate/gradle-8.x into master
Reviewed-on: #12
2023-10-19 06:30:58 +02:00
hodasemi 8e7715915b Merge pull request 'Update dependency flutter_lints to v3' (#11) from renovate/flutter_lints-3.x into master
Reviewed-on: #11
2023-10-19 06:30:49 +02:00
hodasemi aca1c19a00 Merge pull request 'Update dependency com.android.tools.build:gradle to v8' (#10) from renovate/com.android.tools.build-gradle-8.x into master
Reviewed-on: #10
2023-10-19 06:30:42 +02:00
hodasemi 0dc0cf3805 Merge pull request 'Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.10' (#9) from renovate/kotlin-monorepo into master
Reviewed-on: #9
2023-10-19 06:30:30 +02:00
RenovateBot b6c00a0d57 Update dependency gradle to v8
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 20m34s
2023-10-19 02:02:11 +02:00
RenovateBot 503264cc74 Update dependency flutter_lints to v3
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 20m35s
2023-10-19 02:02:00 +02:00
RenovateBot 90a84f52a0 Update dependency com.android.tools.build:gradle to v8
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 20m33s
2023-10-18 02:01:59 +02:00
RenovateBot 17ebebc1d5 Update dependency org.jetbrains.kotlin:kotlin-gradle-plugin to v1.9.10
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 21m14s
2023-10-18 02:01:48 +02:00
hodasemi 20e49df7e9 Merge pull request 'Update dependency gradle to v7.6.3' (#8) from renovate/gradle-7.x into master
Reviewed-on: #8
2023-10-17 07:09:49 +02:00
hodasemi d034f68ea0 Merge pull request 'Update dependency com.android.tools.build:gradle to v7.4.2' (#7) from renovate/com.android.tools.build-gradle-7.x into master
Reviewed-on: #7
2023-10-17 07:09:36 +02:00
RenovateBot 044f7b01b0 Update dependency gradle to v7.6.3
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 21m19s
2023-10-17 02:02:14 +02:00
RenovateBot fb354b5982 Update dependency com.android.tools.build:gradle to v7.4.2
All checks were successful
Home Server Merge / Serial Reader (pull_request) Successful in 21m15s
2023-10-17 02:02:00 +02:00
hodasemi 51bfe0ab4b Fix tibber handler 2023-10-16 14:19:36 +02:00
hodasemi 38cc9facc6 Merge remote-tracking branch 'origin/flutter' 2023-10-16 13:55:19 +02:00
hodasemi d5b429f7e2 rename tibber module 2023-10-16 13:48:51 +02:00
hodasemi 85ed9c1983 Merge remote-tracking branch 'refs/remotes/origin/master' 2023-10-16 13:46:25 +02:00
hodasemi 6783b914d5 Add tibber 2023-10-16 13:37:31 +02:00
28 changed files with 1165 additions and 191 deletions

View file

@ -6,14 +6,16 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rusqlite = "0.29.0"
anyhow = { version = "1.0.75", features = ["backtrace"] }
reqwest = "0.11.22"
rusqlite = "0.31.0"
anyhow = { version = "1.0.86", features = ["backtrace"] }
reqwest = "0.11.27"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
futures = "0.3.28"
tokio = { version = "1.33.0", features=["macros", "rt-multi-thread"] }
chrono = "0.4.31"
actix-web = "4.4.0"
futures = "0.3.30"
tokio = { version = "1.38.0", features=["macros", "rt-multi-thread"] }
tibber = "0.5.0"
chrono = "0.4.38"
actix-web = "4.8.0"
midea = { git = "https://gavania.de/hodasemi/Midea.git" }
actix-cors = "0.6.4"
actix-cors = "0.7.0"
dns-lookup = "2.0.4"

View file

@ -9,4 +9,5 @@ cargo build --release
mkdir -p server
cp devices.conf server/
cp tibber_token.txt server/
cp target/release/home_server server/

View file

@ -4,5 +4,14 @@
["Tasmota-Plug-2", false],
["Tasmota-Plug-3", true],
["Tasmota-Plug-4", true]
],
"thermostat": [
"shellytrv-8CF681A1F886",
"shellytrv-8CF681E9BAEE",
"shellytrv-B4E3F9D9E2A1"
],
"thermometer": [
"shellyplusht-d4d4da7d85b4",
"shellyplusht-80646fc9db9c"
]
}

View file

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '2.0.0'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath 'com.android.tools.build:gradle:8.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

View file

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip

View file

@ -30,14 +30,14 @@ class Category {
final Map<String, List<dynamic>> json =
Map.castFrom(jsonDecode(jsonDecode(response.body)));
for (MapEntry<String, List<dynamic>> entry in json.entries) {
for (final MapEntry<String, List<dynamic>> entry in json.entries) {
final Category category = Category(entry.key);
for (dynamic device_info_dyn in entry.value) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in entry.value) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices
.add(DeviceIdOnly(device_info['id'], device_info['desc']));
.add(DeviceIdOnly(deviceInfo['id'], deviceInfo['desc']));
}
if (category.devices.isNotEmpty) {
@ -65,10 +65,10 @@ class Category {
final Category category = Category('plugs');
final List<dynamic> plugs = json['plugs']!;
for (dynamic device_info_dyn in plugs) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in plugs) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await Plug.create(device_info));
category.devices.add(await Plug.create(deviceInfo));
}
categories.add(category);
@ -79,10 +79,10 @@ class Category {
final Category category = Category('thermostat');
final List<dynamic> thermostats = json['thermostat']!;
for (dynamic device_info_dyn in thermostats) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in thermostats) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await Thermostat.create(device_info));
category.devices.add(await Thermostat.create(deviceInfo));
}
categories.add(category);
@ -91,13 +91,13 @@ class Category {
// create temperature_and_humidity
{
final Category category = Category('temperature_and_humidity');
final List<dynamic> temperature_and_humidities =
final List<dynamic> temperatureAndHumidities =
json['temperature_and_humidity']!;
for (dynamic device_info_dyn in temperature_and_humidities) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in temperatureAndHumidities) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await TemperatureHumidity.create(device_info));
category.devices.add(await TemperatureHumidity.create(deviceInfo));
}
categories.add(category);
@ -106,12 +106,12 @@ class Category {
// create dish_washer
{
final Category category = Category('dish_washer');
final List<dynamic> dish_washer = json['dish_washer']!;
final List<dynamic> dishWasher = json['dish_washer']!;
for (dynamic device_info_dyn in dish_washer) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in dishWasher) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await DishWasher.create(device_info));
category.devices.add(await DishWasher.create(deviceInfo));
}
categories.add(category);
@ -120,12 +120,12 @@ class Category {
// create washing_machines
{
final Category category = Category('washing_machines');
final List<dynamic> washing_machines = json['washing_machines']!;
final List<dynamic> washingMachines = json['washing_machines']!;
for (dynamic device_info_dyn in washing_machines) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in washingMachines) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await WashingMachine.create(device_info));
category.devices.add(await WashingMachine.create(deviceInfo));
}
categories.add(category);
@ -143,9 +143,9 @@ class Category {
}
class CategoryWidget extends StatelessWidget {
final Category category;
CategoryWidget({super.key, required this.category});
const CategoryWidget({super.key, required this.category});
final Category category;
@override
Widget build(BuildContext context) {
@ -169,6 +169,7 @@ class DeviceIdOnly extends Device {
final String device_id;
final String? device_descriptor;
@override
Widget create_widget(BuildContext context) {
throw UnimplementedError();
}

View file

@ -3,7 +3,7 @@ import 'package:flutter/src/widgets/framework.dart';
import 'devices.dart';
class DishWasher extends Device {
static Future<DishWasher> create(Map<String, dynamic> device_info) async {
static Future<DishWasher> create(Map<String, dynamic> deviceInfo) async {
throw UnimplementedError();
}

View file

@ -9,6 +9,9 @@ import '../states/plug_settings.dart';
import 'devices.dart';
class Plug extends Device {
Plug(this.device_id, this.device_descriptor, this.led_state, this.power_state,
this.power_draw, this.power_control);
final String device_id;
String? device_descriptor;
bool led_state;
@ -16,40 +19,37 @@ class Plug extends Device {
double power_draw;
bool power_control;
static Future<Plug> create(Map<String, dynamic> device_info) async {
final String device_id = device_info['id'];
final String? device_descriptor = device_info['desc'];
final bool power_control = device_info['toggle'];
static Future<Plug> create(Map<String, dynamic> deviceInfo) async {
final String deviceId = deviceInfo['id'];
final String? deviceDescriptor = deviceInfo['desc'];
final bool powerControl = deviceInfo['toggle'];
final response = await http
.get(Uri.parse("${Constants.BASE_URL}/plug_state/$device_id"));
.get(Uri.parse("${Constants.BASE_URL}/plug_state/$deviceId"));
if (response.statusCode != 200) {
throw Exception("Failed to fetch plug_state for $device_id");
throw Exception("Failed to fetch plug_state for $deviceId");
}
final Map<String, dynamic> device_state =
final Map<String, dynamic> deviceState =
Map.castFrom(jsonDecode(jsonDecode(response.body)));
return Plug(
device_id,
device_descriptor,
device_state["led"],
device_state["power"],
device_state["power_draw"],
power_control && device_state["power_draw"] < 15,
deviceId,
deviceDescriptor,
deviceState["led"],
deviceState["power"],
deviceState["power_draw"],
powerControl && deviceState["power_draw"] < 15,
);
}
Plug(this.device_id, this.device_descriptor, this.led_state, this.power_state,
this.power_draw, this.power_control);
@override
Widget create_widget(BuildContext context) {
const double header_height = 40;
const double info_height = 30;
const double info_width = 60;
const double x_offset = 0.9;
const double headerHeight = 40;
const double infoHeight = 30;
const double infoWidth = 60;
const double xOffset = 0.9;
return Table(
border: TableBorder(
@ -67,7 +67,7 @@ class Plug extends Device {
),
children: [
SizedBox(
height: header_height,
height: headerHeight,
child: Stack(
children: [
Align(
@ -92,7 +92,7 @@ class Plug extends Device {
]),
TableRow(children: [
SizedBox(
height: info_height,
height: infoHeight,
child: Stack(children: [
const Align(
alignment: Alignment(-0.9, 0.0),
@ -101,16 +101,16 @@ class Plug extends Device {
textAlign: TextAlign.center,
"LED")),
Align(
alignment: const Alignment(x_offset, 0.0),
alignment: const Alignment(xOffset, 0.0),
child: SizedBox(
width: info_width,
width: infoWidth,
child: PlugLed(
device_id: device_id, led_state: led_state))),
]))
]),
TableRow(children: [
SizedBox(
height: info_height,
height: infoHeight,
child: Stack(children: [
const Align(
alignment: Alignment(-0.9, 0.0),
@ -119,9 +119,9 @@ class Plug extends Device {
textAlign: TextAlign.center,
"Power")),
Align(
alignment: const Alignment(x_offset, 0.0),
alignment: const Alignment(xOffset, 0.0),
child: SizedBox(
width: info_width,
width: infoWidth,
child: PlugPower(
device_id: device_id,
power_state: power_state,
@ -130,7 +130,7 @@ class Plug extends Device {
]),
TableRow(children: [
SizedBox(
height: info_height,
height: infoHeight,
child: Stack(children: [
const Align(
alignment: Alignment(-0.9, 0.0),
@ -139,9 +139,9 @@ class Plug extends Device {
textAlign: TextAlign.center,
"Power Draw")),
Align(
alignment: const Alignment(x_offset, 0.0),
alignment: const Alignment(xOffset, 0.0),
child: SizedBox(
width: info_width,
width: infoWidth,
child: Text(
textAlign: TextAlign.center, "$power_draw W")))
]))
@ -151,10 +151,10 @@ class Plug extends Device {
}
class PlugLed extends StatefulWidget {
final String device_id;
bool led_state;
PlugLed({super.key, required this.device_id, required this.led_state});
final String device_id;
bool led_state;
@override
State<PlugLed> createState() => PlugLedState();
@ -164,17 +164,17 @@ class PlugLedState extends State<PlugLed> {
String _led_state_info = "";
void _toggle_led_state() {
String target_state;
String targetState;
if (widget.led_state) {
target_state = "off";
targetState = "off";
} else {
target_state = "on";
targetState = "on";
}
change_plug_state(widget.device_id, "led", target_state)
.then((device_state) {
widget.led_state = device_state["led"];
change_plug_state(widget.device_id, "led", targetState)
.then((deviceState) {
widget.led_state = deviceState["led"];
setState(() {
_led_state_info = widget.led_state ? "On" : "Off";
@ -193,15 +193,15 @@ class PlugLedState extends State<PlugLed> {
}
class PlugPower extends StatefulWidget {
final String device_id;
bool power_state;
bool power_control;
PlugPower(
{super.key,
required this.device_id,
required this.power_state,
required this.power_control});
final String device_id;
bool power_state;
bool power_control;
@override
State<PlugPower> createState() => PlugPowerState();
@ -211,19 +211,19 @@ class PlugPowerState extends State<PlugPower> {
String _power_state_info = "";
void _toggle_power_state() {
String target_state;
String targetState;
if (widget.power_state) {
target_state = "off";
targetState = "off";
} else {
target_state = "on";
targetState = "on";
}
change_plug_state(widget.device_id, "power", target_state)
.then((device_state) {
widget.power_state = device_state["power"];
change_plug_state(widget.device_id, "power", targetState)
.then((deviceState) {
widget.power_state = deviceState["power"];
widget.power_control = device_state["power_draw"] < 15;
widget.power_control = deviceState["power_draw"] < 15;
setState(() {
_power_state_info = widget.power_state ? "On" : "Off";
@ -246,19 +246,19 @@ class PlugPowerState extends State<PlugPower> {
}
Future<Map<String, dynamic>> change_plug_state(
String device_id, String module, String state) async {
String deviceId, String module, String state) async {
var response = await http.post(
Uri.parse("${Constants.BASE_URL}/plug/$device_id/${module}_$state"));
Uri.parse("${Constants.BASE_URL}/plug/$deviceId/${module}_$state"));
if (response.statusCode != 200) {
throw Exception("Failed to post new state");
}
response =
await http.get(Uri.parse("${Constants.BASE_URL}/plug_state/$device_id"));
await http.get(Uri.parse("${Constants.BASE_URL}/plug_state/$deviceId"));
if (response.statusCode != 200) {
throw Exception("Failed to fetch plug_state for $device_id");
throw Exception("Failed to fetch plug_state for $deviceId");
}
return Map.castFrom(jsonDecode(jsonDecode(response.body)));

View file

@ -4,7 +4,7 @@ import 'devices.dart';
class TemperatureHumidity extends Device {
static Future<TemperatureHumidity> create(
Map<String, dynamic> device_info) async {
Map<String, dynamic> deviceInfo) async {
throw UnimplementedError();
}

View file

@ -3,7 +3,7 @@ import 'package:flutter/src/widgets/framework.dart';
import 'devices.dart';
class Thermostat extends Device {
static Future<Thermostat> create(Map<String, dynamic> device_info) async {
static Future<Thermostat> create(Map<String, dynamic> deviceInfo) async {
throw UnimplementedError();
}

View file

@ -3,7 +3,7 @@ import 'package:flutter/src/widgets/framework.dart';
import 'devices.dart';
class WashingMachine extends Device {
static Future<WashingMachine> create(Map<String, dynamic> device_info) async {
static Future<WashingMachine> create(Map<String, dynamic> deviceInfo) async {
throw UnimplementedError();
}

View file

@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'constants.dart';
import 'states/graphs.dart';
import 'states/home_page.dart';
import 'states/plug_settings.dart';
import 'states/graphs.dart';
void main() {
runApp(const MyApp());
@ -24,8 +23,8 @@ class MyApp extends StatelessWidget {
),
home: const MyHomePage(title: 'Home Server'),
routes: <String, WidgetBuilder>{
'/plug_settings': (BuildContext context) => PlugSettings(),
'/graphs': (BuildContext context) => Graphs(),
'/plug_settings': (BuildContext context) => const PlugSettings(),
'/graphs': (BuildContext context) => const Graphs(),
});
}
}

View file

@ -173,45 +173,45 @@ class _GraphsState extends State<Graphs> {
ElevatedButton(
onPressed: () {
setState(() {
for (var device in devices) {
for (final device in devices) {
if (!device.enabled) {
device.data = List.empty();
continue;
}
int start_date;
int end_date;
int startDate;
int endDate;
try {
start_date = (DateFormat('dd.MM.yyyy')
startDate = (DateFormat('dd.MM.yyyy')
.parse(startDateController.text)
.millisecondsSinceEpoch /
1000) as int;
} on Exception catch (_) {
start_date = 0;
startDate = 0;
print('no start date set');
}
try {
end_date = (DateFormat('dd.MM.yyyy')
endDate = (DateFormat('dd.MM.yyyy')
.parse(endDateController.text)
.millisecondsSinceEpoch /
1000) as int;
} on Exception catch (_) {
end_date = 0;
endDate = 0;
print('no end date set');
}
if (start_date == 0 || end_date == 0) {
if (startDate == 0 || endDate == 0) {
device.data = List.empty();
continue;
}
const String filter_type = 'hourly';
const String filterType = 'hourly';
http
.get(Uri.parse(
"${Constants.BASE_URL}/plug_data/${device.device.device_id}/$start_date/$end_date/$filter_type"))
"${Constants.BASE_URL}/plug_data/${device.device.device_id}/$startDate/$endDate/$filterType"))
.then((response) {
if (response.statusCode != 200) {
throw Exception("Failed to fetch data");
@ -220,14 +220,14 @@ class _GraphsState extends State<Graphs> {
final data = jsonDecode(jsonDecode(response.body))
as List<dynamic>;
final List<ChartData> chart_data = [];
final List<ChartData> chartData = [];
for (final entry in data) {
final pair = entry as List<dynamic>;
chart_data.add(ChartData(pair[0], pair[1]));
chartData.add(ChartData(pair[0], pair[1]));
}
device.data = chart_data;
device.data = chartData;
});
}
});
@ -258,7 +258,6 @@ class _GraphsState extends State<Graphs> {
// titlesData: FlTitlesData(bottomTitles: AxisTitles(sideTitles: ))
),
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
)
])));
});

View file

@ -28,15 +28,15 @@ class _MyHomePageState extends State<MyHomePage> {
}
final data = categories.data!;
final category_count = data.length;
final categoryCount = data.length;
if (category_count > expanded.length) {
final int diff = category_count - expanded.length;
if (categoryCount > expanded.length) {
final int diff = categoryCount - expanded.length;
final List<bool> diff_list = List<bool>.filled(diff, true);
expanded.addAll(diff_list);
} else if (category_count < expanded.length) {
final int diff = expanded.length - category_count;
final List<bool> diffList = List<bool>.filled(diff, true);
expanded.addAll(diffList);
} else if (categoryCount < expanded.length) {
final int diff = expanded.length - categoryCount;
expanded = List<bool>.filled(diff, false);
}

View file

@ -11,7 +11,7 @@ class PlugSettingsArguments {
}
class PlugSettings extends StatefulWidget {
PlugSettings({super.key});
const PlugSettings({super.key});
@override
State<PlugSettings> createState() => _PlugSettingsState();

View file

@ -38,8 +38,8 @@ dependencies:
http: ^1.1.0
flutter_spinkit: ^5.2.0
fl_chart: ^0.64.0
intl: ^0.18.1
fl_chart: ^0.68.0
intl: ^0.19.0
dev_dependencies:
flutter_test:
@ -50,7 +50,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -2,5 +2,12 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"prHourlyLimit": 0,
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
]
}

166
src/action.rs Normal file
View file

@ -0,0 +1,166 @@
use core::slice::Iter;
use std::{fmt::Display, str::FromStr};
use anyhow::{bail, Result};
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum ActionType {
GreaterThan,
LessThan,
Push,
Receive,
Update,
}
impl Display for ActionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::GreaterThan => write!(f, "GreaterThan"),
Self::LessThan => write!(f, "LessThan"),
Self::Push => write!(f, "Push"),
Self::Receive => write!(f, "Receive"),
Self::Update => write!(f, "Update"),
}
}
}
impl FromStr for ActionType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"GreaterThan" => Ok(Self::GreaterThan),
"LessThan" => Ok(Self::LessThan),
"Push" => Ok(Self::Push),
"Receive" => Ok(Self::Receive),
"Update" => Ok(Self::Update),
_ => bail!("could not parse ActionType from {s}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ActionID(pub(crate) i64);
#[derive(Debug, Clone, Eq, PartialOrd, Ord, Serialize)]
pub struct Action {
#[serde(skip)]
pub(crate) id: Option<ActionID>,
pub device_id: String,
pub action_type: ActionType,
pub parameter: String,
}
impl Action {
pub fn new(
device_id: impl ToString,
action_type: ActionType,
parameter: impl ToString,
) -> Self {
Self {
id: None,
device_id: device_id.to_string(),
action_type,
parameter: parameter.to_string(),
}
}
}
impl PartialEq for Action {
fn eq(&self, other: &Self) -> bool {
let id_comp = match (self.id, other.id) {
(Some(self_id), Some(other_id)) => self_id == other_id,
_ => true,
};
id_comp
&& self.device_id == other.device_id
&& self.action_type == other.action_type
&& self.parameter == other.parameter
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct ActionSet {
actions: Vec<Action>,
}
impl ActionSet {
pub fn push_device(&self) -> Option<String> {
self.iter()
.find(|action| action.action_type == ActionType::Push)
.map(|action| action.device_id.clone())
}
pub fn receive_device(&self) -> Option<String> {
self.iter()
.find(|action| action.action_type == ActionType::Receive)
.map(|action| action.device_id.clone())
}
pub fn begins_with_device(&self, device_name: &str) -> bool {
match self.actions.get(0) {
Some(action) => action.device_id == device_name,
None => false,
}
}
pub(crate) fn first_id(&self) -> Option<ActionID> {
self.actions.get(0).map(|action| action.id).flatten()
}
pub fn parameter(&self, parameter: &str) -> bool {
match self.actions.get(0) {
Some(action) => action.parameter == parameter,
None => false,
}
}
pub fn chain(&mut self, action: Action) {
self.actions.push(action);
}
pub fn iter(&self) -> Iter<'_, Action> {
self.actions.iter()
}
}
impl<I> From<I> for ActionSet
where
I: IntoIterator<Item = Action>,
{
fn from(value: I) -> Self {
Self {
actions: value.into_iter().collect(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use anyhow::Result;
#[test]
fn example_chain() -> Result<()> {
let mut action_set = ActionSet::default();
action_set.chain(Action::new(
"shelly_plus_ht",
ActionType::Push,
"temperature",
));
action_set.chain(Action::new(
"shelly_trv",
ActionType::Receive,
"temperature",
));
Ok(())
}
}

291
src/db.rs
View file

@ -1,9 +1,12 @@
use std::path::Path;
use std::{path::Path, str::FromStr};
use anyhow::Result;
use rusqlite::{Connection, OptionalExtension, ToSql};
use crate::devices::{DeviceWithName, Devices, DevicesWithName};
use crate::{
action::{Action, ActionID, ActionSet, ActionType},
devices::{DeviceWithName, Devices, DevicesWithName},
};
pub struct DataBase {
sql: Connection,
@ -33,7 +36,7 @@ impl DataBase {
)?;
self.sql.execute(
"CREATE TABLE IF NOT EXISTS devices(
"CREATE TABLE IF NOT EXISTS devices (
id INTEGER PRIMARY KEY,
device VARCHAR(60) NOT NULL,
type VARCHAR(30) NOT NULL,
@ -47,7 +50,8 @@ impl DataBase {
"CREATE TABLE IF NOT EXISTS data (
id INTEGER PRIMARY KEY,
time BIGINT NOT NULL,
watts REAL NOT NULL,
name VARCHAR(30) NOT NULL,
value REAL NOT NULL,
device_id INTEGER NOT NULL,
FOREIGN KEY(device_id) REFERENCES devices(id)
)",
@ -64,9 +68,176 @@ impl DataBase {
[],
)?;
self.sql.execute(
"
CREATE TABLE IF NOT EXISTS actions (
id INTEGER PRIMARY KEY,
device_id INTEGER NOT NULL,
action VARCHAR(30) NOT NULL,
parameter VARCHAR(60) NOT NULL,
action_id INTEGER,
FOREIGN KEY(action_id) REFERENCES actions(id),
FOREIGN KEY(device_id) REFERENCES devices(id)
)
",
[],
)?;
Ok(())
}
fn device_id(&self, device_name: &str) -> Result<i64> {
Ok(self
.sql
.prepare(&format!(
"
SELECT id
FROM devices
WHERE device=\"{}\"
",
device_name
))?
.query_row([], |row| Ok(row.get(0)?))?)
}
pub fn insert_action_set(&self, action_set: ActionSet) -> Result<ActionID> {
let mut action_ids = Vec::new();
for (i, action) in action_set.iter().enumerate() {
// get device id from device name
let device_id = self.device_id(&action.device_id)?;
// insert action to DB
self.sql.execute(
&format!(
"INSERT INTO actions (device_id, action, parameter)
VALUES (?1, \"{}\", \"{}\")",
action.action_type, action.parameter
),
&[&device_id],
)?;
action_ids.push(self.sql.last_insert_rowid());
if i > 0 {
// chain actions
self.sql.execute(
&format!(
"
UPDATE actions
SET action_id=?2
WHERE id=?1
"
),
[&action_ids[i - 1], &action_ids[i]],
)?;
}
}
Ok(ActionID(action_ids[0]))
}
pub fn remove_action_set(&self, action_set: &ActionSet) -> Result<()> {
if let Some(action_id) = action_set.first_id() {
self.sql.execute(
"
DELETE FROM actions
WHERE id=?1
",
&[&action_id.0],
)?;
}
Ok(())
}
pub fn action_set(&self, mut action_id: ActionID) -> Result<ActionSet> {
let mut action_set = ActionSet::default();
loop {
let (device_id, action, parameter, next_action): (i64, String, String, Option<i64>) =
self.sql.query_row(
"
SELECT device_id, action, parameter, action_id
FROM actions
WHERE id=?1
",
&[&action_id.0],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)?;
let device_name: String = self.sql.query_row(
"
SELECT device
FROM devices
WHERE id=?1
",
&[&device_id],
|row| row.get(0),
)?;
let mut action = Action::new(device_name, ActionType::from_str(&action)?, parameter);
action.id = Some(action_id);
action_set.chain(action);
match next_action {
Some(id) => action_id.0 = id,
None => break,
}
}
Ok(action_set)
}
pub fn action_sets(&self, device_name: &str) -> Result<Vec<ActionSet>> {
let mut action_sets = Vec::new();
let device_id = self.device_id(device_name)?;
let base_actions: Vec<i64> = self
.sql
.prepare(
"
SELECT id
FROM actions
WHERE device_id=?1
",
)?
.query_map(&[&device_id], |row| row.get(0))?
.map(|row| {
let r: i64 = row?;
Ok(r)
})
.collect::<Result<Vec<i64>>>()?;
for mut action_id in base_actions {
loop {
match self
.sql
.query_row(
"
SELECT id
FROM actions
WHERE action_id=?1
",
&[&action_id],
|row| row.get(0),
)
.optional()?
{
Some(id) => action_id = id,
None => {
action_sets.push(self.action_set(ActionID(action_id))?);
break;
}
}
}
}
Ok(action_sets)
}
pub fn version(&self) -> Result<String> {
Ok(self
.sql
@ -116,16 +287,50 @@ impl DataBase {
)?;
}
for device in devices.thermostat.iter() {
self.sql.execute(
&format!(
"INSERT INTO devices (device, type, control)
SELECT \"{device}\", \"thermostat\", false
WHERE
NOT EXISTS (
SELECT device
FROM devices
WHERE device=\"{device}\"
)
"
),
[],
)?;
}
for device in devices.thermometer.iter() {
self.sql.execute(
&format!(
"INSERT INTO devices (device, type, control)
SELECT \"{device}\", \"thermometer\", false
WHERE
NOT EXISTS (
SELECT device
FROM devices
WHERE device=\"{device}\"
)
"
),
[],
)?;
}
Ok(())
}
pub fn write(&self, device_name: &str, time: u64, watts: f32) -> Result<()> {
let params: &[&dyn ToSql] = &[&time, &watts];
pub fn write(&self, device_name: &str, time: u64, name: &str, value: f32) -> Result<()> {
let params: &[&dyn ToSql] = &[&time, &value];
self.sql.execute(
&format!(
"INSERT INTO data (time, watts, device_id)
VALUES (?1, ?2, (SELECT id FROM devices WHERE device=\"{device_name}\") )"
"INSERT INTO data (time, name, value, device_id)
VALUES (?1, \"{name}\", ?2, (SELECT id FROM devices WHERE device=\"{device_name}\") )"
),
params,
)?;
@ -156,6 +361,16 @@ impl DataBase {
desc: name,
toggle: control != 0,
}),
"thermostat" => devices.thermostat.push(DeviceWithName {
id: device,
desc: name,
toggle: control != 0,
}),
"thermometer" => devices.temperature_and_humidity.push(DeviceWithName {
id: device,
desc: name,
toggle: control != 0,
}),
_ => panic!(),
}
@ -164,6 +379,19 @@ impl DataBase {
Ok(devices)
}
pub fn device_exists(&self, device_id: &str) -> Result<bool> {
Ok(self
.sql
.prepare(&format!(
"
SELECT *
FROM devices
WHERE device=\"{device_id}\"
"
))?
.exists([])?)
}
pub fn change_device_name(&self, device: &str, description: &str) -> Result<()> {
self.sql.execute(
&format!(
@ -182,7 +410,7 @@ impl DataBase {
pub fn read(&self, device: &str) -> Result<Vec<(u64, f32)>> {
self._read(&format!(
"
SELECT data.time, data.watts
SELECT data.time, data.value
FROM data
INNER JOIN devices
ON data.device_id=devices.id
@ -194,7 +422,7 @@ impl DataBase {
pub fn read_range(&self, device: &str, start: u64, end: u64) -> Result<Vec<(u64, f32)>> {
self._read(&format!(
"
SELECT data.time, data.watts
SELECT data.time, data.value
FROM data
INNER JOIN devices
ON data.device_id=devices.id
@ -273,7 +501,10 @@ mod test {
use anyhow::Result;
use crate::devices::Devices;
use crate::{
action::{Action, ActionSet, ActionType},
devices::Devices,
};
use super::DataBase;
@ -293,6 +524,8 @@ mod test {
db.register_devices(&Devices {
plugs: vec![("test".to_string(), true)],
thermostat: Vec::new(),
thermometer: Vec::new(),
})?;
fs::remove_file("startup_test.db")?;
@ -308,9 +541,11 @@ mod test {
db.register_devices(&Devices {
plugs: vec![(device_name.to_string(), true)],
thermostat: Vec::new(),
thermometer: Vec::new(),
})?;
db.write(device_name, 0, 5.5)?;
db.write(device_name, 0, "watts", 5.5)?;
let device_descriptor = "udo";
db.change_device_name(device_name, device_descriptor)?;
@ -324,4 +559,36 @@ mod test {
Ok(())
}
#[tokio::test]
async fn action_set_test() -> Result<()> {
let db = DataBase::new("action_set_test.db").await?;
let thermometer = "shelly_plus_ht";
let thermostat = "shelly_trv";
db.register_devices(&Devices {
plugs: Vec::new(),
thermostat: vec![thermostat.to_string()],
thermometer: vec![thermometer.to_string()],
})?;
let mut action_set = ActionSet::default();
action_set.chain(Action::new(thermometer, ActionType::Push, "temperature"));
action_set.chain(Action::new(thermostat, ActionType::Receive, "temperature"));
let action_id = db.insert_action_set(action_set.clone())?;
let cmp_action_set = db.action_set(action_id)?;
assert_eq!(action_set, cmp_action_set);
let action_sets_thermometer = db.action_sets(thermometer)?;
let action_sets_thermostat = db.action_sets(thermostat)?;
assert_eq!(action_sets_thermometer, action_sets_thermostat);
fs::remove_file("action_set_test.db")?;
Ok(())
}
}

View file

@ -7,6 +7,8 @@ use serde_json::{from_str, to_string, to_string_pretty};
#[derive(Clone, PartialEq, Eq, Deserialize, Serialize, Debug)]
pub struct Devices {
pub plugs: Vec<(String, bool)>,
pub thermostat: Vec<String>,
pub thermometer: Vec<String>,
}
impl Devices {
@ -55,6 +57,8 @@ mod test {
fn create_conf() -> Result<()> {
let devices = Devices {
plugs: vec![("Dev1".to_string(), true), ("Dev2".to_string(), false)],
thermostat: Vec::new(),
thermometer: Vec::new(),
};
devices.save("test_devices.conf")

View file

@ -1,50 +1,68 @@
use std::{
fs,
sync::{Arc, Mutex},
thread,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use crate::{db::DataBase, midea_helper::MideaDiscovery, web_server::plug_data_range};
mod action;
mod data;
mod db;
mod devices;
mod midea_helper;
mod task_scheduler;
mod tasmota;
mod temperature;
mod tibber_handler;
mod web_server;
use actix_cors::Cors;
use actix_web::{web::Data, App, HttpServer};
use anyhow::Result;
use devices::Devices;
use futures::{future::try_join_all, try_join, Future};
use futures::{try_join, Future};
use midea_helper::MideaDishwasher;
use task_scheduler::{Scheduler, Task};
use tasmota::Tasmota;
use tibber::TimeResolution::Daily;
use tibber_handler::TibberHandler;
use web_server::*;
fn since_epoch() -> Result<u64> {
Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs())
}
fn read_power_usage(
fn handle_error(
f: impl Future<Output = Result<()>> + Send + 'static,
) -> impl Future<Output = ()> + Unpin + Send + 'static {
Box::pin(async move {
if let Err(err) = f.await {
println!("{err}:?");
}
})
}
fn setup_tasmota_tasks(
scheduler: &Scheduler,
tasmota_plugs: Vec<Tasmota>,
db: Arc<Mutex<DataBase>>,
) -> impl Future<Output = Result<()>> {
async move {
loop {
try_join_all(tasmota_plugs.iter().map(|plug| async {
if let Ok(usage) = plug.read_power_usage().await {
db.lock()
.unwrap()
.write(plug.name(), since_epoch()?, usage)?;
}
) {
for plug in tasmota_plugs.into_iter() {
let db_clone = db.clone();
Ok::<(), anyhow::Error>(())
}))
.await?;
let fut = async move {
if let Ok(usage) = plug.read_power_usage().await {
db_clone
.lock()
.unwrap()
.write(plug.name(), since_epoch()?, "watts", usage)?;
}
thread::sleep(Duration::from_secs(3));
}
Ok(())
};
scheduler.add_task(Task::looping(Duration::from_secs(3), handle_error(fut)));
}
}
@ -53,6 +71,7 @@ async fn run_web_server(
plugs: Vec<Tasmota>,
db: Arc<Mutex<DataBase>>,
dishwasher: Vec<Arc<MideaDishwasher>>,
scheduler: Scheduler,
) -> Result<()> {
const IP: &str = "0.0.0.0";
const PORT: u16 = 8062;
@ -71,12 +90,17 @@ async fn run_web_server(
.app_data(Data::new(db.clone()))
.app_data(Data::new(plugs.clone()))
.app_data(Data::new(dishwasher.clone()))
.app_data(Data::new(scheduler.clone()))
.service(device_query)
.service(plug_state)
.service(change_plug_state)
.service(change_device_name)
.service(plug_data)
.service(plug_data_range)
.service(push_temperature)
.service(push_humidity)
.service(update_push_action)
.service(actions)
})
.bind((IP, PORT))
.map_err(|err| anyhow::Error::msg(format!("failed binding to address: {err:#?}")))?
@ -90,8 +114,18 @@ async fn run_web_server(
async fn main() -> Result<()> {
let db_future = DataBase::new("home_server.db");
let devices_future = Devices::read("devices.conf");
let tibber_future = TibberHandler::new(fs::read_to_string("tibber_token.txt")?);
let (db, devices, midea) = try_join!(db_future, devices_future, MideaDiscovery::discover())?;
let (db, devices, tibber, midea) = try_join!(
db_future,
devices_future,
tibber_future,
MideaDiscovery::discover()
)?;
let prices_today = tibber.prices_today().await?;
let prices_tomorrow = tibber.prices_tomorrow().await?;
let consumption = tibber.consumption(Daily, 1).await?;
db.register_devices(&devices)?;
let shared_db = Arc::new(Mutex::new(db));
@ -108,9 +142,19 @@ async fn main() -> Result<()> {
.map(|d| Arc::new(d))
.collect();
let scheduler = Scheduler::default();
setup_tasmota_tasks(&scheduler, tasmota_plugs.clone(), shared_db.clone());
let scheduler_clone = scheduler.clone();
try_join!(
read_power_usage(tasmota_plugs.clone(), shared_db.clone()),
run_web_server(devices, tasmota_plugs, shared_db, dishwasher)
scheduler.run(),
run_web_server(
devices,
tasmota_plugs,
shared_db,
dishwasher,
scheduler_clone
)
)?;
Ok(())

View file

@ -11,10 +11,10 @@ enum LoginInfo {
}
impl LoginInfo {
const MIDEA_KEY_EMAIL: &str = "midea_cloud_mail";
const MIDEA_KEY_PW: &str = "midea_cloud_pw";
const MIDEA_KEY_TOKEN: &str = "midea_token";
const MIDEA_KEY_KEY: &str = "midea_key";
const MIDEA_KEY_EMAIL: &'static str = "midea_cloud_mail";
const MIDEA_KEY_PW: &'static str = "midea_cloud_pw";
const MIDEA_KEY_TOKEN: &'static str = "midea_token";
const MIDEA_KEY_KEY: &'static str = "midea_key";
fn new(db: &Arc<Mutex<DataBase>>, device_id: u64) -> Result<LoginInfo> {
let db_lock = db.lock().unwrap();

127
src/task_scheduler.rs Normal file
View file

@ -0,0 +1,127 @@
use anyhow::Result;
use futures::future::Shared;
use futures::FutureExt;
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::thread;
use std::{
sync::Mutex,
time::{Duration, SystemTime},
};
enum Callback {
Looping(Shared<Pin<Box<dyn Future<Output = ()> + Unpin + Send + 'static>>>),
Once(Pin<Box<dyn Future<Output = ()> + Unpin + Send + 'static>>),
}
pub struct Task {
creation_time: SystemTime,
time: Duration,
callback: Callback,
}
impl Task {
pub fn looping<F>(interval: Duration, f: F) -> Self
where
F: Future<Output = ()> + Unpin + Send + 'static,
{
let c: Pin<Box<dyn Future<Output = ()> + Unpin + Send + 'static>> = Box::pin(f);
Self {
creation_time: SystemTime::now(),
time: interval,
callback: Callback::Looping(c.shared()),
}
}
fn recreate(
start: SystemTime,
interval: Duration,
f: Shared<Pin<Box<dyn Future<Output = ()> + Unpin + Send + 'static>>>,
) -> Self {
Self {
creation_time: start,
time: interval,
callback: Callback::Looping(f),
}
}
pub fn one_shot<F>(time: Duration, f: F) -> Self
where
F: Future<Output = ()> + Unpin + Send + 'static,
{
Self {
creation_time: SystemTime::now(),
time,
callback: Callback::Once(Box::pin(f)),
}
}
fn execution_time(&self) -> SystemTime {
self.creation_time + self.time
}
}
#[derive(Default, Clone)]
pub struct Scheduler {
tasks: Arc<Mutex<VecDeque<Task>>>,
}
impl Scheduler {
pub fn add_task(&self, new_task: Task) {
let mut task_lock = self.tasks.lock().unwrap();
let pos = task_lock
.binary_search_by_key(&new_task.execution_time(), |task| task.execution_time())
.unwrap_or_else(|e| e);
task_lock.insert(pos, new_task);
}
pub fn run(self) -> impl Future<Output = Result<()>> {
async move {
loop {
// exec first if time is up
while let Some(first) = self.check_first() {
let execution_time = first.execution_time();
match first.callback {
Callback::Looping(callback) => {
let callback_clone = callback.clone();
tokio::spawn(callback_clone);
self.add_task(Task::recreate(execution_time, first.time, callback));
}
Callback::Once(callback) => {
tokio::spawn(callback);
}
}
}
thread::sleep(Duration::from_millis(500));
}
}
}
pub fn check_first(&self) -> Option<Task> {
let mut task_lock = self.tasks.lock().unwrap();
// get first element
if let Some(first) = task_lock.front() {
// check if execution time is reached
if first.execution_time() < SystemTime::now() {
return task_lock.pop_front();
}
}
return None;
}
}

View file

@ -96,55 +96,55 @@ impl Tasmota {
}
}
#[cfg(test)]
mod test {
use std::{thread, time::Duration};
// #[cfg(test)]
// mod test {
// use std::{thread, time::Duration};
use super::*;
// use super::*;
use anyhow::Result;
// use anyhow::Result;
#[tokio::test]
async fn test_connection() -> Result<()> {
let dev = Tasmota::new("Tasmota-Plug-1");
// #[tokio::test]
// async fn test_connection() -> Result<()> {
// let dev = Tasmota::new("Tasmota-Plug-1");
let power = dev.read_power_usage().await?;
// let power = dev.read_power_usage().await?;
println!("{power}");
// println!("{power}");
Ok(())
}
// Ok(())
// }
#[tokio::test]
async fn test_toggle() -> Result<()> {
let dev = Tasmota::new("Tasmota-Plug-4");
// #[tokio::test]
// async fn test_toggle() -> Result<()> {
// let dev = Tasmota::new("Tasmota-Plug-4");
dev.switch_off().await?;
assert_eq!(dev.power_state().await?, false);
// dev.switch_off().await?;
// assert_eq!(dev.power_state().await?, false);
thread::sleep(Duration::from_secs(5));
// thread::sleep(Duration::from_secs(5));
dev.switch_on().await?;
assert_eq!(dev.power_state().await?, true);
// dev.switch_on().await?;
// assert_eq!(dev.power_state().await?, true);
Ok(())
}
// Ok(())
// }
#[tokio::test]
async fn test_led() -> Result<()> {
let dev = Tasmota::new("Tasmota-Plug-4");
// #[tokio::test]
// async fn test_led() -> Result<()> {
// let dev = Tasmota::new("Tasmota-Plug-4");
dev.turn_off_led().await?;
assert_eq!(dev.led_state().await?, false);
// dev.turn_off_led().await?;
// assert_eq!(dev.led_state().await?, false);
thread::sleep(Duration::from_secs(5));
// thread::sleep(Duration::from_secs(5));
dev.turn_on_led().await?;
assert_eq!(dev.led_state().await?, true);
// dev.turn_on_led().await?;
// assert_eq!(dev.led_state().await?, true);
Ok(())
}
}
// Ok(())
// }
// }
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]

96
src/temperature.rs Normal file
View file

@ -0,0 +1,96 @@
use std::{
net::IpAddr,
sync::{Arc, Mutex},
time::Duration,
};
use actix_web::web::Data;
use anyhow::{bail, Result};
use dns_lookup::{lookup_addr, lookup_host};
use reqwest::Client;
use crate::{
db::DataBase,
since_epoch,
task_scheduler::{Scheduler, Task},
};
pub struct Thermostat {
device: String,
}
impl Thermostat {
pub fn new(device: impl ToString) -> Self {
Self {
device: device.to_string(),
}
}
pub async fn set_temperature(&self, temperature: f32) -> Result<()> {
let ips = lookup_host(&self.device)?;
if ips.is_empty() {
bail!("could not resolve device name {}", self.device);
}
let resp = Client::new()
.post(format!("http://{}/ext_t?temp={}", ips[0], temperature))
.send()
.await?;
if !resp.status().is_success() {
bail!("response error");
}
Ok(())
}
}
#[derive(Debug)]
pub enum ThermometerChange {
Temperature(f32),
Humidity(f32),
}
pub struct Thermometer;
impl Thermometer {
pub fn push_change(
change: ThermometerChange,
ip: IpAddr,
db: Data<Arc<Mutex<DataBase>>>,
scheduler: Data<Scheduler>,
) -> Result<()> {
let db_lock = db.lock().unwrap();
let device_id = lookup_addr(&ip)?.trim_end_matches(".fritz.box").to_string();
if db_lock.device_exists(&device_id)? {
match change {
ThermometerChange::Temperature(temp) => {
db_lock.write(&device_id, since_epoch()?, "temperature", temp)?;
for action_set in db_lock.action_sets(&device_id)? {
if let Some(push_device) = action_set.push_device() {
if action_set.parameter("temperature") && push_device == device_id {
if let Some(receive_device) = action_set.receive_device() {
scheduler.add_task(Task::one_shot(
Duration::from_secs(0),
Box::pin(async move {
let _ = Thermostat::new(receive_device)
.set_temperature(temp);
}),
));
}
}
}
}
}
ThermometerChange::Humidity(humid) => {
db_lock.write(&device_id, since_epoch()?, "humidity", humid)?;
}
}
}
Ok(())
}
}

149
src/tibber_handler.rs Normal file
View file

@ -0,0 +1,149 @@
use std::sync::Arc;
use anyhow::{Error, Result};
use tibber::{Consumption, HomeId, House, PriceInfo, TibberSession, TimeResolution, User};
pub struct TibberHandler {
session: Arc<TibberSession>,
pub user: User,
pub homes: Vec<(HomeId, House)>,
}
impl TibberHandler {
pub async fn new(token: impl ToString + Send + 'static) -> Result<Self> {
tokio::task::spawn_blocking(move || {
let session = Arc::new(TibberSession::new(token.to_string()));
let user = session.get_user().map_err(|err| {
Error::msg(format!(
"TibberHandler: failed getting user information: {err:?}"
))
})?;
let mut homes = Vec::new();
for home_id in user.homes.clone().into_iter() {
let house = session.get_home(&home_id).map_err(|err| {
Error::msg(format!(
"TibberHandler: failed getting house information: {err:?}"
))
})?;
homes.push((home_id, house));
}
Ok(Self {
homes,
user,
session,
})
})
.await?
}
async fn get_data<F, T>(&self, f: F) -> Result<Vec<(House, T)>>
where
F: Fn(&TibberSession, &HomeId) -> Result<T> + Send + Sync + Copy + 'static,
T: Send + Sync + 'static,
{
let mut v = Vec::new();
for (home_id, house) in self.homes.iter() {
v.push((
house.clone(),
tokio::task::spawn_blocking({
let session = self.session.clone();
let home_id = home_id.clone();
move || f(&session, &home_id)
})
.await??,
));
}
Ok(v)
}
pub async fn current_prices(&self) -> Result<Vec<(House, PriceInfo)>> {
self.get_data(|session, home_id| {
session.get_current_price(home_id).map_err(|err| {
Error::msg(format!(
"TibberHandler: failed getting current price: {err:?}"
))
})
})
.await
}
pub async fn prices_today(&self) -> Result<Vec<(House, Vec<PriceInfo>)>> {
self.get_data(|session, home_id| {
session.get_prices_today(home_id).map_err(|err| {
Error::msg(format!(
"TibberHandler: failed getting prices of today: {err:?}"
))
})
})
.await
}
pub async fn prices_tomorrow(&self) -> Result<Vec<(House, Vec<PriceInfo>)>> {
self.get_data(|session, home_id| {
session.get_prices_tomorrow(home_id).map_err(|err| {
Error::msg(format!(
"TibberHandler: failed getting prices for tomorrow: {err:?}"
))
})
})
.await
}
pub async fn consumption(
&self,
resolution: TimeResolution,
last: u32,
) -> Result<Vec<(House, Vec<Consumption>)>> {
let mut v = Vec::new();
for (home_id, house) in self.homes.iter() {
v.push((
house.clone(),
tokio::task::spawn_blocking({
let session = self.session.clone();
let home_id = home_id.clone();
let resolution = resolution.clone();
move || {
session
.get_consuption(&home_id, resolution, last)
.map_err(|err| {
Error::msg(format!(
"TibberHandler: failed getting consumption: {err:?}"
))
})
}
})
.await??,
));
}
Ok(v)
}
}
// #[cfg(test)]
// mod test {
// use super::TibberHandler;
// use anyhow::Result;
// use std::fs;
// #[tokio::test]
// async fn test_connection() -> Result<()> {
// let tibber = TibberHandler::new(fs::read_to_string("tibber_token.txt")?).await?;
// let current_prices = tibber.current_prices().await?;
// println!("{current_prices:?}");
// Ok(())
// }
// }

View file

@ -1,13 +1,19 @@
use actix_web::{
get, post,
web::{Data, Json, Path},
Error, Responder, ResponseError,
Error, HttpRequest, Responder, ResponseError,
};
use chrono::{Datelike, NaiveDateTime, Timelike};
use serde::Serialize;
use serde_json::to_string;
use crate::{db::DataBase, tasmota::Tasmota};
use crate::{
action::{Action, ActionSet, ActionType},
db::DataBase,
task_scheduler::Scheduler,
tasmota::Tasmota,
temperature::{Thermometer, ThermometerChange},
};
use std::{
collections::HashMap,
@ -226,6 +232,102 @@ async fn plug_data_range(
)?))
}
#[get("/push_temp/{temperature}")]
async fn push_temperature(
param: Path<f32>,
req: HttpRequest,
db: Data<Arc<Mutex<DataBase>>>,
scheduler: Data<Scheduler>,
) -> Result<impl Responder, Error> {
if let Some(val) = req.peer_addr() {
Thermometer::push_change(
ThermometerChange::Temperature(param.into_inner()),
val.ip(),
db,
scheduler,
)
.map_err(|err| MyError::from(err))?;
}
Ok("Ok")
}
#[get("/push_humid/{humidity}")]
async fn push_humidity(
param: Path<f32>,
req: HttpRequest,
db: Data<Arc<Mutex<DataBase>>>,
scheduler: Data<Scheduler>,
) -> Result<impl Responder, Error> {
if let Some(val) = req.peer_addr() {
Thermometer::push_change(
ThermometerChange::Humidity(param.into_inner()),
val.ip(),
db,
scheduler,
)
.map_err(|err| MyError::from(err))?;
}
Ok("Ok")
}
#[post("/update_push_action/{source_device}/{parameter}/{destination_device}")]
async fn update_push_action(
param: Path<(String, String, String)>,
db: Data<Arc<Mutex<DataBase>>>,
) -> Result<impl Responder, Error> {
let (source_device, parameter, destination_device) = param.into_inner();
let db_lock = db.lock().unwrap();
let action_sets = db_lock
.action_sets(&source_device)
.map_err(|err| MyError::from(err))?;
// check if action set is already present
if let Some(old_action_set) = action_sets.iter().find(|action_set| {
action_set.push_device() == Some(source_device.clone())
&& action_set.receive_device() == Some(destination_device.clone())
&& action_set.parameter(&parameter)
}) {
// remove old action set
db_lock
.remove_action_set(old_action_set)
.map_err(|err| MyError::from(err))?;
}
let new_action_set = ActionSet::from(vec![
Action::new(source_device, ActionType::Push, parameter.clone()),
Action::new(destination_device, ActionType::Receive, parameter),
]);
db_lock
.insert_action_set(new_action_set)
.map_err(|err| MyError::from(err))?;
Ok("Ok")
}
#[get("/actions/{device}")]
async fn actions(
param: Path<String>,
db: Data<Arc<Mutex<DataBase>>>,
) -> Result<impl Responder, Error> {
let device_name = param.into_inner();
let db_lock = db.lock().unwrap();
let action_sets: Vec<ActionSet> = db_lock
.action_sets(&device_name)
.map_err(|err| MyError::from(err))?
.into_iter()
.filter(|action_set| action_set.begins_with_device(&device_name))
.collect();
Ok(Json(
to_string(&action_sets).map_err(|err| MyError::from(anyhow::Error::from(err)))?,
))
}
fn collapse_data<F>(data: Vec<(u64, f32)>, f: F) -> Vec<(u64, f32)>
where
F: Fn(NaiveDateTime) -> NaiveDateTime,

1
tibber_token.txt Normal file
View file

@ -0,0 +1 @@
dkvF6ax77CP4v9-sxYyjWUD-9NunpzVPRYHfLJ8a9ps