Heater algoritm now 10/10min, was 5/15min

master
marcel 9 months ago
parent 82342b82a5
commit 08b845f792
  1. 6
      CHANGELOG.md
  2. 9
      article/Makefile
  3. 563
      article/css/mvp.css
  4. 33282
      article/images/weather_station_schematic.svg
  5. 16
      article/template/custom_settings.tex
  6. 120
      article/template/template.html
  7. 205
      article/weather_station_article.html
  8. 71
      article/weather_station_article.md
  9. 12
      build-doc/weather_station.html
  10. 4
      build-doc/weather_station.md
  11. BIN
      build-doc/weather_station.pdf
  12. 57
      build-doc/weather_station_article.md
  13. 8
      firmware/weather_station.ino

@ -51,3 +51,9 @@ All notable changes to this project will be documented in this file.
- Changed some variables to the propper standard (uint8_t, uint16_t, etc.) - Changed some variables to the propper standard (uint8_t, uint16_t, etc.)
- SparkFun wind interrupt now calculates over 3 seconds in stead of 1 second (KNMI standard)] - SparkFun wind interrupt now calculates over 3 seconds in stead of 1 second (KNMI standard)]
## [0.2.3] - 2024-02-28
### Changed
- Humidity often got stuck at 100%. Heater algorithm now heats for 10 minutes and cools down for 10 minutes (was 5min/15min)

@ -0,0 +1,9 @@
html:
pandoc -s weather_station_article.md --toc --toc-depth=1 -c ./css/mvp.css \
--template ./template/template.html -o weather_station_article.html
pdf:
pandoc weather_station_article.md --toc -o weather_station_article.pdf --pdf-engine=xelatex -H ./template/custom_settings.tex
clean:
rm -rvf build

