diff --git a/.github/scripts/ZipBuildOutput.ps1 b/.github/scripts/ZipBuildOutput.ps1 index 6e44736..34856fb 100644 --- a/.github/scripts/ZipBuildOutput.ps1 +++ b/.github/scripts/ZipBuildOutput.ps1 @@ -8,6 +8,7 @@ $destination = "$($Env:GITHUB_HOME)\output" New-Item -ItemType Directory -Force -Path ($destination) Get-ChildItem ($destination) $exclusions = @(git submodule foreach --quiet 'echo $name') +$exclusions += "Newtonsoft.Json.Compact.dll" # Trying to get any .json schema files (not currently working) # Gets any files with the listed extensions. Get-ChildItem -recurse -Path "$($Env:GITHUB_WORKSPACE)" -include "*.clz", "*.cpz", "*.cplz", "*.json", "*.nuspec" | ForEach-Object { diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9e09430..5449af9 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -152,156 +152,3 @@ jobs: run: nuget push **/*.nupkg -source github - name: Publish nuget package to nuget.org run: nuget push **/*.nupkg -Source https://api.nuget.org/v3/index.json - # This step always runs and pushes the build to the internal build rep - Internal_Push_Output: - needs: Build_Project - runs-on: windows-latest - steps: - # Checkout the repo - - name: Checkout Builds Repo - uses: actions/checkout@v2 - with: - token: ${{ secrets.BUILDS_TOKEN }} - repository: PepperDash-Engineering/pepperdash-core-builds - ref: ${{ Env.GITHUB_REF }} - # Download the version artifact from the build job - - name: Download Build Version Info - uses: actions/download-artifact@v1 - with: - name: Version - - name: Check Directory - run: Get-ChildItem "./" - # Set the version number environment variable from the file we just downloaded - - name: Set Version Number - shell: powershell - run: | - Get-ChildItem "./Version" - $version = Get-Content -Path ./Version/version.txt - Write-Host "Version: $version" - echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - Remove-Item -Path ./Version/version.txt - Remove-Item -Path ./Version - # Checkout/Create the branch - - name: Create new branch - run: git checkout -b $($Env:GITHUB_REF -replace "refs/heads/") - # Download the build output into the repo - - name: Download Build output - uses: actions/download-artifact@v1 - with: - name: Build - path: ./ - - name: Check directory - run: Get-ChildItem ./ - # Unzip the build package file - - name: Unzip Build file - run: | - Get-ChildItem .\*.zip | Expand-Archive -DestinationPath .\ - Remove-Item -Path .\*.zip - - name: Check directory again - run: Get-ChildItem ./ - # Copy Contents of output folder to root directory - - name: Copy Files to root & delete output directory - run: | - Remove-Item -Path .\* -Include @("*.cpz","*.md","*.cplz","*.json","*.dll","*.clz") - Get-ChildItem -Path .\output\* | Copy-Item -Destination .\ - Remove-Item -Path .\output -Recurse - # Commits the build output to the branch and tags it with the version - - name: Commit build output and tag the commit - shell: powershell - run: | - git config user.email "actions@pepperdash.com" - git config user.name "GitHub Actions" - git add . - $commit = "Build $($Env:GITHUB_RUN_NUMBER) from commit: https://github.com/$($Env:GITHUB_REPOSITORY)/commit/$($Env:GITHUB_SHA)" - Write-Host "Commit: $commit" - git commit -m $commit - git tag $($Env:VERSION) - # Push the commit - - name: Push to Builds Repo - shell: powershell - run: | - $branch = $($Env:GITHUB_REF) -replace "refs/heads/" - Write-Host "Branch: $branch" - git push -u origin $($branch) --force - # Push the tags - - name: Push tags - run: git push --tags origin - - name: Check Directory - run: Get-ChildItem ./ - # This step only runs if the branch is main or release/ runs and pushes the build to the public build repo - Public_Push_Output: - needs: Build_Project - runs-on: windows-latest - if: contains(github.ref, 'main') || contains(github.ref, 'release') - steps: - # Checkout the repo - - name: Checkout Builds Repo - uses: actions/checkout@v2 - with: - token: ${{ secrets.BUILDS_TOKEN }} - repository: PepperDash/PepperDashCore-Builds - ref: ${{ Env.GITHUB_REF }} - # Download the version artifact from the build job - - name: Download Build Version Info - uses: actions/download-artifact@v1 - with: - name: Version - - name: Check Directory - run: Get-ChildItem "./" - # Set the version number environment variable from the file we just downloaded - - name: Set Version Number - shell: powershell - run: | - Get-ChildItem "./Version" - $version = Get-Content -Path ./Version/version.txt - Write-Host "Version: $version" - echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - Remove-Item -Path ./Version/version.txt - Remove-Item -Path ./Version - # Checkout/Create the branch - - name: Create new branch - run: git checkout -b $($Env:GITHUB_REF -replace "refs/heads/") - # Download the build output into the repo - - name: Download Build output - uses: actions/download-artifact@v1 - with: - name: Build - path: ./ - - name: Check directory - run: Get-ChildItem ./ - # Unzip the build package file - - name: Unzip Build file - run: | - Get-ChildItem .\*.zip | Expand-Archive -DestinationPath .\ - Remove-Item -Path .\*.zip - - name: Check directory again - run: Get-ChildItem ./ - # Copy Contents of output folder to root directory - - name: Copy Files to root & delete output directory - run: | - Remove-Item -Path .\* -Include @("*.cpz","*.md","*.cplz","*.json","*.dll","*.clz") - Get-ChildItem -Path .\output\* | Copy-Item -Destination .\ - Remove-Item -Path .\output -Recurse - # Commits the build output to the branch and tags it with the version - - name: Commit build output and tag the commit - shell: powershell - run: | - git config user.email "actions@pepperdash.com" - git config user.name "GitHub Actions" - git add . - $commit = "Build $($Env:GITHUB_RUN_NUMBER) from commit: https://github.com/$($Env:GITHUB_REPOSITORY)/commit/$($Env:GITHUB_SHA)" - Write-Host "Commit: $commit" - git commit -m $commit - git tag $($Env:VERSION) - # Push the commit - - name: Push to Builds Repo - shell: powershell - run: | - $branch = $($Env:GITHUB_REF) -replace "refs/heads/" - Write-Host "Branch: $branch" - git push -u origin $($branch) --force - # Push the tags - - name: Push tags - run: git push --tags origin - - name: Check Directory - run: Get-ChildItem ./ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3c73887..5acc7a8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -127,148 +127,3 @@ jobs: run: nuget push **/*.nupkg -source github - name: Publish nuget package to nuget.org run: nuget push **/*.nupkg -Source https://api.nuget.org/v3/index.json - Internal_Push_Output: - needs: Build_Project - runs-on: windows-latest - steps: - # Checkout the repo - - name: Checkout Builds Repo - uses: actions/checkout@v2 - with: - token: ${{ secrets.BUILDS_TOKEN }} - repository: PepperDash-Engineering/pepperdash-core-builds - ref: ${{ Env.GITHUB_REF }} - # Download the version artifact from the build job - - name: Download Build Version Info - uses: actions/download-artifact@v1 - with: - name: Version - - name: Check Directory - run: Get-ChildItem "./" - # Set the version number environment variable from the file we just downloaded - - name: Set Version Number - shell: powershell - run: | - Get-ChildItem "./Version" - $version = Get-Content -Path ./Version/version.txt - Write-Host "Version: $version" - echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - Remove-Item -Path ./Version/version.txt - Remove-Item -Path ./Version - # Checkout/Create the branch - - name: Create new branch - run: git checkout main - # Download the build output into the repo - - name: Download Build output - uses: actions/download-artifact@v1 - with: - name: Build - path: ./ - - name: Check directory - run: Get-ChildItem ./ - # Unzip the build package file - - name: Unzip Build file - run: | - Get-ChildItem .\*.zip | Expand-Archive -DestinationPath .\ - Remove-Item -Path .\*.zip - - name: Check directory again - run: Get-ChildItem ./ - # Copy Contents of output folder to root directory - - name: Copy Files to root & delete output directory - run: | - Remove-Item -Path .\* -Include @("*.cpz","*.md","*.cplz","*.json","*.dll","*.clz") - Get-ChildItem -Path .\output\* | Copy-Item -Destination .\ - Remove-Item -Path .\output -Recurse - # Commits the build output to the branch and tags it with the version - - name: Commit build output and tag the commit - shell: powershell - run: | - git config user.email "actions@pepperdash.com" - git config user.name "GitHub Actions" - git add . - $commit = "Build $($Env:GITHUB_RUN_NUMBER) from commit: https://github.com/$($Env:GITHUB_REPOSITORY)/commit/$($Env:GITHUB_SHA)" - Write-Host "Commit: $commit" - git commit -m $commit - git tag $($Env:VERSION) - # Push the commit - - name: Push to Builds Repo - shell: powershell - run: git push -u origin main --force - # Push the tags - - name: Push tags - run: git push --tags origin - - name: Check Directory - run: Get-ChildItem ./ - # This step only runs if the branch is main or release/ runs and pushes the build to the public build repo - Public_Push_Output: - needs: Build_Project - runs-on: windows-latest - steps: - # Checkout the repo - - name: Checkout Builds Repo - uses: actions/checkout@v2 - with: - token: ${{ secrets.BUILDS_TOKEN }} - repository: PepperDash/PepperDashCore-Builds - ref: ${{ Env.GITHUB_REF }} - # Download the version artifact from the build job - - name: Download Build Version Info - uses: actions/download-artifact@v1 - with: - name: Version - - name: Check Directory - run: Get-ChildItem "./" - # Set the version number environment variable from the file we just downloaded - - name: Set Version Number - shell: powershell - run: | - Get-ChildItem "./Version" - $version = Get-Content -Path ./Version/version.txt - Write-Host "Version: $version" - echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - Remove-Item -Path ./Version/version.txt - Remove-Item -Path ./Version - # Checkout/Create the branch - - name: Create new branch - run: git checkout main - # Download the build output into the repo - - name: Download Build output - uses: actions/download-artifact@v1 - with: - name: Build - path: ./ - - name: Check directory - run: Get-ChildItem ./ - # Unzip the build package file - - name: Unzip Build file - run: | - Get-ChildItem .\*.zip | Expand-Archive -DestinationPath .\ - Remove-Item -Path .\*.zip - - name: Check directory again - run: Get-ChildItem ./ - # Copy Contents of output folder to root directory - - name: Copy Files to root & delete output directory - run: | - Remove-Item -Path .\* -Include @("*.cpz","*.md","*.cplz","*.json","*.dll","*.clz") - Get-ChildItem -Path .\output\* | Copy-Item -Destination .\ - Remove-Item -Path .\output -Recurse - # Commits the build output to the branch and tags it with the version - - name: Commit build output and tag the commit - shell: powershell - run: | - git config user.email "actions@pepperdash.com" - git config user.name "GitHub Actions" - git add . - $commit = "Build $($Env:GITHUB_RUN_NUMBER) from commit: https://github.com/$($Env:GITHUB_REPOSITORY)/commit/$($Env:GITHUB_SHA)" - Write-Host "Commit: $commit" - git commit -m $commit - git tag $($Env:VERSION) - # Push the commit - - name: Push to Builds Repo - shell: powershell - run: git push -u origin main --force - # Push the tags - - name: Push tags - run: git push --tags origin - - name: Check Directory - run: Get-ChildItem ./ diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs index 6d9a2e5..5c67178 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs @@ -383,18 +383,24 @@ namespace PepperDash.Core if (bytes.Length > 0) { var bytesHandler = BytesReceived; - if (bytesHandler != null) - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + if (bytesHandler != null) + { + if (StreamDebugging.RxStreamDebuggingIsEnabled) + { + Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); + } + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } + var textHandler = TextReceived; if (textHandler != null) { var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Recevied: '{0}'", str); + Debug.Console(0, this, "Received: '{0}'", ComTextHelper.GetDebugText(str)); - } + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } } } @@ -433,14 +439,18 @@ namespace PepperDash.Core { try { - if (Client != null) + if (Client != null && TheStream != null && IsConnected) { if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, text); + Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); TheStream.Write(text); TheStream.Flush(); + } + else + { + Debug.Console(2, this, "Client is null or disconnected. Cannot Send Text"); } } catch (Exception ex) @@ -462,13 +472,17 @@ namespace PepperDash.Core { try { - if (Client != null) + if (Client != null && TheStream != null && IsConnected) { if (StreamDebugging.TxStreamDebuggingIsEnabled) Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); TheStream.Write(bytes, 0, bytes.Length); TheStream.Flush(); + } + else + { + Debug.Console(2, this, "Client is null or disconnected. Cannot Send Bytes"); } } catch diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs index f151b57..480e4d1 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs @@ -284,9 +284,17 @@ namespace PepperDash.Core /// public void Disconnect() { + DisconnectCalledByUser = true; + + // Stop trying reconnects, if we are + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if (Client != null) { - DisconnectCalledByUser = true; DisconnectClient(); Client = null; Debug.Console(1, this, "Disconnected"); @@ -329,7 +337,15 @@ namespace PepperDash.Core Debug.Console(1, this, "Attempting reconnect, status={0}", Client.ClientStatus); if (!DisconnectCalledByUser) - RetryTimer = new CTimer(o => { Client.ConnectToServerAsync(ConnectToServerCallback); }, AutoReconnectIntervalMs); + RetryTimer = new CTimer(o => + { + if (Client == null) + { + return; + } + + Client.ConnectToServerAsync(ConnectToServerCallback); + }, AutoReconnectIntervalMs); } } @@ -348,14 +364,20 @@ namespace PepperDash.Core var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); var bytesHandler = BytesReceived; if (bytesHandler != null) + { + if (StreamDebugging.RxStreamDebuggingIsEnabled) + { + Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); + } bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } var textHandler = TextReceived; if (textHandler != null) { var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Recevied: '{0}'", str); + Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); textHandler(this, new GenericCommMethodReceiveTextArgs(str)); @@ -376,7 +398,7 @@ namespace PepperDash.Core var bytes = Encoding.GetEncoding(28591).GetBytes(text); // Check debug level before processing byte array if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); if(Client != null) Client.SendData(bytes, bytes.Length); diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs index 1ed3d68..36a2657 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs @@ -1,426 +1,393 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - - - - -namespace PepperDash.Core -{ - public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging - { - private const string SplusKey = "Uninitialized Udp Server"; - public CommunicationStreamDebugging StreamDebugging { get; private set; } - /// - /// - /// - public event EventHandler BytesReceived; - - /// - /// - /// - public event EventHandler TextReceived; - - /// - /// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data. - /// - public event EventHandler DataRecievedExtra; - - /// - /// Queue to temporarily store received messages with the source IP and Port info - /// - private CrestronQueue MessageQueue; - - /// - /// - /// - public event EventHandler ConnectionChange; - - /// - /// - /// - public event EventHandler UpdateConnectionStatus; - - /// - /// - /// - public SocketStatus ClientStatus - { - get - { - return Server.ServerStatus; - } - } - - /// - /// - /// - public ushort UStatus - { - get { return (ushort)Server.ServerStatus; } - } - - - CCriticalSection DequeueLock; - /// - /// Address of server - /// - public string Hostname { get; set; } - - /// - /// IP Address of the sender of the last recieved message - /// - - - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// Another damn S+ helper because S+ seems to treat large port nums as signed ints - /// which screws up things - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Indicates that the UDP Server is enabled - /// - public bool IsConnected - { - get; - private set; - } - - public ushort UIsConnected - { - get { return IsConnected ? (ushort)1 : (ushort)0; } - } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - public UDPServer Server { get; private set; } - - /// - /// Constructor for S+. Make sure to set key, address, port, and buffersize using init method - /// - public GenericUdpServer() - : base(SplusKey) - { - StreamDebugging = new CommunicationStreamDebugging(SplusKey); - BufferSize = 5000; - DequeueLock = new CCriticalSection(); - MessageQueue = new CrestronQueue(); - - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); - } - - /// - /// - /// - /// - /// - /// - /// - public GenericUdpServer(string key, string address, int port, int buffefSize) - : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - Hostname = address; - Port = port; - BufferSize = buffefSize; - - DequeueLock = new CCriticalSection(); - MessageQueue = new CrestronQueue(); - - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); - } - - /// - /// Call from S+ to initialize values - /// - /// - /// - /// - public void Initialize(string key, string address, ushort port) - { - Key = key; - Hostname = address; - UPort = port; - } - - /// - /// - /// - /// - void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) - { - // Re-enable the server if the link comes back up and the status should be connected - if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp - && IsConnected) - { - Connect(); - } - } - - /// - /// - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - Debug.Console(1, this, "Program stopping. Disabling Server"); - Disconnect(); - } - } - - /// - /// Enables the UDP Server - /// - public void Connect() - { - if (Server == null) - { - Server = new UDPServer(); - } - - if (string.IsNullOrEmpty(Hostname)) - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key); - return; - } - if (Port < 1 || Port > 65535) - { - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key); - return; - } - } - - var status = Server.EnableUDPServer(Hostname, Port); - - Debug.Console(2, this, "SocketErrorCode: {0}", status); - if (status == SocketErrorCodes.SOCKET_OK) - IsConnected = true; - - var handler = UpdateConnectionStatus; - if (handler != null) - handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); - - // Start receiving data - Server.ReceiveDataAsync(Receive); - } - - /// - /// Disabled the UDP Server - /// - public void Disconnect() - { - if(Server != null) - Server.DisableUDPServer(); - - IsConnected = false; - - var handler = UpdateConnectionStatus; - if (handler != null) - handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); - } - - - /// - /// Recursive method to receive data - /// - /// - /// - void Receive(UDPServer server, int numBytes) - { - Debug.Console(2, this, "Received {0} bytes", numBytes); - - if (numBytes > 0) - { - var sourceIp = Server.IPAddressLastMessageReceivedFrom; - var sourcePort = Server.IPPortLastMessageReceivedFrom; - var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray(); - var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - MessageQueue.TryToEnqueue(new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes)); - - Debug.Console(2, this, "Bytes: {0}", bytes.ToString()); - var bytesHandler = BytesReceived; - if (bytesHandler != null) - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - else - Debug.Console(2, this, "bytesHandler is null"); - var textHandler = TextReceived; - if (textHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Recevied: '{0}'", str); - - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } - else - Debug.Console(2, this, "textHandler is null"); - } - server.ReceiveDataAsync(Receive); - - // Attempt to enter the CCritical Secion and if we can, start the dequeue thread - var gotLock = DequeueLock.TryEnter(); - if (gotLock) - CrestronInvoke.BeginInvoke((o) => DequeueEvent()); - } - - /// - /// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently. - /// It will dequeue items as they are enqueued automatically. - /// - void DequeueEvent() - { - try - { - while (true) - { - // Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather. - var message = MessageQueue.Dequeue(); - var dataRecivedExtra = DataRecievedExtra; - if (dataRecivedExtra != null) - { - dataRecivedExtra(this, message); - } - } - } - catch (Exception e) - { - Debug.Console(0, "GenericUdpServer DequeueEvent error: {0}\r", e); - } - // Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it. - if (DequeueLock != null) - { - DequeueLock.Leave(); - } - } - - /// - /// General send method - /// - /// - public void SendText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - - if (IsConnected && Server != null) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, text); - - Server.SendData(bytes, bytes.Length); - } - } - - /// - /// - /// - /// - public void SendBytes(byte[] bytes) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - - if (IsConnected && Server != null) - Server.SendData(bytes, bytes.Length); - } - - } - - /// - /// - /// - public class GenericUdpReceiveTextExtraArgs : EventArgs - { - /// - /// - /// - public string Text { get; private set; } - /// - /// - /// - public string IpAddress { get; private set; } - /// - /// - /// - public int Port { get; private set; } - /// - /// - /// - public byte[] Bytes { get; private set; } - - /// - /// - /// - /// - /// - /// - /// - public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes) - { - Text = text; - IpAddress = ipAddress; - Port = port; - Bytes = bytes; - } - - /// - /// Stupid S+ Constructor - /// - public GenericUdpReceiveTextExtraArgs() { } - } - - /// - /// - /// - public class UdpServerPropertiesConfig - { - /// - /// - /// - [JsonProperty(Required = Required.Always)] - public string Address { get; set; } - - /// - /// - /// - [JsonProperty(Required = Required.Always)] - public int Port { get; set; } - - /// - /// Defaults to 32768 - /// - public int BufferSize { get; set; } - - /// - /// - /// - public UdpServerPropertiesConfig() - { - BufferSize = 32768; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + + + + +namespace PepperDash.Core +{ + public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging + { + private const string SplusKey = "Uninitialized Udp Server"; + public CommunicationStreamDebugging StreamDebugging { get; private set; } + /// + /// + /// + public event EventHandler BytesReceived; + + /// + /// + /// + public event EventHandler TextReceived; + + /// + /// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data. + /// + public event EventHandler DataRecievedExtra; + + /// + /// + /// + public event EventHandler ConnectionChange; + + /// + /// + /// + public event EventHandler UpdateConnectionStatus; + + /// + /// + /// + public SocketStatus ClientStatus + { + get + { + return Server.ServerStatus; + } + } + + /// + /// + /// + public ushort UStatus + { + get { return (ushort)Server.ServerStatus; } + } + + /// + /// Address of server + /// + public string Hostname { get; set; } + + /// + /// IP Address of the sender of the last recieved message + /// + + + /// + /// Port on server + /// + public int Port { get; set; } + + /// + /// Another damn S+ helper because S+ seems to treat large port nums as signed ints + /// which screws up things + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Indicates that the UDP Server is enabled + /// + public bool IsConnected + { + get; + private set; + } + + public ushort UIsConnected + { + get { return IsConnected ? (ushort)1 : (ushort)0; } + } + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + public UDPServer Server { get; private set; } + + /// + /// Constructor for S+. Make sure to set key, address, port, and buffersize using init method + /// + public GenericUdpServer() + : base(SplusKey) + { + StreamDebugging = new CommunicationStreamDebugging(SplusKey); + BufferSize = 5000; + + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); + } + + /// + /// + /// + /// + /// + /// + /// + public GenericUdpServer(string key, string address, int port, int buffefSize) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + Hostname = address; + Port = port; + BufferSize = buffefSize; + + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); + } + + /// + /// Call from S+ to initialize values + /// + /// + /// + /// + public void Initialize(string key, string address, ushort port) + { + Key = key; + Hostname = address; + UPort = port; + } + + /// + /// + /// + /// + void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) + { + // Re-enable the server if the link comes back up and the status should be connected + if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp + && IsConnected) + { + Connect(); + } + } + + /// + /// + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType != eProgramStatusEventType.Stopping) + return; + + Debug.Console(1, this, "Program stopping. Disabling Server"); + Disconnect(); + } + + /// + /// Enables the UDP Server + /// + public void Connect() + { + if (Server == null) + { + Server = new UDPServer(); + } + + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key); + return; + } + if (Port < 1 || Port > 65535) + { + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key); + return; + } + } + + var status = Server.EnableUDPServer(Hostname, Port); + + Debug.Console(2, this, "SocketErrorCode: {0}", status); + if (status == SocketErrorCodes.SOCKET_OK) + IsConnected = true; + + var handler = UpdateConnectionStatus; + if (handler != null) + handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); + + // Start receiving data + Server.ReceiveDataAsync(Receive); + } + + /// + /// Disabled the UDP Server + /// + public void Disconnect() + { + if(Server != null) + Server.DisableUDPServer(); + + IsConnected = false; + + var handler = UpdateConnectionStatus; + if (handler != null) + handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); + } + + + /// + /// Recursive method to receive data + /// + /// + /// + void Receive(UDPServer server, int numBytes) + { + Debug.Console(2, this, "Received {0} bytes", numBytes); + + try + { + if (numBytes <= 0) + return; + + var sourceIp = Server.IPAddressLastMessageReceivedFrom; + var sourcePort = Server.IPPortLastMessageReceivedFrom; + var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray(); + var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + + var dataRecivedExtra = DataRecievedExtra; + if (dataRecivedExtra != null) + dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes)); + + Debug.Console(2, this, "Bytes: {0}", bytes.ToString()); + var bytesHandler = BytesReceived; + if (bytesHandler != null) + { + if (StreamDebugging.RxStreamDebuggingIsEnabled) + { + Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); + } + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } + var textHandler = TextReceived; + if (textHandler != null) + { + if (StreamDebugging.RxStreamDebuggingIsEnabled) + Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } + } + catch (Exception ex) + { + Debug.Console(0, "GenericUdpServer Receive error: {0}{1}", ex.Message, ex.StackTrace); + } + finally + { + server.ReceiveDataAsync(Receive); + } + } + + /// + /// General send method + /// + /// + public void SendText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + + if (IsConnected && Server != null) + { + if (StreamDebugging.TxStreamDebuggingIsEnabled) + Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); + + Server.SendData(bytes, bytes.Length); + } + } + + /// + /// + /// + /// + public void SendBytes(byte[] bytes) + { + if (StreamDebugging.TxStreamDebuggingIsEnabled) + Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + + if (IsConnected && Server != null) + Server.SendData(bytes, bytes.Length); + } + + } + + /// + /// + /// + public class GenericUdpReceiveTextExtraArgs : EventArgs + { + /// + /// + /// + public string Text { get; private set; } + /// + /// + /// + public string IpAddress { get; private set; } + /// + /// + /// + public int Port { get; private set; } + /// + /// + /// + public byte[] Bytes { get; private set; } + + /// + /// + /// + /// + /// + /// + /// + public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes) + { + Text = text; + IpAddress = ipAddress; + Port = port; + Bytes = bytes; + } + + /// + /// Stupid S+ Constructor + /// + public GenericUdpReceiveTextExtraArgs() { } + } + + /// + /// + /// + public class UdpServerPropertiesConfig + { + /// + /// + /// + [JsonProperty(Required = Required.Always)] + public string Address { get; set; } + + /// + /// + /// + [JsonProperty(Required = Required.Always)] + public int Port { get; set; } + + /// + /// Defaults to 32768 + /// + public int BufferSize { get; set; } + + /// + /// + /// + public UdpServerPropertiesConfig() + { + BufferSize = 32768; + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/QscCoreDoubleTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/QscCoreDoubleTcpIpClient.cs new file mode 100644 index 0000000..2f61dc8 --- /dev/null +++ b/Pepperdash Core/Pepperdash Core/Comm/QscCoreDoubleTcpIpClient.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core +{ + public class QscCoreDoubleTcpIpClient : IKeyed + { + public string Key { get; private set; } + + public event EventHandler BoolChange; + public event EventHandler UshortChange; + public event EventHandler StringChange; + + public GenericTcpIpClient MasterClient { get; private set; } + public GenericTcpIpClient SlaveClient { get; private set; } + + string Username; + string Password; + string LineEnding; + + CommunicationGather MasterGather; + CommunicationGather SlaveGather; + + bool IsPolling; + int PollingIntervalSeconds; + CTimer PollTimer; + + bool SlaveIsActive; + + /// + /// Default constuctor for S+ + /// + public QscCoreDoubleTcpIpClient() + { + MasterClient = new GenericTcpIpClient("temp-master"); + MasterClient.AutoReconnect = true; + MasterClient.AutoReconnectIntervalMs = 2000; + SlaveClient = new GenericTcpIpClient("temp-slave"); + SlaveClient.AutoReconnect = true; + SlaveClient.AutoReconnectIntervalMs = 2000; + + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void Connect(string key, string masterAddress, int masterPort, + string slaveAddress, int slavePort, string username, string password, + int pollingIntervalSeconds, string lineEnding) + { + Key = key; + + PollingIntervalSeconds = pollingIntervalSeconds; + Username = username; + Password = password; + LineEnding = lineEnding; + + MasterClient.Initialize(key + "-master"); + SlaveClient.Initialize(key + "-slave"); + + MasterClient.Hostname = masterAddress; + MasterClient.Port = masterPort; + + if (MasterClient != null) + { + MasterClient.Disconnect(); + } + + if (SlaveClient != null) + { + SlaveClient.Disconnect(); + } + + if (MasterGather == null) + { + MasterGather = new CommunicationGather(MasterClient, lineEnding); + MasterGather.IncludeDelimiter = true; + } + + MasterGather.LineReceived -= MasterGather_LineReceived; + MasterGather.LineReceived += new EventHandler(MasterGather_LineReceived); + + MasterClient.ConnectionChange -= MasterClient_SocketStatusChange; + MasterClient.ConnectionChange += MasterClient_SocketStatusChange; + + SlaveClient.Hostname = slaveAddress; + SlaveClient.Port = slavePort; + + if (SlaveGather == null) + { + SlaveGather = new CommunicationGather(SlaveClient, lineEnding); + SlaveGather.IncludeDelimiter = true; + } + + SlaveGather.LineReceived -= MasterGather_LineReceived; + SlaveGather.LineReceived += new EventHandler(SlaveGather_LineReceived); + + SlaveClient.ConnectionChange -= SlaveClient_SocketStatusChange; + SlaveClient.ConnectionChange += SlaveClient_SocketStatusChange; + + MasterClient.Connect(); + SlaveClient.Connect(); + } + + /// + /// + /// + public void Disconnect() + { + if (MasterClient != null) + { + MasterGather.LineReceived -= MasterGather_LineReceived; + MasterClient.Disconnect(); + } + if (SlaveClient != null) + { + SlaveGather.LineReceived -= SlaveGather_LineReceived; + SlaveClient.Disconnect(); + } + if (PollTimer != null) + { + IsPolling = false; + + PollTimer.Stop(); + PollTimer = null; + } + } + + /// + /// Does not include line feed + /// + public void SendText(string s) + { + if (SlaveIsActive) + { + if (SlaveClient != null) + { + Debug.Console(2, this, "Sending to Slave: {0}", s); + SlaveClient.SendText(s); + } + } + else + { + if (MasterClient != null) + { + Debug.Console(2, this, "Sending to Master: {0}", s); + MasterClient.SendText(s); + } + } + } + + void MasterClient_SocketStatusChange(object sender, GenericSocketStatusChageEventArgs args) + { + OnUshortChange((ushort)args.Client.ClientStatus, MasterClientStatusId); + + if (args.Client.IsConnected) + { + MasterGather.LineReceived += MasterGather_LineReceived; + + StartPolling(); + } + else + MasterGather.LineReceived -= MasterGather_LineReceived; + } + + void SlaveClient_SocketStatusChange(object sender, GenericSocketStatusChageEventArgs args) + { + OnUshortChange((ushort)args.Client.ClientStatus, SlaveClientStatusId); + + if (args.Client.IsConnected) + { + SlaveGather.LineReceived += SlaveGather_LineReceived; + + StartPolling(); + } + else + SlaveGather.LineReceived -= SlaveGather_LineReceived; + + } + + + void MasterGather_LineReceived(object sender, GenericCommMethodReceiveTextArgs e) + { + if (e.Text.Contains("login_required")) + { + MasterClient.SendText(string.Format("login {0} {1} \x0d\x0a", Username, Password)); + } + else if (e.Text.Contains("login_success")) + { + // START THE POLLING, YO! + } + else if (e.Text.StartsWith("sr")) + { + // example response "sr "MyDesign" "NIEC2bxnVZ6a" 1 1" + + var split = e.Text.Trim().Split(' '); + if (split[split.Length - 1] == "1") + { + SlaveIsActive = false; + OnBoolChange(false, SlaveIsActiveId); + OnBoolChange(true, MasterIsActiveId); + } + } + if (!SlaveIsActive) + OnStringChange(e.Text, LineReceivedId); + } + + void SlaveGather_LineReceived(object sender, GenericCommMethodReceiveTextArgs e) + { + if (e.Text.Contains("login_required")) + { + SlaveClient.SendText(string.Format("login {0} {1} \x0d\x0a", Username, Password)); + } + else if (e.Text.Contains("login_success")) + { + // START THE POLLING, YO! + } + else if (e.Text.StartsWith("sr")) + { + var split = e.Text.Trim().Split(' '); + if (split[split.Length - 1] == "1") + { + SlaveIsActive = true; + OnBoolChange(true, SlaveIsActiveId); + OnBoolChange(false, MasterIsActiveId); + } + } + if (SlaveIsActive) + OnStringChange(e.Text, LineReceivedId); + } + + void StartPolling() + { + if (!IsPolling) + { + IsPolling = true; + + Poll(); + if (PollTimer != null) + PollTimer.Stop(); + + PollTimer = new CTimer(o => Poll(), null, PollingIntervalSeconds * 1000, PollingIntervalSeconds * 1000); + } + } + + void Poll() + { + if (MasterClient != null && MasterClient.IsConnected) + { + Debug.Console(2, this, "Polling Master."); + MasterClient.SendText("sg\x0d\x0a"); + + } + if (SlaveClient != null && SlaveClient.IsConnected) + { + Debug.Console(2, this, "Polling Slave."); + SlaveClient.SendText("sg\x0d\x0a"); + } + } + + + + // login NAME PIN ---> login_success, login_failed + + // status get + // sg --> sr DESIGN_NAME DESIGN_ID IS_PRIMARY IS_ACTIVE + + // CRLF + + void OnBoolChange(bool state, ushort type) + { + var handler = BoolChange; + if (handler != null) + handler(this, new BoolChangeEventArgs(state, type)); + } + + void OnUshortChange(ushort state, ushort type) + { + var handler = UshortChange; + if (handler != null) + handler(this, new UshrtChangeEventArgs(state, type)); + } + + void OnStringChange(string value, ushort type) + { + var handler = StringChange; + if (handler != null) + handler(this, new StringChangeEventArgs(value, type)); + } + + + public const ushort MasterIsActiveId = 3; + public const ushort SlaveIsActiveId = 4; + public const ushort MainModuleInitiailzeId = 5; + + public const ushort MasterClientStatusId = 101; + public const ushort SlaveClientStatusId = 102; + + public const ushort LineReceivedId = 201; + } +} \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs b/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs index c5892a4..9dc4b48 100644 --- a/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs +++ b/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; - +using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -138,5 +138,10 @@ namespace PepperDash.Core var bytes = Encoding.GetEncoding(28591).GetBytes(text); return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); } + + public static string GetDebugText(string text) + { + return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value)); + } } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Device.cs b/Pepperdash Core/Pepperdash Core/Device.cs index 62dc6b1..bfbe359 100644 --- a/Pepperdash Core/Pepperdash Core/Device.cs +++ b/Pepperdash Core/Pepperdash Core/Device.cs @@ -119,7 +119,14 @@ namespace PepperDash.Core /// public virtual bool Deactivate() { return true; } - /// + /// + /// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize() + /// + public virtual void Initialize() + { + } + + /// /// Helper method to check object for bool value false and fire an Action method /// /// Should be of type bool, others will be ignored diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj index f746202..aa8d2c2 100644 --- a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj +++ b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj @@ -83,6 +83,7 @@ + diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigAnalogToken.cs b/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigAnalogToken.cs index 41427fe..6236f3e 100644 --- a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigAnalogToken.cs +++ b/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigAnalogToken.cs @@ -29,7 +29,7 @@ namespace PepperDash.Core.Intersystem.Tokens public override byte[] GetBytes() { return new[] { - (byte)(0xC0 | ((Value & 0xC000) >> 10) | (Index >> 7)), + (byte)(0xC0 | ((Value & 0xC000) >> 10) | (Index - 1 >> 7)), (byte)((Index - 1) & 0x7F), (byte)((Value & 0x3F80) >> 7), (byte)(Value & 0x7F) diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigDigitalToken.cs b/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigDigitalToken.cs index f721387..7840e90 100644 --- a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigDigitalToken.cs +++ b/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigDigitalToken.cs @@ -29,7 +29,7 @@ namespace PepperDash.Core.Intersystem.Tokens public override byte[] GetBytes() { return new[] { - (byte)(0x80 | (Value ? 0 : 0x20) | (Index >> 7)), + (byte)(0x80 | (Value ? 0 : 0x20) | ((Index - 1) >> 7)), (byte)((Index - 1) & 0x7F) }; } diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigSerialToken.cs b/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigSerialToken.cs index cc61b1f..deb721a 100644 --- a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigSerialToken.cs +++ b/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigSerialToken.cs @@ -32,7 +32,7 @@ namespace PepperDash.Core.Intersystem.Tokens var serialBytes = String.IsNullOrEmpty(Value) ? new byte[0] : Encoding.GetEncoding(28591).GetBytes(Value); var xsig = new byte[serialBytes.Length + 3]; - xsig[0] = (byte)(0xC8 | (Index >> 7)); + xsig[0] = (byte)(0xC8 | (Index - 1 >> 7)); xsig[1] = (byte)((Index - 1) & 0x7F); xsig[xsig.Length - 1] = 0xFF;