<# .SYNOPSIS CLI DALI pour automates Distech Controls Eclypse. Lecture et ecriture des configurations de sorties DALI adressable (ECxLightDaliA). .DESCRIPTION DaliACLI permet de gerer les configurations DALI adressable sur des automates Distech Controls Eclypse en utilisant leur API REST v2. Action READ : Lit la configuration des modules ECxLightDaliA de chaque automate et genere un fichier CSV avec une ligne par sortie (output). Action WRITE : Modifie les proprietes des sorties DALI a partir du CSV. Toutes les proprietes de chaque ligne sont reecrites. .PARAMETER Action Action a effectuer : Read - Lire la configuration et generer un CSV de sortie Write - Ecrire les proprietes depuis le CSV vers les automates .PARAMETER CsvInput Chemin vers le fichier CSV d'entree (separateur point-virgule). Pour Read : colonnes minimales Hostname, Current Ip, HttpPort, HttpsPort. Pour Write : CSV enrichi genere par l'action Read. .PARAMETER Username Nom d'utilisateur pour l'authentification API (defaut: admin). Peut etre surcharge par la colonne Username du CSV. .PARAMETER Password Mot de passe pour l'authentification API (defaut: vide). Peut etre surcharge par la colonne Password du CSV. .EXAMPLE .\DaliACLI.ps1 -Action Read -CsvInput ".\automates.csv" Lit la configuration DALI de tous les automates listes dans le CSV. Genere un fichier dali_YYYY-MM-DD_HHhMM.csv dans le repertoire courant. .EXAMPLE .\DaliACLI.ps1 -Action Write -CsvInput ".\dali_2026-04-02_14h30.csv" -Password "MonMotDePasse" Ecrit les proprietes du CSV vers les automates. .EXAMPLE Get-Help .\DaliACLI.ps1 -Detailed Affiche cette aide detaillee. .NOTES Prerequis : PowerShell 5.1+ (inclus dans Windows 10/11) API : Distech Controls Eclypse REST API v2 Securite : TLS 1.2, certificats auto-signes acceptes #> param( [Parameter(Mandatory)] [ValidateSet("Read", "Write")] [string]$Action, [Parameter(Mandatory)] [string]$CsvInput, [string]$Username = "admin", [string]$Password = "" ) # Performance : desactiver la barre de progression Invoke-WebRequest $ProgressPreference = 'SilentlyContinue' # Import des modules $modulesPath = Join-Path $PSScriptRoot "modules" Import-Module (Join-Path $modulesPath "Logger.psm1") -Force Import-Module (Join-Path $modulesPath "CsvHandler.psm1") -Force Import-Module (Join-Path $modulesPath "ApiClient.psm1") -Force Import-Module (Join-Path $modulesPath "DaliParser.psm1") -Force # Initialisation Initialize-Logger Initialize-ApiClient Write-Log -Message "=== DaliACLI demarre - Action: $Action ===" -Level INFO # Chemin de l'API DALI $daliApiPath = "/api/rest/v2/services/subnet/devices/light-sunblind/modules" # Lecture du CSV d'entree $csvRows = Read-AutomateCsv -CsvPath $CsvInput # ============================================================ # ACTION READ : Lire la config DALI de chaque automate # ============================================================ if ($Action -eq "Read") { $timestamp = Get-Date -Format "yyyy-MM-dd_HH\hmm" $CsvOutput = Join-Path (Get-Location) "dali_$timestamp.csv" Write-Log -Message "CSV de sortie : $CsvOutput" -Level INFO $allOutputRows = @() # Deduplication des automates par IP (le CSV d'entree a une ligne par automate) $uniqueAutomates = $csvRows | Sort-Object -Property "Current Ip" -Unique foreach ($automate in $uniqueAutomates) { $hostname = $automate.Hostname $ip = $automate."Current Ip" Update-Stats -Counter AutomatesTotal try { $baseUrl = Get-BaseUrl -Automate $automate if (-not $baseUrl) { Write-Log -Message "[$hostname] Aucun port HTTP/HTTPS valide - ignore" -Level WARN Update-Stats -Counter AutomatesError continue } $creds = Get-Credentials -Automate $automate -DefaultUsername $Username -DefaultPassword $Password $url = "$baseUrl$daliApiPath/" Write-Log -Message "[$hostname] $url (user: $($creds.Username))" -Level INFO # GET modules DALI $jsonContent = Invoke-ApiGet -Url $url -Username $creds.Username -Password $creds.Password # Parser et filtrer les modules ECxLightDaliA $daliOutputs = Get-DaliAModules -JsonContent $jsonContent if ($daliOutputs.Count -eq 0) { Write-Log -Message "[$hostname] Aucun module ECxLightDaliA trouve" -Level WARN continue } Write-Log -Message "[$hostname] $($daliOutputs.Count) sortie(s) ECxLightDaliA trouvee(s)" -Level INFO # Creer une ligne CSV par output foreach ($output in $daliOutputs) { # Copier toutes les colonnes source de l'automate $row = [ordered]@{} foreach ($prop in $automate.PSObject.Properties) { $row[$prop.Name] = $prop.Value } # Ajouter les colonnes DALI $row["ModuleKey"] = $output.ModuleKey $row["ModuleName"] = $output.ModuleName $row["OutputKey"] = $output.OutputKey $row["OutputName"] = $output.OutputName $row["ControlGearKey"] = $output.ControlGearKey $row["Groups"] = $output.Groups $row["SystemFailLevel"] = $output.SystemFailLevel $row["PowerOnLevel"] = $output.PowerOnLevel $row["MinDimmingLevel"] = $output.MinDimmingLevel $row["DefaultValue"] = $output.DefaultValue $row["DimmingTime"] = $output.DimmingTime $row["MaxDimmingLevel"] = $output.MaxDimmingLevel $allOutputRows += [PSCustomObject]$row Update-Stats -Counter OutputsProcessed Write-Log -Message "[$hostname] M$($output.ModuleKey)_O$($output.OutputKey) ($($output.OutputName)) - Groups: $($output.Groups)" -Level SUCCESS } } catch { Write-Log -Message "[$hostname] ERREUR : $($_.Exception.Message)" -Level ERROR Update-Stats -Counter AutomatesError } } # Ecriture du CSV de sortie if ($allOutputRows.Count -gt 0) { Write-OutputCsv -OutputRows $allOutputRows -OutputPath $CsvOutput } else { Write-Log -Message "Aucune sortie trouvee - CSV non genere" -Level WARN } } # ============================================================ # ACTION WRITE : Ecrire la config DALI sur chaque automate # ============================================================ elseif ($Action -eq "Write") { # Grouper les lignes par automate (Current Ip) $automateGroups = $csvRows | Group-Object -Property "Current Ip" foreach ($automateGroup in $automateGroups) { $ip = $automateGroup.Name $rows = $automateGroup.Group $hostname = $rows[0].Hostname Update-Stats -Counter AutomatesTotal try { $baseUrl = Get-BaseUrl -Automate $rows[0] if (-not $baseUrl) { Write-Log -Message "[$hostname] Aucun port HTTP/HTTPS valide - ignore" -Level WARN Update-Stats -Counter AutomatesError continue } $creds = Get-Credentials -Automate $rows[0] -DefaultUsername $Username -DefaultPassword $Password $url = "$baseUrl$daliApiPath" Write-Log -Message "[$hostname] $url (user: $($creds.Username))" -Level INFO # Grouper par ModuleKey pour envoyer un POST par module $moduleGroups = $rows | Group-Object -Property ModuleKey foreach ($moduleGroup in $moduleGroups) { $moduleKey = $moduleGroup.Name $outputRows = $moduleGroup.Group Write-Log -Message "[$hostname] Module $moduleKey : $($outputRows.Count) sortie(s) a ecrire" -Level INFO # Construire le body JSON $jsonBody = Build-ModuleWriteBody -ModuleKey $moduleKey -OutputRows $outputRows Write-Log -Message "[$hostname] Module $moduleKey body: $jsonBody" -Level INFO # POST vers l'API Invoke-ApiPost -Url $url -JsonBody $jsonBody -Username $creds.Username -Password $creds.Password Update-Stats -Counter OutputsProcessed -Increment $outputRows.Count Write-Log -Message "[$hostname] Module $moduleKey : configuration ecrite avec succes" -Level SUCCESS } } catch { Write-Log -Message "[$hostname] ERREUR : $($_.Exception.Message)" -Level ERROR Update-Stats -Counter AutomatesError } } } # Resume final Write-Summary