@ -0,0 +1,563 @@
/* MVP.css v1.14 - https://github.com/andybrewer/mvp */
/* Edited by Konstapel https://meezenest.nl/mees */
:root {
--active-brightness: 0.85;
--border-radius: 5px;
--box-shadow: 2px 2px 10px;
--color-accent: #f4f0ec;
--color-bg: #fff;
--color-bg-secondary: #e9e9e9;
--color-link: #a9a9a9;
--color-secondary: #3366FF;
--color-secondary-accent: #920de90b;
--color-shadow: #f4f4f4;
--color-table: #a9a9a9;
--color-text: #000;
--color-text-secondary: #999;
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
--hover-brightness: 1.2;
--justify-important: center;
--justify-normal: left;
--line-height: 1.5;
--width-card: 285px;
--width-card-medium: 460px;
--width-card-wide: 1080px;
--width-content: 1080px;
}
@media (prefers-color-scheme: dark) {
:root[color-mode="user"] {
--color-accent: #0097fc4f;
--color-bg: #333;
--color-bg-secondary: #555;
--color-link: #0097fc;
--color-secondary: #e20de9;
--color-secondary-accent: #e20de94f;
--color-shadow: #bbbbbb20;
--color-table: #0097fc;
--color-text: #f7f7f7;
--color-text-secondary: #aaa;
}
}
html {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
/* Layout */
article aside {
background: var(--color-secondary-accent);
border-left: 4px solid var(--color-secondary);
padding: 0.01rem 0.8rem;
}
body {
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-family);
line-height: var(--line-height);
margin: 0;
overflow-x: hidden;
padding: 0;
font-size: 110%;
}
footer,
header,
main {
margin: 0 auto;
max-width: var(--width-content);
padding: 3rem 1rem;
}
main {
border: 1px solid lightgray;
border-radius: 5px;
}
footer {
font-size: 0.7em;
}
hr {
background-color: var(--color-bg-secondary);
border: none;
height: 1px;
margin: 4rem 0;
width: 100%;
}
section {
display: flex;
flex-wrap: wrap;
justify-content: var(--justify-important);
}
section img,
article img {
max-width: 100%;
}
section pre {
overflow: auto;
}
section aside {
border: 1px solid var(--color-bg-secondary);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow) var(--color-shadow);
margin: 1rem;
padding: 1.25rem;
width: var(--width-card);
}
section aside:hover {
box-shadow: var(--box-shadow) var(--color-bg-secondary);
}
[hidden] {
display: none;
}
/* Headers */
article header,
div header,
main header {
padding-top: 0;
}
header {
text-align: var(--justify-important);
}
header a b,
header a em,
header a i,
header a strong {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
header nav img {
margin: 1rem 0;
}
section header {
padding-top: 0;
width: 100%;
}
/* Nav */
nav {
align-items: center;
display: flex;
font-weight: bold;
justify-content: space-between;
margin-bottom: 7rem;
}
nav ul {
list-style: none;
padding: 0;
}
nav ul li {
display: inline-block;
margin: 0 0.5rem;
position: relative;
text-align: left;
}
/* Nav Dropdown */
nav ul li:hover ul {
display: block;
}
nav ul li ul {
background: var(--color-bg);
border: 1px solid var(--color-bg-secondary);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow) var(--color-shadow);
display: none;
height: auto;
left: -2px;
padding: .5rem 1rem;
position: absolute;
top: 1.7rem;
white-space: nowrap;
width: auto;
z-index: 1;
}
nav ul li ul::before {
/* fill gap above to make mousing over them easier */
content: "";
position: absolute;
left: 0;
right: 0;
top: -0.5rem;
height: 0.5rem;
}
nav ul li ul li,
nav ul li ul li a {
display: block;
}
/* Typography */
code,
samp {
background-color: var(--color-accent);
border-radius: var(--border-radius);
color: var(--color-text);
display: inline-block;
margin: 0 0.1rem;
padding: 0 0.5rem;
}
details {
margin: 1.3rem 0;
}
details summary {
font-weight: bold;
cursor: pointer;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: var(--line-height);
}
h1 {
font-size: 1.875em;
border-bottom: 1px solid lightgray;
}
h2 {
font-size: 1.4em;
}
mark {
padding: 0.1rem;
}
ol li,
ul li {
padding: 0.2rem 0;
}
p {
margin: 0.75rem 0;
padding: 0;
width: 100%;
}
pre {
margin: 1rem 0;
max-width: var(--width-card-wide);
padding: 1rem 0;
}
pre code,
pre samp {
display: block;
max-width: var(--width-card-wide);
padding: 0.8rem 2rem;
/*white-space: pre-wrap;*/
overflow-x: auto;
}
small {
color: var(--color-text-secondary);
}
sup {
background-color: var(--color-secondary);
border-radius: var(--border-radius);
color: var(--color-bg);
font-size: xx-small;
font-weight: bold;
margin: 0.2rem;
padding: 0.2rem 0.3rem;
position: relative;
top: -2px;
}
/* Links */
a {
color: var(--color-link);
display: inline-block;
font-weight: bold;
text-decoration: underline;
}
a:active {
filter: brightness(var(--active-brightness));
}
a:hover {
filter: brightness(var(--hover-brightness));
}
a b,
a em,
a i,
a strong,
button,
input[type="submit"] {
border-radius: var(--border-radius);
display: inline-block;
font-size: medium;
font-weight: bold;
line-height: var(--line-height);
margin: 0.5rem 0;
padding: 1rem 2rem;
}
button,
input[type="submit"] {
font-family: var(--font-family);
}
button:active,
input[type="submit"]:active {
filter: brightness(var(--active-brightness));
}
button:hover,
input[type="submit"]:hover {
cursor: pointer;
filter: brightness(var(--hover-brightness));
}
a b,
a strong,
button,
input[type="submit"] {
background-color: var(--color-link);
border: 2px solid var(--color-link);
color: var(--color-bg);
}
a em,
a i {
border: 2px solid var(--color-link);
border-radius: var(--border-radius);
color: var(--color-link);
display: inline-block;
padding: 1rem 2rem;
}
article aside a {
color: var(--color-secondary);
}
/* Images */
figure {
margin: 0;
padding: 0;
text-align: center;
}
figure img {
max-width: 100%;
max-height: 500px;
}
figure figcaption {
color: var(--color-text-secondary);
}
/* Forms */
button:disabled,
input:disabled {
background: var(--color-bg-secondary);
border-color: var(--color-bg-secondary);
color: var(--color-text-secondary);
cursor: not-allowed;
}
button[disabled]:hover,
input[type="submit"][disabled]:hover {
filter: none;
}
form {
border: 1px solid var(--color-bg-secondary);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow) var(--color-shadow);
display: block;
max-width: var(--width-card-wide);
min-width: var(--width-card);
padding: 1.5rem;
text-align: var(--justify-normal);
}
form header {
margin: 1.5rem 0;
padding: 1.5rem 0;
}
input,
label,
select,
textarea {
display: block;
font-size: inherit;
max-width: var(--width-card-wide);
}
input[type="checkbox"],
input[type="radio"] {
display: inline-block;
}
input[type="checkbox"]+label,
input[type="radio"]+label {
display: inline-block;
font-weight: normal;
position: relative;
top: 1px;
}
input[type="range"] {
padding: 0.4rem 0;
}
input,
select,
textarea {
border: 1px solid var(--color-bg-secondary);
border-radius: var(--border-radius);
margin-bottom: 1rem;
padding: 0.4rem 0.8rem;
}
input[type="text"],
textarea {
width: calc(100% - 1.6rem);
}
input[readonly],
textarea[readonly] {
background-color: var(--color-bg-secondary);
}
label {
font-weight: bold;
margin-bottom: 0.2rem;
}
/* Popups */
dialog {
border: 1px solid var(--color-bg-secondary);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow) var(--color-shadow);
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50%;
z-index: 999;
}
/* Tables */
table {
border: 1px solid var(--color-bg-secondary);
border-radius: var(--border-radius);
border-spacing: 0;
display: inline-block;
max-width: 100%;
overflow-x: auto;
padding: 0;
white-space: nowrap;
}
table td,
table th,
table tr {
padding: 0.4rem 0.8rem;
text-align: var(--justify-important);
}
table thead {
background-color: var(--color-table);
border-collapse: collapse;
border-radius: var(--border-radius);
/*color: var(--color-bg);*/
margin: 0;
padding: 0;
}
table thead th:first-child {
border-top-left-radius: var(--border-radius);
}
table thead th:last-child {
border-top-right-radius: var(--border-radius);
}
table thead th:first-child,
table tr td:first-child {
text-align: var(--justify-normal);
}
table tr:nth-child(even) {
background-color: var(--color-accent);
}
/* Quotes */
blockquote {
display: block;
font-size: x-large;
line-height: var(--line-height);
margin: 1rem auto;
max-width: var(--width-card-medium);
padding: 1.5rem 1rem;
text-align: var(--justify-important);
}
blockquote footer {
color: var(--color-text-secondary);
display: block;
font-size: small;
line-height: var(--line-height);
padding: 1.5rem 0;
}
/* Scrollbars */
* {
scrollbar-width: thin;
scrollbar-color: rgb(202, 202, 232) auto;
}
*::-webkit-scrollbar {
width: 5px;
height: 5px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: rgb(202, 202, 232);
border-radius: 10px;
}
h1.title {
border-bottom: 0px solid white;
}

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 586 KiB

