** 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
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
* 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
kamino:
kessel:
ord-mantell:
- weather:
- hosts:
- anoat:
dvrs:
hosts:
dantooine:
hosts: campus
roles: [ campus, abbey-cloister ]
-- name: Configure Weather
- hosts: weather
- roles: [ abbey-weather ]
-
- name: Configure DVRs
hosts: dvrs
roles: [ abbey-dvr ]
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
+++ /dev/null
-#!/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;