From 98ba2f3198a393a0c5e62531aa205f2552ea11ea Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Tue, 11 Mar 2025 09:36:55 +0000 Subject: [PATCH] Set device tags after FUOTA complete. --- api/proto/api/fuota.proto | 3 + api/rust/proto/chirpstack/api/fuota.proto | 3 + .../up.sql | 3 +- .../up.sql | 3 +- chirpstack/src/api/fuota.rs | 7 + chirpstack/src/applayer/fuota/flow.rs | 19 +- chirpstack/src/storage/fuota.rs | 5 + chirpstack/src/storage/schema_postgres.rs | 1 + chirpstack/src/storage/schema_sqlite.rs | 1 + ui/src/views/fuota/FuotaDeploymentForm.tsx | 447 ++++++++++-------- 10 files changed, 289 insertions(+), 203 deletions(-) diff --git a/api/proto/api/fuota.proto b/api/proto/api/fuota.proto index 4cfd546e..421d308c 100644 --- a/api/proto/api/fuota.proto +++ b/api/proto/api/fuota.proto @@ -156,6 +156,9 @@ message FuotaDeployment { // Payload. // The FUOTA payload to send. bytes payload = 21; + + // Set device tags on complete. + map on_complete_set_device_tags = 22; } message FuotaDeploymentListItem { diff --git a/api/rust/proto/chirpstack/api/fuota.proto b/api/rust/proto/chirpstack/api/fuota.proto index 4cfd546e..421d308c 100644 --- a/api/rust/proto/chirpstack/api/fuota.proto +++ b/api/rust/proto/chirpstack/api/fuota.proto @@ -156,6 +156,9 @@ message FuotaDeployment { // Payload. // The FUOTA payload to send. bytes payload = 21; + + // Set device tags on complete. + map on_complete_set_device_tags = 22; } message FuotaDeploymentListItem { diff --git a/chirpstack/migrations_postgres/2025-01-21-093745_add_fuota_support/up.sql b/chirpstack/migrations_postgres/2025-01-21-093745_add_fuota_support/up.sql index 7e9164ea..22f03e72 100644 --- a/chirpstack/migrations_postgres/2025-01-21-093745_add_fuota_support/up.sql +++ b/chirpstack/migrations_postgres/2025-01-21-093745_add_fuota_support/up.sql @@ -23,7 +23,8 @@ create table fuota_deployment ( fragmentation_block_ack_delay smallint not null, fragmentation_descriptor bytea not null, request_fragmentation_session_status varchar(20) not null, - payload bytea not null + payload bytea not null, + on_complete_set_device_tags jsonb not null ); create table fuota_deployment_device ( diff --git a/chirpstack/migrations_sqlite/2025-01-27-100007_add_fuota_support/up.sql b/chirpstack/migrations_sqlite/2025-01-27-100007_add_fuota_support/up.sql index 0b1ae080..0f7f006b 100644 --- a/chirpstack/migrations_sqlite/2025-01-27-100007_add_fuota_support/up.sql +++ b/chirpstack/migrations_sqlite/2025-01-27-100007_add_fuota_support/up.sql @@ -23,7 +23,8 @@ create table fuota_deployment ( fragmentation_block_ack_delay smallint not null, fragmentation_descriptor blob not null, request_fragmentation_session_status varchar(20) not null, - payload blob not null + payload blob not null, + on_complete_set_device_tags text not null ); create table fuota_deployment_device ( diff --git a/chirpstack/src/api/fuota.rs b/chirpstack/src/api/fuota.rs index 7925266c..922f9a89 100644 --- a/chirpstack/src/api/fuota.rs +++ b/chirpstack/src/api/fuota.rs @@ -77,6 +77,9 @@ impl FuotaService for Fuota { .request_fragmentation_session_status() .from_proto(), payload: req_dp.payload.clone(), + on_complete_set_device_tags: fields::KeyValue::new( + req_dp.on_complete_set_device_tags.clone(), + ), ..Default::default() }; if req_dp.calculate_fragmentation_fragment_size { @@ -152,6 +155,7 @@ impl FuotaService for Fuota { payload: dp.payload.clone(), calculate_multicast_timeout: false, calculate_fragmentation_fragment_size: false, + on_complete_set_device_tags: dp.on_complete_set_device_tags.into_hashmap(), }), created_at: Some(helpers::datetime_to_prost_timestamp(&dp.created_at)), updated_at: Some(helpers::datetime_to_prost_timestamp(&dp.updated_at)), @@ -227,6 +231,9 @@ impl FuotaService for Fuota { .request_fragmentation_session_status() .from_proto(), payload: req_dp.payload.clone(), + on_complete_set_device_tags: fields::KeyValue::new( + req_dp.on_complete_set_device_tags.clone(), + ), ..Default::default() }; if req_dp.calculate_fragmentation_fragment_size { diff --git a/chirpstack/src/applayer/fuota/flow.rs b/chirpstack/src/applayer/fuota/flow.rs index f2a70fdd..c4a57460 100644 --- a/chirpstack/src/applayer/fuota/flow.rs +++ b/chirpstack/src/applayer/fuota/flow.rs @@ -1,3 +1,4 @@ +use std::ops::DerefMut; use std::time::Duration; use anyhow::Result; @@ -11,7 +12,7 @@ use crate::config; use crate::downlink; use crate::gpstime::ToGpsTime; use crate::storage::fields::{FuotaJob, RequestFragmentationSessionStatus}; -use crate::storage::{device_keys, device_profile, device_queue, fuota, multicast}; +use crate::storage::{device, device_keys, device_profile, device_queue, fuota, multicast}; pub struct Flow { scheduler_interval: Duration, @@ -569,7 +570,7 @@ impl Flow { return Ok(None); } - info!("Complete FUOTA deployment"); + info!("Completing FUOTA deployment"); self.job.attempt_count += 1; if self.fuota_deployment.request_fragmentation_session_status @@ -590,6 +591,20 @@ impl Flow { .await?; } + let fuota_devices = fuota::get_devices(self.job.fuota_deployment_id.into(), -1, 0).await?; + let fuota_devices: Vec = fuota_devices + .into_iter() + .filter(|d| d.completed_at.is_some() && d.error_msg.is_empty()) + .collect(); + + for fuota_device in &fuota_devices { + let mut d = device::get(&fuota_device.dev_eui).await?; + for (k, v) in self.fuota_deployment.on_complete_set_device_tags.iter() { + d.tags.deref_mut().insert(k.to_string(), v.to_string()); + } + let _ = device::update(d).await?; + } + let mut d = self.fuota_deployment.clone(); d.completed_at = Some(Utc::now()); let _ = fuota::update_deployment(d).await?; diff --git a/chirpstack/src/storage/fuota.rs b/chirpstack/src/storage/fuota.rs index 8890054c..1fb6dd52 100644 --- a/chirpstack/src/storage/fuota.rs +++ b/chirpstack/src/storage/fuota.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use anyhow::{Context, Result}; use chrono::{DateTime, Duration, Utc}; use diesel::{dsl, prelude::*}; @@ -43,6 +45,7 @@ pub struct FuotaDeployment { pub fragmentation_descriptor: Vec, pub request_fragmentation_session_status: fields::RequestFragmentationSessionStatus, pub payload: Vec, + pub on_complete_set_device_tags: fields::KeyValue, } impl Default for FuotaDeployment { @@ -76,6 +79,7 @@ impl Default for FuotaDeployment { request_fragmentation_session_status: fields::RequestFragmentationSessionStatus::NoRequest, payload: Vec::new(), + on_complete_set_device_tags: fields::KeyValue::new(HashMap::new()), } } } @@ -227,6 +231,7 @@ pub async fn update_deployment(d: FuotaDeployment) -> Result Varchar, payload -> Bytea, + on_complete_set_device_tags -> Jsonb, } } diff --git a/chirpstack/src/storage/schema_sqlite.rs b/chirpstack/src/storage/schema_sqlite.rs index 72265e0b..1f3ae811 100644 --- a/chirpstack/src/storage/schema_sqlite.rs +++ b/chirpstack/src/storage/schema_sqlite.rs @@ -189,6 +189,7 @@ diesel::table! { fragmentation_descriptor -> Binary, request_fragmentation_session_status -> Text, payload -> Binary, + on_complete_set_device_tags -> Text, } } diff --git a/ui/src/views/fuota/FuotaDeploymentForm.tsx b/ui/src/views/fuota/FuotaDeploymentForm.tsx index 00fd85f2..1fe2ec5c 100644 --- a/ui/src/views/fuota/FuotaDeploymentForm.tsx +++ b/ui/src/views/fuota/FuotaDeploymentForm.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { Form, Input, InputNumber, Select, Row, Col, Button, Upload, UploadFile, Switch } from "antd"; -import { UploadOutlined } from "@ant-design/icons"; +import { Tabs, Form, Input, InputNumber, Select, Row, Col, Button, Upload, UploadFile, Switch } from "antd"; +import { UploadOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; import type { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb"; import { FuotaDeployment, RequestFragmentationSessionStatus } from "@chirpstack/chirpstack-api-grpc-web/api/fuota_pb"; @@ -79,6 +79,11 @@ function FuotaDeploymentForm(props: IProps) { d.setFragmentationFragmentSize(v.fragmentationFragmentSize); d.setPayload(v.payload); + // on complete set device tags + for (const elm of v.onCompleteSetDeviceTagsMap) { + d.getOnCompleteSetDeviceTagsMap().set(elm[0], elm[1]); + } + props.onFinish(d); }; @@ -152,206 +157,250 @@ function FuotaDeploymentForm(props: IProps) { onFinishFailed={onFinishFailed} form={form} > - - - - - - - - - - + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setCalculateMulticastTimeout(v)} disabled={props.disabled} /> + + + + + {isMulticastClassB && ( + + )} + {!isMulticastClassB && ( + + )} + + + + + setCalculateFragmentationFragmentSize(v)} disabled={props.disabled} /> + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setCalculateMulticastTimeout(v)} disabled={props.disabled} /> - - - - - {isMulticastClassB && ( - + + + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }) => ( + + + + + + + + + + + + + remove(name)} /> + + + ))} + + + + )} - {!isMulticastClassB && ( - - )} - - - - - setCalculateFragmentationFragmentSize(v)} disabled={props.disabled} /> - - - - - - - - - - - - - + + +