/** * verifies response on availability of data * * @param response of heat pump * @param request request defined for heat pump response * @return Map of Strings with name and values */ public Map<String, String> parseRecords(final byte[] response, Request request) throws StiebelHeatPumpException { Map<String, String> map = new HashMap<String, String>(); logger.debug("Parse bytes: {}", DataParser.bytesToHex(response)); if (response.length < 2) { logger.error( "response does not have a valid length of bytes: {}", DataParser.bytesToHex(response)); return map; } // parse response and fill map for (RecordDefinition recordDefinition : request.getRecordDefinitions()) { try { String value = parseRecord(response, recordDefinition); logger.debug( "Parsed value {} -> {} with pos: {} , len: {}", recordDefinition.getName(), value, recordDefinition.getPosition(), recordDefinition.getLength()); map.put(recordDefinition.getName(), value); } catch (StiebelHeatPumpException e) { continue; } } return map; }
/** * parses a single record * * @param response of heat pump * @param RecordDefinition that shall be used for parsing the heat pump response * @return string value of the parse response * @throws StiebelHeatPumpException */ public String parseRecord(byte[] response, RecordDefinition recordDefinition) throws StiebelHeatPumpException { try { if (response.length < 2) { logger.error( "response does not have a valid length of bytes: {}", DataParser.bytesToHex(response)); throw new StiebelHeatPumpException(); } ByteBuffer buffer = ByteBuffer.wrap(response); short number = 0; byte[] bytes = null; switch (recordDefinition.getLength()) { case 1: bytes = new byte[1]; System.arraycopy(response, recordDefinition.getPosition(), bytes, 0, 1); number = Byte.valueOf(buffer.get(recordDefinition.getPosition())); break; case 2: bytes = new byte[2]; System.arraycopy(response, recordDefinition.getPosition(), bytes, 0, 2); number = buffer.getShort(recordDefinition.getPosition()); break; } if (recordDefinition.getBitPosition() > 0) { int returnValue = getBit(bytes, recordDefinition.getBitPosition()); return String.valueOf(returnValue); } if (recordDefinition.getScale() != 1.0) { double myDoubleNumber = number * recordDefinition.getScale(); myDoubleNumber = Math.round(myDoubleNumber * 100.0) / 100.0; String returnString = String.format("%s", myDoubleNumber); return returnString; } return String.valueOf(number); } catch (Exception e) { logger.error( "response {} could not be parsed for record definition {} ", DataParser.bytesToHex(response), recordDefinition.getName()); throw new StiebelHeatPumpException(); } }
private Object traverse(Node node) throws FlatwormUnsetFieldValueException, FlatwormConfigurationValueException { int type = node.getNodeType(); if (type == Node.ELEMENT_NODE) { String nodeName = node.getNodeName(); if (nodeName.equals("file-format")) { FileFormat f = new FileFormat(); String encoding = Charset.defaultCharset().name(); if (hasAttributeValueNamed(node, "encoding")) { encoding = getAttributeValueNamed(node, "encoding"); } f.setEncoding(encoding); List<Object> children = getChildNodes(node); for (int i = 0; i < children.size(); i++) { if (children.get(i).getClass().equals(Converter.class)) { f.addConverter((Converter) children.get(i)); } if (children.get(i).getClass().equals(Record.class)) { f.addRecord((Record) children.get(i)); } } return f; } if (nodeName.equals("converter")) { Converter c = new Converter(); c.setConverterClass(getAttributeValueNamed(node, "class")); c.setMethod(getAttributeValueNamed(node, "method")); c.setReturnType(getAttributeValueNamed(node, "return-type")); c.setName(getAttributeValueNamed(node, "name")); return c; } if (nodeName.equals("record")) { Record r = new Record(); r.setName(getAttributeValueNamed(node, "name")); Node identChild = getChildElementNodeOfType("record-ident", node); if (identChild != null) { Node fieldChild = getChildElementNodeOfType("field-ident", identChild); Node lengthChild = getChildElementNodeOfType("length-ident", identChild); if (lengthChild != null) { r.setLengthIdentMin(Integer.parseInt(getAttributeValueNamed(lengthChild, "minlength"))); r.setLengthIdentMax(Integer.parseInt(getAttributeValueNamed(lengthChild, "maxlength"))); r.setIdentTypeFlag('L'); } else if (fieldChild != null) { r.setFieldIdentStart( Integer.parseInt(getAttributeValueNamed(fieldChild, "field-start"))); r.setFieldIdentLength( Integer.parseInt(getAttributeValueNamed(fieldChild, "field-length"))); List<Node> matchNodes = getChildElementNodesOfType("match-string", fieldChild); for (int j = 0; j < matchNodes.size(); j++) { r.addFieldIdentMatchString(getChildTextNodeValue(matchNodes.get(j))); } r.setIdentTypeFlag('F'); } } Node recordChild = getChildElementNodeOfType("record-definition", node); r.setRecordDefinition((RecordDefinition) traverse(recordChild)); return r; } if (nodeName.equals("record-definition")) { RecordDefinition rd = new RecordDefinition(); List<Object> children = getChildNodes(node); for (int i = 0; i < children.size(); i++) { Object o = children.get(i); if (o.getClass().equals(Bean.class)) { rd.addBeanUsed((Bean) o); } if (o.getClass().equals(Line.class)) { rd.addLine((Line) o); } } return rd; } if (nodeName.equals("bean")) { Bean b = new Bean(); b.setBeanName(getAttributeValueNamed(node, "name")); b.setBeanClass(getAttributeValueNamed(node, "class")); try { b.setBeanObjectClass(Class.forName(b.getBeanClass())); } catch (ClassNotFoundException e) { throw new FlatwormConfigurationValueException("Unable to load class " + b.getBeanClass()); } return b; } if (nodeName.equals("line")) { Line li = new Line(); // JBL - Determine if this line is delimited // Determine value of quote character, default = " // These field is optional Node delimit = getAttributeNamed(node, "delimit"); Node quote = getAttributeNamed(node, "quote"); if (delimit != null) { li.setDelimeter(getAttributeValueNamed(node, "delimit")); } if (quote != null) { li.setQuoteChar(getAttributeValueNamed(node, "quote")); } List<Object> v = getChildNodes(node); for (int i = 0; i < v.size(); i++) { Object o = v.get(i); if (o instanceof LineElement) { li.addElement((LineElement) o); } } return li; } if (nodeName.equals("segment-element")) { SegmentElement segment = new SegmentElement(); segment.setCardinalityMode(CardinalityMode.LOOSE); segment.setName(getAttributeValueNamed(node, "name")); segment.setMinCount(Integer.parseInt(getAttributeValueNamed(node, "minCount"))); segment.setMaxCount(Integer.parseInt(getAttributeValueNamed(node, "maxCount"))); segment.setBeanRef(getAttributeValueNamed(node, "beanref")); segment.setParentBeanRef(getAttributeValueNamed(node, "parent-beanref")); segment.setAddMethod(getAttributeValueNamed(node, "addMethod")); String segmentMode = getAttributeValueNamed(node, "cardinality-mode"); if (!StringUtils.isBlank(segmentMode)) { if (segmentMode.toLowerCase().startsWith("strict")) { segment.setCardinalityMode(CardinalityMode.STRICT); } else if (segmentMode.toLowerCase().startsWith("restrict")) { segment.setCardinalityMode(CardinalityMode.RESTRICTED); } } Node fieldChild = getChildElementNodeOfType("field-ident", node); if (fieldChild != null) { segment.setFieldIdentStart( Integer.parseInt(getAttributeValueNamed(fieldChild, "field-start"))); segment.setFieldIdentLength( Integer.parseInt(getAttributeValueNamed(fieldChild, "field-length"))); List<Node> matchNodes = getChildElementNodesOfType("match-string", fieldChild); for (int j = 0; j < matchNodes.size(); j++) { segment.addFieldIdentMatchString(getChildTextNodeValue((Node) matchNodes.get(j))); } } validateSegmentConfiguration(segment); List<Object> v = getChildNodes(node); for (int i = 0; i < v.size(); i++) { Object o = v.get(i); if (o instanceof LineElement) { segment.addElement((LineElement) o); } } return segment; } if (nodeName.equals("record-element")) { RecordElement re = new RecordElement(); Node start = getAttributeNamed(node, "start"); Node end = getAttributeNamed(node, "end"); Node length = getAttributeNamed(node, "length"); Node beanref = getAttributeNamed(node, "beanref"); Node beanType = getAttributeNamed(node, "type"); if ((end == null) && (length == null)) { FlatwormConfigurationValueException err = new FlatwormConfigurationValueException( "Must set either the 'end' or 'length' properties"); throw err; } if ((end != null) && (length != null)) { FlatwormConfigurationValueException err = new FlatwormConfigurationValueException( "Can't specify both the 'end' or 'length' properties"); throw err; } if (start != null) { re.setFieldStart(Integer.parseInt(start.getNodeValue())); } if (end != null) { re.setFieldEnd(Integer.parseInt(end.getNodeValue())); } if (length != null) { re.setFieldLength(Integer.parseInt(length.getNodeValue())); } if (beanref != null) { re.setBeanRef(beanref.getNodeValue()); } if (beanType != null) { re.setType(beanType.getNodeValue()); } List<Node> children = getChildElementNodesOfType("conversion-option", node); for (int i = 0; i < children.size(); i++) { Node o = (Node) children.get(i); String name = getAttributeValueNamed(o, "name"); String value = getAttributeValueNamed(o, "value"); ConversionOption co = new ConversionOption(name, value); re.addConversionOption(name, co); } return re; } } return null; }
@Test // write new value for P04DHWTemperatureStandardMode use case public void testwriteP04DHWTemperatureStandardMode() throws StiebelHeatPumpException { List<Request> result = Requests.searchIn( configuration, new Matcher<Request>() { @Override public boolean matches(Request r) { return r.getName().equals("SettingsNominalValues"); } }); byte[] response = new byte[] { (byte) 0x01, (byte) 0x00, (byte) 0xf0, (byte) 0x17, (byte) 0x00, (byte) 0xa2, (byte) 0x00, (byte) 0xa5, (byte) 0x00, (byte) 0x64, (byte) 0x01, (byte) 0xc2, (byte) 0x01, (byte) 0xe0, (byte) 0x00, (byte) 0x64, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x5e, (byte) 0x01, (byte) 0xc2, (byte) 0x01, (byte) 0x10, (byte) 0x03 }; response = parser.fixDuplicatedBytes(response); Assert.assertEquals(response[3], result.get(0).getRequestByte()); Assert.assertEquals(response[2], parser.calculateChecksum(response)); Map<String, String> data = parser.parseRecords(response, result.get(0)); Assert.assertEquals("45.0", data.get("P04DHWTemperatureStandardMode")); Request request = result.get(0); RecordDefinition recordDefinition = null; for (RecordDefinition record : request.getRecordDefinitions()) { if (record.getName().equals("P04DHWTemperatureStandardMode")) { recordDefinition = record; break; } } byte[] newResponse = parser.composeRecord("45.5", response, recordDefinition); // update the checksum newResponse[2] = parser.calculateChecksum(newResponse); data = parser.parseRecords(newResponse, request); Assert.assertEquals("45.5", data.get("P04DHWTemperatureStandardMode")); }
@Test @Ignore // write new value for bitPosition use case public void testwriteSettingsDomesticWaterProgram() throws StiebelHeatPumpException { List<Request> result = Requests.searchIn( configuration, new Matcher<Request>() { @Override public boolean matches(Request r) { return r.getName().equals("SettingsDomesticHotWaterProgram"); } }); byte[] response = new byte[] { (byte) 0x01, (byte) 0x00, (byte) 0x1d, (byte) 0x0c, (byte) 0x08, (byte) 0x98, (byte) 0x01, (byte) 0xf4, (byte) 0x7b, (byte) 0x00, (byte) 0x10, (byte) 0x03 }; response = parser.fixDuplicatedBytes(response); Assert.assertEquals(response[3], result.get(0).getRequestByte()); Assert.assertEquals(response[2], parser.calculateChecksum(response)); Map<String, String> data = parser.parseRecords(response, result.get(0)); Assert.assertEquals("2200", data.get("BP1StartTime")); Assert.assertEquals("500", data.get("BP1StopTime")); Assert.assertEquals("1", data.get("BP1Monday")); Assert.assertEquals("1", data.get("BP1Tuesday")); Assert.assertEquals("0", data.get("BP1Wednesday")); Assert.assertEquals("1", data.get("BP1Thusday")); Assert.assertEquals("1", data.get("BP1Friday")); Assert.assertEquals("1", data.get("BP1Saturday")); Assert.assertEquals("1", data.get("BP1Sunday")); Assert.assertEquals("0", data.get("BP1Enabled")); Request request = result.get(0); RecordDefinition recordDefinition = null; for (RecordDefinition record : request.getRecordDefinitions()) { if (record.getName().equals("BP1Wednesday")) { recordDefinition = record; break; } } byte[] newResponse = parser.composeRecord("1", response, recordDefinition); // update the checksum newResponse[2] = parser.calculateChecksum(newResponse); data = parser.parseRecords(newResponse, request); Assert.assertEquals("1", data.get("BP1Wednesday")); Assert.assertEquals("2200", data.get("BP1StartTime")); Assert.assertEquals("500", data.get("BP1StopTime")); Assert.assertEquals("1", data.get("BP1Monday")); Assert.assertEquals("1", data.get("BP1Tuesday")); Assert.assertEquals("1", data.get("BP1Thusday")); Assert.assertEquals("1", data.get("BP1Friday")); Assert.assertEquals("1", data.get("BP1Saturday")); Assert.assertEquals("1", data.get("BP1Sunday")); Assert.assertEquals("0", data.get("BP1Enabled")); }
@Test // write new value for short byte value use case public void testWriteTime() throws StiebelHeatPumpException { List<Request> result = Requests.searchIn( configuration, new Matcher<Request>() { @Override public boolean matches(Request r) { return r.getName() == "Time"; } }); byte[] response = new byte[] { (byte) 0x01, (byte) 0x00, (byte) 0x79, (byte) 0xfc, (byte) 0x00, (byte) 0x02, (byte) 0x0a, (byte) 0x21, (byte) 0x24, (byte) 0x0e, (byte) 0x00, (byte) 0x03, (byte) 0x1a, (byte) 0x10, (byte) 0x03 }; response = parser.fixDuplicatedBytes(response); Assert.assertEquals(response[3], result.get(0).getRequestByte()); Assert.assertEquals(response[2], parser.calculateChecksum(response)); Map<String, String> data = parser.parseRecords(response, result.get(0)); Assert.assertEquals("2", data.get("WeekDay")); Assert.assertEquals("10", data.get("Hours")); Assert.assertEquals("33", data.get("Minutes")); Assert.assertEquals("36", data.get("Seconds")); Assert.assertEquals("14", data.get("Year")); Assert.assertEquals("3", data.get("Month")); Assert.assertEquals("26", data.get("Day")); byte[] resultingBytes = new byte[] { (byte) 0x01, (byte) 0x80, (byte) 0xf1, (byte) 0xfc, (byte) 0x00, (byte) 0x02, (byte) 0x0a, (byte) 0x22, (byte) 0x1b, (byte) 0x0e, (byte) 0x00, (byte) 0x03, (byte) 0x1a, (byte) 0x10, (byte) 0x03 }; Request request = result.get(0); RecordDefinition recordDefinition = null; for (RecordDefinition record : request.getRecordDefinitions()) { if (record.getName() == "Minutes") { recordDefinition = record; break; } } byte[] newResponse = parser.composeRecord("34", response, recordDefinition); for (RecordDefinition record : request.getRecordDefinitions()) { if (record.getName() == "Seconds") { recordDefinition = record; break; } } Throwable e = null; try { newResponse = parser.composeRecord("90", newResponse, recordDefinition); } catch (Throwable ex) { e = ex; } Assert.assertTrue(e instanceof StiebelHeatPumpException); newResponse = parser.composeRecord("27", newResponse, recordDefinition); // update the checksum newResponse[2] = parser.calculateChecksum(newResponse); for (int i = 0; i < newResponse.length; i++) { Assert.assertEquals(resultingBytes[i], newResponse[i]); } }
/** * composes the new value of a record definition into a updated set command that can be send back * to heat pump * * @param response of heat pump that should be updated with new value * @param RecordDefinition that shall be used for compose the new value into the heat pump set * command * @param string value to be compose * @return byte[] ready to send to heat pump * @throws StiebelHeatPumpException */ public byte[] composeRecord(String value, byte[] response, RecordDefinition recordDefinition) throws StiebelHeatPumpException { short newValue = 0; if (recordDefinition.getDataType() != Type.Settings) { logger.warn( "The record {} can not be set as it is not a setable value!", recordDefinition.getName()); throw new StiebelHeatPumpException("record is not a setting!"); } double number = Double.parseDouble(value); if (number > recordDefinition.getMax() || number < recordDefinition.getMin()) { logger.warn( "The record {} can not be set to value {} as allowed range is {}<-->{} !", recordDefinition.getName(), value, recordDefinition.getMax(), recordDefinition.getMin()); throw new StiebelHeatPumpException("invalid value !"); } // change response byte to setting command response[1] = SET; // reverse the scale if (recordDefinition.getScale() != 1.0) { number = number / recordDefinition.getScale(); newValue = (short) number; } // set new bit values in a byte if (recordDefinition.getBitPosition() > 0) { byte[] abyte = new byte[] {response[recordDefinition.getPosition()]}; abyte = setBit(abyte, recordDefinition.getBitPosition(), newValue); response[recordDefinition.getPosition()] = abyte[0]; return response; } // create byte values for single and double byte values // and update response switch (recordDefinition.getLength()) { case 1: byte newByteValue = (byte) number; response[recordDefinition.getPosition()] = newByteValue; break; case 2: byte[] newByteValues = shortToByte(newValue); int position = recordDefinition.getPosition(); response[position] = newByteValues[1]; response[position + 1] = newByteValues[0]; break; } response[2] = this.calculateChecksum(response); response = this.addDuplicatedBytes(response); logger.debug( "Updated record {} at position {} to value {}.", recordDefinition.getName(), recordDefinition.getPosition(), value); return response; }