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:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/.idea
|
||||||
|
/plan
|
||||||
|
*.log
|
||||||
|
*.csv
|
||||||
235
DaliACLI.ps1
Normal file
235
DaliACLI.ps1
Normal 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
112
README.md
Normal 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
201
modules/ApiClient.psm1
Normal 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
97
modules/CsvHandler.psm1
Normal 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
124
modules/DaliParser.psm1
Normal 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
95
modules/Logger.psm1
Normal 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
|
||||||
Reference in New Issue
Block a user