Skip to content

Instantly share code, notes, and snippets.

@arekinath
Created October 6, 2012 08:49
Show Gist options
  • Save arekinath/3844425 to your computer and use it in GitHub Desktop.
Save arekinath/3844425 to your computer and use it in GitHub Desktop.
Ascot AA2-WS-15 weather station

Ascot AA2-WS-15 weather station

This is an implementation of an SDR receiver to listen for weather updates (temperature + humidity) from an Ascot AA2-WS-15 weather station transmitter, and put it into an rrdtool database. It uses an RTL-SDR and librtlsdr (http://sdr.osmocom.org/trac/wiki/rtl-sdr)

Setup/using etc

  • download everything
  • run 'make'
  • then sh z_create_rrd.sh && ./z_daemon.rb start
  • now set up a cronjob to run z_graph_rrd.sh every minute or so

Encoding/modulation

  • pulse gap modulation

  • pulses are about 1ms long, spacing between them determines the data

  • first gap of a frame is always 4ms (I call this the X bit)

  • then 2ms gap for a 1, and 1ms gap for a 0

  • frames consist of 3 12-bit words

  • first word = preamble

  • second word = 10 * temperature

  • third word = low 7 (?) bits are rel. humidity in percent... high 5 bits always 30?

  • transmission of data occurs once every ~60sec, and consists of 10+ identical frames (to allow for receiver clock drift)

Examples

32.0 / 27%    X  1 0 1 0 1 1 1 0 1 0 0 0  0 0 0 1 0 1 0 0 0 0 0 0  1 1 1 1 0  0 0 1 1 0 1 1
33.0 / 29%    X  1 0 1 0 1 1 1 0 1 0 0 0  0 0 0 1 0 1 0 0 1 0 1 0  1 1 1 1 0  0 0 1 1 1 0 1
33.3 / 29%    X  1 0 1 0 1 1 1 0 1 0 0 0  0 0 0 1 0 1 0 0 1 1 0 1  1 1 1 1 0  0 0 1 1 1 0 1
33.1 / 29%    X  1 0 1 0 1 1 1 0 1 0 0 0  0 0 0 1 0 1 0 0 1 0 1 1  1 1 1 1 0  0 0 1 1 1 0 1
32.7 / 29%    X  1 0 1 0 1 1 1 0 1 0 0 0  0 0 0 1 0 1 0 0 0 1 1 1  1 1 1 1 0  0 0 1 1 1 0 1
weather: weather.c
gcc -O2 -msse2 -ftree-vectorize -I/opt/local/include -L/opt/local/lib -lrtlsdr -lm -std=c99 weather.c -o weather
/*
* weather.c -- receiver for ascot weather station
*
* Copyright (c) 2012, Alex Wilson (arekinath)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* See the standard BSD license disclaimer for details about warranty, liability,
* fitness for purpose, etc.
*/
#include <rtl-sdr.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
int
main(int argc, char *argv[])
{
rtlsdr_dev_t *dev;
int ret;
if (rtlsdr_open(&dev, 0)) {
fprintf(stderr, "failed to open rtlsdr\n");
return 1;
}
/* 433.798 MHz is the channel 1 freq (approximately) */
const int32_t freq = 433798000;
/* tune 100kHz above the target */
if (rtlsdr_set_center_freq(dev, freq - 100000)) {
fprintf(stderr, "failed to set centre\n");
return 1;
}
/* ask for it back so we know the actual centre */
int32_t rfreq = rtlsdr_get_center_freq(dev);
int32_t devn = freq - rfreq;
uint32_t samprate = 1048576;
if (rtlsdr_set_sample_rate(dev, samprate)) {
fprintf(stderr, "failed set sample rate\n");
return 1;
}
samprate = rtlsdr_get_sample_rate(dev);
const uint32_t centre = rtlsdr_get_center_freq(dev);
/* xxx: sort out what tuner gain to use */
rtlsdr_set_tuner_gain(dev, 490);
const int readsize = 1024;
uint8_t *buf = malloc(readsize);
memset(buf, 0, readsize);
/* generate the ~100kHz oscillator to mix the incoming signal with */
double *wave = malloc(sizeof(double)*readsize);
for (int i = 0; i < (readsize / 2); ++i) {
double t = (devn * (double)i) / (double)samprate;
wave[i*2] = cos(2*3.14159*t);
wave[i*2+1] = sin(2*3.14159*t);
}
double *demod = malloc(sizeof(double)*readsize);
double *demod_smooth = malloc(sizeof(double)*readsize);
int lenout;
/* always have to call reset_buffer before reading */
if (rtlsdr_reset_buffer(dev)) {
fprintf(stderr, "failed reset buffer\n");
return 1;
}
/* number of reads we've been 'high' for */
uint64_t highfor = 0;
uint64_t lowfor = 0;
/* was there a pulse before the last change? */
uint8_t pulse = 0;
/* frame state */
uint64_t last_last_frame = 1;
uint64_t last_frame = 1;
uint64_t frame = 0;
uint8_t bitno = 0;
while (1) {
/* fill up the buffer with incoming I/Q samples */
ret = rtlsdr_read_sync(dev, buf, readsize, &lenout);
if (ret || lenout < readsize) {
fprintf(stderr, "short read! (=%d) %d/%d\n", ret, lenout, readsize);
return 1;
}
/* mix with the local oscillator to downconvert it. also rectify at the same time */
for (int i = 0; i < readsize; ++i) {
demod[i] = ((((double)buf[i]) / 256.0) - 0.5) * wave[i];
if (demod[i] < 0) demod[i] = 0;
}
/* now do a moving average filter to smooth the resulting waveform */
for (int i = 0; i < 20; ++i)
demod_smooth[i] = demod[i];
for (int i = 20; i < readsize; ++i)
demod_smooth[i] = (demod[i] + demod[i-2] + demod[i-4] + demod[i-6] + demod[i-8] + demod[i-10] + demod[i-12] + demod[i-14] + demod[i-16] + demod[i-18] + demod[i-20]) / 11;
/* sum the mags of the samples (don't bother squaring, since we rectified it) */
double magsum = 0;
for (int i = 0; i < readsize; i+=2) {
double mag = demod_smooth[i] + demod_smooth[i+1];
magsum += mag * mag;
}
/* xxx: arbitrary constant warning! somewhere from 0.5 - 1.5 seems to work */
if (magsum > 0.7) {
/* this is a 'high' sample, as in during a pulse */
/* is this the first 'high' sample? ie, did we just end a gap? */
if (highfor == 0) {
const uint64_t one64 = 1;
/* zeros take 1-2 samples */
if (pulse && lowfor >= 1 && lowfor <= 2) {
bitno--;
frame &= ~(one64<<bitno);
/* ones take 3-5 samples */
} else if (pulse && lowfor >= 3 && lowfor <= 5) {
bitno--;
frame |= (one64<<bitno);
/* and 6-8 samples is an 'X' */
} else if (pulse && lowfor >= 6 && lowfor <= 8) {
if (last_frame == frame && last_last_frame == frame && bitno == 0) {
const uint64_t mask12 = 0xfff;
const uint64_t mask7 = 0x7f;
uint64_t preamble = (frame >> 24) & mask12;
if (preamble == 0xae8) {
uint64_t temp = (frame >> 12) & mask12;
uint16_t tempwhole = temp / 10;
uint16_t tempfrac = temp % 10;
uint64_t humid = frame & mask7;
printf("N:%hu.%hu:%llu\n", tempwhole, tempfrac, humid);
break;
}
}
last_last_frame = last_frame;
last_frame = frame;
frame = 0;
bitno = 36;
}
pulse = 0;
lowfor = 0;
}
/* increment the counter for high samples in a row */
highfor++;
} else {
/* this is a low sample */
/* is this the first? did we just end a pulse? */
if (lowfor == 0) {
if (highfor >= 1 && highfor <= 3) {
pulse = 1;
} else {
pulse = 0;
}
highfor = 0;
}
lowfor++;
}
}
/* xxx: todo: cleanup other stuff */
rtlsdr_close(dev);
}
rrdtool create weather.rrd -s 60 \
DS:temperature:GAUGE:300:-10:60 \
DS:humidity:GAUGE:300:0:100 \
RRA:AVERAGE:0.5:1:1440 \
RRA:AVERAGE:0.5:5:2016 \
RRA:AVERAGE:0.5:30:1488 \
RRA:AVERAGE:0.5:240:2190 \
RRA:MIN:0.5:1:1440 \
RRA:MIN:0.5:5:2016 \
RRA:MIN:0.5:30:1488 \
RRA:MAX:0.5:1:1440 \
RRA:MAX:0.5:5:2016 \
RRA:MAX:0.5:30:1488 \
RRA:LAST:0.5:1:1440 \
RRA:LAST:0.5:5:2016 \
RRA:LAST:0.5:30:1488
#!/usr/bin/env ruby
require 'daemons'
Daemons.run_proc('weatherd') do
Dir.chdir("/home/alex/weather")
loop do
data = `./weather`
`rrdtool update weather.rrd #{data}`
sleep(54)
end
end
#!/bin/bash
cd /home/alex/weather
function tempgraph {
rrdtool graph htdocs/temperature-$1.png \
-s $2 \
-t "Temperature ($1)" \
--vertical-label="degrees C" \
-w 400 -h 150 \
DEF:temp=weather.rrd:temperature:AVERAGE \
LINE4:temp#dd4444 \
AREA:temp#eebbbb:temperature \
GPRINT:temp:LAST:" Now\:%4.1lf %s" \
GPRINT:temp:MIN:" Min\:%4.1lf %s" \
GPRINT:temp:MAX:" Max\:%4.1lf %s" \
GPRINT:temp:AVERAGE:" Avg\:%4.1lf\n"
}
tempgraph 12hr -12h
tempgraph 2day -2d
tempgraph 1week -1w
tempgraph 1month -31d
function humgraph {
rrdtool graph htdocs/humidity-$1.png \
-s $2 \
-t "Rel. humidity ($1)" \
--vertical-label="percent rel humidity" \
-w 400 -h 150 \
DEF:hum=weather.rrd:humidity:AVERAGE \
LINE4:hum#4444dd \
AREA:hum#bbbbee:humidity \
GPRINT:hum:LAST:" Now\:%4.0lf%% %s" \
GPRINT:hum:MIN:" Min\:%4.0lf%% %s" \
GPRINT:hum:MAX:" Max\:%4.0lf%% %s" \
GPRINT:hum:AVERAGE:" Avg\:%4.0lf%%\n"
}
humgraph 12hr -12h
humgraph 2day -2d
humgraph 1week -1w
humgraph 1month -31d
function dpgraph {
A="6.1121"
B="18.678"
C="257.14"
D="234.5"
rrdtool graph htdocs/dewpoint-$1.png \
-s $2 \
-t "Dew point ($1)" \
--vertical-label="degrees C" \
-w 400 -h 150 \
DEF:T=weather.rrd:temperature:AVERAGE \
DEF:RH=weather.rrd:humidity:AVERAGE \
CDEF:gamma=T,$C,T,+,/,$B,T,$D,/,-,*,EXP,RH,100,/,*,LOG \
CDEF:Tdp=$C,gamma,*,$B,gamma,-,/ \
LINE4:Tdp#44dd44 \
AREA:Tdp#bbeebb:"dew point" \
GPRINT:Tdp:LAST:" Now\:%4.1lf %s" \
GPRINT:Tdp:MIN:" Min\:%4.1lf %s" \
GPRINT:Tdp:MAX:" Max\:%4.1lf %s" \
GPRINT:Tdp:AVERAGE:" Avg\:%4.1lf\n"
}
dpgraph 12hr -12h
dpgraph 2day -2d
dpgraph 1week -1w
dpgraph 1month -31d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment