diff --git a/central_backend/src/devices/device.rs b/central_backend/src/devices/device.rs index 3366ef0..258cbcb 100644 --- a/central_backend/src/devices/device.rs +++ b/central_backend/src/devices/device.rs @@ -9,7 +9,7 @@ use std::collections::{HashMap, HashSet}; #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct DeviceInfo { /// Device reference - reference: String, + pub reference: String, /// Device firmware / software version version: semver::Version, /// Maximum number of relay that the device can support @@ -62,6 +62,9 @@ pub struct Device { /// /// There cannot be more than [info.max_relays] relays pub relays: Vec, + /// Desired version, ie. the version of the software we would to seen run on the device + #[serde(skip_serializing_if = "Option::is_none")] + pub desired_version: Option, } /// Structure that contains information about the minimal expected execution diff --git a/central_backend/src/devices/devices_list.rs b/central_backend/src/devices/devices_list.rs index 9f196de..dbca28d 100644 --- a/central_backend/src/devices/devices_list.rs +++ b/central_backend/src/devices/devices_list.rs @@ -84,6 +84,7 @@ impl DevicesList { validated: false, enabled: false, relays: vec![], + desired_version: None, }; // First, write CSR @@ -186,6 +187,24 @@ impl DevicesList { Ok(()) } + /// Set a device desired version + pub fn set_desired_version( + &mut self, + id: &DeviceId, + version: Option, + ) -> anyhow::Result<()> { + let dev = self + .0 + .get_mut(id) + .ok_or(DevicesListError::UpdateDeviceFailedDeviceNotFound)?; + + dev.desired_version = version; + + self.persist_dev_config(id)?; + + Ok(()) + } + /// Get single certificate information fn get_cert(&self, id: &DeviceId) -> anyhow::Result { let dev = self diff --git a/central_backend/src/energy/energy_actor.rs b/central_backend/src/energy/energy_actor.rs index 35426be..e1f64c2 100644 --- a/central_backend/src/energy/energy_actor.rs +++ b/central_backend/src/energy/energy_actor.rs @@ -195,6 +195,27 @@ impl Handler for EnergyActor { } } +/// Set device desired version +#[derive(Message)] +#[rtype(result = "anyhow::Result<()>")] +pub struct SetDesiredVersion(pub DeviceId, pub Option); + +impl Handler for EnergyActor { + type Result = anyhow::Result<()>; + + fn handle(&mut self, msg: SetDesiredVersion, _ctx: &mut Context) -> Self::Result { + log::info!( + "Requested to update device desired version {:?} => {:#?}", + &msg.0, + &msg.1 + ); + + self.devices.set_desired_version(&msg.0, msg.1)?; + + Ok(()) + } +} + /// Delete a device #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] diff --git a/central_backend/src/server/servers.rs b/central_backend/src/server/servers.rs index 95d973c..bc8dffe 100644 --- a/central_backend/src/server/servers.rs +++ b/central_backend/src/server/servers.rs @@ -189,11 +189,13 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()> "/web_api/ota/{platform}/{version}", web::post().to(ota_controller::upload_firmware), ) - // TODO : upload a new software update // TODO : list ota software update per platform // TODO : download a OTA file // TODO : delete an OTA file - // TODO : deploy an update to a device + .route( + "/web_api/ota/set_desired_version", + web::post().to(ota_controller::set_desired_version), + ) // Logging controller API .route( "/web_api/logging/logs", diff --git a/central_backend/src/server/web_api/ota_controller.rs b/central_backend/src/server/web_api/ota_controller.rs index 89fde65..e5f6f4d 100644 --- a/central_backend/src/server/web_api/ota_controller.rs +++ b/central_backend/src/server/web_api/ota_controller.rs @@ -1,7 +1,10 @@ use crate::constants; +use crate::devices::device::DeviceId; +use crate::energy::energy_actor; use crate::ota::ota_manager; use crate::ota::ota_update::OTAPlatform; use crate::server::custom_error::HttpResult; +use crate::server::WebEnergyActor; use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::MultipartForm; use actix_web::{web, HttpResponse}; @@ -49,3 +52,48 @@ pub async fn upload_firmware( Ok(HttpResponse::Accepted().body("OTA update successfully saved.")) } + +#[derive(serde::Deserialize)] +pub struct SetDesiredDeviceVersion { + devices: Option>, + platform: Option, + version: semver::Version, +} + +pub async fn set_desired_version( + actor: WebEnergyActor, + body: web::Json, +) -> HttpResult { + if body.devices.is_none() && body.platform.is_none() { + return Ok( + HttpResponse::BadRequest().json("Must specify one filter to select target devices!") + ); + } + + let devices = actor.send(energy_actor::GetDeviceLists).await?; + + for d in devices { + // Filter per platform + if let Some(p) = body.platform { + if d.info.reference != p.to_string() { + continue; + } + } + + // Filter per device + if let Some(ids) = &body.devices { + if !ids.contains(&d.id) { + continue; + } + } + + actor + .send(energy_actor::SetDesiredVersion( + d.id, + Some(body.version.clone()), + )) + .await??; + } + + Ok(HttpResponse::Ok().finish()) +} diff --git a/central_frontend/src/api/DeviceApi.ts b/central_frontend/src/api/DeviceApi.ts index a63f811..cbc4e7d 100644 --- a/central_frontend/src/api/DeviceApi.ts +++ b/central_frontend/src/api/DeviceApi.ts @@ -37,6 +37,7 @@ export interface Device { validated: boolean; enabled: boolean; relays: DeviceRelay[]; + desired_version?: string; } export interface UpdatedInfo { diff --git a/central_frontend/src/routes/DeviceRoute/GeneralDeviceInfo.tsx b/central_frontend/src/routes/DeviceRoute/GeneralDeviceInfo.tsx index 27f21a6..2174584 100644 --- a/central_frontend/src/routes/DeviceRoute/GeneralDeviceInfo.tsx +++ b/central_frontend/src/routes/DeviceRoute/GeneralDeviceInfo.tsx @@ -41,6 +41,10 @@ export function GeneralDeviceInfo(p: { value={p.device.info.reference} /> +