Projekt

Smart Particulate Matter Sensor SPS30

 

            

 

 

 

 

GLAB Logo

 

Version 1.0


 

Dokument Version:

 

Version:                     1.0

Datum:                       2019.03.17

Autor:                         GINTHER, Andreas

Sprache:                    Deutsch

 

 

GLAB:

 

GLAB Logo GLAB steht für Ginther Laboratories.

Es handelt sich hier weder um eine Firma noch ein gemeldetes Gewerbe.

Es werden damit lediglich Konstruktionen, Architekturen und Erfindungen bezeichnet welche im privaten eigennützigen Gebrauch entstehen.

 

Nur mit Zustimmung von Ginther Andreas dürfen mit GLAB gekennzeichnete Software, Hardware und Konzepte verwendet werden.

Es wir keinerlei Haftung oder Gewährleistung für dessen übernommen.

 

 

In Kooperation mit:

 

 

 

 

Image result for paessler

 

Image result for betalayout logo

 

https://www.logolynx.com/images/logolynx/2b/2bb66dba6c07ffeacd588865cc106d7e.png

 


Zusammenfassung:

 

Mit dem GLAB Particulate Matter Sensor SPS30 Gerät kann im Außenbereich stationär Feinstaub PM2.5 zuverlässig gemessen und Langzeitgrafen mit den gewonnenen Daten aufgezeichnet werden.

 

In Kombination mit anderen Sensoren wie Wind, Regen, Heizsystem lassen sich so Rückschlüsse auf dynamische Feinstaubverhältnisse ziehen.

 

Beispielsweise wird festgestellt wie viel Feinstaub beim Hausbrand verursacht wird und wie lange die schlechten Luftverhältnisse anhalten bis Wind die Luft wieder reinigt.

 

Die Feinstaubwert Spitzen entstehen während Hausbrand aktiv ist.

cid:image010.png@01D4D00B.26A285F0

 


 

Es kann auch einfach festgestellt werden daß auch im Stadtgebiet wo erhöhte Feinstaubwerte üblich sind diese bei länger andauernden Stürmen wie sie im Frühjahr üblich sind nahezu Richtung 0 gehen.

 

cid:image001.png@01D4D2C3.94855A10

 


Inhaltsverzeichnis

 

 

 

1        Projekt 5

1.1         SPS30 UART Feinstaub Sensor 5

1.2         Si7021 UART Temperatur Sensor 8

1.3         UART – LAN Modul 9

1.4         PoE – LAN Modul 10

1.5         GLAB PMS – LAN / UART Interface Schaltung. 11

1.6         GLAB PMS – LAN / UART Interface Platine. 12

1.7         GLAB Feinstaub Sensor Halterung und externe Anschlüsse. 15

1.8         Insekten Abwehr 19

1.9         Gehäuse. 20

1.10      Frontplatte. 22

1.11      UART – LAN Modul Konfiguration. 25

1.12      SHDLC Protokoll 26

1.13      MISO Frame. 27

1.14      Power Shell Programm.. 28

1.15      PRTG Sensoren.. 43

1.16      PRTG Grafen.. 44

1.17      PRTG Map. 45

1.18      HomeMatic und Pocket Control Integration.. 47

1.19      Anbringung im Außenbereich. 49

 


 

1     Projekt

 

Projekt Anforderungen und Beschreibung:

 

·         Messen von Feinstaub PM2.5 und weitern Größen im Außenbereich

(Sensor mit Langzeitstabilität hoher Messhäufigkeit und Lebensdauer)

·         Messen der Temperatur des Gehäuseinneren des Feinstaubsensors

(Überprüfung der Betriebstemperatur)

·         Übertragen der Daten von der seriellen UART Schnittstelle über Ethernet LAN TCP

·         Parsen vom MISO / MOSI Frame over TCP und Prüfsumme mit Windows Power Shell

·         Stromversorgung des Modules mit PoE, keine extra Stromversorgung für die Sensorik

·         Langzeitaufzeichnung und Darstellung mit PRTG

·         Spritzwasserdichtes, robustes Metall Gehäuse mit Befestigungsflansch

·         Insekten Abwehr der Feinstaubsensor Luftkanäle

 

1.1    SPS30 UART Feinstaub Sensor

 

Der SPS30 Feinstaubsensor (PM-Sensor) von Sensirion www.sensirion.com ist der nächste technologische Durchbruch bei optischen PM-Sensoren für Innenräume sowie im Freien. Um höchste Präzision über lange Zeit zu erreichen, wird Laserlichtstreuung in Verbindung mit der neusten Technologie gegen Partikelverschmutzung kombiniert. Diese Technologie, zusammen mit hochwertigen und langlebigen Komponenten, ermöglicht genaue Messungen – vom ersten Einsatz bis zu einer Lebensdauer von mehr als acht Jahren. Darüber hinaus bieten die neuesten Algorithmen von Sensirion überlegene Genauigkeit für verschiedene PM-Typen und hochauflösendes Partikelgrößen-Binning. Dadurch eröffnen sich neue Möglichkeiten für die Detektion verschiedener Arten von Umweltstäuben und anderen Partikeln.

 

Die natürliche Ablagerung von Feinstaub in herkömmlichen PM-Sensoren führt zu einer steten Verschlechterung der Signalqualität. Besonders kritisch sind dabei die Ablagerungen auf allen optischen Komponenten wie der Lichtquelle (Laser oder LED) und dem Photodetektor. Dies hat zur Folge, dass Geräte, die diese PM-Sensoren integrieren, nach einiger Zeit nicht mehr richtig funktionieren, was zu Fehlfunktionen, Benutzerbeschwerden, Wartung, Service und/oder Ersatzteilen führt. Die patentierte Verschmutzungsresistenz-Technologie von Sensirion sowie langlebige Komponenten verleihen dem SPS30 unübertroffene Robustheit, einzigartige Langzeitstabilität und hohe Genauigkeit mit einer Lebensdauer von mehr als acht Jahren im Dauerbetrieb bei einer Nutzung von 24 Stunden/Tag.

 

 

Quelle Sensirion  02.11.2018 | Pressemitteilung


 

Die Spezifikation des SPS30 überzeugt mit seiner langen Lebensdauer sowie der Messhäufigkeit und Genauigkeit. Durch die UART Schnittstelle ist es möglich mit relativ einfachen Hardware und Software Mitteln die Daten abzugreifen und weiter zu verarbeiten.

 

 

SPS30 Leistung im Vergleich.

 

 


 

Die UART Schnittstelle des SPS30 kann mit den TTL Pegeln direkt an das UART – LAN Modul angeschlossen werden. Dennoch ist eine Platine mit Stromversorgung, Signalisierung, Konnektivität und Temperatur Sensor Erweiterung notwendig.

 

 

 

Die Daten werden dann über das LAN mit einem Power Shell Programm auf einem Windows Server über TCP abgegriffen und an PRTG zur Langzeitdatenaufzeichnung und Visualisierung übergeben.

 

$TCPConnection.GetStream()

$TCPStream.Read($TCPBuffer, 0, 256)

 

 

Im Datenblatt sind genauere Erläuterungen zum SPS30 ersichtlich.

https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Particulate_Matter/Sensirion_PM_Sensors_SPS30_Datasheet.pdf

 


 

1.2    Si7021 UART Temperatur Sensor

 

Zur Erfassung der Temperatur im Gehäuseinneren stellt der Si7021 eine UART Schnittstelle bereit.

Dies dient zum überwachen der Betriebstemperatur des SPS30 im Außenbereich.

Das Sensor Modul verwendet einen Si7021 Sensor welcher ein I2C Interface bereitstellt.

Damit aber weiter mit einem UART Interface gearbeitet werden kann ist eine STM8S003F3P6 MCU auf der Hauptplatine mit entsprechender Firmware integriert.

 

Sensor                MCU Image result for STM8S003F3P6 MCU

 

 

• Sensor: Si7021 (GY-21)

• Betrieb und Logik Spannung: 1.9 bis 3.3V

• Genauigkeit Luftfeuchtigkeit: 3% (max.) bei 0-80% RH

• Messbereich Luftfeuchtigkeit: 0 bis 80% RH

• Genauigkeit Temperatur:  ±0.4 °C (max.), –10 bis 85°C

• Messbereich Temperatur: -40 bis 125°C

• Schnittstelle: I2C umgewandelt mit STM8S003F3P6 MCU in UART

 

Die UART Schnittstelle liefert im 5 Sec. Intervall als ASCII folgendes Datenpaket als Zeile:

id:255 tem:28.7 hum:31.1

 

Die Daten werden dann über das LAN mit einem Power Shell Programm auf einem Windows Server über TCP abgegriffen und an PRTG zur Langzeitdatenaufzeichnung und Visualisierung übergeben.

 

$TCPConnection.GetStream()

$TCPStream.Read($TCPBuffer, 0, 64)

 


 

1.3    UART – LAN Modul

 

Es wird ein Modul eingesetzt (USR-TCP232-ED2) von www.usriot.com welches 2 UART Ports über eine Ethernet LAN Schnittstelle übertragen kann. Über eine Web Oberfläche können die Ports konfiguriert werden.

Danach können über TCP oder UDP die Daten der UART Ports empfangen werden.

Auf dem Modul arbeitet ein Cortex-M4 Prozessor mit 512Kb Speicher.

Damit ist eine Verarbeitung von 115200 Baud Rate problemlos möglich.

 

 


 

1.4    PoE – LAN Modul

 

Das gesamte Modul mit all seinen Komponenten soll per PoE mit Strom versorgt werden.

Hierzu wird ein PoE Modul eingesetzt. Dadurch ist die Anbringung im Außenbereich auch unter Spritzwasserbedingungen unkompliziert möglich.

 

Das PoE Modul kann per Daten und Storm Modus A oder per Separate Strom Modus B versorgt werden.

Es wird der Modus A gewählt, weil die meisten PoE Switche über die Daten Leitungen die 48V Versorgungsspannung lagern.

 

Das ganze Gerät mit SPS30 und Si7021 verbraucht im Mess- Betrieb ~1,0W.

 


 

1.5    GLAB PMS – LAN / UART Interface Schaltung

3,3V, 5V, PoE, MCU, UART Ports, Konnektivität, Signalisierung und Bedienelemente werden verdrahtet.


 

1.6    GLAB PMS – LAN / UART Interface Platine

 

Es wird eine Platine entworfen mit allen notwendigen Komponenten:

·         UART – LAN Modul als Tochterplatine

·         PoE – LAN Modul

·         PoE – LAN Transformer

·         LED Treiber

·         5 / 3,3 V Versorgungsspannung

·         Si7021 Temperatur Sensor als Tochterplatine

 

 

 


 

Auf der Platine ist die Ethernet Buchse ist nach hinten versetzt damit im engen Gehäuse der RJ45 Stecker Platz findet.

 

 

 


 

Das PoE Modul wird zwecks Wärmeableitung verlötet. Das UART – LAN Modul wird aufgesteckt.

 

