Projekt
Smart Particulate Matter Sensor SPS30
Version 1.0
Dokument Version:
Version:
1.0
Datum:
2019.03.17
Autor:
GINTHER, Andreas
Sprache:
Deutsch
GLAB:
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:
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.
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.
Inhaltsverzeichnis
1.1
SPS30 UART Feinstaub Sensor
1.2
Si7021 UART Temperatur Sensor
1.5
GLAB PMS – LAN / UART Interface Schaltung
1.6
GLAB PMS – LAN / UART Interface Platine
1.7
GLAB Feinstaub Sensor Halterung und externe Anschlüsse
1.11 UART – LAN Modul Konfiguration
1.18
HomeMatic und Pocket
Control Integration..
1.19
Anbringung im Außenbereich
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
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.
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
• 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)
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.
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.
3,3V, 5V, PoE, MCU, UART Ports, Konnektivität,
Signalisierung und Bedienelemente werden verdrahtet.
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.
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.
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.
Die Luft wird mit so wenig wie möglich Wiederständen an
den Sensor geführt.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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
|