Compare commits
12 Commits
d71e334bd8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd272cfffc | ||
|
|
3d980ab38a | ||
|
|
ca2c7ac80b | ||
|
|
92956c2707 | ||
|
|
aaec2a02ad | ||
|
|
eeb6bd846c | ||
|
|
71a3a48af7 | ||
|
|
33a5bbbc24 | ||
|
|
6c1134ca3a | ||
|
|
08b845f792 | ||
|
|
82342b82a5 | ||
|
|
309fef8c1d |
77
CHANGELOG.md
77
CHANGELOG.md
@@ -41,7 +41,7 @@ All notable changes to this project will be documented in this file.
|
||||
- ModBus read register 14: Status bits
|
||||
- ModBus coil register 0 : enable/disable heather algorithm
|
||||
|
||||
## [0.2.2] - 2023-01-21
|
||||
## [0.2.2] - 2024-01-21
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -51,3 +51,78 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
- 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)]
|
||||
|
||||
## [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.2.4] - 2024-03-08
|
||||
|
||||
### Changed
|
||||
|
||||
- Humidity timer back to 5/15 minutes: shorter cooling times give false temperature readings.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Classic 0/1 error in max and average calculating routine, which resulted in a buffer overflow.
|
||||
|
||||
## [0.2.5] - 2024-03-11
|
||||
|
||||
### Fixed
|
||||
|
||||
- 0/1 error in max and average (again, or better said: still!).
|
||||
|
||||
### Changed
|
||||
|
||||
- Humidity threshold heater is now a define instead of hard coded numbers in the source.
|
||||
|
||||
## [0.2.6] - 2024-03-13
|
||||
|
||||
### Fixed
|
||||
|
||||
There is a bug in the Si7021 chip: when the sensor is exposed to extreme environmental conditions for a longer period (very high or very low humidity), the humidity register will wrap around and give wrong values. (https://community.silabs.com/s/question/0D51M00007xeOo9SAE/si7021-relative-humidity-reading-underflowing-?language=en_US).
|
||||
Workaround: let's assume the humidity will never go below 15%. In the Netherlands a humidity below 20% is rare, but can occur and the minimum value ever measured in De Bilt was 6% (on 1 april 1965). At several other sites in the Netherlands the humidity went as low as 10%. But as this was a one time event and the measurements are doubtful, this event was not registered in the original database. (https://nl.wikipedia.org/wiki/Relatieve_luchtvochtigheid)
|
||||
In rare cases, the humidity value could creep up above 15% (when the actual humidity is 100%) and still give wrong values.
|
||||
|
||||
## [0.3.0] - 2024-05-02
|
||||
|
||||
This is the latest release from 2024.
|
||||
|
||||
### Added
|
||||
|
||||
Support for HYT-221 humidity sensor
|
||||
|
||||
### Removed
|
||||
|
||||
Support for si7021 humidity sensor (this sensor was not suited for outdoor measurments)
|
||||
|
||||
## [0.3.1] - 2025-01-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Cleanup of code
|
||||
- Debounce rainfall meter from 100ms to 250mms
|
||||
- Main temperature and backup temperature registers switched: the sensor on the HYT221 heats up by the circuitry and is therefore about 1.5 degrees above ambient temperature. The BMP280 does not heat up.
|
||||
|
||||
### Added
|
||||
|
||||
- Debug messages can be switched on and off with the _DEBUG_ variable
|
||||
- SEN0562 luminosity sensor
|
||||
|
||||
## [0.3.2] - 2025-01-26
|
||||
|
||||
### Fixed
|
||||
|
||||
The first hour, the ModBus register SensorRainIreg is instable. This is because in setup(), the variable RainPerHourCounter is set to the current millis() value. The RainPerHourCounter is the index for the array RainPerHour, which can hold 24 values. But the index is set to the millis() value, which can have a value abouve 23. This causes a buffer overflow. After the first hour, the index is reset and from thereon the register SensorRainIreg holds the proper rainfall value. The solution is that the variable HourTimer should be set to the current millis() value in setup().
|
||||
|
||||
```
|
||||
-- ts = millis();
|
||||
-- RainPerHourCounter = ts;
|
||||
|
||||
++ ts = millis();
|
||||
++ HourTimer = ts;
|
||||
```
|
||||
|
||||
Buffer roll over luminosity sensor. This happened because the raw value was mutiplied by 100. But that was wrong: copied this section of the code from the temperature sensor code, which uses this mutiply by 100 to gain precision.
|
||||
|
||||
16
README.md
16
README.md
@@ -2,19 +2,19 @@
|
||||
|
||||
## Abstract
|
||||
|
||||
A weather station build around a SparkFun Weather Meter Kit (SEN-15901). The temperature, humidity and pressure are measured with I2C sensors housed in an RS1 Passive Radiation Shield from Garni. The data is available via an RS485 ModBus RTU interface. The main processor is an Arduino Pro Mini (ATmega328P 5V@16MHz)
|
||||
A weather station build around a SparkFun Weather Meter Kit (SEN-15901). The temperature, humidity, ambient lightmeter and pressure are measured with I2C sensors housed in an RS1 Passive Radiation Shield from Garni. The data is available via an RS485 ModBus RTU interface. The main processor is an Arduino Pro Mini (ATmega328P 5V@16MHz)
|
||||
|
||||

|
||||
|
||||
## Documentation
|
||||
|
||||
An apprehensive description of the project can be found here: https://meezenest.nl/mees/projects/weather_station/weather_station.html
|
||||
|
||||
An apprehensive description of the project can be found here: [https://meezenest.nl/mees/weather_station.html](https://meezenest.nl/mees/weather_station.html)
|
||||
## Sensors
|
||||
|
||||
- SparkFun SEN-15901 Weather Station (wind speed, wind direction and rain fall)
|
||||
- Silicon Labs Si7021 (humidity and temperature)
|
||||
- Innovative Sensor Technology IST AG HYT 221 (humidity and temperature)
|
||||
- Bosch BMP280 (pressure)
|
||||
- DFRobot SEN0562 (ambient light)
|
||||
|
||||
## Measurements
|
||||
|
||||
@@ -26,6 +26,7 @@ An apprehensive description of the project can be found here: https://meezenest.
|
||||
- Temperature
|
||||
- Humidity
|
||||
- Atmospheric pressure
|
||||
- Ambient light
|
||||
|
||||
## ModBus
|
||||
|
||||
@@ -45,9 +46,14 @@ An apprehensive description of the project can be found here: https://meezenest.
|
||||
|
||||
Libraries are included with the source code of this project
|
||||
|
||||
## Known issues
|
||||
|
||||
- Pressure sensor (BMP280) failed after one year outside. Probably due to the long period of fog we had in the winter.
|
||||
- Rain fall meter is sensitive for mechanical shock. Mount it on a sturdy pole to prevent false readings.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2023, 2024 M.T. Konstapel
|
||||
Copyright (C) 2023-2025 M.T. Konstapel
|
||||
|
||||
### Software
|
||||
|
||||
|
||||
24
TODO.md
Normal file
24
TODO.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Todo list for weather station with ModBus over RS-485
|
||||
|
||||
## Bug in rain meter register [done - 2024-01-26]
|
||||
|
||||
The first hour, the ModBus register SensorRainIreg is instable. This is because in setup(), the variable RainPerHourCounter is set to the current millis() value. The RainPerHourCounter is the index for the array RainPerHour, which can hold 24 values. But the index is set to the millis() value, which can have a value abouve 23. This causes a buffer overflow. After the first hour, the index is reset and from thereon the register SensorRainIreg holds the proper rainfall value. The solution is that the variable HourTimer should be set to the current millis() value in setup().
|
||||
|
||||
```
|
||||
-- ts = millis();
|
||||
-- RainPerHourCounter = ts;
|
||||
|
||||
++ ts = millis();
|
||||
++ HourTimer = ts;
|
||||
```
|
||||
|
||||
## Reset firmware via ModBus coil bit
|
||||
|
||||
Soemtimes the weater station needs to be reset. For example if the I2C communication with the sensors is lost. The pressure sensor tends to have this probem occacionally.
|
||||
|
||||
## Implement support for new humidity sensor [done - 2024-01-14]
|
||||
|
||||
Heater function of former sensor needs to be disabled as the new sensor does not have (or need) a heater.
|
||||
|
||||
|
||||
|
||||
9
article/Makefile
Normal file
9
article/Makefile
Normal file
@@ -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
|
||||
563
article/css/mvp.css
Normal file
563
article/css/mvp.css
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
33282
article/images/weather_station_schematic.svg
Normal file
33282
article/images/weather_station_schematic.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 586 KiB |
16
article/template/custom_settings.tex
Normal file
16
article/template/custom_settings.tex
Normal file
@@ -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
|
||||
120
article/template/template.html
Normal file
120
article/template/template.html
Normal file
@@ -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>©
|
||||
$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>
|
||||
205
article/weather_station_article.html
Normal file
205
article/weather_station_article.html
Normal file
@@ -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>©
|
||||
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>
|
||||
71
article/weather_station_article.md
Normal file
71
article/weather_station_article.md
Normal file
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 593 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -68,7 +68,7 @@ $if(title)$
|
||||
$endif$
|
||||
$if(website)$
|
||||
<li>
|
||||
<a href="$website$">Back</a>
|
||||
<a href="$page_back$">Back</a>
|
||||
</li>
|
||||
$endif$
|
||||
</ul>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<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-01-22" />
|
||||
<meta name="dcterms.date" content="2025-01-14" />
|
||||
<title>Weather station</title>
|
||||
<link rel="stylesheet" href="./css/mvp.css" />
|
||||
<style type="text/css">
|
||||
@@ -53,6 +53,11 @@
|
||||
id="toc-specifications">Specifications</a></li>
|
||||
<li><a href="#schematic"
|
||||
id="toc-schematic">Schematic</a></li>
|
||||
<li><a href="#problems-i-encounter-after-four-months-of-use"
|
||||
id="toc-problems-i-encounter-after-four-months-of-use">Problems
|
||||
I encounter after four months of use</a></li>
|
||||
<li><a href="#additions"
|
||||
id="toc-additions">Additions</a></li>
|
||||
<li><a href="#software-dependencies"
|
||||
id="toc-software-dependencies">Software
|
||||
dependencies</a></li>
|
||||
@@ -63,10 +68,10 @@
|
||||
<a href="./weather_station.pdf">PDF version</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.meezenest.nl/">Git repo</a>
|
||||
<a href="https://git.meezenest.nl/marcel/weather_station">Git repo</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://meezenest.nl/mees/">Back</a>
|
||||
<a href="https://meezenest.nl/mees/weather_station.html">Back</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -75,7 +80,7 @@
|
||||
<h1 class="title">Weather station</h1>
|
||||
<p class="subtitle">with ModBus RTU interface</p>
|
||||
<p class="author">M.T. Konstapel</p>
|
||||
<p class="date">2024-01-22</p>
|
||||
<p class="date">2025-01-14</p>
|
||||
<p><a href="./weather_station.pdf"><i>PDF version</i></a></p>
|
||||
</header>
|
||||
<main>
|
||||
@@ -512,7 +517,8 @@ alt="Microcontroller" />
|
||||
</figure>
|
||||
<p>Both the signals from the rain meter and the cup anemometer are
|
||||
connected to interrupt pins of the micro-controller. The signal from the
|
||||
rain meter is lightly filtered by C8.</p>
|
||||
rain meter is lightly filtered by C8. The output of the wind vane is
|
||||
connectd to an analog input of the micro-controller.</p>
|
||||
<p>The ModBus address can be set by DIP switch J9.</p>
|
||||
<h1 id="theory-of-operation---software">Theory of operation -
|
||||
Software</h1>
|
||||
@@ -608,7 +614,7 @@ import minimalmodbus
|
||||
instrument = minimalmodbus.Instrument('/dev/ttyUSB1', 14)
|
||||
|
||||
# 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)
|
||||
|
||||
# register address, value, function code
|
||||
@@ -774,10 +780,10 @@ by 10 or 100 to get the floating point values.</p>
|
||||
<h1 id="prototype">Prototype</h1>
|
||||
<p>I wanted to locate the weather station at about 100 meters from the
|
||||
house. That meant that interfacing the weather station was not just a
|
||||
matter of connecting a wire to it. And 100 meters is a bit much for a
|
||||
wifi connection either. As I already had experience with LoRa I opted
|
||||
for that. But not LoRaWAN, but LoRa APRS. This is a ham radio network
|
||||
that I often use. I even run my own digipeater. So LoRa APRS it is.</p>
|
||||
matter of connecting a wire to it. And 100 meters is also a bit much for
|
||||
a wifi connection. As I already had experience with LoRa I opted for
|
||||
that. But not LoRaWAN, but LoRa APRS. This is a ham radio network that I
|
||||
often use. I even run my own digipeater. So LoRa APRS it is.</p>
|
||||
<figure>
|
||||
<img src="./images/prototype_block_diagram.svg"
|
||||
title="Block diagram of the prototype"
|
||||
@@ -790,10 +796,12 @@ 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: <a
|
||||
as PE1RXF telemetry messages (see <a
|
||||
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>)
|
||||
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: <a
|
||||
href="https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html">https://www.meezenest.nl/mees-elektronica/RPi_LoRa_shield.html</a></p>
|
||||
<figure>
|
||||
<img src="./images/prototype_overview_small.jpg"
|
||||
@@ -865,6 +873,112 @@ Address : 14</code></pre>
|
||||
<h1 id="schematic">Schematic</h1>
|
||||
<p><a href="./images/weather_station_schematic.pdf"><img
|
||||
src="./images/weather_station_schematic.svg" alt="Schematic" /></a></p>
|
||||
<h1 id="problems-i-encounter-after-four-months-of-use">Problems I
|
||||
encounter after four months of use</h1>
|
||||
<h2 id="humidity-sensor">Humidity sensor</h2>
|
||||
<p>The Si7021 humidity sensor is not made for outdoor use. The datasheet
|
||||
is clear about that. But a lot of people use this cheap sensor for
|
||||
weather stations anyway. So I choose this sensor for my design. But that
|
||||
was not smart. After an initial time without any problems, the sensor
|
||||
started to saturate. This happened during a very wet and mild winter we
|
||||
had. My first solution was to utilize the build in heater to drive of
|
||||
the moisture. That worked, but only for a short period. The heater blew
|
||||
up and the sensor started to report humidity levels above 100% and
|
||||
because of a bug in the firmware of the sensor, the humidity register
|
||||
wrapped around and the sensor reported humidity levels of 0-30%. I tried
|
||||
to find a software solution, which worked for a while, but the sensor
|
||||
deteriorated even more. To the point of being totally useless. So I
|
||||
searched for another, better sensor. And I found the HYT-221 from
|
||||
Innovative Sensor Technology. This sensor is designed to work outdoors
|
||||
and can even be used in saunas, where the humidity levels are always
|
||||
high and the air is condensating. The datasheet specifically mentions
|
||||
outdoor weather stations as an application. The only downside is its
|
||||
price: it is ten times more expensive than the Si7021.</p>
|
||||
<p>The sensor can be controlled via the I2C bus, so implementing the new
|
||||
sensor in the firmware was very easy. From version 0.3.0 onward, this
|
||||
sensor is used. The Si7021 is removed from the code.</p>
|
||||
<h2 id="pressure-sensor">Pressure sensor</h2>
|
||||
<p>After a year the BMP280 pressure sensor started to give odd pressure
|
||||
values. The pressure was around 700hPa and followed the temperature
|
||||
curve. The temperature sensor still worked fine. After replacing the
|
||||
BMP280 the barometric values returned to normal. I suspect that it
|
||||
malfunctioned because of the perpetual fog we had for about two months.
|
||||
The sensor was mounted in the Garni RS1 Passive Radiation Shield. Rain
|
||||
could not enter the shield, but fog could.</p>
|
||||
<h1 id="additions">Additions</h1>
|
||||
<h2 id="luminosity-sensor">Luminosity sensor</h2>
|
||||
<p>From version 0.3.1 onward, the weather station has an ambient light
|
||||
sensor. It is an SEN0562 from DFRobot and it outputs the light intensity
|
||||
in Lux. It is a 5 Volt only device, so it should be connected to the 5
|
||||
Volt I2C bus.</p>
|
||||
<figure>
|
||||
<img
|
||||
src="./images/dfrobot-gravity-ip68-waterproof-ambient-light-sensor-1-65535lx-i2c.png"
|
||||
alt="The SEN0562 ambient light sensor" />
|
||||
<figcaption aria-hidden="true">The SEN0562 ambient light
|
||||
sensor</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img
|
||||
src="./images/dfrobot-gravity-ip68-waterproof-ambient-light-sensor-wiring.png"
|
||||
alt="Connection of the I2C bus" />
|
||||
<figcaption aria-hidden="true">Connection of the I2C bus</figcaption>
|
||||
</figure>
|
||||
<p>The sensor has the following specifications:</p>
|
||||
<ul>
|
||||
<li>Supply Voltage: 5V</li>
|
||||
<li>Operating Current: 1µA</li>
|
||||
<li>Detection Range: 1 - 65535lx</li>
|
||||
<li>Accuracy: 1.2lx</li>
|
||||
<li>Communication Mode: I2C</li>
|
||||
<li>Operating Temperature: -40 to 85°C/-40 to 185℉</li>
|
||||
<li>Waterproof Rating: IP68</li>
|
||||
<li>Thread Length: 10mm</li>
|
||||
<li>Cutout Size: 26mm</li>
|
||||
<li>Wrench Size: 31mm</li>
|
||||
<li>Cable Diameter: 3mm</li>
|
||||
<li>Wire Length: 1m</li>
|
||||
</ul>
|
||||
<p>Sample code:</p>
|
||||
<pre><code>#include "Wire.h"
|
||||
#define address 0x23 //I2C address 0x23
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(9600);
|
||||
Wire.begin();
|
||||
}
|
||||
uint8_t buf[4] = {0};
|
||||
uint16_t data, data1;
|
||||
float Lux;
|
||||
void loop()
|
||||
{
|
||||
readReg(0x10, buf, 2); //Register address 0x10
|
||||
data = buf[0] << 8 | buf[1];
|
||||
Lux = (((float)data )/1.2);
|
||||
Serial.print("LUX:");
|
||||
Serial.print(Lux);
|
||||
Serial.print("lx");
|
||||
Serial.print("\n");
|
||||
delay(500);
|
||||
}
|
||||
uint8_t readReg(uint8_t reg, const void* pBuf, size_t size)
|
||||
{
|
||||
if (pBuf == NULL) {
|
||||
Serial.println("pBuf ERROR!! : null pointer");
|
||||
}
|
||||
uint8_t * _pBuf = (uint8_t *)pBuf;
|
||||
Wire.beginTransmission(address);
|
||||
Wire.write(&reg, 1);
|
||||
if ( Wire.endTransmission() != 0) {
|
||||
return 0;
|
||||
}
|
||||
delay(20);
|
||||
Wire.requestFrom(address, (uint8_t) size);
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
_pBuf[i] = Wire.read();
|
||||
}
|
||||
return size;
|
||||
}</code></pre>
|
||||
<!---
|
||||
# Bill of materials
|
||||
|
||||
@@ -885,7 +999,7 @@ src="./images/weather_station_schematic.svg" alt="Schematic" /></a></p>
|
||||
</ul>
|
||||
<p>Libraries are included with the source code of this project</p>
|
||||
<h1 id="license">License</h1>
|
||||
<p>Copyright (C) 2023, 2024 M.T. Konstapel</p>
|
||||
<p>Copyright (C) 2023-2025 M.T. Konstapel</p>
|
||||
<p><a
|
||||
href="https://meezenest.nl/mees/">https://meezenest.nl/mees/</a></p>
|
||||
<p>The software is published as open-source software (GPL). The hardware
|
||||
@@ -903,7 +1017,7 @@ option) any later version.</p>
|
||||
</main>
|
||||
<footer>
|
||||
<p>©
|
||||
2024-01-22
|
||||
2025-01-14
|
||||
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>.
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
title: Weather station
|
||||
subtitle: with ModBus RTU interface
|
||||
author: M.T. Konstapel
|
||||
date: 2024-01-22
|
||||
date: 2025-01-14
|
||||
website: https://meezenest.nl/mees/
|
||||
page_back: https://meezenest.nl/mees/weather_station.html
|
||||
logo: ./images/mees_logo.svg
|
||||
pdf_version: ./weather_station.pdf
|
||||
git_repo: https://git.meezenest.nl/
|
||||
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"
|
||||
@@ -210,7 +211,7 @@ The heart of the circuit is an Arduino Pro Mini, which is basically an Atmel ATm
|
||||
|
||||

