Set-VEValueFormatter.ps1

function Set-VEValueFormatter {
    <#
        .SYNOPSIS
            Ult
        .DESCRIPTION
            Ult
        .INPUTS
            No object can be piped to the function
        .EXAMPLE
            Set-VEValueFormatter
            Example of how to set value formatting object
        .NOTES
            Author: Decembry Quentin
    #>

    [CmdletBinding(DefaultParameterSetName='Diagnostic')]
    [OutputType([PSCustomObject])]
    [Alias()]
    param(
        # The VE session
        # Default: the default VE session
        [Parameter(Mandatory=$false)] [PSTypeName('VictronEnergy.Session')] $Session = $Script:VE_Session,
        
        # The VE diagnostics
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName='Diagnostic')] [PSTypeName('VictronEnergy.Diagnostic')] $Diagnostic,
        
        # The VE meta
        [Parameter(Mandatory=$true,ParameterSetName='Meta')] [Object] $Meta,
        
        # The site id
        [Parameter(Mandatory=$true,ParameterSetName='Meta')] [string] $SiteId,
        
        # Switch to return the value formatter object
        [Parameter(Mandatory=$false)] [switch] $PassThru
    )

    begin {
        if ($PSBoundParameters['Debug']) {
            $DebugPreference = 'Continue'
        }
        
        if ($MyInvocation.MyCommand.ModuleName) { 
            $Private:ThisFunction = '{0}::{1}' -f $MyInvocation.MyCommand.ModuleName, $MyInvocation.MyCommand.Name
        } else {
            $Private:ThisFunction = '{0}::{1}' -f (Get-PSCallStack)[0].ScriptName, $MyInvocation.MyCommand.Name
        }
        
        Write-Debug "[$Private:ThisFunction] Begin"
    }
    
    process {
        Write-Debug "[$Private:ThisFunction] Process code $Code"
        
        [string] $Code = ''
        [string] $FormatWithUnit = ''
        
        $Hashtable = @{
            PSTypeName = 'VictronEnergy.ValueFormatter'
            Source = $PSCmdlet.ParameterSetName
        }
        
        $Create = $true
        
        switch ($PSCmdlet.ParameterSetName) {
            #
            # Diagnostic is the best source but not always available
            #
            'Diagnostic' {
                $SiteId = $Diagnostic.idSite
                $Code = $Diagnostic.Code

                if ($Script:VE_ValueFormatter.$SiteId.$Code.Source -eq $_) {
                    # Object already exists from Diagnostic
                    $Create = $false
                    break
                }
                
                $Hashtable.BitMask = [Bool] $Diagnostic.Bitmask
                $Hashtable.EnumValues = @{}
                $Diagnostic.dataAttributeEnumValues | ForEach-Object { if ($Key = [string]$_.ValueEnum) { $Hashtable.EnumValues.$Key = $_.NameEnum } }
                $FormatWithUnit = $Diagnostic.formatWithUnit
            }
            #
            # Meta is not the best source because it laks enum values and bitmask information
            #
            'Meta' {
                $Code = $Meta.code
                
                if (!$Script:VE_ValueFormatter.$SiteId.LastDiagnostic) {
                    Write-Debug "[$Private:ThisFunction] Run diagnostics first"
                    $null = Get-VEDiagnostics -SiteId $SiteId -Session $Session -ErrorAction SilentlyContinue -ErrorVariable ErrVar
                    $ErrVar | ForEach-Object { Write-Debug "[$Private:ThisFunction] Error getting diagnostics: $_" }
                }
                
                if ($Script:VE_ValueFormatter.$SiteId.$Code.Source) {
                    # Object already exists from Meta or Diagnostic
                    $Create = $false
                    break
                }
                
                $Hashtable.BitMask = $false
                $Hashtable.EnumValues = @{}
                $FormatWithUnit = $Meta.formatWithUnit
            }
            default {
                throw "Unsupported parameter set name '$_'"
            }
        }
        
        if ($Create) {
            if (!$Script:VE_ValueFormatter.$SiteId) {
                Write-Debug "[$Private:ThisFunction] Create the hastable for site $SiteId"
                # Use 'New-Object System.Collections.Hashtable' instead of '@{}' to get a hashtable case sensitive
                $Script:VE_ValueFormatter.$SiteId = New-Object System.Collections.Hashtable
            }
            
            Write-Debug "[$Private:ThisFunction] Fill in the hashtable properties"
            $Hashtable.SiteId = $SiteId
            $Hashtable.Code = $Code
            $FormatValue, $Unit = $FormatWithUnit -split ' ',2
            
            #Bugs from VE
            switch ($VEObject.Code) {
                { $_ -in 'AI','Af' } { $FormatValue = '%s' }
                { $_ -in 'TO' } { $FormatValue = '%d' }
            }
            
            $Hashtable.TargetType, $Hashtable.Pattern = switch -Regex ($FormatValue) {
                '^%d$' {
                    @([int], '{0:D}')
                }
                '^%s$' {
                    @([string], '{0}')
                }
                '^%\.[0-9]+F$' {
                    $N = $_ -replace '^%.([0-9]+)F$', '$1'
                    @([double], "{0:F$N}")
                }
                default {
                    Write-Warning "[$Private:ThisFunction] Unsupported format '$_' for code '$($VEObject.Code)'"
                    @([string], '{0}')
                }
            }
            if ($Unit) { $Hashtable.Pattern += ' {0}' -f ($Unit -replace '%%','%') }
            
            Write-Debug "[$Private:ThisFunction] Create value formatting object"
            $Object = [PSCustomObject] $Hashtable
            $Object | Add-Member -MemberType ScriptMethod -Name Format -Value {
                param(
                    [string] $RawValue
                )
                
                if ([Int]::TryParse($RawValue, [Ref] 0)) { 
                    $Value = $NumValue = [Int]$RawValue
                } elseif ([Double]::TryParse($RawValue, [Ref] 0)) {
                    $Value = $NumValue = [Double]$RawValue
                } else {
                    $NumValue = 0
                    $Value = [string] $RawValue
                }
                
                if ($this.BitMask) {
                    if ($NumValue -is [Int]) {
                        if ($this.EnumValues.Keys.Count) {
                            $Value = ($this.EnumValues.Keys | ForEach-Object { if ($NumValue -band $_) { $this.EnumValues.$_ } }) -join ', '
                        } else {
                            # Write-Warning "[$Private:ThisFunction] Bit mask without enum values for code '$($Meta.Code)'"
                            $Value = '[{0}]' -f $NumValue
                        }
                    } elseif ($NumValue -is [Double]) {
                        # Unable to convert raw value using enumeration values
                        $Value = 'n.a.'
                    }
                } elseif ($this.EnumValues.Keys.Count) {
                    if ($NumValue -is [Int]) {
                        if ($NumValue -in $this.EnumValues.Keys) {
                            $Value = $this.EnumValues.([string]$NumValue)
                        } else {
                            # Value not in enum keys
                            $Value = '<{0}>' -f $NumValue
                        }
                    } elseif ($NumValue -is [Double]) {
                        # Unable to convert raw value using enumeration values
                        $Value = 'n.a.'
                    }
                }
                
                if ($NumValue -is [Double] -and $this.TargetType -eq [Int]) {
                    $Value = [Math]::Round($NumValue)
                }
                
                try {
                    $Value = [System.Management.Automation.LanguagePrimitives]::ConvertTo($Value, $this.TargetType)
                } catch {
                    Write-Warning "Unable to cast value '$Value' to target type '$($this.TargetType)' for attribute code '$($this.Code)'"
                }
                
                # Create and return value object
                [PSCustomObject] @{
                    PSTypeName = 'VictronEnergy.Value'
                    NumValue = $NumValue
                    Value = $Value
                    FormattedValue = $this.Pattern -f $Value
                } | Add-Member -MemberType ScriptMethod -Name ToString -Value { $this.FormattedValue } -Force -PassThru
            }
            
            Write-Debug "[$Private:ThisFunction] Add formatting object for code $Code from site $SiteId"
            $Script:VE_ValueFormatter.$SiteId.$Code = $Object
        }
        
        if ($PassThru) {
            Write-Debug "[$Private:ThisFunction] Return the value formatter object"
            Write-Output -NoEnumerate -InputObject $Script:VE_ValueFormatter.$SiteId.$Code
        }
    }
    
    end {
        Write-Debug "[$Private:ThisFunction] End"
    }
}

