Update fuota + device-keys structs / storage.

This add the gen_app_key to the device keys which is needed for FUOTA
and adds a random multicast address + key to the fuota deployment. To
the FUOTA job structure, this adds a return msg such that errors can
be captured in the database.
This commit is contained in:
Orne Brocaar 2025-02-18 11:51:59 +00:00
parent aa11db15de
commit 8e47ea1483
16 changed files with 114 additions and 36 deletions

View File

@ -278,6 +278,11 @@ message DeviceKeys {
// Application root key (128 bit). // Application root key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.1.x devices! // Note: This field only needs to be set for LoRaWAN 1.1.x devices!
string app_key = 3; string app_key = 3;
// Gen App Key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.0.x devices that
// implement TS005 (remote multicast setup).
string gen_app_key = 4;
} }
message CreateDeviceRequest { message CreateDeviceRequest {

View File

@ -278,6 +278,11 @@ message DeviceKeys {
// Application root key (128 bit). // Application root key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.1.x devices! // Note: This field only needs to be set for LoRaWAN 1.1.x devices!
string app_key = 3; string app_key = 3;
// Gen App Key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.0.x devices that
// implement TS005 (remote multicast setup).
string gen_app_key = 4;
} }
message CreateDeviceRequest { message CreateDeviceRequest {

View File

@ -1,3 +1,6 @@
alter table device_keys
drop column gen_app_key;
drop table fuota_deployment_job; drop table fuota_deployment_job;
drop table fuota_deployment_gateway; drop table fuota_deployment_gateway;
drop table fuota_deployment_device; drop table fuota_deployment_device;

View File

@ -7,6 +7,8 @@ create table fuota_deployment (
name varchar(100) not null, name varchar(100) not null,
application_id uuid not null references application on delete cascade, application_id uuid not null references application on delete cascade,
device_profile_id uuid not null references device_profile on delete cascade, device_profile_id uuid not null references device_profile on delete cascade,
multicast_addr bytea not null,
multicast_key bytea not null,
multicast_group_type char(1) not null, multicast_group_type char(1) not null,
multicast_class_c_scheduling_type varchar(20) not null, multicast_class_c_scheduling_type varchar(20) not null,
multicast_dr smallint not null, multicast_dr smallint not null,
@ -54,9 +56,16 @@ create table fuota_deployment_job (
max_retry_count smallint not null, max_retry_count smallint not null,
attempt_count smallint not null, attempt_count smallint not null,
scheduler_run_after timestamp with time zone not null, scheduler_run_after timestamp with time zone not null,
return_msg text not null,
primary key (fuota_deployment_id, job) primary key (fuota_deployment_id, job)
); );
create index idx_fuota_deployment_job_completed_at on fuota_deployment_job(completed_at); create index idx_fuota_deployment_job_completed_at on fuota_deployment_job(completed_at);
create index idx_fuota_deployment_job_scheduler_run_after on fuota_deployment_job(scheduler_run_after); create index idx_fuota_deployment_job_scheduler_run_after on fuota_deployment_job(scheduler_run_after);
alter table device_keys
add column gen_app_key bytea not null default decode('00000000000000000000000000000000', 'hex');
alter table device_keys
alter column gen_app_key drop default;

View File

@ -1,3 +1,6 @@
alter table device_keys
drop column gen_app_key;
drop table fuota_deployment_job; drop table fuota_deployment_job;
drop table fuota_deployment_gateway; drop table fuota_deployment_gateway;
drop table fuota_deployment_device; drop table fuota_deployment_device;

View File

@ -7,6 +7,8 @@ create table fuota_deployment (
name varchar(100) not null, name varchar(100) not null,
application_id text not null references application on delete cascade, application_id text not null references application on delete cascade,
device_profile_id text not null references device_profile on delete cascade, device_profile_id text not null references device_profile on delete cascade,
multicast_addr blob not null,
multicast_key blob not null,
multicast_group_type char(1) not null, multicast_group_type char(1) not null,
multicast_class_c_scheduling_type varchar(20) not null, multicast_class_c_scheduling_type varchar(20) not null,
multicast_dr smallint not null, multicast_dr smallint not null,
@ -54,9 +56,13 @@ create table fuota_deployment_job (
max_retry_count smallint not null, max_retry_count smallint not null,
attempt_count smallint not null, attempt_count smallint not null,
scheduler_run_after datetime not null, scheduler_run_after datetime not null,
return_msg text not null,
primary key (fuota_deployment_id, job) primary key (fuota_deployment_id, job)
); );
create index idx_fuota_deployment_job_completed_at on fuota_deployment_job(completed_at); create index idx_fuota_deployment_job_completed_at on fuota_deployment_job(completed_at);
create index idx_fuota_deployment_job_scheduler_run_after on fuota_deployment_job(scheduler_run_after); create index idx_fuota_deployment_job_scheduler_run_after on fuota_deployment_job(scheduler_run_after);
alter table device_keys
add column gen_app_key blob not null default x'00000000000000000000000000000000';

View File

@ -354,11 +354,8 @@ impl DeviceService for Device {
let dk = device_keys::DeviceKeys { let dk = device_keys::DeviceKeys {
dev_eui, dev_eui,
nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?, nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?,
app_key: if !req_dk.app_key.is_empty() { app_key: AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?,
AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())? gen_app_key: AES128Key::from_str(&req_dk.gen_app_key).map_err(|e| e.status())?,
} else {
AES128Key::null()
},
..Default::default() ..Default::default()
}; };
@ -392,6 +389,7 @@ impl DeviceService for Device {
dev_eui: dk.dev_eui.to_string(), dev_eui: dk.dev_eui.to_string(),
nwk_key: dk.nwk_key.to_string(), nwk_key: dk.nwk_key.to_string(),
app_key: dk.app_key.to_string(), app_key: dk.app_key.to_string(),
gen_app_key: dk.gen_app_key.to_string(),
}), }),
created_at: Some(helpers::datetime_to_prost_timestamp(&dk.created_at)), created_at: Some(helpers::datetime_to_prost_timestamp(&dk.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&dk.updated_at)), updated_at: Some(helpers::datetime_to_prost_timestamp(&dk.updated_at)),
@ -428,11 +426,8 @@ impl DeviceService for Device {
dev_nonces: dk.dev_nonces, dev_nonces: dk.dev_nonces,
join_nonce: dk.join_nonce, join_nonce: dk.join_nonce,
nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?, nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?,
app_key: if !req_dk.app_key.is_empty() { app_key: AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?,
AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())? gen_app_key: AES128Key::from_str(&req_dk.gen_app_key).map_err(|e| e.status())?,
} else {
AES128Key::null()
},
..Default::default() ..Default::default()
}; };
let _ = device_keys::update(dk).await.map_err(|e| e.status())?; let _ = device_keys::update(dk).await.map_err(|e| e.status())?;
@ -1393,6 +1388,7 @@ pub mod test {
dev_eui: "0102030405060708".into(), dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(), nwk_key: "01020304050607080102030405060708".into(),
app_key: "02020304050607080202030405060708".into(), app_key: "02020304050607080202030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}), }),
}, },
); );
@ -1411,6 +1407,7 @@ pub mod test {
dev_eui: "0102030405060708".into(), dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(), nwk_key: "01020304050607080102030405060708".into(),
app_key: "02020304050607080202030405060708".into(), app_key: "02020304050607080202030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}), }),
get_keys_resp.get_ref().device_keys get_keys_resp.get_ref().device_keys
); );
@ -1423,6 +1420,7 @@ pub mod test {
dev_eui: "0102030405060708".into(), dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(), nwk_key: "01020304050607080102030405060708".into(),
app_key: "03020304050607080302030405060708".into(), app_key: "03020304050607080302030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}), }),
}, },
); );
@ -1441,6 +1439,7 @@ pub mod test {
dev_eui: "0102030405060708".into(), dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(), nwk_key: "01020304050607080102030405060708".into(),
app_key: "03020304050607080302030405060708".into(), app_key: "03020304050607080302030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}), }),
get_keys_resp.get_ref().device_keys get_keys_resp.get_ref().device_keys
); );

