Skip to content
40 changes: 18 additions & 22 deletions src/Baballonia/Helpers/JsonExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ public JsonDocument ReadUntilValidJson(Func<string> readLineFunction, TimeSpan t
if (DateTime.Now - startTime > timeout)
throw new TimeoutException("Timeout reached");

string content = _buffer.ToString();
var content = _buffer.ToString();

int start = -1;
int braceDepth = 0;
var start = -1;
var braceDepth = 0;

for (int i = _lastScannedIndex; i < content.Length; i++)
for (var i = _lastScannedIndex; i < content.Length; i++)
{
if (content[i] == '{')
{
Expand All @@ -34,32 +34,28 @@ public JsonDocument ReadUntilValidJson(Func<string> readLineFunction, TimeSpan t
else if (content[i] == '}')
{
braceDepth--;
if (braceDepth == 0 && start != -1)
{
int lenghh = i - start + 1;
string candidatestr = content.Substring(start, lenghh);
if (braceDepth != 0 || start == -1) continue;

var candidate = TryParseJson(candidatestr);
if(candidate != null)
{
_buffer.Remove(0, i + 1);
_lastScannedIndex = 0;
return candidate;
}
var lenghh = i - start + 1;
var candidatestr = content.Substring(start, lenghh);

}
var candidate = TryParseJson(candidatestr);
if (candidate == null) continue;

_buffer.Remove(0, i + 1);
_lastScannedIndex = 0;
return candidate;
}

}
_lastScannedIndex = Math.Max(0, content.Length - 1);

// Only read if buffer was processed and still no JSON
string line = readLineFunction();
if (!string.IsNullOrWhiteSpace(line))
{
_buffer.Append(line);
_lastScannedIndex = 0;
}
var line = readLineFunction();
if (string.IsNullOrWhiteSpace(line)) continue;

_buffer.Append(line);
_lastScannedIndex = 0;
}
}

Expand Down
136 changes: 81 additions & 55 deletions src/Baballonia/Helpers/SerialCommandSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,78 @@
using System.Threading;
using Baballonia.Contracts;

namespace Baballonia.Helpers
namespace Baballonia.Helpers;

public class SerialCommandSender : ICommandSender
{
public class SerialCommandSender : ICommandSender
private const int DefaultBaudRate = 115200; // esptool-rs: Setting baud rate higher than 115,200 can cause issues
private readonly SerialPort _serialPort;

public SerialCommandSender(string port)
{
private const int DefaultBaudRate = 115200; // esptool-rs: Setting baud rate higher than 115,200 can cause issues
private readonly SerialPort _serialPort;
_serialPort = new SerialPort(port, DefaultBaudRate);

// Set serial port parameters
_serialPort.DataBits = 8;
_serialPort.StopBits = StopBits.One;
_serialPort.Parity = Parity.None;
_serialPort.Handshake = Handshake.None;

// Set read/write timeouts
_serialPort.ReadTimeout = 30000;
_serialPort.WriteTimeout = 30000;
_serialPort.Encoding = Encoding.UTF8;

public SerialCommandSender(string port)
int maxRetries = 5;
const int sleepTimeInMs = 50;
while (maxRetries > 0)
{
_serialPort = new SerialPort(port, DefaultBaudRate);

// Set serial port parameters
_serialPort.DataBits = 8;
_serialPort.StopBits = StopBits.One;
_serialPort.Parity = Parity.None;
_serialPort.Handshake = Handshake.None;

// Set read/write timeouts
_serialPort.ReadTimeout = 30000;
_serialPort.WriteTimeout = 30000;
_serialPort.Encoding = Encoding.UTF8;

int maxRetries = 5;
const int sleepTimeInMs = 50;
while (maxRetries > 0)
try
{
try
{
_serialPort.Open();
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
break;
}
catch (IOException)
{
// Timeout
maxRetries = 0;
}
catch (Exception ex)
_serialPort.Open();
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
break;
}
catch (Exception ex)
{
switch (ex)
{
if (ex is not FileNotFoundException && ex is not UnauthorizedAccessException) throw;
maxRetries--;
Thread.Sleep(sleepTimeInMs);
case FileNotFoundException:
case UnauthorizedAccessException:
maxRetries--;
Thread.Sleep(sleepTimeInMs);
break;

case IOException:
case InvalidOperationException:
maxRetries = 0;
break;

default:
throw;
}
}
}
}

public void Dispose()
public void Dispose()
{
try
{
if (_serialPort.IsOpen)
_serialPort.Close();

_serialPort.Dispose();
}
catch (IOException) { }
}

public string ReadLine(TimeSpan timeout)
public string ReadLine(TimeSpan timeout)
{
string data;
try
{
string data;

// Read available data
if (_serialPort.BytesToRead > 0)
{
Expand All @@ -74,25 +87,38 @@ public string ReadLine(TimeSpan timeout)
{
return "";
}
}
catch (Exception ex)
{
switch (ex)
{
case IOException:
case InvalidOperationException:
case OperationCanceledException:
return ""; // Port is closed

return data;
default:
throw;
}
}

public void WriteLine(string payload)
{
_serialPort.DiscardInBuffer();
return data;
}

// Convert the payload to bytes
byte[] payloadBytes = Encoding.UTF8.GetBytes(payload);
public void WriteLine(string payload)
{
_serialPort.DiscardInBuffer();

// Write the payload to the serial port
const int chunkSize = 256;
for (int i = 0; i < payloadBytes.Length; i += chunkSize)
{
int length = Math.Min(chunkSize, payloadBytes.Length - i);
_serialPort.Write(payloadBytes, i, length);
Thread.Sleep(50); // Small pause between chunks
}
// Convert the payload to bytes
byte[] payloadBytes = Encoding.UTF8.GetBytes(payload);

// Write the payload to the serial port
const int chunkSize = 256;
for (int i = 0; i < payloadBytes.Length; i += chunkSize)
{
int length = Math.Min(chunkSize, payloadBytes.Length - i);
_serialPort.Write(payloadBytes, i, length);
Thread.Sleep(50); // Small pause between chunks
}
}
}
20 changes: 15 additions & 5 deletions src/Baballonia/Models/FirmwareSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private void SendCommand(string command)
}
}

public FirmwareResponses.Heartbeat? WaitForHeartbeat()
private FirmwareResponses.Heartbeat? WaitForHeartbeat()
{
return WaitForHeartbeat(new TimeSpan(5000));
}
Expand Down Expand Up @@ -121,11 +121,21 @@ public FirmwareResponse<JsonDocument> SendCommand(IFirmwareRequest request, Time
if (response == null)
return FirmwareResponse<JsonDocument>.Failure("Wtf?");

var result = JsonSerializer.Deserialize<FirmwareResponses.GenericResult>(response.results.First());

return FirmwareResponse<JsonDocument>.Success(JsonSerializer.Deserialize<JsonDocument>(result!.result)!);
try
{
// Attempt to extract inner content
var result = JsonSerializer.Deserialize<FirmwareResponses.GenericResult>(response.results.First());
return FirmwareResponse<JsonDocument>.Success(
JsonSerializer.Deserialize<JsonDocument>(result!.result)!);
}
catch (JsonException)
{
// Attempt to extract outer content
return FirmwareResponse<JsonDocument>.Success(
JsonSerializer.Deserialize<JsonDocument>(response.results.First())!);
}
}
catch (TimeoutException ex)
catch (TimeoutException)
{
return FirmwareResponse<JsonDocument>.Failure("Timeout reached");
}
Expand Down
21 changes: 6 additions & 15 deletions src/Baballonia/Models/FirmwareSessionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,9 @@

namespace Baballonia.Models;

public class FirmwareSessionFactory
public class FirmwareSessionFactory(ILoggerFactory loggerFactory, ICommandSenderFactory commandSenderFactory)
{
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger<FirmwareSessionFactory> _logger;
private readonly ICommandSenderFactory _commandSenderFactory;

public FirmwareSessionFactory(ILoggerFactory loggerFactory, ICommandSenderFactory commandSenderFactory)
{
_loggerFactory = loggerFactory;
_commandSenderFactory = commandSenderFactory;
_logger = loggerFactory.CreateLogger<FirmwareSessionFactory>();
}
private readonly ILogger<FirmwareSessionFactory> _logger = loggerFactory.CreateLogger<FirmwareSessionFactory>();

private string[] FindAvailableSerialPorts()
{
Expand Down Expand Up @@ -86,8 +77,8 @@ public async Task<List<PortToSessionMapping>> TryOpenAllSessionsAsync()
private FirmwareSessionV2? TryOpenV2Session(string port)
{
_logger.LogInformation($"Attempting to open V2 session for {port}");
var sender = _commandSenderFactory.Create(CommandSenderType.Serial, port);
var sessionV2 = new FirmwareSessionV2(sender, _loggerFactory.CreateLogger<FirmwareSessionV2>());
var sender = commandSenderFactory.Create(CommandSenderType.Serial, port);
var sessionV2 = new FirmwareSessionV2(sender, loggerFactory.CreateLogger<FirmwareSessionV2>());
var response = sessionV2.SendCommand(new FirmwareRequests.GetWhoAmIRequest(), TimeSpan.FromSeconds(3));
if (!response.IsSuccess)
{
Expand All @@ -111,8 +102,8 @@ public async Task<List<PortToSessionMapping>> TryOpenAllSessionsAsync()
private FirmwareSessionV1? TryOpenV1Session(string port)
{
_logger.LogInformation($"Attempting to open V1 session for {port}");
var sender = _commandSenderFactory.Create(CommandSenderType.Serial, port);
var sessionV1 = new FirmwareSessionV1(sender, _loggerFactory.CreateLogger<FirmwareSessionV1>());
var sender = commandSenderFactory.Create(CommandSenderType.Serial, port);
var sessionV1 = new FirmwareSessionV1(sender, loggerFactory.CreateLogger<FirmwareSessionV1>());
var heartbeat = sessionV1.WaitForHeartbeat(TimeSpan.FromSeconds(3));
if (heartbeat == null)
{
Expand Down
Loading