# SIG # Begin signature block
# MIIIbQYJKoZIhvcNAQcCoIIIXjCCCFoCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUY9SKHZTyrxl+EjJDZ/fBddww
# aeCgggX4MIICYjCCAgigAwIBAgITXwAAAAITcsrrTJecaQAAAAAAAjAKBggqhkjO
# PQQDAjAkMQswCQYDVQQGEwJCRTEVMBMGA1UEAxMMSG9tZSBSb290IENBMB4XDTIy
# MDUwNTE0MDMxOFoXDTMyMDUwNTE0MTMxOFowJzELMAkGA1UEBhMCQkUxGDAWBgNV
# BAMTD0hvbWUgSXNzdWluZyBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDOT
# aIIU+QU7LfsmyPqGkCf53SrJuaBPpb5iVILCvG3cQxL6vgIZaGRaKlzYO+lq3CnN
# b4rtJW2d7rlTeehXtpWjggEUMIIBEDAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4E
# FgQUYf1/nMEp3Df+Nzi+Hvk7FymPsrMwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBD
# AEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUfSS6
# P6qin/3ibmvelGJQhV1r1VgwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL3BraS5x
# ZWRpbnV4LmJlL0hvbWUlMjBSb290JTIwQ0EuY3JsMEYGCCsGAQUFBwEBBDowODA2
# BggrBgEFBQcwAoYqaHR0cDovL3BraS5xZWRpbnV4LmJlL0hvbWUlMjBSb290JTIw
# Q0EuY3J0MAoGCCqGSM49BAMCA0gAMEUCIERRzfMu3vx9vb+Apud4sDkOEKhKrCEl
# gSsedbjZRJ+YAiEA8BBaOqhnbnsUBIX2nty77euunwoAHrCuWzab5xJiy0UwggOO
# MIIDM6ADAgECAhNRAAAAMJpCm89a8zgYAAAAAAAwMAoGCCqGSM49BAMCMCcxCzAJ
# BgNVBAYTAkJFMRgwFgYDVQQDEw9Ib21lIElzc3VpbmcgQ0EwHhcNMjIwNzExMDc0
# NzQ3WhcNMjQwNzExMDc1NzQ3WjAbMRkwFwYDVQQDExBEZWNlbWJyeSBRdWVudGlu
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjALWtmJ1AQG77y9rUBbT
# HXR9FZokTP01bBWfNzk4Kenr/Xpm7Jt09PqLoNS8ToWWKRz9EkM+jg7lnLljWKcO
# G+fT1TbaEwxOpncugnbrioQQonbdWpyn0P7PxlqjUQdWd5Z7xQ0Z8RO3vvERWtDA
# L9QH0VBHNK4WqdWh3ElV6ogJUsy5hYxVTIU0K5VEYkIT4qvqq0RdajKavGWIr57R
# EsYv52Jcyh9FNgZKGdu9NwSKZUvkwHxGsI8zmwfP1I6QRkzsqPXlpk7A8NTD3+h+
# 4Y5AprRFyAWrVNYH+UQQe2D/PjjbKtfsU9xzG9EnqJF09pVRchapis+8DbfWnoC8
# iQIDAQABo4IBfTCCAXkwOgYJKwYBBAGCNxUHBC0wKwYjKwYBBAGCNxUIhorlOcPr
# DIadiwndkzuHjPAAPqb1CYWVxXgCAWQCAQowEwYDVR0lBAwwCgYIKwYBBQUHAwMw
# DgYDVR0PAQH/BAQDAgeAMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUHAwMwHQYD
# VR0OBBYEFAkUh200U2U2epAoy2ZvT7nwAYGBMB8GA1UdIwQYMBaAFGH9f5zBKdw3
# /jc4vh75Oxcpj7KzMD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly9wa2kucWVkaW51
# eC5iZS9Ib21lJTIwSXNzdWluZyUyMENBLmNybDBJBggrBgEFBQcBAQQ9MDswOQYI
# KwYBBQUHMAKGLWh0dHA6Ly9wa2kucWVkaW51eC5iZS9Ib21lJTIwSXNzdWluZyUy
# MENBLmNydDAuBgNVHREEJzAloCMGCisGAQQBgjcUAgOgFQwTRGVjZW1icnkuUUBo
# b21lLmxhbjAKBggqhkjOPQQDAgNJADBGAiEAh39vHCgqIDL4XazyHPiChMbu0by+
# aFP7rdzlJmM0K6ACIQCaP4CXolxlzdfkJsoIoDIJUrunYIlZ0yxs9OSnG1qmCjGC
# Ad8wggHbAgEBMD4wJzELMAkGA1UEBhMCQkUxGDAWBgNVBAMTD0hvbWUgSXNzdWlu
# ZyBDQQITUQAAADCaQpvPWvM4GAAAAAAAMDAJBgUrDgMCGgUAoHgwGAYKKwYBBAGC
# NwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUPlZ+RwbF
# A+ZBkE5JAR+unE5hIPYwDQYJKoZIhvcNAQEBBQAEggEAOa+b7TFj/xH1iWfQRIbh
# zSPDCMF05gS3g01FDzRjschufu7FyN+BjK/ajgpm2Tli7VPF6ocofbeFKsBnrCRt
# ri4CY2WnPPHyhJVRjBCFR91is+6RIAeCyeEX3er1cJ7QjQqLEsL6x3Ieij39PAZg
# 2LDLv1byvF6kvsqA6XYWcr1xkAtPShhz/+SWrAhjWWWf8gpW2xKnat4UFsKyZp1M
# wpe5oS9DYoNPAB+vH+/0TRlW+6YEIA3i6R0rw/ccY5V4/skWZgerBng3+4aAsNJY
# 19dW7FBYJE8tE96iFAOD006DuQNeGN2rVOtQouOjYcVfDcHXEn5sqAPOawjDHbMi
# 5g==
# SIG # End signature block