Compare commits

...

13 Commits

Author SHA1 Message Date
Paul van Tilburg beb49373fb
Bump the version to 0.2.2
Check Details
Lints Details
2023-03-22 15:00:35 +01:00
Paul van Tilburg 99e7e8a68c
Update the changelog 2023-03-22 14:59:18 +01:00
Paul van Tilburg bab9228b0f
Drop unused dependency on the toml crate 2023-03-22 13:38:16 +01:00
Paul van Tilburg 81e82e90da
Cargo update; fixes RUSTSEC-2023-0018 2023-03-22 13:37:56 +01:00
Paul van Tilburg b07bb73da4
Fix clippy issue
Check Details
Lints Details
2023-03-21 12:10:52 +01:00
Paul van Tilburg 58759d5309
Add Gitea Actions workflow for cargo
Check Details
Lints Details
2023-03-21 11:53:05 +01:00
Paul van Tilburg b1764b7fe3
Use 7 chars for the git SHA 2023-01-29 15:44:29 +01:00
Paul van Tilburg 7c704b69ed Merge pull request 'Print the version on lift off and add version endpoint' (#10) from 6-print-version-add-endpoint into main
Reviewed-on: #10
2023-01-29 15:37:14 +01:00
Paul van Tilburg 04e28a33c3
Add a /version API endpoint
* Introduce the `VersionInfo` struct, build from the vergen environment
  variables
* Add the `version` handler to construct and return the version info
* Update the README
2023-01-29 15:29:32 +01:00
Paul van Tilburg de1ad37b95
Use the vergen crate to generate version information
* Add depend on the `vergen` crate (only use the `build` and `git`
  features)
* Add the build script `build.rs` to setup the environment variables
  from the build system
* Update the printed version information to use these environment
  variables
2023-01-29 15:22:47 +01:00
Paul van Tilburg 5cbc3a04fc
Print the version on lift off 2023-01-16 21:18:20 +01:00
Paul van Tilburg ca116351db
Implement error catchers for all requests (closes: #5)
* Introduce an error JSON output
* Return error JSON output if the status data is not there (yet)
* Introduce a default catcher to return error JSON output in all other
  unsupported/unhandled cases
* Update the documentation
2023-01-16 21:08:03 +01:00
Paul van Tilburg 8d892d8619
Tweak documentation (to look like the rest) 2023-01-16 21:05:15 +01:00
7 changed files with 685 additions and 291 deletions

View File

@ -0,0 +1,83 @@
name: "Check, Test and Lint Using Cargo"
on:
- push
- pull_request
- workflow_dispatch
jobs:
check:
name: Check
runs-on: debian-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install stable toolchain
uses: https://github.com/actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Use sparse Cargo index for crates.io
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
- name: Run cargo check
uses: https://github.com/actions-rs/cargo@v1
with:
command: check
# TODO: Add a test suite first!
# test:
# name: Test Suite
# runs-on: debian-latest
# steps:
# - name: Checkout sources
# uses: actions/checkout@v3
#
# - name: Install stable toolchain
# uses: https://github.com/actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: stable
# override: true
#
# - name: Use sparse Cargo index for crates.io
# run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
#
# - name: Run cargo test
# uses: https://github.com/actions-rs/cargo@v1
# with:
# command: test
# args: --all-features
lints:
name: Lints
runs-on: debian-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install stable toolchain
uses: https://github.com/actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Use sparse Cargo index for crates.io
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
- name: Run cargo fmt
uses: https://github.com/actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Run cargo clippy
uses: https://github.com/actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings

View File

@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.2] - 2023-03-22
### Added
* Implement error catchers for all endpoints (#5)
* Print the version on lift off (#6)
* Add `/version` endpoint to the API (#6)
* Add Gitea Actions workflow for cargo
### Fixed
* Fixed/tweaked documentation
### Security
* Update dependencies ([RUSTSEC-2023-0018](https://rustsec.org/advisories/RUSTSEC-2023-0018.html))
## [0.2.1] - 2023-01-16
### Changed
@ -48,7 +65,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Rename Autarco Scraper project to Solar Grabber.
[Unreleased]: https://git.luon.net/paul/solar-grabber/compare/v0.2.1...HEAD
[Unreleased]: https://git.luon.net/paul/solar-grabber/compare/v0.2.2...HEAD
[0.2.2]: https://git.luon.net/paul/solar-grabber/compare/v0.2.1...v0.2.2
[0.2.1]: https://git.luon.net/paul/solar-grabber/compare/v0.2.0...v0.2.1
[0.2.0]: https://git.luon.net/paul/solar-grabber/compare/v0.1.1...v0.2.0
[0.1.1]: https://git.luon.net/paul/solar-grabber/src/tag/v0.1.1

729
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "solar-grabber"
version = "0.2.1"
version = "0.2.2"
authors = ["Paul van Tilburg <paul@luon.net>"]
edition = "2021"
description = """"
@ -10,6 +10,7 @@ get statistical data of your solar panels.
readme = "README.md"
repository = "https://git.luon.net/paul/solar-grabber"
license = "MIT"
build = "build.rs"
[dependencies]
chrono = { version = "0.4.23", features = ["serde"] }
@ -20,9 +21,12 @@ once_cell = "1.9.0"
reqwest = { version = "0.11.6", features = ["cookies", "json"] }
rocket = { version = "0.5.0-rc.2", features = ["json"] }
thiserror = "1.0.38"
toml = "0.5.6"
url = "2.2.2"
[build-dependencies]
anyhow = "1.0.68"
vergen = { version = "7.5.0", default_features = false, features = ["build", "git"] }
[package.metadata.deb]
maintainer = "Paul van Tilburg <paul@luon.net>"
copyright = "2022, Paul van Tilburg"

View File

@ -82,7 +82,7 @@ This also uses `Rocket.toml` from the current working directory as configuration
You can alternatively pass a set of environment variables instead. See
`docker-compose.yml` for a list.
## API endpoint
## Status API Endpoint
The `/` API endpoint provides the current statistical data of your solar panels
once it has successfully logged into the cloud service using your credentials.
@ -92,9 +92,9 @@ There is no path and no query parameters, just:
GET /
```
### Response
### Status API Response
A response uses the JSON format and typically looks like this:
The response uses the JSON format and typically looks like this:
```json
{"current_w":23.0,"total_kwh":6159.0,"last_updated":1661194620}
@ -104,6 +104,36 @@ This contains the current production power (`current_w`) in Watt,
the total of produced energy since installation (`total_kwh`) in kilowatt-hour
and the (UNIX) timestamp that indicates when the information was last updated.
### (Status) API Error Response
If the API endpoint is accessed before any statistical data has been retrieved,
or if any other request than `GET /` is made, an error response is returned
that looks like this:
```json
{"error":"No status found (yet)"}
```
## Version API Endpoint
The `/version` endpoint provides information of the current version and build
of the service. This can be used to check if it needs to be updated.
Again, there is no path and no query parameters, just:
```http
GET /version
```
### Version API Response
The response uses the JSON format and typically looks like this:
```json
{"version":"0.2.1","timestamp":"2023-01-29T14:10:24.971748027Z","git_sha":"5cbc3a04","git_timestamp":"2023-01-16T20:18:20Z"}
```
(Build and git information may be out of date.)
## Integration in Home Assistant
To integrate the Solar Grabber service in your [Home Assistant](https://www.home-assistant.io/)

7
build.rs Normal file
View File

@ -0,0 +1,7 @@
use anyhow::Result;
use vergen::{vergen, Config};
fn main() -> Result<()> {
// Generate the `cargo:` instructions to fill the appropriate environment variables.
vergen(Config::default())
}

View File

@ -20,12 +20,12 @@ mod update;
use std::sync::Mutex;
use once_cell::sync::Lazy;
use rocket::fairing::AdHoc;
use rocket::serde::json::Json;
use rocket::{
catch, catchers,
fairing::AdHoc,
get, routes,
serde::{Deserialize, Serialize},
Build, Rocket,
serde::{json::Json, Deserialize, Serialize},
Build, Request, Rocket,
};
use self::update::update_loop;
@ -45,25 +45,87 @@ struct Config {
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct Status {
/// Current power production (W)
/// The current power production (W).
current_w: f32,
/// Total energy produced since installation (kWh)
/// The total energy produced since installation (kWh).
total_kwh: f32,
/// Timestamp of last update
/// The (UNIX) timestamp of when the status was last updated.
last_updated: u64,
}
/// An error used as JSON response.
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct Error {
/// The error message.
error: String,
}
impl Error {
/// Creates a new error result from a message.
fn from(message: impl AsRef<str>) -> Self {
let error = String::from(message.as_ref());
Self { error }
}
}
/// Returns the current (last known) status.
#[get("/", format = "application/json")]
async fn status() -> Option<Json<Status>> {
async fn status() -> Result<Json<Status>, Json<Error>> {
let status_guard = STATUS.lock().expect("Status mutex was poisoined");
status_guard.map(Json)
status_guard
.map(Json)
.ok_or_else(|| Json(Error::from("No status found (yet)")))
}
/// The version information as JSON response.
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct VersionInfo {
/// The version of the build.
version: String,
/// The timestamp of the build.
timestamp: String,
/// The (most recent) git SHA used for the build.
git_sha: String,
/// The timestamp of the last git commit used for the build.
git_timestamp: String,
}
impl VersionInfo {
/// Retrieves the version information from the environment variables.
fn new() -> Self {
Self {
version: String::from(env!("VERGEN_BUILD_SEMVER")),
timestamp: String::from(env!("VERGEN_BUILD_TIMESTAMP")),
git_sha: String::from(&env!("VERGEN_GIT_SHA")[0..7]),
git_timestamp: String::from(env!("VERGEN_GIT_COMMIT_TIMESTAMP")),
}
}
}
/// Returns the version information.
#[get("/version", format = "application/json")]
async fn version() -> Result<Json<VersionInfo>, Json<Error>> {
Ok(Json(VersionInfo::new()))
}
/// Default catcher for any unsuppored request
#[catch(default)]
fn unsupported(status: rocket::http::Status, _request: &Request<'_>) -> Json<Error> {
let code = status.code;
Json(Error::from(format!(
"Unhandled/unsupported API call or path (HTTP {code})"
)))
}
/// Creates a Rocket and attaches the config parsing and update loop as fairings.
pub fn setup() -> Rocket<Build> {
rocket::build()
.mount("/", routes![status])
.mount("/", routes![status, version])
.register("/", catchers![unsupported])
.attach(AdHoc::config::<Config>())
.attach(AdHoc::on_liftoff("Updater", |rocket| {
Box::pin(async move {
@ -74,7 +136,16 @@ pub fn setup() -> Rocket<Build> {
let service = services::get(config.service).expect("Invalid service");
// We don't care about the join handle nor error results?t
let _ = rocket::tokio::spawn(update_loop(service));
let _service = rocket::tokio::spawn(update_loop(service));
})
}))
.attach(AdHoc::on_liftoff("Version", |_| {
Box::pin(async move {
let name = env!("CARGO_PKG_NAME");
let version = env!("VERGEN_BUILD_SEMVER");
let git_sha = &env!("VERGEN_GIT_SHA")[0..7];
println!("☀️ Started {name} v{version} (git @{git_sha})");
})
}))
}