Classes/XiaomiConnection.psm1
#region Namespaces/Modules using namespace System.Text; using namespace System.Net; using namespace System.Net.Sockets; using namespace System.Diagnostics; #endregion <# .SYNOPSIS A class to connect and interact with Xiaomi Home devices on the network .DESCRIPTION The class stores required network configuration (ports, multicast IP addresses etc.) that it uses to set up a local UDP server. The server is used to communicate (send commands and receive responses) with multiple Xiaomi Gateway devices, as well as the sensors attached to them. #> Class XiaomiConnection { #region Properties # UDP socket instance [UdpClient]$Socket; # Socket status (is is active?) [Bool]$IsAlive; # The UDP port to be used to listen on the local computer [Int]$LocalPort; # Local endpoint: [IPEndpoint]$LocalEndpoint; # Multicast group, which Xiaomi devices belong to [IPAddress]$MulticastGroup; # The multicast peer port, used for Xiaomi gateway discovery [Int]$MulticastPeerPort; #endregion #region Constructors <# .SYNOPSIS Class constructor, responsible for initating the default values .PARAMETER socket UDP socket instance .PARAMETER LocalPort The UDP port to be used to listen on the local computer .PARAMETER multicastGroup Multicast group, which Xiaomi devices belong to .PARAMETER multicastPeerPort The multicast peer port, used for Xiaomi gateway discovery #> XiaomiConnection([UdpClient]$socket, [Int]$localPort, [IPAddress]$multicastGroup, [Int]$multicastPeerPort) { $This.Socket = $socket; $This.LocalPort = $localPort; $This.MulticastGroup = $multicastGroup; $This.MulticastPeerPort = $multicastPeerPort; $This.IsAlive = $TRUE; # Set up the local endpoint: $This.LocalEndpoint = [XiaomiConnection]::CreateEndpoint([IPAddress]::Any, $This.LocalPort); } #endregion #region Methods <# .SYNOPSIS Receive information send to the local UDP server .OUTPUTS [System.Management.Automation.PSObject]. Data received on the server #> [PSObject] Receive() { # Read data from the socket: $bytes = $This.Socket.Receive([Ref]$This.LocalEndpoint); # Convert the received data to a JSON string: $data = [Encoding]::ASCII.GetString($bytes); # Parse the data as JSON object: $data = $data | ConvertFrom-Json; # Do we have nested data object: If ($data.PSobject.Properties.name -Match 'data') { # Convert it from JSON to object as well: $data.data = $data.data | ConvertFrom-Json; } Return ($data); } <# .SYNOPSIS Sends data to a remote endpoint (Xiaomi Gateway or a sensor) .PARAMETER endpoint Endpoint of the remote device, to which the data should be sent to .PARAMETER data Data, which needs to be send to the deive .OUTPUTS [Void]. The method does not return anything #> [Void] Send([IPEndPoint]$endpoint, $data) { # Convert the command to JSON format: $data = (($data | ConvertTo-Json) -Replace '[\r\n\t]','') -Replace '\s+',''; # Convert the command to bytes: $bytes = [Encoding]::ASCII.GetBytes($data); # Send the command: $This.Socket.Send($bytes, $bytes.Length, $endpoint) | Out-Null; } #endregion #region Static Methods <# .SYNOPSIS Static function to check if the specified UDP port is already in use and, if so, who uses it .PARAMETER port Number of the UDP port which we want to check for being in use .OUTPUTS [System.Diagnostics.Process]. Object with the information about the process using the port #> static [Process] CheckIfPortIsInUse([Int]$port) { # Get NETSTAT output with all UDP ports and parse it: $netstat = (NETSTAT -aonp UDP); $endpoints = $netstat[4..$netstat.length] | ConvertFrom-String; # Search for the specific port: $inUse = $endpoints | Where-Object {$_.P3 -LIKE "*:$($port)"}; # If the port was found, it is in use: If (-NOT ($NULL -EQ $inUse)) { # Return the information about the process: Return (Get-Process -Id $inUse.P5); # The port is not in use: } Else { Return $NULL; } } <# .SYNOPSIS Creates and returns a local or remote endpoint object based on the IP and port provided .PARAMETER IP IP of the endpoint (can be [IPAddress]::Any, as well) .PARAMETER port UDP port for the endpoint .OUTPUTS [System.Net.IPEndpoint]. Newly created endpoint #> static [IPEndpoint] CreateEndpoint([IPAddress]$IP, [Int]$port) { Return New-Object -TypeName IPEndPoint -ArgumentList $IP, $port; } #endregion } |