import base64ToArrayBuffer from 'base64-arraybuffer'; import { decimalToBitfield, obisCodeValueToNumeric } from './binary'; import { ChecksumValidation, ObisParser } from './ObisParser'; import { Parser } from './Parser'; import { ParserResult } from './ParserResult'; import { BatteryEventCounterModel, BatteryVoltageModel, CurrentPhaseAModel, CurrentPhaseBModel, CurrentPhaseCModel, DateTimeNowModel, DemandResetCounterModel, EarthFaultsCounter, FirmwareVersionModel, FreeTextModel, MagneticFieldDetectionCounterModel, MainCoverOpeningCounterModel, MemoryFailCounterModel, MeterModelModel, ParametersChangeCounterModel, PhaseAReverseCountModel, PhaseBReverseCountModel, PhaseCReverseCountModel, PowerFailCounterModel, RegisterResetCounterModel, SerialNumberModel, Tariff2ActiveEnergyExportModel, TerminalCoverOpeningCounter, TotalServiceTimeModel, TotalAbsoluteReactiveEnergyModel, TotalActiveEnergyAbsoluteModel, TotalActiveEnergyAbsolutePreviousModel, TotalActiveEnergyExportModel, TotalActiveEnergyExportPreviousModel, TotalActiveEnergyImportModel, TotalActiveEnergyImportPreviousModel, TotalCapacitiveReactiveEnergyExportModel, TotalCapacitiveReactiveEnergyExportPreviousModel, TotalCapacitiveReactiveEnergyImportModel, TotalCapacitiveReactiveEnergyImportPreviousModel, TotalCumulativeMaximumDemandExportModel, TotalCumulativeMaximumDemandExportPreviousModel, TotalCumulativeMaximumDemandImportModel, TotalCumulativeMaximumDemandImportPreviousModel, TotalInductiveReactiveEnergyExportModel, TotalInductiveReactiveEnergyExportPreviousModel, TotalInductiveReactiveEnergyImportModel, TotalInductiveReactiveEnergyImportPreviousModel, TotalMaximumDemandAbsoluteModel, TotalMaximumDemandAbsolutePreviousModel, TotalMaximumDemandExportModel, TotalMaximumDemandExportPreviousModel, TotalMaximumDemandImportModel, TotalMaximumDemandImportPreviousModel, VoltagePhaseAModel, VoltagePhaseBModel, VoltagePhaseCModel, WatchdogCounterModel, NeutralCurrentModel, } from './registers'; import type { RegisterModel } from './registers'; export class HolleyParser extends Parser { // Interpretation codes: 1 = DDSD285, 2 = DTS541 codeToObisMap: [RegisterModel, string | string[], number[]][] = [ [BatteryEventCounterModel, '98.21.0', [1, 2]], [BatteryVoltageModel, '96.6.3', [1, 2]], [CurrentPhaseAModel, '31.7.0', [1, 2]], [CurrentPhaseBModel, '51.7.0', [2]], [CurrentPhaseCModel, '71.7.0', [2]], [DateTimeNowModel, '0.9.4', [1, 2]], [DemandResetCounterModel, '98.20.0', [1, 2]], [EarthFaultsCounter, '98.1.17', [1, 2]], [FirmwareVersionModel, '0.2.0', [1, 2]], [FreeTextModel, ['99.94.01', '99.04.01'], [1, 2]], [MagneticFieldDetectionCounterModel, '98.1.12', [1, 2]], [MainCoverOpeningCounterModel, '98.1.5', [1, 2]], [MemoryFailCounterModel, '98.23.0', [1, 2]], [MeterModelModel, '96.50.7', [1, 2]], [NeutralCurrentModel, '51.7.0', [1]], [NeutralCurrentModel, '91.7.0', [2]], [ParametersChangeCounterModel, '98.22.0', [1, 2]], [PhaseAReverseCountModel, '96.62.05', [2]], [PhaseAReverseCountModel, '98.2.3', [1]], [PhaseBReverseCountModel, '96.62.06', [2]], [PhaseCReverseCountModel, '96.62.07', [2]], [PowerFailCounterModel, '98.2.0', [1, 2]], [RegisterResetCounterModel, '99.94.02', [1, 2]], [SerialNumberModel, 'C.1.0', [1, 2]], [Tariff2ActiveEnergyExportModel, '2.8.2', [1, 2]], [TerminalCoverOpeningCounter, '98.1.6', [1, 2]], [TotalAbsoluteReactiveEnergyModel, '129.8.0', [1, 2]], [TotalActiveEnergyAbsoluteModel, '15.8.0', [1, 2]], [TotalActiveEnergyAbsolutePreviousModel, '15.8.0.1', [1, 2]], [TotalActiveEnergyExportModel, '2.8.0', [1, 2]], [TotalActiveEnergyExportPreviousModel, '2.8.0.1', [1, 2]], [TotalActiveEnergyImportModel, '1.8.0', [1, 2]], [TotalActiveEnergyImportPreviousModel, '1.8.0.1', [1, 2]], [TotalCapacitiveReactiveEnergyExportModel, '6.8.0', [1, 2]], [TotalCapacitiveReactiveEnergyExportPreviousModel, '6.8.0.1', [1, 2]], [TotalCapacitiveReactiveEnergyImportModel, '8.8.0', [1, 2]], [TotalCapacitiveReactiveEnergyImportPreviousModel, '8.8.0.1', [1, 2]], [TotalCumulativeMaximumDemandExportModel, '2.2.0', [1, 2]], [TotalCumulativeMaximumDemandExportPreviousModel, '2.2.0.1', [1, 2]], [TotalCumulativeMaximumDemandImportModel, '1.2.0', [1, 2]], [TotalCumulativeMaximumDemandImportPreviousModel, '1.2.0.1', [1, 2]], [TotalInductiveReactiveEnergyExportModel, '7.8.0', [1, 2]], [TotalInductiveReactiveEnergyExportPreviousModel, '7.8.0.1', [1, 2]], [TotalInductiveReactiveEnergyImportModel, '5.8.0', [1, 2]], [TotalInductiveReactiveEnergyImportPreviousModel, '5.8.0.1', [1, 2]], [TotalMaximumDemandAbsoluteModel, '15.6.0', [1, 2]], [TotalMaximumDemandAbsolutePreviousModel, '15.6.0.1', [1, 2]], [TotalMaximumDemandExportModel, '2.6.0', [1, 2]], [TotalMaximumDemandExportPreviousModel, '2.6.0.1', [1, 2]], [TotalMaximumDemandImportModel, '1.6.0', [1, 2]], [TotalMaximumDemandImportPreviousModel, '1.6.0.1', [1, 2]], [TotalServiceTimeModel, '91.93.01', [1, 2]], [VoltagePhaseAModel, '32.7.0', [1, 2]], [VoltagePhaseBModel, '52.7.0', [2]], [VoltagePhaseCModel, '72.7.0', [2]], [WatchdogCounterModel, '98.24.0', [1, 2]], ]; parse(aBase64String: string): ParserResult { const obisParser = new ObisParser(); const byteArray = new Uint8Array(base64ToArrayBuffer.decode(aBase64String) as ArrayBuffer); const obisRegisters = obisParser.parse(byteArray, { multiBlock: true, expectedBlocks: 3, checksumValidation: ChecksumValidation.CRC_INCLUDE_END, }); const result = new ParserResult(); const addValue = (entry: [RegisterModel, string | string[], number[]]) => { const [recordModel, targetObis] = entry; const rawValue = typeof targetObis === 'string' ? obisRegisters.getValueOfObis(targetObis) : targetObis.map(obis => obisRegisters.getValueOfObis(obis)).find(value => value != null) ?? null; if (rawValue == null) { result.add(recordModel, null); return; } let finalValue: string | null = rawValue; if (recordModel.format === 'datetime') { const [datePart, timePart = ''] = rawValue.split('*'); finalValue = `${datePart}T${timePart.replace(/-/g, ':')}`.trim(); } else if (recordModel.format === 'numeric') { finalValue = obisCodeValueToNumeric(rawValue); } else if (recordModel.format === 'bitfield') { finalValue = decimalToBitfield(rawValue); } else if (recordModel.format === 'text') { finalValue = rawValue.trim(); } result.add(recordModel, finalValue); }; for (const entry of this.codeToObisMap) { const [_model, _obis, interpretationCodes] = entry; if (interpretationCodes.includes(this.interpretationCode)) { addValue(entry); } } return result; } }