added method to upload filesystem images
All checks were successful
Build Project / test (push) Successful in 5m58s

This commit is contained in:
2025-02-18 20:47:31 +00:00
parent a5e374b79f
commit 5e0ba37bc7
6 changed files with 81 additions and 40 deletions

View File

@@ -227,13 +227,11 @@ mod tests {
let db = Database::init_from_pool(pool); let db = Database::init_from_pool(pool);
let device_id = MacAddress::from([0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F]); let device_id = MacAddress::from([0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F]);
db.add_device(&device_id) db.add_device(&device_id).await.unwrap();
.await let msg = ValueMessageFromDevice {
.unwrap();
let msg = ValueMessageFromDevice{
active_errors: 0, active_errors: 0,
value: 112.0, value: 112.0,
value_id: 1 value_id: 1,
}; };
db.add_value(&msg, &device_id).await.unwrap(); db.add_value(&msg, &device_id).await.unwrap();
@@ -242,6 +240,4 @@ mod tests {
assert!((values[0].value - msg.value).abs() < 1e-5); assert!((values[0].value - msg.value).abs() < 1e-5);
assert_eq!(values[0].value_id, msg.value_id); assert_eq!(values[0].value_id, msg.value_id);
} }
} }

View File

@@ -1,9 +1,11 @@
use actix_web::{get, post, put, web, HttpResponse, Responder}; use actix_web::{get, post, put, web, HttpResponse, Responder};
use log::{error, info}; use log::{error, info};
use sqlx::types::mac_address::MacAddress; use sqlx::types::mac_address::MacAddress;
use crate::{schemas::{AppState, DeviceMetadata, TelemetryMessageFromDevice, ValueMessageFromDevice}, util::parse_mac_address}; use crate::{
schemas::{AppState, DeviceMetadata, TelemetryMessageFromDevice, ValueMessageFromDevice},
util::parse_mac_address,
};
#[post("/telemetry/{device_id}")] #[post("/telemetry/{device_id}")]
async fn receive_telemetry( async fn receive_telemetry(
@@ -113,8 +115,12 @@ async fn get_devices(data: web::Data<AppState>) -> impl Responder {
} }
#[put("/device/{device_id}/display_name")] #[put("/device/{device_id}/display_name")]
async fn set_display_name(data: web::Data<AppState>, device_id: web::Path<String>, device_data: web::Json<DeviceMetadata>) -> impl Responder { async fn set_display_name(
info!("PUT - device/{device_id} - Display Name", ); data: web::Data<AppState>,
device_id: web::Path<String>,
device_data: web::Json<DeviceMetadata>,
) -> impl Responder {
info!("PUT - device/{device_id} - Display Name",);
let display_name = device_data.0.display_name; let display_name = device_data.0.display_name;
let db = &data.db; let db = &data.db;
let Ok(mac_converted) = parse_mac_address(&device_id) else { let Ok(mac_converted) = parse_mac_address(&device_id) else {
@@ -122,10 +128,10 @@ async fn set_display_name(data: web::Data<AppState>, device_id: web::Path<String
return HttpResponse::InternalServerError().finish(); return HttpResponse::InternalServerError().finish();
}; };
let mac_converted = MacAddress::from(mac_converted); let mac_converted = MacAddress::from(mac_converted);
if let Ok(()) = db.update_display_name(&mac_converted, &display_name).await { HttpResponse::Ok().finish() } else { if let Ok(()) = db.update_display_name(&mac_converted, &display_name).await {
HttpResponse::Ok().finish()
} else {
error!("Failed to update/set displayName for device"); error!("Failed to update/set displayName for device");
HttpResponse::InternalServerError().finish() HttpResponse::InternalServerError().finish()
} }
} }

View File

@@ -2,46 +2,65 @@ use std::{fs, path::PathBuf};
use actix_web::{get, put, web, HttpResponse, Responder}; use actix_web::{get, put, web, HttpResponse, Responder};
use log::{debug, info, warn}; use log::{debug, info, warn};
use std::str::FromStr;
use crate::{schemas::{AppState, OTAConfigurationList}, util::get_files}; use crate::{
schemas::{AppState, OTAConfigurationList, Services},
util::get_files,
};
// Upload Firmware file // Upload Firmware file
#[put("/firmware/{device}/{config}/{version}")] #[put("/{service}/{device}/{config}/{version}")]
async fn upload_firmware( async fn upload_firmware(
data: web::Data<AppState>, data: web::Data<AppState>,
path: web::Path<(String, String, String)>, path: web::Path<(String, String, String, String)>,
body: web::Bytes, body: web::Bytes,
) -> impl Responder { ) -> impl Responder {
let (device, config, version) = path.into_inner(); let (service, device, config, version) = path.into_inner();
let version = version.replace('.', "-"); let version = version.replace('.', "-");
let firmware_root_path = &data.firmwares_path; let firmware_root_path = &data.firmwares_path;
let Ok(service) = Services::from_str(&service) else {
return HttpResponse::NotFound().finish();
};
let firmware_folder = firmware_root_path.join(&device); let firmware_folder = match service {
Services::Firmware => firmware_root_path.join(&device),
Services::Filesystem => firmware_root_path.join(format!("{}_filesystem", &device))
};
let firmware_path = firmware_folder let firmware_path = firmware_folder
.join(format!("firmware_{config}_{version}")) .join(format!("{service}_{}_{version}", config.to_lowercase()))
.with_extension("bin"); .with_extension("bin");
if firmware_path.is_file() { if firmware_path.is_file() {
warn!("Firmware with product: {device}, config: {config} and version: {version} at path {firmware_path:?} already exists, cant upload"); warn!("{service} with product: {device}, config: {config} and version: {version} at path {firmware_path:?} already exists, cant upload");
return HttpResponse::Conflict().body(format!("{firmware_path:?}")); return HttpResponse::Conflict().body(format!("{firmware_path:?}"));
} }
info!("Uploading firmware with product: {device}, config: {config} and version: {version} to {firmware_path:?}"); info!("Uploading {service} with product: {device}, config: {config} and version: {version} to {firmware_path:?}");
fs::create_dir_all(&firmware_folder).unwrap(); fs::create_dir_all(&firmware_folder).unwrap();
let x = tokio::fs::write(&firmware_path, &body).await; let x = tokio::fs::write(&firmware_path, &body).await;
debug!("{x:?}"); debug!("{x:?}");
HttpResponse::Ok().body(format!( HttpResponse::Ok().body(format!("Firmware version {version} uploaded successfully"))
"Firmware version {version} uploaded successfully"
))
} }
#[get("/firmware/{device}")] #[get("/{service}/{device}")]
async fn get_firmware_json(data: web::Data<AppState>, path: web::Path<String>) -> impl Responder { async fn get_firmware_json(
let device = path.into_inner(); data: web::Data<AppState>,
let fw_path = &data.firmwares_path.join(device); path: web::Path<(String, String)>,
) -> impl Responder {
let (service, device) = path.into_inner();
let Ok(firmware_or_filesystem) = Services::from_str(&service) else {
return HttpResponse::NotFound().finish();
};
let fw_path = match firmware_or_filesystem {
Services::Firmware => &data.firmwares_path.join(device),
Services::Filesystem => &data.firmwares_path.join(format!("{device}_filesystem"))
};
if fw_path.is_dir() { if fw_path.is_dir() {
match get_files(fw_path, &data.hostname) { match get_files(fw_path, &data.hostname) {
Ok(cfg) => HttpResponse::Ok().json(OTAConfigurationList { Ok(cfg) => HttpResponse::Ok().json(OTAConfigurationList {
@@ -56,17 +75,27 @@ async fn get_firmware_json(data: web::Data<AppState>, path: web::Path<String>) -
} }
} }
#[get("/firmware/{product}/{config}/{version}.bin")] #[get("/{service}/{product}/{config}/{version}.bin")]
async fn serve_firmware( async fn serve_firmware(
path: web::Path<(String, String, String)>, path: web::Path<(String, String, String, String)>,
data: web::Data<AppState>, data: web::Data<AppState>,
) -> impl Responder { ) -> impl Responder {
let (product, config, version) = path.into_inner(); let (service, product, config, version) = path.into_inner();
let version = version.replace(['.', '_'], "-"); let version = version.replace(['.', '_'], "-");
let Ok(service) = Services::from_str(&service) else {
return HttpResponse::NotFound().finish();
};
let fw_root_path = &data.firmwares_path; let fw_root_path = &data.firmwares_path;
let file_path = PathBuf::from(format!("{product}/firmware_{config}_{version}.bin"));
let file_path = match service {
Services::Firmware => PathBuf::from(format!("{product}/firmware_{config}_{version}.bin")),
Services::Filesystem => PathBuf::from(format!("{product}/filesystem_{config}_{version}.bin")),
};
let file_path = fw_root_path.join(&file_path); let file_path = fw_root_path.join(&file_path);
info!("Requested firmware for product: {product}, config: {config} and version: {version}, expected to be stored at {file_path:?}"); info!("Requested firmware for product: {product}, config: {config} and version: {version}, expected to be stored at {file_path:?}");
if file_path.exists() { if file_path.exists() {

View File

@@ -7,11 +7,10 @@ use log::{error, info};
use schemas::AppState; use schemas::AppState;
mod database; mod database;
mod device_telemetry_api;
mod firmware_api;
mod schemas; mod schemas;
mod util; mod util;
mod firmware_api;
mod device_telemetry_api;
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
@@ -43,7 +42,7 @@ async fn main() -> std::io::Result<()> {
firmwares_path: PathBuf::from(firmware_path.clone()), firmwares_path: PathBuf::from(firmware_path.clone()),
hostname: external_url.clone(), hostname: external_url.clone(),
})) }))
.app_data(web::PayloadConfig::new(256 * 1024 * 1024)) //256MB .app_data(web::PayloadConfig::new(1 * 1024 * 1024 * 1024)) //1GB
.service(device_telemetry_api::receive_telemetry) .service(device_telemetry_api::receive_telemetry)
.service(device_telemetry_api::get_telemetry) .service(device_telemetry_api::get_telemetry)
.service(device_telemetry_api::receive_value) .service(device_telemetry_api::receive_value)
@@ -57,4 +56,3 @@ async fn main() -> std::io::Result<()> {
.run() .run()
.await .await
} }

View File

@@ -87,12 +87,15 @@ pub struct OTAConfiguration {
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum BoardType { pub enum BoardType {
Waterlevel, Waterlevel,
WaterlevelFilesystem,
} }
#[derive(serde::Serialize, EnumString, PartialEq, Debug, Display)] #[derive(serde::Serialize, EnumString, PartialEq, Debug, Display)]
#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
pub enum BoardConfig { pub enum BoardConfig {
INA226, INA226,
INA233, INA233,
Generic,
} }
pub struct AppState { pub struct AppState {
@@ -105,3 +108,10 @@ pub struct AppState {
pub struct DeviceMetadata { pub struct DeviceMetadata {
pub display_name: String, pub display_name: String,
} }
#[derive(EnumString, PartialEq, Debug, Display, Serialize)]
#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
pub enum Services {
Firmware,
Filesystem,
}

View File

@@ -1,6 +1,6 @@
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use log::info; use log::{debug, info};
use std::str::FromStr; use std::str::FromStr;
use thiserror::Error; use thiserror::Error;
@@ -69,11 +69,13 @@ pub fn get_files(
.strip_suffix(".bin") .strip_suffix(".bin")
.ok_or(GetFilesError::Extension)? .ok_or(GetFilesError::Extension)?
.replace('-', "."); .replace('-', ".");
// TODO this is kinda messy
let board_config = BoardConfig::from_str(split_name[1])?; let board_config = BoardConfig::from_str(split_name[1])?;
let service = split_name[0];
let board_type = BoardType::from_str(&product_name).unwrap(); let board_type = BoardType::from_str(&product_name).unwrap();
let version_replaced = version.replace('.', "_"); let version_replaced = version.replace('.', "_");
let fw_url = let fw_url =
format!("{hostname}/firmware/{board_type}/{board_config}/{version_replaced}.bin"); format!("{hostname}/{service}/{board_type}/{board_config}/{version_replaced}.bin");
let cfg = OTAConfiguration { let cfg = OTAConfiguration {
version: version.to_string(), version: version.to_string(),
url: fw_url, url: fw_url,