#target device, modbus port is always 502 $IPAddr = "192.168.0.125" $Port = "502" #connection parameters [uint16]$RegID = 0 #this can be anything, good idea to increment it with each request [byte]$RegFunct = 3 #3 and 4 are register read commands [uint16]$RegStart = 0 [uint16]$RegCount = 220 [uint16]$BoolID = 0 #this can be anything, good idea to increment it with each request [byte]$BoolFunct = 1 #1 and 2 are bool read commands [uint16]$BoolStart = 0 [uint16]$BoolCount = 368 #This feature will convert 200 registers of int's into 100 floats. #If you are not using the default config, this should probably be turned to $False [bool]$Flag_EnableFloatConversion = $True #open TCP connection $tcpConnection = New-Object System.Net.Sockets.TcpClient($IPAddr, $Port) $tcpStream = $tcpConnection.GetStream() #buffers used for raw transaction data $RegBuffer = new-object System.Byte[] 500 $BoolBuffer = new-object System.Byte[] 60 #arrays used during data-parse $RegTemp = new-object System.uint16[] $RegCount $RegRead = new-object System.int16[] $RegCount $FloatTemp = new-object System.uint32[] 100 $FloatRead = new-object System.single[] 100 $BoolRead = new-object System.Byte[] ([math]::ceiling($BoolCount/8)) #build request strings [Byte[]] $RegRequest = [math]::floor($RegID/256),($RegID % 256),0x00,0x00,0x00,0x06,0x01,$RegFunct, [math]::floor($RegStart/256),($RegStart % 256) , [math]::floor($RegCount/256),($RegCount % 256) [Byte[]] $BoolRequest = [math]::floor($BoolID/256),($BoolID % 256),0x00,0x00,0x00,0x06,0x01,$BoolFunct, [math]::floor($BoolStart/256),($BoolStart % 256) , [math]::floor($BoolCount/256),($BoolCount % 256) #send register read request if ($tcpConnection.Connected) { $tcpStream.Write($RegRequest, 0, $RegRequest.Length) echo sent message } #wait for response, break if over 1 second delay $i = 0 while (!$tcpStream.DataAvailable) { start-sleep -Milliseconds 10 $i++ if ($i -gt 100) {break} } #copy response into buffer register if ($tcpStream.DataAvailable) { echo recieved message $RegResponseLen = $tcpStream.Read($RegBuffer, 0, 500) } #send bool read request if ($tcpConnection.Connected) { $tcpStream.Write($BoolRequest, 0, $BoolRequest.Length) echo sent message } #wait for response, break if over 1 second delay $i = 0 while (!$tcpStream.DataAvailable) { start-sleep -Milliseconds 10 $i++ if ($i -gt 100) {break} } #copy response into buffer register if ($tcpStream.DataAvailable) { echo recieved message $BoolResponseLen = $tcpStream.Read($BoolBuffer, 0, 60) } #close the TCP connection #if we wanted to poll data constantly this should be left open, and messages resent $tcpConnection.Close() if($regbuffer[7] -eq $RegFunct + 0x80) {echo ("Modbus Server responded with exception code: " + $regbuffer[8] + " for Registry Read")} elseif ($regbuffer[7] -ne $RegFunct) {echo "Modbus Server responded with unknown response for Registry Read"} Else { #make sure that the transaction ID matches what we sent if ($RegBuffer[0]*0x100+$RegBuffer[1] -eq $RegID) {$RegCheckID = $True} else {$RegCheckID = $False echo "Registry Id did not match request"} #make sure the length of packet we received matches the length called out inside the packet if ($RegBuffer[4]*0x100+$RegBuffer[5] -eq $RegResponseLen - 6) {$RegCheckPacket = $True} else {$RegCheckPacket = $False echo "Registry Data Length did not match packet received"} #check if the data length matches what we requested if ($RegBuffer[4]*0x100+$RegBuffer[5]-3 -eq $RegCount*2) {$RegCheckLength = $True} else {$RegCheckLength = $False echo "Registry Data Length did not match request"} #note: we could also check if $RegBuffer[8] matched, but in the current example I expect it to be off by 256 already. if ($RegCheckID -and $RegCheckLength -and $RegCheckPacket) { #combine registers back together. Note that 200-219 are directly usable here. #in the strictest sense these should be cast back as int16 instead of being uint16, but 200-219 are only positive. for ($i = 0; $i -lt $RegCount; $i++) { $RegTemp[$i] = $RegBuffer[$i*2+9]*0x100 + $RegBuffer[$i*2+1+9] $RegRead[$i] = [bitconverter]::ToInt16([bitconverter]::GetBytes($RegTemp[$i]), 0) } if ($Flag_EnableFloatConversion) { #combine floats back together. for ($i = 0; $i -lt 100; $i++) { $FloatTemp[$i] = $RegTemp[$i*2+1]*0x10000 + $RegTemp[$i*2] $FloatRead[$i] = [bitconverter]::ToSingle([bitconverter]::GetBytes($floattemp[$i]), 0) } } } } if ($Boolbuffer[7] -eq $BoolFunct + 0x80) {echo ("Modbus Server responded with exception code: " + $Boolbuffer[8] + " for Bool Read")} elseif ($Boolbuffer[7] -ne $BoolFunct) {echo "Modbus Server responded with unknown response for Bool Read"} Else { #make sure that the transaction ID matches what we sent if ($BoolBuffer[0]*0x100+$BoolBuffer[1] -eq $BoolID) {$BoolCheckID = $True} else {$BoolCheckID = $False echo "Bool Id did not match request"} #make sure the length of packet we received matches the length called out inside the packet if ($BoolBuffer[4]*0x100+$BoolBuffer[5] -eq $BoolResponseLen - 6) {$BoolCheckPacket = $True} else {$BoolCheckPacket = $False echo "Bool Data Length did not match packet received"} #check if the data length matches what we requested if ($BoolBuffer[4]*0x100+$BoolBuffer[5]-3 -eq [math]::Ceiling($BoolCount/8)) {$BoolCheckLength = $True} else {$BoolCheckLength = $False echo "Bool Data Length did not match request"} #note: we could also check if $BoolBuffer[8] matched if ($BoolCheckID -and $BoolCheckLength -and $BoolCheckPacket) { #pull the data part out of the bool response for ($i = 0; $i -lt $BoolBuffer[8]; $i++) { $BoolRead[$i] = $BoolBuffer[$i+9] } } } #at this point we are left with: #60 single-precision floats in $FloatRead #20 registers in $RegRead[200..219] #368 coils spread across the first 46 bytes in $BoolRead #For the meaning of this data, please refer to the map in the data appendix. #You can check the data manually by calling $BoolRead, $FloatRead, and $RegRead[200..219]