Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
# rtldavis

## rtldavis
## About this Repository

This fork (mdickers47/rtldavis) is a fork of
[http://github.com/lheijst/rtldavis]. The way the lheijst and bemasher
versions work is that they implement the Davis frequency hopping,
demodulate the packets, and dump them to the log file as hex bytes.
Something else has to decode the messages to create weather data.
Luc wrote [https://github.com/lheijst/weewx-rtldavis](weewx-rtldavis)
which is in Python and operates as a weewx driver to update a weewx
database.

My change is that I have adapted the packet parsing code from
weewx-rtldavis into the go binary. You can now write the decoded weather
data to stdout with the option `-g -`. It will be written as one data
point per line, where each data point is three columns, which are metric
name, value, and UNIX timestamp:

### About this Repository
```
wx.davis.windspeed_raw 0 1703133526
wx.davis.winddir 143.74 1703133526
wx.davis.windspeed 0.00 1703133526
wx.davis.temp 50.20 1703133526
```

This is the Graphite/Carbon "line receiver" format. If you specify
`-g server:udpport`, this rtldavis binary will send the data directly
to Graphite via UDP packets.

*Beware* that I have only implemented the parsing for the Vantage Vue
ISS sensor package with the 0.2 inch rain bucket, because that is what
I have. If the `-g` option is used with a different weather station,
it will be wrong.

- 2023-12-19 M. Dickerson

From here on is the README from lheijst/rtldavis.

This repository is a fork of [https://github.com/bemasher/rtldavis](https://github.com/bemasher/rtldavis) for use with custom receiver modules. It has been modified in numerous ways.
1) Added EU frequencies
Expand Down
54 changes: 50 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,19 @@
package main

import (
"bytes"
"flag"
"io"
"log"
"math/rand"
"net"
"os"
"os/signal"
"time"
"strconv"
"fmt"

"github.com/lheijst/rtldavis/protocol"
"github.com/mdickers47/rtldavis/protocol"
"github.com/jpoirier/gortlsdr"
)
const maxTr = 8
Expand All @@ -60,7 +62,8 @@ var (
verbose *bool // -v = emit verbose debug messages
disableAfc *bool // -noafc = disable any automatic corrections
deviceString *string // -d = device serial number or device index

graphiteSrv *string // -gs = decode the packets and send to graphite
graphitePrefix *string // -gp = prefix for graphite metrics
// general
actChan [maxTr]int // list with actual channels (0-7);
// next values are zero (non-meaning)
Expand Down Expand Up @@ -120,7 +123,7 @@ var (


func init() {
VERSION := "0.15"
VERSION := "0.15.2md"
var (
tr int
mask int
Expand Down Expand Up @@ -148,6 +151,8 @@ var (
verbose = flag.Bool("v", false, "emit verbose debug messages")
disableAfc = flag.Bool("noafc", false, "disable any AFC")
deviceString = flag.String("d","0","device serial number or device index")
graphiteSrv = flag.String("gs", "", "decode packets and send to graphite server")
graphitePrefix = flag.String("gp", "wx.davis.", "prefix for graphite metrics")

flag.Parse()
protocol.Verbose = *verbose
Expand Down Expand Up @@ -295,6 +300,15 @@ func main() {
}
}()

var graphiteChan chan []string
if *graphiteSrv != "" {
graphiteChan = make(chan []string)
go sendToGraphite(*graphiteSrv, *graphitePrefix, graphiteChan)
defer close(graphiteChan)
} else {
graphiteSrv = nil
}

defer func() {
in.Close()
out.Close()
Expand Down Expand Up @@ -430,7 +444,9 @@ func main() {
if *undefined {
log.Printf("%02X %d %d %d %d %d msg.ID=%d undefined:%d",
msg.Data, chTotMsgs[0], chTotMsgs[1], chTotMsgs[2], chTotMsgs[3], totInit, msg.ID, idUndefs)
} else {
} else if graphiteSrv != nil {
graphiteChan <- protocol.DecodeMsg(msg)
} else {
log.Printf("%02X %d %d %d %d %d msg.ID=%d",
msg.Data, chTotMsgs[0], chTotMsgs[1], chTotMsgs[2], chTotMsgs[3], totInit, msg.ID)
}
Expand Down Expand Up @@ -489,3 +505,33 @@ func Min(values []int64) (ptr int) {
}
return ptr
}

func sendToGraphite(server string, prefix string, graphiteChan chan[]string) {
var conn io.Writer
var err error
if server == "-" {
conn = os.Stdout
} else {
conn, err = net.Dial("udp", server)
if err != nil {
log.Printf("failed to open %s: %s", server, err)
os.Exit(1)
return
}
}
log.Printf("connected to %s for graphite output", server)
for msgs := range graphiteChan {
/* note that every call to conn.write generates a udp packet; the
least we can do is bundle the messages that all arrive
together */
var b bytes.Buffer
for _, line := range msgs {
fmt.Fprintf(&b, "%s%s %d\n", prefix, line, time.Now().Unix())
}
_, err = b.WriteTo(conn)
if err != nil {
log.Printf("failed to write to %s: %s", server, err)
os.Exit(1)
}
}
}
243 changes: 243 additions & 0 deletions protocol/decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
This module decodes the binary data packets into weather messages.
Every packet carries one byte for wind speed, one byte for wind
direction, and one other sensor reading of 10 or more bits, labeled by
a message identifier.

The code is adapted from the weewx driver named weewx-rtldavis,
written by Luc Heijst (also the author of the upstream fork of
rtldavis):

https://github.com/lheijst/weewx-rtldavis

If a bug here is also present in the weewx-rtldavis reference code,
it should be fixed in both places. If it is only in this code,
then it was introduced by me when translating from Python to Go.

2023-12-19 M. Dickerson <pomonamikey@gmail.com>
*/

package protocol

import (
"fmt"
"log"
)

func DecodeMsg(m Message) (obs []string) {
/* sensor messages arrive with 'channel' numbers, which has no
relation to a go chan or an RF frequency. we only understand the
ISS ('integrated sensor suite') channel 0, used by Vantage Vue. */
if m.ID != 0 {
log.Printf("received message for unsupported channel %d", m.ID)
return
}

windspeed_raw := m.Data[1]
obs = append(obs, fmt.Sprintf("windspeed_raw %d", windspeed_raw))

winddir_vue := float64(m.Data[2])*1.40625 + 0.3
obs = append(obs, fmt.Sprintf("winddir %.2f", winddir_vue))

/* apply the error correction table that might not even be for the
Vantage Vue; it's unclear */
winddir := CorrectWindspeed(m.Data[1], m.Data[2])
obs = append(obs, fmt.Sprintf("windspeed %.2f", winddir))

msg_type := (m.Data[0] >> 4) & 0x0F
/* most of the time we will use the 10-bit number in this weird place */
raw := ((int16(m.Data[3]) << 2) + int16(m.Data[4])>>6) & 0x03FF
switch msg_type {
case 0x02:
/* supercap voltage */
obs = append(obs, fmt.Sprintf("supercap_v_raw %d", raw))
if raw != 0x03FF {
obs = append(obs, fmt.Sprintf("supercap_v %0.2f", float32(raw)/300.0))
}
case 0x04:
/* UV radiation */
obs = append(obs, fmt.Sprintf("uv_raw %d", raw))
if raw != 0x03FF {
obs = append(obs, fmt.Sprintf("uv %0.2f", raw/50.0))
}
case 0x05:
/* rain rate */
time_between_tips_raw := ((int16(m.Data[4]) & 0x30) << 4) + int16(m.Data[3])
rain_rate := 0.0
if time_between_tips_raw == 0x03FF {
rain_rate = 0.0 /* time between tips is infinity => no rain */
} else if m.Data[4]&0x40 == 0 {
/* "heavy rain", time-between-tips is scaled up by 4 bits */
/* TODO: we are assuming the 0.01 inch (=0.254mm) size bucket */
rain_rate = 3600.0 / (float64(time_between_tips_raw) / 16.0) * 0.254
} else {
/* "light rain" formula */
rain_rate = 3600.0 / float64(time_between_tips_raw) * 0.254
}
obs = append(obs, fmt.Sprintf("rain_rate_mmh %.2f", rain_rate))
case 0x06:
/* solar radiation */
if raw < 0x03fE {
sr := float64(raw) * 1.757936
obs = append(obs, fmt.Sprintf("solar_radiation %.2f", sr))
}
case 0x07:
/* solar panel output */
if raw != 0x03FF {
obs = append(obs, fmt.Sprintf("solar_panel_v %.2f", float32(raw)/300.0))
}
case 0x08:
/* temperature */
raw = (int16(m.Data[3]) << 4) + (int16(m.Data[4]) >> 4)
if raw != 0x0FFC {
if m.Data[4]&0x08 != 0 {
/* digital sensor */
obs = append(obs, fmt.Sprintf("temp %.2f", float32(raw)/10.0))
} else {
/* TODO: thermistor */
log.Printf("can't interpret analog temperature sensor reading %d", raw)
obs = append(obs, fmt.Sprintf("temp_raw %d", raw))
}
}
case 0x09:
/* 10-min average wind gust */
gust_raw := m.Data[3]
gust_index := m.Data[5] >> 4 /* ??? */
obs = append(obs, fmt.Sprintf("gust_mph %d", gust_raw))
obs = append(obs, fmt.Sprintf("gust_index %d", gust_index))
case 0x0a:
/* humidity */
raw = (int16(m.Data[4]>>4) << 8) + int16(m.Data[3])
if raw != 0 {
if m.Data[4]&0x08 != 0 {
/* digital sensor */
obs = append(obs, fmt.Sprintf("humidity %.2f", float32(raw)/10.0))
} else {
/* TODO: two other types of digital sensor and one analog */
log.Printf("can't interpret humidity sensor reading %d", raw)
obs = append(obs, fmt.Sprintf("humidity_raw %d", raw))
}
}
case 0x0e:
/* rain */
raw = int16(m.Data[3]) /* "rain count raw"?? */
/* ignore the high bit because apparently some counters wrap at
128 and others at 256, and it doesn't matter */
if raw != 0x80 {
raw &= 0x7f
obs = append(obs, fmt.Sprintf("rain_count %d", raw))
}
default:
log.Printf("Unknown data packet type 0x%02x: %02x", m.ID, m.Data)
}
return
}

/* this table is from github.com/lheijst/weewx-rtldavis. It says the
offsets were determined by feeding constructed packets to a Davis
Envoy and reading the serial output packets.

Columns are wind direction, rows are windspeed.
*/

var wind_offsets = [55][35]int16{
{0, 1, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 127, 128},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
{4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0},
{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0},
{6, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0},
{7, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 0, 0},
{8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 0, 0},
{9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 0, 0},
{10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0},
{11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0},
{12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0},
{13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 1, 0, 0},
{14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 1, 0, 0},
{15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 1, 0, 0},
{16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 1, 0, 0},
{17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 1, 0, 0},
{18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 1, 0, 0},
{19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 4, 1, 0, 0},
{20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 4, 2, 0, 0},
{21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 4, 2, 0, 0},
{22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 3, 4, 4, 2, 0, 0},
{23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 3, 4, 4, 2, 0, 0},
{24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 2, 0, 0},
{25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 3, 4, 4, 2, 0, 0},
{26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 3, 5, 4, 2, 0, 0},
{27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 3, 5, 5, 2, 0, 0},
{28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 3, 5, 5, 2, 0, 0},
{29, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 3, 5, 5, 2, 0, 0},
{30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 3, 5, 5, 2, 0, 0},
{35, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 4, 6, 5, 2, 0, -1},
{40, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 4, 6, 6, 2, 0, -1},
{45, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 4, 7, 6, 2, -1, -1},
{50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 5, 7, 7, 2, -1, -2},
{55, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 5, 8, 7, 2, -1, -2},
{60, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 5, 8, 8, 2, -1, -2},
{65, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 5, 9, 8, 2, -2, -3},
{70, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 2, 5, 9, 9, 2, -2, -3},
{75, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 2, 6, 10, 9, 2, -2, -3},
{80, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 2, 6, 10, 10, 2, -2, -3},
{85, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 7, 11, 11, 2, -3, -4},
{90, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 7, 12, 11, 2, -3, -4},
{95, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 2, 2, 2, 1, 1, 1, 1, 2, 7, 12, 12, 3, -3, -4},
{100, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 2, 1, 1, 1, 1, 2, 8, 13, 12, 3, -3, -4},
{105, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 2, 8, 13, 13, 3, -3, -4},
{110, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 2, 8, 14, 14, 3, -3, -5},
{115, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 2, 9, 15, 14, 3, -3, -5},
{120, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 3, 9, 15, 15, 3, -4, -5},
{125, 1, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 1, 1, 1, 3, 10, 16, 16, 3, -4, -5},
{130, 1, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 3, 10, 17, 16, 3, -4, -6},
{135, 1, 2, 2, 1, 1, 0, 0, 0, -1, 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4, 3, 3, 2, 2, 2, 1, 1, 3, 10, 17, 17, 4, -4, -6},
{140, 1, 2, 2, 1, 1, 0, 0, 0, -1, 0, 0, 1, 1, 2, 2, 3, 3, 3, 4, 4, 3, 3, 2, 2, 2, 1, 1, 3, 11, 18, 17, 4, -4, -6},
{145, 2, 2, 2, 1, 1, 0, 0, 0, -1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 3, 2, 2, 1, 1, 3, 11, 19, 18, 4, -4, -6},
{150, 2, 2, 2, 1, 1, 0, 0, -1, -1, 0, 0, 1, 1, 2, 3, 3, 4, 4, 4, 4, 4, 3, 3, 2, 2, 1, 1, 3, 12, 19, 19, 4, -4, -6},
}

func CorrectWindspeed(s byte, d byte) float32 {
/* speed is mph, direction is degrees where 0 = north */

/* table is treated as having east-west symmetry */
if d > 127 {
d = byte(256 - int16(d))
}

/* on each axis, find the first term greater than x */
row, col := 1, 1
for wind_offsets[row][0] <= int16(s) && row < 54 {
row++
}
for wind_offsets[0][col] <= int16(d) && col < 34 {
col++
}

corr := float32(wind_offsets[row][col])
var delta_s, delta_d float32

/* we are talking about fractions of a mph at this point, but
nevertheless we soldier on and do a bilinear interpretation to
approximate the corrections where the table does not have an
exact match */

if col > 1 && wind_offsets[0][col] != int16(d) {
d_selected := float32(wind_offsets[0][col])
d_prev := float32(wind_offsets[0][col-1])
corr_prev := float32(wind_offsets[row][col-1])
delta_d = (corr_prev - corr) +
(float32(d)-d_prev)/(d_selected-d_prev)*(corr-corr_prev)
}

if row > 1 && wind_offsets[row][0] != int16(s) {
s_selected := float32(wind_offsets[row][0])
s_prev := float32(wind_offsets[row-1][0])
corr_prev := float32(wind_offsets[row-1][col])
delta_s = (corr_prev - corr) +
(float32(s)-s_prev)/(s_selected-s_prev)*(corr-corr_prev)
}

return float32(s) + corr + delta_d + delta_s
}
Loading