@ -0,0 +1,16 @@
% Override default figure placement To be within the flow of the text rather
% than on it's own page.
\usepackage{float}
\usepackage{graphicx}
\makeatletter
\def\fps@figure{H}
\makeatother
% Scale all large images to 50% of twext width
\makeatletter
\setkeys{Gin}{width=\ifdim\Gin@nat@width>\linewidth
0.5\linewidth
\else
\Gin@nat@width
\fi}
\makeatother

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang$" xml:lang="$lang$"$if(dir)$ dir="$dir$"$endif$>
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
$for(author-meta)$
<meta name="author" content="$author-meta$" />
$endfor$
$if(date-meta)$
<meta name="dcterms.date" content="$date-meta$" />
$endif$
$if(keywords)$
<meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" />
$endif$
<title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title>
$if(highlighting-css)$
<style type="text/css">
$highlighting-css$
</style>
$endif$
$for(css)$
<link rel="stylesheet" href="$css$" />
$endfor$
<style type="text/css">
:root {
--width-content: 1080px;
}
nav {
justify-content: space-around;
}
$if(quotes)$
q { quotes: "“" "”" "‘" "’"; }
$endif$
</style>
$if(math)$
$math$
$endif$
$for(header-includes)$
$header-includes$
$endfor$
</head>
<body>
$for(include-before)$
$include-before$
$endfor$
$if(title)$
<header id="title-block-header">
<nav id="$idprefix$TOC">
<ul>
$if(toc)$
<li>
<a href="#">Index</a>
$table-of-contents$
</li>
$endif$
$if(pdf_version)$
<li>
<a href="$pdf_version$">PDF version</a>
</li>
$endif$
$if(git_repo)$
<li>
<a href="$git_repo$">Git repo</a>
</li>
$endif$
$if(website)$
<li>
<a href="$page_back$">Back</a>
</li>
$endif$
</ul>
<a href="https://www.meezenest.nl/mees/"><img alt="Logo" src="$logo$" height="70"></a>
</nav>
<h1 class="title">$title$</h1>
$if(subtitle)$
<p class="subtitle">$subtitle$</p>
$endif$
$for(author)$
<p class="author">$author$</p>
$endfor$
$if(date)$
<p class="date">$date$</p>
$endif$
$if(pdf_version)$
<p><a href="$pdf_version$"><i>PDF version</i></a></p>
$endif$
</header>
$endif$
<main>
<article>
$if(abstract)$
<p><b>Abstract </b>$abstract$</p>
$endif$
$body$
$for(include-after)$
$include-after$
$endfor$
<hr>
</article>
</main>
<footer>
<p>&copy;
$if(date)$
$date$
$endif$
$for(author)$
$author$
$endfor$
$if(website)$
<a href="$website$">$website$</a>
$endif$
</p><p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
</p>
</footer>
</body>
</html>

