<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Wifi on Andy Little</title><link>https://andylittle.net/tags/wifi/</link><description>Recent content in Wifi on Andy Little</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 17 Sep 2026 05:00:00 -0700</lastBuildDate><atom:link href="https://andylittle.net/tags/wifi/index.xml" rel="self" type="application/rss+xml"/><item><title>Tricking a Weather Station Into Uploading to a Raspberry Pi</title><link>https://andylittle.net/blog/2026/weewx-interceptor-isolated-weather-station/</link><pubDate>Thu, 17 Sep 2026 05:00:00 -0700</pubDate><guid>https://andylittle.net/blog/2026/weewx-interceptor-isolated-weather-station/</guid><description>A Vevor weather station that can only upload to Weather Underground, a Raspberry Pi 3 pretending to be wunderground.com on its own isolated wifi network, and weewx-interceptor catching the data. Plus the WPA2 handshake that failed nine times out of ten, and the console clock that was an hour fast.</description><content:encoded><![CDATA[<p>I bought a Vevor weather station. It&rsquo;s a rebadged Fine Offset unit, and its
wifi console (firmware 1.0.1) can send readings to exactly two places: Weather
Underground and WeatherCloud. The setup page has no custom server field and no
port field. You get those two cloud services or nothing.</p>
<p>I wanted the data on my own hardware, recorded by <a href="https://weewx.com/">weewx</a>,
and I didn&rsquo;t want an inexpensive IoT console on my LAN or talking to the
internet at all.</p>
<p>The end state: a Raspberry Pi 3 (<code>pi3-1</code>) runs its own WPA2 access point just
for the station. On that network the Pi&rsquo;s DNS server says it <em>is</em>
<code>wunderground.com</code>. The station uploads to the Pi, thinking it&rsquo;s Weather
Underground, and
<a href="https://github.com/matthewwall/weewx-interceptor">weewx-interceptor</a> parses the
upload and hands it to weewx. The station can&rsquo;t reach the LAN or the internet,
and the weewx reports are served to the LAN by nginx.</p>
<h2 id="how-it-fits-together">How it fits together</h2>
<pre tabindex="0"><code>Vevor console --wifi (WPA2, SSID weewx-ap)--&gt; Pi wlan0 192.168.5.1
  DHCP + DNS: dnsmasq
    wunderground.com -&gt; 192.168.5.1
    every other name -&gt; NXDOMAIN, nothing forwarded upstream
  HTTP to port 80 --nftables redirect--&gt; :8090 weewx-interceptor (wu-client)
    --&gt; weewxd --&gt; SQLite archive
  weewx reports --&gt; /var/www/weewx --&gt; nginx on port 80 (eth0, LAN)
  IP forwarding off + nftables drops all forwarding to/from wlan0
