package io.openems.edge.goodwe.common;

import static io.openems.common.utils.IntUtils.fitWithin;
import static io.openems.common.utils.IntUtils.maxInt;
import static io.openems.common.utils.IntUtils.minInteger;
import static io.openems.common.utils.IntUtils.sumInt;
import static io.openems.common.utils.IntUtils.sumInteger;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.INVERT;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_2;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_1;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SCALE_FACTOR_MINUS_2;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.SET_NULL_FOR_DEFAULT;
import static io.openems.edge.bridge.modbus.api.ElementToChannelConverter.chain;
import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementOnce;
import static io.openems.edge.bridge.modbus.api.ModbusUtils.readElementsOnce;
import static io.openems.edge.bridge.modbus.api.ModbusUtils.FunctionCode.FC3;
import static io.openems.edge.common.channel.ChannelUtils.setValue;
import static java.lang.Math.min;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.VisibleForTesting;

import io.openems.common.exceptions.OpenemsException;
import io.openems.common.types.OpenemsType;
import io.openems.common.utils.FunctionUtils;
import io.openems.edge.battery.api.Battery;
import io.openems.edge.bridge.modbus.api.AbstractOpenemsModbusComponent;
import io.openems.edge.bridge.modbus.api.ElementToChannelConverter;
import io.openems.edge.bridge.modbus.api.ModbusProtocol;
import io.openems.edge.bridge.modbus.api.ModbusUtils;
import io.openems.edge.bridge.modbus.api.element.BitsWordElement;
import io.openems.edge.bridge.modbus.api.element.DummyRegisterElement;
import io.openems.edge.bridge.modbus.api.element.FloatDoublewordElement;
import io.openems.edge.bridge.modbus.api.element.ModbusElement;
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.FC3ReadRegistersTask;
import io.openems.edge.bridge.modbus.api.task.Task;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.ChannelId.ChannelIdImpl;
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.channel.EnumReadChannel;
import io.openems.edge.common.channel.IntegerReadChannel;
import io.openems.edge.common.channel.internal.OpenemsTypeDoc;
import io.openems.edge.common.component.OpenemsComponent;
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.common.type.TypeUtils;
import io.openems.edge.ess.api.HybridEss;
import io.openems.edge.ess.api.SymmetricEss;
import io.openems.edge.goodwe.charger.GoodWeCharger;
import io.openems.edge.goodwe.charger.twostring.GoodWeChargerTwoString;
import io.openems.edge.goodwe.common.GoodWeStateDefinitions.GwState;
import io.openems.edge.goodwe.common.GoodWeStateDefinitions.GwStateTask;
import io.openems.edge.goodwe.common.enums.BatteryMode;
import io.openems.edge.goodwe.common.enums.EmsPowerMode;
import io.openems.edge.goodwe.common.enums.GoodWeType;
import io.openems.edge.goodwe.genset.GoodWeStsBoxGensetMeter;
import io.openems.edge.timedata.api.TimedataProvider;
import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower;

