Import legacy sensor map into sensor definitions
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
package com.litoralregas.backend.sensor.dto;
|
||||
|
||||
public record SensorDefinitionImportResult(
|
||||
int totalLines,
|
||||
int imported,
|
||||
int skippedExisting,
|
||||
int skippedBlank
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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
|
||||
) {
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.litoralregas.backend.sensor.importer;
|
||||
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionImportResult;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class SensorDefinitionImportController {
|
||||
|
||||
private final SensorDefinitionImportService importService;
|
||||
|
||||
public SensorDefinitionImportController(SensorDefinitionImportService importService) {
|
||||
this.importService = importService;
|
||||
}
|
||||
|
||||
@PostMapping("/api/sensor-definition-import/run")
|
||||
public SensorDefinitionImportResult runImport() {
|
||||
return importService.importSensorMap();
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package com.litoralregas.backend.sensor.importer;
|
||||
|
||||
import com.litoralregas.backend.sensor.SensorDefinition;
|
||||
import com.litoralregas.backend.sensor.SensorDefinitionRepository;
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionImportResult;
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionImportRow;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SensorDefinitionImportService {
|
||||
|
||||
private static final String SENSOR_MAP_PATH = "config/sensor-map.txt";
|
||||
private static final int DEFAULT_POLLING_INTERVAL_SECONDS = 2;
|
||||
|
||||
private final SensorDefinitionMapParser parser;
|
||||
private final SensorDefinitionRepository repository;
|
||||
|
||||
public SensorDefinitionImportService(
|
||||
SensorDefinitionMapParser parser,
|
||||
SensorDefinitionRepository repository
|
||||
) {
|
||||
this.parser = parser;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public SensorDefinitionImportResult importSensorMap() {
|
||||
List<String> lines = readSensorMapLines();
|
||||
|
||||
int imported = 0;
|
||||
int skippedExisting = 0;
|
||||
int skippedBlank = 0;
|
||||
|
||||
for (String line : lines) {
|
||||
if (line == null || line.isBlank()) {
|
||||
skippedBlank++;
|
||||
continue;
|
||||
}
|
||||
|
||||
SensorDefinitionImportRow row = parser.parseLine(line)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Invalid empty sensor row."));
|
||||
|
||||
if (repository.existsByName(row.name())) {
|
||||
skippedExisting++;
|
||||
continue;
|
||||
}
|
||||
|
||||
SensorDefinition sensorDefinition = new SensorDefinition(
|
||||
row.name(),
|
||||
row.modbusAddress(),
|
||||
row.bitOffset(),
|
||||
row.valueType(),
|
||||
row.unit(),
|
||||
row.decimalPlaces(),
|
||||
row.category(),
|
||||
row.sourceType(),
|
||||
DEFAULT_POLLING_INTERVAL_SECONDS,
|
||||
true
|
||||
);
|
||||
|
||||
repository.save(sensorDefinition);
|
||||
imported++;
|
||||
}
|
||||
|
||||
return new SensorDefinitionImportResult(
|
||||
lines.size(),
|
||||
imported,
|
||||
skippedExisting,
|
||||
skippedBlank
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> readSensorMapLines() {
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource(SENSOR_MAP_PATH);
|
||||
|
||||
if (!resource.exists()) {
|
||||
throw new IllegalStateException("Sensor map file not found: " + SENSOR_MAP_PATH);
|
||||
}
|
||||
|
||||
return resource.getContentAsString(StandardCharsets.UTF_8)
|
||||
.lines()
|
||||
.toList();
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("Failed to read sensor map file.", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.litoralregas.backend.sensor.importer;
|
||||
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionImportRow;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class SensorDefinitionImportTestController {
|
||||
|
||||
private final SensorDefinitionMapParser parser;
|
||||
|
||||
public SensorDefinitionImportTestController(SensorDefinitionMapParser parser) {
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@GetMapping("/api/sensor-definition-import/parse-line")
|
||||
public SensorDefinitionImportRow parseLine(@RequestParam String line) {
|
||||
return parser.parseLine(line)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Line is empty."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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
|
||||
) {
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user