Compare commits

...

5 Commits

Author SHA1 Message Date
bb53821aef 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-08 14:29:15 +01:00
d002f5c97b ui: Format code. 2025-04-28 10:18:44 +01:00
9cf12a187c ui: Clear tenant id after delete.
Fixes #635.
2025-04-28 10:18:05 +01:00
1b5e5972f4 ui: Fix JSON enqueue issue / code-editor render issue.
This fixes two issues:

- An error logged to the console when enqueueing a JSON downlink payload
(Q.Va is not a function).
- The codemirror editor has been replaced by ace, to solve a potential
rendering issue within ReactJS / Antd.

Closes #658.
2025-04-28 10:09:52 +01:00
330f5dcae0 Bump version to 4.12.0 2025-04-22 09:01:31 +01:00
27 changed files with 342 additions and 167 deletions

12
Cargo.lock generated
View File

@ -629,7 +629,7 @@ dependencies = [
[[package]]
name = "backend"
version = "4.12.0-test.4"
version = "4.12.0"
dependencies = [
"aes-kw",
"anyhow",
@ -831,7 +831,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chirpstack"
version = "4.12.0-test.4"
version = "4.12.0"
dependencies = [
"aes",
"anyhow",
@ -923,7 +923,7 @@ dependencies = [
[[package]]
name = "chirpstack_api"
version = "4.12.0-test.4"
version = "4.12.0"
dependencies = [
"hex",
"pbjson",
@ -940,7 +940,7 @@ dependencies = [
[[package]]
name = "chirpstack_integration"
version = "4.12.0-test.4"
version = "4.12.0"
dependencies = [
"anyhow",
"async-trait",
@ -2776,7 +2776,7 @@ dependencies = [
[[package]]
name = "lrwn"
version = "4.12.0-test.4"
version = "4.12.0"
dependencies = [
"aes",
"anyhow",
@ -2789,7 +2789,7 @@ dependencies = [
[[package]]
name = "lrwn_filters"
version = "4.12.0-test.4"
version = "4.12.0"
dependencies = [
"hex",
"lrwn",

View File

@ -1,16 +1,16 @@
{
"name": "@chirpstack/chirpstack-api-grpc-web",
"version": "4.12.0-test.4",
"version": "4.12.0",
"description": "Chirpstack gRPC-web API",
"license": "MIT",
"devDependencies": {
"grpc-tools": "^1.12.4",
"grpc-tools": "^1.13.0",
"ts-protoc-gen": "^0.15.0",
"typescript": "^5.1.6"
"typescript": "^5.8.3"
},
"dependencies": {
"@types/google-protobuf": "^3.15.12",
"google-protobuf": "^3.21.2",
"google-protobuf": "^3.21.4",
"grpc-web": "^1.5.0"
}
}

View File

@ -146,15 +146,20 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
google-protobuf@^3.15.5, google-protobuf@^3.21.2:
google-protobuf@^3.15.5:
version "3.21.2"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4"
integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==
grpc-tools@^1.12.4:
version "1.12.4"
resolved "https://registry.yarnpkg.com/grpc-tools/-/grpc-tools-1.12.4.tgz#a044c9e8157941033ea7a5f144c2dc9dc4501de4"
integrity sha512-5+mLAJJma3BjnW/KQp6JBjUMgvu7Mu3dBvBPd1dcbNIb+qiR0817zDpgPjS7gRb+l/8EVNIa3cB02xI9JLToKg==
google-protobuf@^3.21.4:
version "3.21.4"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.4.tgz#2f933e8b6e5e9f8edde66b7be0024b68f77da6c9"
integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==
grpc-tools@^1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/grpc-tools/-/grpc-tools-1.13.0.tgz#a4fea8eebce51fb9fec00055a3e52016dfd5af89"
integrity sha512-7CbkJ1yWPfX0nHjbYG58BQThNhbICXBZynzCUxCb3LzX5X9B3hQbRY2STiRgIEiLILlK9fgl0z0QVGwPCdXf5g==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.5"
@ -376,10 +381,10 @@ ts-protoc-gen@^0.15.0:
dependencies:
google-protobuf "^3.15.5"
typescript@^5.1.6:
version "5.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff"
integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==
typescript@^5.8.3:
version "5.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
util-deprecate@^1.0.1:
version "1.0.2"

View File

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

12
api/js/package.json vendored
View File

@ -1,17 +1,17 @@
{
"name": "@chirpstack/chirpstack-api",
"version": "4.12.0-test.4",
"version": "4.12.0",
"description": "Chirpstack JS and TS API",
"license": "MIT",
"devDependencies": {
"grpc-tools": "^1.12.4",
"grpc-tools": "^1.13.0",
"ts-protoc-gen": "^0.15.0",
"typescript": "^5.1.6"
"typescript": "^5.8.3"
},
"dependencies": {
"@grpc/grpc-js": "^1.10.4",
"@grpc/grpc-js": "^1.13.3",
"@mapbox/node-pre-gyp": "^1.0.11",
"@types/google-protobuf": "^3.15.6",
"google-protobuf": "^3.21.2"
"@types/google-protobuf": "^3.15.12",
"google-protobuf": "^3.21.4"
}
}

39
api/js/yarn.lock vendored
View File

@ -2,10 +2,10 @@
# yarn lockfile v1
"@grpc/grpc-js@^1.10.4":
version "1.10.9"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.9.tgz#468cc1549a3fe37b760a16745fb7685d91f4f10c"
integrity sha512-5tcgUctCG0qoNyfChZifz2tJqbRbXVO9J7X6duFcOjY3HUNCxg5D0ZCK7EP9vIcZ0zRpLU9bWkyCqVCLZ46IbQ==
"@grpc/grpc-js@^1.13.3":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.13.3.tgz#6ad08d186c2a8651697085f790c5c68eaca45904"
integrity sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg==
dependencies:
"@grpc/proto-loader" "^0.7.13"
"@js-sdsl/ordered-map" "^4.4.2"
@ -93,10 +93,10 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
"@types/google-protobuf@^3.15.6":
version "3.15.6"
resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.6.tgz#674a69493ef2c849b95eafe69167ea59079eb504"
integrity sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==
"@types/google-protobuf@^3.15.12":
version "3.15.12"
resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.12.tgz#eb2ba0eddd65712211a2b455dc6071d665ccf49b"
integrity sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==
"@types/node@>=13.7.0":
version "20.4.8"
@ -265,15 +265,20 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
google-protobuf@^3.15.5, google-protobuf@^3.21.2:
google-protobuf@^3.15.5:
version "3.21.2"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4"
integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==
grpc-tools@^1.12.4:
version "1.12.4"
resolved "https://registry.yarnpkg.com/grpc-tools/-/grpc-tools-1.12.4.tgz#a044c9e8157941033ea7a5f144c2dc9dc4501de4"
integrity sha512-5+mLAJJma3BjnW/KQp6JBjUMgvu7Mu3dBvBPd1dcbNIb+qiR0817zDpgPjS7gRb+l/8EVNIa3cB02xI9JLToKg==
google-protobuf@^3.21.4:
version "3.21.4"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.4.tgz#2f933e8b6e5e9f8edde66b7be0024b68f77da6c9"
integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==
grpc-tools@^1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/grpc-tools/-/grpc-tools-1.13.0.tgz#a4fea8eebce51fb9fec00055a3e52016dfd5af89"
integrity sha512-7CbkJ1yWPfX0nHjbYG58BQThNhbICXBZynzCUxCb3LzX5X9B3hQbRY2STiRgIEiLILlK9fgl0z0QVGwPCdXf5g==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.5"
@ -523,10 +528,10 @@ ts-protoc-gen@^0.15.0:
dependencies:
google-protobuf "^3.15.5"
typescript@^5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
typescript@^5.8.3:
version "5.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
util-deprecate@^1.0.1:
version "1.0.2"

View File

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

View File

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

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

@ -104,6 +104,61 @@ 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.
Mesh 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;
}
}
message Mesh {
// 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 MeshEvent events = 4;
}
message MeshEvent {
oneof event {
// Proprietary Mesh event.
MeshEventProprietary proprietary = 1;
// Mesh heartbeat.
MeshEventHeartbeat heartbeat = 2;
}
}
message Modulation {
oneof parameters {
// LoRa modulation information.
@ -611,6 +666,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 +823,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 +839,12 @@ message MeshHeartbeatRelayPath {
// SNR.
int32 snr = 3;
}
// Proprietary mesh event.
message MeshEventProprietary {
// Event type.
uint32 event_type = 1;
// Payload.
bytes payload = 2;
}

View File

@ -18,7 +18,7 @@ CLASSIFIERS = [
setup(
name='chirpstack-api',
version = "4.12.0-test.4",
version = "4.12.0",
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.0-test.4"
version = "4.12.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
license = "MIT"
homepage = "https://www.chirpstack.io"

View File

@ -104,6 +104,61 @@ 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.
Mesh 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;
}
}
message Mesh {
// 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 MeshEvent events = 4;
}
message MeshEvent {
oneof event {
// Proprietary Mesh event.
MeshEventProprietary proprietary = 1;
// Mesh heartbeat.
MeshEventHeartbeat heartbeat = 2;
}
}
message Modulation {
oneof parameters {
// LoRa modulation information.
@ -611,6 +666,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 +823,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 +839,12 @@ message MeshHeartbeatRelayPath {
// SNR.
int32 snr = 3;
}
// Proprietary mesh event.
message MeshEventProprietary {
// Event type.
uint32 event_type = 1;
// Payload.
bytes payload = 2;
}

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.0-test.4"
version = "4.12.0"
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.0-test.4"
version = "4.12.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021"
repository = "https://github.com/chirpstack/chirpstack"
[dependencies]
chirpstack_api = { path = "../api/rust", version = "4.12.0-test.4" }
chirpstack_api = { path = "../api/rust", version = "4.12.0" }
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.0-test.4"
version = "4.12.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021"
publish = false

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::Mesh::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

@ -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::Mesh,
}
impl MeshHeartbeat {
pub async fn handle(s: gw::MeshHeartbeat) {
impl Mesh {
pub async fn handle(s: gw::Mesh) {
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::Mesh) -> 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::Event::Proprietary(_)) | None => continue,
Some(gw::mesh_event::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.0-test.4"
version = "4.12.0"
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.0-test.4"
version = "4.12.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018"
repository = "https://github.com/chirpstack/chirpstack"

View File

@ -1,6 +1,6 @@
{
"name": "chirpstack-ui",
"version": "4.12.0-test.4",
"version": "4.12.0",
"private": true,
"type": "module",
"scripts": {
@ -18,12 +18,12 @@
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"ace-builds": "^1.36.4",
"antd": "^5.23.3",
"buffer": "^6.0.3",
"chart.js": "^4.4.7",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-chart-matrix": "^2.0.1",
"codemirror": "^5.65.16",
"date-fns": "^3.6.0",
"events": "^3.3.0",
"google-palette": "^1.1.1",
@ -32,8 +32,8 @@
"leaflet": "^1.9.4",
"leaflet.awesome-markers": "^2.0.5",
"react": "^18.3.1",
"react-ace": "^13.0.0",
"react-chartjs-2": "^5.2.0",
"react-codemirror2": "^8.0.0",
"react-dom": "^18.3.1",
"react-json-tree": "^0.19.0",
"react-leaflet": "^4.2.1",
@ -43,7 +43,6 @@
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.6",
"@types/codemirror": "^5.60.15",
"@types/events": "^3.0.3",
"@types/leaflet": "^1.9.12",
"@types/leaflet.awesome-markers": "^2.0.28",
@ -56,7 +55,7 @@
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"prettier": "^3.3.2",
"typescript": "^5.2.2",
"typescript": "^5.8.3",
"vite": "^6.2.6"
}
}

View File

@ -1,10 +1,11 @@
import { useState, useEffect } from "react";
import { Controlled as CodeMirror } from "react-codemirror2";
import type { Editor, EditorChange } from "codemirror";
import { Form } from "antd";
import AceEditor from "react-ace";
import "codemirror/mode/javascript/javascript";
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-github";
interface IProps {
label?: string;
@ -12,6 +13,7 @@ interface IProps {
required?: boolean;
disabled?: boolean;
tooltip?: string;
mode?: string;
}
function CodeEditor(props: IProps) {
@ -24,28 +26,25 @@ function CodeEditor(props: IProps) {
setReloadKey(k => k + 1);
}, [form, props]);
const handleChange = (editor: Editor, data: EditorChange, newCode: string) => {
setValue(newCode);
const onChange = (newValue: string) => {
setValue(newValue);
form.setFieldsValue({
[props.name]: newCode,
[props.name]: newValue,
});
};
const codeMirrorOptions = {
lineNumbers: true,
mode: "javascript",
theme: "base16-light",
readOnly: props.disabled,
};
return (
<Form.Item label={props.label} name={props.name} tooltip={props.tooltip}>
<div style={{ border: "1px solid #cccccc" }}>
<CodeMirror
key={`code-editor-refresh-${reloadKey}`}
<AceEditor
mode={props.mode || "javascript"}
theme="github"
onChange={onChange}
value={value}
options={codeMirrorOptions}
onBeforeChange={handleChange}
name={`code-editor-refresh-${reloadKey}`}
width="100%"
height="600px"
editorProps={{ $blockScrolling: true }}
/>
</div>
</Form.Item>

View File

@ -12,8 +12,6 @@ import "antd/dist/reset.css";
import "leaflet/dist/leaflet.css";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "@fortawesome/fontawesome-free/css/all.css";
import "codemirror/lib/codemirror.css";
import "codemirror/theme/base16-light.css";
import "./index.css";
Chart.register(MatrixController, MatrixElement, ...registerables);

View File

@ -268,7 +268,7 @@ function DeviceQueue(props: IProps) {
</Form.Item>
</Tabs.TabPane>
<Tabs.TabPane tab="JSON" key="3">
<CodeEditor name="json" />
<CodeEditor name="json" mode="json" />
</Tabs.TabPane>
</Tabs>
<Button type="primary" htmlType="submit">

View File

@ -168,7 +168,9 @@ function FuotaDeploymentLayout(props: IProps) {
description="Are you sure you want to start the deploymen? Once started, you will not be able to make changes."
onConfirm={startFuotaDeployment}
>
<Button type="primary" disabled={getFuotaDeploymentResponse.getStartedAt() !== undefined}>Start deployment</Button>
<Button type="primary" disabled={getFuotaDeploymentResponse.getStartedAt() !== undefined}>
Start deployment
</Button>
</Popconfirm>
<DeleteConfirm typ="FUOTA deployment" confirm={d.getName()} onConfirm={deleteFuotaDeployment}>
<Button danger type="primary">

View File

@ -8,6 +8,7 @@ import { DeleteTenantRequest } from "@chirpstack/chirpstack-api-grpc-web/api/ten
import TenantStore from "../../stores/TenantStore";
import DeleteConfirm from "../../components/DeleteConfirm";
import SessionStore from "../../stores/SessionStore";
import Admin from "../../components/Admin";
import EditTenant from "./EditTenant";
import TenantDashboard from "./TenantDashboard";
@ -23,6 +24,7 @@ function TenantLayout({ tenant }: { tenant: Tenant }) {
req.setId(tenant.getId());
TenantStore.delete(req, () => {
SessionStore.setTenantId("");
navigate("/tenants");
});
};

View File

@ -127,10 +127,10 @@
regenerator-runtime "^0.14.0"
"@chirpstack/chirpstack-api-grpc-web@file:../api/grpc-web":
version "4.12.0-test.3"
version "4.12.0"
dependencies:
"@types/google-protobuf" "^3.15.12"
google-protobuf "^3.21.2"
google-protobuf "^3.21.4"
grpc-web "^1.5.0"
"@ctrl/tinycolor@^3.4.0":
@ -667,13 +667,6 @@
lodash "^4.17.21"
redent "^3.0.0"
"@types/codemirror@^5.60.15":
version "5.60.15"
resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-5.60.15.tgz#0f82be6f4126d1e59cf4c4830e56dcd49d3c3e8a"
integrity sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==
dependencies:
"@types/tern" "*"
"@types/debug@^4.0.0":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
@ -764,13 +757,6 @@
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/tern@*":
version "0.23.9"
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.9.tgz#6f6093a4a9af3e6bb8dde528e024924d196b367c"
integrity sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==
dependencies:
"@types/estree" "*"
"@types/unist@*", "@types/unist@^3.0.0":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
@ -884,6 +870,11 @@
dependencies:
"@swc/core" "^1.11.21"
ace-builds@^1.36.3, ace-builds@^1.36.4:
version "1.40.1"
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.40.1.tgz#68421283088f0e075f3375cb3d46bb86ad6ddc6e"
integrity sha512-32uwJNwmhqpnYtr6oq8RoO1D6F6tnxisv5f9w2XPX3vi4QruuHNikadHUiHvnxLAV1n5Azv4LFtpItQ5dD1eRw==
acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
@ -1099,11 +1090,6 @@ classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classna
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
codemirror@^5.65.16:
version "5.65.19"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.19.tgz#71016c701d6a4b6e1982b0f6e7186be65e49653d"
integrity sha512-+aFkvqhaAVr1gferNMuN8vkTSrWIFvzlMV9I2KBLCWS2WpZ2+UAkZjlMZmEuT+gcXTi6RrGQCkWq1/bDtGqhIA==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@ -1214,6 +1200,11 @@ devlop@^1.0.0, devlop@^1.1.0:
dependencies:
dequal "^2.0.0"
diff-match-patch@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@ -1513,7 +1504,7 @@ google-palette@^1.1.1:
resolved "https://registry.yarnpkg.com/google-palette/-/google-palette-1.1.1.tgz#671b3b932c8393271b67da908ccd7f2c48e84cc7"
integrity sha512-yZiM5oLl8lCZzf06IMOGdDkxqvCMd9HNFcCiOMqWgGGiGzC22vWBVhKJNvykXXbeC0NAElNH97jA/y0bq6TCrA==
google-protobuf@^3.21.2:
google-protobuf@^3.21.4:
version "3.21.4"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.4.tgz#2f933e8b6e5e9f8edde66b7be0024b68f77da6c9"
integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==
@ -1755,6 +1746,16 @@ lodash-es@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@ -2610,6 +2611,17 @@ rc-virtual-list@^3.14.2, rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2:
rc-resize-observer "^1.0.0"
rc-util "^5.36.0"
react-ace@^13.0.0:
version "13.0.0"
resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-13.0.0.tgz#e69c2aa4ccf1a81c758adba9ba2b4b3e3d929a69"
integrity sha512-PPk2O/ArHzDtbnK82QImfHYXwuiitRgHJf5AxwMQh9zciojbWsPmKJm1tMgWOYLCtGEz8/Dh3MxRxrXe7QcstQ==
dependencies:
ace-builds "^1.36.3"
diff-match-patch "^1.0.5"
lodash.get "^4.4.2"
lodash.isequal "^4.5.0"
prop-types "^15.8.1"
react-base16-styling@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.10.0.tgz#5d5f019bd4dc5870c3e92fd9d5410533a0bbb0c6"
@ -2625,11 +2637,6 @@ react-chartjs-2@^5.2.0:
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz#2d3286339a742bc7f77b5829c33ebab215f714cc"
integrity sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==
react-codemirror2@^8.0.0:
version "8.0.1"
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-8.0.1.tgz#a229c9dda2a6ee9ea18bdb8a4c33a1b2df823613"
integrity sha512-ZALowE5sGK1t66i0Fm1hoJLWT/iZHNjaAmcjwgYl9gyl2v1sqWwxzWHLmgq6K9KCk7p5+I+U3jMF1vsLaIkrmA==
react-dom@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
@ -2967,7 +2974,7 @@ type-fest@^0.20.2:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
typescript@^5.2.2:
typescript@^5.8.3:
version "5.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==