Der Si7021 Temperatursensor ist verlötet damit er sich nicht lösen kann.

 


 

1.7    GLAB Feinstaub Sensor Halterung und externe Anschlüsse

 

Die Sensor Halterung für den SPS30 wird per 3D Druck hergestellt. Damit ist es möglich die Luftkanäle vom SPS30 mit Schlauch Leitungen an eine Gehäuse Außenseite zu führen.

Die Herausforderung ist dabei eine glatte Oberfläche im inneren herzustellen und den Frischluftkanal für den Sensor Eingang auch mit einzuschließen.

 

 

Mit einer 1x1mm Schaumdichtung an welche dann der Sensor anliegt beim einschieben sorgt für Luftdichtheit.

 

 


 

Ein Innenlochdurchmesser von 8mm ist genügend um den Luftstrom zu gewährleisten.

Auf die Stutzen wird dann ein Silikonschlauch gesteckt und zur Schlauchverschraubung nach außen geführt.

 

 

Der SPS30 wird in die Passung eingeschoben drückt auf die Schaumdichtung und wird mit einer Schraube an der Hinterseite verriegelt.

 

 


 

Der SPS30 ist ein wartungsfreier Sensor und in einem 24h Betreib für 8 Jahre ausgelegt.

Durch die clean Air Technologie wird ein sauberer Luftschlauch um den Feinstaub Luftstrom gelegt welcher die empfindlichen Sensor Teile sauber hält.

Dazu ist ein eigener Luftkanal vorhanden welcher mit einem Filter die Luft säubert.

 

 

 


 

Die GLAB Feinstaub Sensor Halterung ist so konstruiert, dass der Luftkanal für die saubere Luft mit von dem Luft Eingangsstutzen versorgt wird.

 

 

 

Der 3D Druck wurde mit einem Renkforce RF2000 3D Drucker mit maximaler Qualität hergestellt.

 

 


 

1.8    Insekten Abwehr

 

Für die Lufteinlässe wird eine Schlauchverschraubung verwendet mit M16 Gewinde und Anschluss für einen 9mm Schlauch.

In die Schlauchverschraubung wird ein Edelstahl Sieb eingelegt mit 15mm Durchmesser.

Das Sieb hat einen Lochabstand von 0,83 mm.

An die Anschlüsse werden dann für den Eingang ein kürzerer Schlauchstummel von 4,5cm und für den Ausgang ein längerer von 8,5cm aufgesteckt. Damit wird der Abstand von Ein- und Ausgang vergrößert und ein Luftkreislauf und damit eine Messdatenverfälschung vermindert.

 

20190223_082828384_iOS

 

Die Luft wird mit so wenig wie möglich Wiederständen an den Sensor geführt.

 

20190223_131241751_iOS

 


 

1.9    Gehäuse

 

Oben ist der Ethernet RJ45 Anschluss mit einem wasserdichten Harting Cat.6 Industriestecker.

      

 

Der SPS30 ist etwas schräg montiert wegen dem geringen Platz im Gehäuse und um ein einknicken und scharfe Kurven der Luftschläuche zu verhindern.


Über dem Feinstaubsensor ist die Platine angeordnet.

Mit dem JST Verbindungskabel ist der SPS mit den UART Ports verbunden.

Ein kurzes Ethernet Kabel verbindet die Platine zum Harting Gehäusestecker.

 

Das ganze Gerät wird stehend montiert damit der SPS wie vom Hersteller empfohlen mit den Luftkanälen nach unten ausgerichtet ist und der Temperatur Sensor am unteren Gehäuserand die Innentemperatur misst.

 

 


 

1.10 Frontplatte

 

Die Frontplatte ist aus Aluminium natureloxiert 1,5mm stark und mit Digitaldruck (Inkjet Tinte 120°C eingebrannt).

Im Gehäusedeckel ist eine 1,5mm tiefe Einfräsung damit die Frontplatte plan eingeklebt werden kann.

20190223_082015423_iOS

 


 

Damit das Gehäuse spritzwasserdicht wird werden als LEDs Hohllichtleiter mit einem sehr flüssigen 2 Komponenten Kleber eingeklebt und vor den LEDs dann beim zuschrauben platziert.

Die Senkkopfschrauben auf der Gehäuseunterseite sind mit einer Schraubendichtung spritzwasserfest abgedichtet.

 

     

 

      

 


 

Das Gerät in Betrieb, es soll stehend mit den Luftanschlüssen nach unten, nicht in direktem Luftstrom >1m/s, nicht in direkter Sonneneinstrahlung montiert werden. Die Luftschläuche nicht verlängern denn sie dienen um den Abstand zu vergrößern. Die Siebe der Insekten Abwehr können von außen zugänglich gereinigt werden.

 

 


 

1.11 UART – LAN Modul Konfiguration

 

Mit einer Weboberfläche kann das Modul konfiguriert werden.

IP Adresse und TTL Port Konfiguration und weitere können eingestellt werden.

 

Gemäß dem SPS30 sind diese UART Parameter einzustellen:

115200 Bit/s, 8 Data Bits, 1 Stop Bits, Parity no, Flow control no

und dem Si0721 9600 Bit/s, 8 Data Bits, 1 Stop Bits, Parity no, Flow control no

 

Das Power Shell Programm welches dann die Daten abgreift verbindet sich an Port 231 TCP für die UART vom SPS30 und Port 232 TCP für die UART vom Si0721 Sensor.

Beide Ports können über das LAN parallel und konfliktfrei angesprochen werden

 

 

 

Mit dem Reset Taster auf der Platine kann das Modul neu gestartet werden.

Mit dem Defaults Taster kann das Modul auf Werkseinstellungen zurückgesetzt werden.

 


 

1.12 SHDLC Protokoll

 

SHDLC ist eine Abkürzung für das Sensirion High-Level Data Link Control Protokoll.

Es ist ein serielles Kommunikationsprotokoll basierend auf einer Master / Slave Architektur.

MOSI bedeutet Master Out Slave In und wird zum Senden von Befehlen verwendet

MISO bedeutet Master In Slave Out und wird zum Empfangen der Daten verwendet.

 

 

 

 


 

1.13 MISO Frame

 

Ein MISO Frame hat eine Länge von etwa 255 Zeichen, Start und End Markierung, Kommando, Status und Länge, 10 Datensätzen und einer Prüfsumme.

 

 

7E00030028 40927D9E 4096C7C9 4096C7C9 4096C7C9 420532CE 421811A2 42187843 421879AD 42187A82 3F079EBE D47E

 

·         Start und Ende eines Frames

·         Adresse

·         Kommando

·         Status

·         Länge

·         10 Datensätze

·         Prüfsumme

 

Die Prüfsumme ist das invertierte niedrigere Byte aller addierten Bytes ohne die Prüfsumme selbst.

Also in dem Fall von 00 bis BE.

 

Damit ein Datensatz empfangen werden kann muss erst ein Kommando gesendet werden.

Es gibt verschiedenste Kommandos. 0x00, 0x01, 0x03, ….

 

Generell muss der Sensor erst in den Messbetrieb geschalten werden, dabei wird dann begonnen die Luft umzuwälzen.

Danach können im Sekunden Takt Messdaten abgegriffen werden.

Am Ende einer Messung kann der Sensor wieder in den Ruhezustand geschalten werden.

 

Per Standard nimmt der Sensor auch 1x in der Woche einen Reinigungslauf vor welcher aber auch konfiguriert werden könnte.

 

 

Im Datenblatt sind genauere Erläuterungen zum SPS30 und dem SHDLC Protokoll ersichtlich.

https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Particulate_Matter/Sensirion_PM_Sensors_SPS30_Datasheet.pdf

 


 

1.14 Power Shell Programm

 

Auf dem Hausautomation Server läuft ein Power Shell Programm welches den TCP Datenstrom oder eine andere Variante welche einen RS232 COM Port Datenstrom liest und dann die MISO Frame auswertet und die Daten in die Windows Registry zwischenspeichert.

Von dort können dann andere Programme unabhängig und parallel die Daten wieder abgreifen und weiterverwenden.

Es wird Paessler PRTG Network Monitor eingesetzt mit mehreren Power Shell Sensoren zum Zeichnen von Langzeit Grafen und dem darstellen in der PRTG Map als Internet Seite.

 

 

Das Power Shell Programm (RS232) als Treiber für den SPS30 und lesen der Messdaten:

 

# Sensirion SPS30 UART Reader and MISO Frame Parser (Windows PowerShell)

# written by Ginther Andreas (GLAB) 2018 http://www.the-ginthers.net/

# Version 1.0.0 (2019.02.23)

 

# 3rd party code

# -----------------------------------

# Function Convert-ByteArrayToHexString (https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert)

# Function Convert-HexStringToByteArray (https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert)

# -----------------------------------

 

# Database Instance = PM25Meter1

# UART Port         = COM3

# Run in 32 bit mode

 

# -----------------------------------

 

# Initializing

$MeterInstance = '1'

$REG32 = '\SOFTWARE64' # Run in 32 bit mode

$ErrorActionPreference = "Continue"

 

# Initializing Logging

$_LogPath = "C:\ProgramData\GLAB\Log"

$_LogDate = Get-Date -UFormat "%Y%m%d"

$MISOLog = $_LogPath + "\MISOFrame" + $MeterInstance + "_" + $_LogDate + ".log"

$MISOLogList = $_LogPath + "\MISOFrame" + $MeterInstance + "_" + $_LogDate + " List.log"

If(!(Test-Path $_LogPath)) {New-Item -ItemType Directory -Force -Path $_LogPath}

$_LogAge = (Get-Date).AddDays(-7)

Get-ChildItem -Path $_LogPath -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $_LogAge } | Remove-Item -Force

 

 

'---------------------------------------' | Add-Content -Path $MISOLog

$_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

'' + $_Date  + '  INFO  ' + 'Program starting' | Add-Content -Path $MISOLog

 

# Function Convert-ByteArrayToHexString

