Création du projet DaliACLI

- CLI PowerShell pour la gestion des sorties DALI adressable (ECxLightDaliA)
  - Action Read : rapport CSV des configurations (une ligne par output)
  - Action Write : modification des propriétés via POST JSON
  - Fallback curl.exe -k pour les automates avec problèmes SSL
This commit is contained in:
2026-04-03 08:42:23 +02:00
commit 89bf57b665
7 changed files with 868 additions and 0 deletions

201
modules/ApiClient.psm1 Normal file
View File

@@ -0,0 +1,201 @@
# Module ApiClient - Communication REST API Eclypse (API v2)
$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" = "DaliACLI/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
}
}
Export-ModuleMember -Function Initialize-ApiClient, Invoke-ApiGet, Invoke-ApiPost

97
modules/CsvHandler.psm1 Normal file
View File

@@ -0,0 +1,97 @@
# Module CsvHandler - Lecture/ecriture CSV automates
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
}
Export-ModuleMember -Function Read-AutomateCsv, Get-BaseUrl, Get-Credentials, Write-OutputCsv

124
modules/DaliParser.psm1 Normal file
View File

@@ -0,0 +1,124 @@
# Module DaliParser - Parsing JSON modules DALI et construction body Write
function Get-DaliAModules {
[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 ECxLightDaliA
if ($module."configured-module-type" -ne "ECxLightDaliA") {
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
ControlGearKey = $output."control-gear-key"
Groups = (ConvertTo-GroupString -GroupArray $output."group-membership")
SystemFailLevel = $output."system-fail-level"
PowerOnLevel = $output."power-on-level"
MinDimmingLevel = $output."minimum-dimming-level"
DefaultValue = $output."default-value"
DimmingTime = $output."dimming-time"
MaxDimmingLevel = $output."maximum-dimming-level"
}
}
}
return $results
}
function ConvertTo-GroupString {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$GroupArray
)
$activeGroups = @()
for ($i = 0; $i -lt $GroupArray.Count; $i++) {
if ($GroupArray[$i] -eq $true) {
$activeGroups += ($i + 1)
}
}
return ($activeGroups -join ",")
}
function ConvertFrom-GroupString {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[AllowEmptyString()]
[string]$GroupString
)
$arr = @($false) * 16
if ($GroupString -and $GroupString.Trim() -ne "") {
$indices = $GroupString.Split(",") | ForEach-Object { [int]$_.Trim() }
foreach ($idx in $indices) {
if ($idx -ge 1 -and $idx -le 16) {
$arr[$idx - 1] = $true
}
}
}
return , $arr
}
function Build-ModuleWriteBody {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$ModuleKey,
[Parameter(Mandatory)]
[array]$OutputRows
)
$outputs = @{}
foreach ($row in $OutputRows) {
$outputKey = $row.OutputKey
$groupArray = ConvertFrom-GroupString -GroupString $row.Groups
$outputs[$outputKey] = [ordered]@{
"control-gear-key" = [string]$row.ControlGearKey
"group-membership" = $groupArray
"system-fail-level" = [double]$row.SystemFailLevel
"power-on-level" = [double]$row.PowerOnLevel
"minimum-dimming-level" = [double]$row.MinDimmingLevel
"default-value" = [double]$row.DefaultValue
"dimming-time" = [double]$row.DimmingTime
"maximum-dimming-level" = [double]$row.MaxDimmingLevel
}
}
$body = @{
$ModuleKey = @{
"outputs" = $outputs
}
}
return ($body | ConvertTo-Json -Depth 10)
}
Export-ModuleMember -Function Get-DaliAModules, ConvertTo-GroupString, ConvertFrom-GroupString, Build-ModuleWriteBody

95
modules/Logger.psm1 Normal file
View File

@@ -0,0 +1,95 @@
# Module Logger - Gestion des logs console + fichier
$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) "dali_$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"
# Couleur console selon le niveau
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 }
}
# Ecriture fichier
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
}
function Get-LogDirectory {
[CmdletBinding()]
param()
if ($script:LogFile) {
return Split-Path $script:LogFile -Parent
}
return $null
}
Export-ModuleMember -Function Initialize-Logger, Write-Log, Update-Stats, Write-Summary, Get-LogDirectory