Added heather algorithm

master
marcel 10 months ago
parent 439a6b6a1f
commit e99422412e
  1. 15
      CHANGELOG.md
  2. BIN
      build-doc/images/smart_heater.odg
  3. 1263
      build-doc/images/smart_heater.svg
  4. 13
      build-doc/images/wind_speed_diagram.svg
  5. 95
      build-doc/weather_station.html
  6. 61
      build-doc/weather_station.md
  7. BIN
      build-doc/weather_station.pdf
  8. 196
      firmware/weather_station.ino
  9. BIN
      test_software/__pycache__/epever_control.cpython-36.pyc
  10. 20
      test_software/epever_control.py
  11. 6
      test_software/weather_station_rs485_client.py

@ -26,3 +26,18 @@ All notable changes to this project will be documented in this file.
### Changed ### Changed
- Heater of humidiy sensor only on when humidity is above 96% (was 80%). When heater is on temperature sensor also heats up, so use temperature sensor of BMP280 when heater is on. - Heater of humidiy sensor only on when humidity is above 96% (was 80%). When heater is on temperature sensor also heats up, so use temperature sensor of BMP280 when heater is on.
## [0.2.1] - 2024-01-17
### Changed
- Heater algorithm updated. Idea from datasheet of humidity sensor from Temco Controls HUM-M2 humidity sensor module.
- ModBus read register 1: Scaled wind direction by 10 in order to also get the decimal position of the direction
### Added
- ModBus read register 12: Raw rain meter
- ModBus read register 13: Temperature from pressure sensor
- ModBus read register 14: Status bits
- ModBus coil register 0 : enable/disable heather algorithm

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 48 KiB

@ -9,9 +9,9 @@
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.2" version="1.2"
width="125.34mm" width="60.926762mm"
height="165.31999mm" height="80.360718mm"
viewBox="0 0 12534 16531.999" viewBox="0 0 6092.6763 8036.0717"
preserveAspectRatio="xMidYMid" preserveAspectRatio="xMidYMid"
fill-rule="evenodd" fill-rule="evenodd"
stroke-width="28.222" stroke-width="28.222"
@ -45,7 +45,8 @@
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="0" inkscape:window-y="0"
inkscape:window-maximized="1" inkscape:window-maximized="1"
inkscape:current-layer="svg439" /> inkscape:current-layer="svg439"
inkscape:document-rotation="0" />
<defs <defs
class="ClipPathGroup" class="ClipPathGroup"
id="defs8"> id="defs8">
@ -347,7 +348,7 @@
</defs> </defs>
<g <g
id="g124" id="g124"
transform="translate(-3234,-4235)"> transform="translate(-3234,-4234.9999)">
<g <g
id="id2" id="id2"
class="Master_Slide"> class="Master_Slide">
@ -362,7 +363,7 @@
<g <g
class="SlideGroup" class="SlideGroup"
id="g437" id="g437"
transform="translate(-3234,-4235)"> transform="matrix(0.48609191,0,0,0.48609191,-1572.0212,-2058.5992)">
<g <g
id="g435"> id="g435">
<g <g

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

