英文:
How to read/write to a USB Serial COM port using powershell?
问题
我现在已经在互联网上搜索过了,也尝试了无数的脚本和解决方案。也就是说,大多数解决方案都是关于单向的读取,很少涉及写入和读取结合。
我已经编写了一个基于所有这些小部分的PowerShell脚本。它通过运行以下的pwsh.exe
命令来运行,该命令启动另一个pwsh shell(最新的7.3.4
),然后在那里运行脚本。
已连接设备是基于USB的3G/4G LTE数据调制解调器。
当连接到PS时,它报告Ports
PNPClass中的以下3个COM端口。
这3个对应于modem+diag+at_mbim
USB模式,可以通过AT+USBMODE?
找到。因此,我可以使用COM 9和11。
问题陈述:
- 脚本成功将数据发送到串行COM端口。
- 但我无法读取响应,即使我知道它在那里。
- 我知道它在那里,因为如果在它发送AT命令(
ATI
)后杀死脚本,或者让它超时。如果我使用任何常见的终端模拟器程序连接到相同的端口,如putty, screen, picocom等,我可以看到发送的数据和结果(见屏幕截图)。 - 非常偶尔会显示一些
OK
,但目前似乎是随机的。
屏幕截图是从脚本发送的命令的结果中缓冲的数据,但脚本没有接收到这些数据。
脚本的问题是,它无法确保从发送的命令中获取所有结果。
英文:
I have now looked all over the internet and now tried countless scripts and solutions. That said, it seem most solutions is about one-way reading, and rarely writing and reading combined.
I have put together a powershell script, based on all these bits & pieces. It works by running the following pwsh.exe
command that fires up another pwsh shell (latest 7.3.4
) and running the script from there.
Start-Process pwsh.exe -ArgumentList '-noprofile -noexit -c "./mecom.ps1"' -PassThru | Out-Null
Connected Device
The connected device in question is a USB based 3G/4G LTE data modem.
When connected to the PS it reports the following 3 COM ports in the Ports
PNPClass.
Alcatel 3G modem debug (COM11) - modem
Application1 Interface (COM10) - diag
Application2 Interface (COM9) - AT_MBIM
These 3 corresponds to the modem+diag+at_mbim
USB mode, found with AT+USBMODE?
.
So here I can use both COM 9 & 11.
Problem Statement:
- The script successfully sends the data to the serial COM port.
- But I am not able to read the response, even if I know it's there.
- I know it's there, because if I kill the script after it has sent the at command (
ATI
), or just let it time out. I can see the data and results sent, if I connect to the same port using any common terminal emulator program, such as putty, screen, picocom, etc. (See screenshot.) - Very occasionally I get a few
OK
s shown, but seem random at the moment.
The Screenshot
Buffer data with results from commands sent from script, but not received by script.
The Script
#!/usr/bin/env pwsh
#
#------------------------------------------------------------------------------
# Run with
# Start-Process pwsh.exe -ArgumentList '-noprofile -noexit -c "./mecom.ps1"' -PassThru | Out-Null
#------------------------------------------------------------------------------
$HL = '-'*50
# Register Exit event & Hit [CTRL+C] to exit shell
Register-EngineEvent PowerShell.Exiting -SupportEvent –Action {
[console]::Beep(500,500); Write-Host "`nExiting...`n" -fore Magenta; sleep(1); Read-Host -Prompt "Hit Return 2 Exit";
}
# Hit [CTRL+D] to exit shell, after script is done.
try { Set-PSReadlineKeyHandler -Key ctrl+d -Function ViExit } catch {}
#------------------------------------------------------------------------------
# Helper Functions
#------------------------------------------------------------------------------
function setTerminalUI {
# Setting the PowerShell UI Terminal Size
# NOTE: Must have: BufferWidth = WindowWidth
[console]::Title = "MeCom Terminal (${PID})"
[console]::BufferHeight=9001
[console]::BufferWidth=140
[console]::WindowHeight=50
[console]::WindowWidth=140
}
function startUp {
Write-Host "`n"
Write-Host -Fo DarkGray "Starting " -NoN
Write-Host -Fo Magenta "MeCom Terminal"
Write-Host -Fo DarkGray "PID: $PID"
Write-Host -Fo DarkGray "Use [CTRL+C] to quit."
}
function showPort($port){
Write-Host -Fo DarkGray $HL
$port | Out-Host
Write-Host -Fo DarkGray $HL
}
function showPortInfo {
# Extract the COM port number into a list
$cports = [System.IO.Ports.SerialPort]::GetPortNames() # is a string array .GetType()
$comList = @()
foreach ($i in $cports) {
$i -Match "COM(\d{1,2})" | Out-Null
$comList += [int]$Matches[1]
}
Write-Host "`nAvailable Ports:"
Write-Host $HL
Write-Host -Fo DarkYellow "$cports"
Write-Host $HL
$mydevs = (Get-PnPDevice | Where-Object{$_.PNPClass -in "WPD","AndroidUsbDeviceClass","Modem","Ports" } |
Where-Object{$_.Present -in "True"} |
Select-Object Name,Description,Manufacturer,PNPClass,Service,Present,Status,DeviceID |
Sort-Object Name)
#$allDevs = (
$mydevs | Format-Table Description, Manufacturer, PNPClass, Service,
@{Label="COM port"; Expression={ ($_.Name -Match "\((COM\d{1,2})\)" | Out-Null && $Matches[1]) }},
@{Label="VID:PID"; Expression={ ($_.DeviceID -Match "USB\\VID_([0-9a-fA-F]{4})\&PID_([0-9a-fA-F]{4})" | Out-Null && ('{0}{1}{2}' -f ${Matches}[1], ":", ${Matches}[2]).ToLower() ) }},
Present, Status
# )
#Write-Host $allDevs
return $comList #| Out-Null -PassThru
}
function getComPort ($cList) {
# Select a COM port
Write-Host $HL; Write-Host -Fo DarkGray "comList:`n ${cList}" ; Write-Host $HL
do {
Write-Host -Fo DarkGreen 'Select a serial COM port number [default is 9]' -NoN
$pNum = Read-Host -Prompt ' '
if ( !($pNum -in $cList)) { Write-Host -Fo Red "ERROR: No COM port with that number!" }
} while ( !($pNum -match '^\d+$') -or ($pNum -notin $cList))
$cNum = "COM${pNum}" #| Out-Null
Write-Host -Fo DarkGray "`nReading from port : " -NoN
Write-Host -Fo White "${cNum}"
return $cNum
}
function ReadCom ($cNum) {
#------------------------------------------------------------
# Configuring the Serial Ports Connection
#------------------------------------------------------------
if (!$cNum) {Write-Host -Fo Red "[WARNING] No cNum received, setting to default COM9."; $cNum ='COM9'}
$port = New-Object System.IO.Ports.SerialPort "${cNum}",115200,None,8,one
#------------------------------------------------------------
$port.ReadTimeout = 10000 # 20 sec -
$port.WriteTimeout = 2000 # 5 sec -
$port.NewLine = "`r" # \r -
$port.ReceivedBytesThreshold = 1 # 256
#------------------------------------------------------------
Start-Sleep -M 500
Write-Host -Fo DarkGray "Opening connection..."
try {
$port.Open()
} catch {
Write-Host -Fo Red "`nERROR: Failed to Open serial port!"
Write-Error "ERROR: ${Error}"
Write-Host -Fo Yellow "`nQuitting!`n"
Read-Host -Prompt "Hit any key to Exit"
Break
}
Start-Sleep -m 1000
showPort($port)
#------------------------------------------------------------
# Housekeeping (removing garbage from previous sessions...
#------------------------------------------------------------
#$port.DiscardInBuffer()
#$port.DiscardOutBuffer()
#------------------------------------------------------------
# AT Command(s) to send
#------------------------------------------------------------
# NOTE:
# Sending AT commands require them to use an "\r" as EOL.
# This can be done automatically if using $port.NewLine="`n"
# You probably must use double quotes, otherwise you get the wrong character.
#------------------------------------------------------------
$ATC = 'ATI'
Write-Host -Fo DarkGray "Sending AT Command : " -NoN
Write-Host -Fo White "$ATC"
$port.WriteLine($ATC)
Start-Sleep -m 1000
showPort($port)
Write-Host -Fo Gray "Attempting to use ReadLine..."
do {
$key = if ($host.UI.RawUI.KeyAvailable) { $host.UI.RawUI.ReadKey('NoEcho, IncludeKeyDown') }
if ($port.IsOpen) {
try {
$data = $port.ReadLine()
#$data = $port.ReadExisting()
}
catch [TimeoutException] {
Write-Host -Fo Red "`nERROR: TimeoutException in ReadLine"
Write-Error "ERROR: ${Error}"
break
}
if (($data).Lengths -gt 0) {
Write-Host -Fo DarkYellow "${data}`n" # -NoN | Out-Host
}
Start-Sleep -m 1000
} else {
Write-Host -Fo Yellow "[INFO] Port was Closed!"
break
}
} until ($key.VirtualKeyCode -eq 81) # Repeat until a 'q' is pressed
Write-Host -Fo DarkGray "`nClosing connection..." -NoN
$port.Close()
Write-Host -Fo Green "OK`n"
}
#------------------------------------------------------------------------------
# MAIN
#------------------------------------------------------------------------------
setTerminalUI
startUp
$comList = showPortInfo
$comList
$cNum = getComPort($comList)
ReadCom($cNum)
Read-Host -Prompt "Hit any key to Exit"
#------------------------------------------------------------------------------
# END
#------------------------------------------------------------------------------
References:
1 system.io.ports.serialport
2 Writing and Reading info from Serial Ports
[3] Serial Port communication using PowerShell
[4] Reading Serial port data continuously in powershell script
[5] How to continuously read Serial COM port in
[6] SerialPort.DataReceived Event
[7] Problems writing AT command to internal modem with System.IO.Ports.SerialPort
[8] System.IO.Ports Namespace
[9] unlock-a-pinlocked-broadband-device-using-powershell-and-atcommands
Given the lack of working public solutions for this, I am starting to think it may be an issue with powershell itself!? Some people are loosely talking about making an Event Listener
, but they always fail to provide any working powershell examples, and always refer back to the same crummy Microsoft C#/.NET web pages on the same topic. [1,2]
How can I fix the script to ensure to get all the results from the commands I send?
答案1
得分: 2
I discovered to my horror that:
- 默认设置的
$port.Handshake
被设置为None
(0
),而它应该被设置为RequestToSend
(2
)。 - 在我的 shell 中不断玩耍串口和变量后,某些东西被损坏,使得使用
$port.ReadLine()
和$port.ReadExisting()
用于接收数据在命令行中默默失败。 - 此外,我在使用建议的改进时犯了个错,使用了
($data).Length
,使得if()
语句始终默默忽略任何数据!
现在它运行得很好,还添加了一个 EventHandler
。
事件处理程序
在使用 [System.IO.Ports.SerialPort]
时,使用事件处理程序的最基本用法是这样的:
# 从命令行
# 确保在注册之前串口已打开!
$p.Open()
# 注册一个新的处理程序
Register-ObjectEvent -InputObject $p -EventName "DataReceived" -SourceIdentifier COM_EVENT_HAND -Action { $Sender.ReadExisting() | Out-Host }
# 获取有关您的事件处理程序的一些信息
Get-EventSubscriber -SourceIdentifier COM_EVENT_HAND
# 发送一些命令以进行查看
$p.Write("ATI`r")
# 注销(在更改之前)
Unregister-Event -SourceIdentifier "COM_EVENT_HAND"
最后注意一点,人们正在使用各种延迟(如 Start-Sleep -m 2000
)。除非您使用非常古老的糟糕的调制解调器并且速度远低于 115200 bps,否则不需要这些延迟,甚至可能破坏正常行为。
祝您愉快! 💖
英文:
I discovered to my horror that:
- That the default setting for
$port.Handshake
was set toNone
(0
),
when it have to be set toRequestToSend
(2
). - After continuously playing with ports and variables in my shell, something got corrupted, making the most basic usage of
$port.ReadLine()
and$port.ReadExisting()
for receiving, silently fail from CLI. - In addition I made a typo from the suggested improvement by using
($data).Length
, making theif()
statement always silent any data!
Now it works beautifully, including an added EventHandler
.
Event Handlers
The most basic usage of an event handler when using [System.IO.Ports.SerialPort]
, you need to use it like this:
# From CLI
# Make sure the port is open before registration!
$p.Open()
# Register the a new handler
Register-ObjectEvent -InputObject $p -EventName "DataReceived" -SourceIdentifier COM_EVENT_HAND -Action { $Sender.ReadExisting() | Out-Host }
# Get some info about your event handler
Get-EventSubscriber -SourceIdentifier COM_EVENT_HAND
# Send some command to be seen
$p.Write("ATI`r")
# Unregister (before changing it)
Unregister-Event -SourceIdentifier "COM_EVENT_HAND"
One last note. People are using all sorts of delays (like Start-Sleep -m 2000
). Those are not needed, and may even corrupt normal behavior, unless you have some really old crummy modem and running on speeds much less than 115200 bps.
Enjoy! 💖
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论