View File

@ -8,9 +8,11 @@ use chirpstack_api::api;
use chirpstack_api::api::fuota_service_server::FuotaService; use chirpstack_api::api::fuota_service_server::FuotaService;
use lrwn::EUI64; use lrwn::EUI64;
use crate::aeskey::get_random_aes_key;
use crate::api::auth::validator; use crate::api::auth::validator;
use crate::api::error::ToStatus; use crate::api::error::ToStatus;
use crate::api::helpers::{self, FromProto, ToProto}; use crate::api::helpers::{self, FromProto, ToProto};
use crate::devaddr::get_random_dev_addr;
use crate::storage::{fields, fuota}; use crate::storage::{fields, fuota};
pub struct Fuota { pub struct Fuota {
@ -50,6 +52,8 @@ impl FuotaService for Fuota {
name: req_dp.name.clone(), name: req_dp.name.clone(),
application_id: app_id.into(), application_id: app_id.into(),
device_profile_id: dp_id.into(), device_profile_id: dp_id.into(),
multicast_addr: get_random_dev_addr(),
multicast_key: get_random_aes_key(),
multicast_group_type: match req_dp.multicast_group_type() { multicast_group_type: match req_dp.multicast_group_type() {
api::MulticastGroupType::ClassB => "B", api::MulticastGroupType::ClassB => "B",
api::MulticastGroupType::ClassC => "C", api::MulticastGroupType::ClassC => "C",
@ -284,8 +288,7 @@ impl FuotaService for Fuota {
fuota::create_job(fuota::FuotaDeploymentJob { fuota::create_job(fuota::FuotaDeploymentJob {
fuota_deployment_id: d.id, fuota_deployment_id: d.id,
job: fields::FuotaJob::McGroupSetup, job: fields::FuotaJob::CreateMcGroup,
max_retry_count: d.unicast_max_retry_count,
..Default::default() ..Default::default()
}) })
.await .await
@ -733,7 +736,7 @@ mod test {
.unwrap(); .unwrap();
assert_eq!(1, jobs.len()); assert_eq!(1, jobs.len());
assert_eq!(create_resp.id, jobs[0].fuota_deployment_id.to_string()); assert_eq!(create_resp.id, jobs[0].fuota_deployment_id.to_string());
assert_eq!(fields::FuotaJob::McGroupSetup, jobs[0].job); assert_eq!(fields::FuotaJob::CreateMcGroup, jobs[0].job);
// add device // add device
let add_dev_req = get_request( let add_dev_req = get_request(

View File

@ -19,7 +19,9 @@ use tracing_subscriber::{filter, prelude::*};
use lrwn::EUI64; use lrwn::EUI64;
mod adr; mod adr;
mod aeskey;
mod api; mod api;
mod applayer;
mod backend; mod backend;
mod certificate; mod certificate;
mod cmd; mod cmd;

View File

@ -1140,6 +1140,8 @@ pub mod test {
count: 1, count: 1,
limit: 10, limit: 10,
offset: 0, offset: 0,
order: OrderBy::Name,
order_by_desc: false,
}, },
FilterTest { FilterTest {
name: "filter by tags - 1.2.0".into(), name: "filter by tags - 1.2.0".into(),
@ -1154,6 +1156,8 @@ pub mod test {
count: 1, count: 1,
limit: 10, limit: 10,
offset: 0, offset: 0,
order: OrderBy::Name,
order_by_desc: false,
}, },
]; ];

View File

@ -20,6 +20,7 @@ pub struct DeviceKeys {
pub app_key: AES128Key, pub app_key: AES128Key,
pub dev_nonces: fields::DevNonces, pub dev_nonces: fields::DevNonces,
pub join_nonce: i32, pub join_nonce: i32,
pub gen_app_key: AES128Key,
} }
impl Default for DeviceKeys { impl Default for DeviceKeys {
@ -27,19 +28,14 @@ impl Default for DeviceKeys {
let now = Utc::now(); let now = Utc::now();
DeviceKeys { DeviceKeys {
dev_eui: EUI64::from_be_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), dev_eui: Default::default(),
created_at: now, created_at: now,
updated_at: now, updated_at: now,
nwk_key: AES128Key::from_bytes([ nwk_key: Default::default(),
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, app_key: Default::default(),
0x00, 0x00, dev_nonces: Default::default(),
]),
app_key: AES128Key::from_bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
]),
dev_nonces: fields::DevNonces::default(),
join_nonce: 0, join_nonce: 0,
gen_app_key: Default::default(),
} }
} }
} }

View File

@ -83,6 +83,9 @@ impl serialize::ToSql<Text, Sqlite> for RequestFragmentationSessionStatus {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, AsExpression, FromSqlRow)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, AsExpression, FromSqlRow)]
#[diesel(sql_type = Text)] #[diesel(sql_type = Text)]
pub enum FuotaJob { pub enum FuotaJob {
CreateMcGroup,
AddDevsToMcGroup,
AddGwsToMcGroup,
McGroupSetup, McGroupSetup,
McSession, McSession,
FragSessionSetup, FragSessionSetup,
@ -99,6 +102,9 @@ impl fmt::Display for FuotaJob {
impl From<&FuotaJob> for String { impl From<&FuotaJob> for String {
fn from(value: &FuotaJob) -> Self { fn from(value: &FuotaJob) -> Self {
match value { match value {
FuotaJob::CreateMcGroup => "CREATE_MC_GROUP",
FuotaJob::AddDevsToMcGroup => "ADD_DEVS_TO_MC_GROUP",
FuotaJob::AddGwsToMcGroup => "ADD_GWS_TO_MC_GROUP",
FuotaJob::McGroupSetup => "MC_GROUP_SETUP", FuotaJob::McGroupSetup => "MC_GROUP_SETUP",
FuotaJob::McSession => "MC_SESSION", FuotaJob::McSession => "MC_SESSION",
FuotaJob::FragSessionSetup => "FRAG_SESSION_SETUP", FuotaJob::FragSessionSetup => "FRAG_SESSION_SETUP",
@ -114,6 +120,9 @@ impl TryFrom<&str> for FuotaJob {
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
"CREATE_MC_GROUP" => Self::CreateMcGroup,
"ADD_DEVS_TO_MC_GROUP" => Self::AddDevsToMcGroup,
"ADD_GWS_TO_MC_GROUP" => Self::AddGwsToMcGroup,
"MC_GROUP_SETUP" => Self::McGroupSetup, "MC_GROUP_SETUP" => Self::McGroupSetup,
"MC_SESSION" => Self::McSession, "MC_SESSION" => Self::McSession,
"FRAG_SESSION_SETUP" => Self::FragSessionSetup, "FRAG_SESSION_SETUP" => Self::FragSessionSetup,

View File

@ -13,7 +13,7 @@ use crate::storage::schema::{
fuota_deployment_job, gateway, tenant, fuota_deployment_job, gateway, tenant,
}; };
use crate::storage::{self, db_transaction, device_profile, fields, get_async_db_conn}; use crate::storage::{self, db_transaction, device_profile, fields, get_async_db_conn};
use lrwn::EUI64; use lrwn::{AES128Key, DevAddr, EUI64};
#[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq, Validate)] #[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq, Validate)]
#[diesel(table_name = fuota_deployment)] #[diesel(table_name = fuota_deployment)]
@ -26,6 +26,8 @@ pub struct FuotaDeployment {
pub name: String, pub name: String,
pub application_id: fields::Uuid, pub application_id: fields::Uuid,
pub device_profile_id: fields::Uuid, pub device_profile_id: fields::Uuid,
pub multicast_addr: DevAddr,
pub multicast_key: AES128Key,
pub multicast_group_type: String, pub multicast_group_type: String,
pub multicast_class_c_scheduling_type: fields::MulticastGroupSchedulingType, pub multicast_class_c_scheduling_type: fields::MulticastGroupSchedulingType,
pub multicast_dr: i16, pub multicast_dr: i16,
@ -56,6 +58,8 @@ impl Default for FuotaDeployment {
name: "".into(), name: "".into(),
application_id: Uuid::nil().into(), application_id: Uuid::nil().into(),
device_profile_id: Uuid::nil().into(), device_profile_id: Uuid::nil().into(),
multicast_addr: Default::default(),
multicast_key: Default::default(),
multicast_group_type: "".into(), multicast_group_type: "".into(),
multicast_class_c_scheduling_type: fields::MulticastGroupSchedulingType::DELAY, multicast_class_c_scheduling_type: fields::MulticastGroupSchedulingType::DELAY,
multicast_dr: 0, multicast_dr: 0,
@ -144,6 +148,7 @@ pub struct FuotaDeploymentJob {
pub max_retry_count: i16, pub max_retry_count: i16,
pub attempt_count: i16, pub attempt_count: i16,
pub scheduler_run_after: DateTime<Utc>, pub scheduler_run_after: DateTime<Utc>,
pub return_msg: String,
} }
impl Default for FuotaDeploymentJob { impl Default for FuotaDeploymentJob {
@ -158,6 +163,7 @@ impl Default for FuotaDeploymentJob {
max_retry_count: 0, max_retry_count: 0,
attempt_count: 0, attempt_count: 0,
scheduler_run_after: now, scheduler_run_after: now,
return_msg: "".into(),
} }
} }
} }
@ -324,15 +330,21 @@ pub async fn get_devices(
limit: i64, limit: i64,
offset: i64, offset: i64,
) -> Result<Vec<FuotaDeploymentDevice>, Error> { ) -> Result<Vec<FuotaDeploymentDevice>, Error> {
fuota_deployment_device::dsl::fuota_deployment_device let mut q = fuota_deployment_device::dsl::fuota_deployment_device
.filter( .filter(
fuota_deployment_device::dsl::fuota_deployment_id fuota_deployment_device::dsl::fuota_deployment_id
.eq(fields::Uuid::from(fuota_deployment_id)), .eq(fields::Uuid::from(fuota_deployment_id)),
) )
.order_by(fuota_deployment_device::dsl::dev_eui) .into_boxed();
.limit(limit)
.offset(offset) if limit != -1 {
.load(&mut get_async_db_conn().await?) q = q
.order_by(fuota_deployment_device::dsl::dev_eui)
.limit(limit)
.offset(offset);
}
q.load(&mut get_async_db_conn().await?)
.await .await
.map_err(|e| Error::from_diesel(e, "".into())) .map_err(|e| Error::from_diesel(e, "".into()))
} }
@ -449,15 +461,21 @@ pub async fn get_gateways(
limit: i64, limit: i64,
offset: i64, offset: i64,
) -> Result<Vec<FuotaDeploymentGateway>, Error> { ) -> Result<Vec<FuotaDeploymentGateway>, Error> {
fuota_deployment_gateway::dsl::fuota_deployment_gateway let mut q = fuota_deployment_gateway::dsl::fuota_deployment_gateway
.filter( .filter(
fuota_deployment_gateway::dsl::fuota_deployment_id fuota_deployment_gateway::dsl::fuota_deployment_id
.eq(fields::Uuid::from(fuota_deployment_id)), .eq(fields::Uuid::from(fuota_deployment_id)),
) )
.order_by(fuota_deployment_gateway::dsl::gateway_id) .into_boxed();
.limit(limit)
.offset(offset) if limit != -1 {
.load(&mut get_async_db_conn().await?) q = q
.order_by(fuota_deployment_gateway::dsl::gateway_id)
.limit(limit)
.offset(offset);
}
q.load(&mut get_async_db_conn().await?)
.await .await
.map_err(|e| Error::from_diesel(e, "".into())) .map_err(|e| Error::from_diesel(e, "".into()))
} }
@ -501,6 +519,7 @@ pub async fn update_job(j: FuotaDeploymentJob) -> Result<FuotaDeploymentJob, Err
fuota_deployment_job::completed_at.eq(&j.completed_at), fuota_deployment_job::completed_at.eq(&j.completed_at),
fuota_deployment_job::attempt_count.eq(&j.attempt_count), fuota_deployment_job::attempt_count.eq(&j.attempt_count),
fuota_deployment_job::scheduler_run_after.eq(&j.scheduler_run_after), fuota_deployment_job::scheduler_run_after.eq(&j.scheduler_run_after),
fuota_deployment_job::return_msg.eq(&j.return_msg),
)) ))
.get_result(&mut get_async_db_conn().await?) .get_result(&mut get_async_db_conn().await?)
.await .await
@ -636,7 +655,7 @@ pub fn get_multicast_timeout(d: &FuotaDeployment) -> Result<usize> {
// Multiply by the number of fragments (+1 for additional margin). // Multiply by the number of fragments (+1 for additional margin).
let mc_class_c_duration_secs = mc_class_c_margin_secs * (total_fragments + 1 as usize); let mc_class_c_duration_secs = mc_class_c_margin_secs * (total_fragments + 1 as usize);
// Calculate the timeout value. In case of Class-B, timeout is defined as seconds, // Calculate the timeout value. In case of Class-C, timeout is defined as seconds,
// where the number of seconds is 2^timeout. // where the number of seconds is 2^timeout.
for i in 0..16 { for i in 0..16 {
// i = 0-15 // i = 0-15
@ -1115,7 +1134,7 @@ mod test {
payload: vec![0; 10], payload: vec![0; 10],
..Default::default() ..Default::default()
}, },
expected_timeout: 3, expected_timeout: 4,
expected_error: None, expected_error: None,
}, },
]; ];

