Adds Avocor Display driver class

This commit is contained in:
Neil Dorin
2017-11-09 16:13:03 -07:00
parent a3580ca15c
commit 6dbc152810
7 changed files with 670 additions and 1 deletions

View File

@@ -184,8 +184,20 @@ namespace PepperDash.Essentials.Core.Routing
/// </summary>
public const string VgaIn = "vgaIn";
/// <summary>
/// vgaIn1
/// </summary>
public const string VgaIn1 = "vgaIn1";
/// <summary>
/// vgaOut
/// </summary>
public const string VgaOut = "vgaOut";
/// <summary>
/// IPC/OPS
/// </summary>
public const string IpcOps = "ipcOps";
/// <summary>
/// MediaPlayer
/// </summary>
public const string MediaPlayer = "mediaPlayer";
}
}

View File

@@ -0,0 +1,649 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro.CrestronThread;
using Crestron.SimplSharpPro;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Routing;
namespace PepperDash.Essentials.Devices.Displays
{
/// <summary>
///
/// </summary>
public class AvocorDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, ICommunicationMonitor, IInputDisplayPort1,
IInputHdmi1, IInputHdmi2, IInputHdmi3, IInputHdmi4, IInputVga1
{
public IBasicCommunication Communication { get; private set; }
public CommunicationGather PortGather { get; private set; }
public StatusMonitorBase CommunicationMonitor { get; private set; }
public byte ID { get; private set; }
bool _PowerIsOn;
bool _IsWarmingUp;
bool _IsCoolingDown;
ushort _VolumeLevelForSig;
int _LastVolumeSent;
bool _IsMuted;
RoutingInputPort _CurrentInputPort;
//byte[] IncomingBuffer = new byte[]{};
ActionIncrementer VolumeIncrementer;
bool VolumeIsRamping;
public bool IsInStandby { get; private set; }
protected override Func<bool> PowerIsOnFeedbackFunc { get { return () => _PowerIsOn; } }
protected override Func<bool> IsCoolingDownFeedbackFunc { get { return () => _IsCoolingDown; } }
protected override Func<bool> IsWarmingUpFeedbackFunc { get { return () => _IsWarmingUp; } }
protected override Func<string> CurrentInputFeedbackFunc { get { return () => _CurrentInputPort.Key; } }
/// <summary>
/// Constructor for IBasicCommunication
/// </summary>
public AvocorDisplay(string key, string name, IBasicCommunication comm, string id)
: base(key, name)
{
Communication = comm;
//Communication.BytesReceived += new EventHandler<GenericCommMethodReceiveBytesArgs>(Communication_BytesReceived);
PortGather = new CommunicationGather(Communication, '\x08');
PortGather.IncludeDelimiter = true;
PortGather.LineReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(PortGather_LineReceived);
ID = id == null ? (byte)0x01 : Convert.ToByte(id, 16); // If id is null, set default value of 0x01, otherwise assign value passed in constructor
Init();
}
/// <summary>
/// Constructor for TCP
/// </summary>
public AvocorDisplay(string key, string name, string hostname, int port, string id)
: base(key, name)
{
Communication = new GenericTcpIpClient(key + "-tcp", hostname, port, 5000);
PortGather = new CommunicationGather(Communication, '\x0d');
PortGather.IncludeDelimiter = true;
PortGather.LineReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(PortGather_LineReceived);
ID = id == null ? (byte)0x01 : Convert.ToByte(id, 16); // If id is null, set default value of 0x01, otherwise assign value passed in constructor
Init();
}
/// <summary>
/// Constructor for COM
/// </summary>
public AvocorDisplay(string key, string name, ComPort port, ComPort.ComPortSpec spec, string id)
: base(key, name)
{
Communication = new ComPortController(key + "-com", port, spec);
PortGather = new CommunicationGather(Communication, '\x0d');
PortGather.IncludeDelimiter = true;
PortGather.LineReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(PortGather_LineReceived);
ID = id == null ? (byte)0x01 : Convert.ToByte(id, 16); // If id is null, set default value of 0x01, otherwise assign value passed in constructor
Init();
}
void AddRoutingInputPort(RoutingInputPort port, byte fbMatch)
{
port.FeedbackMatchObject = fbMatch;
InputPorts.Add(port);
}
void Init()
{
WarmupTime = 10000;
CooldownTime = 8000;
CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 30000, 120000, 300000, StatusGet);
DeviceManager.AddDevice(CommunicationMonitor);
VolumeIncrementer = new ActionIncrementer(655, 0, 65535, 800, 80,
v => SetVolume((ushort)v),
() => _LastVolumeSent);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, new Action(InputHdmi1), this), 0x09);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, new Action(InputHdmi2), this), 0x10);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, new Action(InputHdmi3), this), 0x11);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.HdmiIn4, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, new Action(InputHdmi4), this), 0x12);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.HdmiIn5, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Hdmi, new Action(InputHdmi5), this), 0x17);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.DisplayPortIn1, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.DisplayPort, new Action(InputDisplayPort1), this), 0x13);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.VgaIn, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Dvi, new Action(InputVga1), this), 0x00);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.IpcOps, eRoutingSignalType.AudioVideo,
eRoutingPortConnectionType.Composite, new Action(InputIpcOps), this), 0x14);
AddRoutingInputPort(new RoutingInputPort(RoutingPortNames.MediaPlayer, eRoutingSignalType.Video,
eRoutingPortConnectionType.Vga, new Action(InputMediaPlayer), this), 0x18);
VolumeLevelFeedback = new IntFeedback(() => { return _VolumeLevelForSig; });
MuteFeedback = new BoolFeedback(() => _IsMuted);
StatusGet();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override bool CustomActivate()
{
Communication.Connect();
CommunicationMonitor.StatusChange += (o, a) => { Debug.Console(2, this, "Communication monitor state: {0}", CommunicationMonitor.Status); };
CommunicationMonitor.Start();
return true;
}
public override List<Feedback> Feedbacks
{
get
{
var list = base.Feedbacks;
list.AddRange(new List<Feedback>
{
VolumeLevelFeedback,
MuteFeedback,
CurrentInputFeedback
});
return list;
}
}
///// <summary>
///// /
///// </summary>
///// <param name="sender"></param>
//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));
// // Need to find AA FF and have
// for (int i = 0; i < newBytes.Length; i++)
// {
// if (newBytes[i] == 0xAA && newBytes[i + 1] == 0xFF)
// {
// newBytes = newBytes.Skip(i).ToArray(); // Trim off junk if there's "dirt" in the buffer
// // parse it
// // If it's at least got the header, then process it,
// while (newBytes.Length > 4 && newBytes[0] == 0xAA && newBytes[1] == 0xFF)
// {
// var msgLen = newBytes[3];
// // if the buffer is shorter than the header (3) + message (msgLen) + checksum (1),
// // give and save it for next time
// if (newBytes.Length < msgLen + 4)
// break;
// // Good length, grab the message
// var message = newBytes.Skip(4).Take(msgLen).ToArray();
// // At this point, the ack/nak is the first byte
// if (message[0] == 0x41)
// {
// switch (message[1]) // type byte
// {
// case 0x00: // General status
// UpdatePowerFB(message[2], message[5]); // "power" can be misrepresented when the display sleeps
// UpdateInputFb(message[5]);
// UpdateVolumeFB(message[3]);
// UpdateMuteFb(message[4]);
// UpdateInputFb(message[5]);
// break;
// case 0x11:
// UpdatePowerFB(message[2]);
// break;
// case 0x12:
// UpdateVolumeFB(message[2]);
// break;
// case 0x13:
// UpdateMuteFb(message[2]);
// break;
// case 0x14:
// UpdateInputFb(message[2]);
// break;
// default:
// break;
// }
// }
// // Skip over what we've used and save the rest for next time
// newBytes = newBytes.Skip(5 + msgLen).ToArray();
// }
// break; // parsing will mean we can stop looking for header in loop
// }
// }
// // Save whatever partial message is here
// IncomingBuffer = newBytes;
//}
void PortGather_LineReceived(object sender, GenericCommMethodReceiveTextArgs e)
{
Debug.Console(1, this, "Receivied: '{0}'", ComTextHelper.GetEscapedText(e.Text));
if (e.Text.IndexOf("\x50\x4F\x57") > -1)
{
// Power Status Response
var value = e.Text.ToCharArray();
switch (value[6])
{
case '\x00':
{
_PowerIsOn = false;
break;
}
case '\x01':
{
_PowerIsOn = false;
break;
}
}
PowerIsOnFeedback.FireUpdate();
}
else if (e.Text.IndexOf("\x4D\x49\x4E") > -1)
{
var value = e.Text.ToCharArray();
var b = value[6];
var newInput = InputPorts.FirstOrDefault(i => i.FeedbackMatchObject.Equals(b));
if (newInput != null && newInput != _CurrentInputPort)
{
_CurrentInputPort = newInput;
CurrentInputFeedback.FireUpdate();
}
}
else if (e.Text.IndexOf("\x56\x4F\x4C") > -1)
{
// Volume Status Response
var value = e.Text.ToCharArray();
var b = value[6];
var newVol = (ushort)NumericalHelpers.Scale((double)b, 0, 100, 0, 65535);
if (!VolumeIsRamping)
_LastVolumeSent = newVol;
if (newVol != _VolumeLevelForSig)
{
_VolumeLevelForSig = newVol;
VolumeLevelFeedback.FireUpdate();
}
}
}
/// <summary>
///
/// </summary>
void UpdatePowerFB(byte powerByte)
{
var newVal = powerByte == 1;
if (newVal != _PowerIsOn)
{
_PowerIsOn = newVal;
PowerIsOnFeedback.FireUpdate();
}
}
/// <summary>
/// Updates power status from general updates where source is included.
/// Compensates for errant standby / power off hiccups by ignoring
/// power off states with input < 0x10
/// </summary>
void UpdatePowerFB(byte powerByte, byte inputByte)
{
// This should reject errant power feedbacks when switching away from input on standby.
if (powerByte == 0x01 && inputByte < 0x10)
IsInStandby = true;
if (powerByte == 0x00 && IsInStandby) // Ignore power off if coming from standby - glitch
{
IsInStandby = false;
return;
}
UpdatePowerFB(powerByte);
}
/// <summary>
///
/// </summary>
void UpdateVolumeFB(byte b)
{
var newVol = (ushort)NumericalHelpers.Scale((double)b, 0, 100, 0, 65535);
if (!VolumeIsRamping)
_LastVolumeSent = newVol;
if (newVol != _VolumeLevelForSig)
{
_VolumeLevelForSig = newVol;
VolumeLevelFeedback.FireUpdate();
}
}
/// <summary>
///
/// </summary>
void UpdateMuteFb(byte b)
{
var newMute = b == 1;
if (newMute != _IsMuted)
{
_IsMuted = newMute;
MuteFeedback.FireUpdate();
}
}
/// <summary>
///
/// </summary>
void UpdateInputFb(byte b)
{
var newInput = InputPorts.FirstOrDefault(i => i.FeedbackMatchObject.Equals(b));
if (newInput != null && newInput != _CurrentInputPort)
{
_CurrentInputPort = newInput;
CurrentInputFeedback.FireUpdate();
}
}
/// <summary>
/// Formats an outgoing message. Replaces third byte with ID and replaces last byte with checksum
/// </summary>
/// <param name="b"></param>
void SendBytes(byte[] b)
{
b[1] = ID;
var command = b.ToString();
Debug.Console(1, this, "Sending: '{0}'",ComTextHelper.GetEscapedText(b));
Communication.SendBytes(b);
}
/// <summary>
///
/// </summary>
public void StatusGet()
{
PowerGet();
InputGet();
}
/// <summary>
///
/// </summary>
public override void PowerOn()
{
//Send(PowerOnCmd);
SendBytes(new byte[] { 0x07, ID, 0x02, 0x50, 0x4F, 0x57, 0x01, 0x08, 0x0d });
if (!PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown)
{
_IsWarmingUp = true;
IsWarmingUpFeedback.FireUpdate();
// Fake power-up cycle
WarmupTimer = new CTimer(o =>
{
_IsWarmingUp = false;
_PowerIsOn = true;
IsWarmingUpFeedback.FireUpdate();
PowerIsOnFeedback.FireUpdate();
}, WarmupTime);
}
}
/// <summary>
///
/// </summary>
public override void PowerOff()
{
// If a display has unreliable-power off feedback, just override this and
// remove this check.
if (!_IsWarmingUp && !_IsCoolingDown) // PowerIsOnFeedback.BoolValue &&
{
//Send(PowerOffCmd);
SendBytes(new byte[] { 0x07, ID, 0x02, 0x50, 0x4F, 0x57, 0x00, 0x08, 0x0d });
_IsCoolingDown = true;
_PowerIsOn = false;
PowerIsOnFeedback.FireUpdate();
IsCoolingDownFeedback.FireUpdate();
// Fake cool-down cycle
CooldownTimer = new CTimer(o =>
{
_IsCoolingDown = false;
IsCoolingDownFeedback.FireUpdate();
}, CooldownTime);
}
}
public override void PowerToggle()
{
if (PowerIsOnFeedback.BoolValue && !IsWarmingUpFeedback.BoolValue)
PowerOff();
else if (!PowerIsOnFeedback.BoolValue && !IsCoolingDownFeedback.BoolValue)
PowerOn();
}
public void PowerGet()
{
SendBytes(new byte[] { 0x07, ID, 0x01, 0x50, 0x4F, 0x57, 0x08, 0x0d });
}
public void InputHdmi1()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x09, 0x08, 0x0d });
}
public void InputHdmi2()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x10, 0x08, 0x0d });
}
public void InputHdmi3()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x11, 0x08, 0x0d });
}
public void InputHdmi4()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x12, 0x08, 0x0d });
}
public void InputHdmi5()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x17, 0x08, 0x0d });
}
public void InputDisplayPort1()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x13, 0x08, 0x0d });
}
public void InputVga1()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x00, 0x08, 0x0d });
}
public void InputIpcOps()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x14, 0x08, 0x0d });
}
public void InputMediaPlayer()
{
SendBytes(new byte[] { 0x07, ID, 0x02, 0x4D, 0x49, 0x4E, 0x18, 0x08, 0x0d });
}
public void InputGet()
{
SendBytes(new byte[] { 0x07, ID, 0x01, 0x4D, 0x49, 0x4E, 0x08, 0x0d });
}
public void VolumeGet()
{
SendBytes(new byte[] { 0x07, ID, 0x01, 0x56, 0x4F, 0x4C, 0x08, 0x0d });
}
/// <summary>
/// Executes a switch, turning on display if necessary.
/// </summary>
/// <param name="selector"></param>
public override void ExecuteSwitch(object selector)
{
//if (!(selector is Action))
// Debug.Console(1, this, "WARNING: ExecuteSwitch cannot handle type {0}", selector.GetType());
if (_PowerIsOn)
(selector as Action)();
else // if power is off, wait until we get on FB to send it.
{
// One-time event handler to wait for power on before executing switch
EventHandler<EventArgs> handler = null; // necessary to allow reference inside lambda to handler
handler = (o, a) =>
{
if (!_IsWarmingUp) // Done warming
{
IsWarmingUpFeedback.OutputChange -= handler;
(selector as Action)();
}
};
IsWarmingUpFeedback.OutputChange += handler; // attach and wait for on FB
PowerOn();
}
}
/// <summary>
/// Scales the level to the range of the display and sends the command
/// </summary>
/// <param name="level"></param>
public void SetVolume(ushort level)
{
_LastVolumeSent = level;
var scaled = (int)NumericalHelpers.Scale(level, 0, 65535, 0, 100);
// The inputs to Scale ensure that byte won't overflow
SendBytes(new byte[] { 0x07, ID, 0x02, 0x56, 0x4F, 0x4C, Convert.ToByte(scaled), 0x08, 0x0d });
}
#region IBasicVolumeWithFeedback Members
public IntFeedback VolumeLevelFeedback { get; private set; }
public BoolFeedback MuteFeedback { get; private set; }
/// <summary>
///
/// </summary>
public void MuteOff()
{
SendBytes(new byte[] { 0xAA, 0x13, 0x00, 0x01, 0x00, 0x00 });
}
/// <summary>
///
/// </summary>
public void MuteOn()
{
SendBytes(new byte[] { 0xAA, 0x13, 0x00, 0x01, 0x01, 0x00 });
}
/// <summary>
///
/// </summary>
public void MuteGet()
{
SendBytes(new byte[] { 0xAA, 0x13, 0x00, 0x00, 0x00 });
}
#endregion
#region IBasicVolumeControls Members
/// <summary>
///
/// </summary>
public void MuteToggle()
{
if (_IsMuted)
MuteOff();
else
MuteOn();
}
/// <summary>
///
/// </summary>
/// <param name="pressRelease"></param>
public void VolumeDown(bool pressRelease)
{
if (pressRelease)
{
VolumeIncrementer.StartDown();
VolumeIsRamping = true;
}
else
{
VolumeIsRamping = false;
VolumeIncrementer.Stop();
}
}
/// <summary>
///
/// </summary>
/// <param name="pressRelease"></param>
public void VolumeUp(bool pressRelease)
{
if (pressRelease)
{
VolumeIncrementer.StartUp();
VolumeIsRamping = true;
}
else
{
VolumeIsRamping = false;
VolumeIncrementer.Stop();
}
}
#endregion
}
}