</code></pre><p>The station sends a Weather Underground protocol request, a plain HTTP GET to
<code>/weatherstation/updateweatherstation.php?ID=...&amp;tempf=...&amp;humidity=...</code>, and
the interceptor&rsquo;s <code>wu-client</code> mode already understands that format. Getting
the station to send that request to the Pi only takes a DNS lie and a port
redirect.</p>
<h2 id="hardware-and-host">Hardware and host</h2>
<ul>
<li>Raspberry Pi 3B, Raspberry Pi OS based on Debian 13 (trixie), wired to the
LAN on <code>eth0</code></li>
<li>The Pi 3&rsquo;s built-in BCM43430 wifi (<code>brcmfmac</code> driver, 2.4 GHz only) as the
access point on <code>wlan0</code></li>
<li>Vevor weather station console with a BL602 wifi chip</li>
</ul>
<p>The Pi already runs another service that uses port 8080, which is why the
interceptor listens on 8090 here.</p>
<h2 id="1-base-system">1. Base system</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt-get update <span class="o">&amp;&amp;</span> apt-get install -y git hostapd dnsmasq nginx
</span></span><span class="line"><span class="cl">timedatectl set-timezone America/Los_Angeles
</span></span><span class="line"><span class="cl">raspi-config nonint do_wifi_country US
</span></span><span class="line"><span class="cl">rfkill unblock wifi
</span></span></code></pre></div><h2 id="2-weewx-and-the-interceptor">2. weewx and the interceptor</h2>
<p>I installed weewx 5.5 with the git method, in a Python venv. <strong>Do this as your
normal user, not root.</strong> <code>weectl station create</code> writes the current user and
the venv&rsquo;s Python path into the systemd unit it generates.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 -m venv ~/weewx-venv
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/weewx-venv/bin/activate
</span></span><span class="line"><span class="cl">python3 -m pip install --upgrade pip
</span></span><span class="line"><span class="cl">python3 -m pip install CT3 configobj Pillow ephem
</span></span><span class="line"><span class="cl">git clone https://github.com/weewx/weewx ~/weewx
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">python3 ~/weewx/src/weectl.py station create --no-prompt <span class="se">\
</span></span></span><span class="line"><span class="cl">  --driver<span class="o">=</span>weewx.drivers.simulator --location<span class="o">=</span><span class="s2">&#34;Rocklin, CA&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --latitude<span class="o">=</span>&lt;lat&gt; --longitude<span class="o">=</span>&lt;lon&gt; --altitude<span class="o">=</span>&lt;alt&gt;,foot --units<span class="o">=</span>us
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">python3 ~/weewx/src/weectl.py extension install --yes <span class="se">\
</span></span></span><span class="line"><span class="cl">  https://github.com/matthewwall/weewx-interceptor/archive/master.zip
</span></span></code></pre></div><p>The simulator driver is only there so <code>station create</code> has something to
configure. It gets replaced in the next step. Then, as root, install the
service:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sh ~&lt;user&gt;/weewx-data/scripts/setup-daemon.sh
</span></span></code></pre></div><h2 id="3-weewxconf">3. weewx.conf</h2>
<p>The relevant changes in <code>~/weewx-data/weewx.conf</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Station]</span>
</span></span><span class="line"><span class="cl">    <span class="na">station_type</span> <span class="o">=</span> <span class="s">Interceptor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Interceptor]</span>
</span></span><span class="line"><span class="cl">    <span class="na">driver</span> <span class="o">=</span> <span class="s">user.interceptor
</span></span></span><span class="line"><span class="cl"><span class="s">    device_type = wu-client
</span></span></span><span class="line"><span class="cl"><span class="s">    port = 8090</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[StdReport]</span>
</span></span><span class="line"><span class="cl">    <span class="na">HTML_ROOT</span> <span class="o">=</span> <span class="s">/var/www/weewx</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[StdArchive]</span>
</span></span><span class="line"><span class="cl">    <span class="na">archive_interval</span> <span class="o">=</span> <span class="s">300
</span></span></span><span class="line"><span class="cl"><span class="s">    record_generation = software</span>
</span></span></code></pre></div><p>Two notes:</p>
<ul>
<li><strong><code>record_generation</code> must be <code>software</code>.</strong> The interceptor has no hardware
archive memory to read records from. With <code>hardware</code>, weewx starts up and
logs loop packets, but it never saves an archive record.</li>
<li><code>HTML_ROOT</code> points outside the home directory, at a directory the weewx user
owns. Home directories on this Pi are mode 700, so nginx can&rsquo;t read the
default <code>~/weewx-data/public_html</code>. The smartphone and mobile sub-reports
need their <code>HTML_ROOT</code> moved under <code>/var/www/weewx</code> too.</li>
</ul>
<h2 id="4-patching-the-interceptor">4. Patching the interceptor</h2>
<p>Version 0.60 of the interceptor needed four small fixes for this station, all
in <code>~/weewx-data/bin/user/interceptor.py</code>. Keep a copy of the original, because
reinstalling the extension overwrites your patches.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gu">@@ do_POST
</span></span></span><span class="line"><span class="cl"><span class="gd">-            data = str(self.rfile.read(length))
</span></span></span><span class="line"><span class="cl"><span class="gi">+            data = _bytes_to_str(self.rfile.read(length))
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">@@ WUClient LABEL_MAP
</span></span></span><span class="line"><span class="cl">             &#39;solarradiation&#39;: &#39;solar_radiation&#39;,
</span></span><span class="line"><span class="cl"><span class="gi">+            &#39;solarRadiation&#39;: &#39;solar_radiation&#39;,
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">@@ WUClient IGNORED_LABELS
</span></span></span><span class="line"><span class="cl"><span class="gi">+            &#39;rainin&#39;,
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">@@ WUClient.Parser.parse
</span></span></span><span class="line"><span class="cl"><span class="gd">-                pkt[&#39;dateTime&#39;] = self.decode_datetime(
</span></span></span><span class="line"><span class="cl"><span class="gd">-                    data.pop(&#39;dateutc&#39;, int(time.time() + 0.5)))
</span></span></span><span class="line"><span class="cl"><span class="gi">+                # Console clock is 1 h fast (no DST) and has no NTP: use the Pi clock
</span></span></span><span class="line"><span class="cl"><span class="gi">+                data.pop(&#39;dateutc&#39;, None)
</span></span></span><span class="line"><span class="cl"><span class="gi">+                pkt[&#39;dateTime&#39;] = int(time.time() + 0.5)
</span></span></span></code></pre></div><p>What each one fixes:</p>
<ol>
<li><strong>Python 3 bytes bug.</strong> <code>str()</code> on a <code>bytes</code> object gives you the string
<code>&quot;b'...'&quot;</code>, not the decoded body. The parsed values then fail with
<code>could not convert string to float</code>. The module already has a
<code>_bytes_to_str</code> helper. It just wasn&rsquo;t used here.</li>
<li><strong>camelCase label.</strong> The Vevor sends <code>solarRadiation</code> where the interceptor
expects <code>solarradiation</code>. Without the extra mapping, the log fills with
&ldquo;unrecognized parameter&rdquo; warnings and solar radiation is never recorded.</li>
<li><strong>Rain.</strong> The station sends both <code>rainin</code> (rain in the last hour) and
<code>dailyrainin</code>. weewx works out rain per interval from the running daily
total, so <code>rainin</code> is ignored to avoid counting the same rain twice.</li>
<li><strong>Timestamps.</strong> The console&rsquo;s clock was an hour ahead. It doesn&rsquo;t handle
daylight saving time, and on an isolated network it can&rsquo;t reach an NTP
server to correct itself. Records were being stamped an hour in the future.
The Pi&rsquo;s clock is correct, so the patch ignores <code>dateutc</code> and stamps each
packet when it arrives.</li>
</ol>
<h2 id="5-the-access-point-hostapd-not-networkmanager">5. The access point: hostapd, not NetworkManager</h2>
<p>This part took the most time.</p>
<p>Raspberry Pi OS uses NetworkManager, and NetworkManager can run a wifi hotspot.
An <em>open</em> hotspot worked fine. A WPA2 hotspot never completed a handshake with
any client, not just the weather station. A kernel and firmware upgrade didn&rsquo;t
change that. Switching to plain <code>hostapd</code> got WPA2 working.</p>
<p>First, tell NetworkManager to leave <code>wlan0</code> alone
(<code>/etc/NetworkManager/conf.d/99-weewx-ap.conf</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[keyfile]</span>
</span></span><span class="line"><span class="cl"><span class="na">unmanaged-devices</span><span class="o">=</span><span class="s">interface-name:wlan0</span>
</span></span></code></pre></div><p>Then <code>/etc/hostapd/hostapd.conf</code>:</p>
<pre tabindex="0"><code>interface=wlan0
driver=nl80211
ssid=weewx-ap
country_code=US
ieee80211d=1
hw_mode=g
channel=6
ieee80211n=1
wmm_enabled=1
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
rsn_pairwise=CCMP
ieee80211w=0
wpa_passphrase=&lt;your passphrase&gt;
eapol_version=1
wpa_pairwise_update_count=10
</code></pre><h3 id="the-slow-handshake">The slow handshake</h3>
<p>Even with hostapd and the default settings, the weather station failed to
join about nine times out of ten. With debug logging on, hostapd showed the reason:</p>
<pre tabindex="0"><code>WPA: PTKSTART: Retry limit 4 reached
</code></pre><p>The station&rsquo;s BL602 wifi chip is slow to answer the WPA2 4-way handshake.
hostapd sends message 1, waits, resends, and gives up before the station
replies. The last two lines of the config fix it:</p>
<ul>
<li><code>wpa_pairwise_update_count=10</code> lets hostapd resend handshake messages up to
10 times instead of 4, so a slow client has time to answer.</li>
<li><code>eapol_version=1</code> makes hostapd use the older EAPOL version, which some
embedded wifi stacks handle better.</li>
</ul>
<p>After that change, the station joined on the first try every time. If you
turned up hostapd&rsquo;s logging to debug this (<code>logger_syslog_level=0</code>), turn it
back down afterwards.</p>
<h2 id="6-dhcp-and-the-dns-lie">6. DHCP and the DNS lie</h2>
<p><code>/etc/dnsmasq.d/weewx-ap.conf</code>:</p>
<pre tabindex="0"><code>interface=wlan0
bind-interfaces
except-interface=lo
dhcp-range=192.168.5.10,192.168.5.254,1h
dhcp-option=option:router,192.168.5.1
dhcp-option=option:dns-server,192.168.5.1

# Never forward upstream. Only wunderground.com resolves, to this Pi.
no-resolv
no-poll
address=/wunderground.com/192.168.5.1
address=/#/
</code></pre><p><code>address=/wunderground.com/...</code> covers the domain and all its subdomains, so
<code>rtupdate.wunderground.com</code> and <code>weatherstation.wunderground.com</code> both resolve
to the Pi. <code>address=/#/</code> with no address answers every other name with
NXDOMAIN. <code>no-resolv</code> means dnsmasq never asks an upstream server, so the
station can&rsquo;t learn a real address for anything.</p>
<p>I used 192.168.5.0/24 for the AP subnet because the console&rsquo;s own setup hotspot
uses 192.168.4.1, and I didn&rsquo;t want the two to overlap.</p>
<h2 id="7-redirect-and-isolation-firewall">7. Redirect and isolation firewall</h2>
<p>A small script sets the address on <code>wlan0</code> and loads an nftables table, at
<code>/usr/local/sbin/weewx-ap-net</code> (mode 755):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span> -e
</span></span><span class="line"><span class="cl">ip link <span class="nb">set</span> wlan0 up
</span></span><span class="line"><span class="cl">ip addr replace 192.168.5.1/24 dev wlan0
</span></span><span class="line"><span class="cl">nft delete table inet weewx 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span>
</span></span><span class="line"><span class="cl">nft -f - <span class="s">&lt;&lt;&#39;NFT&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">table inet weewx {
</span></span></span><span class="line"><span class="cl"><span class="s">	chain prerouting {
</span></span></span><span class="line"><span class="cl"><span class="s">		type nat hook prerouting priority dstnat; policy accept;
</span></span></span><span class="line"><span class="cl"><span class="s">		iifname &#34;wlan0&#34; tcp dport 80 counter redirect to :8090
</span></span></span><span class="line"><span class="cl"><span class="s">		iifname &#34;wlan0&#34; tcp dport 443 counter
</span></span></span><span class="line"><span class="cl"><span class="s">	}
</span></span></span><span class="line"><span class="cl"><span class="s">	chain input {
</span></span></span><span class="line"><span class="cl"><span class="s">		type filter hook input priority filter; policy accept;
</span></span></span><span class="line"><span class="cl"><span class="s">		iifname != &#34;wlan0&#34; accept
</span></span></span><span class="line"><span class="cl"><span class="s">		ct state established,related accept
</span></span></span><span class="line"><span class="cl"><span class="s">		udp dport 67 accept
</span></span></span><span class="line"><span class="cl"><span class="s">		meta l4proto { tcp, udp } th dport 53 accept
</span></span></span><span class="line"><span class="cl"><span class="s">		tcp dport 8090 accept
</span></span></span><span class="line"><span class="cl"><span class="s">		icmp type echo-request accept
</span></span></span><span class="line"><span class="cl"><span class="s">		counter drop
</span></span></span><span class="line"><span class="cl"><span class="s">	}
</span></span></span><span class="line"><span class="cl"><span class="s">	chain forward {
</span></span></span><span class="line"><span class="cl"><span class="s">		type filter hook forward priority filter; policy accept;
</span></span></span><span class="line"><span class="cl"><span class="s">		iifname &#34;wlan0&#34; counter drop
</span></span></span><span class="line"><span class="cl"><span class="s">		oifname &#34;wlan0&#34; counter drop
</span></span></span><span class="line"><span class="cl"><span class="s">	}
</span></span></span><span class="line"><span class="cl"><span class="s">}
</span></span></span><span class="line"><span class="cl"><span class="s">NFT</span>
</span></span></code></pre></div><ul>
<li><strong>prerouting</strong> sends the station&rsquo;s HTTP requests to the interceptor. The
port 443 rule only counts packets, so you can see whether the station ever
tries HTTPS.</li>
<li><strong>input</strong> lets wifi clients reach DHCP, DNS, the interceptor and ping, and
nothing else on the Pi. SSH and nginx can&rsquo;t be reached from <code>wlan0</code>.</li>
<li><strong>forward</strong> drops all traffic to or from <code>wlan0</code>. IP forwarding is also
left <strong>off</strong> (no <code>net.ipv4.ip_forward=1</code>, no masquerade), so the
isolation doesn&rsquo;t depend on a single setting.</li>
</ul>
<p>A oneshot unit runs the script at boot
(<code>/etc/systemd/system/weewx-ap-net.service</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">weewx AP: wlan0 address, port redirect and isolation firewall</span>
</span></span><span class="line"><span class="cl"><span class="na">Before</span><span class="o">=</span><span class="s">hostapd.service dnsmasq.service</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">NetworkManager.service</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="cl"><span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">yes</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/local/sbin/weewx-ap-net</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>hostapd and dnsmasq each get a drop-in
(<code>/etc/systemd/system/hostapd.service.d/weewx-ap.conf</code> and the same path for
<code>dnsmasq.service.d</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">weewx-ap-net.service</span>
</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">weewx-ap-net.service</span>
</span></span></code></pre></div><p>Use <code>Wants=</code>, not <code>Requires=</code>. With <code>Requires=</code>, restarting <code>weewx-ap-net</code> to
reload the firewall also restarts hostapd, and the station is kicked off the
network.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl daemon-reload
</span></span><span class="line"><span class="cl">systemctl unmask hostapd
</span></span><span class="line"><span class="cl">systemctl <span class="nb">enable</span> --now weewx-ap-net hostapd dnsmasq
</span></span></code></pre></div><h2 id="8-serving-the-reports">8. Serving the reports</h2>
<p><code>/etc/nginx/sites-available/weewx</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span> <span class="mi">80</span> <span class="s">default_server</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span> <span class="s">[::]:80</span> <span class="s">default_server</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span> <span class="s">_</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">root</span> <span class="s">/var/www/weewx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">index</span> <span class="s">index.html</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span> <span class="kn">try_files</span> <span class="nv">$uri</span> <span class="nv">$uri/</span> <span class="p">=</span><span class="mi">404</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rm /etc/nginx/sites-enabled/default
</span></span><span class="line"><span class="cl">ln -s /etc/nginx/sites-available/weewx /etc/nginx/sites-enabled/weewx
</span></span><span class="line"><span class="cl">systemctl reload nginx
</span></span></code></pre></div><p>LAN clients get the weewx Seasons report at <code>http://&lt;pi-address&gt;/</code>. Devices on
the station&rsquo;s wifi can&rsquo;t load it, because port 80 on <code>wlan0</code> goes to the
interceptor. That&rsquo;s fine, since the station is the only thing on that network.</p>
<h2 id="9-pointing-the-console-at-it">9. Pointing the console at it</h2>
<p>On the Vevor console&rsquo;s setup page, join the <code>weewx-ap</code> network and enable
Weather Underground with any station ID and key (<code>1234</code> works, since the
interceptor doesn&rsquo;t check them). WeatherCloud can stay enabled. Its DNS
lookups get NXDOMAIN and it quietly fails.</p>
<h2 id="checking-that-it-works">Checking that it works</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">iw dev wlan0 station dump                 <span class="c1"># station is associated</span>
</span></span><span class="line"><span class="cl">cat /var/lib/misc/dnsmasq.leases          <span class="c1"># and got a 192.168.5.x lease</span>
</span></span><span class="line"><span class="cl">journalctl -u hostapd <span class="p">|</span> grep <span class="s2">&#34;handshake completed&#34;</span>
</span></span><span class="line"><span class="cl">nft list table inet weewx                 <span class="c1"># port-80 counter rising, drop counters at 0</span>
</span></span><span class="line"><span class="cl">journalctl -u weewx -f                    <span class="c1"># &#34;Added record ...&#34; every 5 minutes</span>
</span></span><span class="line"><span class="cl">curl http://&lt;pi-address&gt;/                 <span class="c1"># weewx Seasons page</span>
</span></span></code></pre></div><p>In <code>nft list table inet weewx</code>, the port 80 redirect counter should go up with
every upload. The forward drop counters should stay at zero. If they start
climbing, the station is trying to reach something other than &ldquo;Weather
Underground&rdquo;.</p>
<h2 id="gotchas-collected">Gotchas, collected</h2>
<ul>
<li><strong>NetworkManager&rsquo;s WPA2 hotspot on the Pi 3 (brcmfmac) never completed a
handshake.</strong> An open hotspot worked. hostapd fixed it.</li>
<li><strong>The BL602 chip answers the handshake slowly.</strong> Set
<code>wpa_pairwise_update_count=10</code> and <code>eapol_version=1</code>, or expect &ldquo;Retry limit
4 reached&rdquo;.</li>
<li><strong>The interceptor&rsquo;s POST handling has a Python 3 bug</strong>, which shows up as
<code>could not convert string to float</code>.</li>
<li><strong><code>record_generation = hardware</code> saves no records</strong> with the interceptor.
Use <code>software</code>.</li>
<li><strong>The console clock is an hour fast</strong> and can&rsquo;t sync on an isolated
network, so use the Pi&rsquo;s clock for timestamps.</li>
<li><strong>Don&rsquo;t overlap the console&rsquo;s setup subnet</strong> (192.168.4.x).</li>
<li><strong><code>sqlite3</code> isn&rsquo;t installed by default.</strong> Query the archive with the venv&rsquo;s
Python <code>sqlite3</code> module instead of installing the CLI.</li>
<li><strong>Run git in <code>~/weewx</code> as the weewx user.</strong> As root, git refuses with a
&ldquo;dubious ownership&rdquo; error.</li>
</ul>
<p>The station now posts its &ldquo;Weather Underground&rdquo; updates to a Pi in the same
house. The station has no idea, and Weather Underground never hears from it.</p>
]]></content:encoded></item></channel></rss>