Add weather service

This commit is contained in:
Elara 2021-12-12 12:43:43 -08:00
parent 522c10a9c0
commit a2ff29ce84
4 changed files with 237 additions and 4 deletions

1
go.mod
View File

@ -3,6 +3,7 @@ module go.arsenm.dev/infinitime
go 1.16 go 1.16
require ( require (
github.com/fxamacker/cbor/v2 v2.3.0
github.com/godbus/dbus/v5 v5.0.3 github.com/godbus/dbus/v5 v5.0.3
github.com/muka/go-bluetooth v0.0.0-20211122080231-b99792bbe62a github.com/muka/go-bluetooth v0.0.0-20211122080231-b99792bbe62a
) )

6
go.sum
View File

@ -3,6 +3,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
github.com/fxamacker/cbor/v2 v2.3.0 h1:aM45YGMctNakddNNAezPxDUpv38j44Abh+hifNuqXik=
github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -24,6 +28,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8= github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@ -4,9 +4,11 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"reflect"
"strings" "strings"
"time" "time"
"github.com/fxamacker/cbor/v2"
bt "github.com/muka/go-bluetooth/api" bt "github.com/muka/go-bluetooth/api"
"github.com/muka/go-bluetooth/bluez/profile/adapter" "github.com/muka/go-bluetooth/bluez/profile/adapter"
"github.com/muka/go-bluetooth/bluez/profile/device" "github.com/muka/go-bluetooth/bluez/profile/device"
@ -27,6 +29,7 @@ const (
HeartRateChar = "00002a37-0000-1000-8000-00805f9b34fb" HeartRateChar = "00002a37-0000-1000-8000-00805f9b34fb"
FSTransferChar = "adaf0200-4669-6c65-5472-616e73666572" FSTransferChar = "adaf0200-4669-6c65-5472-616e73666572"
FSVersionChar = "adaf0100-4669-6c65-5472-616e73666572" FSVersionChar = "adaf0100-4669-6c65-5472-616e73666572"
WeatherDataChar = "00040001-78fc-48fe-8e23-433b3a1942d0"
) )
type Device struct { type Device struct {
@ -42,6 +45,7 @@ type Device struct {
heartRateChar *gatt.GattCharacteristic1 heartRateChar *gatt.GattCharacteristic1
fsVersionChar *gatt.GattCharacteristic1 fsVersionChar *gatt.GattCharacteristic1
fsTransferChar *gatt.GattCharacteristic1 fsTransferChar *gatt.GattCharacteristic1
weatherDataChar *gatt.GattCharacteristic1
notifEventCh chan uint8 notifEventCh chan uint8
notifEventDone bool notifEventDone bool
onReconnect func() onReconnect func()
@ -50,10 +54,11 @@ type Device struct {
} }
var ( var (
ErrNoDevices = errors.New("no InfiniTime devices found") ErrNoDevices = errors.New("no InfiniTime devices found")
ErrNotFound = errors.New("could not find any advertising InfiniTime devices") ErrNotFound = errors.New("could not find any advertising InfiniTime devices")
ErrNotConnected = errors.New("not connected") ErrNotConnected = errors.New("not connected")
ErrCharNotAvail = errors.New("required characteristic is not available") ErrCharNotAvail = errors.New("required characteristic is not available")
ErrNoTimelineHeader = errors.New("events must contain the timeline header")
) )
type Options struct { type Options struct {
@ -333,6 +338,8 @@ func (i *Device) resolveChars() error {
i.fsTransferChar = char i.fsTransferChar = char
case FSVersionChar: case FSVersionChar:
i.fsVersionChar = char i.fsVersionChar = char
case WeatherDataChar:
i.weatherDataChar = char
} }
} }
return nil return nil
@ -685,6 +692,32 @@ func (i *Device) FS() (*blefs.FS, error) {
return blefs.New(i.fsTransferChar) return blefs.New(i.fsTransferChar)
} }
// AddWeatherEvent adds one of the event structs from
// the weather package to the timeline. Input must be
// a struct containing TimelineHeader.
func (i *Device) AddWeatherEvent(event interface{}) error {
if err := i.checkStatus(i.weatherDataChar); err != nil {
return err
}
// Get type of input
inputType := reflect.TypeOf(event)
// Check if input contains TimelineHeader
_, hdrExists := inputType.FieldByName("TimelineHeader")
// If header does not exist or input is not struct
if !hdrExists || inputType.Kind() != reflect.Struct {
return ErrNoTimelineHeader
}
// Encode event as CBOR
data, err := cbor.Marshal(event)
if err != nil {
return err
}
// Write data to weather data characteristic
return i.weatherDataChar.WriteValue(data, nil)
}
func (i *Device) checkStatus(char *gatt.GattCharacteristic1) error { func (i *Device) checkStatus(char *gatt.GattCharacteristic1) error {
if !i.device.Properties.Connected { if !i.device.Properties.Connected {
return ErrNotConnected return ErrNotConnected

193
weather/weather.go Normal file
View File

@ -0,0 +1,193 @@
package weather
import (
"time"
)
// Type of weather event
type EventType uint8
// These event types correspond to structs
// that can be used to add an event to the
// weather timeline
const (
EventTypeObscuration EventType = iota
EventTypePrecipitation
EventTypeWind
EventTypeTemperature
EventTypeAirQuality
EventTypeSpecial
EventTypePressure
EventTypeLocation
EventTypeClouds
EventTypeHumidity
)
// Special event type
type SpecialType uint8
// See https://git.io/JM7Oe for the meaning of each type
const (
SpecialTypeSquall SpecialType = iota
SpecialTypeTsunami
SpecialTypeTornado
SpecialTypeFire
SpecialTypeThunder
)
// Precipitation type
type PrecipitationType uint8
// See https://git.io/JM7YM for the meaning of each type
const (
PrecipitationTypeNone PrecipitationType = iota
PrecipitationTypeRain
PrecipitationTypeDrizzle
PrecipitationTypeFreezingRain
PrecipitationTypeSleet
PrecipitationTypeHail
PrecipitationTypeSmallHail
PrecipitationTypeSnow
PrecipitationTypeSnowGrains
PrecipitationTypeIceCrystals
PrecipitationTypeAsh
)
// Visibility obscuration type
type ObscurationType uint8
// See https://git.io/JM7Yd for the meaning of each type
const (
ObscurationTypeNone ObscurationType = iota
ObscurationTypeFog
ObscurationTypeHaze
ObscurationTypeSmoke
ObscurationTypeAsh
ObscurationTypeDust
ObscurationTypeSand
ObscurationTypeMist
ObscurationTypePrecipitation
)
// TimelineHeader contains the header for a timeline envent
type TimelineHeader struct {
// UNIX timestamp with timezone offset
Timestamp uint64
// Seconds until the event expires
Expires uint32
// Type of weather event
EventType EventType
}
// NewHeader creates and populates a new timeline header
// and returns it
func NewHeader(evtType EventType, expires time.Duration) TimelineHeader {
return TimelineHeader{
Timestamp: uint64(time.Now().Unix()),
Expires: uint32(expires.Seconds()),
EventType: evtType,
}
}
// CloudsEvent corresponds to EventTypeClouds
type CloudsEvent struct {
TimelineHeader
// Cloud coverage percentage
Amount uint8
}
// ObscurationEvent corresponds to EventTypeObscuration
type ObscurationEvent struct {
TimelineHeader
// Type of obscuration
Type ObscurationType
// Visibility in meters. 65535 is unspecified.
Amount uint16
}
// PrecipitationEvent corresponds to EventTypePrecipitation
type PrecipitationEvent struct {
TimelineHeader
// Type of precipitation
Type PrecipitationType
// Amount of rain in millimeters. 255 is unspecified.
Amount uint8
}
// WindEvent corresponds to EventTypeWind
type WindEvent struct {
TimelineHeader
// Minimum speed in meters per second
SpeedMin uint8
// Maximum speed in meters per second
SpeedMax uint8
// Unitless direction, about 1 unit per 0.71 degrees.
DirectionMin uint8
// Unitless direction, about 1 unit per 0.71 degrees
DirectionMax uint8
}
// TemperatureEvent corresponds to EventTypeTemperature
type TemperatureEvent struct {
TimelineHeader
// Temperature in celcius multiplied by 100.
// -32768 is "no data"
Temperature int16
// Dew point in celcius multiplied by 100.
// -32768 is "no data"
DewPoint int16
}
// LocationEvent corresponds to EventTypeLocation
type LocationEvent struct {
TimelineHeader
// Location name
Location string
// Altitude from sea level in meters
Altitude int16
// EPSG:3857 latitude (Google Maps, Openstreetmaps)
Latitude int32
// EPSG:3857 longitude (Google Maps, Openstreetmaps)
Longitude int32
}
// HumidityEvent corresponds to EventTypeHumidity
type HumidityEvent struct {
TimelineHeader
// Relative humidity percentage
Humidity uint8
}
// PressureEvent corresponds to EventTypePressure
type PressureEvent struct {
TimelineHeader
// Air pressure in hectopascals (hPa)
Pressure int16
}
// SpecialEvent corresponds to EventTypeSpecial
type SpecialEvent struct {
TimelineHeader
// Type of special event
Type SpecialType
}
// AirQualityEvent corresponds to EventTypeAirQuality
type AirQualityEvent struct {
TimelineHeader
// Name of the polluting item
//
// Do not localize the name. That should be handled by the watch.
//
// For particulate matter, use "PM0.1"`, "PM5", or "PM10".
//
// For chemicals, use the molecular formula ("NO2", "CO2", or "O3").
//
// For pollen, use the genus of the plant.
Polluter string
// Amount of pollution in SI units
//
// https://ec.europa.eu/environment/air/quality/standards.htm
// http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
Amount uint32
}