Ess doesn't show up in UI

Hello everybody,

I have a problem similar to this old post: PV-Inverter does not show up in UI
I tried using the advice from there but it didn’t work so I am wiriting this new post.

I build a OpenEMS component for controlling a battery inverter (and the Inverter controlls the connected Battery, so in OpenEMS terms it is an Ess). I did this without using the natures implemented in OpenEMS but created my own because the implemented ones didn’t have all the channeld I needed.
Now I am trying to get it to be displayed on the UI. What I need are the SOC and the current dis-/charging power of the battery.
From what I understand from the liked post and the code all I need for that is to implement the SymmetricEss API

That is what I did. And I am setting values into all the channels that this API provides (static values for testing right now, but that shouldn’t matter).
The DebugLog did change:


It shows me the SOC and power I set, but still nothing in the UI.

Is there anything else I need to do?
From what I understand (which is not much at the moment) the UI finds the Ess components using the factoryId, is it necessary to change anything there?
(I created the component using the modus component template and didn’t change anything regarding the factoryId)

I am gratefull for any advise!

Best Regards,

Tobi

Hi Tobi,

your approach is ok. It would help a lot, if you could share your code.

It should be sufficient for your “Ess”-Component to implement the SymmetricEss Java interface, just like e.g. Simulated Ess does:

Regards,
Stefan

Hi Stefan,

thanks for your answer.
But I think I am doing that already, so it should work.
This is the code of the Impl.java (I am not allowed so show you the whole code by uploading it to a GitHub repo)

package io.openems.edge.batteryinverter.kaco.bluestorage;

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.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.openems.common.exceptions.OpenemsException;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent;
import io.openems.edge.bridge.modbus.api.BridgeModbus;
import io.openems.edge.bridge.modbus.api.ElementToChannelConverter;
import io.openems.edge.bridge.modbus.api.ElementToChannelScaleFactorConverter;
import io.openems.edge.bridge.modbus.api.ModbusComponent;
import io.openems.edge.bridge.modbus.api.ElementToChannelConverterChain;
import io.openems.edge.bridge.modbus.api.ElementToChannelOffsetConverter;
import io.openems.edge.bridge.modbus.api.ModbusProtocol;
import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement;
import io.openems.edge.bridge.modbus.api.element.SignedDoublewordElement;
import io.openems.edge.bridge.modbus.api.element.SignedWordElement;
import io.openems.edge.bridge.modbus.api.element.StringWordElement;
import io.openems.edge.bridge.modbus.api.element.UnsignedDoublewordElement;
import io.openems.edge.bridge.modbus.api.element.UnsignedWordElement;
import io.openems.edge.bridge.modbus.api.task.FC16WriteRegistersTask;
import io.openems.edge.bridge.modbus.api.task.FC4ReadInputRegistersTask;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.cycle.Cycle;
import io.openems.edge.common.event.EdgeEventConstants;
import io.openems.edge.common.sum.GridMode;
import io.openems.edge.common.taskmanager.Priority;
import io.openems.edge.batteryinverter.api.HIUBatteryInverter;
import io.openems.edge.batteryinverter.kaco.bluestorage.statemachine.Context;
import io.openems.edge.batteryinverter.kaco.bluestorage.statemachine.StateMachine;
import io.openems.edge.batteryinverter.kaco.bluestorage.statemachine.StateMachine.State;
import io.openems.edge.ess.api.SymmetricEss;



