using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Xylem.Common.CommonCore.Consts; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore; using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig; using Xylem.Common.Utils.ProcessExec; using Xylem.Common.Utils.ProcessExec.EventArguments; namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisFile { /// /// Genesis meter file operations /// public class MeterFile : IProcessState { /// /// Port scan result event for message dispatcher to caller /// public event EventHandler OnProcessUpdate; //the raw data size is limited by the meter input buffer size, removed protocol length private const Int32 MaxRawDataSize = 56; private const Int32 ExtremeFileWriteTimeoutMs = 30000; private const Int32 ExtendedFileIoTimeoutMs = 5000; private const Int32 CommandWriteRetries = 2; //timeout value loaded dynamically starting with extended timeout, on retry to extreme timeout private Int32 _fileWriteTimeOutMs = ExtendedFileIoTimeoutMs; private readonly IGenesisMeter _currentGenesis; private Int32 _filePointer; //private const String StrNewFileReadWriteMode = "wb"; //private const String StrOpenFileReadOnlyMode = "rb"; //private const String StrOpenFileReadWriteMode = "rb+"; private const String StrNewFileReadWriteMode = "w"; private const String StrOpenFileReadOnlyMode = "r"; private const String StrOpenFileReadWriteMode = "r+"; /// /// FSeek for start of file /// private const Int32 FSeekStartOfFile = 0; /// /// FSeek for end of file /// private const Int32 FSeekEndOfFile = 2; private String _fileName; private Int32 _overallBytesCtr; /// /// Configuration file of meter. /// public const String StrMeterConfigFile = "0\\config"; /// /// The test file is for SENSUSRADIO as placeholder for update over the air. /// public const String StrMeterTstFile = "1\\tstfile"; /// /// Engineering log index file of meter. /// public const String StrMeterEngLogIndexFile = "1\\logindex"; /// /// EMEA log files which can be removed on NA installation /// public static readonly String[] EmeaLogs = { "1\\fdrdata", "1\\logdata", "1\\evtdata" }; /// /// NA log files which can be removed on EMEA installation /// public static readonly String[] NaLogs = { "1\\NAalarm", "1\\NAconfig", "1\\NApress", "1\\NAstate", "1\\NAtempC", "1\\NAwater" }; /// /// Essential files which have to be kept /// public static readonly String[] FilesToKeep = { "0\\password", StrMeterConfigFile, "1\\logindex", "1\\mettbl", "1\\mettbl_b", "1\\powcorr" }; /// /// Files which always can be removed to clean the file system up "tidy file" /// public static readonly String[] FilesToRemove = { "1\\upg", "1\\upgrade", StrMeterTstFile }; /// /// Drive zero of meter. /// public const String StrMeterDrive0 = "0\\"; /// /// Drive one of meter. /// public const String StrMeterDrive1 = "1\\"; /// /// String delimiter of files from Genesis. /// public const Char StringDelimiter = '\0'; /// /// Processed bytes counter /// public Int32 ProcessedBytesCtr; private Boolean _stopProcess; /// /// Increase timeout to extreme value /// /// /// - Timeout decreased to speed up stop operation. /// public void SetExtremeFileWriteTimeout() { _fileWriteTimeOutMs = ExtremeFileWriteTimeoutMs; } /// /// Set timeout back to default timeout /// /// /// - Timeout decreased to speed up stop operation. /// public void SetDefaultFileWriteTimeout() { _fileWriteTimeOutMs = ExtendedFileIoTimeoutMs; } /// /// Stop process /// /// /// - Timeout decreased to speed up stop operation. /// /// /// - Check transmit protocol for null. /// public Boolean StopProcess { get => _stopProcess; set { _currentGenesis?.TransmitProtocol?.SetDefaultResponseTimeout(); _stopProcess = value; } } /// /// Ctor for genesis meter file operations /// /// /// /// - Initial /// /// /// - Stop process added /// public MeterFile(IGenesisMeter genesisMeter) { _currentGenesis = genesisMeter; _fileName = ""; _stopProcess = false; } /// /// Process counter in percent /// /// /// - Initial /// public Double ProcessCtrPercent => 100.0 * ProcessedBytesCtr / (_overallBytesCtr > 0 ? _overallBytesCtr : 1); /// /// Writing a file to the meter /// /// file name as string /// data to store /// for partial file write procedures this is the offset to the file pointer /// true, if file can be find and stored and, if required, read back and verified /// /// - Initial /// /// /// - Early return on failed communication /// /// /// - Added overall bytes counter for single file processing information /// /// /// - Stop process added /// /// /// - CHeck if file exists /// /// /// - Extended response timeout /// /// /// - Register read/write retry 0 implemented. /// /// /// - Stop FWrite if FWrite failed, /// - FClose if FWrite failed, /// - avoid FTell if FWrite failed /// /// /// - Retries changed for and back on different position, /// - Timeout changed for and back on different position, /// - Dummy read removed. /// /// /// - Event on changed process counter. /// /// /// - Dynamic timeout load from extended to extreme timeout on retries. /// /// /// - Add a NULL at the end of the file name. /// /// /// - Adapted to new ReadFilePointerOffset. /// /// /// - Combined FOpen to one file open function, /// - Using for multiple write access to register instead of 3 writes. /// /// /// - Check transmit protocol for null. /// public Boolean WriteMeterFile(String fileName, Byte[] dataBytes, Int32 fileOffset = 0) { //set to true to enter the cyclic write routines, will be overwritten with file pointer adjustment var returnValue = true; //set processing counter ProcessedBytesCtr = 0; //inform the caller of changed statue OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("", 0)); _overallBytesCtr = dataBytes.Length; //add file name delimiter if (!fileName.Contains(StringDelimiter)) fileName += StringDelimiter; //check if file is unlocked for write access if (_fileName != fileName) return false; var accessMode = fileOffset == 0 ? StrNewFileReadWriteMode : StrOpenFileReadWriteMode; //file open as new or additional write with immediately return if file cannot be opened if (!OpenMeterFile(fileName, accessMode)) return false; //adjust file pointer if offset is required if (fileOffset > 0) { returnValue = SetFilePointerOffset(fileOffset); if (returnValue) { //read file pointer offset if (!ReadFilePointerOffset(out var readBackFilePointerOffset) || fileOffset != readBackFilePointerOffset) returnValue = false; } } if (returnValue && !_stopProcess) { //set extended timeout for file operations to extended timeout _currentGenesis.TransmitProtocol?.SetResponseTimeout(_fileWriteTimeOutMs); //set register write retries to 0 to avoid doubling of records, if wakeup message from //serial communication routine received, one retry will kicked off in the request protocol CommunicationConfig.RequestRetries = 0; var rpc = BuildRpcInt32(1, dataBytes.Length, _filePointer); returnValue = _currentGenesis.WriteRegister(Register.Configexchange.FileWrite, rpc); //calculate records to send based on MaxRawDataSize var chunkCounts = dataBytes.Length / MaxRawDataSize; if (0 != dataBytes.Length % MaxRawDataSize) { chunkCounts += 1; } var dataByteList = new List(); dataByteList.AddRange(dataBytes); var dataChunk = new List(); var ctr = 0; //the preceding routine needs to wait for response, else this is always true! while (returnValue && ctr < chunkCounts && !_stopProcess) { var index = ctr * MaxRawDataSize; var size = index + MaxRawDataSize > dataByteList.Count ? dataByteList.Count - index : MaxRawDataSize; //prepare data to write dataChunk.Clear(); dataChunk.AddRange(dataByteList.GetRange(index, size)); //break on failed write of this part returnValue = _currentGenesis.WriteRegister(Register.Configexchange.FileWrite, dataChunk.ToArray()); //set processed bytes for write loop ProcessedBytesCtr += size; //inform the caller of changed statue var processCtrPercent = 100.0 * ProcessedBytesCtr / (_overallBytesCtr > 0 ? _overallBytesCtr : 1); OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("", processCtrPercent)); ctr++; } //read back bytes written var writtenBytes = 0; var returnedFileOffset = 0; //the preceding routine needs to wait for response, else this is always true! if (returnValue) { writtenBytes = RegisterConverter.ByteArrayToValue( _currentGenesis.ReadRegister(Register.Configexchange.FileWrite)); ReadFilePointerOffset(out returnedFileOffset); } //set timeout back to default value _currentGenesis.TransmitProtocol?.SetDefaultResponseTimeout(); CommunicationConfig.RequestRetries = CommunicationConfig.DefaultRequestRetries; if (writtenBytes != ProcessedBytesCtr || returnedFileOffset != fileOffset + writtenBytes) return false; } //processed bytes no longer needed OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("", 0)); ProcessedBytesCtr = 0; _overallBytesCtr = 0; returnValue &= CloseMeterFile(); return returnValue; } /// /// Build RPC (remote procedure call) for 3 Int32 parameters. /// This routine concatenates these 3 Int32 to one byte array to have a multiple read or /// write operation to the register instead of having 3 simple register write accesses. /// /// /// /// /// file content as byte array /// /// - Initial /// private static Byte[] BuildRpcInt32(Int32 first, Int32 second, Int32 last) { var rpc = new List(); rpc.AddRange(BitConverter.GetBytes(first)); rpc.AddRange(BitConverter.GetBytes(second)); rpc.AddRange(BitConverter.GetBytes(last)); return rpc.ToArray(); } /// /// Read file from the meter /// /// file name as string /// empty list of data /// file content as byte array /// /// - Initial /// /// /// - Stop process added /// /// /// - Register read/write retry 0 implemented, /// - Extended communication timeout /// /// /// - Command write retry 0 implemented, /// - Extended timeout. /// /// /// - Retries changed for and back, /// - Timeout changed for and back, /// - Dummy read removed. /// /// /// - Wait after writing for response before reading! /// /// /// - Check remaining size to abort operation. /// /// /// - Event on changed process counter. /// /// /// - Add a NULL at the end of the file name. /// /// /// - Read data according to file size searched with FSeek. /// /// /// - Speed up FOpen as read is not so critical. /// /// /// - Avoid reading of file size from FSeek is 0. /// /// /// - Removed extended timeout. /// /// /// - FClose added on failed GetFileSizeInternal. /// - Using for multiple write access to register instead of 3 writes. /// public Boolean ReadMeterFile(String fileName, out List readBackData) { //set processing counter ProcessedBytesCtr = 0; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("", 0)); //add file name delimiter if (!fileName.Contains(StringDelimiter)) fileName += StringDelimiter; readBackData = new List(); //use high speed access if (!OpenMeterFile(fileName, StrOpenFileReadOnlyMode)) return false; //get the current file size, the size of 0 is a valid file size, but reading is not necessary if (!GetFileSizeInternal(fileName, out var fileSize)) { // here the file is already opened and has to be closed to avoid error 0x040E "TOO_MANY_OPEN" CloseMeterFile(); return false; } //set register write retries to 0 to avoid doubling of records CommunicationConfig.RequestRetries = 0; if (fileSize > 0) { var rpc = BuildRpcInt32(1, fileSize, _filePointer); _currentGenesis.WriteRegister(Register.Configexchange.FileRead, rpc); //the preceding routine needs to wait for response, else this is always true! while (fileSize > 0 && !_stopProcess) { var remainingData = RegisterConverter.ByteArrayToValue( _currentGenesis.ReadRegister(Register.Configexchange.FileRead)); fileSize = remainingData > 0 ? fileSize - remainingData : 0; while (remainingData > 0) { var size = remainingData > MaxRawDataSize ? MaxRawDataSize : remainingData; readBackData.AddRange(_currentGenesis.ReadRegister(Register.Configexchange.FileRead, size)); remainingData -= size; //set processed bytes for read loop ProcessedBytesCtr += size; //inform the caller of changed statue var processCtrPercent = 100.0 * ProcessedBytesCtr / (_overallBytesCtr > 0 ? _overallBytesCtr : 1); OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("", processCtrPercent)); } } }// file size unequal to 0 //processed bytes no longer needed ProcessedBytesCtr = 0; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("", 0)); //set timeout back to default value CommunicationConfig.RequestRetries = CommunicationConfig.DefaultRequestRetries; //read raw data from meter return CloseMeterFile(); } /// /// Read catalog of all meter files /// /// file names as string /// e.g. "1\\" for drive 1 /// e.g. "upd*" all upgrade files on drive /// /// /// - Initial. /// /// /// - Default wildcard. /// public Boolean ReadMeterFileCatalog(out List files, String drive, String wildcard = Constants.StrWildcard) { files = new List(); //add file name delimiter if (!wildcard.Contains(StringDelimiter)) wildcard += StringDelimiter; //write the initial key var key = 0; _stopProcess = false; // if both key are identical, the last file has been listed while (!_stopProcess) { Thread.Sleep(1); // remind entry key as this will be changed with the read back var lastKey = key; var searchPattern = drive + wildcard; _currentGenesis.WriteRegister(Register.Configexchange.Catalogue, searchPattern); _currentGenesis.WriteRegister(Register.Configexchange.Catalogue, key); // initial read var readData = ""; String backupData; do { backupData = readData; readData += RegisterConverter.ByteArrayToValue( _currentGenesis.ReadRegister(Register.Configexchange.Catalogue)); } while (!readData.Contains(StringDelimiter) && backupData != readData); if (!string.IsNullOrEmpty(readData)) { //read new key key = RegisterConverter.ByteArrayToValue( _currentGenesis.ReadRegister(Register.Configexchange.Catalogue)); } if (key == lastKey) break; // remove the "\0" var strMeterFile = readData.Split(StringDelimiter); // avoid identical files repetition var filePathName = drive + strMeterFile[0]; files.Add(filePathName); } files.Sort(); return files.Count > 0; } /// /// Create an empty meter file as placeholder for space in the file system. /// /// file name as string /// the file size for the file pointer /// true, if file can be found and stored and, if required, read back and verified /// /// - Initial /// /// /// - Add a NULL at the end of the file name. /// /// /// - Adapted to new ReadFilePointerOffset. /// /// /// - Using for multiple write access to register instead of 3 writes. /// /// /// - Check transmit protocol for null. /// public Boolean CreateEmptyMeterFile(String fileName, Int32 fileSize = 0) { //check if logged on to meter and access level is as expected if (_currentGenesis == null || !_currentGenesis.IsLoggedOn) return false; //add file name delimiter if (!fileName.Contains(StringDelimiter)) fileName += StringDelimiter; //check if file is unlocked for write if (_fileName != fileName) return false; //exit immediately if file cannot be opened if (!OpenMeterFile(fileName, StrNewFileReadWriteMode)) return false; var returnValue = true; //adjust file pointer if offset is required if (fileSize > 0) { // adjust file pointer to the end of file returnValue = SetFilePointerOffset(fileSize - 1); //read file pointer offset returnValue &= ReadFilePointerOffset(out var readBackFileSize); returnValue &= (fileSize - 1) == readBackFileSize; } if (returnValue && !_stopProcess) { //set extended timeout for file operations to extended timeout _currentGenesis.TransmitProtocol?.SetResponseTimeout(_fileWriteTimeOutMs); //set register write retries to 0 to avoid doubling of records, if wakeup message from //serial communication routine received, one retry will kicked off in the request protocol CommunicationConfig.RequestRetries = 0; var rpc = BuildRpcInt32(1, 1, _filePointer); returnValue = _currentGenesis.WriteRegister(Register.Configexchange.FileWrite, rpc); //write one byte at the end of file returnValue &= _currentGenesis.WriteRegister(Register.Configexchange.FileWrite, 0xFF); //read back bytes written var returnedFileOffset = 0; //the preceding routine needs to wait for response, else this is always true! if (returnValue) { RegisterConverter.ByteArrayToValue( _currentGenesis.ReadRegister(Register.Configexchange.FileWrite)); ReadFilePointerOffset(out returnedFileOffset); } //set timeout back to default value _currentGenesis.TransmitProtocol?.SetDefaultResponseTimeout(); CommunicationConfig.RequestRetries = CommunicationConfig.DefaultRequestRetries; if (returnedFileOffset != fileSize) returnValue = false; } returnValue &= CloseMeterFile(); return returnValue; } /// /// Read the file size. /// /// file name as string /// file size of the given file /// true: file size could be read /// /// - Initial /// /// /// - Modified returns. /// public Boolean GetFileSize(String fileName, out Int32 fileSize) { fileSize = 0; //check if logged on to meter and access level is as expected if (_currentGenesis == null || !_currentGenesis.IsLoggedOn) return false; //add file name delimiter if (!fileName.Contains(StringDelimiter)) fileName += StringDelimiter; //return immediately, if file cannot be opened if (!OpenMeterFile(fileName, StrOpenFileReadOnlyMode)) return false; var returnValue = SetFilePointerOffset(fSeekPara: FSeekEndOfFile); returnValue &= ReadFilePointerOffset(out fileSize); returnValue &= CloseMeterFile(); return returnValue; } /// /// Uses FSeek to get the file size. /// For this function the file has to be opened in advance! /// For correct access, the file pointer has to be set after detection of the /// file size back to the start of the file!!!!! /// /// file name as string /// file size of the given file /// true: file found /// /// - Initial /// private Boolean GetFileSizeInternal(String fileName, out Int32 fileSize) { fileSize = 0; var returnValue = SetFilePointerOffset(fSeekPara: FSeekEndOfFile); returnValue &= ReadFilePointerOffset(out fileSize); //set the file pointer back to the start of file!!!!! returnValue &= SetFilePointerOffset(); return returnValue; } /// /// Meter file verification against given data /// Takes into account, that the read file is always integer dividable by 4 due to the chunk size /// of the write and read access to the Genesis. /// /// file name as string /// data to store /// true: file found and verified /// /// - Initial /// /// /// - Changed return value to false, if file could not be read. /// /// /// - Compare byte by byte until length of given buffer, as the returned buffer is always filled /// up to the chunk size of 4. /// public Boolean VerifyMeterFile(String fileName, Byte[] dataBytes) { //check if logged on to meter and access level is as expected if (_currentGenesis == null || !_currentGenesis.IsLoggedOn) return false; var returnValue = true; //add file name delimiter if (!fileName.Contains(StringDelimiter)) fileName += StringDelimiter; if (!ReadMeterFile(fileName, out var binData) || binData == null || binData.Count == 0) return false; // compare the entire buffer which also includes the header of the file, remind the original // buffer may be smaller due to the chunk size of the communication (always dividable by 4), // meaning the receive buffer is always an integer of this chunk size! for (var ctr = 0; ctr < dataBytes.Length; ctr++) { if (binData[ctr] != dataBytes[ctr]) { returnValue = false; } } return returnValue; } /// /// Erase file from the meter /// /// file name as string /// /// /// - Initial /// /// /// - Extended timeout. /// /// /// - Removed retries to speed up erasure as it returns error code 0x040B. /// /// /// - Add a NULL at the end of the file name. /// /// /// - Removed extended timeout. /// public Boolean EraseMeterFile(String fileName) { //check if logged on to meter and access level is as expected if (_currentGenesis == null || !_currentGenesis.IsLoggedOn) return false; //add file name delimiter if (!fileName.Contains(StringDelimiter)) fileName += StringDelimiter; //check if file is unlocked for erase if (_fileName != fileName) return false; //request retries on single write register access, needs to be zero to hold the sequence CommunicationConfig.RequestRetries = 0; //write "remove-command" to meter, return is always false! //ATTENTION: read back returns FALSE because of unreadable content _currentGenesis.WriteRegister(Register.Configexchange.FileRemove, fileName, true, true); //set timeout back to default value CommunicationConfig.RequestRetries = CommunicationConfig.DefaultRequestRetries; return true; } /// /// Two stage unlock sequence for meter file write or erase access /// /// file name as string /// /// /// - Initial /// /// /// - Add a NULL at the end of the file name. /// public Boolean UnlockEraseWriteMeterFile(String fileName) { //add file name delimiter if (!fileName.Contains(StringDelimiter)) fileName += StringDelimiter; //remind file name being unlocked _fileName = fileName; return true; } /// /// FSeek implementation of genesis meter. /// ATTENTION: FOpen has to be executed in advance to get /// the file pointer. /// /// /// positioning to file start or end /// /// /// - Initial /// /// /// - Command write retry implemented. /// /// /// - Extended timeout. /// /// /// - Retries changed for and back, /// - Timeout changed for and back. /// /// /// - Extended FSeek for file end positioning. /// /// /// - Removed extended timeout. /// /// /// - Using for multiple write access to register instead of 3 writes. /// private Boolean SetFilePointerOffset(Int32 filePointerOffset = 0, Int32 fSeekPara = FSeekStartOfFile) { Boolean returnValue; var retryCtr = 0; if (FSeekEndOfFile == fSeekPara) { filePointerOffset = 0; } do { //request retries on single write register access, needs to be zero to hold the sequence CommunicationConfig.RequestRetries = 0; var rpc = BuildRpcInt32(_filePointer, filePointerOffset, fSeekPara); returnValue = _currentGenesis.WriteRegister(Register.Configexchange.SetFilePointerOffset, rpc); //function call returns 0 if FSeek successfully set returnValue &= 0 == RegisterConverter.ByteArrayToValue(_currentGenesis.ReadRegister( Register.Configexchange.SetFilePointerOffset)); //set back to default value CommunicationConfig.RequestRetries = CommunicationConfig.DefaultRequestRetries; } while (!returnValue && retryCtr++ < CommandWriteRetries); return returnValue; } /// /// FTell implementation /// /// file pointer offset /// /// - Initial /// /// /// - Command write retry implemented. /// /// /// - Extended timeout. /// /// /// - Retries changed for and back, /// - Timeout changed for and back. /// /// /// - Modified for return. /// /// /// - Removed extended timeout. /// private Boolean ReadFilePointerOffset(out Int32 filePointerOffset) { var retryCtr = 0; //request retries on single write register access, needs to be zero to hold the sequence CommunicationConfig.RequestRetries = 0; do { //read file pointer offset _currentGenesis.WriteRegister(Register.Configexchange.GetFilePointerOffset, _filePointer, false); filePointerOffset = RegisterConverter.ByteArrayToValue(_currentGenesis.ReadRegister( Register.Configexchange.GetFilePointerOffset)); } while (filePointerOffset <= 0 && retryCtr++ < CommandWriteRetries); //set timeout back to default value CommunicationConfig.RequestRetries = CommunicationConfig.DefaultRequestRetries; return filePointerOffset > 0; } /// /// FOpen implementation of genesis meter /// Open a file of the meter and sets the file pointer /// /// /// read, write eg, /// true if filePointer higher than zero /// /// - Initial /// /// /// - Extended timeouts, /// - Read DUMMY to overcome FW malfunction. /// /// /// - Retries changed for and back, /// - Timeout changed for and back. /// /// /// - Extended timings removed if high speed is active. /// /// /// - Removed retry dummy read. /// /// /// - Removed extended timeout. /// /// /// - Make sure NULL terminator is at the end of the file name and access mode. /// - Combined filename and access mode to use one write access to the register /// instead of two. /// private Boolean OpenMeterFile(String fileName, String accessMode) { //check if logged on to meter and access level is as expected if (_currentGenesis == null || !_currentGenesis.IsLoggedOn) return false; //add file name delimiter if (!fileName.Contains(StringDelimiter)) fileName += StringDelimiter; //add access mode delimiter if (!accessMode.Contains(StringDelimiter)) accessMode += StringDelimiter; var rpc = new List(); rpc.AddRange(fileName.ToCharArray()); // fill it padded to the chunk size while (rpc.Count % RegisterDefinition.ChunkSize != 0) { rpc.Add(StringDelimiter); } var retryCtr = 0; Boolean retValue; //request retries on single write register access, needs to be zero to hold the sequence CommunicationConfig.RequestRetries = 0; do { //write file name to meter with meter open command, don't wait for response var rpcString = new String(rpc.ToArray()); rpcString += accessMode; // wait for response! retValue = _currentGenesis.WriteRegister(Register.Configexchange.FileOpen, rpcString, false); if (retValue) { //get the file pointer _filePointer = RegisterConverter.ByteArrayToValue(_currentGenesis.ReadRegister( Register.Configexchange.FileOpen)); } } while ((_filePointer <= 0 || retValue == false) && retryCtr++ < CommandWriteRetries); //set retries back to default value CommunicationConfig.RequestRetries = CommunicationConfig.DefaultRequestRetries; return _filePointer > 0 && retValue; } /// /// FClose implementation of genesis meter /// Close the _filePointer referenced file of the meter /// /// /// /// - Initial /// /// /// - Command write retry implemented. /// /// /// - Extended timeout. /// /// /// - Retries changed for and back, /// - Timeout changed for and back, /// - Command retry removed. /// /// /// - Wait after writing for response before reading! /// /// /// - Ignore file close write return! /// /// /// - Removed extended timeout. /// /// /// - FClose retries. /// private Boolean CloseMeterFile() { //avoid file close if file pointer assignment failed if (_filePointer <= 0) return false; //request retries on single write register access, needs to be zero to hold the sequence CommunicationConfig.RequestRetries = 0; var retryCtr = 0; Boolean retValue; do { retValue = _currentGenesis.WriteRegister(Register.Configexchange.FileClose, _filePointer); //function call returns 0 if FClose successfully executed retValue &= 0 == RegisterConverter.ByteArrayToValue( _currentGenesis.ReadRegister(Register.Configexchange.FileClose)); }while ( retValue == false && retryCtr++ < CommandWriteRetries); //set timeout back to default value CommunicationConfig.RequestRetries = CommunicationConfig.DefaultRequestRetries; return retValue; } } }