View File

@ -77,6 +77,7 @@ diesel::table! {
app_key -> Bytea, app_key -> Bytea,
dev_nonces -> Jsonb, dev_nonces -> Jsonb,
join_nonce -> Int4, join_nonce -> Int4,
gen_app_key -> Bytea,
} }
} }
@ -192,6 +193,8 @@ diesel::table! {
name -> Varchar, name -> Varchar,
application_id -> Uuid, application_id -> Uuid,
device_profile_id -> Uuid, device_profile_id -> Uuid,
multicast_addr -> Bytea,
multicast_key -> Bytea,
#[max_length = 1] #[max_length = 1]
multicast_group_type -> Bpchar, multicast_group_type -> Bpchar,
#[max_length = 20] #[max_length = 20]
@ -244,6 +247,7 @@ diesel::table! {
max_retry_count -> Int2, max_retry_count -> Int2,
attempt_count -> Int2, attempt_count -> Int2,
scheduler_run_after -> Timestamptz, scheduler_run_after -> Timestamptz,
return_msg -> Text,
} }
} }

View File

@ -72,6 +72,7 @@ diesel::table! {
app_key -> Binary, app_key -> Binary,
dev_nonces -> Text, dev_nonces -> Text,
join_nonce -> Integer, join_nonce -> Integer,
gen_app_key -> Binary,
} }
} }
@ -171,6 +172,8 @@ diesel::table! {
name -> Text, name -> Text,
application_id -> Text, application_id -> Text,
device_profile_id -> Text, device_profile_id -> Text,
multicast_addr -> Binary,
multicast_key -> Binary,
multicast_group_type -> Text, multicast_group_type -> Text,
multicast_class_c_scheduling_type -> Text, multicast_class_c_scheduling_type -> Text,
multicast_dr -> SmallInt, multicast_dr -> SmallInt,
@ -219,6 +222,7 @@ diesel::table! {
max_retry_count -> SmallInt, max_retry_count -> SmallInt,
attempt_count -> SmallInt, attempt_count -> SmallInt,
scheduler_run_after -> TimestamptzSqlite, scheduler_run_after -> TimestamptzSqlite,
return_msg -> Text,
} }
} }

View File

@ -37,6 +37,7 @@ function LW10DeviceKeysForm(props: FormProps) {
// NOTE: this is not an error! In the LoRaWAN 1.1 specs, the what was previously // NOTE: this is not an error! In the LoRaWAN 1.1 specs, the what was previously
// the AppKey has been renamed to the NwkKey and a new value AppKey was added. // the AppKey has been renamed to the NwkKey and a new value AppKey was added.
dk.setNwkKey(v.nwkKey); dk.setNwkKey(v.nwkKey);
dk.setGenAppKey(v.genAppKey);
props.onFinish(dk); props.onFinish(dk);
}; };
@ -56,6 +57,12 @@ function LW10DeviceKeysForm(props: FormProps) {
value={props.initialValues.getNwkKey()} value={props.initialValues.getNwkKey()}
required required
/> />
<AesKeyInput
label="Gen App Key (for Remote Multicast Setup)"
name="genAppKey"
tooltip="For LoRaWAN 1.0 devices. In case your device supports LoRaWAN 1.1, update the device-profile first."
value={props.initialValues.getGenAppKey()}
/>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
Submit Submit