Skip to content

Commit

Permalink
Shelly: common executeWrite() method & many JUnit tests (#2658)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sn0w3y authored Jan 4, 2025
1 parent 74de376 commit ba4bd63
Show file tree
Hide file tree
Showing 19 changed files with 720 additions and 304 deletions.
2 changes: 1 addition & 1 deletion io.openems.edge.io.api/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Bundle-Version: 1.0.0.${tstamp}
-buildpath: \
${buildpath},\
io.openems.common,\
io.openems.edge.common
io.openems.edge.common,\

-testpath: \
${testpath}
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package io.openems.edge.io.shelly.common;

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import io.openems.edge.bridge.http.api.BridgeHttp;
import io.openems.edge.common.channel.BooleanWriteChannel;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.WriteChannel;
import io.openems.edge.common.component.OpenemsComponent;

public class Utils {
Expand All @@ -13,17 +22,20 @@ private Utils() {
* Generates a standard Debug-Log string for Shellys with one relay and power
* meter.
*
* @param relayChannel the Relay-Channel
* @param relayChannels the Relay-Channel
* @param activePowerChannel the ActivePower-Channel
* @return suitable for {@link OpenemsComponent#debugLog()}
*/
public static String generateDebugLog(Channel<Boolean> relayChannel, Channel<Integer> activePowerChannel) {
public static String generateDebugLog(Channel<Boolean>[] relayChannels, Channel<Integer> activePowerChannel) {
var b = new StringBuilder();
relayChannel.value().asOptional().ifPresentOrElse(//
v -> b.append(v ? "On" : "Off"), //
() -> b.append("Unknown"));
b.append("|");
b.append(activePowerChannel.value().asString());
for (int i = 0; i < relayChannels.length; i++) {
var relayChannel = relayChannels[i];
relayChannel.value().asOptional().ifPresentOrElse(v -> b.append(v ? "x" : "-"), () -> b.append("?"));
if (i < relayChannels.length - 1) {
b.append("|");
}
}
b.append("|").append(activePowerChannel.value().asString());
return b.toString();
}

Expand All @@ -35,21 +47,58 @@ public static String generateDebugLog(Channel<Boolean> relayChannel, Channel<Int
*/
public static String generateDebugLog(BooleanWriteChannel[] digitalOutputChannels) {
// TODO share code with AbstractKmtronicRelay.debugLog()
var b = new StringBuilder();
var i = 1;
for (var channel : digitalOutputChannels) {
var valueOpt = channel.value().asOptional();
if (valueOpt.isPresent()) {
b.append(valueOpt.get() ? "x" : "-");
return stream(digitalOutputChannels) //
.map(c -> c.value().asOptional().map(v -> v //
? "x" //
: "-") //
.orElse("?")) //
.collect(joining(" "));
}

/**
* Executes a write command to a specified relay channel by constructing and
* sending an HTTP request based on the channel's current and intended state.
* This method compares the current state with the desired state, and only
* proceeds with the HTTP request if they differ, ensuring no unnecessary
* commands are sent. The method returns a CompletableFuture that completes when
* the HTTP request is finished. It completes normally if the HTTP request
* succeeds, and exceptionally if the request fails due to errors.
*
* @param relayChannel the channel for the relay, specifying the current and
* desired states
* @param baseUrl the base URL for constructing the final endpoint URL
* @param httpBridge the HTTP bridge to send the request
* @param index the index of the DigitalChannel to write to (used for the
* URL)
* @return CompletableFuture{@code <Void>} that completes when the HTTP
* operation completes. Completes exceptionally if there is an error in
* the HTTP request.
*/
public static CompletableFuture<Void> executeWrite(WriteChannel<Boolean> relayChannel, String baseUrl,
BridgeHttp httpBridge, Integer index) {
CompletableFuture<Void> future = new CompletableFuture<>();
Boolean readValue = relayChannel.value().get();
Optional<Boolean> writeValue = relayChannel.getNextWriteValueAndReset();

if (writeValue.isEmpty()) {
future.complete(null); // No action needed
return future;
}
if (Objects.equals(readValue, writeValue.get())) {
future.complete(null); // No change in state
return future;
}

final String url = baseUrl + "/rpc/Switch.Set?id=" + index + "&on=" + (writeValue.get() ? "true" : "false");
httpBridge.get(url).whenComplete((response, exception) -> {
if (exception != null) {
future.completeExceptionally(exception);
} else {
b.append("Unknown");
future.complete(null);
}
if (i < digitalOutputChannels.length) {
b.append("|");
}
i++;
}
return b.toString();
});

return future;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,19 @@ private void processHttpResult(HttpResponse<JsonElement> result, HttpError error
var relay1State = new RelayState(null, null, null);
var relay2State = new RelayState(null, null, null);

try {
final var relays = getAsJsonArray(result.data(), "relays");
relay1State = RelayState.from(getAsJsonObject(relays.get(0)));
relay2State = RelayState.from(getAsJsonObject(relays.get(1)));

} catch (OpenemsNamedException | IndexOutOfBoundsException e) {
this.logDebug(this.log, e.getMessage());
slaveCommunicationFailed = true;
if (error != null) {
this.logDebug(this.log, error.getMessage());

} else {
try {
final var relays = getAsJsonArray(result.data(), "relays");
relay1State = RelayState.from(getAsJsonObject(relays.get(0)));
relay2State = RelayState.from(getAsJsonObject(relays.get(1)));

} catch (OpenemsNamedException | IndexOutOfBoundsException e) {
this.logDebug(this.log, e.getMessage());
slaveCommunicationFailed = true;
}
}

this._setSlaveCommunicationFailed(slaveCommunicationFailed);
Expand Down Expand Up @@ -182,4 +187,4 @@ private void executeWrite(BooleanWriteChannel channel, int index) {
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import static io.openems.common.utils.JsonUtils.getAsFloat;
import static io.openems.common.utils.JsonUtils.getAsJsonArray;
import static io.openems.common.utils.JsonUtils.getAsJsonObject;
import static io.openems.edge.io.shelly.common.Utils.executeWrite;
import static io.openems.edge.io.shelly.common.Utils.generateDebugLog;
import static java.lang.Math.round;

import java.util.Objects;

import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
Expand Down Expand Up @@ -83,6 +82,8 @@ public IoShelly3EmImpl() {
this.digitalOutputChannels = new BooleanWriteChannel[] { this.channel(IoShelly3Em.ChannelId.RELAY) };

ElectricityMeter.calculateSumActivePowerFromPhases(this);
ElectricityMeter.calculateSumCurrentFromPhases(this);
ElectricityMeter.calculateAverageVoltageFromPhases(this);
}

@Activate
Expand Down Expand Up @@ -111,7 +112,7 @@ public BooleanWriteChannel[] digitalOutputChannels() {

@Override
public String debugLog() {
return generateDebugLog(this.getRelayChannel(), this.getActivePowerChannel());
return generateDebugLog(this.digitalOutputChannels, this.getActivePowerChannel());
}

@Override
Expand All @@ -124,7 +125,7 @@ public void handleEvent(Event event) {
case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE //
-> this.calculateEnergy();
case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE //
-> this.executeWrite();
-> executeWrite(this.getRelayChannel(), this.baseUrl, this.httpBridge, 0);
}
}

Expand Down Expand Up @@ -219,32 +220,6 @@ private void processHttpResult(HttpResponse<JsonElement> result, Throwable error
this._setCurrentL3(currentL3);
}

/**
* Execute on Cycle Event "Execute Write".
*/
private void executeWrite() {
var channel = this.getRelayChannel();
var index = 0;
var readValue = channel.value().get();
var writeValue = channel.getNextWriteValueAndReset();
if (writeValue.isEmpty()) {
return;
}
if (Objects.equals(readValue, writeValue.get())) {
return;
}
final var url = this.baseUrl + "/relay/" + index + "?turn=" + (writeValue.get() ? "on" : "off");

this.httpBridge.get(url).whenComplete((t, e) -> {
this._setSlaveCommunicationFailed(e != null);
if (e == null) {
this.logInfo(this.log, "Executed write successfully for URL: " + url);
} else {
this.logError(this.log, "Failed to execute write for URL: " + url + "; Error: " + e.getMessage());
}
});
}

/**
* Calculate the Energy values from ActivePower.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package io.openems.edge.io.shelly.shellyplug;

import static io.openems.common.utils.JsonUtils.getAsBoolean;
import static io.openems.common.utils.JsonUtils.getAsFloat;
import static io.openems.common.utils.JsonUtils.getAsJsonArray;
import static io.openems.common.utils.JsonUtils.getAsJsonObject;
import static io.openems.common.utils.JsonUtils.getAsLong;
import static io.openems.edge.io.shelly.common.Utils.generateDebugLog;
import static java.lang.Math.round;

import java.util.Objects;

Expand All @@ -21,7 +27,6 @@

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.types.MeterType;
import io.openems.common.utils.JsonUtils;
import io.openems.edge.bridge.http.api.BridgeHttp;
import io.openems.edge.bridge.http.api.BridgeHttpFactory;
import io.openems.edge.bridge.http.api.HttpResponse;
Expand Down Expand Up @@ -102,7 +107,7 @@ public BooleanWriteChannel[] digitalOutputChannels() {

@Override
public String debugLog() {
return generateDebugLog(this.getRelayChannel(), this.getActivePowerChannel());
return generateDebugLog(this.digitalOutputChannels, this.getActivePowerChannel());
}

@Override
Expand All @@ -128,17 +133,18 @@ private void processHttpResult(HttpResponse<JsonElement> result, Throwable error
return;
}
try {
final var relays = JsonUtils.getAsJsonArray(result.data(), "relays");
final var relay1 = JsonUtils.getAsJsonObject(relays.get(0));
final var relayIson = JsonUtils.getAsBoolean(relay1, "ison");
final var meters = JsonUtils.getAsJsonArray(result.data(), "meters");
final var meter1 = JsonUtils.getAsJsonObject(meters.get(0));
final var power = Math.round(JsonUtils.getAsFloat(meter1, "power"));
final var energy = JsonUtils.getAsLong(meter1, "total") /* Unit: Wm */ / 60 /* Wh */;
var relays = getAsJsonArray(result.data(), "relays");
var relay1 = getAsJsonObject(relays.get(0));
var relayIson = getAsBoolean(relay1, "ison");
var meters = getAsJsonArray(result.data(), "meters");
var meter1 = getAsJsonObject(meters.get(0));
var power = round(getAsFloat(meter1, "power"));
var energy = getAsLong(meter1, "total")/* Unit: Wm */ / 60 /* Wh */;

this._setRelay(relayIson);
this._setActivePower(power);
this._setActiveProductionEnergy(energy);

} catch (OpenemsNamedException e) {
this._setRelay(null);
this._setActivePower(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public BooleanWriteChannel[] digitalOutputChannels() {

@Override
public String debugLog() {
return generateDebugLog(this.getRelayChannel(), this.getActivePowerChannel());
return generateDebugLog(this.digitalOutputChannels, this.getActivePowerChannel());
}

@Override
Expand Down Expand Up @@ -238,4 +238,4 @@ public Timedata getTimedata() {
return this.timedata;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
import static io.openems.common.utils.JsonUtils.getAsBoolean;
import static io.openems.common.utils.JsonUtils.getAsFloat;
import static io.openems.common.utils.JsonUtils.getAsJsonObject;
import static io.openems.edge.io.shelly.common.Utils.executeWrite;
import static io.openems.edge.io.shelly.common.Utils.generateDebugLog;
import static java.lang.Math.round;

import java.util.Objects;

import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
Expand All @@ -29,7 +28,6 @@
import io.openems.common.types.MeterType;
import io.openems.edge.bridge.http.api.BridgeHttp;
import io.openems.edge.bridge.http.api.BridgeHttpFactory;
import io.openems.edge.bridge.http.api.HttpError;
import io.openems.edge.bridge.http.api.HttpResponse;
import io.openems.edge.common.channel.BooleanWriteChannel;
import io.openems.edge.common.component.AbstractOpenemsComponent;
Expand Down Expand Up @@ -123,7 +121,7 @@ public BooleanWriteChannel[] digitalOutputChannels() {

@Override
public String debugLog() {
return generateDebugLog(this.getRelayChannel(), this.getActivePowerChannel());
return generateDebugLog(this.digitalOutputChannels, this.getActivePowerChannel());
}

@Override
Expand All @@ -136,11 +134,11 @@ public void handleEvent(Event event) {
case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE //
-> this.calculateEnergy();
case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE //
-> this.executeWrite();
-> executeWrite(this.getRelayChannel(), this.baseUrl, this.httpBridge, 0);
}
}

private void processHttpResult(HttpResponse<JsonElement> result, HttpError error) {
private void processHttpResult(HttpResponse<JsonElement> result, Throwable error) {
this._setSlaveCommunicationFailed(result == null);

Boolean relayStatus = null;
Expand Down Expand Up @@ -177,32 +175,6 @@ private void processHttpResult(HttpResponse<JsonElement> result, HttpError error
this.channel(IoShellyPlusPlugs.ChannelId.HAS_UPDATE).setNextValue(updatesAvailable);
}

/**
* Execute on Cycle Event "Execute Write".
*/
private void executeWrite() {
var channel = this.getRelayChannel();
var readValue = channel.value().get();
var writeValue = channel.getNextWriteValueAndReset();
if (writeValue.isEmpty()) {
return;
}
if (Objects.equals(readValue, writeValue.get())) {
return;
}
var index = 0;
final var url = this.baseUrl + "/relay/" + index + "?turn=" + (writeValue.get() ? "on" : "off");

this.httpBridge.get(url).whenComplete((t, e) -> {
this._setSlaveCommunicationFailed(e != null);
if (e == null) {
this.logDebug(this.log, "Executed write successfully for URL: " + url);
} else {
this.logError(this.log, "Failed to execute write for URL: " + url + "; Error: " + e.getMessage());
}
});
}

/**
* Calculate the Energy values from ActivePower.
*/
Expand Down Expand Up @@ -236,4 +208,4 @@ public Timedata getTimedata() {
return this.timedata;
}

}
}
Loading

0 comments on commit ba4bd63

Please sign in to comment.