Files

223 lines
7.0 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
实时显示并保存设备串口数据。
.DESCRIPTION
使用 .NET SerialPort 直接独占打开串口。收到的数据会原样写入二进制文件,
同时解码后实时显示到当前 PowerShell,并按行写入带时间戳的文本日志。
默认只读取数据,不向设备发送指令。DtrEnable 和 RtsEnable 会改变硬件控制线,
只有用户明确要求时才能启用。
.EXAMPLE
.\serial_monitor.ps1 -Port COMx -LogPath F:\tmp\embedded-app-pc-debug\task\logs\serial.log
.EXAMPLE
.\serial_monitor.ps1 -Port COMx -LogPath .\serial.log -DurationSeconds 60
.PARAMETER DtrEnable
改变 DTR 硬件控制线;仅在用户明确要求时使用。
.PARAMETER RtsEnable
改变 RTS 硬件控制线;仅在用户明确要求时使用。
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Port,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$LogPath,
[ValidateRange(1, 4000000)]
[int]$BaudRate = 115200,
[ValidateRange(5, 8)]
[int]$DataBits = 8,
[ValidateSet('None', 'Odd', 'Even', 'Mark', 'Space')]
[string]$Parity = 'None',
[ValidateSet('One', 'OnePointFive', 'Two')]
[string]$StopBits = 'One',
[ValidateSet('None', 'XOnXOff', 'RequestToSend', 'RequestToSendXOnXOff')]
[string]$Handshake = 'None',
[string]$EncodingName = 'utf-8',
[ValidateRange(0, 2147483647)]
[int]$DurationSeconds = 0,
[ValidateRange(1, 5000)]
[int]$PollMilliseconds = 100,
[ValidateRange(64, 1048576)]
[int]$BufferSize = 4096,
[string]$RawPath,
[switch]$DtrEnable,
[switch]$RtsEnable
)
$ErrorActionPreference = 'Stop'
$serialPort = $null
$textWriter = $null
$rawStream = $null
$pendingLine = ''
function Write-TimestampedLines {
param(
[Parameter(Mandatory = $true)]
[string]$Text,
[Parameter(Mandatory = $true)]
[System.IO.StreamWriter]$Writer
)
$script:pendingLine += $Text
while ($true) {
$newlineIndex = $script:pendingLine.IndexOf("`n", [System.StringComparison]::Ordinal)
if ($newlineIndex -lt 0) {
break
}
$line = $script:pendingLine.Substring(0, $newlineIndex).TrimEnd("`r")
$script:pendingLine = $script:pendingLine.Substring($newlineIndex + 1)
$Writer.WriteLine(('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'), $line))
}
}
try {
$resolvedLogPath = [System.IO.Path]::GetFullPath($LogPath)
if ([string]::IsNullOrWhiteSpace($RawPath)) {
$resolvedRawPath = $resolvedLogPath + '.raw.bin'
}
else {
$resolvedRawPath = [System.IO.Path]::GetFullPath($RawPath)
}
$logDirectory = [System.IO.Path]::GetDirectoryName($resolvedLogPath)
$rawDirectory = [System.IO.Path]::GetDirectoryName($resolvedRawPath)
if (-not [string]::IsNullOrWhiteSpace($logDirectory)) {
[System.IO.Directory]::CreateDirectory($logDirectory) | Out-Null
}
if (-not [string]::IsNullOrWhiteSpace($rawDirectory)) {
[System.IO.Directory]::CreateDirectory($rawDirectory) | Out-Null
}
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
$textFileStream = New-Object System.IO.FileStream(
$resolvedLogPath,
[System.IO.FileMode]::Append,
[System.IO.FileAccess]::Write,
[System.IO.FileShare]::Read
)
$textWriter = New-Object System.IO.StreamWriter($textFileStream, $utf8NoBom)
$textWriter.AutoFlush = $true
$rawStream = New-Object System.IO.FileStream(
$resolvedRawPath,
[System.IO.FileMode]::Append,
[System.IO.FileAccess]::Write,
[System.IO.FileShare]::Read
)
try {
$encoding = [System.Text.Encoding]::GetEncoding($EncodingName)
}
catch {
throw "无法识别编码 '$EncodingName':$($_.Exception.Message)"
}
$decoder = $encoding.GetDecoder()
$serialPort = New-Object System.IO.Ports.SerialPort
$serialPort.PortName = $Port
$serialPort.BaudRate = $BaudRate
$serialPort.DataBits = $DataBits
$serialPort.Parity = [System.Enum]::Parse([System.IO.Ports.Parity], $Parity)
$serialPort.StopBits = [System.Enum]::Parse([System.IO.Ports.StopBits], $StopBits)
$serialPort.Handshake = [System.Enum]::Parse([System.IO.Ports.Handshake], $Handshake)
$serialPort.DtrEnable = $DtrEnable.IsPresent
$serialPort.RtsEnable = $RtsEnable.IsPresent
$serialPort.ReadTimeout = $PollMilliseconds
$serialPort.ReadBufferSize = [Math]::Max(4096, $BufferSize)
try {
$serialPort.Open()
}
catch {
throw "无法打开串口 $Port。串口可能不存在或正被其他程序占用:$($_.Exception.Message)"
}
$startTime = Get-Date
$startMessage = '[{0}] 已打开 {1},{2} {3}{4}{5},流控 {6};原始数据:{7}' -f `
(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'),
$Port,
$BaudRate,
$DataBits,
$Parity.Substring(0, 1),
$(if ($StopBits -eq 'One') { '1' } elseif ($StopBits -eq 'Two') { '2' } else { '1.5' }),
$Handshake,
$resolvedRawPath
[Console]::WriteLine($startMessage)
$textWriter.WriteLine($startMessage)
$byteBuffer = New-Object byte[] $BufferSize
$charBuffer = New-Object char[] ($encoding.GetMaxCharCount($BufferSize))
while ($true) {
if ($DurationSeconds -gt 0 -and ((Get-Date) - $startTime).TotalSeconds -ge $DurationSeconds) {
break
}
try {
$count = $serialPort.Read($byteBuffer, 0, $byteBuffer.Length)
}
catch [System.TimeoutException] {
continue
}
if ($count -le 0) {
continue
}
$rawStream.Write($byteBuffer, 0, $count)
$rawStream.Flush()
$charCount = $decoder.GetChars($byteBuffer, 0, $count, $charBuffer, 0, $false)
if ($charCount -gt 0) {
$text = New-Object string($charBuffer, 0, $charCount)
[Console]::Write($text)
Write-TimestampedLines -Text $text -Writer $textWriter
}
}
}
catch {
# 使用字符串连接保留底层异常文本,避免异常内容再次参与格式化。
$errorMessage = '[' + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff') + '] 串口监测失败:' + [string]$_.Exception.Message
[Console]::Error.WriteLine($errorMessage)
if ($null -ne $textWriter) {
$textWriter.WriteLine($errorMessage)
}
exit 1
}
finally {
if ($null -ne $textWriter -and -not [string]::IsNullOrEmpty($script:pendingLine)) {
$textWriter.WriteLine(('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'), $script:pendingLine.TrimEnd("`r")))
}
if ($null -ne $serialPort) {
if ($serialPort.IsOpen) {
$serialPort.Close()
}
$serialPort.Dispose()
}
if ($null -ne $rawStream) {
$rawStream.Dispose()
}
if ($null -ne $textWriter) {
$textWriter.WriteLine('[{0}] 串口监测结束' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'))
$textWriter.Dispose()
}
}