README.md — Documentation complète du projet
Refonte action Write : modifier les XML existants au lieu de les regenerer
This commit is contained in:
165
EnoceanCLI.ps1
165
EnoceanCLI.ps1
@@ -1,5 +1,59 @@
|
|||||||
# EnoceanCLI.ps1 - CLI Enocean pour automates Distech Controls Eclypse
|
<#
|
||||||
# Lecture/ecriture des configurations Enocean via REST API
|
.SYNOPSIS
|
||||||
|
CLI Enocean pour automates Distech Controls Eclypse.
|
||||||
|
Lecture et ecriture des configurations Enocean via REST API.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
EnoceanCLI permet de gerer les configurations EnOcean sur des automates
|
||||||
|
Distech Controls Eclypse Gen 1 (ECY-xxx) en utilisant leur API REST.
|
||||||
|
|
||||||
|
Action READ : Lit la configuration EnOcean de chaque automate et genere
|
||||||
|
un fichier CSV avec les DeviceId et DeviceType de chaque device.
|
||||||
|
|
||||||
|
Action WRITE : Modifie les DeviceId sur chaque automate a partir du CSV.
|
||||||
|
Seul le DeviceId est modifie, toute la configuration existante
|
||||||
|
(Points, MaxReceiveTime, Description...) est preservee.
|
||||||
|
|
||||||
|
.PARAMETER Action
|
||||||
|
Action a effectuer :
|
||||||
|
Read - Lire la configuration et generer un CSV de sortie
|
||||||
|
Write - Ecrire les DeviceId depuis le CSV vers les automates
|
||||||
|
|
||||||
|
.PARAMETER CsvInput
|
||||||
|
Chemin vers le fichier CSV d'entree (separateur point-virgule).
|
||||||
|
Le CSV doit contenir au minimum : Hostname, Current Ip, HttpPort, HttpsPort.
|
||||||
|
Pour l'action Write, il doit aussi contenir les colonnes DeviceId_1, DeviceId_2, etc.
|
||||||
|
|
||||||
|
.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
|
||||||
|
.\EnoceanCLI.ps1 -Action Read -CsvInput ".\automates.csv"
|
||||||
|
|
||||||
|
Lit la configuration EnOcean de tous les automates listes dans le CSV.
|
||||||
|
Genere un fichier enocean_YYYY-MM-DD_HHhMM.csv dans le repertoire courant.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\EnoceanCLI.ps1 -Action Write -CsvInput ".\enocean_2026-03-03_23h07.csv" -Password "MonMotDePasse"
|
||||||
|
|
||||||
|
Ecrit les DeviceId du CSV vers les automates. Les configurations existantes
|
||||||
|
(Points, MaxReceiveTime, etc.) sont preservees.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-Help .\EnoceanCLI.ps1 -Detailed
|
||||||
|
|
||||||
|
Affiche cette aide detaillee.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Prerequis : PowerShell 5.1+ (inclus dans Windows 10/11)
|
||||||
|
API : Distech Controls Eclypse REST API v1
|
||||||
|
Securite : TLS 1.2, certificats auto-signes acceptes
|
||||||
|
#>
|
||||||
|
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -143,47 +197,96 @@ elseif ($Action -eq "Write") {
|
|||||||
|
|
||||||
Write-Log -Message "[$hostname] $baseUrl$apiBasePath (user: $($creds.Username))" -Level INFO
|
Write-Log -Message "[$hostname] $baseUrl$apiBasePath (user: $($creds.Username))" -Level INFO
|
||||||
|
|
||||||
# Lire les colonnes dynamiques DeviceId_N / DeviceType_N
|
# GET liste des devices Enocean existants sur l'automate
|
||||||
$xmlFiles = @()
|
$jsonContent = Invoke-EnoceanGet `
|
||||||
$resourceIndex = 1
|
-BaseUrl $baseUrl `
|
||||||
|
-ApiBasePath $apiBasePath `
|
||||||
|
-ResourcePath "files/enocean/configuration/devices" `
|
||||||
|
-Username $creds.Username `
|
||||||
|
-Password $creds.Password
|
||||||
|
|
||||||
while ($true) {
|
$deviceFiles = Get-DeviceListFromJson -JsonContent $jsonContent
|
||||||
$deviceIdCol = "DeviceId_$resourceIndex"
|
|
||||||
$deviceTypeCol = "DeviceType_$resourceIndex"
|
|
||||||
|
|
||||||
# Verifier si les colonnes existent
|
if ($deviceFiles.Count -eq 0) {
|
||||||
$props = $automate.PSObject.Properties.Name
|
Write-Log -Message "[$hostname] Aucun device Enocean existant sur l'automate - ignore" -Level WARN
|
||||||
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
$xmlContent = New-EnoceanDeviceXml `
|
Write-Log -Message "[$hostname] $($deviceFiles.Count) device(s) existant(s) sur l'automate" -Level INFO
|
||||||
-ResourceNumber $resourceIndex `
|
|
||||||
-DeviceId $deviceId `
|
# GET chaque XML existant et parser pour obtenir ResourceNumber
|
||||||
-DeviceType $deviceType
|
$existingDevices = @()
|
||||||
|
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
|
||||||
|
$existingDevices += @{
|
||||||
|
Name = $deviceFile.Name
|
||||||
|
XmlContent = $xmlContent
|
||||||
|
ResourceNumber = $parsed.ResourceNumber
|
||||||
|
DeviceType = $parsed.DeviceType
|
||||||
|
DeviceId = $parsed.DeviceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log -Message "[$hostname] Erreur lecture $($deviceFile.Name) : $($_.Exception.Message)" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trier par ResourceNumber
|
||||||
|
$existingDevices = @($existingDevices | Sort-Object { $_.ResourceNumber })
|
||||||
|
|
||||||
|
# Associer par index sequentiel : device[0] -> DeviceId_1, device[1] -> DeviceId_2, etc.
|
||||||
|
$xmlFiles = @()
|
||||||
|
for ($i = 0; $i -lt $existingDevices.Count; $i++) {
|
||||||
|
$seqIndex = $i + 1
|
||||||
|
$deviceIdCol = "DeviceId_$seqIndex"
|
||||||
|
$deviceTypeCol = "DeviceType_$seqIndex"
|
||||||
|
$device = $existingDevices[$i]
|
||||||
|
|
||||||
|
# Verifier si la colonne DeviceId_N existe dans le CSV
|
||||||
|
$props = $automate.PSObject.Properties.Name
|
||||||
|
if ($deviceIdCol -notin $props) {
|
||||||
|
Write-Log -Message "[$hostname] Colonne $deviceIdCol absente du CSV - device $($device.Name) non modifie" -Level WARN
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$newDeviceId = $automate.$deviceIdCol
|
||||||
|
|
||||||
|
# Ignorer si DeviceId vide
|
||||||
|
if (-not $newDeviceId -or $newDeviceId -eq "") {
|
||||||
|
$seqIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verifier DeviceType CSV vs XML si la colonne existe
|
||||||
|
if ($deviceTypeCol -in $props) {
|
||||||
|
$csvDeviceType = $automate.$deviceTypeCol
|
||||||
|
if ($csvDeviceType -and $csvDeviceType -ne "" -and $csvDeviceType -ne $device.DeviceType) {
|
||||||
|
Write-Log -Message "[$hostname] Device $($device.Name) : DeviceType CSV ($csvDeviceType) differe du XML ($($device.DeviceType))" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Modifier uniquement le DeviceId dans le XML existant
|
||||||
|
$result = Update-EnoceanDeviceId -XmlContent $device.XmlContent -NewDeviceId $newDeviceId
|
||||||
|
|
||||||
$xmlFiles += @{
|
$xmlFiles += @{
|
||||||
Name = "enoceandevice$resourceIndex.xml"
|
Name = $device.Name
|
||||||
Content = $xmlContent
|
Content = $result.ModifiedXml
|
||||||
}
|
}
|
||||||
|
|
||||||
Update-Stats -Counter DevicesProcessed
|
Update-Stats -Counter DevicesProcessed
|
||||||
Write-Log -Message "[$hostname] XML genere : device $resourceIndex (Id=$deviceId, Type=$deviceType)" -Level INFO
|
Write-Log -Message "[$hostname] Device $($device.Name) : $($result.OldDeviceId) -> $newDeviceId" -Level INFO
|
||||||
|
|
||||||
$resourceIndex++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($xmlFiles.Count -eq 0) {
|
if ($xmlFiles.Count -eq 0) {
|
||||||
Write-Log -Message "[$hostname] Aucun device a ecrire - ignore" -Level WARN
|
Write-Log -Message "[$hostname] Aucun device a modifier - ignore" -Level WARN
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +294,7 @@ elseif ($Action -eq "Write") {
|
|||||||
$zipBytes = New-EnoceanZip -XmlFiles $xmlFiles
|
$zipBytes = New-EnoceanZip -XmlFiles $xmlFiles
|
||||||
$zipFilename = Get-ZipFilename -ZipBytes $zipBytes
|
$zipFilename = Get-ZipFilename -ZipBytes $zipBytes
|
||||||
|
|
||||||
Write-Log -Message "[$hostname] Envoi de $($xmlFiles.Count) device(s) ($zipFilename)..." -Level INFO
|
Write-Log -Message "[$hostname] Envoi de $($xmlFiles.Count) device(s) modifie(s) ($zipFilename)..." -Level INFO
|
||||||
|
|
||||||
Send-EnoceanConfig `
|
Send-EnoceanConfig `
|
||||||
-BaseUrl $baseUrl `
|
-BaseUrl $baseUrl `
|
||||||
|
|||||||
269
README.md
Normal file
269
README.md
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
# EnoceanCLI
|
||||||
|
|
||||||
|
Outil en ligne de commande pour lire et écrire les configurations EnOcean sur les automates **Distech Controls Eclypse Gen 1** via leur API REST.
|
||||||
|
|
||||||
|
## Ce que fait cet outil
|
||||||
|
|
||||||
|
- **Read** : Se connecte à chaque automate listé dans un CSV, récupère la configuration EnOcean (DeviceId, DeviceType de chaque capteur), et génère un fichier CSV de sortie.
|
||||||
|
- **Write** : Prend un CSV contenant les DeviceId souhaités et les applique sur chaque automate. **Seul le DeviceId est modifié** — toute la configuration existante sur l'automate (Points, MaxReceiveTime, Description, etc.) est préservée.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
- **Windows 10 ou 11** (PowerShell 5.1 est inclus par défaut, rien à installer)
|
||||||
|
- Un accès réseau aux automates Eclypse (HTTP ou HTTPS)
|
||||||
|
- Les identifiants de connexion aux automates (par défaut : `admin` / mot de passe vide)
|
||||||
|
|
||||||
|
### Vérifier que PowerShell est disponible
|
||||||
|
|
||||||
|
Ouvrir un terminal (touche `Windows` + `R`, taper `powershell`, puis `Entrée`) et taper :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$PSVersionTable.PSVersion
|
||||||
|
```
|
||||||
|
|
||||||
|
Le numéro `Major` doit être **5 ou plus**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Aucune installation requise. Télécharger ou cloner le dossier du projet :
|
||||||
|
|
||||||
|
```
|
||||||
|
gfxEnocean/
|
||||||
|
├── EnoceanCLI.ps1 <- Script principal
|
||||||
|
├── modules/ <- Modules (ne pas modifier)
|
||||||
|
│ ├── Logger.psm1
|
||||||
|
│ ├── CsvHandler.psm1
|
||||||
|
│ ├── ApiClient.psm1
|
||||||
|
│ ├── XmlParser.psm1
|
||||||
|
│ └── ZipBuilder.psm1
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
### Ouvrir PowerShell dans le bon dossier
|
||||||
|
|
||||||
|
1. Ouvrir l'explorateur de fichiers et naviguer dans le dossier `gfxEnocean`
|
||||||
|
2. Cliquer dans la barre d'adresse, taper `powershell`, puis appuyer sur `Entrée`
|
||||||
|
|
||||||
|
Ou bien dans un terminal PowerShell :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd "C:\chemin\vers\gfxEnocean"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Politique d'exécution
|
||||||
|
|
||||||
|
Si c'est la première fois, PowerShell peut bloquer l'exécution des scripts. Autoriser pour la session en cours :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette commande est sans risque : elle n'autorise les scripts que pour la fenêtre PowerShell en cours.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Action Read — Lire la configuration des automates
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\EnoceanCLI.ps1 -Action Read -CsvInput ".\mon_fichier.csv"
|
||||||
|
```
|
||||||
|
|
||||||
|
Avec un mot de passe :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\EnoceanCLI.ps1 -Action Read -CsvInput ".\mon_fichier.csv" -Password "MonMotDePasse"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat** : Un fichier `enocean_YYYY-MM-DD_HHhMM.csv` est créé dans le dossier courant avec les DeviceId et DeviceType de chaque capteur EnOcean.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Action Write — Écrire les DeviceId sur les automates
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\EnoceanCLI.ps1 -Action Write -CsvInput ".\enocean_2026-03-03_23h07.csv" -Password "MonMotDePasse"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ce qui se passe** :
|
||||||
|
1. Le script se connecte à chaque automate
|
||||||
|
2. Il récupère la configuration EnOcean existante (XML complet avec Points, etc.)
|
||||||
|
3. Il remplace **uniquement le DeviceId** par la valeur du CSV
|
||||||
|
4. Il renvoie la configuration modifiée à l'automate
|
||||||
|
|
||||||
|
**Ce qui est préservé** : Points, MaxReceiveTime, Description, Name, ResourceNumber, DeviceType — tout sauf le DeviceId.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Aide intégrée
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-Help .\EnoceanCLI.ps1 -Detailed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Format du CSV d'entrée
|
||||||
|
|
||||||
|
Le fichier CSV utilise le **point-virgule** (`;`) comme séparateur.
|
||||||
|
|
||||||
|
### Colonnes obligatoires
|
||||||
|
|
||||||
|
| Colonne | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `Hostname` | Nom de l'automate |
|
||||||
|
| `Current Ip` | Adresse IP de l'automate |
|
||||||
|
| `HttpPort` | Port HTTP (`80` ou `-1` si désactivé) |
|
||||||
|
| `HttpsPort` | Port HTTPS (`443` ou `-1` si désactivé) |
|
||||||
|
|
||||||
|
### Colonnes optionnelles
|
||||||
|
|
||||||
|
| Colonne | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `RestServiceURL` | Chemin API (défaut : `/api/rest/v1/`) |
|
||||||
|
| `Username` | Login spécifique à cet automate |
|
||||||
|
| `Password` | Mot de passe spécifique à cet automate |
|
||||||
|
|
||||||
|
### Colonnes pour l'action Write
|
||||||
|
|
||||||
|
| Colonne | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `DeviceId_1` | DeviceId à écrire sur le 1er capteur EnOcean |
|
||||||
|
| `DeviceType_1` | DeviceType du 1er capteur (pour vérification) |
|
||||||
|
| `DeviceId_2` | DeviceId à écrire sur le 2e capteur |
|
||||||
|
| `DeviceType_2` | DeviceType du 2e capteur |
|
||||||
|
| ... | Autant de paires que de capteurs |
|
||||||
|
|
||||||
|
### Exemple de CSV
|
||||||
|
|
||||||
|
```csv
|
||||||
|
"Hostname";"Current Ip";"HttpPort";"HttpsPort";"RestServiceURL";"DeviceId_1";"DeviceType_1";"DeviceId_2";"DeviceType_2"
|
||||||
|
"MON-AUTOMATE-01";"192.168.1.11";"-1";"443";"/api/rest/v1/";"99864513";"A50401";"65313272";"A5100C"
|
||||||
|
"MON-AUTOMATE-02";"192.168.1.12";"80";"-1";"/api/rest/v1/";"45678912";"A53001";"";""
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note** : Les colonnes DeviceId/DeviceType vides sont ignorées — le capteur correspondant n'est pas modifié.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow typique
|
||||||
|
|
||||||
|
### 1. Préparer le CSV d'entrée
|
||||||
|
|
||||||
|
Partir d'un export existant (XNU Export, inventaire réseau...) contenant au minimum les colonnes `Hostname`, `Current Ip`, `HttpPort`, `HttpsPort`.
|
||||||
|
|
||||||
|
### 2. Lire la configuration actuelle
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\EnoceanCLI.ps1 -Action Read -CsvInput ".\automates.csv" -Password "MonMotDePasse"
|
||||||
|
```
|
||||||
|
|
||||||
|
Cela génère un CSV avec les DeviceId actuels de chaque automate.
|
||||||
|
|
||||||
|
### 3. Modifier les DeviceId dans le CSV
|
||||||
|
|
||||||
|
Ouvrir le CSV généré dans Excel ou un éditeur de texte. Modifier les colonnes `DeviceId_1`, `DeviceId_2`, etc. avec les nouvelles valeurs.
|
||||||
|
|
||||||
|
**Attention** : Ne pas modifier les colonnes `DeviceType_N`. Elles servent uniquement de référence. Si le DeviceType du CSV diffère de celui de l'automate, un avertissement sera affiché dans les logs.
|
||||||
|
|
||||||
|
### 4. Écrire la nouvelle configuration
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\EnoceanCLI.ps1 -Action Write -CsvInput ".\enocean_modifie.csv" -Password "MonMotDePasse"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Vérifier
|
||||||
|
|
||||||
|
Relancer un Read pour confirmer que les DeviceId ont bien changé :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\EnoceanCLI.ps1 -Action Read -CsvInput ".\automates.csv" -Password "MonMotDePasse"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Logs
|
||||||
|
|
||||||
|
Chaque exécution génère un fichier log dans le dossier courant :
|
||||||
|
|
||||||
|
```
|
||||||
|
enocean_2026-03-04_14h30.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Niveaux de log
|
||||||
|
|
||||||
|
| Niveau | Couleur console | Signification |
|
||||||
|
|--------|----------------|---------------|
|
||||||
|
| `INFO` | Cyan | Opération normale |
|
||||||
|
| `SUCCESS` | Vert | Opération réussie |
|
||||||
|
| `WARN` | Jaune | Avertissement (DeviceType différent, colonne manquante...) |
|
||||||
|
| `ERROR` | Rouge | Erreur (connexion échouée, automate injoignable...) |
|
||||||
|
|
||||||
|
### Exemple de log (action Write)
|
||||||
|
|
||||||
|
```
|
||||||
|
[2026-03-04 00:17:43] [INFO] === EnoceanCLI demarre - Action: Write ===
|
||||||
|
[2026-03-04 00:17:43] [INFO] [MON-AUTOMATE-01] https://10.60.105.42/api/rest/v1 (user: admin)
|
||||||
|
[2026-03-04 00:17:43] [INFO] [MON-AUTOMATE-01] 3 device(s) existant(s) sur l'automate
|
||||||
|
[2026-03-04 00:17:43] [INFO] [MON-AUTOMATE-01] Device enoceandevice1.xml : 12345678 -> 99864513
|
||||||
|
[2026-03-04 00:17:43] [INFO] [MON-AUTOMATE-01] Device enoceandevice2.xml : 87654321 -> 65313272
|
||||||
|
[2026-03-04 00:17:44] [WARN] [MON-AUTOMATE-01] Device enoceandevice3.xml : DeviceType CSV (D50001) differe du XML (A50401)
|
||||||
|
[2026-03-04 00:17:44] [INFO] [MON-AUTOMATE-01] Device enoceandevice3.xml : 11112222 -> 32602064
|
||||||
|
[2026-03-04 00:17:44] [SUCCESS] [MON-AUTOMATE-01] Configuration envoyee avec succes
|
||||||
|
[2026-03-04 00:17:44] [INFO] ========== RESUME ==========
|
||||||
|
[2026-03-04 00:17:44] [INFO] Automates traites : 1
|
||||||
|
[2026-03-04 00:17:44] [INFO] Automates en erreur : 0
|
||||||
|
[2026-03-04 00:17:44] [INFO] Devices traites : 3
|
||||||
|
[2026-03-04 00:17:44] [INFO] Duree totale : 00:00:01.23
|
||||||
|
[2026-03-04 00:17:44] [INFO] ============================
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des paramètres
|
||||||
|
|
||||||
|
```
|
||||||
|
.\EnoceanCLI.ps1 -Action <Read|Write> -CsvInput <chemin> [-Username <login>] [-Password <mdp>]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Paramètre | Obligatoire | Défaut | Description |
|
||||||
|
|-----------|:-----------:|--------|-------------|
|
||||||
|
| `-Action` | Oui | — | `Read` ou `Write` |
|
||||||
|
| `-CsvInput` | Oui | — | Chemin du fichier CSV d'entrée |
|
||||||
|
| `-Username` | Non | `admin` | Login API (surchargeable par le CSV) |
|
||||||
|
| `-Password` | Non | *(vide)* | Mot de passe API (surchargeable par le CSV) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dépannage
|
||||||
|
|
||||||
|
### "Impossible d'exécuter le script car l'exécution de scripts est désactivée"
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Aucun port HTTP/HTTPS valide"
|
||||||
|
|
||||||
|
Vérifier que les colonnes `HttpPort` et `HttpsPort` du CSV contiennent des valeurs valides. Un port à `-1` signifie "désactivé". Au moins un des deux doit être actif.
|
||||||
|
|
||||||
|
### "Aucun device Enocean existant sur l'automate"
|
||||||
|
|
||||||
|
L'automate n'a pas de capteurs EnOcean configurés. L'action Write ne peut pas modifier des devices qui n'existent pas encore — ils doivent d'abord être créés via le logiciel EC-gfxProgram.
|
||||||
|
|
||||||
|
### Timeout ou erreur de connexion
|
||||||
|
|
||||||
|
- Vérifier que l'automate est joignable (`ping 10.60.x.x`)
|
||||||
|
- Vérifier les identifiants (Username / Password)
|
||||||
|
- Vérifier que le port est correct (HTTP 80 ou HTTPS 443)
|
||||||
|
|
||||||
|
### Le WARN "DeviceType CSV diffère du XML"
|
||||||
|
|
||||||
|
C'est un avertissement informatif. Le DeviceId sera quand même appliqué. Ce message signifie que le type de capteur indiqué dans le CSV ne correspond pas à celui configuré sur l'automate. Vérifier que le bon DeviceId est associé au bon capteur.
|
||||||
@@ -75,9 +75,9 @@ function Invoke-EnoceanGet {
|
|||||||
|
|
||||||
$response = Invoke-WebRequest -Uri $url -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 30
|
$response = Invoke-WebRequest -Uri $url -Method GET -Headers $headers -UseBasicParsing -TimeoutSec 30
|
||||||
|
|
||||||
# Si la reponse est un byte[] (encode=bin), decoder en string UTF-8
|
# Si la reponse est un byte[] (encode=bin), decoder en string UTF-8 sans BOM
|
||||||
if ($response.Content -is [byte[]]) {
|
if ($response.Content -is [byte[]]) {
|
||||||
return [System.Text.Encoding]::UTF8.GetString($response.Content)
|
return [System.Text.Encoding]::UTF8.GetString($response.Content).TrimStart([char]0xFEFF)
|
||||||
}
|
}
|
||||||
return $response.Content
|
return $response.Content
|
||||||
}
|
}
|
||||||
@@ -105,28 +105,21 @@ function Send-EnoceanConfig {
|
|||||||
[string]$Password
|
[string]$Password
|
||||||
)
|
)
|
||||||
|
|
||||||
$headers = Get-AuthHeader -Username $Username -Password $Password
|
$authHeaders = Get-AuthHeader -Username $Username -Password $Password
|
||||||
$url = "$BaseUrl$ApiBasePath/files/bacnet/inputConfiguration"
|
$url = "$BaseUrl$ApiBasePath/files/bacnet/inputConfiguration"
|
||||||
|
|
||||||
Write-Log -Message "POST $url (fichier: $ZipFilename, taille: $($ZipBytes.Length) octets)" -Level INFO
|
Write-Log -Message "POST $url (fichier: $ZipFilename, taille: $($ZipBytes.Length) octets)" -Level INFO
|
||||||
|
|
||||||
# Construction multipart manuelle (compatible PS 5.1, pas de -Form)
|
# Construction multipart manuelle
|
||||||
$boundary = [System.Guid]::NewGuid().ToString("N")
|
$boundary = [System.Guid]::NewGuid().ToString("N")
|
||||||
$headers["Content-Type"] = "multipart/form-data; boundary=$boundary"
|
|
||||||
|
|
||||||
$encoding = [System.Text.Encoding]::ASCII
|
$encoding = [System.Text.Encoding]::ASCII
|
||||||
|
|
||||||
# Partie avant le fichier
|
# Partie avant le fichier
|
||||||
$headerPart = @"
|
$headerPart = "--$boundary`r`nContent-Disposition: form-data; name=`"File`"; filename=`"$ZipFilename`"`r`nContent-Type: application/octet-stream`r`n`r`n"
|
||||||
--$boundary
|
|
||||||
Content-Disposition: form-data; name="File"; filename="$ZipFilename"
|
|
||||||
Content-Type: application/octet-stream
|
|
||||||
|
|
||||||
"@
|
|
||||||
# Partie finale
|
# Partie finale
|
||||||
$footerPart = "`r`n--$boundary--`r`n"
|
$footerPart = "`r`n--$boundary--`r`n"
|
||||||
|
|
||||||
$headerBytes = $encoding.GetBytes($headerPart.Replace("`n", "`r`n"))
|
$headerBytes = $encoding.GetBytes($headerPart)
|
||||||
$footerBytes = $encoding.GetBytes($footerPart)
|
$footerBytes = $encoding.GetBytes($footerPart)
|
||||||
|
|
||||||
# Assembler le body complet en byte[]
|
# Assembler le body complet en byte[]
|
||||||
@@ -137,10 +130,39 @@ Content-Type: application/octet-stream
|
|||||||
$bodyBytes = $bodyStream.ToArray()
|
$bodyBytes = $bodyStream.ToArray()
|
||||||
$bodyStream.Close()
|
$bodyStream.Close()
|
||||||
|
|
||||||
$response = Invoke-WebRequest -Uri $url -Method POST -Headers $headers -Body $bodyBytes -UseBasicParsing -TimeoutSec 60
|
# Utiliser HttpWebRequest directement pour envoyer les bytes bruts sans troncature
|
||||||
|
$request = [System.Net.HttpWebRequest]::Create($url)
|
||||||
|
$request.Method = "POST"
|
||||||
|
$request.ContentType = "multipart/form-data; boundary=$boundary"
|
||||||
|
$request.ContentLength = $bodyBytes.Length
|
||||||
|
$request.Timeout = 60000
|
||||||
|
$request.UserAgent = $authHeaders["User-Agent"]
|
||||||
|
$request.Accept = $authHeaders["Accept"]
|
||||||
|
$request.Headers.Add("Authorization", $authHeaders["Authorization"])
|
||||||
|
|
||||||
Write-Log -Message "POST reponse : $($response.StatusCode)" -Level SUCCESS
|
try {
|
||||||
return $response
|
$reqStream = $request.GetRequestStream()
|
||||||
|
$reqStream.Write($bodyBytes, 0, $bodyBytes.Length)
|
||||||
|
$reqStream.Close()
|
||||||
|
|
||||||
|
$response = $request.GetResponse()
|
||||||
|
$statusCode = [int]$response.StatusCode
|
||||||
|
$response.Close()
|
||||||
|
|
||||||
|
Write-Log -Message "POST reponse : $statusCode" -Level SUCCESS
|
||||||
|
}
|
||||||
|
catch [System.Net.WebException] {
|
||||||
|
$responseBody = ""
|
||||||
|
if ($_.Exception.Response) {
|
||||||
|
$stream = $_.Exception.Response.GetResponseStream()
|
||||||
|
$reader = New-Object System.IO.StreamReader($stream)
|
||||||
|
$responseBody = $reader.ReadToEnd()
|
||||||
|
$reader.Close()
|
||||||
|
$stream.Close()
|
||||||
|
}
|
||||||
|
Write-Log -Message "POST erreur $($_.Exception.Message) - Reponse serveur: $responseBody" -Level ERROR
|
||||||
|
throw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Export-ModuleMember -Function Initialize-ApiClient, Get-AuthHeader, Invoke-EnoceanGet, Send-EnoceanConfig
|
Export-ModuleMember -Function Initialize-ApiClient, Get-AuthHeader, Invoke-EnoceanGet, Send-EnoceanConfig
|
||||||
|
|||||||
@@ -71,4 +71,30 @@ function New-EnoceanDeviceXml {
|
|||||||
return $xml
|
return $xml
|
||||||
}
|
}
|
||||||
|
|
||||||
Export-ModuleMember -Function Get-DeviceListFromJson, Parse-EnoceanDeviceXml, New-EnoceanDeviceXml
|
function Update-EnoceanDeviceId {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$XmlContent,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$NewDeviceId
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parser pour extraire les valeurs actuelles
|
||||||
|
[xml]$xml = $XmlContent
|
||||||
|
$config = $xml.EnOceanDevice.Configuration
|
||||||
|
$oldDeviceId = $config.DeviceId
|
||||||
|
$deviceType = $config.DeviceType
|
||||||
|
|
||||||
|
# Remplacement par regex sur le XML brut (preserve l'encodage et le formatage original)
|
||||||
|
$modifiedXml = $XmlContent -replace "<DeviceId>[^<]*</DeviceId>", "<DeviceId>$NewDeviceId</DeviceId>"
|
||||||
|
|
||||||
|
return @{
|
||||||
|
ModifiedXml = $modifiedXml
|
||||||
|
OldDeviceId = $oldDeviceId
|
||||||
|
DeviceType = $deviceType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Export-ModuleMember -Function Get-DeviceListFromJson, Parse-EnoceanDeviceXml, New-EnoceanDeviceXml, Update-EnoceanDeviceId
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ function New-EnoceanZip {
|
|||||||
$entry = $archive.CreateEntry($entryPath, [System.IO.Compression.CompressionLevel]::Optimal)
|
$entry = $archive.CreateEntry($entryPath, [System.IO.Compression.CompressionLevel]::Optimal)
|
||||||
|
|
||||||
$entryStream = $entry.Open()
|
$entryStream = $entry.Open()
|
||||||
$writer = New-Object System.IO.StreamWriter($entryStream, [System.Text.Encoding]::UTF8)
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
$writer = New-Object System.IO.StreamWriter($entryStream, $utf8NoBom)
|
||||||
$writer.Write($xmlFile.Content)
|
$writer.Write($xmlFile.Content)
|
||||||
$writer.Flush()
|
$writer.Flush()
|
||||||
$writer.Close()
|
$writer.Close()
|
||||||
|
|||||||
Reference in New Issue
Block a user