fix camera functionality now using CameraLineReader for quick buffered read

This commit is contained in:
Michal Buzik 2026-07-12 08:35:41 +02:00
parent 7129960b12
commit 38abca2e8a
9 changed files with 1569 additions and 625 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TBF.Rig.Network.Camera.CJMS11
{
internal sealed class CameraLineReader
{
private readonly Stream stream;
private readonly byte[] receiveBuffer;
private int bufferStart;
private int bufferEnd;
public CameraLineReader(
Stream stream,
int bufferSize = 64 * 1024)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanRead)
throw new ArgumentException(
"Stream must be readable.",
nameof(stream));
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException(
nameof(bufferSize));
this.stream = stream;
receiveBuffer = new byte[bufferSize];
}
public async Task<string> ReadLineAsync(
int timeoutMs,
CancellationToken cancellationToken)
{
if (timeoutMs <= 0)
throw new ArgumentOutOfRangeException(
nameof(timeoutMs));
using (var lineBuffer = new MemoryStream())
{
while (true)
{
int newLineIndex = FindNewLine();
if (newLineIndex >= 0)
{
int count = newLineIndex - bufferStart;
if (count > 0)
{
lineBuffer.Write(
receiveBuffer,
bufferStart,
count);
}
bufferStart = newLineIndex + 1;
return ConvertLineToString(lineBuffer);
}
if (bufferStart < bufferEnd)
{
lineBuffer.Write(
receiveBuffer,
bufferStart,
bufferEnd - bufferStart);
}
bufferStart = 0;
bufferEnd = 0;
int read = await ReadChunkAsync(
timeoutMs,
cancellationToken)
.ConfigureAwait(false);
if (read == 0)
{
throw new IOException(
"Camera connection was closed.");
}
bufferStart = 0;
bufferEnd = read;
}
}
}
private int FindNewLine()
{
for (int i = bufferStart; i < bufferEnd; i++)
{
if (receiveBuffer[i] == (byte)'\n')
return i;
}
return -1;
}
private async Task<int> ReadChunkAsync(
int timeoutMs,
CancellationToken cancellationToken)
{
using (var timeoutCts =
CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken))
{
timeoutCts.CancelAfter(timeoutMs);
try
{
return await stream.ReadAsync(
receiveBuffer,
0,
receiveBuffer.Length,
timeoutCts.Token)
.ConfigureAwait(false);
}
catch (OperationCanceledException)
when (!cancellationToken.IsCancellationRequested)
{
throw new TimeoutException(
$"Camera read timeout after {timeoutMs} ms.");
}
}
}
private static string ConvertLineToString(
MemoryStream lineBuffer)
{
int length = checked((int)lineBuffer.Length);
if (length == 0)
return string.Empty;
byte[] data = lineBuffer.GetBuffer();
if (data[length - 1] == (byte)'\r')
length--;
return Encoding.ASCII.GetString(
data,
0,
length);
}
}
}

View File

