Skip to content

Commit

Permalink
Export Channels to Excel (OpenEMS#1526)
Browse files Browse the repository at this point in the history
Adds a link in UI Profile to download an Excel file with all Channels and their current value. Also adds information about the 'source' of a Channel, e.g. the modbus register address.
  • Loading branch information
sfeilmeier authored Jun 21, 2021
1 parent 16bef31 commit d311364
Show file tree
Hide file tree
Showing 15 changed files with 550 additions and 141 deletions.
2 changes: 1 addition & 1 deletion cnf/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
<message key="name.invalidPattern" value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]$)"/>
<message key="name.invalidPattern" value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="InterfaceTypeParameterName">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,7 @@ protected final AbstractModbusElement<?> m(BitsWordElement bitsWordElement) {
*/
protected final <T extends AbstractModbusElement<?>> T m(io.openems.edge.common.channel.ChannelId channelId,
T element) {
return new ChannelMapper<T>(element) //
.m(channelId, ElementToChannelConverter.DIRECT_1_TO_1) //
.build();
return this.m(channelId, element, ElementToChannelConverter.DIRECT_1_TO_1);
}

/**
Expand All @@ -330,8 +328,9 @@ protected final <T extends AbstractModbusElement<?>> T m(io.openems.edge.common.
* @param converter the ElementToChannelConverter
* @return the element parameter
*/
protected final <T extends AbstractModbusElement<?>> AbstractModbusElement<?> m(
io.openems.edge.common.channel.ChannelId channelId, T element, ElementToChannelConverter converter) {
protected final <T extends AbstractModbusElement<?>> T m(io.openems.edge.common.channel.ChannelId channelId,
T element, ElementToChannelConverter converter) {
channelId.doc().source(new ModbusChannelSource(element.getStartAddress()));
return new ChannelMapper<T>(element) //
.m(channelId, converter) //
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.openems.edge.bridge.modbus.api;

public class ModbusChannelSource {

/**
* Holds the Start-Address of the Modbus Register.
*/
private final int address;

/**
* Holds the index of the bit inside the Register if applicable.
*/
private final int bit;

public ModbusChannelSource(int address) {
this.address = address;
this.bit = -1;
}

public ModbusChannelSource(int address, int bit) {
this.address = address;
this.bit = bit;
}

@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("0x").append(Integer.toHexString(this.address));
if (this.bit >= 0) {
b.append("|bit").append(String.format("%02d", this.bit));
}
return b.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.openems.common.types.OpenemsType;
import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent;
import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent.BitConverter;
import io.openems.edge.bridge.modbus.api.ModbusChannelSource;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.ChannelId;
import io.openems.edge.common.channel.WriteChannel;
Expand Down Expand Up @@ -68,6 +69,9 @@ public BitsWordElement bit(int bitIndex, ChannelId channelId, BitConverter conve
@SuppressWarnings("unchecked")
Channel<Boolean> booleanChannel = (Channel<Boolean>) channel;

// Add Modbus Address and Bit-Index to Channel Source
channelId.doc().source(new ModbusChannelSource(this.getStartAddress(), bitIndex));

// Handle Writes to Bit-Channels
ChannelWrapper channelWrapper = new ChannelWrapper(booleanChannel, converter);
if (channel instanceof WriteChannel<?>) {
Expand Down
28 changes: 25 additions & 3 deletions io.openems.edge.common/src/io/openems/edge/common/channel/Doc.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
/**
* Provides static meta information for a {@link Channel}.
*
* <p>
* Possible meta information include:
*
* <ul>
* <li>access-mode (read-only/read-write/write-only) flag
* {@link Doc#accessMode(AccessMode)}. Defaults to Read-Only.
Expand Down Expand Up @@ -94,9 +96,9 @@ public static StateChannelDoc of(Level level) {
public OpenemsType getType();

/**
* Gets the 'Access-Mode' information
* Gets the 'Access-Mode' information.
*
* @return
* @return the {@link AccessMode}
*/
public AccessMode getAccessMode();

Expand Down Expand Up @@ -134,7 +136,25 @@ public static StateChannelDoc of(Level level) {
public String getText();

/**
* Is the more verbose debug mode activated?
* Sets an object that holds information about the source of this Channel, i.e.
* a Modbus Register or REST-Api endpoint address. Defaults to null.
*
* @param <SOURCE> the type of the source attachment
* @param source the source object
* @return myself
*/
public <SOURCE> Doc source(SOURCE source);

/**
* Gets the source information object. Defaults to empty String.
*
* @param <SOURCE> the type of the source attachment
* @return the soure information object
*/
public <SOURCE> SOURCE getSource();

/**
* Is the more verbose debug mode activated?.
*
* @return true for debug mode
*/
Expand All @@ -144,6 +164,8 @@ public static StateChannelDoc of(Level level) {
* Creates an instance of {@link Channel} for the given Channel-ID using its
* Channel-{@link AbstractDoc}.
*
* @param <C> the type of the Channel
* @param component the {@link OpenemsComponent}
* @param channelId the Channel-ID
* @return the Channel
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,24 @@ public String getText() {
return this.text;
}

/**
* An object that holds information about the source of this Channel, i.e. a
* Modbus Register or REST-Api endpoint address. Defaults to null.
*/
private Object source = null;

@Override
public <SOURCE> AbstractDoc<T> source(SOURCE source) {
this.source = source;
return this.self();
}

@Override
@SuppressWarnings("unchecked")
public <SOURCE> SOURCE getSource() {
return (SOURCE) this.source;
}

@Override
public Unit getUnit() {
return Unit.NONE;
Expand Down
7 changes: 5 additions & 2 deletions io.openems.edge.core/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.edge.predictor.api,\
io.openems.edge.scheduler.api,\
io.openems.edge.timedata.api,\
io.openems.wrapper.sdnotify
io.openems.wrapper.fastexcel,\
io.openems.wrapper.sdnotify

-testpath: \
${testpath}
${testpath},\
io.openems.wrapper.fastexcel,\
io.openems.wrapper.opczip
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.jsonapi.JsonApi;
import io.openems.edge.common.user.User;
import io.openems.edge.core.componentmanager.jsonrpc.ChannelExportXlsxRequest;
import io.openems.edge.core.componentmanager.jsonrpc.ChannelExportXlsxResponse;

@Designate(ocd = Config.class, factory = false)
@Component(//
Expand Down Expand Up @@ -204,6 +206,9 @@ public CompletableFuture<JsonrpcResponseSuccess> handleJsonrpcRequest(User user,
case DeleteComponentConfigRequest.METHOD:
return this.handleDeleteComponentConfigRequest(user, DeleteComponentConfigRequest.from(request));

case ChannelExportXlsxRequest.METHOD:
return this.handleChannelExportXlsxRequest(user, ChannelExportXlsxRequest.from(request));

default:
throw OpenemsError.JSONRPC_UNHANDLED_METHOD.exception(request.getMethod());
}
Expand Down Expand Up @@ -369,6 +374,24 @@ protected CompletableFuture<JsonrpcResponseSuccess> handleDeleteComponentConfigR
return CompletableFuture.completedFuture(new GenericJsonrpcResponseSuccess(request.getId()));
}

/**
* Handles a {@link ChannelExportXlsxRequest}.
*
* @param user the {@link User}
* @param request the {@link ChannelExportXlsxRequest}
* @return the Future JSON-RPC Response
* @throws OpenemsNamedException on error
*/
protected CompletableFuture<JsonrpcResponseSuccess> handleChannelExportXlsxRequest(User user,
ChannelExportXlsxRequest request) throws OpenemsNamedException {
user.assertRoleIsAtLeast("ChannelExportXlsxRequest", Role.ADMIN);
OpenemsComponent component = this.getComponent(request.getComponentId());
if (component == null) {
throw OpenemsError.EDGE_NO_COMPONENT_WITH_ID.exception(request.getComponentId());
}
return CompletableFuture.completedFuture(new ChannelExportXlsxResponse(request.getId(), component));
}

/**
* Updates the Configuration from the given Properties and adds some meta
* information.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.openems.edge.core.componentmanager.jsonrpc;

import com.google.gson.JsonObject;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.jsonrpc.base.JsonrpcRequest;
import io.openems.common.utils.JsonUtils;

/**
* Exports Channels with current value and metadata to an Excel (xlsx) file.
*
* <pre>
* {
* "jsonrpc": "2.0",
* "id": "UUID",
* "method": "channelExportXlsx",
* "params": {
* "componentId": string
* }
* }
* </pre>
*/
public class ChannelExportXlsxRequest extends JsonrpcRequest {

public static final String METHOD = "channelExportXlsx";

/**
* Create {@link ChannelExportXlsxRequest} from a template
* {@link JsonrpcRequest}.
*
* @param r the template {@link JsonrpcRequest}
* @return the {@link ChannelExportXlsxRequest}
* @throws OpenemsNamedException on parse error
*/
public static ChannelExportXlsxRequest from(JsonrpcRequest r) throws OpenemsNamedException {
JsonObject p = r.getParams();
String componentId = JsonUtils.getAsString(p, "componentId");
return new ChannelExportXlsxRequest(r, componentId);
}

private final String componentId;

public ChannelExportXlsxRequest(String componentId) {
super(METHOD);
this.componentId = componentId;
}

private ChannelExportXlsxRequest(JsonrpcRequest request, String componentId) {
super(request, METHOD);
this.componentId = componentId;
}

@Override
public JsonObject getParams() {
return JsonUtils.buildJsonObject() //
.addProperty("componentId", this.componentId) //
.build();
}

/**
* Gets the Component-ID.
*
* @return Component-ID
*/
public String getComponentId() {
return this.componentId;
}

}
Loading

0 comments on commit d311364

Please sign in to comment.