mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-08-31 01:39:47 +08:00
docs(comments): fill the real comment gaps across the repository
A comment-health audit driven by an AST scanner (regex scanners kept false-positiving on annotation-separated javadoc; the AST pass with constructor and @Override-implementation exemptions is what produced a trustworthy worklist) ended with a much smaller true gap than the raw numbers suggested: class-level javadoc is already at 100% repository-wide. What actually gets filled here: - AnalyticsModel: all 30 nested records of the S19 analytics facet get maintainer-voice javadoc (the @Schema descriptions serve the API side; these serve code readers). - The whole entity/ext family (29 files, 47 nested classes): every Content/Ui/Validation/Security/AppliesTo/Template/Dedup/... schema class states what it actually holds — read from the fields, not templated. - Adapter families: the @Bean methods of all six MQ adapters, all four TSDB adapters and the three DB dialect adapters get one-line maintainer docs (what binds to what, what is overridable). - Nested support types across data/agentic/constant/public/facade: NotifyBindKey, CacheTuning, Credential, dashboard VOs' Item/BucketVO, ExpireListener (plus its null-doc placeholder javadoc replaced with a real contract), agentic chat/stream VOs' choice/delta/usage shapes, tool-context records, RequestHeader envelopes, FleetSummary, visualization Type/Scale codes. - PointValueSampleConverter's boundary methods now state the port-side contract (quality default injection point, batch order preservation). Comment drift fixed: the class javadoc of the can/mqtt/opc-da driver skeletons claimed "see TODO markers in method bodies" — those markers do not exist; the claims are corrected to plain work-in-progress notes. The genuine TODOs (zigbee API verification, lwm2m lookup, ethernet-ip CIP framing, CAN JNI) stay: they mark real unimplemented behavior. Vendored-code boundary: the opc-da module carries a ported org.openscada library (46 files, package org.openscada.*). Its trivial accessors stay bare on purpose — filler comments on get/set pairs are exactly the kind of noise this pass exists to remove — but the non-obvious JIStruct wire-decoder methods (getStruct/fromStruct FILETIME + three DO wrappers) now document the COM bridge shape. Gates: full-repo compile green; data/model/constant/agentic/public test suites green.
This commit is contained in:
@@ -7,7 +7,7 @@ types use `io.github.pnoker.api.center.data`; proto sources live under `src/main
|
||||
|
||||
| Service | RPCs | Purpose |
|
||||
|---|---|---|
|
||||
| `PointValueApi` | `GetLastValue`, `ListHistoryValues` | query point values |
|
||||
| `PointValueApi` | `GetLastValue`, `ListHistoryValues`, `ListSeriesVolumes` | query point values and value-volume series |
|
||||
| `PointValueApi` | `ReadCommand`, `WriteCommand` | submit point read/write commands |
|
||||
| `CommandHistoryApi` | `CallCommand`, `GetByRecordId`, `ListByPage` | dispatch and query command history |
|
||||
| `EventHistoryApi` | `ReportEvent`, `GetByRecordId`, `ListByPage` | report and query event history |
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
## Overview
|
||||
|
||||
`dc3-center-data` is the Data Center of the IoT DC3 platform. It integrates common messaging middleware including AMQP,
|
||||
WebSocket, and MQTT for collecting device point values from drivers, storing them in the time-series repository, and
|
||||
exposing data query APIs.
|
||||
`dc3-center-data` is the Data Center of the IoT DC3 platform. It consumes device point values from drivers over
|
||||
RabbitMQ (AMQP), stores them through the pluggable time-series port (`dc3-tsdb`, TimescaleDB by default), and exposes
|
||||
data query and command APIs.
|
||||
|
||||
## Module Information
|
||||
|
||||
@@ -96,4 +96,4 @@ mvn -s .mvn/settings.xml -pl dc3-center/dc3-center-data -am test
|
||||
- `dc3-api-data` - gRPC API contracts for point value queries
|
||||
- `dc3-api-manager` - gRPC API for resolving driver/point metadata
|
||||
- `dc3-common-data` - Business logic implementation
|
||||
- `dc3-common-repository` - Pluggable time-series storage adapter
|
||||
- `dc3-tsdb-core` - Pluggable time-series storage adapter (TimescaleDB adapter selected by default)
|
||||
|
||||
@@ -27,7 +27,7 @@ management, and command interfaces.
|
||||
- **Point Management**: Point definitions, type flags, scale/precision settings
|
||||
- **gRPC Server**: Implements `DriverApi`, `DeviceApi`, `PointApi` for driver registration and data query
|
||||
- **Metadata Events**: Publishes metadata change events over RabbitMQ to notify relevant drivers
|
||||
- **Scheduled Jobs**: Hourly jobs for platform statistics (e.g., point data volume)
|
||||
- **Scheduled Jobs**: Quartz-based hourly maintenance job (`HourlyJobForManager`)
|
||||
|
||||
## REST Endpoints (via Gateway)
|
||||
|
||||
@@ -43,9 +43,14 @@ Key endpoint prefixes (defined in `ManagerConstant`):
|
||||
| `/point` | Point definitions |
|
||||
| `/driver_attribute` | Driver-level attributes |
|
||||
| `/point_attribute` | Point-level attributes |
|
||||
| `/command` | Device commands |
|
||||
| `/event` | Device events |
|
||||
| `/group` | Device groups |
|
||||
| `/topic` | MQTT/data topics |
|
||||
|
||||
The complete prefix set (labels, dictionaries, attribute configs, dashboards, batch operations) lives in
|
||||
`ManagerConstant`.
|
||||
|
||||
## gRPC Services (consumed by drivers and data service)
|
||||
|
||||
| Service | Used by |
|
||||
|
||||
@@ -30,9 +30,6 @@ center and driver applications.
|
||||
| Module | Responsibility |
|
||||
|---|---|
|
||||
| `dc3-common-dal` | shared label/group persistence |
|
||||
| `dc3-common-postgres` | datasource and MyBatis-Plus configuration |
|
||||
| `dc3-common-repository` | point-value storage abstraction |
|
||||
| `dc3-common-rabbitmq` | shared exchanges, connection configuration, and message conversion |
|
||||
| `dc3-common-mqtt` | MQTT client configuration |
|
||||
| `dc3-common-quartz` | scheduling infrastructure |
|
||||
| `dc3-common-thread` | managed executors |
|
||||
@@ -42,6 +39,13 @@ center and driver applications.
|
||||
| `dc3-common-resource-registrar` | API/resource annotation discovery and synchronization |
|
||||
| `dc3-common-test` | shared tests, harnesses, and Testcontainers |
|
||||
|
||||
Datasource, messaging, and time-series storage were split into dedicated top-level families; business modules depend on
|
||||
them directly:
|
||||
|
||||
- `dc3-db` — relational dialect adapters (PostgreSQL/MySQL/MariaDB) behind `dc3.db.type`
|
||||
- `dc3-mq` — broker-neutral messaging port with per-broker adapters (RabbitMQ, Kafka, RocketMQ, Pulsar, ActiveMQ, MQTT)
|
||||
- `dc3-tsdb` — store-neutral time-series port with per-store adapters (TimescaleDB, TDengine, InfluxDB, IoTDB)
|
||||
|
||||
## Architecture rules
|
||||
|
||||
- Preserve `Controller -> Service -> Manager -> Mapper` layering.
|
||||
|
||||
@@ -31,7 +31,7 @@ model uses to read and operate platform resources.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `spring-ai-starter-model-chat-memory-repository-jdbc` — Spring AI chat client + JDBC memory
|
||||
- `spring-ai-starter-model-chat-memory-repository-jdbc` — Spring AI JDBC-backed conversation memory
|
||||
|
||||
## Build Instructions
|
||||
|
||||
|
||||
+3
@@ -74,6 +74,7 @@ public class AgenticMessageContent implements Serializable {
|
||||
return content;
|
||||
}
|
||||
|
||||
/** Tool-call trace entry (type, title, detail) for the run timeline. */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@@ -142,6 +143,7 @@ public class AgenticMessageContent implements Serializable {
|
||||
|
||||
}
|
||||
|
||||
/** One context block (type + content) fed to the model. */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@@ -171,6 +173,7 @@ public class AgenticMessageContent implements Serializable {
|
||||
|
||||
}
|
||||
|
||||
/** Token accounting: input/output/text/context counts of one message. */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
|
||||
+2
@@ -62,6 +62,7 @@ public class AgenticVisualizationSpec implements Serializable {
|
||||
|
||||
private List<Annotation> annotations;
|
||||
|
||||
/** Visual-channel bindings: which field maps to x/y/color/size. */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@@ -98,6 +99,7 @@ public class AgenticVisualizationSpec implements Serializable {
|
||||
|
||||
}
|
||||
|
||||
/** One chart annotation (type, value, label). */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
|
||||
+2
@@ -60,6 +60,7 @@ public class ChatCompletionChunkVO {
|
||||
@Schema(description = "List of streaming choices included in this chunk; typically contains exactly one element for non-branching completions.")
|
||||
private List<ChunkChoice> choices;
|
||||
|
||||
/** One streaming choice: index + incremental delta. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -82,6 +83,7 @@ public class ChatCompletionChunkVO {
|
||||
|
||||
}
|
||||
|
||||
/** Incremental content/role fragment of one chunk. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+3
@@ -64,6 +64,7 @@ public class ChatCompletionResponseVO {
|
||||
@Schema(description = "Token usage statistics for the request and response.")
|
||||
private Usage usage;
|
||||
|
||||
/** One completion choice: index + assistant message. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -85,6 +86,7 @@ public class ChatCompletionResponseVO {
|
||||
|
||||
}
|
||||
|
||||
/** Generated assistant message (role + content). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -107,6 +109,7 @@ public class ChatCompletionResponseVO {
|
||||
|
||||
}
|
||||
|
||||
/** Token usage of the completion (prompt/completion/total). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -48,6 +48,7 @@ public class TenantTool {
|
||||
return AgenticToolResult.ok("Current tenant context loaded", new CurrentTenantContext(tenantId));
|
||||
}
|
||||
|
||||
/** The tenant injected from the authenticated ToolContext (never agent-supplied). */
|
||||
public record CurrentTenantContext(Long tenantId) {
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -51,6 +51,7 @@ public class UserTool {
|
||||
new CurrentUserProfile(userId, header.getUserName(), header.getNickName()));
|
||||
}
|
||||
|
||||
/** The acting user resolved from the ToolContext identity. */
|
||||
public record CurrentUserProfile(Long userId, String username, String nickname) {
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ builder utilities for constructing gRPC request/response objects from domain mod
|
||||
|
||||
- **`GrpcBuilderUtil`** — Utility class for building common gRPC DTOs from BO/DO entities (e.g., setting pagination,
|
||||
building result wrappers)
|
||||
- **`GrpcRFactory`** — Builds `GrpcR` response envelopes
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ consumer, and deployed queue migration.
|
||||
|
||||
### Enumerations
|
||||
|
||||
Located in `io.github.pnoker.common.enums`:
|
||||
Located in `io.github.pnoker.common.enums`. The list below is a selection of the most commonly referenced enums; see
|
||||
the package for the complete set (alarm, MCP, OAuth, notify, and other domain enums live there too):
|
||||
|
||||
- `EnableFlagEnum` — Boolean-like enable/disable state
|
||||
- `EntityStatusEnum` — Online, offline, maintenance, and fault states
|
||||
|
||||
+2
@@ -147,6 +147,7 @@ public class AgenticConstant {
|
||||
throw new IllegalStateException(BaseConstant.UTILITY_CLASS);
|
||||
}
|
||||
|
||||
/** Chart type codes the visualization channel accepts (line/area/column/bar/pie/donut/heatmap/scatter/stat). */
|
||||
public static class Type {
|
||||
|
||||
public static final String LINE = "line";
|
||||
@@ -173,6 +174,7 @@ public class AgenticConstant {
|
||||
|
||||
}
|
||||
|
||||
/** Axis scale kinds: linear or time. */
|
||||
public static class Scale {
|
||||
|
||||
public static final String LINEAR = "linear";
|
||||
|
||||
@@ -33,6 +33,6 @@ mvn -s .mvn/settings.xml -q -pl dc3-common/dc3-common-dal -am -DskipTests compil
|
||||
|
||||
## Related Modules
|
||||
|
||||
- `dc3-common-postgres` — PostgreSQL and MyBatis-Plus base configuration
|
||||
- `dc3-db-core` / `dc3-db-postgres` — datasource and MyBatis-Plus base configuration
|
||||
- `dc3-common-model` — Base BO/VO/DTO model definitions
|
||||
- `dc3-common-manager` / `dc3-common-auth` / `dc3-common-data` — Consumers of this DAL layer
|
||||
|
||||
@@ -98,5 +98,5 @@ mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-data -am test
|
||||
|
||||
- `dc3-center-data` — Bootstraps this module as a Spring Boot service
|
||||
- `dc3-api-manager` — gRPC API consumed by this module for driver/point resolution
|
||||
- `dc3-common-repository` — Storage adapter for persisting point values
|
||||
- `dc3-common-rabbitmq` — RabbitMQ exchange/queue configuration
|
||||
- `dc3-tsdb-core` / `dc3-tsdb-timescale` — store-neutral time-series port and TimescaleDB adapter
|
||||
- `dc3-mq-core` / `dc3-mq-rabbitmq` — broker-neutral messaging port and RabbitMQ adapter
|
||||
|
||||
+1
@@ -243,6 +243,7 @@ public class NotifyConfigCache {
|
||||
bindCache.invalidateAll();
|
||||
}
|
||||
|
||||
/** Cache key of one notify rule's channel bindings: tenant + notify id. */
|
||||
public record NotifyBindKey(Long tenantId, Long notifyId) {
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -52,6 +52,10 @@ public class PointValueSampleConverter {
|
||||
return Objects.isNull(instant) ? null : LocalDateTime.ofInstant(instant, TimeConstant.DEFAULT_ZONEID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Business BO → port sample. Quality defaults to 0 (GOOD) — the business
|
||||
* layer does not model quality yet; when it does, this is the injection point.
|
||||
*/
|
||||
public PointValueSample toSample(PointValueBO valueBO) {
|
||||
return new PointValueSample(
|
||||
new SeriesKey(valueBO.getTenantId(), valueBO.getDeviceId(), valueBO.getPointId()),
|
||||
@@ -64,6 +68,7 @@ public class PointValueSampleConverter {
|
||||
valueBO.getFencingToken(), valueBO.getDriverId());
|
||||
}
|
||||
|
||||
/** Port sample → business BO; timestamps return to wall-clock in the platform zone. */
|
||||
public PointValueBO toBO(PointValueSample sample) {
|
||||
return PointValueBO.builder()
|
||||
.tenantId(sample.series().tenantId())
|
||||
@@ -83,10 +88,12 @@ public class PointValueSampleConverter {
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Batch form of {@link #toSample(PointValueBO)}, order-preserving. */
|
||||
public List<PointValueSample> toSamples(List<PointValueBO> values) {
|
||||
return values.stream().map(this::toSample).toList();
|
||||
}
|
||||
|
||||
/** Batch form of {@link #toBO(PointValueSample)}, order-preserving. */
|
||||
public List<PointValueBO> toBOs(List<PointValueSample> samples) {
|
||||
return samples.stream().map(this::toBO).toList();
|
||||
}
|
||||
|
||||
Vendored
+5
-3
@@ -176,14 +176,16 @@ public class LocalCacheImpl {
|
||||
expireListeners.add(listener);
|
||||
}
|
||||
|
||||
/** Callback fired (best-effort, on the eviction path) when a TTL-bound entry expires. */
|
||||
@FunctionalInterface
|
||||
public interface ExpireListener {
|
||||
|
||||
/**
|
||||
* On expire.
|
||||
* React to one expired entry. Exceptions here are logged and swallowed —
|
||||
* an eviction listener must never break the cache itself.
|
||||
*
|
||||
* @param key key
|
||||
* @param lastValue last value
|
||||
* @param key the expired cache key
|
||||
* @param lastValue the entry's value as of eviction
|
||||
*/
|
||||
void onExpire(String key, Object lastValue);
|
||||
|
||||
|
||||
+7
-1
@@ -47,6 +47,11 @@ public class AlarmCacheProperties {
|
||||
@Valid
|
||||
private CacheTuning notify = new CacheTuning(5_000L, 60L);
|
||||
|
||||
/**
|
||||
* Per-cache sizing: Caffeine maximum entries plus entry TTL in seconds. The
|
||||
* defaults favor correctness over memory (short TTLs) — rule/notify lookups
|
||||
* are cheap to rebuild from the database.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public static class CacheTuning {
|
||||
@@ -57,10 +62,11 @@ public class AlarmCacheProperties {
|
||||
@Min(value = 1, message = "Cache ttl-seconds must be at least 1")
|
||||
private long ttlSeconds;
|
||||
|
||||
/** Required by Spring property binding. */
|
||||
public CacheTuning() {
|
||||
// Spring property binding requires a no-arg constructor.
|
||||
}
|
||||
|
||||
/** All-args constructor for programmatic tuning. */
|
||||
public CacheTuning(long maxSize, long ttlSeconds) {
|
||||
this.maxSize = maxSize;
|
||||
this.ttlSeconds = ttlSeconds;
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ public class NotifyCredentialProperties {
|
||||
*/
|
||||
private Map<String, Credential> credentials = new LinkedHashMap<>();
|
||||
|
||||
/** One channel-type credential bundle (e.g. Feishu bot webhook URL), keyed by type. */
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Credential {
|
||||
|
||||
+102
@@ -43,6 +43,10 @@ public final class AnalyticsModel {
|
||||
* One series addressed either by ids or by names — ids win when both are
|
||||
* present; ambiguous or unknown names surface the candidates in the error.
|
||||
*/
|
||||
/**
|
||||
* One series addressed either by ids or by names — ids win when both are present;
|
||||
* ambiguous or unknown names surface the candidates in the error.
|
||||
*/
|
||||
public record SeriesSelector(
|
||||
@Schema(description = "Device id; takes precedence over deviceName when both are given", example = "1024") Long deviceId,
|
||||
@Schema(description = "Point id; takes precedence over pointName when both are given", example = "2048") Long pointId,
|
||||
@@ -54,6 +58,10 @@ public final class AnalyticsModel {
|
||||
* Window as an ISO-8601 instant pair, or {@code rangeHours} back from now
|
||||
* when the pair is absent. An empty range defaults to the last 24 hours.
|
||||
*/
|
||||
/**
|
||||
* Window as an ISO-8601 instant pair, or {@code rangeHours} back from now when the pair is
|
||||
* absent. An empty range defaults to the last 24 hours.
|
||||
*/
|
||||
public record TimeRange(
|
||||
@Schema(description = "Inclusive window start, ISO-8601 instant", example = "2026-08-20T00:00:00Z") String fromIso,
|
||||
@Schema(description = "Exclusive window end, ISO-8601 instant; defaults to now", example = "2026-08-21T00:00:00Z") String toIso,
|
||||
@@ -62,10 +70,17 @@ public final class AnalyticsModel {
|
||||
|
||||
// ===== requests =====
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/query_latest}: current values of one or more series.
|
||||
*/
|
||||
public record QueryLatestRequest(
|
||||
@Schema(description = "Series to read; at most 20 per call") List<SeriesSelector> series) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/query_history}: RAW mode returns the newest samples (bounded);
|
||||
* M4 mode returns per-bucket first/min/max/last for chart-grade rendering.
|
||||
*/
|
||||
public record QueryHistoryRequest(
|
||||
@Schema(description = "Series to read; at most 20 per call") List<SeriesSelector> series,
|
||||
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
|
||||
@@ -73,18 +88,29 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Target point count per series (1..1000); RAW caps the total pull, M4 sizes the buckets", example = "200") Integer maxPoints) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/compute_stats}: statistical profile request with optional
|
||||
* percentiles (defaults to 0.5 and 0.95).
|
||||
*/
|
||||
public record ComputeStatsRequest(
|
||||
@Schema(description = "Series to profile; at most 20 per call") List<SeriesSelector> series,
|
||||
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
|
||||
@Schema(description = "Percentiles in [0,1] to compute per series; defaults to 0.5 and 0.95", example = "[0.5, 0.99]") List<Double> percentiles) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/compare_periods}: same series across two windows.
|
||||
*/
|
||||
public record ComparePeriodsRequest(
|
||||
@Schema(description = "Series to compare; at most 20 per call") List<SeriesSelector> series,
|
||||
@Schema(description = "Current window (e.g. this week)") TimeRange current,
|
||||
@Schema(description = "Baseline window (e.g. last week)") TimeRange baseline) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/rank_entities}: ranking by ACTIVITY (sample count) or by
|
||||
* MEAN / MAX / MIN over each entity's series.
|
||||
*/
|
||||
public record RankEntitiesRequest(
|
||||
@Schema(description = "Grouping dimension of the ranking", example = "DEVICE", allowableValues = {"DEVICE", "POINT", "DRIVER"}) String dimension,
|
||||
@Schema(description = "Ranking metric: ACTIVITY (sample count) or MEAN / MAX / MIN over each entity's series", example = "ACTIVITY", allowableValues = {"ACTIVITY", "MEAN", "MAX", "MIN"}) String metric,
|
||||
@@ -92,12 +118,20 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Top-N size (1..50); defaults to 10", example = "10") Integer limit) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/trend_analysis}: per-series least-squares trend over bucket
|
||||
* averages; {@code buckets} clamped to [2, 200].
|
||||
*/
|
||||
public record TrendAnalysisRequest(
|
||||
@Schema(description = "Series to analyze; at most 20 per call") List<SeriesSelector> series,
|
||||
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
|
||||
@Schema(description = "Bucket count for the regression (2..200); defaults to 50", example = "50") Integer buckets) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/threshold_report}: exceedance report against a threshold
|
||||
* with a GREATER / LESS operator.
|
||||
*/
|
||||
public record ThresholdReportRequest(
|
||||
@Schema(description = "Series to report; at most 20 per call") List<SeriesSelector> series,
|
||||
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
|
||||
@@ -105,6 +139,10 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Threshold value samples are compared against", example = "80.0") Double threshold) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/correlate}: Pearson correlation between two series over
|
||||
* aligned buckets (default 300 s).
|
||||
*/
|
||||
public record CorrelateRequest(
|
||||
@Schema(description = "First series of the pair") SeriesSelector seriesA,
|
||||
@Schema(description = "Second series of the pair") SeriesSelector seriesB,
|
||||
@@ -112,6 +150,10 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Bucket length in seconds for alignment (10..86400); defaults to 300", example = "300") Long alignBucketSeconds) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Body of {@code POST /analytics/data_quality_report}: tenant-level coverage, silence and
|
||||
* quality-code census.
|
||||
*/
|
||||
public record QualityReportRequest(
|
||||
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
|
||||
@Schema(description = "Minutes of inactivity after which a series counts as silent (5..1440); defaults to 30", example = "30") Integer silentMinutes) {
|
||||
@@ -119,6 +161,9 @@ public final class AnalyticsModel {
|
||||
|
||||
// ===== response atoms =====
|
||||
|
||||
/**
|
||||
* Response atom: the series identity plus a human-readable deviceName/pointName label.
|
||||
*/
|
||||
public record SeriesRef(
|
||||
@Schema(description = "Device id of the series") Long deviceId,
|
||||
@Schema(description = "Point id of the series") Long pointId,
|
||||
@@ -127,6 +172,9 @@ public final class AnalyticsModel {
|
||||
|
||||
// ===== responses =====
|
||||
|
||||
/**
|
||||
* Response of {@code query_latest}: conclusion, degradation note and one item per series.
|
||||
*/
|
||||
public record LatestValuesResponse(
|
||||
@Schema(description = "Self-contained summary of the latest values") String conclusion,
|
||||
@Schema(description = "Number of value rows behind the conclusion") long sampleCount,
|
||||
@@ -134,6 +182,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Latest value per requested series") List<LatestItem> values) {
|
||||
}
|
||||
|
||||
/**
|
||||
* One series' current value; {@code hasValue} is false when no reading exists yet.
|
||||
*/
|
||||
public record LatestItem(
|
||||
@Schema(description = "Series the value belongs to") SeriesRef series,
|
||||
@Schema(description = "Raw value as captured from the device") String rawValue,
|
||||
@@ -144,6 +195,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "False when the series has no reading yet") boolean hasValue) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of {@code query_history}: points per series keyed by deviceName/pointName.
|
||||
*/
|
||||
public record HistoryResponse(
|
||||
@Schema(description = "Self-contained summary of the returned history") String conclusion,
|
||||
@Schema(description = "Points behind the conclusion") long sampleCount,
|
||||
@@ -152,6 +206,9 @@ public final class AnalyticsModel {
|
||||
}
|
||||
|
||||
/** RAW carries {@code value}; M4 carries the first/min/max/last quadruple. */
|
||||
/**
|
||||
* One history point — RAW carries {@code value}; M4 carries the first/min/max/last quadruple.
|
||||
*/
|
||||
public record HistoryPoint(
|
||||
@Schema(description = "Sample time / bucket start, ISO-8601 instant") String time,
|
||||
@Schema(description = "Numeric value in RAW mode") Double value,
|
||||
@@ -161,6 +218,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Last value of the bucket in M4 mode") Double last) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of {@code compute_stats}: per-series statistical profile.
|
||||
*/
|
||||
public record StatsResponse(
|
||||
@Schema(description = "Self-contained statistical summary") String conclusion,
|
||||
@Schema(description = "Samples behind the statistics") long sampleCount,
|
||||
@@ -168,6 +228,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Profile per series, keyed by deviceName/pointName") Map<String, StatItem> stats) {
|
||||
}
|
||||
|
||||
/**
|
||||
* One series' profile: mean, population std-dev, extremes, count and requested percentiles.
|
||||
*/
|
||||
public record StatItem(
|
||||
@Schema(description = "Series the profile belongs to") SeriesRef series,
|
||||
@Schema(description = "Arithmetic mean of numeric values") Double mean,
|
||||
@@ -178,6 +241,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Requested percentiles, keyed by the percentile in [0,1]") Map<Double, Double> percentiles) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of {@code compare_periods}: per-series current vs baseline averages.
|
||||
*/
|
||||
public record CompareResponse(
|
||||
@Schema(description = "Self-contained comparison summary") String conclusion,
|
||||
@Schema(description = "Samples behind the comparison") long sampleCount,
|
||||
@@ -185,6 +251,10 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Comparison per series, keyed by deviceName/pointName") Map<String, CompareItem> comparisons) {
|
||||
}
|
||||
|
||||
/**
|
||||
* One series' comparison: current, baseline, delta and percentage change (null when the
|
||||
* baseline is zero).
|
||||
*/
|
||||
public record CompareItem(
|
||||
@Schema(description = "Series the comparison belongs to") SeriesRef series,
|
||||
@Schema(description = "Current window average") Double current,
|
||||
@@ -195,6 +265,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Sample count in the baseline window") long baselineCount) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of {@code rank_entities}: ranked entities, best first.
|
||||
*/
|
||||
public record RankResponse(
|
||||
@Schema(description = "Self-contained ranking summary") String conclusion,
|
||||
@Schema(description = "Samples behind the ranking") long sampleCount,
|
||||
@@ -202,6 +275,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Ranked entities, best first") List<RankItem> ranked) {
|
||||
}
|
||||
|
||||
/**
|
||||
* One ranked entity — {@code metricValue} is set for MEAN/MAX/MIN rankings and null for ACTIVITY.
|
||||
*/
|
||||
public record RankItem(
|
||||
@Schema(description = "Entity id of the ranked entry") String entityId,
|
||||
@Schema(description = "Entity display name") String label,
|
||||
@@ -209,6 +285,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Metric value for MEAN/MAX/MIN rankings; null for ACTIVITY") Double metricValue) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of {@code trend_analysis}: per-series trend verdicts.
|
||||
*/
|
||||
public record TrendResponse(
|
||||
@Schema(description = "Self-contained trend summary") String conclusion,
|
||||
@Schema(description = "Buckets behind the trends") long sampleCount,
|
||||
@@ -216,6 +295,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Trend per series, keyed by deviceName/pointName") Map<String, TrendItem> trends) {
|
||||
}
|
||||
|
||||
/**
|
||||
* One series' trend: least-squares slope in value-per-bucket plus total percentage change.
|
||||
*/
|
||||
public record TrendItem(
|
||||
@Schema(description = "Series the trend belongs to") SeriesRef series,
|
||||
@Schema(description = "Least-squares slope of bucket averages, in value per bucket") double slopePerBucket,
|
||||
@@ -225,6 +307,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Buckets with data in the window") int bucketCount) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of {@code threshold_report}: per-series exceedance report.
|
||||
*/
|
||||
public record ThresholdResponse(
|
||||
@Schema(description = "Self-contained threshold summary") String conclusion,
|
||||
@Schema(description = "Exceeding samples behind the report") long sampleCount,
|
||||
@@ -232,6 +317,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Report per series, keyed by deviceName/pointName") Map<String, ThresholdItem> report) {
|
||||
}
|
||||
|
||||
/**
|
||||
* One series' exceedance summary: count, total seconds, peak value and merged intervals.
|
||||
*/
|
||||
public record ThresholdItem(
|
||||
@Schema(description = "Series the report belongs to") SeriesRef series,
|
||||
@Schema(description = "Samples beyond the threshold") long exceedCount,
|
||||
@@ -240,11 +328,18 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Merged exceedance intervals, at most 20 per series") List<ThresholdInterval> intervals) {
|
||||
}
|
||||
|
||||
/**
|
||||
* One merged exceedance interval [from, to], ISO-8601 instants.
|
||||
*/
|
||||
public record ThresholdInterval(
|
||||
@Schema(description = "Interval start, ISO-8601 instant") String from,
|
||||
@Schema(description = "Interval end, ISO-8601 instant") String to) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of {@code correlate}: Pearson coefficient, aligned-bucket count and the method
|
||||
* actually used (STORE vs FACADE).
|
||||
*/
|
||||
public record CorrelationResponse(
|
||||
@Schema(description = "Self-contained correlation verdict") String conclusion,
|
||||
@Schema(description = "Aligned buckets behind the coefficient") long sampleCount,
|
||||
@@ -254,6 +349,10 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "STORE (SQL-side) or FACADE (bucketed pulls)") String method) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of {@code data_quality_report}: coverage, silent series and sampled quality-code
|
||||
* distribution.
|
||||
*/
|
||||
public record QualityResponse(
|
||||
@Schema(description = "Self-contained quality summary") String conclusion,
|
||||
@Schema(description = "Samples in the quality census") long sampleCount,
|
||||
@@ -265,6 +364,9 @@ public final class AnalyticsModel {
|
||||
@Schema(description = "Quality-code distribution of the sampled census, keyed by code") Map<String, Long> qualityDistribution) {
|
||||
}
|
||||
|
||||
/**
|
||||
* One series that went silent, with its last sample time.
|
||||
*/
|
||||
public record SilentItem(
|
||||
@Schema(description = "The silent series") SeriesRef series,
|
||||
@Schema(description = "Last sample time of the series, ISO-8601 instant") String lastSeen) {
|
||||
|
||||
+1
@@ -58,6 +58,7 @@ public class AlertBulkConfirmVO implements Serializable {
|
||||
@Schema(description = "Alert targets to apply the bulk operation to; each entry identifies a single alert row in the current tenant scope.")
|
||||
private List<Item> items;
|
||||
|
||||
/** One (source, id) pair in the bulk confirm/reject request body. */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
|
||||
+1
@@ -83,6 +83,7 @@ public class AlertStatsVO implements Serializable {
|
||||
@Schema(description = "24-element hourly count series for the sparkline, oldest first, always length 24")
|
||||
private List<Long> sparkline24h;
|
||||
|
||||
/** Alert count for one alarm-type bucket of the byType distribution. */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ public class CoverageGapVO implements Serializable {
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
/** One point that has never reported any sample (missing coverage item). */
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
## Overview
|
||||
|
||||
`dc3-common-driver` is the shared driver dependency module of the IoT DC3 platform. It provides the driver SDK shared by
|
||||
all protocol drivers, including auto-registration with Manager Center, PostgreSQL-backed runtime ownership, metadata
|
||||
sync, RabbitMQ command handling, durable telemetry publication, and scheduled data collection. Redis is not part of the
|
||||
all protocol drivers, including auto-registration with Manager Center, manager-backed runtime ownership
|
||||
(membership, assignments, and fencing tokens are persisted in PostgreSQL by the Manager Center), metadata sync,
|
||||
RabbitMQ command handling, durable telemetry publication, and scheduled data collection. Redis is not part of the
|
||||
driver coordination path.
|
||||
|
||||
## Module Information
|
||||
@@ -102,5 +103,5 @@ mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-driver -am test
|
||||
|
||||
- All `dc3-driver-*` modules — Consume this SDK
|
||||
- `dc3-api-driver` — gRPC contracts consumed by this SDK
|
||||
- `dc3-common-rabbitmq` — RabbitMQ exchange configuration
|
||||
- `dc3-mq-core` / `dc3-mq-rabbitmq` — broker-neutral messaging port and RabbitMQ adapter
|
||||
- `dc3-common-constant` — `RabbitConstant` routing key prefixes
|
||||
|
||||
@@ -17,15 +17,31 @@ services and modules use these exceptions to signal business errors consistently
|
||||
| Exception | Usage |
|
||||
|----------------------|------------------------------------------|
|
||||
| `ServiceException` | General service-level business error |
|
||||
| `BusinessException` | Business rule violation |
|
||||
| `UpdateException` | Entity update failure |
|
||||
| `AddException` | Entity add failure |
|
||||
| `DeleteException` | Entity delete failure |
|
||||
| `NotFoundException` | Entity or resource not found |
|
||||
| `DuplicateException` | Duplicate entity conflict |
|
||||
| `EmptyException` | Required collection or value is empty |
|
||||
| `OutRangeException` | Value outside the allowed range |
|
||||
| `JsonException` | JSON serialization/deserialization error |
|
||||
| `SecurityException` | General security policy violation |
|
||||
| `UnAuthorizedException` | Missing or invalid authentication |
|
||||
| `AccessDeniedException` | Authenticated principal lacks access |
|
||||
| `DuplicateException` | Duplicate entity conflict |
|
||||
| `PasswordChangeRequiredException` | A password change is enforced |
|
||||
| `RegisterException` | Driver/service registration failure |
|
||||
| `ConnectorException` | Device connection failure |
|
||||
| `ReadPointException` | Point read failure |
|
||||
| `WritePointException` | Point write failure |
|
||||
| `RepositoryException` | Time-series or storage repository error |
|
||||
| `RequestException` | Invalid or rejected request |
|
||||
| `TypeException` | Unsupported type conversion |
|
||||
| `ConfigException` | Invalid configuration |
|
||||
| `CronException` | Invalid cron expression |
|
||||
| `ImportException` | Import/export failure |
|
||||
| `UnSupportException` | Unsupported operation or protocol |
|
||||
| `AssociatedException` | Constraint violation by an association |
|
||||
|
||||
### Utilities
|
||||
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ public class FacadeSystemHealthBO implements Serializable {
|
||||
|
||||
private FleetSummary devices;
|
||||
|
||||
/** Per-fleet online/total counts for one entity kind (drivers, devices). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
@@ -19,6 +19,8 @@ factory and supporting services that validate tokens with the Auth Center before
|
||||
| `AuthenticGatewayFilter` | Applies token validation logic; injects principal headers downstream |
|
||||
| `FilterServiceImpl` | Calls Auth Center via gRPC to validate the Bearer token |
|
||||
| `GatewayInitRunner` | Startup runner for gateway-specific initialization |
|
||||
| `McpGatewayController` | OAuth2 authorization-server and MCP discovery endpoints exposed at the gateway |
|
||||
| `McpGatewayProperties` | Binds MCP gateway settings from YAML |
|
||||
|
||||
## Filter Flow
|
||||
|
||||
|
||||
@@ -36,10 +36,11 @@ mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-log -am package
|
||||
|
||||
## Testing
|
||||
|
||||
This module has no Java implementation classes or module-specific tests. Verify packaging from the repository root:
|
||||
This module has no production Java implementation classes. Its tests verify the shared Logback configuration and
|
||||
logging policy (`LogbackConfigurationTest`, `LoggingPolicyTest`):
|
||||
|
||||
```bash
|
||||
mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-log -am package
|
||||
mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-log -am test
|
||||
```
|
||||
|
||||
## Related Modules
|
||||
|
||||
@@ -20,7 +20,7 @@ Center. It is wired into `dc3-center-manager`.
|
||||
| gRPC Servers (Spring `@Service`) | `DriverDriverServer`, `DriverDeviceServer`, `DriverPointServer`, `ManagerPointServer` |
|
||||
| DAL Managers | `DriverManager`, `DeviceManager`, `ProfileManager`, `PointManager` (MyBatis-Plus `IService`) |
|
||||
| Metadata Events | `MetadataEventPublisher`, `MetadataEventListener` — async metadata change notification via RabbitMQ |
|
||||
| Scheduled Jobs | `ScheduleForManagerServiceImpl` — Quartz-based hourly statistics |
|
||||
| Scheduled Jobs | `ScheduleForManagerServiceImpl` — Quartz-based hourly maintenance job (`HourlyJobForManager`) |
|
||||
|
||||
## gRPC Services Exposed
|
||||
|
||||
|
||||
@@ -33,7 +33,10 @@ Used with `@Validated(...)` in controllers:
|
||||
| `Add` | Marks fields required only on creation (`@PostMapping("/add")`) |
|
||||
| `Update` | Marks fields required only on update (`@PostMapping("/update")`) |
|
||||
| `Select` | Marks fields for query operations |
|
||||
| `Read` / `Auth` | Specialized validation groups |
|
||||
| `Read` | Read-path validation (e.g. `read` requests) |
|
||||
| `Write` | Write-path validation (e.g. `write` requests) |
|
||||
| `Auth` | Authentication-scoped validation |
|
||||
| `Check` / `Parent` / `Upload` | Specialized validation groups |
|
||||
|
||||
### Shared DTOs and Extensions
|
||||
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class ApiExt extends BaseExt {
|
||||
@Schema(description = "Extended content of the API interface; discriminated by Type and Version of the parent extension")
|
||||
private Content content;
|
||||
|
||||
/** Human-facing API metadata (title, doc URL, remark) rendered in the tool catalog and OpenAPI quality merge. */
|
||||
@Schema(description = "Extended content of the API interface")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+5
@@ -47,6 +47,7 @@ public class CommandAttributeExt extends BaseExt {
|
||||
@Schema(description = "Command attribute content, holding the detailed configuration of the attribute.")
|
||||
private Content content;
|
||||
|
||||
/** Attribute-value retention strategy hint. */
|
||||
@Schema(description = "Command attribute content. Detailed configuration of a command attribute, grouped into retention, UI, validation, security and applicability sections.")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -71,6 +72,7 @@ public class CommandAttributeExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Front-end rendering hints for the attribute input. */
|
||||
@Schema(description = "UI rendering configuration. Describes how the command attribute is displayed and edited in the front-end form.", example = "form")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -101,6 +103,7 @@ public class CommandAttributeExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Value constraints (min/max/range) for attribute input validation. */
|
||||
@Schema(description = "Validation rules. Constraints applied to the command attribute value.", example = "standard")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -119,6 +122,7 @@ public class CommandAttributeExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Access-control hints for the attribute. */
|
||||
@Schema(description = "Security configuration. Security-related settings for the command attribute.", example = "basic")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -131,6 +135,7 @@ public class CommandAttributeExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Applicability scoping of the attribute (which entities/contexts it targets). */
|
||||
@Schema(description = "Applicability scope. Defines the command and call types the attribute applies to.", example = "READ")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ public class CommandExt extends BaseExt {
|
||||
@Schema(description = "Extended content payload of the command metadata; carries command-specific attributes such as the reserved keep field")
|
||||
private Content content;
|
||||
|
||||
/** Reserved command extension slot — driver/vendor-specific command metadata. */
|
||||
@Schema(title = "CommandExt.Content", description = "Extended content payload for command metadata")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ public class CommandParamExt extends BaseExt {
|
||||
@Schema(description = "Extended content of the command param")
|
||||
private Content content;
|
||||
|
||||
/** Extra command-param data not covered by typed fields. */
|
||||
@Schema(description = "Extended content payload for the command param")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class DeviceExt extends BaseExt {
|
||||
@Schema(description = "Device extension content payload carrying type-specific configuration")
|
||||
private Content content;
|
||||
|
||||
/** Reserved device extension slot — arbitrary custom device data that does not fit a typed field. */
|
||||
@Schema(description = "Device extension content payload carrying type-specific configuration")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ public class DriverAttributeExt extends BaseExt {
|
||||
@Schema(description = "Driver attribute extension payload carrying the reserved keep field")
|
||||
private Content content;
|
||||
|
||||
/** Attribute-value retention strategy hint for driver attributes. */
|
||||
@Schema(description = "Driver attribute extension content detail")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ public class DriverExt extends BaseExt {
|
||||
@Schema(description = "Driver configuration content payload; a structured object carrying driver-specific settings, not a scalar value")
|
||||
private Content content;
|
||||
|
||||
/** Reserved driver extension slot for driver-specific hints. */
|
||||
@Schema(description = "Driver configuration content holding driver-specific settings")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+5
@@ -47,6 +47,7 @@ public class EventAttributeExt extends BaseExt {
|
||||
@Schema(description = "Event attribute configuration content")
|
||||
private Content content;
|
||||
|
||||
/** Attribute-value retention strategy hint. */
|
||||
@Schema(description = "Event attribute configuration content")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -71,6 +72,7 @@ public class EventAttributeExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Front-end rendering hints for the attribute input. */
|
||||
@Schema(description = "UI rendering configuration for the attribute", example = "form")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -101,6 +103,7 @@ public class EventAttributeExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Value constraints for attribute input validation. */
|
||||
@Schema(description = "Validation rules for the attribute value", example = "standard")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -119,6 +122,7 @@ public class EventAttributeExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Access-control hints for the attribute. */
|
||||
@Schema(description = "Security configuration for the attribute", example = "basic")
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -131,6 +135,7 @@ public class EventAttributeExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Applicability scoping of the attribute. */
|
||||
@Schema(description = "Scope to which the attribute applies")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ public class EventExt extends BaseExt {
|
||||
@Schema(description = "Structured payload carrying the event's extension content; serialized as JSON inside the extension object")
|
||||
private Content content;
|
||||
|
||||
/** Reserved event extension slot, currently a placeholder. */
|
||||
@Schema(description = "Extended content payload nested in the event extension object")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ public class EventParamExt extends BaseExt {
|
||||
@Schema(description = "Structured event param payload carrying the reserved keep field")
|
||||
private Content content;
|
||||
|
||||
/** Reserved event-param extension slot, currently a placeholder. */
|
||||
@Schema(description = "Extended content payload of the event param")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -48,6 +48,7 @@ public class MenuExt extends BaseExt {
|
||||
@Schema(description = "Extended content, distinguished by Type and Version")
|
||||
private Content content;
|
||||
|
||||
/** Menu display metadata: localized titles (authoritative), icon and route URL — drives the settings sidebar rendering. */
|
||||
@Schema(description = "Extended content of the menu, carrying localized titles, icon, link and description")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+2
@@ -49,6 +49,7 @@ public class MessageExt extends BaseExt {
|
||||
@Schema(description = "Extended content payload of the message; the concrete shape is selected by the inherited type and version fields")
|
||||
private Content content;
|
||||
|
||||
/** Message template core content (level, body text). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -70,6 +71,7 @@ public class MessageExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Per-channel rendering overrides keyed by channel type (FEISHU_BOT, WEBHOOK, EMAIL...). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ public class NotifyChannelBindExt extends BaseExt {
|
||||
@Schema(description = "Extended content holding notification channel binding settings")
|
||||
private Content content;
|
||||
|
||||
/** Bind-specific extension slot (routing hints for one rule-channel pair). */
|
||||
@Schema(description = "Extended content holding notification channel binding settings")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class NotifyChannelExt extends BaseExt {
|
||||
@Schema(description = "Non-sensitive channel configuration payload; provider-specific options and toggle flags.")
|
||||
private Content content;
|
||||
|
||||
/** Channel-level extension slot (provider metadata for one channel). */
|
||||
@Schema(description = "Extended content for a notify channel.")
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
+8
@@ -48,6 +48,7 @@ public class NotifyExt extends BaseExt {
|
||||
@Schema(description = "Extended notification content, distinguished by the type and version fields of the base extension")
|
||||
private Content content;
|
||||
|
||||
/** Notify-rule core content. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -93,6 +94,7 @@ public class NotifyExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Duplicate suppression: enabled + grouping key (e.g. ruleId). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -108,6 +110,7 @@ public class NotifyExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Per-key send rate limiting. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -123,6 +126,7 @@ public class NotifyExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Quiet-window suppression of notifications. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -138,6 +142,7 @@ public class NotifyExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Active sending window (timezone + HH:mm start/end) outside which sends are held. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -159,6 +164,7 @@ public class NotifyExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Repeat-reminder policy while a condition stays true. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -177,6 +183,7 @@ public class NotifyExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Recovery-notification policy when a condition clears. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -195,6 +202,7 @@ public class NotifyExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Escalation policy when a condition stays unacknowledged. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ public class NotifyHistoryRequestExt extends BaseExt {
|
||||
@Schema(description = "Rendered notification content carried by the delivery request")
|
||||
private Content content;
|
||||
|
||||
/** Request payload schema extension for notify history calls. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ public class NotifyHistoryResponseExt extends BaseExt {
|
||||
@Schema(description = "Extended content describing the channel provider response of a notification delivery")
|
||||
private Content content;
|
||||
|
||||
/** Response payload schema extension for notify history calls. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class PointAttributeExt extends BaseExt {
|
||||
@Schema(description = "Extended content payload, distinguished by the type and version fields of the base extension")
|
||||
private Content content;
|
||||
|
||||
/** Attribute-value retention strategy hint for point attributes. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class PointExt extends BaseExt {
|
||||
@Schema(description = "Extended content payload of the point; the shape is discriminated by the type and version fields inherited from the owning point's tenant scope")
|
||||
private Content content;
|
||||
|
||||
/** Reserved point extension slot — custom point data beyond typed fields. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class ProfileExt extends BaseExt {
|
||||
@Schema(description = "Extended content payload of the profile/template; interpretation is driven by the type and version fields of the parent BaseExt")
|
||||
private Content content;
|
||||
|
||||
/** Reserved profile extension slot, currently a placeholder kept for forward compatibility. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class ResourceExt extends BaseExt {
|
||||
@Schema(description = "Extended content, distinguished by the type and version fields inherited from the base extension")
|
||||
private Content content;
|
||||
|
||||
/** Resource extension slot (permission metadata beyond the typed resource fields). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class RoleExt extends BaseExt {
|
||||
@Schema(description = "Role extension payload; its shape is distinguished by the Type and Version fields of the base extension")
|
||||
private Content content;
|
||||
|
||||
/** Reserved role extension slot. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ public class RuleAlarmEventExt extends BaseExt {
|
||||
@Schema(description = "Extended content holding the rule alarm event snapshot")
|
||||
private Content content;
|
||||
|
||||
/** Alarm-event payload schema extension emitted by rule evaluation. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+4
@@ -49,6 +49,7 @@ public class RuleExt extends BaseExt {
|
||||
@Schema(description = "Extended content, which can be distinguished by type and version.")
|
||||
private Content content;
|
||||
|
||||
/** Alarm rule core content. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -94,6 +95,7 @@ public class RuleExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** The match predicate: field + operator + expected/threshold operands. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -145,6 +147,7 @@ public class RuleExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Time-window scoping of the rule evaluation. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -172,6 +175,7 @@ public class RuleExt extends BaseExt {
|
||||
|
||||
}
|
||||
|
||||
/** Recovery (condition-clear) behavior of the rule. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ public class RuleStateExt extends BaseExt {
|
||||
@Schema(description = "Extended content holding the rule runtime state snapshot")
|
||||
private Content content;
|
||||
|
||||
/** Rule-runtime state extension (per-rule evaluation state metadata). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class TenantExt extends BaseExt {
|
||||
@Schema(description = "Extended content payload for the tenant; structure is interpreted according to the type and version fields of the extension")
|
||||
private Content content;
|
||||
|
||||
/** Reserved tenant extension slot. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class UserIdentityExt extends BaseExt {
|
||||
@Schema(description = "Extended content payload, distinguished by the type and version fields")
|
||||
private Content content;
|
||||
|
||||
/** User identity extension slot (credential/profile metadata per identity). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public class UserSocialExt extends BaseExt {
|
||||
@Schema(description = "Extended content, distinguished by Type and Version")
|
||||
private Content content;
|
||||
|
||||
/** User social-binding extension slot (third-party account metadata). */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
@@ -24,22 +24,32 @@ the MQTT driver and any service requiring MQTT connectivity.
|
||||
|
||||
## Configuration Properties
|
||||
|
||||
Configure in `application*.yml` under the `dc3.driver.mqtt` prefix:
|
||||
Configure in `application*.yml` under the `dc3.driver.mqtt` prefix. The shared `application-mqtt.yml` ships
|
||||
these defaults:
|
||||
|
||||
```yaml
|
||||
dc3:
|
||||
driver:
|
||||
mqtt:
|
||||
url: tcp://${MQTT_BROKER_HOST:dc3-rabbitmq}:${MQTT_BROKER_PORT:2883}
|
||||
auth-type: USERNAME # NONE | USERNAME | CLIENT
|
||||
username: ${MQTT_USERNAME:dc3}
|
||||
password: ${MQTT_PASSWORD:}
|
||||
ca-crt: classpath:/certs/ca.crt
|
||||
client-crt: classpath:/certs/client.crt
|
||||
client-key: classpath:/certs/client.key
|
||||
client-key-pass: dc3-client
|
||||
topic-prefix: dc3/${dc3.driver.tenant}/${spring.application.name}/
|
||||
receive-topics:
|
||||
- name: data
|
||||
qos: 1
|
||||
default-send-topic:
|
||||
qos: 1
|
||||
name: command
|
||||
keep-alive: 15
|
||||
completion-timeout: 3000
|
||||
batch:
|
||||
speed: ${MQTT_BATCH_SPEED:100}
|
||||
interval: ${MQTT_BATCH_INTERVAL:5}
|
||||
```
|
||||
|
||||
Connection settings such as `url` (dev default `tcp://${MQTT_BROKER_HOST:dc3-rabbitmq}:${MQTT_BROKER_PORT:2883}`),
|
||||
`auth-type` (`NONE` | `USERNAME` | `CLIENT`), `username`, `password`, and `receive-topics` are supplied by the
|
||||
consumer application (e.g. `dc3-driver-mqtt`).
|
||||
|
||||
## Usage
|
||||
|
||||
This module is activated when the `mqtt` profile is included or MQTT-related auto-configuration is on the classpath. The
|
||||
|
||||
+2
@@ -37,6 +37,7 @@ public class RequestHeader {
|
||||
throw new IllegalStateException(ExceptionConstant.UTILITY_CLASS);
|
||||
}
|
||||
|
||||
/** X-Auth-Token envelope: cipher + salt the gateway re-validates per hop. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@@ -55,6 +56,7 @@ public class RequestHeader {
|
||||
|
||||
}
|
||||
|
||||
/** X-Auth-Principal envelope: authenticated identity (principal/tenant) for downstream authz and audit. */
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
@@ -47,4 +47,5 @@ mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-quartz -am test
|
||||
|
||||
## Related Modules
|
||||
|
||||
- `dc3-common-manager` — Uses `QuartzService` for hourly data-volume statistics jobs
|
||||
- `dc3-common-manager` — Uses `QuartzService` for its hourly maintenance job
|
||||
- `dc3-common-data` / `dc3-common-mqtt` / `dc3-common-driver` — periodic persistence, message, and scheduling tasks
|
||||
|
||||
@@ -19,6 +19,9 @@ REST-based center services.
|
||||
| `WebFluxSecurityConfig` | Security chain, public-path rules, and facade-backed authorization |
|
||||
| `RequestIdWebFilter` | Adds and propagates request IDs for tracing |
|
||||
| `ExceptionConfig` | `@ControllerAdvice` global exception handler mapping exceptions to `R<T>` responses |
|
||||
| `BaseController` | Reactive controller helpers plus user/tenant context resolution |
|
||||
| `PrincipalHeaderUtil` | Reads the signed principal headers injected by the gateway |
|
||||
| `SpringDocConfig` | Shared springdoc/OpenAPI group configuration |
|
||||
| `ResponseUtil` | Utilities for writing non-controller `ServerHttpResponse` bodies in reactive context |
|
||||
|
||||
## Exception Handling
|
||||
@@ -28,8 +31,8 @@ All exceptions thrown by controllers are caught by `ExceptionConfig` and mapped
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"code": "FAILURE",
|
||||
"message": "Resource not found"
|
||||
"code": "R500",
|
||||
"message": "Service exception"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# DC3 DB
|
||||
|
||||
dc3-db is the relational dialect layer of IoT DC3. It provides dialect-neutral JDBC infrastructure — MyBatis-Plus
|
||||
wiring, the tenant-line handler, and generator utilities — plus one adapter per supported database engine behind the
|
||||
dc3.db.type property.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Role |
|
||||
|---|---|
|
||||
| dc3-db-core | dialect-neutral JDBC infrastructure: MybatisPlusConfig, TenantLineHandlerImpl, MybatisUtil, profile wiring |
|
||||
| dc3-db-postgres | PostgreSQL dialect adapter (default): driver, timestamptz type handler, pagination DbType |
|
||||
| dc3-db-mysql | MySQL 8 dialect adapter: driver, DATETIME(6) UTC conventions |
|
||||
| dc3-db-mariadb | MariaDB 10.6+ dialect adapter — MySQL-compatible surface except ODKU row aliases (uses VALUES()) |
|
||||
| dc3-db-tck | dual-dialect relational contract suite — identical mapper-level assertions against PostgreSQL and MySQL |
|
||||
|
||||
## Selection
|
||||
|
||||
The active dialect is chosen by the dc3.db.type property (default postgres). Only the selected adapter's
|
||||
auto-configuration is active.
|
||||
|
||||
```yaml
|
||||
dc3:
|
||||
db:
|
||||
type: postgres
|
||||
```
|
||||
|
||||
## Build and verify
|
||||
|
||||
```bash
|
||||
mvn -s .mvn/settings.xml -q -f dc3-db/pom.xml -DskipTests compile
|
||||
mvn -s .mvn/settings.xml -f dc3-db/pom.xml test
|
||||
```
|
||||
|
||||
Dialect conventions and migration notes live in docs/db-dialects.md.
|
||||
@@ -84,6 +84,7 @@ public class MybatisPlusConfig {
|
||||
return provider;
|
||||
}
|
||||
|
||||
/** Tenant-line before pagination; dialect pagination DbType injected by the db adapter. */
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor(TenantLineHandler tenantLineHandler) {
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnProperty(prefix = "dc3.db", name = "type", havingValue = "mariadb")
|
||||
public class MariadbDbAutoConfiguration {
|
||||
|
||||
/** MyBatis-Plus pagination dialect for MariaDB. */
|
||||
@Bean
|
||||
public DbType paginationDbType() {
|
||||
return DbType.MARIADB;
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnProperty(prefix = "dc3.db", name = "type", havingValue = "mysql")
|
||||
public class MysqlDbAutoConfiguration {
|
||||
|
||||
/** MyBatis-Plus pagination dialect for MySQL. */
|
||||
@Bean
|
||||
public DbType paginationDbType() {
|
||||
return DbType.MYSQL;
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnProperty(prefix = "dc3.db", name = "type", havingValue = "postgres", matchIfMissing = true)
|
||||
public class PostgresDbAutoConfiguration {
|
||||
|
||||
/** MyBatis-Plus pagination dialect for PostgreSQL. */
|
||||
@Bean
|
||||
public DbType paginationDbType() {
|
||||
return DbType.POSTGRE_SQL;
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ import java.util.concurrent.TimeUnit;
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> This driver is a work-in-progress skeleton. Protocol-level
|
||||
* I/O is not yet fully implemented — see TODO markers in method bodies.
|
||||
* I/O is not yet fully implemented.
|
||||
* </p>
|
||||
*
|
||||
* @author pnoker
|
||||
|
||||
+2
@@ -66,6 +66,7 @@ public class CoapProperties {
|
||||
private Dtls dtls = new Dtls();
|
||||
|
||||
@NoArgsConstructor
|
||||
/** CoAP role of the endpoint: client (collect from devices), server (accept device pushes), or both. */
|
||||
public enum ModeEnum {
|
||||
|
||||
CLIENT, SERVER, BOTH,
|
||||
@@ -74,6 +75,7 @@ public class CoapProperties {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
/** DTLS credentials: PSK identity/secret or a trust-store path for certificate mode. */
|
||||
public static class Dtls {
|
||||
|
||||
private String pskIdentity;
|
||||
|
||||
@@ -37,9 +37,21 @@ one native `Runtime` + TCP `MasterChannel` + association per outstation, class 0
|
||||
| Point Index | pointIndex | INT | 0 | DNP3 point index within the selected point type |
|
||||
| Point Type | pointType | STRING | BINARY_INPUT | BINARY_INPUT, ANALOG_INPUT, COUNTER, DOUBLE_BIT_BINARY_INPUT, BINARY_OUTPUT, or ANALOG_OUTPUT |
|
||||
|
||||
## Command Attributes (write)
|
||||
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|-----------|------|------|---------|-------------|
|
||||
| Point Index | pointIndex | INT | 0 | DNP3 point index within the selected output point type |
|
||||
| Point Type | pointType | STRING | BINARY_OUTPUT | BINARY_OUTPUT or ANALOG_OUTPUT |
|
||||
|
||||
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
|
||||
buffering. Keep this README aligned when those user-facing settings change.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A reachable DNP3 outstation over TCP (default port 20000) with matching master/outstation link-layer addresses. Native
|
||||
library loading must be verified on the target platform before commissioning.
|
||||
|
||||
## Running Locally
|
||||
|
||||
```bash
|
||||
|
||||
@@ -20,28 +20,28 @@ polling for IEC 104 client connections.
|
||||
|
||||
## Driver Attributes (Device-level)
|
||||
|
||||
| Attribute | Description |
|
||||
|-----------------|-------------|
|
||||
| Host | |
|
||||
| Port | |
|
||||
| ASDU Address | |
|
||||
| COT Length | |
|
||||
| CA Length | |
|
||||
| IOA Length | |
|
||||
| Connect Timeout | |
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|-----------------|----------------|--------|-----------|--------------------------------------|
|
||||
| Host | host | STRING | localhost | IEC 104 server address |
|
||||
| Port | port | INT | 2404 | IEC 104 TCP service port |
|
||||
| ASDU Address | asduAddress | INT | 1 | Common ASDU address |
|
||||
| COT Length | cotLength | INT | 2 | Cause-of-transmission field length |
|
||||
| CA Length | caLength | INT | 2 | Common-address field length |
|
||||
| IOA Length | ioaLength | INT | 3 | Information-object-address field length |
|
||||
| Connect Timeout | connectTimeout | INT | 10000 | Connection timeout in milliseconds |
|
||||
|
||||
## Point Attributes
|
||||
|
||||
| Attribute | Description |
|
||||
|-----------|-------------|
|
||||
| IOA | |
|
||||
| ASDU Type | |
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|-----------|----------|--------|----------|----------------------------------|
|
||||
| IOA | ioa | INT | 0 | Information object address |
|
||||
| ASDU Type | asduType | STRING | M_ME_NC_1 | ASDU type (e.g. M_ME_NC_1) |
|
||||
|
||||
## Command Attributes (write)
|
||||
|
||||
| Attribute | Description |
|
||||
|--------------|-------------|
|
||||
| Send Command | |
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|--------------|-------------|--------|----------|--------------------------|
|
||||
| Send Command | sendCommand | STRING | ${value} | Command payload template |
|
||||
|
||||
The module `application.yml` is authoritative for attribute codes, types, default values, scheduling, health, and
|
||||
local buffering. Keep this README aligned when those user-facing settings change.
|
||||
|
||||
@@ -26,9 +26,19 @@ per IED device and reads/writes data attributes addressed by an object reference
|
||||
| Object Reference | objectReference | STRING | | Data object reference, e.g. S1MMXU1.TotW.actVal |
|
||||
| Functional Constraint | functionalConstraint | STRING | MX | Functional constraint, e.g. MX, ST, CO, SP, SE |
|
||||
|
||||
## Command Attributes (write)
|
||||
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|------------------|-----------------|--------|---------|--------------------------------------------------------|
|
||||
| Object Reference | objectReference | STRING | | Data object reference to write, e.g. S1MMXU1.TotW.actVal |
|
||||
|
||||
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
|
||||
buffering. Keep this README aligned when those user-facing settings change.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A reachable IEC 61850 server (IED) exposing the configured data objects, typically on MMS port 102.
|
||||
|
||||
## Running Locally
|
||||
|
||||
```bash
|
||||
|
||||
@@ -25,9 +25,19 @@ latest cached value, and a point write produces a message to the configured topi
|
||||
| Topic | topic | STRING | | Override topic (defaults to driver topic) |
|
||||
| Key | key | STRING | | Message key used for produce and cache lookup |
|
||||
|
||||
## Command Attributes (write)
|
||||
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|-----------|-------|--------|---------|------------------------------------------|
|
||||
| Topic | topic | STRING | | Topic to produce the write message to |
|
||||
|
||||
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
|
||||
buffering. Keep this README aligned when those user-facing settings change.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A reachable Kafka cluster. Point the `spring.kafka.*` properties (or `KAFKA_BOOTSTRAP_SERVERS`) at the target broker.
|
||||
|
||||
## Connection
|
||||
|
||||
The broker connection is configured through Spring Boot `spring.kafka.*` properties in `application.yml`, driven by
|
||||
|
||||
@@ -30,9 +30,19 @@ control values.
|
||||
| Data Type | dataType | STRING | BOOL | BOOL, UINT, FLOAT, or CONTROL |
|
||||
| DPT | dpt | STRING | | Datapoint type for UINT reads/writes, e.g. 5.001 |
|
||||
|
||||
## Command Attributes (write)
|
||||
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|---------------|--------------|--------|---------|--------------------------------------|
|
||||
| Group Address | groupAddress | STRING | | KNX group address to write to |
|
||||
|
||||
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
|
||||
buffering. Keep this README aligned when those user-facing settings change.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A KNX installation reachable through a KNX IP gateway (default port 3671) with tunneling enabled.
|
||||
|
||||
## Running Locally
|
||||
|
||||
```bash
|
||||
|
||||
@@ -59,4 +59,4 @@ mvn -s .mvn/settings.xml -pl dc3-driver/dc3-driver-listening-virtual -am test
|
||||
|
||||
## Related Modules
|
||||
|
||||
- `dc3-common-driver` — Driver SDK for registration and RabbitMQ integration
|
||||
- `dc3-common-driver` — Driver SDK for registration, scheduling, and RabbitMQ integration
|
||||
|
||||
@@ -30,9 +30,20 @@ the ChirpStack `command/down` topic.
|
||||
| DevEUI | devEui | STRING | | LoRaWAN device EUI (16 hex characters) |
|
||||
| Field | field | STRING | | Cayenne LPP object field; empty returns raw base64 |
|
||||
|
||||
## Command Attributes (write)
|
||||
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|-----------|--------|--------|---------|--------------------------------------|
|
||||
| DevEUI | devEui | STRING | | Target device EUI for the downlink |
|
||||
|
||||
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
|
||||
buffering. Keep this README aligned when those user-facing settings change.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A ChirpStack instance publishing uplinks to the configured MQTT broker topic
|
||||
(`application/+/device/+/event/up`).
|
||||
|
||||
## Running Locally
|
||||
|
||||
```bash
|
||||
|
||||
@@ -32,6 +32,12 @@ record by index and decoding it as BCD, integer, IEEE-754, or raw HEX.
|
||||
| Record Index | recordIndex | INT | 0 | Zero-based index of the DIF/VIF record |
|
||||
| Data Format | dataFormat | STRING | AUTO | Data format: AUTO, HEX, BCD, INT, FLOAT |
|
||||
|
||||
## Command Attributes (write)
|
||||
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|-------------|------------|--------|---------|------------------------------------------------|
|
||||
| Data Format | dataFormat | STRING | ASCII | Format used to encode the written value |
|
||||
|
||||
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
|
||||
buffering. Keep this README aligned when those user-facing settings change.
|
||||
|
||||
|
||||
@@ -79,5 +79,5 @@ mvn -s .mvn/settings.xml -pl dc3-driver/dc3-driver-mqtt -am test
|
||||
|
||||
## Related Modules
|
||||
|
||||
- `dc3-common-driver` — Driver SDK for registration and RabbitMQ integration
|
||||
- `dc3-common-driver` — Driver SDK for registration, scheduling, and RabbitMQ integration
|
||||
- `dc3-common-mqtt` — MQTT client configuration and utilities
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ import java.util.Objects;
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> This driver is a work-in-progress skeleton. Protocol-level
|
||||
* I/O is not yet fully implemented — see TODO markers in method bodies.
|
||||
* I/O is not yet fully implemented.
|
||||
* </p>
|
||||
*
|
||||
* @author pnoker
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> This driver is a work-in-progress skeleton. Protocol-level
|
||||
* I/O is not yet fully implemented — see TODO markers in method bodies.
|
||||
* I/O is not yet fully implemented.
|
||||
* </p>
|
||||
*
|
||||
* @author pnoker
|
||||
|
||||
@@ -48,6 +48,7 @@ public class FILETIME {
|
||||
this.low = low;
|
||||
}
|
||||
|
||||
/** The COM wire shape: a two-int JIStruct (low, high), member order fixed by the OPC spec. */
|
||||
public static JIStruct getStruct() throws JIException {
|
||||
final JIStruct struct = new JIStruct();
|
||||
|
||||
@@ -57,6 +58,7 @@ public class FILETIME {
|
||||
return struct;
|
||||
}
|
||||
|
||||
/** Decode a wire FILETIME (low first) into the typed wrapper. */
|
||||
public static FILETIME fromStruct(final JIStruct struct) {
|
||||
final FILETIME ft = new FILETIME();
|
||||
|
||||
@@ -112,6 +114,7 @@ public class FILETIME {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** The FILETIME epoch (1601-01-01) reinterpreted as a Calendar. */
|
||||
public Calendar asCalendar() {
|
||||
final Calendar c = Calendar.getInstance();
|
||||
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ public class OPCITEMRESULT {
|
||||
return struct;
|
||||
}
|
||||
|
||||
/** Decode the COM wire struct into the typed OPCITEMRESULT wrapper. */
|
||||
public static OPCITEMRESULT fromStruct(final JIStruct struct) {
|
||||
OPCITEMRESULT result = new OPCITEMRESULT();
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ public class OPCITEMSTATE {
|
||||
return struct;
|
||||
}
|
||||
|
||||
/** Decode the COM wire struct into the typed OPCITEMSTATE wrapper. */
|
||||
public static OPCITEMSTATE fromStruct(final JIStruct struct) {
|
||||
OPCITEMSTATE itemState = new OPCITEMSTATE();
|
||||
|
||||
|
||||
+1
@@ -72,6 +72,7 @@ public class OPCSERVERSTATUS {
|
||||
return struct;
|
||||
}
|
||||
|
||||
/** Decode the COM wire struct into the typed OPCSERVERSTATUS wrapper. */
|
||||
public static OPCSERVERSTATUS fromStruct(final JIStruct struct) {
|
||||
OPCSERVERSTATUS status = new OPCSERVERSTATUS();
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ servers to read and write node values from industrial automation systems using t
|
||||
| Attribute | Description |
|
||||
|-----------|--------------------------------------|
|
||||
| Host | OPC UA server hostname or IP |
|
||||
| Port | OPC UA server port (typically 4840) |
|
||||
| Path | OPC UA endpoint path (e.g., `/milo`) |
|
||||
| Port | OPC UA server port (default 18600) |
|
||||
| Path | OPC UA endpoint path (default `/`) |
|
||||
|
||||
## Point Attributes
|
||||
|
||||
|
||||
@@ -26,9 +26,20 @@ logical devices within one Redis instance.
|
||||
| Data Type | dataType | STRING | STRING | Redis data type: STRING or HASH |
|
||||
| Field | field | STRING | | Hash field, required when dataType=HASH |
|
||||
|
||||
## Command Attributes (write)
|
||||
|
||||
| Attribute | Code | Type | Default | Description |
|
||||
|-----------|----------|--------|---------|------------------------------------------------|
|
||||
| Data Type | dataType | STRING | STRING | Redis data type of the written key: STRING or HASH |
|
||||
|
||||
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
|
||||
buffering. Keep this README aligned when those user-facing settings change.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A reachable Redis instance. Point the `spring.data.redis.*` properties (or `REDIS_HOST`, `REDIS_PORT`,
|
||||
`REDIS_PASSWORD`, `REDIS_DATABASE`) at the target server.
|
||||
|
||||
## Connection
|
||||
|
||||
The Redis connection is configured through Spring Boot `spring.data.redis.*` properties in `application.yml`, driven
|
||||
|
||||
@@ -12,7 +12,7 @@ hardware.
|
||||
- **Artifact ID**: dc3-driver-virtual
|
||||
- **Driver Name**: Virtual Driver
|
||||
|
||||
## Driver Attributes
|
||||
## Driver Attributes (Device-level)
|
||||
|
||||
| Attribute | Description |
|
||||
|-----------|---------------------------|
|
||||
|
||||
@@ -22,6 +22,7 @@ storage, RabbitMQ delivery, and TimescaleDB hypertables.
|
||||
| `EventReportE2eIT` | Event report end-to-end |
|
||||
| `RabbitDeliveryIT` | RabbitMQ message delivery |
|
||||
| `PostgresHypertableIT` | TimescaleDB hypertable behavior |
|
||||
| `MqttVendorNeutralityIT` | Broker-neutral MQTT delivery across vendors |
|
||||
| `E2eEnvironmentGuardIT` | Guards that the target environment is configured |
|
||||
|
||||
The harness (`E2eStack`, `BaseE2eIT`) lives under `src/test/java/io/github/pnoker/e2e/harness`.
|
||||
|
||||
@@ -14,9 +14,9 @@ routing, and reverse proxying.
|
||||
|
||||
## Service Ports
|
||||
|
||||
| Protocol | Port |
|
||||
|----------|--------|
|
||||
| HTTP | `8000` |
|
||||
| Protocol | Port | Override |
|
||||
|----------|--------|--------------------|
|
||||
| HTTP | `8000` | `DC3_GATEWAY_PORT` |
|
||||
|
||||
## Key Responsibilities
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# DC3 MQ
|
||||
|
||||
dc3-mq is the pluggable message-broker layer of IoT DC3. It defines a broker-neutral messaging port — logical topics,
|
||||
subscriptions, delivery envelopes, and capability negotiation — plus one certified adapter per supported broker.
|
||||
Business code (centers, drivers, common modules) depends on the port and never on broker-specific classes.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Role |
|
||||
|---|---|
|
||||
| dc3-mq-core | broker-neutral port: BrokerAdapter SPI, Dc3Listener/MqListener annotations, MessageSender, envelopes, retry policy, and batch-consumer properties (dc3.data.point.batch) |
|
||||
| dc3-mq-rabbitmq | RabbitMQ adapter (default); physical topology is byte-for-byte identical to the legacy RabbitConstant names |
|
||||
| dc3-mq-kafka | Kafka adapter |
|
||||
| dc3-mq-rocketmq | RocketMQ adapter |
|
||||
| dc3-mq-pulsar | Pulsar adapter |
|
||||
| dc3-mq-activemq | ActiveMQ (Artemis / Classic, JMS 2.0) adapter |
|
||||
| dc3-mq-mqtt | MQTT 5 adapter (EMQX / HiveMQ / NanoMQ / ...) |
|
||||
| dc3-mq-tck | broker-neutral contract suite: an adapter that passes these tests is compliant |
|
||||
|
||||
## Selection
|
||||
|
||||
The active broker is chosen by the dc3.mq.type property (default rabbitmq). Only the selected adapter's
|
||||
auto-configuration is active; the others remain dormant on the classpath.
|
||||
|
||||
```yaml
|
||||
dc3:
|
||||
mq:
|
||||
type: rabbitmq
|
||||
```
|
||||
|
||||
Logical topics (MqTopic) are mapped to broker-specific exchange/queue/topic names by each adapter; the RabbitMQ adapter
|
||||
keeps the legacy names such as dc3.e.value and dc3.q.value.point, so existing deployments and the dc3.rabbit.tag
|
||||
prefixing behaviour keep working unchanged.
|
||||
|
||||
## Build and verify
|
||||
|
||||
```bash
|
||||
mvn -s .mvn/settings.xml -q -f dc3-mq/pom.xml -DskipTests compile
|
||||
mvn -s .mvn/settings.xml -f dc3-mq/pom.xml test
|
||||
```
|
||||
|
||||
Adapter selection guides and trade-offs live in docs/mq-brokers.md.
|
||||
+2
@@ -38,6 +38,7 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "activemq")
|
||||
public class ActiveMqAdapterConfiguration {
|
||||
|
||||
/** Connection factory from the adapter url/credentials; overridable by a user bean. */
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(jakarta.jms.ConnectionFactory.class)
|
||||
public ActiveMQConnectionFactory activeMqConnectionFactory(
|
||||
@@ -47,6 +48,7 @@ public class ActiveMqAdapterConfiguration {
|
||||
return new ActiveMQConnectionFactory(url, user, password);
|
||||
}
|
||||
|
||||
/** The port adapter bound to this broker's connection factory. */
|
||||
@Bean
|
||||
public ActiveMqAdapter activeMqAdapter(jakarta.jms.ConnectionFactory connectionFactory,
|
||||
BatchConsumerProperties batchProperties) {
|
||||
|
||||
+2
@@ -44,6 +44,7 @@ import java.util.Objects;
|
||||
@EnableConfigurationProperties(BatchConsumerProperties.class)
|
||||
public class MqAutoConfiguration {
|
||||
|
||||
/** Publishing facade over the active adapter. */
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(MessageSender.class)
|
||||
public MessageSender messageSender(ObjectProvider<BrokerAdapter> adapterProvider) {
|
||||
@@ -63,6 +64,7 @@ public class MqAutoConfiguration {
|
||||
return new MessageSenderImpl(adapter);
|
||||
}
|
||||
|
||||
/** Registers beans carrying @Dc3Listener methods with the active adapter. */
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Dc3ListenerProcessor dc3ListenerProcessor(ObjectProvider<BrokerAdapter> adapterProvider) {
|
||||
|
||||
@@ -63,6 +63,7 @@ public class MqMessage {
|
||||
@Builder.Default
|
||||
private final Duration delay = Duration.ZERO;
|
||||
|
||||
/** Build a message for a topic with an explicit partition key (ordered streams). */
|
||||
public static MqMessage of(MqTopic topic, String partitionKey, Object payload) {
|
||||
return MqMessage.builder()
|
||||
.topic(topic)
|
||||
|
||||
+2
@@ -40,6 +40,7 @@ import java.util.Map;
|
||||
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "kafka")
|
||||
public class KafkaMqAdapterConfiguration {
|
||||
|
||||
/** Producer template on the adapter bootstrap servers; overridable by a user bean. */
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(KafkaTemplate.class)
|
||||
public KafkaTemplate<String, byte[]> kafkaMqTemplate(
|
||||
@@ -47,6 +48,7 @@ public class KafkaMqAdapterConfiguration {
|
||||
return KafkaMqAdapter.template(bootstrapServers);
|
||||
}
|
||||
|
||||
/** The port adapter bound to the Kafka template. */
|
||||
@Bean
|
||||
public KafkaMqAdapter kafkaMqAdapter(KafkaTemplate<String, byte[]> kafkaTemplate,
|
||||
@Value("${dc3.mq.kafka.bootstrap-servers:${DC3_MQ_KAFKA_BOOTSTRAP:${spring.kafka.bootstrap-servers:localhost:9092}}}")
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "mqtt")
|
||||
public class MqttMqAdapterConfiguration {
|
||||
|
||||
/** The port adapter over the HiveMQ MQTT5 client. */
|
||||
@Bean
|
||||
public MqttMqAdapter mqttMqAdapter(@Value("${dc3.mq.mqtt.host:localhost}") String host,
|
||||
@Value("${dc3.mq.mqtt.port:1883}") int port,
|
||||
|
||||
+2
@@ -38,6 +38,7 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "pulsar")
|
||||
public class PulsarMqAdapterConfiguration {
|
||||
|
||||
/** Shared Pulsar client on the service url. */
|
||||
@Bean(destroyMethod = "close")
|
||||
@ConditionalOnMissingBean(PulsarClient.class)
|
||||
public PulsarClient pulsarClient(
|
||||
@@ -46,6 +47,7 @@ public class PulsarMqAdapterConfiguration {
|
||||
return PulsarClient.builder().serviceUrl(serviceUrl).build();
|
||||
}
|
||||
|
||||
/** The port adapter bound to the shared client. */
|
||||
@Bean
|
||||
public PulsarMqAdapter pulsarMqAdapter(PulsarClient pulsarClient, BatchConsumerProperties batchProperties) {
|
||||
return new PulsarMqAdapter(pulsarClient, batchProperties);
|
||||
|
||||
+2
@@ -43,10 +43,12 @@ public final class RabbitAcknowledgment implements Acknowledgment {
|
||||
this.multiple = multiple;
|
||||
}
|
||||
|
||||
/** Ack exactly one delivery ({@code multiple=false}). */
|
||||
public static RabbitAcknowledgment single(Channel channel, long deliveryTag) {
|
||||
return new RabbitAcknowledgment(channel, deliveryTag, false);
|
||||
}
|
||||
|
||||
/** Ack everything up to the tag ({@code multiple=true}) — the broker-batch commit path. */
|
||||
public static RabbitAcknowledgment batch(Channel channel, long lastDeliveryTag) {
|
||||
return new RabbitAcknowledgment(channel, lastDeliveryTag, true);
|
||||
}
|
||||
|
||||
+4
@@ -47,12 +47,14 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "rabbitmq", matchIfMissing = true)
|
||||
public class RabbitMqAdapterConfiguration {
|
||||
|
||||
/** JSON converter with typed envelope headers. */
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MessageConverter messageConverter() {
|
||||
return new JacksonJsonMessageConverter(JsonUtil.getJsonMapper());
|
||||
}
|
||||
|
||||
/** Publisher-confirms template with mandatory returns. */
|
||||
@Bean(name = "rabbitTemplate")
|
||||
@ConditionalOnMissingBean(RabbitTemplate.class)
|
||||
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
|
||||
@@ -79,12 +81,14 @@ public class RabbitMqAdapterConfiguration {
|
||||
return rabbitTemplate;
|
||||
}
|
||||
|
||||
/** Declares queues/exchanges/bindings at startup. */
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
|
||||
return new RabbitAdmin(connectionFactory);
|
||||
}
|
||||
|
||||
/** The port adapter bound to the template and admin. */
|
||||
@Bean
|
||||
public RabbitMqAdapter rabbitMqAdapter(RabbitTemplate rabbitTemplate, RabbitAdmin rabbitAdmin,
|
||||
ConnectionFactory connectionFactory, BatchConsumerProperties batchProperties,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user