View File

@@ -36,6 +36,12 @@ namespace PepperDash.Essentials.Devices.Displays
var comm = CommFactory.CreateCommForDevice(dc);
if (comm != null)
return new SamsungMDC(dc.Key, dc.Name, comm, dc.Properties["id"].Value<string>());
}
if (typeName == "avocorvtf")
{
var comm = CommFactory.CreateCommForDevice(dc);
if (comm != null)
return new AvocorDisplay(dc.Key, dc.Name, comm, null);
}
}

View File

@@ -11,6 +11,7 @@ namespace PepperDash.Essentials.Devices.Displays
public interface IInputHdmi3 { void InputHdmi3(); }
public interface IInputHdmi4 { void InputHdmi4(); }
public interface IInputDisplayPort1 { void InputDisplayPort1(); }
public interface IInputDisplayPort2 { void InputDisplayPort2(); }
public interface IInputDisplayPort2 { void InputDisplayPort2(); }
public interface IInputVga1 { void InputVga1(); }
}

View File

@@ -105,6 +105,7 @@
<Compile Include="Codec\iHasScheduleAwareness.cs" />
<Compile Include="Crestron\Gateways\CenRfgwController.cs" />
<Compile Include="Display\ComTcpDisplayBase.cs" />
<Compile Include="Display\AvocorVTFDisplay.cs" />
<Compile Include="Display\InputInterfaces.cs" />
<Compile Include="Display\SamsungMDCDisplay.cs" />
<Compile Include="Display\DeviceFactory.cs" />