DIY Battery/Solar Powered Zigbee weather station

Hi all,

first of all, this is a variation of DIY Zigbee weather station which is a zigbee weather station but requiring a power supply. I needed it to be battery powered.

At the Core its using a CC2530 Zigbee Module and the configurable PTVO firmware.

This one requires the premium version of the ptvo firmware In order to be able to sleep most of the time in power saving mode, I had to use an external counter module (PCF8583) for both the rain sensor and the wind speed sensor. I soldered up a small PCB, using two of these plus simple RC debounce filters and pull up resistors for the reed switches for wind and rain. The entire setup is powered using a 3.7V LiIon cell which is connected using a buck converter (it needs to be a 3.3V Step-Up-Down converter). The Cell is recharged using a small Solar Panel and a charge converter.


All four I2C devices are using a shared SCL pin. on the right hand side you can see the pins used on the CC2530 board and heres the corresponding PTVO config:

Heres a List of Parts:
Zigbee Module
Buck Converter
BME280
18650 battery holder
Solar charge controller
Solar Panel (5V 230mA)
Counter module
INA219 volatge/current meter
Anemometer (Wind Speed)
Rain Gauge
Housing
Additionally some resistors, wires, capacitors and dupont connectors.
Total should be around 55 to 60 Euros or USD.
Here’s two pictures of the weather station, including some 3D printed brackets:
(Yes the wiring looks messy…)


2 Likes

In order for this to work, one has to give the device a unique name under the expert tab in the PTVO configurator and save a custom converter. This is added to Zigbee2Mqtt without modifications. Place it in config/zigbee2mqtt and add it via the z2m GUI (Settings → External converters).

Since its using counter modules which accumulate, I used the statistics module to create the required sensors. Heres the Yaml config for that:
(in my case the friendly name is “Wetterstation”, may differ for you)

#########################################################
#                                                       #
#      BME280 DATA                                      #
#                                                       #
######################################################### 

  - platform: template
    sensors:
      wetterstation_abs_luftfeuchtigkeit:
        value_template: >-
          {% set h, t = states('sensor.wetterstation_humidity_l1') | float, states('sensor.wetterstation_temperature_l1') %}
          {% if not h or t == 'unknown' -%}
            'unknown'
          {%- else %}
            {% set t = t | float %}
            {{ (h*6.112*2.1674*e**((t*17.67)/(t+243.5))/(t+273.15))|round(1) }}
          {% endif %}
        unit_of_measurement: g/m³
        friendly_name: Wetterstation Absolute Luftfeuchtigkeit

  - platform: template
    sensors:
      wetterstation_taupunkt:
        friendly_name: "Wetterstation Taupunkt"
        value_template: >-
          {{ (log( states('sensor.wetterstation_humidity_l1')|int / 100 ) + 18.678 * states('sensor.wetterstation_temperature_l1')|float / (257.14 + states('sensor.wetterstation_temperature_l1')|float ) )| round (1) }}  
        unit_of_measurement: °C

#########################################################
#                                                       #
#      RAIN METER                                       #
#                                                       #
#########################################################  

  - platform: statistics
    name: "Rain Clicks 24 hours"
    entity_id: sensor.wetterstation_l3
    state_characteristic: sum_differences_nonnegative
    max_age:
      hours: 24

  - platform: statistics
    name: "last rain clicks"
    entity_id: sensor.wetterstation_l3
    state_characteristic: sum_differences_nonnegative
    sampling_size: 2
    
  - platform: statistics
    name: "last rain click age"
    entity_id: sensor.wetterstation_l3
    state_characteristic: datetime_newest
    sampling_size: 2

  - platform: template
    sensors:
      rainfall_now:
        value_template: >-
          {%if states('sensor.last_rain_clicks') == 'unknown' %}
            0.0
          {% else %}
            {% if as_timestamp(now()) - as_timestamp(states('sensor.last_rain_click_age')) > 65 %}
              0.0
            {% else %}
              {{ states('sensor.last_rain_clicks') }}
            {% endif %}
          {% endif %}
    
  - platform: template
    sensors:
      rainfall_24h:
        friendly_name: Regen 24 Stunden
        unit_of_measurement: mm
        value_template: >-
          {%if states('sensor.rain_clicks_24_hours') == 'unknown' %}
            0.0
          {% else %}
            {% set count = states('sensor.rain_clicks_24_hours') | int %}
            {% set mm_per_pulse = 0.30303 %}
            {% set mm = count * mm_per_pulse %}
            {{ mm|round(1, 'floor') }}
          {% endif %}
          
