commit 23adf77430636dec3a835b2ec351f9eabdaabfb1 Author: Charles Date: Thu Aug 13 17:41:23 2026 +0200 Ajout du projet Light4dCLI (lecture/écriture des sorties d'éclairage 0-10V ECxLight4D) diff --git a/Light4dCLI.ps1 b/Light4dCLI.ps1 new file mode 100644 index 0000000..7552104 --- /dev/null +++ b/Light4dCLI.ps1 @@ -0,0 +1,702 @@ +<# +.SYNOPSIS + Light4dCLI - CLI pour la gestion des configurations de sorties d'eclairage 0-10V (ECxLight4D) + sur automates Distech Controls Eclypse. + +.DESCRIPTION + Light4dCLI permet de gerer les configurations des sorties d'eclairage pilotees en 0-10V + sur des automates Distech Controls Eclypse en utilisant leur API REST v2. + + Action READ : Lit la configuration des modules ECxLight4D de chaque automate + et genere un fichier CSV avec une ligne par sortie (output). + + Action WRITE : Modifie les proprietes des sorties 0-10V 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 + .\Light4dCLI.ps1 -Action Read -CsvInput ".\automates.csv" + + Lit la configuration 0-10V de tous les automates listes dans le CSV. + Genere un fichier light4d_YYYY-MM-DD_HHhMM.csv dans le repertoire courant. + +.EXAMPLE + .\Light4dCLI.ps1 -Action Write -CsvInput ".\light4d_2026-08-11_18h00.csv" -Password "MonMotDePasse" + + Ecrit les proprietes du CSV vers les automates. + +.EXAMPLE + Get-Help .\Light4dCLI.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, fallback curl.exe + Fichier unique pour faciliter le transfert (un seul Unblock-File necessaire). + + Si Windows bloque le fichier apres telechargement : + Unblock-File .\Light4dCLI.ps1 +#> + +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' + +# ============================================================ +#region LOGGER +# ============================================================ + +$script:LogFile = $null +$script:Stopwatch = $null +$script:Stats = @{ + AutomatesTotal = 0 + AutomatesError = 0 + OutputsProcessed = 0 +} + +function Initialize-Logger { + [CmdletBinding()] + param() + + $timestamp = Get-Date -Format "yyyy-MM-dd_HH\hmm" + $script:LogFile = Join-Path (Get-Location) "light4d_$timestamp.log" + New-Item -ItemType File -Path $script:LogFile -Force | Out-Null + + $script:Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + + $script:Stats = @{ + AutomatesTotal = 0 + AutomatesError = 0 + OutputsProcessed = 0 + } + + Write-Log -Message "Logger initialise - fichier: $($script:LogFile)" -Level INFO +} + +function Write-Log { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Message, + + [ValidateSet("INFO", "WARN", "ERROR", "SUCCESS")] + [string]$Level = "INFO" + ) + + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $line = "[$timestamp] [$Level] $Message" + + switch ($Level) { + "INFO" { Write-Host $line -ForegroundColor Cyan } + "WARN" { Write-Host $line -ForegroundColor Yellow } + "ERROR" { Write-Host $line -ForegroundColor Red } + "SUCCESS" { Write-Host $line -ForegroundColor Green } + } + + if ($script:LogFile) { + Add-Content -Path $script:LogFile -Value $line -Encoding UTF8 + } +} + +function Update-Stats { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [ValidateSet("AutomatesTotal", "AutomatesError", "OutputsProcessed")] + [string]$Counter, + + [int]$Increment = 1 + ) + + $script:Stats[$Counter] += $Increment +} + +function Write-Summary { + [CmdletBinding()] + param() + + $script:Stopwatch.Stop() + $duration = $script:Stopwatch.Elapsed + + Write-Log -Message "========== RESUME ==========" -Level INFO + Write-Log -Message "Automates traites : $($script:Stats.AutomatesTotal)" -Level INFO + Write-Log -Message "Automates en erreur : $($script:Stats.AutomatesError)" -Level $(if ($script:Stats.AutomatesError -gt 0) { "WARN" } else { "INFO" }) + Write-Log -Message "Outputs traites : $($script:Stats.OutputsProcessed)" -Level INFO + Write-Log -Message "Duree totale : $($duration.ToString('hh\:mm\:ss\.ff'))" -Level INFO + Write-Log -Message "============================" -Level INFO +} + +#endregion + +# ============================================================ +#region CSVHANDLER +# ============================================================ + +function Read-AutomateCsv { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$CsvPath + ) + + if (-not (Test-Path $CsvPath)) { + throw "Fichier CSV introuvable : $CsvPath" + } + + $rows = Import-Csv -Path $CsvPath -Delimiter ";" + Write-Log -Message "CSV charge : $($rows.Count) lignes depuis $CsvPath" -Level INFO + return $rows +} + +function Get-BaseUrl { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Automate + ) + + $ip = $Automate."Current Ip" + $httpsPort = $Automate.HttpsPort + $httpPort = $Automate.HttpPort + + # HTTPS si port > 0 et != -1 + if ($httpsPort -and $httpsPort -ne "" -and [int]$httpsPort -gt 0 -and [int]$httpsPort -ne -1) { + if ([int]$httpsPort -eq 443) { + return "https://$ip" + } + return "https://${ip}:$httpsPort" + } + + # HTTP si port > 0 + if ($httpPort -and $httpPort -ne "" -and [int]$httpPort -gt 0 -and [int]$httpPort -ne -1) { + if ([int]$httpPort -eq 80) { + return "http://$ip" + } + return "http://${ip}:$httpPort" + } + + return $null +} + +function Get-Credentials { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Automate, + + [string]$DefaultUsername = "admin", + [string]$DefaultPassword = "" + ) + + $username = $DefaultUsername + $password = $DefaultPassword + + # Override depuis le CSV si colonnes presentes et renseignees + $props = $Automate.PSObject.Properties.Name + if ("Username" -in $props -and $Automate.Username -and $Automate.Username -ne "") { + $username = $Automate.Username + } + if ("Password" -in $props -and $Automate.Password -and $Automate.Password -ne "") { + $password = $Automate.Password + } + + return @{ + Username = $username + Password = $password + } +} + +function Write-OutputCsv { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [array]$OutputRows, + + [Parameter(Mandatory)] + [string]$OutputPath + ) + + # Creer le repertoire de sortie si necessaire + $outputDir = Split-Path $OutputPath -Parent + if ($outputDir -and -not (Test-Path $outputDir)) { + New-Item -ItemType Directory -Path $outputDir -Force | Out-Null + } + + $OutputRows | Export-Csv -Path $OutputPath -Delimiter ";" -NoTypeInformation -Encoding UTF8 + Write-Log -Message "CSV de sortie ecrit : $OutputPath ($($OutputRows.Count) lignes)" -Level SUCCESS +} + +#endregion + +# ============================================================ +#region APICLIENT +# ============================================================ + +$script:CurlAvailable = $false + +function Initialize-ApiClient { + [CmdletBinding()] + param() + + # Forcer TLS 1.2 + 1.1 + 1.0 + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls + + # Desactiver la verification SSL (certificats auto-signes) + if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy').Type) { + Add-Type @" +using System.Net; +using System.Security.Cryptography.X509Certificates; +public class TrustAllCertsPolicy : ICertificatePolicy { + public bool CheckValidationResult( + ServicePoint srvPoint, X509Certificate certificate, + WebRequest request, int certificateProblem) { + return true; + } +} +"@ + } + [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy + + # Verifier si curl.exe est disponible (fallback SSL) + $script:CurlAvailable = [bool](Get-Command curl.exe -ErrorAction SilentlyContinue) + if ($script:CurlAvailable) { + Write-Log -Message "ApiClient initialise (TLS multi, SSL bypass, curl.exe fallback)" -Level INFO + } + else { + Write-Log -Message "ApiClient initialise (TLS multi, SSL bypass)" -Level INFO + } +} + +function Get-AuthHeader { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Username, + + [Parameter(Mandatory)] + [AllowEmptyString()] + [string]$Password + ) + + $pair = "${Username}:${Password}" + $bytes = [System.Text.Encoding]::ASCII.GetBytes($pair) + $base64 = [System.Convert]::ToBase64String($bytes) + + return @{ + "Authorization" = "Basic $base64" + "Accept" = "application/json" + "User-Agent" = "Light4dCLI/1.0 (PowerShell; Win32NT)" + } +} + +function Test-SslError { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Exception]$Exception + ) + + $msg = $Exception.Message + return ($msg -match "SSL" -or $msg -match "TLS" -or $msg -match "confiance" -or $msg -match "trust" -or $msg -match "certificate") +} + +function Invoke-CurlGet { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Url, + + [Parameter(Mandatory)] + [string]$Username, + + [Parameter(Mandatory)] + [AllowEmptyString()] + [string]$Password + ) + + Write-Log -Message "GET $Url (curl -k fallback)" -Level WARN + + $output = & curl.exe -s -k -u "${Username}:${Password}" -H "Accept: application/json" $Url 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "curl GET erreur (exit code $LASTEXITCODE): $output" + } + + Write-Log -Message "GET OK (curl)" -Level SUCCESS + return ($output | Out-String) +} + +function Invoke-CurlPost { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Url, + + [Parameter(Mandatory)] + [string]$JsonBody, + + [Parameter(Mandatory)] + [string]$Username, + + [Parameter(Mandatory)] + [AllowEmptyString()] + [string]$Password + ) + + Write-Log -Message "POST $Url (curl -k fallback)" -Level WARN + + $tempFile = [System.IO.Path]::GetTempFileName() + try { + [System.IO.File]::WriteAllText($tempFile, $JsonBody, [System.Text.Encoding]::UTF8) + $output = & curl.exe -s -k -u "${Username}:${Password}" -H "Content-Type: application/json; charset=utf-8" -d "@$tempFile" $Url 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "curl POST erreur (exit code $LASTEXITCODE): $output" + } + Write-Log -Message "POST OK (curl)" -Level SUCCESS + } + finally { + Remove-Item $tempFile -Force -ErrorAction SilentlyContinue + } +} + +function Invoke-ApiGet { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Url, + + [Parameter(Mandatory)] + [string]$Username, + + [Parameter(Mandatory)] + [AllowEmptyString()] + [string]$Password + ) + + $headers = Get-AuthHeader -Username $Username -Password $Password + + Write-Log -Message "GET $Url" -Level INFO + + try { + $response = Invoke-WebRequest -Uri $Url -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 30 + + if ($response.Content -is [byte[]]) { + return [System.Text.Encoding]::UTF8.GetString($response.Content).TrimStart([char]0xFEFF) + } + return $response.Content + } + catch { + if ((Test-SslError -Exception $_.Exception) -and $script:CurlAvailable) { + return Invoke-CurlGet -Url $Url -Username $Username -Password $Password + } + throw + } +} + +function Invoke-ApiPost { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Url, + + [Parameter(Mandatory)] + [string]$JsonBody, + + [Parameter(Mandatory)] + [string]$Username, + + [Parameter(Mandatory)] + [AllowEmptyString()] + [string]$Password + ) + + $headers = Get-AuthHeader -Username $Username -Password $Password + + Write-Log -Message "POST $Url" -Level INFO + + try { + $bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($JsonBody) + $response = Invoke-WebRequest -Uri $Url -Method POST -Headers $headers -Body $bodyBytes -ContentType "application/json; charset=utf-8" -UseBasicParsing -TimeoutSec 30 + $statusCode = $response.StatusCode + Write-Log -Message "POST reponse : $statusCode" -Level SUCCESS + return $statusCode + } + catch { + if ((Test-SslError -Exception $_.Exception) -and $script:CurlAvailable) { + Invoke-CurlPost -Url $Url -JsonBody $JsonBody -Username $Username -Password $Password + return + } + Write-Log -Message "POST erreur : $($_.Exception.Message)" -Level ERROR + throw + } +} + +#endregion + +# ============================================================ +#region PARSER +# ============================================================ + +function Get-Light4dModules { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$JsonContent + ) + + $data = $JsonContent | ConvertFrom-Json + $results = @() + + foreach ($prop in $data.PSObject.Properties) { + $moduleKey = $prop.Name + $module = $prop.Value + + # Filtrer uniquement les modules ECxLight4D (sorties 0-10V) + if ($module."configured-module-type" -ne "ECxLight4D") { + continue + } + + $moduleName = $module.name + + foreach ($outProp in $module.outputs.PSObject.Properties) { + $outputKey = $outProp.Name + $output = $outProp.Value + + $results += [PSCustomObject][ordered]@{ + ModuleKey = $moduleKey + ModuleName = $moduleName + OutputKey = $outputKey + OutputName = $output.name + DefaultValue = $output."default-value" + DimmingTime = $output."dimming-time" + DimmingManagement = $output."dimming-management" + DisableDimming = $output."disable-dimming" + } + } + } + + return $results +} + +function Build-ModuleWriteBody { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$ModuleKey, + + [Parameter(Mandatory)] + [array]$OutputRows + ) + + $outputs = @{} + + foreach ($row in $OutputRows) { + $outputKey = $row.OutputKey + + $outputs[$outputKey] = [ordered]@{ + "default-value" = [double]$row.DefaultValue + "dimming-time" = [double]$row.DimmingTime + "dimming-management" = [string]$row.DimmingManagement + "disable-dimming" = [System.Convert]::ToBoolean($row.DisableDimming) + } + } + + $body = @{ + $ModuleKey = @{ + "outputs" = $outputs + } + } + + return ($body | ConvertTo-Json -Depth 10) +} + +#endregion + +# ============================================================ +#region MAIN +# ============================================================ + +Initialize-Logger +Initialize-ApiClient + +Write-Log -Message "=== Light4dCLI demarre - Action: $Action ===" -Level INFO + +# Chemin de l'API modules (sorties 0-10V ECxLight4D) +$light4dApiPath = "/api/rest/v2/services/subnet/devices/light-sunblind/modules" + +# Lecture du CSV d'entree +$csvRows = Read-AutomateCsv -CsvPath $CsvInput + +# ============================================================ +# ACTION READ : Lire la config 0-10V de chaque automate +# ============================================================ +if ($Action -eq "Read") { + $timestamp = Get-Date -Format "yyyy-MM-dd_HH\hmm" + $CsvOutput = Join-Path (Get-Location) "light4d_$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$light4dApiPath/" + + Write-Log -Message "[$hostname] $url (user: $($creds.Username))" -Level INFO + + # GET modules 0-10V + $jsonContent = Invoke-ApiGet -Url $url -Username $creds.Username -Password $creds.Password + + # Parser et filtrer les modules ECxLight4D + $light4dOutputs = Get-Light4dModules -JsonContent $jsonContent + + if ($light4dOutputs.Count -eq 0) { + Write-Log -Message "[$hostname] Aucun module ECxLight4D trouve" -Level WARN + continue + } + + Write-Log -Message "[$hostname] $($light4dOutputs.Count) sortie(s) ECxLight4D trouvee(s)" -Level INFO + + # Creer une ligne CSV par output + foreach ($output in $light4dOutputs) { + # Copier toutes les colonnes source de l'automate + $row = [ordered]@{} + foreach ($prop in $automate.PSObject.Properties) { + $row[$prop.Name] = $prop.Value + } + # Ajouter les colonnes 0-10V + $row["ModuleKey"] = $output.ModuleKey + $row["ModuleName"] = $output.ModuleName + $row["OutputKey"] = $output.OutputKey + $row["OutputName"] = $output.OutputName + $row["DefaultValue"] = $output.DefaultValue + $row["DimmingTime"] = $output.DimmingTime + $row["DimmingManagement"] = $output.DimmingManagement + $row["DisableDimming"] = $output.DisableDimming + + $allOutputRows += [PSCustomObject]$row + Update-Stats -Counter OutputsProcessed + + Write-Log -Message "[$hostname] M$($output.ModuleKey)_O$($output.OutputKey) ($($output.OutputName)) - DefaultValue: $($output.DefaultValue)" -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 0-10V 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$light4dApiPath" + + 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 + +#endregion diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e86286 --- /dev/null +++ b/README.md @@ -0,0 +1,231 @@ + +# Light4dCLI + +CLI PowerShell pour la gestion des configurations de sorties d'éclairage **0-10V** (**ECxLight4D**) sur des automates Distech Controls Eclypse. + +--- + +> **IMPORTANT : Ce projet est un développement personnel indépendant.** +> +> Ce projet a été développé par Charles-Arthur DAVID à titre personnel. +> Distech Controls n'est pas responsable de ce projet et ne le supporte pas. +> Aucune demande de support ne sera prise en charge par Distech Controls. +> Distech Controls ne fournit aucune garantie ni assistance technique pour ce projet. + +--- + +## Prérequis + +- Windows 10 ou Windows 11 +- Automates **Distech Controls ECY Facilities (V2)** +- Être connecté au même réseau que les automates (réseau local ou VPN) +- Avoir les identifiants de connexion aux automates (nom d'utilisateur et mot de passe) + +## Fonctionnalités + +- **Read** : Lit la configuration des modules ECxLight4D et génère un rapport CSV (une ligne par sortie) +- **Write** : Modifie les propriétés des sorties 0-10V à partir du CSV enrichi + +## Utilisation avec cadMasterCLI + +Ce script peut être piloté depuis **[cadMasterCLI](https://git.cadjou.net/DistechControls/cadMasterCLI)**, qui évite d'avoir à utiliser PowerShell manuellement. + +1. Lancez `cadMasterCLI.ps1` +2. Sélectionnez **Light4dCLI** dans la liste des scripts à gauche. +3. Choisissez l'action `Read` ou `Write` (RadioButtons). +4. Renseignez `CsvInput` avec le bouton **Parcourir...** (voir le [format CSV d'entrée](#format-csv-dentrée-automates) ci-dessous). +5. Renseignez `Username` / `Password` si besoin (sinon `admin` / vide par défaut). +6. Cliquez sur **Lancer le script** et suivez la progression dans le journal d'exécution intégré. + +## Utilisation en ligne de commande (script autonome) + +### Lecture des configurations + +```powershell +.\Light4dCLI.ps1 -Action Read -CsvInput ".\automates.csv" +``` + +Génère un fichier `light4d_YYYY-MM-DD_HHhmm.csv` avec les propriétés de chaque sortie. + +### Écriture des configurations + +```powershell +.\Light4dCLI.ps1 -Action Write -CsvInput ".\light4d_2026-08-11_18h00.csv" +``` + +### Avec mot de passe + +```powershell +.\Light4dCLI.ps1 -Action Read -CsvInput ".\automates.csv" -Password "MonMotDePasse" +``` + +### Aide + +```powershell +Get-Help .\Light4dCLI.ps1 -Detailed +``` + +### Format CSV d'entrée (automates) + +Séparateur : point-virgule (`;`) + +```csv +Hostname;Current Ip;HttpPort;HttpsPort +Eclypse-RDC;192.168.1.11;0;443 +Eclypse-Etage1;192.168.1.12;0;443 +``` + +Colonnes optionnelles : `Username`, `Password` (surcharge les credentials par défaut `admin` / vide). + +### Format CSV de sortie (Read) + +Une ligne par sortie 0-10V : + +| Colonne | Description | +|---------|-------------| +| Hostname | Nom de l'automate | +| Current Ip | Adresse IP | +| HttpPort | Port HTTP | +| HttpsPort | Port HTTPS | +| ModuleKey | Identifiant du module | +| ModuleName | Nom du module | +| OutputKey | Identifiant de la sortie | +| OutputName | Nom de la sortie | +| DefaultValue | Valeur par défaut | +| DimmingTime | Temps de variation (secondes) | +| DimmingManagement | Mode de gestion de la variation | +| DisableDimming | Variation désactivée (True/False) | + +### Filtrage + +Seuls les modules de type **ECxLight4D** sont traités. Les autres types (ECxLightDaliA, etc.) sont ignorés automatiquement. + +### API + +- **Endpoint** : `/api/rest/v2/services/subnet/devices/light-sunblind/modules` +- **Auth** : Basic Auth +- **TLS 1.2** avec support des certificats auto-signés + +### Structure du projet + +``` +Light4dCLI/ +├── Light4dCLI.ps1 # Script unique (Logger, CsvHandler, ApiClient, Parser, Main) +└── README.md +``` + + +# Light4dCLI + +PowerShell CLI for managing **0-10V** lighting output configurations (**ECxLight4D**) on Distech Controls Eclypse controllers. + +--- + +> **IMPORTANT: This project is an independent personal project.** +> +> This project was developed by Charles-Arthur DAVID on a personal basis. +> Distech Controls is not responsible for this project and does not support it. +> No support request will be handled by Distech Controls. +> Distech Controls provides no warranty or technical assistance for this project. + +--- + +## Requirements + +- Windows 10 or Windows 11 +- **Distech Controls ECY Facilities (V2)** controllers +- Connected to the same network as the controllers (local network or VPN) +- Controller login credentials (username and password) + +## Features + +- **Read**: Reads the ECxLight4D modules configuration and generates a CSV report (one line per output) +- **Write**: Updates 0-10V output properties from the enriched CSV + +## Usage with cadMasterCLI + +This script can be run from **[cadMasterCLI](https://git.cadjou.net/DistechControls/cadMasterCLI)**, which avoids having to use PowerShell manually. + +1. Launch `cadMasterCLI.ps1` +2. Select **Light4dCLI** in the script list on the left. +3. Choose the `Read` or `Write` action (RadioButtons). +4. Set `CsvInput` using the **Browse...** button (see the [input CSV format](#input-csv-format-controllers) below). +5. Set `Username` / `Password` if needed (otherwise defaults to `admin` / empty). +6. Click **Run script** and follow the progress in the built-in execution log. + +## Command-line usage (standalone script) + +### Reading configurations + +```powershell +.\Light4dCLI.ps1 -Action Read -CsvInput ".\automates.csv" +``` + +Generates a `light4d_YYYY-MM-DD_HHhmm.csv` file with the properties of each output. + +### Writing configurations + +```powershell +.\Light4dCLI.ps1 -Action Write -CsvInput ".\light4d_2026-08-11_18h00.csv" +``` + +### With password + +```powershell +.\Light4dCLI.ps1 -Action Read -CsvInput ".\automates.csv" -Password "MyPassword" +``` + +### Help + +```powershell +Get-Help .\Light4dCLI.ps1 -Detailed +``` + +### Input CSV format (controllers) + +Separator: semicolon (`;`) + +```csv +Hostname;Current Ip;HttpPort;HttpsPort +Eclypse-RDC;192.168.1.11;0;443 +Eclypse-Etage1;192.168.1.12;0;443 +``` + +Optional columns: `Username`, `Password` (overrides the default `admin` / empty credentials). + +### Output CSV format (Read) + +One line per 0-10V output: + +| Column | Description | +|---------|-------------| +| Hostname | Controller name | +| Current Ip | IP address | +| HttpPort | HTTP port | +| HttpsPort | HTTPS port | +| ModuleKey | Module identifier | +| ModuleName | Module name | +| OutputKey | Output identifier | +| OutputName | Output name | +| DefaultValue | Default value | +| DimmingTime | Dimming time (seconds) | +| DimmingManagement | Dimming management mode | +| DisableDimming | Dimming disabled (True/False) | + +### Filtering + +Only **ECxLight4D** module types are processed. Other types (ECxLightDaliA, etc.) are automatically ignored. + +### API + +- **Endpoint**: `/api/rest/v2/services/subnet/devices/light-sunblind/modules` +- **Auth**: Basic Auth +- **TLS 1.2** with self-signed certificate support + +### Project structure + +``` +Light4dCLI/ +├── Light4dCLI.ps1 # Single file (Logger, CsvHandler, ApiClient, Parser, Main) +└── README.md +```