/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.dsmr.internal.messages; import java.lang.reflect.Constructor; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import org.openhab.binding.dsmr.internal.cosem.CosemDate; import org.openhab.binding.dsmr.internal.cosem.CosemInteger; import org.openhab.binding.dsmr.internal.cosem.CosemValue; import org.openhab.binding.dsmr.internal.cosem.CosemValueDescriptor; import org.openhab.core.types.State; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class for OBIS Message implementation * * @author M. Volaart * @since 1.7.0 */ public class OBISMessage { // logger private static final Logger logger = LoggerFactory.getLogger(OBISMessage.class); // Identifier of the first power failure date element public static final int FIRST_POWER_FAILURE_DATE = 2; // Identifier of the first power failure duration element public static final int FIRST_POWER_FAILURE_DURATION = 3; // OBIS Message Type private final OBISMsgType msgType; // List of COSEM value in this message private List<CosemValue<? extends State>> cosemValues; /** * Construct a new OBISMessage with the specified OBIS Message Type * * @param msgType * {@link OBISMsgType} */ public OBISMessage(OBISMsgType msgType) { this.msgType = msgType; cosemValues = new ArrayList<CosemValue<? extends State>>(); } /** * Return the {@link OBISMsgType} for this OBIS message * * @return the {@link OBISMsgType} for this OBIS message */ public OBISMsgType getMsgType() { return msgType; } /** * Returns string representation of this OBISMessage * * @return string representation of this OBISMessage */ @Override public String toString() { return "OBIS Message(type:" + msgType.toString() + ", description:" + msgType.description + ", openHABValues:" + cosemValues + ")"; } /** * Returns the openHAB values that are part of this OBIS message * * @return List of {@link CosemValue} that are part of this OBIS message */ public List<? extends CosemValue<? extends Object>> getOpenHABValues() { return cosemValues; } /** * Parses the List of COSEM String value to internal openHAB values. * <p> * When the parser has problems it throws an {@link ParseException}. The * already parsed values will still be available. It is up to the caller how * to handle a partially parsed message. * * @param cosemStringValues * the List of COSEM String values * @throws ParseException * if parsing fails */ public void parseCosemValues(List<String> cosemStringValues) throws ParseException { logger.debug("Received items: {}, Needed items: {}", cosemStringValues.size(), msgType.cosemValueDescriptors.size()); /* * It is not necessarily a problem if 'Needed items' > 'Received items'. * Since some items have a dynamic number of values (e.g. Power Failure * Log). * * Since the minority of the messages has such features, differences * between received and needed could indicate problems */ if (cosemStringValues.size() <= msgType.cosemValueDescriptors.size()) { for (int i = 0; i < cosemStringValues.size(); i++) { CosemValue<? extends State> cosemValue = getCosemValue(msgType.cosemValueDescriptors.get(i)); if (cosemValue != null) { cosemValue.setValue(cosemStringValues.get(i)); cosemValues.add(cosemValue); } else { logger.error("Failed to parse: {} for OBISMsgType: {}", cosemStringValues.get(i), msgType); } } } else { throw new ParseException("Received items:" + cosemStringValues.size() + ", Needed items:" + msgType.cosemValueDescriptors.size(), 0); } /* * Here we do a post processing on the values */ switch (msgType) { case EMETER_POWER_FAILURE_LOG: postProcessKaifaE0003(); break; default: break; } } /** * Creates an empty CosemValue object * * @param cosemValueDescriptor * the CosemValueDescriptor object that describes the CosemValue * @return the instantiated CosemValue based on the specified * CosemValueDescriptor */ private CosemValue<? extends State> getCosemValue(CosemValueDescriptor cosemValueDescriptor) { Class<? extends CosemValue<? extends State>> cosemValueClass = cosemValueDescriptor.getCosemValueClass(); String unit = cosemValueDescriptor.getUnit(); String dsmrItemId = cosemValueDescriptor.getDsmrItemId(); try { Constructor<? extends CosemValue<? extends State>> c = cosemValueClass.getConstructor(String.class, String.class); return c.newInstance(unit, dsmrItemId); } catch (ReflectiveOperationException roe) { logger.error("Failed to create {} message", msgType.obisId, roe); } return null; } /** * On the Kaifa E0003 we have seen power failure entries that occurred at * 1-1-1970 and have a 2^32 - 1 duration * * This method filters the values belonging to this entry */ private void postProcessKaifaE0003() { logger.debug("postProcessKaifaE0003"); /* * The list of cosemValues for this OBIS Message is: * - [0] Number of entries in the list * - [1] Cosem Identifier * - [2] power failure date entry 1 [Optional] * - [3] power failure duration entry 1 [Optional] * - [entry n * 2] power failure date entry n * - [entry n * 2 + 1] power failure duration entry n */ // First check of there is at least one entry of a power failure present // (i.e. date at idx 2 and duration at idx 3) if (cosemValues.size() > FIRST_POWER_FAILURE_DURATION) { CosemDate powerFailureDate = (CosemDate) cosemValues.get(FIRST_POWER_FAILURE_DATE); CosemInteger powerFailureDuration = (CosemInteger) cosemValues.get(FIRST_POWER_FAILURE_DURATION); Calendar epoch = Calendar.getInstance(); epoch.setTime(new Date(0)); // Check if the first entry it as epoc and has a 2^32-1 value // If so, filter this value, since it has no added value if (powerFailureDate.getValue().getCalendar().before(epoch) && powerFailureDuration.getValue().intValue() == Integer.MAX_VALUE) { logger.debug("Filter invalid power failure entry"); cosemValues.remove(FIRST_POWER_FAILURE_DURATION); cosemValues.remove(FIRST_POWER_FAILURE_DATE); } } } }