tbf/TBF/Rig/Network/Camera/CJMS11/Camera.cs

3604 lines
89 KiB
C#

///
/// Copyright (c) 2017-2022 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using log4net;
using Renci.SshNet;
using Common;
using Config.Entities;
using FluentNHibernate.Conventions;
using Newtonsoft.Json;
using TBF.Rig.Network.Telnet;
using TBF.Resources;
using TBF.Rig.Generic;
using TBF.Rig.Network.Camera.CJMS11.POJO;
using TBF.Rig.Network.Camera.common;
using TBF.Rig.Network.Camera.RoiForFixedStartCJMS11;
using TBF.UiBridge;
namespace TBF.Rig.Network.Camera.CJMS11
{
/// <summary>
/// This class implements: (1) Camera device, (2) Measurement operation
/// </summary>
public class Camera : ComponentBase, GenericDevices.ICamera, Generic.IDevice, IOperation
{
private static readonly ILog log = LogManager.GetLogger(typeof(Camera));
public override string ToString()
{
return string.Format("{0}: Name={1}, Addr={2}, IP={3}, s/n={4}, SD={5}",
this.GetType().Namespace.Substring(8),
CameraCfg.Name,
CameraCfg.HardwareAddress,
CameraCfg.DebugLevel == DebugMode.Simulate ? "simulated" : ((ipAddress == null) ? "not detected" : ipAddress.ToString()),
CameraCfg.DebugLevel == DebugMode.Simulate ? "simulated" : (string.IsNullOrEmpty(cpuSerialNr) ? "not detected" : cpuSerialNr),
CameraCfg.DebugLevel == DebugMode.Simulate ? "simulated" : (string.IsNullOrEmpty(sdCardVer) ? "not detected" : sdCardVer));
}
private const int iPort = 32456;
string ClpUserName = string.Empty; /// Either "tbf" or "pi" after a successful detection
const string ClpPassword = "C1ern4V0d4";
private readonly SemaphoreSlim cameraReadLock = new SemaphoreSlim(1, 1);
//private readonly SemaphoreSlim cameraWriteLock = new SemaphoreSlim(1, 1);
///
/// Properties
///
public readonly CameraCfg CameraCfg;
public readonly AdapterJMS.Netadapter NetAdapter;
private RoiCfg roiConfiguration;
public RoiCfg RoiConfiguration
{
get {
if (roiConfiguration!=null) {return roiConfiguration;}
else { roiConfiguration = GetFirstRoiCfgChild(); return roiConfiguration; }
}
set { if (roiConfiguration == null) roiConfiguration = value;}
}
private RoiForFixedStartCJMS11.Roi _roi;
public RoiForFixedStartCJMS11.Roi CamRoi
{
get {
if (_roi!=null) {return _roi;}
else { _roi = GetFirstRoiChild(); return _roi; }
}
set { if (_roi == null) _roi = value;}
}
public IPAddress IPAddress { get { return ipAddress; } }
IPAddress ipAddress;
public string CpuHardware { get { return cpuHardware; } }
string cpuHardware;
public string CpuRevision { get { return cpuRevision; } }
string cpuRevision;
public string CpuSerialNr { get { return cpuSerialNr; } }
string cpuSerialNr;
public string SDCardVer { get { return sdCardVer; } }
string sdCardVer;
public bool Running { get { return running; } }
bool running;
public bool ShowOverlayRect { set { showOverlayRect = value; } }
bool showOverlayRect = false;
public Rectangle OverlayRect { set { overlayRect = value; } }
Rectangle overlayRect;
public int OverlayThickness { set { overlayThickness = value; } }
int overlayThickness;
public int CameraIdx { get { return cameraIdx; } }
int cameraIdx;
///
static int nextCameraIdx = 0;
static int GetCameraIdx() { return nextCameraIdx++; } /// Used in Initialize()
///
/// Telnet support
///
const string TelnetPrompt = "$ ";
const bool TelnetSkipFirstLine = true;
private TerminalDlg terminalDlg;
string sha1sumResponse;
JmsMessage jmsMessage;
bool sha1sumResponseProcessed;
///
/// scp support
///
PasswordConnectionInfo connectionInfo;
///
/// MJpeg RTP protocol support
///
public bool CameraConnected{ get {return cameraTcpClient != null && cameraTcpClient.Connected;} }
int cameraTcpLocalPort;
TcpClient cameraTcpClient;
private NetworkStream cameraStream = null;
//private StreamWriter cameraWriter = null;
private StreamReader cameraReader = null;
private Image lastImage;
private bool lockCameraInitialisation = false;
Thread liveStreamListenerThread;
private bool stopLiveStreamListenerFlag;
public bool IsGrabImageListenerThreadAlive { get { return grabImageListenerThread != null && grabImageListenerThread.IsAlive; } }
Thread grabImageListenerThread;
private bool stopGrabImageListenerFlag;
public void SetStopGrabImageListenerFlag() { stopGrabImageListenerFlag = true; }
Thread stopUIListenerThread;
private bool stopUIListenerFlag;
Thread saveImageListenerThread;
private bool stopSaveImageListenerFlag;
public bool IsSaveImageListenerThreadAlive { get { return saveImageListenerThread != null && saveImageListenerThread.IsAlive; } }
/// <summary>
/// Send images part
/// </summary>
private Task<IReadOnlyList<TimedCameraImage>> sendImagesTask;
private CancellationTokenSource sendImagesCts;
public bool IsSendImagesInProgress => sendImagesTask != null && !sendImagesTask.IsCompleted;
///
/// ROI related parameters
///
RoisAndResults roisAndResults;
///
public void ClearRoiParams()
{
roisAndResults.ClearRoiParams();
}
public int RegisterRoi(string roiParams)
{
return roisAndResults.RegisterRoi(roiParams);
}
public int GetResult(int roiHandle, out long timeMs)
{
return roisAndResults.GetResult(roiHandle, out timeMs);
}
public Camera() { }
public Camera(Generic.IComponentCfg cfg, IList<Generic.IComponent> components)
: base(cfg)
{
CameraCfg = cfg as CameraCfg;
// NetAdapter = TbfComponents.FindComponent(cfg.ParentName, components) as TBF.Rig.Network.AdapterJMS.Netadapter;
//if (NetAdapter == null) throw new ArgumentNullException("no network adapter");
}
public override void Initialize()
{
cameraIdx = GetCameraIdx();
running = false;
terminalDlg = null;
roisAndResults = new RoisAndResults(); /// object for measurement results, writes/reads to/from this object are locked
// if (CameraCfg.DebugLevel == DebugMode.Simulate)
// {
// UiBridge.Bridge.OnCameraInfo(cameraIdx, string.Format("{0} s/n {1} si simulated", Name, CameraCfg.HardwareAddress));
// return;
// }
/// Detect a camera
CameraDetectionResult cameraDetectionResult = null;
try
{
cameraDetectionResult = Task
.Run(() => DetectJMSCamera())
.GetAwaiter()
.GetResult();
}
catch (Exception exc)
{
log.Error($"Camera detection failed: {exc.Message}");
cameraDetectionResult = new CameraDetectionResult
{
IsDetected = false
};
}
//test
// var detectJmsCamera1 = DetectJMSCamera("192.168.0.170",32456);
// var detectJmsCamera2 = DetectJMSCamera("192.168.0.168",32456);
// Task.WaitAll(detectJmsCamera, detectJmsCamera1, detectJmsCamera2);
if (CameraCfg.DebugLevel == DebugMode.AutoDetect)
{
CameraCfg.DebugLevel = cameraDetectionResult.IsDetected ? DebugMode.DetectedOn : DebugMode.DetectedOff;
}
else if (!cameraDetectionResult.IsDetected)
{
throw new Exception(string.Format("{0}={1}", Strings.Address, CameraCfg.HardwareAddress));
}
if (cameraDetectionResult.IsDetected)
{
ipAddress = cameraDetectionResult.IpAddress;
UiBridge.Bridge.OnCameraInfo(
cameraIdx,
string.Format(
"{0} ver.{1} {2} s/n {3}",
Name,
sdCardVer,
ipAddress,
CameraCfg.HardwareAddress));
// start live stream, grab image, ... listener thread
UiBridge.Bridge.CameraUICmdHandler -= HandleCameraUiCmd;
UiBridge.Bridge.CameraUICmdHandler += HandleCameraUiCmd;
//PromptCameraReceivedHandler -= OnPromptReceivedCamera;
//PromptCameraReceivedHandler += OnPromptReceivedCamera;
ImageCameraHandler -= OnPromptImageReceived;
ImageCameraHandler += OnPromptImageReceived;
ImagesCameraHandler -= OnPromptImagesReceived;
ImagesCameraHandler += OnPromptImagesReceived;
}
else
{
UiBridge.Bridge.OnCameraInfo(this.cameraIdx, string.Format("{0} s/n {1} was not detected", Name, CameraCfg.HardwareAddress));
log.FatalFormat("'{0}' NOT detected: Id={1} addr={2}", Name, CameraIdx, CameraCfg.HardwareAddress);
}
}
private void OnPromptImageReceived(object sender, PromptReceivedImageEventArgs e)
{
if (e == null || e.image == null || cameraIdx != e.idxCamera)
{
return;
}
/*
* Bridge dostane svoju vlastnú kópiu.
*/
Image bridgeImage = CloneImageToBitmap(e.image);
UiBridge.Bridge.OnImage( e.idxCamera, bridgeImage);
log.Debug( $"Image received from camera: {e.idxCamera}, pushed to bridge UI!");
}
private void OnPromptImagesReceived(object sender, PromptReceivedImagesEventArgs e)
{
if (e != null && e.image != null)
{
if (this.cameraIdx == e.idxCamera)
{
UiBridge.Bridge.OnImage(e.idxCamera, e.image);
log.Debug($"Image received from camera: {e.idxCamera}, pushed to bridge UI!");
}
}
}
public void StopDevice()
{
if ((CameraCfg.DebugLevel != DebugMode.Simulate) && (CameraCfg.DebugLevel != DebugMode.DetectedOff))
{
running = false;
stopLiveStreamListenerFlag = true;
stopGrabImageListenerFlag = true;
}
}
public void StopDevice2()
{
if ((CameraCfg.DebugLevel != DebugMode.Simulate) && (CameraCfg.DebugLevel != DebugMode.DetectedOff))
{
if (liveStreamListenerThread != null) liveStreamListenerThread.Join(50);
if (grabImageListenerThread != null) grabImageListenerThread.Join(50);
if (cameraTcpClient != null)
{
cameraTcpClient.Close();
cameraTcpClient = null;
}
}
}
public void RunDeviceBefore()
{
if ((CameraCfg.DebugLevel != DebugMode.Simulate) && (CameraCfg.DebugLevel != DebugMode.DetectedOff))
{
if (sha1sumResponse != null && !sha1sumResponseProcessed)
{
sha1sumResponseProcessed = true;
}
}
}
public void RunDeviceAfter() { }
void OnPromptReceived(object sndr, PromptReceivedJMSEventArgs a)
{
if (sha1sumResponse == null)
{
sha1sumResponse = a.Response;
jmsMessage = a.Message;
}
}
void OnReceivedImageX(object sndr, PromptReceivedImageEventArgs a)
{
if (a != null)
{
lastImage = a.image;
}
}
void OnPromptReceivedCamera(object sndr, PromptReceivedJMSEventArgs a)
{
try
{
JmsPacket packet = new JmsPacket(a.Response);
jmsMessage = packet.JmsMessage;
}catch(Exception exc)
{
log.Error(exc.Message);
}
}
void OnReceivedPackage(object sndr, PromptReceivedEventArgs a)
{
if (sha1sumResponse == null)
{
sha1sumResponse = a.Response;
try
{
JmsPacket packet = new JmsPacket(a.Response);
jmsMessage = packet.JmsMessage;
}catch(Exception exc)
{
log.Error(exc.Message);
}
}
}
#region Configuration Change Handling
public static void OnCfgChange(object sender, CfgChangeArgs args)
{
if (CfgChangeHandler == null) return;
try { CfgChangeHandler(sender, args); }
catch (Exception e) { log.Error("CfgChangeHandler(...) failed", e); }
}
public static event EventHandler<CfgChangeArgs> CfgChangeHandler;
public override void StartChangeHandler()
{
CfgChangeHandler += delegate(object sender, CfgChangeArgs args)
{
CameraCfg tmpcfg = args.Cfg as CameraCfg;
if (tmpcfg != null && tmpcfg.Name.Equals(Name))
{
if (args.Command == CfgChangeCmd.CfgChange)
{
CameraCfg.TestImagesMode = tmpcfg.TestImagesMode;
CameraCfg.TestImagesCount = tmpcfg.TestImagesCount;
CameraCfg.IPAddressCJMS = tmpcfg.IPAddressCJMS;
}
}
};
}
#endregion Configuration Change Handling
public class CameraDetectionResult
{
public bool IsDetected { get; set; }
public IPAddress IpAddress { get; set; }
public string Hardware { get; set; }
public string Revision { get; set; }
public string Serial { get; set; }
public string SdCardVersion { get; set; }
public string Response { get; set; }
public double Duration_ms { get; set; }
}
async Task<CameraDetectionResult> DetectJMSCamera()
{
return await DetectJMSCamera(CameraCfg.IPAddressCJMS, iPort);
}
async Task<CameraDetectionResult> DetectJMSCamera( string ipAddressParam, int portParam)
{
var result = new CameraDetectionResult
{
IsDetected = false,
IpAddress = null,
Hardware = null,
Revision = null,
Serial = null,
SdCardVersion = null
};
if (CameraCfg.DebugLevel == DebugMode.Simulate)
{
result.IsDetected = true;
return result;
}
if (string.IsNullOrWhiteSpace(ipAddressParam))
{
log.Error( $"Camera {CameraCfg.Name}: IP address is missing.");
return result;
}
var detectionStopwatch = Stopwatch.StartNew();
try
{
using (var client = new TcpClient())
{
Task connectTask =
client.ConnectAsync(
ipAddressParam,
portParam);
Task timeoutTask =
Task.Delay(TimeSpan.FromSeconds(1));
Task completedTask =
await Task.WhenAny(
connectTask,
timeoutTask)
.ConfigureAwait(false);
if (completedTask == timeoutTask)
{
client.Close();
throw new TimeoutException( $"Camera connection timeout after 1 second: {ipAddressParam}:{portParam}");
}
// Vyhodí SocketException, ak ConnectAsync zlyhal.
await connectTask.ConfigureAwait(false);
if (!client.Connected)
{
throw new IOException( $"Camera connection was not established: {ipAddressParam}:{portParam}");
}
log.Debug( $"Camera connected: name={CameraCfg.Name}, IP={ipAddressParam}, port={portParam}");
using (NetworkStream stream = client.GetStream())
{
// Jeden reader pre celé spojenie.
var reader = new CameraLineReader( stream, 64 * 1024);
this.ipAddress = IPAddress.Parse(ipAddressParam);
/*
* 1. Nastavenie typu kamery
*/
string configMessage = getConfigMessage();
JmsPacket typeCameraAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.type_camera_,
configMessage,
5000,
CancellationToken.None)
.ConfigureAwait(false);
if (typeCameraAnswer == null || typeCameraAnswer.JmsMessage.Status != MessageStatus.ACK)
{
log.Warn( $"type_camera failed: " + $"camera={CameraCfg.Name}, " + $"IP={ipAddressParam}, " + $"answer={FormatPacketForLog(typeCameraAnswer)}");
}
/*
* 2. Prepare camera
*/
JmsPacket prepareAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.prepare_camera_,
null,
3000,
CancellationToken.None)
.ConfigureAwait(false);
bool alreadyOpened =
prepareAnswer != null &&
prepareAnswer.JmsMessage.Status ==
MessageStatus.NACK &&
!string.IsNullOrEmpty(
prepareAnswer.JmsMessage.Payload) &&
prepareAnswer.JmsMessage.Payload.Contains(
"OPENED");
if (alreadyOpened)
{
log.Debug( $"Camera already opened: " + $"name={CameraCfg.Name}, " + $"IP={ipAddressParam}");
}
else if (prepareAnswer == null || prepareAnswer.JmsMessage.Status != MessageStatus.ACK)
{
log.Warn( $"Prepare camera failed: " + $"camera={CameraCfg.Name}, " + $"IP={ipAddressParam}, " + $"answer={FormatPacketForLog(prepareAnswer)}");
/*
* Pokus o reset:
* close -> prepare
*/
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.close_,
null,
2000,
CancellationToken.None)
.ConfigureAwait(false);
prepareAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.prepare_camera_,
null,
3000,
CancellationToken.None)
.ConfigureAwait(false);
bool restartSucceeded =
prepareAnswer != null &&
(
prepareAnswer.JmsMessage.Status ==
MessageStatus.ACK ||
(
prepareAnswer.JmsMessage.Status ==
MessageStatus.NACK &&
!string.IsNullOrEmpty(
prepareAnswer.JmsMessage.Payload) &&
prepareAnswer.JmsMessage.Payload.Contains(
"OPENED")
)
);
if (!restartSucceeded)
{
log.Error(
$"Camera prepare restart failed: " +
$"camera={CameraCfg.Name}, " +
$"IP={ipAddressParam}, " +
$"answer={FormatPacketForLog(prepareAnswer)}");
}
}
/*
* 3.1 Warm-up grab after type_camera + prepare_camera.
*
* Hardvér po inicializácii potrebuje vytvoriť aspoň jeden obrázok.
* Obrázok pri detekcii nepotrebujeme ukladať ani posielať do UI.
*/
JmsPacket grabAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.grab_image_,
null,
10000,
CancellationToken.None)
.ConfigureAwait(false);
if (grabAnswer == null || grabAnswer.JmsMessage == null || grabAnswer.JmsMessage.Status != MessageStatus.ACK)
{
throw new IOException( $"Camera initialization grab failed: camera={CameraCfg.Name}, " +
$"IP={ipAddressParam}, answer={FormatPacketForLog(grabAnswer)}");
}
try
{
ParsedImage initializationImage =
grabAnswer.JmsMessage.getImage();
if (initializationImage == null)
{
throw new IOException( "Initialization grab returned no parsed image.");
}
if (initializationImage.Encoding != ImageType.BASE_64)
{
throw new IOException( $"Initialization grab returned unsupported encoding: " + $"{initializationImage.Encoding}");
}
// Vyvolá Base64 dekódovanie a tým overí, že obrázok je platný.
using (Image warmUpImage = initializationImage.Image)
{
if (warmUpImage == null)
{
throw new IOException(
"Initialization grab returned a null image.");
}
log.Debug(
$"Initialization image captured: " +
$"camera={CameraCfg.Name}, " +
$"IP={ipAddressParam}, " +
$"size={warmUpImage.Width}x{warmUpImage.Height}");
}
}
catch (Exception exc)
{
throw new IOException( $"Cannot process initialization image: camera={CameraCfg.Name}, " +
$"IP={ipAddressParam}, error={exc.Message}", exc);
}
/*
* 3.2 Status
*/
JmsPacket statusAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.status_,
null,
3000,
CancellationToken.None)
.ConfigureAwait(false);
if (statusAnswer == null ||
statusAnswer.JmsMessage.Status !=
MessageStatus.ACK)
{
log.Warn(
$"Camera status failed: " +
$"camera={CameraCfg.Name}, " +
$"IP={ipAddressParam}, " +
$"answer={FormatPacketForLog(statusAnswer)}");
}
/*
* 4. Get configuration
*/
JmsPacket configAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.get_cfg_,
null,
3000,
CancellationToken.None)
.ConfigureAwait(false);
if (configAnswer == null ||
configAnswer.JmsMessage.Status !=
MessageStatus.ACK)
{
throw new IOException(
$"Camera get_cfg failed: " +
$"camera={CameraCfg.Name}, " +
$"IP={ipAddressParam}, " +
$"answer={FormatPacketForLog(configAnswer)}");
}
result.IpAddress =
IPAddress.Parse(ipAddressParam);
result.Hardware = string.Empty;
result.Revision = string.Empty;
result.Serial = string.Empty;
result.SdCardVersion = string.Empty;
result.Response = configAnswer.ToString();
result.IsDetected = true;
string origin =
string.IsNullOrEmpty(configAnswer.MessageOrigin)
? "unknown origin"
: configAnswer.MessageOrigin;
detectionStopwatch.Stop();
result.Duration_ms =
detectionStopwatch.Elapsed.TotalMilliseconds;
log.Debug(
$"Camera detected: " +
$"name={CameraCfg.Name}, " +
$"duration={result.Duration_ms:F0} ms, " +
$"HWAddress={CameraCfg.HardwareAddress}, " +
$"IP={ipAddressParam}, " +
$"origin={origin}");
/*
* Voliteľný cleanup.
*
* Ak má kamera po detekcii zostať otvorená,
* túto časť odstráň.
*/
try
{
JmsPacket closeAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.close_,
null,
2000,
CancellationToken.None)
.ConfigureAwait(false);
bool closeAccepted =
closeAnswer != null &&
(
closeAnswer.JmsMessage.Status == MessageStatus.ACK ||
(
closeAnswer.JmsMessage.Status == MessageStatus.NACK &&
!string.IsNullOrEmpty(closeAnswer.JmsMessage.Payload) &&
closeAnswer.JmsMessage.Payload.Contains(
"Close command already in progress")
)
);
if (!closeAccepted)
{
log.Warn(
$"Close after detection was not confirmed: " +
$"camera={CameraCfg.Name}, " +
$"answer={FormatPacketForLog(closeAnswer)}");
}
}
catch (Exception closeException)
{
log.Warn(
$"Camera close after detection failed: " +
$"{closeException.Message}");
}
/*
* Disconnect after detection.
*/
try
{
await SendCommandAsync(
stream,
CommandM.disconnect_,
CancellationToken.None,
null)
.ConfigureAwait(false);
log.Debug(
$"Disconnect command sent after detection: " +
$"camera={CameraCfg.Name}, IP={ipAddressParam}");
}
catch (Exception disconnectException)
{
log.Warn(
$"Camera disconnect after detection failed: " +
$"{disconnectException.Message}");
}
return result;
}
}
}
catch (TimeoutException exc)
{
detectionStopwatch.Stop();
result.Duration_ms =
detectionStopwatch.Elapsed.TotalMilliseconds;
log.Error(
$"Camera detection timeout: " +
$"name={CameraCfg.Name}, " +
$"HWAddress={CameraCfg.HardwareAddress}, " +
$"IP={ipAddressParam}, " +
$"port={portParam}, " +
$"duration={result.Duration_ms:F0} ms, " +
$"error={exc.Message}");
}
catch (SocketException exc)
{
detectionStopwatch.Stop();
result.Duration_ms =
detectionStopwatch.Elapsed.TotalMilliseconds;
log.Error(
$"Camera socket connection failed: " +
$"name={CameraCfg.Name}, " +
$"IP={ipAddressParam}, " +
$"port={portParam}, " +
$"socketError={exc.SocketErrorCode}, " +
$"error={exc.Message}");
}
catch (Exception exc)
{
detectionStopwatch.Stop();
result.Duration_ms =
detectionStopwatch.Elapsed.TotalMilliseconds;
log.Error(
$"Camera detection failed: " +
$"name={CameraCfg.Name}, " +
$"HWAddress={CameraCfg.HardwareAddress}, " +
$"IP={ipAddressParam}, " +
$"port={portParam}, " +
$"duration={result.Duration_ms:F0} ms, " +
$"error={exc.Message}",
exc);
}
result.IsDetected = false;
result.IpAddress = null;
result.Hardware = null;
result.Revision = null;
result.Serial = null;
result.SdCardVersion = null;
return result;
}
private static string FormatPacketForLog( JmsPacket packet)
{
if (packet == null)
return "<null>";
if (packet.JmsMessage == null)
return "<packet without message>";
string payload = packet.JmsMessage.Payload;
if (!string.IsNullOrEmpty(payload) && payload.Length > 200)
{
payload = payload.Substring(0, 200) + "...";
}
return $"command={packet.JmsMessage.Command}, " + $"status={packet.JmsMessage.Status}, " + $"payload={payload}";
}
private async Task ClearPendingResponsesAsync(StreamReader reader)
{
await cameraReadLock.WaitAsync();
try
{
while (reader.Peek() >= 0)
{
string oldLine = reader.ReadLine();
log.Debug("Discarded old camera response: " + oldLine);
}
}
catch (IOException)
{
}
catch (Exception exc)
{
log.Warn("ClearPendingResponses failed: " + exc.Message);
}
finally
{
cameraReadLock.Release();
}
}
class SensorConfig{
public string type { get; set; } = "all";
public int width { get; set; } = 640;
public int height { get; set; } = 480;
public int fps { get; set; } = 10;
public int exposure { get; set; } = 2000;
public float brightness { get; set; } = 0.5f;
public float contrast { get; set; } = 1.5f;
public int nBuffersOverload { get; set; } = 5;
public static string getJsonString(SensorConfig sensorConfig)
{
string returnStr = JsonConvert.SerializeObject(sensorConfig, Formatting.Indented);
returnStr = returnStr.Replace("\n", "");
returnStr = returnStr.Replace("\r", "");
return returnStr;
}
}
private RoiCfg GetFirstRoiCfgChild()
{
/// Load the list of components from the database
var session = TBF.DB.CreateSession(DBKind.Config);
var cmptnEntities = session.QueryOver<Config.Entities.Component>().OrderBy(x => x.ItemNr).Asc.List();
List<RoiCfg> cfgs = new List<RoiCfg>();
IList<IComponent> components = new List<Rig.Generic.IComponent>();
foreach (var component in cmptnEntities)
{
if (String.Compare(component.Parent, Name, StringComparison.Ordinal) == 0)
{
IComponentFactory cmpntFactory = Rig.TbfComponents.CmpntFactoryFromClassName(component.ClassName);
if (cmpntFactory == null) continue;
IList<IComponent> tbfComponents = Rig.TbfComponents.LoadComponentsFromDB(cmptnEntities);
IComponentCfg cmpntCfgFromCmpntEntity = cmpntFactory.CmpntCfgFromCmpntEntity(component);
IComponent cmpnt = cmpntFactory.GetComponent(cmpntCfgFromCmpntEntity, components);
RoiForFixedStartCJMS11.RoiCfg roi = cmpntCfgFromCmpntEntity as RoiForFixedStartCJMS11.RoiCfg;
if (roi != null)
{
cfgs.Add(roi);
}
}
}
/// get fist found configuration
return cfgs.First();
}
private RoiForFixedStartCJMS11.Roi GetFirstRoiChild()
{
using (var session = TBF.DB.CreateSession(DBKind.Config))
{
var componentEntities = session
.QueryOver<Config.Entities.Component>()
.OrderBy(x => x.ItemNr)
.Asc
.List();
IList<IComponent> components =
Rig.TbfComponents.LoadComponentsFromDB(componentEntities);
var roi = components
.OfType<RoiForFixedStartCJMS11.Roi>()
.FirstOrDefault(x =>
x.Cfg != null &&
string.Equals(
x.Cfg.ParentName,
Name,
StringComparison.Ordinal));
if (roi == null)
{
log.WarnFormat(
"No ROI child was found for camera '{0}'.",
Name);
return null;
}
var roiCfg = roi.Cfg as RoiForFixedStartCJMS11.RoiCfg;
if (roiCfg == null)
{
log.WarnFormat(
"ROI '{0}' has an invalid configuration type.",
roi.Name);
return roi;
}
LoadProcedureParams(session, roiCfg);
return roi;
}
}
private void LoadProcedureParams(
NHibernate.ISession session,
RoiForFixedStartCJMS11.RoiCfg roiCfg)
{
if (roiCfg == null)
{
return;
}
var currentProcedure = StateMachine.Procedure;
if (currentProcedure == null)
{
log.WarnFormat(
"Cannot load procedure parameters for ROI '{0}': " +
"current procedure is null.",
roiCfg.Name);
return;
}
var dbParams = session
.QueryOver<ComponentProcedure>()
.Where(x => x.CmpntName == roiCfg.Name)
.And(x => x.Procedure == currentProcedure)
.SingleOrDefault();
if (dbParams == null)
{
log.WarnFormat(
"No procedure parameters found for ROI '{0}', procedure '{1}'. " +
"Using default values.",
roiCfg.Name,
currentProcedure);
return;
}
if (roiCfg.ProcParams == null)
{
roiCfg.ProcParams = new ProcedureParams();
}
if (!roiCfg.ProcParams.UpdateFromDbEntity(dbParams))
{
log.WarnFormat(
"Failed to deserialize procedure parameters for ROI '{0}', " +
"procedure '{1}'.",
roiCfg.Name,
currentProcedure);
return;
}
log.DebugFormat(
"Loaded ROI procedure parameters: ROI='{0}', procedure='{1}', " +
"RoiPicCount={2}, RoiDeltaTimeMs={3}.",
roiCfg.Name,
currentProcedure,
roiCfg.ProcParams.RoiPicCount,
roiCfg.ProcParams.RoiDeltaTimeMs);
}
private string getConfigMessage()
{
//CameraCfg
SensorConfig sensorConfig = new SensorConfig();
sensorConfig.height = RoiConfiguration.ImageFrame.Height;
sensorConfig.width = RoiConfiguration.ImageFrame.Width;
return SensorConfig.getJsonString(sensorConfig);
}
void CameraInfoRequest(UdpClient udpClient, int hardwareAddress)
{
string strToSend = string.Format("getinfo {0}", hardwareAddress);
}
void SetDateTime(UdpClient udpClient, DateTime dateTime)
{
string strToSend = string.Format("setdatetime {0:yyMMddHHmmss}", dateTime);
}
private CameraUICmd lockBrich = CameraUICmd.None;
// Define the handler method
private void HandleCameraUiCmd(object sender, CameraUICmdEventArgs e)
{
if(lockBrich != CameraUICmd.None && e.CameraUICmd != CameraUICmd.Stop)
return;
if (!(e.CameraIdx == -1 || e.CameraIdx == cameraIdx))
{
return;
}
try
{
//lock
lockBrich = e.CameraUICmd;
// Example reaction to event
if (e.CameraUICmd == CameraUICmd.Live)
{
if (stopLiveStreamListenerFlag == false && liveStreamListenerThread != null &&
liveStreamListenerThread.IsAlive)
return;
log.Debug("Handle CameraUICmd received - Live");
stopLiveStreamListenerFlag = false;
stopUIListenerFlag = true;
StartLiveStreamListener();
//StartLiveStreamListener(IPAddress, iPort);
}
else if (e.CameraUICmd == CameraUICmd.Stop)
{
StopUiThreads();
stopUIListenerFlag = true;
log.Debug("Handle CameraUICmd received - Stop Live");
}
else if (e.CameraUICmd == CameraUICmd.Grab)
{
log.Debug($"Handle CameraUICmd received - GrabImage - camera idx: {cameraIdx}");
//stopUIListenerFlag = true;
StopUiThreads();
StartGrabImageListener();
}
else if (e.CameraUICmd == CameraUICmd.SaveImage)
{
log.Debug("Handle CameraUICmd received - SaveImage - will end Live Stream");
StopUiThreads();
StartSaveImageListener();
}
else
{
}
//Unlock - if is not in live - live will finish only by CameraUICmd.Stop
if (lockBrich != CameraUICmd.Live)
lockBrich = CameraUICmd.None;
}
catch (Exception exc)
{
lockBrich = CameraUICmd.None; //Unlock - solve hard lock
stopUIListenerFlag = true;
}
}
void SaveImageToFile(Image imageToSave)
{
if (imageToSave == null)
{
log.Error("Nothing to save in SaveImageToFile");
return; // nothing to save
}
using (SaveFileDialog dialog = new SaveFileDialog())
{
dialog.Title = "Save Image As";
dialog.Filter = "PNG Image|*.png|JPEG Image|*.jpg|Bitmap Image|*.bmp";
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
dialog.FileName = "my_image.png";
if (dialog.ShowDialog() == DialogResult.OK)
{
// Choose format based on extension
var ext = System.IO.Path.GetExtension(dialog.FileName).ToLower();
System.Drawing.Imaging.ImageFormat format = System.Drawing.Imaging.ImageFormat.Png;
if (ext == ".jpg") format = System.Drawing.Imaging.ImageFormat.Jpeg;
else if (ext == ".bmp") format = System.Drawing.Imaging.ImageFormat.Bmp;
try
{
SaveImageToFile(imageToSave, dialog.FileName);
MessageBox.Show("Image saved to: " + dialog.FileName);
}catch(Exception exc)
{
MessageBox.Show("Error saving image: " + exc.Message);
}
}
}
}
public void SaveImageToFile(
Image imageToSave,
string fileNamePath)
{
if (imageToSave == null)
{
throw new ArgumentNullException(nameof(imageToSave));
}
if (string.IsNullOrWhiteSpace(fileNamePath))
{
throw new ArgumentException(
"Path to save is missing.",
nameof(fileNamePath));
}
string directory =
Path.GetDirectoryName(fileNamePath);
if (!string.IsNullOrWhiteSpace(directory))
{
Directory.CreateDirectory(directory);
}
ImageFormat format =
GetImageFormatFromFileName(fileNamePath);
using (var clonedImage = CloneImageToBitmap(imageToSave))
{
clonedImage.Save(fileNamePath, format);
}
log.Debug("Image saved to: " + fileNamePath);
}
private static ImageFormat GetImageFormatFromFileName(
string fileName)
{
string extension =
Path.GetExtension(fileName)?.ToLowerInvariant();
switch (extension)
{
case ".bmp":
return ImageFormat.Bmp;
case ".jpg":
case ".jpeg":
return ImageFormat.Jpeg;
case ".png":
default:
return ImageFormat.Png;
}
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
return ImageCodecInfo.GetImageDecoders().FirstOrDefault(codec => codec.FormatID == format.Guid);
}
private Bitmap CloneImageToBitmap(Image originalImage)
{
return new Bitmap(originalImage);
}
private Image CloneImage(Image originalImage)
{
using (var ms = new MemoryStream())
{
originalImage.Save(ms, ImageFormat.Png); // Use a safe format for copying
ms.Position = 0;
return Image.FromStream(ms);
}
}
public void StartSaveImageToFileListener(Image imageToSave, string fileName)
{
saveImageListenerThread = new Thread(() => SaveImageToFile(imageToSave, fileName));
saveImageListenerThread.Start();
}
private Task saveImageTask;
public bool IsSaveImageInProgress =>
saveImageTask != null &&
!saveImageTask.IsCompleted;
public Task StartSaveImageToFileAsync( Image image, string fileName)
{
if (image == null)
throw new ArgumentNullException(nameof(image));
if (string.IsNullOrWhiteSpace(fileName))
throw new ArgumentException(
"File name is required.",
nameof(fileName));
if (saveImageTask != null && !saveImageTask.IsCompleted)
{
log.Warn( $"Image save already running: cameraIdx={cameraIdx}, file={fileName}");
return saveImageTask;
}
saveImageTask = Task.Run( () => SaveImageToFile(image, fileName));
return saveImageTask;
}
private void StartSaveImageListener()
{
Task<Image> saveImageTask = Task.Run(() => SaveImageListenerAsynch());
var timeoutAnswerTask = Task.Delay(TimeSpan.FromSeconds(1));
var completedAnswerTask = Task.WhenAny(saveImageTask, timeoutAnswerTask).Result;
if (completedAnswerTask == timeoutAnswerTask)
{
log.Error("Connection attempt timed out after 1 second.");
return;
}
try
{
SaveImageToFile(saveImageTask.Result);
}
catch (Exception exc)
{
MessageBox.Show("Error saving image: " + exc.Message);
}
}
public void StartSaveImageListener(string filePath)
{
Task<Image> saveImageTask = Task.Run(() => SaveImageListenerAsynch());
var timeoutAnswerTask = Task.Delay(TimeSpan.FromSeconds(1));
var completedAnswerTask = Task.WhenAny(saveImageTask, timeoutAnswerTask).Result;
if (completedAnswerTask == timeoutAnswerTask)
{
log.Error("Connection attempt timed out after 1 second.");
return;
}
try
{
SaveImageToFile(saveImageTask.Result);
}
catch (Exception exc)
{
MessageBox.Show("Error saving image: " + exc.Message);
}
}
private void EstablishCameraConnection(ref TcpClient camTcpClient, string localIP, int localPort)
{
var connectTask = camTcpClient.ConnectAsync(localIP, localPort);
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(1));
var completedTask = Task.WhenAny(connectTask, timeoutTask).Result;
if (completedTask == timeoutTask)
{
throw new TimeoutException("Connection attempt timed out after 1 second.");
}
}
private void EstablishCameraConnectionRepeticaly(ref TcpClient camTcpClient, string localIP, int localPort)
{
int[] timeoutIntervals = new int[] { 500, 1000 };
foreach (int interval in timeoutIntervals)
{
// Dispose previous TcpClient if needed
//camTcpClient?.Close();
//camTcpClient = new TcpClient();
try
{
// Begin connection attempt
var connectTask = camTcpClient.ConnectAsync(localIP, localPort);
var timeoutTask = Task.Delay(interval);
var completedTask = Task.WhenAny(connectTask, timeoutTask).GetAwaiter().GetResult();
if (completedTask == timeoutTask)
{
// Timed out
continue;
}
// Check if the connect task completed successfully
//connectTask.GetAwaiter().GetResult()); // Throws if failed
if (camTcpClient.Connected)
{
// Connected successfully
return;
}
}
catch (Exception e)
{
// Ignore and continue to next retry interval
continue;
}
}
// If we reach here, all attempts failed
throw new TimeoutException("Connection attempt timed out after multiple retries.");
}
private void EstablishCameraConnectionX(ref TcpClient camTcpClient, string localIP, int localPort)
{
Task connectTask = null;
Task timeoutTask = null;
Task completedTask = null;
int[] timeoutIntervals = new int[] {100, 300, 500, 1000};
foreach (int interval in timeoutIntervals)
{
if (camTcpClient.Connected)
{
return;
}
try
{
connectTask = camTcpClient.ConnectAsync(localIP, localPort);
timeoutTask = Task.Delay(TimeSpan.FromMilliseconds(interval));
completedTask = Task.WhenAny(connectTask, timeoutTask).Result;
if (completedTask == timeoutTask)
{
continue;
}
break;
}
catch (Exception exc)
{
continue;
}
}
if (completedTask == timeoutTask)
{
throw new TimeoutException("Connection attempt timed out after 1 second.");
}
if (connectTask == null || connectTask.IsFaulted || connectTask.IsCanceled)
{
throw new Exception("Connection attempt failed: " + connectTask.Exception.Message);
}
if (camTcpClient.Connected == false)
{
throw new Exception("Connection attempt failed: " + camTcpClient.Client.RemoteEndPoint.ToString());
}
}
private async Task<TcpClient> ConnectCameraAsync(int timeoutMs, CancellationToken ct)
{
var client = new TcpClient();
var connectTask = client.ConnectAsync(CameraCfg.IPAddressCJMS, iPort);
var timeoutTask = Task.Delay(timeoutMs, ct);
var completed = await Task.WhenAny(connectTask, timeoutTask);
if (completed == timeoutTask)
{
client.Close();
throw new TimeoutException($"Connection timeout after {timeoutMs} ms.");
}
await connectTask;
log.Debug($"Camera connected to server {CameraCfg.IPAddressCJMS}:{iPort}.");
ipAddress = IPAddress.Parse(CameraCfg.IPAddressCJMS);
return client;
}
private async Task<TcpClient> ConnectCameraAsync(
int timeoutMs,
CancellationToken ct,
int maxAttempts = 3,
int retryDelayMs = 300)
{
Exception lastException = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
ct.ThrowIfCancellationRequested();
var client = new TcpClient();
try
{
log.Debug( $"Connecting to camera: cameraIdx={cameraIdx}, IP={CameraCfg.IPAddressCJMS}, port={iPort}, " +
$"attempt={attempt}/{maxAttempts}");
Task connectTask = client.ConnectAsync( CameraCfg.IPAddressCJMS, iPort);
Task timeoutTask = Task.Delay( timeoutMs, ct);
Task completedTask = await Task.WhenAny(connectTask, timeoutTask).ConfigureAwait(false);
if (completedTask == timeoutTask)
{
ct.ThrowIfCancellationRequested();
client.Close();
throw new TimeoutException( $"Connection timeout after {timeoutMs} ms. Attempt {attempt}/{maxAttempts}.");
}
// Dôležité: načíta prípadnú SocketException.
await connectTask.ConfigureAwait(false);
if (!client.Connected)
{
throw new SocketException(
(int)SocketError.NotConnected);
}
client.NoDelay = true;
log.Debug( $"Camera connected: cameraIdx={cameraIdx}, IP={CameraCfg.IPAddressCJMS}, port={iPort}, " +
$"attempt={attempt}");
ipAddress =
IPAddress.Parse(
CameraCfg.IPAddressCJMS);
return client;
}
catch (OperationCanceledException)
{
client.Close();
throw;
}
catch (Exception exc)
{
client.Close();
lastException = exc;
log.Warn( $"Camera connection attempt failed: cameraIdx={cameraIdx}, IP={CameraCfg.IPAddressCJMS}, " +
$"attempt={attempt}/{maxAttempts}, error={exc.Message}");
if (attempt < maxAttempts)
{
await Task.Delay(
retryDelayMs,
ct)
.ConfigureAwait(false);
}
}
}
throw new IOException(
$"Unable to connect to camera {CameraCfg.IPAddressCJMS}:{iPort} after {maxAttempts} attempts.",
lastException);
}
public async Task<Image> SaveImageListenerAsynch()
{
saveCts?.Cancel();
saveCts = new CancellationTokenSource();
return await SaveImageListenerAsynch(saveCts.Token);
}
public async Task<Image> SaveImageListenerAsynch(CancellationToken ct)
{
Image imageToReturn = null;
await cameraOperationLock.WaitAsync(ct);
try
{
using (TcpClient client = await ConnectCameraAsync(1000, ct))
using (NetworkStream stream = client.GetStream())
{
var reader = new CameraLineReader(
stream,
64 * 1024);
JmsPacket prepareAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.prepare_camera_,
null,
3000,
ct)
.ConfigureAwait(false);
bool alreadyOpened =
prepareAnswer != null &&
prepareAnswer.JmsMessage.Status ==
MessageStatus.NACK &&
!string.IsNullOrEmpty(
prepareAnswer.JmsMessage.Payload) &&
prepareAnswer.JmsMessage.Payload.Contains(
"OPENED");
if (alreadyOpened)
{
log.Debug( $"Camera already OPENED: " + $"cameraIdx={cameraIdx}, " + $"IP={CameraCfg.IPAddressCJMS}");
}
else if (prepareAnswer == null || prepareAnswer.JmsMessage.Status != MessageStatus.ACK)
{
log.Error( $"Prepare camera failed: " + $"cameraIdx={cameraIdx}, " + $"IP={CameraCfg.IPAddressCJMS}");
return null;
}
JmsPacket grabAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.grab_image_,
null,
10000,
ct)
.ConfigureAwait(false);
if (grabAnswer == null || grabAnswer.JmsMessage.Status != MessageStatus.ACK)
{
log.Error( $"Grab image failed: " + $"cameraIdx={cameraIdx}, " + $"IP={CameraCfg.IPAddressCJMS}");
return null;
}
ParsedImage parsedImage = grabAnswer.JmsMessage.getImage();
if (parsedImage == null)
{
log.Error( $"Parsed image is null: " + $"cameraIdx={cameraIdx}");
return null;
}
if (parsedImage.Encoding != ImageType.BASE_64)
{
log.Error( $"Unsupported image encoding: " + $"cameraIdx={cameraIdx}, " + $"encoding={parsedImage.Encoding}");
return null;
}
imageToReturn = parsedImage.Image;
if (imageToReturn == null)
{
log.Error( $"Decoded image is null: " + $"cameraIdx={cameraIdx}");
return null;
}
DrawOverlay(imageToReturn);
await TryCloseCameraAsync( stream, reader).ConfigureAwait(false);
return imageToReturn;
}
}
catch (OperationCanceledException)
{
log.Debug( $"Save image cancelled: cameraIdx={cameraIdx}");
return null;
}
catch (TimeoutException exc)
{
log.Error( $"Save image timeout: cameraIdx={cameraIdx}, IP={CameraCfg.IPAddressCJMS}, error={exc.Message}");
return null;
}
catch (Exception exc)
{
log.Error( $"Save image exception: cameraIdx={cameraIdx}, IP={CameraCfg.IPAddressCJMS}, error={exc.Message}", exc);
return null;
}
finally
{
cameraOperationLock.Release();
}
}
void StopUiThreads()
{
liveCts?.Cancel();
grabCts?.Cancel();
saveCts?.Cancel();
sendImagesCts?.Cancel();
stopLiveStreamListenerFlag = true;
stopGrabImageListenerFlag = true;
stopUIListenerFlag = true;
log.Debug("Handle CameraUICmd StopUIListener");
var taskStopUi = Task.Run(StopUiListener);
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
var completedTask = Task.WhenAny(taskStopUi, timeoutTask).Result;
if (completedTask == timeoutTask)
{
stopUIListenerFlag = true;
throw new TimeoutException("Connection attempt timed out after 5 second.");
}
CloseCameraResources();
}
void StopUiListener()
{
stopUIListenerFlag = false;
while (!stopUIListenerFlag)
{
stopLiveStreamListenerFlag = true;
stopGrabImageListenerFlag = true;
if ((liveStreamListenerThread != null && liveStreamListenerThread.IsAlive) ||
(grabImageListenerThread != null && grabImageListenerThread.IsAlive))
{
Thread.Sleep(100);
}
else
{
stopUIListenerFlag = true;
}
}
//stopLiveStreamListenerFlag = false;
//stopGrabImageListenerFlag = false;
}
public bool IsGrabImageInProgress
{
get
{
return grabImageTask != null &&
!grabImageTask.IsCompleted;
}
}
public Task GrabImageTask
{
get { return grabImageTask; }
}
public void StartGrabImageListener()
{
_ = StartGrabImageListenerAsync();
}
public Task<Image> StartGrabImageListenerAsync(
bool retryUntilValidImage = false,
int maxAttempts = 1,
int retryDelayMs = 200)
{
if (grabImageTask != null && !grabImageTask.IsCompleted)
{
log.Warn(
$"Grab already running: cameraIdx={cameraIdx}, " +
$"IP={CameraCfg.IPAddressCJMS}");
return grabImageTask;
}
grabCts?.Cancel();
grabCts?.Dispose();
grabCts = new CancellationTokenSource();
if (!retryUntilValidImage)
{
maxAttempts = 1;
}
maxAttempts = Math.Max(1, maxAttempts);
retryDelayMs = Math.Max(0, retryDelayMs);
grabImageTask = GrabImageListenerWithRetryAsync(
grabCts.Token,
maxAttempts,
retryDelayMs);
return grabImageTask;
}
private async Task EstablishCameraConnectionAsync(
TcpClient client,
string ip,
int port,
int timeoutMs)
{
Task connectTask = client.ConnectAsync(ip, port);
Task timeoutTask = Task.Delay(timeoutMs);
Task completedTask = await Task.WhenAny(connectTask, timeoutTask);
if (completedTask == timeoutTask)
throw new TimeoutException($"Connection attempt timed out after {timeoutMs} ms.");
await connectTask;
}
private Task<Image> grabImageTask;
private CancellationTokenSource grabCts;
//private CancellationTokenSource liveCts;
private CancellationTokenSource saveCts;
private readonly SemaphoreSlim cameraOperationLock = new SemaphoreSlim(1, 1);
private async Task<Image> GrabImageListenerWithRetryAsync(
CancellationToken ct,
int maxAttempts,
int retryDelayMs)
{
bool lockAcquired = false;
try
{
await cameraOperationLock.WaitAsync(ct).ConfigureAwait(false);
lockAcquired = true;
for (int attempt = 1;
attempt <= maxAttempts;
attempt++)
{
ct.ThrowIfCancellationRequested();
Image image = null;
try
{
log.Debug( $"Starting grab image attempt: cameraIdx={cameraIdx}, attempt={attempt}/{maxAttempts}");
image = await GrabImageSingleAttemptAsync(ct).ConfigureAwait(false);
if (IsValidGrabbedImage(image))
{
/*
* Obrázok upravíme ešte predtým,
* než ho sprístupníme iným threadom.
*/
ImageUtils.Rotate(
image,
RoiConfiguration.ImageRotation);
DrawOverlay(image);
/*
* UI dostane vlastnú Image inštanciu.
* Originál zostáva pre návrat z Task<Image>
* a následné uloženie do súboru.
*/
Image imageForUi = CloneImageToBitmap(image);
try
{
OnImageReceived( this, cameraIdx, imageForUi, ImageRotation.None);
}
catch
{
imageForUi.Dispose();
throw;
}
log.Debug( $"Valid image received: cameraIdx={cameraIdx}, attempt={attempt}/{maxAttempts}, " +
$"size={image.Width}x{image.Height}");
return image;
}
image?.Dispose();
log.Warn(
$"Invalid image received: " +
$"cameraIdx={cameraIdx}, " +
$"attempt={attempt}/{maxAttempts}");
}
catch (OperationCanceledException)
{
image?.Dispose();
throw;
}
catch (Exception exc)
{
image?.Dispose();
log.Warn(
$"Grab image attempt failed: " +
$"cameraIdx={cameraIdx}, " +
$"attempt={attempt}/{maxAttempts}, " +
$"error={exc.Message}");
}
if (attempt < maxAttempts &&
retryDelayMs > 0)
{
await Task.Delay(retryDelayMs, ct)
.ConfigureAwait(false);
}
}
log.Error(
$"No valid image received after all attempts: " +
$"cameraIdx={cameraIdx}, " +
$"attempts={maxAttempts}");
UiBridge.Bridge.OnImage(cameraIdx, null);
return null;
}
catch (OperationCanceledException)
{
log.Debug(
$"Grab image cancelled: " +
$"cameraIdx={cameraIdx}");
throw;
}
finally
{
if (lockAcquired)
{
cameraOperationLock.Release();
}
}
}
private static bool IsValidGrabbedImage(Image image)
{
if (image == null)
{
return false;
}
try
{
if (image.Width <= 0 ||
image.Height <= 0)
{
return false;
}
// Vytvorenie Bitmap zároveň preverí,
// či je obrázok použiteľný cez GDI+.
using (var bitmap = new Bitmap(image))
{
return bitmap.Width > 0 &&
bitmap.Height > 0;
}
}
catch
{
return false;
}
}
private async Task<Image> GrabImageSingleAttemptAsync(
CancellationToken ct)
{
TcpClient client = null;
NetworkStream stream = null;
CameraLineReader reader = null;
try
{
client =
await ConnectCameraAsync(5000, ct)
.ConfigureAwait(false);
stream = client.GetStream();
reader = new CameraLineReader(
stream,
64 * 1024);
JmsPacket prepareAnswer =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.prepare_camera_,
null,
3000,
ct)
.ConfigureAwait(false);
bool alreadyOpened =
prepareAnswer != null &&
prepareAnswer.JmsMessage != null &&
prepareAnswer.JmsMessage.Status ==
MessageStatus.NACK &&
!string.IsNullOrEmpty(
prepareAnswer.JmsMessage.Payload) &&
prepareAnswer.JmsMessage.Payload.Contains(
"OPENED");
bool prepared =
prepareAnswer != null &&
prepareAnswer.JmsMessage != null &&
prepareAnswer.JmsMessage.Status ==
MessageStatus.ACK;
if (!prepared && !alreadyOpened)
{
log.Warn(
$"Prepare camera failed: " +
$"cameraIdx={cameraIdx}, " +
$"answer={FormatPacketForLog(prepareAnswer)}");
return null;
}
if (alreadyOpened)
{
log.Debug(
$"Camera already OPENED: " +
$"cameraIdx={cameraIdx}");
}
await SendCommandAsync(
stream,
CommandM.grab_image_,
ct,
null)
.ConfigureAwait(false);
while (true)
{
ct.ThrowIfCancellationRequested();
string response =
await reader.ReadLineAsync(10000, ct)
.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(response))
{
continue;
}
JmsPacket packet;
try
{
packet = new JmsPacket(response);
}
catch (Exception exc)
{
log.Warn(
$"Cannot parse grab response: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
continue;
}
if (packet.JmsMessage == null)
{
continue;
}
if (packet.JmsMessage.Command !=
CommandM.grab_image_)
{
log.Debug(
$"Unexpected response during grab: " +
$"cameraIdx={cameraIdx}, " +
$"command={packet.JmsMessage.Command}");
continue;
}
if (packet.JmsMessage.Status !=
MessageStatus.ACK)
{
log.Warn(
$"Grab image returned NACK: " +
$"cameraIdx={cameraIdx}, " +
$"payload={packet.JmsMessage.Payload}");
return null;
}
ParsedImage parsedImage =
packet.JmsMessage.getImage();
if (parsedImage == null)
{
log.Warn(
$"Parsed image is null: " +
$"cameraIdx={cameraIdx}");
return null;
}
if (parsedImage.Encoding !=
ImageType.BASE_64)
{
log.Warn(
$"Unsupported image encoding: " +
$"cameraIdx={cameraIdx}, " +
$"encoding={parsedImage.Encoding}");
return null;
}
Image image = parsedImage.Image;
if (image == null)
{
log.Warn(
$"Decoded image is null: " +
$"cameraIdx={cameraIdx}");
return null;
}
return image;
}
}
catch (OperationCanceledException)
{
log.Debug(
$"Grab image attempt cancelled: " +
$"cameraIdx={cameraIdx}");
throw;
}
catch (TimeoutException exc)
{
log.Warn(
$"Grab image attempt timeout: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
return null;
}
catch (Exception exc)
{
log.Warn(
$"Grab image attempt failed: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}",
exc);
return null;
}
finally
{
if (stream != null &&
reader != null)
{
try
{
await TryCloseCameraAsync(
stream,
reader)
.ConfigureAwait(false);
}
catch (Exception exc)
{
log.Warn(
$"Grab cleanup failed: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
}
}
try
{
stream?.Dispose();
}
catch
{
}
try
{
client?.Close();
client?.Dispose();
}
catch
{
}
}
}
private async Task TryCloseCameraAsync(
NetworkStream stream,
CameraLineReader reader)
{
/*
* CLOSE
*
* Na close ešte odpoveď očakávame.
*/
try
{
JmsPacket closeResponse =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.close_,
null,
2000,
CancellationToken.None)
.ConfigureAwait(false);
if (closeResponse == null ||
closeResponse.JmsMessage == null)
{
log.Warn(
$"No close response: " +
$"cameraIdx={cameraIdx}");
return;
}
bool closeAccepted =
closeResponse.JmsMessage.Status ==
MessageStatus.ACK;
bool closeAlreadyInProgress =
closeResponse.JmsMessage.Status ==
MessageStatus.NACK &&
!string.IsNullOrEmpty(
closeResponse.JmsMessage.Payload) &&
closeResponse.JmsMessage.Payload.Contains(
"Close command already in progress");
if (closeAlreadyInProgress)
{
log.Debug(
$"Camera close already in progress: " +
$"cameraIdx={cameraIdx}");
}
else if (!closeAccepted)
{
log.Warn(
$"Camera close returned NACK: " +
$"cameraIdx={cameraIdx}, " +
$"payload={closeResponse.JmsMessage.Payload}");
}
}
catch (TimeoutException exc)
{
log.Warn(
$"Close camera timeout: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
}
catch (Exception exc)
{
log.Warn(
$"Close camera failed: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
}
/*
* DISCONNECT
*
* Dôležité: príkaz iba odošleme.
* Na odpoveď nečakáme.
*/
try
{
if (stream.CanWrite)
{
await SendCommandAsync(
stream,
CommandM.disconnect_,
CancellationToken.None,
null)
.ConfigureAwait(false);
log.Debug(
$"Disconnect command sent: " +
$"cameraIdx={cameraIdx}");
}
}
catch (Exception exc)
{
log.Warn(
$"Disconnect camera failed: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
}
}
private void CloseCameraResources()
{
cameraReader?.Close();
cameraReader = null;
//cameraWriter?.Close();
//cameraWriter = null;
cameraStream?.Close();
cameraStream = null;
cameraTcpClient?.Close();
cameraTcpClient?.Dispose();
cameraTcpClient = null;
}
public void CancelGrabImage()
{
try
{
grabCts?.Cancel();
}
catch (ObjectDisposedException)
{
// CTS už bolo uvoľnené.
}
}
/// <summary>
/// listener for multiple - repetetive image receiving
/// </summary>
/// <detail>
/// method uses reader to receive data from camera
/// </detail>
/// <param name="localIP"></param>
/// <param name="localPort"></param>
// private void StartLiveStreamListener(IPAddress localIP, int localPort)
// {
// liveStreamListenerThread = new Thread(new ThreadStart(LiveStreamListener));
// liveStreamListenerThread.Start();
// }
private CancellationTokenSource liveCts;
private Task liveStreamTask;
private static int maxAttempts = 4;
private static int retryDelayMs = 1000;
public void StartLiveStreamListener()
{
if (liveStreamTask != null && !liveStreamTask.IsCompleted)
{
log.Warn( $"Live stream already running: cameraIdx={cameraIdx}");
return;
}
liveCts?.Cancel();
liveCts?.Dispose();
liveCts = new CancellationTokenSource();
liveStreamTask = LiveStreamListenerAsync(liveCts.Token);
}
private async Task LiveStreamListenerAsync(
CancellationToken ct)
{
bool lockAcquired = false;
TcpClient client = null;
NetworkStream stream = null;
CameraLineReader reader = null;
bool streamStarted = false;
try
{
log.Debug(
$"Live stream waiting for camera lock: " +
$"cameraIdx={cameraIdx}");
await cameraOperationLock
.WaitAsync(ct)
.ConfigureAwait(false);
lockAcquired = true;
log.Debug(
$"Live stream acquired camera lock: " +
$"cameraIdx={cameraIdx}");
client = await ConnectCameraAsync(
timeoutMs: 3000,
ct: ct,
maxAttempts: 3,
retryDelayMs: 300)
.ConfigureAwait(false);
stream = client.GetStream();
reader = new CameraLineReader( stream, 64 * 1024);
JmsPacket prepare =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.prepare_camera_,
null,
3000,
ct)
.ConfigureAwait(false);
bool alreadyOpened =
prepare != null &&
prepare.JmsMessage != null &&
prepare.JmsMessage.Status ==
MessageStatus.NACK &&
!string.IsNullOrEmpty(
prepare.JmsMessage.Payload) &&
prepare.JmsMessage.Payload.Contains(
"OPENED");
bool prepared =
prepare != null &&
prepare.JmsMessage != null &&
prepare.JmsMessage.Status ==
MessageStatus.ACK;
if (!prepared && !alreadyOpened)
{
log.Error( $"Prepare camera for live stream failed: cameraIdx={cameraIdx}, answer={FormatPacketForLog(prepare)}");
return;
}
if (alreadyOpened)
{
log.Debug( $"Camera already opened for live stream: cameraIdx={cameraIdx}");
}
JmsPacket start =
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.start_stream_images_,
null,
5000,
ct)
.ConfigureAwait(false);
if (start == null || start.JmsMessage == null || start.JmsMessage.Status != MessageStatus.ACK)
{
log.Error( $"Unable to start live stream: cameraIdx={cameraIdx}, answer={FormatPacketForLog(start)}");
return;
}
streamStarted = true;
log.Debug( $"Live stream started: cameraIdx={cameraIdx}");
while (!ct.IsCancellationRequested)
{
string response =
await reader.ReadLineAsync(
10000,
ct)
.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(response))
continue;
JmsPacket packet =
new JmsPacket(response);
if (packet.JmsMessage == null)
continue;
if (packet.JmsMessage.Command !=
CommandM.grab_image_)
{
log.Debug( $"Unexpected packet during live stream: cameraIdx={cameraIdx}, command={packet.JmsMessage.Command}");
continue;
}
if (packet.JmsMessage.Status !=
MessageStatus.ACK)
{
log.Warn( $"Live frame returned NACK: cameraIdx={cameraIdx}, payload={packet.JmsMessage.Payload}");
continue;
}
ParsedImage parsed = packet.JmsMessage.getImage();
if (parsed == null || parsed.Encoding != ImageType.BASE_64)
{
continue;
}
Image image = parsed.Image;
if (image == null)
continue;
DrawOverlay(image);
OnImageReceived(
this,
cameraIdx,
image,
RoiConfiguration.ImageRotation,
0,
1);
}
}
catch (OperationCanceledException)
{
log.Debug( $"Live stream cancelled: cameraIdx={cameraIdx}");
}
catch (TimeoutException exc)
{
log.Error( $"Live stream timeout: cameraIdx={cameraIdx}, IP={CameraCfg.IPAddressCJMS}, error={exc.Message}");
}
catch (SocketException exc)
{
log.Error( $"Live stream socket error: cameraIdx={cameraIdx}, IP={CameraCfg.IPAddressCJMS}, socketError={exc.SocketErrorCode}, " +
$"error={exc.Message}");
}
catch (Exception exc)
{
log.Error( $"Live stream exception: cameraIdx={cameraIdx}, error={exc.Message}", exc);
UiBridge.Bridge.OnImage( cameraIdx, null);
}
finally
{
/*
* Cleanup nesmie používať zrušený ct.
*/
if (stream != null &&
reader != null)
{
if (streamStarted)
{
try
{
await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.stop_stream_images_,
null,
2000,
CancellationToken.None)
.ConfigureAwait(false);
}
catch (Exception exc)
{
log.Warn(
$"Stop live stream failed: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
}
}
try
{
await TryCloseCameraAsync(
stream,
reader)
.ConfigureAwait(false);
}
catch (Exception exc)
{
log.Warn(
$"Live stream cleanup failed: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
}
}
try
{
stream?.Dispose();
}
catch
{
}
try
{
client?.Close();
client?.Dispose();
}
catch
{
}
if (lockAcquired)
{
cameraOperationLock.Release();
}
log.Debug( $"Live stream finished: cameraIdx={cameraIdx}");
}
}
private void DrawOverlay(Image image)
{
if (image == null)
return;
if (!showOverlayRect || overlayThickness <= 0)
return;
using (Graphics g = Graphics.FromImage(image))
using (Pen pen = new Pen(Color.Green, overlayThickness))
{
g.DrawRectangle(pen, overlayRect);
}
}
//public void OnMeasurementDataReceived(object sender, MeasuredDataEventArgs msrmtData)
//{
// string[] pulsesArr = msrmtData.MeasuredData.Split(new char[] { ';' });
// uint uiVal;
// if (result != null && result.Length + 1 == pulsesArr.Length && uint.TryParse(pulsesArr[0], out uiVal))
// {
// cameraTimeMs = (long)uiVal;
// for (int i = 0; i < result.Length; i++)
// {
// int val;
// if (int.TryParse(pulsesArr[i + 1], out val))
// {
// result[i] = Math.Abs(val);
// }
// }
// }
//}
public IOperation LiveStreamOp(bool hiRes)
{
int wid = hiRes ? 1280 : 640;
int hgh = hiRes ? 960 : 480;
//string command = string.Format("~/userland/build/bin/raspivid -t 0 -cd MJPEG -w {0} -h {1} -fps 30 -b 8000000 -o - | gst-launch-1.0 fdsrc ! \"image/jpeg,framerate=30/1\" ! jpegparse ! rtpjpegpay ! udpsink host={2} port={3}",
// wid, hgh, NetAdapter.IPAddress, rtpUdpLocalPort);
return new LiveStreamOp(this, command);
}
public IOperation GrabImagesOp(string[] imgFileNames, bool blackAndWhite, bool lowResolution, ImageRotation imageRotation)
{
return new GrabImagesOp(this, imgFileNames, blackAndWhite, lowResolution, imageRotation);
}
public IOperation MeasurementOp()
{
return null;
}
string command;
bool measurementCommandSent;
private void SendCommand(CommandM command)
{
try
{
if (cameraStream != null && cameraStream.CanWrite)
{
string commandStr = CommandMEnum.GetVal(command) + "\n";
byte[] bytes = Encoding.ASCII.GetBytes(commandStr);
cameraStream.Write(bytes, 0, bytes.Length);
cameraStream.Flush();
}
}
catch (Exception exc)
{
log.ErrorFormat("{0} SendCommand: {1}", Name, exc.Message);
}
}
//
private async Task<JmsPacket> SendCommandAndWaitToAnswerAsync(
NetworkStream stream,
CameraLineReader reader,
CommandM command,
string payload = null,
int timeoutMs = 1000,
CancellationToken ct = default)
{
await SendCommandAsync( stream, command, ct, payload).ConfigureAwait(false);
try
{
for (int iReads = 0; iReads < 6; iReads++)
{
string responseAnswer = await reader.ReadLineAsync( timeoutMs, ct).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(responseAnswer))
continue;
log.Debug( $"Received from camera: idx={cameraIdx}, expected={command}, length={responseAnswer.Length}, prefix={GetLogPrefix(responseAnswer, 200)}");
JmsPacket result;
try
{
result = new JmsPacket(responseAnswer);
}
catch (Exception exc)
{
log.Warn( $"Cannot parse camera response: idx={cameraIdx}, length={responseAnswer.Length}, error={exc.Message}");
continue;
}
if (result.JmsMessage == null)
{
log.Warn( $"Parsed packet contains no JmsMessage: idx={cameraIdx}");
continue;
}
if (result.JmsMessage.Command == command)
{
return result;
}
log.Debug( $"Unexpected response command: idx={cameraIdx}, expected={command}, received={result.JmsMessage.Command}. Reading next response.");
}
log.Error( $"Expected camera response not received: idx={cameraIdx}, command={command}");
return null;
}
catch (OperationCanceledException)
{
throw;
}
catch (TimeoutException)
{
throw;
}
catch (IOException ioEx)
{
log.Error( $"IO error while reading from camera: idx={cameraIdx}, command={command}, error={ioEx.Message}");
return null;
}
catch (Exception exc)
{
log.Error( $"Error while reading from camera: idx={cameraIdx}, command={command}, error={exc.Message}", exc);
return null;
}
}
private static string GetLogPrefix(
string value,
int maximumLength)
{
if (string.IsNullOrEmpty(value))
return string.Empty;
if (value.Length <= maximumLength)
return value;
return value.Substring(0, maximumLength) +
"...";
}
public void Start()
{
///Prepare communication with the camera
log.DebugFormat("Camera Start() - JMCameras.CameraCfg.IPAddressCJMS: {0}, CameraCfg.Port: {1}! ");
StopUiListener();
this.cameraTcpClient = new TcpClient();
lockCameraInitialisation = true;
try
{
EstablishCameraConnection(ref this.cameraTcpClient, CameraCfg.IPAddressCJMS, iPort);
}catch(Exception exc)
{
log.ErrorFormat("Camera Start() - JMCameras.CameraCfg.IPAddressCJMS: {0}, CameraCfg.Port: {1}! Exception: {2}", CameraCfg.IPAddressCJMS, iPort, exc.Message);
return;
}
}
public Event Run()
{
return Event.None;
}
public void Stop()
{
if (measurementCommandSent)
{
log.WarnFormat("{0} SEND_COMMAND: cancel", Name);
if (cameraStream.CanWrite)
{
SendCommand(CommandM.close_);
SendCommand(CommandM.disconnect_);
}
}
}
///
/// PromptReceviedEventHandler delegate and handler
///
public delegate void PromptCameraReceivedEventHandler(object sender, PromptReceivedJMSEventArgs e);
public static event PromptCameraReceivedEventHandler PromptCameraReceivedHandler;
/// <summary>
/// This method is called from within this class when
/// a prompt string was received from the telnet server.
/// </summary>
public static void OnPromptReceived(object sender, string response, JmsPacket package)
{
log.Info("Prompt received, response:" + Environment.NewLine + response);
if (null != PromptCameraReceivedHandler)
{
try
{
PromptCameraReceivedHandler(sender, new PromptReceivedJMSEventArgs(response, package.JmsMessage));
}
catch (Exception e)
{
log.Info("Internal error: PromptReceivedHandler exception: " + e.Message);
}
}
}
///
/// PromptReceviedEventHandler delegate and handler
///
public delegate void PromptImagesReceivedEventHandler(object sender, PromptReceivedImagesEventArgs e);
public static event EventHandler<PromptReceivedImagesEventArgs> ImagesCameraHandler;
public static event EventHandler<PromptReceivedImageEventArgs> ImageCameraHandler;
public static void OnImageReceived(
object sender,
int idxImage,
Image image,
int iImageIdx = 0,
int imageCount = 1)
{
if (image == null)
{
return;
}
log.Info( $"Image received from camera: {idxImage}, iImageIdx: {iImageIdx}, imageCount: {imageCount}");
EventHandler<PromptReceivedImageEventArgs> handler = ImageCameraHandler;
if (handler == null)
{
image.Dispose();
return;
}
try
{
handler( sender, new PromptReceivedImageEventArgs( idxImage, image));
log.Info( $"ImageCameraHandler reached camera: {idxImage}, iImageIdx: {iImageIdx}, imageCount: {imageCount}");
}
catch (Exception exc)
{
log.Error( $"ImageCameraHandler exception: cameraIdx={idxImage}, error={exc.Message}", exc);
image.Dispose();
}
}
/// <summary>
/// This method is called from within this class when
/// a prompt string was received from the telnet server.
/// </summary>
public static void OnImageReceived(object sender, int idxImage, Image image, ImageRotation imageRotation, int iImageIdx = 0, int imageCount = 1)
{
log.Info($"Image received from camera: {idxImage}, iImageIdx: {iImageIdx}, imageCount: {imageCount}");
if (null != ImageCameraHandler)
{
try
{
//rotation
ImageUtils.Rotate(image, imageRotation);
//Send image to bridge
ImageCameraHandler(sender, new PromptReceivedImageEventArgs(idxImage, image));
log.Info($"ImageCameraHandler reached camera: {idxImage}, iImageIdx: {iImageIdx}, imageCount: {imageCount}");
}
catch (Exception ex)
{
log.Info("Internal error: PromptReceivedHandler exception: " + ex.Message);
}
}
}
/// <summary>
/// This method is called from within this class when
/// a prompt string was received from the telnet server.
/// </summary>
public static void OnImagesReceived(
object sender,
int cameraIndex,
Image image,
int imageIndex,
int imageCount,
DateTimeOffset? frameTime)
{
if (image == null)
{
return;
}
log.Info( $"Images received from camera: {cameraIndex}, imageIndex={imageIndex}, " +
$"imageCount={imageCount}, frameTime={frameTime:O}");
EventHandler<PromptReceivedImagesEventArgs> handler = ImagesCameraHandler;
if (handler == null)
{
image.Dispose();
return;
}
try
{
handler( sender,
new PromptReceivedImagesEventArgs(
cameraIndex,
imageIndex,
imageCount,
image,
frameTime));
log.Info( $"ImagesCameraHandler reached camera: {cameraIndex}, imageIndex={imageIndex}, " +
$"imageCount={imageCount}");
}
catch (Exception exc)
{
log.Error( $"ImagesCameraHandler failed: cameraIdx={cameraIndex}, imageIndex={imageIndex}, " +
$"error={exc.Message}", exc);
image.Dispose();
}
}
private async Task<string> ReadCameraLineAsync(
NetworkStream stream,
int timeoutMs,
CancellationToken ct)
{
await cameraReadLock.WaitAsync(ct);
try
{
var line = new List<byte>();
var buffer = new byte[1];
while (true)
{
Task<int> readTask = stream.ReadAsync(buffer, 0, 1, ct);
Task timeoutTask = Task.Delay(timeoutMs, ct);
Task completed = await Task.WhenAny(readTask, timeoutTask);
if (completed == timeoutTask)
{
ct.ThrowIfCancellationRequested();
throw new TimeoutException($"Camera {cameraIdx} read timeout after {timeoutMs} ms.");
}
int read = await readTask;
if (read == 0)
throw new IOException("Camera connection closed.");
if (buffer[0] == '\n')
break;
if (buffer[0] != '\r')
line.Add(buffer[0]);
}
return Encoding.ASCII.GetString(line.ToArray());
}
finally
{
cameraReadLock.Release();
}
}
private readonly object cameraReadSyncLock = new object();
private string ReadCameraLine(NetworkStream stream, int timeoutMs)
{
lock (cameraReadSyncLock)
{
stream.ReadTimeout = timeoutMs;
var line = new List<byte>();
while (true)
{
int b = stream.ReadByte(); // tu funguje ReadTimeout
if (b == -1)
throw new IOException("Camera connection closed.");
if (b == '\n')
break;
if (b != '\r')
line.Add((byte)b);
}
return Encoding.ASCII.GetString(line.ToArray());
}
}
private async Task SendCommandAsync(NetworkStream stream, CommandM command, CancellationToken ct, string payload = null)
{
string commandStr = CommandMEnum.GetVal(command);
if (!string.IsNullOrEmpty(payload))
{
commandStr += "=" + payload;
}
//commandStr += " ";
byte[] bytes = Encoding.ASCII.GetBytes(commandStr + "\n");
//await cameraWriteLock.WaitAsync();
log.Info($"Sending command: '{commandStr}'");
await stream.WriteAsync(bytes, 0, bytes.Length);
await stream.FlushAsync();
//stream.Write(bytes, 0, bytes.Length);
//stream.Flush();
//await stream.WriteLineAsync(commandStr);
//await stream.FlushAsync();
log.Info($"Command sent: '{commandStr}'");
}
private async Task<string> ReadCameraLineWithTimeout(NetworkStream stream, int timeoutMs)
{
var readTask = Task.Run(() => ReadCameraLine(stream, timeoutMs));
var timeoutTask = Task.Delay(timeoutMs);
var completed = await Task.WhenAny(readTask, timeoutTask);
if (completed == timeoutTask)
{
try { stream.Close(); } catch { }
try { cameraTcpClient?.Close(); } catch { }
throw new TimeoutException($"Camera read timeout after {timeoutMs} ms.");
}
return await readTask;
}
//------------------------------ SEND PART --------------------------------------
public Task<IReadOnlyList<TimedCameraImage>> StartSendImagesPreviewAsync(
int count,
int delayMs,
string taskId)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(count),
"Image count must be greater than zero.");
}
if (delayMs < 0)
{
throw new ArgumentOutOfRangeException(
nameof(delayMs),
"Delay cannot be negative.");
}
if (string.IsNullOrWhiteSpace(taskId))
{
throw new ArgumentException(
"Task ID is required.",
nameof(taskId));
}
if (sendImagesTask != null &&
!sendImagesTask.IsCompleted)
{
log.Warn(
$"Send images preview already running: " +
$"cameraIdx={cameraIdx}, taskId={taskId}");
return sendImagesTask;
}
sendImagesCts?.Cancel();
sendImagesCts?.Dispose();
sendImagesCts =
new CancellationTokenSource();
sendImagesTask =
SendImagesPreviewListenerAsync(
count,
delayMs,
taskId,
sendImagesCts.Token);
return sendImagesTask;
}
public void CancelSendImagesPreview()
{
try
{
sendImagesCts?.Cancel();
}
catch (ObjectDisposedException)
{
}
}
private async Task<IReadOnlyList<TimedCameraImage>> SendImagesPreviewListenerAsync(
int requestedCount,
int delayMs,
string taskId,
CancellationToken ct)
{
bool lockAcquired = false;
TcpClient client = null;
NetworkStream stream = null;
CameraLineReader reader = null;
var imagesByIndex = new Dictionary<int, TimedCameraImage>();
try
{
await cameraOperationLock.WaitAsync(ct).ConfigureAwait(false);
lockAcquired = true;
client = await ConnectCameraAsync( timeoutMs: 5000, ct: ct).ConfigureAwait(false);
stream = client.GetStream();
reader = new CameraLineReader( stream, 64 * 1024);
/*
* Kamera musí byť pripravená.
*/
JmsPacket prepareAnswer = await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.prepare_camera_,
null,
3000,
ct)
.ConfigureAwait(false);
bool prepared = prepareAnswer != null &&
prepareAnswer.JmsMessage != null &&
prepareAnswer.JmsMessage.Status == MessageStatus.ACK;
bool alreadyOpened =
prepareAnswer != null &&
prepareAnswer.JmsMessage != null &&
prepareAnswer.JmsMessage.Status == MessageStatus.NACK &&
!string.IsNullOrEmpty( prepareAnswer.JmsMessage.Payload) &&
prepareAnswer.JmsMessage.Payload.Contains( "OPENED");
if (!prepared && !alreadyOpened)
{
throw new IOException( $"Prepare camera for send failed: cameraIdx={cameraIdx}, " +
$"answer={FormatPacketForLog(prepareAnswer)}");
}
var request = new SendImagesRequest { Count = requestedCount, Delay = delayMs, TaskId = taskId };
string requestPayload = JsonConvert.SerializeObject( request, Formatting.None);
log.Debug( $"Starting send images preview: cameraIdx={cameraIdx}, count={requestedCount}, " +
$"delay={delayMs}, taskId={taskId}");
/*
* Pri send_ neposielame cez
* SendCommandAndWaitToAnswerAsync(),
* pretože očakávame viac odpovedí.
*/
await SendCommandAsync(
stream,
CommandM.send_,
ct,
requestPayload)
.ConfigureAwait(false);
/*
* Timeout musí zahŕňať plánovaný čas snímania.
*
* count=5, delay=2050:
* približne 4 * 2050 ms medzi prvým a posledným frame
* plus rezerva na prenos Base64 obrázkov.
*/
int totalTimeoutMs = CalculateSendTimeout( requestedCount, delayMs);
using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
timeoutCts.CancelAfter(totalTimeoutMs);
while (imagesByIndex.Count < requestedCount)
{
timeoutCts.Token.ThrowIfCancellationRequested();
string response = await reader.ReadLineAsync( 10000, timeoutCts.Token).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(response))
{
continue;
}
JmsPacket packet;
try
{
packet = new JmsPacket(response);
}
catch (Exception exc)
{
log.Warn( $"Cannot parse send response: cameraIdx={cameraIdx}, error={exc.Message}");
continue;
}
if (packet.JmsMessage == null)
{
continue;
}
if (packet.JmsMessage.Command != CommandM.send_)
{
log.Debug( $"Unexpected packet during send: cameraIdx={cameraIdx}, command={packet.JmsMessage.Command}");
continue;
}
if (packet.JmsMessage.Status != MessageStatus.ACK)
{
log.Warn( $"Send image returned NACK: cameraIdx={cameraIdx}, payload={packet.JmsMessage.Payload}");
continue;
}
string payload = packet.JmsMessage.Payload;
if (string.IsNullOrWhiteSpace(payload))
{
continue;
}
/*
* Posledný sumarizačný packet nemá
* pictureBase64 ani workerId.
*/
if (!PayloadContainsImage(payload))
{
TryLogSendSummary( payload, cameraIdx);
continue;
}
SendImagePayload imagePayload;
try
{
imagePayload = JsonConvert.DeserializeObject<SendImagePayload>(payload);
}
catch (Exception exc)
{
log.Warn( $"Cannot deserialize send image: cameraIdx={cameraIdx}, error={exc.Message}");
continue;
}
if (imagePayload == null || string.IsNullOrWhiteSpace( imagePayload.PictureBase64))
{
continue;
}
if (!string.Equals( imagePayload.Status, "SUCCESSFUL", StringComparison.OrdinalIgnoreCase))
{
log.Warn( $"Send image status is not successful: cameraIdx={cameraIdx}, " +
$"workerId={imagePayload.WorkerId}, status={imagePayload.Status}");
continue;
}
/*
* workerId je podľa logu 1..count.
*/
int imageIndex = imagePayload.WorkerId;
if (imageIndex <= 0)
{
log.Warn( $"Invalid send image index: cameraIdx={cameraIdx}, workerId={imageIndex}");
continue;
}
/*
* Duplicitný packet ignorujeme.
*/
if (imagesByIndex.ContainsKey( imageIndex))
{
log.Warn( $"Duplicate send image: cameraIdx={cameraIdx}, workerId={imageIndex}");
continue;
}
Image image = DecodeBase64Image( imagePayload.PictureBase64);
if (!IsValidGrabbedImage(image))
{
image?.Dispose();
log.Warn(
$"Invalid send image: " +
$"cameraIdx={cameraIdx}, " +
$"workerId={imageIndex}");
continue;
}
DateTimeOffset? frameTime =
ParseFrameTime(
imagePayload.FrameTime);
/*
* Úpravy obrázka dokončíme ešte predtým,
* než ho odovzdáme UI.
*/
ImageUtils.Rotate(
image,
RoiConfiguration.ImageRotation);
DrawOverlay(image);
var timedImage =
new TimedCameraImage
{
Index = imageIndex,
TaskId =
imagePayload.TaskId,
FrameTime = frameTime,
FrameTimeMillis =
imagePayload.FrameTimeMillis,
FrameGap =
imagePayload.FrameGap,
Image = image
};
imagesByIndex.Add(
imageIndex,
timedImage);
/*
* UI dostane klon.
* Originál zostáva vo výslednom zozname.
*/
Image imageForUi = CloneImageToBitmap(image);
OnImagesReceived(
this,
cameraIdx,
imageForUi,
imageIndex,
requestedCount,
frameTime);
log.Debug( $"Send image received: cameraIdx={cameraIdx}, workerId={imageIndex}, " +
$"received={imagesByIndex.Count}/{requestedCount}, frameTime={frameTime:O}, " +
$"size={image.Width}x{image.Height}");
}
}
List<TimedCameraImage> result =
imagesByIndex
.OrderBy(x => x.Key)
.Select(x => x.Value)
.ToList();
log.Debug(
$"Send images preview completed: " +
$"cameraIdx={cameraIdx}, " +
$"received={result.Count}, " +
$"taskId={taskId}");
return result;
}
catch (OperationCanceledException)
{
foreach (TimedCameraImage image
in imagesByIndex.Values)
{
image.Image?.Dispose();
}
log.Debug(
$"Send images preview cancelled: " +
$"cameraIdx={cameraIdx}, " +
$"taskId={taskId}");
throw;
}
catch (Exception exc)
{
foreach (TimedCameraImage image
in imagesByIndex.Values)
{
image.Image?.Dispose();
}
log.Error(
$"Send images preview failed: " +
$"cameraIdx={cameraIdx}, " +
$"taskId={taskId}, " +
$"error={exc.Message}",
exc);
UiBridge.Bridge.OnImage(
cameraIdx,
null);
throw;
}
finally
{
if (stream != null &&
reader != null)
{
try
{
await TryCloseCameraAsync(
stream,
reader)
.ConfigureAwait(false);
}
catch (Exception exc)
{
log.Warn(
$"Send preview cleanup failed: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
}
}
try
{
stream?.Dispose();
}
catch
{
}
try
{
client?.Close();
client?.Dispose();
}
catch
{
}
if (lockAcquired)
{
cameraOperationLock.Release();
}
}
}
private static int CalculateSendTimeout(
int count,
int delayMs)
{
/*
* Medzi count obrázkami je count - 1 intervalov.
* Pridáme 15 sekúnd rezervu na capture,
* Base64 a sieťový prenos.
*/
long captureDuration =
(long)Math.Max(0, count - 1) *
delayMs;
long timeout =
captureDuration + 15000L;
return timeout > int.MaxValue
? int.MaxValue
: (int)timeout;
}
private static Image DecodeBase64Image( string pictureBase64)
{
if (string.IsNullOrWhiteSpace( pictureBase64))
{
return null;
}
try
{
byte[] imageBytes = Convert.FromBase64String( pictureBase64);
using (var stream = new MemoryStream(imageBytes))
using (Image decoded = Image.FromStream( stream, useEmbeddedColorManagement: false, validateImageData: true))
{
/*
* Bitmap odpojí výsledok od MemoryStream.
*/
return new Bitmap(decoded);
}
}
catch
{
return null;
}
}
private static bool PayloadContainsImage( string payload)
{
if (string.IsNullOrWhiteSpace(payload))
{
return false;
}
return payload.IndexOf( "\"pictureBase64\"", StringComparison.OrdinalIgnoreCase) >= 0;
}
private static DateTimeOffset? ParseFrameTime( string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
DateTimeOffset parsed;
if (DateTimeOffset.TryParse(
value,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.RoundtripKind,
out parsed))
{
return parsed;
}
return null;
}
private static void TryLogSendSummary( string payload, int cameraIdx)
{
try
{
SendSummaryPayload summary = JsonConvert.DeserializeObject<SendSummaryPayload>(payload);
if (summary == null)
{
return;
}
log.Debug( $"Send summary received: cameraIdx={cameraIdx}, picCount={summary.PictureCount}, " +
$"delay={summary.Delay}, timeStamp={summary.TimeStamp}");
}
catch (Exception exc)
{
log.Debug( $"Payload is not a send summary: cameraIdx={cameraIdx}, error={exc.Message}");
}
}
}
}