Implement a new Device to Backend

Hello ! :slight_smile:
I invented a new Meter Class but it does not show up in th UI of the Backend… why is this ?

On the Local UI it shows up but on the Backend side it is not shown - i see the Log and under Configuration i can see i aswell!

I tried to install it to the Backend via Package Installer but i get:

io.openems.common.channel,version=[1.0,2) from io.openems.common (22)
io.openems.common.exceptions,version=[1.0,2) from io.openems.common (22)
io.openems.edge.bridge.modbus.api,version=[1.0,1.1) – Cannot be resolved
io.openems.edge.bridge.modbus.api.element,version=[1.0,2) – Cannot be resolved
io.openems.edge.bridge.modbus.api.task,version=[1.0,2) – Cannot be resolved
io.openems.edge.common.channel,version=[1.0,2) – Cannot be resolved
io.openems.edge.common.channel.internal,version=[1.0,2) – Cannot be resolved
io.openems.edge.common.channel.value,version=[1.0,2) – Cannot be resolved
io.openems.edge.common.component,version=[1.0,2) – Cannot be resolved
io.openems.edge.common.modbusslave,version=[1.0,2) – Cannot be resolved
io.openems.edge.common.taskmanager,version=[1.0,2) – Cannot be resolved
io.openems.edge.meter.api,version=[1.0,1.1) – Cannot be resolved
java.lang from org.apache.felix.framework (0) – Overwritten by Boot Delegation
java.lang.annotation from org.apache.felix.framework (0) – Overwritten by Boot Delegation
java.lang.invoke from org.apache.felix.framework (0) – Overwritten by Boot Delegation
java.util from org.apache.felix.framework (0) – Overwritten by Boot Delegation
java.util.concurrent from org.apache.felix.framework (0) – Overwritten by Boot Delegation
java.util.function from org.apache.felix.framework (0) – Overwritten by Boot Delegation
org.osgi.service.cm,version=[1.6,2) from org.apache.felix.configadmin (43)
org.osgi.service.component,version=[1.5,2) from org.osgi.service.component (56)
org.slf4j,version=[2.0,3) – Cannot be resolved

Greetings

Hi,

this is strange, because Local UI and Backend UI are exactly the same code base, so there should not be any difference.

Is there any error message in the UI log? Without any further information it’s hard to debug the problem.

It is not required (nor possible) to install a Edge Component in Backend.

Regards,
Stefan

I will share the SourceCode of the Impl here… Maybe you could have a look…
I have 3 Problems in Summary :frowning:

  1. Meter is not shown in UI (only in Log) if i activate it.
  2. 2023-07-25T02:20:19,083 [_cycle ] INFO [ebuglog.ControllerDebugLogImpl] [ctrlDebugLog0] _sum[State:Ok Production:-65536000 W Consumption:-65536000 W] meter0[[ActivePower]: -65536000 W[L1 Volt]: 230000 mV[L2 Volt]: 230000 mV[L3 Volt]: 230000 mV[L1 Amp]: 230000 mA[L2 Amp]: 150000 mA[L3 Amp]: 150000 mA[Frequency]:200 mHz]

As you can see i get Production AND Consumption even the MeterType is Production…

package io.openems.edge.meter.test;


import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_1;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_1_AND_INVERT_IF_TRUE;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2;

import io.openems.common.channel.AccessMode;
import io.openems.common.exceptions.OpenemsException;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.api.task.FC4ReadInputRegistersTask;
import io.openems.edge.bridge.modbus.api.ModbusComponent;
import io.openems.edge.bridge.modbus.api.element.*;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.modbusslave.ModbusSlave;
import io.openems.edge.common.modbusslave.ModbusSlaveTable;
import io.openems.edge.common.taskmanager.Priority;
import io.openems.edge.meter.api.ElectricityMeter;
import io.openems.edge.meter.api.MeterType;
import io.openems.edge.meter.test.AbstractTestMeter;
import io.openems.edge.meter.test.TestMeter;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.osgi.service.metatype.annotations.Designate;


