3610 lines
90 KiB
C#
3610 lines
90 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;
|
|
}
|
|
|
|
/*
|
|
* Server môže po spustení streamu poslať ešte jeden
|
|
* oneskorený alebo duplicitný ACK start_stream_images.
|
|
* Nie je to chyba streamu.
|
|
*/
|
|
if (packet.JmsMessage.Command == CommandM.start_stream_images_)
|
|
{
|
|
log.Debug( $"Ignoring delayed start stream response: cameraIdx={cameraIdx}");
|
|
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();
|
|
}
|
|
|
|
/*
|
|
* Dôležité:
|
|
* ak stream skončí timeoutom, socket chybou alebo výnimkou,
|
|
* UI musí povoliť nový pokus o spustenie.
|
|
*/
|
|
stopLiveStreamListenerFlag = true;
|
|
lockBrich = CameraUICmd.None;
|
|
|
|
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}");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|