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

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/.idea
/plan
*.log
*.csv

235
DaliACLI.ps1 Normal file
View File

@@ -0,0 +1,235 @@
<#
.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

112
README.md Normal file
View File

@@ -0,0 +1,112 @@
# DaliACLI
CLI PowerShell pour la gestion des configurations de sorties DALI adressable (**ECxLightDaliA**) 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
---
## Fonctionnalités
- **Read** : Lit la configuration des modules ECxLightDaliA et génère un rapport CSV (une ligne par sortie)
- **Write** : Modifie les propriétés des sorties DALI à partir du CSV enrichi
## Prérequis
- PowerShell 5.1+ (inclus dans Windows 10/11)
- Accès réseau aux automates Eclypse
## Utilisation
### Lecture des configurations
```powershell
.\DaliACLI.ps1 -Action Read -CsvInput ".\automates.csv"
```
Génère un fichier `dali_YYYY-MM-DD_HHhmm.csv` avec les propriétés de chaque sortie.
### Écriture des configurations
```powershell
.\DaliACLI.ps1 -Action Write -CsvInput ".\dali_2026-04-02_14h30.csv"
```
### Avec mot de passe
```powershell
.\DaliACLI.ps1 -Action Read -CsvInput ".\automates.csv" -Password "MonMotDePasse"
```
### Aide
```powershell
Get-Help .\DaliACLI.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 DALI :
| 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 |
| ControlGearKey | Clé du control gear DALI |
| Groups | Groupes DALI actifs (ex: `1,3,5`) |
| SystemFailLevel | Niveau en cas de défaillance système |
| PowerOnLevel | Niveau à la mise sous tension |
| MinDimmingLevel | Niveau minimum de variation |
| DefaultValue | Valeur par défaut |
| DimmingTime | Temps de variation (secondes) |
| MaxDimmingLevel | Niveau maximum de variation |
## Filtrage
Seuls les modules de type **ECxLightDaliA** sont traités. Les autres types (ECxLight4Dali, 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
```
daliA/
├── DaliACLI.ps1 # Script principal
├── modules/
│ ├── Logger.psm1 # Logging console + fichier
│ ├── CsvHandler.psm1 # Lecture/écriture CSV
│ ├── ApiClient.psm1 # Communication REST API
│ └── DaliParser.psm1 # Parsing JSON DALI
├── plan/
│ └── daliacli.md # Plan de développement
└── README.md
```

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