@ -0,0 +1,205 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="author" content="M.T. Konstapel" />
<meta name="dcterms.date" content="2024-02-03" />
<title>APRS weerstation</title>
<link rel="stylesheet" href="./css/mvp.css" />
<style type="text/css">
:root {
--width-content: 1080px;
}
nav {
justify-content: space-around;
}
</style>
</head>
<body>
<header id="title-block-header">
<nav id="TOC">
<ul>
<li>
<a href="#">Index</a>
<ul>
<li><a href="#weerstation"
id="toc-weerstation">Weerstation</a></li>
<li><a href="#aprs" id="toc-aprs">APRS</a></li>
<li><a href="#hamnet" id="toc-hamnet">HamNet</a></li>
<li><a href="#meer-informatie" id="toc-meer-informatie">Meer
informatie</a></li>
<li><a href="#verantwoording"
id="toc-verantwoording">Verantwoording</a></li>
</ul>
</li>
<li>
<a href="./weather_station_article.pdf">PDF version</a>
</li>
<li>
<a href="https://git.meezenest.nl/marcel/weather_station">Git repo</a>
</li>
<li>
<a href="https://meezenest.nl/mees/weather_station.html">Back</a>
</li>
</ul>
<a href="https://www.meezenest.nl/mees/"><img alt="Logo" src="./images/mees_logo.svg" height="70"></a>
</nav>
<h1 class="title">APRS weerstation</h1>
<p class="subtitle">met hamnet verbinding</p>
<p class="author">M.T. Konstapel</p>
<p class="date">2024-02-03</p>
<p><a href="./weather_station_article.pdf"><i>PDF version</i></a></p>
</header>
<main>
<article>
<p><b>Abstract </b><p>Ik maak veel gebruik van het APRS netwerk:
positiebepaling, telemetry en berichten versturen; ik doe het allemaal.
Dit kan omdat ik dicht bij de Duitse grens woon: anders dan in Nederland
waar het APRS netwerk dankzij lastige regelgeving nagenoeg is verdwenen,
is in Duitsland het netwerk nog springlevend. Ik maak gebruik van Duitse
digipeaters en iGates, die gewoon berichten vanaf het internet mogen
doorzenden. Wat een geluk! Het enige wat ik nog niet kon, was
weergegevens delen via het netwerk. Om daar verandering in te brengen
heb ik een weerstation ontworpen dat elke 10 minuten een weerbericht kan
uitzenden via het APRS netwerk. En omdat het systeem zo’n 100 meter van
mijn huis in de acchtertuin staat, heb ik er ook een 5 GHz hamnet
verbinding naar toe gemaakt, zodat ik het systeem op afstand kan
bedienen. Het hamnet gebruik ik ook om de weermetingen naar mijn Grafana
dashboard te sturen. Oh, en omdat twee beter is dan een heb ik er ook
een 20KB/s hamnet link over 70cm LoRa als backup in geknutseld.</p></p>
<h1 id="weerstation">Weerstation</h1>
<p>Als uitgangspunt van het weerstation gebruik ik de SparkFun Weather
Meter. Dit is een kit met drie sensors: een windvaan, een anemometer en
een regenmeter. Deze kit heb ik aangevuld met sensors voor temperatuur,
luchtdruk en luctvochtigheid. Al deze sensors zijn rechtstreeks
aangesloten op een Arduino Mini Pro. Ik heb daar een RS-485 driverchip
en een ompoolbeveiliging aan toegevoegd. Het uiteindelijke schema is
hieronder te zien. Ingewikkeld is de hardware niet, want alle
fuctionaliteit zit in de software.</p>
<figure>
<img src="./images/weather_station_schematic.svg" title="Schema"
alt="Schema" />
<figcaption aria-hidden="true">Schema</figcaption>
</figure>
<p>Het weerstation is uit te lezen via een ModBus interface. Dit is een
industriestandaard, dus er zijn legio mogelijkheden om met het
weerstation te communiceren. De ModBus registers bevatten de meetwaarden
van de sensors en worden elke twee seconde ververst. Dit bepaald dus de
maximale uitleesfrequentie. De volgende gegevens zijn beschikbaar:</p>
<ul>
<li>Windrichting in graden</li>
<li>Gemiddelde wind snelheid van de laatste 10 minuten in m/s</li>
<li>Maximale windstoot van de laatste 10 minuten in m/s</li>
<li>Hoeveelheid regen in het afgelopen uur in mm</li>
<li>Hoeveelheid regen in de afgelopen 24 uur in mm</li>
<li>Temperatuur in graden C</li>
<li>Luchtvochtigheid in %</li>
<li>Luchtdruk in hPa</li>
</ul>
<p>Daarnaast zijn er nog een aantal statusregisters beschikbaar. Deze
worden besproken in de uitgebreide bouwbeschrijving die beschikbaar is
op mijn website.</p>
<p>De luchtvochtigheidssensor kan bij een hoge luchtvochtigheid
verzadigd raken en zo blijven steken op 100%. Om dit te voorkomen is het
mogelijk om de sensor automatisch te laten verwarmen wanneer de
luchtvochtigheid langer dan een uur boven de 96% is. De verwarming wordt
dan elke 20 minuten voor 5 minuten aangezet. In de 15 minuten die
overblijven koelt de sensor weer af tot de omgevingstemperatuur. Dit
proces wordt heraald totdat de sensor weer een waarde beneden de 96%
aangeeft. Tijdens het opwarmen en afkoelen kan de luchtvochtigheid en
temperatuur maar eens in de 20 minuten worden gemeten. Dit is de prijs
die betaald moet worden wanneer we een goedkope luchtvochtigheidssensor
gebruiken.</p>
<h1 id="aprs">APRS</h1>
<p>Om de weermetingen te kunnen uitzenden via het APRS netwerk is er een
2 meter FM zender (een oude Alinco portofoon) en een 1200baud modem (een
variant op het MicroModem van markqvist) nodig. En een computer om de
gegevens via de ModBus uit het weerstation te lezen en door te sturen
naar het modem. Een Raspberry Pi Zero 2W is daar perfect geschikt voor.
Deze is goedkoop, klein en verbruikt weinig energie. Omdat een APRS
weerstation ook zijn positie en tijd moet doorgeven om op de kaart gezet
te kunnen worden is er een GPS module via USB aangesloten op de
Raspberry Pi. Strikt genomen is de tijd niet noodzakelijk en omdat het
station vast is opgesteld kan de positie ook handmatig worden ingesteld,
maar een gps module voegt weer extra complexiteit toe en dat maakt het
project net weer wat interessanter. Een eenvoudig Python programma leest
het weerstation uit, vraagt de positie en de tijd van de gps ontvanger
op en construeerd het APRS frame dat uitgezonden moet worden. Dit frame
wordt vervolgens via de Linux AX.25 stack naar het modem gestuurd.</p>
<p>Omdat APRS over LoRa op de 70cm band steeds poulairder wordt heb ik
ook een LoRa module op de Raspberry Pi aangeloten. Het weerbericht kan
zo ook via LoRa worden uitgezonden. De software hiervoor is een in
Python geschreven KISS interface. Via deze software kan de LoRa module
gekoppeld worden aan de AX.25 stack. De Raspberry Pi ziet het modem als
elk ander KISS compatible modem.</p>
<p>Met een diplexer worden de signalen van beide zenders samengevoegd en
gaan zo naar een dualband antene.</p>
<h1 id="hamnet">HamNet</h1>
<p>Het syteem kan autonoom werken, maar het is handig (en noodzakelijk)
om het systeem van afstand te kunnen bedienen en wanneer dat nodig is
ook uit te kunnen schakelen. Daarvoor heb ik een 5GHz HamLink tussen het
huis en het weerstation aangelegt. Op deze manier heb ik een snelle
netwerkverbinding naar de Raspberry Pi en kan ik via telnet inloggen en
het systeem bedienen. De verbindig wordt ook gebruikt om verbindig te
maken met het APRS-IS netwerk op het internet. Zo doet mijn weerstation
ook dienst als RX-only iGate voor zowel traditioneel APRS als LoRa APRS.
De HamNet link maakt gebruik van commercieel verkrijgbare schotels. Ik
gebruik apparatuur van Unifi Ubiquiti, maar apparatuur van Mikrotik is
even goed geschikt.</p>
<p>De snelle netwerkverbinding is handig, maar wanneer het systeem
autonoom werkt is het wel een beetje een overkill, want de verbinding
wordt dan enkel gebruikt om APRS berichten door te sturen naar het
APRS-IS netwerk. Daarom heb ik ook nog een lage snelheid
netwerkverbinding geintegreerd. Deze heeft een doorvoersnelheid van iets
meer dan 20KB/s, wat genoeg is voor de toepassing. Het is zelfs mogelijk
om via deze langzame verbinding in te loggen via telnet. Dat gaat dan
wat trager, maar als backup is het prima geschikt. Zo heb ik twee
manieren om het systeem van afstand te beheren. De verbinding gaat over
LoRa via de 70cm band. Hiervoor gebruik ik een kant en klaar board, een
LilyGO TTGO T3 LoRa32 433MHz V1.6.1 ESP32. Hierop heb ik de Rnode
firmware van unsigned.io gezet. Met de bijbehorende Linux software
(tncattach) wordt dit een netwerkinterface onder Linux waarover ik het
netwerkverkeer kan leiden.</p>
<h1 id="meer-informatie">Meer informatie</h1>
<p>Dit artikel is slechts een introductie van het APRS weerstation. Meer
informatie is te vinden op mijn website https://meezenest.nl/mees Daar
vind je uitgebreide documentatie en alle ontwerpbestanden en broncode
die je nodig hebt om zelf aan deslag te gaan.</p>
<h1 id="verantwoording">Verantwoording</h1>
<p><a
href="https://www.meezenest.nl/mees-elektronica/projects/weather_station/build_doc/weather_station.html">Bouwbeschrijving
weerstation:
https://www.meezenest.nl/mees-elektronica/projects/weather_station/build_doc/weather_station.html</a></p>
<p><a
href="https://www.meezenest.nl/mees-elektronica/packetmodem_nano.html">1200bd
modem:
https://www.meezenest.nl/mees-elektronica/packetmodem_nano.html</a></p>
<p><a href="https://git.meezenest.nl/marcel/RPi-LoRa-KISS-TNC">LoRa KISS
software: https://git.meezenest.nl/marcel/RPi-LoRa-KISS-TNC</a></p>
<p><a
href="https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html">Raspberry
Pi met LoRa module:
https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html</a></p>
<p>Copyright (C) 2023, 2024 M.T. Konstapel - PE1RXF</p>
<p><a
href="https://meezenest.nl/mees/">https://meezenest.nl/mees/</a></p>
<p>This work is licensed under a Creative Commons Attribution-ShareAlike
4.0 International License.</p>
<hr>
</article>
</main>
<footer>
<p>&copy;
2024-02-03
M.T. Konstapel
<a href="https://meezenest.nl/mees/">https://meezenest.nl/mees/</a>
</p><p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
</p>
</footer>
</body>
</html>

