Refactor telemetry registry to use sensor definitions .json and historian identity
This commit is contained in:
@@ -76,7 +76,9 @@ public class BlockPollingService {
|
|||||||
|
|
||||||
telemetryCache.put(new TelemetrySnapshot(
|
telemetryCache.put(new TelemetrySnapshot(
|
||||||
sensor.getId(),
|
sensor.getId(),
|
||||||
|
sensor.getKey(),
|
||||||
sensor.getName(),
|
sensor.getName(),
|
||||||
|
sensor.getCategory(),
|
||||||
sensor.getModbusAddress(),
|
sensor.getModbusAddress(),
|
||||||
sensor.getBitOffset(),
|
sensor.getBitOffset(),
|
||||||
rawValue,
|
rawValue,
|
||||||
|
|||||||
+32
-5
@@ -5,6 +5,9 @@ import com.litoralregas.backend.acquisition.block.BlockPollingService;
|
|||||||
import com.litoralregas.backend.dashboard.DashboardOverviewResponse;
|
import com.litoralregas.backend.dashboard.DashboardOverviewResponse;
|
||||||
import com.litoralregas.backend.dashboard.DashboardOverviewService;
|
import com.litoralregas.backend.dashboard.DashboardOverviewService;
|
||||||
import com.litoralregas.backend.historian.HistorianService;
|
import com.litoralregas.backend.historian.HistorianService;
|
||||||
|
import com.litoralregas.backend.modules.climate.ClimateModuleResponse;
|
||||||
|
import com.litoralregas.backend.modules.climate.ClimateModuleService;
|
||||||
|
import com.litoralregas.backend.modules.climate.websocket.ClimateModuleWebSocketPublisher;
|
||||||
import com.litoralregas.backend.modules.meteo.MeteoModuleResponse;
|
import com.litoralregas.backend.modules.meteo.MeteoModuleResponse;
|
||||||
import com.litoralregas.backend.modules.meteo.websocket.MeteoModuleWebSocketPublisher;
|
import com.litoralregas.backend.modules.meteo.websocket.MeteoModuleWebSocketPublisher;
|
||||||
import com.litoralregas.backend.websocket.dashboard.DashboardOverviewWebSocketPublisher;
|
import com.litoralregas.backend.websocket.dashboard.DashboardOverviewWebSocketPublisher;
|
||||||
@@ -35,6 +38,9 @@ public class AcquisitionSchedulerService {
|
|||||||
|
|
||||||
private final AtomicBoolean polling = new AtomicBoolean(false);
|
private final AtomicBoolean polling = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private final ClimateModuleWebSocketPublisher climateModuleWebSocketPublisher;
|
||||||
|
private final ClimateModuleService climateModuleService;
|
||||||
|
|
||||||
public AcquisitionSchedulerService(
|
public AcquisitionSchedulerService(
|
||||||
BlockPollingService blockPollingService,
|
BlockPollingService blockPollingService,
|
||||||
AcquisitionSchedulerProperties properties,
|
AcquisitionSchedulerProperties properties,
|
||||||
@@ -44,7 +50,9 @@ public class AcquisitionSchedulerService {
|
|||||||
MeteoModuleWebSocketPublisher meteoModuleWebSocketPublisher,
|
MeteoModuleWebSocketPublisher meteoModuleWebSocketPublisher,
|
||||||
HistorianService historianService,
|
HistorianService historianService,
|
||||||
DashboardOverviewService dashboardOverviewService,
|
DashboardOverviewService dashboardOverviewService,
|
||||||
MeteoModuleService meteoModuleService
|
MeteoModuleService meteoModuleService,
|
||||||
|
ClimateModuleWebSocketPublisher climateModuleWebSocketPublisher,
|
||||||
|
ClimateModuleService climateModuleService
|
||||||
) {
|
) {
|
||||||
this.blockPollingService = blockPollingService;
|
this.blockPollingService = blockPollingService;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
@@ -55,6 +63,8 @@ public class AcquisitionSchedulerService {
|
|||||||
this.historianService = historianService;
|
this.historianService = historianService;
|
||||||
this.dashboardOverviewService = dashboardOverviewService;
|
this.dashboardOverviewService = dashboardOverviewService;
|
||||||
this.meteoModuleService = meteoModuleService;
|
this.meteoModuleService = meteoModuleService;
|
||||||
|
this.climateModuleWebSocketPublisher = climateModuleWebSocketPublisher;
|
||||||
|
this.climateModuleService = climateModuleService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@@ -94,14 +104,31 @@ public class AcquisitionSchedulerService {
|
|||||||
|
|
||||||
telemetryWebSocketPublisher.publishLatestTelemetry();
|
telemetryWebSocketPublisher.publishLatestTelemetry();
|
||||||
|
|
||||||
DashboardOverviewResponse overview = dashboardOverviewService.getOverview();
|
DashboardOverviewResponse overview =
|
||||||
historianService.recordDashboardOverview(overview);
|
dashboardOverviewService.getOverview();
|
||||||
|
|
||||||
dashboardOverviewWebSocketPublisher.publishOverview(overview);
|
dashboardOverviewWebSocketPublisher.publishOverview(overview);
|
||||||
|
|
||||||
MeteoModuleResponse meteo = meteoModuleService.getLatest();
|
MeteoModuleResponse meteo =
|
||||||
historianService.recordModuleSensors("meteo", meteo.sensors(), meteo.timestamp());
|
meteoModuleService.getLatest();
|
||||||
|
|
||||||
|
historianService.recordModuleSensors(
|
||||||
|
meteo.sensors(),
|
||||||
|
meteo.timestamp()
|
||||||
|
);
|
||||||
|
|
||||||
meteoModuleWebSocketPublisher.publishLatest(meteo);
|
meteoModuleWebSocketPublisher.publishLatest(meteo);
|
||||||
|
|
||||||
|
ClimateModuleResponse climate =
|
||||||
|
climateModuleService.getLatest();
|
||||||
|
|
||||||
|
historianService.recordModuleSensors(
|
||||||
|
climate.sensors(),
|
||||||
|
climate.timestamp()
|
||||||
|
);
|
||||||
|
|
||||||
|
climateModuleWebSocketPublisher.publishLatest(climate);
|
||||||
|
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
runtimeStatus.setLastError(exception.getMessage());
|
runtimeStatus.setLastError(exception.getMessage());
|
||||||
|
|
||||||
|
|||||||
+32
-9
@@ -1,7 +1,5 @@
|
|||||||
package com.litoralregas.backend.acquisition.telemetry;
|
package com.litoralregas.backend.acquisition.telemetry;
|
||||||
|
|
||||||
import com.litoralregas.backend.acquisition.telemetry.TelemetryCache;
|
|
||||||
import com.litoralregas.backend.acquisition.telemetry.TelemetrySnapshot;
|
|
||||||
import com.litoralregas.backend.modbus.LrModbusClient;
|
import com.litoralregas.backend.modbus.LrModbusClient;
|
||||||
import com.litoralregas.backend.modbus.ModbusReadResult;
|
import com.litoralregas.backend.modbus.ModbusReadResult;
|
||||||
import com.litoralregas.backend.modbus.ModbusUnit;
|
import com.litoralregas.backend.modbus.ModbusUnit;
|
||||||
@@ -32,11 +30,19 @@ public class SensorTelemetryReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TelemetrySnapshot readSensor(Integer sensorId) {
|
public TelemetrySnapshot readSensor(Integer sensorId) {
|
||||||
SensorDefinition sensorDefinition = sensorDefinitionRepository.findById(sensorId)
|
|
||||||
.orElseThrow(() -> new EntityNotFoundException("Sensor definition not found: " + sensorId));
|
SensorDefinition sensorDefinition =
|
||||||
|
sensorDefinitionRepository.findById(sensorId)
|
||||||
|
.orElseThrow(() ->
|
||||||
|
new EntityNotFoundException(
|
||||||
|
"Sensor definition not found: " + sensorId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if (sensorDefinition.getSourceType() != SensorSourceType.MODBUS) {
|
if (sensorDefinition.getSourceType() != SensorSourceType.MODBUS) {
|
||||||
throw new IllegalArgumentException("Only MODBUS sensors can be read directly.");
|
throw new IllegalArgumentException(
|
||||||
|
"Only MODBUS sensors can be read directly."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ModbusReadResult result = modbusClient.readInputRegisters(
|
ModbusReadResult result = modbusClient.readInputRegisters(
|
||||||
@@ -46,11 +52,17 @@ public class SensorTelemetryReader {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Integer rawValue = result.values().getFirst();
|
Integer rawValue = result.values().getFirst();
|
||||||
Object value = convertValue(sensorDefinition, rawValue);
|
|
||||||
|
Object value = convertValue(
|
||||||
|
sensorDefinition,
|
||||||
|
rawValue
|
||||||
|
);
|
||||||
|
|
||||||
TelemetrySnapshot snapshot = new TelemetrySnapshot(
|
TelemetrySnapshot snapshot = new TelemetrySnapshot(
|
||||||
sensorDefinition.getId(),
|
sensorDefinition.getId(),
|
||||||
|
sensorDefinition.getKey(),
|
||||||
sensorDefinition.getName(),
|
sensorDefinition.getName(),
|
||||||
|
sensorDefinition.getCategory(),
|
||||||
sensorDefinition.getModbusAddress(),
|
sensorDefinition.getModbusAddress(),
|
||||||
sensorDefinition.getBitOffset(),
|
sensorDefinition.getBitOffset(),
|
||||||
rawValue,
|
rawValue,
|
||||||
@@ -64,19 +76,30 @@ public class SensorTelemetryReader {
|
|||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object convertValue(SensorDefinition sensorDefinition, Integer rawValue) {
|
private Object convertValue(
|
||||||
|
SensorDefinition sensorDefinition,
|
||||||
|
Integer rawValue
|
||||||
|
) {
|
||||||
|
|
||||||
if (sensorDefinition.getValueType() == SensorValueType.BOOLEAN) {
|
if (sensorDefinition.getValueType() == SensorValueType.BOOLEAN) {
|
||||||
|
|
||||||
Integer bitOffset = sensorDefinition.getBitOffset();
|
Integer bitOffset = sensorDefinition.getBitOffset();
|
||||||
|
|
||||||
if (bitOffset == null) {
|
if (bitOffset == null) {
|
||||||
throw new IllegalStateException("BOOLEAN sensor requires bitOffset.");
|
throw new IllegalStateException(
|
||||||
|
"BOOLEAN sensor requires bitOffset."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((rawValue >> bitOffset) & 1) == 1;
|
return ((rawValue >> bitOffset) & 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sensorDefinition.getValueType() == SensorValueType.DECIMAL) {
|
if (sensorDefinition.getValueType() == SensorValueType.DECIMAL) {
|
||||||
return rawValue / Math.pow(10, sensorDefinition.getDecimalPlaces());
|
|
||||||
|
return rawValue / Math.pow(
|
||||||
|
10,
|
||||||
|
sensorDefinition.getDecimalPlaces()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawValue;
|
return rawValue;
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import java.time.Instant;
|
|||||||
|
|
||||||
public record TelemetrySnapshot(
|
public record TelemetrySnapshot(
|
||||||
Integer sensorId,
|
Integer sensorId,
|
||||||
|
String key,
|
||||||
String name,
|
String name,
|
||||||
|
String category,
|
||||||
Integer modbusAddress,
|
Integer modbusAddress,
|
||||||
Integer bitOffset,
|
Integer bitOffset,
|
||||||
Integer rawValue,
|
Integer rawValue,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.litoralregas.backend.historian;
|
package com.litoralregas.backend.historian;
|
||||||
|
|
||||||
import com.litoralregas.backend.dashboard.DashboardOverviewResponse;
|
|
||||||
import com.litoralregas.backend.modules.shared.ModuleSensorResponse;
|
import com.litoralregas.backend.modules.shared.ModuleSensorResponse;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -13,78 +12,12 @@ import java.util.Map;
|
|||||||
@Service
|
@Service
|
||||||
public class HistorianService {
|
public class HistorianService {
|
||||||
|
|
||||||
private static final String SOURCE_DASHBOARD_OVERVIEW = "DASHBOARD_OVERVIEW";
|
|
||||||
|
|
||||||
private final HistorianSampleRepository historianSampleRepository;
|
private final HistorianSampleRepository historianSampleRepository;
|
||||||
|
private static final String SOURCE_MODULE = "MODULE";
|
||||||
public HistorianService(HistorianSampleRepository historianSampleRepository) {
|
public HistorianService(HistorianSampleRepository historianSampleRepository) {
|
||||||
this.historianSampleRepository = historianSampleRepository;
|
this.historianSampleRepository = historianSampleRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public void recordDashboardOverview(DashboardOverviewResponse overview) {
|
|
||||||
System.out.println("Historian recording overview at " + overview.timestamp());
|
|
||||||
Instant sampledAt = overview.timestamp();
|
|
||||||
|
|
||||||
System.out.println("Meteo object = " + overview.meteo());
|
|
||||||
|
|
||||||
if (overview.climate() != null && !overview.climate().zones().isEmpty()) {
|
|
||||||
System.out.println(
|
|
||||||
"Zone 1 temp = " +
|
|
||||||
overview.climate().zones().get(0).temperature()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
recordNumber(sampledAt, "meteo.exterior_temperature", overview.meteo().exteriorTemperature(), "°C");
|
|
||||||
recordNumber(sampledAt, "meteo.exterior_humidity", overview.meteo().exteriorHumidity(), "%");
|
|
||||||
recordNumber(sampledAt, "meteo.radiation", overview.meteo().radiation(), "W/m²");
|
|
||||||
recordNumber(sampledAt, "meteo.wind_speed", overview.meteo().windSpeed(), "Km/h");
|
|
||||||
recordNumber(sampledAt, "meteo.wind_direction", overview.meteo().windDirection(), "°");
|
|
||||||
recordBoolean(sampledAt, "meteo.raining", overview.meteo().raining());
|
|
||||||
|
|
||||||
if (overview.climate() != null && overview.climate().zones() != null) {
|
|
||||||
for (DashboardOverviewResponse.ClimateZoneOverview zone : overview.climate().zones()) {
|
|
||||||
String prefix = "climate.zone_" + zone.zoneNumber();
|
|
||||||
|
|
||||||
recordNumber(sampledAt, prefix + ".temperature", zone.temperature(), "°C");
|
|
||||||
recordNumber(sampledAt, prefix + ".humidity", zone.humidity(), "%");
|
|
||||||
recordNumber(sampledAt, prefix + ".co2", zone.co2(), "ppm");
|
|
||||||
|
|
||||||
recordBoolean(sampledAt, prefix + ".fans_on", zone.fansOn());
|
|
||||||
recordBoolean(sampledAt, prefix + ".extractors_on", zone.extractorsOn());
|
|
||||||
|
|
||||||
recordNumber(sampledAt, prefix + ".zenital_left_percent", zone.zenitalLeftPercent(), "%");
|
|
||||||
recordNumber(sampledAt, prefix + ".zenital_right_percent", zone.zenitalRightPercent(), "%");
|
|
||||||
recordNumber(sampledAt, prefix + ".lateral_left_percent", zone.lateralLeftPercent(), "%");
|
|
||||||
recordNumber(sampledAt, prefix + ".lateral_right_percent", zone.lateralRightPercent(), "%");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overview.irrigation() != null) {
|
|
||||||
recordNumber(
|
|
||||||
sampledAt,
|
|
||||||
"irrigation.active_valve_count",
|
|
||||||
overview.irrigation().activeValveCount(),
|
|
||||||
"valves"
|
|
||||||
);
|
|
||||||
|
|
||||||
recordNumber(
|
|
||||||
sampledAt,
|
|
||||||
"irrigation.active_pump_count",
|
|
||||||
overview.irrigation().activePumpCount(),
|
|
||||||
"pumps"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overview.lighting() != null) {
|
|
||||||
recordNumber(
|
|
||||||
sampledAt,
|
|
||||||
"lighting.active_sector_count",
|
|
||||||
overview.lighting().activeSectorCount(),
|
|
||||||
"sectors"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<HistorianSeriesPoint> getSeries(String keyName, Instant from, Instant to) {
|
public List<HistorianSeriesPoint> getSeries(String keyName, Instant from, Instant to) {
|
||||||
return historianSampleRepository
|
return historianSampleRepository
|
||||||
@@ -126,7 +59,7 @@ public class HistorianService {
|
|||||||
sample.setKeyName(keyName);
|
sample.setKeyName(keyName);
|
||||||
sample.setNumericValue(value.doubleValue());
|
sample.setNumericValue(value.doubleValue());
|
||||||
sample.setUnit(unit);
|
sample.setUnit(unit);
|
||||||
sample.setSource(SOURCE_DASHBOARD_OVERVIEW);
|
sample.setSource(SOURCE_MODULE);
|
||||||
System.out.println("Saving historian sample: " + keyName + " = " + value);
|
System.out.println("Saving historian sample: " + keyName + " = " + value);
|
||||||
historianSampleRepository.save(sample);
|
historianSampleRepository.save(sample);
|
||||||
}
|
}
|
||||||
@@ -138,7 +71,7 @@ public class HistorianService {
|
|||||||
sample.setSampledAt(sampledAt);
|
sample.setSampledAt(sampledAt);
|
||||||
sample.setKeyName(keyName);
|
sample.setKeyName(keyName);
|
||||||
sample.setBooleanValue(value);
|
sample.setBooleanValue(value);
|
||||||
sample.setSource(SOURCE_DASHBOARD_OVERVIEW);
|
sample.setSource(SOURCE_MODULE);
|
||||||
|
|
||||||
historianSampleRepository.save(sample);
|
historianSampleRepository.save(sample);
|
||||||
}
|
}
|
||||||
@@ -154,13 +87,12 @@ public class HistorianService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void recordModuleSensors(
|
public void recordModuleSensors(
|
||||||
String moduleName,
|
|
||||||
List<ModuleSensorResponse> sensors,
|
List<ModuleSensorResponse> sensors,
|
||||||
Instant sampledAt
|
Instant sampledAt
|
||||||
) {
|
) {
|
||||||
for (ModuleSensorResponse sensor : sensors) {
|
for (ModuleSensorResponse sensor : sensors) {
|
||||||
|
|
||||||
String keyName = moduleName + "." + sensor.key();
|
String key = sensor.key();
|
||||||
|
|
||||||
Object value = sensor.value();
|
Object value = sensor.value();
|
||||||
|
|
||||||
@@ -168,7 +100,7 @@ public class HistorianService {
|
|||||||
|
|
||||||
recordNumber(
|
recordNumber(
|
||||||
sampledAt,
|
sampledAt,
|
||||||
keyName,
|
key,
|
||||||
numberValue,
|
numberValue,
|
||||||
sensor.unit()
|
sensor.unit()
|
||||||
);
|
);
|
||||||
@@ -177,7 +109,7 @@ public class HistorianService {
|
|||||||
|
|
||||||
recordBoolean(
|
recordBoolean(
|
||||||
sampledAt,
|
sampledAt,
|
||||||
keyName,
|
key,
|
||||||
booleanValue
|
booleanValue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ public class ClimateModuleService {
|
|||||||
private ModuleSensorResponse toResponse(TelemetrySnapshot snapshot) {
|
private ModuleSensorResponse toResponse(TelemetrySnapshot snapshot) {
|
||||||
return new ModuleSensorResponse(
|
return new ModuleSensorResponse(
|
||||||
snapshot.sensorId(),
|
snapshot.sensorId(),
|
||||||
|
snapshot.key(),
|
||||||
snapshot.name(),
|
snapshot.name(),
|
||||||
buildKey(snapshot.name()),
|
buildKey(snapshot.name()),
|
||||||
snapshot.value(),
|
snapshot.value(),
|
||||||
|
|||||||
+20
@@ -1,4 +1,24 @@
|
|||||||
package com.litoralregas.backend.modules.climate.websocket;
|
package com.litoralregas.backend.modules.climate.websocket;
|
||||||
|
|
||||||
|
import com.litoralregas.backend.modules.climate.ClimateModuleResponse;
|
||||||
|
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
public class ClimateModuleWebSocketPublisher {
|
public class ClimateModuleWebSocketPublisher {
|
||||||
|
|
||||||
|
private static final String DESTINATION =
|
||||||
|
"/topic/modules/climate/latest";
|
||||||
|
|
||||||
|
private final SimpMessagingTemplate messagingTemplate;
|
||||||
|
|
||||||
|
public ClimateModuleWebSocketPublisher(
|
||||||
|
SimpMessagingTemplate messagingTemplate
|
||||||
|
) {
|
||||||
|
this.messagingTemplate = messagingTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publishLatest(ClimateModuleResponse response) {
|
||||||
|
messagingTemplate.convertAndSend(DESTINATION, response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -45,6 +45,7 @@ public class MeteoModuleService {
|
|||||||
private ModuleSensorResponse toResponse(TelemetrySnapshot snapshot) {
|
private ModuleSensorResponse toResponse(TelemetrySnapshot snapshot) {
|
||||||
return new ModuleSensorResponse(
|
return new ModuleSensorResponse(
|
||||||
snapshot.sensorId(),
|
snapshot.sensorId(),
|
||||||
|
snapshot.key(),
|
||||||
snapshot.name(),
|
snapshot.name(),
|
||||||
buildKey(snapshot.name()),
|
buildKey(snapshot.name()),
|
||||||
snapshot.value(),
|
snapshot.value(),
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import java.time.Instant;
|
|||||||
|
|
||||||
public record ModuleSensorResponse(
|
public record ModuleSensorResponse(
|
||||||
Integer sensorId,
|
Integer sensorId,
|
||||||
String name,
|
|
||||||
String key,
|
String key,
|
||||||
|
String name,
|
||||||
|
String category,
|
||||||
Object value,
|
Object value,
|
||||||
String unit,
|
String unit,
|
||||||
Integer modbusAddress,
|
Integer modbusAddress,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.litoralregas.backend.sensor;
|
package com.litoralregas.backend.sensor;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -11,10 +12,16 @@ public class SensorDefinition {
|
|||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false)
|
||||||
|
private String key;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Column(name = "modbus_address", nullable = false)
|
@Column(nullable = false)
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
@Column(name = "modbus_address")
|
||||||
private Integer modbusAddress;
|
private Integer modbusAddress;
|
||||||
|
|
||||||
@Column(name = "bit_offset")
|
@Column(name = "bit_offset")
|
||||||
@@ -29,9 +36,6 @@ public class SensorDefinition {
|
|||||||
@Column(name = "decimal_places", nullable = false)
|
@Column(name = "decimal_places", nullable = false)
|
||||||
private Integer decimalPlaces;
|
private Integer decimalPlaces;
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String category;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@Column(name = "source_type", nullable = false)
|
@Column(name = "source_type", nullable = false)
|
||||||
private SensorSourceType sourceType;
|
private SensorSourceType sourceType;
|
||||||
@@ -49,24 +53,26 @@ public class SensorDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SensorDefinition(
|
public SensorDefinition(
|
||||||
|
String key,
|
||||||
String name,
|
String name,
|
||||||
|
String category,
|
||||||
Integer modbusAddress,
|
Integer modbusAddress,
|
||||||
Integer bitOffset,
|
Integer bitOffset,
|
||||||
SensorValueType valueType,
|
SensorValueType valueType,
|
||||||
String unit,
|
String unit,
|
||||||
Integer decimalPlaces,
|
Integer decimalPlaces,
|
||||||
String category,
|
|
||||||
SensorSourceType sourceType,
|
SensorSourceType sourceType,
|
||||||
Integer pollingIntervalSeconds,
|
Integer pollingIntervalSeconds,
|
||||||
Boolean enabled
|
Boolean enabled
|
||||||
) {
|
) {
|
||||||
|
this.key = key;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.category = category;
|
||||||
this.modbusAddress = modbusAddress;
|
this.modbusAddress = modbusAddress;
|
||||||
this.bitOffset = bitOffset;
|
this.bitOffset = bitOffset;
|
||||||
this.valueType = valueType;
|
this.valueType = valueType;
|
||||||
this.unit = unit;
|
this.unit = unit;
|
||||||
this.decimalPlaces = decimalPlaces;
|
this.decimalPlaces = decimalPlaces;
|
||||||
this.category = category;
|
|
||||||
this.sourceType = sourceType;
|
this.sourceType = sourceType;
|
||||||
this.pollingIntervalSeconds = pollingIntervalSeconds;
|
this.pollingIntervalSeconds = pollingIntervalSeconds;
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
@@ -77,6 +83,14 @@ public class SensorDefinition {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -85,6 +99,14 @@ public class SensorDefinition {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategory(String category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getModbusAddress() {
|
public Integer getModbusAddress() {
|
||||||
return modbusAddress;
|
return modbusAddress;
|
||||||
}
|
}
|
||||||
@@ -125,14 +147,6 @@ public class SensorDefinition {
|
|||||||
this.decimalPlaces = decimalPlaces;
|
this.decimalPlaces = decimalPlaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCategory() {
|
|
||||||
return category;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCategory(String category) {
|
|
||||||
this.category = category;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SensorSourceType getSourceType() {
|
public SensorSourceType getSourceType() {
|
||||||
return sourceType;
|
return sourceType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.litoralregas.backend.sensor.dto;
|
|
||||||
|
|
||||||
import com.litoralregas.backend.sensor.SensorSourceType;
|
|
||||||
import com.litoralregas.backend.sensor.SensorValueType;
|
|
||||||
|
|
||||||
public record SensorDefinitionImportRow(
|
|
||||||
String name,
|
|
||||||
Integer modbusAddress,
|
|
||||||
Integer bitOffset,
|
|
||||||
SensorValueType valueType,
|
|
||||||
String unit,
|
|
||||||
Integer decimalPlaces,
|
|
||||||
String category,
|
|
||||||
SensorSourceType sourceType
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.litoralregas.backend.sensor.importer;
|
||||||
|
|
||||||
|
public record ModbusConfig(
|
||||||
|
Integer address,
|
||||||
|
Integer bitOffset
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.litoralregas.backend.sensor.importer;
|
||||||
|
|
||||||
|
public record SensorDefinitionConfig(
|
||||||
|
String key,
|
||||||
|
String name,
|
||||||
|
String category,
|
||||||
|
ModbusConfig modbus,
|
||||||
|
String valueType,
|
||||||
|
String unit,
|
||||||
|
Integer decimalPlaces,
|
||||||
|
Integer pollingIntervalSeconds,
|
||||||
|
Boolean enabled
|
||||||
|
) {
|
||||||
|
}
|
||||||
+1
-1
@@ -15,6 +15,6 @@ public class SensorDefinitionImportController {
|
|||||||
|
|
||||||
@PostMapping("/api/sensor-definition-import/run")
|
@PostMapping("/api/sensor-definition-import/run")
|
||||||
public SensorDefinitionImportResult runImport() {
|
public SensorDefinitionImportResult runImport() {
|
||||||
return importService.importSensorMap();
|
return importService.importSensorDefinitions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+51
-60
@@ -1,113 +1,104 @@
|
|||||||
package com.litoralregas.backend.sensor.importer;
|
package com.litoralregas.backend.sensor.importer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.litoralregas.backend.sensor.SensorDefinition;
|
import com.litoralregas.backend.sensor.SensorDefinition;
|
||||||
import com.litoralregas.backend.sensor.SensorDefinitionRepository;
|
import com.litoralregas.backend.sensor.SensorDefinitionRepository;
|
||||||
|
import com.litoralregas.backend.sensor.SensorSourceType;
|
||||||
|
import com.litoralregas.backend.sensor.SensorValueType;
|
||||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionImportResult;
|
import com.litoralregas.backend.sensor.dto.SensorDefinitionImportResult;
|
||||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionImportRow;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class SensorDefinitionImportService {
|
public class SensorDefinitionImportService {
|
||||||
|
|
||||||
private static final String SENSOR_MAP_PATH = "config/sensor-map.txt";
|
private static final String SENSOR_DEFINITIONS_PATH =
|
||||||
private static final int DEFAULT_POLLING_INTERVAL_SECONDS = 2;
|
"config/sensor-definitions.json";
|
||||||
|
|
||||||
private final SensorDefinitionMapParser parser;
|
|
||||||
private final SensorDefinitionRepository repository;
|
private final SensorDefinitionRepository repository;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
public SensorDefinitionImportService(
|
public SensorDefinitionImportService(
|
||||||
SensorDefinitionMapParser parser,
|
SensorDefinitionRepository repository,
|
||||||
SensorDefinitionRepository repository
|
ObjectMapper objectMapper
|
||||||
) {
|
) {
|
||||||
this.parser = parser;
|
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public SensorDefinitionImportResult importSensorMap() {
|
public SensorDefinitionImportResult importSensorDefinitions() {
|
||||||
List<String> lines = readSensorMapLines();
|
|
||||||
|
SensorDefinitionsFile file = readDefinitionsFile();
|
||||||
|
|
||||||
int imported = 0;
|
int imported = 0;
|
||||||
int skippedExisting = 0;
|
int skippedExisting = 0;
|
||||||
int skippedBlank = 0;
|
|
||||||
|
|
||||||
for (String line : lines) {
|
for (SensorDefinitionConfig config : file.sensors()) {
|
||||||
if (line == null || line.isBlank()) {
|
|
||||||
skippedBlank++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
SensorDefinitionImportRow row = parser.parseLine(line)
|
if (repository.findByName(config.name()).isPresent()) {
|
||||||
.orElseThrow(() -> new IllegalArgumentException(
|
|
||||||
"Invalid sensor row: " + line
|
|
||||||
));
|
|
||||||
|
|
||||||
List<SensorDefinition> existingSensors = repository.findAllByHardwareAddress(
|
|
||||||
row.modbusAddress(),
|
|
||||||
row.bitOffset()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!existingSensors.isEmpty()) {
|
|
||||||
for (SensorDefinition sensorDefinition : existingSensors) {
|
|
||||||
sensorDefinition.setValueType(row.valueType());
|
|
||||||
sensorDefinition.setUnit(row.unit());
|
|
||||||
sensorDefinition.setDecimalPlaces(row.decimalPlaces());
|
|
||||||
sensorDefinition.setCategory(row.category());
|
|
||||||
sensorDefinition.setSourceType(row.sourceType());
|
|
||||||
}
|
|
||||||
|
|
||||||
skippedExisting += existingSensors.size();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repository.findByName(row.name()).isPresent()) {
|
|
||||||
skippedExisting++;
|
skippedExisting++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SensorDefinition sensorDefinition = new SensorDefinition(
|
SensorDefinition sensorDefinition = new SensorDefinition(
|
||||||
row.name(),
|
config.key(),
|
||||||
row.modbusAddress(),
|
config.name(),
|
||||||
row.bitOffset(),
|
config.category(),
|
||||||
row.valueType(),
|
config.modbus().address(),
|
||||||
row.unit(),
|
config.modbus().bitOffset(),
|
||||||
row.decimalPlaces(),
|
SensorValueType.valueOf(config.valueType()),
|
||||||
row.category(),
|
config.unit(),
|
||||||
row.sourceType(),
|
config.decimalPlaces(),
|
||||||
DEFAULT_POLLING_INTERVAL_SECONDS,
|
SensorSourceType.MODBUS,
|
||||||
true
|
config.pollingIntervalSeconds(),
|
||||||
|
config.enabled()
|
||||||
);
|
);
|
||||||
|
|
||||||
repository.save(sensorDefinition);
|
repository.save(sensorDefinition);
|
||||||
|
|
||||||
imported++;
|
imported++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SensorDefinitionImportResult(
|
return new SensorDefinitionImportResult(
|
||||||
lines.size(),
|
file.sensorCount(),
|
||||||
imported,
|
imported,
|
||||||
skippedExisting,
|
skippedExisting,
|
||||||
skippedBlank
|
0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> readSensorMapLines() {
|
private SensorDefinitionsFile readDefinitionsFile() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ClassPathResource resource = new ClassPathResource(SENSOR_MAP_PATH);
|
|
||||||
|
ClassPathResource resource =
|
||||||
|
new ClassPathResource(SENSOR_DEFINITIONS_PATH);
|
||||||
|
|
||||||
if (!resource.exists()) {
|
if (!resource.exists()) {
|
||||||
throw new IllegalStateException("Sensor map file not found: " + SENSOR_MAP_PATH);
|
throw new IllegalStateException(
|
||||||
|
"Sensor definitions file not found: "
|
||||||
|
+ SENSOR_DEFINITIONS_PATH
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream inputStream = resource.getInputStream()) {
|
||||||
|
|
||||||
|
return objectMapper.readValue(
|
||||||
|
inputStream,
|
||||||
|
SensorDefinitionsFile.class
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resource.getContentAsString(StandardCharsets.UTF_8)
|
|
||||||
.lines()
|
|
||||||
.toList();
|
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new IllegalStateException("Failed to read sensor map file.", exception);
|
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Failed to load sensor definitions.",
|
||||||
|
exception
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package com.litoralregas.backend.sensor.importer;
|
|
||||||
|
|
||||||
import com.litoralregas.backend.sensor.SensorSourceType;
|
|
||||||
import com.litoralregas.backend.sensor.SensorValueType;
|
|
||||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionImportRow;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class SensorDefinitionMapParser {
|
|
||||||
|
|
||||||
public Optional<SensorDefinitionImportRow> parseLine(String line) {
|
|
||||||
if (line == null || line.isBlank()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] parts = line.split("\\*");
|
|
||||||
|
|
||||||
if (parts.length != 5) {
|
|
||||||
throw new IllegalArgumentException("Invalid sensor map line: " + line);
|
|
||||||
}
|
|
||||||
|
|
||||||
String name = parts[0].trim();
|
|
||||||
String addressPart = parts[1].trim();
|
|
||||||
Integer decimalPlaces = Integer.parseInt(parts[2].trim());
|
|
||||||
String unit = normalizeUnit(parts[3].trim());
|
|
||||||
String category = mapCategory(parts[4].trim());
|
|
||||||
|
|
||||||
ParsedAddress parsedAddress = parseAddress(addressPart);
|
|
||||||
|
|
||||||
SensorSourceType sourceType = parsedAddress.modbusAddress() < 0
|
|
||||||
? SensorSourceType.CALCULATED
|
|
||||||
: SensorSourceType.MODBUS;
|
|
||||||
|
|
||||||
SensorValueType valueType = parsedAddress.bitOffset() != null
|
|
||||||
? SensorValueType.BOOLEAN
|
|
||||||
: decimalPlaces > 0 ? SensorValueType.DECIMAL : SensorValueType.INTEGER;
|
|
||||||
|
|
||||||
return Optional.of(new SensorDefinitionImportRow(
|
|
||||||
name,
|
|
||||||
parsedAddress.modbusAddress(),
|
|
||||||
parsedAddress.bitOffset(),
|
|
||||||
valueType,
|
|
||||||
unit,
|
|
||||||
decimalPlaces,
|
|
||||||
category,
|
|
||||||
sourceType
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ParsedAddress parseAddress(String addressPart) {
|
|
||||||
if (addressPart.contains(",")) {
|
|
||||||
String[] addressParts = addressPart.split(",");
|
|
||||||
|
|
||||||
if (addressParts.length != 2) {
|
|
||||||
throw new IllegalArgumentException("Invalid bit address: " + addressPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ParsedAddress(
|
|
||||||
Integer.parseInt(addressParts[0].trim()),
|
|
||||||
Integer.parseInt(addressParts[1].trim())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ParsedAddress(
|
|
||||||
Integer.parseInt(addressPart),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeUnit(String unit) {
|
|
||||||
if (unit == null || unit.isBlank() || unit.equalsIgnoreCase("SU")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String mapCategory(String categoryCode) {
|
|
||||||
return switch (categoryCode.toLowerCase()) {
|
|
||||||
case "c" -> "CLIMATE";
|
|
||||||
case "r" -> "IRRIGATION";
|
|
||||||
case "i" -> "LIGHTING";
|
|
||||||
case "h" -> "HYDRO";
|
|
||||||
case "a" -> "AEROPONICS";
|
|
||||||
default -> "UNKNOWN";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private record ParsedAddress(
|
|
||||||
Integer modbusAddress,
|
|
||||||
Integer bitOffset
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package com.litoralregas.backend.sensor.importer;
|
||||||
|
|
||||||
|
import com.litoralregas.backend.sensor.SensorDefinitionRepository;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SensorDefinitionStartupImporter {
|
||||||
|
|
||||||
|
private final SensorDefinitionRepository repository;
|
||||||
|
private final SensorDefinitionImportService importService;
|
||||||
|
|
||||||
|
public SensorDefinitionStartupImporter(
|
||||||
|
SensorDefinitionRepository repository,
|
||||||
|
SensorDefinitionImportService importService
|
||||||
|
) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.importService = importService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void importSensorsIfMissing() {
|
||||||
|
if (repository.count() > 0) {
|
||||||
|
System.out.println("Sensor definitions already imported. Skipping startup import.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("No sensor definitions found. Importing sensor-definitions.json...");
|
||||||
|
|
||||||
|
importService.importSensorDefinitions();
|
||||||
|
|
||||||
|
System.out.println("Sensor definitions imported successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.litoralregas.backend.sensor.importer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record SensorDefinitionsFile(
|
||||||
|
Integer version,
|
||||||
|
Integer sensorCount,
|
||||||
|
List<SensorDefinitionConfig> sensors
|
||||||
|
) {
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,27 @@
|
|||||||
CREATE TABLE sensor_definition (
|
CREATE TABLE sensor_definition (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
||||||
|
key VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
modbus_address INTEGER NOT NULL,
|
|
||||||
bit_offset INTEGER,
|
|
||||||
value_type VARCHAR(50) NOT NULL,
|
|
||||||
unit VARCHAR(50),
|
|
||||||
decimal_places INTEGER NOT NULL DEFAULT 0,
|
|
||||||
category VARCHAR(100) NOT NULL,
|
category VARCHAR(100) NOT NULL,
|
||||||
|
|
||||||
|
modbus_address INTEGER,
|
||||||
|
|
||||||
|
bit_offset INTEGER,
|
||||||
|
|
||||||
|
value_type VARCHAR(50) NOT NULL,
|
||||||
|
|
||||||
|
unit VARCHAR(50),
|
||||||
|
|
||||||
|
decimal_places INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
source_type VARCHAR(50) NOT NULL,
|
source_type VARCHAR(50) NOT NULL,
|
||||||
|
|
||||||
polling_interval_seconds INTEGER NOT NULL DEFAULT 1,
|
polling_interval_seconds INTEGER NOT NULL DEFAULT 1,
|
||||||
|
|
||||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
|
||||||
created_at TIMESTAMP NOT NULL
|
created_at TIMESTAMP NOT NULL
|
||||||
);
|
);
|
||||||
+2
@@ -2,6 +2,7 @@ CREATE TABLE historian_sample (
|
|||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
||||||
sampled_at TIMESTAMP NOT NULL,
|
sampled_at TIMESTAMP NOT NULL,
|
||||||
|
|
||||||
key_name VARCHAR(160) NOT NULL,
|
key_name VARCHAR(160) NOT NULL,
|
||||||
|
|
||||||
numeric_value REAL,
|
numeric_value REAL,
|
||||||
@@ -9,6 +10,7 @@ CREATE TABLE historian_sample (
|
|||||||
text_value VARCHAR(255),
|
text_value VARCHAR(255),
|
||||||
|
|
||||||
unit VARCHAR(32),
|
unit VARCHAR(32),
|
||||||
|
|
||||||
source VARCHAR(50) NOT NULL,
|
source VARCHAR(50) NOT NULL,
|
||||||
|
|
||||||
created_at TIMESTAMP NOT NULL
|
created_at TIMESTAMP NOT NULL
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
INSERT INTO sensor_definition (
|
|
||||||
name,
|
|
||||||
modbus_address,
|
|
||||||
bit_offset,
|
|
||||||
value_type,
|
|
||||||
unit,
|
|
||||||
decimal_places,
|
|
||||||
category,
|
|
||||||
source_type,
|
|
||||||
polling_interval_seconds,
|
|
||||||
enabled,
|
|
||||||
created_at
|
|
||||||
) VALUES
|
|
||||||
(
|
|
||||||
'Greenhouse Temperature',
|
|
||||||
100,
|
|
||||||
NULL,
|
|
||||||
'DECIMAL',
|
|
||||||
'ºC',
|
|
||||||
1,
|
|
||||||
'CLIMATE',
|
|
||||||
'MODBUS',
|
|
||||||
2,
|
|
||||||
TRUE,
|
|
||||||
CURRENT_TIMESTAMP
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'Greenhouse Humidity',
|
|
||||||
101,
|
|
||||||
NULL,
|
|
||||||
'DECIMAL',
|
|
||||||
'%',
|
|
||||||
1,
|
|
||||||
'CLIMATE',
|
|
||||||
'MODBUS',
|
|
||||||
2,
|
|
||||||
TRUE,
|
|
||||||
CURRENT_TIMESTAMP
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'Irrigation Pump Running',
|
|
||||||
200,
|
|
||||||
0,
|
|
||||||
'BOOLEAN',
|
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
'IRRIGATION',
|
|
||||||
'MODBUS',
|
|
||||||
1,
|
|
||||||
TRUE,
|
|
||||||
CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
CREATE UNIQUE INDEX ux_sensor_definition_name
|
|
||||||
ON sensor_definition(name);
|
|
||||||
Reference in New Issue
Block a user