#########################################################
#                                                       #
#      ANEMOMETER                                       #
#                                                       #
#########################################################
  - platform: statistics
    name: "last wind clicks"
    entity_id: sensor.wetterstation_l4
    state_characteristic: sum_differences_nonnegative
    sampling_size: 2

  - platform: statistics
    name: "last wind click age"
    entity_id: sensor.wetterstation_l4
    state_characteristic: datetime_newest
    sampling_size: 2

  - platform: template
    sensors:
      wind_now:
        value_template: >-
          {%if states('sensor.last_wind_clicks') == 'unknown' %}
            0.0
          {% else %}
            {% if as_timestamp(now()) - as_timestamp(states('sensor.last_wind_click_age')) > 65 %}
              0.0
            {% else %}
              {{ states('sensor.last_wind_clicks') }}
            {% endif %}
          {% endif %}

  - platform: template
    sensors:
      windspeed:
        friendly_name: Windgeschwindigkeit
        unit_of_measurement: m/s
        # m_per_pulse = (r * 2 * Pi)/2 = (0.09 * 2 * 3.14)/2 = 0.2862 m
        value_template: >-
          {% set count = states('sensor.wind_now') | int %}
          {% set m_per_pulse = 0.2826 %} 
          {% set mps = count * m_per_pulse / 60  %}
          {{ mps|round(1) }}           

  - platform: template
    sensors:
      windforce:
        friendly_name: Windstärke
        icon_template: " mdi:weather-windy"
        value_template: "{% set wind = (states('sensor.windspeed')|float)*3.6 %}
        {% set wind_round = wind|round(0) %}
        {% if wind <= 1 %}Bft: 0, Windstill, {{wind_round}} km/h
        {% elif wind <= 5 %}Bft: 1, leiser Zug, {{wind_round}} km/h
        {% elif wind <= 11 %}Bft: 2, leichte Briese, {{wind_round}} km/h
        {% elif wind <= 20 %}Bft: 3, schwache Briese, {{wind_round}} km/h
        {% elif wind <= 28 %}Bft: 4, mäßige Briese, {{wind_round}} km/h
        {% elif wind <= 39 %}Bft: 5, frische Briese, {{wind_round}} km/h
        {% elif wind <= 50 %}Bft: 6, starker Wind, {{wind_round}} km/h
        {% elif wind <= 62 %}Bft: 7, steifer Wind, {{wind_round}} km/h
        {% elif wind <= 75 %}Bft: 8, stürmischer Wind, {{wind_round}} km/h
        {% elif wind <= 89 %}Bft: 9, Sturm, {{wind_round}} km/h
        {% elif wind <= 103 %}Bft: 10, schwerer Sturm, {{wind_round}} km/h
        {% elif wind <= 117 %}Bft: 11, orkanartiger Sturm, {{wind_round}} km/h
        {% else %} > 117 %}Bft: 12, Orkan, {{wind_round}} km/h
        {%- endif %}"

  - platform: template
    sensors:
      beaufort: 
        friendly_name: Beaufort
        value_template: "{% set wind = states('sensor.windgeschwindigkeit')|float %}
        {% if wind <= 0.3 %}0
        {% elif wind <= 1.5 %}1
        {% elif wind <= 3.3 %}2
        {% elif wind <= 5.4 %}3
        {% elif wind <= 7.9 %}4
        {% elif wind <= 10.7 %}5
        {% elif wind <= 13.8 %}6
        {% elif wind <= 17.1 %}7
        {% elif wind <= 20.7 %}8
        {% elif wind <= 24.4 %}9
        {% elif wind <= 28.4 %}10
        {% elif wind <= 32.6 %}11
        {% else %} > 32.6 %}12
        {%- endif %}"
        entity_picture_template: "
        {% set state = states('sensor.beaufort') %}
        {% set path = '/local/beaufort/' %}
        {% set ext = '.jpg'%}
        {{[path,state,ext]|join('')|lower}}"        
            
#########################################################
#                                                       #
#            END OF CONFIGURATION FILE                  #
#                                                       #
#########################################################

Also under the PTVO expert tab, I set the reporting interval to 60s which is rather short, but I want to react to rain or strong winds by closing an awning. If you change the reporting interval, the following line needs to be adjusted:
{% set mps = count * m_per_pulse / 60 %}

In my Case I had to enable some of the used sensors in mqtt first.
And here’s the final result in HA:
Screenshot 2024-04-16 223435

Credit to @Doublet who created the original version.

1 Like

Nice work, could you please show how is the INA 219 connected?

Ups slight mistake in the schematics. Its sitting in between the battery and the DC DC Buck Converter so the converter is not directly connected to the battery but via the INA219 instead.


Also I noticed I don’t really need it. Even with moderate sunlight, the battery is sitting at full charge pretty much all the time. Maybe I’ll reduce the reporting interval to better catch wind gusts.