Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,7 @@
/**
* State class for keeping state of a <code>AnalogInfoControl</code>.
*/
public class AnalogInfoControlState extends ControlState<AnalogInfoControl> {

/**
* Current value of the AnalogInfoControl.
*/
@Getter
@Nullable
private Double value;

public class AnalogInfoControlState extends ControlState<Double, AnalogInfoControl> {

public AnalogInfoControlState(Loxone loxone, AnalogInfoControl control) {
super(loxone, control);
Expand All @@ -30,17 +22,8 @@ public AnalogInfoControlState(Loxone loxone, AnalogInfoControl control) {
*/
@Override
void accept(@NotNull ValueEvent event) {
super.accept(event);
if (event.getUuid().equals(control.stateValue())) {
processValueEvent(event);
if (event.getUuid().equals(getControl().stateValue())) {
setState(event.getValue());
}
}

/**
* Process the ValueEvent as a value event message and update the state of the control accordingly.
* @param event value event received
*/
private void processValueEvent(ValueEvent event) {
value = event.getValue();
}
}
70 changes: 63 additions & 7 deletions src/main/java/cz/smarteon/loxone/app/state/ControlState.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,95 @@
import cz.smarteon.loxone.app.Control;
import cz.smarteon.loxone.message.TextEvent;
import cz.smarteon.loxone.message.ValueEvent;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;

import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

/**
* Base class for all the controlStates in loxone application.
* @param <T> The type of control this class keeps trakc of the state.
* <p>
* This class keeps track of the state of the control based on the events of the miniserver.
* </p>
* @param <S> The type of the state.
* @param <T> The type of control this class keeps track of.
*/
@RequiredArgsConstructor
public abstract class ControlState<T extends Control> {
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class ControlState<S, T extends Control> {

/**
* The webSocket connection to communicate with the loxone miniserver.
*/
protected final Loxone loxone;
@Getter
private final Loxone loxone;

/**
* The control that this state refers to.
*/
protected final T control;
@Getter
private final T control;

/**
* The current state of the control.
*/
@Getter
private S state;

/**
* The set of listeners registered for this control state.
*/
private final Set<ControlStateListener<S, T>> listeners = new CopyOnWriteArraySet<>();

/**
* Registers a listener to be notified of state changes.
* @param listener the listener to register (should not be null).
*/
public void registerListener(@NotNull ControlStateListener<S, T> listener) {
listeners.add(listener);
}

/**
* Unregisters a previously registered listener.
* @param listener the listener to unregister (should not be null).
*/
public void unregisterListener(@NotNull ControlStateListener<S, T> listener) {
listeners.remove(listener);
}

/**
* Method that accepts ValueEvent from the miniserver to update the internal state.
* @param event value event received (should not be null)
* @param event value event received (should not be null).
*/
void accept(@NotNull ValueEvent event) {
// default implementation
}

/**
* Method that accepts TextEvent from the miniserver to update the internal state.
* @param event text event received (should not be null)
* @param event text event received (should not be null).
*/
void accept(@NotNull TextEvent event) {
// default implementation
}

void setState(@NotNull S state) {
boolean stateChanged = this.state == null || !this.state.equals(state);
this.state = state;
if (stateChanged) {
notifyStateChanged();
}
}

/**
* Notifies all registered listeners of a state change.
*/
void notifyStateChanged() {
for (ControlStateListener<S, T> listener : listeners) {
listener.onStateChange(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cz.smarteon.loxone.app.state;

import cz.smarteon.loxone.app.Control;
import org.jetbrains.annotations.NotNull;

/**
* Allows reacting on control state changes.
* @param <T> The type of control this listener is interested in.
*/
public interface ControlStateListener<S, T extends Control> {

/**
* Called when an event is received and processed by the control state and the state of the control has changed.
* @param controlState the control state that received the event (should not be null)
*/
void onStateChange(@NotNull ControlState<S, T> controlState);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,7 @@
/**
* State class for keeping state of a <code>DigitalInfoControl</code>.
*/
public class DigitalInfoControlState extends ControlState<DigitalInfoControl> {

/**
* Current value of the DigitalInfoControl.
*/
@Getter
@Nullable
private Boolean state;

public class DigitalInfoControlState extends ControlState<Boolean,DigitalInfoControl> {

public DigitalInfoControlState(Loxone loxone, DigitalInfoControl control) {
super(loxone, control);
Expand All @@ -30,17 +22,8 @@ public DigitalInfoControlState(Loxone loxone, DigitalInfoControl control) {
*/
@Override
void accept(@NotNull ValueEvent event) {
super.accept(event);
if (event.getUuid().equals(control.stateActive())) {
processActiveEvent(event);
if (event.getUuid().equals(getControl().stateActive())) {
setState(event.getValue() == 1);
}
}

/**
* Process the ValueEvent as an active state event message and update the state of the control accordingly.
* @param event value event received
*/
private void processActiveEvent(ValueEvent event) {
state = event.getValue() == 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import cz.smarteon.loxone.app.state.events.LockedEvent;
import cz.smarteon.loxone.message.TextEvent;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -14,20 +15,23 @@

/**
* Base class for the controlStates in loxone application that supports locking.
* @param <T> The type of control this class keeps trakc of the state.
* <p>
* This class keeps track of the state of the control based on the events of the miniserver.
* </p>
* @param <T> The type of control this class keeps track of.
*/
@Slf4j
public abstract class LockableControlState<T extends Control> extends ControlState<T> {
public abstract class LockableControlState<S, T extends Control> extends ControlState<S, T> {

/**
* Current state of the lock of the control.
* Current state of the lock.
*/
@Getter
@Nullable
private Locked locked;

/**
* Extra free format reason in case of lock of the control.
* Extra free format reason in case of lock.
*/
@Getter
@Nullable
Expand All @@ -43,7 +47,7 @@ protected LockableControlState(Loxone loxone, T control) {
*/
@Override
void accept(@NotNull TextEvent event) {
if (event.getUuid().equals(control.stateLocked())) {
if (event.getUuid().equals(getControl().stateLocked())) {
processLockedEvent(event);
}
}
Expand All @@ -54,14 +58,20 @@ void accept(@NotNull TextEvent event) {
*/
private void processLockedEvent(TextEvent event) {
try {
final boolean isCurrentState;
if (event.getText().isEmpty()) {
isCurrentState = Locked.NO.equals(locked) && lockedReason == null;
this.locked = Locked.NO;
this.lockedReason = null;
return;
} else {
final LockedEvent lockedEvent = Codec.readMessage(event.getText(), LockedEvent.class);
isCurrentState = lockedEvent.getLocked().equals(locked) && lockedEvent.getReason().equals(lockedReason);
this.locked = lockedEvent.getLocked();
this.lockedReason = lockedEvent.getReason();
}
if (!isCurrentState) {
notifyStateChanged();
}
final LockedEvent lockedEvent = Codec.readMessage(event.getText(), LockedEvent.class);
this.locked = lockedEvent.getLocked();
this.lockedReason = lockedEvent.getReason();
} catch (IOException e) {
log.info("Unable to parse locked event!", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package cz.smarteon.loxone.app.state;

import cz.smarteon.loxone.LoxoneException;

public class LoxoneLockedException extends LoxoneException {

public LoxoneLockedException(String message) {
super(message);
}
}
12 changes: 7 additions & 5 deletions src/main/java/cz/smarteon/loxone/app/state/LoxoneState.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
@Slf4j
public class LoxoneState implements LoxoneAppListener, LoxoneEventListener {

private static final Map<Class<? extends Control>, Class<? extends ControlState<? extends Control>>> SUPPORTED_CONTROLS_STATE_MAP;
private static final Map<
Class<? extends Control>,
Class<? extends ControlState<?, ? extends Control>>> SUPPORTED_CONTROLS_STATE_MAP;

private final Loxone loxone;

private LoxoneApp loxoneApp;

private Map<LoxoneUuid, ControlState<?>> controlStates;
private Map<LoxoneUuid, ControlState<?, ?>> controlStates;

static {
SUPPORTED_CONTROLS_STATE_MAP = new HashMap<>();
Expand All @@ -59,12 +61,12 @@ public LoxoneState(@NotNull Loxone loxone) {
}

@TestOnly
Map<Class<? extends Control>, Class<? extends ControlState<? extends Control>>> getSupportedControlsStateMap() {
Map<Class<? extends Control>, Class<? extends ControlState<?, ? extends Control>>> getSupportedControlsStateMap() {
return SUPPORTED_CONTROLS_STATE_MAP;
}

@TestOnly
Map<LoxoneUuid, ControlState<?>> getControlStates() {
Map<LoxoneUuid, ControlState<?, ?>> getControlStates() {
return controlStates;
}

Expand Down Expand Up @@ -103,7 +105,7 @@ private void initializeState() {
Map.Entry::getKey,
e -> {
try {
return (ControlState<?>) SUPPORTED_CONTROLS_STATE_MAP.get(e.getValue().getClass())
return (ControlState<?, ?>) SUPPORTED_CONTROLS_STATE_MAP.get(e.getValue().getClass())
.getConstructors()[0].newInstance(loxone, e.getValue());
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException ex) {
Expand Down
34 changes: 12 additions & 22 deletions src/main/java/cz/smarteon/loxone/app/state/SwitchControlState.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@
/**
* State class for keeping and managing state of a <code>SwitchControl</code>.
*/
public class SwitchControlState extends LockableControlState<SwitchControl> {

/**
* Current state of the SwitchControl.
*/
@Getter
@Nullable
private Boolean state;
public class SwitchControlState extends LockableControlState<Boolean, SwitchControl> {

public SwitchControlState(Loxone loxone, SwitchControl control) {
super(loxone, control);
Expand All @@ -29,7 +22,7 @@ public SwitchControlState(Loxone loxone, SwitchControl control) {
* Toggles state of SwitchControl. When current state is <code>SwitchState.UNINITIALIZED</code> it switches to On.
*/
public void toggleState() {
if (Boolean.TRUE.equals(state)) {
if (Boolean.TRUE.equals(getState())) {
stateOff();
} else {
stateOn();
Expand All @@ -40,15 +33,21 @@ public void toggleState() {
* Sets state of SwitchControl to On.
*/
public void stateOn() {
loxone.sendControlCommand(control, switchControl -> genericControlCommand(switchControl.getUuid().toString(),
if (getLocked() != null && !Locked.NO.equals(getLocked())) {
throw new LoxoneLockedException("SwitchControl is locked, so no state change is possible");
}
getLoxone().sendControlCommand(getControl(), switchControl -> genericControlCommand(switchControl.getUuid().toString(),
"On"));
}

/**
* Sets state of SwitchControl to Off.
*/
public void stateOff() {
loxone.sendControlCommand(control, switchControl -> genericControlCommand(switchControl.getUuid().toString(),
if (getLocked() != null && !Locked.NO.equals(getLocked())) {
throw new LoxoneLockedException("SwitchControl is locked, so no state change is possible");
}
getLoxone().sendControlCommand(getControl(), switchControl -> genericControlCommand(switchControl.getUuid().toString(),
"Off"));
}

Expand All @@ -58,17 +57,8 @@ public void stateOff() {
*/
@Override
void accept(@NotNull ValueEvent event) {
super.accept(event);
if (event.getUuid().equals(control.stateActive())) {
processActiveEvent(event);
if (event.getUuid().equals(getControl().stateActive())) {
setState(event.getValue() == 1);
}
}

/**
* Process the ValueEvent as an active state event message and update the state of the control accordingly.
* @param event value event received
*/
private void processActiveEvent(ValueEvent event) {
state = event.getValue() == 1;
}
}
Loading
Loading