import moment from 'moment'; import base64ToArrayBuffer from 'base64-arraybuffer'; import { base64ToHex, hexToBase64, uint8ToBCD } from './binary'; export enum MeterFamilyCode { Hexing = 'hexing', HX110Hexing = 'hx110_hexing', HXE310AndHXF300Hexing = 'hxe310_hxf300_hexing', Elster = 'elster', Iskra = 'iskra', AlphaA1 = 'alpha_a1', AlphaA3 = 'alpha_a3', Holley = 'holley', } export type ProbeCommand = Function; export interface MeterFamily { code: MeterFamilyCode; probeCommand: ProbeCommand; setTimeProbeCommand?: ProbeCommand; setDateAndTimeCommand?: ProbeCommand; resetToZeroCommand?: ProbeCommand; } /** * Meter family definitions */ // Simple meter families (no helper functions required) export const HexingMeterFamily: MeterFamily = { code: MeterFamilyCode.Hexing, probeCommand: () => hexToBase64('7E 02'), }; export const HX110HexingMeterFamily: MeterFamily = { code: MeterFamilyCode.HX110Hexing, probeCommand: () => hexToBase64('7E 22'), }; export const HXE310AndHXF300HexingMeterFamily: MeterFamily = { code: MeterFamilyCode.HXE310AndHXF300Hexing, probeCommand: () => hexToBase64('7E 23'), resetToZeroCommand: () => hexToBase64('7E 24'), }; export const ElsterMeterFamily: MeterFamily = { code: MeterFamilyCode.Elster, probeCommand: () => hexToBase64('7E 32'), }; export const IskraMeterFamily: MeterFamily = { code: MeterFamilyCode.Iskra, probeCommand: () => hexToBase64('7E 32'), }; export const HolleyMeterFamily: MeterFamily = { code: MeterFamilyCode.Holley, probeCommand: () => hexToBase64('7E 02'), }; // Helper functions for Alpha meter families const createCommandWithTableAndPassword = ({ probeCommandInHex, meterPassword }) => hexToBase64(`${probeCommandInHex} ${meterPassword}`); const createAlphaProbeCommand = ({ meterPassword, passwordLength, probeCommandInHex }) => { const meterPasswordTrim = meterPassword.trim(); if (passwordLength !== meterPasswordTrim.length) { throw 'received password error'; } return createCommandWithTableAndPassword({ probeCommandInHex, meterPassword: meterPasswordTrim }); }; const buildSetTimeProbeCommandForAlphaA1InHex = (command, time) => { const timeInMoment = moment(`${time}`, 'hh:mm:ss'); const hours = uint8ToBCD(timeInMoment.hours()); const minutes = uint8ToBCD(timeInMoment.minutes()); const seconds = uint8ToBCD(timeInMoment.seconds()); const arrayBuffer = [hours, minutes, seconds]; return `${command} ${base64ToHex( base64ToArrayBuffer.encode(Int8Array.from(arrayBuffer) as unknown as ArrayBuffer), )}`; }; const searchLastLeapYear = aMoment => { const momentCopy = aMoment.clone(); while (!momentCopy.isLeapYear()) { momentCopy.subtract(1, 'year'); } return momentCopy; }; const buildSetDateAndTimeProbeCommandForAlphaA1InHex = (command, time, date) => { const dateInMoment = moment(`${date} ${time}`, 'YYYY-MM-DD hh:mm:ss'); const hours = uint8ToBCD(dateInMoment.hours()); const minutes = uint8ToBCD(dateInMoment.minutes()); const seconds = uint8ToBCD(dateInMoment.seconds()); const year = uint8ToBCD(dateInMoment.year() % 100); const month = uint8ToBCD(dateInMoment.month() + 1); const monthDay = uint8ToBCD(dateInMoment.date()); const weekday = uint8ToBCD(dateInMoment.day() + 1); const lastLeapYearMoment = searchLastLeapYear(dateInMoment); const counterSinceLastLeapYear = dateInMoment.diff(lastLeapYearMoment, 'years'); // eslint-disable-next-line no-bitwise const finalByte = ((weekday << 5) & 0b11100000) | ((counterSinceLastLeapYear << 2) & 0b00001100); const arrayBuffer = [year, month, monthDay, hours, minutes, seconds, 0, finalByte]; return `${command} ${base64ToHex( base64ToArrayBuffer.encode(Int8Array.from(arrayBuffer) as unknown as ArrayBuffer), )}`; }; const A1_PASSWORD_LENGTH = 11; export const AlphaA1MeterFamily: MeterFamily = { code: MeterFamilyCode.AlphaA1, probeCommand: (meterPassword, billingTable) => { const command = '7E 33'; if (!meterPassword) return hexToBase64(billingTable ? `${command} ${billingTable}` : command); return createAlphaProbeCommand({ meterPassword, passwordLength: A1_PASSWORD_LENGTH, probeCommandInHex: `${command} ${billingTable}`, }); }, setTimeProbeCommand: (meterPassword, time) => { const command = '7E 36'; if (!meterPassword) return hexToBase64(command); return createAlphaProbeCommand({ meterPassword, passwordLength: A1_PASSWORD_LENGTH, probeCommandInHex: buildSetTimeProbeCommandForAlphaA1InHex(command, time), }); }, setDateAndTimeCommand: (meterPassword, time, date) => { const command = '7E 35'; if (!meterPassword) return hexToBase64(command); return createAlphaProbeCommand({ meterPassword, passwordLength: A1_PASSWORD_LENGTH, probeCommandInHex: buildSetDateAndTimeProbeCommandForAlphaA1InHex(command, time, date), }); }, resetToZeroCommand: () => hexToBase64('7E 06'), }; const A3_PASSWORD_LENGTH = 59; export const AlphaA3MeterFamily: MeterFamily = { code: MeterFamilyCode.AlphaA3, probeCommand: (meterPassword, billingTable) => { const command = '7E 34'; if (!meterPassword) return hexToBase64(billingTable ? `${command} ${billingTable}` : command); return createAlphaProbeCommand({ meterPassword, passwordLength: A3_PASSWORD_LENGTH, probeCommandInHex: `${command} ${billingTable}`, }); }, resetToZeroCommand: () => hexToBase64('7E 0C'), }; export const MeterFamilies = [ HexingMeterFamily, HX110HexingMeterFamily, HXE310AndHXF300HexingMeterFamily, ElsterMeterFamily, IskraMeterFamily, AlphaA1MeterFamily, AlphaA3MeterFamily, HolleyMeterFamily, ];