@ -32,12 +32,13 @@ namespace TBF.Rig.Network.Camera.CJMS11
string command;
bool grabImageCommandSent;
string response;
bool grabPassed;
bool grabFailed;
bool transferringGrabbedImage;
Image grabedImage;
Dictionary<int, Image> grabedImages = new Dictionary<int, Image>();
private Task<Image> grabImageTask;
private Task saveImageTask;
/// <summary>
@ -58,20 +59,17 @@ namespace TBF.Rig.Network.Camera.CJMS11
grabImageCommandSent = false;
transferringGrabbedImage = false;
/// image incomming event
if (camera.DebugLevel == DebugMode.Normal || camera.DebugLevel == DebugMode.DetectedOn)
{
Camera.ImageCameraHandler += ImageReceived;
}
// /// image incomming event
// if (camera.DebugLevel == DebugMode.Normal || camera.DebugLevel == DebugMode.DetectedOn)
// {
// Camera.ImageCameraHandler -= ImageReceived;
// Camera.ImageCameraHandler += ImageReceived;
// }
//TODO BUMI improve by simulate image
Camera.ImageCameraHandler -= ImageReceived;
Camera.ImageCameraHandler += ImageReceived;
/// inmages incomming event rotation
if (camera.DebugLevel == DebugMode.Normal || camera.DebugLevel == DebugMode.DetectedOn)
{
Camera.ImagesCameraHandler += ImagesReceived;
}
//TODO BUMI improve by simulate image
Camera.ImagesCameraHandler -= ImagesReceived;
Camera.ImagesCameraHandler += ImagesReceived;
}
@ -80,11 +78,17 @@ namespace TBF.Rig.Network.Camera.CJMS11
response = a.Response;
}
private readonly object imageSync = new object();
void ImageReceived(object sndr, PromptReceivedImageEventArgs a)
{
if(a.idxCamera != camera.CameraIdx) return;
grabedImage = a.image;
grabPassed = true;
if (a == null || a.idxCamera != camera.CameraIdx)
return;
lock (imageSync)
{
grabedImage = a.image;
}
}
void ImagesReceived(object sndr, PromptReceivedImagesEventArgs a)
@ -92,7 +96,6 @@ namespace TBF.Rig.Network.Camera.CJMS11
if(a.idxCamera != camera.CameraIdx) return;
grabedImage = a.image; //upgraded image common
grabedImages[a.idxImage] = a.image; // stored image from sent to list of images
grabPassed = true;
}
@ -100,99 +103,166 @@ namespace TBF.Rig.Network.Camera.CJMS11
{
grabImageCommandSent = false;
transferringGrabbedImage = false;
grabPassed = false;
grabedImage = null;
grabImageTask = null;
saveImageTask = null;
grabedImages.Clear();
if ((imgFileNames != null) && (imgFileNames.Length > 0) && File.Exists(imgFileNames[0]))
{
///
/// An image specified, (1) delete previous image, (2) check if this is a simulation
///
File.Delete(imgFileNames[0]);
/* if (camera.CameraCfg.DebugLevel == DebugMode.Simulate || camera.CameraCfg.DebugLevel == DebugMode.DetectedOff)
{
///
/// Camera is in simulation mode => create a simulated image
///
File.Copy(string.Format("{0}\\Pictures\\sample.jpg", Program.ExecutableDir), imgFileNames[0]);
}*/
}
if (imgFileNames != null &&
imgFileNames.Length > 0 &&
!string.IsNullOrEmpty(imgFileNames[0]) &&
File.Exists(imgFileNames[0]))
{
File.Delete(imgFileNames[0]);
}
}
public Event Run()
{
if ((imgFileNames == null) || (imgFileNames.Length < 1) || (imgFileNames[0] == null))
if (imgFileNames == null ||
imgFileNames.Length < 1 ||
string.IsNullOrEmpty(imgFileNames[0]))
{
///
/// No images to be grabbed => Done
///
return Event.GrabPassed;
UnsubscribeEvents();
return Event.GrabPassed;
}
/*else if (camera.CameraCfg.DebugLevel == DebugMode.Simulate ||
camera.CameraCfg.DebugLevel == DebugMode.DetectedOff)
{
///
/// Camera is in simulation mode => create a simulated image and complete
///
return Event.GrabPassed;
}*/
else if (camera.IsGrabImageListenerThreadAlive)
{
return Event.CameraBusy;
}
else if (!grabImageCommandSent)
if (!grabImageCommandSent)
{
///
/// Normal operation, no command sent yet => (1) wait until telnet state = Inactive, (2) send a command
///
camera.StartGrabImageListener();
grabImageCommandSent = true;
log.Debug( $"Starting asynchronous grab: cameraIdx={camera.CameraIdx}");
grabImageCommandSent = true;
grabImageTask = camera.StartGrabImageListenerAsync();
return Event.CameraBusy;
}
else if (grabPassed)
if (grabImageTask == null)
{
///
/// Normal operation, grab passed => transfer/transferring the image
if (transferringGrabbedImage)
{
/// Grab passed and image transfer is in progress
return Event.GrabPassed;
}
else if (camera.IsSaveImageListenerThreadAlive)
{
/// Wait until the previous image transfer completes
return Event.CameraBusy;
}
else //if (0 == camera.DownloadFile(blackAndWhite ? "grabbed.jpg" : "grabbed.bmp", imgFileNames[0]))
{
camera.StartSaveImageToFileListener(grabedImage, imgFileNames[0]);
/// File transfer successfully started
transferringGrabbedImage = true;
return Event.GrabPassed;
}
}
else
{
///
/// Normal operation, grab failed => there in no image to transfer
///
log.Error(
$"Grab task was not created: cameraIdx={camera.CameraIdx}");
UnsubscribeEvents();
return Event.GrabFailed;
}
if (!grabImageTask.IsCompleted)
{
return Event.CameraBusy;
}
if (grabImageTask.IsCanceled)
{
log.Warn(
$"Grab task cancelled: cameraIdx={camera.CameraIdx}");
UnsubscribeEvents();
return Event.GrabFailed;
}
if (grabImageTask.IsFaulted)
{
log.Error(
$"Grab task failed: cameraIdx={camera.CameraIdx}",
grabImageTask.Exception);
UnsubscribeEvents();
return Event.GrabFailed;
}
if (grabedImage == null)
{
grabedImage = grabImageTask.GetAwaiter().GetResult();
}
if (grabedImage == null)
{
log.Error(
$"Grab completed without valid image: cameraIdx={camera.CameraIdx}");
UnsubscribeEvents();
return Event.GrabFailed;
}
if (!transferringGrabbedImage)
{
saveImageTask =
camera.StartSaveImageToFileAsync(
grabedImage,
imgFileNames[0]);
transferringGrabbedImage = true;
return Event.CameraBusy;
}
if (saveImageTask == null)
{
log.Error(
$"Save image task was not created: " +
$"cameraIdx={camera.CameraIdx}");
UnsubscribeEvents();
return Event.GrabFailed;
}
if (!saveImageTask.IsCompleted)
{
return Event.CameraBusy;
}
if (saveImageTask.IsCanceled)
{
log.Warn(
$"Save image task cancelled: " +
$"cameraIdx={camera.CameraIdx}");
UnsubscribeEvents();
return Event.GrabFailed;
}
if (saveImageTask.IsFaulted)
{
log.Error(
$"Save image task failed: " +
$"cameraIdx={camera.CameraIdx}",
saveImageTask.Exception);
UnsubscribeEvents();
return Event.GrabFailed;
}
bool fileSaved = File.Exists(imgFileNames[0]);
UnsubscribeEvents();
return fileSaved
? Event.GrabPassed
: Event.GrabFailed;
}
public void Stop()
{
//if (camera.CameraCfg.DebugLevel == DebugMode.Simulate) return;
if (camera.IsGrabImageListenerThreadAlive)
{
camera.SetStopGrabImageListenerFlag();
}
// if (camera.IsGrabImageListenerThreadAlive)
// {
// camera.SetStopGrabImageListenerFlag();
// }
camera.CancelGrabImage();
UnsubscribeEvents();
}
private void UnsubscribeEvents()
{
Camera.ImageCameraHandler -= ImageReceived;
Camera.ImagesCameraHandler -= ImagesReceived;
}
}
}

