From ccc2e2ed3f76ec59d5006687d0ff95f804ec06d3 Mon Sep 17 00:00:00 2001 From: Matt Birkholz Date: Wed, 18 Sep 2024 11:17:48 -0600 Subject: [PATCH] Punt abbey-weather role, entirely replaced by Home Assistant. The 1-Wire hardware was replaced with an Ecowitt IoT hub and sensors including wind and rain. --- README.org | 429 ++--------------------- hosts | 3 - playbooks/site.yml | 4 - roles_t/abbey-weather/files/daemon-anoat | 130 ------- roles_t/abbey-weather/handlers/main.yml | 10 - roles_t/abbey-weather/tasks/main.yml | 114 ------ 6 files changed, 23 insertions(+), 667 deletions(-) delete mode 100644 roles_t/abbey-weather/files/daemon-anoat delete mode 100644 roles_t/abbey-weather/handlers/main.yml delete mode 100644 roles_t/abbey-weather/tasks/main.yml diff --git a/README.org b/README.org index e08fdd9..379797d 100644 --- a/README.org +++ b/README.org @@ -815,12 +815,10 @@ directory, =playbooks/=. ** Install Additional Packages -The scripts that maintain the abbey's web site and run the Weather -project use a number of additional software packages. The -=/WWW/live/Private/make-top-index= script uses ~HTML::TreeBuilder~ in -the ~libhtml-tree-perl~ package. The house task list uses JQuery. -Weather scripts use ~mit-scheme~ and ~gnuplot~ (in pseudonymous -packages). +The scripts that maintain the abbey's web site use a number of +additional software packages. The =/WWW/live/Private/make-top-index= +script uses ~HTML::TreeBuilder~ in the ~libhtml-tree-perl~ package. +The house task list uses JQuery. #+CAPTION: [[file:roles_t/abbey-core/tasks/main.yml][=roles_t/abbey-core/tasks/main.yml=]] #+BEGIN_SRC conf :tangle roles_t/abbey-core/tasks/main.yml @@ -1490,24 +1488,6 @@ Monkey's photo processing scripts use ~netpbm~ commands like apt: pkg=netpbm #+END_SRC -** Configure Weather Updates - -Monkey on Core runs =/WWW/campus/Weather/Private/cronjob= every 5 -minutes and =cronjob-midnight= at midnight. - -#+CAPTION: [[file:roles_t/abbey-core/tasks/main.yml][=roles_t/abbey-core/tasks/main.yml=]] -#+BEGIN_SRC :tangle roles_t/abbey-core/tasks/main.yml - -- name: Create Monkey's weather job. - become: yes - cron: - name: weather - hour: "*" - minute: "*/5" - job: "[ -d /WWW/house ] && /WWW/house/Weather/Private/cronjob" - user: monkey -#+END_SRC - ** Install Samba The abbey core provides NAS (Network Attached Storage) service to the @@ -1810,379 +1790,23 @@ The monks of the abbey are masters of the staff and Emacs. * The Abbey Weather Role -Birchwood Abbey's weather hosts use the 1-Wire server (from the -~owserver~ package) and a 1-Wire USB adapter. They use an -unprivileged account (~monkey~) to run a SystemD service named -~weatherd~ (aka "the daemon"). The daemon is a Perl script that runs -~owread~ and logs the new measurements once per minute. - -The log files are collected by Monkey on Core (via ~rsync~), then -processed and published in campus web pages by The Weather Project's -code (old, using ~gnuplot(1)~, and so... unpublished). - -** The Abbey Weather Hardware - -The abbey currently has one weather host, Gate, and a couple 1-Wire -sensor modules. The modules measure inside and outside temperature -and humidity. Their desired locations are 7-8m from the core servers -so they are plugged into a custom Y cable, with the inside sensor -cable spliced into the middle of the outside/main cable. The proximal -end's RJ11 plugs into a 1-Wire USB adapter (a DS9490R) plugged into -Gate. The outside end goes out the window with the Starlink cable. - -** The Abbey Weather Host Setup - -The Ansible code in the ~abbey-weather~ role assumes it is working -with a cloistered host (as described in [[*Cloistering][Cloistering]]) and proceeds in -two phases. The first installs the ~ow-server~ package and configures -it to use a DS9490 (USB adapter) rather than a debugging fake. After -the first ~./abbey config new~, the new weather host seems to need a -reboot before the 1-Wire bus becomes visible via ~owdir~. - -After a reboot ~owdir~ should list one or more type 26 device IDs. -Listing them (e.g. running ~owdir /26.nnnnnnnn~ or ~owdir -/26.nnnnnnnn/HIH~) should reveal "files" named =temperature= and -=HIH/humidity=. These pseudo-file paths are used in the daemon script -below. A test session is shown below. - -#+BEGIN_EXAMPLE -monkey@new$ owdir -... - /26.2153B6000000/ -... -monkey@new$ owdir /26.2153B6000000 -... - /26.2153B6000000/temperature -... -monkey@new$ owread /26.2153B6000000/temperature; echo -26.125 -monkey@new$ -#+END_EXAMPLE - -The second phase of weather host configuration waits for the host- -specific weather daemon script to appear in the role's =files/=. - -** The Abbey Weather Daemons - -Different weather hosts, with different 1-Wire devices, need different -daemon scripts, to call ~owread~ with different paths (containing the -IDs of each host's devices). At the moment there is just the -one weather host, ~anoat~. - -#+CAPTION: [[file:roles_t/abbey-weather/files/daemon-anoat][=roles_t/abbey-weather/files/daemon-anoat=]] -#+BEGIN_SRC perl :tangle roles_t/abbey-weather/files/daemon-anoat :mkdirp yes -#!/usr/bin/perl -w -# -*- CPerl -*- -# -# Weather/daemon -# -# Fetches data from the local owserver once per minute. Appends to -# Log/{In,Out}side/YEAR/MONTH/DAY.txt. - -use strict; -use IO::File; -use Date::Format; - -my $ILOG; -my $OLOG; -my $ymd = ""; -sub mymkdir ($); -sub reopen_logs () -{ - my $time = time; - my $datime = time2str ("%Y-%m-%d %H:%M:%S", $time, "UTC"); - my ($year, $month, $day) = $datime =~ /^(\d{4})-(\d\d)-(\d\d) /; - my $new_ymd = "$year/$month/$day"; - return if $new_ymd eq $ymd; - close $ILOG if defined $ILOG; - close $OLOG if defined $OLOG; - umask 07; - mymkdir "Inside/$year/$month"; - mymkdir "Outside/$year/$month"; - umask 027; - my $filename = "Inside/$new_ymd.txt"; - $ILOG = new IO::File; - open $ILOG, ">>$filename" or die "Could not open $filename: $!\n"; - $filename = "Outside/$new_ymd.txt"; - $OLOG = new IO::File; - open $OLOG, ">>$filename" or die "Could not open $filename: $!\n"; - $ymd = $new_ymd; -} - -sub logit ($$$); -sub main () { - die "usage: $0\n" if @ARGV != 0; - $0 = "weatherd"; - chdir "/home/monkey/Weather/Log" or die; - umask 027; - my $start = time; - { - my $secs = 60 - $start % 60; - $start += $secs; - sleep ($secs); - } - while (1) { - reopen_logs; - logit $OLOG, "T", "/26.2153B6000000/temperature"; - logit $OLOG, "H", "/26.2153B6000000/HIH4000/humidity"; - logit $ILOG, "T", "/26.8859B6000000/temperature"; - logit $ILOG, "H", "/26.8859B6000000/HIH4000/humidity"; - $start += 60; - my $now = time; - while ($start < $now) { $start += 60; } - my $secs = $start - $now; - sleep ($secs); - } -} - -sub logit ($$$) -{ - my ($log, $name, $query) = @_; - - my $tries = 0; - while ($tries < 3) { - my $time = time; - my $datime = time2str ("%Y-%m-%d %H:%M:%S", $time, "UTC"); - $tries += 1; - my @lines = `/usr/bin/owread $query`; - chomp @lines; - my $status = $?; - my $sig = $status & 127; - $status >>= 8; - if ($status != 0) { - my $L = join "\\n", @lines; - print $log "$datime\t$name\terror: status $status: $L\n"; - $log->flush; - } elsif (@lines != 1) { - my $L = join "\\n", @lines; - print $log "$datime\t$name\terror: multiple lines: $L\n"; - $log->flush; - } elsif ($lines[0] !~ /^ *(-?\d+(\.\d+)?)$/) { - my $L = $lines[0]; - print $log "$datime\t$name\terror: bogus line: $L\n"; - $log->flush; - } else { - my $datum = $1; - print $log "$datime\t$name\t$datum\n"; - $log->flush; - return; - } - } -} - -sub mymkdir ($) -{ - my ($dirpath) = @_; - - my @path_names = split /\//, $dirpath; - my $path; - if (!$path_names[0]) { - $path = "/"; - shift @path_names; - } else { - $path = "."; - } - my @created; - while (@path_names) { - $path .= "/" . shift @path_names; - if (! -d $path) { - if (-e $path) { - die "mkdir $dirpath: already exists; not a directory!\n"; - } - if (! mkdir $path) { - die "mkdir $path: $!\n"; - } else { - chmod 02775, $path; - push @created, $path; - } - } - } - return @created; -} - -main; -#+END_SRC - -The above Perl script uses the ~Date::Format~ module, which is -installed by the following task. - -#+CAPTION: [[file:roles_t/abbey-weather/tasks/main.yml][=roles_t/abbey-weather/tasks/main.yml=]] -#+BEGIN_SRC conf :tangle roles_t/abbey-weather/tasks/main.yml :mkdirp yes ---- -- name: Install weather daemon packages. - become: yes - apt: pkg=libtimedate-perl -#+END_SRC - -** Install 1-Wire Server - -This next task installs the 1-Wire server and shell commands. The -abbey uses the Dallas Semiconductor DS9490R, a USB to 1-Wire adapter, -on all its weather hosts, so it also configures the server to use the -USB adapter (rather than a test "fake" adapter). - -#+CAPTION: [[file:roles_t/abbey-weather/tasks/main.yml][=roles_t/abbey-weather/tasks/main.yml=]] -#+BEGIN_SRC conf :tangle roles_t/abbey-weather/tasks/main.yml - -- name: Install 1-Wire server. - become: yes - apt: - pkg: [ owserver, ow-shell ] - -- name: Configure 1-Wire server. - become: yes - lineinfile: - path: /etc/owfs.conf - regexp: "{{ item.regexp }}" - line: "{{ item.line }}" - backrefs: yes - loop: - - { regexp: '^[# ]*server: *FAKE(.*)$', line: '#server: FAKE\1' } - - { regexp: '^[# ]*server: *usb(.*)$', line: 'server: usb\1' } -#+END_SRC - -** Install Rsync - -Monkey on Core will want to download log records (files) using -~rsync(1)~. - -#+CAPTION: [[file:roles_t/abbey-weather/tasks/main.yml][=roles_t/abbey-weather/tasks/main.yml=]] -#+BEGIN_SRC conf :tangle roles_t/abbey-weather/tasks/main.yml - -- name: Install Rsync. - become: yes - apt: pkg=rsync -#+END_SRC - -** Create Monkey - -The weather daemon is run by an unprivileged ~monkey~ account (/not/ -~sysadm~) which allows ~monkey~ on Core shell access. This is also -executed during the initial phase of configuration, allowing the -administrator to login on the new weather host as ~monkey~ and thus to -test access to the 1-Wire adapter and devices. To facilitate -debugging, the ~sysadm~ account is included in the ~monkey~ group. - -#+CAPTION: [[file:roles_t/abbey-weather/tasks/main.yml][=roles_t/abbey-weather/tasks/main.yml=]] -#+BEGIN_SRC conf :tangle roles_t/abbey-weather/tasks/main.yml - -- name: Create monkey. - become: yes - user: - name: monkey - system: yes - -- name: Authorize monkey@core. - become: yes - vars: - pubkeyfile: ../Secret/ssh_monkey/id_rsa.pub - authorized_key: - user: monkey - key: "{{ lookup('file', pubkeyfile) }}" - manage_dir: yes - -- name: Add {{ ansible_user }} to monkey group. - become: yes - user: - name: "{{ ansible_user }}" - append: yes - groups: monkey -#+END_SRC - -** Install Weather Daemon - -The weather daemon is kept alive as a Systemd service unit. This task -creates and starts that service /after/ the host-specific -=files/daemon-HOST= file becomes available. - -The ~ExecStartPre=/bin/sleep 30~ is intended to avoid recent hangs in -~owread~. - -#+CAPTION: [[file:roles_t/abbey-weather/tasks/main.yml][=roles_t/abbey-weather/tasks/main.yml=]] -#+BEGIN_SRC conf :tangle roles_t/abbey-weather/tasks/main.yml - -- name: Install weather directory. - become: yes - file: - path: /home/monkey/Weather/Log - state: directory - owner: monkey - group: monkey - mode: u=rwx,g=rx,o=rx - -- name: Test for weather daemon script. - vars: - dir: ../roles/abbey-weather/files - file: "{{ dir }}/daemon-{{ inventory_hostname }}" - stat: path="{{ file }}" - delegate_to: localhost - register: weather - -- name: Note missing weather daemon script. - vars: - dir: ../roles/abbey-weather/files - script: "{{ dir }}/daemon-{{ inventory_hostname }}" - debug: - msg: "{{ script }}: not found" - when: not weather.stat.exists - -- name: Install weather daemon. - become: yes - vars: - dir: ../roles/abbey-weather/files - script: "{{ dir }}/daemon-{{ inventory_hostname }}" - copy: - src: "{{ script }}" - dest: /home/monkey/Weather/daemon - owner: monkey - group: monkey - mode: u=rwx,g=rx,o= - when: weather.stat.exists - -- name: Install weatherd service. - become: yes - copy: - content: | - [Unit] - Description=Weather Logger - After=owserver.service - - [Service] - User=monkey - ExecStartPre=/bin/sleep 30 - ExecStart=/home/monkey/Weather/daemon - Restart=always - - [Install] - WantedBy=multi-user.target - dest: /etc/systemd/system/weatherd.service - when: weather.stat.exists - notify: - - Reload Systemd. - - Restart weather daemon. +Birchwood Abbey now uses Home Assistant to record and display weather +data from an Ecowitt GW2001 IoT gateway connecting wirelessly to a +WS90 (7 function weather station) and a couple WN31s (temp/humidity +sensors). -- name: Enable/Start weather daemon. - become: yes - systemd: - service: weatherd - enabled: yes - state: started - when: weather.stat.exists -#+END_SRC - -#+CAPTION: [[file:roles_t/abbey-weather/handlers/main.yml][=roles_t/abbey-weather/handlers/main.yml=]] -#+BEGIN_SRC conf :tangle roles_t/abbey-weather/handlers/main.yml :mkdirp yes ---- -- name: Reload Systemd. - become: yes - command: systemctl daemon-reload +The configuration of the GW2001 IoT hub involved turning off the Wi-Fi +access point, and disabling unused channels. The hub reports the data +from all sensors in range, /anyone's/ sensors. These new data sources +are noticed and recorded by Home Assistant automatically as similarly +equipped campers come and go. Disabling unused channels helps avoid +these distractions. -- name: Restart weather daemon. - become: yes - systemd: - service: weatherd - state: restarted -#+END_SRC +The configuration of Home Assistant involved installing the Ecowitt +"integration". This was accomplished by choosing "Settings", then +"Devices & services", then "Add Integration", and searching for +"Ecowitt". Once installed, the integration created dozens of weather +entities which were organized into an "Abbey" dashboard. * The Abbey DVR Role @@ -3272,9 +2896,6 @@ all: kamino: kessel: ord-mantell: - weather: - hosts: - anoat: dvrs: hosts: dantooine: @@ -3324,10 +2945,6 @@ institutional roles, then the liturgical roles. hosts: campus roles: [ campus, abbey-cloister ] -- name: Configure Weather - hosts: weather - roles: [ abbey-weather ] - - name: Configure DVRs hosts: dvrs roles: [ abbey-dvr ] @@ -3515,10 +3132,10 @@ if ($ARGV[0] eq "versions") { The abbey changes location almost weekly, so its timezone changes occasionally. Droplet does not move. Gate and other simple servers -(the weather monitors) are kept in UTC. Core, the DVRs, TVRs, and the -desktops all want updating to the current local timezone. The -desktops are managed maually, but the rest can all be updated using -Ansible. +are kept in UTC. Core, the DVRs, TVRs, Home Assistant and the +desktops all want updating to the current local timezone. Home +Assistant and the desktops are managed maually, but the rest can all +be updated using Ansible. The ~tz~ sub-command runs the =timezone.yml= playbook, which uses the current timezone/city on the administrator's notebook and updates diff --git a/hosts b/hosts index ba5280e..8346a7b 100644 --- a/hosts +++ b/hosts @@ -47,9 +47,6 @@ all: kamino: kessel: ord-mantell: - weather: - hosts: - anoat: dvrs: hosts: dantooine: diff --git a/playbooks/site.yml b/playbooks/site.yml index a294cc4..a1723c4 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -19,10 +19,6 @@ hosts: campus roles: [ campus, abbey-cloister ] -- name: Configure Weather - hosts: weather - roles: [ abbey-weather ] - - name: Configure DVRs hosts: dvrs roles: [ abbey-dvr ] diff --git a/roles_t/abbey-weather/files/daemon-anoat b/roles_t/abbey-weather/files/daemon-anoat deleted file mode 100644 index 6006c14..0000000 --- a/roles_t/abbey-weather/files/daemon-anoat +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/perl -w -# -*- CPerl -*- -# -# Weather/daemon -# -# Fetches data from the local owserver once per minute. Appends to -# Log/{In,Out}side/YEAR/MONTH/DAY.txt. - -use strict; -use IO::File; -use Date::Format; - -my $ILOG; -my $OLOG; -my $ymd = ""; -sub mymkdir ($); -sub reopen_logs () -{ - my $time = time; - my $datime = time2str ("%Y-%m-%d %H:%M:%S", $time, "UTC"); - my ($year, $month, $day) = $datime =~ /^(\d{4})-(\d\d)-(\d\d) /; - my $new_ymd = "$year/$month/$day"; - return if $new_ymd eq $ymd; - close $ILOG if defined $ILOG; - close $OLOG if defined $OLOG; - umask 07; - mymkdir "Inside/$year/$month"; - mymkdir "Outside/$year/$month"; - umask 027; - my $filename = "Inside/$new_ymd.txt"; - $ILOG = new IO::File; - open $ILOG, ">>$filename" or die "Could not open $filename: $!\n"; - $filename = "Outside/$new_ymd.txt"; - $OLOG = new IO::File; - open $OLOG, ">>$filename" or die "Could not open $filename: $!\n"; - $ymd = $new_ymd; -} - -sub logit ($$$); -sub main () { - die "usage: $0\n" if @ARGV != 0; - $0 = "weatherd"; - chdir "/home/monkey/Weather/Log" or die; - umask 027; - my $start = time; - { - my $secs = 60 - $start % 60; - $start += $secs; - sleep ($secs); - } - while (1) { - reopen_logs; - logit $OLOG, "T", "/26.2153B6000000/temperature"; - logit $OLOG, "H", "/26.2153B6000000/HIH4000/humidity"; - logit $ILOG, "T", "/26.8859B6000000/temperature"; - logit $ILOG, "H", "/26.8859B6000000/HIH4000/humidity"; - $start += 60; - my $now = time; - while ($start < $now) { $start += 60; } - my $secs = $start - $now; - sleep ($secs); - } -} - -sub logit ($$$) -{ - my ($log, $name, $query) = @_; - - my $tries = 0; - while ($tries < 3) { - my $time = time; - my $datime = time2str ("%Y-%m-%d %H:%M:%S", $time, "UTC"); - $tries += 1; - my @lines = `/usr/bin/owread $query`; - chomp @lines; - my $status = $?; - my $sig = $status & 127; - $status >>= 8; - if ($status != 0) { - my $L = join "\\n", @lines; - print $log "$datime\t$name\terror: status $status: $L\n"; - $log->flush; - } elsif (@lines != 1) { - my $L = join "\\n", @lines; - print $log "$datime\t$name\terror: multiple lines: $L\n"; - $log->flush; - } elsif ($lines[0] !~ /^ *(-?\d+(\.\d+)?)$/) { - my $L = $lines[0]; - print $log "$datime\t$name\terror: bogus line: $L\n"; - $log->flush; - } else { - my $datum = $1; - print $log "$datime\t$name\t$datum\n"; - $log->flush; - return; - } - } -} - -sub mymkdir ($) -{ - my ($dirpath) = @_; - - my @path_names = split /\//, $dirpath; - my $path; - if (!$path_names[0]) { - $path = "/"; - shift @path_names; - } else { - $path = "."; - } - my @created; - while (@path_names) { - $path .= "/" . shift @path_names; - if (! -d $path) { - if (-e $path) { - die "mkdir $dirpath: already exists; not a directory!\n"; - } - if (! mkdir $path) { - die "mkdir $path: $!\n"; - } else { - chmod 02775, $path; - push @created, $path; - } - } - } - return @created; -} - -main; diff --git a/roles_t/abbey-weather/handlers/main.yml b/roles_t/abbey-weather/handlers/main.yml deleted file mode 100644 index e314f9d..0000000 --- a/roles_t/abbey-weather/handlers/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -- name: Reload Systemd. - become: yes - command: systemctl daemon-reload - -- name: Restart weather daemon. - become: yes - systemd: - service: weatherd - state: restarted diff --git a/roles_t/abbey-weather/tasks/main.yml b/roles_t/abbey-weather/tasks/main.yml deleted file mode 100644 index 4764eda..0000000 --- a/roles_t/abbey-weather/tasks/main.yml +++ /dev/null @@ -1,114 +0,0 @@ ---- -- name: Install weather daemon packages. - become: yes - apt: pkg=libtimedate-perl - -- name: Install 1-Wire server. - become: yes - apt: - pkg: [ owserver, ow-shell ] - -- name: Configure 1-Wire server. - become: yes - lineinfile: - path: /etc/owfs.conf - regexp: "{{ item.regexp }}" - line: "{{ item.line }}" - backrefs: yes - loop: - - { regexp: '^[# ]*server: *FAKE(.*)$', line: '#server: FAKE\1' } - - { regexp: '^[# ]*server: *usb(.*)$', line: 'server: usb\1' } - -- name: Install Rsync. - become: yes - apt: pkg=rsync - -- name: Create monkey. - become: yes - user: - name: monkey - system: yes - -- name: Authorize monkey@core. - become: yes - vars: - pubkeyfile: ../Secret/ssh_monkey/id_rsa.pub - authorized_key: - user: monkey - key: "{{ lookup('file', pubkeyfile) }}" - manage_dir: yes - -- name: Add {{ ansible_user }} to monkey group. - become: yes - user: - name: "{{ ansible_user }}" - append: yes - groups: monkey - -- name: Install weather directory. - become: yes - file: - path: /home/monkey/Weather/Log - state: directory - owner: monkey - group: monkey - mode: u=rwx,g=rx,o=rx - -- name: Test for weather daemon script. - vars: - dir: ../roles/abbey-weather/files - file: "{{ dir }}/daemon-{{ inventory_hostname }}" - stat: path="{{ file }}" - delegate_to: localhost - register: weather - -- name: Note missing weather daemon script. - vars: - dir: ../roles/abbey-weather/files - script: "{{ dir }}/daemon-{{ inventory_hostname }}" - debug: - msg: "{{ script }}: not found" - when: not weather.stat.exists - -- name: Install weather daemon. - become: yes - vars: - dir: ../roles/abbey-weather/files - script: "{{ dir }}/daemon-{{ inventory_hostname }}" - copy: - src: "{{ script }}" - dest: /home/monkey/Weather/daemon - owner: monkey - group: monkey - mode: u=rwx,g=rx,o= - when: weather.stat.exists - -- name: Install weatherd service. - become: yes - copy: - content: | - [Unit] - Description=Weather Logger - After=owserver.service - - [Service] - User=monkey - ExecStartPre=/bin/sleep 30 - ExecStart=/home/monkey/Weather/daemon - Restart=always - - [Install] - WantedBy=multi-user.target - dest: /etc/systemd/system/weatherd.service - when: weather.stat.exists - notify: - - Reload Systemd. - - Restart weather daemon. - -- name: Enable/Start weather daemon. - become: yes - systemd: - service: weatherd - enabled: yes - state: started - when: weather.stat.exists -- 2.25.1