Function Convert-ByteArrayToHexString {

    [CmdletBinding()] Param (

    [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [System.Byte[]] $ByteArray,

    [Parameter()] [Int] $Width = 10,

    [Parameter()] [String] $Delimiter = ",0x",

    [Parameter()] [String] $Prepend = "",

    [Parameter()] [Switch] $AddQuotes )

    if ($Width -lt 1) { $Width = 1 }

    if ($ByteArray.Length -eq 0) { Return }

    $FirstDelimiter = $Delimiter -Replace "^[\,\:\t]",""

    $From = 0

    $To = $Width - 1

    Do {

        $String = [System.BitConverter]::ToString($ByteArray[$From..$To])

        $String = $FirstDelimiter + ($String -replace "\-",$Delimiter)

        if ($AddQuotes) { $String = '"' + $String + '"' }

        if ($Prepend -ne "") { $String = $Prepend + $String }

        $String

        $From += $Width

        $To += $Width

    } While ($From -lt $ByteArray.Length)

}

 

# Function Convert-HexStringToByteArray

Function Convert-HexStringToByteArray {

    [CmdletBinding()]

    Param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [String] $String )

    $String = $String.ToLower() -replace '[^a-f0-9\\,x\-\:]',"

    $String = $String -replace '0x|\x|\-|,',':'

    $String = $String -replace '^:+|:+$|x|\',"

    if ($String.Length -eq 0) { ,@() ; return }

    if ($String.Length -eq 1)

        { ,@([System.Convert]::ToByte($String,16)) }

    elseif (($String.Length % 2 -eq 0) -and ($String.IndexOf(":") -eq -1))

        { ,@($String -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}}) }

    elseif ($String.IndexOf(":") -ne -1)

        { ,@($String -split ':+' | foreach-object {[System.Convert]::ToByte($_,16)}) }

    else

        { ,@() }

}

 

 

    # Initializing the COM port

    $Port = 'COM3'

    $BaudRate = 115200

    $DataBits = 8

    $StopBits = [System.IO.Ports.StopBits]::one

    $Parity = [System.IO.Ports.Parity]::None

    $Port = New-Object System.IO.Ports.SerialPort $Port,$BaudRate,$Parity,$DataBits,$StopBits

    $Port.ReadTimeout = 400

    $Port.WriteTimeout = 400

    $Port.Open()

    Write-Output 'COM Port opened'

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'COM Port opened' | Add-Content -Path $MISOLog

 

 

    # Read Serial Number (CMD: 0xD0)

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Serial Number reading...' | Add-Content -Path $MISOLog

    # Create an empty char array and discard the COM port read buffer

    $PortDataByteArraySerial = $null

    $PortDataByteArraySerial = New-Object System.Collections.ArrayList

    $Port.DiscardInBuffer()

    Write-Output 'Serial Number reading...'

    [Byte[]] $request = 0x7E, 0x00, 0xD0, 0x01, 0x03, 0x2B, 0x7E

    $Port.Write($request, 0, $request.Count)

    Sleep -Milliseconds 200

    # Read the COM port bytes

    $Portloop = 0

    Do {

        $PortDataRAWSerial = $Port.ReadByte()    # get ASCII codes

        #$PortDataByteArraySerial += $PortDataRAWSerial

        $PortDataByteArraySerial.Add($PortDataRAWSerial) > $null

        #    $PortDataByteArraySerial.count

        #    Write-Output $PortDataRAWSerial

        $Portloop++

    }

    Until ($Portloop -gt 24)

    # Convert the char array to a hex array

    $PortDataHex = $null

    $PortDataHex = New-Object System.Collections.ArrayList

    $PortDataHex = Convert-ByteArrayToHexString -ByteArray $PortDataByteArraySerial -Width '' -Delimiter ' '

    #Write-Host $PortDataHex

    # Convert the hex array to a hex string and cutout the bytes

    [string]$PortDataHexLine = ''

    [string]$MISOFrameSerial = ''

    [string]$DeviceSerialNumber = ''

    [string]$PortDataHexLine = $PortDataHex -join ''

    [string]$MISOFrameSerial = $PortDataHexLine.Replace(' ','')

    $MISOFrameSerialOnly = $MISOFrameSerial.Substring(12,32)

    Write-Host $MISOFrameSerial

    Write-Host $MISOFrameSerialOnly

    $MISOFrameSerialOnlyByteArray = Convert-HexStringToByteArray -String $MISOFrameSerialOnly

    $MISOFrameSerialOnlyHexArray = $MISOFrameSerialOnlyByteArray | ForEach-Object { ("{0:X2}" -f $_) }

    $MISOFrameSerialOnlyASCIIArray = $MISOFrameSerialOnlyHexArray | ForEach {[char]([convert]::toint16($_,16))}

    [string]$DeviceSerialNumber = [system.String]::Join("", $MISOFrameSerialOnlyASCIIArray)

    Write-Host $DeviceSerialNumber

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Serial Number = ' + $DeviceSerialNumber | Add-Content -Path $MISOLog

    Write-Output 'Serial Number red'

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Serial Number red' | Add-Content -Path $MISOLog

    New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "Serial Number" -PropertyType String -Value $DeviceSerialNumber -Force

 

   

    # Start Measurement (CMD: 0x00)

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement starting...' | Add-Content -Path $MISOLog

    # Create an empty char array and discard the COM port read buffer

    $PortDataByteArrayStartM = $null

    $PortDataByteArrayStartM = New-Object System.Collections.ArrayList

    $Port.DiscardInBuffer()

    Write-Output 'Measurement starting...'

    [Byte[]] $request = 0x7E, 0x00, 0x00, 0x02, 0x01, 0x03, 0xF9, 0x7E

    $Port.Write($request, 0, $request.Count)

    Sleep -Milliseconds 200

    # Read the COM port bytes

    $Portloop = 0

    Do {

        $PortDataRAWSartM = $Port.ReadByte()    # get ASCII codes

        #$PortDataByteArrayStartM += $PortDataRAWSartM

        $PortDataByteArrayStartM.Add($PortDataRAWSartM) > $null

        #    $PortDataByteArrayStartM.count

        #    Write-Output $PortDataRAWSartM

        $Portloop++

    }

    Until ($Portloop -gt 6)

    # Convert the char array to a hex array

    $PortDataHex = $null

    $PortDataHex = New-Object System.Collections.ArrayList

    $PortDataHex = Convert-ByteArrayToHexString -ByteArray $PortDataByteArrayStartM -Width '' -Delimiter ' '

    #Write-Host $PortDataHex

    # Convert the hex array to a hex string and cutout the bytes

    [string]$PortDataHexLine = ''

    [string]$MISOFrameStartM = ''

    [string]$PortDataHexLine = $PortDataHex -join ''

    [string]$MISOFrameStartM = $PortDataHexLine.Replace(' ','')

    Write-Host $MISOFrameStartM

    Write-Output 'Measurement started'

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement started' | Add-Content -Path $MISOLog

 

 

    # Wait of the sensor to be ready after start

    #Sleep -Seconds 5

 

 

    # Get sensor measured values

    $getdataloop = 1

    Do {

    # Read measured values (CMD: 0x03), send the MOSI command

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement reading...' | Add-Content -Path $MISOLog

    # Create an empty char array and discard the COM port read buffer

    $PortDataByteArrayReadM = $null

    $PortDataByteArrayReadM = New-Object System.Collections.ArrayList

    $Port.DiscardInBuffer()

    Write-Output 'Read Measurement'

    [Byte[]] $request = 0x7E, 0x00, 0x03, 0x00, 0xFC, 0x7E

    $Port.Write($request, 0, $request.Count)

    Sleep -Milliseconds 200

    $PortBytesInBuffer = $Port.BytesToRead

    # Read the COM port bytes (about 50 bytes for one complete MISO frame)

    $Portloop = 0

    Do {

        $PortDataRAWReadM = $Port.ReadByte()    # get ASCII codes

        #$PortDataByteArrayReadM += $PortDataRAWReadM

        $PortDataByteArrayReadM.Add($PortDataRAWReadM) > $null

        #    $PortDataByteArrayReadM.count

        #    Write-Output $PortDataRAWReadM

        $Portloop++

    }

    Until ($Portloop -gt $PortBytesInBuffer-1) # about max 50 times

    Clear-Variable PortBytesInBuffer

    # Convert the char array to a hex array

    $PortDataHex = $null

    $PortDataHex = New-Object System.Collections.ArrayList

    $PortDataHex = Convert-ByteArrayToHexString -ByteArray $PortDataByteArrayReadM -Width '' -Delimiter '0x'

    #Write-Host $PortDataHex

 

    # Convert the hex array to a hex string and cut out the bytes

    [string]$PortDataHexLine = ''

    [string]$MISOFrameReadM = ''

    [string]$MISOFrameReadMBS = ''

    [string]$PortDataHexLine = $PortDataHex -join ','

    [string]$MISOFrameReadM = $PortDataHexLine.Replace(' ','')

    [string]$MISOFrameStartByte = ''

    [string]$MISOFrameWoSUM = ''

   

    $MISOFrameReadMLog = ''

    $MISOFrameReadMLog = $MISOFrameReadM.Replace('0x','')

    $MISOFrameReadMLog = $MISOFrameReadMLog.Replace(',','')

 

    # Do byte-stuffing and cut the end of the MISO Frame

    $MISOFrameReadMBS =   $MISOFrameReadM.Replace('0x7D,0x5E','0x7E')

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace('0x7D,0x31','0x11')

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace('0x7D,0x33','0x13')

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace('0x7D,0x5D','0x7D')

    $MISOFrameReadMBSHex = $MISOFrameReadMBS.Clone()

 

    # remove 0x

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace('0x','')

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace(',','')

 

    # Cut out bytes

    $MISOFrameStartByte = ''

    $MISOFrameAddrByte = ''

    $MISOFrameCMDByte = ''

    $MISOFrameStateByte = ''

    $MISOFrameLByte = ''

    $MISOFrameCHKByte = ''

    $MISOFrameStopByte = ''

    $MISOFrameWoSUM = ''

    $MISOFrameStartByte = $MISOFrameReadMBS.Substring(0,2)

    $MISOFrameAddrByte = $MISOFrameReadMBS.Substring(2,2)

    $MISOFrameCMDByte = $MISOFrameReadMBS.Substring(4,2)

    $MISOFrameStateByte = $MISOFrameReadMBS.Substring(6,2)

    $MISOFrameLByte = $MISOFrameReadMBS.Substring(8,2)

    $MISOFrameCHKByte = $MISOFrameReadMBS.Substring(90,2)

    $MISOFrameStopByte = $MISOFrameReadMBS.Substring(92,2)

    $MISOFrameWoSUM = $MISOFrameReadMBS.Substring(2,88)

    $MISOFrameDataOnly = $MISOFrameReadMBS.Substring(10,80)

    Write-Host $MISOFrameReadM

    Write-Host $MISOFrameReadMBS

    Write-Host $MISOFrameWoSUM

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + $MISOFrameReadMLog | Add-Content -Path $MISOLog

    '' + $_Date  + '  INFO  ' + $MISOFrameReadMBS | Add-Content -Path $MISOLog

    '' + $_Date  + '  INFO  ' + $MISOFrameReadMLog | Add-Content -Path $MISOLogList

    '' + $_Date  + '  INFO  ' + $MISOFrameReadMBS | Add-Content -Path $MISOLogList

    New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "CurrentMISOFrame" -PropertyType String -Value $MISOFrameReadMBS -Force

 

    # Verify bytes

    if ($MISOFrameStopByte -eq '7E')

    {

        Write-Output ('MISO Frame End ok !')

        $MISOFramesEndGood = $true

     

        # Calculate checksum

        # hex to decimal and add together

        $MISOFrameCHKCalc = $MISOFrameWoSUM -split '(?<=\G.{2})(?=.)' | Foreach-Object {$c = 0}{$c+=[Convert]::ToByte($_,16)}{$c}

        # convert the sum to hex

        $MISOFrameCHKCalcHex = '{0:x}' -f $MISOFrameCHKCalc

        # convert the 2 hex numbers to a byte array

        #$MISOFrameCHKCalcArray = Convert-HexStringToByteArray -String $MISOFrameCHKCalcHex

        # get the LSB last significant bit from the array

        #$MISOFrameCHKCalcByte = $MISOFrameCHKCalcArray.Get(1)

        # convert the 2 hex numbers to a byte and get the LSB last significant bit

        $MISOFrameCHKCalcByte = ([Net.IPAddress]$MISOFrameCHKCalc).GetAddressBytes()[0]

        # invert the LSB

        $MISOFrameCHKCalcByteInvert = $MISOFrameCHKCalcByte -bxor 0xFF

        # convert the hex checksum of the frame into an array

        $MISOFrameCHKFrameByte = [convert]::ToInt32($MISOFrameCHKByte,16)

        Write-Host $MISOFrameCHKCalcByteInvert

        Write-Host $MISOFrameCHKFrameByte

        if ($MISOFrameCHKFrameByte -ne $MISOFrameCHKCalcByteInvert)

        {

            Write-Host "Checksum mismatch" -ForegroundColor Red

            $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

            '' + $_Date  + '  ERRO  ' + 'MISO Frame Checksum mismatch !' | Add-Content -Path $MISOLog

            '' + $_Date  + '  ERRO  ' + 'MISO Frame Checksum Calc  ' + $MISOFrameCHKCalcByteInvert | Add-Content -Path $MISOLog

            '' + $_Date  + '  ERRO  ' + 'MISO Frame Checksum Frame ' + $MISOFrameCHKFrameByte | Add-Content -Path $MISOLog

            # Count bad MISO Frames Checksum

            $BadMISOFramesCHKCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'BadMISOFramesCHKCounter'

            New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "BadMISOFramesCHKCounter" -PropertyType QWord -Value ($BadMISOFramesCHKCounter + 1) -Force

            New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "BadMISOFramesCHKTime" -PropertyType String -Value (get-date) -Force

        }

        else

        {

            Write-Host "Checksum pass" -ForegroundColor Green

            $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

            '' + $_Date  + '  INFO  ' + 'MISO Frame Checksum pass' | Add-Content -Path $MISOLog

           

            # Cut out bytes of measurement values

            $MISOFramePM1wByte = ''

            $MISOFramePM25wByte = ''

            $MISOFramePM4wByte = ''

            $MISOFramePM10wByte = ''

            $MISOFramePM05cByte = ''

            $MISOFramePM1cByte = ''

            $MISOFramePM25cByte = ''

            $MISOFramePM4cByte = ''

            $MISOFramePM10cByte = ''

            $MISOFrameSizeByte = ''

            $MISOFramePM1wByte = $MISOFrameReadMBS.Substring(10,8)

            $MISOFramePM25wByte = $MISOFrameReadMBS.Substring(18,8)

            $MISOFramePM4wByte = $MISOFrameReadMBS.Substring(26,8)

            $MISOFramePM10wByte = $MISOFrameReadMBS.Substring(34,8)

            $MISOFramePM05cByte = $MISOFrameReadMBS.Substring(42,8)

            $MISOFramePM1cByte = $MISOFrameReadMBS.Substring(50,8)

            $MISOFramePM25cByte = $MISOFrameReadMBS.Substring(58,8)

            $MISOFramePM4cByte = $MISOFrameReadMBS.Substring(66,8)

            $MISOFramePM10cByte = $MISOFrameReadMBS.Substring(74,8)

            $MISOFrameSizeByte = $MISOFrameReadMBS.Substring(82,8)

 

            # Convert bytes to float and to string

            $MISOFramePM1wByteArray = Convert-HexStringToByteArray -String $MISOFramePM1wByte

            $MISOFramePM1wByteArrayReverse = $MISOFramePM1wByteArray.Clone()

            [array]::Reverse($MISOFramePM1wByteArrayReverse)

            $MISOFramePM1wFloat = [BitConverter]::ToSingle($MISOFramePM1wByteArrayReverse, 0)

            $MISOFramePM1wString = [string]$MISOFramePM1wFloat

            Write-Output ('PM 1.0  = '+$MISOFramePM1wString +' µg/m³')

 

            $MISOFramePM25wByteArray = Convert-HexStringToByteArray -String $MISOFramePM25wByte

            $MISOFramePM25wByteArrayReverse = $MISOFramePM25wByteArray.Clone()

            [array]::Reverse($MISOFramePM25wByteArrayReverse)

            $MISOFramePM25wFloat = [BitConverter]::ToSingle($MISOFramePM25wByteArrayReverse, 0)

            $MISOFramePM25wString = [string]$MISOFramePM25wFloat

            Write-Output ('PM 2.5  = '+$MISOFramePM25wString +' µg/m³')

   

            $MISOFramePM4wByteArray = Convert-HexStringToByteArray -String $MISOFramePM4wByte

            $MISOFramePM4wByteArrayReverse = $MISOFramePM4wByteArray.Clone()

            [array]::Reverse($MISOFramePM4wByteArrayReverse)

            $MISOFramePM4wFloat = [BitConverter]::ToSingle($MISOFramePM4wByteArrayReverse, 0)

            $MISOFramePM4wString = [string]$MISOFramePM4wFloat

            Write-Output ('PM 4.0  = '+$MISOFramePM4wString +' µg/m³')

 

            $MISOFramePM10wByteArray = Convert-HexStringToByteArray -String $MISOFramePM10wByte

            $MISOFramePM10wByteArrayReverse = $MISOFramePM10wByteArray.Clone()

            [array]::Reverse($MISOFramePM10wByteArrayReverse)

            $MISOFramePM10wFloat = [BitConverter]::ToSingle($MISOFramePM10wByteArrayReverse, 0)

            $MISOFramePM10wString = [string]$MISOFramePM10wFloat

            Write-Output ('PM 10.0 = '+$MISOFramePM10wString +' µg/m³')

   

            $MISOFramePM05cByteArray = Convert-HexStringToByteArray -String $MISOFramePM05cByte

            $MISOFramePM05cByteArrayReverse = $MISOFramePM05cByteArray.Clone()

            [array]::Reverse($MISOFramePM05cByteArrayReverse)

            $MISOFramePM05cFloat = [BitConverter]::ToSingle($MISOFramePM05cByteArrayReverse, 0)

            $MISOFramePM05cString = [string]$MISOFramePM05cFloat

            Write-Output ('PM 0.5  = '+$MISOFramePM05cString +' #/cm³')

   

            $MISOFramePM1cByteArray = Convert-HexStringToByteArray -String $MISOFramePM1cByte

            $MISOFramePM1cByteArrayReverse = $MISOFramePM1cByteArray.Clone()

            [array]::Reverse($MISOFramePM1cByteArrayReverse)

            $MISOFramePM1cFloat = [BitConverter]::ToSingle($MISOFramePM1cByteArrayReverse, 0)

            $MISOFramePM1cString = [string]$MISOFramePM1cFloat

            Write-Output ('PM 1.0  = '+$MISOFramePM1cString +' #/cm³')

    

            $MISOFramePM25cByteArray = Convert-HexStringToByteArray -String $MISOFramePM25cByte

            $MISOFramePM25cByteArrayReverse = $MISOFramePM25cByteArray.Clone()

            [array]::Reverse($MISOFramePM25cByteArrayReverse)

            $MISOFramePM25cFloat = [BitConverter]::ToSingle($MISOFramePM25cByteArrayReverse, 0)

            $MISOFramePM25cString = [string]$MISOFramePM25cFloat

            Write-Output ('PM 2.5  = '+$MISOFramePM25cString +' #/cm³')

    

            $MISOFramePM4cByteArray = Convert-HexStringToByteArray -String $MISOFramePM4cByte

            $MISOFramePM4cByteArrayReverse = $MISOFramePM4cByteArray.Clone()

            [array]::Reverse($MISOFramePM4cByteArrayReverse)

            $MISOFramePM4cFloat = [BitConverter]::ToSingle($MISOFramePM4cByteArrayReverse, 0)

            $MISOFramePM4cString = [string]$MISOFramePM4cFloat

            Write-Output ('PM 4.0  = '+$MISOFramePM4cString +' #/cm³')

    

            $MISOFramePM10cByteArray = Convert-HexStringToByteArray -String $MISOFramePM10cByte

            $MISOFramePM10cByteArrayReverse = $MISOFramePM10cByteArray.Clone()

            [array]::Reverse($MISOFramePM10cByteArrayReverse)

            $MISOFramePM10cFloat = [BitConverter]::ToSingle($MISOFramePM10cByteArrayReverse, 0)

            $MISOFramePM10cString = [string]$MISOFramePM10cFloat

            Write-Output ('PM 10.0 = '+$MISOFramePM10cString +' #/cm³')

   

            $MISOFrameSizeByteArray = Convert-HexStringToByteArray -String $MISOFrameSizeByte

            $MISOFrameSizeByteArrayReverse = $MISOFrameSizeByteArray.Clone()

            [array]::Reverse($MISOFrameSizeByteArrayReverse)

            $MISOFrameSizeFloat = [BitConverter]::ToSingle($MISOFrameSizeByteArrayReverse, 0)

            $MISOFrameSizeString = [string]$MISOFrameSizeFloat

            Write-Output ('Particle Size = '+$MISOFrameSizeString +' µm')

   

            # initializing high performance write access to registry

            $REGKEY = ('SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance)

            $REGHIVE = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', 'Default')

            $REGKEY = $REGHIVE.CreateSubKey($REGKEY, $true)

            $REGKEY.SetValue('SizePM1',   $MISOFramePM1wString, 'String')

            $REGKEY.SetValue('SizePM25',  $MISOFramePM25wString, 'String')

            $REGKEY.SetValue('SizePM4',   $MISOFramePM4wString, 'String')

            $REGKEY.SetValue('SizePM10',  $MISOFramePM10wString, 'String')

            $REGKEY.SetValue('CountPM05', $MISOFramePM05cString, 'String')

            $REGKEY.SetValue('CountPM1',  $MISOFramePM1cString, 'String')

            $REGKEY.SetValue('CountPM25', $MISOFramePM25cString, 'String')

            $REGKEY.SetValue('CountPM4',  $MISOFramePM4cString, 'String')

            $REGKEY.SetValue('CountPM10', $MISOFramePM10cString, 'String')

            $REGKEY.SetValue('ParticleSize', $MISOFrameSizeString, 'String')

           

            # Count good MISO Frames

            $GoodMISOFramesCHKCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'GoodMISOFramesCHKCounter'

            New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "GoodMISOFramesCHKCounter" -PropertyType QWord -Value ($GoodMISOFramesCHKCounter + 1) -Force

        }

    }

    else

    {

        Write-Output ('MISO Frame End corrupt !')

        Write-Output ('MISO Frame End = '+$MISOFrameStopByte)

        Write-Output ('get once again data')

        $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

        '' + $_Date  + '  ERRO  ' + 'MISO Frame End corrupt !' | Add-Content -Path $MISOLog

 

        # Count bad MISO Frames End

        $BadMISOFramesEndCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'BadMISOFramesEndCounter'

        New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "BadMISOFramesEndCounter" -PropertyType QWord -Value ($BadMISOFramesEndCounter + 1) -Force

        New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "BadMISOFramesEndTime" -PropertyType String -Value (get-date) -Force

        $MISOFramesEndGood = $false

    }

 

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement read' | Add-Content -Path $MISOLog

 

    $getdataloop++

    Sleep -Milliseconds 2500

}

    While ($getdataloop -lt 0)

 

 

    <#

    # Stop Measurement (CMD: 0x01)

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement stopping...' | Add-Content -Path $MISOLog

    # Create an empty char array and discard the COM port read buffer

    $PortDataByteArrayStopM = $null

    $PortDataByteArrayStopM = New-Object System.Collections.ArrayList

    $Port.DiscardInBuffer()

    Write-Output 'Measurement stopping...'

    [Byte[]] $request = 0x7E, 0x00, 0x01, 0x00, 0xFE, 0x7E

    $Port.Write($request, 0, $request.Count)

    Sleep -Milliseconds 200

    # Read the COM port bytes

    $Portloop = 0

    Do {

        $PortDataRAWStopM = $Port.ReadByte()    # get ASCII codes

        #$PortDataByteArrayStopM += $PortDataRAWStopM

        $PortDataByteArrayStopM.Add($PortDataRAWStopM) > $null

        #    $PortDataByteArrayStopM.count

        #    Write-Output $PortDataRAWStopM

        $Portloop++

    }

    Until ($Portloop -gt 6)

    # Convert the char array to a hex array

    $PortDataHex = $null

    $PortDataHex = New-Object System.Collections.ArrayList

    $PortDataHex = Convert-ByteArrayToHexString -ByteArray $PortDataByteArrayStopM -Width '' -Delimiter ' '

    # Write-Host $PortDataHex

    # Convert the hex array to a hex string and cutout the bytes

    [string]$PortDataHexLine = ''

    [string]$MISOFrameStopM = ''

    [string]$PortDataHexLine = $PortDataHex -join ''

    [string]$MISOFrameStopM = $PortDataHexLine.Replace(' ','')

    Write-Host $MISOFrameStopM

    Write-Output 'Measurement stopped'

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement stopped' | Add-Content -Path $MISOLog

    #>

 

 

    # Uninitializing the COM port

    $Port.Close()

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'COM Port closed' | Add-Content -Path $MISOLog

    Write-Output 'COM Port closed'

   

 

# Program Counters

$GoodMISOFramesCHKCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'GoodMISOFramesCHKCounter'

$BadMISOFramesCHKCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'BadMISOFramesCHKCounter'

 

# Write XML for PRTG

[string]$prtgresult=""

$prtgresult+="<?xml version=""1.0"" encoding=""Windows-1252"" ?>`r`n"

$prtgresult+="<prtg>`r`n"

$prtgresult+="    <result>`r`n"

$prtgresult+="        <channel>MISOGood</channel>`r`n"

$prtgresult+="        <mode>Absolute</mode>`r`n"

$prtgresult+="        <customunit>Frames</customunit>`r`n"

$prtgresult+="        <value>$GoodMISOFramesCHKCounter</value>`r`n"

$prtgresult+="        <float>1</float>`r`n"

$prtgresult+="    </result>`r`n"

$prtgresult+="    <result>`r`n"

$prtgresult+="        <channel>MISOBad</channel>`r`n"

$prtgresult+="        <mode>Absolute</mode>`r`n"

$prtgresult+="        <customunit>Frames</customunit>`r`n"

$prtgresult+="        <value>$BadMISOFramesCHKCounter</value>`r`n"

$prtgresult+="        <float>1</float>`r`n"

$prtgresult+="    </result>`r`n"

$prtgresult+="</prtg>"

 

if ($errorfound) {

       write-host "Error Found. Ending with EXIT Code" ([xml]$prtgresult).prtg.error

}

write-host "Sending PRTGRESULT to STDOUT"

$prtgresult

 

if ($errorfound) {

       exit ([xml]$prtgresult).prtg.error

}

 

 

Das Power Shell Programm wird vom PRTG ausgelöst und läuft in dessen Kontext.

So kann es im Service Kontext laufen und ein neustarten des Servers nimmt automatisch wieder den Betrieb auf.

 


 

 Das Power Shell Programm (TCP Socket) als Treiber für den SPS30 und lesen der Messdaten:

 

# Sensirion SPS30 UART - TCP Reader and MISO Frame Parser (Windows PowerShell)

# written by Ginther Andreas (GLAB) 2018 http://www.the-ginthers.net/

# Version 2.0.0 (2019.03.30)

 

# 3rd party code

# -----------------------------------

# Function Convert-ByteArrayToHexString (https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert)

# Function Convert-HexStringToByteArray (https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert)

# -----------------------------------

 

# Database Instance = PM25Meter1

# TCP Port          = 231

# Run in 32 bit mode

 

# -----------------------------------

 

# Initializing

$TCPPort = '231'

$TCPHost = 'gabc1011.glab.dom'

$MeterInstance = '1'

$REG32 = '\SOFTWARE64' # Run in 32 bit mode

$ErrorActionPreference = "Continue"

 

# Initializing Logging

$_LogPath = "C:\ProgramData\GLAB\Log"

$_LogDate = Get-Date -UFormat "%Y%m%d"

$MISOLog = $_LogPath + "\MISOFrame" + $MeterInstance + "_" + $_LogDate + ".log"

$MISOLogList = $_LogPath + "\MISOFrame" + $MeterInstance + "_" + $_LogDate + " List.log"

If(!(Test-Path $_LogPath)) {New-Item -ItemType Directory -Force -Path $_LogPath}

$_LogAge = (Get-Date).AddDays(-7)

Get-ChildItem -Path $_LogPath -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $_LogAge } | Remove-Item -Force

 

 

