pySiEdge 0.1.1
Funktionen für Abruf, Verarbeitung und Konvertierung mittels 'Analyze MyWorkpiece /Capture4Analysis' geloggter Daten.
Requires Python: >=3.9

<!--
SPDX-FileCopyrightText: Copyright © 2024-2025 Damian Anders <damian.anders@tu-dresden.de>

SPDX-License-Identifier: GPL-3.0-or-later
-->

# Rechtliche Hinweise
## English
### License
pySiEdge is a Python package for processing data generated by Siemens "Analyze MyWorkpiece /Capture4Analysis".  
Copyright © 2024-2025 Damian Anders <damian.anders@tu-dresden.de>

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General
Public License as published by the Free Software Foundation, either version 3 of the License, or any later
version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.  
You should have received a copy of the GNU General Public License
along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).

### Brand names and trademarks
All brand names and trademarks mentioned in this project are the property of their respective owners.
This project and its author(s) are not affiliated, endorsed, or sponsored by any of the brands or companies mentioned.

### Third-party links
This project may contain links to third-party websites or services that are not owned or controlled by the project
owner. The project owner has no control over, and assumes no responsibility for, the content, privacy policies, or
practices of any third-party websites or services. You acknowledge and agree that the project owner shall not be
responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in
connection with the use of or reliance on any such content, goods, or services available on or through any such
websites or services.

## Deutsch
_Im Zweifelsfall ist der englische Text bindend._

### Lizenz
pySiEdge ist ein Python-Package zur Verarbeitung der von Siemens „Analyze MyWorkpiece /Capture4Analysis“ erzeugten
Daten.  
Copyright © 2024-2025 Damian Anders, damian.anders@tu-dresden.de

Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU General Public License,
wie von der Free Software Foundation veröffentlicht, weitergeben und/oder modifizieren, entweder gemäß Version 3
der Lizenz oder jeder späteren Version.
Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, dass es Ihnen von Nutzen sein wird, aber OHNE
IRGENDEINE GARANTIE, sogar ohne die implizite Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN
ZWECK. Details finden Sie in der GNU General Public License.  
Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem Programm erhalten haben.
Falls nicht, siehe [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).

### Markenrechte
Alle in diesem Projekt erwähnten Markennamen und Warenzeichen sind Eigentum ihrer jeweiligen Inhaber.
Dieses Projekt und sein(e) Autor(en) stehen in keiner Verbindung zu, werden nicht unterstützt oder gesponsert
von den genannten Marken oder Unternehmen.

### Links zu Drittanbietern
Dieses Projekt kann Links zu Webseiten oder Diensten von Drittanbietern enthalten, die nicht im Besitz oder unter der
Kontrolle des Autors/der Autoren stehen. Der Autor/die Autoren hat/haben keine Kontrolle über und übernimmt/übernehmen
keine Verantwortung für die Inhalte, Datenschutzrichtlinien oder Praktiken dieser Webseiten oder Dienste. Sie
erkennen an und stimmen zu, dass der Autor/die Autoren nicht direkt oder indirekt für Schäden oder Verluste
verantwortlich ist/sind, die durch die Nutzung oder das Vertrauen auf solche Inhalte, Waren oder Dienstleistungen,
die auf oder durch solche Webseiten oder Dienste verfügbar sind, verursacht wurden oder angeblich verursacht wurden.

# Informationen
## Funktion und Zweck
Das Package `pySiEdge` wurde geschrieben, um Daten abzurufen und zu verarbeiten, die mit der Software
Siemens „Analyze MyWorkpiece /Capture4Analysis“ auf einem Siemens _Industrial Edge Device_ erstellt wurden.
Detailliertere Informationen zu jener Software sind der Hersteller-Dokumentation
"SINUMERIK Analyze MyWorkpiece /Capture" (`A5E49004592A` in verschiedenen Versionen) zu entnehmen.

