feat(heatpump): add ModbusSetpointRegisters
CI / Backend tests (Maven / Java 21) (push) Successful in 1m6s
CI / Backend tests (Maven / Java 21) (push) Successful in 1m6s
Adapter for the SetpointRegisters port: FC3 read and FC6 write of whole values in the controller's sign-magnitude words. Negative values encode correctly and values over 15 bits are rejected; limits stay with the service. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a6e6f36e87
commit
82ff6565ad
2 files changed
+140
No files matched your search
@@ -0,0 +1,42 @@
|
||||
package home.gateway.adapters.heatpump;
|
||||
|
||||
import home.gateway.adapters.modbus.ModbusClient;
|
||||
import home.gateway.application.SetpointRegisters;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Setpoint registers of the controller: whole values in sign-magnitude words, read with FC3 and written with FC6. */
|
||||
public class ModbusSetpointRegisters implements SetpointRegisters {
|
||||
|
||||
private static final int SIGN_BIT = 0x8000;
|
||||
private static final int MAX_MAGNITUDE = 0x7FFF;
|
||||
|
||||
private final ModbusClient modbus;
|
||||
private final int unitId;
|
||||
|
||||
public ModbusSetpointRegisters(ModbusClient modbus, int unitId) {
|
||||
this.modbus = modbus;
|
||||
this.unitId = unitId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(int register) throws IOException {
|
||||
int[] words = modbus.readHoldingRegisters(unitId, register, 1);
|
||||
if (words.length != 1) {
|
||||
throw new IOException("Expected 1 word from register " + register + ", got " + words.length);
|
||||
}
|
||||
return (int) Scale.UNITS.decode(words[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int register, int value) throws IOException {
|
||||
modbus.writeSingleRegister(unitId, register, encode(value));
|
||||
}
|
||||
|
||||
private static int encode(int value) {
|
||||
int magnitude = Math.abs(value);
|
||||
if (magnitude > MAX_MAGNITUDE) {
|
||||
throw new IllegalArgumentException("Setpoint does not fit 15 bits: " + value);
|
||||
}
|
||||
return value < 0 ? magnitude | SIGN_BIT : magnitude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package home.gateway.adapters.heatpump;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import home.gateway.adapters.modbus.ModbusClient;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ModbusSetpointRegistersTest {
|
||||
|
||||
private final FakeClient client = new FakeClient();
|
||||
private final ModbusSetpointRegisters registers = new ModbusSetpointRegisters(client, 7);
|
||||
|
||||
@Test
|
||||
void readsOneHoldingRegisterOfItsUnit() throws IOException {
|
||||
client.word = 8;
|
||||
|
||||
assertEquals(8, registers.read(4143));
|
||||
assertEquals(List.of("read 7:4143+1"), client.calls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodesSignMagnitudeOnRead() throws IOException {
|
||||
client.word = 0x8005;
|
||||
|
||||
assertEquals(-5, registers.read(4143));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writesOneRegisterOfItsUnit() throws IOException {
|
||||
registers.write(4143, 9);
|
||||
|
||||
assertEquals(List.of("write 7:4143=9"), client.calls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodesNegativeValuesAsSignMagnitude() throws IOException {
|
||||
registers.write(4143, -5);
|
||||
|
||||
assertEquals(List.of("write 7:4143=" + 0x8005), client.calls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsValuesThatDoNotFitFifteenBits() {
|
||||
assertThrows(IllegalArgumentException.class, () -> registers.write(4143, 0x8000));
|
||||
assertThrows(IllegalArgumentException.class, () -> registers.write(4143, -0x8000));
|
||||
assertEquals(List.of(), client.calls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void propagatesWriteFailure() {
|
||||
client.failure = new IOException("timeout");
|
||||
|
||||
assertThrows(IOException.class, () -> registers.write(4143, 9));
|
||||
}
|
||||
|
||||
@Test
|
||||
void propagatesReadFailureAndShortAnswer() {
|
||||
client.failure = new IOException("timeout");
|
||||
assertThrows(IOException.class, () -> registers.read(4143));
|
||||
|
||||
client.failure = null;
|
||||
client.empty = true;
|
||||
assertThrows(IOException.class, () -> registers.read(4143));
|
||||
}
|
||||
|
||||
private static final class FakeClient implements ModbusClient {
|
||||
final List<String> calls = new ArrayList<>();
|
||||
int word;
|
||||
boolean empty;
|
||||
IOException failure;
|
||||
|
||||
@Override
|
||||
public int[] readHoldingRegisters(int unitId, int address, int count) throws IOException {
|
||||
if (failure != null) {
|
||||
throw failure;
|
||||
}
|
||||
calls.add("read " + unitId + ":" + address + "+" + count);
|
||||
return empty ? new int[0] : new int[] {word};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] readInputRegisters(int unitId, int address, int count) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSingleRegister(int unitId, int address, int value) throws IOException {
|
||||
if (failure != null) {
|
||||
throw failure;
|
||||
}
|
||||
calls.add("write " + unitId + ":" + address + "=" + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user