# EnoceanCLI.ps1 - CLI Enocean pour automates Distech Controls Eclypse # Lecture/ecriture des configurations Enocean via REST API 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 "XmlParser.psm1") -Force Import-Module (Join-Path $modulesPath "ApiClient.psm1") -Force Import-Module (Join-Path $modulesPath "ZipBuilder.psm1") -Force # Initialisation Initialize-Logger Initialize-ApiClient Write-Log -Message "=== EnoceanCLI demarre - Action: $Action ===" -Level INFO # Lecture du CSV d'entree $automates = Read-AutomateCsv -CsvPath $CsvInput # ============================================================ # ACTION READ : Lire la config Enocean de chaque automate # ============================================================ if ($Action -eq "Read") { # Chemin CSV de sortie automatique dans le repertoire courant $timestamp = Get-Date -Format "yyyy-MM-dd_HH\hmm" $CsvOutput = Join-Path (Get-Location) "enocean_$timestamp.csv" Write-Log -Message "CSV de sortie : $CsvOutput" -Level INFO # Stockage des devices par IP : @{ "10.60.x.x" = @( @{DeviceId=...; DeviceType=...}, ... ) } $deviceData = @{} foreach ($automate in $automates) { $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 } $apiBasePath = Get-ApiBasePath -Automate $automate $creds = Get-Credentials -Automate $automate -DefaultUsername $Username -DefaultPassword $Password Write-Log -Message "[$hostname] $baseUrl$apiBasePath (user: $($creds.Username))" -Level INFO # GET liste des devices Enocean $jsonContent = Invoke-EnoceanGet ` -BaseUrl $baseUrl ` -ApiBasePath $apiBasePath ` -ResourcePath "files/enocean/configuration/devices" ` -Username $creds.Username ` -Password $creds.Password $deviceFiles = Get-DeviceListFromJson -JsonContent $jsonContent if ($deviceFiles.Count -eq 0) { Write-Log -Message "[$hostname] Aucun device Enocean trouve" -Level WARN $deviceData[$ip] = @() continue } Write-Log -Message "[$hostname] $($deviceFiles.Count) device(s) trouve(s)" -Level INFO # GET chaque XML device et parser $devices = @() foreach ($deviceFile in $deviceFiles) { try { $xmlContent = Invoke-EnoceanGet ` -BaseUrl $baseUrl ` -ApiBasePath $apiBasePath ` -ResourcePath "files/enocean/configuration/devices/$($deviceFile.Name)?encode=bin" ` -Username $creds.Username ` -Password $creds.Password $parsed = Parse-EnoceanDeviceXml -XmlContent $xmlContent $devices += $parsed Update-Stats -Counter DevicesProcessed Write-Log -Message "[$hostname] Device $($deviceFile.Name) : Id=$($parsed.DeviceId), Type=$($parsed.DeviceType)" -Level SUCCESS } catch { Write-Log -Message "[$hostname] Erreur lecture $($deviceFile.Name) : $($_.Exception.Message)" -Level ERROR } } # Trier par ResourceNumber puis stocker $devices = @($devices | Sort-Object { $_.ResourceNumber }) $deviceData[$ip] = $devices } catch { Write-Log -Message "[$hostname] ERREUR : $($_.Exception.Message)" -Level ERROR Update-Stats -Counter AutomatesError $deviceData[$ip] = @() } } # Ecriture du CSV de sortie Write-OutputCsv -InputRows $automates -DeviceData $deviceData -OutputPath $CsvOutput } # ============================================================ # ACTION WRITE : Ecrire la config Enocean sur chaque automate # ============================================================ elseif ($Action -eq "Write") { foreach ($automate in $automates) { $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 } $apiBasePath = Get-ApiBasePath -Automate $automate $creds = Get-Credentials -Automate $automate -DefaultUsername $Username -DefaultPassword $Password Write-Log -Message "[$hostname] $baseUrl$apiBasePath (user: $($creds.Username))" -Level INFO # Lire les colonnes dynamiques DeviceId_N / DeviceType_N $xmlFiles = @() $resourceIndex = 1 while ($true) { $deviceIdCol = "DeviceId_$resourceIndex" $deviceTypeCol = "DeviceType_$resourceIndex" # Verifier si les colonnes existent $props = $automate.PSObject.Properties.Name if ($deviceIdCol -notin $props -or $deviceTypeCol -notin $props) { break } $deviceId = $automate.$deviceIdCol $deviceType = $automate.$deviceTypeCol # Ignorer si vide if (-not $deviceId -or $deviceId -eq "" -or -not $deviceType -or $deviceType -eq "") { $resourceIndex++ continue } $xmlContent = New-EnoceanDeviceXml ` -ResourceNumber $resourceIndex ` -DeviceId $deviceId ` -DeviceType $deviceType $xmlFiles += @{ Name = "enoceandevice$resourceIndex.xml" Content = $xmlContent } Update-Stats -Counter DevicesProcessed Write-Log -Message "[$hostname] XML genere : device $resourceIndex (Id=$deviceId, Type=$deviceType)" -Level INFO $resourceIndex++ } if ($xmlFiles.Count -eq 0) { Write-Log -Message "[$hostname] Aucun device a ecrire - ignore" -Level WARN continue } # Creer le ZIP et l'envoyer $zipBytes = New-EnoceanZip -XmlFiles $xmlFiles $zipFilename = Get-ZipFilename -ZipBytes $zipBytes Write-Log -Message "[$hostname] Envoi de $($xmlFiles.Count) device(s) ($zipFilename)..." -Level INFO Send-EnoceanConfig ` -BaseUrl $baseUrl ` -ApiBasePath $apiBasePath ` -ZipBytes $zipBytes ` -ZipFilename $zipFilename ` -Username $creds.Username ` -Password $creds.Password Write-Log -Message "[$hostname] Configuration envoyee avec succes" -Level SUCCESS } catch { Write-Log -Message "[$hostname] ERREUR : $($_.Exception.Message)" -Level ERROR Update-Stats -Counter AutomatesError } } } # Resume final Write-Summary