@Designate(ocd = Config.class, factory = true)
@Component(//
		name = "Meter.Test.Test", //
		immediate = true, //
		configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class TestImpl extends AbstractTestMeter implements TestMeter,
	ElectricityMeter, ModbusComponent, OpenemsComponent, ModbusSlave {

    private Config config;
    
	@Reference
	private ConfigurationAdmin cm;
	
	@Override
	@Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)
	protected void setModbus(BridgeModbus modbus) {
		super.setModbus(modbus);
	}


	public TestImpl() throws OpenemsException {
		super(//
				OpenemsComponent.ChannelId.values(), //
				ModbusComponent.ChannelId.values(), //
				ElectricityMeter.ChannelId.values(), //
				TestMeter.ChannelId.values() //
		);

		// Automatically calculate sum values from L1/L2/L3
		ElectricityMeter.calculateSumCurrentFromPhases(this);
		ElectricityMeter.calculateAverageVoltageFromPhases(this);
		ElectricityMeter.calculatePhasesFromActivePower(this);
	}

	
	@Activate
	private void activate(ComponentContext context, Config config) throws OpenemsException {
		if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm,
				"Modbus", config.modbus_id())) {
			return;
		}
		this.config = config;
		this.identifyTestMeter();
	}

	@Override
	@Deactivate
	protected void deactivate() {
		super.deactivate();
	}

	@Override
	public MeterType getMeterType() {
		return this.config.type();
	}
	


	@Override
	protected void identifiedTestMeter() throws OpenemsException {
		this.modbusProtocol.addTask(//
				new FC4ReadInputRegistersTask(5018, Priority.HIGH, //
						m(ElectricityMeter.ChannelId.VOLTAGE_L1, new UnsignedWordElement(5018), SCALE_FACTOR_2), //
						m(ElectricityMeter.ChannelId.VOLTAGE_L2, new UnsignedWordElement(5019), SCALE_FACTOR_2), //
						m(ElectricityMeter.ChannelId.VOLTAGE_L3, new UnsignedWordElement(5020), SCALE_FACTOR_2), //
						m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(5021), SCALE_FACTOR_2), //
						m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(5022), SCALE_FACTOR_2), //
						m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(5023), SCALE_FACTOR_2), //
						new DummyRegisterElement(5024, 5029), //
						m(ElectricityMeter.ChannelId.ACTIVE_POWER, new UnsignedDoublewordElement(5030),SCALE_FACTOR_1_AND_INVERT_IF_TRUE(this.config.invert())), //
						m(ElectricityMeter.ChannelId.REACTIVE_POWER, new SignedDoublewordElement(5032),SCALE_FACTOR_1_AND_INVERT_IF_TRUE(this.config.invert())), //
						new DummyRegisterElement(5034), //
						m(ElectricityMeter.ChannelId.FREQUENCY, new UnsignedWordElement(5035), SCALE_FACTOR_1)));
		}


	
	@Override
	public String debugLog() {
	    return "[ActivePower]: " + this.getActivePower().asString()
	        + "[L1 Volt]: " + this.getVoltageL1().asString()
	        + "[L2 Volt]: " + this.getVoltageL2().asString() 
	        + "[L3 Volt]: " + this.getVoltageL3().asString() 
	        + "[L1 Amp]: " + this.getCurrentL1().asString()
	        + "[L2 Amp]: " + this.getCurrentL2().asString() 
	        + "[L3 Amp]: " + this.getCurrentL3().asString()
	    	+ "[Frequency]:" + this.getFrequency().asString();
	}



	@Override
	public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) {
		return new ModbusSlaveTable(//
				OpenemsComponent.getModbusSlaveNatureTable(accessMode), //
				ElectricityMeter.getModbusSlaveNatureTable(accessMode) //
		);
	}}

Would be nice, if you could have a look and help me out :slight_smile:

@stefan.feilmeier Could you please have a look over it ? :slight_smile:

Greetings !

Hi @Sn0w3y,

I tried to understand the problem, but things look ok.

  1. Consumption is always a calculated value from Grid Buy/Sell, AC-Production and ESS Charge/Discharge. In your case Grid and ESS are 0 W, so Consumption = Production - that would be ok.

  2. I still don’t know why it is not shown in UI. Are you using the latest version of the UI, i.e. one, that already ‘knows’ about the ElectricityMeter?

Regards,
Stefan

Hello Stefan,

THATS indeed true, my Backend does not “know” about the Electricity Meter!

How do i install it on my Backend? I indeed know how to Upgrade the UI but not the Java Part as it throws a DB Hikari Pool Error on me if I use the latest Version (as described in one of my recent posts)

Would be great if you can tell me how to Update my Backend to the latest Version WITHOUT changing the database structure :slight_smile:

The Backend would not have to know about ElectrityMeter, but for the UI it’s important.

If you cannot update the UI entirely, you could at least cherry pick this PR: UI: add electricityMeter fallback for symmetricMeter by lukasrgr · Pull Request #2228 · OpenEMS/openems · GitHub

Sorry for the inconvenience… we are moving fast :smiley:

1 Like

Will try thanks! :slight_smile:

So if i understand correctly:

  • All the Modules of the Edges are represented in the UI if it knows of them ? :slight_smile:

Okay, i got it running now :slight_smile:

Next question:

  • The Production is shown as “Consumption” but in fact there is no “Consumption-Meter” there, so it should be sent to grid instead on the UI…

This is my Impl. so far, where i handle if the inverter is only a String-Inverter without a Battery:

