-
-
Save jdanders/56e5e49c185adec4020f2603e4eb8342 to your computer and use it in GitHub Desktop.
Upload data from Home Assistant to the CWOP.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #You should use https://github.com/lancer73/ha-weather-uploader instead | |
| blueprint: | |
| name: 'CWOP Upload' | |
| description: > | |
| Periodically uploads weather station data from Home Assistant to CWOP | |
| (Citizen Weather Observer Program) via APRS-IS, whenever any of the | |
| configured trigger entities change state. | |
| Setup: | |
| Add the following shell_command to configuration.yaml. This pipes the | |
| generated APRS packet to `nc`, which opens a TCP connection to APRS-IS, | |
| sends the login + weather packet, and closes. | |
| shell_command: | |
| cwop_upload: >- | |
| printf "%b" "{{ packet }}" | nc -w 10 {{ host }} {{ port }} | |
| Notes on sensor units: | |
| - Temperature sensors: °F or °C (auto-converted to °F for APRS). | |
| - Wind speed / gust sensors: mph or km/h (auto-converted to mph). | |
| - Rain sensors (hourly/24hr/daily): inches or mm (auto-converted to | |
| hundredths of an inch, per APRS spec). | |
| - Pressure sensor: mbar/hPa or any other unit assumed to be inHg | |
| (auto-converted to tenths of a millibar). | |
| - Luminosity sensor: W/m² or lux (auto-converted to W/m²; values | |
| ≥1000 W/m² use the APRS 'l' overflow field per spec). | |
| - Any sensor left blank is reported as APRS "not available" (dots). | |
| domain: automation | |
| source_url: 'https://gist.github.com/jdanders/56e5e49c185adec4020f2603e4eb8342#file-cwop_upload-yaml' | |
| input: | |
| host: | |
| name: 'APRS Internet Service (APRS-IS) Host' | |
| selector: | |
| text: | |
| default: 'cwop.aprs.net' | |
| port: | |
| name: 'Port (DO NOT CHANGE)' | |
| description: 'The standard APRS-IS full-feed port. Only change this if you know what you are doing.' | |
| selector: | |
| text: | |
| default: '14580' | |
| callsign: | |
| name: 'CWOP Callsign' | |
| description: 'Your registered CWOP station ID, e.g. CW1234.' | |
| selector: | |
| text: | |
| default: '' | |
| station: | |
| name: 'Station Type' | |
| description: '2–4 character code identifying the type of weather station hardware.' | |
| selector: | |
| text: | |
| default: '' | |
| lat: | |
| name: 'Station Latitude' | |
| description: 'APRS-compliant format, e.g. 4903.50N.' | |
| selector: | |
| text: | |
| default: '' | |
| lon: | |
| name: 'Station Longitude' | |
| description: 'APRS-compliant format, e.g. 07201.75W.' | |
| selector: | |
| text: | |
| default: '' | |
| trigger_entities: | |
| name: 'Trigger Entities' | |
| description: 'Entities to watch for state changes; any change triggers an upload (subject to the delay below).' | |
| selector: | |
| entity: | |
| multiple: true | |
| default: '' | |
| delay: | |
| name: 'Delay After Uploading' | |
| description: 'Minimum time to wait after an upload before another can be triggered, to avoid flooding APRS-IS.' | |
| selector: | |
| number: | |
| min: 0 | |
| max: 300 | |
| step: 1 | |
| unit_of_measurement: seconds | |
| mode: slider | |
| default: 10 | |
| sensor_winddir: | |
| name: 'Wind Direction Sensor' | |
| description: 'Must report direction in degrees (0–360).' | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: wind_direction | |
| default: '' | |
| sensor_windspeed: | |
| name: 'Wind Speed Sensor' | |
| description: 'Accepts mph or km/h.' | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: wind_speed | |
| default: '' | |
| sensor_windgust: | |
| name: 'Wind Gust Sensor' | |
| description: 'Accepts mph or km/h.' | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: wind_speed | |
| default: '' | |
| sensor_temp: | |
| name: 'Temperature Sensor' | |
| description: 'Accepts °F or °C.' | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: temperature | |
| default: '' | |
| sensor_hrrain: | |
| name: 'Rainfall Last Hour Sensor' | |
| description: 'Accepts inches or mm.' | |
| selector: | |
| entity: | |
| domain: sensor | |
| default: '' | |
| sensor_twentyfourhrrain: | |
| name: 'Rainfall Last 24 Hours Sensor' | |
| description: 'Accepts inches or mm.' | |
| selector: | |
| entity: | |
| domain: sensor | |
| default: '' | |
| sensor_dayrain: | |
| name: 'Rainfall Since Midnight Sensor' | |
| description: 'Accepts inches or mm.' | |
| selector: | |
| entity: | |
| domain: sensor | |
| default: '' | |
| sensor_humidity: | |
| name: 'Humidity Sensor' | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: humidity | |
| default: '' | |
| sensor_pressure: | |
| name: 'Barometric Pressure Sensor' | |
| description: 'Accepts mbar/hPa; any other unit is assumed to be inHg.' | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: | |
| - pressure | |
| - atmospheric_pressure | |
| default: '' | |
| sensor_luminosity: | |
| name: 'Luminosity Sensor' | |
| description: 'Accepts W/m² or lux.' | |
| selector: | |
| entity: | |
| domain: sensor | |
| device_class: | |
| - irradiance | |
| - illuminance | |
| default: '' | |
| mode: single | |
| max_exceeded: silent | |
| trigger: | |
| - platform: state | |
| entity_id: !input trigger_entities | |
| variables: | |
| host: !input host | |
| port: !input port | |
| callsign: !input callsign | |
| station: !input station | |
| lat: !input lat | |
| lon: !input lon | |
| delay: !input delay | |
| sensor_winddir: !input sensor_winddir | |
| sensor_windspeed: !input sensor_windspeed | |
| sensor_windgust: !input sensor_windgust | |
| sensor_temp: !input sensor_temp | |
| sensor_hrrain: !input sensor_hrrain | |
| sensor_twentyfourhrrain: !input sensor_twentyfourhrrain | |
| sensor_dayrain: !input sensor_dayrain | |
| sensor_humidity: !input sensor_humidity | |
| sensor_pressure: !input sensor_pressure | |
| sensor_luminosity: !input sensor_luminosity | |
| # APRS timestamp: HHMMSS in UTC | |
| time: > | |
| {% set utc_time = utcnow() %} | |
| {{ '%02d' | format(utc_time.hour | int) }}{{ '%02d' | format(utc_time.minute | int) }}{{ '%02d' | format(utc_time.second | int) }} | |
| # Wind direction in degrees, zero-padded to 3 digits: _DDD | |
| winddir: > | |
| {% if sensor_winddir == '' %} | |
| _... | |
| {% else %} | |
| {% set value = states(sensor_winddir) %} | |
| _{{ | |
| '...' if value in ('unavailable', 'unknown') | |
| else '%03d' | format(value | float | round | int) | |
| }} | |
| {% endif %} | |
| # Sustained wind speed in mph, converts from km/h: /SSS | |
| windspeed: > | |
| {% if sensor_windspeed == '' %} | |
| /... | |
| {% else %} | |
| {% set value = states(sensor_windspeed) %} | |
| {% set unit_of_measurement = state_attr(sensor_windspeed, 'unit_of_measurement') %} | |
| /{{ | |
| '...' if value in ('unavailable', 'unknown') | |
| else '%03d' | format(((value | float) * (0.6213 if unit_of_measurement == 'km/h' else 1)) | round | int) | |
| }} | |
| {% endif %} | |
| # Wind gust in mph, converts from km/h: gGGG | |
| windgust: > | |
| {% if sensor_windgust == '' %} | |
| g... | |
| {% else %} | |
| {% set value = states(sensor_windgust) %} | |
| {% set unit_of_measurement = state_attr(sensor_windgust, 'unit_of_measurement') %} | |
| g{{ | |
| '...' if value in ('unavailable', 'unknown') | |
| else '%03d' | format(((value | float) * (0.6213 if unit_of_measurement == 'km/h' else 1)) | round | int) | |
| }} | |
| {% endif %} | |
| # Temperature in °F, converts from °C: tTTT | |
| temp: > | |
| {% if sensor_temp == '' %} | |
| t... | |
| {% else %} | |
| {% set value = states(sensor_temp) %} | |
| {% set unit_of_measurement = state_attr(sensor_temp, 'unit_of_measurement') %} | |
| t{{ | |
| '...' if value in ('unavailable', 'unknown') | |
| else '%03d' | format(value | float | round | int) if unit_of_measurement == '°F' | |
| else '%03d' | format((((value | float) * 1.8) + 32) | round | int) | |
| }} | |
| {% endif %} | |
| # Rainfall in the last hour, hundredths of an inch, converts from mm: rRRR | |
| hrrain: > | |
| {% if sensor_hrrain == '' %} | |
| r... | |
| {% else %} | |
| {% set value = states(sensor_hrrain) %} | |
| {% set unit_of_measurement = state_attr(sensor_hrrain, 'unit_of_measurement') %} | |
| r{{ | |
| '...' if value in ('unavailable', 'unknown') | |
| else '%03d' | format((((value | float) / (25.4 if unit_of_measurement == 'mm' else 1)) * 100) | round | int) | |
| }} | |
| {% endif %} | |
| # Rainfall in the last 24 hours, hundredths of an inch, converts from mm: pPPP | |
| twentyfourhrrain: > | |
| {% if sensor_twentyfourhrrain == '' %} | |
| p... | |
| {% else %} | |
| {% set value = states(sensor_twentyfourhrrain) %} | |
| {% set unit_of_measurement = state_attr(sensor_twentyfourhrrain, 'unit_of_measurement') %} | |
| p{{ | |
| '...' if value in ('unavailable', 'unknown') | |
| else '%03d' | format((((value | float) / (25.4 if unit_of_measurement == 'mm' else 1)) * 100) | round | int) | |
| }} | |
| {% endif %} | |
| # Rainfall since midnight, hundredths of an inch, converts from mm: PPPP | |
| dayrain: > | |
| {% if sensor_dayrain == '' %} | |
| P... | |
| {% else %} | |
| {% set value = states(sensor_dayrain) %} | |
| {% set unit_of_measurement = state_attr(sensor_dayrain, 'unit_of_measurement') %} | |
| P{{ | |
| '...' if value in ('unavailable', 'unknown') | |
| else '%03d' | format((((value | float) / (25.4 if unit_of_measurement == 'mm' else 1)) * 100) | round | int) | |
| }} | |
| {% endif %} | |
| # Relative humidity, 2 digits (100% encoded as "00"): hHH | |
| humidity: > | |
| {% if sensor_humidity == '' %} | |
| h.. | |
| {% else %} | |
| {% set value = states(sensor_humidity) %} | |
| {% if value in ('unavailable', 'unknown') %} | |
| h.. | |
| {% else %} | |
| {% set filtered_value = value | float | round | int %} | |
| h{{ | |
| '..' if filtered_value < 1 else '00' if filtered_value == 100 else '%02d' | format(filtered_value) | |
| }} | |
| {% endif %} | |
| {% endif %} | |
| # Barometric pressure, tenths of a millibar, converts from inHg: bBBBBB | |
| pressure: > | |
| {% if sensor_pressure == '' %} | |
| b..... | |
| {% else %} | |
| {% set value = states(sensor_pressure) %} | |
| {% set unit_of_measurement = state_attr(sensor_pressure, 'unit_of_measurement') %} | |
| b{{ | |
| '.....' if value in ('unavailable', 'unknown') | |
| else '%05d' | format(((value | float) * 338.639) | round | int) if unit_of_measurement not in ['mbar', 'hPa'] | |
| else '%05d' | format(((value | float) * 10) | round | int) | |
| }} | |
| {% endif %} | |
| # Solar irradiance in W/m², converts from lux; uses 'l' prefix above 1000: LLLL / lLLL | |
| luminosity: > | |
| {% if sensor_luminosity == '' %} | |
| L... | |
| {% else %} | |
| {% set value = states(sensor_luminosity) %} | |
| {% set unit_of_measurement = state_attr(sensor_luminosity, 'unit_of_measurement') %} | |
| {% if value in ('unavailable', 'unknown') %} | |
| L... | |
| {% else %} | |
| {% set converted_value = ((value | float) / (126.7 if unit_of_measurement == 'lux' else 1)) | round | int %} | |
| {% if converted_value < 1000 %} | |
| L{{ '%03d' | format(converted_value) }} | |
| {% else %} | |
| l{{ '%03d' | format(converted_value - 1000) }} | |
| {% endif %} | |
| {% endif %} | |
| {% endif %} | |
| action: | |
| - alias: 'Upload to CWOP' | |
| if: >- | |
| {{ host != '' and port != '' and callsign != '' and lat != '' and lon != '' }} | |
| then: | |
| - action: shell_command.cwop_upload | |
| data: | |
| host: '{{ host }}' | |
| port: '{{ port }}' | |
| packet: >- | |
| user {{ callsign }} pass -1 vers HomeAssistant\n{{ callsign }}>APRS,TCPIP*:/{{ time }}h{{ lat }}/{{ lon }}{{ winddir }}{{ windspeed }}{{ windgust }}{{ temp }}{{ hrrain }}{{ twentyfourhrrain }}{{ dayrain }}{{ humidity }}{{ pressure }}{{ luminosity }}{{ station }}\n | |
| - alias: 'Wait To Prevent Multiple Uploads' | |
| delay: | |
| seconds: '{{ delay | int }}' |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'd recommend using https://github.com/lancer73/ha-weather-uploader instead.