From 50a38bc557d8a255c481f41cdeeec4764354cc0f Mon Sep 17 00:00:00 2001 From: Michal Buzik Date: Wed, 30 Jul 2025 10:34:56 +0200 Subject: [PATCH] "Refactor CJMS11 image grab functionality with timeout handling and error resilience - Introduced a new `GrabImageListener` method with asynchronous timeout support for improved response handling. - Enhanced error logging and implemented retry logic for capturing images to address NACK scenarios. - Added overlay rendering and event-based image delivery for better UI integration. - Refined `SendImageListener` logic to include command handling improvements and listener termination checks. - Minor parameter adjustments in `OnImageReceived` for default values support." --- TBF/Rig/Network/Camera/CJMS11/Camera.cs | 138 +++++++++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/TBF/Rig/Network/Camera/CJMS11/Camera.cs b/TBF/Rig/Network/Camera/CJMS11/Camera.cs index 493a336b0..88971451d 100644 --- a/TBF/Rig/Network/Camera/CJMS11/Camera.cs +++ b/TBF/Rig/Network/Camera/CJMS11/Camera.cs @@ -1088,6 +1088,139 @@ namespace TBF.Rig.Network.Camera.CJMS11 // Get network stream + using (cameraStream = this.cameraTcpClient.GetStream()) + using (cameraWriter = new StreamWriter(cameraStream, Encoding.ASCII) { AutoFlush = true }) + using (cameraReader = new StreamReader(cameraStream, Encoding.ASCII)) + { + cameraStream.ReadTimeout = 1000; + + try + { + SendCommand(CommandM.prepare_camera_); + } + catch (Exception exc) + { + log.Error($"Initialisation of connection readers failed: {exc.Message}"); + return; + } + + stopGrabImageListenerFlag = false; + int iCountOf_NACK = 0; + int iActualImageIndex = 0; + + SendCommand(CommandM.grab_image_); + + + while (!stopGrabImageListenerFlag) + { + try + { + + Task responseAnswerTask = cameraReader.ReadLineAsync(); + var timeoutAnswerTask = Task.Delay(TimeSpan.FromSeconds(1)); + var completedAnswerTask = Task.WhenAny(responseAnswerTask, timeoutAnswerTask).Result; + + if (completedAnswerTask == timeoutAnswerTask) + { + throw new TimeoutException("Answer response attempt timed out after 1 second."); + } + + string responseAnswer = responseAnswerTask.Result; + if (responseAnswer.Length > 0) + { + /// Process the packet + JmsPacket newPacket = new JmsPacket(responseAnswer); + + if (newPacket.JmsMessage.Status != MessageStatus.ACK) + { + if (iCountOf_NACK < 5) + { + iCountOf_NACK++; + continue; + } + + log.ErrorFormat("Error occured more as 5 time in GrabImageListener: {0}", + newPacket.JmsMessage.Status); + stopGrabImageListenerFlag = true; // mam obrazok ukoncim a zavriem thread + break; + } + else + { + + switch (newPacket.JmsMessage.Command) + { + case CommandM.grab_image_: + ParsedImage parsedImage = newPacket.JmsMessage.getImage(); + if (parsedImage.Encoding == ImageType.BASE_64) + { + Image image = parsedImage.Image; + + + if (showOverlayRect && overlayRect != null && overlayThickness > 0) + { + int penWidth = 1; + Pen pen = new Pen(Color.Green, penWidth); + using (var grph = Graphics.FromImage(image)) + { + if (penWidth != overlayThickness) + { + penWidth = overlayThickness; + pen = new Pen(Color.Green, penWidth); + } + + grph.DrawRectangle(pen, overlayRect); + } + } + + OnImageReceived(this, this.cameraIdx, image, RoiConfiguration.ImageRotation); + iActualImageIndex++; + if (iActualImageIndex >= RoiConfiguration.ImageOpacity.Count) + { + stopGrabImageListenerFlag = + true; // mam obrazok ukoncim a zavriem thread + } + } + + break; + } + } + } + } + catch (Exception exc) + { + log.Error($"Grab Image - Hardware Addr: {CameraCfg.HardwareAddress} Camera IP: {CameraCfg.IPAddressCJMS}, Port: {iPort} Exception in MJpeg RTP listener thread: { exc.Message}"); + } + } + + stopGrabImageListenerFlag = false; + SendCommandAndWaitToAnswer(cameraWriter, cameraReader, CommandM.close_); + SendCommandAndWaitToAnswer(cameraWriter, cameraReader, CommandM.disconnect_); + + CloseCameraResources(); + + return; + } + }catch(Exception exc) + { + log.Error($"Grab Image - Hardware Addr: {CameraCfg.HardwareAddress} Camera IP: {CameraCfg.IPAddressCJMS}, Port: {iPort} exception: {exc.Message}"); + UiBridge.Bridge.OnImage(this.cameraIdx, null); + } + } + } + + void SendImageListener() + { + using (this.cameraTcpClient = new TcpClient()) + { + try + { + EstablishCameraConnection(ref this.cameraTcpClient, CameraCfg.IPAddressCJMS, iPort); + + log.Info(string.Format("Camera connected to server {0}:{1}.", CameraCfg.IPAddressCJMS, iPort)); + ipAddress = IPAddress.Parse(CameraCfg.IPAddressCJMS); + // Get network stream + + using (cameraStream = this.cameraTcpClient.GetStream()) using (cameraWriter = new StreamWriter(cameraStream, Encoding.ASCII) { AutoFlush = true }) using (cameraReader = new StreamReader(cameraStream, Encoding.ASCII)) @@ -1109,7 +1242,7 @@ namespace TBF.Rig.Network.Camera.CJMS11 int iActualImageIndex = 0; //TODO BUMI send grab image or send - based on count of images - but must work similary - if (RoiConfiguration.ImageOpacity.Count > 1) + if (RoiConfiguration.ImageOpacity.Count <= 1) { SendCommand(CommandM.grab_image_); } @@ -1156,6 +1289,7 @@ namespace TBF.Rig.Network.Camera.CJMS11 switch (newPacket.JmsMessage.Command) { + case CommandM.send_: case CommandM.grab_image_: ParsedImage parsedImage = newPacket.JmsMessage.getImage(); if (parsedImage.Encoding == ImageType.BASE_64) @@ -1579,7 +1713,7 @@ namespace TBF.Rig.Network.Camera.CJMS11 /// This method is called from within this class when /// a prompt string was received from the telnet server. /// - public static void OnImageReceived(object sender, int idxImage, Image image, ImageRotation imageRotation, int iImageIdx, int imageCount) + public static void OnImageReceived(object sender, int idxImage, Image image, ImageRotation imageRotation, int iImageIdx = 0, int imageCount = 1) { log.Info("Image received");