From f1830497b017e51ecf9fd361837d34c4300adf78 Mon Sep 17 00:00:00 2001
From: Wolfgang Hottgenroth <woho@hottis.de>
Date: Thu, 10 Aug 2017 23:33:20 +0200
Subject: [PATCH] start to parse variable data blocks, tests are yet missing,
 coding tables and especially encoder functions not completex

---
 src/codetables.ts    | 118 ++++++++++++++++++++++++-------
 src/longframe.ts     | 165 +++++++++++++++++++++++++++++++++++++++++++
 src/meterbus.test.ts |   1 +
 src/utils.test.ts    |  13 ++++
 src/utils.ts         |   9 +++
 5 files changed, 279 insertions(+), 27 deletions(-)

diff --git a/src/codetables.ts b/src/codetables.ts
index 39786c1..331b7b1 100644
--- a/src/codetables.ts
+++ b/src/codetables.ts
@@ -1,31 +1,95 @@
-export namespace MeterbusLibCodeTables {
+import {MeterbusLibUtils} from './utils'
+
+
+export namespace MeterbusLibCodeTables { 
     export const MEDIUM_CODE : string[] =
-            ['Other', 
-               'Oil', 
-               'Electrity', 
-               'Gas', 
-               'Heat (Volume measured at return temperature: outlet',
-               'Steam', 
-               'Hot Water', 
-               'Water', 
-               'Heat Cost Allocator', 
-               'Compressed Air',
-               'Cooling Load Meter (Volume measured at return temperature: outlet', 
-               'Cooling Load Meter (Volume measured at flow temperature: inlet',
-               'Heat (Volume measured at flow temperature: inlet)', 
-               'Heat / Cooling Load Meter',
-               'Bus / System', 
-               'Unknown Medium',
-               'Reserved (10)',
-               'Reserved (11)',
-               'Reserved (12)',
-               'Reserved (13)',
-               'Reserved (14)',
-               'Reserved (15)',
-               'Cold Water',
-               'Dual Water',
-               'Pressure',
-               'A/D Converter',
+            [
+                'Other', 
+                'Oil', 
+                'Electrity', 
+                'Gas', 
+                'Heat (Volume measured at return temperature: outlet',
+                'Steam', 
+                'Hot  Water', 
+                'Water', 
+                'Heat Cost Allocator', 
+                'Compressed Air',
+                'Cooling Load Meter (Volume measured at return temperature: outlet', 
+                'Cooling Load Meter (Volume measured at flow temperature: inlet',
+                'Heat (Volume measured at flow temperature: inlet)', 
+                'Heat / Cooling Load Meter',
+                'Bus / System', 
+                'Unknown Medium',
+                'Reserved (10)',
+                'Reserved (11)',
+                'Reserved (12)',
+                'Reserved (13)',
+                'Reserved (14)',
+                'Reserved (15)',
+                'Cold Water',
+                'Dual Water',
+                'Pressure',
+                'A/D Converter',
             ]
 
+    export const DIF_FUNCTION_FIELD : string[] =
+            [
+                'Instantaneous value',
+                'Maximum value',
+                'Minimum value',
+                'Value during error state'
+            ]
+
+    export class DCFt { 
+        name : string
+        length : number
+        encoder : (v: number[]) => any
+        constructor(n : string, l : number, e : (v: number[]) => any) {
+            this.name = n
+            this.length = l
+            this.encoder = e
+        }
+    }
+
+    export const DIF_CODING_FIELD : DCFt[] =
+            [
+                new DCFt('No Data',                   0, (x)=>{ return x}),
+                new DCFt('8 Bit Integer',             1, (x)=>{ return x[0]}),
+                new DCFt('16 Bit Integer',            2, (x)=>{ return (x[1] << 8) + x[0]}),
+                new DCFt('24 Bit Integer',            3, (x)=>{ return (x[2] << 16) + (x[1] << 8) + x[0]}),
+                new DCFt('32 Bit Integer',            4, (x)=>{ return (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]}),
+                new DCFt('32 Bit Real',               4, (x)=>{ return x}), // FIXME
+                new DCFt('48 Bit Integer',            6, (x)=>{ return (x[5] << 40) + (x[4] << 32) + (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]}),       
+                new DCFt('64 Bit Integer',            8, (x)=>{ return (x[7] << 56) + (x[6] << 48) + (x[5] << 40) + (x[4] << 32) + (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]}),
+                new DCFt('Selection for Readout',     0, (x)=>{ return x}),
+                new DCFt('2 Digit BCD',               1, (x)=>{ return MeterbusLibUtils.bcd(x)}),
+                new DCFt('4 Digit BCD',               2, (x)=>{ return MeterbusLibUtils.bcd(x)}),
+                new DCFt('6 Digit BCD',               3, (x)=>{ return MeterbusLibUtils.bcd(x)}),
+                new DCFt('8 Digit BCD',               4, (x)=>{ return MeterbusLibUtils.bcd(x)}),
+                new DCFt('variable length',          -1, (x)=>{ return x}),
+                new DCFt('12 Digit BCD',              6, (x)=>{ return MeterbusLibUtils.bcd(x)}),
+                new DCFt('Special Function',          0, (x)=>{ return x})
+            ]
+
+/*            
+                    DATA_FIELD_CODES = (
+                            ('No Data',                   0, None),
+                            ('8 Bit Integer',             1, lambda x: x[0]),
+                            ('16 Bit Integer',            2, lambda x: (x[1] << 8) + x[0]),
+                            ('24 Bit Integer',            3, lambda x: (x[2] << 16) + (x[1] << 8) + x[0]),
+                            ('32 Bit Integer',            4, lambda x: (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]),
+                            ('32 Bit Real',               4, lambda x: struct.unpack('f', str(bytearray(x)))[0]),
+                            ('48 Bit Integer',            6, lambda x: (x[5] << 40) + (x[4] << 32) + (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]),       
+                            ('64 Bit Integer',            8, lambda x: (x[7] << 56) + (x[6] << 48) + (x[5] << 40) + (x[4] << 32) + (x[3] << 24) + (x[2] << 16) + (x[1] << 8) + x[0]),
+                            ('Selection for Readout',     0, None),
+                            ('2 Digit BCD',               1, lambda x: MeterbusTypeConversion.bcd(x)),
+                            ('4 Digit BCD',               2, lambda x: MeterbusTypeConversion.bcd(x)),
+                            ('6 Digit BCD',               3, lambda x: MeterbusTypeConversion.bcd(x)),
+                            ('8 Digit BCD',               4, lambda x: MeterbusTypeConversion.bcd(x)),
+                            ('variable length',          -1, None),
+                            ('12 Digit BCD',              6, lambda x: MeterbusTypeConversion.bcd(x)),
+                            ('Special Function',          0, None),
+                           )
+*/                        
+
 }
\ No newline at end of file
diff --git a/src/longframe.ts b/src/longframe.ts
index bb33a7e..89acf8e 100644
--- a/src/longframe.ts
+++ b/src/longframe.ts
@@ -6,6 +6,8 @@ import {MeterbusLibCodeTables} from './codetables'
 export namespace MeterbusLibLongFrame {
     export class LongFrame extends MeterbusLibFrames.ControlFrame {
         private _fixedDataHeader : FixedDataHeader
+        private _variableDataBlocks : DataBlock[] = []
+
         get fixedDataHeader() : FixedDataHeader { return this._fixedDataHeader }
 
         constructor(telegram : number[]) {
@@ -16,6 +18,16 @@ export namespace MeterbusLibLongFrame {
             super.parse2() // control frame parse2 method
             this._fixedDataHeader = new FixedDataHeader(this._telegram.slice(7, 19))
             this._fixedDataHeader.parse()
+            
+            let consumed : number = 0
+            while (true) {
+                let die : DataBlock = 
+                    new DataBlock(this._telegram.slice(19 + consumed))
+                die.parse()
+                this._variableDataBlocks.push(die)
+                consumed += die.consumed
+                break  //FIXME                
+            }
         }
     }
 
@@ -61,4 +73,157 @@ export namespace MeterbusLibLongFrame {
         }
     }
 
+    export abstract class ADataBlock {
+        protected __consumed : number = 0
+
+        get consumed() : number {
+            return this.__consumed
+        }
+
+        abstract parse() : void
+
+        toJSON() : any {
+            console.log("x")
+            return MeterbusLibUtils.jsonPrepaper(this, [])
+        }
+
+        getJSON() : string {
+            return JSON.stringify(this)
+        }
+    }
+
+    export class DataBlock extends ADataBlock {
+        private _content : VariableDataBlock | ManufacturerSpecificData
+
+        constructor(data : number[]) {
+            super()
+            if ((data[0] & 0x0f) == 0x0f) {
+                this._content = new ManufacturerSpecificData(data)
+            } else {
+                this._content = new VariableDataBlock(data)
+            }
+        }
+
+
+        parse() : void {
+            this._content.parse()
+        }
+
+        toJSON() : any {
+            return MeterbusLibUtils.jsonPrepaper(this._content, [])
+        }
+    }
+
+    export class DIF {
+        raw : number
+        function : number
+        coding : number
+        lsb : number
+        extension : number
+
+        toJSON() : any {
+            let dup = MeterbusLibUtils.clone(this)
+            dup.function = MeterbusLibCodeTables.DIF_FUNCTION_FIELD[dup.function]
+            dup.coding = MeterbusLibCodeTables.DIF_CODING_FIELD[dup.coding].name
+            return dup
+        }
+    }
+
+    export class DIFE {
+        raw : number
+        extension : number
+    }
+
+    export class VIF {
+        raw : number
+        unitAndMultiplier : number
+        extension : number
+    }
+
+    export class VIFE {
+        raw : number
+        extension : number
+    }
+
+    export class VariableDataBlock extends ADataBlock {
+        private _type : string = "VDB"
+        private __indata : number[]
+        private _dif : DIF
+        private _dife : DIFE[] = []
+        private _vif : VIF
+        private _vife : VIFE[] = []
+        private _data : number[]
+        private _value : any
+
+        constructor(data : number[]) {
+            super()
+            this.__indata = data
+        }
+
+        parse() : void {
+            this._dif = new DIF()
+            this._dif.raw = this.__indata[0]
+            this.__consumed++
+            this._dif.function = (this._dif.raw & 0x30) >> 4
+            this._dif.coding = this._dif.raw & 0x0f
+            this._dif.lsb = (this._dif.raw & 0x40) >> 6
+            this._dif.extension = (this._dif.raw & 0x80) >> 7
+
+            if (this._dif.extension == 1) {
+                while (true) {
+                    let dife = new DIFE()
+                    dife.raw = this.__indata[this.__consumed]
+                    this.__consumed++
+                    dife.extension = (dife.raw & 0x80) >> 7
+                    this._dife.push(dife)
+                    if (dife.extension == 0) {
+                        break
+                    }
+                }
+            }
+
+            this._vif = new VIF()
+            this._vif.raw = this.__indata[this.__consumed]
+            this.__consumed++
+            this._vif.unitAndMultiplier = this._vif.raw & 0x7f
+            this._vif.extension = (this._vif.raw & 0x80) >> 7
+
+            if (this._vif.extension == 1) {
+                while (true) {
+                    let vife = new VIFE()
+                    vife.raw = this.__indata[this.__consumed]
+                    this.__consumed++
+                    vife.extension = (vife.raw & 0x80) >> 7
+                    this._vife.push(vife)
+                    if (vife.extension == 0) {
+                        break
+                    }
+                }
+            }
+            
+            let dcf : MeterbusLibCodeTables.DCFt = MeterbusLibCodeTables.DIF_CODING_FIELD[this._dif.coding]
+            this._data = this.__indata.slice(this.__consumed, 
+                this.__consumed + dcf.length)
+            this.__consumed += dcf.length
+
+            console.log(dcf.encoder)
+            this._value = dcf.encoder(this._data)
+            console.log(this._value)
+        }
+    }
+
+    export class ManufacturerSpecificData extends ADataBlock {
+        private _type : string = "MSD"
+        private _data : number[]
+
+        constructor(data : number[]) {
+            super()
+            this._data = data
+        }
+
+        parse() : void {
+            
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/src/meterbus.test.ts b/src/meterbus.test.ts
index 1153c5d..b578df2 100644
--- a/src/meterbus.test.ts
+++ b/src/meterbus.test.ts
@@ -180,6 +180,7 @@ describe('The Meterbus Longframe Library', () => {
         .to.deep.equal([0,0])
     })
     it('should prepare itself as JSON', () => {
+        console.log((telegram.frame as MeterbusLibLongFrame.LongFrame).getJSON())
         expect((telegram.frame as MeterbusLibLongFrame.LongFrame).getJSON()).to.equal(json_1phase_electric)
     })
 })
diff --git a/src/utils.test.ts b/src/utils.test.ts
index ec90883..b58c99d 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -48,4 +48,17 @@ describe('The jsonPrepare function in the Meterbus Library Utils', () => {
         let objOut = {'a': 1, 'b':2}
         expect(MeterbusLibUtils.jsonPrepaper(objIn, [])).to.deep.equal(objOut)
     })
+})
+
+describe('The clone function in the Meterbus Library Utils', () => {
+    it('should clone the attributes of an object', () => {
+        let objIn = {'a': 1, 'b': 2}
+        let objOut = {'a': 1, 'b': 2}
+        expect(MeterbusLibUtils.clone(objIn)).to.deep.equal(objOut)
+    })
+    it('should not just return the same object', () => {
+        let objIn = {'a': 1, 'b':2}
+        let objOut = objIn
+        expect(MeterbusLibUtils.clone(objIn)).to.not.equal(objOut)
+    })
 })
\ No newline at end of file
diff --git a/src/utils.ts b/src/utils.ts
index 3f42a9a..04afde2 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -24,5 +24,14 @@ export namespace MeterbusLibUtils {
         }
         return dup
     }
+
+    export function clone(obj:any) : any {
+        let dup = {}
+        for (let key in obj) {
+            dup[key] = obj[key]
+        }
+        return dup
+    }
+
 }