import base64ToArrayBuffer from 'base64-arraybuffer'; import { charCodesToAscii, decimalArrayAsBigEndianNumber, decimalToBitfield, parseBCD, parseDateAscii, parseTimeAscii, uint8ArrayToAsciiNumber, } from './binary'; import { NeedMoreBytesError, ParserError } from './errors'; import { Parser } from './Parser'; import { ParserResult } from './ParserResult'; import * as Registers from './registers'; const VARIABLE_BODY_LENGTH = 65535; const ISKRA_MT_174_V9_BODY_LENGTH = 0x00b9; const ISKRA_MT_174_V6_BODY_LENGTH = 0x00a4; const ISKRA_ME_152_V1_BODY_LENGTH = 0x0046; const ISKRA_ME_154_BODY_LENGTH = 0x0072; export class IskraParser extends Parser { parseIskraMT174V9(arrayBuffer: Uint8Array): ParserResult { const result = new ParserResult(); result.add(Registers.MeterModelModel, charCodesToAscii(arrayBuffer.slice(0, 8))); result.add(Registers.DateNowModel, parseDateAscii(arrayBuffer.slice(8, 14))); result.add(Registers.TimeNowModel, parseTimeAscii(arrayBuffer.slice(14, 20))); result.add(Registers.FirmwareVersionModel, charCodesToAscii(arrayBuffer.slice(20, 24))); result.add(Registers.SerialNumberModel, uint8ArrayToAsciiNumber(arrayBuffer.slice(24, 32))); result.add(Registers.ManufacturingSerialNumberModel, uint8ArrayToAsciiNumber(arrayBuffer.slice(32, 40))); result.add(Registers.ConfigurationNumberModel, charCodesToAscii(arrayBuffer.slice(40, 44))); result.add(Registers.TotalActiveEnergyAbsoluteModel, parseBCD(arrayBuffer.slice(44, 49), 3)); result.add(Registers.TotalActiveEnergyImportModel, parseBCD(arrayBuffer.slice(49, 54), 3)); result.add(Registers.TotalActiveEnergyExportModel, parseBCD(arrayBuffer.slice(54, 59), 3)); result.add(Registers.TotalInductiveReactiveEnergyImportModel, parseBCD(arrayBuffer.slice(59, 64), 3)); result.add(Registers.TotalInductiveReactiveEnergyExportModel, parseBCD(arrayBuffer.slice(64, 69), 3)); result.add(Registers.TotalActiveEnergyAbsolutePreviousModel, parseBCD(arrayBuffer.slice(69, 74), 3)); result.add(Registers.TotalActiveEnergyImportPreviousModel, parseBCD(arrayBuffer.slice(74, 79), 3)); result.add(Registers.TotalActiveEnergyExportPreviousModel, parseBCD(arrayBuffer.slice(79, 84), 3)); result.add( Registers.TotalInductiveReactiveEnergyImportPreviousModel, parseBCD(arrayBuffer.slice(84, 89), 3), ); result.add( Registers.TotalInductiveReactiveEnergyExportPreviousModel, parseBCD(arrayBuffer.slice(89, 94), 3), ); result.add(Registers.TotalMaximumDemandAbsolutePreviousModel, parseBCD(arrayBuffer.slice(94, 96), 2)); result.add(Registers.TotalCumulativeMaximumDemandAbsoluteModel, parseBCD(arrayBuffer.slice(96, 99), 2)); result.add(Registers.TotalMaximumDemandImportPreviousModel, parseBCD(arrayBuffer.slice(99, 101), 2)); result.add(Registers.TotalCumulativeMaximumDemandImportModel, parseBCD(arrayBuffer.slice(101, 104), 2)); result.add(Registers.TotalMaximumDemandExportPreviousModel, parseBCD(arrayBuffer.slice(104, 106), 2)); result.add(Registers.TotalCumulativeMaximumDemandExportModel, parseBCD(arrayBuffer.slice(106, 109), 2)); result.add(Registers.VoltagePhaseAModel, parseBCD(arrayBuffer.slice(109, 111), 1)); result.add(Registers.VoltagePhaseBModel, parseBCD(arrayBuffer.slice(111, 113), 1)); result.add(Registers.VoltagePhaseCModel, parseBCD(arrayBuffer.slice(113, 115), 1)); result.add(Registers.CurrentPhaseAModel, parseBCD(arrayBuffer.slice(115, 117), 1)); result.add(Registers.CurrentPhaseBModel, parseBCD(arrayBuffer.slice(117, 119), 1)); result.add(Registers.CurrentPhaseCModel, parseBCD(arrayBuffer.slice(119, 121), 1)); result.add(Registers.PhaseAPowerFactorModel, parseBCD(arrayBuffer.slice(121, 123), 3)); result.add(Registers.PhaseBPowerFactorModel, parseBCD(arrayBuffer.slice(123, 125), 3)); result.add(Registers.PhaseCPowerFactorModel, parseBCD(arrayBuffer.slice(125, 127), 3)); result.add(Registers.ApparentPowerPhaseAModel, parseBCD(arrayBuffer.slice(127, 129), 2)); result.add(Registers.ApparentPowerPhaseBModel, parseBCD(arrayBuffer.slice(129, 131), 2)); result.add(Registers.ApparentPowerPhaseCModel, parseBCD(arrayBuffer.slice(131, 133), 2)); result.add(Registers.FrequencyModel, parseBCD(arrayBuffer.slice(133, 135), 2)); result.add(Registers.ErrorFlagsModel, decimalToBitfield(arrayBuffer[135])); result.add(Registers.DemandResetCounterModel, parseBCD(arrayBuffer.slice(136, 138), 0)); result.add(Registers.PowerOffTime, parseBCD(arrayBuffer.slice(138, 141), 0)); result.add(Registers.PowerFailCounterModel, parseBCD(arrayBuffer.slice(141, 143), 0)); result.add(Registers.PhaseAPowerFailCounterModel, parseBCD(arrayBuffer.slice(143, 145), 0)); result.add(Registers.PhaseBPowerFailCounterModel, parseBCD(arrayBuffer.slice(145, 147), 0)); result.add(Registers.PhaseCPowerFailCounterModel, parseBCD(arrayBuffer.slice(147, 149), 0)); result.add(Registers.ReverseEnergyCountModel, parseBCD(arrayBuffer.slice(149, 151), 0)); result.add(Registers.RegisterResetCounterModel, parseBCD(arrayBuffer.slice(151, 152), 0)); result.add(Registers.TerminalCoverOpeningCounter, parseBCD(arrayBuffer.slice(152, 154), 0)); result.add(Registers.MainCoverOpeningCounterModel, parseBCD(arrayBuffer.slice(154, 156), 0)); result.add(Registers.MagneticFieldDetectionCounterModel, parseBCD(arrayBuffer.slice(156, 158), 0)); result.add(Registers.ParametersChangeCounterModel, parseBCD(arrayBuffer.slice(158, 160), 0)); result.add(Registers.WatchdogCounterModel, parseBCD(arrayBuffer.slice(160, 161), 0)); result.add(Registers.BatteryPercentageModel, parseBCD(arrayBuffer.slice(161, 163), 2)); result.add(Registers.FraudFlagsModel, decimalToBitfield(arrayBuffer[163])); result.add(Registers.EarthFaultEnergyModel, parseBCD(arrayBuffer.slice(164, 169), 3)); result.add(Registers.TotalMaximumDemandAbsoluteModel, parseBCD(arrayBuffer.slice(169, 171), 2)); result.add(Registers.TotalMaximumDemandImportModel, parseBCD(arrayBuffer.slice(171, 173), 2)); result.add(Registers.TotalMaximumDemandExportModel, parseBCD(arrayBuffer.slice(173, 175), 2)); result.add(Registers.TotalCapacitiveReactiveEnergyExportModel, parseBCD(arrayBuffer.slice(175, 180), 3)); result.add(Registers.TotalCapacitiveReactiveEnergyImportModel, parseBCD(arrayBuffer.slice(180, 185), 3)); return result; } parseIskraMT174V6(arrayBuffer: Uint8Array): ParserResult { const result = new ParserResult(); result.add(Registers.MeterModelModel, charCodesToAscii(arrayBuffer.slice(0, 8))); result.add(Registers.DateNowModel, parseDateAscii(arrayBuffer.slice(8, 14))); result.add(Registers.TimeNowModel, parseTimeAscii(arrayBuffer.slice(14, 20))); result.add(Registers.FirmwareVersionModel, charCodesToAscii(arrayBuffer.slice(20, 24))); result.add(Registers.SerialNumberModel, uint8ArrayToAsciiNumber(arrayBuffer.slice(24, 32))); result.add(Registers.ManufacturingSerialNumberModel, uint8ArrayToAsciiNumber(arrayBuffer.slice(32, 40))); result.add(Registers.ConfigurationNumberModel, charCodesToAscii(arrayBuffer.slice(40, 44))); result.add(Registers.TotalActiveEnergyAbsoluteModel, parseBCD(arrayBuffer.slice(44, 49), 3)); result.add(Registers.TotalActiveEnergyImportModel, parseBCD(arrayBuffer.slice(49, 54), 3)); result.add(Registers.TotalActiveEnergyExportModel, parseBCD(arrayBuffer.slice(54, 59), 3)); result.add(Registers.TotalInductiveReactiveEnergyImportModel, parseBCD(arrayBuffer.slice(59, 64), 3)); result.add(Registers.TotalInductiveReactiveEnergyExportModel, parseBCD(arrayBuffer.slice(64, 69), 3)); result.add(Registers.TotalActiveEnergyAbsolutePreviousModel, parseBCD(arrayBuffer.slice(69, 74), 3)); result.add(Registers.TotalActiveEnergyImportPreviousModel, parseBCD(arrayBuffer.slice(74, 79), 3)); result.add(Registers.TotalActiveEnergyExportPreviousModel, parseBCD(arrayBuffer.slice(79, 84), 3)); result.add( Registers.TotalInductiveReactiveEnergyImportPreviousModel, parseBCD(arrayBuffer.slice(84, 89), 3), ); result.add( Registers.TotalInductiveReactiveEnergyExportPreviousModel, parseBCD(arrayBuffer.slice(89, 94), 3), ); result.add(Registers.TotalMaximumDemandAbsolutePreviousModel, parseBCD(arrayBuffer.slice(94, 96), 2)); result.add(Registers.TotalCumulativeMaximumDemandAbsoluteModel, parseBCD(arrayBuffer.slice(96, 99), 2)); result.add(Registers.TotalMaximumDemandImportPreviousModel, parseBCD(arrayBuffer.slice(99, 101), 2)); result.add(Registers.TotalCumulativeMaximumDemandImportModel, parseBCD(arrayBuffer.slice(101, 104), 2)); result.add(Registers.TotalMaximumDemandExportPreviousModel, parseBCD(arrayBuffer.slice(104, 106), 2)); result.add(Registers.TotalCumulativeMaximumDemandExportModel, parseBCD(arrayBuffer.slice(106, 109), 2)); result.add(Registers.VoltagePhaseAModel, parseBCD(arrayBuffer.slice(109, 111), 1)); result.add(Registers.VoltagePhaseBModel, parseBCD(arrayBuffer.slice(111, 113), 1)); result.add(Registers.VoltagePhaseCModel, parseBCD(arrayBuffer.slice(113, 115), 1)); result.add(Registers.CurrentPhaseAModel, parseBCD(arrayBuffer.slice(115, 117), 1)); result.add(Registers.CurrentPhaseBModel, parseBCD(arrayBuffer.slice(117, 119), 1)); result.add(Registers.CurrentPhaseCModel, parseBCD(arrayBuffer.slice(119, 121), 1)); result.add(Registers.PhaseAPowerFactorModel, parseBCD(arrayBuffer.slice(121, 123), 3)); result.add(Registers.PhaseBPowerFactorModel, parseBCD(arrayBuffer.slice(123, 125), 3)); result.add(Registers.PhaseCPowerFactorModel, parseBCD(arrayBuffer.slice(125, 127), 3)); result.add(Registers.ApparentPowerPhaseAModel, parseBCD(arrayBuffer.slice(127, 129), 2)); result.add(Registers.ApparentPowerPhaseBModel, parseBCD(arrayBuffer.slice(129, 131), 2)); result.add(Registers.ApparentPowerPhaseCModel, parseBCD(arrayBuffer.slice(131, 133), 2)); result.add(Registers.FrequencyModel, parseBCD(arrayBuffer.slice(133, 135), 2)); result.add(Registers.ErrorFlagsModel, decimalToBitfield(arrayBuffer[135])); result.add(Registers.DemandResetCounterModel, parseBCD(arrayBuffer.slice(136, 138), 0)); result.add(Registers.PowerOffTime, parseBCD(arrayBuffer.slice(138, 141), 0)); result.add(Registers.PowerFailCounterModel, parseBCD(arrayBuffer.slice(141, 143), 0)); result.add(Registers.PhaseAPowerFailCounterModel, parseBCD(arrayBuffer.slice(143, 145), 0)); result.add(Registers.PhaseBPowerFailCounterModel, parseBCD(arrayBuffer.slice(145, 147), 0)); result.add(Registers.PhaseCPowerFailCounterModel, parseBCD(arrayBuffer.slice(147, 149), 0)); result.add(Registers.ReverseEnergyCountModel, parseBCD(arrayBuffer.slice(149, 151), 0)); result.add(Registers.RegisterResetCounterModel, parseBCD(arrayBuffer.slice(151, 152), 0)); result.add(Registers.TerminalCoverOpeningCounter, parseBCD(arrayBuffer.slice(152, 154), 0)); result.add(Registers.MainCoverOpeningCounterModel, parseBCD(arrayBuffer.slice(154, 156), 0)); result.add(Registers.MagneticFieldDetectionCounterModel, parseBCD(arrayBuffer.slice(156, 158), 0)); result.add(Registers.ParametersChangeCounterModel, parseBCD(arrayBuffer.slice(158, 160), 0)); result.add(Registers.WatchdogCounterModel, parseBCD(arrayBuffer.slice(160, 161), 0)); result.add(Registers.BatteryPercentageModel, parseBCD(arrayBuffer.slice(161, 163), 2)); result.add(Registers.FraudFlagsModel, decimalToBitfield(arrayBuffer[163])); result.add(Registers.EarthFaultEnergyModel, null); result.add(Registers.TotalMaximumDemandAbsoluteModel, null); result.add(Registers.TotalMaximumDemandImportModel, null); result.add(Registers.TotalMaximumDemandExportModel, null); result.add(Registers.TotalCapacitiveReactiveEnergyExportModel, null); result.add(Registers.TotalCapacitiveReactiveEnergyImportModel, null); return result; } parseIskraME152V1(arrayBuffer: Uint8Array): ParserResult { const result = new ParserResult(); result.add(Registers.MeterModelModel, charCodesToAscii(arrayBuffer.slice(0, 8))); result.add(Registers.SerialNumberModel, uint8ArrayToAsciiNumber(arrayBuffer.slice(8, 16))); result.add(Registers.TotalActiveEnergyAbsoluteModel, parseBCD(arrayBuffer.slice(16, 21), 2)); result.add(Registers.TotalActiveEnergyExportModel, parseBCD(arrayBuffer.slice(21, 26), 2)); result.add(Registers.TotalReactiveEnergyImportModel, parseBCD(arrayBuffer.slice(26, 31), 2)); result.add(Registers.TotalCapacitiveReactiveEnergyImportModel, parseBCD(arrayBuffer.slice(31, 36), 2)); result.add(Registers.TotalAbsoluteReactiveEnergyModel, parseBCD(arrayBuffer.slice(36, 41), 2)); result.add(Registers.TotalMaximumDemandImportModel, parseBCD(arrayBuffer.slice(41, 43), 2)); result.add(Registers.TotalMaximumDemandImportPreviousModel, parseBCD(arrayBuffer.slice(43, 45), 2)); result.add(Registers.VoltagePhaseAModel, parseBCD(arrayBuffer.slice(45, 47), 1)); result.add(Registers.CurrentPhaseAModel, parseBCD(arrayBuffer.slice(47, 49), 2)); result.add(Registers.NeutralCurrentModel, parseBCD(arrayBuffer.slice(49, 51), 2)); result.add(Registers.FraudFlagsModel, decimalToBitfield(arrayBuffer[51])); result.add(Registers.DemandResetCounterModel, parseBCD(arrayBuffer.slice(52, 54), 0)); result.add(Registers.PowerFailCounterModel, parseBCD(arrayBuffer.slice(54, 56), 0)); result.add(Registers.MainCoverOpeningCounterModel, parseBCD(arrayBuffer.slice(56, 58), 0)); result.add(Registers.TerminalCoverOpeningCounter, parseBCD(arrayBuffer.slice(58, 60), 0)); result.add(Registers.PhaseAReverseCountModel, parseBCD(arrayBuffer.slice(60, 62), 0)); result.add(Registers.EarthFaultsCounter, parseBCD(arrayBuffer.slice(62, 64), 0)); result.add(Registers.RegisterResetCounterModel, parseBCD(arrayBuffer.slice(64, 65), 0)); result.add(Registers.TotalServiceTimeModel, parseBCD(arrayBuffer.slice(65, 68), 0)); const timeToNextInMinutes = Number(parseBCD(arrayBuffer.slice(68, 70), 0)); result.add(Registers.TimeToNextDemandAutoResetModel, (timeToNextInMinutes * 3600).toString()); return result; } parseIskraME154(arrayBuffer: Uint8Array): ParserResult { const result = new ParserResult(); const readAscii = (start: number, end: number) => charCodesToAscii(arrayBuffer.slice(start, end)); const readBCD = (start: number, length: number, decimals = 0) => parseBCD(arrayBuffer.slice(start, start + length), decimals); const readUint = (start: number, length = 2) => Number(decimalArrayAsBigEndianNumber(arrayBuffer.slice(start, start + length))) || 0; const readUintString = (start: number, length = 2) => decimalArrayAsBigEndianNumber(arrayBuffer.slice(start, start + length)); result.add(Registers.MeterModelModel, readAscii(0, 12)); result.add(Registers.FirmwareVersionModel, readAscii(12, 21)); result.add(Registers.ConfigurationNumberModel, readAscii(21, 25)); const serial = readAscii(25, 41); result.add(Registers.SerialNumberModel, serial.replace(/^0+/, '') || '0'); result.add(Registers.FreeTextModel, readAscii(41, 50).trim()); result.add(Registers.TotalActiveEnergyAbsoluteModel, readBCD(50, 5, 3)); result.add(Registers.TotalActiveEnergyImportModel, readBCD(55, 5, 3)); result.add(Registers.TotalActiveEnergyExportModel, readBCD(60, 5, 3)); result.add(Registers.TotalInductiveReactiveEnergyImportModel, readBCD(65, 5, 3)); result.add(Registers.TotalCapacitiveReactiveEnergyExportModel, readBCD(70, 5, 3)); result.add(Registers.TotalInductiveReactiveEnergyExportModel, readBCD(75, 5, 3)); result.add(Registers.TotalCapacitiveReactiveEnergyImportModel, readBCD(80, 5, 3)); result.add(Registers.TotalMaximumDemandAbsoluteModel, readBCD(85, 2, 2)); result.add(Registers.TotalMaximumDemandAbsolutePreviousModel, readBCD(87, 2, 2)); result.add(Registers.TotalCumulativeMaximumDemandAbsoluteModel, readBCD(89, 3, 2)); const voltage = (readUint(92) / 10).toString(); result.add(Registers.VoltagePhaseAModel, voltage); const current = (readUint(94) / 100).toString(); result.add(Registers.CurrentPhaseAModel, current); result.add(Registers.PowerFactorModel, (readUint(96, 1) / 100).toString()); const statusFlags = decimalToBitfield(arrayBuffer[97] ?? 0); result.add(Registers.StatusFlags1Model, statusFlags); const errorBitfield = decimalToBitfield(arrayBuffer[98] ?? 0); result.add(Registers.ErrorFlagsModel, errorBitfield); result.add(Registers.TotalServiceTimeModel, readBCD(99, 3, 0)); result.add(Registers.PowerOnTimeModel, readBCD(102, 3, 0)); result.add(Registers.PowerFailCounterModel, readUintString(105, 2)); result.add(Registers.ReverseEnergyCountModel, readUintString(107, 2)); result.add(Registers.DemandResetCounterModel, readUintString(109, 2)); result.add(Registers.RegisterResetCounterModel, (arrayBuffer[111] ?? 0).toString()); result.add(Registers.TimeToNextDemandAutoResetModel, readBCD(112, 2, 0)); return result; } parseBody(arrayBuffer: Uint8Array): ParserResult { if (arrayBuffer.length === ISKRA_MT_174_V9_BODY_LENGTH) { return this.parseIskraMT174V9(arrayBuffer); } if (arrayBuffer.length === ISKRA_MT_174_V6_BODY_LENGTH) { return this.parseIskraMT174V6(arrayBuffer); } if (arrayBuffer.length === ISKRA_ME_152_V1_BODY_LENGTH) { return this.parseIskraME152V1(arrayBuffer); } if (arrayBuffer.length === ISKRA_ME_154_BODY_LENGTH) { return this.parseIskraME154(arrayBuffer); } throw new ParserError('Unexpected Iskra Body Length'); } getMeterNumber(arrayBuffer) { return charCodesToAscii(arrayBuffer.slice(0, 8)); } parse(aBase64String: string, recoverMeterNumber?: Function): ParserResult { let arrayBuffer = new Uint8Array(base64ToArrayBuffer.decode(aBase64String)); if (arrayBuffer.length < 4) { throw new NeedMoreBytesError(); } const headerLength = Number(decimalArrayAsBigEndianNumber(arrayBuffer.slice(2, 4))); if (arrayBuffer.length < headerLength) { throw new NeedMoreBytesError(); } const bodyLength = Number(decimalArrayAsBigEndianNumber(arrayBuffer.slice(4, 6))); if (bodyLength !== VARIABLE_BODY_LENGTH && arrayBuffer.length < headerLength + bodyLength) { throw new NeedMoreBytesError(); } const headerArrayBuffer = arrayBuffer.slice(0, headerLength); const bodyArrayBuffer = arrayBuffer.slice(headerLength, headerLength + bodyLength); const typeByte = headerArrayBuffer[8] ?? -1; this.assert(typeByte === 1, `Type Byte should be 1, ${typeByte} found instead`); if (recoverMeterNumber) { return recoverMeterNumber(this.getMeterNumber(bodyArrayBuffer)); } return this.parseBody(bodyArrayBuffer); } }