Add manual telemetry reading and cache

This commit is contained in:
litoral05
2026-05-19 16:29:32 +01:00
parent 4c4025f37f
commit fa32550aaa
5 changed files with 155 additions and 22 deletions
@@ -0,0 +1,82 @@
package com.litoralregas.backend.acquisition;
import com.litoralregas.backend.modbus.LrModbusClient;
import com.litoralregas.backend.modbus.ModbusReadResult;
import com.litoralregas.backend.modbus.ModbusUnit;
import com.litoralregas.backend.sensor.SensorDefinition;
import com.litoralregas.backend.sensor.SensorDefinitionRepository;
import com.litoralregas.backend.sensor.SensorSourceType;
import com.litoralregas.backend.sensor.SensorValueType;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.stereotype.Service;
import java.time.Instant;
@Service
public class SensorTelemetryReader {
private final SensorDefinitionRepository sensorDefinitionRepository;
private final LrModbusClient modbusClient;
private final TelemetryCache telemetryCache;
public SensorTelemetryReader(
SensorDefinitionRepository sensorDefinitionRepository,
LrModbusClient modbusClient,
TelemetryCache telemetryCache
) {
this.sensorDefinitionRepository = sensorDefinitionRepository;
this.modbusClient = modbusClient;
this.telemetryCache = telemetryCache;
}
public TelemetrySnapshot readSensor(Integer sensorId) {
SensorDefinition sensorDefinition = sensorDefinitionRepository.findById(sensorId)
.orElseThrow(() -> new EntityNotFoundException("Sensor definition not found: " + sensorId));
if (sensorDefinition.getSourceType() != SensorSourceType.MODBUS) {
throw new IllegalArgumentException("Only MODBUS sensors can be read directly.");
}
ModbusReadResult result = modbusClient.readInputRegisters(
ModbusUnit.PC,
sensorDefinition.getModbusAddress(),
1
);
Integer rawValue = result.values().getFirst();
Object value = convertValue(sensorDefinition, rawValue);
TelemetrySnapshot snapshot = new TelemetrySnapshot(
sensorDefinition.getId(),
sensorDefinition.getName(),
sensorDefinition.getModbusAddress(),
sensorDefinition.getBitOffset(),
rawValue,
value,
sensorDefinition.getUnit(),
Instant.now()
);
telemetryCache.put(snapshot);
return snapshot;
}
private Object convertValue(SensorDefinition sensorDefinition, Integer rawValue) {
if (sensorDefinition.getValueType() == SensorValueType.BOOLEAN) {
Integer bitOffset = sensorDefinition.getBitOffset();
if (bitOffset == null) {
throw new IllegalStateException("BOOLEAN sensor requires bitOffset.");
}
return ((rawValue >> bitOffset) & 1) == 1;
}
if (sensorDefinition.getValueType() == SensorValueType.DECIMAL) {
return rawValue / Math.pow(10, sensorDefinition.getDecimalPlaces());
}
return rawValue;
}
}
@@ -0,0 +1,38 @@
package com.litoralregas.backend.acquisition;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class TelemetryCache {
private final Map<Integer, TelemetrySnapshot> snapshotsBySensorId = new ConcurrentHashMap<>(); //Thread-safe ;)
public void put(TelemetrySnapshot snapshot) {
snapshotsBySensorId.put(snapshot.sensorId(), snapshot);
}
public Optional<TelemetrySnapshot> get(Integer sensorId) {
return Optional.ofNullable(snapshotsBySensorId.get(sensorId));
}
public Collection<TelemetrySnapshot> getAll() {
return snapshotsBySensorId.values()
.stream()
.sorted(Comparator.comparing(TelemetrySnapshot::sensorId))
.toList();
}
public void clear() {
snapshotsBySensorId.clear();
}
public int size() {
return snapshotsBySensorId.size();
}
}
@@ -0,0 +1,20 @@
package com.litoralregas.backend.acquisition;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TelemetryController {
private final SensorTelemetryReader sensorTelemetryReader;
public TelemetryController(SensorTelemetryReader sensorTelemetryReader) {
this.sensorTelemetryReader = sensorTelemetryReader;
}
@GetMapping("/api/telemetry/sensors/{sensorId}")
public TelemetrySnapshot readSensor(@PathVariable Integer sensorId) {
return sensorTelemetryReader.readSensor(sensorId);
}
}
@@ -0,0 +1,15 @@
package com.litoralregas.backend.acquisition;
import java.time.Instant;
public record TelemetrySnapshot(
Integer sensorId,
String name,
Integer modbusAddress,
Integer bitOffset,
Integer rawValue,
Object value,
String unit,
Instant timestamp
) {
}
@@ -1,22 +0,0 @@
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."));
}
}