|
||||
|
||||
Both the signals from the rain meter and the cup anemometer are connected to interrupt pins of the micro-controller. The signal from the rain meter is lightly filtered by C8.
|
||||
Both the signals from the rain meter and the cup anemometer are connected to interrupt pins of the micro-controller. The signal from the rain meter is lightly filtered by C8. The output of the wind vane is connectd to an analog input of the micro-controller.
|
||||
|
||||
The ModBus address can be set by DIP switch J9.
|
||||
|
||||
@@ -271,7 +272,7 @@ Below an example of how to read the wind direction and enable the heater algorit
|
||||
instrument = minimalmodbus.Instrument('/dev/ttyUSB1', 14)
|
||||
|
||||
# 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)
|
||||
|
||||
# register address, value, function code
|
||||
@@ -319,11 +320,11 @@ Output coils registers are numbered 1 to 9999 but have data addresses 0x000 to 0
|
||||
|
||||
# Prototype
|
||||
|
||||
I wanted to locate the weather station at about 100 meters from the house. That meant that interfacing the weather station was not just a matter of connecting a wire to it. And 100 meters is a bit much for a wifi connection either. As I already had experience with LoRa I opted for that. But not LoRaWAN, but LoRa APRS. This is a ham radio network that I often use. I even run my own digipeater. So LoRa APRS it is.
|
||||
I wanted to locate the weather station at about 100 meters from the house. That meant that interfacing the weather station was not just a matter of connecting a wire to it. And 100 meters is also a bit much for a wifi connection. As I already had experience with LoRa I opted for that. But not LoRaWAN, but LoRa APRS. This is a ham radio network that I often use. I even run my own digipeater. So LoRa APRS it is.
|
||||
|
||||

