Skip to content

Commit

Permalink
Initial version.
Browse files Browse the repository at this point in the history
  • Loading branch information
cavedon committed Oct 9, 2016
0 parents commit 8c6e067
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 0 deletions.
123 changes: 123 additions & 0 deletions bin/user/aprs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from datetime import datetime
import os

import weeutil.weeutil
import weewx.engine
import weewx.units


class APRS(weewx.engine.StdService):
def __init__(self, engine, config_dict):
super(APRS, self).__init__(engine, config_dict)
conf = config_dict['APRS']
self._output_filename = conf['output_filename']
self._output_filename_tmp = self._output_filename + '.tmp'
self._include_position = int(conf.get('include_position', 0))
self._symbol_table = conf.get('symbol_table', '/')
self._symbol_code = conf.get('symbol_code', '_')
self._comment = conf.get('comment', '')

self._message_type = '_' # Weather report (no position)
self._time_format = '%m%d%H%M'
self._latitude = None
self._longitude = None
self._wind_direction_marker = 'c'
self._wind_speed_marker = 's'
if self._include_position:
# Position with timestamp (no APRS messaging)
self._message_type = '/'
self._time_format = '%d%H%Mz'
self._latitude = ''.join(weeutil.weeutil.latlon_string(
self.engine.stn_info.latitude_f,
('N', 'S'), 'lat'))
self._longitude = ''.join(weeutil.weeutil.latlon_string(
self.engine.stn_info.longitude_f,
('E', 'W'), 'lon'))
self._wind_direction_marker = ''
self._wind_speed_marker = '/'

self.bind(weewx.NEW_ARCHIVE_RECORD, self._handle_new_archive_record)

def _handle_new_archive_record(self, event):
"""Generate a positionless APRS weather report and write it to a file"""
record = event.record
data = [
self._message_type,
datetime.strftime(
datetime.utcfromtimestamp(record['dateTime']),
self._time_format),
]
if self._include_position:
data.append(self._latitude)
data.append(self._symbol_table)
data.append(self._longitude)
data.append(self._symbol_code)

if record.get('windDir') is not None:
# Wind direction (in degrees)
# Wind from North needs to be reported as 360.
# Wind from 0 means N/A in the APRS standard.
# We need to make sure it does not get to 0, so do not rely on
# the format string rouding, but round to no decimals before
# comparing with 0
wind_dir = int(round(record['windDir'], 0))
if wind_dir <= 0:
wind_dir = 360
data.append('%s%03u' % (self._wind_direction_marker,
wind_dir))
else:
data.append('%s...' % self._wind_direction_marker)

if record.get('wind_average') is not None:
# Sustained one-minute wind speed (in mph)
data.append('%s%03.f' % (self._wind_speed_marker,
record['wind_average']))
else:
data.append('%s...' % self._wind_speed_marker)

if record.get('windGust') is not None:
# Gust (peak wind speed in mph in the last 5 minutes)
data.append('g%03.f' % record['wind_average'])
else:
data.append('g...')

if record.get('outTemp') is not None:
# Temperature (in degrees Fahrenheit)
data.append('t%03.f' % record['outTemp'])
else:
data.append('t...')

if record.get('rainRate') is not None:
# Rainfall (in hundredths of an inch) in the last hour
data.append('r%03.f' % (record['rainRate'] * 100))

if record.get('daily_rain') is not None:
# Rainfall (in hundredths of an inch) since midnight
data.append('P%03.f' % (record['daily_rain'] * 100))

if record.get('outHumidity') is not None:
# Humidity (in %. 00 = 100%)
# We need to make sure it does not get over 99, so do not rely on
# the format string rouding, but round to no decimals before
# comparing with 100
humidity = int(round(record['outHumidity'], 0))
if humidity >= 100:
humidity = 0
data.append('h%02u' % humidity)

if record.get('barometer') is not None:
# Barometric pressure (in tenths of millibars/tenths of hPascal)
barometer = weewx.units.convert(
(record['barometer'], 'inHg', 'group_pressure'),
'mbar')[0] * 10
data.append('b%05.f' % barometer)

if self._comment:
data.append(self._comment)

wxdata = ''.join(data)

# Atomic update of self._output_filename.
with open(self._output_filename_tmp, 'w') as f:
f.write(wxdata)
os.rename(self._output_filename_tmp, self._output_filename)
27 changes: 27 additions & 0 deletions install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from setup import ExtensionInstaller


def loader():
return APRSInstaller()


class APRSInstaller(ExtensionInstaller):
def __init__(self):
super(APRSInstaller, self).__init__(
version='0.1',
name='aprs',
description='Write archive data in APRS positionless format.',
author='Ludovico Cavedon (K6LUD)',
author_email='[email protected]',
process_services='user.aprs.APRS',
config={
'APRS': {
'output_filename': '/dev/shm/aprs.pkt',
'include_position': 0,
'symbol_table': '/',
'symbol_code': '_',
'comment': '',
},
},
files=[('bin/user', ['bin/user/aprs.py'])]
)
19 changes: 19 additions & 0 deletions license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2016 Ludovico Cavedon (K6LUD) <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
43 changes: 43 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
aprs - weewx extension for generating APRS-compliant packets

This weewx[1] extension allows the generation of APRS-compliant packets
containing weather information collected by weewx.
This extension was written for the purpose of easy integration with aprx[2].
When this extension is enabled, weewx will generate a new APRS packet every
StdArchive.archive_interval seconds.

Installation:
wee_extension --install aprs.tar.gz

Configuration:
[APRS]
# The APRS weather packet payload will be written in output_filename
output_filename = /dev/shm/aprs.pkt
# include_position:
# 0: a positionless weather report will be generated (default)
# 1: a weather report with position will be generated
include_position = 1
# In case of a position packet, symbol_table and symbol_code will
# determine what symbol will be used to display the station (e.g. on
# aprs.fi). "/_" is the default and will generate a blue WX icon.
# A positionless packet does not include a symbol specification and
# these two settings will be ignored
symbol_table = /
symbol_code = _
# The string in comment will be appended to the end of the packet.
comment = SBARC U2k WX station

Example of integration in the aprx configuration:
<beacon>
beaconmode both # Send packet via APRS-IS and radio.
cycle-size 5m
beacon srccall N0CALL-1 via WIDE2-1 file "/dev/shm/aprs.pkt"
</beacon>

Note: this configuration has the problem that aprx and weewx are not
syncronized, so aprx may send out the same packet twice or miss one from time
to time.


[1] http://www.weewx.com/
[2] http://thelifeofkenneth.com/aprx/

0 comments on commit 8c6e067

Please sign in to comment.