From 7c6887eaaf812b63bab6c5e134f80a2ef36aeb31 Mon Sep 17 00:00:00 2001 From: fishsoupisgood Date: Tue, 12 Jan 2021 16:58:31 +0000 Subject: works --- .gitignore | 1 + boiler-monster/mr3020/etc/stm32/startup | 5 + boiler-monster/mr3020/usr/bin/thermostat | 14 +- heating-cgi/.gitignore | 3 + heating-cgi/Makefile | 22 + heating-cgi/boiler_rx | 65 ++ heating-cgi/heating.cgi | 707 +++++++++++++++++++++ heating-cgi/run_boiler_rx | 6 + munin/.gitignore | 1 - munin/boiler | 552 ---------------- munin/boiler_rx | 65 -- prometheus/.gitignore | 3 + prometheus/Makefile | 11 + .../html/weatherstation/updateweatherstation.php | 1 + .../html/weatherstation/updateweatherstation.pl | 83 +++ prometheus/usr/local/bin/make_habitable | 11 + prometheus/usr/local/bin/midnight_tidy | 15 + prometheus/usr/local/bin/set_radiator_target | 47 ++ prometheus/usr/local/bin/vent | 17 + radiator-plc/stm32/Makefile.rules | 2 +- radiator-plc/stm32/app/Makefile | 9 +- radiator-plc/stm32/app/control.c | 114 ---- radiator-plc/stm32/app/logic.c | 178 ++++-- radiator-plc/stm32/app/main.c | 4 +- radiator-plc/stm32/app/project.h | 1 + radiator-plc/stm32/app/prototypes.h | 1 + radiator-plc/stm32/app/rain.c | 100 --- radiator-plc/stm32/app/ticker.c | 5 +- set_radiator_target | 10 - tasmota-config/configure-radiators | 12 +- watch | 6 + 31 files changed, 1168 insertions(+), 903 deletions(-) create mode 100644 .gitignore create mode 100644 heating-cgi/.gitignore create mode 100644 heating-cgi/Makefile create mode 100755 heating-cgi/boiler_rx create mode 100755 heating-cgi/heating.cgi create mode 100755 heating-cgi/run_boiler_rx delete mode 100644 munin/.gitignore delete mode 100755 munin/boiler delete mode 100755 munin/boiler_rx create mode 100644 prometheus/.gitignore create mode 100644 prometheus/Makefile create mode 120000 prometheus/home/httpd/html/weatherstation/updateweatherstation.php create mode 100755 prometheus/home/httpd/html/weatherstation/updateweatherstation.pl create mode 100755 prometheus/usr/local/bin/make_habitable create mode 100755 prometheus/usr/local/bin/midnight_tidy create mode 100755 prometheus/usr/local/bin/set_radiator_target create mode 100755 prometheus/usr/local/bin/vent delete mode 100644 radiator-plc/stm32/app/control.c delete mode 100644 radiator-plc/stm32/app/rain.c delete mode 100755 set_radiator_target create mode 100755 watch diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb96c79 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +NOT diff --git a/boiler-monster/mr3020/etc/stm32/startup b/boiler-monster/mr3020/etc/stm32/startup index 9422745..ca19e1d 100755 --- a/boiler-monster/mr3020/etc/stm32/startup +++ b/boiler-monster/mr3020/etc/stm32/startup @@ -3,7 +3,11 @@ logger -t stm32 "startup" LOCK=/var/lock/LCK..ttyATH0 +LOCK2=/var/lock/LCK..ttyATH0.stm32 echo $$ > ${LOCK}.tmp +mv -f ${LOCK}.tmp ${LOCK} +echo $$ > ${LOCK2}.tmp +mv -f ${LOCK2}.tmp ${LOCK2} #killall -9 ser2net @@ -48,3 +52,4 @@ logger -t stm32 "Booting" stm32flash -g 0 "${PORT}" rm -f ${LOCK} +rm -f ${LOCK2} diff --git a/boiler-monster/mr3020/usr/bin/thermostat b/boiler-monster/mr3020/usr/bin/thermostat index 1689964..af99a4a 100755 --- a/boiler-monster/mr3020/usr/bin/thermostat +++ b/boiler-monster/mr3020/usr/bin/thermostat @@ -1,10 +1,12 @@ #!/bin/sh -LOCK=/var/lock/LCK..ttyATH0 +LOCK=/var/lock/LCK..ttyATH0.stm32 M=10.32.139.1 WATERS=0 -MAX=70 +MAX=65 + +OUTSIDE="$(mosquitto_sub -t tele/weather/tempc -h ${M} -W 1 -C 1 | sed -e 's/\..*$//g') " # conventions # POWER is power to valve coil @@ -30,13 +32,15 @@ done #... others where we care about the delta -for i in kstudy_radiator bedroom_radiator spare_bedroom_radiator dd_radiator1 dd_radiator2 dd_radiator3 hall_radiator kitchen_radiator music_room_radiator; do +for i in kstudy_radiator bedroom_radiator spare_bedroom_radiator dd_radiator1 dd_radiator2 dd_radiator3 hall_radiator kitchen_radiator music_room_radiator 2fl_stair_radiator 2fl_main_radiator; do O="$(mosquitto_sub -t stat/$i/OPEN -h ${M} -W 1 -C 1)" - D="$(mosquitto_sub -t stat/$i/DELTA -h ${M} -W 1 -C 1 | sed -e 's/\..*$//g') " + T="$(mosquitto_sub -t stat/$i/TEMPERATURE -h ${M} -W 1 -C 1 | sed -e 's/\..*$//g') " + + D="$(expr $T - $OUTSIDE)" W=0 if [ "$O" == "1" ]; then - W=$[ $D * 3 + 40] + W="$( expr \( \( \( $D * 2 \) / 3 \) + 55 \) )" fi logger -t thermostat " $i O=$O D=$D W=$W" diff --git a/heating-cgi/.gitignore b/heating-cgi/.gitignore new file mode 100644 index 0000000..574889a --- /dev/null +++ b/heating-cgi/.gitignore @@ -0,0 +1,3 @@ +*.stamp +*.bak +*.swp diff --git a/heating-cgi/Makefile b/heating-cgi/Makefile new file mode 100644 index 0000000..7a7bc3b --- /dev/null +++ b/heating-cgi/Makefile @@ -0,0 +1,22 @@ + +foo: other.stamp prometheus.stamp + +SRC=heating.cgi boiler_rx run_boiler_rx + + +other.stamp: ${SRC} + scp heating.cgi other:/export/home/httpd/heating/cgi-bin/index.cgi + scp boiler_rx run_boiler_rx other:/usr/local/sbin/ + touch $@ + +prometheus.stamp: ${SRC} + scp heating.cgi prometheus:/home/httpd/html/heating.cgi + scp boiler_rx run_boiler_rx dhoma:/usr/local/sbin/ + touch $@ + +clean: + /bin/rm -f *.bak + +tidy: + perltidy -b heating.cgi boiler_rx + diff --git a/heating-cgi/boiler_rx b/heating-cgi/boiler_rx new file mode 100755 index 0000000..777b3ee --- /dev/null +++ b/heating-cgi/boiler_rx @@ -0,0 +1,65 @@ +#!/usr/bin/env perl + +use Net::Telnet; +use IO::File; + +sub dump_file($$) { + my ( $name, $guts ) = @_; + + my $fh = new IO::File ">" . $name; + return unless defined $fh; + $fh->print($guts); + $fh->close; + undef $fh; +} + +my $timeout = 60; + +my $host = $ARGV[0]; + +my $path = "/var/run/boiler_" . $host; + +mkdir( $path, 0755 ); + +while (1) { + my $telnet = new Net::Telnet( + Timeout => 10, + Telnetmode => 0, + Binmode => 1, + Host => $host, + Port => 2001 + ); + + #$telnet->open($bits[1]); + + my $nodata = 0; + + while ( ( not $telnet->eof ) and ( $nodata < $timeout ) ) { + my $line = $telnet->getline( Timeout => 1, Errmode => 'return' ); + + if ( not defined $line or ( length($line) == 0 ) ) { + $nodata++; + } + else { + $nodata = 0; + } + + chomp $line; + chomp $line; + + $line = uc($line); + + next unless $line =~ /^([TB])(.)(.)(..)(....)/; + + my $fn = $1 . $2 . "x" . $4; + my $c = $1 . $2 . $3 . $4 . $5; + + dump_file( $path . "/" . $fn, $c ); + + } + + $telnet->close; + +} + +exit(0); diff --git a/heating-cgi/heating.cgi b/heating-cgi/heating.cgi new file mode 100755 index 0000000..88474dd --- /dev/null +++ b/heating-cgi/heating.cgi @@ -0,0 +1,707 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Net::MQTT::Simple; +use JSON::Parse; + +use Data::Dumper; + +use CGI qw/:standard :cgi-lib/; +use CGI::Carp qw(fatalsToBrowser); + +package Boiler { + + use IO::File; + + #0 Status => 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + #17 Relative modulation level => 0 + #18 CH water pressure => 1.14453125 + #25 Boiler water temperature => 53 + #26 DHW temperature => 49 + #28 Return water temperature => 48.375 + #57 Max CH water setpoint => 80 + #80 Supply inlet temperature => 21.25 + #256 Status => 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + #257 Control setpoint => 7 + #270 Maximum relative modulation level => 100 + #272 Room setpoint => 18 + #280 Room temperature => 26.7265625 + #312 DHW setpoint => 60 + + my $ot_registers = { + 0 => { + format => ["flag16"], + name => "Status", + names => [ + '', '', + '', '', + '', '', + 'DHW enabled', 'CH enabled', + '', 'diagnostic indication', + '', '', + 'Flame', 'DHW active', + 'CH active', 'fault' + ], + warnings => [ + undef, undef, undef, undef, undef, undef, undef, undef, + undef, undef, undef, undef, undef, undef, undef, '1' + ] + }, + 1 => { + format => ["f8.8"], + name => "Control setpoint", + units => 'C', + graphs => { '1' => '', '3' => '' } + }, + 2 => { format => [ "flag8", "u8" ], name => "Master configuration" }, + 3 => { format => [ "flag8", "u8" ], name => "Slave configuration" }, + 4 => { format => [ "u8", "u8" ], name => "Remote command" }, + 5 => + { format => [ "flag8", "u8" ], name => "Application-specific flags" }, + 6 => + { format => [ "flag8", "flag8" ], name => "Remote parameter flags" }, + 7 => { format => ["f8.8"], name => "Cooling control signal" }, + 8 => { format => ["f8.8"], name => "Control setpoint 2" }, + 9 => { format => ["f8.8"], name => "Remote override room setpoint" }, + 10 => { format => [ "u8", "nu" ], name => "Number of TSPs" }, + 11 => { format => [ "u8", "u8" ], name => "TSP setting" }, + 12 => { format => [ "u8", "nu" ], name => "Size of fault buffer" }, + 13 => { format => [ "u8", "u8" ], name => "Fault buffer entry" }, + 14 => { + format => ["f8.8"], + name => "Maximum relative modulation level", + units => '%' + }, + 15 => { + format => [ "u8", "u8" ], + name => "Boiler capacity and modulation limits" + }, + 16 => { + format => ["f8.8"], + name => "Room setpoint", + units => 'C', + graphs => { '3' => '' } + }, + 17 => { + format => ["f8.8"], + name => "Relative modulation level", + units => '%' + }, + 18 => { + format => ["f8.8"], + name => "CH water pressure", + units => 'bar', + warning => '0.9:2.6', + critical => '0.8:2.8' + }, + 19 => { format => ["f8.8"], name => "DHW flow rate" }, + 20 => { format => ["time"], name => "Day of week and time of day" }, + 21 => { format => ["date"], name => "Date" }, + 22 => { format => ["u16"], name => "Year" }, + 23 => { format => ["f8.8"], name => "Room Setpoint CH2" }, + 24 => { + format => ["f8.8"], + name => "Room temperature", + units => 'C', + graphs => { '3' => '' } + }, + 25 => { + format => ["f8.8"], + name => "Boiler water temperature", + units => 'C', + graphs => { '1' => '', '2' => '' } + }, + 26 => { + format => ["f8.8"], + name => "DHW temperature", + units => 'C', + graphs => { '2' => '' } + }, + 27 => { format => ["f8.8"], name => "Outside temperature" }, + 28 => { + format => ["f8.8"], + name => "Return water temperature", + units => 'C', + graphs => { '1' => '' } + }, + 29 => { format => ["f8.8"], name => "Solar storage temperature" }, + 30 => { format => ["f8.8"], name => "Solar collector temperature" }, + 31 => { format => ["f8.8"], name => "Flow temperature CH2" }, + 32 => { format => ["f8.8"], name => "DHW2 temperature" }, + 33 => { format => ["s16"], name => "Exhaust temperature" }, + 34 => + { format => ["f8.8"], name => "Boiler heat exchanger temperature" }, + 35 => + { format => [ "u8", "u8" ], name => "Boiler fan speed and setpoint" }, + 48 => { format => [ "s8", "s8" ], name => "DHW setpoint boundaries" }, + 49 => + { format => [ "s8", "s8" ], name => "Max CH setpoint boundaries" }, + 50 => { + format => [ "s8", "s8" ], + name => "OTC heat curve ratio boundaries" + }, + 51 => + { format => [ "s8", "s8" ], name => "Remote parameter 4 boundaries" }, + 52 => + { format => [ "s8", "s8" ], name => "Remote parameter 5 boundaries" }, + 53 => + { format => [ "s8", "s8" ], name => "Remote parameter 6 boundaries" }, + 54 => + { format => [ "s8", "s8" ], name => "Remote parameter 7 boundaries" }, + 55 => + { format => [ "s8", "s8" ], name => "Remote parameter 8 boundaries" }, + 56 => { + format => ["f8.8"], + name => "DHW setpoint", + units => 'C', + graphs => { '2' => '' } + }, + 57 => { + format => ["f8.8"], + name => "Max CH water setpoint", + units => 'C', + graphs => { '1' => '' } + }, + 58 => { format => ["f8.8"], name => "OTC heat curve ratio" }, + 59 => { format => ["f8.8"], name => "Remote parameter 4" }, + 60 => { format => ["f8.8"], name => "Remote parameter 5" }, + 61 => { format => ["f8.8"], name => "Remote parameter 6" }, + 62 => { format => ["f8.8"], name => "Remote parameter 7" }, + 63 => { format => ["f8.8"], name => "Remote parameter 8" }, + 70 => { format => [ "flag8", "flag8" ], name => "Status V/H" }, + 71 => { format => [ "nu", "u8" ], name => "Control setpoint V/H" }, + 72 => { format => [ "flag8", "u8" ], name => "Fault flags/code V/H" }, + 73 => { format => ["u16"], name => "OEM diagnostic code V/H" }, + 74 => + { format => [ "flag8", "u8" ], name => "Configuration/memberid V/H" }, + 75 => { format => ["f8.8"], name => "OpenTherm version V/H" }, + 76 => { format => [ "u8", "u8" ], name => "Product version V/H" }, + 77 => { format => [ "nu", "u8" ], name => "Relative ventilation" }, + 78 => + { format => [ "u8", "u8" ], name => "Relative humidity exhaust air" }, + 79 => { format => ["u16"], name => "CO2 level exhaust air" }, + 80 => { + format => ["f8.8"], + name => "Supply inlet temperature", + units => 'C', + graphs => { '2' => '' } + }, + 81 => { format => ["f8.8"], name => "Supply outlet temperature" }, + 82 => { format => ["f8.8"], name => "Exhaust inlet temperature" }, + 83 => { format => ["f8.8"], name => "Exhaust outlet temperature" }, + 84 => { format => ["u16"], name => "Exhaust fan speed" }, + 85 => { format => ["u16"], name => "Inlet fan speed" }, + 86 => { + format => [ "flag8", "flag8" ], + name => "Remote parameter settings V/H" + }, + 87 => { format => [ "u8", "nu" ], name => "Nominal ventilation value" }, + 88 => { format => [ "u8", "nu" ], name => "Number of TSPs V/H" }, + 89 => { format => [ "u8", "u8" ], name => "TSP setting V/H" }, + 90 => { format => [ "u8", "nu" ], name => "Size of fault buffer V/H" }, + 91 => { format => [ "u8", "u8" ], name => "Fault buffer entry V/H" }, + 100 => + { format => [ "nu", "flag8" ], name => "Remote override function" }, + 101 => { + format => [ "flag8", "flag8" ], + name => "Solar storage mode and status" + }, + 102 => + { format => [ "flag8", "u8" ], name => "Solar storage fault flags" }, + 103 => { + format => [ "flag8", "u8" ], + name => "Solar storage config/memberid" + }, + 104 => + { format => [ "u8", "u8" ], name => "Solar storage product version" }, + 105 => + { format => [ "u8", "nu" ], name => "Number of TSPs solar storage" }, + 106 => + { format => [ "u8", "u8" ], name => "TSP setting solar storage" }, + 107 => { + format => [ "u8", "u8" ], + name => "Size of fault buffer solar storage" + }, + 108 => { + format => [ "u8", "u8" ], + name => "Fault buffer entry solar storage" + }, + 113 => { format => ["u16"], name => "Unsuccessful burner starts" }, + 114 => { format => ["u16"], name => "Flame signal too low count" }, + 115 => { format => ["u16"], name => "OEM diagnostic code" }, + 116 => { format => ["u16"], name => "Burner starts" }, + 117 => { format => ["u16"], name => "CH pump starts" }, + 118 => { format => ["u16"], name => "DHW pump/valve starts" }, + 119 => { format => ["u16"], name => "DHW burner starts" }, + 120 => { format => ["u16"], name => "Burner operation hours" }, + 121 => { format => ["u16"], name => "CH pump operation hours" }, + 122 => { format => ["u16"], name => "DHW pump/valve operation hours" }, + 123 => { format => ["u16"], name => "DHW burner operation hours" }, + 124 => { format => ["f8.8"], name => "OpenTherm version Master" }, + 125 => { format => ["f8.8"], name => "OpenTherm version Slave" }, + 126 => { format => [ "u8", "u8" ], name => "Master product version" }, + 127 => { format => [ "u8", "u8" ], name => "Slave product version" }, + }; + + my $ot_types = { + 0 => "read data", + 1 => "write data", + 2 => "invalidate data", + 3 => "unknown 0x3", + 4 => "read ack", + 5 => "write ack", + 6 => "data invalid", + 7 => "unknown 0x7" + }; + + sub fetch_file($) { + my ($name) = @_; + + my $fh = new IO::File "<" . $name; + return undef unless defined $fh; + local $/; + my $guts = $fh->getline; + $fh->close; + undef $fh; + + return $guts; + } + + sub decode($$$) { + my ( $dataref, $string, $offset ) = @_; + my $b0 = hex( substr( $string, 0, 2 ) ); + + my $type = ( $b0 >> 4 ) & 7; + my $id = hex( substr( $string, 2, 2 ) ); + + my $data = pack( 'H4', substr( $string, 4, 4 ) ); + my $format = ["u16"]; + + my $ret = { + id => $id, + fake_id => $id + $offset, + type => $type, + type_str => $ot_types->{$type}, + }; + + my $bits = undef; + + if ( exists $ot_registers->{$id} ) { + $ret->{id_str} = $ot_registers->{$id}->{name}; + $ret->{units} = $ot_registers->{$id}->{units}; + $format = $ot_registers->{$id}->{format}; + $bits = $ot_registers->{$id}->{names}; + } + + $ret->{format} = join( ',', @$format ); + $ret->{raw_hex} = substr( $string, 4, 4 ); + $ret->{raw} = $data; + + $ret->{values} = []; + for my $f (@$format) { + + if ( $f eq "u16" ) { + push @{ $ret->{values} }, unpack( 'S>', $data ); + $data = substr( $data, 2 ); + } + elsif ( $f eq 's16' ) { + push @{ $ret->{values} }, unpack( 's>', $data ); + $data = substr( $data, 2 ); + } + elsif ( $f eq 'u8' ) { + push @{ $ret->{values} }, unpack( 'C', $data ); + $data = substr( $data, 1 ); + } + elsif ( $f eq 's8' ) { + push @{ $ret->{values} }, unpack( 'c', $data ); + $data = substr( $data, 1 ); + } + elsif ( $f eq 'nu' ) { + $data = substr( $data, 1 ); + } + elsif ( $f eq 'flag16' ) { + push @{ $ret->{values} }, + ( unpack( "(A)*", unpack( "B16", $data ) ) ); + $data = substr( $data, 2 ); + } + elsif ( $f eq 'flag8' ) { + push @{ $ret->{values} }, + ( unpack( "(A)*", unpack( "B8", $data ) ) ); + $data = substr( $data, 1 ); + } + elsif ( $f eq 'f8.8' ) { + push @{ $ret->{values} }, unpack( 's>', $data ) / 256.0; + } + + } + + $dataref->{ $id + $offset } = $ret; + + if ( defined $bits ) { + my @vs = ( @{ $ret->{values} } ); + my $bit = scalar( @{ $ret->{values} } ); + + for my $label (@$bits) { + $bit--; + my $v = shift(@vs); + next unless length($label) > 0; + my $key = sprintf( "%d.%d", $id + $offset, $bit ); + $ret = {}; + + $ret->{id} = sprintf( "%d.%d", $id, $bit ); + $ret->{fake_id} = $key; + $ret->{type} = $type; + $ret->{type_str} = $ot_types->{$type}; + $ret->{id_str} = $label; + $ret->{values} = [$v]; + + $dataref->{$key} = $ret; + } + } + + } + + sub get_units($) { + my $id = shift; + + if ( $id =~ /^\d+$/ ) { + return '' unless exists $ot_registers->{$id}; + return '' unless exists $ot_registers->{$id}->{units}; + return $ot_registers->{$id}->{units}; + } + elsif ( $id =~ /^(\d+)\.(\d+)$/ ) { + my $base_id = $1; + my $bit = $2; + + return '' unless exists $ot_registers->{$base_id}; + return '' unless exists $ot_registers->{$base_id}->{unitss}; + + $bit = + ( scalar( @{ $ot_registers->{$base_id}->{unitss} } ) - 1 ) - $bit; + return $ot_registers->{$base_id}->{unitss}->[$bit]; + } + else { + return ''; + } + } + + sub get_name($) { + my $id = shift; + + if ( $id =~ /^\d+$/ ) { + return "Unknown $id" unless exists $ot_registers->{$id}; + return $ot_registers->{$id}->{name}; + } + elsif ( $id =~ /^(\d+)\.(\d+)$/ ) { + my $base_id = $1; + my $bit = $2; + + return "Unknown $id" unless exists $ot_registers->{$base_id}; + return "Unknown $id" + unless exists $ot_registers->{$base_id}->{names}; + + $bit = + ( scalar( @{ $ot_registers->{$base_id}->{names} } ) - 1 ) - $bit; + return $ot_registers->{$base_id}->{names}->[$bit]; + } + else { + return undef; + } + } + + my $report = [ + '0.0', '0.1', '0.2', '0.3', '0.6', '0.8', '0.9', '17', '18', '25', + '26', '28', '57', '80', '257', '270', '272', '280', '312' + ]; + + sub load($) { + my $host = shift; + my $path = "/var/run/boiler_" . $host; + + return undef unless -d $path; + + my $wanted = { map { my $q = $_; $q =~ s/\..*$//; $q => 1 } @$report }; + + my $data = {}; + + for my $t ( keys %$wanted ) { + if ( $t < 0x100 ) { + my $fn = sprintf( "%s/B4x%02X", $path, $t ); + my $guts = fetch_file($fn); + decode( $data, substr( $guts, 1 ), 0x0 ) if defined $guts; + } + else { + my $fn = sprintf( "%s/T9x%02X", $path, $t - 0x100 ); + my $guts = fetch_file($fn); + decode( $data, substr( $guts, 1 ), 0x100 ) if defined $guts; + } + } + + return $data; + } + + 1; +} + +my $mqtt_host = "10.32.139.1"; +my $mqtt_data = {}; +my $outside_temp = ""; + +sub fmt($) { + my $v = shift; + return sprintf( "%.1f", $v ); +} + +sub fmt2($) { + my $v = shift; + return sprintf( "%.2f", $v ); +} + +sub mqtt_msg($$) { + my ( $topic, $message ) = @_; + + $outside_temp = $message if $topic eq 'tele/weather/tempc'; + + return unless $topic =~ /^[^\/]+\/[^\/]+_radiator/; + return unless $topic =~ /^[^\/]+\/[^\/]+_radiator/; + return unless $topic =~ /^[^\/]+\/([^\/]+)\/(.+)$/; + + $mqtt_data->{$1}->{$2} = $message; +} + +sub do_radiators() { + my $mqtt = Net::MQTT::Simple->new($mqtt_host); + + $mqtt->subscribe( 'stat/+/+', \&mqtt_msg ); + $mqtt->subscribe( 'tele/+/SENSOR', \&mqtt_msg ); + $mqtt->subscribe( 'tele/weather/tempc', \&mqtt_msg ); + + my $then = time; + $mqtt->tick(1) while ( time - $then ) < 3; + + $mqtt->disconnect(); + + for my $r ( keys(%$mqtt_data) ) { + if ( exists $mqtt_data->{$r}->{SENSOR} ) { + my $pj = JSON::Parse::parse_json( $mqtt_data->{$r}->{SENSOR} ); + + if ( exists $pj->{'SI7021'} ) { + $mqtt_data->{$r}->{'TEMPERATURE'} = + $pj->{'SI7021'}->{'Temperature'} + if exists $pj->{'SI7021'}->{'Temperature'}; + $mqtt_data->{$r}->{'HUMIDITY'} = $pj->{'SI7021'}->{'Humidity'} + if exists $pj->{'SI7021'}->{'Humidity'}; + } + } + + if ( exists $mqtt_data->{$r}->{POWER} ) { + if ( $mqtt_data->{$r}->{POWER} =~ /off/i ) { + $mqtt_data->{$r}->{POWER} = 0; + } + else { + $mqtt_data->{$r}->{POWER} = 1; + } + } + + if ( ( $mqtt_data->{$r}->{POWER} == 0 ) + && ( $mqtt_data->{$r}->{OPEN} == 0 ) ) + { + $mqtt_data->{$r}->{state} = "closed"; + $mqtt_data->{$r}->{state_colour} = "#c0c0ff"; + } + elsif (( $mqtt_data->{$r}->{POWER} == 1 ) + && ( $mqtt_data->{$r}->{OPEN} == 0 ) ) + { + $mqtt_data->{$r}->{state} = "opening"; + $mqtt_data->{$r}->{state_colour} = "#ffe0e0"; + } + elsif (( $mqtt_data->{$r}->{POWER} == 1 ) + && ( $mqtt_data->{$r}->{OPEN} == 1 ) ) + { + $mqtt_data->{$r}->{state} = "open"; + $mqtt_data->{$r}->{state_colour} = "#ffc0c0"; + } + elsif (( $mqtt_data->{$r}->{POWER} == 0 ) + && ( $mqtt_data->{$r}->{OPEN} == 1 ) ) + { + $mqtt_data->{$r}->{state} = "closing"; + $mqtt_data->{$r}->{state_colour} = "#e0e0ff"; + } + else { + $mqtt_data->{$r}->{state} = "unknown"; + $mqtt_data->{$r}->{state_colour} = "#ffffff"; + } + + if ( $mqtt_data->{$r}->{TEMPERATURE} < $mqtt_data->{$r}->{var1} ) { + $mqtt_data->{$r}->{temp_colour} = "#c0c0ff"; + } + elsif ( $mqtt_data->{$r}->{TEMPERATURE} > $mqtt_data->{$r}->{var2} ) { + $mqtt_data->{$r}->{temp_colour} = "#ffc0c0"; + } + else { + $mqtt_data->{$r}->{temp_colour} = "#ffffff"; + } + + for my $t (qw(var1 var2 TEMPERATURE HUMIDITY DELTA)) { + next unless exists $mqtt_data->{$r}->{$t}; + + $mqtt_data->{$r}->{$t} = fmt( $mqtt_data->{$r}->{$t} ); + } + + } + + print "\n"; + print +"\n"; + for my $r ( sort( keys(%$mqtt_data) ) ) { + my $rd = $mqtt_data->{$r}; + + print ""; + print +""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + + if ( exists $rd->{failed_reads} and ( $rd->{failed_reads} > 5 ) ) { + print ""; + } + else { + print ""; + } + + for my $t (qw(10 15 18 19 20 21 22 23 24 25)) { + my $s = ""; + $s = "color: red" if $t == $rd->{var1}; + print ""; + } + + print "\n"; + } + + print "
RadiatorsLowTempHighHumidDeltaValveSensorSet target
", $r, "", $rd->{var1}, "", + $rd->{TEMPERATURE}, "", $rd->{var2}, "", $rd->{HUMIDITY}, "", $rd->{DELTA}, "", + $rd->{state}, "FailedOk"; + print submit( -name => $r, -value => $t, -style => $s ); + print "
\n"; +} + +sub do_boiler($) { + my $outside_temp = shift; + + my $boiler = Boiler::load("boiler-monster.prometheus.james.local"); + + return unless defined $boiler; + + my $url = +'http://munin.backdown.james.local/prometheus.james.local/boilermonster.prometheus.james.local/'; + print "\n"; + print "\n"; + my $pump = $boiler->{0.1}->{values}->[0]; + my $pump_colour = '#c0c0ff'; + $pump_colour = '#ffc0c0' if $pump == 1; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print ""; + print +"\n"; + print "
Boiler
CH Pump running:", $pump, + "
Target water temp:", + fmt( $boiler->{257}->{values}->[0] ), + "
Current water temp:", + fmt( $boiler->{25}->{values}->[0] ), + "
Return water temp:", + fmt( $boiler->{28}->{values}->[0] ), + "
Modulation level:", + fmt( $boiler->{17}->{values}->[0] ), + "
CH Pressure:", + fmt2( $boiler->{18}->{values}->[0] ), + "
Fault:", + $boiler->{0.0}->{values}->[0], + "
Outside temp:", + sprintf( "%.1f", $outside_temp ), "
\n"; +} + +my $css = <<'EOF'; +tr:nth-child(odd) td { + background-color: #ffffff +} +tr:nth-child(even) td { + background-color: #c0ffc0 +} +EOF + +my $all_params = Vars(); + +my $here = $ENV{SCRIPT_NAME}; + +print header( + -type => 'text/html', + -charset => 'utf-8', + -refresh => '30; url=' . $here +); + +print start_html( + -title => "heating", + -head => "" +); + +print start_form(); +do_radiators(); +print end_form(); + +print "
Refresh
\n"; + +if ( scalar( keys %$all_params ) > 0 ) { + print "