@SuppressWarnings("deprecation")
public abstract class AbstractGoodWe extends AbstractOpenemsModbusComponent
		implements GoodWe, OpenemsComponent, TimedataProvider, EventHandler {

	private static final Logger LOG = LoggerFactory.getLogger(AbstractGoodWe.class);
	private static final Map<Integer, GoodWe.ChannelId> DIAG_STATUS_H_STATES = Map.of(//
			0x00000001, GoodWe.ChannelId.DIAG_STATUS_BATTERY_PRECHARGE_RELAY_OFF, //
			0x00000002, GoodWe.ChannelId.DIAG_STATUS_BYPASS_RELAY_STICK, //
			0x10000000, GoodWe.ChannelId.DIAG_STATUS_METER_VOLTAGE_SAMPLE_FAULT, //
			0x20000000, GoodWe.ChannelId.DIAG_STATUS_EXTERNAL_STOP_MODE_ENABLE, //
			0x40000000, GoodWe.ChannelId.DIAG_STATUS_BATTERY_OFFGRID_DOD, //
			0x80000000, GoodWe.ChannelId.DIAG_STATUS_BATTERY_SOC_ADJUST_ENABLE);

	private final Logger log = LoggerFactory.getLogger(AbstractGoodWe.class);

	private final io.openems.edge.common.channel.ChannelId activePowerChannelId;
	private final io.openems.edge.common.channel.ChannelId reactivePowerChannelId;
	private final io.openems.edge.common.channel.ChannelId dcDischargePowerChannelId;
	private final CalculateEnergyFromPower calculateAcChargeEnergy;
	private final CalculateEnergyFromPower calculateAcDischargeEnergy;
	private final CalculateEnergyFromPower calculateDcChargeEnergy;
	private final CalculateEnergyFromPower calculateDcDischargeEnergy;

	protected final Set<GoodWeCharger> chargers = new HashSet<>();

	protected final ApplyPowerHandler applyPowerHandler = new ApplyPowerHandler(this);

	protected AbstractGoodWe(//
			io.openems.edge.common.channel.ChannelId activePowerChannelId, //
			io.openems.edge.common.channel.ChannelId reactivePowerChannelId, //
			io.openems.edge.common.channel.ChannelId dcDischargePowerChannelId, //
			io.openems.edge.common.channel.ChannelId activeChargeEnergyChannelId, //
			io.openems.edge.common.channel.ChannelId activeDischargeEnergyChannelId, //
			io.openems.edge.common.channel.ChannelId dcChargeEnergyChannelId, //
			io.openems.edge.common.channel.ChannelId dcDischargeEnergyChannelId, //
			io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, //
			io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) {
		super(firstInitialChannelIds, furtherInitialChannelIds);
		this.activePowerChannelId = activePowerChannelId;
		this.reactivePowerChannelId = reactivePowerChannelId;
		this.dcDischargePowerChannelId = dcDischargePowerChannelId;
		this.calculateAcChargeEnergy = new CalculateEnergyFromPower(this, activeChargeEnergyChannelId);
		this.calculateAcDischargeEnergy = new CalculateEnergyFromPower(this, activeDischargeEnergyChannelId);
		this.calculateDcChargeEnergy = new CalculateEnergyFromPower(this, dcChargeEnergyChannelId);
		this.calculateDcDischargeEnergy = new CalculateEnergyFromPower(this, dcDischargeEnergyChannelId);
	}

	@Override
	protected final ModbusProtocol defineModbusProtocol() {
		var protocol = new ModbusProtocol(this, //

				new FC3ReadRegistersTask(35001, Priority.LOW, //
						m(SymmetricEss.ChannelId.MAX_APPARENT_POWER, new UnsignedWordElement(35001)), //
						new DummyRegisterElement(35002), //
						m(GoodWe.ChannelId.SERIAL_NUMBER, new StringWordElement(35003, 8)) //
				),

				new FC3ReadRegistersTask(35016, Priority.LOW, //
						m(GoodWe.ChannelId.DSP_FM_VERSION_MASTER, new UnsignedWordElement(35016)), //
						m(GoodWe.ChannelId.DSP_FM_VERSION_SLAVE, new UnsignedWordElement(35017)), //
						m(GoodWe.ChannelId.DSP_BETA_VERSION, new UnsignedWordElement(35018)), //
						m(GoodWe.ChannelId.ARM_FM_VERSION, new UnsignedWordElement(35019)), //
						m(GoodWe.ChannelId.ARM_BETA_VERSION, new UnsignedWordElement(35020)) //
				),

				new FC3ReadRegistersTask(35111, Priority.LOW, //
						// Registers for PV1 and PV2 (35103 to 35110) are read via DC-Charger
						// implementation
						m(GoodWe.ChannelId.V_PV3, new UnsignedWordElement(35111),
								chain(SET_NULL_FOR_DEFAULT(0xFFFF), SCALE_FACTOR_MINUS_1)), //
						m(GoodWe.ChannelId.I_PV3, new UnsignedWordElement(35112), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.P_PV3, new UnsignedDoublewordElement(35113)), //
						m(GoodWe.ChannelId.V_PV4, new UnsignedWordElement(35115),
								chain(SET_NULL_FOR_DEFAULT(0xFFFF), SCALE_FACTOR_MINUS_1)), //
						m(GoodWe.ChannelId.I_PV4, new UnsignedWordElement(35116),
								chain(SET_NULL_FOR_DEFAULT(0xFFFF), SCALE_FACTOR_MINUS_1)), //
						m(GoodWe.ChannelId.P_PV4, new UnsignedDoublewordElement(35117),
								chain(SET_NULL_FOR_DEFAULT(0xFFFFFFFFL), SCALE_FACTOR_MINUS_1)), //

						m(GoodWe.ChannelId.PV_MODE, new UnsignedDoublewordElement(35119)), //
						// Registers for Grid Smart-Meter (35121 to 35135) are read via GridMeter
						// implementation
						new DummyRegisterElement(35121, 35135),
						m(SymmetricEss.ChannelId.GRID_MODE, new UnsignedWordElement(35136), //
								new ElementToChannelConverter(value -> {
									Integer intValue = TypeUtils.<Integer>getAsType(OpenemsType.INTEGER, value);
									final var goodWeType = this.getGoodweType();
									final var isGensetActive = this.getGensetOperatingMode().isDefined()
											&& this.getGensetOperatingMode().get();

									final var gridMode = mapGridMode(goodWeType, intValue, isGensetActive);
									setValue(this, GoodWe.ChannelId.GRID_MODE_FAULT, gridMode == GridMode.UNDEFINED);
									return gridMode;
								}))), //

				new FC3ReadRegistersTask(35137, Priority.LOW, //
						m(GoodWe.ChannelId.TOTAL_INV_POWER, new SignedDoublewordElement(35137)), //
						m(GoodWe.ChannelId.AC_ACTIVE_POWER, new SignedDoublewordElement(35139), //
								INVERT), //
						m(this.reactivePowerChannelId, new SignedDoublewordElement(35141), //
								INVERT), //
						m(GoodWe.ChannelId.AC_APPARENT_POWER, new SignedDoublewordElement(35143), //
								INVERT), //
						new DummyRegisterElement(35145, 35147), //
						m(GoodWe.ChannelId.LOAD_MODE_R, new UnsignedWordElement(35148)), //
						new DummyRegisterElement(35149, 35153), //
						m(GoodWe.ChannelId.LOAD_MODE_S, new UnsignedWordElement(35154)), //
						new DummyRegisterElement(35155, 35159), //
						m(GoodWe.ChannelId.LOAD_MODE_T, new UnsignedWordElement(35160)), //
						new DummyRegisterElement(35161, 35162), //
						m(GoodWe.ChannelId.P_LOAD_R, new SignedDoublewordElement(35163)), //
						m(GoodWe.ChannelId.P_LOAD_S, new SignedDoublewordElement(35165)), //
						m(GoodWe.ChannelId.P_LOAD_T, new SignedDoublewordElement(35167)), //
						m(GoodWe.ChannelId.TOTAL_BACK_UP_LOAD_POWER, new SignedDoublewordElement(35169)), //
						m(GoodWe.ChannelId.TOTAL_LOAD_POWER, new SignedDoublewordElement(35171)), //
						m(GoodWe.ChannelId.UPS_LOAD_PERCENT, new UnsignedWordElement(35173), SCALE_FACTOR_MINUS_2), //
						m(GoodWe.ChannelId.AIR_TEMPERATURE, new SignedWordElement(35174), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MODULE_TEMPERATURE, new SignedWordElement(35175), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.RADIATOR_TEMPERATURE, new SignedWordElement(35176), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.FUNCTION_BIT_VALUE, new UnsignedWordElement(35177)), //
						m(GoodWe.ChannelId.BUS_VOLTAGE, new UnsignedWordElement(35178), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.NBUS_VOLTAGE, new UnsignedWordElement(35179), SCALE_FACTOR_MINUS_1)), //

				new FC3ReadRegistersTask(35180, Priority.HIGH, //
						m(GoodWe.ChannelId.V_BATTERY1, new UnsignedWordElement(35180), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.I_BATTERY1, new SignedWordElement(35181), SCALE_FACTOR_MINUS_1), //
						// Required for calculation of ActivePower; wrongly documented in official
						// Modbus protocol v1.9 as being Unsigned.
						m(GoodWe.ChannelId.P_BATTERY1, new SignedDoublewordElement(35182)),
						m(GoodWe.ChannelId.BATTERY_MODE, new UnsignedWordElement(35184))), //

				new FC3ReadRegistersTask(35186, Priority.LOW, //
						m(GoodWe.ChannelId.SAFETY_COUNTRY, new UnsignedWordElement(35186)), //
						m(GoodWe.ChannelId.WORK_MODE, new UnsignedWordElement(35187)), //
						new DummyRegisterElement(35188), //
						m(new BitsWordElement(35189, this) //
								.bit(0, GoodWe.ChannelId.STATE_16) //
								.bit(1, GoodWe.ChannelId.STATE_17) //
								.bit(2, GoodWe.ChannelId.STATE_18) //
								.bit(3, GoodWe.ChannelId.STATE_19) //
								.bit(4, GoodWe.ChannelId.STATE_20) //
								.bit(5, GoodWe.ChannelId.STATE_21) //
								.bit(6, GoodWe.ChannelId.STATE_22) //
								.bit(7, GoodWe.ChannelId.STATE_23) //
								.bit(8, GoodWe.ChannelId.STATE_24) //
								.bit(9, GoodWe.ChannelId.STATE_25) //
								.bit(10, GoodWe.ChannelId.STATE_26) //
								.bit(11, GoodWe.ChannelId.STATE_27) //
								.bit(12, GoodWe.ChannelId.STATE_28) //
								.bit(13, GoodWe.ChannelId.STATE_29) //
								.bit(14, GoodWe.ChannelId.STATE_30) //
								.bit(15, GoodWe.ChannelId.STATE_31) //
						), //
						m(new BitsWordElement(35190, this) //
								.bit(0, GoodWe.ChannelId.STATE_0) //
								.bit(1, GoodWe.ChannelId.STATE_1) //
								.bit(2, GoodWe.ChannelId.STATE_2) //
								.bit(3, GoodWe.ChannelId.STATE_3) //
								.bit(4, GoodWe.ChannelId.STATE_4) //
								.bit(5, GoodWe.ChannelId.STATE_5) //
								.bit(6, GoodWe.ChannelId.STATE_6) //
								.bit(7, GoodWe.ChannelId.STATE_7) //
								.bit(8, GoodWe.ChannelId.STATE_8) //
								.bit(9, GoodWe.ChannelId.STATE_9) //
								.bit(10, GoodWe.ChannelId.STATE_10) //
								.bit(11, GoodWe.ChannelId.STATE_11) //
								.bit(12, GoodWe.ChannelId.STATE_12) //
								.bit(13, GoodWe.ChannelId.STATE_13)//
								.bit(14, GoodWe.ChannelId.STATE_14)//
								.bit(15, GoodWe.ChannelId.STATE_15)//
						), //

						// The total PV production energy from installation
						m(GoodWe.ChannelId.PV_E_TOTAL, new UnsignedDoublewordElement(35191), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.PV_E_DAY, new UnsignedDoublewordElement(35193), SCALE_FACTOR_MINUS_1), //
						new DummyRegisterElement(35195, 35196), //
						m(GoodWe.ChannelId.H_TOTAL, new UnsignedDoublewordElement(35197)), //
						m(GoodWe.ChannelId.E_DAY_SELL, new UnsignedWordElement(35199), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_TOTAL_BUY, new UnsignedDoublewordElement(35200), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_DAY_BUY, new UnsignedWordElement(35202), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_TOTAL_LOAD, new UnsignedDoublewordElement(35203), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_LOAD_DAY, new UnsignedWordElement(35205), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_BATTERY_CHARGE, new UnsignedDoublewordElement(35206), //
								SCALE_FACTOR_2), //
						m(GoodWe.ChannelId.E_CHARGE_DAY, new UnsignedWordElement(35208), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_BATTERY_DISCHARGE, new UnsignedDoublewordElement(35209), SCALE_FACTOR_2), //
						m(GoodWe.ChannelId.E_DISCHARGE_DAY, new UnsignedWordElement(35211), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_STRINGS, new UnsignedWordElement(35212)), //
						m(GoodWe.ChannelId.CPLD_WARNING_CODE, new UnsignedWordElement(35213)), //
						new DummyRegisterElement(35214, 35217), //
						new UnsignedDoublewordElement(35218).onUpdateCallback(code -> detectDiagStatesH(code) //
								.forEach((channel, value) -> this.channel(channel).setNextValue(value))),

						m(new BitsWordElement(35220, this) //
								.bit(0, GoodWe.ChannelId.DIAG_STATUS_BMS_OVER_TEMPERATURE)//
								.bit(1, GoodWe.ChannelId.DIAG_STATUS_BMS_OVERCHARGE)//
								.bit(2, GoodWe.ChannelId.DIAG_STATUS_BMS_CHARGE_DISABLE)//
								.bit(3, GoodWe.ChannelId.DIAG_STATUS_SELF_USE_OFF)//
								.bit(4, GoodWe.ChannelId.DIAG_STATUS_SOC_DELTA_OVER_RANGE)//
								.bit(5, GoodWe.ChannelId.DIAG_STATUS_BATTERY_SELF_DISCHARGE)//
								.bit(6, GoodWe.ChannelId.DIAG_STATUS_OFFGRID_SOC_LOW)//
								.bit(7, GoodWe.ChannelId.DIAG_STATUS_GRID_WAVE_UNSTABLE)//
								.bit(8, GoodWe.ChannelId.DIAG_STATUS_FEED_POWER_LIMIT)//
								.bit(9, GoodWe.ChannelId.DIAG_STATUS_PF_VALUE_SET)//
								.bit(10, GoodWe.ChannelId.DIAG_STATUS_REAL_POWER_LIMIT)//
								.bit(12, GoodWe.ChannelId.DIAG_STATUS_SOC_PROTECT_OFF)), //

						m(new BitsWordElement(35221, this) //
								.bit(0, GoodWe.ChannelId.DIAG_STATUS_BATTERY_VOLT_LOW)//
								.bit(1, GoodWe.ChannelId.DIAG_STATUS_BATTERY_SOC_LOW)//
								.bit(2, GoodWe.ChannelId.DIAG_STATUS_BATTERY_SOC_IN_BACK)//
								.bit(3, GoodWe.ChannelId.DIAG_STATUS_BMS_DISCHARGE_DISABLE)//
								.bit(4, GoodWe.ChannelId.DIAG_STATUS_DISCHARGE_TIME_ON)//
								.bit(5, GoodWe.ChannelId.DIAG_STATUS_CHARGE_TIME_ON)//
								.bit(6, GoodWe.ChannelId.DIAG_STATUS_DISCHARGE_DRIVE_ON)//
								.bit(7, GoodWe.ChannelId.DIAG_STATUS_BMS_DISCHG_CURRENT_LOW)//
								.bit(8, GoodWe.ChannelId.DIAG_STATUS_DISCHARGE_CURRENT_LOW)//
								.bit(9, GoodWe.ChannelId.DIAG_STATUS_METER_COMM_LOSS)//
								.bit(10, GoodWe.ChannelId.DIAG_STATUS_METER_CONNECT_REVERSE)//
								.bit(11, GoodWe.ChannelId.DIAG_STATUS_SELF_USE_LOAD_LIGHT)//
								.bit(12, GoodWe.ChannelId.DIAG_STATUS_EMS_DISCHARGE_IZERO)//
								.bit(13, GoodWe.ChannelId.DIAG_STATUS_DISCHARGE_BUS_HIGH)//
								.bit(14, GoodWe.ChannelId.DIAG_STATUS_BATTERY_DISCONNECT)//
								.bit(15, GoodWe.ChannelId.DIAG_STATUS_BATTERY_OVERCHARGE)), //

						new DummyRegisterElement(35222, 35224), //
						m(GoodWe.ChannelId.EH_BATTERY_FUNCTION_ACTIVE, new UnsignedWordElement(35225)), //
						m(GoodWe.ChannelId.ARC_SELF_CHECK_STATUS, new UnsignedWordElement(35226)) //
				),

				new FC3ReadRegistersTask(35250, Priority.LOW, //

						/*
						 * Table 8-30 Grid Detailed WARNING (35250 - 35253, U64)
						 */
						new DummyRegisterElement(35250, 35251), //
						m(new BitsWordElement(35252, this) //
								.bit(0, GoodWe.ChannelId.STATE_86) //
								.bit(1, GoodWe.ChannelId.STATE_87) //
								.bit(2, GoodWe.ChannelId.STATE_88) //
								.bit(3, GoodWe.ChannelId.STATE_89) //
								.bit(4, GoodWe.ChannelId.STATE_90) //
								.bit(5, GoodWe.ChannelId.STATE_91) //
								.bit(6, GoodWe.ChannelId.STATE_92) //
								.bit(7, GoodWe.ChannelId.STATE_93) //
						), //
						m(new BitsWordElement(35253, this) //
								.bit(0, GoodWe.ChannelId.STATE_70) //
								.bit(1, GoodWe.ChannelId.STATE_71) //
								.bit(2, GoodWe.ChannelId.STATE_72) //
								.bit(3, GoodWe.ChannelId.STATE_73) //
								.bit(4, GoodWe.ChannelId.STATE_74) //
								.bit(5, GoodWe.ChannelId.STATE_75) //
								.bit(6, GoodWe.ChannelId.STATE_76) //
								.bit(7, GoodWe.ChannelId.STATE_77) //
								.bit(8, GoodWe.ChannelId.STATE_78) //
								.bit(9, GoodWe.ChannelId.STATE_79) //
								.bit(10, GoodWe.ChannelId.STATE_80) //
								.bit(11, GoodWe.ChannelId.STATE_81) //
								.bit(12, GoodWe.ChannelId.STATE_82) //
								.bit(13, GoodWe.ChannelId.STATE_83) //
								.bit(14, GoodWe.ChannelId.STATE_84) //
								.bit(15, GoodWe.ChannelId.STATE_85)), //

						/*
						 * Table 8-31 Inverter detailed error (35254 - 35257, U64)
						 */
						new DummyRegisterElement(35254, 35255), //
						m(new BitsWordElement(35256, this) //
								.bit(0, GoodWe.ChannelId.STATE_110) //
								.bit(1, GoodWe.ChannelId.STATE_111) //
								.bit(2, GoodWe.ChannelId.STATE_112) //
								.bit(3, GoodWe.ChannelId.STATE_113) //
								.bit(4, GoodWe.ChannelId.STATE_114) //
								.bit(5, GoodWe.ChannelId.STATE_115) //
								.bit(6, GoodWe.ChannelId.STATE_116) //
						), //
						m(new BitsWordElement(35257, this) //
								.bit(0, GoodWe.ChannelId.STATE_94) //
								.bit(1, GoodWe.ChannelId.STATE_95) //
								.bit(2, GoodWe.ChannelId.STATE_96) //
								.bit(3, GoodWe.ChannelId.STATE_97) //
								.bit(4, GoodWe.ChannelId.STATE_98) //
								.bit(5, GoodWe.ChannelId.STATE_99) //
								.bit(6, GoodWe.ChannelId.STATE_100) //
								.bit(7, GoodWe.ChannelId.STATE_101) //
								.bit(8, GoodWe.ChannelId.STATE_102) //
								.bit(9, GoodWe.ChannelId.STATE_103) //
								.bit(10, GoodWe.ChannelId.STATE_104) //
								.bit(11, GoodWe.ChannelId.STATE_105) //
								.bit(12, GoodWe.ChannelId.STATE_106) //
								.bit(13, GoodWe.ChannelId.STATE_107) //
								.bit(14, GoodWe.ChannelId.STATE_108) //
								.bit(15, GoodWe.ChannelId.STATE_109)), //

						/*
						 * Table 8-32 Inverter detailed status (35258 - 35261, U64)
						 */
						new DummyRegisterElement(35258, 35259), //
						m(new BitsWordElement(35260, this) //
								.bit(0, GoodWe.ChannelId.STATE_133) //
								.bit(1, GoodWe.ChannelId.STATE_134) //
								.bit(2, GoodWe.ChannelId.STATE_135) //
								.bit(3, GoodWe.ChannelId.STATE_136) //
								.bit(4, GoodWe.ChannelId.STATE_137) //
						), //
						m(new BitsWordElement(35261, this) //
								.bit(0, GoodWe.ChannelId.STATE_117) //
								.bit(1, GoodWe.ChannelId.STATE_118) //
								.bit(2, GoodWe.ChannelId.STATE_119) //
								.bit(3, GoodWe.ChannelId.STATE_120) //
								.bit(4, GoodWe.ChannelId.STATE_121) //
								.bit(5, GoodWe.ChannelId.STATE_122) //
								.bit(6, GoodWe.ChannelId.STATE_123) //
								.bit(7, GoodWe.ChannelId.STATE_124) //
								.bit(8, GoodWe.ChannelId.STATE_125) //
								.bit(9, GoodWe.ChannelId.STATE_126) //
								.bit(10, GoodWe.ChannelId.STATE_127) //
								.bit(11, GoodWe.ChannelId.STATE_128) //
								.bit(12, GoodWe.ChannelId.STATE_129) //
								.bit(13, GoodWe.ChannelId.STATE_130) //
								.bit(14, GoodWe.ChannelId.STATE_131) //
								.bit(15, GoodWe.ChannelId.STATE_132))), //

				new FC3ReadRegistersTask(35262, Priority.HIGH, //
						m(GoodWe.ChannelId.V_BATTERY2, new UnsignedWordElement(35262), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.I_BATTERY2, new SignedWordElement(35263), SCALE_FACTOR_MINUS_1), //
						// Required for calculation of ActivePower; wrongly documented in official
						// Modbus protocol v1.9 as being Unsigned.
						m(GoodWe.ChannelId.P_BATTERY2, new SignedDoublewordElement(35264)),
						m(GoodWe.ChannelId.BATTERY2_MODE, new UnsignedWordElement(35266))), //

				new FC3ReadRegistersTask(35262, Priority.LOW, //
						new DummyRegisterElement(35262, 35267), //
						m(GoodWe.ChannelId.MAX_GRID_FREQ_WITHIN_1_MINUTE, new UnsignedWordElement(35268),
								SCALE_FACTOR_MINUS_2), //
						m(GoodWe.ChannelId.MIN_GRID_FREQ_WITHIN_1_MINUTE, new UnsignedWordElement(35269),
								SCALE_FACTOR_MINUS_2), //
						m(GoodWe.ChannelId.MAX_GRID_VOLTAGE_WITHIN_1_MINUTE_R, new UnsignedWordElement(35270),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MIN_GRID_VOLTAGE_WITHIN_1_MINUTE_R, new UnsignedWordElement(35271),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MAX_GRID_VOLTAGE_WITHIN_1_MINUTE_S, new UnsignedWordElement(35272),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MIN_GRID_VOLTAGE_WITHIN_1_MINUTE_S, new UnsignedWordElement(35273),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MAX_GRID_VOLTAGE_WITHIN_1_MINUTE_T, new UnsignedWordElement(35274),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MIN_GRID_VOLTAGE_WITHIN_1_MINUTE_T, new UnsignedWordElement(35275),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MAX_BACKUP_POWER_WITHIN_1_MINUTE_R, new UnsignedDoublewordElement(35276)), //
						m(GoodWe.ChannelId.MAX_BACKUP_POWER_WITHIN_1_MINUTE_S, new UnsignedDoublewordElement(35278)), //
						m(GoodWe.ChannelId.MAX_BACKUP_POWER_WITHIN_1_MINUTE_T, new UnsignedDoublewordElement(35280)), //
						m(GoodWe.ChannelId.MAX_BACKUP_POWER_WITHIN_1_MINUTE_TOTAL,
								new UnsignedDoublewordElement(35282)), //
						m(GoodWe.ChannelId.GRID_HVRT_EVENT_TIMES, new UnsignedWordElement(35284)), //
						m(GoodWe.ChannelId.GRID_LVRT_EVENT_TIMES, new UnsignedWordElement(35285)), //
						m(GoodWe.ChannelId.INV_ERROR_MSG_RECORD_FOR_EMS, new UnsignedDoublewordElement(35286)), //
						m(GoodWe.ChannelId.INV_WARNING_CODE_RECORD_FOR_EMS, new UnsignedDoublewordElement(35288)), //
						m(GoodWe.ChannelId.INV_CPLD_WARNING_RECORD_FOR_EMS, new UnsignedDoublewordElement(35290)) //
				),

				// Registers 36066 to 36120 throw "Illegal Data Address"

				// 37000 - DRM Status - DRED only for Australia
				new FC3ReadRegistersTask(37000, Priority.LOW, //
						m(new BitsWordElement(37000, this) //
								.bit(0, GoodWe.ChannelId.DRM0)//
								.bit(1, GoodWe.ChannelId.DRM1)//
								.bit(2, GoodWe.ChannelId.DRM2)//
								.bit(3, GoodWe.ChannelId.DRM3)//
								.bit(4, GoodWe.ChannelId.DRM4)//
								.bit(5, GoodWe.ChannelId.DRM5)//
								.bit(6, GoodWe.ChannelId.DRM6)//
								.bit(7, GoodWe.ChannelId.DRM7)//
								.bit(8, GoodWe.ChannelId.DRM8)//
								.bit(15, GoodWe.ChannelId.DRED_CONNECT)//
						), //
						m(GoodWe.ChannelId.BATTERY_TYPE_INDEX, new UnsignedWordElement(37001)), //
						m(GoodWe.ChannelId.BMS_STATUS, new UnsignedWordElement(37002)), //
						m(GoodWe.ChannelId.BMS_PACK_TEMPERATURE, new UnsignedWordElement(37003), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BMS_CHARGE_IMAX, new UnsignedWordElement(37004)), //
						m(GoodWe.ChannelId.BMS_DISCHARGE_IMAX, new UnsignedWordElement(37005)), //
						m(new BitsWordElement(37006, this) //
								.bit(0, GoodWe.ChannelId.STATE_42)//
								.bit(1, GoodWe.ChannelId.STATE_43)//
								.bit(2, GoodWe.ChannelId.STATE_44)//
								.bit(3, GoodWe.ChannelId.STATE_45)//
								.bit(4, GoodWe.ChannelId.STATE_46)//
								.bit(5, GoodWe.ChannelId.STATE_47)//
								.bit(6, GoodWe.ChannelId.STATE_48)//
								.bit(7, GoodWe.ChannelId.STATE_49)//
								.bit(8, GoodWe.ChannelId.STATE_50)//
								.bit(9, GoodWe.ChannelId.STATE_51)//
								.bit(10, GoodWe.ChannelId.STATE_52)//
								.bit(11, GoodWe.ChannelId.STATE_53)//
								.bit(12, GoodWe.ChannelId.STATE_54)//
								.bit(13, GoodWe.ChannelId.STATE_55)//
								.bit(14, GoodWe.ChannelId.STATE_56)//
								.bit(15, GoodWe.ChannelId.STATE_57)//
						), //
						this.getSocModbusElement(37007), //
						m(GoodWe.ChannelId.BMS_SOH, new UnsignedWordElement(37008)), //
						m(GoodWe.ChannelId.BMS_BATTERY_STRINGS, new UnsignedWordElement(37009)), //
						m(new BitsWordElement(37010, this) //
								.bit(0, GoodWe.ChannelId.STATE_58)//
								.bit(1, GoodWe.ChannelId.STATE_59)//
								.bit(2, GoodWe.ChannelId.STATE_60)//
								.bit(3, GoodWe.ChannelId.STATE_61)//
								.bit(4, GoodWe.ChannelId.STATE_62)//
								.bit(5, GoodWe.ChannelId.STATE_63)//
								.bit(6, GoodWe.ChannelId.STATE_64)//
								.bit(7, GoodWe.ChannelId.STATE_65)//
								.bit(8, GoodWe.ChannelId.STATE_66)//
								.bit(9, GoodWe.ChannelId.STATE_67)//
								.bit(10, GoodWe.ChannelId.STATE_68)//
								.bit(11, GoodWe.ChannelId.STATE_69)//
						), //
						m(GoodWe.ChannelId.BATTERY_PROTOCOL, new UnsignedWordElement(37011)), //
						// TODO BMS_ERROR_CODE_H register 37012 Table 8-7 BMS Alarm Code bits Bit16-31
						// are reserved
						// TODO Same for BMS_WARNING_CODE_H Table 8-8
						new DummyRegisterElement(37012, 37013), //
						m(GoodWe.ChannelId.BMS_SOFTWARE_VERSION, new UnsignedWordElement(37014)), //
						m(GoodWe.ChannelId.BATTERY_HARDWARE_VERSION, new UnsignedWordElement(37015)), //
						m(GoodWe.ChannelId.MAXIMUM_CELL_TEMPERATURE_ID, new UnsignedWordElement(37016)), //
						m(GoodWe.ChannelId.MINIMUM_CELL_TEMPERATURE_ID, new UnsignedWordElement(37017)), //
						m(GoodWe.ChannelId.MAXIMUM_CELL_VOLTAGE_ID, new UnsignedWordElement(37018)), //
						m(GoodWe.ChannelId.MINIMUM_CELL_VOLTAGE_ID, new UnsignedWordElement(37019)), //
						m(GoodWe.ChannelId.MAXIMUM_CELL_TEMPERATURE, new UnsignedWordElement(37020),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MINIMUM_CELL_TEMPERATURE, new UnsignedWordElement(37021),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MAXIMUM_CELL_VOLTAGE, new UnsignedWordElement(37022)), //
						m(GoodWe.ChannelId.MINIMUM_CELL_VOLTAGE, new UnsignedWordElement(37023)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_1, new UnsignedWordElement(37024)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_2, new UnsignedWordElement(37025)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_3, new UnsignedWordElement(37026)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_4, new UnsignedWordElement(37027)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_5, new UnsignedWordElement(37028)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_6, new UnsignedWordElement(37029)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_7, new UnsignedWordElement(37030)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_8, new UnsignedWordElement(37031)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_9, new UnsignedWordElement(37032)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_10, new UnsignedWordElement(37033)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_11, new UnsignedWordElement(37034)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_12, new UnsignedWordElement(37035)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_13, new UnsignedWordElement(37036)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_14, new UnsignedWordElement(37037)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_15, new UnsignedWordElement(37038)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_16, new UnsignedWordElement(37039)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_17, new UnsignedWordElement(37040)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_18, new UnsignedWordElement(37041)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_19, new UnsignedWordElement(37042)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_20, new UnsignedWordElement(37043)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_21, new UnsignedWordElement(37044)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_22, new UnsignedWordElement(37045)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_23, new UnsignedWordElement(37046)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_24, new UnsignedWordElement(37047)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_25, new UnsignedWordElement(37048)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_26, new UnsignedWordElement(37049)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_27, new UnsignedWordElement(37050)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_28, new UnsignedWordElement(37051)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_29, new UnsignedWordElement(37052)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_30, new UnsignedWordElement(37053)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_31, new UnsignedWordElement(37054)), //
						m(GoodWe.ChannelId.PASS_INFORMATION_32, new UnsignedWordElement(37055))), //

				// Registers 40000 to 42011 for BTC and ETC throw "Illegal Data Address"

				// Setting and Controlling Data Registers
				new FC3ReadRegistersTask(45127, Priority.LOW, //
						m(GoodWe.ChannelId.INVERTER_UNIT_ID, new UnsignedWordElement(45127)), //
						new DummyRegisterElement(45128, 45131), //
						m(GoodWe.ChannelId.MODBUS_BAUDRATE, new UnsignedDoublewordElement(45132))), //

				new FC3ReadRegistersTask(45222, Priority.LOW, //
						// to read or write the accumulated energy battery discharged, of the day Not
						// from BMS
						m(GoodWe.ChannelId.PV_E_TOTAL_2, new UnsignedDoublewordElement(45222), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.PV_E_DAY_2, new UnsignedDoublewordElement(45224), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_TOTAL_SELL_2, new UnsignedDoublewordElement(45226), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.H_TOTAL_2, new UnsignedDoublewordElement(45228)), //
						m(GoodWe.ChannelId.E_DAY_SELL_2, new UnsignedWordElement(45230), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_TOTAL_BUY_2, new UnsignedDoublewordElement(45231), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_DAY_BUY_2, new UnsignedWordElement(45233), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_TOTAL_LOAD_2, new UnsignedDoublewordElement(45234), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_LOAD_DAY_2, new UnsignedWordElement(45236), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_BATTERY_CHARGE_2, new UnsignedDoublewordElement(45237),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_CHARGE_DAY_2, new UnsignedWordElement(45239), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_BATTERY_DISCHARGE_2, new UnsignedDoublewordElement(45240),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_DISCHARGE_DAY_2, new UnsignedWordElement(45242), SCALE_FACTOR_MINUS_1), //
						new DummyRegisterElement(45243), //
						// to set safety code for inverter or read the preset safety code for the
						// inverter
						m(GoodWe.ChannelId.SAFETY_COUNTRY_CODE, new UnsignedWordElement(45244)), //
						m(GoodWe.ChannelId.ISO_LIMIT, new UnsignedWordElement(45245)), //
						m(GoodWe.ChannelId.LVRT_HVRT, new UnsignedWordElement(45246))), //

				new FC3ReadRegistersTask(45250, Priority.LOW, //
						m(GoodWe.ChannelId.PV_START_VOLTAGE, new UnsignedWordElement(45250), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.MPPT_FOR_SHADOW_ENABLE, new UnsignedWordElement(45251)), //
						m(GoodWe.ChannelId.BACK_UP_ENABLE, new UnsignedWordElement(45252)), //
						m(GoodWe.ChannelId.AUTO_START_BACKUP, new UnsignedWordElement(45253)), //
						m(GoodWe.ChannelId.WAVE_FORM_DETECTION, new UnsignedWordElement(45254)), //
						new DummyRegisterElement(45255), //
						m(GoodWe.ChannelId.BACKUP_START_DLY, new UnsignedWordElement(45256)), //
						m(GoodWe.ChannelId.UPS_STD_VOLT_TYPE, new UnsignedWordElement(45257)), //
						new DummyRegisterElement(45258, 45262), //
						// Only can set 70, only for German
						m(GoodWe.ChannelId.DERATE_RATE_VDE, new UnsignedWordElement(45263)), //
						// this function is deactivated as default, set "1" to activate. After
						// activated, All power needs to be turned off and restarted
						m(GoodWe.ChannelId.THREE_PHASE_UNBALANCED_OUTPUT, new UnsignedWordElement(45264)), //
						new DummyRegisterElement(45265), //
						// For weak grid area
						m(GoodWe.ChannelId.HIGH_IMP_MODE, new UnsignedWordElement(45266)), //
						new DummyRegisterElement(45267, 45274), //
						// 0:Normal mode 1: cancel ISO test when offgrid to ongrid
						m(GoodWe.ChannelId.ISO_CHECK_MODE, new UnsignedWordElement(45275)), //
						// The delay time when grid is available
						m(GoodWe.ChannelId.OFF_GRID_TO_ON_GRID_DELAY, new UnsignedWordElement(45276)), //
						// If set 80%, when offgrid output voltage less than 230*80%=184V, inverter will
						// have the error.
						m(GoodWe.ChannelId.OFF_GRID_UNDER_VOLTAGE_PROTECT_COEFFICIENT, new UnsignedWordElement(45277)), //
						// When offgrid and the battery SOC is low, PV charge the battery
						m(GoodWe.ChannelId.BATTERY_MODE_PV_CHARGE_ENABLE, new UnsignedWordElement(45278)), //
						// Default fisresttt.ing is 1
						m(GoodWe.ChannelId.DCV_CHECK_OFF, new UnsignedWordElement(45279))//

				), //

				// Registers 45333 to 45339 for License throw "Illegal Data Address"

				new FC3ReadRegistersTask(45350, Priority.LOW, //
						m(GoodWe.ChannelId.BMS_CAPACITY, new UnsignedWordElement(45350)), //
						m(GoodWe.ChannelId.BMS_STRINGS, new UnsignedWordElement(45351)), //
						m(GoodWe.ChannelId.BMS_CHARGE_MAX_VOLTAGE, new UnsignedWordElement(45352),
								SCALE_FACTOR_MINUS_1), // [500*N,600*N]
						m(GoodWe.ChannelId.BMS_CHARGE_MAX_CURRENT, new UnsignedWordElement(45353),
								SCALE_FACTOR_MINUS_1), // [0,1000]
						m(GoodWe.ChannelId.BMS_DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(45354),
								SCALE_FACTOR_MINUS_1), // [400*N,480*N]
						m(GoodWe.ChannelId.BMS_DISCHARGE_MAX_CURRENT, new UnsignedWordElement(45355),
								SCALE_FACTOR_MINUS_1), // [0,1000]
						m(GoodWe.ChannelId.BMS_SOC_UNDER_MIN, new UnsignedWordElement(45356)), // [0,100]
						m(GoodWe.ChannelId.BMS_OFFLINE_DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(45357),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BMS_OFFLINE_SOC_UNDER_MIN, new UnsignedWordElement(45358))), //

				new FC3ReadRegistersTask(45374, Priority.LOW, //
						m(GoodWe.ChannelId.BATTERY_2_ENABLE, new UnsignedWordElement(45374)), //
						m(GoodWe.ChannelId.BATTERY_2_CAPACITY, new UnsignedWordElement(45375)), //
						m(GoodWe.ChannelId.BATTERY_2_STRINGS, new UnsignedWordElement(45376)), //
						m(GoodWe.ChannelId.BATTERY_2_CHARGE_VOLTAGE_MAX, new UnsignedWordElement(45377),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_CHARGE_CURRENT_MAX, new UnsignedWordElement(45378),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_VOLTAGE_UNDER_MIN, new UnsignedWordElement(45379),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_DISCHARGE_CURRENT_MAX, new UnsignedWordElement(45380),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_SOC_UNDER_MIN, new UnsignedWordElement(45381)), //
						m(GoodWe.ChannelId.BATTERY_2_OFFLINE_VOLTAGE_UNDER_MIN, new UnsignedWordElement(45382),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_OFFLINE_SOC_UNDER_MIN, new UnsignedWordElement(45383)) //
				), //

				new FC16WriteRegistersTask(45374, //
						m(GoodWe.ChannelId.BATTERY_2_ENABLE, new UnsignedWordElement(45374)), //
						m(GoodWe.ChannelId.BATTERY_2_CAPACITY, new UnsignedWordElement(45375)), //
						m(GoodWe.ChannelId.BATTERY_2_STRINGS, new UnsignedWordElement(45376)), //
						m(GoodWe.ChannelId.BATTERY_2_CHARGE_VOLTAGE_MAX, new UnsignedWordElement(45377),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_CHARGE_CURRENT_MAX, new UnsignedWordElement(45378),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_VOLTAGE_UNDER_MIN, new UnsignedWordElement(45379),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_DISCHARGE_CURRENT_MAX, new UnsignedWordElement(45380),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_SOC_UNDER_MIN, new UnsignedWordElement(45381)), //
						m(GoodWe.ChannelId.BATTERY_2_OFFLINE_VOLTAGE_UNDER_MIN, new UnsignedWordElement(45382),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BATTERY_2_OFFLINE_SOC_UNDER_MIN, new UnsignedWordElement(45383)) //
				), //

				new FC3ReadRegistersTask(47500, Priority.LOW, //
						m(GoodWe.ChannelId.STOP_SOC_PROTECT, new UnsignedWordElement(47500)), //
						m(GoodWe.ChannelId.BMS_FLOAT_VOLT, new UnsignedWordElement(47501), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BMS_FLOAT_CURRENT, new UnsignedWordElement(47502)), //
						m(GoodWe.ChannelId.BMS_FLOAT_TIME, new UnsignedWordElement(47503)), //
						m(GoodWe.ChannelId.BMS_TYPE_INDEX_ARM, new UnsignedWordElement(47504)), //
						m(GoodWe.ChannelId.MANUFACTURE_CODE, new UnsignedWordElement(47505)), //
						m(GoodWe.ChannelId.DC_VOLT_OUTPUT, new UnsignedWordElement(47506)), //
						m(GoodWe.ChannelId.BMS_AVG_CHG_VOLT, new UnsignedWordElement(47507), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.BMS_AVG_CHG_HOURS, new UnsignedWordElement(47508)), //
						m(GoodWe.ChannelId.FEED_POWER_ENABLE, new UnsignedWordElement(47509)), //
						m(GoodWe.ChannelId.FEED_POWER_PARA_SET, new UnsignedWordElement(47510)), //
						new DummyRegisterElement(47511, 47512), //
						m(GoodWe.ChannelId.BMS_CURR_LMT_COFF, new UnsignedWordElement(47513)), //
						m(GoodWe.ChannelId.BATTERY_PROTOCOL_ARM, new UnsignedWordElement(47514)), //

						m(GoodWe.ChannelId.WORK_WEEK_1_START_TIME, new UnsignedWordElement(47515)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_END_TIME, new UnsignedWordElement(47516)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_BAT_POWER_PERCENT, new UnsignedWordElement(47517)), //
						m(new BitsWordElement(47518, this)//
								.bit(0, GoodWe.ChannelId.WORK_WEEK_1_SUNDAY)//
								.bit(1, GoodWe.ChannelId.WORK_WEEK_1_MONDAY)//
								.bit(2, GoodWe.ChannelId.WORK_WEEK_1_TUESDAY)//
								.bit(3, GoodWe.ChannelId.WORK_WEEK_1_WEDNESDAY)//
								.bit(4, GoodWe.ChannelId.WORK_WEEK_1_THURSDAY)//
								.bit(5, GoodWe.ChannelId.WORK_WEEK_1_FRIDAY)//
								.bit(6, GoodWe.ChannelId.WORK_WEEK_1_SATURDAY)//
								.bit(7, GoodWe.ChannelId.WORK_WEEK_1_NA)//
								.bit(8, GoodWe.ChannelId.WORK_WEEK_1_ENABLED)//
						)), //

				new FC16WriteRegistersTask(45216, //
						// Choose "Warehouse" safety code first and then Set "1" to factory settings
						m(GoodWe.ChannelId.FACTORY_SETTING, new UnsignedWordElement(45216)), //
						// Reset inverter accumulated data like E-total, E-day, error log running data
						// etc
						m(GoodWe.ChannelId.CLEAR_DATA, new UnsignedWordElement(45217)), //
						new DummyRegisterElement(45218, 45219), //
						// Inverter will re-check and reconnect to utility again. Inverter does not
						// shutdown
						m(GoodWe.ChannelId.RESTART, new UnsignedWordElement(45220)), //
						// inverter will total shutdown and wake up again
						m(GoodWe.ChannelId.RESET_SPS, new UnsignedWordElement(45221)), //
						m(GoodWe.ChannelId.PV_E_TOTAL_2, new UnsignedDoublewordElement(45222), SCALE_FACTOR_MINUS_1), //
						// to read or write the total PV production energy of the day
						m(GoodWe.ChannelId.PV_E_DAY_2, new UnsignedDoublewordElement(45224), SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated exporting energy to grid from the
						// installation date
						m(GoodWe.ChannelId.E_TOTAL_SELL_2, new UnsignedDoublewordElement(45226), SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated operation hours from the installation date
						m(GoodWe.ChannelId.H_TOTAL_2, new UnsignedDoublewordElement(45228)), //
						// to read or write the accumulated exporting energy to grid of the day
						m(GoodWe.ChannelId.E_DAY_SELL_2, new UnsignedWordElement(45230), SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated energy imported from grid from the
						// installation date
						m(GoodWe.ChannelId.E_TOTAL_BUY_2, new UnsignedDoublewordElement(45231), SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated energy imported from grid of the day
						m(GoodWe.ChannelId.E_DAY_BUY_2, new UnsignedWordElement(45233), SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated load consumption energy from the
						// installation date, not include backup load.
						m(GoodWe.ChannelId.E_TOTAL_LOAD_2, new UnsignedDoublewordElement(45234), SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated load consumption energy of the day Not
						// include backup loads
						m(GoodWe.ChannelId.E_LOAD_DAY_2, new UnsignedWordElement(45236), SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated energy charged to battery from the
						// installation date Not from BMS
						m(GoodWe.ChannelId.E_BATTERY_CHARGE_2, new UnsignedDoublewordElement(45237),
								SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated energy charged to battery of the day Not
						// from BMS
						m(GoodWe.ChannelId.E_CHARGE_DAY_2, new UnsignedWordElement(45239), SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated energy battery discharged, from the
						// installation date Not from BMS
						m(GoodWe.ChannelId.E_BATTERY_DISCHARGE_2, new UnsignedDoublewordElement(45240),
								SCALE_FACTOR_MINUS_1), //
						// to read or write the accumulated energy battery discharged, of the day Not
						// from BMS
						m(GoodWe.ChannelId.E_DISCHARGE_DAY_2, new UnsignedWordElement(45242), SCALE_FACTOR_MINUS_1), //
						new DummyRegisterElement(45243), //
						// to set safety code for inverter or read the preset safety code for the
						// inverter
						m(GoodWe.ChannelId.SAFETY_COUNTRY_CODE, new UnsignedWordElement(45244)), //
						// default 100 kilo Ohm, to read or set Isolation protection threshold for the
						// inverter
						m(GoodWe.ChannelId.ISO_LIMIT, new UnsignedWordElement(45245)), //
						// as default is deactivated, set "1" to activate LVRT function, Set "2" to
						// activate HVRT, The same as 45499
						m(GoodWe.ChannelId.LVRT_HVRT, new UnsignedWordElement(45246))).debug(), //

				new FC16WriteRegistersTask(45250, //
						// to write or read the start up PV voltage of the inverter.Please refer to the
						// user manual
						m(GoodWe.ChannelId.PV_START_VOLTAGE, new UnsignedWordElement(45250), SCALE_FACTOR_MINUS_1), //
						// as default is deactivated, set "1" to activate "Shadow Scan" function
						m(GoodWe.ChannelId.MPPT_FOR_SHADOW_ENABLE, new UnsignedWordElement(45251)), //
						// as default is deactivated, set "1" to activate "Shadow Scan" function
						m(GoodWe.ChannelId.BACK_UP_ENABLE, new UnsignedWordElement(45252)), //
						// Off-Grid Auto startup, as default is deactivated, set "1" to activate "Shadow
						// Scan" function
						m(GoodWe.ChannelId.AUTO_START_BACKUP, new UnsignedWordElement(45253)), //
						// As default is "0"
						m(GoodWe.ChannelId.WAVE_FORM_DETECTION, new UnsignedWordElement(45254)), //
						new DummyRegisterElement(45255), //
						// Default is 1500 (30s)
						m(GoodWe.ChannelId.BACKUP_START_DLY, new UnsignedWordElement(45256)), //
						m(GoodWe.ChannelId.UPS_STD_VOLT_TYPE, new UnsignedWordElement(45257)), //
						new DummyRegisterElement(45258, 45262), //
						// Only can set 70, only for German
						m(GoodWe.ChannelId.DERATE_RATE_VDE, new UnsignedWordElement(45263)), //
						// This function is deactivated as default, set "1" to activate. After
						// activated, All power needs to be turned off and restarted
						m(GoodWe.ChannelId.THREE_PHASE_UNBALANCED_OUTPUT, new UnsignedWordElement(45264)), //
						new DummyRegisterElement(45265), //
						// For weak grid area
						m(GoodWe.ChannelId.HIGH_IMP_MODE, new UnsignedWordElement(45266)), //
						new DummyRegisterElement(45267, 45270), //
						// only for inverters with AFCI function
						m(GoodWe.ChannelId.ARC_SELF_CHECK, new UnsignedWordElement(45271)), //
						// only for inverters with AFCI function
						m(GoodWe.ChannelId.ARC_FAULT_REMOVE, new UnsignedWordElement(45272)), //
						new DummyRegisterElement(45273, 45274), //
						// 0:Normal mode 1: cancel ISO test when offgrid to ongrid
						m(GoodWe.ChannelId.ISO_CHECK_MODE, new UnsignedWordElement(45275)), //
						// The delay time when grid is available
						m(GoodWe.ChannelId.OFF_GRID_TO_ON_GRID_DELAY, new UnsignedWordElement(45276)), //
						// If set 80%, when offgrid output voltage less than 230*80%=184V, inverter will
						// have the error.Default setting is
						m(GoodWe.ChannelId.OFF_GRID_UNDER_VOLTAGE_PROTECT_COEFFICIENT, new UnsignedWordElement(45277)), //
						// When offgrid and the battery SOC is low, PV charge the battery
						m(GoodWe.ChannelId.BATTERY_MODE_PV_CHARGE_ENABLE, new UnsignedWordElement(45278)), //
						// Default setting 1, [1,20]
						m(GoodWe.ChannelId.DCV_CHECK_OFF, new UnsignedWordElement(45279))), //

				// These registers is to set the protection parameters on battery
				// charge/discharge operation ON INVERTER SIDE. The real
				// operation will still follow battery BMS limitations (or registers
				// 47900~47916) if it is not out of the range.Eg. Set BattChargeCurrMax (45353)
				// as 25A, but battery BMS limit the max charge current as 20A, then the battery
				// charge at max 20A. but if battery BMS limit max charge current as 50A,then
				// the real charge current of the battery will exceed 25A.
				new FC16WriteRegistersTask(45350, //
						m(GoodWe.ChannelId.BMS_CAPACITY, new UnsignedWordElement(45350)), //
						m(GoodWe.ChannelId.BMS_STRINGS, new UnsignedWordElement(45351)), //
						m(GoodWe.ChannelId.BMS_CHARGE_MAX_VOLTAGE, new UnsignedWordElement(45352),
								SCALE_FACTOR_MINUS_1), // [500*N,600*N]
						m(GoodWe.ChannelId.BMS_CHARGE_MAX_CURRENT, new UnsignedWordElement(45353),
								SCALE_FACTOR_MINUS_1), // [0,1000]
						m(GoodWe.ChannelId.BMS_DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(45354),
								SCALE_FACTOR_MINUS_1), // [400*N,480*N]
						m(GoodWe.ChannelId.BMS_DISCHARGE_MAX_CURRENT, new UnsignedWordElement(45355),
								SCALE_FACTOR_MINUS_1), // [0,1000]
						m(GoodWe.ChannelId.BMS_SOC_UNDER_MIN, new UnsignedWordElement(45356)), // [0,100]
						m(GoodWe.ChannelId.BMS_OFFLINE_DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(45357),
								SCALE_FACTOR_MINUS_1), // ), //
						m(GoodWe.ChannelId.BMS_OFFLINE_SOC_UNDER_MIN, new UnsignedWordElement(45358))), //

				new FC16WriteRegistersTask(47500, //
						// Internal GoodWe Battery Protection
						m(GoodWe.ChannelId.STOP_SOC_PROTECT, new UnsignedWordElement(47500))), //

				new FC16WriteRegistersTask(47505, //
						// If using EMS, must set to "2"
						m(GoodWe.ChannelId.MANUFACTURE_CODE, new UnsignedWordElement(47505)), //
						new DummyRegisterElement(47506, 47508), //
						m(GoodWe.ChannelId.FEED_POWER_ENABLE, new UnsignedWordElement(47509)), //
						m(GoodWe.ChannelId.FEED_POWER_PARA_SET, new SignedWordElement(47510)) //
				), //

				new FC16WriteRegistersTask(47514, //
						m(GoodWe.ChannelId.BATTERY_PROTOCOL_ARM, new UnsignedWordElement(47514)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_START_TIME, new UnsignedWordElement(47515)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_END_TIME, new UnsignedWordElement(47516)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_BAT_POWER_PERCENT, new SignedWordElement(47517)), //
						m(GoodWe.ChannelId.WORK_WEEK_1, new UnsignedWordElement(47518)) //
				// TODO .debug()
				), //

				new FC3ReadRegistersTask(47618, Priority.LOW, //
						m(GoodWe.ChannelId.BATTERY_2_PROTOCOL, new UnsignedWordElement(47618))), //
				new FC16WriteRegistersTask(47618, //
						m(GoodWe.ChannelId.BATTERY_2_PROTOCOL, new UnsignedWordElement(47618))), //

				// Real-Time BMS Data for EMS Control (the data directly from BMS. Please refer
				// to the comments on registers 45352~45358)
				new FC16WriteRegistersTask(47900, //
						m(GoodWe.ChannelId.WBMS_VERSION, new UnsignedWordElement(47900)), //
						m(GoodWe.ChannelId.WBMS_STRINGS, new UnsignedWordElement(47901)), //
						m(GoodWe.ChannelId.WBMS_CHARGE_MAX_VOLTAGE, new UnsignedWordElement(47902),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_CHARGE_MAX_CURRENT, new UnsignedWordElement(47903),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(47904),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_DISCHARGE_MAX_CURRENT, new UnsignedWordElement(47905),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_VOLTAGE, new UnsignedWordElement(47906), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_CURRENT, new UnsignedWordElement(47907), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_SOC, new UnsignedWordElement(47908)), //
						m(GoodWe.ChannelId.WBMS_SOH, new UnsignedWordElement(47909)), //
						m(GoodWe.ChannelId.WBMS_TEMPERATURE, new SignedWordElement(47910), SCALE_FACTOR_MINUS_1), //
						/*
						 * Warning Codes (table 8-8).
						 *
						 * <ul> <li>Bit 12-31 Reserved <li>Bit 11: System High Temperature <li>Bit 10:
						 * System Low Temperature 2 <li>Bit 09: System Low Temperature 1 <li>Bit 08:
						 * Cell Imbalance <li>Bit 07: System Reboot <li>Bit 06: Communication Failure
						 * <li>Bit 05: Discharge Over-Current <li>Bit 04: Charge Over-Current <li>Bit
						 * 03: Cell Low Temperature <li>Bit 02: Cell High Temperature <li>Bit 01:
						 * Discharge Under-Voltage <li>Bit 00: Charge Over-Voltage </ul>
						 */
						m(GoodWe.ChannelId.WBMS_WARNING_CODE, new UnsignedDoublewordElement(47911)), //
						/*
						 * Alarm Codes (table 8-7).
						 *
						 * <ul> <li>Bit 16-31 Reserved <li>Bit 15: Charge Over-Voltage Fault <li>Bit 14:
						 * Discharge Under-Voltage Fault <li>Bit 13: Cell High Temperature <li>Bit 12:
						 * Communication Fault <li>Bit 11: Charge Circuit Fault <li>Bit 10: Discharge
						 * Circuit Fault <li>Bit 09: Battery Lock <li>Bit 08: Battery Break <li>Bit 07:
						 * DC Bus Fault <li>Bit 06: Precharge Fault <li>Bit 05: Discharge Over-Current
						 * <li>Bit 04: Charge Over-Current <li>Bit 03: Cell Low Temperature <li>Bit 02:
						 * Cell High Temperature <li>Bit 01: Discharge Under-Voltage <li>Bit 00: Charge
						 * Over-Voltage </ul>
						 */
						m(GoodWe.ChannelId.WBMS_ALARM_CODE, new UnsignedDoublewordElement(47913)), //
						/*
						 * BMS Status
						 *
						 * <ul> <li>Bit 2: Stop Discharge <li>Bit 1: Stop Charge <li>Bit 0: Force Charge
						 * </ul>
						 */
						m(GoodWe.ChannelId.WBMS_STATUS, new UnsignedWordElement(47915)), //
						m(GoodWe.ChannelId.WBMS_DISABLE_TIMEOUT_DETECTION, new UnsignedWordElement(47916)), //

						m(GoodWe.ChannelId.BMS_BATTERY_STRING_RATE_VOLTAGE, new UnsignedWordElement(47917)), //

						// Real-Time BMS Data for EMS Control (WBMS) of the second battery port
						m(GoodWe.ChannelId.WBMS_VERSION_2, new UnsignedWordElement(47918)), //
						m(GoodWe.ChannelId.WBMS_STRINGS_2, new UnsignedWordElement(47919)), //
						m(GoodWe.ChannelId.WBMS_CHARGE_MAX_VOLTAGE_2, new UnsignedWordElement(47920),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_CHARGE_MAX_CURRENT_2, new UnsignedWordElement(47921),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_DISCHARGE_MIN_VOLTAGE_2, new UnsignedWordElement(47922),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_DISCHARGE_MAX_CURRENT_2, new UnsignedWordElement(47923),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_VOLTAGE_2, new UnsignedWordElement(47924), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_CURRENT_2, new UnsignedWordElement(47925)), //
						m(GoodWe.ChannelId.WBMS_SOC_2, new UnsignedWordElement(47926)), //
						m(GoodWe.ChannelId.WBMS_SOH_2, new UnsignedWordElement(47927)), //
						m(GoodWe.ChannelId.WBMS_TEMPERATURE_2, new SignedWordElement(47928), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_WARNING_CODE_2, new UnsignedDoublewordElement(47929)), //
						m(GoodWe.ChannelId.WBMS_ALARM_CODE_2, new UnsignedDoublewordElement(47931)), //
						m(GoodWe.ChannelId.WBMS_STATUS_2, new UnsignedWordElement(47933)), //
						m(GoodWe.ChannelId.WBMS_DISABLE_TIMEOUT_DETECTION_2, new UnsignedWordElement(47934)) //
				),

				new FC3ReadRegistersTask(47900, Priority.LOW, //
						m(GoodWe.ChannelId.WBMS_VERSION, new UnsignedWordElement(47900)), //
						m(GoodWe.ChannelId.WBMS_STRINGS, new UnsignedWordElement(47901)), //
						m(GoodWe.ChannelId.WBMS_CHARGE_MAX_VOLTAGE, new UnsignedWordElement(47902),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_CHARGE_MAX_CURRENT, new UnsignedWordElement(47903),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_DISCHARGE_MIN_VOLTAGE, new UnsignedWordElement(47904),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_DISCHARGE_MAX_CURRENT, new UnsignedWordElement(47905),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_VOLTAGE, new UnsignedWordElement(47906), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_CURRENT, new UnsignedWordElement(47907)), //
						m(GoodWe.ChannelId.WBMS_SOC, new UnsignedWordElement(47908)), //
						m(GoodWe.ChannelId.WBMS_SOH, new UnsignedWordElement(47909)), //
						m(GoodWe.ChannelId.WBMS_TEMPERATURE, new SignedWordElement(47910), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_WARNING_CODE, new UnsignedDoublewordElement(47911)), //
						m(GoodWe.ChannelId.WBMS_ALARM_CODE, new UnsignedDoublewordElement(47913)), //

						// TODO reset to individual states
						m(GoodWe.ChannelId.WBMS_STATUS, new UnsignedWordElement(47915)), //
						m(GoodWe.ChannelId.WBMS_DISABLE_TIMEOUT_DETECTION, new UnsignedWordElement(47916)), //
						m(GoodWe.ChannelId.BMS_BATTERY_STRING_RATE_VOLTAGE, new UnsignedWordElement(47917)) //
				), //

				new FC3ReadRegistersTask(47918, Priority.LOW, //
						m(GoodWe.ChannelId.WBMS_VERSION_2, new UnsignedWordElement(47918)), //
						m(GoodWe.ChannelId.WBMS_STRINGS_2, new UnsignedWordElement(47919)), //
						m(GoodWe.ChannelId.WBMS_CHARGE_MAX_VOLTAGE_2, new UnsignedWordElement(47920),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_CHARGE_MAX_CURRENT_2, new UnsignedWordElement(47921),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_DISCHARGE_MIN_VOLTAGE_2, new UnsignedWordElement(47922),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_DISCHARGE_MAX_CURRENT_2, new UnsignedWordElement(47923),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_VOLTAGE_2, new UnsignedWordElement(47924), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_CURRENT_2, new UnsignedWordElement(47925)), //
						m(GoodWe.ChannelId.WBMS_SOC_2, new UnsignedWordElement(47926)), //
						m(GoodWe.ChannelId.WBMS_SOH_2, new UnsignedWordElement(47927)), //
						m(GoodWe.ChannelId.WBMS_TEMPERATURE_2, new SignedWordElement(47928), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.WBMS_WARNING_CODE_2, new UnsignedDoublewordElement(47929)), //
						m(GoodWe.ChannelId.WBMS_ALARM_CODE_2, new UnsignedDoublewordElement(47931)), //

						// TODO reset to individual states
						m(GoodWe.ChannelId.WBMS_STATUS_2, new UnsignedWordElement(47933)), //
						m(GoodWe.ChannelId.WBMS_DISABLE_TIMEOUT_DETECTION_2, new UnsignedWordElement(47934)) //
				), //

				// Registers for detailed analysis
				new FC3ReadRegistersTask(48000, Priority.LOW,
						m(GoodWe.ChannelId.GW_A_48000_BATTTERY1_CHARGE_MAX, new UnsignedWordElement(48000)), //
						m(GoodWe.ChannelId.GW_A_48001_PV_LIMIT_POWER, new UnsignedDoublewordElement(48001)), //
						m(GoodWe.ChannelId.GW_A_48003_INVERTER_MODE_CONTROL, new UnsignedWordElement(48003)), //
						m(GoodWe.ChannelId.GW_A_48004_AC_LIMIT_POWER, new UnsignedDoublewordElement(48004)), //
						m(GoodWe.ChannelId.GW_A_48006_BATTERY_MODE, new UnsignedWordElement(48006)), //
						m(GoodWe.ChannelId.GW_A_48007_BATTERY_CHARGE_POWER, new UnsignedDoublewordElement(48007)), //
						m(GoodWe.ChannelId.GW_A_48009_BATTERY_DISCHARGE_POWER, new UnsignedDoublewordElement(48009)), //
						m(GoodWe.ChannelId.GW_A_48011_BMS_DISCHARGE_CURRENT_LIMIT, new UnsignedWordElement(48011)), //
						m(GoodWe.ChannelId.GW_A_48012_BMS_CHARGE_CURRENT_LIMIT, new UnsignedWordElement(48012)), //
						m(GoodWe.ChannelId.GW_A_48013_BATTERY_LEVEL, new UnsignedWordElement(48013)), //
						m(GoodWe.ChannelId.GW_A_48014_R_PHASE_AC_LIMIT, new UnsignedWordElement(48014)), //
						m(GoodWe.ChannelId.GW_A_48015_S_PHASE_AC_LIMIT, new UnsignedWordElement(48015)), //
						m(GoodWe.ChannelId.GW_A_48016_T_PHASE_AC_LIMIT, new UnsignedWordElement(48016)), //
						m(GoodWe.ChannelId.GW_A_48017_METER_STATUS, new UnsignedWordElement(48017)), //
						m(GoodWe.ChannelId.GW_A_48018_METER_TOTAL_APPARENT_POWER, new SignedDoublewordElement(48018)), //
						m(GoodWe.ChannelId.GW_A_48020_METER_TOTAL_ACTIVE_POWER, new SignedDoublewordElement(48020)), //
						m(GoodWe.ChannelId.GW_A_48022_R_PHASE_LIMIT_INPUT, new SignedWordElement(48022)), //
						m(GoodWe.ChannelId.GW_A_48023_S_PHASE_LIMIT_INPUT, new SignedWordElement(48023)), //
						m(GoodWe.ChannelId.GW_A_48024_T_PHASE_LIMIT_INPUT, new SignedWordElement(48024)), //
						m(GoodWe.ChannelId.GW_A_48025_MAX_BMS_DISCHARGE_CURRENT, new UnsignedWordElement(48025)), //
						m(GoodWe.ChannelId.GW_A_48026_FEED_POWER_ENABLE, new UnsignedWordElement(48026)), //
						m(GoodWe.ChannelId.GW_A_48027_BATTERY1_PERCENT_CHARGE, new UnsignedWordElement(48027)), //
						m(GoodWe.ChannelId.GW_A_48028_BATTERY1_PERCENT_DISCHARGE, new UnsignedWordElement(48028)), //
						m(GoodWe.ChannelId.GW_A_48029_BATTERY2_PERCENT_CHARGE, new UnsignedWordElement(48029)), //
						m(GoodWe.ChannelId.GW_A_48030_BATTERY2_PERCENT_DISCHARGE, new UnsignedWordElement(48030)), //
						m(GoodWe.ChannelId.GW_A_48031_BATTERY2_MODE, new UnsignedWordElement(48031)), //
						m(GoodWe.ChannelId.GW_A_48032_BATTERY2_CHARGE_POWER, new UnsignedDoublewordElement(48032)), //
						m(GoodWe.ChannelId.GW_A_48034_BATTERY2_DISCHARGE_POWER, new UnsignedDoublewordElement(48034)), //
						m(GoodWe.ChannelId.GW_A_48036_BMS2_DISCHARGE_CURRENT_LIMIT, new UnsignedWordElement(48036)), //
						m(GoodWe.ChannelId.GW_A_48037_BMS2_CHARGE_CURRENT_LIMIT, new UnsignedWordElement(48037)), //
						m(GoodWe.ChannelId.GW_A_48038_BATTERY2_LEVEL, new UnsignedWordElement(48038)), //
						m(GoodWe.ChannelId.GW_A_48039_BATTERY_CHARGE_VOLTAGE_LIMIT, new UnsignedWordElement(48039)), //
						m(GoodWe.ChannelId.GW_A_48040_MAX_BMS2_DISCHARGE_CURRENT, new UnsignedWordElement(48040)), //
						m(GoodWe.ChannelId.GW_A_48041_GENERATOR_OPERATING_MODE, new UnsignedWordElement(48041)) //
				) //
		);

		UnsignedWordElement[] elementsToRead = GoodWeStateDefinitions.GOODWE_STATE_REGISTER_TASKS.stream()
				.map(GwStateTask::register) //
				.map(UnsignedWordElement::new) //
				.toArray(UnsignedWordElement[]::new);

		readElementsOnce(FC3, protocol, ModbusUtils::doNotRetry, elementsToRead) //
				.thenAccept((rsr) -> {

					if (rsr.values().stream().allMatch(Objects::isNull)) {
						return;
					}

					var modbusElements = new ArrayList<ModbusElement>();
					IntStream.range(0, elementsToRead.length).forEach(i -> {
						var address = elementsToRead[i].startAddress;
						var gwStates = GoodWeStateDefinitions.GOODWE_STATE_REGISTER_TASKS.get(i).gwStates();
						Integer registerValue = TypeUtils.getAsType(OpenemsType.INTEGER, rsr.values().get(i));

						if (registerValue == null || registerValue == 0xffff || gwStates.isEmpty()) {
							modbusElements.add(new DummyRegisterElement(address));
							return;
						}
						var element = this.generateElementsAndChannels(address, gwStates);
						modbusElements.add(this.m(element));
					});

					protocol.addTasks(new FC3ReadRegistersTask(32000, Priority.LOW,
							modbusElements.toArray(ModbusElement[]::new)));
				});

		/*
		 * Handle different GoodWe Types.
		 *
		 * GoodweType Firmware is differing from Type ET-Plus to ETT.
		 *
		 * Register 35011: GoodWeType as String (Not supported for GoodWe 20 & 30 - ETT)
		 * Register 35003: Serial number as String (Fallback for GoodWe 20 & 30 - ETT)
		 */
		readElementOnce(FC3, protocol, ModbusUtils::retryOnNull, new StringWordElement(35011, 5)) //
				.thenAccept(value -> {

					/*
					 * Evaluate GoodweType from GoodWe type register
					 */
					final var resultFromString = getGoodWeTypeFromStringValue(
							TypeUtils.getAsType(OpenemsType.STRING, value));

					if (resultFromString != GoodWeType.UNDEFINED) {

						/*
						 * ET-Plus
						 */
						this.logInfo(this.log, "Identified " + resultFromString.getName());
						this.updateGoodWeType(resultFromString, protocol);

						// Handles different ET-Plus DSP versions
						ModbusUtils
								.readElementOnce(FC3, protocol, ModbusUtils::retryOnNull,
										new UnsignedWordElement(35016)) //
								.thenAccept(dspVersion -> {
									try {
										if (dspVersion >= 5) {
											this.handleDspVersion5(protocol);
										}
										if (dspVersion >= 6) {
											this.handleDspVersion6(protocol);
										}
										if (dspVersion >= 7) {
											this.handleDspVersion7(protocol);
										}
									} catch (OpenemsException e) {
										this.logError(this.log, "Unable to add task for modbus protocol");
									}
								});
						return;
					}

					/*
					 * Evaluate GoodweType from serial number
					 */
					readElementOnce(FC3, protocol, ModbusUtils::retryOnNull, new StringWordElement(35003, 8)) //
							.thenAccept(serialNr -> {
								try {
									this.handleDspVersion5(protocol);
									this.handleDspVersion6(protocol);
									this.handleDspVersion7(protocol);
								} catch (OpenemsException e) {
									this.logError(this.log, "Unable to add charger tasks for modbus protocol");
								}

								final var hardwareType = getGoodWeTypeFromSerialNr(serialNr);
								this.updateGoodWeType(hardwareType, protocol);
							});
				});

		return protocol;
	}

	private void updateGoodWeType(GoodWeType goodWeType, ModbusProtocol protocol) {
		try {
			this._setGoodweType(goodWeType);

			switch (goodWeType) {
			case UNDEFINED, GOODWE_10K_BT, GOODWE_8K_BT, GOODWE_5K_BT, GOODWE_10K_ET, GOODWE_8K_ET, GOODWE_5K_ET,
					FENECON_FHI_10_DAH, FENECON_GEN2_6K, FENECON_GEN2_10K, FENECON_GEN2_15K -> {
				this.handleDefaultEmsPower(protocol);
			}

			case FENECON_50K -> {
				this.handleDefaultEmsPower(protocol);
				this.handleMultipleStringChargers(protocol);
				this.handleExtendedFeedPower(protocol);
				this.handleNewFixPfRegisters(protocol);
				this.handleEnablePfCurve(protocol);
				this.handleGensetOperatingMode(protocol);
			}

			case FENECON_100K -> {
				this.handleMultipleStringChargers(protocol);
				this.handleExtendedFeedPower(protocol);
				this.handleNewFixPfRegisters(protocol);
				this.handleNewEmsPower(protocol);
				this.handleGensetOperatingMode(protocol);
			}

			case FENECON_FHI_20_DAH, FENECON_FHI_29_9_DAH -> {
				this.handleMultipleStringChargers(protocol);
				this.handleDefaultEmsPower(protocol);
			}
			}
		} catch (OpenemsException e) {
			this.logError(this.log, "Unable to add charger tasks for modbus protocol");
		}
	}

	@Override
	public void handleEvent(Event event) {
		if (!this.isEnabled()) {
			return;
		}
		switch (event.getTopic()) {
		case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE:

			// Set charger information for MPPTs having more than one PV e.g.
			// GoodWeChargerTwoString.
			this.setMultipleStringChannels();
		}
	}

	/**
	 * Get GoodWe type from the GoodWe string representation.
	 *
	 * @param stringValue GoodWe type as String
	 * @return type as {@link GoodWeType}
	 */
	protected static GoodWeType getGoodWeTypeFromStringValue(String stringValue) {
		if (stringValue == null || stringValue.isEmpty()) {
			return GoodWeType.UNDEFINED;
		}

		return switch (stringValue) {
		case "GW10K-BT" -> GoodWeType.GOODWE_10K_BT;
		case "GW8K-BT" -> GoodWeType.GOODWE_8K_BT;
		case "GW5K-BT" -> GoodWeType.GOODWE_5K_BT;
		case "GW10K-ET" -> GoodWeType.GOODWE_10K_ET;
		case "GW8K-ET" -> GoodWeType.GOODWE_8K_ET;
		case "GW5K-ET" -> GoodWeType.GOODWE_5K_ET;
		case "FHI-10-DAH" -> GoodWeType.FENECON_FHI_10_DAH;
		default -> GoodWeType.UNDEFINED;
		};
	}

	/**
	 * Get GoodWe type from serial number.
	 *
	 * @param serialNr Serial number
	 * @return type as {@link GoodWeType}
	 */
	protected static GoodWeType getGoodWeTypeFromSerialNr(String serialNr) {
		if (serialNr == null || serialNr.isEmpty()) {
			return GoodWeType.UNDEFINED;
		}

		// Example serial numbers: default=9010KETT228W0004 float(29.9)=929K9ETT231W0159
		return Stream.of(GoodWeType.values()) //
				.filter(t -> {
					try {
						return t.serialNrFilter.apply(serialNr);
					} catch (Exception e) {
						LOG.warn("Unable to parse GoodWe Serial Number [" + serialNr + "] with [" + t.name() + "]: "
								+ e.getMessage());
						e.printStackTrace();
						return false;
					}
				}) //
				.findFirst() //
				.orElse(GoodWeType.UNDEFINED);
	}

	private void handleExtendedFeedPower(ModbusProtocol protocol) {
		protocol.addTask(//
				new FC3ReadRegistersTask(42003, Priority.LOW, //
						m(GoodWe.ChannelId.EXTENDED_FEED_POWER_ENABLE, new UnsignedWordElement(42003)), //
						m(GoodWe.ChannelId.EXTENDED_FEED_POWER_PARA_SET, new SignedDoublewordElement(42004)) //
				) //
		);
		protocol.addTask(//
				new FC16WriteRegistersTask(42003,
						m(GoodWe.ChannelId.EXTENDED_FEED_POWER_ENABLE, new UnsignedWordElement(42003)), //
						m(GoodWe.ChannelId.EXTENDED_FEED_POWER_PARA_SET, new SignedDoublewordElement(42004)) //
				) //
		);

	}

	private void handleNewFixPfRegisters(ModbusProtocol protocol) {
		protocol.addTask(//
				new FC3ReadRegistersTask(45539, Priority.LOW, //
						m(GoodWe.ChannelId.ENABLE_FIXED_POWER_FACTOR_V2, new UnsignedWordElement(45539)), //
						m(GoodWe.ChannelId.FIXED_POWER_FACTOR_V2, new UnsignedWordElement(45540)) //
				) //
		);
		protocol.addTask(//
				new FC16WriteRegistersTask(45539,
						m(GoodWe.ChannelId.ENABLE_FIXED_POWER_FACTOR_V2, new UnsignedWordElement(45539)), //
						m(GoodWe.ChannelId.FIXED_POWER_FACTOR_V2, new UnsignedWordElement(45540)) //
				) //
		);
	}

	private void handleDefaultEmsPower(ModbusProtocol protocol) {
		protocol.addTasks(//
				new FC3ReadRegistersTask(47511, Priority.LOW, //
						m(GoodWe.ChannelId.EMS_POWER_MODE, new UnsignedWordElement(47511)), //
						m(GoodWe.ChannelId.EMS_POWER_SET, new UnsignedWordElement(47512)) //
				), //
				new FC16WriteRegistersTask(47511, //
						m(GoodWe.ChannelId.EMS_POWER_MODE, new UnsignedWordElement(47511)), //
						m(GoodWe.ChannelId.EMS_POWER_SET, new UnsignedWordElement(47512)) //
				) //
		);
	}

	private void handleNewEmsPower(ModbusProtocol protocol) {
		protocol.addTasks(//
				new FC3ReadRegistersTask(42000, Priority.LOW, //
						m(GoodWe.ChannelId.EMS_POWER_MODE, new UnsignedWordElement(42000)), //
						m(GoodWe.ChannelId.EMS_POWER_SET, new UnsignedDoublewordElement(42001)) //
				), //
				new FC16WriteRegistersTask(42000, //
						m(GoodWe.ChannelId.EMS_POWER_MODE, new UnsignedWordElement(42000)), //
						m(GoodWe.ChannelId.EMS_POWER_SET, new UnsignedDoublewordElement(42001)) //
				) //
		);
	}

	private void handleEnablePfCurve(ModbusProtocol protocol) {
		protocol.addTasks(//
				new FC3ReadRegistersTask(45751, Priority.LOW,
						m(GoodWePowerSetting.ChannelId.V2_APM_ENABLE_PF_OVERFREQUENZY_CURVE,
								new UnsignedWordElement(45751)),
						new DummyRegisterElement(45752, 45775),
						m(GoodWePowerSetting.ChannelId.V2_APM_ENABLE_PF_UNDERFREQUENZY_CURVE,
								new UnsignedWordElement(45776))),
				new FC16WriteRegistersTask(45751,
						m(GoodWePowerSetting.ChannelId.V2_APM_ENABLE_PF_OVERFREQUENZY_CURVE,
								new UnsignedWordElement(45751))),
				new FC16WriteRegistersTask(45776, m(GoodWePowerSetting.ChannelId.V2_APM_ENABLE_PF_UNDERFREQUENZY_CURVE,
						new UnsignedWordElement(45776))));
	}

	private void handleGensetOperatingMode(ModbusProtocol protocol) {
		protocol.addTask(//
				new FC3ReadRegistersTask(36197, Priority.HIGH,
						m(GoodWeStsBoxGensetMeter.ChannelId.GENSET_OPERATING_MODE, new UnsignedWordElement(36197))));
	}

	/**
	 * Handle multiple string chargers.
	 *
	 * <p>
	 * For MPPT connectors e.g. two string on one MPPT the power information is
	 * spread over several registers that should be read as complete blocks.
	 *
	 * @param protocol current protocol
	 * @throws OpenemsException on error
	 */
	private void handleMultipleStringChargers(ModbusProtocol protocol) throws OpenemsException {
		/*
		 * For two string charger the registers the power information is spread over
		 * several registers that should be read as complete blocks
		 */
		/*
		 * Block 1: PV1 - PV4 voltage & current
		 */
		protocol.addTask(//
				new FC3ReadRegistersTask(35103, Priority.HIGH, //
						m(GoodWe.ChannelId.TWO_S_PV1_V, new UnsignedWordElement(35103),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV1_I, new UnsignedWordElement(35104),
								ElementToChannelConverter.SCALE_FACTOR_2),

						// Power having wrong values for two-string charger
						new DummyRegisterElement(35105, 35106),

						m(GoodWe.ChannelId.TWO_S_PV2_V, new UnsignedWordElement(35107),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV2_I, new UnsignedWordElement(35108),
								ElementToChannelConverter.SCALE_FACTOR_2),
						new DummyRegisterElement(35109, 35110),
						m(GoodWe.ChannelId.TWO_S_PV3_V, new UnsignedWordElement(35111),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV3_I, new UnsignedWordElement(35112),
								ElementToChannelConverter.SCALE_FACTOR_2),
						new DummyRegisterElement(35113, 35114),
						m(GoodWe.ChannelId.TWO_S_PV4_V, new UnsignedWordElement(35115),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV4_I, new UnsignedWordElement(35116),
								ElementToChannelConverter.SCALE_FACTOR_2)) //
		);

		/*
		 * Block 2: PV5 - PV6 voltage & current (would continue till PV16) and MPPT
		 * total power and current values
		 */
		protocol.addTask(//
				new FC3ReadRegistersTask(35304, Priority.HIGH, //
						m(GoodWe.ChannelId.TWO_S_PV5_V, new UnsignedWordElement(35304),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV5_I, new UnsignedWordElement(35305),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV6_V, new UnsignedWordElement(35306),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV6_I, new UnsignedWordElement(35307),
								ElementToChannelConverter.SCALE_FACTOR_2), //
						m(GoodWe.ChannelId.TWO_S_PV7_V, new UnsignedWordElement(35308),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV7_I, new UnsignedWordElement(35309),
								ElementToChannelConverter.SCALE_FACTOR_2), //
						m(GoodWe.ChannelId.TWO_S_PV8_V, new UnsignedWordElement(35310),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV8_I, new UnsignedWordElement(35311),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV9_V, new UnsignedWordElement(35312),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV9_I, new UnsignedWordElement(35313),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV10_V, new UnsignedWordElement(35314),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV10_I, new UnsignedWordElement(35315),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV11_V, new UnsignedWordElement(35316),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV11_I, new UnsignedWordElement(35317),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV12_V, new UnsignedWordElement(35318),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV12_I, new UnsignedWordElement(35319),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV13_V, new UnsignedWordElement(35320),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV13_I, new UnsignedWordElement(35321),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV14_V, new UnsignedWordElement(35322),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV14_I, new UnsignedWordElement(35323),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV15_V, new UnsignedWordElement(35324),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV15_I, new UnsignedWordElement(35325),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV16_V, new UnsignedWordElement(35326),
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.TWO_S_PV16_I, new UnsignedWordElement(35327),
								ElementToChannelConverter.SCALE_FACTOR_2),
						new DummyRegisterElement(35328, 35336),

						m(GoodWe.ChannelId.MPPT1_P, new UnsignedWordElement(35337)), //
						m(GoodWe.ChannelId.MPPT2_P, new UnsignedWordElement(35338)), //
						m(GoodWe.ChannelId.MPPT3_P, new UnsignedWordElement(35339)), //
						m(GoodWe.ChannelId.MPPT4_P, new UnsignedWordElement(35340)), //
						m(GoodWe.ChannelId.MPPT5_P, new UnsignedWordElement(35341)), //
						m(GoodWe.ChannelId.MPPT6_P, new UnsignedWordElement(35342)), //
						m(GoodWe.ChannelId.MPPT7_P, new UnsignedWordElement(35343)), //
						m(GoodWe.ChannelId.MPPT8_P, new UnsignedWordElement(35344)), //

						m(GoodWe.ChannelId.MPPT1_I, new UnsignedWordElement(35345), //
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.MPPT2_I, new UnsignedWordElement(35346), //
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.MPPT3_I, new UnsignedWordElement(35347), //
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.MPPT4_I, new UnsignedWordElement(35348), //
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.MPPT5_I, new UnsignedWordElement(35349), //
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.MPPT6_I, new UnsignedWordElement(35350), //
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.MPPT7_I, new UnsignedWordElement(35351), //
								ElementToChannelConverter.SCALE_FACTOR_2),
						m(GoodWe.ChannelId.MPPT8_I, new UnsignedWordElement(35352), //
								ElementToChannelConverter.SCALE_FACTOR_2)));
	}

	// TODO: Can be removed when GoodWeChargerTwoStringImpl has been deleted
	private void setMultipleStringChannels() {

		this.chargers.stream() //
				.filter(GoodWeChargerTwoString.class::isInstance) //
				.map(GoodWeChargerTwoString.class::cast) //
				.forEach(charger -> {
					var pvPort = charger.pvPort();

					// Get actual Channels
					IntegerReadChannel totalMpptPowerChannel = this.channel(pvPort.mpptPowerChannelId);
					IntegerReadChannel totalMpptCurrentChannel = this.channel(pvPort.mpptCurrentChannelId);
					IntegerReadChannel stringCurrentChannel = this.channel(pvPort.pvCurrentId);
					IntegerReadChannel stringVoltageChannel = this.channel(pvPort.pvVoltageId);

					// Power value from the total MPPT power and current values
					charger._setActualPower(//
							GoodWeChargerTwoString.calculateByRuleOfThree(//
									totalMpptPowerChannel.getNextValue().asOptional(), //
									totalMpptCurrentChannel.getNextValue().asOptional(), //
									stringCurrentChannel.getNextValue().asOptional()) //
									// If at least one value was present, the result should not be null.
									.orElse(0) //
					);

					/*
					 * TODO: Could also be achieved by using listeners for onSetNextValue in
					 * addCharger and removeCharger.
					 */
					charger._setCurrent(stringCurrentChannel.getNextValue().get());
					charger._setVoltage(stringVoltageChannel.getNextValue().get());
				});
	}

	private void handleDspVersion7(ModbusProtocol protocol) throws OpenemsException {
		protocol.addTask(//
				new FC3ReadRegistersTask(47519, Priority.LOW, //
						m(GoodWe.ChannelId.WORK_WEEK_2_START_TIME, new UnsignedWordElement(47519)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_END_TIME, new UnsignedWordElement(47520)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_BAT_POWER_PERCENT, new UnsignedWordElement(47521)), //
						m(new BitsWordElement(47522, this)//
								.bit(0, GoodWe.ChannelId.WORK_WEEK_2_SUNDAY)//
								.bit(1, GoodWe.ChannelId.WORK_WEEK_2_MONDAY)//
								.bit(2, GoodWe.ChannelId.WORK_WEEK_2_TUESDAY)//
								.bit(3, GoodWe.ChannelId.WORK_WEEK_2_WEDNESDAY)//
								.bit(4, GoodWe.ChannelId.WORK_WEEK_2_THURSDAY)//
								.bit(5, GoodWe.ChannelId.WORK_WEEK_2_FRIDAY)//
								.bit(6, GoodWe.ChannelId.WORK_WEEK_2_SATURDAY)//
								.bit(7, GoodWe.ChannelId.WORK_WEEK_2_NA)//
								.bit(8, GoodWe.ChannelId.WORK_WEEK_2_ENABLED)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_START_TIME, new UnsignedWordElement(47523)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_END_TIME, new UnsignedWordElement(47524)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_BAT_POWER_PERCENT, new UnsignedWordElement(47525)), //
						m(new BitsWordElement(47526, this)//
								.bit(0, GoodWe.ChannelId.WORK_WEEK_3_SUNDAY)//
								.bit(1, GoodWe.ChannelId.WORK_WEEK_3_MONDAY)//
								.bit(2, GoodWe.ChannelId.WORK_WEEK_3_TUESDAY)//
								.bit(3, GoodWe.ChannelId.WORK_WEEK_3_WEDNESDAY)//
								.bit(4, GoodWe.ChannelId.WORK_WEEK_3_THURSDAY)//
								.bit(5, GoodWe.ChannelId.WORK_WEEK_3_FRIDAY)//
								.bit(6, GoodWe.ChannelId.WORK_WEEK_3_SATURDAY)//
								.bit(7, GoodWe.ChannelId.WORK_WEEK_3_NA)//
								.bit(8, GoodWe.ChannelId.WORK_WEEK_3_ENABLED)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_START_TIME, new UnsignedWordElement(47527)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_END_TIME, new UnsignedWordElement(47528)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_BMS_POWER_PERCENT, new UnsignedWordElement(47529)), //
						m(new BitsWordElement(47530, this)//
								.bit(0, GoodWe.ChannelId.WORK_WEEK_4_SUNDAY)//
								.bit(1, GoodWe.ChannelId.WORK_WEEK_4_MONDAY)//
								.bit(2, GoodWe.ChannelId.WORK_WEEK_4_TUESDAY)//
								.bit(3, GoodWe.ChannelId.WORK_WEEK_4_WEDNESDAY)//
								.bit(4, GoodWe.ChannelId.WORK_WEEK_4_THURSDAY)//
								.bit(5, GoodWe.ChannelId.WORK_WEEK_4_FRIDAY)//
								.bit(6, GoodWe.ChannelId.WORK_WEEK_4_SATURDAY)//
								.bit(7, GoodWe.ChannelId.WORK_WEEK_4_NA)//
								.bit(8, GoodWe.ChannelId.WORK_WEEK_4_ENABLED)), //
						m(GoodWe.ChannelId.SOC_START_TO_FORCE_CHARGE, new UnsignedWordElement(47531)), //
						m(GoodWe.ChannelId.SOC_STOP_TO_FORCE_CHARGE, new UnsignedWordElement(47532)), //
						new DummyRegisterElement(47533, 47541), //
						m(GoodWe.ChannelId.PEAK_SHAVING_POWER_LIMIT, new UnsignedWordElement(47542)), //
						new DummyRegisterElement(47543), //
						m(GoodWe.ChannelId.PEAK_SHAVING_SOC, new UnsignedWordElement(47544)), //
						m(GoodWe.ChannelId.FAST_CHARGE_ENABLE, new UnsignedWordElement(47545)), // [0,1]
						m(GoodWe.ChannelId.FAST_CHARGE_STOP_SOC, new UnsignedWordElement(47546))) // [0,100]
		);

		protocol.addTask(//
				new FC16WriteRegistersTask(47519, //
						m(GoodWe.ChannelId.WORK_WEEK_2_START_TIME, new UnsignedWordElement(47519)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_END_TIME, new UnsignedWordElement(47520)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_BAT_POWER_PERCENT, new SignedWordElement(47521)), //
						m(GoodWe.ChannelId.WORK_WEEK_2, new UnsignedWordElement(47522)), //

						m(GoodWe.ChannelId.WORK_WEEK_3_START_TIME, new UnsignedWordElement(47523)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_END_TIME, new UnsignedWordElement(47524)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_BAT_POWER_PERCENT, new SignedWordElement(47525)), //
						m(GoodWe.ChannelId.WORK_WEEK_3, new UnsignedWordElement(47526)), //

						m(GoodWe.ChannelId.WORK_WEEK_4_START_TIME, new UnsignedWordElement(47527)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_END_TIME, new UnsignedWordElement(47528)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_BMS_POWER_PERCENT, new SignedWordElement(47529)), //
						m(GoodWe.ChannelId.WORK_WEEK_4, new UnsignedWordElement(47530)), //

						// To set the SOC level to start/stop battery force charge.(this is not the
						// command from BMS, but the protection on inverter side. Eg. StartchgSOC
						// (47531) is set as 5%, but the battery BMS gives a force charge signal at
						// SOC
						// 6%, then battery will start force charge at 6% SOC; if BMS does not send
						// force charge command at 5% SOC, then battery will still start force
						// charge at
						// 5% SOC. ) Note: the default setting is 5% SOC to start and 10% to stop.
						// force
						// charge power is 1000W from PV or Grid as well
						m(GoodWe.ChannelId.SOC_START_TO_FORCE_CHARGE, new UnsignedWordElement(47531),
								SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.SOC_STOP_TO_FORCE_CHARGE, new UnsignedWordElement(47532),
								SCALE_FACTOR_MINUS_1), //
						// to clear all economical mode settings (47515-47530) enter self Use Mode
						m(GoodWe.ChannelId.CLEAR_ALL_ECONOMIC_MODE, new UnsignedWordElement(47533)), //
						new DummyRegisterElement(47534, 47538), //
						m(GoodWe.ChannelId.WIFI_RESET, new UnsignedWordElement(47539)), //
						new DummyRegisterElement(47540), //
						m(GoodWe.ChannelId.WIFI_RELOAD, new UnsignedWordElement(47541)), //
						// to set the threshold of importing power, where peak-shaving acts. Eg. If
						// set
						// peak-shaving power as 20kW, then battery will only discharge when
						// imported
						// power from grid exceed 20kW to make sure the importing power keeps below
						// 20kW
						m(GoodWe.ChannelId.PEAK_SHAVING_POWER_LIMIT, new UnsignedDoublewordElement(47542)), //
						m(GoodWe.ChannelId.PEAK_SHAVING_SOC, new UnsignedWordElement(47544)), //
						// 0: Disable 1:Enable
						m(GoodWe.ChannelId.FAST_CHARGE_ENABLE, new UnsignedWordElement(47545)), //
						m(GoodWe.ChannelId.FAST_CHARGE_STOP_SOC, new UnsignedWordElement(47546))) //
		);

		protocol.addTask(//
				// Economic mode setting for ARM version => 18
				new FC3ReadRegistersTask(47547, Priority.LOW, //
						m(GoodWe.ChannelId.WORK_WEEK_1_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47547)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47548)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47549)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_PARAMETER1_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47550)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_PARAMETER1_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47551)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_PARAMETER1_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47552)), //

						m(GoodWe.ChannelId.WORK_WEEK_2_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47553)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47554)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47555)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_PARAMETER2_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47556)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_PARAMETER2_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47557)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_PARAMETER2_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47558)), //

						m(GoodWe.ChannelId.WORK_WEEK_3_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47559)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47560)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47561)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_PARAMETER3_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47562)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_PARAMETER3_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47563)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_PARAMETER3_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47564)), //

						m(GoodWe.ChannelId.WORK_WEEK_4_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47565)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47566)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47567)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_PARAMETER4_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47568)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_PARAMETER4_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47569)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_PARAMETER4_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47570)), //

						m(GoodWe.ChannelId.WORK_WEEK_5_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47571)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47572)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47573)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_PARAMETER5_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47574)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_PARAMETER5_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47575)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_PARAMETER5_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47576)), //

						m(GoodWe.ChannelId.WORK_WEEK_6_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47577)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47578)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47579)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_PARAMETER6_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47580)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_PARAMETER6_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47581)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_PARAMETER6_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47582)), //

						m(GoodWe.ChannelId.WORK_WEEK_7_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47583)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47584)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47585)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_PARAMETER7_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47586)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_PARAMETER7_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47587)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_PARAMETER7_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47588)), //

						m(GoodWe.ChannelId.WORK_WEEK_8_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47589)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47590)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47591)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_PARAMETER8_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47592)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_PARAMETER8_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47593)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_PARAMETER8_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47594)), //
						// 0,Disable 1,switching mode 2,Time manage mode
						// Only for inverter with ARM version equal or greater 18 To select Load
						// control
						// mode
						m(GoodWe.ChannelId.LOAD_REGULATION_INDEX, new UnsignedWordElement(47595)), //
						m(GoodWe.ChannelId.LOAD_SWITCH_STATUS, new UnsignedWordElement(47596)), //
						// For load control function, if the controlled load on Backup side, use
						// this to
						// switch the load off when battery reaches the SOC set
						m(GoodWe.ChannelId.BACKUP_SWITCH_SOC_MIN, new UnsignedWordElement(47597)), //
						new DummyRegisterElement(47598), //
						m(GoodWe.ChannelId.HARDWARE_FEED_POWER, new UnsignedWordElement(47599))) //
		);

		protocol.addTask(//
				new FC16WriteRegistersTask(47547, //
						// Economic mode setting for ARM version => 18
						m(GoodWe.ChannelId.WORK_WEEK_1_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47547)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47548)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47549)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_PARAMETER1_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47550)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_PARAMETER1_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47551)), //
						m(GoodWe.ChannelId.WORK_WEEK_1_PARAMETER1_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47552)), //

						m(GoodWe.ChannelId.WORK_WEEK_2_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47553)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47554)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47555)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_PARAMETER2_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47556)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_PARAMETER2_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47557)), //
						m(GoodWe.ChannelId.WORK_WEEK_2_PARAMETER2_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47558)), //

						m(GoodWe.ChannelId.WORK_WEEK_3_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47559)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47560)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47561)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_PARAMETER3_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47562)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_PARAMETER3_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47563)), //
						m(GoodWe.ChannelId.WORK_WEEK_3_PARAMETER3_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47564)), //

						m(GoodWe.ChannelId.WORK_WEEK_4_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47565)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47566)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47567)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_PARAMETER4_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47568)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_PARAMETER4_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47569)), //
						m(GoodWe.ChannelId.WORK_WEEK_4_PARAMETER4_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47570)), //

						m(GoodWe.ChannelId.WORK_WEEK_5_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47571)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47572)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47573)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_PARAMETER5_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47574)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_PARAMETER5_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47575)), //
						m(GoodWe.ChannelId.WORK_WEEK_5_PARAMETER5_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47576)), //

						m(GoodWe.ChannelId.WORK_WEEK_6_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47577)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47578)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47579)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_PARAMETER6_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47580)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_PARAMETER6_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47581)), //
						m(GoodWe.ChannelId.WORK_WEEK_6_PARAMETER6_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47582)), //

						m(GoodWe.ChannelId.WORK_WEEK_7_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47583)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47584)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47585)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_PARAMETER7_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47586)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_PARAMETER7_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47587)), //
						m(GoodWe.ChannelId.WORK_WEEK_7_PARAMETER7_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47588)), //

						m(GoodWe.ChannelId.WORK_WEEK_8_START_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47589)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_END_TIME_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47590)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_ECO_MODE_FOR_ARM_18_AND_GREATER, new UnsignedWordElement(47591)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_PARAMETER8_1_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47592)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_PARAMETER8_2_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47593)), //
						m(GoodWe.ChannelId.WORK_WEEK_8_PARAMETER8_3_ECO_MODE_FOR_ARM_18_AND_GREATER,
								new UnsignedWordElement(47594)), //
						// 0,Disable 1,switching mode 2,Time manage mode
						// Only for inverter with ARM version equal or greater 18 To select Load
						// control
						// mode
						m(GoodWe.ChannelId.LOAD_REGULATION_INDEX, new UnsignedWordElement(47595)), //
						m(GoodWe.ChannelId.LOAD_SWITCH_STATUS, new UnsignedWordElement(47596)), //
						// For load control function, if the controlled load on Backup side, use
						// this to
						// switch the load off when battery reaches the SOC set
						m(GoodWe.ChannelId.BACKUP_SWITCH_SOC_MIN, new UnsignedWordElement(47597)), //
						new DummyRegisterElement(47598), //
						m(GoodWe.ChannelId.HARDWARE_FEED_POWER, new UnsignedWordElement(47599)))//
		);
	}

	private void handleDspVersion6(ModbusProtocol protocol) throws OpenemsException {
		// Registers 36000 for COM_MODE throw "Illegal Data Address"

		protocol.addTasks(//
				new FC3ReadRegistersTask(36001, Priority.LOW, //
						m(GoodWe.ChannelId.RSSI, new UnsignedWordElement(36001))), //
				new FC3ReadRegistersTask(36004, Priority.LOW, //
						m(GoodWe.ChannelId.METER_COMMUNICATE_STATUS, new UnsignedWordElement(36004))), //
				new FC3ReadRegistersTask(36015, Priority.LOW, //
						m(GoodWe.ChannelId.E_TOTAL_SELL, new FloatDoublewordElement(36015), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_TOTAL_BUY_F, new FloatDoublewordElement(36017), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.METER_ACTIVE_POWER_R, new SignedDoublewordElement(36019)), //
						m(GoodWe.ChannelId.METER_ACTIVE_POWER_S, new SignedDoublewordElement(36021)), //
						m(GoodWe.ChannelId.METER_ACTIVE_POWER_T, new SignedDoublewordElement(36023)), //
						m(GoodWe.ChannelId.METER_TOTAL_ACTIVE_POWER, new SignedDoublewordElement(36025)), //
						m(GoodWe.ChannelId.METER_REACTIVE_POWER_R, new SignedDoublewordElement(36027)), //
						m(GoodWe.ChannelId.METER_REACTIVE_POWER_S, new SignedDoublewordElement(36029)), //
						m(GoodWe.ChannelId.METER_REACTIVE_POWER_T, new SignedDoublewordElement(36031)), //
						m(GoodWe.ChannelId.METER_TOTAL_REACTIVE_POWER, new SignedDoublewordElement(36033)), //
						m(GoodWe.ChannelId.METER_APPARENT_POWER_R, new SignedDoublewordElement(36035)), //
						m(GoodWe.ChannelId.METER_APPARENT_POWER_S, new SignedDoublewordElement(36037)), //
						m(GoodWe.ChannelId.METER_APPARENT_POWER_T, new SignedDoublewordElement(36039)), //
						m(GoodWe.ChannelId.METER_TOTAL_APPARENT_POWER, new SignedDoublewordElement(36041)), //
						// Only for GoodWe smart meter
						m(GoodWe.ChannelId.METER_TYPE, new UnsignedWordElement(36043)), //
						m(GoodWe.ChannelId.METER_SOFTWARE_VERSION, new UnsignedWordElement(36044)), //
						// Only for AC coupled inverter. Detect Pv Meter
						m(GoodWe.ChannelId.METER_CT2_ACTIVE_POWER, new SignedDoublewordElement(36045)), //
						m(GoodWe.ChannelId.CT2_E_TOTAL_SELL, new UnsignedDoublewordElement(36047),
								SCALE_FACTOR_MINUS_2), //
						m(GoodWe.ChannelId.CT2_E_TOTAL_BUY, new UnsignedDoublewordElement(36049), SCALE_FACTOR_MINUS_2), //
						m(GoodWe.ChannelId.METER_CT2_STATUS, new UnsignedWordElement(36051))) //
		);

		protocol.addTask(//
				new FC3ReadRegistersTask(47000, Priority.LOW, //
						m(GoodWe.ChannelId.SELECT_WORK_MODE, new UnsignedWordElement(47000)), //
						new DummyRegisterElement(47001), //
						m(GoodWe.ChannelId.WMETER_CONNECT_CHECK_FLAG, new UnsignedWordElement(47002)), //
						new DummyRegisterElement(47003, 47004), //
						m(GoodWe.ChannelId.LOG_DATA_ENABLE, new UnsignedWordElement(47005)), //
						m(GoodWe.ChannelId.DATA_SEND_INTERVAL, new UnsignedWordElement(47006)), //
						m(GoodWe.ChannelId.DRED_CMD, new UnsignedWordElement(47007)), //
						new DummyRegisterElement(47008), //
						m(GoodWe.ChannelId.WIFI_OR_LAN_SWITCH, new UnsignedWordElement(47009)), //
						m(GoodWe.ChannelId.DRED_REMOTE_SHUTDOWN_RCR_FUNCTIONS_ENABLE, new UnsignedWordElement(47010)), //
						new DummyRegisterElement(47011), //
						m(GoodWe.ChannelId.LED_BLINK_TIME, new UnsignedWordElement(47012)), //
						m(GoodWe.ChannelId.WIFI_LED_STATE, new UnsignedWordElement(47013)), //
						m(GoodWe.ChannelId.COM_LED_STATE, new UnsignedWordElement(47014)), //
						m(GoodWe.ChannelId.METER_CT1_REVERSE_ENABLE, new UnsignedWordElement(47015)), //
						m(GoodWe.ChannelId.ERROR_LOG_READ_PAGE, new UnsignedWordElement(47016)), //
						// 1:on 0:off If not connect to Internet, please set 1
						m(GoodWe.ChannelId.MODBUS_TCP_WITHOUT_INTERNET, new UnsignedWordElement(47017)), //
						// 1: off, 2: on, 3: flash 1x, 4: flash 2x, 5: flash 4x
						m(GoodWe.ChannelId.BACKUP_LED, new UnsignedWordElement(47018)), // [1,5]
						m(GoodWe.ChannelId.GRID_LED, new UnsignedWordElement(47019)), // [1,5]
						m(GoodWe.ChannelId.SOC_LED_1, new UnsignedWordElement(47020)), // [1,5]
						m(GoodWe.ChannelId.SOC_LED_2, new UnsignedWordElement(47021)), // [1,5]
						m(GoodWe.ChannelId.SOC_LED_3, new UnsignedWordElement(47022)), // [1,5]
						m(GoodWe.ChannelId.SOC_LED_4, new UnsignedWordElement(47023)), // [1,5]
						m(GoodWe.ChannelId.BATTERY_LED, new UnsignedWordElement(47024)), // [1,5]
						m(GoodWe.ChannelId.SYSTEM_LED, new UnsignedWordElement(47025)), // [1,5]
						m(GoodWe.ChannelId.FAULT_LED, new UnsignedWordElement(47026)), // [1,5]
						m(GoodWe.ChannelId.ENERGY_LED, new UnsignedWordElement(47027)), // [1,5]
						m(GoodWe.ChannelId.LED_EXTERNAL_CONTROL, new UnsignedWordElement(47028)), // [42343]
						new DummyRegisterElement(47029, 47037), //
						// 1 Enable, After restart the inverter, setting saved
						m(GoodWe.ChannelId.STOP_MODE_SAVE_ENABLE, new UnsignedWordElement(47038))) //
		);

		protocol.addTask(//
				// The same function as that for Operation Mode on PV Master App
				new FC16WriteRegistersTask(47000, //
						m(GoodWe.ChannelId.SELECT_WORK_MODE, new UnsignedWordElement(47000)), //
						new DummyRegisterElement(47001), //
						m(GoodWe.ChannelId.WMETER_CONNECT_CHECK_FLAG, new UnsignedWordElement(47002)), //
						new DummyRegisterElement(47003, 47004), //
						// Breakpoint Resume for Data transferring. Activated as default, time
						// interval 5
						// minutes
						m(GoodWe.ChannelId.LOG_DATA_ENABLE, new UnsignedWordElement(47005)), //
						// Time interval for data send to cloud or EMS,default is 1 minute
						m(GoodWe.ChannelId.DATA_SEND_INTERVAL, new UnsignedWordElement(47006)), //
						// Only for Australia, Refer to Table 8-22
						m(GoodWe.ChannelId.DRED_CMD, new UnsignedWordElement(47007)), //
						new DummyRegisterElement(47008), //
						// For wifi+Lan module, to switch to LAN or WiFi communication
						m(GoodWe.ChannelId.WIFI_OR_LAN_SWITCH, new UnsignedWordElement(47009)), //
						// Ripple Control Receiver on/off
						m(GoodWe.ChannelId.DRED_REMOTE_SHUTDOWN_RCR_FUNCTIONS_ENABLE, new UnsignedWordElement(47010)), //
						new DummyRegisterElement(47011), //
						m(GoodWe.ChannelId.LED_BLINK_TIME, new UnsignedWordElement(47012)), //
						// 1: off, 2: on, 3: flash 1x, 4: flash 2x, 5: flash 4x
						m(GoodWe.ChannelId.WIFI_LED_STATE, new UnsignedWordElement(47013)), //
						m(GoodWe.ChannelId.COM_LED_STATE, new UnsignedWordElement(47014)), //
						// 1:on 0:off only for single phase Smart meter
						m(GoodWe.ChannelId.METER_CT1_REVERSE_ENABLE, new UnsignedWordElement(47015)), //
						m(GoodWe.ChannelId.ERROR_LOG_READ_PAGE, new UnsignedWordElement(47016)), //
						// 1:on 0:off If not connect to Internet, please set 1
						m(GoodWe.ChannelId.MODBUS_TCP_WITHOUT_INTERNET, new UnsignedWordElement(47017)), //
						// 1: off, 2: on, 3: flash 1x, 4: flash 2x, 5: flash 4x
						m(GoodWe.ChannelId.BACKUP_LED, new UnsignedWordElement(47018)), // [1,5]
						m(GoodWe.ChannelId.GRID_LED, new UnsignedWordElement(47019)), // [1,5]
						m(GoodWe.ChannelId.SOC_LED_1, new UnsignedWordElement(47020)), // [1,5]
						m(GoodWe.ChannelId.SOC_LED_2, new UnsignedWordElement(47021)), // [1,5]
						m(GoodWe.ChannelId.SOC_LED_3, new UnsignedWordElement(47022)), // [1,5]
						m(GoodWe.ChannelId.SOC_LED_4, new UnsignedWordElement(47023)), // [1,5]
						m(GoodWe.ChannelId.BATTERY_LED, new UnsignedWordElement(47024)), // [1,5]
						m(GoodWe.ChannelId.SYSTEM_LED, new UnsignedWordElement(47025)), // [1,5]
						m(GoodWe.ChannelId.FAULT_LED, new UnsignedWordElement(47026)), // [1,5]
						m(GoodWe.ChannelId.ENERGY_LED, new UnsignedWordElement(47027)), // [1,5]
						m(GoodWe.ChannelId.LED_EXTERNAL_CONTROL, new UnsignedWordElement(47028)), // [42343]
						new DummyRegisterElement(47029, 47037), //
						// 1 Enable, After restart the inverter, setting saved
						m(GoodWe.ChannelId.STOP_MODE_SAVE_ENABLE, new UnsignedWordElement(47038)))//
		);
	}

	private void handleDspVersion5(ModbusProtocol protocol) throws OpenemsException {
		// Registers 36000 for COM_MODE throw "Illegal Data Address"

		protocol.addTasks(//
				new FC3ReadRegistersTask(36001, Priority.LOW, //
						m(GoodWe.ChannelId.RSSI, new UnsignedWordElement(36001))), //
				new FC3ReadRegistersTask(36004, Priority.LOW, //
						m(GoodWe.ChannelId.METER_COMMUNICATE_STATUS, new UnsignedWordElement(36004))), //
				new FC3ReadRegistersTask(36015, Priority.LOW, //
						m(GoodWe.ChannelId.E_TOTAL_SELL, new FloatDoublewordElement(36015), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.E_TOTAL_BUY_F, new FloatDoublewordElement(36017), SCALE_FACTOR_MINUS_1), //
						m(GoodWe.ChannelId.METER_ACTIVE_POWER_R, new SignedDoublewordElement(36019)), //
						m(GoodWe.ChannelId.METER_ACTIVE_POWER_S, new SignedDoublewordElement(36021)), //
						m(GoodWe.ChannelId.METER_ACTIVE_POWER_T, new SignedDoublewordElement(36023)), //
						m(GoodWe.ChannelId.METER_TOTAL_ACTIVE_POWER, new SignedDoublewordElement(36025)), //
						m(GoodWe.ChannelId.METER_REACTIVE_POWER_R, new SignedDoublewordElement(36027)), //
						m(GoodWe.ChannelId.METER_REACTIVE_POWER_S, new SignedDoublewordElement(36029)), //
						m(GoodWe.ChannelId.METER_REACTIVE_POWER_T, new SignedDoublewordElement(36031)), //
						m(GoodWe.ChannelId.METER_TOTAL_REACTIVE_POWER, new SignedDoublewordElement(36033)), //
						m(GoodWe.ChannelId.METER_APPARENT_POWER_R, new SignedDoublewordElement(36035)), //
						m(GoodWe.ChannelId.METER_APPARENT_POWER_S, new SignedDoublewordElement(36037)), //
						m(GoodWe.ChannelId.METER_APPARENT_POWER_T, new SignedDoublewordElement(36039)), //
						m(GoodWe.ChannelId.METER_TOTAL_APPARENT_POWER, new SignedDoublewordElement(36041)), //
						// Only for GoodWe smart meter
						m(GoodWe.ChannelId.METER_TYPE, new UnsignedWordElement(36043)), //
						m(GoodWe.ChannelId.METER_SOFTWARE_VERSION, new UnsignedWordElement(36044)), //
						// Only for AC coupled inverter. Detect Pv Meter
						m(GoodWe.ChannelId.METER_CT2_ACTIVE_POWER, new SignedDoublewordElement(36045)), //
						m(GoodWe.ChannelId.CT2_E_TOTAL_SELL, new UnsignedDoublewordElement(36047),
								SCALE_FACTOR_MINUS_2), //
						m(GoodWe.ChannelId.CT2_E_TOTAL_BUY, new UnsignedDoublewordElement(36049), SCALE_FACTOR_MINUS_2), //
						m(GoodWe.ChannelId.METER_CT2_STATUS, new UnsignedWordElement(36051)) //
				));
	}

	protected ModbusElement getSocModbusElement(int address) {
		if (this instanceof HybridEss) {
			return m(SymmetricEss.ChannelId.SOC, new UnsignedWordElement(address), new ElementToChannelConverter(
					// element -> channel
					value -> {
						// Set SoC to undefined if there is No Battery
						EnumReadChannel batteryModeChannel = this.channel(GoodWe.ChannelId.BATTERY_MODE);
						BatteryMode batteryMode = batteryModeChannel.value().asEnum();
						if (batteryMode == BatteryMode.NO_BATTERY || batteryMode == BatteryMode.UNDEFINED) {
							return null;
						} else {
							return value;
						}
					},
					// channel -> element
					value -> value));

		} else {
			return new DummyRegisterElement(address);
		}
	}

	@Override
	public final void addCharger(GoodWeCharger charger) {
		this.chargers.add(charger);
	}

	@Override
	public final void removeCharger(GoodWeCharger charger) {
		this.chargers.remove(charger);
	}

	/**
	 * Gets the PV production from chargers ACTUAL_POWER. Returns null if the PV
	 * production is not available.
	 *
	 * @return production power
	 */
	protected final Integer calculatePvProduction() {
		Integer productionPower = null;
		for (GoodWeCharger charger : this.chargers) {
			productionPower = sumInteger(productionPower, charger.getActualPower().get());
		}
		return productionPower;
	}

	protected void updatePowerAndEnergyChannels(Integer soc, Integer batteryCurrent) {
		final var productionPower = this.calculatePvProduction();
		final Channel<Integer> pBattery1Channel = this.channel(GoodWe.ChannelId.P_BATTERY1);
		final Channel<Integer> pBattery2Channel = this.channel(GoodWe.ChannelId.P_BATTERY2);
		var dcDischargePower = pBattery1Channel.value().asOptional() //
				.map(t -> t + pBattery2Channel.value().orElse(0)) //
				.orElse(pBattery2Channel.value().get());

		final IntegerReadChannel dcDischargePowerChannel = this.channel(this.dcDischargePowerChannelId);

		/*
		 * Ignore impossible values of P_BATTERY
		 */
		dcDischargePower = postprocessPBattery1(dcDischargePower, this.getWbmsVoltage().get(),
				this.getGoodweType().maxDcCurrent.apply(null),
				state -> this.channel(GoodWe.ChannelId.IGNORE_IMPOSSIBLE_P_BATTERY_VALUE).setNextValue(state),
				dcDischargePowerChannel.value().asOptional());

		dcDischargePower = ignoreImpossibleMinPower(dcDischargePower, soc, batteryCurrent,
				((EnumReadChannel) this.channel(GoodWe.ChannelId.EMS_POWER_MODE)).getNextValue().asEnum(),
				this.getEmsPowerSetChannel().getNextValue().get());

		var acActivePower = sumInteger(productionPower, dcDischargePower);

		/*
		 * Update AC Active Power
		 */
		IntegerReadChannel activePowerChannel = this.channel(this.activePowerChannelId);
		activePowerChannel.setNextValue(acActivePower);

		/*
		 * Calculate AC Energy
		 */
		if (acActivePower == null) {
			// Not available
			this.calculateAcChargeEnergy.update(null);
			this.calculateAcDischargeEnergy.update(null);
		} else if (acActivePower > 0) {
			// Discharge
			this.calculateAcChargeEnergy.update(0);
			this.calculateAcDischargeEnergy.update(acActivePower);
		} else {
			// Charge
			this.calculateAcChargeEnergy.update(acActivePower * -1);
			this.calculateAcDischargeEnergy.update(0);
		}

		/*
		 * Update DC Discharge Power
		 */
		dcDischargePowerChannel.setNextValue(dcDischargePower);

		/*
		 * Calculate DC Energy
		 */
		if (dcDischargePower == null) {
			// Not available
			this.calculateDcChargeEnergy.update(null);
			this.calculateDcDischargeEnergy.update(null);
		} else if (dcDischargePower > 0) {
			// Discharge
			this.calculateDcChargeEnergy.update(0);
			this.calculateDcDischargeEnergy.update(dcDischargePower);
		} else {
			// Charge
			this.calculateDcChargeEnergy.update(dcDischargePower * -1);
			this.calculateDcDischargeEnergy.update(0);
		}
	}

	/**
	 * Postprocess PBattery1 value.
	 * 
	 * <p>
	 * Impossible battery power values will be ignored.
	 * 
	 * <p>
	 * The total max DC current including a buffer of 10A is used to identify an
	 * impossible power value, as WBMS_VOLTAGE has lower priority than pBattery and
	 * the battery could charge even if the BmsChargeMaxCurrent is 0.
	 * 
	 * @param pBattery     battery power
	 * @param dcVoltage    dc voltage
	 * @param maxDcCurrent max dc current
	 * @param setState     consume state
	 * @param prevPBattery previous battery power
	 * @return possible battery power
	 */
	protected static Integer postprocessPBattery1(Integer pBattery, Integer dcVoltage, Integer maxDcCurrent,
			Consumer<Boolean> setState, Optional<Integer> prevPBattery) {

		/*
		 * Values above maximum charge/discharge power
		 */
		var stateIgnoreImpossiblePBatteryValue = false;
		if (pBattery != null && dcVoltage != null && maxDcCurrent != null) {

			var impossibleDcChargePower = (maxDcCurrent + 10) * dcVoltage;
			var impossibleDcDischargePower = (maxDcCurrent + 10) * dcVoltage;

			if (pBattery < impossibleDcChargePower * -1 || pBattery > impossibleDcDischargePower) {
				stateIgnoreImpossiblePBatteryValue = true;
				pBattery = prevPBattery
						.orElse(fitWithin(maxDcCurrent * dcVoltage * -1, maxDcCurrent * dcVoltage, pBattery));
			}
		}

		setState.accept(stateIgnoreImpossiblePBatteryValue);
		return pBattery;
	}

	/**
	 * Ignore impossible minimum power.
	 * 
	 * <p>
	 * Even if there is no real power charged to the battery, the GoodWe Channel
	 * could remain on minimum power values. These values are ignored.
	 * 
	 * @param goodweDcPower GoodWe DC battery power in W
	 * @param soc           State of Charge
	 * @param cBattery      Battery current in A
	 * @param powerMode     EMS power mode
	 * @param powerSet      EMS power set
	 * @return possible battery power
	 */
	protected static Integer ignoreImpossibleMinPower(Integer goodweDcPower, Integer soc, Integer cBattery,
			EmsPowerMode powerMode, Long powerSet) {
		if (cBattery == null || soc == null || goodweDcPower == null || cBattery != 0 || powerMode == null
				|| powerSet == null) {
			return goodweDcPower;
		}

		final var batFullOrEmpty = soc >= 100 || soc <= 0;
		final var emsTargetOfZero = (powerMode == EmsPowerMode.CHARGE_BAT || powerMode == EmsPowerMode.DISCHARGE_BAT)
				&& powerSet == 0;

		if (batFullOrEmpty || emsTargetOfZero) {
			return Math.abs(goodweDcPower) < 100 /* W */ ? 0 : goodweDcPower;
		}

		return goodweDcPower;
	}

	public record MaxAcPower(int maxAcImport, int maxAcExport) {
	}

	/**
	 * Calculate and store Max-AC-Export and -Import channels.
	 *
	 * <p>
	 * Calculate and store Max-AC-Export and -Import for use in
	 * getStaticConstraints()
	 * </p>
	 *
	 * @param maxApparentPower the max apparent power
	 * @param battery          the battery used to get charge/discharge current and
	 *                         voltage
	 */
	protected void handleMaxAcPower(int maxApparentPower, Battery battery) {
		this.handleMaxAcPower(maxApparentPower, battery.getChargeMaxCurrent().get(),
				battery.getDischargeMaxCurrent().get(), battery.getVoltage().get());
	}

	/**
	 * Calculate and store Max-AC-Export and -Import channels.
	 *
	 * <p>
	 * Calculate and store Max-AC-Export and -Import for use in
	 * getStaticConstraints()
	 * </p>
	 * 
	 * @param maxApparentPower       the max apparent power
	 * @param batChargeMaxCurrent    the charge max current of the battery
	 * @param batDischargeMaxCurrent the discharge max current of the battery
	 * @param batVoltage             the voltage of the battery
	 */
	protected void handleMaxAcPower(int maxApparentPower, Integer batChargeMaxCurrent, Integer batDischargeMaxCurrent,
			Integer batVoltage) {

		final var result = calculateMaxAcPower(//
				maxApparentPower, //
				batChargeMaxCurrent, //
				batDischargeMaxCurrent, //
				batVoltage, //
				this.getGoodweType().maxBatChargeP, //
				this.getGoodweType().maxBatDischargeP, //
				this.calculatePvProduction());

		// Set Channels
		this._setMaxAcImport(result.maxAcImport);
		this._setMaxAcExport(result.maxAcExport);
	}

	/**
	 * Calculate Max-AC-Export and -Import power.
	 *
	 * @param maxApparentPower       the max apparent power
	 * @param batChargeMaxCurrent    the charge max current of the battery
	 * @param batDischargeMaxCurrent the discharge max current of the battery
	 * @param batVoltage             the voltage of the battery
	 * @param maxInvDcChargeP        the maximum inverter DC charge power
	 * @param maxInvDcDischargeP     the maximum inverter DC discharge power
	 * @param pvProductionInteger    the DC production power; or null
	 * @return MaxAcPower with maxAcImport and maxAcExport
	 */
	protected static MaxAcPower calculateMaxAcPower(int maxApparentPower, Integer batChargeMaxCurrent,
			Integer batDischargeMaxCurrent, Integer batVoltage, Integer maxInvDcChargeP, Integer maxInvDcDischargeP,
			Integer pvProductionInteger) {
		final var pvProduction = maxInt(0, pvProductionInteger);

		/*
		 * Calculate Max-Ac-Import
		 */
		final var maxDcChargePower = calculateDcLimitation(batChargeMaxCurrent, batVoltage, maxInvDcChargeP);

		var maxAcImport = maxDcChargePower //
				- min(//
						maxInt(0, maxDcChargePower) /* avoid negative number for `subtract` */, //
						pvProduction);

		/*
		 * Calculate Max-Ac-Export
		 */
		final var maxDcDischargePower = calculateDcLimitation(batDischargeMaxCurrent, batVoltage, maxInvDcDischargeP);
		var maxAcExport = sumInt(pvProduction, maxDcDischargePower);

		// Limit Max-AC-Power to inverter specific limit
		maxAcImport = min(maxApparentPower, maxAcImport);
		maxAcExport = min(maxApparentPower, maxAcExport);

		return new MaxAcPower(//
				-maxAcImport /* negate */, // Max-Ac-Import is negative
				maxAcExport);
	}

	/**
	 * Calculate DC Limitation.
	 *
	 * <p>
	 * Calculate the maximum DC power limit based on BMS max current, voltage & a
	 * fixed inverter limit.
	 * </p>
	 *
	 * @param bmsMaxCurrent BMS maximum current in A. Can be negative for
	 *                      force-discharge
	 * @param voltage       voltage in V
	 * @param inverterLimit hard limit for DC power in W
	 * @return the maximum DC power in W
	 */
	protected static int calculateDcLimitation(Integer bmsMaxCurrent, Integer voltage, Integer inverterLimit) {
		final var bmsLimit = bmsMaxCurrent == null || voltage == null //
				? 0 //
				: bmsMaxCurrent * voltage;

		return minInteger(bmsLimit, inverterLimit);
	}

	/**
	 * Detect the current diagnostic high states.
	 * 
	 * @param value register value
	 * @return DiagnosticStates with the information if it is active or not
	 */
	protected static Map<GoodWe.ChannelId, Boolean> detectDiagStatesH(Long value) {
		return DIAG_STATUS_H_STATES.entrySet().stream().collect(
				Collectors.toMap(Map.Entry::getValue, e -> !(Objects.isNull(value) || (value & e.getKey()) == 0)));
	}

	/**
	 * Gets Surplus Power.
	 *
	 * @return {@link Integer}
	 */
	public abstract Integer getSurplusPower();

	/**
	 * Generates a Channel-ID for channels that are specific to a State.
	 *
	 * <p>
	 * Generated ChannelName e.g. "GW_STATE_32007_B_10"
	 * </p>
	 *
	 * @param register            register of the state
	 * @param bit                 bit of the state
	 * @param openemsType         specified type e.g. "INTEGER"
	 * @param additionalDocConfig the additional doc configuration
	 * @param channelConsumer     the additional configuration on the created
	 *                            channel
	 * @return the Channel-ID
	 */
	private ChannelIdImpl generateStateChannel(int register, int bit, OpenemsType openemsType,
			Consumer<OpenemsTypeDoc<?>> additionalDocConfig, Consumer<Channel<?>> channelConsumer) {
		final var doc = Doc.of(openemsType);
		if (additionalDocConfig != null) {
			additionalDocConfig.accept(doc);
		}
		var channelId = new ChannelIdImpl("GW_STATE_" + register + "_B_" + bit, doc);
		final var channel = this.addChannel(channelId);

		channelConsumer.accept(channel);

		return channelId;
	}

	private BitsWordElement generateElementsAndChannels(int address, List<GwState> gwStates) {
		var bitsElement = new BitsWordElement(address, this);
		for (var state : gwStates) {
			var channelId = this.generateStateChannel(address, state.bit(), OpenemsType.BOOLEAN,
					t -> t.text(state.description()), FunctionUtils::doNothing);
			bitsElement.bit(state.bit(), channelId);
		}
		return bitsElement;
	}

	@VisibleForTesting
	static GridMode mapGridMode(GoodWeType goodWeType, Integer value, boolean isGensetActive) {
		if (value == null) {
			return GridMode.UNDEFINED;
		}
		return switch (goodWeType) {
		case UNDEFINED, GOODWE_10K_BT, GOODWE_8K_BT, GOODWE_5K_BT, GOODWE_10K_ET, GOODWE_8K_ET, GOODWE_5K_ET,
				FENECON_FHI_10_DAH, FENECON_FHI_20_DAH, FENECON_FHI_29_9_DAH, FENECON_GEN2_6K, FENECON_GEN2_10K,
				FENECON_GEN2_15K -> {
			yield defaultMapGridMode(value);
		}
		case FENECON_50K, FENECON_100K -> {
			if (isGensetActive) {
				yield GridMode.OFF_GRID_GENSET;
			}
			yield switch (value) {
			case 0 -> GridMode.OFF_GRID;
			case 1 -> GridMode.ON_GRID;
			default -> GridMode.UNDEFINED;
			};
		}
		case null -> defaultMapGridMode(value);
		};
	}

	private static GridMode defaultMapGridMode(int value) {
		return switch (value) {
		case 1 -> GridMode.ON_GRID;
		case 2 -> GridMode.OFF_GRID;
		default -> GridMode.UNDEFINED;
		};
	}

	protected static void removeTasks(//
			ModbusProtocol protocol, //
			List<Task> removingTasks //
	) {
		removingTasks.stream() //
				.forEach(protocol::removeTask);
	}

}
