import { S7DataType, ReadPoint, ParsedAddr } from './types.js'; export function parseAddress(address: string): ParsedAddr { const [dbPart, addrPart, bitPart] = address.split('.'); const dbNumber = parseInt(dbPart.substring(2)); if (addrPart.startsWith('DBX')) { return { dbNumber, addrType: 'DBX', byteOffset: parseInt(addrPart.slice(3)), bitOffset: parseInt(bitPart) }; } if (addrPart.startsWith('DBD')) { return { dbNumber, addrType: 'DBD', byteOffset: parseInt(addrPart.slice(3)), bitOffset: 0 }; } if (addrPart.startsWith('DBW')) { return { dbNumber, addrType: 'DBW', byteOffset: parseInt(addrPart.slice(3)), bitOffset: 0 }; } if (addrPart.startsWith('DBB')) { return { dbNumber, addrType: 'DBB', byteOffset: parseInt(addrPart.slice(3)), bitOffset: 0 }; } throw new Error(`Invalid S7 address: ${address}`); } export function typeSize(dataType: S7DataType, stringLen?: number): number { switch (dataType) { case 'bool': return 1; case 'byte': return 1; case 'int': return 2; case 'word': return 2; case 'dint': return 4; case 'dword': return 4; case 'lint': return 8; case 'ulint': return 8; case 'real': return 4; case 'string': return (stringLen ?? 254) + 2; case 'wstring': return (stringLen ?? 512) * 2 + 4; } } export function decode(buf: Buffer, addr: ParsedAddr, dataType: S7DataType): any { const off = addr.byteOffset; if (addr.addrType === 'DBX') { return (buf.readUInt8(off) & (1 << addr.bitOffset)) !== 0; } switch (dataType) { case 'bool': return buf.readUInt8(off) !== 0; case 'byte': return buf.readUInt8(off); case 'int': return buf.readInt16BE(off); case 'word': return buf.readUInt16BE(off); case 'dint': return buf.readInt32BE(off); case 'dword': return buf.readUInt32BE(off); case 'lint': return buf.readBigInt64BE(off); case 'ulint': return buf.readBigUInt64BE(off); case 'real': return buf.readFloatBE(off); case 'string': { const len = buf.readUInt8(off + 1); return buf.toString('utf-8', off + 2, off + 2 + len); } case 'wstring': { const len = buf.readUInt16BE(off + 2); return buf.toString('utf-16le', off + 4, off + 4 + len * 2); } } } export function encode(buf: Buffer, addr: ParsedAddr, dataType: S7DataType, value: any, stringLen?: number): void { const off = addr.byteOffset; if (addr.addrType === 'DBX') { const cur = buf.readUInt8(off); buf.writeUInt8(value ? cur | (1 << addr.bitOffset) : cur & ~(1 << addr.bitOffset), off); return; } switch (dataType) { case 'bool': buf.writeUInt8(value ? 1 : 0, off); break; case 'byte': buf.writeUInt8(value, off); break; case 'int': buf.writeInt16BE(value, off); break; case 'word': buf.writeUInt16BE(value, off); break; case 'dint': buf.writeInt32BE(value, off); break; case 'dword': buf.writeUInt32BE(value, off); break; case 'lint': buf.writeBigInt64BE(BigInt(value), off); break; case 'ulint': buf.writeBigUInt64BE(BigInt(value), off); break; case 'real': buf.writeFloatBE(value, off); break; case 'string': { const maxLen = stringLen ?? 254; const s = String(value ?? ''); buf.writeUInt8(maxLen, off); buf.writeUInt8(Math.min(s.length, maxLen), off + 1); buf.write(s.substring(0, maxLen), off + 2, 'utf-8'); break; } case 'wstring': { const maxLen = stringLen ?? 512; const s = String(value ?? ''); const chars = s.substring(0, maxLen); buf.writeUInt16BE(maxLen, off); buf.writeUInt16BE(chars.length, off + 2); buf.write(chars, off + 4, 'utf-16le'); break; } } } const VALID_TYPES = new Set(['bool', 'byte', 'int', 'word', 'dint', 'dword', 'lint', 'ulint', 'real', 'string', 'wstring']); export function normalize(p: T): T { const lower = p.dataType.toLowerCase(); if (!VALID_TYPES.has(lower)) { throw new Error(`Invalid dataType "${p.dataType}", must be one of: ${[...VALID_TYPES].join(', ')}`); } return { ...p, dataType: lower as S7DataType }; }