|
||||
|
||||
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)
|
||||
|
||||

|
||||
|
||||
@@ -391,6 +392,85 @@ As a housing for the prototype, I used an old beehive. These are weatherproof an
|
||||
|
||||
[](./images/weather_station_schematic.pdf)
|
||||
|
||||
# Problems I encounter after four months of use
|
||||
|
||||
## Humidity sensor
|
||||
|
||||
The Si7021 humidity sensor is not made for outdoor use. The datasheet is clear about that. But a lot of people use this cheap sensor for weather stations anyway. So I choose this sensor for my design. But that was not smart. After an initial time without any problems, the sensor started to saturate. This happened during a very wet and mild winter we had. My first solution was to utilize the build in heater to drive of the moisture. That worked, but only for a short period. The heater blew up and the sensor started to report humidity levels above 100% and because of a bug in the firmware of the sensor, the humidity register wrapped around and the sensor reported humidity levels of 0-30%. I tried to find a software solution, which worked for a while, but the sensor deteriorated even more. To the point of being totally useless. So I searched for another, better sensor. And I found the HYT-221 from Innovative Sensor Technology. This sensor is designed to work outdoors and can even be used in saunas, where the humidity levels are always high and the air is condensating. The datasheet specifically mentions outdoor weather stations as an application. The only downside is its price: it is ten times more expensive than the Si7021.
|
||||
|
||||
The sensor can be controlled via the I2C bus, so implementing the new sensor in the firmware was very easy. From version 0.3.0 onward, this sensor is used. The Si7021 is removed from the code.
|
||||
|
||||
## Pressure sensor
|
||||
|
||||
After a year the BMP280 pressure sensor started to give odd pressure values. The pressure was around 700hPa and followed the temperature curve. The temperature sensor still worked fine. After replacing the BMP280 the barometric values returned to normal. I suspect that it malfunctioned because of the perpetual fog we had for about two months. The sensor was mounted in the Garni RS1 Passive Radiation Shield. Rain could not enter the shield, but fog could.
|
||||
|
||||
# Additions
|
||||
|
||||
## Luminosity sensor
|
||||
|
||||
From version 0.3.1 onward, the weather station has an ambient light sensor. It is an SEN0562 from DFRobot and it outputs the light intensity in Lux. It is a 5 Volt only device, so it should be connected to the 5 Volt I2C bus.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The sensor has the following specifications:
|
||||
|
||||
- Supply Voltage: 5V
|
||||
- Operating Current: 1µA
|
||||
- Detection Range: 1 - 65535lx
|
||||
- Accuracy: 1.2lx
|
||||
- Communication Mode: I2C
|
||||
- Operating Temperature: -40 to 85°C/-40 to 185℉
|
||||
- Waterproof Rating: IP68
|
||||
- Thread Length: 10mm
|
||||
- Cutout Size: 26mm
|
||||
- Wrench Size: 31mm
|
||||
- Cable Diameter: 3mm
|
||||
- Wire Length: 1m
|
||||
|
||||
Sample code:
|
||||
|
||||
#include "Wire.h"
|
||||
#define address 0x23 //I2C address 0x23
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(9600);
|
||||
Wire.begin();
|
||||
}
|
||||
uint8_t buf[4] = {0};
|
||||
uint16_t data, data1;
|
||||
float Lux;
|
||||
void loop()
|
||||
{
|
||||
readReg(0x10, buf, 2); //Register address 0x10
|
||||
data = buf[0] << 8 | buf[1];
|
||||
Lux = (((float)data )/1.2);
|
||||
Serial.print("LUX:");
|
||||
Serial.print(Lux);
|
||||
Serial.print("lx");
|
||||
Serial.print("\n");
|
||||
delay(500);
|
||||
}
|
||||
uint8_t readReg(uint8_t reg, const void* pBuf, size_t size)
|
||||
{
|
||||
if (pBuf == NULL) {
|
||||
Serial.println("pBuf ERROR!! : null pointer");
|
||||
}
|
||||
uint8_t * _pBuf = (uint8_t *)pBuf;
|
||||
Wire.beginTransmission(address);
|
||||
Wire.write(®, 1);
|
||||
if ( Wire.endTransmission() != 0) {
|
||||
return 0;
|
||||
}
|
||||
delay(20);
|
||||
Wire.requestFrom(address, (uint8_t) size);
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
_pBuf[i] = Wire.read();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
<!---
|
||||
# Bill of materials
|
||||
|
||||
@@ -414,7 +494,7 @@ Libraries are included with the source code of this project
|
||||
|
||||
# License
|
||||
|
||||
Copyright (C) 2023, 2024 M.T. Konstapel
|
||||
Copyright (C) 2023-2025 M.T. Konstapel
|
||||
|
||||
[https://meezenest.nl/mees/](https://meezenest.nl/mees/)
|
||||
|
||||
|
||||
Binary file not shown.
57
build-doc/weather_station_article.md
Normal file
57
build-doc/weather_station_article.md
Normal file
@@ -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.
|
||||
|
||||

|
||||
|
||||
# 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.
|
||||
BIN
datasheet/HYT 221_application_note.pdf
Normal file
BIN
datasheet/HYT 221_application_note.pdf
Normal file
Binary file not shown.
BIN
datasheet/HYT 221_datasheet.pdf
Normal file
BIN
datasheet/HYT 221_datasheet.pdf
Normal file
Binary file not shown.
@@ -10,7 +10,7 @@
|
||||
* BLINK : I2C ERROR
|
||||
* FLASH : Heartbeat
|
||||
*
|
||||
* Copyright (C) 2023, 2024 M.T. Konstapel https://meezenest.nl/mees
|
||||
* Copyright (C) 2023-2025 M.T. Konstapel https://meezenest.nl/mees
|
||||
*
|
||||
* This file is part of weather_station
|
||||
*
|
||||
@@ -32,7 +32,26 @@
|
||||
* - 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)
|
||||
*
|
||||
* 2024-05-02: - Removed code for si7021
|
||||
* - Added code for HYT221 humidity sensor
|
||||
*
|
||||
* 2025-01-13: - Cleanup of code
|
||||
* - Debounce rainfall meter from 100ms to 250mms
|
||||
* - Debug messages can be switched on and off with the _DEBUG_ variable
|
||||
* - Main temperature and backup temperature registers switched: the sensor on the HYT221 heats up by
|
||||
* the circuitry and is therefore about 1.5 degrees above ambient temperature. The BMP280 does not heat up.
|
||||
* - Added SEN0562 luminosity sensor
|
||||
*
|
||||
* 2025-01-26: - Bug rainmeter fixed
|
||||
* - Bug luminosity sensor fixed
|
||||
*
|
||||
* See CHANGELOG.md
|
||||
*
|
||||
**********************************************************************************/
|
||||
|
||||
// When set to 1 debug messages are send to the serial port, set it to 0 for production code!
|
||||
#define _DEBUG_ 0
|
||||
|
||||
#include <ModbusSerial.h>
|
||||
#include "SparkFun_Weather_Meter_Kit_Arduino_Library.h"
|
||||
|
||||
@@ -41,13 +60,15 @@
|
||||
#include "i2c.h"
|
||||
|
||||
//Temperature and humidity sensor
|
||||
#include "i2c_SI7021.h"
|
||||
SI7021 si7021;
|
||||
#define HYT_ADDR 0x28 // I2C address of the HYT 221, 271, 371 and most likely the rest of the family
|
||||
|
||||
// Light meter
|
||||
#define SEN0562_ADDR 0x23 //I2C address of Gravity SEN0562 light meter
|
||||
|
||||
// Pressure sensor
|
||||
#include "i2c_BMP280.h"
|
||||
BMP280 bmp280;
|
||||
float PRESSURE_OFFSET = 210; // Calibration of BMP280: offset in Pascal
|
||||
float PRESSURE_OFFSET = -100; // Calibration of BMP280: offset in Pascal
|
||||
|
||||
/**************************/
|
||||
/* Configurable variables */
|
||||
@@ -102,7 +123,7 @@ const int SensorStatusBitsIreg = 14;
|
||||
* Coils
|
||||
* 0 = Heater algorithm (0 = disable, 1 = enable)
|
||||
*/
|
||||
const int HeaterCoil = 0;
|
||||
const int HeaterCoil = 0; // Legacy register, not used anymore.
|
||||
|
||||
// RS-485 serial port
|
||||
#define MySerial Serial // define serial port used, Serial most of the time, or Serial1, Serial2 ... if available
|
||||
@@ -138,140 +159,98 @@ struct MeasuredData {
|
||||
uint16_t RainLast24;
|
||||
uint16_t SensorRainSinceMidnight;
|
||||
uint16_t Pressure;
|
||||
uint16_t Luminosity;
|
||||
uint16_t StatusBits = 0;
|
||||
uint16_t RainfallCounter = 0;
|
||||
|
||||
float Temperature;
|
||||
float TemperatureHygrometer;
|
||||
float Humidity;
|
||||
float TemperatureBackup;
|
||||
float TemperatureBarometer;
|
||||
float Luminosity;
|
||||
|
||||
bool HeaterStatus = 0;
|
||||
} MeasuredData;
|
||||
|
||||
// State machine implementing smart heater to prevent saturation of the sensor
|
||||
char HeaterSi7021 (float humidity)
|
||||
void ReadHYT221 (void)
|
||||
{
|
||||
static int state=0;
|
||||
static unsigned long StatemachineTimer=0;
|
||||
bool TempValid=1;
|
||||
bool Heater=0;
|
||||
double humidity;
|
||||
double temperature;
|
||||
|
||||
// If Smart heater algorithm is disabled, reset the statemachine forever.
|
||||
// TempValid bit is also forced to 1, but it could be that we just came out of a heater period.
|
||||
// We assume that the client on the other side of the ModBus is smart enough to understand.
|
||||
if ( (MeasuredData.StatusBits & 0x04) == 0)
|
||||
state = 0;
|
||||
Wire.beginTransmission(HYT_ADDR); // Begin transmission with given device on I2C bus
|
||||
Wire.requestFrom(HYT_ADDR, 4); // Request 4 bytes
|
||||
|
||||
switch (state)
|
||||
{
|
||||
// Default state: humidity is below 95%
|
||||
case 0:
|
||||
Heater = 0;
|
||||
if (humidity >= 95) {
|
||||
StatemachineTimer = millis();
|
||||
state = 1;
|
||||
}
|
||||
break;
|
||||
// Humidity went above 95%. See if humidity stays above 95% for more than an hour, if so turn on heater
|
||||
case 1:
|
||||
if (humidity >= 95) {
|
||||
if ( (millis() - StatemachineTimer) >= 3.6e+6 ) {
|
||||
//if ( (millis() - StatemachineTimer) >= 300000 ) { // short delay for testing
|
||||
Heater = 1;
|
||||
TempValid = 0;
|
||||
StatemachineTimer = millis();
|
||||
state = 2;
|
||||
} else {
|
||||
Heater = 0;
|
||||
}
|
||||
} else {
|
||||
Heater = 0;
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
// Read the bytes if they are available
|
||||
// The first two bytes are humidity the last two are temperature
|
||||
if(Wire.available() == 4) {
|
||||
int b1 = Wire.read();
|
||||
int b2 = Wire.read();
|
||||
int b3 = Wire.read();
|
||||
int b4 = Wire.read();
|
||||
|
||||
// Heater is now on, let the sensor cook for 5 minutes
|
||||
case 2:
|
||||
if ( (millis() - StatemachineTimer) >= 300000 ) {
|
||||
StatemachineTimer = millis();
|
||||
Heater = 0;
|
||||
state = 3;
|
||||
} else {
|
||||
Heater = 1;
|
||||
}
|
||||
TempValid = 0;
|
||||
break;
|
||||
// Heater is now off, let the sensor cool for 15 minutes
|
||||
case 3:
|
||||
if ( (millis() - StatemachineTimer) >= 900000 ) {
|
||||
TempValid = 1; // Sensor cooled, so we can take a valid temperature reading
|
||||
if (humidity >= 95) {
|
||||
// Humidity still above 95%, repeat heating/cooling
|
||||
Heater = 1;
|
||||
StatemachineTimer = millis();
|
||||
state = 2;
|
||||
} else {
|
||||
// Humidity below 95%, reset statemachine
|
||||
Heater = 0;
|
||||
state = 0;
|
||||
}
|
||||
} else {
|
||||
Heater = 0;
|
||||
TempValid = 0;
|
||||
}
|
||||
break;
|
||||
Wire.endTransmission(); // End transmission and release I2C bus
|
||||
|
||||
default:
|
||||
Heater = 0;
|
||||
state = 0;
|
||||
break;
|
||||
// combine humidity bytes and calculate humidity
|
||||
int rawHumidity = b1 << 8 | b2;
|
||||
// compound bitwise to get 14 bit measurement first two bits
|
||||
// are status/stall bit (see intro text)
|
||||
rawHumidity = (rawHumidity &= 0x3FFF);
|
||||
humidity = 100.0 / pow(2,14) * rawHumidity;
|
||||
|
||||
// Scale for more decimal positions when converted to integer value for ModBus
|
||||
MeasuredData.Humidity = 100 * humidity;
|
||||
|
||||
// combine temperature bytes and calculate temperature
|
||||
b4 = (b4 >> 2); // Mask away 2 least significant bits see HYT 221 doc
|
||||
int rawTemperature = b3 << 6 | b4;
|
||||
temperature = 165.0 / pow(2,14) * rawTemperature - 40;
|
||||
|
||||
// Scale for more decimal positions when converted to integer value for ModBus
|
||||
MeasuredData.TemperatureHygrometer = 100 * temperature;
|
||||
|
||||
if (_DEBUG_) {
|
||||
Serial.print("HYT221 humidity: ");
|
||||
Serial.print(MeasuredData.Humidity/100);
|
||||
Serial.print("% - Temperature: ");
|
||||
Serial.println(MeasuredData.TemperatureHygrometer/100);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Serial.println("Not enough bytes available on wire.");
|
||||
}
|
||||
|
||||
return TempValid<<1 | Heater;
|
||||
|
||||
}
|
||||
// Read Si7021 sensor and process data
|
||||
void ReadSi7021 (void)
|
||||
|
||||
uint8_t ReadSEN0562_register(uint8_t reg, const void* pBuf)
|
||||
{
|
||||
char result=0x2;
|
||||
float humidity;
|
||||
|
||||
si7021.triggerMeasurement();
|
||||
si7021.getHumidity(humidity);
|
||||
|
||||
if (humidity>100 || humidity<0)
|
||||
humidity = 100;
|
||||
|
||||
//If humidity is larger than 95% switch on heater to get more acurate measurement and prevent memory offset
|
||||
result = HeaterSi7021(humidity);
|
||||
MeasuredData.StatusBits &= 0xFFFC; // Reset heater status bits to zero
|
||||
MeasuredData.StatusBits |= result; // And set the proper bits to one if there are any. The result is we copied the status bits to the register
|
||||
|
||||
// Temperture and humidity readings are valid (as the sensor is not heated)
|
||||
if (result & 0x2) {
|
||||
si7021.getTemperature(MeasuredData.Temperature);
|
||||
// Scale for more decimal positions when converted to integer value for ModBus
|
||||
MeasuredData.Temperature *= 100;
|
||||
//Serial.print(F("Valid temp"));
|
||||
|
||||
si7021.getHumidity(MeasuredData.Humidity);
|
||||
if (MeasuredData.Humidity>100 || MeasuredData.Humidity<0)
|
||||
MeasuredData.Humidity = 100;
|
||||
// Scale for more decimal positions when converted to integer value for ModBus
|
||||
MeasuredData.Humidity *= 100;
|
||||
if (pBuf == NULL) {
|
||||
Serial.println("pBuf ERROR!! : null pointer");
|
||||
}
|
||||
// Statemachine thinks it is time to switch on the heater
|
||||
if (result & 0x1) {
|
||||
//Serial.print(F("Heater on."));
|
||||
MeasuredData.HeaterStatus = 1;
|
||||
|
||||
} else {
|
||||
//Serial.print(F("Heater off."));
|
||||
MeasuredData.HeaterStatus = 0;
|
||||
uint8_t * _pBuf = (uint8_t *)pBuf;
|
||||
Wire.beginTransmission(SEN0562_ADDR);
|
||||
Wire.write(®, 1);
|
||||
if ( Wire.endTransmission() != 0) {
|
||||
return 0;
|
||||
}
|
||||
si7021.setHeater(MeasuredData.HeaterStatus);
|
||||
delay(20);
|
||||
Wire.requestFrom(SEN0562_ADDR, 2);
|
||||
for (uint16_t i = 0; i < 2; i++) {
|
||||
_pBuf[i] = Wire.read();
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
void ReadSEN0562()
|
||||
{
|
||||
uint8_t buf[4] = {0};
|
||||
uint16_t data, data1;
|
||||
|
||||
ReadSEN0562_register(0x10, buf); //Register address 0x10
|
||||
data = buf[0] << 8 | buf[1];
|
||||
MeasuredData.Luminosity = ((float)data )/1.2;
|
||||
|
||||
if (_DEBUG_) {
|
||||
Serial.print("SEN0562 light intensity: ");
|
||||
Serial.print(MeasuredData.Luminosity);
|
||||
Serial.println(" Lux");
|
||||
}
|
||||
}
|
||||
|
||||
// Read BMP280
|
||||
@@ -284,20 +263,29 @@ void ReadBMP280 (void)
|
||||
pascal = (pascal - PRESSURE_OFFSET) / 10; // Convert to hPa
|
||||
MeasuredData.Pressure = pascal;
|
||||
|
||||
bmp280.getTemperature(MeasuredData.TemperatureBackup);
|
||||
bmp280.getTemperature(MeasuredData.TemperatureBarometer);
|
||||
// Scale for more decimal positions when converted to integer value for ModBus
|
||||
MeasuredData.TemperatureBackup *= 100;
|
||||
MeasuredData.TemperatureBarometer *= 100;
|
||||
|
||||
bmp280.triggerMeasurement();
|
||||
|
||||
if (_DEBUG_) {
|
||||
Serial.print("BMP280 pressure: ");
|
||||
Serial.print(MeasuredData.Pressure);
|
||||
Serial.print("hPa - Temperature: ");
|
||||
Serial.println(MeasuredData.TemperatureBarometer/100);
|
||||
}
|
||||
}
|
||||
|
||||
int MaxOfArray (int array[], uint16_t length)
|
||||
{
|
||||
int maximum_value = 0;
|
||||
|
||||
while (length--)
|
||||
while (length)
|
||||
{
|
||||
// decrement length, because 0/1 problem: if lenght = n, than last position off array is n-1
|
||||
length--;
|
||||
|
||||
if (array[length] > maximum_value)
|
||||
maximum_value = array[length];
|
||||
}
|
||||
@@ -310,8 +298,11 @@ uint16_t AverageOfArray (uint16_t array[], uint16_t length)
|
||||
uint8_t tmp_length = length;
|
||||
uint16_t average_value = 0;
|
||||
|
||||
while (length--)
|
||||
while (length)
|
||||
{
|
||||
// decrement length, because 0/1 problem: if lenght = n, than last position off array is n-1
|
||||
length--;
|
||||
|
||||
tmp_value += array[length];
|
||||
}
|
||||
average_value = tmp_value/tmp_length;
|
||||
@@ -451,34 +442,11 @@ void setup() {
|
||||
mb.Ireg (SensorRainSinceMidnightIreg, 0);
|
||||
mb.Ireg (SensorSnowFallIreg, 0);
|
||||
|
||||
Serial.println(F("Weather station v0.2.2"));
|
||||
Serial.println(F("(C)2024 M.T. Konstapel"));
|
||||
Serial.println(F("Weather station v0.3.2"));
|
||||
Serial.println(F("(C)2024-2025 M.T. Konstapel"));
|
||||
Serial.println(F("This project is free and open source"));
|
||||
Serial.println(F("More details: https://meezenest.nl/mees/"));
|
||||
|
||||
//Initialize Si7021 sensor
|
||||
Serial.print(F("Humidity sensor SI7021 "));
|
||||
if (si7021.initialize())
|
||||
Serial.println(F("found"));
|
||||
else
|
||||
{
|
||||
Serial.println(F("missing"));
|
||||
while(1) {
|
||||
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
|
||||
delay(500); // wait for half a second
|
||||
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
|
||||
delay(500);
|
||||
}
|
||||
}
|
||||
|
||||
// The standard library of the Si7021 sets the heater element to the default 3.1mA, but we want the full power
|
||||
// We could alter the library, but than we break compatibility. So for this one time we do a raw-write to the
|
||||
// heater register.
|
||||
const uint8_t SI7021_I2C_ADDRESS =(0x40);
|
||||
const uint8_t SI7021_CMD_WRITE_HEATER_CONTROL_REG =(0x51);
|
||||
const uint8_t SI7021_HEATER_FULL_BLAST =(0x0F); // Set heater to 94mA
|
||||
i2c.writeByte(SI7021_I2C_ADDRESS, SI7021_CMD_WRITE_HEATER_CONTROL_REG, SI7021_HEATER_FULL_BLAST);
|
||||
|
||||
// Initialize BMP280 pressure sensor
|
||||
Serial.print(F("Pressure sensor BMP280 "));
|
||||
if (bmp280.initialize())
|
||||
@@ -546,7 +514,7 @@ void setup() {
|
||||
// The rainfall detector switch can sometimes bounce, causing multiple extra
|
||||
// triggers. This input is debounced by ignoring extra triggers within a
|
||||
// time window, which defaults to 100ms
|
||||
calibrationParams.minMillisPerRainfall = 100;
|
||||
calibrationParams.minMillisPerRainfall = 250;
|
||||
|
||||
// The anemometer contains a switch that opens and closes as it spins. The
|
||||
// rate at which the switch closes depends on the wind speed. The datasheet
|
||||
@@ -569,7 +537,7 @@ void setup() {
|
||||
weatherMeterKit.begin();
|
||||
|
||||
ts = millis();
|
||||
RainPerHourCounter = ts;
|
||||
HourTimer = ts;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
@@ -585,7 +553,9 @@ void loop() {
|
||||
digitalWrite(LED_BUILTIN, HIGH); // LED as heartbeat
|
||||
|
||||
// Read temperature and humidity
|
||||
ReadSi7021();
|
||||
ReadHYT221();
|
||||
|
||||
ReadSEN0562();
|
||||
|
||||
// Read pressure and temperature
|
||||
ReadBMP280();
|
||||
@@ -599,19 +569,15 @@ void loop() {
|
||||
mb.Ireg (SensorWindGustIreg, MeasuredData.WindGust);
|
||||
mb.Ireg (SensorRainIreg, MeasuredData.Rain);
|
||||
mb.Ireg (SensorRainLast24Ireg, MeasuredData.RainLast24);
|
||||
mb.Ireg (SensorTemperatureIreg, MeasuredData.Temperature);
|
||||
mb.Ireg (SensorTemperatureIreg, MeasuredData.TemperatureBarometer);
|
||||
mb.Ireg (SensorHumidityIreg, MeasuredData.Humidity);
|
||||
mb.Ireg (SensorPressureIreg, MeasuredData.Pressure);
|
||||
mb.Ireg (SensorTemperatureBackupIreg, MeasuredData.TemperatureBackup);
|
||||
mb.Ireg (SensorTemperatureBackupIreg, MeasuredData.TemperatureHygrometer);
|
||||
mb.Ireg (SensorLuminosityIreg, MeasuredData.Luminosity);
|
||||
mb.Ireg (SensorRainfallRawIreg, MeasuredData.RainfallCounter);
|
||||
mb.Ireg (SensorStatusBitsIreg, MeasuredData.StatusBits);
|
||||
|
||||
// Debug wind vane
|
||||
//Serial.print(F("\n Measured ADC: "));
|
||||
//Serial.print(analogRead(windDirectionPin));
|
||||
|
||||
// enable or disable smart heater
|
||||
// enable or disable smart heater (legacy code, does not do anything except setting the status bit)
|
||||
if (mb.Coil (HeaterCoil)) {
|
||||
MeasuredData.StatusBits |= 0x04; // Set bit
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user