## Einschränkungen
### Anwendungsbereich
Das Package wurde für die _DMG DMU80_ am LWM der TUD geschrieben und bezieht sich folglich auf deren Softwarestand.
Stand 2025-03-10 steht „Analyze MyWorkpiece /Capture4Analysis“ in der Version 2.5.1.0.4 zur Verfügung.
Aktuell scheint [Version 3.2](https://support.industry.siemens.com/cs/us/en/view/109825208) zu sein, wobei in
[Version 3.0](https://support.industry.siemens.com/cs/us/en/view/109822005) erhebliche Änderungen vorgenommen
wurden.  
Es besteht folglich das Risiko, dass das Package `pySiEdge` für neuere Softwarestände nicht mehr funktioniert!

### Datenbank-Fehler
Dieser Fehler besteht nicht in diesem Package, sondern auf Seiten des _Industrial Edge Device_:  
Einige Jobs bzw. JobRuns lassen sich nicht mehr entfernen und erscheinen zwar nicht in der
Weboberfläche, aber bei der Abfrage der Jobs per REST API. In einem
[Artikel auf _SiePortal_](https://support.industry.siemens.com/cs/us/en/view/109801067) wird auf eine Funktion
in _MindSphere_ verwiesen, mit der die Datenbank zurückgesetzt werden kann. Diese steht aber ohne entsprechende,
aktive Lizenz nicht zur Verfügung.

### EOL
Laut diesem [Artikel auf _SiePortal_](https://support.industry.siemens.com/cs/us/en/view/109976904) wurde das
Produkt "Analyze MyWorkpiece /Capture4Analysis" 2024-11-05 abgekündigt und wird 2025-11-15 eingestellt.

# Verarbeitung aufgezeichneter Daten
## Datenabruf
### Datenstruktur
In der Weboberfläche von „Analyze MyWorkpiece /Capture4Analysis“ werden _Jobs_ erstellt, konfiguriert und
ggf. manuell gestartet. Jeder Job dabei verfügt über einen eindeutigen, wählbaren _JobNamen_ und erhält
vom System automatisch eine numerische _JobId_.

Jeder Start eines Jobs, egal ob manuell oder automatisch, beginnt einen _JobRun_. Dieser erhält zur Identifizierung
eine einmalige UUID, die sogenannte _Job**Run**Id_. Jeder Start eines Jobs beginnt einen neuen JobRun.  
Je nach Anzahl der aufzuzeichnenden Kanäle, ihrer Samplingrate und der Aufzeichnungsdauer kann ein JobRun die
in der Software einstellbare maximale Dateigröße überschreiten. Der Maximalwert von 100 MB für diesen Wert ist
gleichzeitig der Standardwert. Bei Erreichen dieser Schwelle wird eine neue Datei erstellt und die Aufzeichnung in
dieser fortgesetzt. Da es sich jedoch um denselben JobRun handelt, bleibt die JobRunId gleich.

Der Dateiname setzt sich aus JobNamen, JobRunId sowie Datum und Uhrzeit der Dateierstellung zusammen und sieht
beispielsweise so aus:  
`Beispielbezeichnung_4d22bd59-3c34-4d30-8434-497d2dc8e6bc_20250310-104237.zip`

### Vorgehensweise
#### Vorbereitungen
##### Zertifikate
Die Authentifizierung von `pySiEdge` gegenüber der REST API von „Analyze MyWorkpiece /Capture4Analysis“ geschieht auf
Basis eines Nutzerzertifikats. Hierfür sind einige Randbedingungen zu beachten:
1. Das Zertifikat muss bereits erstellt worden sein wie in der Doku `A5E49004592A` beschrieben. Insbesondere muss es
	sich um das	Zertifikat eines Nutzers handeln, der auf dem _Industrial Edge Device_ Mitglied der Gruppe
	`amwcapture` ist.  
	In dieser Dokumentation wird davon ausgegangen, dass, wie auch in der Herstellerdokumentation, der Benutzername
	ebenfalls `amwcapture` ist.
2. Das Zertifikat für den Benutzer liegt lokal unverschlüsselt vor.
	Das ist nach einem Export aus dem _Industrial Edge Device_ nicht der Fall, es muss mithilfe von `openssl` folgender Befehl
	beispielsweise in der Powershell ausgeführt werden:

	`openssl pkcs12 -in {NAME}.p12 -legacy -clcerts -nodes -out {NAME}.cert`

	Für `{NAME}` ist der Dateiname des Zertifikats (ohne Endung) einzusetzen.
3. Das Serverzertifikat kann analog zum Zertifikat des Benutzers `amwcapture` heruntergeladen werden, wenn der "Ablageort" von
	`TrustedClients` auf `ServerCerts` geändert wird. Anschließend ist ebenfalls der obige Befehl zum Entschlüsseln
	auszuführen.

**Hinweis:** Aktuell kann das Serverzertifikat am LWM _nicht_ verifiziert werden, da es für eine falsche IP-Adresse ausgestellt ist.

##### Adresse _Industrial Edge Device_
Zur Verwendung von `pySiEdge` muss die IPv4-Adresse des _Industrial Edge Device_ bekannt sein.

##### Ordnerpfad
Optional kann der Ordnerpfad, in dem die übertragenen Dateien abgelegt werden sollen, übergeben werden. Dieser kann
absolut oder relativ sein. Wenn kein Pfad übergeben wird, werden die Dateien im aktuellen Ordner abgelegt.

##### Prüfsummenverifikation
Die Prüfsummenverifikation ist standardmäßig aktiviert. `pySiEdge` erkennt den korrekten Algorithmus automatisch aus den
Metadaten und prüft die Dateien auf Übertragungsfehler.  
Sollte die Erkennnung fehlschlagen kann der korrekte Algorithmus erzwungen werden, gültige Werte für diesen Parameter siehe
[https://docs.python.org/3/library/hashlib.html#hash-algorithms](https://docs.python.org/3/library/hashlib.html#hash-algorithms).  
In besonderen Fällen kann die Verifikation auch ganz deaktiviert werden.

##### Löschen der übertragenen Dateien
Optional können Dateien nach erfolgreicher Übertragung vom _Industrial Edge Device_ gelöscht werden, um Speicherplatz
freizugeben.  
Standardmäßig ist dies deaktiviert, im Hinblick auf einen möglichen Datenverlust ist diese Option mit Bedacht einzusetzen.

##### Jobauswahl
Die Funktion `selectJobs` ermöglicht eine Vorauswahl der Jobs, deren Dateien übertragen werden sollen. Dafür müssen ihr die
entsprechenden Jobnamen übergeben werden:
* Ein einzelner Job als `str`.
* Mehrere Jobs als `list[str]`.
* Ein leerer `str`, eine leere `list` oder `None` führen zur Auswahl aller vorhandenen Jobs. In diesem Fall kann die Ausgabe von
	`getAvailableJobs` allerdings auch direkt an `getJobFiles` übergeben werden.

**Hinweis:** Die in [Datenbank-Fehler](#Datenbank-Fehler) erwähnten, nicht mehr existierenden Jobs werden zwar von
`getAvailableJobs` mit ausgegeben, müssen aber nicht explizit herausgefiltert werden. Durch die Arbeitsweise von von `getJobFiles`
werden sie automatisch ignoriert.

### Beispielimplementierung
#### Module importieren

```python
from pySiEdge.jobHandling import *
```

#### Parameter festlegen
Die Festlegung der in [Vorbereitungen](#Vorbereitungen) beschriebenen Parameter kann wie folgt aussehen:

```python
# IP-Address
edgeAddress = "169.254.13.37"

# Path to user certificate
certPathUser = "./certs/amwcapture.cert"

# Server certificate verification
certPathServer = "./certs/ServerCA.cert"
# certPathServer = False	# to disable verification

#######################
# optional parameters #
#######################

# Path for downloaded files
downloadedPath = "path/to/folder"

# Verify checksum
checkFile = True
# if not empty: force certain checksum algorithm,
#	see https://docs.python.org/3/library/hashlib.html#hash-algorithms
checkFileAlgo = ""

# Delete files on Industrial Edge Device after retrieval;
#	jobs remain unchanged
deleteFile = False

# if only data from certain jobs is to be processed:
# - String with the name of a single job, or
# - List with strings of all jobs to be processed
# otherwise empty string or `None` for processing all jobs
myJobNames = ""
```

#### Jobs filtern
Die Funktion `getAvailableJobs` ruft die verfügbaren Jobs ab.  
Bei interaktiven Sessions kann der Parameter `verbose=True` genutzt werden, um die verfügbaren Jobs
in menschenlesbarer Form in der Konsole auszugeben. Diese können so in den `str` bzw. die `list` der
zu übertragenen Jobs kopiert werden. Alternativ können die Jobnamen auch der Weboberfläche von
„Analyze MyWorkpiece /Capture4Analysis“ entnommen werden.

Die Funktion `selectJobs` wird zur Auswahl der zu übertragenden Jobs aus den existierenden genutzt.

```python
# Job selection; choose the line that fits your case:
# -single job
myJobNames = "ExampleName_singleJob"
# -multiple jobs
myJobNames = ["ExampleName_1of2", "ExampleName_2of2"]
# -all jobs
myJobNames = ""

# Get all available jobs
allJobs = getAvailableJobs(edgeAddress, certPath=certPathUser, certVerify=certPathServer)

# Filter selected jobs
selectedJobs = selectJobs(allJobs, myJobNames)
```

#### Jobs abrufen
Die JobRuns der ausgewählten Jobs können nun mit der Funktion `getJobFiles` übertragen werden.
Dies kann eine gewisse Zeit in Anspruch nehmen.

```python
for job in selectedJobs:
	getJobFiles(edgeAddress,
				jobMeta=job,
				certPath=certPathUser,
				certVerify=certPathServer,
				filePath=downloadedPath,
				fileCheck=checkFile,
				fileCheckAlgo=checkFileAlgo,
				fileDelete=deleteFile)
```

## Import und Konvertierung
### Beschreibung
Die abgerufenen Daten müssen, um sie weiterverarbeiten zu können, in ein entsprechendes Format gebracht werden.
Hierfür wurde das HDF5-Format gewählt, da es für große Datensätze ausgelegt ist und neben entsprechenden
Strukturierungsmöglichkeiten auch Kompression bietet.

Folgende Vorgehensweise wird empfohlen und im Weiteren beispielhaft implementiert:
1. Falls nötig: Entpacken der abgerufenen ZIP-Dateien.
1. Erstellen der Liste `jsonFilePaths`, welche die Pfade zu allen Dateien enthält.
1. Parsen der "Footer" aller Dateien, um robuste Informationen über deren Reihenfolge sowie Zugehörigkeit zu einzelnen
	Runs zu erhalten.
1. Zuordnen der Dateien zu ihren Runs und Sortierung innerhalb der Runs.
1. Import und Konvertierung der Dateien sowie Speichern in jeweils einer Datei pro Run.

### Beispielimplementierungen
#### einfache Implementierung
Die vorhandenen `.zip`-Dateien werden entpackt, analysiert und in ihre Runs sortiert nacheinander importiert.

```python
# Import package.
import pySiEdge as pSE

# set path to folder
folderPath = "path/to/folder"

# extract ZIP-Files and return list of paths to JSON files
# NOTE:	This function always extracts the ZIP files, even if JSON files are already present.
allPathsJSON = pSE.prepareC4AFiles(folderPath)

# parse all footers
allFPC = pSE.parseFilePathChain(allPathsJSON)

# group into runs and sort
order = pSE.sortByRun(allFPC)

# import all files and save to HDF5, grouped by run
for run in order:
	pSE.jsonToHdf5(allPathsJSON, run, allFPC)
```

#### optimierte Implementierung
Es wird geprüft ob, bereits `.json`-Dateien im Order vorhanden sind.
Falls ja, wird eine Liste dieser Dateien übergeben, statt ggf. ebenfalls vorhandene `.zip`-Dateien (erneut) zu entpacken.  
Import und Verarbeitung erfolgen parallel, wobei die Anzahl der Threads gleich der Anzahl von Runs oder der
Anzahl physischer CPU-Kerne ist, je nachdem, welche Zahl kleiner ist. Die Parallelisierung kann insbesondere auf AMD-Prozessoren zu kritischen Abstürzen, sog. _hard freezes_ führen.
Zur Umgehung dieses Problems wird die Anzahl der von `OpenBLAS` zu startenden Threads auf 1 begrenzt.

```python
# Umgehung von hard freezes durch OpenBLAS; muss immer vor Numpy importiert werden
import os
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["OPENBLAS_NUM_THREADS"] = "1"

# Import package.
import pySiEdge as pSE

# Enable parallel processing of multiple runs.
from multiprocessing import Pool

# Determine the number of available processor cores.
from psutil import cpu_count

numThreads = int(cpu_count(logical=False))

# Set path to folder.
folderPath = "path/to/folder"

# Check for existing JSON files. If there are none, extract ZIP Files.
# Return list of paths to JSON files.
# NOTE: If any JSON files are already present, all ZIP files will be ignored.
allPathsJSON = pSE.compilePaths(folderPath, 'json')
if not allPathsJSON:
	allPathsJSON = pSE.prepareC4AFiles(folderPath)

# Parse all footers.
allFPC = pSE.parseFilePathChain(allPathsJSON)

# Group files into runs and sort them.
order = pSE.sortByRun(allFPC)

# Compile list of function arguments.
parmStar = list()
for run in order:
	parmStar.append((allPathsJSON, run, allFPC))

# Import all files and save to HDF5, grouped by run.
with Pool(numThreads) as pool:
	pool.starmap(pSE.jsonToHdf5, parmStar, chunksize=1)
```

## Defekte Header
### Problembeschreibung
Der Header wird gelegentlich unvollständig geschrieben. Konkret sind den aufgezeichneten Messkanälen keine Achsen
zugeordnet, das Problem tritt dann in allen Dateien des Runs auf. In diesen Fällen muss der fehlerhafte Header jeder
JSON-Datei des Runs mit dem Header einer intakten Datei überschrieben werden. Dies kann grundsätzlich manuell
durchgeführt werden, ist aber mühsam und bei größeren Dateigrößen nicht mehr mit jedem Editor durchführbar.  
Zur Unterstützung bei der Korrektur fehlerhafter Header stehen dem Nutzer deshalb mehrere Funktionen zur Verfügung.

Die Funktionen werden nicht standardmäßig importiert und müssen daher bei Bedarf aus
`pySiEdge.headerFixes` geladen werden.

### Korrekturfunktionen
#### Header aus intakter Datei
##### Verwendung
Die Funktion `fixHeaderByCopying(jsonFilePaths, runSequence)` versucht, einen korrekten Header aus einer Datei eines
anderen Runs desselben Jobs zu extrahieren und den defekten Header damit zu reparieren. Dafür muss der
Eingangsparameter `jsonFilePaths` (Ausgabewert der Funktion `compilePaths`) die Dateipfade der Dateien mehrerer Runs
desselben Jobs enthalten. Voraussetzung ist, dass einer dieser Runs mit korrektem Header gespeichert wurde.  
Dazu kann der Job ggf. manuell gestartet und sofort wieder gestoppt werden. Im schlimmsten Fall muss dies so lange
wiederholt werden, bis eine Datei mit gültigem Header erzeugt wurde.  
Die Funktion gibt neben der korrigierten Kanalzuordnung `headerAxis` auch den Index `idxFix` zurück.
Dieser verweist auf den Pfad in `allJsonPaths`, welcher zur ersten Datei des Runs mit defektem Header führt.

##### Alternative Methode
Alternativ kann einmalig eine Referenz-Datei erstellt werden, die für alle zukünftigen Korrekturen von Messdaten
_nur der betreffenden Maschine_ verwendet werden kann, solange deren Kanalzuordnung nicht geändert wird.  
Dazu ist in "Analyze MyWorkpiece /Capture4Analysis" ein Job anzulegen, der alle "HF Signals" aufzeichnet.
Dieser Job muss kurz laufen, um eine Datei zu erzeugen. Wenn in dieser Datei der Header korrekt ist, kann sie bei
Problemen in den Ordner der Dateien mit defektem Header kopiert werden. Dort wird sie von `compilePaths` erfasst
und kann so von `fixHeaderByCopying` verwendet werden.

##### weitere Hinweise
Die Funktion sucht den ersten Run mit defektem Header und bearbeitet _nur diesen_ Run. Wenn sich in `jsonFilePaths` weitere
Runs mit defektem Header befinden muss zunächst der korrigierte Header mit `writeHeaderFix` in die Datei geschrieben
werden, anschließend ist der Vorgang ab dem Aufruf von `compilePaths` für jeden weiteren Run mit defektem Header zu
wiederholen.

#### Header aus Encoder-Mapping
##### Verwendung
Der Fehler im Header lässt die Achsbezeichnungen leer, die Hardware-Adresse des Kanals wird allerdings korrekt
gespeichert. Bei bekannter korrekter Zuordnung kann so die Achsbezeichnung in Abhängigkeit der Hardware-Adresse
erstellt werden. Dazu muss `fixHeaderByMapping` mit dem Pfad einer Datei des Runs mit defektem Header
(`jsonFilePath`) und der Zuordnung aufgerufen werden.  
Die Zuordnung `map` ist ein Dictionary mit den Kanalnummern als _keys_ und den Achsbezeichnungen als _values_. Dabei
haben die _keys_ den Datentyp `int` und die _values_ den Datentyp `str`.  
Die Zuordnung lässt sich in einem intakten Header ablesen; die Achsbezeichnung eines Kanals entspricht dem Wert von
`Axis`, die Kanalnummer entspricht der Zahl am Ende des Werts von `Address`.

Für die bei der Entwicklung dieses Packages betrachtete DMU80 beispielsweise ergibt sich so folgende Zuordnung:  
`map={1: "X1", 2: "Y1", 3: "Z1", 4: "C1", 5: "B1", 6: "SP1", 0: ""}`  
Der Kanal `0` fängt hier einen Fehler eines inhaltlich ohnehin fehlerhaften Kanals ab.

#### Schreiben des korrigierten Headers
##### Verwendung
Der durch `fixHeaderByCopying` oder `fixHeaderByMapping` korrigierte Header muss in alle Dateien des Runs geschrieben
werden. Dies übernimmt die Funktion `writeHeaderFix`. Sie wird entweder mit dem Pfad einer einzelnen Datei mit defektem
Header oder mit einer Liste mit den Pfaden aller Dateien eines Runs mit defektem Header,
sowie mit dem korrigierten Header (Rückgabewert einer der Korrekturfunktionen) aufgerufen. Im Parameter `idxFilePath`
können Indizes auf `jsonFilePath` übergeben werden. Die Funktion bearbeitet dann nur die ausgewählten Pfade. 

##### weitere Hinweise
Die Funktion nimmt keine Änderungen an den Originaldateien vor, diese erhalten lediglich die geänderte Dateiendung `.ORIG`.
Die nach Aufruf dieser Funktion vorliegenden JSON-Dateien sind Kopien der Originaldateien mit korrigiertem Header.
Der Schreib- und Kopiervorgang erfolgt ohne Öffnen der Datei als Ganzes und ist daher speichereffizient.  
Möglicherweise kann durch Ändern des Parameters `chunkSize` die Ausführung der Funktion beschleunigt werden, dies ist
jedoch abhängig vom ausführenden System.

### Beispielaufrufe
#### Korrektur mittels intakter Datei
Voraussetzung ist hier, dass sich im Pfad `folderPath` Dateien sowohl eines Runs mit defektem Header als auch
mindestens eines Runs mit intaktem Header befinden.

```python
# import functions
import pySiEdge as pSE
from pySiEdge import headerFixes as hF

# folder path
folderPath = "path/to/folder"

# compile paths to JSON files
allJsonPaths = pSE.compilePaths(folderPath, "json")

# sort files into runs
order = pSE.sortByRun(pSE.parseFilePathChain(allJsonPaths))

# fix header
headerAxis, idxFix = hF.fixHeaderByCopying(allJsonPaths, order)

# write file(s) with new header
hF.writeHeaderFix(allJsonPaths, headerAxis, idxFilePath=idxFix)
```

### Korrektur mittels Mapping
```python
# import functions
import pySiEdge as pSE
from pySiEdge import headerFixes as hF

# folder path
# NOTE: Make sure the folder contains only files from a single faulty run!
folderPath = "path/to/folder"

# define mapping (depends on machine)
map = {1: "X1", 2: "Y1", 3: "Z1", 4: "C1", 5: "B1", 6: "SP1", 0: ""}

# compile paths to JSON files
allJsonPaths = pSE.compilePaths(folderPath, "json")

# fix header
headerAxis = hF.fixHeaderByMapping(allJsonPaths[0], map)

# write all files of faulty run with new header
hF.writeHeaderFix(allJsonPaths, headerAxis)
```

# Datenstruktur JSON "Analyze MyWorkpiece /Capture4Analysis"
Dieser Abschnitt beschreibt die Datenstruktur, die nach dem Parsen einer mit "Analyze MyWorkpiece /Capture4Analysis"
erstellten Datei mit folgendem Befehl in Python vorliegt:  
`edgeJson = json.load("Pfad/zur/Datei.json")`  
`edgeJson` ist dann ein Dictionary, welches die Keys `Header`, `Payload` und `Footer` enthält.

Die offizielle Dokumentation ist abrufbar in der UI von "Analyze MyWorkpiece /Capture4Analysis"
über `Help`-> `Output format`.

## Struktur `Header`
* `edgeJson['Header']` ist ein Dictionary mit folgenden Keys:
	* `Version`
	* `Format` - Immer `"DEFAULT"`
	* `Description`
	* `MachineInfo`
	* `JobDescription` - Informationen zu Start- und End-Triggern
	* `SignalListHFData`
	* `SignalListLFData`
	* `TimeStamp` - Startpunkt der Aufzeichnung in UTC
	* `CycleTimeMs` - Zykluszeit der Aufzeichnung in ms
	* `Initial`
* `edgeJson['Header']['SignalListHFData']` ist eine Liste von identisch aufgebauten Dictionaries.
* `edgeJson['Header']['SignalListHFData'][c]` ist ein Dictionary, welches die Metadaten des Messkanals `c` beinhaltet
	und folgende Keys enthält:
	* `Name` - Art des Messwerts
	* `Type` - Datentyp des Messwerts
	* `Axis` - Achse, deren Messwert `Name` der Kanal enthält
	* `Address` - Adresse des Messkanals in der Steuerung
* analog ist `edgeJson['Header']['SignalListLFData']` ebenfalls eine Liste von identisch aufgebauten Dictionaries.
  * `edgeJson['Header']['SignalListLFData'][d]` ist ein Dictionary, welches die Metadaten des Messkanals `d` beinhaltet
	und folgende Keys enthält:
	  * `id` - laufende Nummer des LF-Kanals, null-basiert
	  * `device` - _fehlerhafte Implementierung:_ lt. Doku Pfad des Messkanals ohne Signalname,
		  ist jedoch immer leer
	  * `path` - Pfad des Messkanals inkl. Signalname (siehe z.B. "SINUMERIK Listenhandbuch", Best.-Nr. 6FC5297-7AB71-0AP0)
	  * `samplingPeriod` - Zeitschrittweite der Abtastung des Kanals

## Struktur `Payload`
### Allgemeines
* `edgeJson['Payload']` ist eine Liste von Dictionaries.
* Jedes dieser Dictionaries `edgeJson['Payload'][i]` kann, muss aber nicht, _einen_ der Keys `HFData` oder `LFData` enthalten.
* für bestimmte NC-Ereignisse werden weitere Dictionaries verwendet, auf die hier nicht eingegangen wird.

### Key `HFData`
* `edgeJson['Payload'][i]['HFData']` ist eine einfach verschachtelte Liste, bei der
	* die obere Ebene `z` den Zyklustick iteriert und
	* die untere Ebene `c` den Messkanal angibt.
* Folglich gibt `edgeJson['Payload'][i]['HFData'][z][c]` einen einzelnen Messwert _eines_ Kanals aus.

Dabei ist zu beachten:
* Die Range von `c` bleibt konstant.
* Die Range von `z` kann für jedes `i` unterschiedlich sein.
* Der erste Messkanal (`c = 0`) enthält die laufende Nummer des Zyklusticks der Messung

### Key `LFData`
* `edgeJson['Payload'][i]['LFData']` ist eine Liste mit mindestens einem Element
* jedes Element `n` in `edgeJson['Payload'][i]['LFData'][n]` ist ein Dictionary mit folgenden Keys:
	* `HFProbeCounter` - Zyklustick, in dem der Wert des Kanals erfasst wurde
	* `timestamp` - Zeitpunkt (in Lokalzeit, abweichend von Dokumentation, s.u.), an dem der Wert des Kanals erfasst wurde
	* `address` - Pfad des Messkanals inkl. Signalname, identisch mit Angabe im Header
	* `value_type` - Datentyp des Messwerts
	* `value` - Messwert

Dabei ist zu beachten:
* Die Implementierung der Zeitstempel ist fehlerhaft. Siemens gibt in der Dokumentation an, 
dass alle Zeitstempel in Zeitzone UTC erstellt würden. Dies gilt jedoch offenbar nur für den Zeitstempel
in `edgeJson['Header']['TimeStamp']`, die Zeitstempel in `edgeJson['Header']['Initial']['Time']`
sowie alle Zeitstempel in `edgeJson['Payload'][i]['LFData']` sind in Lokalzeit.

## Struktur `Footer`
`edgeJson['Footer']` ist ein Dictionary mit den zwei Keys `ErrorMessages` und `FilePathChain`.

### Key `ErrorMessages`
`edgeJson['Footer']['ErrorMessages']` ist eine Liste, die im Normalfall leer ist. Laut Doku werden hier Fehlermeldungen
der internen Datenverarbeitung ausgegeben.

### Key `FilePathChain`
`edgeJson['Footer']['FilePathChain']['Index']` ist ein Dictionary mit folgenden Keys:
* `Previous` - (originaler) Dateiname der vorhergehenden Datei desselben Runs
* `Actual` - (originaler) Dateiname der aktuellen Datei
* `Next` - (originaler) Dateiname der folgenden Datei desselben Runs
* `Index` - laufende Nummer der Datei über alle Dateien des Jobs, Eins-basiert
* `StreamIndex` - _nicht dokumentiert_; scheint immer `null` zu sein