Compare commits

...

11 Commits

Author SHA1 Message Date
e8d001441c Bump version to 4.13.0-test.1 2025-05-23 09:36:26 +01:00
10e7907251 Generate API code. 2025-05-23 09:30:16 +01:00
39df55afb4 MeshCommand does not need a timestamp. 2025-05-23 09:14:57 +01:00
038e45e8f0 api: Rename Mesh to MeshEvent.
This is needed as we are also adding commands, thus we need MeshEvent
and MeshCommand messages.
2025-05-23 09:14:57 +01:00
5cf1120f20 Update gw proto & refactor Mesh Heartbeat event.
This refactors the gateway protobuf payloads, such that the
Concentratord can publish an Event message, containing one of the
possible events published by the Concentratord (uplink, stats or mesh
event). It also combines the possible Concentratord commands into a
single Command message. This simplifies the ZMQ interface as it is no
longer needed to match the payload type by string.

This also refactors the MeshHeartbeat message into a Mesh message, which
can contain multiple events, of which the Heartbeat is one of the
possible events.

The future goal is to make it possible to send different types of events
from the Gateway Mesh Relay gateways (e.g. battery status, ...) and to
make it possible to also send proprietary event types.
2025-05-23 09:14:57 +01:00
b8e14058f2 Geplace GETDEL by pipelined GET and DEL commands. (#682)
GETDEL was added in Redis 6.2, but is unfortunately not supported on all Azure Cache for Redis tiers.

Fixes #680.
2025-05-21 10:15:05 +01:00
10731c2be5 Make amqp integration exchange configurable (#677) 2025-05-21 09:45:19 +01:00
f1d46b1bc9 Update scheduler_run_after at device-session get.
This moves the setting of the scheduler_run_after to the function to get
the device-session based on provided PHYPayload. This way we reduce the
risk of overlapping Class-B / -C downlinks with a Class-A downlink, as
we update the scheduler_run_after during the "FOR UPDATE" transaction.
This also reduces one SQL query, as we combine it with the updating of
the device-session query (to update the uplink FCnt value).
2025-05-12 11:05:02 +01:00
c954cd3645 Store temp. mac-command state in device-session. 2025-05-09 14:27:25 +01:00
188ef3d8f3 Bump version to 4.12.1 2025-05-02 11:16:19 +01:00
0cff864f60 ui: Remove CodeEditor reloadKey.
With the migration from codemirror to ace, this workaround is no longer
needed to correctly render the UI component.
2025-05-02 11:09:59 +01:00
33 changed files with 2193 additions and 1177 deletions

12
Cargo.lock generated
View File

@ -636,7 +636,7 @@ dependencies = [
[[package]]
name = "backend"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
dependencies = [
"aes-kw",
"anyhow",
@ -838,7 +838,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chirpstack"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
dependencies = [
"aes",
"anyhow",
@ -930,7 +930,7 @@ dependencies = [
[[package]]
name = "chirpstack_api"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
dependencies = [
"hex",
"pbjson",
@ -947,7 +947,7 @@ dependencies = [
[[package]]
name = "chirpstack_integration"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
dependencies = [
"anyhow",
"async-trait",
@ -2794,7 +2794,7 @@ dependencies = [
[[package]]
name = "lrwn"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
dependencies = [
"aes",
"anyhow",
@ -2807,7 +2807,7 @@ dependencies = [
[[package]]
name = "lrwn_filters"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
dependencies = [
"hex",
"lrwn",

2532
api/go/gw/gw.pb.go vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@chirpstack/chirpstack-api-grpc-web",
"version": "4.12.1-test.1",
"version": "4.13.0-test.1",
"description": "Chirpstack gRPC-web API",
"license": "MIT",
"devDependencies": {

View File

@ -8,7 +8,7 @@ plugins {
}
group = "io.chirpstack"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
repositories {
mavenCentral()

2
api/js/package.json vendored
View File

@ -1,6 +1,6 @@
{
"name": "@chirpstack/chirpstack-api",
"version": "4.12.1-test.1",
"version": "4.13.0-test.1",
"description": "Chirpstack JS and TS API",
"license": "MIT",
"devDependencies": {

View File

@ -9,7 +9,7 @@ plugins {
}
group = "io.chirpstack"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
repositories {
mavenCentral()

View File

@ -3,7 +3,7 @@
"description": "Chirpstack PHP API",
"license": "MIT",
"type": "library",
"version": "4.12.1-test.1",
"version": "4.13.0-test.1",
"require": {
"php": ">=7.0.0",
"grpc/grpc": "^v1.57.0",

125
api/proto/gw/gw.proto vendored
View File

@ -104,6 +104,82 @@ enum TxAckStatus {
DUTY_CYCLE_OVERFLOW = 11;
}
// Gateway events as reported by the ChirpStack Concentratord ZMQ interface.
message Event {
oneof event {
// Uplink frame.
UplinkFrame uplink_frame = 1;
// Gateway stats.
GatewayStats gateway_stats = 2;
// Gateway Mesh Event.
MeshEvent mesh = 3;
}
}
// Commands that can be sent to the ChirpStack Concentratord ZMQ interface.
message Command {
oneof command {
// Downlink frame.
DownlinkFrame send_downlink_frame = 1;
// Gateway configuration.
GatewayConfiguration set_gateway_configuration = 2;
// Get Gateway ID.
GetGatewayIdRequest get_gateway_id = 3;
// Get location.
GetLocationRequest get_location = 4;
// Gateway Mesh Command.
MeshCommand mesh = 5;
}
}
message MeshEvent {
// Gateway ID (of the Border Gateway).
string gateway_id = 1;
// Relay ID.
string relay_id = 2;
// Timestamp (second precision).
google.protobuf.Timestamp time = 3;
// Mesh events.
repeated MeshEventItem events = 4;
}
message MeshEventItem {
oneof event {
// Proprietary Mesh event.
MeshEventProprietary proprietary = 1;
// Mesh heartbeat.
MeshEventHeartbeat heartbeat = 2;
}
}
message MeshCommand {
// Gateway ID (of the Border Gateway).
string gateway_id = 1;
// Relay ID.
string relay_id = 2;
// Mesh events.
repeated MeshCommandItem commands = 3;
}
message MeshCommandItem {
oneof command {
// Proprietary Mesh command.
MeshCommandProprietary proprietary = 1;
}
}
message Modulation {
oneof parameters {
// LoRa modulation information.
@ -611,6 +687,23 @@ message GatewayConfiguration {
google.protobuf.Duration stats_interval = 4;
}
message GetGatewayIdRequest {}
message GetGatewayIdResponse {
// Gateway ID.
string gateway_id = 1;
}
message GetLocationRequest {}
message GetLocationResponse {
// Location.
common.Location location = 1;
// Last updated at.
google.protobuf.Timestamp updated_at = 2;
}
message ChannelConfiguration {
// Frequency (Hz).
uint32 frequency = 1;
@ -751,21 +844,13 @@ message ConnState {
}
// Gateway Mesh heartbeat (sent periodically by the Relay Gateways).
message MeshHeartbeat {
// Gateway ID (of the Border Gateway).
string gateway_id = 1;
// Relay ID.
string relay_id = 2;
// Timestamp (second precision).
google.protobuf.Timestamp time = 3;
message MeshEventHeartbeat {
// Relay path.
repeated MeshHeartbeatRelayPath relay_path = 4;
repeated MeshEventHeartbeatRelayPath relay_path = 4;
}
message MeshHeartbeatRelayPath {
message MeshEventHeartbeatRelayPath {
// Relay ID.
string relay_id = 1;
@ -775,3 +860,21 @@ message MeshHeartbeatRelayPath {
// SNR.
int32 snr = 3;
}
// Proprietary mesh event.
message MeshEventProprietary {
// Event type.
uint32 event_type = 1;
// Payload.
bytes payload = 2;
}
// Proprietary mesh command.
message MeshCommandProprietary {
// Command type.
uint32 command_type = 1;
// Payload.
bytes payload = 2;
}

View File

@ -142,6 +142,9 @@ message DeviceSession {
// Relay state.
Relay relay = 41;
// Pending mac-commands.
map<uint32, bytes> mac_command_pending = 43;
}
message UplinkAdrHistory {

View File

@ -18,7 +18,7 @@ CLASSIFIERS = [
setup(
name='chirpstack-api',
version = "4.12.1-test.1",
version = "4.13.0-test.1",
url='https://github.com/brocaar/chirpstack-api',
author='Orne Brocaar',
author_email='info@brocaar.com',

2
api/rust/Cargo.toml vendored
View File

@ -1,7 +1,7 @@
[package]
name = "chirpstack_api"
description = "ChirpStack Protobuf / gRPC API definitions."
version = "4.12.1-test.1"
version = "4.13.0-test.1"
authors = ["Orne Brocaar <info@brocaar.com>"]
license = "MIT"
homepage = "https://www.chirpstack.io"

View File

@ -104,6 +104,82 @@ enum TxAckStatus {
DUTY_CYCLE_OVERFLOW = 11;
}
// Gateway events as reported by the ChirpStack Concentratord ZMQ interface.
message Event {
oneof event {
// Uplink frame.
UplinkFrame uplink_frame = 1;
// Gateway stats.
GatewayStats gateway_stats = 2;
// Gateway Mesh Event.
MeshEvent mesh = 3;
}
}
// Commands that can be sent to the ChirpStack Concentratord ZMQ interface.
message Command {
oneof command {
// Downlink frame.
DownlinkFrame send_downlink_frame = 1;
// Gateway configuration.
GatewayConfiguration set_gateway_configuration = 2;
// Get Gateway ID.
GetGatewayIdRequest get_gateway_id = 3;
// Get location.
GetLocationRequest get_location = 4;
// Gateway Mesh Command.
MeshCommand mesh = 5;
}
}
message MeshEvent {
// Gateway ID (of the Border Gateway).
string gateway_id = 1;
// Relay ID.
string relay_id = 2;
// Timestamp (second precision).
google.protobuf.Timestamp time = 3;
// Mesh events.
repeated MeshEventItem events = 4;
}
message MeshEventItem {
oneof event {
// Proprietary Mesh event.
MeshEventProprietary proprietary = 1;
// Mesh heartbeat.
MeshEventHeartbeat heartbeat = 2;
}
}
message MeshCommand {
// Gateway ID (of the Border Gateway).
string gateway_id = 1;
// Relay ID.
string relay_id = 2;
// Mesh events.
repeated MeshCommandItem commands = 3;
}
message MeshCommandItem {
oneof command {
// Proprietary Mesh command.
MeshCommandProprietary proprietary = 1;
}
}
message Modulation {
oneof parameters {
// LoRa modulation information.
@ -611,6 +687,23 @@ message GatewayConfiguration {
google.protobuf.Duration stats_interval = 4;
}
message GetGatewayIdRequest {}
message GetGatewayIdResponse {
// Gateway ID.
string gateway_id = 1;
}
message GetLocationRequest {}
message GetLocationResponse {
// Location.
common.Location location = 1;
// Last updated at.
google.protobuf.Timestamp updated_at = 2;
}
message ChannelConfiguration {
// Frequency (Hz).
uint32 frequency = 1;
@ -751,21 +844,13 @@ message ConnState {
}
// Gateway Mesh heartbeat (sent periodically by the Relay Gateways).
message MeshHeartbeat {
// Gateway ID (of the Border Gateway).
string gateway_id = 1;
// Relay ID.
string relay_id = 2;
// Timestamp (second precision).
google.protobuf.Timestamp time = 3;
message MeshEventHeartbeat {
// Relay path.
repeated MeshHeartbeatRelayPath relay_path = 4;
repeated MeshEventHeartbeatRelayPath relay_path = 4;
}
message MeshHeartbeatRelayPath {
message MeshEventHeartbeatRelayPath {
// Relay ID.
string relay_id = 1;
@ -775,3 +860,21 @@ message MeshHeartbeatRelayPath {
// SNR.
int32 snr = 3;
}
// Proprietary mesh event.
message MeshEventProprietary {
// Event type.
uint32 event_type = 1;
// Payload.
bytes payload = 2;
}
// Proprietary mesh command.
message MeshCommandProprietary {
// Command type.
uint32 command_type = 1;
// Payload.
bytes payload = 2;
}

View File

@ -142,6 +142,9 @@ message DeviceSession {
// Relay state.
Relay relay = 41;
// Pending mac-commands.
map<uint32, bytes> mac_command_pending = 43;
}
message UplinkAdrHistory {

9
api/rust/src/lib.rs vendored
View File

@ -1,14 +1,17 @@
pub use prost;
pub use prost_types;
#[cfg(feature = "json")]
pub use pbjson_types;
pub use prost;
#[cfg(feature = "api")]
pub use tonic;
#[cfg(feature = "api")]
pub mod api;
#[cfg(feature = "internal")]
pub mod internal;
pub mod common;
pub mod gw;
pub mod integration;
#[cfg(feature = "internal")]
pub mod internal;
pub mod stream;

View File

@ -1,6 +1,6 @@
[package]
name = "backend"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018"
publish = false

View File

@ -3,13 +3,13 @@
description = "Library for building external ChirpStack integrations"
homepage = "https://www.chirpstack.io/"
license = "MIT"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021"
repository = "https://github.com/chirpstack/chirpstack"
[dependencies]
chirpstack_api = { path = "../api/rust", version = "4.12.1-test.1" }
chirpstack_api = { path = "../api/rust", version = "4.13.0-test.1" }
redis = { version = "0.29", features = [
"cluster-async",
"tokio-rustls-comp",

View File

@ -3,7 +3,7 @@
description = "ChirpStack is an open-source LoRaWAN(TM) Network Server"
repository = "https://github.com/chirpstack/chirpstack"
homepage = "https://www.chirpstack.io/"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021"
publish = false

View File

@ -352,6 +352,7 @@ pub struct AmqpIntegration {
pub url: String,
pub json: bool,
pub event_routing_key: String,
pub exchange: String,
}
impl Default for AmqpIntegration {
@ -361,6 +362,7 @@ impl Default for AmqpIntegration {
json: true,
event_routing_key: "application.{{application_id}}.device.{{dev_eui}}.event.{{event}}"
.to_string(),
exchange: "amq.topic".to_string(),
}
}
}

View File

@ -707,7 +707,8 @@ impl Data {
fn set_phy_payloads(&mut self) -> Result<()> {
trace!("Setting downlink PHYPayloads");
let mut f_pending = self.more_device_queue_items;
let ds = self.device.get_device_session()?;
let dev_addr = self.device.get_dev_addr()?;
let ds = self.device.get_device_session_mut()?;
for item in self.downlink_frame_items.iter_mut() {
let mut mac_size: usize = 0;
@ -729,6 +730,8 @@ impl Data {
for mac in &**mac_set {
mac_commands.push(mac.clone());
}
mac_command::set_pending(ds, mac_set)?;
}
// LoRaWAN MHDR
@ -740,7 +743,7 @@ impl Data {
// LoRaWAN MAC payload
let mut mac_pl = lrwn::MACPayload {
fhdr: lrwn::FHDR {
devaddr: self.device.get_dev_addr()?,
devaddr: dev_addr,
f_cnt: ds.n_f_cnt_down,
f_ctrl: lrwn::FCtrl {
adr: !self.network_conf.adr_disabled,
@ -1196,8 +1199,6 @@ impl Data {
if let Some(block) =
maccommand::new_channel::request(3, &current_channels, &wanted_channels)
{
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::NewChannelReq, &block)
.await?;
self.mac_commands.push(block);
}
@ -1207,7 +1208,7 @@ impl Data {
// Note: this must come before ADR!
async fn _request_channel_mask_reconfiguration(&mut self) -> Result<()> {
trace!("Requesting channel-mask reconfiguration");
let ds = self.device.get_device_session()?;
let ds = self.device.get_device_session_mut()?;
let enabled_uplink_channel_indices: Vec<usize> = ds
.enabled_uplink_channel_indices
@ -1239,7 +1240,6 @@ impl Data {
.collect(),
);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::LinkADRReq, &set).await?;
self.mac_commands.push(set);
Ok(())
@ -1257,12 +1257,15 @@ impl Data {
.get_data_rate(self.uplink_frame_set.as_ref().unwrap().dr)?;
let ufs = self.uplink_frame_set.as_ref().unwrap();
let ds = self.device.get_device_session()?;
let dev_eui = self.device.dev_eui;
let device_variables = self.device.variables.into_hashmap();
let ds = self.device.get_device_session_mut()?;
let req = adr::Request {
dev_eui,
device_variables,
region_config_id: ufs.region_config_id.clone(),
region_common_name: ufs.region_common_name,
dev_eui: self.device.dev_eui,
mac_version: self.device_profile.mac_version,
reg_params_revision: self.device_profile.reg_params_revision,
adr: ds.adr,
@ -1291,7 +1294,6 @@ impl Data {
max_dr: self.network_conf.max_dr,
uplink_history: ds.uplink_adr_history.clone(),
skip_f_cnt_check: ds.skip_f_cnt_check,
device_variables: self.device.variables.into_hashmap(),
};
let resp = adr::handle(&self.device_profile.adr_algorithm_id, &req).await;
@ -1304,24 +1306,14 @@ impl Data {
{
let mut adr_set = false;
for set in self.mac_commands.iter_mut() {
let mut is_link_adr_set = false;
for mac in &mut **set {
if let lrwn::MACCommand::LinkADRReq(pl) = mac {
pl.dr = resp.dr;
pl.tx_power = resp.tx_power_index;
pl.redundancy.nb_rep = resp.nb_trans;
adr_set = true;
is_link_adr_set = true;
}
}
if is_link_adr_set {
// We need to update the pending mac-command.
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::LinkADRReq, set)
.await?;
}
}
// There was no existing LinkADRReq to be sent, we need to construct a new one.
@ -1358,7 +1350,6 @@ impl Data {
},
)]);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::LinkADRReq, &set).await?;
self.mac_commands.push(set);
}
}
@ -1406,7 +1397,7 @@ impl Data {
async fn _request_rejoin_param_setup(&mut self) -> Result<()> {
trace!("Requesting rejoin param setup");
let ds = self.device.get_device_session()?;
let ds = self.device.get_device_session_mut()?;
// Rejoin-request is disabled or device does not support LoRaWAN 1.1.
if !self.network_conf.rejoin_request.enabled
@ -1423,8 +1414,6 @@ impl Data {
self.network_conf.rejoin_request.max_time_n,
self.network_conf.rejoin_request.max_count_n,
);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::RejoinParamSetupReq, &set)
.await?;
self.mac_commands.push(set);
}
@ -1434,7 +1423,7 @@ impl Data {
async fn _set_ping_slot_parameters(&mut self) -> Result<()> {
trace!("Setting ping-slot parameters");
let ds = self.device.get_device_session()?;
let ds = self.device.get_device_session_mut()?;
if !self.device_profile.supports_class_b {
return Ok(());
@ -1447,8 +1436,6 @@ impl Data {
self.network_conf.class_b.ping_slot_dr,
self.network_conf.class_b.ping_slot_frequency,
);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::PingSlotChannelReq, &set)
.await?;
self.mac_commands.push(set);
}
@ -1457,7 +1444,7 @@ impl Data {
async fn _set_rx_parameters(&mut self) -> Result<()> {
trace!("Setting rx parameters");
let ds = self.device.get_device_session()?;
let ds = self.device.get_device_session_mut()?;
if ds.rx2_frequency != self.network_conf.rx2_frequency
|| ds.rx2_dr as u8 != self.network_conf.rx2_dr
@ -1468,8 +1455,6 @@ impl Data {
self.network_conf.rx2_frequency,
self.network_conf.rx2_dr,
);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::RxParamSetupReq, &set)
.await?;
self.mac_commands.push(set);
}
@ -1481,8 +1466,6 @@ impl Data {
if dev_rx1_delay != req_rx1_delay {
let set = maccommand::rx_timing_setup::request(req_rx1_delay);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::RxTimingSetupReq, &set)
.await?;
self.mac_commands.push(set);
}
@ -1491,7 +1474,7 @@ impl Data {
async fn _set_tx_parameters(&mut self) -> Result<()> {
trace!("Setting tx parameters");
let ds = self.device.get_device_session()?;
let ds = self.device.get_device_session_mut()?;
if !self
.region_conf
@ -1512,8 +1495,6 @@ impl Data {
self.network_conf.downlink_dwell_time_400ms,
uplink_eirp_index,
);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::TxParamSetupReq, &set)
.await?;
self.mac_commands.push(set);
}
@ -1567,8 +1548,8 @@ impl Data {
|| rd.uplink_limit_reload_rate
!= device.relay_ed_uplink_limit_reload_rate as u32
{
let d = device::get(&device.dev_eui).await?;
let ds = match d.get_device_session() {
let mut d = device::get(&device.dev_eui).await?;
let ds = match d.get_device_session_mut() {
Ok(v) => v,
Err(_) => {
// It is valid that the device is no longer activated.
@ -1595,13 +1576,17 @@ impl Data {
},
),
]);
mac_command::set_pending(
&dev_eui,
lrwn::CID::UpdateUplinkListReq,
&set,
self.mac_commands.push(set);
// Update device-session of device.
device::partial_update(
d.dev_eui,
&device::DeviceChangeset {
device_session: Some(d.device_session.clone()),
..Default::default()
},
)
.await?;
self.mac_commands.push(set);
rd.dev_addr = dev_addr.to_vec();
rd.root_wor_s_key = root_wor_s_key.to_vec();
@ -1651,8 +1636,6 @@ impl Data {
root_wor_s_key,
},
)]);
mac_command::set_pending(&dev_eui, lrwn::CID::UpdateUplinkListReq, &set)
.await?;
self.mac_commands.push(set);
ds.relay
@ -1788,8 +1771,6 @@ impl Data {
if !commands.is_empty() {
let set = lrwn::MACCommandSet::new(commands);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::CtrlUplinkListReq, &set)
.await?;
self.mac_commands.push(set);
}
@ -1799,7 +1780,6 @@ impl Data {
async fn _configure_fwd_limit_req(&mut self) -> Result<()> {
trace!("Configuring Relay Fwd Limit");
let dev_eui = self.device.dev_eui;
let ds = self.device.get_device_session_mut()?;
let relay_params = self.device_profile.relay_params.clone().unwrap_or_default();
@ -1843,7 +1823,6 @@ impl Data {
},
},
)]);
mac_command::set_pending(&dev_eui, lrwn::CID::ConfigureFwdLimitReq, &set).await?;
self.mac_commands.push(set);
}
@ -1915,7 +1894,6 @@ impl Data {
}
let set = lrwn::MACCommandSet::new(commands);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::FilterListReq, &set).await?;
self.mac_commands.push(set);
// The deletes needs to be processed before we can add new entries.
@ -1944,8 +1922,6 @@ impl Data {
filter_list_eui: vec![],
},
)]);
mac_command::set_pending(&self.device.dev_eui, lrwn::CID::FilterListReq, &set)
.await?;
self.mac_commands.push(set);
// Return because we can't add multiple sets and if we would combine
@ -1977,7 +1953,6 @@ impl Data {
filter_list_eui: eui,
},
)]);
mac_command::set_pending(&dev_eui, lrwn::CID::FilterListReq, &set).await?;
self.mac_commands.push(set);
f.join_eui = device.join_eui.to_vec();
@ -2009,7 +1984,6 @@ impl Data {
filter_list_eui: eui,
},
)]);
mac_command::set_pending(&dev_eui, lrwn::CID::FilterListReq, &set).await?;
self.mac_commands.push(set);
ds.relay
@ -2037,7 +2011,6 @@ impl Data {
async fn _update_relay_conf(&mut self) -> Result<()> {
trace!("Updating Relay Conf");
let dev_eui = self.device.dev_eui;
let ds = self.device.get_device_session_mut()?;
let relay_params = self.device_profile.relay_params.clone().unwrap_or_default();
@ -2075,7 +2048,6 @@ impl Data {
second_ch_freq: relay_params.second_channel_freq,
},
)]);
mac_command::set_pending(&dev_eui, lrwn::CID::RelayConfReq, &set).await?;
self.mac_commands.push(set);
}
@ -2087,7 +2059,6 @@ impl Data {
async fn _update_end_device_conf(&mut self) -> Result<()> {
trace!("Updating End Device Conf");
let dev_eui = self.device.dev_eui;
let ds = self.device.get_device_session_mut()?;
let relay_params = self.device_profile.relay_params.clone().unwrap_or_default();
@ -2124,7 +2095,6 @@ impl Data {
second_ch_freq: relay_params.second_channel_freq,
},
)]);
mac_command::set_pending(&dev_eui, lrwn::CID::EndDeviceConfReq, &set).await?;
self.mac_commands.push(set);
}

View File

@ -1,5 +1,4 @@
use std::collections::HashMap;
use std::io::Cursor;
use std::sync::{LazyLock, RwLock};
use std::time::Duration;
@ -352,7 +351,7 @@ async fn message_callback(
.inc();
let mut event = match json {
true => serde_json::from_slice(&p.payload)?,
false => chirpstack_api::gw::UplinkFrame::decode(&mut Cursor::new(&p.payload))?,
false => chirpstack_api::gw::UplinkFrame::decode(p.payload.as_ref())?,
};
if v4_migrate {
@ -377,7 +376,7 @@ async fn message_callback(
.inc();
let mut event = match json {
true => serde_json::from_slice(&p.payload)?,
false => chirpstack_api::gw::GatewayStats::decode(&mut Cursor::new(&p.payload))?,
false => chirpstack_api::gw::GatewayStats::decode(p.payload.as_ref())?,
};
if v4_migrate {
@ -401,7 +400,7 @@ async fn message_callback(
.inc();
let mut event = match json {
true => serde_json::from_slice(&p.payload)?,
false => chirpstack_api::gw::DownlinkTxAck::decode(&mut Cursor::new(&p.payload))?,
false => chirpstack_api::gw::DownlinkTxAck::decode(p.payload.as_ref())?,
};
if v4_migrate {
@ -410,18 +409,18 @@ async fn message_callback(
set_gateway_json(&event.gateway_id, json);
tokio::spawn(downlink::tx_ack::TxAck::handle(event));
} else if topic.ends_with("/mesh-heartbeat") {
} else if topic.ends_with("/mesh") {
EVENT_COUNTER
.get_or_create(&EventLabels {
event: "mesh-heartbeat".to_string(),
event: "mesh".to_string(),
})
.inc();
let event = match json {
true => serde_json::from_slice(&p.payload)?,
false => chirpstack_api::gw::MeshHeartbeat::decode(&mut Cursor::new(&p.payload))?,
false => chirpstack_api::gw::MeshEvent::decode(p.payload.as_ref())?,
};
tokio::spawn(uplink::mesh::MeshHeartbeat::handle(event));
tokio::spawn(uplink::mesh::Mesh::handle(event));
} else {
return Err(anyhow!("Unknown event type"));
}

View File

@ -27,6 +27,7 @@ pub struct Integration<'a> {
templates: Handlebars<'a>,
json: bool,
url: String,
exchange: String,
}
#[derive(Serialize)]
@ -49,6 +50,7 @@ impl<'a> Integration<'a> {
templates,
url: conf.url.clone(),
json: conf.json,
exchange: conf.exchange.clone(),
};
i.connect().await?;
@ -90,7 +92,7 @@ impl<'a> Integration<'a> {
.as_ref()
.unwrap()
.basic_publish(
"amq.topic",
&self.exchange,
&routing_key,
BasicPublishOptions::default(),
b,

View File

@ -77,7 +77,7 @@ pub async fn handle_uplink(
);
// Get pending mac-command block, this could return None.
let pending = match mac_command::get_pending(&dev.dev_eui, cid).await {
let pending = match mac_command::get_pending(dev.get_device_session_mut()?, cid).await {
Ok(v) => v,
Err(e) => {
error!(dev_eui = %dev.dev_eui, cid = %cid, error = %e, "Get pending mac-command block error");
@ -85,13 +85,6 @@ pub async fn handle_uplink(
}
};
// Delete the pending mac-command.
if pending.is_some() {
if let Err(e) = mac_command::delete_pending(&dev.dev_eui, cid).await {
error!(dev_eui = %dev.dev_eui, cid = %cid, error = %e, "Delete pending mac-command error");
}
}
// Handle the mac-command, which might return a block to answer the uplink mac-command
// request.
let res = match handle(

View File

@ -327,6 +327,15 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
return Err(Error::InvalidPayload("MacPayload".to_string()));
};
// We calculate the scheduler_run_after timestamp, such that we can update
// it directly when updating the device-session (to update the frame-counter).
// This way, we limit the risk of overlapping Class-A downlinks with Class-B / -C
// downlinks.
let conf = config::get();
let scheduler_run_after = Utc::now()
+ Duration::from_std(conf.network.scheduler.class_a_lock_duration)
.map_err(anyhow::Error::new)?;
let mut c = get_async_db_conn().await?;
db_transaction::<ValidationStatus, Error, _>(&mut c, |c| {
@ -427,10 +436,20 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up(
let ds_f_cnt_up = ds.f_cnt_up;
ds.f_cnt_up = full_f_cnt + 1;
let _ = diesel::update(device::dsl::device.find(d.dev_eui))
.set(device::device_session.eq(&ds.clone()))
.execute(c)
.await?;
if scheduler_run_after > d.scheduler_run_after.unwrap_or_default() {
let _ = diesel::update(device::dsl::device.find(d.dev_eui))
.set((
device::device_session.eq(&ds.clone()),
device::scheduler_run_after.eq(&scheduler_run_after),
))
.execute(c)
.await?;
} else {
let _ = diesel::update(device::dsl::device.find(d.dev_eui))
.set(device::device_session.eq(&ds.clone()))
.execute(c)
.await?;
}
// We do return the device-session with original frame-counter
ds.f_cnt_up = ds_f_cnt_up;

View File

@ -24,7 +24,10 @@ pub async fn save(df: &internal::DownlinkFrame) -> Result<()> {
pub async fn get_and_del(id: u32) -> Result<internal::DownlinkFrame, Error> {
let key = redis_key(format!("frame:{}", id));
let v: Vec<u8> = redis::cmd("GETDEL")
let (v, _): (Vec<u8>, u8) = redis::pipe()
.cmd("GET")
.arg(key.clone())
.cmd("DEL")
.arg(key)
.query_async(&mut get_async_redis_conn().await?)
.await?;

View File

@ -1,34 +1,24 @@
use anyhow::Result;
use tracing::info;
use super::{get_async_redis_conn, redis_key};
use crate::config;
use lrwn::EUI64;
use chirpstack_api::internal;
pub async fn set_pending(dev_eui: &EUI64, cid: lrwn::CID, set: &lrwn::MACCommandSet) -> Result<()> {
let conf = config::get();
let key = redis_key(format!("device:{}:mac:pending:{}", dev_eui, cid.to_u8()));
let ttl = conf.network.device_session_ttl.as_millis() as usize;
pub fn set_pending(ds: &mut internal::DeviceSession, set: &lrwn::MACCommandSet) -> Result<()> {
let cid = set.cid()?;
let b = set.to_vec()?;
() = redis::cmd("PSETEX")
.arg(key)
.arg(ttl)
.arg(b)
.query_async(&mut get_async_redis_conn().await?)
.await?;
info!(dev_eui = %dev_eui, cid = %cid, "Pending mac-command block set");
ds.mac_command_pending.insert(cid.to_u8().into(), b);
info!(cid = %cid, "Pending mac-command block set");
Ok(())
}
pub async fn get_pending(dev_eui: &EUI64, cid: lrwn::CID) -> Result<Option<lrwn::MACCommandSet>> {
let key = redis_key(format!("device:{}:mac:pending:{}", dev_eui, cid.to_u8()));
let b: Vec<u8> = redis::cmd("GET")
.arg(key)
.query_async(&mut get_async_redis_conn().await?)
.await?;
pub async fn get_pending(
ds: &mut internal::DeviceSession,
cid: lrwn::CID,
) -> Result<Option<lrwn::MACCommandSet>> {
let b = ds
.mac_command_pending
.remove(&cid.to_u8().into())
.unwrap_or_default();
let out = if !b.is_empty() {
let mut mac = lrwn::MACCommandSet::from_slice(&b);
@ -44,49 +34,3 @@ pub async fn get_pending(dev_eui: &EUI64, cid: lrwn::CID) -> Result<Option<lrwn:
Ok(out)
}
pub async fn delete_pending(dev_eui: &EUI64, cid: lrwn::CID) -> Result<()> {
let key = redis_key(format!("device:{}:mac:pending:{}", dev_eui, cid.to_u8()));
() = redis::cmd("DEL")
.arg(key)
.query_async(&mut get_async_redis_conn().await?)
.await?;
info!(dev_eui = %dev_eui, cid = %cid, "Pending mac-command block deleted");
Ok(())
}
#[cfg(test)]
pub mod test {
use super::*;
use crate::test;
#[tokio::test]
async fn test_mac_command() {
let _guard = test::prepare().await;
let dev_eui = EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]);
let mac = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::DevStatusReq]);
// set
set_pending(&dev_eui, lrwn::CID::DevStatusReq, &mac)
.await
.unwrap();
// get
let mac_get = get_pending(&dev_eui, lrwn::CID::DevStatusReq)
.await
.unwrap();
assert_eq!(mac, mac_get.unwrap());
// delete
delete_pending(&dev_eui, lrwn::CID::DevStatusReq)
.await
.unwrap();
let resp = get_pending(&dev_eui, lrwn::CID::DevStatusReq)
.await
.unwrap();
assert!(resp.is_none());
}
}

View File

@ -4230,33 +4230,30 @@ async fn test_lorawan_10_adr() {
name: "acknowledgement of pending adr request".into(),
dev_eui: dev.dev_eui,
device_queue_items: vec![],
before_func: Some(Box::new(move || {
let dev_eui = dev.dev_eui;
Box::pin(async move {
mac_command::set_pending(
&dev_eui,
lrwn::CID::LinkADRReq,
&lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRReq(
lrwn::LinkADRReqPayload {
dr: 0,
tx_power: 3,
ch_mask: lrwn::ChMask::new([
true, true, true, false, false, false, false, false, false,
false, false, false, false, false, false, false,
]),
redundancy: lrwn::Redundancy {
ch_mask_cntl: 0,
nb_rep: 1,
},
},
)]),
)
.await
.unwrap();
})
})),
before_func: None,
after_func: None,
device_session: Some(ds.clone()),
device_session: Some({
let mut ds = ds.clone();
mac_command::set_pending(
&mut ds,
&lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRReq(
lrwn::LinkADRReqPayload {
dr: 0,
tx_power: 3,
ch_mask: lrwn::ChMask::new([
true, true, true, false, false, false, false, false, false, false,
false, false, false, false, false, false,
]),
redundancy: lrwn::Redundancy {
ch_mask_cntl: 0,
nb_rep: 1,
},
},
)]),
)
.unwrap();
ds
}),
tx_info: tx_info.clone(),
rx_info: rx_info.clone(),
phy_payload: lrwn::PhyPayload {
@ -4298,33 +4295,30 @@ async fn test_lorawan_10_adr() {
name: "negative acknowledgement of pending adr request".into(),
dev_eui: dev.dev_eui,
device_queue_items: vec![],
before_func: Some(Box::new(move || {
let dev_eui = dev.dev_eui;
Box::pin(async move {
mac_command::set_pending(
&dev_eui,
lrwn::CID::LinkADRReq,
&lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRReq(
lrwn::LinkADRReqPayload {
dr: 0,
tx_power: 3,
ch_mask: lrwn::ChMask::new([
true, true, true, false, false, false, false, false, false,
false, false, false, false, false, false, false,
]),
redundancy: lrwn::Redundancy {
ch_mask_cntl: 0,
nb_rep: 1,
},
},
)]),
)
.await
.unwrap();
})
})),
before_func: None,
after_func: None,
device_session: Some(ds.clone()),
device_session: Some({
let mut ds = ds.clone();
mac_command::set_pending(
&mut ds,
&lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRReq(
lrwn::LinkADRReqPayload {
dr: 0,
tx_power: 3,
ch_mask: lrwn::ChMask::new([
true, true, true, false, false, false, false, false, false, false,
false, false, false, false, false, false,
]),
redundancy: lrwn::Redundancy {
ch_mask_cntl: 0,
nb_rep: 1,
},
},
)]),
)
.unwrap();
ds
}),
tx_info: tx_info.clone(),
rx_info: rx_info.clone(),
phy_payload: lrwn::PhyPayload {
@ -4540,33 +4534,30 @@ async fn test_lorawan_10_adr() {
name: "new channel re-configuration ack-ed".into(),
dev_eui: dev.dev_eui,
device_queue_items: vec![],
before_func: Some(Box::new(move || {
let dev_eui = dev.dev_eui;
Box::pin(async move {
mac_command::set_pending(
&dev_eui,
lrwn::CID::LinkADRReq,
&lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRReq(
lrwn::LinkADRReqPayload {
dr: 0,
tx_power: 1,
ch_mask: lrwn::ChMask::new([
true, true, true, false, false, false, false, false, false,
false, false, false, false, false, false, false,
]),
redundancy: lrwn::Redundancy {
ch_mask_cntl: 0,
nb_rep: 0,
},
},
)]),
)
.await
.unwrap();
})
})),
before_func: None,
after_func: None,
device_session: Some(ds_7chan.clone()),
device_session: Some({
let mut ds = ds_7chan.clone();
mac_command::set_pending(
&mut ds,
&lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRReq(
lrwn::LinkADRReqPayload {
dr: 0,
tx_power: 1,
ch_mask: lrwn::ChMask::new([
true, true, true, false, false, false, false, false, false, false,
false, false, false, false, false, false,
]),
redundancy: lrwn::Redundancy {
ch_mask_cntl: 0,
nb_rep: 0,
},
},
)]),
)
.unwrap();
ds
}),
tx_info: tx_info.clone(),
rx_info: rx_info.clone(),
phy_payload: lrwn::PhyPayload {
@ -4606,33 +4597,30 @@ async fn test_lorawan_10_adr() {
name: "new channel re-configuration not ack-ed".into(),
dev_eui: dev.dev_eui,
device_queue_items: vec![],
before_func: Some(Box::new(move || {
let dev_eui = dev.dev_eui;
Box::pin(async move {
mac_command::set_pending(
&dev_eui,
lrwn::CID::LinkADRReq,
&lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRReq(
lrwn::LinkADRReqPayload {
dr: 0,
tx_power: 1,
ch_mask: lrwn::ChMask::new([
true, true, true, false, false, false, false, false, false,
false, false, false, false, false, false, false,
]),
redundancy: lrwn::Redundancy {
ch_mask_cntl: 0,
nb_rep: 0,
},
},
)]),
)
.await
.unwrap();
})
})),
before_func: None,
after_func: None,
device_session: Some(ds_7chan.clone()),
device_session: Some({
let mut ds = ds_7chan.clone();
mac_command::set_pending(
&mut ds,
&lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRReq(
lrwn::LinkADRReqPayload {
dr: 0,
tx_power: 1,
ch_mask: lrwn::ChMask::new([
true, true, true, false, false, false, false, false, false, false,
false, false, false, false, false, false,
]),
redundancy: lrwn::Redundancy {
ch_mask_cntl: 0,
nb_rep: 0,
},
},
)]),
)
.unwrap();
ds
}),
tx_info: tx_info.clone(),
rx_info: rx_info.clone(),
phy_payload: lrwn::PhyPayload {

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::str::FromStr;
use anyhow::{Context, Result};
use chrono::{DateTime, Duration, Local, Utc};
use chrono::{DateTime, Local, Utc};
use tracing::{debug, error, info, span, trace, warn, Instrument, Level};
use super::error::Error;
@ -126,7 +126,6 @@ impl Data {
ctx.set_device_info()?;
ctx.set_device_gateway_rx_info()?;
ctx.handle_retransmission_reset().await?;
ctx.set_scheduler_run_after().await?;
ctx.decrypt_f_opts_mac_commands()?;
ctx.decrypt_frm_payload()?;
ctx.log_uplink_frame_set().await?;
@ -522,36 +521,6 @@ impl Data {
Err(Error::Abort)
}
// For Class-B and Class-C devices, set the scheduler_run_after timestamp to avoid collisions with
// the Class-A downlink and Class-B/C scheduler.
async fn set_scheduler_run_after(&mut self) -> Result<()> {
let dev = self.device.as_mut().unwrap();
let conf = config::get();
if dev.enabled_class == DeviceClass::B || dev.enabled_class == DeviceClass::C {
trace!("Setting scheduler_run_after for device");
let scheduler_run_after =
Utc::now() + Duration::from_std(conf.network.scheduler.class_a_lock_duration)?;
// Only set the new scheduler_run_after if it is currently None
// or when the current value is before the calculated scheduler_run_after.
if dev.scheduler_run_after.is_none()
|| scheduler_run_after > dev.scheduler_run_after.unwrap()
{
*dev = device::partial_update(
dev.dev_eui,
&device::DeviceChangeset {
scheduler_run_after: Some(Some(scheduler_run_after)),
..Default::default()
},
)
.await?;
}
}
Ok(())
}
async fn filter_rx_info_by_tenant(&mut self) -> Result<()> {
trace!("Filtering rx_info by tenant_id");

View File

@ -14,14 +14,15 @@ use crate::storage::{
};
use lrwn::EUI64;
pub struct MeshHeartbeat {
pub struct Mesh {
gateway_id: EUI64,
relay_id: RelayId,
mesh_stats: gw::MeshHeartbeat,
time: DateTime<Utc>,
mesh_event: gw::MeshEvent,
}
impl MeshHeartbeat {
pub async fn handle(s: gw::MeshHeartbeat) {
impl Mesh {
pub async fn handle(s: gw::MeshEvent) {
let gateway_id = match EUI64::from_str(&s.gateway_id) {
Ok(v) => v,
Err(e) => {
@ -38,9 +39,9 @@ impl MeshHeartbeat {
}
};
let span = span!(Level::INFO, "mesh_stats", gateway_id = %gateway_id, relay_id = %relay_id);
let span = span!(Level::INFO, "mesh", gateway_id = %gateway_id, relay_id = %relay_id);
if let Err(e) = MeshHeartbeat::_handle(gateway_id, relay_id, s)
if let Err(e) = Mesh::_handle(gateway_id, relay_id, s)
.instrument(span)
.await
{
@ -48,52 +49,61 @@ impl MeshHeartbeat {
Some(Error::NotFound(_)) => {
let conf = config::get();
if !conf.gateway.allow_unknown_gateways {
error!(error = %e.full(), "Handle mesh-stats error");
error!(error = %e.full(), "Handle mesh error");
}
}
Some(_) | None => {
error!(error = %e.full(), "Handle mesh-stats error");
error!(error = %e.full(), "Handle mesh error");
}
}
}
}
async fn _handle(gateway_id: EUI64, relay_id: RelayId, s: gw::MeshHeartbeat) -> Result<()> {
let mut ctx = MeshHeartbeat {
async fn _handle(gateway_id: EUI64, relay_id: RelayId, s: gw::MeshEvent) -> Result<()> {
let ctx = Mesh {
gateway_id,
relay_id,
mesh_stats: s,
time: s
.time
.ok_or_else(|| anyhow!("Time field is empty"))?
.try_into()
.map_err(|e| anyhow!("Covert time error: {}", e))?,
mesh_event: s,
};
ctx.update_or_create_relay_gateway().await?;
ctx.handle_events().await?;
Ok(())
}
async fn update_or_create_relay_gateway(&mut self) -> Result<()> {
trace!("Getting Border Gateway");
let border_gw = gateway::get(&self.gateway_id).await?;
async fn handle_events(&self) -> Result<()> {
trace!("Handling mesh events");
let ts: DateTime<Utc> = match &self.mesh_stats.time {
Some(v) => (*v)
.try_into()
.map_err(|e| anyhow!("Convert time error: {}", e))?,
None => {
warn!("Stats message does not have time field set");
return Ok(());
for event in &self.mesh_event.events {
match &event.event {
Some(gw::mesh_event_item::Event::Proprietary(_)) | None => continue,
Some(gw::mesh_event_item::Event::Heartbeat(v)) => self._handle_heartbeat(v).await?,
}
};
}
Ok(())
}
async fn _handle_heartbeat(&self, _pl: &gw::MeshEventHeartbeat) -> Result<()> {
trace!("Handling heartbeat event");
let border_gw = gateway::get(&self.gateway_id).await?;
match gateway::get_relay_gateway(border_gw.tenant_id.into(), self.relay_id).await {
Ok(mut v) => {
if let Some(last_seen_at) = v.last_seen_at {
if last_seen_at > ts {
warn!("Time is less than last seen timestamp, ignoring stats");
if last_seen_at > self.time {
warn!("Time is less than last seen timestamp, ignoring heartbeat");
return Ok(());
}
}
v.last_seen_at = Some(ts);
v.last_seen_at = Some(self.time);
v.region_config_id = border_gw
.properties
.get("region_config_id")
@ -106,7 +116,7 @@ impl MeshHeartbeat {
tenant_id: border_gw.tenant_id,
relay_id: self.relay_id,
name: self.relay_id.to_string(),
last_seen_at: Some(ts),
last_seen_at: Some(self.time),
..Default::default()
})
.await?;

View File

@ -3,7 +3,7 @@
description = "Library for filtering LoRaWAN payloads on DevAddr and JoinEUIs prefixes"
homepage = "https://www.chirpstack.io/"
license = "MIT"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021"
repository = "https://github.com/chirpstack/chirpstack"

View File

@ -3,7 +3,7 @@
description = "Library for encoding / decoding LoRaWAN frames."
homepage = "https://www.chirpstack.io"
license = "MIT"
version = "4.12.1-test.1"
version = "4.13.0-test.1"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018"
repository = "https://github.com/chirpstack/chirpstack"

View File

@ -402,6 +402,15 @@ impl MACCommandSet {
MACCommandSet(macs)
}
// This reads the CID from the first mac-command in the set. It is assumed
// that all mac-commands in the set share the same CID.
pub fn cid(&self) -> Result<CID> {
self.0
.first()
.map(|v| v.cid())
.ok_or_else(|| anyhow!("Set is empty"))
}
pub fn size(&self) -> Result<usize> {
let b = self.to_vec()?;
Ok(b.len())

View File

@ -1,6 +1,6 @@
{
"name": "chirpstack-ui",
"version": "4.12.1-test.1",
"version": "4.13.0-test.1",
"private": true,
"type": "module",
"scripts": {

View File

@ -19,11 +19,9 @@ interface IProps {
function CodeEditor(props: IProps) {
const form = Form.useFormInstance();
const [value, setValue] = useState<string>("");
const [reloadKey, setReloadKey] = useState<number>(1);
useEffect(() => {
setValue(form.getFieldValue(props.name));
setReloadKey(k => k + 1);
setValue(form.getFieldValue(props.name) || "");
}, [form, props]);
const onChange = (newValue: string) => {
@ -41,7 +39,6 @@ function CodeEditor(props: IProps) {
theme="github"
onChange={onChange}
value={value}
name={`code-editor-refresh-${reloadKey}`}
width="100%"
height="600px"
editorProps={{ $blockScrolling: true }}