added method to upload filesystem images
All checks were successful
Build Project / test (push) Successful in 5m58s
All checks were successful
Build Project / test (push) Successful in 5m58s
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user