Private/Convert-BMSMessage.ps1
Function Convert-BMSMessage { [cmdletbinding()] Param($iO) begin { if (!$iO.ByteStreamReceive) { Throw "This Instruction Object does not contain received byte stream(s) to decode." } Function Get-BMSCharsFromByteStream { [CmdletBinding()] param($Bytes) $L = ([int]$Bytes[3] + 3) (($Bytes[4..$L]) | ForEach-Object{[char]$_}) -join "" } Function Get-BMSIntMixedBytesFromByteStream { [CmdletBinding()] param($Bytes) $L = ([int]$Bytes[3] + 3) ($Bytes[4..$L]) } Function Get-BMSIntFromByteStream { [CmdletBinding()] param($String) $L = ([int]$Bytes[3] + 3) ($Bytes[4..$L]) | ForEach-Object{[int]$_} } Function Get-BMSBytesFromByteStream { [CmdletBinding()] param($Bytes) #assign floating point byte array size switch ($BMSInstructionSet.Config.Message.FloatPrecision) { single { #single signed float is 4 bytes long $SegmentOffset = [int]4 } Default { throw ("Requested float precision " + $BMSInstructionSet.Config.Message.FloatPrecision + " is not available.") } } #End offset length index -1 for array count, and -2 for crc and -1 for etx $Offset = ($Bytes.Length -4) Write-Verbose ("[Parser]: Parsing float as [" + $SegmentOffset + "] Byte Arrays") Write-Verbose ("[Parser]: Payload Length: [" + ($MessageStream.Count) + "]") #Front offset is +1 $ByteStream = $Bytes[4..($Offset)] if ($ByteStream.Count -eq $SegmentLength) { [BitConverter]::ToSingle($ByteStream, 0) } else { $i = 1 #Initalize byte stream counter $LSB = 0 $MSB = ($SegmentOffset -1) #initialize byte segment counter $b = 1 #initialize first byte segment array based on FloatingPrecisionBits #$ByteSegment = [byte[]]::new($SegmentLength) do { try { [BitConverter]::ToSingle($ByteStream[$LSB..$MSB], 0) } catch { Throw "Segment offset out of bounds!" } $LSB+=$SegmentOffset $MSB+=$SegmentOffset $i++ } until ($i -gt ($ByteStream.Count / $SegmentOffset)) } } Function LabelHeaderValues { [CmdletBinding()] param($iOInstance) $Header = $null switch ($iOInstance.Instruction.Handler) { array { $Descriptor = $iOInstance.Instruction.Return.Unit.Array | ?{$_.Position -eq 0} switch ($Descriptor.Value) { char { #note that we just have a handler for char types, because bms appears to send segment 0 data types as ascii values that are cast to integers $h = [int](Get-BMSCharsFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[0]) $Header = @{ "Unit" = $Descriptor.Unit; "Value" = $h; "Description" = $Descriptor.Description } } Default { Write-Error ("Unknown Value Type Declaration: " + $iOInstance.Instruction.Return.Unit.Array[0].Value) } } } Default { $Descriptor = $iOInstance.Instruction.Return $Header = @{ "Unit" = $Descriptor.Unit; "Value" = $Descriptor.Value; "Description" = $iOInstance.Instruction.Name } } } # present the count types as either count of bytes or counts of bms units depending on library data Write-Verbose ("[Parser]: Instruction Identification: [" + $Descriptor.Description + "]") Return ([PSCustomObject]$Header) } Function LabelDataArrayValues { [CmdletBinding()] param($iOInstance) $Data = @() switch ($iOInstance.Instruction.Return.Unit.Type) { Int { Write-Verbose "[Parser]: Using Int array parser" $Values = Get-BMSIntFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[1] } Byte { Write-Verbose "[Parser]: Using Bytes array parser" $Values = Get-BMSBytesFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[1] } IntMixedBytes { Write-Verbose "[Parser]: Using Int Mixed with Bytes array parser" $Values = Get-BMSIntMixedBytesFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[1] } Char { Write-Verbose "[Parser]: Using Char array parser" $Values = Get-BMSCharsFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[1] } Default { Throw "I don't know how to handle that array object class. :(" } } if ($iOInstance.Instruction.Return.Unit.Array | ?{$_.Position -eq "template"}) { #store the template $Descriptor = @() $Template = ($iOInstance.Instruction.Return.Unit.Array | ?{$_.Position -eq "template"}) #assign template to position 1 in array #add a copy of template with position ID $Values.Count -1 times $i = 1 #add a dingus to the top of descriptor array. in other non-template arrays, #the first index [0] is the header data and is skipped. This keeps template type arrays #aligned with non-template style arrays. Write-Verbose ("[Parser]: Processing: [" + $Values.Count + "] values") $Descriptor += $Template.PSObject.Copy() do { $TemplateCopy = $Template.PSObject.Copy() $TemplateCopy.Position = $i $TemplateCopy.Description = ($TemplateCopy.Description + ": [" + $i + "]") $Descriptor += $TemplateCopy $i++ } until ($i -gt $Values.Count) } else { $Descriptor = ($iOInstance.Instruction.Return.Unit.Array | Sort-Object -Property Position) } $i = 0 do { #for each descriptor index, process the value $Row = @{ "Description" = $Descriptor[($i+1)].Description; "Value" = $Values[$i]; "Unit" = $Descriptor[($i+1)].Unit } $Data += [PSCustomObject]$Row $i++ } until ($i -gt ($Descriptor.Count -1)) Return ($Data) } } process { foreach ($iOInstance in $iO) { Write-Verbose ("[Parser]: Instruction Decoding Handler: [" + $iOInstance.Instruction.Return.Value + "]") Write-Verbose ("[Parser]: Instruction Description: [" + $iOInstance.Instruction.Alias + "]") Write-Verbose ([char][Convert]::ToInt16($BMSInstructionSet.Config.Message.Components.QRY,16) ) #assert value type when the instruction was setting a parameter instead of reading a parameter if ([char][Convert]::ToInt16($BMSInstructionSet.Config.Message.Components.QRY,16) -ne $iOInstance.Plain) { Write-Verbose ("[Parser]: Instruction Is Configuration Type: []") $BMSData = [PSCustomObject]@{"0"=(LabelHeaderValues $iOInstance)} ($BMSData.0).Value = [string](Get-BMSCharsFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[0]) } else { switch ($iOInstance.Instruction.Return.Value) { string { $BMSData = [PSCustomObject]@{"0"=(LabelHeaderValues $iOInstance)} ($BMSData.0).Value = [string](Get-BMSCharsFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[0]) } float { $BMSData = [PSCustomObject]@{"0"=(LabelHeaderValues $iOInstance)} #Single value returns from BMS come back as chars, which we then cast into float. ($BMSData.0).Value = [float]("{0:N}" -f [float](Get-BMSCharsFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[0])) } char { $BMSData = [PSCustomObject]@{"0"=(LabelHeaderValues $iOInstance)} ($BMSData.0).Value = [char](Get-BMSCharsFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[0]) #special fixes for single ranged char data types if ($iOInstance.Command -eq "CHEM") { $id = ($BMSData.0).Value ($BMSData.0).Description = ("Chemistry: [" + $BMSInstructionSet.Config.Battery.ChemistryTypes.($id) + "]") } } int { $BMSData = [PSCustomObject]@{"0"=(LabelHeaderValues $iOInstance)} #Single value returns from BMS come back as chars, we cast these into formatted int ($BMSData.0).Value = [int]("{0:D0}" -f [int](Get-BMSCharsFromByteStream $iOInstance.ByteStreamReceive.ParsedStream[0])) } array { # each type of array has two parts: # first part is some sort of metadata about the BMS ID, or number of instructions/values expected # there is some inconsistency in the first value, so handler cases are necessary for this. # # because there isn't an expectation of this structure to change very much, there isn't any type of function recursion # for managing these use cases. # # the second part is the value array, which can be dynamic from a single value (like BMS controller temperature), # to several values such as error reporting, to many values, such as cell voltages #$BMSData = [PSCustomObject]@{"1"=(LabelDataArrayValues $iOInstance)} $BMSData = [PSCustomObject]@{"0"=(LabelHeaderValues $iOInstance);"1"=(LabelDataArrayValues $iOInstance)} #special fix/hacks for array data types if ($iOInstance.Command -eq "LCD3") { #this datatype is an AmpHour mesaurement, which packs a value in individual LSB-MSB values $Ah = [bitconverter]::ToInt16(($BMSData.1)[7..8].value,0) $BMSData.1 = ($BMSData.1)[0..5] $BMSData.1 += [PSCustomObject]@{ "Unit" = "Ah"; "Value" = $Ah; "Description" = "Amp Hours since last charge" } } } Default { Write-Warning ("[Parser]: No handler for type [" + $iOInstance.Instruction.Return.Value + "]") break } } } if ($BMSData.1) { Write-Verbose ("[Parser]: " + (($BMSData.1)[0]).Description) Write-Verbose ("[Parser]: " + (($BMSData.1)[0]).Unit) Write-Verbose ("[Parser]: " + (($BMSData.1)[0]).Value) Write-Verbose ("[Parser]: Values continue: [" + (($BMSData.1).Count -1) + "] more in data") } else { Write-Verbose ("[Parser]: " + ($BMSData.0).Description) Write-Verbose ("[Parser]: " + ($BMSData.0).Unit) Write-Verbose ("[Parser]: " + ($BMSData.0).Value) } $iOInstance | Add-Member -MemberType NoteProperty -Name BMSData -Value $BMSData $iOInstance } } } |