@ -564,7 +564,17 @@ But not before the temperature is measured and stored, as the sensor is
now cooled off to ambient temperature. If the humidity is below 95% the now cooled off to ambient temperature. If the humidity is below 95% the
sensor is free from moisture and the process is not repeated for another sensor is free from moisture and the process is not repeated for another
hour.</p> hour.</p>
<p>Flow chart under development.</p> <p>When the heater algorithm is active, the temperature is updated every
20 minutes instead of every 2 seconds. Statis bit 0 (ModBus register
30014) indicated if the heater is on or off and status bit 1 gives the
update rate of the main temperature sensor.</p>
<p>This algorithm can be enabled by setting the HeaterCoil (see ModBus
secion).</p>
<figure>
<img src="./images/smart_heater.svg" title="Heater algorithm"
alt="Heater algorithm" />
<figcaption aria-hidden="true">Heater algorithm</figcaption>
</figure>
<h2 id="temperature-1">Temperature</h2> <h2 id="temperature-1">Temperature</h2>
<p>The temperature is read from the humidity sensor as this sensor gives <p>The temperature is read from the humidity sensor as this sensor gives
the most accurate temperature readings. When the heater is on (see the most accurate temperature readings. When the heater is on (see
@ -582,8 +592,8 @@ boring.</p>
now, the ModBus address is hard coded as 14 in the software. The values now, the ModBus address is hard coded as 14 in the software. The values
are available in the input registers and can be read via function code are available in the input registers and can be read via function code
04.</p> 04.</p>
<p>Below an example of how to read the wind direction in Python using <p>Below an example of how to read the wind direction and enable the
the minimalmodbus library.</p> heater algorithm in Python using the minimalmodbus library.</p>
<pre><code>#!/usr/bin/env python3 <pre><code>#!/usr/bin/env python3
import minimalmodbus import minimalmodbus
@ -592,10 +602,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, 0, 4)
print(wind_direction)</code></pre> print(wind_direction)
<h3 id="input-registers">Input registers</h3>
<p>The measurements and order of the measurements are the same as for # register address, value, function code
APRS weather reports. But of course we use SI units.</p> instrument.write_bit(0, 1, 5)</code></pre>
<h3 id="input-registers-read-only">Input registers (read only)</h3>
<p>Input registers are numbered 30001 to 39999 but have data addresses
0x000 to 0x270E. The measurements and order of the measurements are the
same as for APRS weather reports. But of course we use SI units.</p>
<table> <table>
<colgroup> <colgroup>
<col style="width: 13%" /> <col style="width: 13%" />
@ -611,77 +625,77 @@ APRS weather reports. But of course we use SI units.</p>
</thead> </thead>
<tbody> <tbody>
<tr class="odd"> <tr class="odd">
<td>30000</td> <td>00</td>
<td>Device ID (0x5758)</td> <td>Device ID (0x5758)</td>
<td>NO UNIT</td> <td>NO UNIT</td>
</tr> </tr>
<tr class="even"> <tr class="even">
<td>30001</td> <td>01</td>
<td>Wind direction</td> <td>Wind direction</td>
<td>degrees</td> <td>degrees * 10</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td>30002</td> <td>02</td>
<td>Wind speed (average of 10 minutes)</td> <td>Wind speed (average of 10 minutes)</td>
<td>m/s * 100</td> <td>m/s * 100</td>
</tr> </tr>
<tr class="even"> <tr class="even">
<td>30003</td> <td>03</td>
<td>Wind gust (peak of last 10 minutes)</td> <td>Wind gust (peak of last 10 minutes)</td>
<td>m/s * 100</td> <td>m/s * 100</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td>30004</td> <td>04</td>
<td>Temperature (two’s complement)</td> <td>Temperature (two’s complement)</td>
<td>degrees Celcius * 100</td> <td>degrees Celcius * 100</td>
</tr> </tr>
<tr class="even"> <tr class="even">
<td>30005</td> <td>05</td>
<td>Rain last hour</td> <td>Rain last hour</td>
<td>l/m2 * 100</td> <td>l/m2 * 100</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td>30006</td> <td>06</td>
<td>Rain last 24 hours</td> <td>Rain last 24 hours</td>
<td>l/m2 * 100</td> <td>l/m2 * 100</td>
</tr> </tr>
<tr class="even"> <tr class="even">
<td>30007</td> <td>07</td>
<td>Rain since midnight</td> <td>Rain since midnight</td>
<td>NOT IMPLEMENTED</td> <td>NOT IMPLEMENTED</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td>30008</td> <td>08</td>
<td>Humidity</td> <td>Humidity</td>
<td>percent * 100</td> <td>percent * 100</td>
</tr> </tr>
<tr class="even"> <tr class="even">
<td>30009</td> <td>09</td>
<td>Barometric pressure</td> <td>Barometric pressure</td>
<td>hPa * 10</td> <td>hPa * 10</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td>30010</td> <td>10</td>
<td>Luminosity</td> <td>Luminosity</td>
<td>W/m2</td> <td>W/m2</td>
</tr> </tr>
<tr class="even"> <tr class="even">
<td>30011</td> <td>11</td>
<td>Snow fall</td> <td>Snow fall</td>
<td>NOT IMPLEMENTED</td> <td>NOT IMPLEMENTED</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td>30012</td> <td>12</td>
<td>Raw rain counter</td> <td>Raw rain counter</td>
<td>NOT IMPLEMENTED</td> <td>l/m2 * 100</td>
</tr> </tr>
<tr class="even"> <tr class="even">
<td>30013</td> <td>13</td>
<td>Temperature (two’s complement)</td> <td>Temperature (two’s complement)</td>
<td>degrees Celcius * 100</td> <td>degrees Celcius * 100</td>
</tr> </tr>
<tr class="odd"> <tr class="odd">
<td>30014</td> <td>14</td>
<td>Status bits</td> <td>Status bits</td>
<td>see table below</td> <td>see table below</td>
</tr> </tr>
@ -705,12 +719,45 @@ from the pressure sensor.</p>
<td>heater off</td> <td>heater off</td>
<td>heater on</td> <td>heater on</td>
</tr> </tr>
<tr class="even">
<td>1</td>
<td>Temp update</td>
<td>every 2 sec</td>
<td>every 20 minutes</td>
</tr>
<tr class="odd">
<td>2</td>
<td>Heater algorithm</td>
<td>disabled</td>
<td>enabled</td>
</tr>
</tbody> </tbody>
</table> </table>
<p>The ModBus registers are 16 bit wide. For better precision, some <p>The ModBus registers are 16 bit wide. For better precision, some
units are scaled by a factor of 10 or 100. This way, values with up to units are scaled by a factor of 10 or 100. This way, values with up to
two decimal points can be stored as 16 bit integer values. Just divide two decimal points can be stored as 16 bit integer values. Just divide
by 10 or 100 to get the floating point values.</p> by 10 or 100 to get the floating point values.</p>
<h3 id="output-coils-write-only">Output coils (write only)</h3>
<p>Input registers are numbered 1 to 9999 but have data addresses 0x000
to 0x270E. The default value is of a register is 0.</p>
<table>
<thead>
<tr class="header">
<th>Address</th>
<th>Description</th>
<th>logic 0</th>
<th>logic 1</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>0</td>
<td>Heater algorithm</td>
<td>disabled</td>
<td>enabled</td>
</tr>
</tbody>
</table>
<h1 id="schematic">Schematic</h1> <h1 id="schematic">Schematic</h1>
<p><a href="./images/weather_station_schematic.pdf"><img <p><a href="./images/weather_station_schematic.pdf"><img
src="./images/weather_station_schematic.svg" alt="Schematic" /></a></p> src="./images/weather_station_schematic.svg" alt="Schematic" /></a></p>

@ -240,7 +240,11 @@ Via the I²C bus, the humidity value of the sensor is read. As the sensor can be
When the humidity rises above 95% for more than an hour the current temperature is stored and the heater is switched on for 10 minutes. Than the heater is switched off again. If after 10 minutes the humidity is still above 95% the heater is turned on again for another 10 minutes. But not before the temperature is measured and stored, as the sensor is now cooled off to ambient temperature. If the humidity is below 95% the sensor is free from moisture and the process is not repeated for another hour. When the humidity rises above 95% for more than an hour the current temperature is stored and the heater is switched on for 10 minutes. Than the heater is switched off again. If after 10 minutes the humidity is still above 95% the heater is turned on again for another 10 minutes. But not before the temperature is measured and stored, as the sensor is now cooled off to ambient temperature. If the humidity is below 95% the sensor is free from moisture and the process is not repeated for another hour.
Flow chart under development. When the heater algorithm is active, the temperature is updated every 20 minutes instead of every 2 seconds. Statis bit 0 (ModBus register 30014) indicated if the heater is on or off and status bit 1 gives the update rate of the main temperature sensor.
This algorithm can be enabled by setting the HeaterCoil (see ModBus secion).
![Heater algorithm](./images/smart_heater.svg "Heater algorithm")
## Temperature ## Temperature
@ -258,7 +262,7 @@ This sensor is still under development.
The weather station uses ModBus RTU over a simplex RS-485 line. For now, the ModBus address is hard coded as 14 in the software. The values are available in the input registers and can be read via function code 04. The weather station uses ModBus RTU over a simplex RS-485 line. For now, the ModBus address is hard coded as 14 in the software. The values are available in the input registers and can be read via function code 04.
Below an example of how to read the wind direction in Python using the minimalmodbus library. Below an example of how to read the wind direction and enable the heater algorithm in Python using the minimalmodbus library.
#!/usr/bin/env python3 #!/usr/bin/env python3
import minimalmodbus import minimalmodbus
@ -270,36 +274,49 @@ Below an example of how to read the wind direction in Python using the minimalmo
wind_direction = instrument.read_register(1, 0, 4) wind_direction = instrument.read_register(1, 0, 4)
print(wind_direction) print(wind_direction)
### Input registers # register address, value, function code
instrument.write_bit(0, 1, 5)
### Input registers (read only)
The measurements and order of the measurements are the same as for APRS weather reports. But of course we use SI units. Input registers are numbered 30001 to 39999 but have data addresses 0x000 to 0x270E. The measurements and order of the measurements are the same as for APRS weather reports. But of course we use SI units.
| Address | Description | Units | | Address | Description | Units |
|---------|-------------------------------------|-----------------------| |---------|-------------------------------------|-----------------------|
| 30000 | Device ID (0x5758) | NO UNIT | | 00 | Device ID (0x5758) | NO UNIT |
| 30001 | Wind direction | degrees | | 01 | Wind direction | degrees * 10 |
| 30002 | Wind speed (average of 10 minutes) | m/s * 100 | | 02 | Wind speed (average of 10 minutes) | m/s * 100 |
| 30003 | Wind gust (peak of last 10 minutes) | m/s * 100 | | 03 | Wind gust (peak of last 10 minutes) | m/s * 100 |
| 30004 | Temperature (two's complement) | degrees Celcius * 100 | | 04 | Temperature (two's complement) | degrees Celcius * 100 |
| 30005 | Rain last hour | l/m2 * 100 | | 05 | Rain last hour | l/m2 * 100 |
| 30006 | Rain last 24 hours | l/m2 * 100 | | 06 | Rain last 24 hours | l/m2 * 100 |
| 30007 | Rain since midnight | NOT IMPLEMENTED | | 07 | Rain since midnight | NOT IMPLEMENTED |
| 30008 | Humidity | percent * 100 | | 08 | Humidity | percent * 100 |
| 30009 | Barometric pressure | hPa * 10 | | 09 | Barometric pressure | hPa * 10 |
| 30010 | Luminosity | W/m2 | | 10 | Luminosity | W/m2 |
| 30011 | Snow fall | NOT IMPLEMENTED | | 11 | Snow fall | NOT IMPLEMENTED |
| 30012 | Raw rain counter | NOT IMPLEMENTED | | 12 | Raw rain counter | l/m2 * 100 |
| 30013 | Temperature (two's complement) | degrees Celcius * 100 | | 13 | Temperature (two's complement) | degrees Celcius * 100 |
| 30014 | Status bits | see table below | | 14 | Status bits | see table below |
^NOTE^ Register 30013 holds the backup temperature reading from the pressure sensor. ^NOTE^ Register 30013 holds the backup temperature reading from the pressure sensor.
| Status bits | Description | logic 0 | logic 1 | | Status bits | Description | logic 0 | logic 1 |
|-------------|---------------|------------|-----------| |-------------|------------------|-------------|------------------|
| 0 | Heater status | heater off | heater on | | 0 | Heater status | heater off | heater on |
| 1 | Temp update | every 2 sec | every 20 minutes |
| 2 | Heater algorithm | disabled | enabled |
The ModBus registers are 16 bit wide. For better precision, some units are scaled by a factor of 10 or 100. This way, values with up to two decimal points can be stored as 16 bit integer values. Just divide by 10 or 100 to get the floating point values. The ModBus registers are 16 bit wide. For better precision, some units are scaled by a factor of 10 or 100. This way, values with up to two decimal points can be stored as 16 bit integer values. Just divide by 10 or 100 to get the floating point values.
### Output coils (write only)
Input registers are numbered 1 to 9999 but have data addresses 0x000 to 0x270E. The default value is of a register is 0.
| Address | Description | logic 0 | logic 1 |
|---------|------------------|----------|---------|
| 0 | Heater algorithm | disabled | enabled |
# Schematic # Schematic
[![Schematic](./images/weather_station_schematic.svg)](./images/weather_station_schematic.pdf) [![Schematic](./images/weather_station_schematic.svg)](./images/weather_station_schematic.pdf)

Binary file not shown.

@ -67,9 +67,14 @@ const byte SlaveId = 14;
* 30004: Temperature (degrees Celcius) * 30004: Temperature (degrees Celcius)
* 30005: Rain last hour (l/m2) * 30005: Rain last hour (l/m2)
* 30006: Rain last 24 hours (l/m2) * 30006: Rain last 24 hours (l/m2)
* 30007: Rain since midnight (l/m2) * 30007: Rain since midnight (l/m2) [NOT IMPLEMENTED, always 0]
* 30008: Humidity (percent) * 30008: Humidity (percent)
* 30009: Barometric pressure (hPa) * 30009: Barometric pressure (hPa)
* 30010: Luminosity (W/m2)
* 30011: Snow fall [NOT IMPLEMENTED, always 0]
* 30012: Raw rainfall counter (mm)
* 30013: Temperature pressure sensor (degrees Celsius)
* 30014: Status bits 0=heater, 1-15: reserved
* *
*/ */
const int SensorIDIreg = 0; const int SensorIDIreg = 0;
@ -82,6 +87,18 @@ const int SensorRainLast24Ireg = 6;
const int SensorRainSinceMidnightIreg = 7; const int SensorRainSinceMidnightIreg = 7;
const int SensorHumidityIreg = 8; const int SensorHumidityIreg = 8;
const int SensorPressureIreg = 9; const int SensorPressureIreg = 9;
const int SensorLuminosityIreg = 10;
const int SensorSnowFallIreg = 11;
const int SensorRainfallRawIreg = 12;
const int SensorTemperatureBackupIreg = 13;
const int SensorStatusBitsIreg = 14;
/* Modbus Registers Offsets (0-9999)
* Coils
* 0 = Heater algorithm (0 = disable, 1 = enable)
*/
const int HeaterCoil = 0;
// RS-485 serial port // RS-485 serial port
#define MySerial Serial // define serial port used, Serial most of the time, or Serial1, Serial2 ... if available #define MySerial Serial // define serial port used, Serial most of the time, or Serial1, Serial2 ... if available
const unsigned long Baudrate = 9600; const unsigned long Baudrate = 9600;
@ -116,67 +133,152 @@ struct MeasuredData {
int RainLast24; int RainLast24;
int SensorRainSinceMidnight; int SensorRainSinceMidnight;
int Pressure; int Pressure;
int Luminosity;
int StatusBits = 0;
unsigned int RainfallCounter = 0;
float Temperature; float Temperature;
float Humidity; float Humidity;
float TemperatureBackup;
bool HeaterStatus = 0; bool HeaterStatus = 0;
} MeasuredData; } MeasuredData;
// State machine implementing smart heater to prevent saturation of the sensor
char HeaterSi7021 (float humidity)
{
static int state=0;
static unsigned long StatemachineTimer=0;
bool TempValid=1;
bool Heater=0;
// 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)
state = 0;
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 ) {
Heater = 1;
TempValid = 0;
StatemachineTimer = millis();
state = 2;
} else {
Heater = 0;
}
} else {
Heater = 0;
state = 0;
}
break;
// Heater is now on, let the sensor cook for 10 minutes
case 2:
if ( (millis() - StatemachineTimer) >= 600000 ) {
StatemachineTimer = millis();
Heater = 0;
state = 3;
} else {
Heater = 1;
}
TempValid = 0;
break;
// Heater is now off, let the sensor cool for 10 minutes
case 3:
if ( (millis() - StatemachineTimer) >= 600000 ) {
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;
default:
Heater = 0;
state = 0;
break;
}
return TempValid<<1 | Heater;
}
// Read Si7021 sensor and process data // Read Si7021 sensor and process data
void ReadSi7021 (void) void ReadSi7021 (void)
{ {
char result=0x2;
si7021.triggerMeasurement(); si7021.triggerMeasurement();
si7021.getHumidity(MeasuredData.Humidity); si7021.getHumidity(MeasuredData.Humidity);
si7021.getTemperature(MeasuredData.Temperature);
if (MeasuredData.Humidity>100 || MeasuredData.Humidity<0) if (MeasuredData.Humidity>100 || MeasuredData.Humidity<0)
MeasuredData.Humidity = 100; MeasuredData.Humidity = 100;
//If humidity is larger than 96% switch on heater to get more acurate measurement and prevent memory offset //If humidity is larger than 95% switch on heater to get more acurate measurement and prevent memory offset
//Switch off when lower than 94% (hysteresis) result = HeaterSi7021(MeasuredData.Humidity);
if (MeasuredData.Humidity > 96 && !MeasuredData.HeaterStatus) { MeasuredData.StatusBits &= 0xFFFC; // Reset heater status bits to zero
Serial.print(F("Heater on.")); 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
MeasuredData.HeaterStatus = 1;
si7021.setHeater(MeasuredData.HeaterStatus); // Scale for more decimal positions when converted to integer value for ModBus
MeasuredData.Humidity *= 100;
// Temperture 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"));
} }
if (MeasuredData.Humidity < 94 && MeasuredData.HeaterStatus) { // Statemachine thinks it is time to switch on the heater
Serial.print(F("Heater off.")); if (result & 0x1) {
//Serial.print(F("Heater on."));
MeasuredData.HeaterStatus = 1;
} else {
//Serial.print(F("Heater off."));
MeasuredData.HeaterStatus = 0; MeasuredData.HeaterStatus = 0;
si7021.setHeater(MeasuredData.HeaterStatus);
} }
si7021.setHeater(MeasuredData.HeaterStatus);
// Scale for more decimal positions when converted to integer value for ModBus
MeasuredData.Humidity *= 100;
MeasuredData.Temperature *= 100;
} }
// Read BMP280 // Read BMP280
void ReadBMP280 (void) void ReadBMP280 (void)
{ {
// MeasuredData.Pressure=0;
bmp280.awaitMeasurement(); bmp280.awaitMeasurement();
float temperature;
bmp280.getTemperature(temperature);
float pascal; float pascal;
bmp280.getPressure(pascal); bmp280.getPressure(pascal);
pascal = (pascal - PRESSURE_OFFSET) / 10; // Convert to hPa pascal = (pascal - PRESSURE_OFFSET) / 10; // Convert to hPa
MeasuredData.Pressure = pascal; MeasuredData.Pressure = pascal;
bmp280.triggerMeasurement(); bmp280.getTemperature(MeasuredData.TemperatureBackup);
// Scale for more decimal positions when converted to integer value for ModBus
MeasuredData.TemperatureBackup *= 100;
// When humidity is high, the heater of the Si7021 is on. This causes the temperature sensor of the humidity sensor to heat up. bmp280.triggerMeasurement();
// Use temperature sensor of BMP280 instead.
if (MeasuredData.HeaterStatus) {
bmp280.getTemperature(MeasuredData.Temperature);
// Scale for more decimal positions when converted to integer value for ModBus
MeasuredData.Temperature *= 100;
}
} }
@ -214,7 +316,8 @@ void ReadSparkfunWeatherStation (void)
float tmpRegister; float tmpRegister;
MeasuredData.WindDirection = weatherMeterKit.getWindDirection(); tmpRegister = 10*weatherMeterKit.getWindDirection(); // Use float for conversion to degrees times 10, than put it in integer register for ModBus
MeasuredData.WindDirection = tmpRegister;
tmpRegister = 100*(weatherMeterKit.getWindSpeed())/3.6; // Use float for conversion to m/s times 100, than put it in integer register for ModBus tmpRegister = 100*(weatherMeterKit.getWindSpeed())/3.6; // Use float for conversion to m/s times 100, than put it in integer register for ModBus
MeasuredData.WindSpeed = tmpRegister; MeasuredData.WindSpeed = tmpRegister;
tmpRegister = 100*weatherMeterKit.getTotalRainfall(); // Use float for conversion to l/m2 times 100, than put it in integer register for ModBus tmpRegister = 100*weatherMeterKit.getTotalRainfall(); // Use float for conversion to l/m2 times 100, than put it in integer register for ModBus
@ -279,6 +382,9 @@ void ReadSparkfunWeatherStation (void)
RainPerHourCounter=0; RainPerHourCounter=0;
} }
RainPerHour[RainPerHourCounter] = MeasuredData.Rain; RainPerHour[RainPerHourCounter] = MeasuredData.Rain;
// Every time before we reset the TotalRainCounter we add the amount to the RawRainCounter.
// This 16 bit register will eventually overflow, but 655.35mm of rain fall is a lot!
MeasuredData.RainfallCounter += MeasuredData.Rain; // We don't care about the rounding error due to the convertion from float to int
weatherMeterKit.resetTotalRainfall(); weatherMeterKit.resetTotalRainfall();
// Calculate rain fall in the last 24 hours // Calculate rain fall in the last 24 hours
@ -320,13 +426,22 @@ void setup() {
mb.addIreg (SensorRainSinceMidnightIreg); mb.addIreg (SensorRainSinceMidnightIreg);
mb.addIreg (SensorHumidityIreg); mb.addIreg (SensorHumidityIreg);
mb.addIreg (SensorPressureIreg); mb.addIreg (SensorPressureIreg);
mb.addIreg (SensorLuminosityIreg);
mb.addIreg (SensorSnowFallIreg);
mb.addIreg (SensorRainfallRawIreg);
mb.addIreg (SensorTemperatureBackupIreg);
mb.addIreg (SensorStatusBitsIreg);
// Add HeaterCoil register
mb.addCoil (HeaterCoil);
// Set Weather station ID // Set Weather station ID
mb.Ireg (SensorIDIreg, 0x5758); mb.Ireg (SensorIDIreg, 0x5758);
// Set unused register to zero // Set unused register to zero
mb.Ireg (SensorRainSinceMidnightIreg, 0); mb.Ireg (SensorRainSinceMidnightIreg, 0);
mb.Ireg (SensorSnowFallIreg, 0);
Serial.println(F("Weather station v0.1.0")); Serial.println(F("Weather station v0.2.1"));
Serial.println(F("(C)2024 M.T. Konstapel")); Serial.println(F("(C)2024 M.T. Konstapel"));
Serial.println(F("This project is free and open source")); Serial.println(F("This project is free and open source"));
Serial.println(F("More details: https://meezenest.nl/mees/")); Serial.println(F("More details: https://meezenest.nl/mees/"));
@ -346,6 +461,14 @@ void setup() {
} }
} }
// 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 // Initialize BMP280 pressure sensor
Serial.print(F("Pressure sensor BMP280 ")); Serial.print(F("Pressure sensor BMP280 "));
if (bmp280.initialize()) if (bmp280.initialize())
@ -467,11 +590,22 @@ void loop() {
mb.Ireg (SensorTemperatureIreg, MeasuredData.Temperature); mb.Ireg (SensorTemperatureIreg, MeasuredData.Temperature);
mb.Ireg (SensorHumidityIreg, MeasuredData.Humidity); mb.Ireg (SensorHumidityIreg, MeasuredData.Humidity);
mb.Ireg (SensorPressureIreg, MeasuredData.Pressure); mb.Ireg (SensorPressureIreg, MeasuredData.Pressure);
mb.Ireg (SensorTemperatureBackupIreg, MeasuredData.TemperatureBackup);
mb.Ireg (SensorLuminosityIreg, MeasuredData.Luminosity);
mb.Ireg (SensorRainfallRawIreg, MeasuredData.RainfallCounter);
mb.Ireg (SensorStatusBitsIreg, MeasuredData.StatusBits);
// Debug wind vane // Debug wind vane
//Serial.print(F("\n Measured ADC: ")); //Serial.print(F("\n Measured ADC: "));
//Serial.print(analogRead(windDirectionPin)); //Serial.print(analogRead(windDirectionPin));
// enable or disable smart heater
if (mb.Coil (HeaterCoil)) {
MeasuredData.StatusBits |= 0x04; // Set bit
} else {
MeasuredData.StatusBits &= 0x0B; // Reset bit
}
digitalWrite(LED_BUILTIN, LOW); // LED as heartbeat digitalWrite(LED_BUILTIN, LOW); // LED as heartbeat

@ -53,6 +53,10 @@ class EpeverChargeController(minimalmodbus.Instrument):
def retriable_read_bit(self, registeraddress, functioncode): def retriable_read_bit(self, registeraddress, functioncode):
return self.read_bit(registeraddress, functioncode) return self.read_bit(registeraddress, functioncode)
@retry(wait_fixed=200, stop_max_attempt_number=5)
def retriable_write_bit(self, registeraddress, data, functioncode):
return self.write_bit(registeraddress, data, functioncode)
#Address range 0x3000 #Address range 0x3000
def get_id(self): def get_id(self):
"""PV array rated voltage""" """PV array rated voltage"""
@ -60,7 +64,7 @@ class EpeverChargeController(minimalmodbus.Instrument):
def get_wind_direction(self): def get_wind_direction(self):
"""PV array rated current""" """PV array rated current"""
return self.retriable_read_register(1, 0, 4) return self.retriable_read_register(1, 1, 4)
def get_wind_speedl(self): def get_wind_speedl(self):
"""PV array rated power (low 16 bits)""" """PV array rated power (low 16 bits)"""
@ -93,3 +97,17 @@ class EpeverChargeController(minimalmodbus.Instrument):
def get_pressure(self): def get_pressure(self):
"""Charging mode: 0x0001 = PWM""" """Charging mode: 0x0001 = PWM"""
return self.retriable_read_register(9, 1, 4) return self.retriable_read_register(9, 1, 4)
def get_temperature_backup(self):
"""Charging mode: 0x0001 = PWM"""
return self.retriable_read_register(13, 2, 4)
def get_status_bits(self):
"""Charging mode: 0x0001 = PWM"""
return self.retriable_read_register(14, 0, 4)
def enable_heater(self):
self.retriable_write_bit(0, 1, 5)
def disable_heater(self):
self.retriable_write_bit(0, 0, 5)

@ -594,7 +594,9 @@ elif dump_file:
dump_all_registers() dump_all_registers()
else: else:
status = 1
print("Enable heater function")
controller.enable_heater()
while (1): while (1):
time.sleep(3) # Sleep for 3 seconds time.sleep(3) # Sleep for 3 seconds
print ("Retrieving all known registers.") print ("Retrieving all known registers.")
@ -608,6 +610,8 @@ else:
rawdat['Temperature'] = controller.get_temperature() rawdat['Temperature'] = controller.get_temperature()
rawdat['Humidity'] = controller.get_humidity() rawdat['Humidity'] = controller.get_humidity()
rawdat['Pressure'] = controller.get_pressure() rawdat['Pressure'] = controller.get_pressure()
rawdat['Temp backup'] = controller.get_temperature_backup()
rawdat['Status bits'] = controller.get_status_bits()
print (json.dumps(rawdat, indent=1, sort_keys=False)) print (json.dumps(rawdat, indent=1, sort_keys=False))

Loading…
Cancel
Save