Updates

\n"; + for my $param ( keys %$all_params ) { + + my $mqtt = Net::MQTT::Simple->new($mqtt_host); + my $slop = 1; + + $slop = 2 if $param =~ /bathroom/; + + $mqtt->publish( "cmnd/" . $param . "/var1", $all_params->{$param} ); + $mqtt->publish( "cmnd/" . $param . "/var2", + $all_params->{$param} + $slop ); + $mqtt->tick(1); + $mqtt->disconnect(); + + print "Set $param to ", fmt( $all_params->{$param} ), "-", + fmt( $all_params->{$param} + $slop ), "
"; + + } + print ""; +} + +print ""; +do_boiler($outside_temp); +print end_html(); diff --git a/heating-cgi/run_boiler_rx b/heating-cgi/run_boiler_rx new file mode 100755 index 0000000..c607ab3 --- /dev/null +++ b/heating-cgi/run_boiler_rx @@ -0,0 +1,6 @@ +#!/bin/sh + +while true; do +/usr/local/sbin/boiler_rx "$@" +sleep 30 +done diff --git a/munin/.gitignore b/munin/.gitignore deleted file mode 100644 index 751553b..0000000 --- a/munin/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.bak diff --git a/munin/boiler b/munin/boiler deleted file mode 100755 index 199893c..0000000 --- a/munin/boiler +++ /dev/null @@ -1,552 +0,0 @@ -#!/usr/bin/env perl - -use Net::Telnet; -use Data::Dumper; - -#0 Status => 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -#17 Relative modulation level => 0 -#18 CH water pressure => 1.14453125 -#25 Boiler water temperature => 53 -#26 DHW temperature => 49 -#28 Return water temperature => 48.375 -#57 Max CH water setpoint => 80 -#80 Supply inlet temperature => 21.25 -#256 Status => 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -#257 Control setpoint => 7 -#270 Maximum relative modulation level => 100 -#272 Room setpoint => 18 -#280 Room temperature => 26.7265625 -#312 DHW setpoint => 60 - -my $ot_registers = { - 0 => { - format => ["flag16"], - name => "Status", - names => [ - '', '', - '', '', - '', '', - 'DHW enabled', 'CH enabled', - '', 'diagnostic indication', - '', '', - 'Flame', 'DHW active', - 'CH active', 'fault' - ], - warnings => [ - undef, undef, undef, undef, undef, undef, undef, undef, - undef, undef, undef, undef, undef, undef, undef, '1' - ] - }, - 1 => { format => ["f8.8"], name => "Control setpoint", units => 'C' }, - 2 => { format => [ "flag8", "u8" ], name => "Master configuration" }, - 3 => { format => [ "flag8", "u8" ], name => "Slave configuration" }, - 4 => { format => [ "u8", "u8" ], name => "Remote command" }, - 5 => { format => [ "flag8", "u8" ], name => "Application-specific flags" }, - 6 => { format => [ "flag8", "flag8" ], name => "Remote parameter flags" }, - 7 => { format => ["f8.8"], name => "Cooling control signal" }, - 8 => { format => ["f8.8"], name => "Control setpoint 2" }, - 9 => { format => ["f8.8"], name => "Remote override room setpoint" }, - 10 => { format => [ "u8", "nu" ], name => "Number of TSPs" }, - 11 => { format => [ "u8", "u8" ], name => "TSP setting" }, - 12 => { format => [ "u8", "nu" ], name => "Size of fault buffer" }, - 13 => { format => [ "u8", "u8" ], name => "Fault buffer entry" }, - 14 => { - format => ["f8.8"], - name => "Maximum relative modulation level", - units => '%' - }, - 15 => { - format => [ "u8", "u8" ], - name => "Boiler capacity and modulation limits" - }, - 16 => { format => ["f8.8"], name => "Room setpoint", units => 'C' }, - 17 => - { format => ["f8.8"], name => "Relative modulation level", units => '%' }, - 18 => { - format => ["f8.8"], - name => "CH water pressure", - units => 'bar', - warning => '0.9:2.6', - critical => '0.8:2.8' - }, - 19 => { format => ["f8.8"], name => "DHW flow rate" }, - 20 => { format => ["time"], name => "Day of week and time of day" }, - 21 => { format => ["date"], name => "Date" }, - 22 => { format => ["u16"], name => "Year" }, - 23 => { format => ["f8.8"], name => "Room Setpoint CH2" }, - 24 => { format => ["f8.8"], name => "Room temperature", units => 'C' }, - 25 => - { format => ["f8.8"], name => "Boiler water temperature", units => 'C' }, - 26 => { format => ["f8.8"], name => "DHW temperature", units => 'C' }, - 27 => { format => ["f8.8"], name => "Outside temperature" }, - 28 => - { format => ["f8.8"], name => "Return water temperature", units => 'C' }, - 29 => { format => ["f8.8"], name => "Solar storage temperature" }, - 30 => { format => ["f8.8"], name => "Solar collector temperature" }, - 31 => { format => ["f8.8"], name => "Flow temperature CH2" }, - 32 => { format => ["f8.8"], name => "DHW2 temperature" }, - 33 => { format => ["s16"], name => "Exhaust temperature" }, - 34 => { format => ["f8.8"], name => "Boiler heat exchanger temperature" }, - 35 => { format => [ "u8", "u8" ], name => "Boiler fan speed and setpoint" }, - 48 => { format => [ "s8", "s8" ], name => "DHW setpoint boundaries" }, - 49 => { format => [ "s8", "s8" ], name => "Max CH setpoint boundaries" }, - 50 => - { format => [ "s8", "s8" ], name => "OTC heat curve ratio boundaries" }, - 51 => { format => [ "s8", "s8" ], name => "Remote parameter 4 boundaries" }, - 52 => { format => [ "s8", "s8" ], name => "Remote parameter 5 boundaries" }, - 53 => { format => [ "s8", "s8" ], name => "Remote parameter 6 boundaries" }, - 54 => { format => [ "s8", "s8" ], name => "Remote parameter 7 boundaries" }, - 55 => { format => [ "s8", "s8" ], name => "Remote parameter 8 boundaries" }, - 56 => { format => ["f8.8"], name => "DHW setpoint", units => 'C' }, - 57 => { format => ["f8.8"], name => "Max CH water setpoint", units => 'C' }, - 58 => { format => ["f8.8"], name => "OTC heat curve ratio" }, - 59 => { format => ["f8.8"], name => "Remote parameter 4" }, - 60 => { format => ["f8.8"], name => "Remote parameter 5" }, - 61 => { format => ["f8.8"], name => "Remote parameter 6" }, - 62 => { format => ["f8.8"], name => "Remote parameter 7" }, - 63 => { format => ["f8.8"], name => "Remote parameter 8" }, - 70 => { format => [ "flag8", "flag8" ], name => "Status V/H" }, - 71 => { format => [ "nu", "u8" ], name => "Control setpoint V/H" }, - 72 => { format => [ "flag8", "u8" ], name => "Fault flags/code V/H" }, - 73 => { format => ["u16"], name => "OEM diagnostic code V/H" }, - 74 => { format => [ "flag8", "u8" ], name => "Configuration/memberid V/H" }, - 75 => { format => ["f8.8"], name => "OpenTherm version V/H" }, - 76 => { format => [ "u8", "u8" ], name => "Product version V/H" }, - 77 => { format => [ "nu", "u8" ], name => "Relative ventilation" }, - 78 => { format => [ "u8", "u8" ], name => "Relative humidity exhaust air" }, - 79 => { format => ["u16"], name => "CO2 level exhaust air" }, - 80 => - { format => ["f8.8"], name => "Supply inlet temperature", units => 'C' }, - 81 => { format => ["f8.8"], name => "Supply outlet temperature" }, - 82 => { format => ["f8.8"], name => "Exhaust inlet temperature" }, - 83 => { format => ["f8.8"], name => "Exhaust outlet temperature" }, - 84 => { format => ["u16"], name => "Exhaust fan speed" }, - 85 => { format => ["u16"], name => "Inlet fan speed" }, - 86 => { - format => [ "flag8", "flag8" ], - name => "Remote parameter settings V/H" - }, - 87 => { format => [ "u8", "nu" ], name => "Nominal ventilation value" }, - 88 => { format => [ "u8", "nu" ], name => "Number of TSPs V/H" }, - 89 => { format => [ "u8", "u8" ], name => "TSP setting V/H" }, - 90 => { format => [ "u8", "nu" ], name => "Size of fault buffer V/H" }, - 91 => { format => [ "u8", "u8" ], name => "Fault buffer entry V/H" }, - 100 => { format => [ "nu", "flag8" ], name => "Remote override function" }, - 101 => { - format => [ "flag8", "flag8" ], - name => "Solar storage mode and status" - }, - 102 => { format => [ "flag8", "u8" ], name => "Solar storage fault flags" }, - 103 => - { format => [ "flag8", "u8" ], name => "Solar storage config/memberid" }, - 104 => - { format => [ "u8", "u8" ], name => "Solar storage product version" }, - 105 => { format => [ "u8", "nu" ], name => "Number of TSPs solar storage" }, - 106 => { format => [ "u8", "u8" ], name => "TSP setting solar storage" }, - 107 => { - format => [ "u8", "u8" ], - name => "Size of fault buffer solar storage" - }, - 108 => - { format => [ "u8", "u8" ], name => "Fault buffer entry solar storage" }, - 113 => { format => ["u16"], name => "Unsuccessful burner starts" }, - 114 => { format => ["u16"], name => "Flame signal too low count" }, - 115 => { format => ["u16"], name => "OEM diagnostic code" }, - 116 => { format => ["u16"], name => "Burner starts" }, - 117 => { format => ["u16"], name => "CH pump starts" }, - 118 => { format => ["u16"], name => "DHW pump/valve starts" }, - 119 => { format => ["u16"], name => "DHW burner starts" }, - 120 => { format => ["u16"], name => "Burner operation hours" }, - 121 => { format => ["u16"], name => "CH pump operation hours" }, - 122 => { format => ["u16"], name => "DHW pump/valve operation hours" }, - 123 => { format => ["u16"], name => "DHW burner operation hours" }, - 124 => { format => ["f8.8"], name => "OpenTherm version Master" }, - 125 => { format => ["f8.8"], name => "OpenTherm version Slave" }, - 126 => { format => [ "u8", "u8" ], name => "Master product version" }, - 127 => { format => [ "u8", "u8" ], name => "Slave product version" }, -}; - -my $ot_types = { - 0 => "read data", - 1 => "write data", - 2 => "invalidate data", - 3 => "unknown 0x3", - 4 => "read ack", - 5 => "write ack", - 6 => "data invalid", - 7 => "unknown 0x7" -}; - -sub decode($$$) { - my ( $dataref, $string, $offset ) = @_; - my $b0 = hex( substr( $string, 0, 2 ) ); - - my $type = ( $b0 >> 4 ) & 7; - my $id = hex( substr( $string, 2, 2 ) ); - - my $data = pack( 'H4', substr( $string, 4, 4 ) ); - my $format = ["u16"]; - - my $ret = { - id => $id, - fake_id => $id + $offset, - type => $type, - type_str => $ot_types->{$type}, - }; - - my $bits = undef; - - if ( exists $ot_registers->{$id} ) { - $ret->{id_str} = $ot_registers->{$id}->{name}; - $ret->{units} = $ot_registers->{$id}->{units}; - $format = $ot_registers->{$id}->{format}; - $bits = $ot_registers->{$id}->{names}; - } - - $ret->{format} = join( ',', @$format ); - $ret->{raw_hex} = substr( $string, 4, 4 ); - $ret->{raw} = $data; - - $ret->{values} = []; - for my $f (@$format) { - - if ( $f eq "u16" ) { - push @{ $ret->{values} }, unpack( 'S>', $data ); - $data = substr( $data, 2 ); - } - elsif ( $f eq 's16' ) { - push @{ $ret->{values} }, unpack( 's>', $data ); - $data = substr( $data, 2 ); - } - elsif ( $f eq 'u8' ) { - push @{ $ret->{values} }, unpack( 'C', $data ); - $data = substr( $data, 1 ); - } - elsif ( $f eq 's8' ) { - push @{ $ret->{values} }, unpack( 'c', $data ); - $data = substr( $data, 1 ); - } - elsif ( $f eq 'nu' ) { - $data = substr( $data, 1 ); - } - elsif ( $f eq 'flag16' ) { - push @{ $ret->{values} }, - ( unpack( "(A)*", unpack( "B16", $data ) ) ); - $data = substr( $data, 2 ); - } - elsif ( $f eq 'flag8' ) { - push @{ $ret->{values} }, - ( unpack( "(A)*", unpack( "B8", $data ) ) ); - $data = substr( $data, 1 ); - } - elsif ( $f eq 'f8.8' ) { - push @{ $ret->{values} }, unpack( 's>', $data ) / 256.0; - } - - } - - $dataref->{ $id + $offset } = $ret; - - if ( defined $bits ) { - my @vs = ( @{ $ret->{values} } ); - my $bit = scalar( @{ $ret->{values} } ); - - for my $label (@$bits) { - $bit--; - my $v = shift(@vs); - next unless length($label) > 0; - my $key = sprintf( "%d.%d", $id + $offset, $bit ); - $ret = {}; - - $ret->{id} = sprintf( "%d.%d", $id, $bit ); - $ret->{fake_id} = $key; - $ret->{type} = $type; - $ret->{type_str} = $ot_types->{$type}; - $ret->{id_str} = $label; - $ret->{values} = [$v]; - - $dataref->{$key} = $ret; - } - } - - return $ret; -} - -sub thermostat($$) { - my ( $string, $data ) = @_; - - return decode( $data, $string, 0x100 ); - -} - -sub boiler($$) { - my ( $string, $data ) = @_; - - return decode( $data, $string, 0x0 ); - -} - -sub get_units($) { - my $id = shift; - - if ( $id =~ /^\d+$/ ) { - return '' unless exists $ot_registers->{$id}; - return '' unless exists $ot_registers->{$id}->{units}; - return $ot_registers->{$id}->{units}; - } - elsif ( $id =~ /^(\d+)\.(\d+)$/ ) { - my $base_id = $1; - my $bit = $2; - - return '' unless exists $ot_registers->{$base_id}; - return '' unless exists $ot_registers->{$base_id}->{unitss}; - - $bit = - ( scalar( @{ $ot_registers->{$base_id}->{unitss} } ) - 1 ) - $bit; - return $ot_registers->{$base_id}->{unitss}->[$bit]; - } - else { - return ''; - } -} - -sub get_name($) { - my $id = shift; - - if ( $id =~ /^\d+$/ ) { - return "Unknown $id" unless exists $ot_registers->{$id}; - return $ot_registers->{$id}->{name}; - } - elsif ( $id =~ /^(\d+)\.(\d+)$/ ) { - my $base_id = $1; - my $bit = $2; - - return "Unknown $id" unless exists $ot_registers->{$base_id}; - return "Unknown $id" unless exists $ot_registers->{$base_id}->{names}; - - $bit = ( scalar( @{ $ot_registers->{$base_id}->{names} } ) - 1 ) - $bit; - return $ot_registers->{$base_id}->{names}->[$bit]; - } - else { - return undef; - } -} - -sub get_warning($) { - my $id = shift; - - if ( $id =~ /^\d+$/ ) { - return "Unknown $id" unless exists $ot_registers->{$id}; - return $ot_registers->{$id}->{warning}; - } - elsif ( $id =~ /^(\d+)\.(\d+)$/ ) { - my $base_id = $1; - my $bit = $2; - - return "Unknown $id" unless exists $ot_registers->{$base_id}; - return "Unknown $id" unless exists $ot_registers->{$base_id}->{names}; - - $bit = - ( scalar( @{ $ot_registers->{$base_id}->{warnings} } ) - 1 ) - $bit; - return $ot_registers->{$base_id}->{warnings}->[$bit]; - } - else { - return undef; - } -} - -sub get_critical($) { - my $id = shift; - - if ( $id =~ /^\d+$/ ) { - return "Unknown $id" unless exists $ot_registers->{$id}; - return $ot_registers->{$id}->{crtitical}; - } - elsif ( $id =~ /^(\d+)\.(\d+)$/ ) { - my $base_id = $1; - my $bit = $2; - - return "Unknown $id" unless exists $ot_registers->{$base_id}; - return "Unknown $id" unless exists $ot_registers->{$base_id}->{names}; - - $bit = - ( scalar( @{ $ot_registers->{$base_id}->{crtiticals} } ) - 1 ) - $bit; - return $ot_registers->{$base_id}->{crtiticals}->[$bit]; - } - else { - return undef; - } -} - -sub maul($) { - my $n = shift; - - $n =~ s/\s/_/g; - return "boiler_" . lc($n); -} - -my $report = [ - '0.0', '0.1', '0.2', '0.3', '0.6', '0.8', '0.9', '17', '18', '25', - '26', '28', '57', '80', '257', '270', '272', '280', '312' -]; - -sub do_thing($) { - my $t = shift; - - my $name = get_name($t); - my $maul = maul($name); - my $warning = get_warning($t); - my $critical = get_critical($t); - - print "$maul.draw LINE2\n"; - print "$maul.type GAUGE\n"; - print "$maul.label $name\n"; - - print "$maul.warning ", $warning, "\n" if $warning; - print "$maul.critical ", $critical, "\n" if $critical; -} - -sub do_units($) { - my $want_unit = shift; - - for my $t (@$report) { - my $unit = get_units($t); - - next unless $want_unit eq $unit; - do_thing($t); - } -} - -sub do_values_units($$) { - my ( $data, $wanted_units ) = @_; - - for my $t (@$report) { - next unless exists $data->{$t}; - - my $units = get_units($t); - next unless $units eq $wanted_units; - - my $name = get_name($t); - my $maul = maul($name); - - print $maul, ".value ", $data->{$t}->{values}->[0], "\n"; - } -} - -my $wanted = { map { my $q = $_; $q =~ s/\..*$//; $q => 1 } @$report }; -my $n_wanted = scalar( keys %$wanted ); - -for my $t ( keys %$ot_registers ) { - $ot_registers->{ $t + 0x100 } = $ot_registers->{$t}; -} - -my @bits = split( /_+/, $0 ); - -exit 0 unless scalar(@bits) > 1; - -my $host = $bits[1]; - -$host =~ s/-//g; - -my $short_host = $host; - -$short_host =~ s/\..*$//; - -if ( $ARGV[0] eq 'config' ) { - - print "host_name ", $host, "\n"; - - print "multigraph temperatures_${short_host}\n"; - print "graph_title Temperatures\n"; - print "graph_category boiler\n"; - print "graph_vlabel centigrade\n"; - - do_units('C'); - - print "multigraph pressures_${short_host}\n"; - print "graph_title Pressures\n"; - print "graph_category boiler\n"; - print "graph_vlabel bar\n"; - - do_units('bar'); - - print "multigraph modulation_${short_host}\n"; - print "graph_title Modulation\n"; - print "graph_category boiler\n"; - print "graph_vlabel bar\n"; - - do_units('%'); - - for my $t (@$report) { - next if length( get_units($t) ) > 0; - - my $name = get_name($t); - my $maul = maul($name); - - print "multigraph ${maul}_${short_host}\n"; - print "graph_title $name\n"; - print "graph_category boiler\n"; - - do_thing($t); - - } - - exit(0); -} - -my $telnet = new Net::Telnet( - Timeout => 10, - Telnetmode => 0, - Binmode => 1, - Host => $bits[1], - Port => 2001 -); - -#$telnet->open($bits[1]); - -my $data = {}; -my $then = time; -my $timeout = 60; - -while ( ($n_wanted) and ( ( time - $then ) < $timeout ) ) { - my $line = $telnet->getline( Timeout => 1, Errmode => 'return' ); - my $id = -1; - - chomp $line; - chomp $line; - - if ( $line =~ /^T/ ) { - $id = thermostat( substr( $line, 1, 8 ), $data ); - } - elsif ( $line =~ /^B/ ) { - $id = boiler( substr( $line, 1, 8 ), $data ); - } - - if ( exists $wanted->{$id} ) { - $n_wanted--; - delete $wanted->{$id}; - } - -} - -print "multigraph temperatures_${short_host}\n"; -do_values_units( $data, 'C' ); -print "multigraph pressures_${short_host}\n"; -do_values_units( $data, 'bar' ); -print "multigraph modulation_${short_host}\n"; -do_values_units( $data, '%' ); - -for my $t (@$report) { - next unless exists $data->{$t}; - - my $units = get_units($t); - next unless $units eq ''; - - my $name = get_name($t); - my $maul = maul($name); - - print "multigraph ${maul}_${short_host}\n"; - print $maul, ".value ", $data->{$t}->{values}->[0], "\n"; -} - -exit(0); diff --git a/munin/boiler_rx b/munin/boiler_rx deleted file mode 100755 index f794999..0000000 --- a/munin/boiler_rx +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env perl - -use Net::Telnet; -use Data::Dumper; - -sub dump_file($$) { - my ( $name, $guts ) = @_; - - $fh = new IO::File ">" . $name; - return unless defined $fh; - $fh->print($guts); - $fh->close; - undef $fh; -} - -my $timeout = 60; - -my $host = $ARGV[0]; - -my $path = "/var/run/boiler_" . $host; - -mkdir( $path, 0755 ); - -while (1) { - my $telnet = new Net::Telnet( - Timeout => 10, - Telnetmode => 0, - Binmode => 1, - Host => $host, - Port => 2001 - ); - - #$telnet->open($bits[1]); - - my $nodata = 0; - - while ( ( not $telnet->eof ) and ( $nodata < $timeout ) ) { - my $line = $telnet->getline( Timeout => 1, Errmode => 'return' ); - - if ( not defined $line or ( length($line) == 0 ) ) { - $nodata++; - } - else { - $nodata = 0; - } - - chomp $line; - chomp $line; - - $line = uc($line); - - next unless $line =~ /^([TB])(.)(.)(..)(....)/; - - my $fn = $1 . $2 . "x" . $4; - my $c = $1 . $2 . $3 . $4 . $5; - - dump_file( $path . "/" . $fn, $c ); - - } - - $telnet->close; - -} - -exit(0); diff --git a/prometheus/.gitignore b/prometheus/.gitignore new file mode 100644 index 0000000..2adf2fd --- /dev/null +++ b/prometheus/.gitignore @@ -0,0 +1,3 @@ +stamp +*.bak +*.swp diff --git a/prometheus/Makefile b/prometheus/Makefile new file mode 100644 index 0000000..4c6b24f --- /dev/null +++ b/prometheus/Makefile @@ -0,0 +1,11 @@ +STUFF=$(shell find usr home \! -type d -print) + +stamp: ${STUFF} + tar cf - ${STUFF} | ssh prometheus "cd / && tar xvpf -" + touch $@ + +tidy: + perltidy -b ./home/httpd/html/weatherstation/updateweatherstation.pl + +clean: + /bin/rm -f ./home/httpd/html/weatherstation/updateweatherstation.pl.bak diff --git a/prometheus/home/httpd/html/weatherstation/updateweatherstation.php b/prometheus/home/httpd/html/weatherstation/updateweatherstation.php new file mode 120000 index 0000000..b629fc9 --- /dev/null +++ b/prometheus/home/httpd/html/weatherstation/updateweatherstation.php @@ -0,0 +1 @@ +updateweatherstation.pl \ No newline at end of file diff --git a/prometheus/home/httpd/html/weatherstation/updateweatherstation.pl b/prometheus/home/httpd/html/weatherstation/updateweatherstation.pl new file mode 100755 index 0000000..87b8893 --- /dev/null +++ b/prometheus/home/httpd/html/weatherstation/updateweatherstation.pl @@ -0,0 +1,83 @@ +#!/usr/bin/perl + +#use CGI qw(:standard); +#use Sys::Syslog qw(:standard :macros); +use Net::MQTT::Simple; +use IO::Socket; + +#openlog("weather", "", "local0"); + +my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = + gmtime(time); + +my @mons = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); +my @days = qw(Sun Mon Tue Wed Thr Fri Sat); + +my $dt = sprintf( + "%s, %02d %s %04d %02d:%02d:%02d GMT", + $days[$wday], $mday, $mons[$mon], 1900 + $year, + $hour, $min, $sec +); + +print "Content-Type: text/plain\r\n"; +print "Date: ", $dt, "\n"; +print "\n"; +print "success\n"; + +my $mqtt = Net::MQTT::Simple->new("10.32.139.1"); + +for my $t ( split( /&/, $ENV{'QUERY_STRING'} ) ) { + $t =~ s/%20/ /g; + + next unless $t =~ /^([^=]*)=([^=]*)$/; + + my ( $wot, $value ) = ( $1, $2 ); + + if ( $wot =~ /f$/ ) { + $wot =~ s/f$/c/; + $value = sprintf( "%.1f", 5.0 * ( ( $value - 32.0 ) / 9.0 ) ); + } + elsif ( $wot eq "baromin" ) { + $wot = "barommb"; + $value = sprintf( "%.1f", $value * 33.8639 ); + } + elsif ( $wot =~ /in$/ ) { + $wot =~ s/in$/mm/; + $value = sprintf( "%.1f", $value * 25.4 ); + } + + if ( $wot eq "baromin" ) { + $wot = "barommb"; + $value = sprintf( "%.1f", $value * 33.8639 ); + } + + if ( $wot eq "tempc" ) { + $mqtt->retain( "tele/weather/" . $wot, $value ); + } + else { + $mqtt->publish( "tele/weather/" . $wot, $value ); + } + + #syslog(LOG_WARNING,"fish: $wot => $value"); +} + +$mqtt->tick(0); +$mqtt->disconnect; + +alarm(5); + +my $sock = IO::Socket::INET->new( + PeerAddr => "88.96.137.6", + PeerPort => 30956, + Proto => 'tcp', + Timeout => 5 +); + +exit 0 unless defined $sock; + +$sock->print( "GET /weatherstation/updateweatherstation.php?" + . $ENV{'QUERY_STRING'} + . " HTTP/1.0\r\n" ); +$sock->print("\r\n"); +$sock->shutdown( $socket, 1 ); + diff --git a/prometheus/usr/local/bin/make_habitable b/prometheus/usr/local/bin/make_habitable new file mode 100755 index 0000000..730d632 --- /dev/null +++ b/prometheus/usr/local/bin/make_habitable @@ -0,0 +1,11 @@ +#!/bin/sh +set -x + +for i in 2fl_main_radiator 2fl_stair_radiator bathroom_radiator; do + /usr/local/bin/set_radiator_target $i 20 +done + + +for i in hall_radiator kitchen_radiator laundry_radiator kstudy_radiator bedroom_radiator ; do + set_radiator_target $i 18 +done diff --git a/prometheus/usr/local/bin/midnight_tidy b/prometheus/usr/local/bin/midnight_tidy new file mode 100755 index 0000000..2e6686e --- /dev/null +++ b/prometheus/usr/local/bin/midnight_tidy @@ -0,0 +1,15 @@ +#!/bin/sh +set -x + +/usr/local/bin/vent close + +for i in 2fl_main_radiator 2fl_stair_radiator bathroom_radiator bedroom_radiator hall_radiator kitchen_radiator kstudy_radiator laundry_radiator spare_bedroom_radiator; do + /usr/local/bin/set_radiator_target $i 10 +done + +for i in kitchen_left/POWER kitchen_right/POWER bathroom/POWER1 bathroom/POWER2 front_door/POWER 2ndfl_3gang_switch/POWER1 2ndfl_3gang_switch/POWER2 2ndfl_3gang_switch/POWER3; do + mosquitto_pub -h 127.0.0.1 -t cmnd/$i -m 0 + sleep 1 +done + + diff --git a/prometheus/usr/local/bin/set_radiator_target b/prometheus/usr/local/bin/set_radiator_target new file mode 100755 index 0000000..c52d22a --- /dev/null +++ b/prometheus/usr/local/bin/set_radiator_target @@ -0,0 +1,47 @@ +#!/bin/sh + +R=$1 +T1=$2 + + +case "$R" in + bathroom_radiator) + T2=$( expr $T1 + 2 ) + ;; + *) + T2=$( expr $T1 + 1 ) + ;; +esac + +H=127.0.0.1 + +for tries in 1 2 3 4 5 6 7 8 9 10 11 12; do + + V1=$( mosquitto_sub -h 10.32.139.1 -t stat/$R/var1 --retained-only -W 1 | sed -e 's/\..*//' ) + V2=$( mosquitto_sub -h 10.32.139.1 -t stat/$R/var2 --retained-only -W 1 | sed -e 's/\..*//' ) + + if [ $T1 -ne $V1 ]; then + mosquitto_pub -h $H -t cmnd/$R/var1 -m $T1 + sleep 1 + fi + + if [ $T2 -ne $V2 ]; then + mosquitto_pub -h $H -t cmnd/$R/var2 -m $T2 + sleep 1 + fi + + + if [ \( $T1 -eq $V1 \) -a \( $T2 -eq $V2 \) ]; then + echo Success + exit 0 + fi + + sleep 3 + +done + +echo Failed + + + + diff --git a/prometheus/usr/local/bin/vent b/prometheus/usr/local/bin/vent new file mode 100755 index 0000000..28e5ad4 --- /dev/null +++ b/prometheus/usr/local/bin/vent @@ -0,0 +1,17 @@ +#!/bin/sh + +case "$1" in + open) + mosquitto_pub -h 10.32.135.1 -t cmnd/vent/POWER2 -m 1 + sleep 20 + mosquitto_pub -h 10.32.135.1 -t cmnd/vent/POWER2 -m 0 + ;; + close) + mosquitto_pub -h 10.32.135.1 -t cmnd/vent/POWER1 -m 1 + sleep 20 + mosquitto_pub -h 10.32.135.1 -t cmnd/vent/POWER1 -m 0 + ;; + *) + echo unknown command + ;; +esac diff --git a/radiator-plc/stm32/Makefile.rules b/radiator-plc/stm32/Makefile.rules index a723e6f..cf67b1a 100644 --- a/radiator-plc/stm32/Makefile.rules +++ b/radiator-plc/stm32/Makefile.rules @@ -137,7 +137,7 @@ srec: $(BINARY).srec list: $(BINARY).list images: $(BINARY).images -flash: $(BINARY).flash +#flash: $(BINARY).flash %.images: %.bin %.hex %.srec %.list %.map %.dfu @#printf "*** $* images generated ***\n" diff --git a/radiator-plc/stm32/app/Makefile b/radiator-plc/stm32/app/Makefile index 8ef06f7..2ff2f51 100644 --- a/radiator-plc/stm32/app/Makefile +++ b/radiator-plc/stm32/app/Makefile @@ -20,7 +20,8 @@ CPROTO=cproto PROG=radiator-plc -D=radiator-plc0 +D=radiator-plc0 radiator-plc1 +#D=10.32.94.238 V=1 default: ${PROG}.elf @@ -34,17 +35,19 @@ OBJS = ${CSRCS:%.c=%.o} ${OBJS}: + include ../Makefile.include INCLUDES += -I.. +flash: ${PROG}.hex + for d in ${D}; do ssh $$d flash_stm32 < ${PROG}.hex; done + protos: ${CSRCS} echo -n > prototypes.h ${CPROTO} $(INCLUDES) $(DEFINES) -e -v ${CSRCS} > prototypes.h.tmp mv -f prototypes.h.tmp prototypes.h -flash: ${PROG}.hex - ssh ${D} flash_stm32 < ${PROG}.hex local_flash: ${PROG}.hex $(Q)$(OOCD) -f $(OOCD_INTERFACE) \ diff --git a/radiator-plc/stm32/app/control.c b/radiator-plc/stm32/app/control.c deleted file mode 100644 index f3a35c9..0000000 --- a/radiator-plc/stm32/app/control.c +++ /dev/null @@ -1,114 +0,0 @@ -#include "project.h" - - -static uint32_t target = 25000; -static uint32_t value = ~0U; - -#define SHORT 200 -#define LONG 1200 -#define REPEAT 400 - -#define HYST 75UL - - -static void -show_temp (char t, uint32_t v, int y) -{ - char buf[16]; - uint32_t i, f; - - i = v / 1000; - f = v / 100 - (i * 10); - - if (v == ~0U) - { - sprintf (buf, "%c:error ", t); - } - else - { - sprintf (buf, "%c:%2d.%1d%cC", t, (int) i, (int) f, 0xdf); - } - - lcd_write (buf, 0, y); -} - -void -control_update (void) -{ - - show_temp ('P', value, 0); - show_temp ('T', target, 1); - - if (value == ~0U) - relay_off (); - else if (value < (target - HYST)) - relay_on (); - else if (value > (target + HYST)) - relay_off (); - - -} - -void -control_tick (void) -{ - static int down, up, update; - - if (!gpio_get (GPIOB, GPIO4)) - down++; - else - down = 0; - - if (!gpio_get (GPIOB, GPIO6)) - up++; - else - up = 0; - - if ((down == SHORT) || (down == LONG)) - { - if (target > 100) - target -= 100; - if (down == LONG) - down = LONG - REPEAT; - control_update (); - } - - if ((up == SHORT) || (up == LONG)) - { - if (target < 99900) - target += 100; - if (up == LONG) - up = LONG - REPEAT; - control_update (); - } - - update++; - - if (update == 1000) - { - control_update (); - update = 0; - } -} - -void -control_report (uint32_t v) -{ - - value = v; - control_update (); -} - -void -control_init (void) -{ - - gpio_set_mode (GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, - GPIO4 | GPIO6); - gpio_set_mode (GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, - GPIO5); - gpio_set (GPIOB, GPIO4 | GPIO6); - gpio_clear (GPIOB, GPIO5); - - -} diff --git a/radiator-plc/stm32/app/logic.c b/radiator-plc/stm32/app/logic.c index 8cab7ca..a9f845c 100644 --- a/radiator-plc/stm32/app/logic.c +++ b/radiator-plc/stm32/app/logic.c @@ -7,7 +7,7 @@ static volatile int semaphore = 0; -static const char *valve_name[N_VALVES] = { +static const char *plc0_valve_names[N_VALVES] = { "dd_radiator1", "dd_radiator2", "dd_radiator3", @@ -18,16 +18,57 @@ static const char *valve_name[N_VALVES] = { "plc0_radiator7", }; -static Onewire_addr valve_owa[N_VALVES] = { - {{0x28, 0xe3, 0x5d, 0x56, 0xb5, 0x01, 0x3c, 0x96}}, - {{0x28, 0x0d, 0xaf, 0x56, 0xb5, 0x01, 0x3c, 0x89}}, - {{0x28, 0x79, 0xb1, 0x56, 0xb5, 0x01, 0x3c, 0x13}}, - {{0x28, 0x99, 0x8f, 0x56, 0xb5, 0x01, 0x3c, 0x1c}}, - {{0x28, 0xb4, 0x98, 0x6e, 0x32, 0x20, 0x01, 0xc7}}, - {{0x28, 0x0d, 0x4d, 0x56, 0xb5, 0x01, 0x3c, 0xd0}}, - {{0x28, 0xb1, 0x71, 0x75, 0x32, 0x20, 0x01, 0xa9}}, +static const Onewire_addr plc0_valve_owas[N_VALVES] = { + {{0x28, 0x09, 0xb3, 0xc3, 0x0b, 0x00, 0x00, 0xf9}}, + {{0x28, 0x89, 0xda, 0xc3, 0x0b, 0x00, 0x00, 0xf1}}, + {{0x28, 0xdf, 0xb2, 0xc6, 0x0b, 0x00, 0x00, 0xc3}}, + {{0x28, 0x42, 0x23, 0xc4, 0x0b, 0x00, 0x00, 0x45}}, + {{0x28, 0xf9, 0xd6, 0xc6, 0x0b, 0x00, 0x00, 0xc9}}, + {{0x28, 0x8d, 0x0a, 0xc6, 0x0b, 0x00, 0x00, 0xf2}}, + {{0x28, 0x5f, 0xb7, 0xc6, 0x0b, 0x00, 0x00, 0xfb}}, + {{0x28, 0x25, 0xe5, 0xc6, 0x0b, 0x00, 0x00, 0xba}}, +}; + + + + + + + + + + + + +static const char *plc1_valve_names[N_VALVES] = { + "2fl_main_radiator", + "2fl_stair_radiator", + "plc1_radiator2", + "plc1_radiator3", + "plc1_radiator4", + "plc1_radiator5", + "plc1_radiator6", + "plc1_radiator7", +}; + + +static const Onewire_addr plc1_valve_owas[N_VALVES] = { + {{0x28, 0x03, 0x01, 0xc5, 0x0b, 0x00, 0x00, 0xf3}}, + {{0x28, 0xea, 0x79, 0xc5, 0x0b, 0x00, 0x00, 0x1f}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, }; + + +Onewire_addr owa_zero = {0}; + + +static const char **valve_name; +static const Onewire_addr *valve_owa; volatile int temp[N_VALVES]; #define BAD_TEMP 0x7fffffff @@ -56,80 +97,98 @@ int high_limit[N_VALVES] = { }; int valve_state[N_VALVES]; +int failed_reads[N_VALVES]; + + + +static float scale (int i) +{ + float ret; + ret = i; + return ret / 1000.; +} void mqtt_dispatch (char *type, char *who, char *what, char *msg) { unsigned i; + static char msg_buf[80]; if (strcmp (type, "cmnd")) return; for (i = 0; i < N_VALVES; ++i) { if (!strcmp (who, valve_name[i])) { - if (!strcmp (what, "var1")) + if (!strcmp (what, "var1")) { low_limit[i] = atoi (msg) * 1000; + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/var1 %.2f\r\n", valve_name[i], scale (low_limit[i]))); + } - if (!strcmp (what, "var2")) + if (!strcmp (what, "var2")) { high_limit[i] = atoi (msg) * 1000; - } - } -} - + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/var2 %.2f\r\n", valve_name[i], scale (high_limit[i]))); + } -static float scale(int i) -{ -float ret; -ret=i; -return ret/1000.; + } + } } void logic_tick (void) { static unsigned i; static int read_failed = 1; + static int not_present = 1; static char msg_buf[80]; int t; int o; int v; + if (!not_present) { + if (!read_failed) + read_failed = ds18b20_read (&valve_owa[i], &t); + if (read_failed) { + temp[i] = BAD_TEMP; + valve_state[i] = 0; + failed_reads[i]++; + } else { + temp[i] = t; + failed_reads[i] = 0; - if (!read_failed) - read_failed = ds18b20_read (&valve_owa[i], &t); + if (t < low_limit[i]) + valve_state[i] = 1; - if (read_failed) { - temp[i] = BAD_TEMP; - valve_state[i] = 0; - } else { - temp[i] = t; + if (t >= high_limit[i]) + valve_state[i] = 0; + } - if (t < low_limit[i]) - valve_state[i] = 1; + o = valve_state[i]; - if (t >= high_limit[i]) - valve_state[i] = 0; - } + output_write (i, o); + v = input_get (i); - o = valve_state[i]; + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/POWER %s\r\n", valve_name[i], o ? "ON" : "OFF")); + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/OPEN %d\r\n", valve_name[i], v)); - output_write (i, o); - v = input_get (i); + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/var1 %.2f\r\n", valve_name[i], scale (low_limit[i]))); - usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/POWER %s\r\n", valve_name[i], o ? "ON" : "OFF")); - usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/OPEN %d\r\n", valve_name[i], v)); + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/var2 %.2f\r\n", valve_name[i], scale (high_limit[i]))); - usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/var1 %.2f\r\n", valve_name[i], scale(low_limit[i]))); - usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/var2 %.2f\r\n", valve_name[i], scale(high_limit[i]))); + if (!read_failed) { + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/TEMPERATURE %.2f\r\n", valve_name[i], scale (temp[i]))); + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/DELTA %.2f\r\n", valve_name[i], scale (low_limit[i] - temp[i]))); + } + usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/failed_reads %d\r\n", valve_name[i], failed_reads[i])); - if (!read_failed) { - usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/TEMPERATURE %.2f\r\n", valve_name[i], scale(temp[i]))); - usart1_queue_buf (msg_buf, sprintf (msg_buf, "\r\nMQTTR stat/%s/DELTA %.2f\r\n", valve_name[i], scale(low_limit[i]-temp[i]))); + } else { + valve_state[i] = 0; + o = valve_state[i]; + output_write (i, o); } @@ -143,7 +202,12 @@ void logic_tick (void) onewire_search(); } - read_failed = ds18b20_set_12_convert (&valve_owa[i]); + if (!memcmp (&valve_owa[i], &owa_zero, sizeof (owa_zero))) + not_present = 1; + else { + not_present = 0; + read_failed = ds18b20_set_12_convert (&valve_owa[i]); + } } @@ -156,3 +220,31 @@ void logic_slow_tick (void) void logic_dispatch (void) { } + + +void logic_init (void) +{ + char serial[25] = {0}; + unsigned i; + + desig_get_unique_id_as_string (serial, sizeof (serial)); + + if (!strcmp (serial, "325715574155303632FFD305")) { + valve_name = plc0_valve_names; + valve_owa = plc0_valve_owas; + + for (i = 0; i < 3; ++i) { + low_limit[i] = 19000; + high_limit[i] = 20000; + } + } else if (!strcmp (serial, "378125574155383139FFD805")) { + valve_name = plc1_valve_names; + valve_owa = plc1_valve_owas; + } else { + printf ("serial: %s\n", serial); + valve_name = plc0_valve_names; + valve_owa = plc0_valve_owas; + } + + +} diff --git a/radiator-plc/stm32/app/main.c b/radiator-plc/stm32/app/main.c index 57b6752..4a00a70 100644 --- a/radiator-plc/stm32/app/main.c +++ b/radiator-plc/stm32/app/main.c @@ -19,6 +19,7 @@ static void mqtt_cmd (char *type) type++; who = index (type, ' '); + if (who) *who = 0; @@ -133,11 +134,10 @@ main (void) onewire_init(); inputs_init(); outputs_init(); + logic_init(); printf ("D: RESET\n"); - //onewire_search(); - for (;;) { if (!ring_empty (&rx1_ring)) kbd_dispatch(); diff --git a/radiator-plc/stm32/app/project.h b/radiator-plc/stm32/app/project.h index 85f14b9..f4f308a 100644 --- a/radiator-plc/stm32/app/project.h +++ b/radiator-plc/stm32/app/project.h @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/radiator-plc/stm32/app/prototypes.h b/radiator-plc/stm32/app/prototypes.h index 2347f01..6c6f36a 100644 --- a/radiator-plc/stm32/app/prototypes.h +++ b/radiator-plc/stm32/app/prototypes.h @@ -68,6 +68,7 @@ extern void mqtt_dispatch(char *type, char *who, char *what, char *msg); extern void logic_tick(void); extern void logic_slow_tick(void); extern void logic_dispatch(void); +extern void logic_init(void); /* ds18b20.c */ extern unsigned extract_leu16(uint8_t *d); extern int extract_les16(uint8_t *d); diff --git a/radiator-plc/stm32/app/rain.c b/radiator-plc/stm32/app/rain.c deleted file mode 100644 index 4188990..0000000 --- a/radiator-plc/stm32/app/rain.c +++ /dev/null @@ -1,100 +0,0 @@ -#include "project.h" - -#define DEBOUNCE 20 - -static uint8_t r_state; -static uint32_t r_ticks; -static uint8_t r_current; -static uint8_t r_filtered; -static unsigned r_expiry; - - -#define RAIN_BANK GPIOA -#define RAIN_GPIO GPIO15 - -static int -raw_rain (void) -{ - return !gpio_get (RAIN_BANK, RAIN_GPIO); -} - - -void rain_notify (void) -{ - if (r_current) - vents_rain_notify(); -} - - -void rain_ticker (void) -{ - unsigned s; - static int ticker = 0; - - - s = raw_rain(); - - if (s == r_state) { - if (r_ticks < DEBOUNCE) - r_ticks++; - else if (r_ticks == DEBOUNCE) { - r_current = s; - r_ticks++; - printf ("D: rain state now %d\r\n", r_current); - } - } else { - r_ticks = 0; - r_state = s; - } - - if (r_current) { - r_filtered = 1; - r_expiry = 1800; - } - - rain_notify(); - - ticker++; - - if (ticker < 1000) return; - - ticker = 0; - - if (r_expiry) - r_expiry--; - else - r_filtered = r_current; - -} - -void rain_slow_ticker (void) -{ - printf ("R: %d %d\r\n", r_current, r_filtered); -} - - -int rain_filtered_state (void) -{ - return r_filtered; -} - -int rain_state (void) -{ - return r_current; -} - -char rain_state_char (void) -{ - return r_current ? 'Y' : 'N'; -} - -void -rain_init (void) -{ - gpio_set_mode (RAIN_BANK, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, - RAIN_GPIO); - - gpio_set (RAIN_BANK, RAIN_GPIO); -} - - diff --git a/radiator-plc/stm32/app/ticker.c b/radiator-plc/stm32/app/ticker.c index bb346fb..ffe63d0 100644 --- a/radiator-plc/stm32/app/ticker.c +++ b/radiator-plc/stm32/app/ticker.c @@ -47,7 +47,10 @@ sys_tick_handler (void) slow++; - if (slow < 2) return; + if (slow < 5) return; + + slow = 0; + logic_tick(); diff --git a/set_radiator_target b/set_radiator_target deleted file mode 100755 index 85ef1f9..0000000 --- a/set_radiator_target +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -R=$1 -T=$2 -H=10.32.139.1 - -mosquitto_pub -h $H -t cmnd/$R/var1 -m $T -sleep 1 -mosquitto_pub -h $H -t cmnd/$R/var2 -m $[ T + 1 ] - - diff --git a/tasmota-config/configure-radiators b/tasmota-config/configure-radiators index 488e7e3..6799acd 100755 --- a/tasmota-config/configure-radiators +++ b/tasmota-config/configure-radiators @@ -18,7 +18,7 @@ M=10.32.139.1 set -x -for T in laundry_radiator ; do #kstudy_radiator bedroom_radiator spare_bedroom_radiator; do +for T in laundry_radiator kstudy_radiator bedroom_radiator spare_bedroom_radiator; do mosquitto_pub -h "${M}" -t "cmnd/${T}/Backlog" -m "Switchmode1 3; Rule1 1; Rule1 4; Rule2 1; Rule2 4; Rule3 1; Rule3 4" sleep 3 mosquitto_pub -h "${M}" -t "cmnd/${T}/Backlog" -m "TelePeriod 60; SetOption26 0; SetOption0 0; PowerOnState 0" @@ -32,8 +32,8 @@ for T in laundry_radiator ; do #kstudy_radiator bedroom_radiator spare_bedroom_r ON system#boot DO Backlog RuleTimer1 70; var1 5; var2 5; var3 0; var4 0 ENDON ON Rules#Timer=1 DO Backlog RuleTimer1 70; Power %var3% ENDON ON tele-SI7021#temperature DO Backlog RuleTimer1 70; Event temp=%value% ENDON - ON Switch1#State=0 DO Backlog var3 0; RuleTimer2 0; Event temp=100 ENDON - ON Switch1#State=1 DO Backlog var3 1; RuleTimer2 1800; Event temp=100 ENDON + ON Button1#State DO event toggle3=%var3% ENDON + ON Button2#State DO event toggle3=%var3% ENDON EOF R="$(echo $R)" @@ -43,8 +43,8 @@ EOF ## read -r -d '' R << EOF - ON Switch2#State=0 DO Backlog var3 0; RuleTimer2 0; Event temp=100 ENDON - ON Switch2#State=1 DO Backlog var3 1; RuleTimer2 1800; Event temp=100 ENDON + ON event#toggle3<1 DO BackLog var3 1; RuleTimer2 1800; Event temp=100 ENDON + ON event#toggle3>0 DO BackLog var3 0; RuleTimer2 0; Event temp=100 ENDON ON Rules#Timer=2 DO var3 0 ENDON ON Power1#State DO BackLog Var4 %value%; Publish2 stat/${T}/OPEN %value% ENDON ON Event#temp DO BackLog Publish2 stat/${T}/var1 %var1%; Publish2 stat/${T}/var2 %var2% ENDON @@ -60,7 +60,7 @@ EOF ON Event#temp<%var1% DO Power1 1 ENDON ON Event#temp>%var2% DO Power1 %var3% ENDON ON Event#temp<5 DO Power1 1 ENDON - ON Event#temp DO BackLog Var5 %var1%; Sub5 %value%; Publish2 stat/${T}/DELTA %var5%; Publish2 stat/${T}/OPEN %var4% ENDON + ON Event#temp DO BackLog Var5 %var1%; Sub5 %value%; Publish2 stat/${T}/DELTA %var5%; Publish2 stat/${T}/OPEN %var4%; Publish2 stat/${T}/TEMPERATURE %value% ENDON EOF R="$(echo $R)" diff --git a/watch b/watch new file mode 100755 index 0000000..67b88c9 --- /dev/null +++ b/watch @@ -0,0 +1,6 @@ +#!/bin/bash +H=10.32.139.1 + +mosquitto_sub -h $H -t stat/+/OPEN -v + + -- cgit v1.2.3