diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraControl.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraControl.cs
index 89b6002a..8262bf2f 100644
--- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraControl.cs
+++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraControl.cs
@@ -135,6 +135,13 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
void TriggerAutoFocus();
}
+ public interface IHasAutoFocusMode
+ {
+ void SetFocusModeAuto();
+ void SetFocusModeManual();
+ void ToggleFocusMode();
+ }
+
public interface IHasCameraAutoMode : IHasCameraControls
{
void CameraAutoModeOn();
diff --git a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraVisca.cs b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraVisca.cs
index 4393229c..332a6a20 100644
--- a/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraVisca.cs
+++ b/essentials-framework/Essentials Devices Common/Essentials Devices Common/Cameras/CameraVisca.cs
@@ -12,29 +12,86 @@ using PepperDash.Essentials.Devices.Common.Codec;
using System.Text.RegularExpressions;
using Crestron.SimplSharp.Reflection;
+using Newtonsoft.Json;
+
namespace PepperDash.Essentials.Devices.Common.Cameras
{
- public class CameraVisca : CameraBase, IHasCameraPtzControl, ICommunicationMonitor, IHasCameraPresets, IPower, IBridgeAdvanced
+ public class CameraVisca : CameraBase, IHasCameraPtzControl, ICommunicationMonitor, IHasCameraPresets, IPower, IBridgeAdvanced, IHasCameraFocusControl, IHasAutoFocusMode
{
+ CameraViscaPropertiesConfig PropertiesConfig;
+
public IBasicCommunication Communication { get; private set; }
- public CommunicationGather PortGather { get; private set; }
public StatusMonitorBase CommunicationMonitor { get; private set; }
- public byte PanSpeed = 0x10;
- public byte TiltSpeed = 0x10;
+ ///
+ /// Used to store the actions to parse inquiry responses as the inquiries are sent
+ ///
+ private CrestronQueue> InquiryResponseQueue;
+
+ ///
+ /// Camera ID (Default 1)
+ ///
+ public byte ID = 0x01;
+ public byte ResponseID;
+
+
+ public byte PanSpeedSlow = 0x10;
+ public byte TiltSpeedSlow = 0x10;
+ public byte ZoomSpeedSlow = 0x03;
+
+ public byte PanSpeedFast = 0x13;
+ public byte TiltSpeedFast = 0x13;
+ public byte ZoomSpeedFast = 0x05;
+
private bool IsMoving;
private bool IsZooming;
- public bool PowerIsOn { get; private set; }
+
+ bool _powerIsOn;
+ public bool PowerIsOn
+ {
+ get
+ {
+ return _powerIsOn;
+ }
+ private set
+ {
+ if (value != _powerIsOn)
+ {
+ _powerIsOn = value;
+ PowerIsOnFeedback.FireUpdate();
+ }
+ }
+ }
+
+ const byte ZoomInCmd = 0x02;
+ const byte ZoomOutCmd = 0x03;
+ const byte ZoomStopCmd = 0x00;
+
+ ///
+ /// Used to determine when to move the camera at a faster speed if a direction is held
+ ///
+ CTimer SpeedTimer;
+
+ long FastSpeedHoldTimeMs = 2000;
byte[] IncomingBuffer = new byte[] { };
public BoolFeedback PowerIsOnFeedback { get; private set; }
- public CameraVisca(string key, string name, IBasicCommunication comm, CameraPropertiesConfig props) :
+ public CameraVisca(string key, string name, IBasicCommunication comm, CameraViscaPropertiesConfig props) :
base(key, name)
{
+ InquiryResponseQueue = new CrestronQueue>(15);
+
Presets = props.Presets;
+ PropertiesConfig = props;
+
+ ID = (byte)(props.Id + 0x80);
+ ResponseID = (byte)((props.Id * 0x10) + 0x80);
+
+ SetupCameraSpeeds();
+
OutputPorts.Add(new RoutingOutputPort("videoOut", eRoutingSignalType.Video, eRoutingPortConnectionType.None, null, this, true));
// Default to all capabilties
@@ -51,8 +108,6 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
{
// This instance uses RS-232 control
}
- PortGather = new CommunicationGather(Communication, "\xFF");
-
Communication.BytesReceived += new EventHandler(Communication_BytesReceived);
PowerIsOnFeedback = new BoolFeedback(() => { return PowerIsOn; });
@@ -66,9 +121,47 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 20000, 120000, 300000, "\x81\x09\x04\x00\xFF");
}
DeviceManager.AddDevice(CommunicationMonitor);
-
-
}
+
+
+ ///
+ /// Sets up camera speed values based on config
+ ///
+ void SetupCameraSpeeds()
+ {
+ if (PropertiesConfig.FastSpeedHoldTimeMs > 0)
+ {
+ FastSpeedHoldTimeMs = PropertiesConfig.FastSpeedHoldTimeMs;
+ }
+
+ if (PropertiesConfig.PanSpeedSlow > 0)
+ {
+ PanSpeedSlow = (byte)PropertiesConfig.PanSpeedSlow;
+ }
+ if (PropertiesConfig.PanSpeedFast > 0)
+ {
+ PanSpeedFast = (byte)PropertiesConfig.PanSpeedFast;
+ }
+
+ if (PropertiesConfig.TiltSpeedSlow > 0)
+ {
+ TiltSpeedSlow = (byte)PropertiesConfig.TiltSpeedSlow;
+ }
+ if (PropertiesConfig.TiltSpeedFast > 0)
+ {
+ TiltSpeedFast = (byte)PropertiesConfig.TiltSpeedFast;
+ }
+
+ if (PropertiesConfig.ZoomSpeedSlow > 0)
+ {
+ ZoomSpeedSlow = (byte)PropertiesConfig.ZoomSpeedSlow;
+ }
+ if (PropertiesConfig.ZoomSpeedFast > 0)
+ {
+ ZoomSpeedFast = (byte)PropertiesConfig.ZoomSpeedFast;
+ }
+ }
+
public override bool CustomActivate()
{
Communication.Connect();
@@ -110,21 +203,148 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
Communication.SendBytes(b);
}
+
void Communication_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e)
{
- // This is probably not thread-safe buffering
- // Append the incoming bytes with whatever is in the buffer
- var newBytes = new byte[IncomingBuffer.Length + e.Bytes.Length];
- IncomingBuffer.CopyTo(newBytes, 0);
- e.Bytes.CopyTo(newBytes, IncomingBuffer.Length);
- if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1
- Debug.Console(2, this, "Received:{0}", ComTextHelper.GetEscapedText(newBytes));
- }
+ var newBytes = new byte[IncomingBuffer.Length + e.Bytes.Length];
+ try
+ {
+ // This is probably not thread-safe buffering
+ // Append the incoming bytes with whatever is in the buffer
+ IncomingBuffer.CopyTo(newBytes, 0);
+ e.Bytes.CopyTo(newBytes, IncomingBuffer.Length);
+ if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1
+ Debug.Console(2, this, "Received:{0}", ComTextHelper.GetEscapedText(newBytes));
+
+ byte[] message = new byte[] { };
+
+ // Search for the delimiter 0xFF character
+ for (int i = 0; i < newBytes.Length; i++)
+ {
+ if (newBytes[i] == 0xFF)
+ {
+ // i will be the index of the delmiter character
+ message = newBytes.Take(i).ToArray();
+ // Skip over what we just took and save the rest for next time
+ newBytes = newBytes.Skip(i).ToArray();
+ }
+ }
+
+ if (message.Length > 0)
+ {
+ // Check for matching ID
+ if (message[0] != ResponseID)
+ {
+ return;
+ }
+
+ switch (message[1])
+ {
+ case 0x40:
+ {
+ // ACK received
+ Debug.Console(2, this, "ACK Received");
+ break;
+ }
+ case 0x50:
+ {
+
+ if (message[2] == 0xFF)
+ {
+ // Completion received
+ Debug.Console(2, this, "Completion Received");
+ }
+ else
+ {
+ // Inquiry response received. Dequeue the next response handler and invoke it
+ if (InquiryResponseQueue.Count > 0)
+ {
+ var inquiryAction = InquiryResponseQueue.Dequeue();
+
+ inquiryAction.Invoke(message.Skip(2).ToArray());
+ }
+ else
+ {
+ Debug.Console(2, this, "Response Queue is empty. Nothing to dequeue.");
+ }
+ }
+
+ break;
+ }
+ case 0x60:
+ {
+ // Error message
+
+ switch (message[2])
+ {
+ case 0x01:
+ {
+ // Message Length Error
+ Debug.Console(2, this, "Error from device: Message Length Error");
+ break;
+ }
+ case 0x02:
+ {
+ // Syntax Error
+ Debug.Console(2, this, "Error from device: Syntax Error");
+ break;
+ }
+ case 0x03:
+ {
+ // Command Buffer Full
+ Debug.Console(2, this, "Error from device: Command Buffer Full");
+ break;
+ }
+ case 0x04:
+ {
+ // Command Cancelled
+ Debug.Console(2, this, "Error from device: Command Cancelled");
+ break;
+ }
+ case 0x05:
+ {
+ // No Socket
+ Debug.Console(2, this, "Error from device: No Socket");
+ break;
+ }
+ case 0x41:
+ {
+ // Command not executable
+ Debug.Console(2, this, "Error from device: Command not executable");
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (message == new byte[] { ResponseID, 0x50, 0x02, 0xFF })
+ {
+ PowerIsOn = true;
+ }
+ else if (message == new byte[] { ResponseID, 0x50, 0x03, 0xFF })
+ {
+ PowerIsOn = false;
+ }
+
+ }
+
+ }
+ catch (Exception err)
+ {
+ Debug.Console(2, this, "Error parsing feedback: {0}", err);
+ }
+ finally
+ {
+ // Save whatever partial message is here
+ IncomingBuffer = newBytes;
+ }
+ }
private void SendPanTiltCommand (byte[] cmd)
{
- var temp = new Byte[] { 0x81, 0x01, 0x06, 0x01, PanSpeed, TiltSpeed };
+ var temp = new byte[] { ID, 0x01, 0x06, 0x01, PanSpeedSlow, TiltSpeedSlow };
int length = temp.Length + cmd.Length + 1;
byte[] sum = new byte[length];
@@ -134,16 +354,40 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
SendBytes(sum);
}
+ void SendPowerQuery()
+ {
+ SendBytes(new byte[] { ID, 0x09, 0x04, 0x00, 0xFF });
+ InquiryResponseQueue.Enqueue(HandlePowerResponse);
+ }
+
public void PowerOn()
{
-
- SendBytes(new Byte[] { 0x81, 0x01, 0x04, 0x00, 0x02, 0xFF });
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x00, 0x02, 0xFF });
+ SendPowerQuery();
}
+ void HandlePowerResponse(byte[] response)
+ {
+ switch (response[0])
+ {
+ case 0x02:
+ {
+ PowerIsOn = true;
+ break;
+ }
+ case 0x03:
+ {
+ PowerIsOn = false;
+ break;
+ }
+ }
+ }
+
public void PowerOff()
{
- SendBytes(new Byte[] {0x81, 0x01, 0x04, 0x00, 0x03, 0xFF});
- }
+ SendBytes(new byte[] {ID, 0x01, 0x04, 0x00, 0x03, 0xFF});
+ SendPowerQuery();
+ }
public void PowerToggle()
{
@@ -153,6 +397,8 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
PowerOn();
}
+
+
public void PanLeft()
{
SendPanTiltCommand(new byte[] {0x01, 0x03});
@@ -184,16 +430,16 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
private void SendZoomCommand (byte cmd)
{
- SendBytes(new byte[] {0x81, 0x01, 0x04, 0x07, cmd, 0xFF} );
+ SendBytes(new byte[] {ID, 0x01, 0x04, 0x07, cmd, 0xFF} );
}
public void ZoomIn()
{
- SendZoomCommand(0x02);
+ SendZoomCommand(ZoomInCmd);
IsZooming = true;
}
public void ZoomOut()
{
- SendZoomCommand(0x03);
+ SendZoomCommand(ZoomOutCmd);
IsZooming = true;
}
public void ZoomStop()
@@ -205,7 +451,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
{
if (IsZooming)
{
- SendZoomCommand(0x00);
+ SendZoomCommand(ZoomStopCmd);
IsZooming = false;
}
else
@@ -216,15 +462,16 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
}
public void PositionHome()
{
- throw new NotImplementedException();
+ SendBytes(new byte[] { ID, 0x01, 0x06, 0x02, PanSpeedFast, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF });
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x47, 0x00, 0x00, 0x00, 0x00, 0xFF });
}
public void RecallPreset(int presetNumber)
{
- SendBytes(new byte[] {0x81, 0x01, 0x04, 0x3F, 0x02, (byte)presetNumber, 0xFF} );
+ SendBytes(new byte[] {ID, 0x01, 0x04, 0x3F, 0x02, (byte)presetNumber, 0xFF} );
}
public void SavePreset(int presetNumber)
{
- SendBytes(new byte[] { 0x81, 0x01, 0x04, 0x3F, 0x01, (byte)presetNumber, 0xFF });
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x3F, 0x01, (byte)presetNumber, 0xFF });
}
#region IHasCameraPresets Members
@@ -244,6 +491,78 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
}
#endregion
+
+ #region IHasCameraFocusControl Members
+
+ public void FocusNear()
+ {
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x03, 0xFF });
+ }
+
+ public void FocusFar()
+ {
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x02, 0xFF });
+ }
+
+ public void FocusStop()
+ {
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x00, 0xFF });
+ }
+
+ public void TriggerAutoFocus()
+ {
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x18, 0x01, 0xFF });
+ SendAutoFocusQuery();
+ }
+
+ #endregion
+
+ #region IHasAutoFocus Members
+
+ public void SetFocusModeAuto()
+ {
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x02, 0xFF });
+ SendAutoFocusQuery();
+ }
+
+ public void SetFocusModeManual()
+ {
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x03, 0xFF });
+ SendAutoFocusQuery();
+ }
+
+ public void ToggleFocusMode()
+ {
+ SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x10, 0xFF });
+ SendAutoFocusQuery();
+ }
+
+ #endregion
+
+ void SendAutoFocusQuery()
+ {
+ SendBytes(new byte[] { ID, 0x09, 0x04, 0x38, 0xFF });
+ InquiryResponseQueue.Enqueue(HandleAutoFocusResponse);
+ }
+
+ void HandleAutoFocusResponse(byte[] response)
+ {
+ switch (response[0])
+ {
+ case 0x02:
+ {
+ // Auto Mode
+ PowerIsOn = true;
+ break;
+ }
+ case 0x03:
+ {
+ // Manual Mode
+ PowerIsOn = false;
+ break;
+ }
+ }
+ }
}
public class CameraViscaFactory : EssentialsDeviceFactory
@@ -257,10 +576,63 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
{
Debug.Console(1, "Factory Attempting to create new CameraVisca Device");
var comm = CommFactory.CreateCommForDevice(dc);
- var props = Newtonsoft.Json.JsonConvert.DeserializeObject(
+ var props = Newtonsoft.Json.JsonConvert.DeserializeObject(
dc.Properties.ToString());
return new Cameras.CameraVisca(dc.Key, dc.Name, comm, props);
}
}
+
+ public class CameraViscaPropertiesConfig : CameraPropertiesConfig
+ {
+ ///
+ /// Control ID of the camera (1-7)
+ ///
+ [JsonProperty("id")]
+ public uint Id { get; set; }
+
+ ///
+ /// Slow Pan speed (0-18)
+ ///
+ [JsonProperty("panSpeedSlow")]
+ public uint PanSpeedSlow { get; set; }
+
+ ///
+ /// Fast Pan speed (0-18)
+ ///
+ [JsonProperty("panSpeedFast")]
+ public uint PanSpeedFast { get; set; }
+
+ ///
+ /// Slow tilt speed (0-18)
+ ///
+ [JsonProperty("tiltSpeedSlow")]
+ public uint TiltSpeedSlow { get; set; }
+
+ ///
+ /// Fast tilt speed (0-18)
+ ///
+ [JsonProperty("tiltSpeedFast")]
+ public uint TiltSpeedFast { get; set; }
+
+ ///
+ /// Slow zoom speed (0-7)
+ ///
+ [JsonProperty("zoomSpeedSlow")]
+ public uint ZoomSpeedSlow { get; set; }
+
+ ///
+ /// Fast zoom speed (0-7)
+ ///
+ [JsonProperty("zoomSpeedFast")]
+ public uint ZoomSpeedFast { get; set; }
+
+ ///
+ /// Time a button must be held before fast speed is engaged (Milliseconds)
+ ///
+ [JsonProperty("fastSpeedHoldTimeMs")]
+ public uint FastSpeedHoldTimeMs { get; set; }
+
+ }
+
}
\ No newline at end of file