View File

@ -1,3 +1,4 @@
using System;
using TBF.Rig.Network.Camera.CJMS11.POJO;
namespace TBF.Rig.Network.Camera.CJMS11
@ -11,6 +12,7 @@ namespace TBF.Rig.Network.Camera.CJMS11
private string orig_status;
private string orig_command;
private string orig_payload;
private DateTime timeStamp;
public POJO.MessageStatus Status
{
@ -35,6 +37,12 @@ namespace TBF.Rig.Network.Camera.CJMS11
public string OrigCommand => orig_command;
public string OrigPayload => orig_payload;
public DateTime TimeStamp
{
get => timeStamp;
set => timeStamp = value;
}
public JmsMessage(string status, string command, string payload)
{

View File

@ -1,6 +1,7 @@
using System;
using System.Text.RegularExpressions;
using log4net;
using Newtonsoft.Json;
using TBF.Rig.Network.Camera.CJMS11.POJO;
namespace TBF.Rig.Network.Camera.CJMS11
@ -44,6 +45,16 @@ namespace TBF.Rig.Network.Camera.CJMS11
if (message.Length < 1) return false;
this.jmsMessage = JmsMessageFromString(message);
try
{
CameraResponse response =
JsonConvert.DeserializeObject<CameraResponse>(jmsMessage.Payload);
jmsMessage.TimeStamp = response.timeStamp;
}catch(Exception e)
{
log.Debug("Error parsing message timestamp: " + e.Message);
}
return true;
}
@ -70,23 +81,39 @@ namespace TBF.Rig.Network.Camera.CJMS11
return null;
}
public static ParsedImage ImageParser(string payloadImage)
public static ParsedImage ImageParser(
string payloadImage)
{
// Equivalent regex pattern
var imagePattern = new Regex(@"IMAGE:([A-Z0-9_]+):\s*(.*?)\s*~IMAGE_END", RegexOptions.Singleline);
const string prefix = "IMAGE:";
const string endMarker = "~IMAGE_END";
var match = imagePattern.Match(payloadImage);
if (match.Success)
{
string encoding = match.Groups[1].Value;
string imageData = match.Groups[2].Value;
int prefixIndex = payloadImage.IndexOf( prefix, StringComparison.Ordinal);
if (prefixIndex < 0)
throw new ArgumentException( "IMAGE prefix was not found.");
return new ParsedImage(ImageTypeEnum.GetCommandM(encoding), imageData);
}
else
{
throw new ArgumentException($"Payload does not match expected image format: {payloadImage}");
}
int encodingStart =
prefixIndex + prefix.Length;
int encodingEnd =
payloadImage.IndexOf(
':',
encodingStart);
if (encodingEnd < 0)
throw new ArgumentException(
"Image encoding separator was not found.");
int dataStart = encodingEnd + 1;
int dataEnd = payloadImage.IndexOf( endMarker, dataStart, StringComparison.Ordinal);
if (dataEnd < 0)
throw new ArgumentException( "IMAGE_END marker was not found.");
string encoding = payloadImage .Substring( encodingStart, encodingEnd - encodingStart) .Trim();
string imageData = payloadImage.Substring( dataStart,dataEnd - dataStart).Trim();
return new ParsedImage( ImageTypeEnum.GetCommandM(encoding), imageData);
}
public override string ToString()