@ -0,0 +1,71 @@
---
title: APRS weerstation
subtitle: met hamnet verbinding
author: M.T. Konstapel
date: 2024-02-03
website: https://meezenest.nl/mees/
page_back: https://meezenest.nl/mees/weather_station.html
logo: ./images/mees_logo.svg
pdf_version: ./weather_station_article.pdf
git_repo: https://git.meezenest.nl/marcel/weather_station
numbersections: true
# Formatting:
geometry: "a4paper, left=2.0cm, right=2.0cm, top=1.9cm, bottom=2.54cm"
abstract: >
Ik maak veel gebruik van het APRS netwerk: positiebepaling, telemetry en berichten versturen; ik doe het allemaal. Dit kan omdat ik dicht bij de Duitse grens woon: anders dan in Nederland waar het APRS netwerk dankzij lastige regelgeving nagenoeg is verdwenen, is in Duitsland het netwerk nog springlevend. Ik maak gebruik van Duitse digipeaters en iGates, die gewoon berichten vanaf het internet mogen doorzenden. Wat een geluk! Het enige wat ik nog niet kon, was weergegevens delen via het netwerk. Om daar verandering in te brengen heb ik een weerstation ontworpen dat elke 10 minuten een weerbericht kan uitzenden via het APRS netwerk. En omdat het systeem zo'n 100 meter van mijn huis in de acchtertuin staat, heb ik er ook een 5 GHz hamnet verbinding naar toe gemaakt, zodat ik het systeem op afstand kan bedienen. Het hamnet gebruik ik ook om de weermetingen naar mijn Grafana dashboard te sturen. Oh, en omdat twee beter is dan een heb ik er ook een 20KB/s hamnet link over 70cm LoRa als backup in geknutseld.
---
# Weerstation
Als uitgangspunt van het weerstation gebruik ik de SparkFun Weather Meter. Dit is een kit met drie sensors: een windvaan, een anemometer en een regenmeter. Deze kit heb ik aangevuld met sensors voor temperatuur, luchtdruk en luctvochtigheid. Al deze sensors zijn rechtstreeks aangesloten op een Arduino Mini Pro. Ik heb daar een RS-485 driverchip en een ompoolbeveiliging aan toegevoegd. Het uiteindelijke schema is hieronder te zien. Ingewikkeld is de hardware niet, want alle fuctionaliteit zit in de software.
![Schema](./images/weather_station_schematic.svg "Schema")
Het weerstation is uit te lezen via een ModBus interface. Dit is een industriestandaard, dus er zijn legio mogelijkheden om met het weerstation te communiceren. De ModBus registers bevatten de meetwaarden van de sensors en worden elke twee seconde ververst. Dit bepaald dus de maximale uitleesfrequentie. De volgende gegevens zijn beschikbaar:
- Windrichting in graden
- Gemiddelde wind snelheid van de laatste 10 minuten in m/s
- Maximale windstoot van de laatste 10 minuten in m/s
- Hoeveelheid regen in het afgelopen uur in mm
- Hoeveelheid regen in de afgelopen 24 uur in mm
- Temperatuur in graden C
- Luchtvochtigheid in %
- Luchtdruk in hPa
Daarnaast zijn er nog een aantal statusregisters beschikbaar. Deze worden besproken in de uitgebreide bouwbeschrijving die beschikbaar is op mijn website.
De luchtvochtigheidssensor kan bij een hoge luchtvochtigheid verzadigd raken en zo blijven steken op 100%. Om dit te voorkomen is het mogelijk om de sensor automatisch te laten verwarmen wanneer de luchtvochtigheid langer dan een uur boven de 96% is. De verwarming wordt dan elke 20 minuten voor 5 minuten aangezet. In de 15 minuten die overblijven koelt de sensor weer af tot de omgevingstemperatuur. Dit proces wordt heraald totdat de sensor weer een waarde beneden de 96% aangeeft. Tijdens het opwarmen en afkoelen kan de luchtvochtigheid en temperatuur maar eens in de 20 minuten worden gemeten. Dit is de prijs die betaald moet worden wanneer we een goedkope luchtvochtigheidssensor gebruiken.
# APRS
Om de weermetingen te kunnen uitzenden via het APRS netwerk is er een 2 meter FM zender (een oude Alinco portofoon) en een 1200baud modem (een variant op het MicroModem van markqvist) nodig. En een computer om de gegevens via de ModBus uit het weerstation te lezen en door te sturen naar het modem. Een Raspberry Pi Zero 2W is daar perfect geschikt voor. Deze is goedkoop, klein en verbruikt weinig energie. Omdat een APRS weerstation ook zijn positie en tijd moet doorgeven om op de kaart gezet te kunnen worden is er een GPS module via USB aangesloten op de Raspberry Pi. Strikt genomen is de tijd niet noodzakelijk en omdat het station vast is opgesteld kan de positie ook handmatig worden ingesteld, maar een gps module voegt weer extra complexiteit toe en dat maakt het project net weer wat interessanter. Een eenvoudig Python programma leest het weerstation uit, vraagt de positie en de tijd van de gps ontvanger op en construeerd het APRS frame dat uitgezonden moet worden. Dit frame wordt vervolgens via de Linux AX.25 stack naar het modem gestuurd.
Omdat APRS over LoRa op de 70cm band steeds poulairder wordt heb ik ook een LoRa module op de Raspberry Pi aangeloten. Het weerbericht kan zo ook via LoRa worden uitgezonden. De software hiervoor is een in Python geschreven KISS interface. Via deze software kan de LoRa module gekoppeld worden aan de AX.25 stack. De Raspberry Pi ziet het modem als elk ander KISS compatible modem.
Met een diplexer worden de signalen van beide zenders samengevoegd en gaan zo naar een dualband antene.
# HamNet
Het syteem kan autonoom werken, maar het is handig (en noodzakelijk) om het systeem van afstand te kunnen bedienen en wanneer dat nodig is ook uit te kunnen schakelen. Daarvoor heb ik een 5GHz HamLink tussen het huis en het weerstation aangelegt. Op deze manier heb ik een snelle netwerkverbinding naar de Raspberry Pi en kan ik via telnet inloggen en het systeem bedienen. De verbindig wordt ook gebruikt om verbindig te maken met het APRS-IS netwerk op het internet. Zo doet mijn weerstation ook dienst als RX-only iGate voor zowel traditioneel APRS als LoRa APRS. De HamNet link maakt gebruik van commercieel verkrijgbare schotels. Ik gebruik apparatuur van Unifi Ubiquiti, maar apparatuur van Mikrotik is even goed geschikt.
De snelle netwerkverbinding is handig, maar wanneer het systeem autonoom werkt is het wel een beetje een overkill, want de verbinding wordt dan enkel gebruikt om APRS berichten door te sturen naar het APRS-IS netwerk. Daarom heb ik ook nog een lage snelheid netwerkverbinding geintegreerd. Deze heeft een doorvoersnelheid van iets meer dan 20KB/s, wat genoeg is voor de toepassing. Het is zelfs mogelijk om via deze langzame verbinding in te loggen via telnet. Dat gaat dan wat trager, maar als backup is het prima geschikt. Zo heb ik twee manieren om het systeem van afstand te beheren. De verbinding gaat over LoRa via de 70cm band. Hiervoor gebruik ik een kant en klaar board, een LilyGO TTGO T3 LoRa32 433MHz V1.6.1 ESP32. Hierop heb ik de Rnode firmware van unsigned.io gezet. Met de bijbehorende Linux software (tncattach) wordt dit een netwerkinterface onder Linux waarover ik het netwerkverkeer kan leiden.
# Meer informatie
Dit artikel is slechts een introductie van het APRS weerstation. Meer informatie is te vinden op mijn website https://meezenest.nl/mees Daar vind je uitgebreide documentatie en alle ontwerpbestanden en broncode die je nodig hebt om zelf aan de slag te gaan.
# Verantwoording
[Bouwbeschrijving weerstation: https://www.meezenest.nl/mees-elektronica/projects/weather_station/build_doc/weather_station.html](https://www.meezenest.nl/mees-elektronica/projects/weather_station/build_doc/weather_station.html)
[1200bd modem: https://www.meezenest.nl/mees-elektronica/packetmodem_nano.html](https://www.meezenest.nl/mees-elektronica/packetmodem_nano.html)
[LoRa KISS software: https://git.meezenest.nl/marcel/RPi-LoRa-KISS-TNC](https://git.meezenest.nl/marcel/RPi-LoRa-KISS-TNC)
[Raspberry Pi met LoRa module: https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html](https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html)
Copyright (C) 2023, 2024 M.T. Konstapel - PE1RXF
[https://meezenest.nl/mees/](https://meezenest.nl/mees/)
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

@ -609,7 +609,7 @@ import minimalmodbus
instrument = minimalmodbus.Instrument(&#39;/dev/ttyUSB1&#39;, 14) instrument = minimalmodbus.Instrument(&#39;/dev/ttyUSB1&#39;, 14)
# register number, number of decimals, function code # register number, number of decimals, function code
wind_direction = instrument.read_register(1, 0, 4) wind_direction = instrument.read_register(1, 1, 4)
print(wind_direction) print(wind_direction)
# register address, value, function code # register address, value, function code
@ -791,10 +791,12 @@ Zero 2W running the aprx digipeater software, as well as some specially
written Python programs to interface the build in LoRa transceiver, the written Python programs to interface the build in LoRa transceiver, the
GPS module and the weather station itself. Every 10 minutes the GPS module and the weather station itself. Every 10 minutes the
digipeater will read the weather station’s registers and sends the data digipeater will read the weather station’s registers and sends the data
as PE1RXF telemetry messages over the APRS network to a server, which as PE1RXF telemetry messages (see <a
presents the data in a Grafana dashboard. The digipeater can also send href="https://www.meezenest.nl/mees-elektronica/projects/aprs_telemetry/APRS_protocol_nodes_PE1RXF.pdf">https://www.meezenest.nl/mees-elektronica/projects/aprs_telemetry/APRS_protocol_nodes_PE1RXF.pdf</a>)
standardized APRS weather reports over the APRS network. But more about over the APRS network to a server, which presents the data in a Grafana
this project can be found here: <a dashboard. The digipeater can also send standardized APRS weather
reports over the APRS network. But more about this project can be found
here: <a
href="https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html">https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html</a></p> href="https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html">https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html</a></p>
<figure> <figure>
<img src="./images/prototype_overview_small.jpg" <img src="./images/prototype_overview_small.jpg"

@ -272,7 +272,7 @@ Below an example of how to read the wind direction and enable the heater algorit
instrument = minimalmodbus.Instrument('/dev/ttyUSB1', 14) instrument = minimalmodbus.Instrument('/dev/ttyUSB1', 14)
# register number, number of decimals, function code # register number, number of decimals, function code
wind_direction = instrument.read_register(1, 0, 4) wind_direction = instrument.read_register(1, 1, 4)
print(wind_direction) print(wind_direction)
# register address, value, function code # register address, value, function code
@ -324,7 +324,7 @@ I wanted to locate the weather station at about 100 meters from the house. That
![Block diagram of the prototype](./images/prototype_block_diagram.svg "Block diagram of the prototype") ![Block diagram of the prototype](./images/prototype_block_diagram.svg "Block diagram of the prototype")
The weather station's RS-485 interface is connected to a Raspberry Pi Zero 2W running the aprx digipeater software, as well as some specially written Python programs to interface the build in LoRa transceiver, the GPS module and the weather station itself. Every 10 minutes the digipeater will read the weather station's registers and sends the data as PE1RXF telemetry messages over the APRS network to a server, which presents the data in a Grafana dashboard. The digipeater can also send standardized APRS weather reports over the APRS network. But more about this project can be found here: [https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html](https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html) The weather station's RS-485 interface is connected to a Raspberry Pi Zero 2W running the aprx digipeater software, as well as some specially written Python programs to interface the build in LoRa transceiver, the GPS module and the weather station itself. Every 10 minutes the digipeater will read the weather station's registers and sends the data as PE1RXF telemetry messages (see [https://www.meezenest.nl/mees-elektronica/projects/aprs_telemetry/APRS_protocol_nodes_PE1RXF.pdf](https://www.meezenest.nl/mees-elektronica/projects/aprs_telemetry/APRS_protocol_nodes_PE1RXF.pdf)) over the APRS network to a server, which presents the data in a Grafana dashboard. The digipeater can also send standardized APRS weather reports over the APRS network. But more about this project can be found here: [https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html](https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html)
![The prototype in the garden](./images/prototype_overview_small.jpg "The prototype in the garden") ![The prototype in the garden](./images/prototype_overview_small.jpg "The prototype in the garden")

Binary file not shown.

@ -0,0 +1,57 @@
---
title: APRS weerstation
subtitle: met hamnet verbinding
author: M.T. Konstapel
date: 2024-02-03
website: https://meezenest.nl/mees/
page_back: https://meezenest.nl/mees/weather_station.html
logo: ./images/mees_logo.svg
pdf_version: ./weather_station_article.pdf
git_repo: https://git.meezenest.nl/marcel/weather_station
numbersections: true
# Formatting:
geometry: "a4paper, left=2.0cm, right=2.0cm, top=1.9cm, bottom=2.54cm"
abstract: >
Ik maak veel gebruik van het APRS netwerk: positiebepaling, telemetry en berichten versturen; ik doe het allemaal. Dit kan omdat ik dicht bij de Duitse grens woon: anders dan in Nederland waar het APRS netwerk dankzij lastige regelgeving nagenoeg is verdwenen, is in Duitsland het netwerk nog springlevend. Ik maak gebruik van Duitse digipeaters en iGates, die gewoon berichten vanaf het internet mogen doorzenden. Wat een geluk! Het enige wat ik nog niet kon, was weergegevens delen via het netwerk. Om daar verandering in te brengen heb ik een weerstation ontworpen dat elke 10 minuten een weerbericht kan uitzenden via het APRS netwerk. En omdat het systeem zo'n 100 meter van mijn huis in de acchtertuin staat, heb ik er ook een 5 GHz hamnet verbinding naar toe gemaakt, zodat ik het systeem op afstand kan bedienen. Oh, en omdat twee beter is dan een heb ik er ook een 20KB/s hamnet link over 70cm LoRa als backup in geknutseld. Het hamnet gebruik ik ook om de weermetingen naar mijn Grafana dashboard te sturen.
---
# Weerstation
Als uitgangspunt van het weerstation gebruik ik de SparkFun Weather Meter. Dit is een kit met drie sensors: een windvaan, een anemometer en een regenmeter. Deze kit heb ik aangevuld met sensors voor temperatuur, luchtdruk en luctvochtigheid. Al deze sensors zijn rechtstreeks aangesloten op een Arduino Mini Pro. Ik heb daar een RS-485 driverchip en een ompoolbeveiliging aan toegevoegd. Het uiteindelijke schema is hieronder te zien. Ingewikkeld is de hardware niet, want alle fuctionaliteit zit in de software.
Het weerstation is uit te lezen via een ModBus interface. Dit is een industriestandaard, dus er zijn legio mogelijkheden om met het weerstation te communiceren. De ModBus registers bevatten de meetwaarden van de sensors en worden elke twee seconde ververst. Dit bepaald dus de maximale uitleesfrequentie. De volgende gegevens zijn beschikbaar:
- Windrichting in graden
- Gemiddelde wind snelheid van de laatste 10 minuten in m/s
- Maximale windstoot van de laatste 10 minuten in m/s
- Hoeveelheid regen in het afgelopen uur in mm
- Hoeveelheid regen in de afgelopen 24 uur in mm
- Temperatuur in graden C
- Luchtvochtigheid in %
- Luchtdruk in hPa
Daarnaast zijn er nog een aantal statusregisters beschikbaar. Deze worden besproken in de uitgebreide bouwbeschrijving die beschikbaar is op mijn website.
De Luchtvochtigheidssensor kan bij een hoge Luchtvochtigheid verzadigd raken en zo blijven steken op 100%. Om dit te voorkomen is het mogelijk om de sensor automatisch te laten verwarmen wanneer de luchtvochtigheid langer dan een uur boven de 96% is. De verwarming wordt dan elke 20 minuten voor 5 minuten aangezet. In de 15 minuten die overblijven koelt de sensor weer af tot de omgevingstemperatuur. Dit proces wordt heraald totdat de sensor weer een waarde beneden de 96% aangeeft. Tijdens het opwarmen en afkoelen kan de luchtvochtigheid en temperatuur maar eens in de 20 minuten worden gemeten. Dit is de prijs die betaald moet worden wanneer we een goedkope luchtvochtigheidssensor gebruiken.
# APRS iGate
Om de weermetingen te kunnen uitzenden via het APRS netwerk is er een 2 meter FM zender en een 1200baud modem nodig. En een computer om de gegevens via de ModBus uit het weerstation te lezen en door te sturen naar het modem. Een Raspberry Pi Zero 2W is daar perfect geschikt voor. Deze is goedkoop, klein en verbruikt weinig energie. Omdat een APRS weerstation ook zijn positie en tijd moet doorgeven om op de kaart gezet te kunnen worden gezet is er een GPS module via USB aangesloten op de Raspberry Pi. Strikt genomen is de tijd niet noodzakelijk en omdat het station vast is opgesteld kan de positie ook handmatig worden ingesteld. Een eenvoudig Python programma leest het weerstation uit, vraagt de positie en de tijd van de gps ontvanger op en construeerd het APRS frame dat uitgezonden moet worden. Dit frame wordt vervolgens via de AX.25 stack naar het modem gestuurd.
Omdat APRS over LoRa op de 70cm band steeds poulairder wordt heb ik ook een LoRa module op de Raspberry Pi aangeloten. Het weerbericht kan zo ook via LoRa worden uitgezonden. Met een diplexer worden de signalen van beide zenders samengevoegd en gaan zo naar een dualband antene. De software hiervoor is een in Python geschreven KISS interface. Via deze software kan de LoRa module gekoppeld worden aan de AX.25 stack. De Raspberry Pi ziet het modem als elk ander KISS compatible modem.
# HamNet
Het syteem kan autonoom werken, maar het is handig (en noodzakelijk) om het systeem van afstand te kunnen bedienen en wanneer dat nodig is ook uit te kunnen schakelen. Daarvoor heb ik een 5GHz HamLink tussen het huis en de Raspberry Pi aangelegt. Op deze manier heb ik een snelle netwerkverbinding naar het weerstation en kan ik via telnet inloggen en het systeem bedienen. De HamNet link maakt gebruik van commercieel verkrijgbare schotels. Ik gebruik apparatuur van Unifi Ubiquiti, maar apparatuur van Mikrotik is even goed geschikt. Deze verbindig wordt ook gebruikt om verbindig te maken met et APRS-IS netwerk op het internet. Zo doet mijn weerstation ook dienst als RX-only iGate voor zowel traditioneel APRS als LoRa APRS.
De snelle netwerkverbinding is handog, maar wanneer het systeem autonoom werkt is het wel een beetje een overkill, want de verbinding wordt dan enkel gebruikt om APRS berichten door te sturen naar het APRS-IS netwerk. Daarom heb ik ook nog een lage snelheid netwerkverbinding geintegreerd. Deze heeft een doorvoersnelheid van iets meer dan 20KB/s, wat genoeg is voor de toepassing. Het is zelfs mogelijk om daarnaast ook nog in te loggen via telnet. Dat gaat dan wat trager, maar als backup is het prima geschikt. Zo heb ik twee manieren om het systeem van afstand te beheren. De verbinding gaat over LoRa via de 70cm band. Hiervoor gebruik ik een kant en klaar board, een LilyGO TTGO T3 LoRa32 433MHz V1.6.1 ESP32. Hierom heb ik firmware van unsigned.io gezet. Met de bijbehorende Linux software (tncattach) wordt dit een netwerkinterface onder Linux waarover ik het netwerkverkeer kan leiden.
![SparkFun Weather Meter](./images/SparkFun-Weather_Meter.jpg "SparkFun Weather Meter")
# Verantwoording
Copyright (C) 2023, 2024 M.T. Konstapel - PE1RXF
[https://meezenest.nl/mees/](https://meezenest.nl/mees/)
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

@ -191,9 +191,9 @@ char HeaterSi7021 (float humidity)
} }
break; break;
// Heater is now on, let the sensor cook for 5 minutes // Heater is now on, let the sensor cook for 10 minutes
case 2: case 2:
if ( (millis() - StatemachineTimer) >= 300000 ) { if ( (millis() - StatemachineTimer) >= 600000 ) {
StatemachineTimer = millis(); StatemachineTimer = millis();
Heater = 0; Heater = 0;
state = 3; state = 3;
@ -202,9 +202,9 @@ char HeaterSi7021 (float humidity)
} }
TempValid = 0; TempValid = 0;
break; break;
// Heater is now off, let the sensor cool for 15 minutes // Heater is now off, let the sensor cool for 10 minutes
case 3: case 3:
if ( (millis() - StatemachineTimer) >= 900000 ) { if ( (millis() - StatemachineTimer) >= 600000 ) {
TempValid = 1; // Sensor cooled, so we can take a valid temperature reading TempValid = 1; // Sensor cooled, so we can take a valid temperature reading
if (humidity >= 95) { if (humidity >= 95) {
// Humidity still above 95%, repeat heating/cooling // Humidity still above 95%, repeat heating/cooling

Loading…
Cancel
Save