/// /// Copyright (c) 2017 Sensus Metering Systems /// using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Timers; using log4net; using Renci.SshNet; using Config.Entities; using TBF.BenchControl.Network.Telnet; using TBF.Resources; namespace TBF.BenchControl.Network.Camera.CLP1611 { /// /// 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(17), 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)); } 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 int CameraIdx { get { return cameraIdx; } } int cameraIdx; /// static int nextCameraIdx = 0; static int GetCameraIdx() { return nextCameraIdx++; } /// Used in Initialize() public static new void ResetStaticProperties() { nextCameraIdx = 0; } /// /// 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 rtpUdpLocalPort; UdpClient rtpUdpClient; Thread rtpListenerThread; 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"); Telnet = null; terminalDlg = null; roisAndResults = new RoisAndResults(); /// object for measurement results, writes/reads to/from this object are locked running = false; } public void Initialize() { cameraIdx = GetCameraIdx(); if (CameraCfg.DebugLevel == DebugMode.Simulate) { UiBridge.Bridge.OnCameraInfo(this.cameraIdx, string.Format("{0} s/n {1} si simulated", Name, CameraCfg.HardwareAddress)); return; } /// Detect a camera bool cameraDetected = DetectCamera(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(this.cameraIdx, string.Format("{0} ver.{1} {2} s/n {3}", Name, sdCardVer, ipAddress, CameraCfg.HardwareAddress)); ClpUserName = (sdCardVer.Length > 0 && sdCardVer[0] == '1') ? "tbf" : "pi"; rtpUdpLocalPort = UdpPortBase + 4 * cameraIdx; rtcpUdpLocalPort = UdpPortBase + 4 * cameraIdx + 1; msrmntUdpLocalPort = UdpPortBase + 4 * cameraIdx + 2; StartRtpListener(NetAdapter.IPAddress, rtpUdpLocalPort); StartMsrmntListener(NetAdapter.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}' with address {1} detected, IP = {2}, SD = {3}", Name, 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}' with address {1} not detected", Name, 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.Join(50); if (rtpListenerThread != null) rtpListenerThread.Join(50); if (msrmntListenerThread != null) msrmntListenerThread.Join(50); if (rtpUdpClient != null) { rtpUdpClient.Close(); rtpUdpClient = 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; } } }; } #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 } 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; } private void StartRtpListener(IPAddress localIP, int localPort) { rtpUdpClient = new UdpClient(new IPEndPoint(localIP, localPort)); rtpListenerThread = new Thread(new ThreadStart(RtpListener)); rtpListenerThread.Start(); } void RtpListener() { JpegFrame jpegFrame = new JpegFrame(); bool synced = false; while (!stopRtpListenerFlag) { try { IPEndPoint endpoint = null; Byte[] data = rtpUdpClient.Receive(ref endpoint); if (data.Length > 20) { /// Process the packet RtpPacket newPacket = new RtpPacket(data); if (synced) { /// Sychronized: process the new packet switch (jpegFrame.AddPacket(newPacket)) { case RtpFrame.STATE.Complete: UiBridge.Bridge.OnImage(this.cameraIdx, jpegFrame.ToImage()); jpegFrame.Clear(); break; case RtpFrame.STATE.Error: jpegFrame.Clear(); 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, rtpUdpLocalPort); //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)); } } } }