View File

@ -0,0 +1,12 @@
using System;
namespace TBF.Rig.Network.Camera.CJMS11.POJO
{
public class CameraResponse
{
public string message { get; set; }
public DateTime timeStamp { get; set; }
}
}

View File

@ -1016,11 +1016,13 @@
<Compile Include="Rig\Network\Camera\CJMS11\CameraCfgCtrl.designer.cs">
<DependentUpon>CameraCfgCtrl.cs</DependentUpon>
</Compile>
<Compile Include="Rig\Network\Camera\CJMS11\CameraLineReader.cs" />
<Compile Include="Rig\Network\Camera\CJMS11\Factory.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\GrabImagesOp.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\JmsMessage.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\JmsPacket.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\LiveStreamOp.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\POJO\CameraResponse.cs" />
<Compile Include="Rig\Network\Camera\CJMS11\POJO\CommandM.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\POJO\CommandMEnum.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\POJO\ImageType.cs"/>

View File

@ -3,6 +3,7 @@
///
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using log4net;
using NHibernate;
using Common;
@ -229,7 +230,26 @@ namespace TBF.UiBridge
public static void OnCameraUICmd(object sender, CameraUICmdEventArgs data)
{
if (CameraUICmdHandler == null) return;
try { CameraUICmdHandler(sender, data); }
try
{
log.DebugFormat("CameraUICmdHandler: {0}", data.CameraUICmd);
foreach (EventHandler<CameraUICmdEventArgs> handler in CameraUICmdHandler.GetInvocationList())
{
var localHandler = handler;
Task.Run(() =>
{
try
{
localHandler(sender, data);
}
catch (Exception e)
{
log.Error("CameraUICmdHandler(...) failed", e);
}
});
}
}
catch (Exception e) { log.Error("CameraUICmdHandler(...) failed", e); }
}
public static event EventHandler<CameraUICmdEventArgs> CameraUICmdHandler;

View File

@ -131,7 +131,7 @@ namespace TBFTests.Rig.Network.Camera.CJMS11
var camera = GetCamera(IPCamera1);
Task<Image> saveImageTask = Task.Run(() => camera.SaveImageListener());
Task<Image> saveImageTask = Task.Run(() => camera.SaveImageListenerAsynch());
var timeoutAnswerTask = Task.Delay(TimeSpan.FromSeconds(3));
var completedAnswerTask = Task.WhenAny(saveImageTask, timeoutAnswerTask).Result;
if (completedAnswerTask == timeoutAnswerTask)
@ -152,7 +152,7 @@ namespace TBFTests.Rig.Network.Camera.CJMS11
// {
// var camera = GetCamera(IPCamera2);
//
// Task<Image> saveImageTask = Task.Run(() => camera.SaveImageListener());
// Task<Image> saveImageTask = Task.Run(() => camera.SaveImageListenerAsynch());
// var timeoutAnswerTask = Task.Delay(TimeSpan.FromSeconds(3));
// var completedAnswerTask = Task.WhenAny(saveImageTask, timeoutAnswerTask).Result;
// if (completedAnswerTask == timeoutAnswerTask)