Initial Spring Boot backend with Modbus TCP infrastructure
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.litoralregas.backend;
|
||||
|
||||
import com.litoralregas.backend.modbus.ModbusConnectionProperties;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(ModbusConnectionProperties.class)
|
||||
public class BackendApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BackendApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.litoralregas.backend.common;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
public class HealthController {
|
||||
|
||||
@GetMapping("/api/health")
|
||||
public Map<String, String> health() {
|
||||
return Map.of(
|
||||
"status", "UP",
|
||||
"service", "backend"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.litoralregas.backend.common.api;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
public record ApiErrorResponse(
|
||||
Instant timestamp,
|
||||
int status,
|
||||
String error,
|
||||
String message,
|
||||
String path,
|
||||
Map<String, String> fieldErrors
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.litoralregas.backend.common.api;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiErrorResponse handleIllegalArgument(
|
||||
IllegalArgumentException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
return new ApiErrorResponse(
|
||||
Instant.now(),
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
HttpStatus.BAD_REQUEST.getReasonPhrase(),
|
||||
exception.getMessage(),
|
||||
request.getRequestURI(),
|
||||
Map.of()
|
||||
);
|
||||
}
|
||||
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public ApiErrorResponse handleEntityNotFound(
|
||||
EntityNotFoundException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
return new ApiErrorResponse(
|
||||
Instant.now(),
|
||||
HttpStatus.NOT_FOUND.value(),
|
||||
HttpStatus.NOT_FOUND.getReasonPhrase(),
|
||||
exception.getMessage(),
|
||||
request.getRequestURI(),
|
||||
Map.of()
|
||||
);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiErrorResponse handleValidation(
|
||||
MethodArgumentNotValidException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
Map<String, String> fieldErrors = new LinkedHashMap<>();
|
||||
|
||||
exception.getBindingResult().getFieldErrors().forEach(error ->
|
||||
fieldErrors.put(error.getField(), error.getDefaultMessage())
|
||||
);
|
||||
|
||||
return new ApiErrorResponse(
|
||||
Instant.now(),
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
HttpStatus.BAD_REQUEST.getReasonPhrase(),
|
||||
"Request validation failed.",
|
||||
request.getRequestURI(),
|
||||
fieldErrors
|
||||
);
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiErrorResponse handleUnreadableJson(
|
||||
HttpMessageNotReadableException exception,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
return new ApiErrorResponse(
|
||||
Instant.now(),
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
HttpStatus.BAD_REQUEST.getReasonPhrase(),
|
||||
"Malformed JSON request body.",
|
||||
request.getRequestURI(),
|
||||
Map.of()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.litoralregas.backend.modbus;
|
||||
|
||||
import de.re.easymodbus.modbusclient.ModbusClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Component
|
||||
public class EasyModbusLrClient implements LrModbusClient {
|
||||
|
||||
private final ModbusConnectionProperties properties;
|
||||
|
||||
public EasyModbusLrClient(ModbusConnectionProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModbusReadResult readInputRegisters(ModbusUnit unit, int startingAddress, int quantity) {
|
||||
validateRegisterRead(startingAddress, quantity, 125);
|
||||
|
||||
int[] values = executeWithRetry(
|
||||
unit,
|
||||
client -> client.ReadInputRegisters(startingAddress, quantity, 1, 0)
|
||||
);
|
||||
|
||||
return new ModbusReadResult(
|
||||
unit,
|
||||
ModbusRegisterType.INPUT_REGISTER,
|
||||
startingAddress,
|
||||
quantity,
|
||||
Arrays.stream(values).boxed().toList(),
|
||||
Instant.now()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModbusReadResult readHoldingRegisters(ModbusUnit unit, int startingAddress, int quantity) {
|
||||
validateRegisterRead(startingAddress, quantity, 125);
|
||||
|
||||
int[] values = executeWithRetry(
|
||||
unit,
|
||||
client -> client.ReadHoldingRegisters(startingAddress, quantity, 1, 0)
|
||||
);
|
||||
|
||||
return new ModbusReadResult(
|
||||
unit,
|
||||
ModbusRegisterType.HOLDING_REGISTER,
|
||||
startingAddress,
|
||||
quantity,
|
||||
Arrays.stream(values).boxed().toList(),
|
||||
Instant.now()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] readCoils(ModbusUnit unit, int startingAddress, int quantity) {
|
||||
validateRegisterRead(startingAddress, quantity, 2000);
|
||||
|
||||
return executeWithRetry(
|
||||
unit,
|
||||
client -> client.ReadCoils(startingAddress, quantity, 1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSingleCoil(ModbusUnit unit, int startingAddress, boolean value) {
|
||||
validateAddress(startingAddress);
|
||||
|
||||
executeWithRetry(
|
||||
unit,
|
||||
client -> {
|
||||
client.WriteSingleCoil(startingAddress, value);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private <T> T executeWithRetry(ModbusUnit unit, ModbusOperation<T> operation) {
|
||||
RuntimeException lastException = null;
|
||||
|
||||
for (int attempt = 1; attempt <= properties.getMaxAttempts(); attempt++) {
|
||||
ModbusClient client = new ModbusClient(
|
||||
properties.getHost(),
|
||||
properties.getPort(),
|
||||
unit.getUnitId()
|
||||
);
|
||||
|
||||
try {
|
||||
client.setConnectionTimeout(properties.getTimeoutMillis());
|
||||
client.Connect();
|
||||
|
||||
return operation.execute(client);
|
||||
} catch (Exception exception) {
|
||||
lastException = new ModbusException(
|
||||
"Modbus operation failed on attempt " + attempt + " for unit " + unit + ".",
|
||||
exception
|
||||
);
|
||||
|
||||
sleepBeforeRetry(attempt);
|
||||
} finally {
|
||||
try {
|
||||
client.Disconnect();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastException != null
|
||||
? lastException
|
||||
: new ModbusException("Modbus operation failed.");
|
||||
}
|
||||
|
||||
private void sleepBeforeRetry(int attempt) {
|
||||
if (attempt >= properties.getMaxAttempts()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(properties.getRetryDelayMillis());
|
||||
} catch (InterruptedException exception) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new ModbusException("Modbus retry interrupted.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRegisterRead(int startingAddress, int quantity, int maxQuantity) {
|
||||
validateAddress(startingAddress);
|
||||
|
||||
if (quantity < 1 || quantity > maxQuantity) {
|
||||
throw new IllegalArgumentException("Quantity must be between 1 and " + maxQuantity + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAddress(int startingAddress) {
|
||||
if (startingAddress < 0 || startingAddress > 65535) {
|
||||
throw new IllegalArgumentException("Starting address must be between 0 and 65535.");
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ModbusOperation<T> {
|
||||
T execute(ModbusClient client) throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.litoralregas.backend.modbus;
|
||||
|
||||
public interface LrModbusClient {
|
||||
|
||||
ModbusReadResult readInputRegisters(ModbusUnit unit, int startingAddress, int quantity);
|
||||
|
||||
ModbusReadResult readHoldingRegisters(ModbusUnit unit, int startingAddress, int quantity);
|
||||
|
||||
boolean[] readCoils(ModbusUnit unit, int startingAddress, int quantity);
|
||||
|
||||
void writeSingleCoil(ModbusUnit unit, int startingAddress, boolean value);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.litoralregas.backend.modbus;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = "litoralregas.modbus")
|
||||
public class ModbusConnectionProperties {
|
||||
|
||||
private String host = "127.0.0.1";
|
||||
private int port = 533;
|
||||
private int timeoutMillis = 500;
|
||||
private int maxAttempts = 3;
|
||||
private long retryDelayMillis = 1000;
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getTimeoutMillis() {
|
||||
return timeoutMillis;
|
||||
}
|
||||
|
||||
public void setTimeoutMillis(int timeoutMillis) {
|
||||
this.timeoutMillis = timeoutMillis;
|
||||
}
|
||||
|
||||
public int getMaxAttempts() {
|
||||
return maxAttempts;
|
||||
}
|
||||
|
||||
public void setMaxAttempts(int maxAttempts) {
|
||||
this.maxAttempts = maxAttempts;
|
||||
}
|
||||
|
||||
public long getRetryDelayMillis() {
|
||||
return retryDelayMillis;
|
||||
}
|
||||
|
||||
public void setRetryDelayMillis(long retryDelayMillis) {
|
||||
this.retryDelayMillis = retryDelayMillis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.litoralregas.backend.modbus;
|
||||
|
||||
public class ModbusException extends RuntimeException {
|
||||
|
||||
public ModbusException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ModbusException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.litoralregas.backend.modbus;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public record ModbusReadResult(
|
||||
ModbusUnit unit,
|
||||
ModbusRegisterType registerType,
|
||||
int startingAddress,
|
||||
int quantity,
|
||||
List<Integer> values,
|
||||
Instant timestamp
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.litoralregas.backend.modbus;
|
||||
|
||||
public enum ModbusRegisterType {
|
||||
COIL,
|
||||
DISCRETE_INPUT,
|
||||
HOLDING_REGISTER,
|
||||
INPUT_REGISTER
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.litoralregas.backend.modbus;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/modbus")
|
||||
public class ModbusTestController {
|
||||
|
||||
private final LrModbusClient modbusClient;
|
||||
|
||||
public ModbusTestController(LrModbusClient modbusClient) {
|
||||
this.modbusClient = modbusClient;
|
||||
}
|
||||
|
||||
@GetMapping("/input-registers")
|
||||
public ModbusReadResult readInputRegisters(
|
||||
@RequestParam(defaultValue = "PC") ModbusUnit unit,
|
||||
@RequestParam int address,
|
||||
@RequestParam(defaultValue = "1") int quantity
|
||||
) {
|
||||
return modbusClient.readInputRegisters(
|
||||
unit,
|
||||
address,
|
||||
quantity
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/holding-registers")
|
||||
public ModbusReadResult readHoldingRegisters(
|
||||
@RequestParam(defaultValue = "PC") ModbusUnit unit,
|
||||
@RequestParam int address,
|
||||
@RequestParam(defaultValue = "1") int quantity
|
||||
) {
|
||||
return modbusClient.readHoldingRegisters(
|
||||
unit,
|
||||
address,
|
||||
quantity
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/coils")
|
||||
public boolean[] readCoils(
|
||||
@RequestParam(defaultValue = "PC") ModbusUnit unit,
|
||||
@RequestParam int address,
|
||||
@RequestParam(defaultValue = "1") int quantity
|
||||
) {
|
||||
return modbusClient.readCoils(
|
||||
unit,
|
||||
address,
|
||||
quantity
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.litoralregas.backend.modbus;
|
||||
|
||||
public enum ModbusUnit {
|
||||
|
||||
GENERAL((byte) 99),
|
||||
PC((byte) 106);
|
||||
|
||||
private final byte unitId;
|
||||
|
||||
ModbusUnit(byte unitId) {
|
||||
this.unitId = unitId;
|
||||
}
|
||||
|
||||
public byte getUnitId() {
|
||||
return unitId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.litoralregas.backend.sensor;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
@Table(name = "sensor_definition")
|
||||
public class SensorDefinition {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "modbus_address", nullable = false)
|
||||
private Integer modbusAddress;
|
||||
|
||||
@Column(name = "bit_offset")
|
||||
private Integer bitOffset;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "value_type", nullable = false)
|
||||
private SensorValueType valueType;
|
||||
|
||||
private String unit;
|
||||
|
||||
@Column(name = "decimal_places", nullable = false)
|
||||
private Integer decimalPlaces;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String category;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "source_type", nullable = false)
|
||||
private SensorSourceType sourceType;
|
||||
|
||||
@Column(name = "polling_interval_seconds", nullable = false)
|
||||
private Integer pollingIntervalSeconds;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Boolean enabled;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
protected SensorDefinition() {
|
||||
}
|
||||
|
||||
public SensorDefinition(
|
||||
String name,
|
||||
Integer modbusAddress,
|
||||
Integer bitOffset,
|
||||
SensorValueType valueType,
|
||||
String unit,
|
||||
Integer decimalPlaces,
|
||||
String category,
|
||||
SensorSourceType sourceType,
|
||||
Integer pollingIntervalSeconds,
|
||||
Boolean enabled
|
||||
) {
|
||||
this.name = name;
|
||||
this.modbusAddress = modbusAddress;
|
||||
this.bitOffset = bitOffset;
|
||||
this.valueType = valueType;
|
||||
this.unit = unit;
|
||||
this.decimalPlaces = decimalPlaces;
|
||||
this.category = category;
|
||||
this.sourceType = sourceType;
|
||||
this.pollingIntervalSeconds = pollingIntervalSeconds;
|
||||
this.enabled = enabled;
|
||||
this.createdAt = Instant.now();
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getModbusAddress() {
|
||||
return modbusAddress;
|
||||
}
|
||||
|
||||
public void setModbusAddress(Integer modbusAddress) {
|
||||
this.modbusAddress = modbusAddress;
|
||||
}
|
||||
|
||||
public Integer getBitOffset() {
|
||||
return bitOffset;
|
||||
}
|
||||
|
||||
public void setBitOffset(Integer bitOffset) {
|
||||
this.bitOffset = bitOffset;
|
||||
}
|
||||
|
||||
public SensorValueType getValueType() {
|
||||
return valueType;
|
||||
}
|
||||
|
||||
public void setValueType(SensorValueType valueType) {
|
||||
this.valueType = valueType;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public Integer getDecimalPlaces() {
|
||||
return decimalPlaces;
|
||||
}
|
||||
|
||||
public void setDecimalPlaces(Integer decimalPlaces) {
|
||||
this.decimalPlaces = decimalPlaces;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public SensorSourceType getSourceType() {
|
||||
return sourceType;
|
||||
}
|
||||
|
||||
public void setSourceType(SensorSourceType sourceType) {
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
|
||||
public Integer getPollingIntervalSeconds() {
|
||||
return pollingIntervalSeconds;
|
||||
}
|
||||
|
||||
public void setPollingIntervalSeconds(Integer pollingIntervalSeconds) {
|
||||
this.pollingIntervalSeconds = pollingIntervalSeconds;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.litoralregas.backend.sensor;
|
||||
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionCreateRequest;
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionResponse;
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionUpdateRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/sensor-definitions")
|
||||
public class SensorDefinitionController {
|
||||
|
||||
private final SensorDefinitionService sensorDefinitionService;
|
||||
|
||||
public SensorDefinitionController(SensorDefinitionService sensorDefinitionService) {
|
||||
this.sensorDefinitionService = sensorDefinitionService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<SensorDefinitionResponse> findAll(
|
||||
@RequestParam(name = "enabledOnly", defaultValue = "false") boolean enabledOnly
|
||||
) {
|
||||
if (enabledOnly) {
|
||||
return sensorDefinitionService.findEnabled();
|
||||
}
|
||||
|
||||
return sensorDefinitionService.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public SensorDefinitionResponse findById(@PathVariable Integer id) {
|
||||
return sensorDefinitionService.findById(id);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public SensorDefinitionResponse create(@Valid @RequestBody SensorDefinitionCreateRequest request) {
|
||||
return sensorDefinitionService.create(request);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public SensorDefinitionResponse update(
|
||||
@PathVariable Integer id,
|
||||
@Valid @RequestBody SensorDefinitionUpdateRequest request
|
||||
) {
|
||||
return sensorDefinitionService.update(id, request);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void delete(@PathVariable Integer id) {
|
||||
sensorDefinitionService.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.litoralregas.backend.sensor;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SensorDefinitionRepository extends JpaRepository<SensorDefinition, Integer> {
|
||||
|
||||
boolean existsByName(String name);
|
||||
|
||||
List<SensorDefinition> findByEnabledTrueOrderByNameAsc();
|
||||
|
||||
List<SensorDefinition> findAllByOrderByNameAsc();
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.litoralregas.backend.sensor;
|
||||
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionCreateRequest;
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionResponse;
|
||||
import com.litoralregas.backend.sensor.dto.SensorDefinitionUpdateRequest;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SensorDefinitionService {
|
||||
|
||||
private final SensorDefinitionRepository sensorDefinitionRepository;
|
||||
|
||||
public SensorDefinitionService(SensorDefinitionRepository sensorDefinitionRepository) {
|
||||
this.sensorDefinitionRepository = sensorDefinitionRepository;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<SensorDefinitionResponse> findAll() {
|
||||
return sensorDefinitionRepository.findAllByOrderByNameAsc()
|
||||
.stream()
|
||||
.map(SensorDefinitionResponse::fromEntity)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<SensorDefinitionResponse> findEnabled() {
|
||||
return sensorDefinitionRepository.findByEnabledTrueOrderByNameAsc()
|
||||
.stream()
|
||||
.map(SensorDefinitionResponse::fromEntity)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public SensorDefinitionResponse findById(Integer id) {
|
||||
SensorDefinition sensorDefinition = getRequiredSensorDefinition(id);
|
||||
return SensorDefinitionResponse.fromEntity(sensorDefinition);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public SensorDefinitionResponse create(SensorDefinitionCreateRequest request) {
|
||||
if (sensorDefinitionRepository.existsByName(request.name())) {
|
||||
throw new IllegalArgumentException("A sensor definition with this name already exists.");
|
||||
}
|
||||
|
||||
validateBitOffset(request.valueType(), request.bitOffset());
|
||||
|
||||
SensorDefinition sensorDefinition = new SensorDefinition();
|
||||
sensorDefinition.setName(request.name().trim());
|
||||
sensorDefinition.setModbusAddress(request.modbusAddress());
|
||||
sensorDefinition.setBitOffset(request.bitOffset());
|
||||
sensorDefinition.setValueType(request.valueType());
|
||||
sensorDefinition.setUnit(normalizeNullableText(request.unit()));
|
||||
sensorDefinition.setDecimalPlaces(request.decimalPlaces());
|
||||
sensorDefinition.setCategory(request.category().trim());
|
||||
sensorDefinition.setSourceType(request.sourceType());
|
||||
sensorDefinition.setPollingIntervalSeconds(request.pollingIntervalSeconds());
|
||||
sensorDefinition.setEnabled(request.enabled());
|
||||
sensorDefinition.setCreatedAt(Instant.now());
|
||||
|
||||
SensorDefinition saved = sensorDefinitionRepository.save(sensorDefinition);
|
||||
return SensorDefinitionResponse.fromEntity(saved);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public SensorDefinitionResponse update(Integer id, SensorDefinitionUpdateRequest request) {
|
||||
SensorDefinition sensorDefinition = getRequiredSensorDefinition(id);
|
||||
|
||||
if (request.name() != null) {
|
||||
String normalizedName = request.name().trim();
|
||||
|
||||
if (!normalizedName.equals(sensorDefinition.getName())
|
||||
&& sensorDefinitionRepository.existsByName(normalizedName)) {
|
||||
throw new IllegalArgumentException("A sensor definition with this name already exists.");
|
||||
}
|
||||
|
||||
sensorDefinition.setName(normalizedName);
|
||||
}
|
||||
|
||||
if (request.modbusAddress() != null) {
|
||||
sensorDefinition.setModbusAddress(request.modbusAddress());
|
||||
}
|
||||
|
||||
if (request.bitOffset() != null || request.valueType() != null) {
|
||||
SensorValueType effectiveValueType =
|
||||
request.valueType() != null ? request.valueType() : sensorDefinition.getValueType();
|
||||
|
||||
Integer effectiveBitOffset =
|
||||
request.bitOffset() != null ? request.bitOffset() : sensorDefinition.getBitOffset();
|
||||
|
||||
validateBitOffset(effectiveValueType, effectiveBitOffset);
|
||||
|
||||
sensorDefinition.setValueType(effectiveValueType);
|
||||
sensorDefinition.setBitOffset(effectiveBitOffset);
|
||||
}
|
||||
|
||||
if (request.unit() != null) {
|
||||
sensorDefinition.setUnit(normalizeNullableText(request.unit()));
|
||||
}
|
||||
|
||||
if (request.decimalPlaces() != null) {
|
||||
sensorDefinition.setDecimalPlaces(request.decimalPlaces());
|
||||
}
|
||||
|
||||
if (request.category() != null) {
|
||||
sensorDefinition.setCategory(request.category().trim());
|
||||
}
|
||||
|
||||
if (request.sourceType() != null) {
|
||||
sensorDefinition.setSourceType(request.sourceType());
|
||||
}
|
||||
|
||||
if (request.pollingIntervalSeconds() != null) {
|
||||
sensorDefinition.setPollingIntervalSeconds(request.pollingIntervalSeconds());
|
||||
}
|
||||
|
||||
if (request.enabled() != null) {
|
||||
sensorDefinition.setEnabled(request.enabled());
|
||||
}
|
||||
|
||||
return SensorDefinitionResponse.fromEntity(sensorDefinition);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Integer id) {
|
||||
SensorDefinition sensorDefinition = getRequiredSensorDefinition(id);
|
||||
sensorDefinitionRepository.delete(sensorDefinition);
|
||||
}
|
||||
|
||||
private SensorDefinition getRequiredSensorDefinition(Integer id) {
|
||||
return sensorDefinitionRepository.findById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Sensor definition not found: " + id));
|
||||
}
|
||||
|
||||
private void validateBitOffset(SensorValueType valueType, Integer bitOffset) {
|
||||
if (valueType == SensorValueType.BOOLEAN && bitOffset == null) {
|
||||
throw new IllegalArgumentException("Boolean sensor definitions require bitOffset.");
|
||||
}
|
||||
|
||||
if (valueType != SensorValueType.BOOLEAN && bitOffset != null) {
|
||||
throw new IllegalArgumentException("Only boolean sensor definitions may use bitOffset.");
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeNullableText(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.litoralregas.backend.sensor;
|
||||
|
||||
public enum SensorSourceType {
|
||||
MODBUS,
|
||||
CALCULATED,
|
||||
MANUAL
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.litoralregas.backend.sensor;
|
||||
|
||||
public enum SensorValueType {
|
||||
BOOLEAN,
|
||||
INTEGER,
|
||||
DECIMAL
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.litoralregas.backend.sensor.dto;
|
||||
|
||||
import com.litoralregas.backend.sensor.SensorSourceType;
|
||||
import com.litoralregas.backend.sensor.SensorValueType;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public record SensorDefinitionCreateRequest(
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 255)
|
||||
String name,
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
Integer modbusAddress,
|
||||
|
||||
@Min(0)
|
||||
@Max(15)
|
||||
Integer bitOffset,
|
||||
|
||||
@NotNull
|
||||
SensorValueType valueType,
|
||||
|
||||
@Size(max = 50)
|
||||
String unit,
|
||||
|
||||
@NotNull
|
||||
@Min(0)
|
||||
@Max(6)
|
||||
Integer decimalPlaces,
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 100)
|
||||
String category,
|
||||
|
||||
@NotNull
|
||||
SensorSourceType sourceType,
|
||||
|
||||
@NotNull
|
||||
@Min(1)
|
||||
Integer pollingIntervalSeconds,
|
||||
|
||||
@NotNull
|
||||
Boolean enabled
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.litoralregas.backend.sensor.dto;
|
||||
|
||||
import com.litoralregas.backend.sensor.SensorDefinition;
|
||||
import com.litoralregas.backend.sensor.SensorSourceType;
|
||||
import com.litoralregas.backend.sensor.SensorValueType;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record SensorDefinitionResponse(
|
||||
Integer id,
|
||||
String name,
|
||||
Integer modbusAddress,
|
||||
Integer bitOffset,
|
||||
SensorValueType valueType,
|
||||
String unit,
|
||||
Integer decimalPlaces,
|
||||
String category,
|
||||
SensorSourceType sourceType,
|
||||
Integer pollingIntervalSeconds,
|
||||
Boolean enabled,
|
||||
Instant createdAt
|
||||
) {
|
||||
|
||||
public static SensorDefinitionResponse fromEntity(SensorDefinition sensorDefinition) {
|
||||
return new SensorDefinitionResponse(
|
||||
sensorDefinition.getId(),
|
||||
sensorDefinition.getName(),
|
||||
sensorDefinition.getModbusAddress(),
|
||||
sensorDefinition.getBitOffset(),
|
||||
sensorDefinition.getValueType(),
|
||||
sensorDefinition.getUnit(),
|
||||
sensorDefinition.getDecimalPlaces(),
|
||||
sensorDefinition.getCategory(),
|
||||
sensorDefinition.getSourceType(),
|
||||
sensorDefinition.getPollingIntervalSeconds(),
|
||||
sensorDefinition.getEnabled(),
|
||||
sensorDefinition.getCreatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.litoralregas.backend.sensor.dto;
|
||||
|
||||
import com.litoralregas.backend.sensor.SensorSourceType;
|
||||
import com.litoralregas.backend.sensor.SensorValueType;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public record SensorDefinitionUpdateRequest(
|
||||
|
||||
@Size(max = 255)
|
||||
String name,
|
||||
|
||||
@Min(0)
|
||||
Integer modbusAddress,
|
||||
|
||||
@Min(0)
|
||||
@Max(15)
|
||||
Integer bitOffset,
|
||||
|
||||
SensorValueType valueType,
|
||||
|
||||
@Size(max = 50)
|
||||
String unit,
|
||||
|
||||
@Min(0)
|
||||
@Max(6)
|
||||
Integer decimalPlaces,
|
||||
|
||||
@Size(max = 100)
|
||||
String category,
|
||||
|
||||
SensorSourceType sourceType,
|
||||
|
||||
@Min(1)
|
||||
Integer pollingIntervalSeconds,
|
||||
|
||||
Boolean enabled
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* */ package de.re.easymodbus.exceptions;
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public class CRCCheckFailedException
|
||||
/* */ extends ModbusException
|
||||
/* */ {
|
||||
/* */ public CRCCheckFailedException() {}
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public CRCCheckFailedException(String s)
|
||||
/* */ {
|
||||
/* 22 */ super(s);
|
||||
/* */ }
|
||||
/* */ }
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\exceptions\CRCCheckFailedException.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
@@ -0,0 +1,30 @@
|
||||
/* */ package de.re.easymodbus.exceptions;
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public class ConnectionException
|
||||
/* */ extends ModbusException
|
||||
/* */ {
|
||||
/* */ public ConnectionException() {}
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public ConnectionException(String s)
|
||||
/* */ {
|
||||
/* 22 */ super(s);
|
||||
/* */ }
|
||||
/* */ }
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\exceptions\ConnectionException.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
@@ -0,0 +1,30 @@
|
||||
/* */ package de.re.easymodbus.exceptions;
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public class FunctionCodeNotSupportedException
|
||||
/* */ extends ModbusException
|
||||
/* */ {
|
||||
/* */ public FunctionCodeNotSupportedException() {}
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public FunctionCodeNotSupportedException(String s)
|
||||
/* */ {
|
||||
/* 22 */ super(s);
|
||||
/* */ }
|
||||
/* */ }
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\exceptions\FunctionCodeNotSupportedException.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
@@ -0,0 +1,30 @@
|
||||
/* */ package de.re.easymodbus.exceptions;
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public class ModbusException
|
||||
/* */ extends Exception
|
||||
/* */ {
|
||||
/* */ public ModbusException() {}
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public ModbusException(String s)
|
||||
/* */ {
|
||||
/* 22 */ super(s);
|
||||
/* */ }
|
||||
/* */ }
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\exceptions\ModbusException.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
@@ -0,0 +1,30 @@
|
||||
/* */ package de.re.easymodbus.exceptions;
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public class QuantityInvalidException
|
||||
/* */ extends ModbusException
|
||||
/* */ {
|
||||
/* */ public QuantityInvalidException() {}
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public QuantityInvalidException(String s)
|
||||
/* */ {
|
||||
/* 22 */ super(s);
|
||||
/* */ }
|
||||
/* */ }
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\exceptions\QuantityInvalidException.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
@@ -0,0 +1,30 @@
|
||||
/* */ package de.re.easymodbus.exceptions;
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public class StartingAddressInvalidException
|
||||
/* */ extends ModbusException
|
||||
/* */ {
|
||||
/* */ public StartingAddressInvalidException() {}
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public StartingAddressInvalidException(String s)
|
||||
/* */ {
|
||||
/* 22 */ super(s);
|
||||
/* */ }
|
||||
/* */ }
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\exceptions\StartingAddressInvalidException.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/* */ package de.re.easymodbus.modbusclient;
|
||||
/* */
|
||||
/* */ import java.text.DateFormat;
|
||||
/* */ import java.text.SimpleDateFormat;
|
||||
/* */ import java.util.Calendar;
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ public class DateTime
|
||||
/* */ {
|
||||
/* */ protected static long getDateTimeTicks()
|
||||
/* */ {
|
||||
/* 18 */ long TICKS_AT_EPOCH = 621355968000000000L;
|
||||
/* 19 */ long tick = System.currentTimeMillis() * 10000L + TICKS_AT_EPOCH;
|
||||
/* 20 */ return tick;
|
||||
/* */ }
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */
|
||||
/* */ protected static String getDateTimeString()
|
||||
/* */ {
|
||||
/* 29 */ DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
|
||||
/* 30 */ Calendar cal = Calendar.getInstance();
|
||||
/* 31 */ return dateFormat.format(cal.getTime());
|
||||
/* */ }
|
||||
/* */ }
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\modbusclient\DateTime.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
package de.re.easymodbus.modbusclient;
|
||||
|
||||
public abstract interface ReceiveDataChangedListener
|
||||
{
|
||||
public abstract void ReceiveDataChanged();
|
||||
}
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\modbusclient\ReceiveDataChangedListener.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.re.easymodbus.modbusclient;
|
||||
|
||||
public abstract interface SendDataChangedListener
|
||||
{
|
||||
public abstract void SendDataChanged();
|
||||
}
|
||||
|
||||
|
||||
/* Location: C:\Users\Admin\Desktop\JAVA DEV\EasyModbusJava.jar!\de\re\easymodbus\modbusclient\SendDataChangedListener.class
|
||||
* Java compiler version: 8 (52.0)
|
||||
* JD-Core Version: 0.7.1
|
||||
*/
|
||||
@@ -0,0 +1,24 @@
|
||||
spring:
|
||||
application:
|
||||
name: backend
|
||||
|
||||
datasource:
|
||||
url: jdbc:sqlite:./data/backend.db
|
||||
driver-class-name: org.sqlite.JDBC
|
||||
|
||||
jpa:
|
||||
database-platform: org.hibernate.community.dialect.SQLiteDialect
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
|
||||
litoralregas:
|
||||
modbus:
|
||||
host: 198.19.0.176
|
||||
port: 533
|
||||
timeout-millis: 500
|
||||
max-attempts: 3
|
||||
retry-delay-millis: 1000
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE sensor_definition (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
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,
|
||||
source_type VARCHAR(50) NOT NULL,
|
||||
polling_interval_seconds INTEGER NOT NULL DEFAULT 1,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
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
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE UNIQUE INDEX ux_sensor_definition_name
|
||||
ON sensor_definition(name);
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.litoralregas.backend;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class BackendApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user