@Override
protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
    if (isFeedGrid()) {
        // In feed-grid mode, read only production and grid feeding registers
        return new ModbusProtocol(this, new FC4ReadInputRegistersTask(4989, Priority.HIGH,
                // Read production registers
                m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY,
                        new UnsignedDoublewordElement(5003).wordOrder(WordOrder.LSWMSW),
                        ElementToChannelConverter.SCALE_FACTOR_3),
                new DummyRegisterElement(5004, 5029),
                // Read grid feeding registers
                m(ElectricityMeter.ChannelId.ACTIVE_POWER,
                        new UnsignedDoublewordElement(5030).wordOrder(WordOrder.LSWMSW))
        ));
    } else {

How do i fix this?

Yes, Edge sends a EdgeConfig object to UI, that holds information about all Natures, Components and Channels. This is used to dynamically build the UI.

This is not the way OpenEMS Edge works. OpenEMS always calculates the Consumption from measured Grid, Production and ESS. If any of those is not there, it is assumed to be zero. The code for this calculation is here: https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java#L359-L360C24

In your case, you would have to simulate a Grid-Meter. You can use Simulator.GridMeter.Reacting: https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImpl.java

Regards,
Stefan

1 Like

Perfect! I added a Simulator.Reactive.Meter in the Config and now it works - but the UI on the Backend looks empty under Production here:

Also the Phase-Values are not Displayed wether in the Edge nor in the Backend…

Most likely this still relates to your old version of OpenEMS UI, that is not yet compatible with ElectricityMeter.

1 Like

Sorry if i still need to bother you… but i still can not get behind this…

private void fetchAndUpdateEssRealtimeStatus() {
    try {
        // Fetch the necessary data from the API
        JsonObject necessaryData = samsungApi.getEssRealtimeStatus();

        // Populate the appropriate channels with the fetched data
        double PvPw = necessaryData.get("PvPw").getAsDouble();						//PV-Power in kW
        double AbsPcsPw = necessaryData.get("AbsPcsPw").getAsDouble() * 1000;		//Absolute Battery Power in kW
        double gridPw = necessaryData.get("GridPw").getAsDouble() * 1000;			//Grid Power in kW
        double PcsPw = necessaryData.get("PcsPw").getAsDouble();					//Battery Power in kW
        int consPw = (int) necessaryData.get("ConsPw").getAsDouble();				//Consume Power in kW
        int btSoc = necessaryData.get("BtSoc").getAsInt();							//SoC in %

        this._setAbsPcsPw(AbsPcsPw);
        this._setPvPw(PvPw);
        this._setGridPw(gridPw);
        this._setConsPw(consPw);
        this._setBtSoc(btSoc);
   	        
        this.channel(SymmetricEss.ChannelId.SOC).setNextValue(btSoc);
        
        
        
        this.channel(SymmetricEss.ChannelId.ACTIVE_POWER).setNextValue(PcsPw);
        
        this.channel(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY).setNextValue(consPw);
               		
        this.channel(ElectricityMeter.ChannelId.ACTIVE_POWER).setNextValue(gridPw);
        
        
        
    } catch (OpenemsNamedException e) {
        this.logError(log, "Failed to fetch ESS Real-time Status: " + e.getMessage());
    }
}

This code shows me this Debug Line:

28.8.2023, 01:19:50
INFO
io.openems.edge.controller.debuglog.ControllerDebugLogImpl
[ctrlDebugLog0] _sum[Core.Sum|State:Ok Ess SoC:51 %|L:40 W Consumption:40 W] ess0[SoC:51 %|L:40 W]

but in fact its these values:

{
“ESSRealtimeStatus”:{
“ColecTm”:“20230828012026”,
“PowerOutletPw”:“0”,
“GridPw”:0.04,
“UnitPrice”:0.00,
“ConsPw”:0.30,
“BtSoc”:51,
“PcsPw”:259.00,
“AbsPcsPw”:0.26,
“PvPw”:0.00,
“GridStusCd”:“0”,
“BtStusCd”:“0”,
“BtPw”:0.30,
“OperStusCd”:“0”,
“EmsOpMode”:“0”,
“RankPer”:0,
“ErrorCnt”:0
}
}

What’s the exact problem?

If I understand it correctly:

  • you are setting channel values on component ess0.
  • State-of-Charge (SOC) is coming from JSON as "BtSoc":51 and result in 51 % for ess0/Soc
  • Active Power (ACTIVE_POWER) is coming from JSON as "GridPw":0.04; you convert kW to W by multiplying with 1000, resulting in 40 W for ess0/ActivePower
  • You configured only ess0, there is no meter.

Consumption is calculated using this formula: (-> https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java#L359-L360)

this._setConsumptionActivePower(TypeUtils.sum(essActivePowerSum, gridActivePowerSum, productionAcActivePowerSum));

  • essActivePowerSum is 40
  • gridActivePowerSum is null (because there is no grid meter configured)
  • productionAcActivePowerSum is null (because there is no production meter configured)

TypeUtils.sum() calculates 40 + null + null as 40
As a result 40 is then set as _sum/ConsumptionActivePower.

Ok?

Regards,
Stefan

1 Like