'---------------------------------------' | Add-Content -Path $MISOLog

$_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

'' + $_Date  + '  INFO  ' + 'Program starting' | Add-Content -Path $MISOLog

 

# Function Convert-ByteArrayToHexString

Function Convert-ByteArrayToHexString {

    [CmdletBinding()] Param (

    [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [System.Byte[]] $ByteArray,

    [Parameter()] [Int] $Width = 10,

    [Parameter()] [String] $Delimiter = ",0x",

    [Parameter()] [String] $Prepend = "",

    [Parameter()] [Switch] $AddQuotes )

    if ($Width -lt 1) { $Width = 1 }

    if ($ByteArray.Length -eq 0) { Return }

    $FirstDelimiter = $Delimiter -Replace "^[\,\:\t]",""

    $From = 0

    $To = $Width - 1

    Do {

        $String = [System.BitConverter]::ToString($ByteArray[$From..$To])

        $String = $FirstDelimiter + ($String -replace "\-",$Delimiter)

        if ($AddQuotes) { $String = '"' + $String + '"' }

        if ($Prepend -ne "") { $String = $Prepend + $String }

        $String

        $From += $Width

        $To += $Width

    } While ($From -lt $ByteArray.Length)

}

 

# Function Convert-HexStringToByteArray

Function Convert-HexStringToByteArray {

    [CmdletBinding()]

    Param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [String] $String )

    $String = $String.ToLower() -replace '[^a-f0-9\\,x\-\:]',"

    $String = $String -replace '0x|\x|\-|,',':'

    $String = $String -replace '^:+|:+$|x|\',"

    if ($String.Length -eq 0) { ,@() ; return }

    if ($String.Length -eq 1)

        { ,@([System.Convert]::ToByte($String,16)) }

    elseif (($String.Length % 2 -eq 0) -and ($String.IndexOf(":") -eq -1))

        { ,@($String -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}}) }

    elseif ($String.IndexOf(":") -ne -1)

        { ,@($String -split ':+' | foreach-object {[System.Convert]::ToByte($_,16)}) }

    else

        { ,@() }

}

 

 

    # Initializing the TCP port

    $TCPIP = [System.Net.Dns]::GetHostAddresses($TCPHost)

    $TCPAddress = [System.Net.IPAddress]::Parse($TCPIP)

    # Create the TCP endpoint

    $TCPConnection = New-Object System.Net.IPEndPoint $TCPAddress, $TCPPort

    # Create the TCP Socket

    $TCPAddress = [System.Net.Sockets.AddressFamily]::InterNetwork

    $TCPSocketType = [System.Net.Sockets.SocketType]::Stream

    $TCPProtocolType = [System.Net.Sockets.ProtocolType]::TCP

    $TCPSocket = New-Object System.Net.Sockets.Socket $TCPAddress, $TCPSocketType, $TCPProtocolType

    $TCPSocket.TTL = 36

    $TCPSocket.SendTimeout = 1000

    $TCPSocket.ReceiveTimeout = 1000

    $TCPSocket.ReceiveBufferSize = 64

    # Connect

    $TCPSocket.Connect($TCPConnection)

    IF($TCPSocket.Connected)

    {

        Write-Host -ForegroundColor Green "TCP Socket ok"

         $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

        '' + $_Date  + '  INFO  ' + 'TCP Socket ok' | Add-Content -Path $MISOLog

    }

    ELSE

    {

        Write-Host -ForegroundColor Red "TCP Socket failed"

        $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

        '' + $_Date  + '  INFO  ' + 'TCP Socket failed' | Add-Content -Path $MISOLog

    }

 

 

    # Read Serial Number (CMD: 0xD0)

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Serial Number reading...' | Add-Content -Path $MISOLog

    # Create an empty char array and discard the COM port read buffer

    $PortDataByteArraySerial = $null

    $PortDataByteArraySerial = New-Object System.Collections.ArrayList # high performance array

    Write-Output 'Serial Number reading...'

    [Byte[]] $request = 0x7E, 0x00, 0xD0, 0x01, 0x03, 0x2B, 0x7E

    $TCPSent = $TCPSocket.Send($request)

    Start-Sleep -Milliseconds 100

    # Read received bytes

    IF($TCPSocket.Available)

        {

        $TCPBuffer = New-Object System.Byte[] 32

        $TCPReceive = $TCPSocket.Receive($TCPBuffer)

        Write-Host "TCP data received:" $TCPBuffer

        }

    # Convert the char array to a hex array

    $PortDataHex = $null

    $PortDataHex = New-Object System.Collections.ArrayList # high performance array

    $PortDataHex = Convert-ByteArrayToHexString -ByteArray $TCPBuffer -Width '' -Delimiter ' '

    #Write-Host $PortDataHex

    # Convert the hex array to a hex string and cutout the bytes

    [string]$PortDataHexLine = ''

    [string]$MISOFrameSerial = ''

    [string]$DeviceSerialNumber = ''

    [string]$PortDataHexLine = $PortDataHex -join ''

    [string]$MISOFrameSerial = $PortDataHexLine.Replace(' ','')

    $MISOFrameSerialOnly = $MISOFrameSerial.Substring(12,32)

    Write-Host $MISOFrameSerial

    Write-Host $MISOFrameSerialOnly

    $MISOFrameSerialOnlyByteArray = Convert-HexStringToByteArray -String $MISOFrameSerialOnly

    $MISOFrameSerialOnlyHexArray = $MISOFrameSerialOnlyByteArray | ForEach-Object { ("{0:X2}" -f $_) }

    $MISOFrameSerialOnlyASCIIArray = $MISOFrameSerialOnlyHexArray | ForEach {[char]([convert]::toint16($_,16))}

    [string]$DeviceSerialNumber = [system.String]::Join("", $MISOFrameSerialOnlyASCIIArray)

    Write-Host $DeviceSerialNumber

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Serial Number = ' + $DeviceSerialNumber | Add-Content -Path $MISOLog

    Write-Output 'Serial Number red'

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Serial Number red' | Add-Content -Path $MISOLog

    New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "Serial Number" -PropertyType String -Value $DeviceSerialNumber -Force

 

   

    # Start Measurement (CMD: 0x00)

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement starting...' | Add-Content -Path $MISOLog

    # Create an empty char array and discard the COM port read buffer

    $PortDataByteArrayStartM = $null

    $PortDataByteArrayStartM = New-Object System.Collections.ArrayList # high performance array

    Write-Output 'Measurement starting...'

    [Byte[]] $request = 0x7E, 0x00, 0x00, 0x02, 0x01, 0x03, 0xF9, 0x7E

    $TCPSent = $TCPSocket.Send($request)

    Start-Sleep -Milliseconds 100

    # Read received bytes

    IF($TCPSocket.Available)

        {

        $TCPBuffer = New-Object System.Byte[] 32

        $TCPReceive = $TCPSocket.Receive($TCPBuffer)

        Write-Host "TCP data received:" $TCPBuffer

        }

    # Convert the char array to a hex array

    $PortDataHex = $null

    $PortDataHex = New-Object System.Collections.ArrayList # high performance array

    $PortDataHex = Convert-ByteArrayToHexString -ByteArray $TCPBuffer -Width '' -Delimiter ' '

    #Write-Host $PortDataHex

    # Convert the hex array to a hex string and cutout the bytes

    [string]$PortDataHexLine = ''

    [string]$MISOFrameStartM = ''

    [string]$PortDataHexLine = $PortDataHex -join ''

    [string]$MISOFrameStartM = $PortDataHexLine.Replace(' ','')

    Write-Host $MISOFrameStartM

    Write-Output 'Measurement started'

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement started' | Add-Content -Path $MISOLog

 

 

    # Wait of the sensor to be ready after start

    #Start-Sleep -Seconds 5

 

 

    # Get sensor measured values every 5sec.

    $getdataloop = 1

    Do {

    # Read measured values (CMD: 0x03), send the MOSI command

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement reading...' | Add-Content -Path $MISOLog

    # Create an empty char array and discard the COM port read buffer

    $PortDataByteArrayReadM = $null

    $PortDataByteArrayReadM = New-Object System.Collections.ArrayList # high performance array

    Write-Output 'Read Measurement'

    [Byte[]] $request = 0x7E, 0x00, 0x03, 0x00, 0xFC, 0x7E

    $TCPSent = $TCPSocket.Send($request)

    Start-Sleep -Milliseconds 100

    # Read received bytes

    IF($TCPSocket.Available)

        {

        $TCPBuffer = New-Object System.Byte[] 64

        $TCPReceive = $TCPSocket.Receive($TCPBuffer)

        Write-Host "TCP data received:" $TCPBuffer

        }

    # Cut empty end bytes   

    $TCPBufferCut = $TCPBuffer[0..([int]$TCPReceive-1)]

    # Convert the char array to a hex array

    $PortDataHex = $null

    $PortDataHex = New-Object System.Collections.ArrayList # high performance array

    $PortDataHex = Convert-ByteArrayToHexString -ByteArray $TCPBufferCut -Width '' -Delimiter '0x'

    #Write-Host $PortDataHex

 

    # Convert the hex array to a hex string and cut out the bytes

    [string]$PortDataHexLine = ''

    [string]$MISOFrameReadM = ''

    [string]$MISOFrameReadMBS = ''

    [string]$PortDataHexLine = $PortDataHex -join ','

    [string]$MISOFrameReadM = $PortDataHexLine.Replace(' ','')

    [string]$MISOFrameStartByte = ''

    [string]$MISOFrameWoSUM = ''

    

    $MISOFrameReadMLog = ''

    $MISOFrameReadMLog = $MISOFrameReadM.Replace('0x','')

    $MISOFrameReadMLog = $MISOFrameReadMLog.Replace(',','')

 

    # Do byte-stuffing and cut the end of the MISO Frame

    $MISOFrameReadMBS =   $MISOFrameReadM.Replace('0x7D,0x5E','0x7E')

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace('0x7D,0x31','0x11')

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace('0x7D,0x33','0x13')

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace('0x7D,0x5D','0x7D')

    $MISOFrameReadMBSHex = $MISOFrameReadMBS.Clone()

 

    # remove 0x

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace('0x','')

    $MISOFrameReadMBS = $MISOFrameReadMBS.Replace(',','')

 

    # Cut out bytes

    $MISOFrameStartByte = ''

    $MISOFrameAddrByte = ''

    $MISOFrameCMDByte = ''

    $MISOFrameStateByte = ''

    $MISOFrameLByte = ''

    $MISOFrameCHKByte = ''

    $MISOFrameStopByte = ''

    $MISOFrameWoSUM = ''

    $MISOFrameStartByte = $MISOFrameReadMBS.Substring(0,2)

    $MISOFrameAddrByte = $MISOFrameReadMBS.Substring(2,2)

    $MISOFrameCMDByte = $MISOFrameReadMBS.Substring(4,2)

    $MISOFrameStateByte = $MISOFrameReadMBS.Substring(6,2)

    $MISOFrameLByte = $MISOFrameReadMBS.Substring(8,2)

    $MISOFrameCHKByte = $MISOFrameReadMBS.Substring(90,2)

    $MISOFrameStopByte = $MISOFrameReadMBS.Substring(92,2)

    $MISOFrameWoSUM = $MISOFrameReadMBS.Substring(2,88)

    $MISOFrameDataOnly = $MISOFrameReadMBS.Substring(10,80)

    Write-Host $MISOFrameReadM

    Write-Host $MISOFrameReadMBS

    Write-Host $MISOFrameWoSUM

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + $MISOFrameReadMLog | Add-Content -Path $MISOLog

    '' + $_Date  + '  INFO  ' + $MISOFrameReadMBS | Add-Content -Path $MISOLog

    '' + $_Date  + '  INFO  ' + $MISOFrameReadMLog | Add-Content -Path $MISOLogList

    '' + $_Date  + '  INFO  ' + $MISOFrameReadMBS | Add-Content -Path $MISOLogList

    New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "CurrentMISOFrame" -PropertyType String -Value $MISOFrameReadMBS -Force

 

    # Verify bytes

    if ($MISOFrameStopByte -eq '7E')

    {

        Write-Output ('MISO Frame End ok !')

        $MISOFramesEndGood = $true

     

        # Calculate checksum

        # hex to decimal and add together

        $MISOFrameCHKCalc = $MISOFrameWoSUM -split '(?<=\G.{2})(?=.)' | Foreach-Object {$c = 0}{$c+=[Convert]::ToByte($_,16)}{$c}

        # convert the sum to hex

        $MISOFrameCHKCalcHex = '{0:x}' -f $MISOFrameCHKCalc

        # convert the 2 hex numbers to a byte array

        #$MISOFrameCHKCalcArray = Convert-HexStringToByteArray -String $MISOFrameCHKCalcHex

        # get the LSB last significant bit from the array

        #$MISOFrameCHKCalcByte = $MISOFrameCHKCalcArray.Get(1)

        # convert the 2 hex numbers to a byte and get the LSB last significant bit

        $MISOFrameCHKCalcByte = ([Net.IPAddress]$MISOFrameCHKCalc).GetAddressBytes()[0]

        # invert the LSB

        $MISOFrameCHKCalcByteInvert = $MISOFrameCHKCalcByte -bxor 0xFF

        # convert the hex checksum of the frame into an array

        $MISOFrameCHKFrameByte = [convert]::ToInt32($MISOFrameCHKByte,16)

        Write-Host $MISOFrameCHKCalcByteInvert

        Write-Host $MISOFrameCHKFrameByte

        if ($MISOFrameCHKFrameByte -ne $MISOFrameCHKCalcByteInvert)

        {

            Write-Host "Checksum mismatch" -ForegroundColor Red

            $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

            '' + $_Date  + '  ERRO  ' + 'MISO Frame Checksum mismatch !' | Add-Content -Path $MISOLog

            '' + $_Date  + '  ERRO  ' + 'MISO Frame Checksum Calc  ' + $MISOFrameCHKCalcByteInvert | Add-Content -Path $MISOLog

            '' + $_Date  + '  ERRO  ' + 'MISO Frame Checksum Frame ' + $MISOFrameCHKFrameByte | Add-Content -Path $MISOLog

            # Count bad MISO Frames Checksum

            $BadMISOFramesCHKCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'BadMISOFramesCHKCounter'

            New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "BadMISOFramesCHKCounter" -PropertyType QWord -Value ($BadMISOFramesCHKCounter + 1) -Force

            New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "BadMISOFramesCHKTime" -PropertyType String -Value (get-date) -Force

        }

        else

        {

            Write-Host "Checksum pass" -ForegroundColor Green

            $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

            '' + $_Date  + '  INFO  ' + 'MISO Frame Checksum pass' | Add-Content -Path $MISOLog

           

            # Cut out bytes of measurement values

            $MISOFramePM1wByte = ''

            $MISOFramePM25wByte = ''

            $MISOFramePM4wByte = ''

            $MISOFramePM10wByte = ''

            $MISOFramePM05cByte = ''

            $MISOFramePM1cByte = ''

            $MISOFramePM25cByte = ''

            $MISOFramePM4cByte = ''

            $MISOFramePM10cByte = ''

            $MISOFrameSizeByte = ''

            $MISOFramePM1wByte = $MISOFrameReadMBS.Substring(10,8)

            $MISOFramePM25wByte = $MISOFrameReadMBS.Substring(18,8)

            $MISOFramePM4wByte = $MISOFrameReadMBS.Substring(26,8)

            $MISOFramePM10wByte = $MISOFrameReadMBS.Substring(34,8)

            $MISOFramePM05cByte = $MISOFrameReadMBS.Substring(42,8)

            $MISOFramePM1cByte = $MISOFrameReadMBS.Substring(50,8)

            $MISOFramePM25cByte = $MISOFrameReadMBS.Substring(58,8)

            $MISOFramePM4cByte = $MISOFrameReadMBS.Substring(66,8)

            $MISOFramePM10cByte = $MISOFrameReadMBS.Substring(74,8)

            $MISOFrameSizeByte = $MISOFrameReadMBS.Substring(82,8)

 

            # Convert bytes to float and to string

            $MISOFramePM1wByteArray = Convert-HexStringToByteArray -String $MISOFramePM1wByte

            $MISOFramePM1wByteArrayReverse = $MISOFramePM1wByteArray.Clone()

            [array]::Reverse($MISOFramePM1wByteArrayReverse)

            $MISOFramePM1wFloat = [BitConverter]::ToSingle($MISOFramePM1wByteArrayReverse, 0)

            $MISOFramePM1wString = [string]$MISOFramePM1wFloat

            Write-Output ('PM 1.0  = '+$MISOFramePM1wString +' µg/m³')

 

            $MISOFramePM25wByteArray = Convert-HexStringToByteArray -String $MISOFramePM25wByte

            $MISOFramePM25wByteArrayReverse = $MISOFramePM25wByteArray.Clone()

            [array]::Reverse($MISOFramePM25wByteArrayReverse)

            $MISOFramePM25wFloat = [BitConverter]::ToSingle($MISOFramePM25wByteArrayReverse, 0)

            $MISOFramePM25wString = [string]$MISOFramePM25wFloat

            Write-Output ('PM 2.5  = '+$MISOFramePM25wString +' µg/m³')

   

            $MISOFramePM4wByteArray = Convert-HexStringToByteArray -String $MISOFramePM4wByte

            $MISOFramePM4wByteArrayReverse = $MISOFramePM4wByteArray.Clone()

            [array]::Reverse($MISOFramePM4wByteArrayReverse)

            $MISOFramePM4wFloat = [BitConverter]::ToSingle($MISOFramePM4wByteArrayReverse, 0)

            $MISOFramePM4wString = [string]$MISOFramePM4wFloat

            Write-Output ('PM 4.0  = '+$MISOFramePM4wString +' µg/m³')

 

            $MISOFramePM10wByteArray = Convert-HexStringToByteArray -String $MISOFramePM10wByte

            $MISOFramePM10wByteArrayReverse = $MISOFramePM10wByteArray.Clone()

            [array]::Reverse($MISOFramePM10wByteArrayReverse)

            $MISOFramePM10wFloat = [BitConverter]::ToSingle($MISOFramePM10wByteArrayReverse, 0)

            $MISOFramePM10wString = [string]$MISOFramePM10wFloat

            Write-Output ('PM 10.0 = '+$MISOFramePM10wString +' µg/m³')

   

            $MISOFramePM05cByteArray = Convert-HexStringToByteArray -String $MISOFramePM05cByte

            $MISOFramePM05cByteArrayReverse = $MISOFramePM05cByteArray.Clone()

            [array]::Reverse($MISOFramePM05cByteArrayReverse)

            $MISOFramePM05cFloat = [BitConverter]::ToSingle($MISOFramePM05cByteArrayReverse, 0)

            $MISOFramePM05cString = [string]$MISOFramePM05cFloat

            Write-Output ('PM 0.5  = '+$MISOFramePM05cString +' #/cm³')

   

            $MISOFramePM1cByteArray = Convert-HexStringToByteArray -String $MISOFramePM1cByte

            $MISOFramePM1cByteArrayReverse = $MISOFramePM1cByteArray.Clone()

            [array]::Reverse($MISOFramePM1cByteArrayReverse)

            $MISOFramePM1cFloat = [BitConverter]::ToSingle($MISOFramePM1cByteArrayReverse, 0)

            $MISOFramePM1cString = [string]$MISOFramePM1cFloat

            Write-Output ('PM 1.0  = '+$MISOFramePM1cString +' #/cm³')

    

            $MISOFramePM25cByteArray = Convert-HexStringToByteArray -String $MISOFramePM25cByte

            $MISOFramePM25cByteArrayReverse = $MISOFramePM25cByteArray.Clone()

            [array]::Reverse($MISOFramePM25cByteArrayReverse)

            $MISOFramePM25cFloat = [BitConverter]::ToSingle($MISOFramePM25cByteArrayReverse, 0)

            $MISOFramePM25cString = [string]$MISOFramePM25cFloat

            Write-Output ('PM 2.5  = '+$MISOFramePM25cString +' #/cm³')

    

            $MISOFramePM4cByteArray = Convert-HexStringToByteArray -String $MISOFramePM4cByte

            $MISOFramePM4cByteArrayReverse = $MISOFramePM4cByteArray.Clone()

            [array]::Reverse($MISOFramePM4cByteArrayReverse)

            $MISOFramePM4cFloat = [BitConverter]::ToSingle($MISOFramePM4cByteArrayReverse, 0)

            $MISOFramePM4cString = [string]$MISOFramePM4cFloat

            Write-Output ('PM 4.0  = '+$MISOFramePM4cString +' #/cm³')

    

            $MISOFramePM10cByteArray = Convert-HexStringToByteArray -String $MISOFramePM10cByte

            $MISOFramePM10cByteArrayReverse = $MISOFramePM10cByteArray.Clone()

            [array]::Reverse($MISOFramePM10cByteArrayReverse)

            $MISOFramePM10cFloat = [BitConverter]::ToSingle($MISOFramePM10cByteArrayReverse, 0)

            $MISOFramePM10cString = [string]$MISOFramePM10cFloat

            Write-Output ('PM 10.0 = '+$MISOFramePM10cString +' #/cm³')

   

            $MISOFrameSizeByteArray = Convert-HexStringToByteArray -String $MISOFrameSizeByte

            $MISOFrameSizeByteArrayReverse = $MISOFrameSizeByteArray.Clone()

            [array]::Reverse($MISOFrameSizeByteArrayReverse)

            $MISOFrameSizeFloat = [BitConverter]::ToSingle($MISOFrameSizeByteArrayReverse, 0)

            $MISOFrameSizeString = [string]$MISOFrameSizeFloat

            Write-Output ('Particle Size = '+$MISOFrameSizeString +' µm')

   

            # initializing high performance write access to registry

            $REGKEY = ('SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance)

            $REGHIVE = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', 'Default')

            $REGKEY = $REGHIVE.CreateSubKey($REGKEY, $true)

            $REGKEY.SetValue('SizePM1',   $MISOFramePM1wString, 'String')

            $REGKEY.SetValue('SizePM25',  $MISOFramePM25wString, 'String')

            $REGKEY.SetValue('SizePM4',   $MISOFramePM4wString, 'String')

            $REGKEY.SetValue('SizePM10',  $MISOFramePM10wString, 'String')

            $REGKEY.SetValue('CountPM05', $MISOFramePM05cString, 'String')

            $REGKEY.SetValue('CountPM1',  $MISOFramePM1cString, 'String')

            $REGKEY.SetValue('CountPM25', $MISOFramePM25cString, 'String')

            $REGKEY.SetValue('CountPM4',  $MISOFramePM4cString, 'String')

            $REGKEY.SetValue('CountPM10', $MISOFramePM10cString, 'String')

            $REGKEY.SetValue('ParticleSize', $MISOFrameSizeString, 'String')

           

            # Count good MISO Frames

            $GoodMISOFramesCHKCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'GoodMISOFramesCHKCounter'

            New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "GoodMISOFramesCHKCounter" -PropertyType QWord -Value ($GoodMISOFramesCHKCounter + 1) -Force

        }

    }

    else

    {

        Write-Output ('MISO Frame End corrupt !')

        Write-Output ('MISO Frame End = '+$MISOFrameStopByte)

        Write-Output ('get once again data')

        $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

        '' + $_Date  + '  ERRO  ' + 'MISO Frame End corrupt !' | Add-Content -Path $MISOLog

 

        # Count bad MISO Frames End

        $BadMISOFramesEndCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'BadMISOFramesEndCounter'

        New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "BadMISOFramesEndCounter" -PropertyType QWord -Value ($BadMISOFramesEndCounter + 1) -Force

        New-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name "BadMISOFramesEndTime" -PropertyType String -Value (get-date) -Force

        $MISOFramesEndGood = $false

    }

 

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement read' | Add-Content -Path $MISOLog

 

    $getdataloop++

    #Start-Sleep -Milliseconds 2000  # enable this when to run in loop

}

    While ($getdataloop -lt 0)       # set 0 to higer value when to run in loop

 

 

    <#

    # Stop Measurement (CMD: 0x01)

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement stopping...' | Add-Content -Path $MISOLog

    # Create an empty char array and discard the COM port read buffer

    $PortDataByteArrayStopM = $null

    $PortDataByteArrayStopM = New-Object System.Collections.ArrayList # high performance array

    Write-Output 'Measurement stopping...'

    [Byte[]] $request = 0x7E, 0x00, 0x01, 0x00, 0xFE, 0x7E

    $TCPSent = $TCPSocket.Send($request)

    Start-Sleep -Milliseconds 100

    # Read received bytes

    IF($TCPSocket.Available)

        {

        $TCPBuffer = New-Object System.Byte[] 32

        $TCPReceive = $TCPSocket.Receive($TCPBuffer)

        Write-Host "TCP data received:" $TCPBuffer

        }

    # Convert the char array to a hex array

    $PortDataHex = $null

    $PortDataHex = New-Object System.Collections.ArrayList # high performance array

    $PortDataHex = Convert-ByteArrayToHexString -ByteArray $TCPBuffer -Width '' -Delimiter ' '

    # Write-Host $PortDataHex

    # Convert the hex array to a hex string and cutout the bytes

    [string]$PortDataHexLine = ''

    [string]$MISOFrameStopM = ''

    [string]$PortDataHexLine = $PortDataHex -join ''

    [string]$MISOFrameStopM = $PortDataHexLine.Replace(' ','')

    Write-Host $MISOFrameStopM

    Write-Output 'Measurement stopped'

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'Measurement stopped' | Add-Content -Path $MISOLog

    #>

 

    # Uninitializing the TCP port

    $TCPSocket.Disconnect($TCPConnection)

    $TCPSocket.Close()

    $_Date = Get-Date -UFormat "%Y%m%d %H%M%S"

    '' + $_Date  + '  INFO  ' + 'TCP Port closed' | Add-Content -Path $MISOLog

    Write-Output 'TCP Port closed'

   

 

# Program Counters

$GoodMISOFramesCHKCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'GoodMISOFramesCHKCounter'

$BadMISOFramesCHKCounter = Get-ItemPropertyValue -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name 'BadMISOFramesCHKCounter'

 

# Write XML for PRTG

[string]$prtgresult=""

$prtgresult+="<?xml version=""1.0"" encoding=""Windows-1252"" ?>`r`n"

$prtgresult+="<prtg>`r`n"

$prtgresult+="    <result>`r`n"

$prtgresult+="        <channel>MISOGood</channel>`r`n"

$prtgresult+="        <mode>Absolute</mode>`r`n"

$prtgresult+="        <customunit>Frames</customunit>`r`n"

$prtgresult+="        <value>$GoodMISOFramesCHKCounter</value>`r`n"

$prtgresult+="        <float>1</float>`r`n"

$prtgresult+="    </result>`r`n"

$prtgresult+="    <result>`r`n"

$prtgresult+="        <channel>MISOBad</channel>`r`n"

$prtgresult+="        <mode>Absolute</mode>`r`n"

$prtgresult+="        <customunit>Frames</customunit>`r`n"

$prtgresult+="        <value>$BadMISOFramesCHKCounter</value>`r`n"

$prtgresult+="        <float>1</float>`r`n"

$prtgresult+="    </result>`r`n"

$prtgresult+="</prtg>"

 

if ($errorfound) {

       write-host "Error Found. Ending with EXIT Code" ([xml]$prtgresult).prtg.error

}

write-host "Sending PRTGRESULT to STDOUT"

$prtgresult

 

if ($errorfound) {

       exit ([xml]$prtgresult).prtg.error

}

 


 

 Das PowerShell Program zum lesen der Cache Daten und übergeben an PRTG:

 

# Registry Cache Reader and PRTG XML Interface (Windows PowerShell)

# written by Ginther Andreas (GLAB) 2018 http://www.the-ginthers.net/

# Version 1.0.0 (2019.02.23)

 

# Database Instance = 1

# Run in 32 bit mode

 

# -----------------------------------

 

# Initializing

$MeterInstance = '1'

$REG32 = '\SOFTWARE64'

 

 

# Get Current Value

$SizePM1 = (Get-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name ("SizePM1")).SizePM1

$SizePM25 = (Get-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name ("SizePM25")).SizePM25

$SizePM4 = (Get-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name ("SizePM4")).SizePM4

$SizePM10 = (Get-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name ("SizePM10")).SizePM10

 

 

# Write PRTG XML

[string]$prtgresult=""

$prtgresult+="<?xml version=""1.0"" encoding=""Windows-1252"" ?>`r`n"

$prtgresult+="<prtg>`r`n"

$prtgresult+="    <result>`r`n"

$prtgresult+="        <channel>PM1</channel>`r`n"

$prtgresult+="        <mode>Absolute</mode>`r`n"

$prtgresult+="        <customunit>µg/m³</customunit>`r`n"

$prtgresult+="        <value>"+$SizePM1+"</value>`r`n"

$prtgresult+="        <float>1</float>`r`n"

$prtgresult+='        <current value="'+$SizePM1+'"/>' + "`r`n"

$prtgresult+="    </result>`r`n"

$prtgresult+="    <result>`r`n"

$prtgresult+="        <channel>PM2.5</channel>`r`n"

$prtgresult+="        <mode>Absolute</mode>`r`n"

$prtgresult+="        <customunit>µg/m³</customunit>`r`n"

$prtgresult+="        <value>"+$SizePM25+"</value>`r`n"

$prtgresult+="        <float>1</float>`r`n"

$prtgresult+='        <current value="'+$SizePM25+'"/>' + "`r`n"

$prtgresult+="    </result>`r`n"

$prtgresult+="    <result>`r`n"

$prtgresult+="        <channel>PM4</channel>`r`n"

$prtgresult+="        <mode>Absolute</mode>`r`n"

$prtgresult+="        <customunit>µg/m³</customunit>`r`n"

$prtgresult+="        <value>"+$SizePM4+"</value>`r`n"

$prtgresult+="        <float>1</float>`r`n"

$prtgresult+='        <current value="'+$SizePM4+'"/>' + "`r`n"

$prtgresult+="    </result>`r`n"

$prtgresult+="    <result>`r`n"

$prtgresult+="        <channel>PM10</channel>`r`n"

$prtgresult+="        <mode>Absolute</mode>`r`n"

$prtgresult+="        <customunit>µg/m³</customunit>`r`n"

$prtgresult+="        <value>"+$SizePM10+"</value>`r`n"

$prtgresult+="        <float>1</float>`r`n"

$prtgresult+='        <current value="'+$SizePM10+'"/>' + "`r`n"

$prtgresult+="    </result>`r`n"

$prtgresult+="</prtg>"

 

if ($errorfound) {

       write-host "Error Found. Ending with EXIT Code" ([xml]$prtgresult).prtg.error

}

write-host "Sending PRTGRESULT to STDOUT"

$prtgresult

 

if ($errorfound) {

       exit ([xml]$prtgresult).prtg.error

}

 

 

Registry Datenbank

 

Als einfachste Datenbank wird die Windows Registry hergenommen.

Diese ist völlig ausreichend um nur temporär die Messdaten abzulegen.

 

Hier sind dann schon vom Power Shell Programm in aufbereiteter Form die einzelnen Werte vom MISO und Si Frame herausgelöst.

Die Frames selbst werden auch zu Debugging Zwecken immer aktuell mit abgelegt.

Markant ist das MISO Frame mit dem Byte 7E zum Beginn erkennbar.

Auch wie viele Frames empfangen wurden und wie viele fehlerhaft waren wird mit aufgezeichnet.

·         GoodMISOFramesCHKCounter

·         BadMISOFramesCHKCounter

·         GoodSiFramesCHKCounter

·         BadSiFramesCHKCounter

 

 


 

1.15 PRTG Sensoren

 

Weitere einzelne Power Shell Sensoren werden von Paessler PRTG verwendet um die Grafen zu zeichnen.

Es wird alle 30 Sekunden aufgezeichnet.

 

 

Der Sensor liefert folgende Daten:

 

·         Ping und TCP Ports

·         PM2.5 Programm Zähler

·         Temperatur und Feuchtigkeit des Sensor Gehäuseinneren

·         Partikel Anzahl pro cm³ PM0.5, 1, 2.5, 4, 10

·         Partikel Konzentration in µg pro m³ PM1, 2.5, 4, 10

·         Typische Partikel Größe in µm

 

 

 

Im Datenblatt sind genauere Erläuterungen der SPS30 Daten ersichtlich.

https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Particulate_Matter/Sensirion_PM_Sensors_SPS30_Datasheet.pdf

 


 

1.16 PRTG Grafen

 

Es lassen sich nun im 30sec. Intervall Grafen des Feinstaubsensors aufzeichnen.

Sämtliche Feinstaubwerte mit Stunden, Tage, Monat und Jahres Grafen.

 

 

 

Ebenso ist das überwachen der Betriebstemperatur mit einbezogen. Hierfür ist auch ein Power Shell Programm notwendig was über TCP die Daten abgreift und ähnlich wie mit dem SPS30 verfährt.

 

 


 

1.17 PRTG Map

 

Mit einer PRTG Map werden die Daten dann grafisch aufbereitet und könnten als Internet Seite verschiedensten Endgeräten und Browsern angezeigt werden.

 

In der PRTG Map werden auch andere Umwelt Einflüsse mit derselben Zeitachse mit dargestellt um Abhängigkeiten zu erkennen und Schüsse ziehen zu können.

 

Beispielsweise sieht man eine deutliche Abhängigkeit der Feinstaubwerte zu Windgeschwindigkeit und stehender Luft.

 

 


 

1.18 HomeMatic und Pocket Control Integration

 

Die Daten aus der Registry Cache Datenbank können nun einfach hergenommen werden und in die HomeMatic Hausautomation übertragen werden. Damit können die Werte dann auch dort angezeigt werden und weitere Programverknüpfungen und Schaltaktionen hergestellt werden.

 

Es wird dazu ein Power Shell Script im Minuten Takt laufen gelassen als Windows Task der die Werte in eine CCU Systemvariable schreibt.

 

 

Das Power Shell Programm:

 

# PM2.5 - CCU3 Connector (Windows PowerShell)

# query PM2.5 Cache Values and set CCU3 System Variables

# written by Ginther Andreas (GLAB) 2018 http://www.the-ginthers.net/

# Version 1.0.0 (2019.02.23)

 

# Database Instance = 1

# Run in 64 bit mode

 

# -----------------------------------

 

# Initializing

$MeterInstance = '1'

$REG32 = '' #'\SOFTWARE64'

$HomematicHost = "gabc1001.glab.dom"

$HomeMaticClient = New-Object System.Net.WebClient

 

# Get Current Value

$SizePM1 = (Get-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name ("SizePM1")).SizePM1

$SizePM25 = (Get-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name ("SizePM25")).SizePM25

$SizePM4 = (Get-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name ("SizePM4")).SizePM4

$SizePM10 = (Get-ItemProperty -Path ('HKLM:\SOFTWARE' + $REG32 + '\GLAB\ParticulateMeter' + $MeterInstance) -Name ("SizePM10")).SizePM10

 

$SizePM1Round = [math]::Round($SizePM1,1)

$SizePM25Round = [math]::Round($SizePM25,1)

$SizePM4Round = [math]::Round($SizePM4,1)

$SizePM10Round = [math]::Round($SizePM10,1)

 

# Set BidCos Values

$HomeMaticClient.DownloadString("http://" + $HomematicHost + "/config/xmlapi/statechange.cgi?ise_id=59156&new_value="+$SizePM25Round)

 

 

 

Die HomeMatic Systemvariable kann dann mit einem GET Request geschrieben werden.

("http://" + $HomematicHost + "/config/xmlapi/statechange.cgi?ise_id=59156&new_value="+$SizePM25Round)

 

 


 

Letztlich kann der PM2.5 Wert dann in der Pocket Control App angezeigt werden.

 

 


 

1.19 Anbringung im Außenbereich

 

Ein Gerät ist in Österreich auf dem Land installiert.

Hier sind aufgrund der Gebirgslandschaft ständig ändernde Windverhältnisse vorhanden.

 

Bei der Installation ist auf folgendes geachtet:

 

·         Keine Gefährdung durch Blitzeinschlag

·         Keiner Aussetzung von Regen

·         Nicht im direkten Wind installiert

·         Abstand von der Hausfassade wegen aufsteigender Wärmeluft

·         Von direkter Sonneneinstrahlung geschützt

·         Diebstahl- und Manipulationssicher

 

 


 

Ein weiteres Gerät ist in Stadtgebiet in München installiert.

 

Hier sind nur Saisonale stärkere Windbewegungen vorhanden, stehende Luft speziell im Hochsommer ist üblich.

 

Bei der Installation ist auf folgendes geachtet:

 

·         Keine Gefährdung durch Blitzeinschlag

·         Keiner Aussetzung von Regen

·         Nicht im direkten Wind installiert

·         Abstand von der Hausfassade wegen aufsteigender Wärmeluft

·         Von direkter Sonneneinstrahlung geschützt

·         Diebstahl- und Manipulationssicher

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

GLAB Logo