Ajout du projet Light4dCLI (lecture/écriture des sorties d'éclairage 0-10V ECxLight4D)

This commit is contained in:
2026-08-13 17:41:23 +02:00
commit 23adf77430
2 changed files with 933 additions and 0 deletions
+702
View File
@@ -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
+231
View File
@@ -0,0 +1,231 @@
<!-- FR -->
# 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
```
<!-- EN -->
# 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
```