diff --git a/src/main/java/com/litoralregas/backend/acquisition/SensorTelemetryReader.java b/src/main/java/com/litoralregas/backend/acquisition/SensorTelemetryReader.java new file mode 100644 index 0000000..c213ff8 --- /dev/null +++ b/src/main/java/com/litoralregas/backend/acquisition/SensorTelemetryReader.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/backend/acquisition/TelemetryCache.java b/src/main/java/com/litoralregas/backend/acquisition/TelemetryCache.java new file mode 100644 index 0000000..02d21f4 --- /dev/null +++ b/src/main/java/com/litoralregas/backend/acquisition/TelemetryCache.java @@ -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 snapshotsBySensorId = new ConcurrentHashMap<>(); //Thread-safe ;) + + public void put(TelemetrySnapshot snapshot) { + snapshotsBySensorId.put(snapshot.sensorId(), snapshot); + } + + public Optional get(Integer sensorId) { + return Optional.ofNullable(snapshotsBySensorId.get(sensorId)); + } + + public Collection getAll() { + return snapshotsBySensorId.values() + .stream() + .sorted(Comparator.comparing(TelemetrySnapshot::sensorId)) + .toList(); + } + + public void clear() { + snapshotsBySensorId.clear(); + } + + public int size() { + return snapshotsBySensorId.size(); + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/backend/acquisition/TelemetryController.java b/src/main/java/com/litoralregas/backend/acquisition/TelemetryController.java new file mode 100644 index 0000000..25dd10c --- /dev/null +++ b/src/main/java/com/litoralregas/backend/acquisition/TelemetryController.java @@ -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); + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/backend/acquisition/TelemetrySnapshot.java b/src/main/java/com/litoralregas/backend/acquisition/TelemetrySnapshot.java new file mode 100644 index 0000000..e322c58 --- /dev/null +++ b/src/main/java/com/litoralregas/backend/acquisition/TelemetrySnapshot.java @@ -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 +) { +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/backend/sensor/importer/SensorDefinitionImportTestController.java b/src/main/java/com/litoralregas/backend/sensor/importer/SensorDefinitionImportTestController.java deleted file mode 100644 index 0c26c25..0000000 --- a/src/main/java/com/litoralregas/backend/sensor/importer/SensorDefinitionImportTestController.java +++ /dev/null @@ -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.")); - } -} \ No newline at end of file