@Designate(ocd = Config.class, factory = true)
@Component(//
		name = "Battery-Inverter KACO Bluestorage", //
		immediate = true, //
		configurationPolicy = ConfigurationPolicy.REQUIRE, //
		property = {//
				EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_BEFORE_WRITE, //
				EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE //
		}
)
public class KacoBluestorageImpl extends AbstractOpenemsModbusComponent 
	implements KacoBluestorage, OpenemsComponent, ModbusComponent, HIUBatteryInverter, EventHandler, SymmetricEss {

	private Config config = null;
	private final Logger log = LoggerFactory.getLogger(KacoBluestorageImpl.class);
	
	private State[] previousState =  {State.UNDEFINED, State.UNDEFINED};
	
	public enum ManualState {
		DISCONNECTED,
		STANDBY,
		RUN
	}

	@Reference
	protected Cycle cycle;
	
	
	/**
	 * Manages the {@link State}s of the StateMachine.
	 * Gives initial state
	 */
	private final StateMachine stateMachine = new StateMachine(State.UNDEFINED);
	
	/**
	 * Class for applying the power limit and damp factor
	 */
	private final PowerLimitAndDamping powerLimitAndDamping = new PowerLimitAndDamping(this);
	

	public KacoBluestorageImpl() {
		super(//
				OpenemsComponent.ChannelId.values(), //
				ModbusComponent.ChannelId.values(), //
				HIUBatteryInverter.ChannelId.values(), //
				KacoBluestorage.ChannelId.values(), //
				SymmetricEss.ChannelId.values() //
		);
	}

	@Reference
	protected ConfigurationAdmin cm;

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

	@Activate
	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;
		
	}

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

	/**
	 * Writes and reads all necessary modbus registers. 
	 * <p>
	 * Gets executed by the system at every cycle parallel and asynchronous to the data processing.
	 * Synchronization at the end of the cycle
	 * <p>
	 * For the values with scale factor the scale factor get applied right when reading (if the scale factor would be applied later, 
	 * it would case a delay on one cycle until the values are available) <br>
	 * For that the scale factor gets read from its channel. So in case of an switch in the scale factor, for one cycle the old, wrong scale factor will be applied.
	 */
	@Override
	protected ModbusProtocol defineModbusProtocol() throws OpenemsException {
		return new ModbusProtocol(this,
				// starts with writing the values given by the controller and state machine
				new FC16WriteRegistersTask(152,
						m(HIUBatteryInverter.ChannelId.p_ac_dcacbatt_target, new UnsignedWordElement(152),
								ElementToChannelConverter.DIRECT_1_TO_1)),
				new FC16WriteRegistersTask(158,
						m(HIUBatteryInverter.ChannelId.run_batt_system, new UnsignedWordElement(158),
								ElementToChannelConverter.DIRECT_1_TO_1)),
				
				// KACO C001
				new FC4ReadInputRegistersTask(20, Priority.HIGH,
						m(HIUBatteryInverter.ChannelId.Model_dcacbatt_raw, new StringWordElement(20, 16)),
						new DummyRegisterElement(36, 43),
						m(HIUBatteryInverter.ChannelId.SW_version_dcacbatt_raw, new StringWordElement(44, 8), 
								ElementToChannelConverter.DIRECT_1_TO_1)),
				
				// KACO I103
				new FC4ReadInputRegistersTask(72, Priority.HIGH,
						m(HIUBatteryInverter.ChannelId.i_tot_ac_dcacbatt_act, new UnsignedWordElement(72), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.A_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.i_l1_ac_dcacbatt_act, new UnsignedWordElement(73), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.A_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.i_l2_ac_dcacbatt_act, new UnsignedWordElement(74), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.A_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.i_l3_ac_dcacbatt_act, new UnsignedWordElement(75), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.A_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.A_scale_factor_raw, new SignedWordElement(76), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						new DummyRegisterElement(77, 79),
						m(HIUBatteryInverter.ChannelId.u_l1_ac_dcacbatt_act, new UnsignedWordElement(80),
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.V_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.u_l2_ac_dcacbatt_act, new UnsignedWordElement(81), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.V_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.u_l3_ac_dcacbatt_act, new UnsignedWordElement(82), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.V_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.V_scale_factor_raw, new SignedWordElement(83), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.p_ac_dcacbatt_act, new UnsignedWordElement(84), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.W_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.W_scale_factor_raw, new SignedWordElement(85), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.freq_ac_dcacbatt_act, new UnsignedWordElement(86), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.Hz_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.Hz_scale_factor_raw, new SignedWordElement(87), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						new DummyRegisterElement(88, 93),
						m(HIUBatteryInverter.ChannelId.e_tot_dcacbatt_act, new SignedDoublewordElement(94), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.WH_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.WH_scale_factor_raw, new SignedWordElement(96), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						new DummyRegisterElement(97, 102),
						m(HIUBatteryInverter.ChannelId.temp_cab_dcacbatt_act, new UnsignedWordElement(103),
								new ElementToChannelConverterChain(new ElementToChannelScaleFactorConverter(
										this, HIUBatteryInverter.ChannelId.Tmp_scale_factor_raw),
										new ElementToChannelOffsetConverter(273))),
						new DummyRegisterElement(104, 106),
						m(HIUBatteryInverter.ChannelId.Tmp_scale_factor_raw, new SignedWordElement(107), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.status_dcacbatt, new UnsignedWordElement(108), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						new DummyRegisterElement(109),
						m(HIUBatteryInverter.ChannelId.error_dcacbatt, new UnsignedDoublewordElement(110), 
								ElementToChannelConverter.DIRECT_1_TO_1)),
				
				// KACO IC120
				new FC4ReadInputRegistersTask(125, Priority.HIGH,
						m(HIUBatteryInverter.ChannelId.p_dcacbatt_nom, new UnsignedWordElement(125), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.WRtg_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.WRtg_scale_factor_raw, new SignedWordElement(126), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.s_dcacbatt_nom, new UnsignedWordElement(127), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.VARtg_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.VARtg_scale_factor_raw, new SignedWordElement(128), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						new DummyRegisterElement(129, 133),
						m(HIUBatteryInverter.ChannelId.i_rms_dcacbatt_nom, new UnsignedWordElement(134), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.ARtg_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.ARtg_scale_factor_raw, new SignedWordElement(135), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						new DummyRegisterElement(136, 140),
						m(HIUBatteryInverter.ChannelId.e_batt_nom, new UnsignedWordElement(141), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.WHRtg_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.WHRtg_scale_factor_raw, new SignedWordElement(142), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.capa_batt_nom, new UnsignedWordElement(143), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.AhrRtg_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.AhrRtg_scale_factor_raw, new SignedWordElement(144), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.p_batt_chg_max_nom, new UnsignedWordElement(145), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.MaxChaRte_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.MaxChaRte_scale_factor_raw, new SignedWordElement(146), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.p_batt_dis_max_nom, new UnsignedWordElement(147), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.MaxDisChaRte_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.MaxDisChaRte_scale_factor_raw, new SignedWordElement(148), 
								ElementToChannelConverter.DIRECT_1_TO_1)),
				
				// KACO IC121
				new FC4ReadInputRegistersTask (152, Priority.HIGH,
						m(HIUBatteryInverter.ChannelId.p_ac_dcacbatt_target, new UnsignedWordElement(152), 
								ElementToChannelConverter.SCALE_FACTOR_MINUS_2),
						new DummyRegisterElement(153, 157),
						m(HIUBatteryInverter.ChannelId.run_batt_system, new UnsignedWordElement(158), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.status_batt_system, new UnsignedWordElement(159), 
								ElementToChannelConverter.DIRECT_1_TO_1)),
				
				// KACO IC124
				new FC4ReadInputRegistersTask (166, Priority.HIGH,
						m(HIUBatteryInverter.ChannelId.p_batt_chg_max_act, new UnsignedWordElement(166), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.Wmax_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.p_batt_dis_max_act, new UnsignedWordElement(167), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.Wmax_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.soc_batt_act, new UnsignedWordElement(168), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.SOPct_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.soh_batt_act, new UnsignedWordElement(169), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.SOPct_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.u_batt_chg_act, new UnsignedWordElement(170), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.BatV_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.u_batt_dis_act, new UnsignedWordElement(171), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.BatV_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.u_batt_act, new UnsignedWordElement(172), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.BatV_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.i_batt_act, new SignedWordElement(173), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.BatA_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.u_cell_max_batt_act, new UnsignedWordElement(174), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.CellV_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.u_cell_min_batt_act, new UnsignedWordElement(175), 
								new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.CellV_scale_factor_raw)),
						m(HIUBatteryInverter.ChannelId.temp_cell_max_batt_act, new UnsignedWordElement(176), 
								new ElementToChannelConverterChain(new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.Tmp2_scale_factor_raw), 
										 new ElementToChannelOffsetConverter(273))),
						m(HIUBatteryInverter.ChannelId.temp_cell_min_batt_act, new UnsignedWordElement(177), 
								new ElementToChannelConverterChain(new ElementToChannelScaleFactorConverter(this, HIUBatteryInverter.ChannelId.Tmp2_scale_factor_raw), 
										 new ElementToChannelOffsetConverter(273))),
						m(HIUBatteryInverter.ChannelId.status_batt, new UnsignedWordElement(178), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						new DummyRegisterElement(179),
						m(HIUBatteryInverter.ChannelId.warning_batt, new UnsignedDoublewordElement(180), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.error_batt, new UnsignedDoublewordElement(182), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.Wmax_scale_factor_raw, new SignedWordElement(184), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.SOPct_scale_factor_raw, new SignedWordElement(185), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.BatV_scale_factor_raw, new SignedWordElement(186), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.BatA_scale_factor_raw, new SignedWordElement(187), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.CellV_scale_factor_raw, new SignedWordElement(188), 
								ElementToChannelConverter.DIRECT_1_TO_1),
						m(HIUBatteryInverter.ChannelId.Tmp2_scale_factor_raw, new SignedWordElement(189), 
								ElementToChannelConverter.DIRECT_1_TO_1))
				);
	}
	

	/**
	 * Gets executed by the system at every cycle parallel and asynchronous to modbus communication <br>
	 * Handles the data processing and the state machine for the inverter
	 * <p>
	 * The processing of the values gets done at {@link EdgeEventConstants#TOPIC_CYCLE_BEFORE_PROCESS_IMAGE} <br>
	 * Which means right before the channels values get set for the next cycle. This way the values are as new as possible
	 * <p>
	 * The Inverter State machine gets executed at {@link EdgeEventConstants#TOPIC_CYCLE_BEFORE_WRITE} <br>
	 * Which means after all the controllers and right before the write values get written. This way the calculated values get written right away.
	 */
	@Override
	public void handleEvent(Event event) {
		if (!this.isEnabled()) {
			return;
		}
		switch (event.getTopic()) {
		case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE:
			// Processing of the _raw values gotten from Battery-Inverter
			// processes values get written in channels for other components so use
			this.processRawValues();
			
			// writing dummy values into the symmetricEss channels for testing with UI
//			this._setSoc(Math.round(this.get_soc_batt_act().get()));
			this._setSoc(50);
			this._setCapacity(76000);
			this._setGridMode(GridMode.UNDEFINED);
			this._setActivePower(1000);
			this._setReactivePower(0);
			this._setMaxApparentPower(60000);
			this._setActiveChargeEnergy(0);
			this._setActiveDischargeEnergy(0);
			this._setMaxCellVoltage(4);
			this._setMinCellVoltage(3);
			this._setMaxCellTemperature(300);
			this._setMinCellTemperature(200);
			break;
			
		case EdgeEventConstants.TOPIC_CYCLE_BEFORE_WRITE:
			// Check if power target from Controller is within the Limits and apply a damping factor if necessary
			int powerTargetChecked = powerLimitAndDamping.checkValue(this.getNextPowerValueUnchecked(), cycle.getCycleTime());
			
			// Prepare Context for state machine
			// always use powerTragetchecked as transfer parameter. In manual mode this Value doesn't get used and the manual power target from config is used
			Context context = new Context(this, this.config, this.get_status_batt_system().get(), powerTargetChecked, previousState[1], cycle.getCycleTime(), this.getNextControllerStateRequest());
			System.out.println("state requested by controller: " + this.getNextControllerStateRequest());
			
			// Calling the state machine
			// Inside the state machine the write Values for p_ac_dcacbatt_target and run_batt_system get set.
			try {
				this.stateMachine.run(context);
				
				// saving the state for next iteration
				// needs to be an array of to values, because the state machine goes through 2 states when is runs:
				// run: state -> runAndGetNextSate -> new state
				previousState[1] = previousState[0];
				previousState[0] = this.stateMachine.getCurrentState();
				
				// setting the write Values
				if (config.manualInput()) {
					// In case of manual Input set the manually given power target
					// applying scale factor (hardcoded because it doesn't change)
					this.setNext_p_ac_dcacbatt_target((int) (this.config.manualPowerTarget() * Math.pow(10, -2)));
					// writing the value in a channel for the database component to read
					this.set_p_ac_dcacbatt_target_set(this.config.manualPowerTarget());
					System.out.println("Battery Inverter power target write: " + this.config.manualPowerTarget());
				}
				else {
					// else use the target from the limit controller
					// applying scale factor (hardcoded because it doesn't change)
					this.setNext_p_ac_dcacbatt_target((int) (powerTargetChecked * Math.pow(10, -2)));
					// writing the value in a channel for the database component to read
					this.set_p_ac_dcacbatt_target_set(powerTargetChecked);
					System.out.println("Battery Inverter power target write: " + powerTargetChecked);
					}
				this.setNext_run_batt_system(this.getSetState());
				// writing the value in a channel for the database component to read
				this.set_run_batt_system_set(this.getSetState());
				System.out.println("Battery Inverter send state: " + this.getSetState());
			} catch (OpenemsNamedException e) {
				this.logError(log, "State Machine Error: " + e.getMessage());
				
				// if state machine failed always set power target to 0 and state to fault
				try {
					setNext_run_batt_system(7);
					setNext_p_ac_dcacbatt_target(0);
				} catch (OpenemsNamedException e1) {
					e1.printStackTrace();
				} 
			}
			catch (IllegalArgumentException e) {
				e.getMessage();
				// if state machine failed always set power target to 0 and state to fault
				try {
					setNext_run_batt_system(7);
					setNext_p_ac_dcacbatt_target(0);
				} catch (OpenemsNamedException e1) {
					e1.printStackTrace();
				} 
			}
			
			break;
		}
	}
	
	/**
	 * Strings new to be processes to correct the character order, because the get read wrong <br>
	 * (I didn't find a way to fix the reading)
	 */
	private void processRawValues() {
		
		// gets the read out value (as a string, not a value object)
		// applies fixChaarcterOrder function
		// and sets that fixed String as next Value for the channel (which can be read by other components)
		this.setNext_Model_dcacbatt(fixCharacterOrder(this.get_Model_dcacbatt_raw().asString()));
		this.setNext_SW_version_dcacbatt(fixCharacterOrder(this.get_SW_version_dcacbatt_raw().asString()));
	}
	
	/**
	 * fixes the character order of a String, if always the to consecutive characters are switched <br>
	 * e.g. turns String 21436587 into 12345678
	 */
	private String fixCharacterOrder (String stringWrongOrder) {
		// turn string into char array to be able to switch characters individually
		char[] charWrongOrder = stringWrongOrder.toCharArray();
		
		// create new char array of same size to safe fixed order 
		char[] charWrightOrder = new char[charWrongOrder.length];
		
		// changes the order of the to consecutive characters
		for (int i = 1; i < (charWrongOrder.length/2)+1; i++) {
			charWrightOrder[2*i-2] = charWrongOrder[2*i-1];
			charWrightOrder[2*i-1] = charWrongOrder[2*i-2];
		}
		String stringWrightOrder = new String(charWrightOrder);
		
		return stringWrightOrder;
	}
	
	@Override
	public String debugLog() {
		return ", V1: " + get_u_l1_ac_dcacbatt_act().get() + ", status_bat_system: " 
				+ get_status_batt_system().asString() + ", manual Input: " + this.config.manualInput() 
				+ ", e_tot_dcacbatt_act: " + get_e_tot_dcacbatt_act().asString() + ", temp_cab_dcacbatt_act: " 
				+ this.get_temp_cab_dcacbatt_act().get() + ", Model Number: " + this.get_Model_dcacbatt();
	}
	
}

And my UI looks like this:


(I also have some other meter components running)
The Ess doesn’t show up as you can see.

Is there any further advise you can give me?

Thanks in advance!

Tobi

Hi Tobi,

I finally found some time to check your implementation by recreating a bundle from your code. It turns out, that you should already get a Warning currently, that your component name is invalid:
Screenshot 2022-02-16 210555

It turns out, that OSGi does not allow spaces in the component name. Once you fix it, the ESS should show up in the UI - e.g. like this:

@Component(//
		name = "Battery-Inverter.KACO.Bluestorage", //
		immediate = true, //
		configurationPolicy = ConfigurationPolicy.REQUIRE, //
		property = { //
				EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_BEFORE_WRITE, //
				EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE //
		})

grafik


This is very unfortunate, because a pull-request against the official repository would give you the opportunity to get a proper review of the code by an experienced OpenEMS developer. There are some things, that are currently ‘fishy’ in your code, e.g.

  • Are you really implementing a SymmetricEss or rather a ManagedSymmetricBatteryInverter? If it is really a BatteryInverter you will save yourself a lot of implementation, by using the existing GenericEss.
  • You can leave out all the ElementToChannelConverter.DIRECT_1_TO_1 in ModbusProtocol. This is the default anyway
  • Use Priority wisely in the ModbusProtocol to speed up control and avoid unnecessary reads
  • Your comment is “Check if power target from Controller is within the Limits and apply a damping factor if necessary”. This will always be guaranteed if you get your MaxApparentPower, MaxChargePower, MaxDischargePower and possibly the getStaticConstraints() correct.
  • Manual ‘damping’ should not be required, There is a proper PID filter in place by default, that can be configured in the Power component.
  • and much more…

Apart from that of course every contribution helps to achive wider compatibilty for OpenEMS. Thanks for reconsidering your decision!

Regards,
Stefan

Hi Stefan,

thank you very much. It was indeed the name and now it works.
I have overlooked it in the warnings because I currently have a lot of those because my code isn’t finfished yet so I have a lot of “is never used” warnings.

You are right, looking back now it would have saved me a lot of time to use more of the already implemented features of OpenEMS. But I have to confess that I find it quite difficult to get a good overview of what all the components and APIs do and when I started this I unterstood even less. So it was easier to just create my own solutions for some things.
In addition to that I am using some code developed at my university (for example the damping algorithm) (I am doing this for my Bachelor thesis) and I have a few “special” requirements for the project so I sometimes had to create my own solutions.

This is also the reason why I can’t just share the whole code. Some of the algorithms should not be published. I hope you can understand that.

Best Regards,
Tobi

Hi Tobi,

I’m glad the problem is solved. I know that getting a full understanding of how all the components are connected with each other and how to proceed with implementation and configuration is very difficult in the beginning. I am happy to hear your suggestions based on your own experience on how we can improve the onboarding process and where and how the documentation should go more into depth (without being overwhelming… seeing how difficult it is already for many people to understand and follow the Getting Started guide).

I fully understand also that parts of your code cannot be published. This is exactly why we chose the Eclipse Public License for OpenEMS, as it explicitely allows keeping new developments closed source, e.g. to allow software-based business opportunities for companies/start-ups that want to focus on algorithms or driver implementation. The modular architecture allows exactly that: keep certain modules closed source (like your damping algorithm) and others (like a modbus implementation for a Battery Inverter) open source.

I just want to convince developers (you and anybody who reads this) to consider sharing all or parts of the code and creating pull-requests, because it makes it much easier for us to give useful hints on code improvements and it helps the OpenEMS project to achive wider adoption because of better compatibility and features.

Best of luck with your Thesis. If the Thesis itself can be published as open access I would be glad if you could share it with the OpenEMS community (we would for sure also promote it directly in the blog on openems.io) and I would love to read it myself.

Regards,
Stefan