using System.IO.Ports; using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Devices.BatteryDeligreen; public class BatteryDeligreenDevice { private const Parity Parity = System.IO.Ports.Parity.None; private const StopBits StopBits = System.IO.Ports.StopBits.One; private const Int32 BaudRate = 19200; private const Int32 DataBits = 8; private readonly SerialPort _serialPort; public UInt16 SlaveId { get; } // Dynamically construct the frame to send private const String FrameStart = "7E"; // Starting of the frame private const String Version = "3230"; // Protocol version private const String DeviceCode = "3436"; // Device Code private const String TelemetryFunctionCode = "3432"; private const String TelcommandFunctionCode = "3434"; private static SerialPort _sharedSerialPort; private static SerialPort GetSharedPort(string tty) { if (_sharedSerialPort == null) { _sharedSerialPort = new SerialPort(tty, BaudRate, Parity, DataBits, StopBits) { ReadTimeout = 1000, // 1 second timeout for reads WriteTimeout = 1000 // 1 second timeout for writes }; try { // Open the shared serial port _sharedSerialPort.Open(); Console.WriteLine("Shared Serial Port opened successfully."); } catch (Exception e) { Console.WriteLine(e); } } return _sharedSerialPort; } public static void CloseSharedPort() { _sharedSerialPort?.Close(); Console.WriteLine("Shared Serial Port closed."); } // Constructor for local serial port connection public BatteryDeligreenDevice(String tty, UInt16 slaveId) { SlaveId = slaveId; _serialPort = GetSharedPort(tty); } // Method to send data to the device private void Write(String hexCommand) { try { // Convert hex string to byte array byte[] commandBytes = HexStringToByteArray(hexCommand); // Send the command _serialPort.Write(commandBytes, 0, commandBytes.Length); //Console.WriteLine("Write Command sent successfully."); } catch (TimeoutException) { Console.WriteLine("Write operation timed out."); } catch (Exception e) { Console.WriteLine($"Error during write operation: {e.Message}"); throw; } } // Method to read data from the device private Byte[] Read(Int32 bufferSize) { try { // Read data from the serial port var buffer = new Byte[bufferSize]; var bytesRead = _serialPort.Read(buffer, 0, bufferSize); //Console.WriteLine($"Read {bytesRead} bytes from the device."); // Return only the received bytes var responseData = new Byte[bytesRead]; Array.Copy(buffer, responseData, bytesRead); return responseData; } catch (TimeoutException) { Console.WriteLine("Read operation timed out."); return Array.Empty(); // Return empty array on timeout } catch (Exception e) { Console.WriteLine($"Error during read operation: {e.Message}"); throw; } } private static String BytesToHexString(byte[] byteArray) { return BitConverter.ToString(byteArray).Replace("-", "").ToUpper(); } // Helper method to convert a hex string to a byte array private static Byte[] HexStringToByteArray(string hex) { if (string.IsNullOrWhiteSpace(hex) || hex.Length % 2 != 0) throw new ArgumentException("Invalid hex string."); byte[] bytes = new byte[hex.Length / 2]; for (var i = 0; i < hex.Length; i += 2) { bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); } return bytes; } // Dispose method to release the serial port public void Dispose() { if (_serialPort.IsOpen) { _serialPort.Close(); Console.WriteLine("Serial port closed."); } } // Read telemetry data from the connected device private async Task ReadTelemetryData(UInt16 batteryId) { var frameToSend = batteryId switch { 0 => "7E3230303034363432453030323030464433370D", 1 => "7E3230303134363432453030323031464433350D", 2 => "7E3230303234363432453030323032464433330D", 3 => "7E3230303334363432453030323033464433310D", 4 => "7E3230303434363432453030323034464432460D", 5 => "7E3230303534363432453030323035464432440D", 6 => "7E3230303634363432453030323036464432420D", 7 => "7E3230303734363432453030323037464432390D", 8 => "7E3230303834363432453030323038464432370D", 9 => "7E3230303934363432453030323039464432350D", _ => "0" }; // var frameToSend = ConstructFrameToSend(batteryId,TelemetryFunctionCode); try { // Write the frame to the channel (send it to the device) Write(frameToSend); // Read the response from the channel (assuming max response size) var responseBytes = await ReadFullResponse(168, 64); // Convert the byte array to a hexadecimal string var responseHex = BytesToHexString(responseBytes); return new TelemetryFrameParser().ParsingTelemetryFrame(responseHex); } catch (Exception ex) { Console.WriteLine($"Error during telemetry data retrieval: {ex.Message}"); throw; } } private Task ReadFullResponse(Int32 totalBytes, Int32 chunkSize) { var responseBuffer = new List(); while (responseBuffer.Count < totalBytes) { // Calculate how many more bytes need to be read var bytesToRead = Math.Min(chunkSize, totalBytes - responseBuffer.Count); var chunk = Read(bytesToRead); if (chunk.Length == 0) { throw new TimeoutException("Failed to read the expected number of bytes from the device."); } responseBuffer.AddRange(chunk); } return Task.FromResult(responseBuffer.ToArray()); } private async Task ReadTelecomandData(UInt16 batteryId) { var frameToSend = batteryId switch { 0 => "7E3230303034363434453030323030464433350D", 1 => "7E3230303134363434453030323031464433330D", 2 => "7E3230303234363434453030323032464433310D", 3 => "7E3230303334363434453030323033464432460D", 4 => "7E3230303434363434453030323034464432440D", 5 => "7E3230303534363434453030323035464432420D", 6 => "7E3230303634363434453030323036464432390D", 7 => "7E3230303734363434453030323037464432370D", 8 => "7E3230303834363434453030323038464432350D", 9 => "7E3230303934363434453030323039464432330D", _ => "0" }; try { // Write the frame to the channel (send it to the device) Write(frameToSend); // Read the response from the channel (assuming max response size) var responseBytes = await ReadFullResponse(116, 64); // Assuming Read can be executed asynchronously // Convert the byte array to a hexadecimal string var responseHex = BytesToHexString(responseBytes); return new TelecommandFrameParser().ParsingTelecommandFrame(responseHex); } catch (Exception ex) { Console.WriteLine($"Error during Telecomnd data retrieval: {ex.Message}"); throw; } } public async Task Reads() { var dataRecord = ReadTelemetryData(SlaveId).Result; var alarmRecord = ReadTelecomandData(SlaveId).Result; await Task.Delay(5); // looks like this is need. A time delay needed between each frame to send to each battery return (dataRecord != null && alarmRecord != null ) ? new BatteryDeligreenRecord(dataRecord, alarmRecord) : null; // to check how this work if one of the record is null } private static String ConstructFrameToSend(UInt16 batteryId, String functionCode) { // Convert batteryId to a 2-character ASCII string var batteryIdAscii = $"{(Char)(batteryId / 10 + '0')}{(char)(batteryId % 10 + '0')}"; var batteryIdHex = string.Concat(batteryIdAscii.Select(c => ((Int32)c).ToString("X2"))); Console.WriteLine("Battery ID " + batteryIdHex); var frameToSend = FrameStart + Version + batteryIdHex + DeviceCode + functionCode + "453030323030464433370D"; // Example custom frame with dynamic batteryId Console.WriteLine(frameToSend); return frameToSend; } }