/* * Test implementation of systacomfort2 node * * Project: de.captica.nodered.node.systacomfort2 * Author: Marc Böhm * Copyright (c) captica GmbH est. 2022 / MIT License */ const { expect } = require('chai') const dgram = require('node:dgram') const systaComfort2Node = require('../systacomfort2/systacomfort2') const { dataParser, SYSTACOMFORT_VALUES } = require('../systacomfort2/dataparser') const { assert } = require('node:console') const MESSAGE_INIT = 'AJe+LI4NzW0JCQwAMtoAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAIAAAEAAAACAAAAAAAAADIAAAAyAAAAMgAAAGQAAAC4MP4/APgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqAwAAGgMAAE4DAAA8AwAAUgEAAAAAAAA5AAAAlwMAAA0AAAAAAAAAvAIAAIQDAAC8AgAAvAAAAC6TBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy9tHAsmsAQBQAAAANwAAAAAAAAAEAAAAGgMAAGsDAABOAwAAUgEAAAAAAABrAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAruTDkA==' const MESSAGE_INIT_ANSWER = 'AJe+LI4NzW0AAAAAAQAAABGcjK0=' const MESSAGE_DATA = 'AJe+LI4NzG0JCQwAMtoAAAEAAAAAAAAAOgEAAFYBAADU/v//SwMAAEUDAABCAwAA1P7//9T+///U/v//AAAAAAAAAABrAwAAXwIAAFoCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgCAADNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAyAAAAOYAAACgAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA+gAAAAAAAAAGAAAAAAAAACYCAADIAAAAZAAAABQAAAB4AAAAAAAAADIAAABQAAAAZAAAAAUAAAACAAAAMgAAAB4AAAAAAAAAAAAAAAAAAAADAAAAMgAAAPQBAAAEAAAAMgAAAHISAADYAQAAAAAAAAAAAAABAAAAyAAAANwAAACWAAAAAAAAAAAAAAAAAAAAIwcAACQHAAAAAAAAXgEAAAAAAAANAAAAAAAAALwCAADIAAAAZAAAABQAAAB4AAAAAAAAAAAAAADIAAAAZAAAAAUAAAACAAAAMgAAAB4AAAAAAAAAAAAAAAAAAAADAAAAMgAAAPQBAAAEAAAAMgAAACMHAAAAAAAAAAAAAAAAAAABAAAAyAAAANwAAACWAAAAAAAAAAAAAAAAAAAAIwcAACQHAABeAQAAAAAAAA0AAAAAAAAAvAIAAMgAAABkAAAAFAAAAHgAAAAAAAAAAAAAAMgAAABkAAAABQAAAAIAAAAAAAAAAAAAAAMAAAAyAAAA9AEAAAQAAAAyAAAAIwcAAAAAAABYAgAAigIAAAEAAABkAAAAZAAAAAAAAAAyAAAAUgMAAAAAAAAFAAAAtgMAAAAAAAAyAAAAAAAAAJABAABkAAAAAQAAAAEAAAADAAAAAwAAAAMAAAAPAAAAMgAAAKQAAAAAAAAAAAAAABMAAAAFAAAAAAAAAAAAAAAjHAAAIwAAAAVZAAANAAAAEgAAAB51AAAcAAAAAAAAAAAAAAAAAAAALAEAAIoCAABkAAAAGQAAAAAAAAAAAAAA+gAAABgBAAAAAAAAAAAAAAAAAAC8AgAAAAAAAMgAAABkAAAAAgAAAMgAAAAeAAAAAAAAABQAAAAeAAAAHgAAADIAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAQAAAAEAAAAHAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/h54Ug==' const TOPIC_PREFIX = 'my/custom/topic/prefix' const CONFIG = { listenPort: 44556 } const OUTPUT_MESSAGE_PAYLOAD = { BOILER_START_COUNTER: 22789, BOILER_WORKING_HOURS: 7203, BUFFER_TANK_TEMPERATURE_TOP: 83.7, BUFFER_TANK_TEMPERATURE_BOTTOM: 83.4, SOLAR_YIELD_CURRENT: 1.3, SOLAR_YIELD_DAY: 18, SOLAR_YIELD_TOTAL: 29982, SOLAR_TEMPERATURE_COLLECTOR:87.5, SYSTEM_TEMPERATURE_FLOW: 34.2, SYSTEM_TEMPERATURE_FLOW_RETURN: null, SYSTEM_TEMPERATURE_EXTERNAL: 31.4, SYSTEM_TEMPERATURE_PROCESS_WATER_CURRENT: 84.3, SYSTEM_TEMPERATURE_PROCESS_WATER_TARGET: 60, SYSTEM_TEMPERATURE_ROOM_TARGET: 20.5, } /** * Node-RED registration/handling */ describe('SystaComfort2 - Node-RED node', () => { // Constructor will be announced on register. We have to keep the object for later tests let testNodeConstructor = null let closeCallback = null // Check node registration first. We fake the RED-objec to capture the registration part systaComfort2Node({ nodes: { registerType: function(name, nodeConstructor) { it('should be registered on node-RED as a new node type', () => { expect(name).to.equal('systacomfort2') expect(nodeConstructor).to.not.be.undefined }) testNodeConstructor = nodeConstructor }, createNode: function(context, config) { // Extend context with additional special function (normally provided by node red context object) context.on = function(eventName, callback) { closeCallback = callback } context.status = function(statusObj) { it('status object should be present', () => { expect(configObj).to.not.be.undefined }) } it('should be provided the same config object', () => { expect(config).to.be.equal(CONFIG) }) } } }) /** * Call the node constructor */ it('should be possible to call node constructor and access udp server', () => { try { testNodeConstructor(CONFIG) // Create a udp client to connect to service const client = dgram.createSocket('udp4') client.connect(CONFIG.listenPort, (err) => { expect(err).to.be.undefined client.close() }) } finally { // Don't forget to send close event after tests closeCallback() } }) }) /** * Data parser */ describe('SystaComfort2 - Data Parser', function(done) { /** * MAC address utils */ describe('#createMACPart()', () => { it('should convert a byte into string and pad with a leading "0"', () => { expect(dataParser.createMACPart(15)).to.equal('0f') }) it('should convert a byte into as string and NOT pad with a leading "0"', () => { expect(dataParser.createMACPart(255)).to.equal('ff') }) }) /** * Check init answer conversion */ describe('#createResponse()', () => { it('should create an response data buffer based on the input message', () => { let answerBuffer = dataParser.createResponse(Buffer.from(MESSAGE_INIT, 'base64')) expect(answerBuffer).to.not.be.undefined expect(answerBuffer.toString('base64')).to.equal(MESSAGE_INIT_ANSWER) }) }) /** * Check data parser */ describe('#parseData()', () => { it('should parse the data message and return an null object because of invalid packet type != 1', () => { let outputMsg = dataParser.parseData(Buffer.from(MESSAGE_INIT, 'base64')) expect(outputMsg).to.be.null }) it('should parse the data message and provide a structured output message', () => { let outputMsg = dataParser.parseData(Buffer.from(MESSAGE_DATA, 'base64')) expect(outputMsg).to.not.be.undefined }) it('should parse the data message and return an structured output message where payload topic starts with: ' + TOPIC_PREFIX, () => { let outputMsg = dataParser.parseData(Buffer.from(MESSAGE_DATA, 'base64'), TOPIC_PREFIX) expect(outputMsg).to.not.be.undefined expect(outputMsg).to.have.own.property('payload') expect(outputMsg.payload).to.not.be.undefined Object.values(outputMsg.payload).forEach(valueObj => { expect(valueObj).to.not.be.undefined expect(valueObj).to.have.own.property('topic') expect(valueObj.topic).to.not.be.null expect(valueObj.topic.startsWith(TOPIC_PREFIX)).to.be.true }) }) /** * Check values */ let outputMsg = dataParser.parseData(Buffer.from(MESSAGE_DATA, 'base64')) Object.entries(SYSTACOMFORT_VALUES).forEach(([key, valueObj]) => { const expectedValue = OUTPUT_MESSAGE_PAYLOAD[key] it(`should have the value ${key} in payload object and with value ${expectedValue}, description, topic and unit`, () => { expect(outputMsg.payload).to.have.own.property(key) let payloadValue = outputMsg.payload[key] expect(payloadValue).to.not.be.undefined expect(payloadValue.value).to.be.equal(expectedValue) expect(payloadValue.topic).to.be.equal(valueObj.topic) expect(payloadValue.description).to.be.equal(valueObj.description) expect(payloadValue.unit).to.be.equal(valueObj.unit) }) }) }) })