Experiments with air quality sensors
-Scripts in this directory talk to sensors and store data into influxdb.
+Scripts in this directory read from sensor serial port output (which
+is 3.3V serial connected to usb serial dongle) and store data to
+influxdb using curl.
When receiving data from sensors, they check header and checksum to
avoid sending garbage data. This also helps when you select wrong
latency can vary just enough to create empty vertical stripes in
graphs which is ugly and incorrect).
+
+Exception to that rule is dsm501.pl which is general serial to
+influx bridge used by helper shell scripts to handle sensors
+which require 5V by connecting them to Arduino-like mcu.
+Example of that is:
+
+mq7.sh
+
+which uses https://github.com/dpavlin/mq7-co-monitor/
+
+on Arduino to produce output which is then fed to dsm501.pl.
+
+
+Another strage one is audio2influx.sh which doesn't require
+any external hardware but instead report sox info about 1s
+or audio to influxdb. I'm using this to passivly monitor
+fan rotation by connecting it directly to microphone input
+on netbook and adjusting input gain to prevent clipping
+using alsamixer.
+
+To make alsamixer work, I had to remove pulseaudio.
--- /dev/null
+#!/bin/sh
+
+while true ; do
+
+arecord -d 1 -r 96000 /dev/shm/tmp.wav 2>/dev/null
+time=`date +'%s%N'`
+info=`sox /dev/shm/tmp.wav -n stat 2>&1 | grep '[0-9][0-9][0-9]*$' | sed -e 's/[()]//g' -e 's/: */=/' -e 's/ */_/g' | tee /dev/shm/audio | tr '\n' ',' | sed 's/[, ]*$//'`
+curl --silent -XPOST 'http://10.60.0.92:8086/write?consistency=any&db=rot13' --data-binary "fan,dc=trnjanska $info $time"
+
+done
--- /dev/null
+#!/bin/sh -e
+mac=A4:C1:38:90:DC:63
+test ! -z "$1" && mac=$1
+
+# based on https://ndimension.design.blog/2021/12/16/reading-data-from-xiaomi-mi-temperature-and-humidity-monitor-2-lywsd03mmc/
+# find sensor with: sudo hcitool lescan
+# A4:C1:38:D8:3F:9C LYWSD03MMC
+
+gatttool -b $mac --char-write-req --handle='0x0038' --value="0100" --listen | \
+ awk 'BEGIN { OFS=","; ORS="\n" } /value:/ { print "temperature=" strtonum("0x"$7$6) / 100, "humidity=" strtonum("0x"$8), "battery=" strtonum("0x"$10$9) / 1000 ; fflush() }' | \
+ xargs -i curl --silent -XPOST 'http://10.60.0.92:8086/write?consistency=any&db=rot13' --data-binary "mijia,dc=trnjanska,mac=$mac {}"
+
--- /dev/null
+#!/bin/sh -xe
+
+apt install libdevice-serialport-perl libdata-dump-perl
--- /dev/null
+#!/usr/bin/perl
+use warnings;
+use strict;
+# sudo apt install libdevice-serialport-perl libdata-dump-perl
+use Device::SerialPort;
+use Time::HiRes;
+use Data::Dump qw(dump);
+
+my $port = shift @ARGV || '/dev/serial/by-path/platform-3f980000.usb-usb-0:1.3:1.0-port0';
+my $influx_url = shift @ARGV || 'http://192.168.3.40:8086/write?consistency=any&db=rot13';
+my $measurement = $ENV{MEASUREMENT} || 'dsm501';
+
+my $s = new Device::SerialPort( $port ) || die $!;
+$s->baudrate(115200);
+$s->databits(8);
+$s->parity('none');
+$s->stopbits(1);
+$s->handshake('none');
+$s->read_char_time(0); # don't wait for each char
+$s->read_const_time(200); # ms for complete read
+
+
+while (1) {
+
+ my ($len, $string) = $s->read(128);
+ my $t = int( Time::HiRes::time() * 1_000_000_000 );
+ die $! if ! defined($len);
+ if ( $len > 0 ) {
+ #warn "# len=$len ",dump($string);
+ if ( $string !~ m/^#/ ) {
+ $string =~ s/[\r\n]+$//;
+ $string =~ s/\s/,/g;
+ my $influx = "$measurement,dc=trnjanska $string $t";
+ print "$influx\n" if -e '/dev/shm/air-debug';
+ system "curl --silent -XPOST '$influx_url' --data-binary '$influx'"
+ } else {
+ warn "SKIP: $string\n";
+ }
+ }
+
+};
+
+
+$s->close;
--- /dev/null
+#!/bin/sh -xe
+
+cd /nuc/air-quality/
+MEASUREMENT=dust ./dsm501.pl /dev/ttyACM0
--- /dev/null
+#!/usr/bin/perl
+use warnings;
+use strict;
+
+use Time::HiRes;
+
+# sudo apt install libiio-utils mosquitto-clients
+
+my $influx_url = shift @ARGV || 'http://192.168.3.40:8086/write?consistency=any&db=rot13';
+
+my $delay = $ENV{DELAY} || 1;
+
+my $hostname = `hostname -s`;
+chomp($hostname);
+
+while(1) {
+
+ my $t = Time::HiRes::time;
+ my $t_influx = int( $t * 1_000_000_000 );
+
+ my $iio = `iio_info`;
+
+ my $device;
+ my $name;
+
+ my $influx = '';
+
+ foreach ( split(/\n/, $iio) ) {
+ if ( m/iio:device\d+:\s+(\S+)/ ) {
+ $device = $1;
+
+ if ( $influx =~ m/,$/ ) {
+ $influx =~ s/,$/ $t_influx\n/;
+ } elsif ( $influx =~ m/ $/ ) { # only device
+ $influx =~ s/\S+\s$//;
+ }
+ $influx .= "iio,dc=trnjanska,host=$hostname,device=$device ";
+
+ } elsif ( m/(\S+):\s+\(input\)/ ) {
+ $name = $1;
+ } elsif ( m/attr\s+0:\s+input\svalue: (\d+[\.\d]+)/ ) {
+ my $val = $1;
+ if ( $val !~ m/\d+\.\d+/ ) {
+ $val = $val / 1000;
+ }
+ my $topic = "iio/$hostname/$device/$name";
+ #print "$topic $val\n";
+ system "mosquitto_pub -h rpi2 -t $topic -m $val";
+
+ $influx .= "$name=$val,";
+ } else {
+ #warn "# $_\n";
+ }
+ }
+
+ $influx =~ s/,$/ $t_influx/;
+ system "curl --silent -XPOST '$influx_url' --data-binary '$influx'";
+ warn "$influx\n";
+
+ my $dt = Time::HiRes::time + $delay - $t - 0.01;
+ if ( $dt > 0 ) {
+ sleep $dt;
+ }
+}
--- /dev/null
+#!/usr/bin/perl
+use warnings;
+use strict;
+# sudo apt install libdevice-serialport-perl libdata-dump-perl
+use Device::SerialPort;
+use Time::HiRes;
+use Data::Dump qw(dump);
+
+my $port = shift @ARGV || '/dev/serial/by-path/platform-3f980000.usb-usb-0:1.5:1.2';
+my $influx_url = shift @ARGV || 'http://192.168.3.40:8086/write?consistency=any&db=rot13';
+
+my $s = new Device::SerialPort( $port ) || die $!;
+$s->baudrate(9600);
+$s->databits(8);
+$s->parity('none');
+$s->stopbits(1);
+$s->handshake('none');
+$s->read_char_time(1);
+$s->read_const_time(10);
+$s->debug(1);
+
+$s->write("\xFF\x01\x86\x00\x00\x00\x00\x00\x79");
+
+while (1) {
+
+ my ($len, $string) = $s->read(9);
+ my $t = int( Time::HiRes::time() * 1_000_000_000 );
+ die $! if ! defined($len);
+ if ( $len > 0 ) {
+ my @v = unpack('C*', $string);
+ if ( $#v < 8 ) {
+ die "# $len ",dump($string), dump( @v ), $/;
+ }
+
+ my $sum = 0;
+ foreach my $i ( 1 .. $#v - 1 ) {
+ $sum += $v[$i];
+ $sum = $sum & 0xff;
+ }
+ $sum = 0xff - $sum + 1;
+
+ my $checksum = $v[8];
+ my $co2 = $v[2] * 255 + $v[3];
+ if ( $v[0] == 0xff && $sum == $checksum ) {
+ my $influx = "mh-z19b,dc=trnjanska co2=$co2 $t";
+ print "$influx\n" if -e '/dev/shm/air-debug';
+ system "curl --max-time 2 --silent -XPOST '$influx_url' --data-binary '$influx'";
+ system "mosquitto_pub -h rpi2 -t 'air/mh-z19b/co2' -m $co2";
+ } else {
+ die "checksum error";
+ }
+ sleep 1;
+ $s->write("\xFF\x01\x86\x00\x00\x00\x00\x00\x79");
+ }
+
+};
+
+
+$s->close;
--- /dev/null
+#!/bin/sh
+
+MEASUREMENT=mq /home/dpavlin/air-quality/dsm501.pl /dev/serial/by-path/pci-0000:00:1d.0-usb-0:2:1.0-port0 'http://10.60.0.92:8086/write?consistency=any&db=rot13' # debee
--- /dev/null
+#!/bin/sh -xe
+
+# https://github.com/dpavlin/mq7-co-monitor/
+MEASUREMENT=mq7 /home/pi/air-quality/dsm501.pl /dev/serial/by-path/platform-3f980000.usb-usb-0:1.2:1.0-port0
use Time::HiRes;
use Data::Dump qw(dump);
-my $port = shift @ARGV || '/dev/ttyUSB0';
-my $influx_url = shift @ARGV || 'http://10.13.37.229:8186/write?db=telegraf';
-$influx_url = 'http://10.13.37.92:8086/write?db=rot13';
+my $port = shift @ARGV || '/dev/serial/by-path/platform-3f980000.usb-usb-0:1.3:1.0-port0';
+my $influx_url = shift @ARGV || 'http://192.168.3.40:8086/write?consistency=any&db=rot13';
my $debug = $ENV{DEBUG} || 0;
};
$influx =~ s/,$//;
$influx .= " $t";
- print "$influx\n";
- system "curl --silent -XPOST '$influx_url' --data-binary '$influx'"
+ print "$influx\n" if -e '/dev/shm/air-debug';
+ system "curl --max-time 2 --silent -XPOST '$influx_url' --data-binary '$influx'"
}
}
--- /dev/null
+[Unit]
+Description=Air
+
+[Service]
+User=dpavlin
+ExecStart=/home/dpavlin/air-quality/dsm501.pl
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+[Unit]
+Description=Air
+
+[Service]
+User=pi
+ExecStart=/home/pi/air-quality/mh-z19b.pl
+Restart=always
+RestartSec=1s
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+[Unit]
+Description=Air
+
+[Service]
+User=dpavlin
+ExecStart=/home/dpavlin/air-quality/mq.sh
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+[Unit]
+Description=Air
+
+[Service]
+User=pi
+ExecStart=/home/pi/air-quality/mq7.sh
+Restart=on-failure
+RestartSec=2s
+
+[Install]
+WantedBy=network-online.target
--- /dev/null
+[Unit]
+Description=Air
+
+[Service]
+User=pi
+ExecStart=/home/pi/air-quality/pms3003.pl
+Restart=on-failure
+RestartSec=2s
+
+[Install]
+WantedBy=network-online.target
--- /dev/null
+[Unit]
+Description=Air
+
+[Service]
+User=pi
+ExecStart=/home/pi/air-quality/zph02.pl
+Restart=always
+RestartSec=3s
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+[Unit]
+Description=audio to influx
+
+[Service]
+User=dpavlin
+ExecStart=/home/dpavlin/air-quality/audio2influx.sh
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+[Unit]
+Description=Xiomi Mijia %i
+
+[Service]
+User=pi
+ExecStart=/home/pi/air-quality/ble-mijia.sh %i
+Restart=always
+RestartSec=3s
+
+[Install]
+WantedBy=network-online.target
--- /dev/null
+[Unit]
+Description=dust sensor
+
+[Service]
+User=dpavlin
+ExecStart=/nuc/air-quality/dust.sh
+Restart=always
+RestartSec=2s
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+[Unit]
+Description=iio2mqtt
+
+[Service]
+User=dpavlin
+ExecStart=/home/dpavlin/air-quality/iio2mqtt.pl
+Restart=always
+RestartSec=2s
+
+[Install]
+WantedBy=multi-user.target
use Time::HiRes;
use Data::Dump qw(dump);
-my $port = shift @ARGV || '/dev/ttyUSB0';
-my $influx_url = shift @ARGV || 'http://10.13.37.229:8186/write?db=telegraf';
-$influx_url = 'http://10.13.37.92:8086/write?db=rot13';
+my $port = shift @ARGV || '/dev/serial/by-path/platform-3f980000.usb-usb-0:1.5:1.0';
+my $influx_url = shift @ARGV || 'http://192.168.3.40:8086/write?consistency=any&db=rot13';
my $s = new Device::SerialPort( $port ) || die $!;
$s->baudrate(9600);
while (1) {
+ alarm 3;
+ # Usb serial which I'm using is buggy and blocks from time to time.
+ # This will ensure that we have passed here every 3 seconds
+ # or we will be killed and systemd will restart us
+
my ($len, $string) = $s->read(9);
my $t = int( Time::HiRes::time() * 1_000_000_000 );
die $! if ! defined($len);
my $checksum = $v[8];
my $pcnt = $v[3] + ( $v[4] / 100 );
- if ( $v[0] == 0xff && $sum == $checksum ) {
+ if ( $v[0] == 0xff && $sum == $checksum && $pcnt > 0) {
my $influx = "zph02,dc=trnjanska pm25_pcnt=$pcnt $t";
- print "$influx\n";
- system "curl --silent -XPOST '$influx_url' --data-binary '$influx'"
+ print "$influx\n" if -e '/dev/shm/air-debug';
+ system "curl --max-time 2 --silent -XPOST '$influx_url' --data-binary '$influx'";
+ system "mosquitto_pub -h rpi2 -t 'air/zph02/pm25' -m $pcnt";
}
}