From 891795b101b3cb592b06bfca539db74a780b8151 Mon Sep 17 00:00:00 2001 From: Michal Buzik Date: Wed, 21 May 2025 11:31:19 +0200 Subject: [PATCH] Add support for CJMS11 camera integration Introduced the CJMS11 camera component and its factory in TbfComponents. Added implementation for the CJMS11 camera class, along with unit tests to validate its functionality and integration. This enhances existing camera support in the system. --- TBF.sln.DotSettings.user | 33 +- TBF/Rig/Network/Camera/CJMS11/Camera.cs | 999 ++++++++++++++++++ TBF/Rig/Network/Camera/CJMS11/CameraCfg.cs | 74 ++ .../Network/Camera/CJMS11/CameraCfgCtrl.cs | 276 +++++ .../Camera/CJMS11/CameraCfgCtrl.designer.cs | 260 +++++ .../Network/Camera/CJMS11/CameraCfgCtrl.resx | 120 +++ TBF/Rig/Network/Camera/CJMS11/Factory.cs | 25 + TBF/Rig/Network/Camera/CJMS11/GrabImagesOp.cs | 224 ++++ TBF/Rig/Network/Camera/CJMS11/JmsMessage.cs | 54 + TBF/Rig/Network/Camera/CJMS11/JmsPacket.cs | 90 ++ TBF/Rig/Network/Camera/CJMS11/LiveStreamOp.cs | 68 ++ .../Network/Camera/CJMS11/POJO/CommandM.cs | 22 + .../Camera/CJMS11/POJO/CommandMEnum.cs | 44 + .../Network/Camera/CJMS11/POJO/ImageType.cs | 10 + .../Camera/CJMS11/POJO/ImageTypeEnum.cs | 31 + .../Camera/CJMS11/POJO/MessageStatus.cs | 10 + .../Camera/CJMS11/POJO/MessageStatusEnum.cs | 31 + .../Network/Camera/CJMS11/POJO/ParsedImage.cs | 38 + .../Network/Camera/CJMS11/RoisAndResults.cs | 91 ++ .../Camera/CJMS11/TerminalDlg.Designer.cs | 62 ++ TBF/Rig/Network/Camera/CJMS11/TerminalDlg.cs | 37 + .../Network/Camera/CJMS11/TerminalDlg.resx | 120 +++ TBF/Rig/Network/Camera/Roi/RoiCfgCtrl.cs | 6 +- .../Camera/RoiForFixedStartCJMS11/Factory.cs | 25 + .../RoiForFixedStartCJMS11/ProcedureParams.cs | 115 ++ .../Camera/RoiForFixedStartCJMS11/Roi.cs | 139 +++ .../Camera/RoiForFixedStartCJMS11/RoiCfg.cs | 56 + .../RoiForFixedStartCJMS11/RoiCfgCtrl.cs | 175 +++ .../RoiCfgCtrl.designer.cs | 185 ++++ .../RoiForFixedStartCJMS11/RoiCfgCtrl.resx | 120 +++ TBF/Rig/TbfComponents.cs | 2 + TBF/TBF.csproj | 46 + .../Rig/Network/Camera/CJMS11/CameraTest.cs | 30 + TBFTests/TBFTests.csproj | 1 + .../TBFTests.csproj.CoreCompileInputs.cache | 2 +- 35 files changed, 3606 insertions(+), 15 deletions(-) create mode 100644 TBF/Rig/Network/Camera/CJMS11/Camera.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/CameraCfg.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.designer.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.resx create mode 100644 TBF/Rig/Network/Camera/CJMS11/Factory.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/GrabImagesOp.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/JmsMessage.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/JmsPacket.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/LiveStreamOp.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/POJO/CommandM.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/POJO/CommandMEnum.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/POJO/ImageType.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/POJO/ImageTypeEnum.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/POJO/MessageStatus.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/POJO/MessageStatusEnum.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/POJO/ParsedImage.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/RoisAndResults.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/TerminalDlg.Designer.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/TerminalDlg.cs create mode 100644 TBF/Rig/Network/Camera/CJMS11/TerminalDlg.resx create mode 100644 TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/Factory.cs create mode 100644 TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/ProcedureParams.cs create mode 100644 TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/Roi.cs create mode 100644 TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfg.cs create mode 100644 TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.cs create mode 100644 TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.designer.cs create mode 100644 TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.resx create mode 100644 TBFTests/Rig/Network/Camera/CJMS11/CameraTest.cs diff --git a/TBF.sln.DotSettings.user b/TBF.sln.DotSettings.user index 651fe81c0..e90914110 100644 --- a/TBF.sln.DotSettings.user +++ b/TBF.sln.DotSettings.user @@ -1,18 +1,29 @@  + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded True 77EB589F-C670-4489-AAD6-2A3C02061FD1 77EB589F-C670-4489-AAD6-2A3C02061FD1 d6790ab7-33c2-4425-b2c9-51480cd1a852 - <SessionState ContinuousTestingMode="0" Name="GetCorrection" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="GetCorrection" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <TestAncestor> <TestId>MSTest::77EB589F-C670-4489-AAD6-2A3C02061FD1::.NETFramework,Version=v4.7.2::TBFTests.Entities.MeasurementCorrectionTest.GetCorrection</TestId> <TestId>MSTest::77EB589F-C670-4489-AAD6-2A3C02061FD1::.NETFramework,Version=v4.7.2::TBFTests.Rig.Modbus.Meret.AdjustableScale.AdjustableMeterTest.GetCorrection</TestId> <TestId>MSTest::77EB589F-C670-4489-AAD6-2A3C02061FD1::.NETFramework,Version=v4.7.2::TBFTests.Rig.Network.Camera.KeyenceIV3G120.CameraTest.RtpListener</TestId> <TestId>MSTest::77EB589F-C670-4489-AAD6-2A3C02061FD1::.NETFramework,Version=v4.7.2::TBFTests.Rig.Network.Camera.KeyenceIV3G120.CameraTest.Initialize</TestId> + <TestId>MSTest::77EB589F-C670-4489-AAD6-2A3C02061FD1::.NETFramework,Version=v4.7.2::TBFTests.Rig.Network.Camera.CJMS11.CameraTest.Initialize</TestId> </TestAncestor> </SessionState> - <SessionState ContinuousTestingMode="0" IsActive="True" Name="Initialize" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" Name="Initialize" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <TestAncestor> <TestId>MSTest::77EB589F-C670-4489-AAD6-2A3C02061FD1::.NETFramework,Version=v4.7.2::TBFTests.Rig.Network.Camera.KeyenceIV3G120.CameraTest.Initialize</TestId> </TestAncestor> @@ -22,17 +33,21 @@ False - True + False False - True + False False - True - True - True + False + False + False - True + False + True + False + + False False - True + False True False diff --git a/TBF/Rig/Network/Camera/CJMS11/Camera.cs b/TBF/Rig/Network/Camera/CJMS11/Camera.cs new file mode 100644 index 000000000..589b442c3 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/Camera.cs @@ -0,0 +1,999 @@ +/// +/// Copyright (c) 2017-2022 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using log4net; +using Renci.SshNet; +using Common; +using TBF.Rig.Network.Telnet; +using TBF.Resources; +using TBF.Rig.Network.Camera.CJMS11.POJO; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + /// + /// This class implements: (1) Camera device, (2) Measurement operation + /// + 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 static Object fileUploadLock = new Object(); + + + /// + /// Properties + /// + public readonly CameraCfg CameraCfg; + public readonly GenericDevices.INetworkAdapter NetAdapter; + + 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 2; + } //nextCameraIdx++; } /// Used in Initialize() + + /// + /// Telnet support + /// + const string TelnetPrompt = "$ "; + const bool TelnetSkipFirstLine = true; + public Telnet.TelnetClient Telnet; + private TerminalDlg terminalDlg; + string sha1sumResponse; + bool sha1sumResponseProcessed; + + /// + /// scp support + /// + Thread scpThread; + PasswordConnectionInfo connectionInfo; + bool scpConnected; + bool stopScpThreadFlag; + string scpRemoteFileName; /// Camera file name + string scpLocalFileName; /// Local PC file name + enum ScpCommand + { + None, + Get, + Put, + } + ScpCommand scpCommand; + + + const int UdpPortBase = 20000; /// Port base for all UDP protocols + + /// + /// MJpeg RTP protocol support + /// + int rtpTcpLocalPort; + TcpClient rtpTcpClient; + Thread rtpListenerThread; + NetworkStream rtpStream = null; + StreamReader rtpReader = null; + static bool stopRtpListenerFlag; + + /// + /// RTCP protocol (reserved) + /// + int rtcpUdpLocalPort; + + /// + /// Measurement results protocol + /// + int msrmntUdpLocalPort; + UdpClient msrmntUdpClient; /// If != null msrmntListenerThread + Thread msrmntListenerThread; + static bool stopMsrmntListenerFlag; + + + /// + /// 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 components) + : base(cfg) + { + CameraCfg = cfg as CameraCfg; + + NetAdapter = TbfComponents.FindComponent(cfg.ParentName, components) as GenericDevices.INetworkAdapter; + if (NetAdapter == null) throw new ArgumentNullException("no network adapter"); + } + + + public override void Initialize() + { + cameraIdx = GetCameraIdx(); + + running = false; + Telnet = null; + 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 + bool cameraDetected = DetectJMSCamera(CameraCfg.HardwareAddress % 100, ref ipAddress, true, + out cpuHardware, out cpuRevision, out cpuSerialNr, out sdCardVer); + + if (CameraCfg.DebugLevel == DebugMode.AutoDetect) + { + CameraCfg.DebugLevel = cameraDetected ? DebugMode.DetectedOn : DebugMode.DetectedOff; + } + else if (!cameraDetected) + { + throw new Exception(string.Format("{0}={1}", Strings.Address, CameraCfg.HardwareAddress)); + } + + if (cameraDetected) + { + UiBridge.Bridge.OnCameraInfo(cameraIdx, string.Format("{0} ver.{1} {2} s/n {3}", Name, sdCardVer, ipAddress, CameraCfg.HardwareAddress)); + + ClpUserName = (sdCardVer.Length > 0 && sdCardVer[0] == '1') ? "tbf" : "pi"; + + StartRtpListener(IPAddress, iPort); + StartMsrmntListener(IPAddress, msrmntUdpLocalPort); + + connectionInfo = new PasswordConnectionInfo(IPAddress.ToString(), ClpUserName, ClpPassword); + + /// Open telnet clint and start the log-in process + Telnet = new Telnet.TelnetClient(this, CameraCfg.Name, ClpUserName, ClpPassword, TelnetPrompt, TelnetSkipFirstLine, CameraCfg.DisplayTerminal); + + Telnet.promptReceivedHandler += delegate(object sndr, PromptReceivedEventArgs a) + { + OnPromptReceived(sndr, a); + }; + + if (CameraCfg.DisplayTerminal) + { + terminalDlg = new TerminalDlg(CameraCfg.Name, Telnet); + terminalDlg.Show(); + } + + Telnet.Enqueue(new Telnet.Command(Network.Telnet.CmdAction.CONNECT, ipAddress.ToString(), 10, 30)); + Telnet.Enqueue(new Telnet.Command(Network.Telnet.CmdAction.CLEAR_RESPONSE)); + Telnet.Enqueue(new Telnet.Command(Network.Telnet.CmdAction.SEND_COMMAND, "sha1sum clp/*")); + + log.FatalFormat("'{0}' detected: Id={1} addr={2} IP={3} SD={4}", Name, CameraIdx, CameraCfg.HardwareAddress, ipAddress, sdCardVer); + } + 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); + } + } + + public void StopDevice() + { + if ((CameraCfg.DebugLevel != DebugMode.Simulate) && (CameraCfg.DebugLevel != DebugMode.DetectedOff)) + { + running = false; + + stopScpThreadFlag = true; + stopRtpListenerFlag = true; + stopMsrmntListenerFlag = true; + + if (Telnet != null) + { + Telnet.Dispose(); + Telnet = null; + } + } + } + + public void StopDevice2() + { + if ((CameraCfg.DebugLevel != DebugMode.Simulate) && (CameraCfg.DebugLevel != DebugMode.DetectedOff)) + { + if (scpThread != null && scpThread.IsAlive) scpThread.Join(50); + if (rtpListenerThread != null) rtpListenerThread.Join(50); + if (msrmntListenerThread != null) msrmntListenerThread.Join(50); + + if (rtpTcpClient != null) + { + rtpTcpClient.Close(); + rtpTcpClient = null; + } + + if (msrmntUdpClient != null) + { + msrmntUdpClient.Close(); + msrmntUdpClient = null; + } + } + } + + public void RunDeviceBefore() + { + if ((CameraCfg.DebugLevel != DebugMode.Simulate) && (CameraCfg.DebugLevel != DebugMode.DetectedOff)) + { + if (sha1sumResponse != null && !sha1sumResponseProcessed) + { + scpThread = new Thread(new ThreadStart(ScpWorker)); + scpThread.Start(); + + sha1sumResponseProcessed = true; + } + } + } + public void RunDeviceAfter() { } + + + void OnPromptReceived(object sndr, PromptReceivedEventArgs a) + { + if (sha1sumResponse == null) + { + sha1sumResponse = a.Response; + } + } + + + #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 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 + + + /// + /// Detect the camera with specified hardware address (DIP switches). + /// Optionally set camera date and time. + /// + /// Hardware address (DIP switches) + /// true = Broadcast 'SetDateTime' to all cameras + /// Detected IP address + /// Detected hardware + /// Detected HW revision + /// Detected serial number + /// Detected OS image + /// true when camera detected successfully + bool DetectCamera(int hwAddress, ref IPAddress ipAddress, bool setDateTime, out string hardware, out string revision, out string serial, out string sdCardVer) + { + for (int trials = 1; trials <= 3; trials++) + { + UdpClient udpClient = new UdpClient(); + + try + { + if (setDateTime) + { + SetDateTime(udpClient, DateTime.Now); + } + + /// + /// Send a request for camera information + /// + DateTime detectionStart = DateTime.Now; + CameraInfoRequest(udpClient, hwAddress); + + /// + /// Receive a response or timeout + /// + IAsyncResult asyncRslt = udpClient.BeginReceive(null, null); + if (asyncRslt.AsyncWaitHandle.WaitOne(500)) + { + /// Response received within time limit + IPEndPoint cameraIPEndpoint = new IPEndPoint(IPAddress.Any, 1852); + byte[] inputBuffer = udpClient.EndReceive(asyncRslt, ref cameraIPEndpoint); + + double duration_ms = (DateTime.Now - detectionStart).TotalMilliseconds; + ipAddress = cameraIPEndpoint.Address; + + udpClient.Close(); + + string response = Encoding.ASCII.GetString(inputBuffer, 0, inputBuffer.Length); + string[] strArr = response.Split(new char[] { ' ', '=' }); + if (strArr.Length == 10 && strArr[0] == "Address" && strArr[2] == "Hardware" && + strArr[4] == "Revision" && strArr[6] == "Serial" && strArr[8] == "Image") + { + hardware = strArr[3]; + revision = strArr[5]; + serial = strArr[7]; + sdCardVer = strArr[9]; + } + else if (strArr.Length == 8 && strArr[0] == "Address" && strArr[2] == "Hardware" && + strArr[4] == "Revision" && strArr[6] == "Serial") + { + hardware = strArr[3]; + revision = strArr[5]; + serial = strArr[7]; + sdCardVer = string.Empty; + } + else + { + continue; + } + + string msg = string.Format("Camera {0} detected in {1} ms: HWAddress={2} IPAddress={3} {4}", + CameraCfg.Name, + duration_ms, + CameraCfg.HardwareAddress, + ipAddress == null ? "not detected" : ipAddress.ToString(), + response); + log.Debug(msg); + Console.WriteLine(msg); + + return true; /// Camera detected + } + } + catch (Exception exc) + { + log.ErrorFormat("Camera {0} with HWAddress={1} not detected : {2}", CameraCfg.Name, CameraCfg.HardwareAddress, exc.Message); + } + + udpClient.Close(); + } + + ipAddress = null; + hardware = null; + revision = null; + serial = null; + sdCardVer = null; + return false; /// No camera detected + } + + bool DetectJMSCamera(int hwAddress, ref IPAddress ipAddress, bool setDateTime, out string hardware, out string revision, out string serial, out string sdCardVer) + { + string command_CFG = "get_cfg"; + if (CameraCfg.IPAddressCJMS != null) + { + TcpClient tcpClient = new TcpClient(); + // Get network stream + NetworkStream stream = null; + StreamReader reader = null; + + try + { + + + tcpClient.Connect(CameraCfg.IPAddressCJMS, iPort); + log.Debug(string.Format("Camera connected to server {0}:{1}.", CameraCfg.IPAddressCJMS, iPort)); + ipAddress = IPAddress.Parse(CameraCfg.IPAddressCJMS); + // Get network stream + stream = tcpClient.GetStream(); + stream.ReadTimeout = 1000; + reader = new StreamReader(stream, Encoding.ASCII); + + // Send command + DateTime detectionStart = DateTime.Now; + byte[] commandBytes = Encoding.ASCII.GetBytes(command_CFG); + stream.Write(commandBytes, 0, commandBytes.Length); + Console.WriteLine("Command sent."); + + + string responseAnswer = ""; + try + { + int iReads = 0; + while (true) + { + //get config + responseAnswer = reader.ReadLine(); + log.Debug(string.Format("received from camera: {0}", responseAnswer)); + + + if (responseAnswer.Contains(command_CFG)) + { + break; + } + + if (iReads > 5) + { + log.Error($"Maximum Count of unexcepted answers excited!"); + break; + } + + iReads++; + } + } + catch (IOException ioEx) + { + log.Error($"Timeout or IO error while reading from camera: {ioEx.Message}"); + } + catch (Exception e) + { + log.Error(string.Format("Error while reading from camera: {0}", e.Message)); + } + + double duration_ms = (DateTime.Now - detectionStart).TotalMilliseconds; + + hardware = string.Empty; + revision = string.Empty; + serial = string.Empty; + sdCardVer = string.Empty; + + string msg = string.Format("Camera {0} detected in {1} ms: HWAddress={2} IPAddress={3} {4}", + CameraCfg.Name, + duration_ms, + CameraCfg.HardwareAddress, + ipAddress == null ? "not detected" : ipAddress.ToString(), + responseAnswer); + log.Debug(msg); + Console.WriteLine(msg); + + return true; /// Camera detected + } + catch (Exception exc) + { + log.ErrorFormat("Camera {0} with HWAddress={1} not detected : {2}", CameraCfg.Name, + CameraCfg.HardwareAddress, exc.Message); + } + finally + { + // Clean up + if (reader != null) + reader.Close(); + if (stream != null) + stream.Close(); + if (tcpClient.Connected) + tcpClient.Close(); + } + + + } + + if (CameraCfg.DebugLevel == DebugMode.Simulate) + { + ipAddress = null; + hardware = null; + revision = null; + serial = null; + sdCardVer = null; + return true; + } + + ipAddress = null; + hardware = null; + revision = null; + serial = null; + sdCardVer = null; + return false; /// No camera detected + } + + void CameraInfoRequest(UdpClient udpClient, int hardwareAddress) + { + string strToSend = string.Format("getinfo {0}", hardwareAddress); + byte[] dataToSend = Encoding.ASCII.GetBytes(strToSend); + udpClient.Send(dataToSend, dataToSend.Length, new IPEndPoint(NetAdapter.BroadcastAddress, 1852)); + } + + void SetDateTime(UdpClient udpClient, DateTime dateTime) + { + string strToSend = string.Format("setdatetime {0:yyMMddHHmmss}", dateTime); + byte[] dataToSend = Encoding.ASCII.GetBytes(strToSend); + udpClient.Send(dataToSend, dataToSend.Length, new IPEndPoint(NetAdapter.BroadcastAddress, 1852)); + } + + + + void ScpWorker() + { + try + { + using (var scpClient = new ScpClient(connectionInfo)) + { + scpClient.Connect(); + + DirectoryInfo dirInfo = null; + if (sdCardVer.Length > 0) + { + switch (sdCardVer[0]) + { + case '1': + dirInfo = new DirectoryInfo(Path.Combine(System.Windows.Forms.Application.StartupPath, "CameraBinaryFiles")); + break; + case '2': + dirInfo = new DirectoryInfo(Path.Combine(System.Windows.Forms.Application.StartupPath, "CameraBinaryFiles2")); + break; + } + } + + if (dirInfo != null) + { + /// + /// Synchronize files in camera /home/tbf/clp directory with the files in "TBF/CameraBinaryFiles" project folder + /// + using (var sshClient = new SshClient(connectionInfo)) + { + sshClient.Connect(); + + SHA1 sha1calculator = SHA1.Create(); + StringBuilder sha1str = new StringBuilder(); + + FileInfo[] fileInfos = dirInfo.GetFiles(); + foreach (FileInfo fi in fileInfos) + { + //lock (fileUploadLock) + //{ + // FileStream fs = fi.Open(FileMode.Open); + // fs.Position = 0; + // byte[] sha1 = sha1calculator.ComputeHash(fs); + // fs.Close(); + + // sha1str.Clear(); + // foreach (var b in sha1) sha1str.Append(b.ToString("x2")); + + // if (!sha1sumResponse.Contains(string.Format("{0} clp/{1}", sha1str, fi.Name))) + // { + // /// Copy a file to the camera if SHA1 sum is different of the file is missing + // log.InfoFormat("{0}, IP={1}, Uploading file {2}", Name, IPAddress, fi.Name); + // scpClient.Upload(fi, string.Format("~/clp/{0}", fi.Name)); + // } + //} + + //if (!sha1sumResponse.Contains(fi.Name)) + //{ + // /// Change file properties if it was a new file + // string command = string.Format("chmod 755 ~/clp/{0}", fi.Name); + // log.InfoFormat("{0}, IP={1}, Executing command '{2}'", Name, IPAddress, command); + // sshClient.CreateCommand(command).Execute(); + + // //if (fi.Name.Equals("runmeonce")) + // //{ + // // log.InfoFormat("{0}, IP={1}, Executing command 'sudo clp/runmeonce'", Name, IPAddress); + // // sshClient.CreateCommand("sudo clp/runmeonce").Execute(); + // //} + //} + } + } + } + + scpConnected = scpClient.IsConnected; + running = true; + + /// + /// Run SCP command loop + /// + while (scpConnected && !stopScpThreadFlag) + { + switch (scpCommand) + { + case ScpCommand.Get: + scpCommand = ScpCommand.None; + scpClient.Download(scpRemoteFileName, new System.IO.FileInfo(scpLocalFileName)); + break; + + case ScpCommand.Put: + scpCommand = ScpCommand.None; + scpClient.Upload(new System.IO.FileInfo(scpLocalFileName), scpRemoteFileName); + break; + + default: + Thread.Sleep(250); + break; + } + + scpConnected = scpClient.IsConnected; + } + + running = false; + scpClient.Disconnect(); + scpConnected = false; + } + } + catch (Exception exc) + { + log.ErrorFormat( "Exception in {0} ScpWorker(): {1}", Name, exc.Message); + if (exc.InnerException != null) + { + log.ErrorFormat(" Inner exception: {0}", exc.InnerException.Message); + } + + scpConnected = false; + } + } + + /// + /// Download a file from the camera via SCP + /// + /// + /// 0 (success), -1 (busy), -2 (no session) + public int DownloadFile(string srcFileName, string dstFileName) + { + if (!scpConnected) return -2; + if (scpCommand != ScpCommand.None) return -1; + + scpRemoteFileName = srcFileName; + scpLocalFileName = dstFileName; + scpCommand = ScpCommand.Get; + return 0; + } + + /// + /// Upload a file to the camera via SCP + /// + /// + /// 0 (success), -1 (busy), -2 (no session) + public int UploadFile(string srcFileName, string dstFileName) + { + if (!scpConnected) return -2; + if (scpCommand != ScpCommand.None) return -1; + + scpLocalFileName = srcFileName; + scpRemoteFileName = dstFileName; + scpCommand = ScpCommand.Put; + return 0; + } + + + /// + /// listener for multiple - repetetive image receiving + /// + /// + /// method uses reader to receive data from camera + /// + /// + /// + + private void StartRtpListener(IPAddress localIP, int localPort) + { + rtpTcpClient = new TcpClient(new IPEndPoint(localIP, localPort)); + rtpListenerThread = new Thread(new ThreadStart(RtpListener)); + rtpListenerThread.Start(); + } + + void RtpListener() + { + JpegFrame jpegFrame = new JpegFrame(); + bool synced = false; + int penWidth = 1; + Pen pen = new Pen(Color.Green, penWidth); + + rtpStream = rtpTcpClient.GetStream(); + rtpStream.ReadTimeout = 1000; + rtpReader = new StreamReader(rtpStream, Encoding.ASCII); + + while (!stopRtpListenerFlag) + { + try + { + string responseAnswer = rtpReader.ReadLine(); + + if (responseAnswer.Length > 0) + { + /// Process the packet + JmsPacket newPacket = new JmsPacket(responseAnswer); + // if (synced) + // { + /// Sychronized: process the new packet + switch (newPacket.JmsMessage.Command) + { + case CommandM.grab_image_: + ParsedImage parsedImage = newPacket.JmsMessage.getImage(); + if (parsedImage.Encoding == ImageType.BASE_64) + { + Image image = parsedImage.Image; + + + if (showOverlayRect && overlayRect != null && overlayThickness > 0) + { + using (var grph = Graphics.FromImage(image)) + { + if (penWidth != overlayThickness) + { + penWidth = overlayThickness; + pen = new Pen(Color.Green, penWidth); + } + + grph.DrawRectangle(pen, overlayRect); + } + } + + UiBridge.Bridge.OnImage(this.cameraIdx, image); + } + break; + case CommandM.close_: + synced = false; + break; + default: + break; + } + //} + // else if (newPacket.Mark) + // { + // /// Synchronize when a packet with Mark occurs, process the next packet + // jpegFrame.Clear(); + // synced = true; + // } + // else + // { + // /// Stay unsynchronized + // } + } + } + catch (Exception exc) + { + string msg = string.Format("Exception in MJpeg RTP listener thread: {0}", exc.Message); + log.Error(msg); + } + } + + return; + } + + + + private void StartMsrmntListener(IPAddress localIP, int localPort) + { + msrmntUdpClient = new UdpClient(new IPEndPoint(localIP, localPort)); + msrmntListenerThread = new Thread(new ThreadStart(MsrmntListener)); + msrmntListenerThread.Start(); + } + + void MsrmntListener() + { + StringBuilder toBeProcessed = new StringBuilder(); + int[] angles = new int[RoisAndResults.MaxRegisteredRois]; + + while (!stopMsrmntListenerFlag) + { + try + { + IPEndPoint endpoint = null; + Byte[] data = msrmntUdpClient.Receive(ref endpoint); + + toBeProcessed.Append(Encoding.ASCII.GetString(data, 0, data.Length)); + log.DebugFormat("MeasurementUdpListener() ... toBeProcessed = {0}", toBeProcessed); + + while (true) + { + int from = toBeProcessed.ToString().IndexOf('['); + if (from < 0) break; + + int len = toBeProcessed.ToString(from + 1, toBeProcessed.Length - from - 1).IndexOf(']'); + if (len < 0) break; + + string[] pulsesArr = toBeProcessed.ToString(from + 1, len).Split(new char[] { ';' }); + + bool fmtError = false; + long timeMs; + if ((pulsesArr.Length >= 2) && + (pulsesArr.Length <= RoisAndResults.MaxRegisteredRois + 1) && + long.TryParse(pulsesArr[0], out timeMs)) + { + for (int i = 1; i < pulsesArr.Length; i++) + { + int val; + if (int.TryParse(pulsesArr[i], out val)) + { + angles[i - 1] = Math.Abs(val); /// TODO: pozerat CW/CCW a podla toho dat +/- + } + else + { + fmtError = true; + } + } + + if (!fmtError) roisAndResults.SetResults(timeMs, angles); + } + + toBeProcessed.Remove(0, from + len + 2); + } + } + catch (Exception exc) + { + string msg = string.Format("Exception in the measurement listener thread: {0}", exc.Message); + log.Error(msg); + } + } + + return; + } + + + //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("/opt/vc/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, rtpTcpLocalPort); + //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, Telnet, command); + } + + + public IOperation GrabImagesOp(string[] imgFileNames, bool blackAndWhite, bool lowResolution, ImageRotation imageRotation) + { + return new GrabImagesOp(this, imgFileNames, blackAndWhite, lowResolution, imageRotation); + } + + + public IOperation MeasurementOp() + { + if (Telnet != null) + { + return this; + } + else + { + return null; + } + } + + string command; + bool measurementCommandSent; + + + public void Start() + { + /// Prepare the telent command + StringBuilder sb = new StringBuilder(); + for (int i = 1; i <= roisAndResults.RegisteredRoisCount; i++) + { + sb.Append(" "); + sb.Append(roisAndResults.GetRoiParams(i)); + } + + int logImagesArg; + switch (CameraCfg.TestImagesMode) + { + default: + case TestImagesMode.None: + case TestImagesMode.LoadImages: + logImagesArg = 0; + break; + case TestImagesMode.SaveFirstImages: + logImagesArg = CameraCfg.TestImagesCount; + break; + case TestImagesMode.SaveLastImages: + logImagesArg = -CameraCfg.TestImagesCount; + break; + } + + command = string.Format("clp/Measurement -udp {0} {1}{2}{3} 1 7 0 0 {4}", + NetAdapter.IPAddress, + msrmntUdpLocalPort, + (CameraCfg.TestImagesMode == TestImagesMode.LoadImages) ? (" -l " + CameraCfg.TestImagesCount.ToString()) : string.Empty, + sb, + logImagesArg); + + measurementCommandSent = false; + + /// If ready send (=enqueue) the command + if (Telnet.State == TelnetClient.TelnetState.Inactive) + { + log.WarnFormat("{0} SEND_MSRMNT_CMD: {1}", Name, command); + Telnet.Enqueue(new Telnet.Command(CmdAction.SEND_MSRMNT_CMD, command)); + measurementCommandSent = true; + } + } + + public Event Run() + { + if (!measurementCommandSent) + { + /// When ready send (=enqueue) the command + if (Telnet.State == TelnetClient.TelnetState.Inactive) + { + log.WarnFormat("{0} SEND_MSRMNT_CMD: {1}", Name, command); + Telnet.Enqueue(new Telnet.Command(CmdAction.SEND_MSRMNT_CMD, command)); + measurementCommandSent = true; + } + } + + return Event.None; + } + + public void Stop() + { + if (measurementCommandSent) + { + log.WarnFormat("{0} SEND_COMMAND: CTRL-Q", Name); + Telnet.Enqueue(new Telnet.Command(CmdAction.SEND_COMMAND, TelnetClient.Q_Command)); + } + } + } +} diff --git a/TBF/Rig/Network/Camera/CJMS11/CameraCfg.cs b/TBF/Rig/Network/Camera/CJMS11/CameraCfg.cs new file mode 100644 index 000000000..256e60025 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/CameraCfg.cs @@ -0,0 +1,74 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Xml.Serialization; +using Common; +using TBF.Rig.Generic; +using TBF.Resources; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public enum TestImagesMode + { + None, + SaveFirstImages, + SaveLastImages, + LoadImages, + Count, + Invalid, + } + + public class CameraCfg : ComponentCfgBase, IChildComponentCfg + { + public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(CameraCfg) })[0]; + public override XmlSerializer GetSerializer() { return Serializer; } + + public IComponentCfgCtrl GetControl(IList cmpntEntities) { return new CameraCfgCtrl(); } + + /// + /// Serialized parameters + /// + public int HardwareAddress; + public bool DisplayTerminal; + + [XmlIgnore] + public TestImagesMode TestImagesMode; + + [XmlIgnore] + public int TestImagesCount; + + public string IPAddressCJMS; + + + /// Private parameterless constructor invoked by all other (public) constructors + CameraCfg() { } + + public CameraCfg(string name, IComponentFactory factory) + : this() + { + Name = name; + Factory = factory; + ParentName = "Network.Adapter"; + HardwareAddress = 1; + TestImagesMode = TestImagesMode.None; + TestImagesCount = 3000; + DebugLevel = DebugMode.AutoDetect; + IPAddressCJMS = "192.168.1.100"; + } + + public string ToString(int i) + { + return string.Format("Name={0}, s/n={1}, Terminal={2}, TestImagesMode={3}, TestImagesCount={4}, Parent={5}, IPAddressCJMS={6}", + Name, + HardwareAddress, + DisplayTerminal ? Strings.yes : Strings.no, + TestImagesMode, + TestImagesCount, + ParentName, + IPAddressCJMS); + } + } +} diff --git a/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.cs b/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.cs new file mode 100644 index 000000000..d935c5f31 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.cs @@ -0,0 +1,276 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// + +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.RegularExpressions; +using System.Windows.Forms; +using Castle.Components.DictionaryAdapter.Xml; +using Common; +using Config.Entities; +using log4net; +using TBF.Rig.Generic; +using TBF.Resources; +using TBF.Rig.Network.Adapter; +using TBF.UI.Bench.Components; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public partial class CameraCfgCtrl : UserControl, IComponentCfgCtrl + { + static readonly ILog log = LogManager.GetLogger(typeof(CameraCfgCtrl)); + ComponentParametersDlg parent; + + private bool isIPCalculated = false; + + public bool ShowMore + { + get { return false; } + } + + CameraCfg config; + + public IComponentCfg Config + { + get { return config as IComponentCfg; } + set + { + config = value as CameraCfg; + Redraw(); + } + } + + public CameraCfgCtrl() + { + InitializeComponent(); + } + + private void PumpCfgCtrl_Load(object sender, EventArgs e) + { + Localize(); + + parent = ParentForm as ComponentParametersDlg; + if (parent == null) return; + + if (parent.CmpntEntities != null) + { + foreach (var cmpnt in parent.CmpntEntities) + { + if (TbfComponents.CmpntFactoryFromClassName(cmpnt.ClassName) is TBF.Rig.Network.Adapter.Factory) + { + parentNameComboBox.Items.Add(cmpnt.Name); + } + } + } + + for (TestImagesMode mode = 0; mode < TestImagesMode.Count; mode++) + { + testImagesModeComboBox.Items.Add(mode.ToString()); + } + + Redraw(); + } + + void Localize() + { + nameLabel.Text = Strings.Name; + parentNameLabel.Text = Strings.Parent_name; + hwAddressLabel.Text = Strings.Serial_number; + } + + public void Closing() + { + } + + void Redraw() + { + if (config == null) return; /// Control was not loaded, settings were not changed + + classNameLabel.Text = config.Factory.ClassName; + nameTextBox.Text = config.Name; + parentNameComboBox.Text = string.IsNullOrEmpty(config.ParentName) ? "---" : config.ParentName; + hwAddressTextBox.Text = config.HardwareAddress.ToString(); + displayTerminalCheckBox.Checked = config.DisplayTerminal; + testImagesModeComboBox.Text = config.TestImagesMode.ToString(); + testImagesCountTextBox.Text = config.TestImagesCount.ToString(); + if (!(isIPCalculated && (config.IPAddressCJMS == null || config.IPAddressCJMS.Equals("")))) + { + ipAddressTextBox.Text = config.IPAddressCJMS; + } + } + + public void Unlock() + { + nameTextBox.Enabled = true; + parentNameComboBox.Enabled = true; + hwAddressTextBox.Enabled = true; + displayTerminalCheckBox.Enabled = true; + testImagesModeComboBox.Enabled = true; + testImagesCountTextBox.Enabled = true; + ipAddressTextBox.Enabled = true; + } + + public CfgUpdateFlags VerifyCfg(ref string message) + { + CfgUpdateFlags flags = CfgUpdateFlags.None; + + int dummy; + if (!parentNameComboBox.Items.Contains(parentNameComboBox.Text)) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + string.Format(Strings.Invalid_0, parentNameLabel.Text); + } + + if (!int.TryParse(hwAddressTextBox.Text, out dummy) || dummy % 100 > 63) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + string.Format(Strings.Invalid_0, hwAddressLabel.Text); + } + + TestImagesMode newTestImagesMode = TestImagesMode.Invalid; + for (TestImagesMode mode = 0; mode < TestImagesMode.Count; mode++) + { + if (testImagesModeComboBox.Text == mode.ToString()) newTestImagesMode = mode; + } + + if (newTestImagesMode == TestImagesMode.Invalid) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + string.Format(Strings.Invalid_0, testImagesModeLabel.Text); + } + + if (!int.TryParse(testImagesCountTextBox.Text, out dummy) || dummy < 0 || dummy > 3000 + || ((dummy == 0) && (newTestImagesMode != TestImagesMode.None))) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + string.Format(Strings.Invalid_0, testImagesCountLabel.Text); + } + + + return flags; + } + + public CfgUpdateFlags UpdateCfg() + { + CfgUpdateFlags flags = CfgUpdateFlags.None; + + if (config == null) return CfgUpdateFlags.Error; /// Control was not loaded, settings were not changed + + string newParentName = parentNameComboBox.Text.Equals("---") ? string.Empty : parentNameComboBox.Text; + int newHWAddress = int.Parse(hwAddressTextBox.Text); + TestImagesMode newTestImagesMode = 0; + for (TestImagesMode mode = 0; mode < TestImagesMode.Count; mode++) + { + if (testImagesModeComboBox.Text == mode.ToString()) newTestImagesMode = mode; + } + + int newTestImagesCount = int.Parse(testImagesCountTextBox.Text); + + + if (config.Name != nameTextBox.Text || + config.ParentName != newParentName || + config.HardwareAddress != newHWAddress || + config.DisplayTerminal != displayTerminalCheckBox.Checked || + (config.IPAddressCJMS != ipAddressTextBox.Text && !ipAddressTextBox.Text.Contains("?")) + ) + { + config.Name = nameTextBox.Text; + config.ParentName = newParentName; + config.HardwareAddress = newHWAddress; + config.DisplayTerminal = displayTerminalCheckBox.Checked; + if (!ipAddressTextBox.Text.Contains("?")) + { + config.IPAddressCJMS = ipAddressTextBox.Text; + } + + flags |= (CfgUpdateFlags.RestartRqrd | CfgUpdateFlags.AnyChange); + } + + + if (config.TestImagesMode != newTestImagesMode || + config.TestImagesCount != newTestImagesCount) + { + config.TestImagesMode = newTestImagesMode; + config.TestImagesCount = newTestImagesCount; + + flags |= (CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.VolatileChange); + } + + if ((flags & CfgUpdateFlags.InvokeCfgChange) != 0) + { + Camera.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.CfgChange, config)); + } + + return flags; + } + + + private void parentNameComboBox_TextChanged(object sender, EventArgs e) + { + if (parent == null) return; + + if (parent.CmpntEntities != null) + { + foreach (var cmpnt in parent.CmpntEntities) + { + IComponentFactory cmpntFactoryFromClassName = + TbfComponents.CmpntFactoryFromClassName(cmpnt.ClassName); + if (cmpntFactoryFromClassName is TBF.Rig.Network.Adapter.Factory) + { + NetadapterCfg netadapterCfg = + cmpntFactoryFromClassName.CmpntCfgFromCmpntEntity(cmpnt) as NetadapterCfg; + string adapterDescription = netadapterCfg.Description; + AdapterInfo netadapter = AdapterInfo.GetNetAdapter(adapterDescription); + ipAddressAdapterTextBox.Text = netadapter.IPAddress.ToString(); + if (config.IPAddressCJMS == null || config.IPAddressCJMS.Equals("")) + { + ipAddressTextBox.Text = getAdatpterIP(netadapter); + } + + isIPCalculated = true; + break; + } + } + } + } + + private string getAdatpterIP(AdapterInfo netadapter) + { + List ipSegments = GetIPAddressInSegments(netadapter.IPAddress.ToString()); + + string address = string.Format( + "{0}.{1}.{2}.???", + ipSegments[0], + ipSegments[1], + ipSegments[2] + ); + + return address; + } + + private List GetIPAddressInSegments(string ipAddress) + { + List ipAddressSegments = new List(); + // Regular expression to match IPv4 and capture 4 segments + string pattern = @"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$"; + + Match match = Regex.Match(ipAddress, pattern); + + if (match.Success) + { + ipAddressSegments.Add(match.Groups[1].Value); + ipAddressSegments.Add(match.Groups[2].Value); + ipAddressSegments.Add(match.Groups[3].Value); + ipAddressSegments.Add(match.Groups[4].Value); + } + else + { + log.Error("Invalid IP address format."); + } + + return ipAddressSegments; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.designer.cs b/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.designer.cs new file mode 100644 index 000000000..ab7ed8822 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.designer.cs @@ -0,0 +1,260 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +namespace TBF.Rig.Network.Camera.CJMS11 +{ + partial class CameraCfgCtrl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.hwAddressTextBox = new System.Windows.Forms.TextBox(); + this.hwAddressLabel = new System.Windows.Forms.Label(); + this.parentNameLabel = new System.Windows.Forms.Label(); + this.nameTextBox = new System.Windows.Forms.TextBox(); + this.nameLabel = new System.Windows.Forms.Label(); + this.classNameLabel = new System.Windows.Forms.Label(); + this.parentNameComboBox = new System.Windows.Forms.ComboBox(); + this.displayTerminalCheckBox = new System.Windows.Forms.CheckBox(); + this.testImagesCountTextBox = new System.Windows.Forms.TextBox(); + this.testImagesCountLabel = new System.Windows.Forms.Label(); + this.testImagesModeComboBox = new System.Windows.Forms.ComboBox(); + this.testImagesModeLabel = new System.Windows.Forms.Label(); + this.ipAddressTextBox = new System.Windows.Forms.TextBox(); + this.iPAddressLabel = new System.Windows.Forms.Label(); + this.ipAddressAdapterTextBox = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // hwAddressTextBox + // + this.hwAddressTextBox.Enabled = false; + this.hwAddressTextBox.Location = new System.Drawing.Point(198, 209); + this.hwAddressTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.hwAddressTextBox.Name = "hwAddressTextBox"; + this.hwAddressTextBox.Size = new System.Drawing.Size(193, 26); + this.hwAddressTextBox.TabIndex = 6; + // + // hwAddressLabel + // + this.hwAddressLabel.AutoSize = true; + this.hwAddressLabel.Location = new System.Drawing.Point(42, 213); + this.hwAddressLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.hwAddressLabel.Name = "hwAddressLabel"; + this.hwAddressLabel.Size = new System.Drawing.Size(107, 20); + this.hwAddressLabel.TabIndex = 5; + this.hwAddressLabel.Text = "Serial number"; + // + // parentNameLabel + // + this.parentNameLabel.AutoSize = true; + this.parentNameLabel.Location = new System.Drawing.Point(42, 106); + this.parentNameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.parentNameLabel.Name = "parentNameLabel"; + this.parentNameLabel.Size = new System.Drawing.Size(100, 20); + this.parentNameLabel.TabIndex = 3; + this.parentNameLabel.Text = "Parent name"; + // + // nameTextBox + // + this.nameTextBox.Enabled = false; + this.nameTextBox.Location = new System.Drawing.Point(198, 63); + this.nameTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.nameTextBox.Name = "nameTextBox"; + this.nameTextBox.Size = new System.Drawing.Size(193, 26); + this.nameTextBox.TabIndex = 2; + // + // nameLabel + // + this.nameLabel.AutoSize = true; + this.nameLabel.Location = new System.Drawing.Point(42, 68); + this.nameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.nameLabel.Name = "nameLabel"; + this.nameLabel.Size = new System.Drawing.Size(51, 20); + this.nameLabel.TabIndex = 1; + this.nameLabel.Text = "Name"; + // + // classNameLabel + // + this.classNameLabel.AutoSize = true; + this.classNameLabel.Location = new System.Drawing.Point(194, 26); + this.classNameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.classNameLabel.Name = "classNameLabel"; + this.classNameLabel.Size = new System.Drawing.Size(125, 20); + this.classNameLabel.TabIndex = 0; + this.classNameLabel.Text = "ComonentName"; + // + // parentNameComboBox + // + this.parentNameComboBox.Enabled = false; + this.parentNameComboBox.FormattingEnabled = true; + this.parentNameComboBox.Location = new System.Drawing.Point(198, 102); + this.parentNameComboBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.parentNameComboBox.Name = "parentNameComboBox"; + this.parentNameComboBox.Size = new System.Drawing.Size(193, 28); + this.parentNameComboBox.TabIndex = 4; + this.parentNameComboBox.TextChanged += new System.EventHandler(this.parentNameComboBox_TextChanged); + // + // displayTerminalCheckBox + // + this.displayTerminalCheckBox.AutoSize = true; + this.displayTerminalCheckBox.Enabled = false; + this.displayTerminalCheckBox.Location = new System.Drawing.Point(200, 255); + this.displayTerminalCheckBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.displayTerminalCheckBox.Name = "displayTerminalCheckBox"; + this.displayTerminalCheckBox.Size = new System.Drawing.Size(146, 24); + this.displayTerminalCheckBox.TabIndex = 7; + this.displayTerminalCheckBox.Text = "Display terminal"; + this.displayTerminalCheckBox.UseVisualStyleBackColor = true; + // + // testImagesCountTextBox + // + this.testImagesCountTextBox.Enabled = false; + this.testImagesCountTextBox.Location = new System.Drawing.Point(198, 335); + this.testImagesCountTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.testImagesCountTextBox.Name = "testImagesCountTextBox"; + this.testImagesCountTextBox.Size = new System.Drawing.Size(67, 26); + this.testImagesCountTextBox.TabIndex = 12; + // + // testImagesCountLabel + // + this.testImagesCountLabel.AutoSize = true; + this.testImagesCountLabel.Location = new System.Drawing.Point(42, 339); + this.testImagesCountLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.testImagesCountLabel.Name = "testImagesCountLabel"; + this.testImagesCountLabel.Size = new System.Drawing.Size(139, 20); + this.testImagesCountLabel.TabIndex = 11; + this.testImagesCountLabel.Text = "Test images count"; + // + // testImagesModeComboBox + // + this.testImagesModeComboBox.Enabled = false; + this.testImagesModeComboBox.FormattingEnabled = true; + this.testImagesModeComboBox.Location = new System.Drawing.Point(198, 295); + this.testImagesModeComboBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.testImagesModeComboBox.Name = "testImagesModeComboBox"; + this.testImagesModeComboBox.Size = new System.Drawing.Size(193, 28); + this.testImagesModeComboBox.TabIndex = 14; + // + // testImagesModeLabel + // + this.testImagesModeLabel.AutoSize = true; + this.testImagesModeLabel.Location = new System.Drawing.Point(42, 299); + this.testImagesModeLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.testImagesModeLabel.Name = "testImagesModeLabel"; + this.testImagesModeLabel.Size = new System.Drawing.Size(139, 20); + this.testImagesModeLabel.TabIndex = 13; + this.testImagesModeLabel.Text = "Test images mode"; + // + // ipAddressTextBox + // + this.ipAddressTextBox.Enabled = false; + this.ipAddressTextBox.Location = new System.Drawing.Point(198, 176); + this.ipAddressTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.ipAddressTextBox.Name = "ipAddressTextBox"; + this.ipAddressTextBox.Size = new System.Drawing.Size(193, 26); + this.ipAddressTextBox.TabIndex = 15; + // + // iPAddressLabel + // + this.iPAddressLabel.AutoSize = true; + this.iPAddressLabel.Location = new System.Drawing.Point(42, 176); + this.iPAddressLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.iPAddressLabel.Name = "iPAddressLabel"; + this.iPAddressLabel.Size = new System.Drawing.Size(89, 20); + this.iPAddressLabel.TabIndex = 16; + this.iPAddressLabel.Text = "IP address:"; + // + // ipAddressAdapterTextBox + // + this.ipAddressAdapterTextBox.Enabled = false; + this.ipAddressAdapterTextBox.Location = new System.Drawing.Point(200, 140); + this.ipAddressAdapterTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.ipAddressAdapterTextBox.Name = "ipAddressAdapterTextBox"; + this.ipAddressAdapterTextBox.Size = new System.Drawing.Size(193, 26); + this.ipAddressAdapterTextBox.TabIndex = 17; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(42, 140); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(150, 20); + this.label1.TabIndex = 18; + this.label1.Text = "Adapter IP address:"; + // + // CameraCfgCtrl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.label1); + this.Controls.Add(this.ipAddressAdapterTextBox); + this.Controls.Add(this.iPAddressLabel); + this.Controls.Add(this.ipAddressTextBox); + this.Controls.Add(this.testImagesModeComboBox); + this.Controls.Add(this.testImagesModeLabel); + this.Controls.Add(this.hwAddressTextBox); + this.Controls.Add(this.hwAddressLabel); + this.Controls.Add(this.testImagesCountTextBox); + this.Controls.Add(this.testImagesCountLabel); + this.Controls.Add(this.displayTerminalCheckBox); + this.Controls.Add(this.parentNameComboBox); + this.Controls.Add(this.parentNameLabel); + this.Controls.Add(this.nameTextBox); + this.Controls.Add(this.nameLabel); + this.Controls.Add(this.classNameLabel); + this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.Name = "CameraCfgCtrl"; + this.Size = new System.Drawing.Size(603, 391); + this.Load += new System.EventHandler(this.PumpCfgCtrl_Load); + this.ResumeLayout(false); + this.PerformLayout(); + } + + private System.Windows.Forms.Label label1; + + private System.Windows.Forms.TextBox ipAddressAdapterTextBox; + + private System.Windows.Forms.TextBox ipAddressTextBox; + private System.Windows.Forms.Label iPAddressLabel; + + #endregion + + private System.Windows.Forms.TextBox hwAddressTextBox; + private System.Windows.Forms.Label hwAddressLabel; + private System.Windows.Forms.Label parentNameLabel; + private System.Windows.Forms.TextBox nameTextBox; + private System.Windows.Forms.Label nameLabel; + private System.Windows.Forms.Label classNameLabel; + private System.Windows.Forms.ComboBox parentNameComboBox; + private System.Windows.Forms.CheckBox displayTerminalCheckBox; + private System.Windows.Forms.TextBox testImagesCountTextBox; + private System.Windows.Forms.Label testImagesCountLabel; + private System.Windows.Forms.ComboBox testImagesModeComboBox; + private System.Windows.Forms.Label testImagesModeLabel; + } +} diff --git a/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.resx b/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/CameraCfgCtrl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/Factory.cs b/TBF/Rig/Network/Camera/CJMS11/Factory.cs new file mode 100644 index 000000000..d7dfd7558 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/Factory.cs @@ -0,0 +1,25 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +using System.Collections.Generic; +using TBF.Rig.Generic; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public class Factory : IComponentFactory + { + public string ClassName { get { return GetType().Namespace.Substring(8); } } + public override string ToString() { return ClassName; } + + public IComponent DummyComponent() { return new Camera(); } + + public IComponent GetComponent(IComponentCfg cfg, IList components) { return new Camera(cfg, components); } + + public IComponentCfg DefaultConfig() { return new CameraCfg(this.GetType().Namespace.Substring(8), this); } + + public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component) + { + return ComponentCfgBase.CreateFromDbEntity(CameraCfg.Serializer, component, this); + } + } +} diff --git a/TBF/Rig/Network/Camera/CJMS11/GrabImagesOp.cs b/TBF/Rig/Network/Camera/CJMS11/GrabImagesOp.cs new file mode 100644 index 000000000..2a492fd31 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/GrabImagesOp.cs @@ -0,0 +1,224 @@ +/// +/// Copyright (c) 2017 Sensus Slovensko a.s. +/// +using System; +using System.Diagnostics; +using log4net; +using Common; +using Config.Entities; +using TBF.Rig.Network.Telnet; +using System.IO; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public class GrabImagesOp : IOperation + { + /// TODO: Implement support for grabbing multiple images, now imgFileNames.Lenght should be 1 + + private static readonly ILog log = LogManager.GetLogger(typeof(GrabImagesOp)); + public override string ToString() { return string.Format("GrabImageOp()"); } + + readonly Camera camera; + readonly Telnet.TelnetClient telnet; + readonly string[] imgFileNames; + readonly bool blackAndWhite; + readonly bool lowResolution; + readonly ImageRotation imageRotation; + + string command; + bool grabImageCommandSent; + string response; + bool grabPassed; + bool grabFailed; + bool transferringGrabbedImage; + + + /// + /// Events: Event.None or Event.Error + /// + /// CLP1611.Camera reference + public GrabImagesOp(Camera camera, string[] imgFileNames, bool blackAndWhite, bool lowResolution, ImageRotation imageRotation) + { + /// TODO: Implement support for grabbing multiple images, now imgFileNames.Lenght should be 1 + + this.camera = camera; + this.telnet = camera.Telnet; + this.imgFileNames = imgFileNames; + this.blackAndWhite = blackAndWhite; + this.lowResolution = lowResolution; + this.imageRotation = imageRotation; + + grabImageCommandSent = false; + transferringGrabbedImage = false; + + if (camera.DebugLevel == DebugMode.Normal || camera.DebugLevel == DebugMode.DetectedOn) + { + telnet.promptReceivedHandler += delegate(object sndr, PromptReceivedEventArgs a) + { + OnPromptReceived(sndr, a); + }; + } + } + + void OnPromptReceived(object sndr, PromptReceivedEventArgs a) + { + response = a.Response; + /// TODO + } + + + public void Start() + { + grabImageCommandSent = false; + transferringGrabbedImage = false; + + if ((imgFileNames != null) && (imgFileNames.Length > 0) && File.Exists(imgFileNames[0])) + { + /// + /// An image specified, (1) delete previous image, (2) check if this is a simulation + /// + File.Delete(imgFileNames[0]); + + if (camera.CameraCfg.DebugLevel == DebugMode.Simulate || camera.CameraCfg.DebugLevel == DebugMode.DetectedOff) + { + /// + /// Camera is in simulation mode => create a simulated image + /// + File.Copy(string.Format("{0}\\Pictures\\sample.jpg", Program.ExecutableDir), imgFileNames[0]); + } + } + } + + public Event Run() + { + if ((imgFileNames == null) || (imgFileNames.Length < 1) || (imgFileNames[0] == null)) + { + /// + /// No images to be grabbed => Done + /// + return Event.GrabPassed; + } + else if (camera.CameraCfg.DebugLevel == DebugMode.Simulate || + camera.CameraCfg.DebugLevel == DebugMode.DetectedOff) + { + /// + /// Camera is in simulation mode => create a simulated image and complete + /// + return Event.GrabPassed; + } + else if (!grabImageCommandSent) + { + /// + /// Normal operation, no command sent yet => (1) wait until telnet state = Inactive, (2) send a command + /// + if (camera.Running && (telnet.State == TelnetClient.TelnetState.Inactive)) + { + /// + /// State variables + /// + response = null; + + /// + /// Prepare a command + /// + int rotation = 0; + if (imageRotation == ImageRotation.Deg90) + rotation = 90; + else if (imageRotation == ImageRotation.Deg180) + rotation = 180; + else if (imageRotation == ImageRotation.Deg270) + rotation = 270; + + command = string.Format("clp/GrabImage {0} {1} -r {2} {3} -n 1 {4}", + blackAndWhite ? "-bmp" : "-bmp", /// 0 ... file format and quality + blackAndWhite ? "-y8" : "-rgb", /// 1 ... B&W / color + rotation.ToString(), /// 2 ... roration + lowResolution ? "-lr" : "-hr", /// 4 ... resolution + "grabbed.bmp"); /// 5 ... filename + + // + // Send (or enqueue) command + // + telnet.Enqueue(new Telnet.Command(Telnet.CmdAction.CLEAR_RESPONSE)); + telnet.Enqueue(new Telnet.Command(Telnet.CmdAction.SEND_COMMAND, command)); + grabImageCommandSent = true; + log.WarnFormat("{0} IP={1} command={2}", camera.Name, camera.IPAddress, command); + } + return Event.CameraBusy; + } + else if (grabPassed) + { + /// + /// Normal operation, grab passed => transfer/transferring the image + /// + if (transferringGrabbedImage) + { + /// Grab passed and image transfer is in progress + return Event.GrabPassed; + } + else if (0 == camera.DownloadFile(blackAndWhite ? "grabbed.jpg" : "grabbed.bmp", imgFileNames[0])) + { + /// File transfer successfully started + transferringGrabbedImage = true; + return Event.GrabPassed; + } + else + { + /// Wait until the previous image transfer completes + return Event.CameraBusy; + } + } + else if (grabFailed) + { + /// + /// Normal operation, grab failed => there in no image to transfer + /// + return Event.GrabFailed; + } + else if (response != null) + { + /// + /// Normal operation + /// + if (response.Contains("completed")) + { + grabPassed = true; + + /// + /// Start the file transfer + /// + if (0 == camera.DownloadFile(blackAndWhite ? "grabbed.jpg" : "grabbed.bmp", imgFileNames[0])) + { + /// File transfer successfully started + transferringGrabbedImage = true; + return Event.GrabPassed; + } + else + { + return Event.CameraBusy; /// File transfer not started yet + } + } + else + { + grabFailed = true; + return Event.GrabFailed; + } + } + else + { + return Event.CameraBusy; + } + } + + public void Stop() + { + if (camera.CameraCfg.DebugLevel == DebugMode.Simulate) return; + + if (grabImageCommandSent) + { + telnet.Enqueue(new Telnet.Command(Telnet.CmdAction.SEND_COMMAND, Telnet.TelnetClient.CtrlCCommand)); + log.WarnFormat("{0} IP={1} command=CTRL-C", camera.Name, camera.IPAddress); + } + } + } +} diff --git a/TBF/Rig/Network/Camera/CJMS11/JmsMessage.cs b/TBF/Rig/Network/Camera/CJMS11/JmsMessage.cs new file mode 100644 index 000000000..d5fc42f2c --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/JmsMessage.cs @@ -0,0 +1,54 @@ +using TBF.Rig.Network.Camera.CJMS11.POJO; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public class JmsMessage + { + private POJO.MessageStatus status; + private CommandM command; + private string payload; + + private string orig_status; + private string orig_command; + private string orig_payload; + + public POJO.MessageStatus Status + { + get => status; + set => status = value; + } + + public CommandM Command + { + get => command; + set => command = value; + } + + public string Payload + { + get => payload; + set => payload = value; + } + + public JmsMessage(string status, string command, string payload) + { + this.status = MessageStatusEnum.GetCommandM(status); + this.command = CommandMEnum.GetCommandM(command); + + orig_status = status; + orig_command = command; + orig_payload = payload; + } + + public bool isMessageImageType(){ + return this.command == CommandM.grab_image_; + } + + public ParsedImage getImage(){ + if(isMessageImageType()){ + return JmsPacket.ImageParser(this.payload); + } + return null; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/JmsPacket.cs b/TBF/Rig/Network/Camera/CJMS11/JmsPacket.cs new file mode 100644 index 000000000..74d6cea51 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/JmsPacket.cs @@ -0,0 +1,90 @@ +using System; +using System.Text.RegularExpressions; +using log4net; +using TBF.Rig.Network.Camera.CJMS11.POJO; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public class JmsPacket + { + private static readonly ILog log = LogManager.GetLogger(typeof(JmsPacket)); + + private JmsMessage jmsMessage; + + public JmsMessage JmsMessage + { + get => jmsMessage; + set => jmsMessage = value; + } + + public JmsPacket(string message) + { + string received_message = message; + + try + { + if (ParseMessage(received_message)) + { + //success + } + + } + catch (Exception e) + { + log.Error("Error parsing message: " + e.Message); + } + } + + private bool ParseMessage(string message) + { + message = message.Trim(); + if (message.Length < 1) return false; + + this.jmsMessage = JmsMessageFromString(message); + + return true; + } + + public static JmsMessage JmsMessageFromString(string message) + { + // Equivalent regex pattern + var pattern = new Regex(@"^(ACK|NACK):\s*(\S+)\s*(?:~(.*)~)?\s*END$", RegexOptions.Singleline); + + var match = pattern.Match(message); + if (match.Success) + { + string status = match.Groups[1].Value; + string commandStr = match.Groups[2].Value; + string payload = match.Groups[3].Success ? match.Groups[3].Value : ""; + + return new JmsMessage(status, commandStr, payload); + } + else + { + throw new ArgumentException($"Message format not recognized: {message}"); + } + + return null; + } + + public static ParsedImage ImageParser(string payloadImage) + { + // Equivalent regex pattern + var imagePattern = new Regex(@"IMAGE:([A-Z0-9_]+):\s*(.*?)\s*~IMAGE_END", RegexOptions.Singleline); + + var match = imagePattern.Match(payloadImage); + if (match.Success) + { + string encoding = match.Groups[1].Value; + string imageData = match.Groups[2].Value; + + return new ParsedImage(ImageTypeEnum.GetCommandM(encoding), imageData); + } + else + { + throw new ArgumentException($"Payload does not match expected image format: {payloadImage}"); + } + } + } + +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/LiveStreamOp.cs b/TBF/Rig/Network/Camera/CJMS11/LiveStreamOp.cs new file mode 100644 index 000000000..ef7a98cef --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/LiveStreamOp.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics; +using log4net; +using Common; +using TBF.Rig.Network.Telnet; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public class LiveStreamOp : IOperation + { + private static readonly ILog log = LogManager.GetLogger(typeof(LiveStreamOp)); + + readonly Camera camera; + readonly Telnet.TelnetClient telnet; + readonly string command; + + bool liveStreamCommandSent; + + /// + /// Events: Event.None or Event.Error + /// + /// CLP1611.Camera reference + public LiveStreamOp(Camera camera, Telnet.TelnetClient telnet, string command) + { + this.camera = camera; + this.telnet = telnet; + this.command = command; + } + + + public void Start() + { + liveStreamCommandSent = false; + } + + public Event Run() + { + if (camera.CameraCfg.DebugLevel == DebugMode.Simulate) return Event.None; + + log.DebugFormat("Run(): telnet.State = {0}, liveStreamCommandSent = {1}", telnet.State, liveStreamCommandSent); + + if (!liveStreamCommandSent) + { + if (camera.Running && (telnet.State == TelnetClient.TelnetState.Inactive)) + { + telnet.Enqueue(new Telnet.Command(Telnet.CmdAction.SEND_STRING, command)); + liveStreamCommandSent = true; + log.WarnFormat("{0} IP={1} command={2}", camera.Name, camera.IPAddress, command); + } + } + + return Event.None; + } + + public void Stop() + { + if (camera.CameraCfg.DebugLevel == DebugMode.Simulate) return; + + log.DebugFormat("Stop(): telnet.State = {0}, liveStreamCommandSent = {1}", telnet.State, liveStreamCommandSent); + + if (liveStreamCommandSent) + { + telnet.Enqueue(new Telnet.Command(Telnet.CmdAction.SEND_COMMAND, Telnet.TelnetClient.CtrlCCommand)); + log.WarnFormat("{0} IP={1} command=CTRL-C", camera.Name, camera.IPAddress); + } + } + } +} diff --git a/TBF/Rig/Network/Camera/CJMS11/POJO/CommandM.cs b/TBF/Rig/Network/Camera/CJMS11/POJO/CommandM.cs new file mode 100644 index 000000000..578db4280 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/POJO/CommandM.cs @@ -0,0 +1,22 @@ +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public enum CommandM + { + noCommand, + unknown_, // special: only return - unknown command + connect_, // special: only return - TCP connection created + type_camera_, + status_, + prepare_camera_, + grab_image_, + send_, + start_stream_, + stop_stream_, + get_cfg_, + set_cfg_, + close_, + disconnect_, + overload_config_, + client_count_ + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/POJO/CommandMEnum.cs b/TBF/Rig/Network/Camera/CJMS11/POJO/CommandMEnum.cs new file mode 100644 index 000000000..f17f7aed2 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/POJO/CommandMEnum.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Linq; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public static class CommandMEnum + { + private static readonly Dictionary commandToString = new Dictionary + { + { CommandM.noCommand, "noCommand" }, + { CommandM.unknown_, "unknown" }, + { CommandM.connect_, "connect" }, + { CommandM.type_camera_, "type_camera" }, + { CommandM.status_, "status" }, + { CommandM.prepare_camera_, "prepare_camera" }, + { CommandM.grab_image_, "grab_image" }, + { CommandM.send_, "send" }, + { CommandM.start_stream_, "start_stream" }, + { CommandM.stop_stream_, "stop_stream" }, + { CommandM.get_cfg_, "get_cfg" }, + { CommandM.set_cfg_, "set_cfg" }, + { CommandM.close_, "close" }, + { CommandM.disconnect_, "disconnect" }, + { CommandM.overload_config_, "overload_config" }, + { CommandM.client_count_, "client_count" } + }; + + + private static readonly Dictionary stringToCommand = commandToString.ToDictionary(kvp => kvp.Value, kvp => kvp.Key); + + public static string GetVal(CommandM cmd) + { + return commandToString.TryGetValue(cmd, out var str) ? str : "unknown"; + } + + public static CommandM GetCommandM(string token) + { + if (string.IsNullOrEmpty(token)) + return CommandM.noCommand; + + return stringToCommand.TryGetValue(token, out var cmd) ? cmd : CommandM.noCommand; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/POJO/ImageType.cs b/TBF/Rig/Network/Camera/CJMS11/POJO/ImageType.cs new file mode 100644 index 000000000..fc4c28240 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/POJO/ImageType.cs @@ -0,0 +1,10 @@ +namespace TBF.Rig.Network.Camera.CJMS11.POJO +{ + public enum ImageType + { + UNKNOWN, + BASE_64, + MJPEG, + RGB888 + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/POJO/ImageTypeEnum.cs b/TBF/Rig/Network/Camera/CJMS11/POJO/ImageTypeEnum.cs new file mode 100644 index 000000000..6082a0f89 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/POJO/ImageTypeEnum.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; + +namespace TBF.Rig.Network.Camera.CJMS11.POJO +{ + public class ImageTypeEnum + { + private static readonly Dictionary commandToString = new Dictionary + { + { ImageType.BASE_64, "BASE_64" }, + { ImageType.MJPEG, "MJPEG" }, + { ImageType.RGB888, "RGB888" }, + { ImageType.UNKNOWN, "" } + }; + + private static readonly Dictionary stringToCommand = commandToString.ToDictionary(kvp => kvp.Value, kvp => kvp.Key); + + public static string GetVal(ImageType cmd) + { + return commandToString.TryGetValue(cmd, out var str) ? str : "unknown"; + } + + public static ImageType GetCommandM(string token) + { + if (string.IsNullOrEmpty(token)) + return ImageType.UNKNOWN; + + return stringToCommand.TryGetValue(token, out var cmd) ? cmd : ImageType.UNKNOWN; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/POJO/MessageStatus.cs b/TBF/Rig/Network/Camera/CJMS11/POJO/MessageStatus.cs new file mode 100644 index 000000000..d43a23684 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/POJO/MessageStatus.cs @@ -0,0 +1,10 @@ +namespace TBF.Rig.Network.Camera.CJMS11.POJO +{ + public enum MessageStatus + { + UNDEFINED, + ACK, + NACK, + ERROR + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/POJO/MessageStatusEnum.cs b/TBF/Rig/Network/Camera/CJMS11/POJO/MessageStatusEnum.cs new file mode 100644 index 000000000..fd9ba8140 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/POJO/MessageStatusEnum.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; + +namespace TBF.Rig.Network.Camera.CJMS11.POJO +{ + public class MessageStatusEnum + { + private static readonly Dictionary commandToString = new Dictionary + { + { MessageStatus.UNDEFINED, "" }, + { MessageStatus.ACK, "ACK" }, + { MessageStatus.NACK, "NACK" }, + { MessageStatus.ERROR, "ERROR" } + }; + + private static readonly Dictionary stringToCommand = commandToString.ToDictionary(kvp => kvp.Value, kvp => kvp.Key); + + public static string GetVal(MessageStatus cmd) + { + return commandToString.TryGetValue(cmd, out var str) ? str : "undefined"; + } + + public static MessageStatus GetCommandM(string token) + { + if (string.IsNullOrEmpty(token)) + return MessageStatus.UNDEFINED; + + return stringToCommand.TryGetValue(token, out var cmd) ? cmd : MessageStatus.UNDEFINED; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/POJO/ParsedImage.cs b/TBF/Rig/Network/Camera/CJMS11/POJO/ParsedImage.cs new file mode 100644 index 000000000..8f9ae5c96 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/POJO/ParsedImage.cs @@ -0,0 +1,38 @@ +using System; +using TBF.Rig.Network.Camera.CJMS11.POJO; +using System.Drawing; +using System.IO; + + + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public class ParsedImage + { + public ImageType Encoding { get; } + public string ImageData { get; } + + public Image Image { get { return (ImageData.Length>0 ? Base64ToImage(ImageData) : null); } } + + public ParsedImage(ImageType encoding, string imageData) + { + Encoding = encoding; + ImageData = imageData; + } + + public override string ToString() + { + return $"ParsedImage{{encoding='{Encoding}', imageData='{ImageData}'}}"; + } + + public static Image Base64ToImage(string base64Image) + { + byte[] imageBytes = Convert.FromBase64String(base64Image); + + using (var ms = new MemoryStream(imageBytes)) + { + return Image.FromStream(ms); + } + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/RoisAndResults.cs b/TBF/Rig/Network/Camera/CJMS11/RoisAndResults.cs new file mode 100644 index 000000000..c99e092f2 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/RoisAndResults.cs @@ -0,0 +1,91 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +using System; +using System.Collections.Generic; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public class RoisAndResults + { + public const int MaxRegisteredRois = 4; /// Max. count of registered (and measured) ROI-s + + public long CameraTimeMs; + public int[] Angles; + + private IList roiParams; + public int RegisteredRoisCount { get { return roiParams.Count; } } + + + public RoisAndResults() + { + Angles = new int[MaxRegisteredRois]; + roiParams = new List(); + } + + + public void ClearRoiParams() + { + lock (this) + { + roiParams.Clear(); + } + } + + public int RegisterRoi(string roiParam) + { + lock (this) + { + if (this.roiParams.Count >= MaxRegisteredRois) return 0; + + this.roiParams.Add(roiParam); + return this.roiParams.Count; /// return a handle 1 .. MaxRegisteredRois + } + } + + public string GetRoiParams(int roiHandle) + + { + lock (this) + { + if (roiHandle > 0 && roiHandle <= roiParams.Count) + { + return roiParams[roiHandle - 1]; + } + else + { + return string.Empty; + } + } + } + + public void SetResults(long timeMs, int[] angles) + { + lock (this) + { + CameraTimeMs = timeMs; + for (int i = 0; i < Math.Min(angles.Length, Angles.Length); i++) + { + Angles[i] = angles[i]; + } + } + } + + public int GetResult(int roiHandle, out long timeMs) + { + lock (this) + { + if (roiHandle > 0 && roiHandle <= roiParams.Count) + { + timeMs = CameraTimeMs; + return Angles[roiHandle - 1]; + } + else + { + timeMs = 0; + return 0; + } + } + } + } +} diff --git a/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.Designer.cs b/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.Designer.cs new file mode 100644 index 000000000..7c95301b4 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.Designer.cs @@ -0,0 +1,62 @@ +namespace TBF.Rig.Network.Camera.CJMS11 +{ + partial class TerminalDlg + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.vtTextBox = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // vtTextBox + // + this.vtTextBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.vtTextBox.Location = new System.Drawing.Point(0, 0); + this.vtTextBox.Multiline = true; + this.vtTextBox.Name = "vtTextBox"; + this.vtTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.vtTextBox.Size = new System.Drawing.Size(737, 437); + this.vtTextBox.TabIndex = 0; + // + // TerminalDlg + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(737, 437); + this.Controls.Add(this.vtTextBox); + this.Name = "TerminalDlg"; + this.Text = "Terminal"; + this.Load += new System.EventHandler(this.Terminal_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox vtTextBox; + } +} \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.cs b/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.cs new file mode 100644 index 000000000..ee4789a0e --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.cs @@ -0,0 +1,37 @@ +using System; +using System.Windows.Forms; +using TBF.Rig.Network.Telnet; + +namespace TBF.Rig.Network.Camera.CJMS11 +{ + public partial class TerminalDlg : Form + { + TelnetClient telnet; + + public TerminalDlg() + { + InitializeComponent(); + } + + public TerminalDlg(string name, TelnetClient telnet) + { + InitializeComponent(); + Text = name; + this.telnet = telnet; + } + + private void Terminal_Load(object sender, EventArgs e) + { + telnet.vtTextChangedHandler += delegate(object sndr, VtTextChangedEventArgs args) + { + if (InvokeRequired) { Invoke(new EventHandler(OnTextChanged), sndr, args); } + else OnTextChanged(sndr, args); + }; + } + + void OnTextChanged(object sndr, VtTextChangedEventArgs args) + { + vtTextBox.Text = args.VtText; + } + } +} diff --git a/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.resx b/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/TBF/Rig/Network/Camera/CJMS11/TerminalDlg.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TBF/Rig/Network/Camera/Roi/RoiCfgCtrl.cs b/TBF/Rig/Network/Camera/Roi/RoiCfgCtrl.cs index 4a6bc9d74..24464e050 100644 --- a/TBF/Rig/Network/Camera/Roi/RoiCfgCtrl.cs +++ b/TBF/Rig/Network/Camera/Roi/RoiCfgCtrl.cs @@ -42,14 +42,10 @@ namespace TBF.Rig.Network.Camera.Roi { foreach (var cmpnt in parent.CmpntEntities) { - if (TbfComponents.CmpntFactoryFromClassName(cmpnt.ClassName) is TBF.Rig.Network.Camera.CLP1611.Factory) + if (TbfComponents.CmpntFactoryFromClassName(cmpnt.ClassName) is TBF.Rig.Network.Camera.CJMS11.Factory) { parentNameComboBox.Items.Add(cmpnt.Name); } - if (TbfComponents.CmpntFactoryFromClassName(cmpnt.ClassName) is TBF.Rig.Network.Camera.Roi.Factory) - { - previousRoiComboBox.Items.Add(cmpnt.Name); - } } } diff --git a/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/Factory.cs b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/Factory.cs new file mode 100644 index 000000000..924b756b9 --- /dev/null +++ b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/Factory.cs @@ -0,0 +1,25 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +using System.Collections.Generic; +using TBF.Rig.Generic; + +namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11 +{ + public class Factory : IComponentFactory + { + public string ClassName { get { return this.GetType().Namespace.Substring(8); } } + public override string ToString() { return ClassName; } + + public IComponent DummyComponent() { return new Roi(); } + + public IComponent GetComponent(IComponentCfg cfg, IList components) { return new Roi(cfg, components); } + + public IComponentCfg DefaultConfig() { return new RoiCfg(this.GetType().Namespace.Substring(8), this); } + + public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component) + { + return ComponentCfgBase.CreateFromDbEntity(RoiCfg.Serializer, component, this); + } + } +} diff --git a/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/ProcedureParams.cs b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/ProcedureParams.cs new file mode 100644 index 000000000..f36571ebe --- /dev/null +++ b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/ProcedureParams.cs @@ -0,0 +1,115 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +using System.IO; +using System.Xml.Serialization; +using Common; +using Config.Entities; +using TBF.Rig.Generic; +using TBF.Resources; + +namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11 +{ + public class ProcedureParams : ProcedureParamsBase, IParamsProvider, IProcedureParams + { + public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(ProcedureParams) })[0]; + public override XmlSerializer GetSerializer() { return Serializer; } + + public double PulsesPerLtr; /// [l^-1] + + public override void InitializeAll() + { + PulsesPerLtr = 1.0; + } + + string[] paramNames = new string[] + { + Strings.PulsesPerLtr, + }; + public override string ParamName(int i) { return paramNames[i]; } + public override int ParamsCount() { return paramNames.Length; } + + public override string ToString(int i) + { + switch (i) + { + case 0: return PulsesPerLtr.ToString(); + default: return string.Empty; + } + } + + /// Retrieves parameters from UI controls + public CfgUpdateFlags UpdateParam(int i, string strValue) + { + switch (i) + { + case 0: PulsesPerLtr = Utils.ParseUDouble(strValue); return CfgUpdateFlags.None; + default: return CfgUpdateFlags.None; + } + } + + /// Verifies whether strings in UI controls represent valid parameters + public bool ValidateParam(int i, string strValue, out string message) + { + message = string.Empty; + + double dummy; + switch (i) + { + case 0: + if (Utils.TryParseUDouble(strValue, out dummy)) return true; + break; + default: + message = "Invalid index"; + return false; + } + + message = ParamName(i) + " is invalid"; + return false; + } + + void CopyContentTo(ProcedureParams prms) + { + prms.PulsesPerLtr = this.PulsesPerLtr; + } + + public IParamsProvider Clone() + { + ProcedureParams pars = new ProcedureParams(); + CopyContentTo(pars); + return pars; + } + + public override void UpdateFromDbEntity(ComponentProcedure dbEntity) + { + if (dbEntity == null) return; + try + { + ProcedureParams tmp = Serializer.Deserialize(new StringReader(dbEntity.Parameters)) as ProcedureParams; + + procedureParamsEntity = dbEntity; + componentName = dbEntity.CmpntName; + procedure = dbEntity.Procedure; + + if (tmp != null) tmp.CopyContentTo(this); + } + catch + { + } + } + + public ProcedureParams() { } + + public ProcedureParams(bool initialize) + { + if (initialize) InitializeAll(); + } + + public ProcedureParams(ComponentProcedure procedureParamsEntity, string componentName, Procedure procedure) + { + this.procedureParamsEntity = procedureParamsEntity; + this.componentName = componentName; + this.procedure = procedure; + } + } +} diff --git a/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/Roi.cs b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/Roi.cs new file mode 100644 index 000000000..f69318cd2 --- /dev/null +++ b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/Roi.cs @@ -0,0 +1,139 @@ +/// +/// Copyright (c) 2017-2021 Sensus Metering Systems +/// +using System; +using System.Collections.Generic; +using System.Text; +using log4net; +using Common; +using Config.Entities; +using TBF.Rig.GenericDevices; + +namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11 +{ + public class Roi : ComponentBase, GenericDevices.IRegReaderStillCamera + { + /// TODO: Implement support for sharing one camera by multiple ROI-s + + private static readonly ILog log = LogManager.GetLogger(typeof(Roi)); + public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } + + readonly RoiCfg roiCfg; + + /// + /// Camera and ICamera + /// + public readonly CLP1611.Camera NetCamera; + public GenericDevices.ICamera Camera { get { return NetCamera as GenericDevices.ICamera; } } + + public int Position + { + get + { + int firstDigitPos = Name.IndexOfAny(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }); + int position; + return (firstDigitPos < 0) ? 0 : (int.TryParse(Name.Substring(firstDigitPos), out position) ? position : 0); + } + } + public RegisterReaderType RegisterReaderType { get { return RegisterReaderType.Manual; } } + public double PulsesPerLtr { get { return roiCfg.ProcParams.PulsesPerLtr; } } + public double LtrsPerPulse { get { return (PulsesPerLtr <= float.Epsilon) ? 1.0 : (1 / PulsesPerLtr); } } + + + double beginWMState; + public double BeginWMState + { + get { return beginWMState; } + set { beginWMState = value; } + } + + double endWMState; + public double EndWMState + { + get { return endWMState; } + set { endWMState = value; } + } + + public double WMVolume { get { return endWMState - beginWMState; } } + + public int WMPulses { get { return (int)(WMVolume / LtrsPerPulse); } } + + public int WMRefPulses { get { return StateMachine.ControlBoardMain.RefPulses; ; } } + + + public Roi() { } + + public Roi(Generic.IComponentCfg cfg, IList components) + : base(cfg) + { + roiCfg = cfg as RoiCfg; + NetCamera = TbfComponents.FindComponent(cfg.ParentName, components) as CLP1611.Camera; + log.Warn(this.ToString()); + } + + public override void Initialize() + { + Clear(); + } + + #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 CfgChangeHandler; + + public override void StartChangeHandler() + { + CfgChangeHandler += delegate(object sender, CfgChangeArgs args) + { + RoiCfg tmpcfg = args.Cfg as RoiCfg; + if (tmpcfg != null && tmpcfg.Name.Equals(Name)) + { + if (args.Command == CfgChangeCmd.CfgChange) + { + roiCfg.BlackAndWhite = tmpcfg.BlackAndWhite; + roiCfg.LowResolution = tmpcfg.LowResolution; + roiCfg.ImageRotation = tmpcfg.ImageRotation; + } + } + }; + } + + #endregion Configuration Change Handling + + public void Clear() + { + log.DebugFormat("{0}:Clear()", Name); + beginWMState = 0; + endWMState = 0; + } + + + public IOperation GrabImageOp(string imageFileName) + { + return GrabImageOp(imageFileName, roiCfg.BlackAndWhite, roiCfg.LowResolution, roiCfg.ImageRotation); + } + + public IOperation GrabImageOp(string imageFileName, + bool blackAndWhite, bool lowResolution, ImageRotation imageRotation) + { + if (NetCamera != null) + { + /// TODO: Implement support for sharing one camera by multiple ROI-s + return NetCamera.GrabImagesOp(new string[] { imageFileName }, blackAndWhite, lowResolution, imageRotation); + } + else + return null; + } + + public IValve GetValve() + { + return null; + } + } +} diff --git a/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfg.cs b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfg.cs new file mode 100644 index 000000000..ba6e10961 --- /dev/null +++ b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfg.cs @@ -0,0 +1,56 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +using System.Collections.Generic; +using System.Xml.Serialization; +using Common; +using Config.Entities; +using TBF.Rig.Generic; + +namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11 +{ + public class RoiCfg : ComponentCfgBase, IChildComponentCfg + { + public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(RoiCfg) })[0]; + public override XmlSerializer GetSerializer() { return Serializer; } + + public IComponentCfgCtrl GetControl(IList cmpntEntities) { return new RoiCfgCtrl(); } + + /// + /// Serialized parameters + /// + public bool BlackAndWhite; + public bool LowResolution; + public ImageRotation ImageRotation; + + /// Procedure parameters + [XmlIgnore] + public ProcedureParams ProcParams; + public override IParamsProvider GetRuntimeProcParamsProvider() { return ProcParams; } + public override IParamsProvider CreateProcParamsProvider() { return new ProcedureParams(true); } + + /// Private parameterless constructor invoked by all other (public) constructors + RoiCfg() + { + ProcParams = new ProcedureParams(true); + } + + public RoiCfg(string name, IComponentFactory factory) + : this() + { + Name = name; + Factory = factory; + ParentName = "Camera"; + BlackAndWhite = false; + LowResolution = true; + ImageRotation = ImageRotation.None; + DebugLevel = DebugMode.Inherit; + } + + public string ToString(int i) + { + return string.Format("{0} Camera={1} B&W={2}, LowRes={3}, Rotation={4}", + Name, ParentName, BlackAndWhite, LowResolution, ImageRotation); + } + } +} diff --git a/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.cs b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.cs new file mode 100644 index 000000000..2b47af0f4 --- /dev/null +++ b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.cs @@ -0,0 +1,175 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +using System; +using System.Windows.Forms; +using log4net; +using Common; +using Config.Entities; +using TBF.Rig.Generic; +using TBF.Resources; +using TBF.UI.Bench.Components; + +namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11 +{ + public partial class RoiCfgCtrl : UserControl, IComponentCfgCtrl + { + static readonly ILog log = LogManager.GetLogger(typeof(RoiCfgCtrl)); + + ComponentParametersDlg parent; + + public bool ShowMore { get { return false; } } + + RoiCfg config; + public IComponentCfg Config + { + get { return config as IComponentCfg; } + set + { + config = value as RoiCfg; + Redraw(); + } + } + + public RoiCfgCtrl() + { + InitializeComponent(); + } + + private void PumpCfgCtrl_Load(object sender, EventArgs e) + { + parent = ParentForm as ComponentParametersDlg; + if (parent == null) return; + + if (parent.CmpntEntities != null) + { + foreach (var cmpnt in parent.CmpntEntities) + { + if (TbfComponents.CmpntFactoryFromClassName(cmpnt.ClassName) is TBF.Rig.Network.Camera.CLP1611.Factory) + { + parentNameComboBox.Items.Add(cmpnt.Name); + } + } + } + + for (ImageRotation ir = 0; ir < ImageRotation.Count; ir++) + { + imageRotationComboBox.Items.Add(ir.ToDescription()); + } + + Redraw(); + } + + public void Closing() + { + } + + void Redraw() + { + if (config == null) return; /// Control was not loaded, settings were not changed + + classNameLabel.Text = config.Factory.ClassName; + nameTextBox.Text = config.Name; + parentNameComboBox.Text = string.IsNullOrEmpty(config.ParentName) ? "---" : config.ParentName; + blackAndWhiteCheckBox.Checked = config.BlackAndWhite; + loResolutionCheckBox.Checked = config.LowResolution; + imageRotationComboBox.Text = config.ImageRotation.ToDescription(); + } + + public void Unlock() + { + nameTextBox.Enabled = true; + parentNameComboBox.Enabled = true; + blackAndWhiteCheckBox.Enabled = true; + loResolutionCheckBox.Enabled = true; + imageRotationComboBox.Enabled = true; + } + + public CfgUpdateFlags VerifyCfg(ref string message) + { + CfgUpdateFlags flags = CfgUpdateFlags.None; + + if (!parentNameComboBox.Items.Contains(parentNameComboBox.Text)) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + string.Format(Strings.Invalid_0, parentNameLabel.Text); + } + + if (!imageRotationComboBox.Items.Contains(imageRotationComboBox.Text)) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + string.Format(Strings.Invalid_0, imageRotationLabel.Text); + } + + return flags; + } + + public CfgUpdateFlags UpdateCfg() + { + CfgUpdateFlags flags = CfgUpdateFlags.None; + + if (config == null) return CfgUpdateFlags.Error; /// Control was not loaded, settings were not changed + + if (config.Name != nameTextBox.Text) + { + config.Name = nameTextBox.Text; + flags |= (CfgUpdateFlags.RestartRqrd | CfgUpdateFlags.AnyChange); + } + + string newParentName = parentNameComboBox.Text.Equals("---") ? string.Empty : parentNameComboBox.Text; + if (config.ParentName != newParentName) + { + config.ParentName = newParentName; + flags |= (CfgUpdateFlags.RestartRqrd | CfgUpdateFlags.AnyChange); + } + + if (config.BlackAndWhite != blackAndWhiteCheckBox.Checked) + { + config.BlackAndWhite = blackAndWhiteCheckBox.Checked; + flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.InvokeCfgChange); + } + + if (config.LowResolution != loResolutionCheckBox.Checked) + { + config.LowResolution = loResolutionCheckBox.Checked; + flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.InvokeCfgChange); + } + + for (ImageRotation ir = 0; ir < ImageRotation.Count; ir++) + { + if (imageRotationComboBox.Text == ir.ToDescription()) + { + if (config.ImageRotation != ir) + { + config.ImageRotation = ir; + flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.InvokeCfgChange); + break; + } + } + } + + if ((flags & CfgUpdateFlags.InvokeCfgChange) != 0) + { + Roi.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.CfgChange, config)); + } + + return flags; + } + + #region Configuration Change Handling + + public static void OnCmdResponse(object sender, CmdResponseArgs args) + { + if (CmdResponseHandler == null) return; + try { CmdResponseHandler(sender, args); } + catch (Exception e) { log.Error("CmdResponseHandler(...) failed", e); } + } + + public static event EventHandler CmdResponseHandler; + + public void StartResponseHandler() { } + public void StopResponseHandler() { } + + #endregion Configuration Change Handling + } +} diff --git a/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.designer.cs b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.designer.cs new file mode 100644 index 000000000..d0bb56573 --- /dev/null +++ b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.designer.cs @@ -0,0 +1,185 @@ +/// +/// Copyright (c) 2017 Sensus Metering Systems +/// +namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11 +{ + partial class RoiCfgCtrl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.parentNameLabel = new System.Windows.Forms.Label(); + this.nameTextBox = new System.Windows.Forms.TextBox(); + this.nameLabel = new System.Windows.Forms.Label(); + this.classNameLabel = new System.Windows.Forms.Label(); + this.parentNameComboBox = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.imageRotationComboBox = new System.Windows.Forms.ComboBox(); + this.imageRotationLabel = new System.Windows.Forms.Label(); + this.blackAndWhiteCheckBox = new System.Windows.Forms.CheckBox(); + this.loResolutionCheckBox = new System.Windows.Forms.CheckBox(); + this.SuspendLayout(); + // + // parentNameLabel + // + this.parentNameLabel.AutoSize = true; + this.parentNameLabel.Location = new System.Drawing.Point(20, 77); + this.parentNameLabel.Name = "parentNameLabel"; + this.parentNameLabel.Size = new System.Drawing.Size(43, 13); + this.parentNameLabel.TabIndex = 3; + this.parentNameLabel.Text = "Camera"; + // + // nameTextBox + // + this.nameTextBox.Enabled = false; + this.nameTextBox.Location = new System.Drawing.Point(139, 49); + this.nameTextBox.Name = "nameTextBox"; + this.nameTextBox.Size = new System.Drawing.Size(174, 20); + this.nameTextBox.TabIndex = 2; + // + // nameLabel + // + this.nameLabel.AutoSize = true; + this.nameLabel.Location = new System.Drawing.Point(20, 52); + this.nameLabel.Name = "nameLabel"; + this.nameLabel.Size = new System.Drawing.Size(35, 13); + this.nameLabel.TabIndex = 1; + this.nameLabel.Text = "Name"; + // + // classNameLabel + // + this.classNameLabel.AutoSize = true; + this.classNameLabel.Location = new System.Drawing.Point(136, 26); + this.classNameLabel.Name = "classNameLabel"; + this.classNameLabel.Size = new System.Drawing.Size(114, 13); + this.classNameLabel.TabIndex = 0; + this.classNameLabel.Text = "ComponentClassName"; + // + // parentNameComboBox + // + this.parentNameComboBox.Enabled = false; + this.parentNameComboBox.FormattingEnabled = true; + this.parentNameComboBox.Location = new System.Drawing.Point(139, 74); + this.parentNameComboBox.Name = "parentNameComboBox"; + this.parentNameComboBox.Size = new System.Drawing.Size(174, 21); + this.parentNameComboBox.TabIndex = 4; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(-211, -147); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(57, 13); + this.label1.TabIndex = 5; + this.label1.Text = "Arguments"; + // + // textBox1 + // + this.textBox1.Enabled = false; + this.textBox1.Location = new System.Drawing.Point(-125, -150); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(372, 20); + this.textBox1.TabIndex = 6; + // + // imageRotationComboBox + // + this.imageRotationComboBox.Enabled = false; + this.imageRotationComboBox.FormattingEnabled = true; + this.imageRotationComboBox.Location = new System.Drawing.Point(139, 154); + this.imageRotationComboBox.Name = "imageRotationComboBox"; + this.imageRotationComboBox.Size = new System.Drawing.Size(93, 21); + this.imageRotationComboBox.TabIndex = 8; + // + // imageRotationLabel + // + this.imageRotationLabel.AutoSize = true; + this.imageRotationLabel.Location = new System.Drawing.Point(20, 157); + this.imageRotationLabel.Name = "imageRotationLabel"; + this.imageRotationLabel.Size = new System.Drawing.Size(103, 13); + this.imageRotationLabel.TabIndex = 7; + this.imageRotationLabel.Text = "Image rotation (ccw)"; + // + // blackAndWhiteCheckBox + // + this.blackAndWhiteCheckBox.AutoSize = true; + this.blackAndWhiteCheckBox.Enabled = false; + this.blackAndWhiteCheckBox.Location = new System.Drawing.Point(139, 108); + this.blackAndWhiteCheckBox.Name = "blackAndWhiteCheckBox"; + this.blackAndWhiteCheckBox.Size = new System.Drawing.Size(93, 17); + this.blackAndWhiteCheckBox.TabIndex = 5; + this.blackAndWhiteCheckBox.Text = "Black && White"; + this.blackAndWhiteCheckBox.UseVisualStyleBackColor = true; + // + // loResolutionCheckBox + // + this.loResolutionCheckBox.AutoSize = true; + this.loResolutionCheckBox.Enabled = false; + this.loResolutionCheckBox.Location = new System.Drawing.Point(139, 131); + this.loResolutionCheckBox.Name = "loResolutionCheckBox"; + this.loResolutionCheckBox.Size = new System.Drawing.Size(94, 17); + this.loResolutionCheckBox.TabIndex = 6; + this.loResolutionCheckBox.Text = "Low resolution"; + this.loResolutionCheckBox.UseVisualStyleBackColor = true; + // + // RoiCfgCtrl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.imageRotationComboBox); + this.Controls.Add(this.imageRotationLabel); + this.Controls.Add(this.blackAndWhiteCheckBox); + this.Controls.Add(this.loResolutionCheckBox); + this.Controls.Add(this.parentNameComboBox); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.Controls.Add(this.parentNameLabel); + this.Controls.Add(this.nameTextBox); + this.Controls.Add(this.nameLabel); + this.Controls.Add(this.classNameLabel); + this.Name = "RoiCfgCtrl"; + this.Size = new System.Drawing.Size(500, 300); + this.Load += new System.EventHandler(this.PumpCfgCtrl_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label parentNameLabel; + private System.Windows.Forms.TextBox nameTextBox; + private System.Windows.Forms.Label nameLabel; + private System.Windows.Forms.Label classNameLabel; + private System.Windows.Forms.ComboBox parentNameComboBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.ComboBox imageRotationComboBox; + private System.Windows.Forms.Label imageRotationLabel; + private System.Windows.Forms.CheckBox blackAndWhiteCheckBox; + private System.Windows.Forms.CheckBox loResolutionCheckBox; + } +} diff --git a/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.resx b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/TBF/Rig/Network/Camera/RoiForFixedStartCJMS11/RoiCfgCtrl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TBF/Rig/TbfComponents.cs b/TBF/Rig/TbfComponents.cs index 4ef66b2d9..8e849cc9a 100644 --- a/TBF/Rig/TbfComponents.cs +++ b/TBF/Rig/TbfComponents.cs @@ -76,11 +76,13 @@ namespace TBF.Rig new Modbus.WaterAnalyzer.Factory(), new Network.Adapter.Factory(), new Network.AdapterFTP.Factory(), + new Network.Camera.CJMS11.Factory(), new Network.Camera.CLP1611.Factory(), new Network.Camera.KeyenceIV3G120.Factory(), new Network.Camera.Display.Factory(), new Network.Camera.Roi.Factory(), new Network.Camera.RoiForFixedStart.Factory(), + new Network.Camera.RoiForFixedStartCJMS11.Factory(), new Network.Camera.RoiForFixedStartKeyence.Factory(), new Network.Comet.Ambient.Factory(), new Network.RestAPI.Factory(), diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index 42eb13944..eb99e48c9 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -813,6 +813,33 @@ NetadapterCfgCtrl.cs + + + + UserControl + + + CameraCfgCtrl.cs + + + + + + + + + + + + + + + + Form + + + TerminalDlg.cs + @@ -861,6 +888,16 @@ + + + + + + UserControl + + + RoiCfgCtrl.cs + @@ -2891,6 +2928,12 @@ NetadapterCfgCtrl.cs + + CameraCfgCtrl.cs + + + TerminalDlg.cs + CameraCfgCtrl.cs @@ -2906,6 +2949,9 @@ CameraCfgCtrl.cs + + RoiCfgCtrl.cs + RoiCfgCtrl.cs diff --git a/TBFTests/Rig/Network/Camera/CJMS11/CameraTest.cs b/TBFTests/Rig/Network/Camera/CJMS11/CameraTest.cs new file mode 100644 index 000000000..538e20fed --- /dev/null +++ b/TBFTests/Rig/Network/Camera/CJMS11/CameraTest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using JetBrains.Annotations; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TBF.Rig.Generic; +using TBF.Rig.Network.Camera.CJMS11; + +namespace TBFTests.Rig.Network.Camera.CJMS11 +{ + [TestClass] + [TestSubject(typeof(TBF.Rig.Network.Camera.CJMS11.Camera))] + public class CameraTest + { + + [TestMethod] + public void Initialize() + { + Factory factory = new Factory(); + IComponentCfg config = new CameraCfg("Network.Adapter", factory); + + + TBF.Rig.Network.Camera.CJMS11.Camera camera = new TBF.Rig.Network.Camera.CJMS11.Camera(config, null); + + camera.Initialize(); + + Thread.Sleep(10000); + camera.Stop(); + Thread.Sleep(10000); + } + } +} \ No newline at end of file diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj index 26fc94f62..13852062d 100644 --- a/TBFTests/TBFTests.csproj +++ b/TBFTests/TBFTests.csproj @@ -97,6 +97,7 @@ + diff --git a/TBFTests/obj/Debug/TBFTests.csproj.CoreCompileInputs.cache b/TBFTests/obj/Debug/TBFTests.csproj.CoreCompileInputs.cache index b955293cd..2b71a7c5e 100644 --- a/TBFTests/obj/Debug/TBFTests.csproj.CoreCompileInputs.cache +++ b/TBFTests/obj/Debug/TBFTests.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -42f452de9b0393e2b8a43441d6acf2f1268eaff5eb4a5cb56624e462418cf079 +8f46c126994e2e4deda67fad3f33cb2a280d2b4b7ce364598de6bc2d540d1738