diff --git a/.github/workflows/EssentialsPlugins-builds-caller.yml b/.github/workflows/EssentialsPlugins-builds-caller.yml index 6d9f244..c4b4d2c 100644 --- a/.github/workflows/EssentialsPlugins-builds-caller.yml +++ b/.github/workflows/EssentialsPlugins-builds-caller.yml @@ -9,16 +9,6 @@ jobs: getVersion: uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main secrets: inherit - build-3Series: - uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-3Series-builds.yml@main - secrets: inherit - needs: getVersion - if: needs.getVersion.outputs.newVersion == 'true' - with: - newVersion: ${{ needs.getVersion.outputs.newVersion }} - version: ${{ needs.getVersion.outputs.version }} - tag: ${{ needs.getVersion.outputs.tag }} - channel: ${{ needs.getVersion.outputs.channel }} build-4Series: uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main secrets: inherit diff --git a/src/Rs232Commands.cs b/src/Rs232Commands.cs index 788c372..a047fc7 100644 --- a/src/Rs232Commands.cs +++ b/src/Rs232Commands.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Independentsoft.Email.Mime; using PepperDash.Core; using PepperDash.Essentials.Core.Queues; @@ -311,6 +312,29 @@ public static IQueueMessage GetInputQuery(IBasicCommunication coms, Action action; + private readonly byte[] _message; + + public Rs232Response(byte[] message, Action action) + { + _message = message; + + this.action = action; + } + + public void Dispatch() + { + if(action == null || _message.Length == 0) + { + return; + } + + action(_message); + } + } + public class Rs232Command:IQueueMessage { private readonly Action _action; @@ -321,6 +345,8 @@ public class Rs232Command:IQueueMessage private readonly eCommandType _commandType; + public eCommandType CommandType => _commandType; + public Rs232Command(IBasicCommunication coms, byte[] message, Action updateCommandAction, eCommandType commandType) { if(coms == null) @@ -360,6 +386,7 @@ public void Dispatch() { _action(_commandType); + Debug.Console(DebugLevels.DebugLevel, "Sending command {0}", _message.ToReadableString()); _comm.SendBytes(_message); } diff --git a/src/Rs232ParsingUtils.cs b/src/Rs232ParsingUtils.cs index 5e3f106..8830d8b 100644 --- a/src/Rs232ParsingUtils.cs +++ b/src/Rs232ParsingUtils.cs @@ -26,10 +26,11 @@ public static class Rs232ParsingUtils public static bool ParsePowerResponse(this byte[] response) { - Debug.Console(DebugLevels.DebugLevel, "ParsePowerResponse response: {0}", response.ToReadableString()); + Debug.Console(DebugLevels.DebugLevel, "ParsePowerResponse response: {0}", ComTextHelper.GetEscapedText(response)); if (response[2] == 0x00) { + return response[3] == 0x01; } @@ -44,7 +45,7 @@ public static bool ParsePowerResponse(this byte[] response) public static string ParseInputResponse(this byte[] response) { // TODO [ ] actually add in parsing - Debug.Console(DebugLevels.DebugLevel, "ParseInputResponse response: {0}", response.ToReadableString()); + Debug.Console(DebugLevels.DebugLevel, "ParseInputResponse response: {0}", ComTextHelper.GetEscapedText(response)); //add together the input type byte & the input number byte var inputNumber = response[3] << 8 | response[4]; @@ -62,6 +63,7 @@ public static string ParseInputResponse(this byte[] response) public static int ParseVolumeResponse(this byte[] response) { + Debug.Console(DebugLevels.DebugLevel, "ParseVolumeResponse response: {0}", ComTextHelper.GetEscapedText(response)); //not a direct volume response if (response[3] != 0x01) { @@ -78,6 +80,7 @@ public static int ParseVolumeResponse(this byte[] response) /// public static bool ParseMuteResponse(this byte[] response) { + Debug.Console(DebugLevels.DebugLevel, "ParseMuteResponse response: {0}", ComTextHelper.GetEscapedText(response)); //not a direct mute response if (response[3] != 0x01) { return false; } diff --git a/src/SonyBraviaConfig.cs b/src/SonyBraviaConfig.cs index 94d58bb..4e81840 100644 --- a/src/SonyBraviaConfig.cs +++ b/src/SonyBraviaConfig.cs @@ -1,5 +1,7 @@ -using PepperDash.Core; +using Newtonsoft.Json; +using PepperDash.Core; using PepperDash.Essentials.Core; +using System.Collections.Generic; namespace SonyBraviaEpi { @@ -11,5 +13,20 @@ public class SonyBraviaConfig public long? WarmingTimeMs { get; set; } public long? CoolingTimeMs { get; set; } public bool ForceRs232 { get; set; } + + [JsonProperty("maxVolumeLevel")] + public byte MaxVolumeLevel { get; set; } = 0xFF; + + [JsonProperty("activeInputs")] + public List ActiveInputs { get; set; } = new List(); + } + + public class SonyBraviaInputConfig:IKeyName + { + [JsonProperty("key")] + public string Key { get; set; } = string.Empty; + + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/src/SonyBraviaDevice.cs b/src/SonyBraviaDevice.cs index 0f88eca..df7b942 100644 --- a/src/SonyBraviaDevice.cs +++ b/src/SonyBraviaDevice.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.Drawing.Text; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharpPro.CrestronThread; using Crestron.SimplSharpPro.DeviceSupport; +using Org.BouncyCastle.Asn1.Cmp; using PepperDash.Core; +using PepperDash.Core.Logging; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; @@ -25,8 +28,10 @@ public class SonyBraviaDevice : TwoWayDisplayBase, ICommunicationMonitor, IBridg , IHasInputs #endif { + private const long pollTime = 2000; private readonly IBasicCommunication _coms; private readonly bool _comsIsRs232; + public bool ComsIsRs232 { get { return _comsIsRs232; } } public static GenericQueue CommandQueue; public static readonly CommunicationMonitorConfig DefaultMonitorConfig = new CommunicationMonitorConfig @@ -37,10 +42,14 @@ public class SonyBraviaDevice : TwoWayDisplayBase, ICommunicationMonitor, IBridg }; private readonly CTimer _pollTimer; + private CTimer _volumeTimer; + private int _volumeCounter; private readonly IQueueMessage _powerOffCommand; - private readonly IQueueMessage _powerOnCommand; + private readonly IQueueMessage _powerOnCommand; - private readonly CrestronQueue _queueRs232; + private byte[] _incomingBuffer = { }; + + private readonly GenericQueue _queueRs232; private readonly CrestronQueue _queueSimpleIp; private string _currentInput; private bool _powerIsOn; @@ -49,13 +58,20 @@ public class SonyBraviaDevice : TwoWayDisplayBase, ICommunicationMonitor, IBridg private readonly long _coolingTimeMs; private readonly long _warmingtimeMs; + private int pollIndex = 0; + private List _activeInputs; + private Dictionary _ackStringFormats = new Dictionary { {0x00, "Control complete ({0})"}, {0x01, "Abnormal End: over maximum value ({0})" }, {0x02, "Abnormal End: under minimum value ({0})" }, {0x03, "Abnormal End: command cancelled ({0})"}, {0x04, "Abnormal End: parse error/data format error ({0})" } - }; + }; + + private byte maxVolumeLevel = 0xFF; + private Dictionary _defaultInputs; + /// /// Constructor @@ -75,21 +91,29 @@ public SonyBraviaDevice(DeviceConfig config, IBasicCommunication comms) IQueueMessage inputQuery; IQueueMessage volumeQuery; IQueueMessage muteQuery; - + _coms = comms; var socket = _coms as ISocketStatus; _comsIsRs232 = socket == null || props.ForceRs232; if (_comsIsRs232) { - _queueRs232 = new CrestronQueue(50); - _coms.BytesReceived += (sender, args) => _queueRs232.Enqueue(args.Bytes); + _queueRs232 = new GenericQueue(string.Format("{0}-r232queue", Key), 50); + + _coms.BytesReceived += (sender, args) => { + Debug.Console(DebugLevels.DebugLevel, this, "received response: {0}", ComTextHelper.GetEscapedText(args.Bytes)); + + _queueRs232.Enqueue(new Rs232Response(args.Bytes, ProcessRs232Response)); + }; - _powerOnCommand = Rs232Commands.GetPowerOn(_coms, UpdateLastSentCommandType); - _powerOffCommand = Rs232Commands.GetPowerOff(_coms, UpdateLastSentCommandType); - powerQuery = Rs232Commands.GetPowerQuery(_coms, UpdateLastSentCommandType); - inputQuery = Rs232Commands.GetInputQuery(_coms, UpdateLastSentCommandType); - volumeQuery = Rs232Commands.GetVolumeQuery(_coms, UpdateLastSentCommandType); - muteQuery = Rs232Commands.GetMuteQuery(_coms, UpdateLastSentCommandType); + //_coms.BytesReceived += (sender, args) => ProcessRs232Response(args.Bytes); + + + _powerOnCommand = Rs232Commands.GetPowerOn(_coms, (c) => { }); + _powerOffCommand = Rs232Commands.GetPowerOff(_coms, (c) => { }); + powerQuery = Rs232Commands.GetPowerQuery(_coms, (c) => { }); + inputQuery = Rs232Commands.GetInputQuery(_coms, (c) => { }); + volumeQuery = Rs232Commands.GetVolumeQuery(_coms, (c) => { }); + muteQuery = Rs232Commands.GetMuteQuery(_coms, (c) => { }); } else { @@ -106,7 +130,7 @@ public SonyBraviaDevice(DeviceConfig config, IBasicCommunication comms) } if (CommandQueue == null) - CommandQueue = new GenericQueue(string.Format("{0}-commandQueue", config.Key),100, 50); + CommandQueue = new GenericQueue(string.Format("{0}-commandQueue", config.Key),500, 50); var monitorConfig = props.CommunicationMonitorProperties ?? DefaultMonitorConfig; CommunicationMonitor = new GenericCommunicationMonitor( @@ -115,17 +139,91 @@ public SonyBraviaDevice(DeviceConfig config, IBasicCommunication comms) BuildInputRoutingPorts(); + _activeInputs = props.ActiveInputs; + + this.LogVerbose("active inputs from config {@activeInputs}", _activeInputs); + + var empty = new byte[] { }; + + _defaultInputs = new Dictionary + { + { + "hdmi1", _comsIsRs232 + ? new SonyBraviaInput("Hdmi1", "HDMI 1", this, Rs232Commands.InputHdmi1.WithChecksum()) + : new SonyBraviaInput("Hdmi1", "HDMI 1", this, SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 1)) + }, + { + + "hdmi2", _comsIsRs232 + ? new SonyBraviaInput("Hdmi2", "HDMI 2", this, Rs232Commands.InputHdmi2.WithChecksum()) + : new SonyBraviaInput("Hdmi2", "HDMI 2", this, SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 2)) + }, + { + "hdmi3", _comsIsRs232 + ? new SonyBraviaInput("Hdmi3", "HDMI 3", this, Rs232Commands.InputHdmi3.WithChecksum()) + : new SonyBraviaInput("Hdmi3", "HDMI 3", this, SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 3)) + }, + { + "hdmi4", _comsIsRs232 + ? new SonyBraviaInput("Hdmi4", "HDMI 4", this, Rs232Commands.InputHdmi4.WithChecksum()) + : new SonyBraviaInput("Hdmi4", "HDMI 4", this, SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 4)) + }, + { + "hdmi5", _comsIsRs232 + ? new SonyBraviaInput("Hdmi5", "HDMI 5", this, Rs232Commands.InputHdmi5.WithChecksum()) + : new SonyBraviaInput("Hdmi5", "HDMI 5", this, SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 5)) + }, + { + "video1",_comsIsRs232 + ? new SonyBraviaInput("video1", "Video 1", this, Rs232Commands.InputVideo1.WithChecksum()) + : new SonyBraviaInput("video1", "Video 1", this, SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 1)) + }, + { + "video2",_comsIsRs232 + ? new SonyBraviaInput("video2", "Video 2", this, Rs232Commands.InputVideo2.WithChecksum()) + : new SonyBraviaInput("video2", "Video 2", this, SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 2)) + }, + { + "video3", _comsIsRs232 + ? new SonyBraviaInput("video3", "Video 3", this, Rs232Commands.InputVideo3.WithChecksum()) + : new SonyBraviaInput("video3", "Video 3", this, SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 3)) + }, + { + "component1", _comsIsRs232 + ? new SonyBraviaInput("component1", "Component 1", this, Rs232Commands.InputComponent1.WithChecksum()) + : new SonyBraviaInput("component1", "Component 1", this,SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 1)) + }, + { + "component2", _comsIsRs232 + ? new SonyBraviaInput("component2", "Component 2", this, Rs232Commands.InputComponent2.WithChecksum()) + : new SonyBraviaInput("component2", "Component 2", this,SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 2)) + }, + { + "component3", _comsIsRs232 + ? new SonyBraviaInput("component3", "Component 3", this, Rs232Commands.InputComponent3.WithChecksum()) + : new SonyBraviaInput("component3", "Component 3", this,SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 3)) + }, + { + "vga1",_comsIsRs232 ? new SonyBraviaInput("vga1", "VGA 1", this, Rs232Commands.InputComponent1.WithChecksum()) + : new SonyBraviaInput("vga1", "VGA 1", this, empty) + } + }; + SetupInputs(); var worker = _comsIsRs232 - ? new Thread(ProcessRs232Response, null) + ? null //new Thread(ProcessRs232Response, null) : new Thread(ProcessSimpleIpResponse, null); - _pollTimer = new CTimer((o) => Poll(new List { powerQuery, inputQuery, muteQuery, volumeQuery }),Timeout.Infinite); + _pollTimer = _comsIsRs232 + ? new CTimer((o) => PollRs232(new List { Rs232Commands.PowerQuery.WithChecksum(), Rs232Commands.InputQuery.WithChecksum(), Rs232Commands.VolumeQuery.WithChecksum(), Rs232Commands.MuteQuery.WithChecksum()}),Timeout.Infinite) + : new CTimer((o) => Poll(new List { powerQuery, inputQuery, muteQuery, volumeQuery }),Timeout.Infinite); + + maxVolumeLevel = props.MaxVolumeLevel; MuteFeedback = new BoolFeedback(() => _muted); - VolumeLevelFeedback = new IntFeedback(() => CrestronEnvironment.ScaleWithLimits(_rawVolume, 255, 0, 65535, 0)); + VolumeLevelFeedback = new IntFeedback(() => CrestronEnvironment.ScaleWithLimits(_rawVolume,maxVolumeLevel, 0, 65535, 0)); CrestronEnvironment.ProgramStatusEventHandler += type => { @@ -134,7 +232,7 @@ public SonyBraviaDevice(DeviceConfig config, IBasicCommunication comms) if (type != eProgramStatusEventType.Stopping) return; - worker.Abort(); + worker?.Abort(); _pollTimer.Stop(); _pollTimer.Dispose(); @@ -153,7 +251,7 @@ public override void Initialize() { _coms.Connect(); CommunicationMonitor.Start(); - _pollTimer.Reset(5000, 15000); + _pollTimer.Reset(0, pollTime); } catch (Exception ex) { @@ -162,13 +260,26 @@ public override void Initialize() } } - private eCommandType _lastCommandType; + private void PollRs232(List pollCommands) + { + if (pollIndex >= pollCommands.Count) + { + pollIndex = 0; + } - private void UpdateLastSentCommandType(eCommandType commandType) - { - _lastCommandType = commandType; + var command = pollCommands[pollIndex]; + + Debug.Console(2, this, "Sending command {0}", ComTextHelper.GetEscapedText(command)); + + _lastCommand = command; + _coms.SendBytes(command); + + pollIndex += 1; } + private byte[] _lastCommand; + public byte[] LastCommand { set { _lastCommand = value; } } + /// /// Device power is on /// @@ -177,6 +288,11 @@ public bool PowerIsOn get { return _powerIsOn; } set { + if(_powerIsOn == value) + { + return; + } + _powerIsOn = value; if (_powerIsOn) { @@ -289,8 +405,15 @@ public static void Poll(List commands) /// public override void PowerOn() { + if (_comsIsRs232) { + var command = Rs232Commands.PowerOn.WithChecksum(); + _lastCommand = command; + _coms.SendBytes(command); + _pollTimer.Reset(1000, pollTime); + return; + } CommandQueue.Enqueue(_powerOnCommand); - _pollTimer.Reset(1000, 15000); + _pollTimer.Reset(1000, pollTime); } /// @@ -298,8 +421,16 @@ public override void PowerOn() /// public override void PowerOff() { + if (_comsIsRs232) + { + var command = Rs232Commands.PowerOff.WithChecksum(); + _lastCommand = command; + _coms.SendBytes(command); + _pollTimer.Reset(1000, pollTime); + return; + } CommandQueue.Enqueue(_powerOffCommand); - _pollTimer.Reset(1000, 15000); + _pollTimer.Reset(1000, pollTime); } /// @@ -322,9 +453,16 @@ public override void PowerToggle() /// public void PowerPoll() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetPowerQuery(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetQueryCommand(_coms, "POWR")); + if (_comsIsRs232) + { + var command = Rs232Commands.PowerQuery.WithChecksum(); + _lastCommand = command; + _coms.SendBytes(command); + _pollTimer.Reset(1000, pollTime); + return; + } + CommandQueue.Enqueue(SimpleIpCommands.GetQueryCommand(_coms, "POWR")); + _pollTimer.Reset(1000, pollTime); } /// @@ -405,74 +543,16 @@ public void BuildInputRoutingPorts() private void SetupInputs() { -#if SERIES4 + this.LogDebug("Found {activeInputCount} active Inputs & {defaultInputCount} default Inputs", _activeInputs?.Count, _defaultInputs?.Count); + Inputs = new SonyBraviaInputs { - Items = new Dictionary - { - { - "hdmi1", new SonyBraviaInput("Hdmi1", "HDMI 1", this, - _comsIsRs232 ? Rs232Commands.GetHdmi1(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 1))}, - { - - "hdmi2", new SonyBraviaInput("hdmi2", "HDMI 2", this, - _comsIsRs232 ? Rs232Commands.GetHdmi2(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 2)) - }, - { - "hdmi3", new SonyBraviaInput("hdmi3", "HDMI 3", this, - _comsIsRs232 ? Rs232Commands.GetHdmi3(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 3)) - }, - { - "hdmi4", new SonyBraviaInput("hdmi4", "HDMI 4", this, - _comsIsRs232 ? Rs232Commands.GetHdmi4(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 4)) - }, - { - "hdmi5", new SonyBraviaInput("hdmi5", "HDMI 5", this, - _comsIsRs232 ? Rs232Commands.GetHdmi5(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 5)) - }, - { - "video1", new SonyBraviaInput("video1", "Video 1", this, - _comsIsRs232 ? Rs232Commands.GetVideo1(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 1)) - }, - { - "video2", new SonyBraviaInput("video2", "Video 2", this, - _comsIsRs232 ? Rs232Commands.GetVideo2(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 2)) - }, - { - "video3", new SonyBraviaInput("video3", "Video 3", this, - _comsIsRs232 ? Rs232Commands.GetVideo3(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 3)) - }, - { - "component1", new SonyBraviaInput("component1", "Component 1", this, - _comsIsRs232 ? Rs232Commands.GetComponent1(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 1)) - }, - { - "component2", new SonyBraviaInput("component2", "Component 2", this, - _comsIsRs232 ? Rs232Commands.GetComponent2(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 2)) - }, - { - "component3", new SonyBraviaInput("component3", "Component 3", this, - _comsIsRs232 ? Rs232Commands.GetComponent3(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 3)) - }, - { - "vga1", new SonyBraviaInput("vga1", "VGA 1", this, - _comsIsRs232 ? Rs232Commands.GetComponent1(_coms, UpdateLastSentCommandType) - : null) - } - } + Items = _activeInputs == null || _activeInputs.Count == 0 + ? _defaultInputs + : _defaultInputs + .Where(kv => _activeInputs.Any((i) => i.Key.Equals(kv.Key, StringComparison.InvariantCultureIgnoreCase))) + .ToDictionary(kv => kv.Key, kv => kv.Value) }; -#endif } /// @@ -480,9 +560,15 @@ private void SetupInputs() /// public void InputHdmi1() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetHdmi1(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 1)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputHdmi1.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 1)); } /// @@ -490,9 +576,15 @@ public void InputHdmi1() /// public void InputHdmi2() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetHdmi2(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 2)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputHdmi2.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 2)); } /// @@ -500,9 +592,15 @@ public void InputHdmi2() /// public void InputHdmi3() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetHdmi3(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 3)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputHdmi3.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 3)); } /// @@ -510,9 +608,15 @@ public void InputHdmi3() /// public void InputHdmi4() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetHdmi4(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 4)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputHdmi4.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 4)); } /// @@ -520,9 +624,15 @@ public void InputHdmi4() /// public void InputHdmi5() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetHdmi5(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 5)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputHdmi5.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Hdmi, 5)); } /// @@ -530,9 +640,15 @@ public void InputHdmi5() /// public void InputVideo1() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetVideo1(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 1)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputVideo1.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 1)); } /// @@ -540,9 +656,15 @@ public void InputVideo1() /// public void InputVideo2() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetVideo2(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 2)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputVideo2.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 2)); } /// @@ -550,9 +672,15 @@ public void InputVideo2() /// public void InputVideo3() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetVideo3(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 3)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputVideo3.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Composite, 3)); } /// @@ -560,9 +688,15 @@ public void InputVideo3() /// public void InputComponent1() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetComponent1(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 1)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputComponent1.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 1)); } /// @@ -570,9 +704,15 @@ public void InputComponent1() /// public void InputComponent2() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetComponent2(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 2)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputComponent2.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 2)); } /// @@ -580,9 +720,15 @@ public void InputComponent2() /// public void InputComponent3() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetComponent3(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 3)); + if (_comsIsRs232) + { + var command = Rs232Commands.InputComponent3.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetInputCommand(_coms, SimpleIpCommands.InputTypes.Component, 3)); } /// @@ -590,22 +736,14 @@ public void InputComponent3() /// public void InputVga1() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetComponent1(_coms, UpdateLastSentCommandType) - : null); - } + if (!_comsIsRs232) return; - /// - /// Poll device for input state - /// - public void InputPoll() - { - // byte[] poll = { 0x83, 0x00, 0x02, 0xFF, 0xFF, 0x83 }; - //CommandQueue.Enqueue(Rs232Commands.GetInputQuery(_coms)); - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetInputQuery(_coms, UpdateLastSentCommandType) - : SimpleIpCommands.GetQueryCommand(_coms, "INPT")); + var command = Rs232Commands.InputComponent1.WithChecksum(); + _coms.SendBytes(command); + _lastCommand = command; + return; } + /// /// Execute switch @@ -641,150 +779,116 @@ public override void ExecuteSwitch(object selector) } } - private object ProcessRs232Response(object _) + public void ResetPolling() { - var seperator = new string('-', 50); + _pollTimer.Reset(100, pollTime); + } - byte[] buffer = null; - while (true) + public void SendRs232Command(byte[] command) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Sending command: {command}",this, command); + + _lastCommand = command; + _coms.SendBytes(command); + _pollTimer.Reset(100, pollTime); + } + + private void ProcessRs232Response(byte[] response) + { + try { - try - { - var bytes = _queueRs232.Dequeue(); - if (bytes == null) - { - Debug.Console(DebugLevels.ErrorLevel, this, "ProcessRs232Response: _queueRs232.Dequeue failed, object was null"); - return null; - } + var buffer = new byte[_incomingBuffer.Length + response.Length]; + _incomingBuffer.CopyTo(buffer, 0); + response.CopyTo(buffer, _incomingBuffer.Length); - Debug.Console(DebugLevels.ErrorLevel, this, "ProcessRs232Response: bytes-'{0}' (len-'{1}')", bytes.ToReadableString(), bytes.Length); + // Debug.Console(DebugLevels.DebugLevel, this, "ProcessRs232Response: {0}", ComTextHelper.GetEscapedText(buffer)); - if (buffer == null) - buffer = bytes; - else + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "ProcessRs232Response: {lastCommand}:{response}", this, ComTextHelper.GetEscapedText(_lastCommand), ComTextHelper.GetEscapedText(buffer)); + + //it starts a valid response + if (buffer.Length >= 3) + { + //If the message is an ack, byte3 will be the sum of the first 2 bytes + if (buffer[0] == 0x70 & buffer[2] == buffer[0] + buffer[1]) { - var newBuffer = new byte[buffer.Length + bytes.Length]; - buffer.CopyTo(newBuffer, 0); - bytes.CopyTo(newBuffer, buffer.Length); - buffer = newBuffer; + var message = new byte[3]; + buffer.CopyTo(message, 0); + HandleAck(message); + byte[] clear = { }; + _incomingBuffer = clear; + return; } - Debug.Console(DebugLevels.ErrorLevel, this, "ProcessRs232Response: bytes-'{0}' (len-'{1}') | buffer-'{2}' (len-'{3}')", bytes.ToReadableString(), bytes.Length, buffer.ToReadableString(), buffer.Length); + //header length is 3. + var messageLength = 3 + buffer[2]; - if (!buffer.ContainsHeader()) + if(buffer[0] == 0x70 && buffer.Length >= messageLength) { - Debug.Console(DebugLevels.ErrorLevel, this, "ProcessRs232Response: buffer-'{0}' (len-'{1}') did not contain a header", - buffer.ToReadableString(), buffer.Length); - - continue; + var message = new byte[messageLength]; + buffer.CopyTo(message, 0); + ParseMessage(message); + byte[] clear = { }; + _incomingBuffer = clear; + return; } + } - if (buffer.ElementAtOrDefault(0) != 0x70) - buffer = buffer.CleanToFirstHeader(); - - Debug.Console(DebugLevels.ErrorLevel, this, "ProcessRs232Response: bytes-'{0}' (len-'{1}') | buffer-'{2}' (len-'{3}')", - bytes.ToReadableString(), bytes.Length, buffer.ToReadableString(), buffer.Length); + if (buffer[0] == 0x70) + { + _incomingBuffer = buffer; + } else + { + byte[] clear = { }; + _incomingBuffer = clear; + } + + } + catch (Exception ex) + { + Debug.Console(DebugLevels.TraceLevel, this, Debug.ErrorLogLevel.Error, "ProcessRs232Response Exception: {0}", ex.Message); + Debug.Console(DebugLevels.DebugLevel, this, Debug.ErrorLogLevel.Error, "ProcessRs232Response Exception Stack Trace: {0}", ex.StackTrace); + if (ex.InnerException != null) + Debug.Console(DebugLevels.ErrorLevel, this, Debug.ErrorLogLevel.Error, "ProcessRs232Response Inner Exception: {0}", ex.InnerException); + } + } - const int safety = 10; - var numberOfSpins = 0; - while (buffer.Length >= 3 && numberOfSpins <= safety) + private void ParseMessage(byte[] message) + { + // 3rd byte is the command type + switch (_lastCommand[2]) + { + case 0x00: //power + PowerIsOn = Rs232ParsingUtils.ParsePowerResponse(message); + PowerIsOnFeedback.FireUpdate(); + break; + case 0x02: //input + _currentInput = Rs232ParsingUtils.ParseInputResponse(message); + CurrentInputFeedback.FireUpdate(); +#if SERIES4 + if (Inputs.Items.ContainsKey(_currentInput)) { - ++numberOfSpins; - if (numberOfSpins == safety) - Debug.Console(0, - this, - Debug.ErrorLogLevel.Notice, - "We hit our safety limit, something is wrong... Buffer:{0}, Bytes:{1}", - buffer.ToReadableString(), - bytes.ToReadableString()); - - var message = buffer.GetFirstMessage(); - Debug.Console(DebugLevels.ErrorLevel, this, "ProcessRs232Response: bytes-'{0}' (len-'{1}') | buffer-'{2}' (len-'{3}') | message-'{4}' (len-'{5}')", - bytes.ToReadableString(), bytes.Length, - buffer.ToReadableString(), buffer.Length, - message.ToReadableString(), message.Length); - switch (_lastCommandType) + foreach(var input in Inputs.Items) { - case eCommandType.Command: - { - HandleAck(message); - buffer = buffer.CleanOutFirstMessage(); - continue; - } - - } - - // we have a full message, lets check it out - Debug.Console(DebugLevels.ErrorLevel, this, "ProcessRs232Response: message-'{0}' (len-'{1}')", - message.ToReadableString(), message.Length); - - var dataSize = message[2]; - var totalDataSize = dataSize + 3; - var isComplete = totalDataSize == message.Length; - Debug.Console( - DebugLevels.ErrorLevel, this, "ProcessRs232Response: dataSize-'{0}' | totalDataSize-'{1}' | message.Length-'{2}'", - dataSize, totalDataSize, message.Length); - - if (!isComplete) - { - Debug.Console(DebugLevels.DebugLevel, this, "Message is incomplete... spinning around"); - break; + input.Value.IsSelected = input.Key.Equals(_currentInput); } + } - switch (_lastCommandType) - { - case eCommandType.PowerQuery: - { - PowerIsOn = buffer.ParsePowerResponse(); - - PowerIsOnFeedback.FireUpdate(); - break; - } - case eCommandType.InputQuery: - { - _currentInput = buffer.ParseInputResponse(); - CurrentInputFeedback.FireUpdate(); - -#if SERIES4 - if (Inputs.Items.ContainsKey(_currentInput)) - { - foreach (var item in Inputs.Items) - { - item.Value.IsSelected = item.Key.Equals(_currentInput); - } - } - - Inputs.CurrentItem = _currentInput; + Inputs.CurrentItem = _currentInput; #endif - break; - } - case eCommandType.VolumeQuery: - { - _rawVolume = buffer.ParseVolumeResponse(); - VolumeLevelFeedback.FireUpdate(); - break; - } - case eCommandType.MuteQuery: - { - _muted = buffer.ParseMuteResponse(); - MuteFeedback.FireUpdate(); - break; - } - } - buffer = buffer.NumberOfHeaders() > 1 ? buffer.CleanOutFirstMessage() : new byte[0]; - } - } - catch (Exception ex) - { - Debug.Console(DebugLevels.TraceLevel, this, Debug.ErrorLogLevel.Error, "ProcessRs232Response Exception: {0}", ex.Message); - Debug.Console(DebugLevels.DebugLevel, this, Debug.ErrorLogLevel.Error, "ProcessRs232Response Exception Stack Trace: {0}", ex.StackTrace); - if (ex.InnerException != null) - Debug.Console(DebugLevels.ErrorLevel, this, Debug.ErrorLogLevel.Error, "ProcessRs232Response Inner Exception: {0}", ex.InnerException); - - Debug.Console(DebugLevels.DebugLevel, this, seperator); - } + break; + case 0x05: //volume + _rawVolume = Rs232ParsingUtils.ParseVolumeResponse(message); + VolumeLevelFeedback.FireUpdate(); + break; + case 0x06: //mute + _muted = Rs232ParsingUtils.ParseMuteResponse(message); + MuteFeedback.FireUpdate(); + break; + default: + Debug.Console(0, this, "Unknown response received: {0}", ComTextHelper.GetEscapedText(message)); + break; } } @@ -794,11 +898,11 @@ private void HandleAck(byte[] message) if (!_ackStringFormats.TryGetValue(message[1], out consoleMessageFormat)) { - Debug.Console(DebugLevels.DebugLevel, this, "Unknown Response: {0}", message.ToReadableString()); + Debug.Console(DebugLevels.DebugLevel, this, "Unknown Response: {0}", ComTextHelper.GetEscapedText(message)); return; } - Debug.Console(DebugLevels.DebugLevel, this, consoleMessageFormat, message.ToReadableString()); + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Ack: {message}", this, string.Format(consoleMessageFormat, ComTextHelper.GetEscapedText(message))); } private object ProcessSimpleIpResponse(object _) @@ -949,23 +1053,41 @@ public void SetInput(string selector) { input.Select(); } + + _pollTimer.Reset(1000, pollTime); } #endif public void MuteOn() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetMuteOn(_coms, UpdateLastSentCommandType) - : null); + if (_comsIsRs232) + { + var command = Rs232Commands.MuteOn.WithChecksum(); + _lastCommand = command; + _coms.SendBytes(command); + _pollTimer.Reset(100, pollTime); + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetControlCommand(_coms, "MUTE", 0)); + _pollTimer.Reset(100, pollTime); } public void MuteOff() { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetMuteOff(_coms, UpdateLastSentCommandType) - : null); + if (_comsIsRs232) + { + var command = Rs232Commands.MuteOff.WithChecksum(); + _lastCommand = command; + _coms.SendBytes(command); + _pollTimer.Reset(1000, pollTime); + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetControlCommand(_coms, "MUTE", 1)); + _pollTimer.Reset(1000, pollTime); } public void MuteToggle() - { + { if (_muted) { MuteOff(); @@ -977,27 +1099,101 @@ public void MuteToggle() public void SetVolume(ushort level) { - var scaledVolume = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, 255, 0); + var scaledVolume = CrestronEnvironment.ScaleWithLimits(level, 65535, 0, maxVolumeLevel, 0); - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetVolumeDirect(_coms, UpdateLastSentCommandType, scaledVolume) - : null); + Debug.Console(2, this, "Input level: {0} scaled: {1}", level, scaledVolume); + + var volumeCommand = Rs232Commands.VolumeDirect; + + volumeCommand[5] = (byte) scaledVolume; + + if (_comsIsRs232) + { + var command = volumeCommand.WithChecksum(); + _lastCommand = command; + _coms.SendBytes(command); + + _pollTimer.Reset(0, pollTime); + + return; + } + + CommandQueue.Enqueue(SimpleIpCommands.GetControlCommand(_coms, "VOLM", scaledVolume)); + _pollTimer.Reset(1000, pollTime); } public void VolumeUp(bool pressRelease) { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetVolumeUp(_coms, UpdateLastSentCommandType) - : null); + if (!pressRelease) + { + if (_volumeTimer != null) + { + _volumeTimer.Stop(); + _volumeTimer.Dispose(); + _volumeTimer = null; + } + + var command = Rs232Commands.VolumeQuery.WithChecksum(); + _lastCommand = command; + _coms.SendBytes(command); + + _pollTimer.Reset(1000, pollTime); + return; + } + + if (_comsIsRs232) + { + _pollTimer.Stop(); + + _volumeCounter = 0; + + _volumeTimer = new CTimer(o => { + var command = _volumeCounter % 2 == 0 ? Rs232Commands.VolumeUp.WithChecksum() : Rs232Commands.VolumeQuery.WithChecksum(); + _lastCommand = command; + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, _volumeCounter % 2 == 0 ? "Sending Volume Up command {command}" : "Sending Volume Query command {command}", this, ComTextHelper.GetEscapedText(command)); + _coms.SendBytes(command); + _volumeCounter += 1; + }, null, 0, 500); + + return; + } } public void VolumeDown(bool pressRelease) { - CommandQueue.Enqueue(_comsIsRs232 - ? Rs232Commands.GetVolumeDown(_coms, UpdateLastSentCommandType) - : null); - } + if (!pressRelease) + { + if (_volumeTimer != null) + { + _volumeTimer.Stop(); + _volumeTimer.Dispose(); + _volumeTimer = null; + } + + var command = Rs232Commands.VolumeQuery.WithChecksum(); + _lastCommand = command; + _coms.SendBytes(command); + + _pollTimer.Reset(1000, pollTime); + return; + } + + if (_comsIsRs232) + { + _pollTimer.Stop(); + _volumeCounter = 0; + _volumeTimer = new CTimer(o => { + var command = _volumeCounter % 2 == 0 ? Rs232Commands.VolumeDown.WithChecksum() : Rs232Commands.VolumeQuery.WithChecksum(); + _lastCommand = command; + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, _volumeCounter % 2 == 0 ? "Sending Volume Down command {command}" : "Sending Volume Query command {command}", this, ComTextHelper.GetEscapedText(command)); + _coms.SendBytes(command); + _volumeCounter += 1; + }, null, 0, 500); + + return; + } + } } } \ No newline at end of file diff --git a/src/SonyBraviaFactory.cs b/src/SonyBraviaFactory.cs index 1222a19..339224a 100644 --- a/src/SonyBraviaFactory.cs +++ b/src/SonyBraviaFactory.cs @@ -16,19 +16,19 @@ public SonyBraviaFactory() public override EssentialsDevice BuildDevice(DeviceConfig dc) { - Debug.Console(DebugLevels.TraceLevel, "[{0}] Building {1} plugin instance...", dc.Name, dc.Type); + Debug.Console(DebugLevels.TraceLevel, "[{0}] Building {1} plugin instance...", dc.Key, dc.Type); var props = dc.Properties.ToObject(); if (props == null) { - Debug.Console(DebugLevels.TraceLevel, "[{0}] Failed to build {1} plugin", dc.Name, dc.Type); + Debug.Console(DebugLevels.TraceLevel, "[{0}] Failed to build {1} plugin", dc.Key, dc.Type); return null; } var comms = CommFactory.CreateCommForDevice(dc); if (comms != null) return new SonyBraviaDevice(dc, comms); - Debug.Console(DebugLevels.TraceLevel, "[{0}] Failed to build {1} plugin using {2}", dc.Name, dc.Type, props.Control.Method); + Debug.Console(DebugLevels.TraceLevel, "[{0}] Failed to build {1} plugin using {2}", dc.Key, dc.Type, props.Control.Method); return null; } } diff --git a/src/SonyBraviaInputs.cs b/src/SonyBraviaInputs.cs index 0c24c88..f478e5d 100644 --- a/src/SonyBraviaInputs.cs +++ b/src/SonyBraviaInputs.cs @@ -1,4 +1,5 @@  +using PepperDash.Core; using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.Queues; using SonyBraviaEpi; @@ -60,8 +61,10 @@ public class SonyBraviaInput : ISelectableItem { private bool _isSelected; + private readonly byte[] _command; + private readonly IQueueMessage _inputCommand; - private readonly SonyBraviaDevice _parent; + private readonly SonyBraviaDevice _parent; public SonyBraviaInput(string key, string name, SonyBraviaDevice parent, IQueueMessage inputCommand) { @@ -71,6 +74,14 @@ public SonyBraviaInput(string key, string name, SonyBraviaDevice parent, IQueueM _inputCommand = inputCommand; } + public SonyBraviaInput(string key, string name, SonyBraviaDevice parent, byte[] command) + { + Key = key; + Name = name; + _command = command; + _parent = parent; + } + public string Key { get; private set; } public string Name { get; private set; } @@ -93,6 +104,15 @@ public bool IsSelected public void Select() { + if(_parent.ComsIsRs232) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Sending input command for {name}: {command}",this, Name, ComTextHelper.GetEscapedText(_command)); + + _parent.SendRs232Command(_command); + + return; + } + _parent.EnqueueCommand(_inputCommand); } } diff --git a/src/epi-sony-bravia.4Series.csproj b/src/epi-sony-bravia.4Series.csproj index 807931b..77ab97f 100644 --- a/src/epi-sony-bravia.4Series.csproj +++ b/src/epi-sony-bravia.4Series.csproj @@ -10,7 +10,7 @@ PepperDash Technologies This software is a plugin designed to work as a part of PepperDash Essentials for Crestron control processors. This plugin allows for control of Sony Bravia Displays. Copyright 2024 - 1.0.0-local + 1.0.1-local true $(Version) 4Series\bin\$(Configuration)\ @@ -43,6 +43,6 @@ - +