-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathweatherstation.go
302 lines (246 loc) · 7.38 KB
/
weatherstation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
package main
import (
"bytes"
"code.google.com/p/gcfg"
"flag"
"fmt"
"github.com/tarm/goserial"
"log"
"net/http"
"strconv"
"strings"
"time"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
//owm "./openweathermap"
)
//Config is the data type for the config file
type Config struct {
Data struct {
Latitude string
Longitude string
}
Communication struct {
Port string
BaudRate int
}
OpenWeatherMap struct {
StationName string
Username string
Password string
}
Webserver struct {
Address string
}
Database struct {
Connection string
}
}
type WeatherData struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
Temperature float64
Humidity float64
Windspeed float64
Raining bool
RainTicks int
}
var dbSql *sql.DB
var db gorm.DB
var lastUpdate = time.Now()
//Every 1 min a value is saved
var rain1hArray [60]int
var rain1hIndex int
var rain24hArray [60 * 24]int
var rain24hIndex int
var firstData = true
//current values, ment to be served by the http server
var currentTemp float64
var currentHumidity float64
var currentSpeed float64
var currentRain bool
var currentRain1h float64
var currentRain24h float64
func main() {
var configFile = flag.String("config", "config.gcfg", "Path to Config-File.")
flag.Parse()
//Read and parse the cofig file
log.Print("Reading Config...")
cfg, err := ParseConfig(*configFile)
if err != nil {
log.Fatal(err)
return
}
log.Print("Config has been read.")
//Setting up the database
dbSql, err = sql.Open("mysql", cfg.Database.Connection)
if err != nil {
//Abort
log.Fatal(err)
return
}
db , err = gorm.Open("mysql", dbSql)
if err != nil {
log.Fatal(err)
return
}
defer db.Close()
db.CreateTable(&WeatherData{})
//start the serial communication
go StartCommunication(cfg)
//start the web server
StartWebserver(cfg)
}
//StartWebserver starts and initializes the web server to serve the weather data
func StartWebserver(cfg Config) {
log.Print("Starting webserver on " + cfg.Webserver.Address)
http.HandleFunc("/data", DataHTTPHandler)
//http.Handle("/", http.FileServer(assetFS()))
http.ListenAndServe(cfg.Webserver.Address, nil)
}
//DataHTTPHandler handles the rain data requests
func DataHTTPHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
log.Print("HTTP: data requested")
//get the latest record
var record = WeatherData{}
res := db.Order("created_at desc").First(&record)
if res.Error != nil {
fmt.Fprint(w, "{\"error\": \"Something went wrong when querying the database!\"}")
log.Print("Something went wrong:")
log.Print(res)
return
}
now := time.Now()
rain1h := CalculateRain(GetRainTicksSince(now, now.Add(-1 * time.Hour)))
rain24h := CalculateRain(GetRainTicksSince(now, now.Add(-24 * time.Hour)))
fmt.Fprintf(w, "{\"temp\": %.1f,\"humidity\": %.0f,\"wind_speed\": %.1f,\"rain\": {\"h1\": %.1f, \"h24\": %.1f, \"current\": %t }}", record.Temperature, record.Humidity, record.Windspeed, rain1h, rain24h, record.Raining)
}
func GetRainTicksSince(t1, t2 time.Time) (int){
log.Print(t2)
weatherData := []WeatherData{}
res := db.Where("created_at BETWEEN ? AND ?", t2, t1).Order("created_at desc").Find(&weatherData)
if res.Error != nil {
log.Print("An error occured getting the weatherdata")
return -1
}
var overflowCounter = 0
var lastTicks = weatherData[0].RainTicks
var initTicks = weatherData[len(weatherData) -1].RainTicks
for _, dataset := range weatherData {
if dataset.RainTicks > lastTicks {
log.Print("OVERFLOW now at " + strconv.Itoa(overflowCounter) + " (" + strconv.Itoa(dataset.RainTicks) + ">" + strconv.Itoa(lastTicks) + ")")
overflowCounter++
}
lastTicks = dataset.RainTicks
}
log.Print("Found " + strconv.Itoa(overflowCounter) + " overflows")
var result = overflowCounter * 4096 + weatherData[0].RainTicks - initTicks
log.Printf("%d Ticks calculated (endTicks=%d, startTicks=%d)", result, weatherData[0].RainTicks, initTicks)
return result
}
//StartCommunication opens the serial connection and reads the data from it
func StartCommunication(cfg Config) {
//initialize the serial connection
c := &serial.Config{Name: cfg.Communication.Port, Baud: cfg.Communication.BaudRate}
log.Print("Starting Communication")
//and now open the port
s, err := serial.OpenPort(c)
if err != nil || s == nil {
log.Fatal(err)
return
}
log.Print("Port has been opened.")
//endlosschleife
for true {
var buffer bytes.Buffer
stop := false
for !stop {
buf := make([]byte, 4)
_, err = s.Read(buf)
if err != nil {
log.Fatal(err)
} else {
value := string(buf)
buffer.WriteString(value)
stop = value[0] == '\015'
}
}
data := buffer.String()
data = strings.TrimSpace(data)
//parse the incoming data
Parse(data, cfg)
}
}
//Convert transformes the string data to the corresponding float64 value
func Convert(data string) (float64, error) {
return strconv.ParseFloat(strings.TrimSpace(strings.Replace(strings.Replace(data, ",", ".", 1), "\x00", "", 42)), 64)
}
//Parse takes the received data and parses it
func Parse(data string, cfg Config) {
log.Print("Received Data: ", data)
split := strings.Split(data, ";")
//calculate average temperature
temperature1, err1 := Convert(split[3])
temperature2, err2 := Convert(split[19])
temperature := (temperature1 + temperature2) / 2
if err1 != nil && err2 == nil {
temperature = temperature1
} else if err1 == nil && err2 != nil {
temperature = temperature2
}
//calculate average humdity
humidity1, errA := Convert(split[11])
humidity2, errB := Convert(split[20])
humidity := (humidity1 + humidity2) / 2
if errA != nil && errB == nil {
humidity = humidity1
} else if errA == nil && errB != nil {
humidity = humidity2
}
//get the wind speed
windSpeed, _ := Convert(split[21])
//and get the rain
rainTicks, _ := Convert(split[22])
//rain1h, rain24h := calculateRain(int(rainTicks))
rain := split[23]
//Put all the data into the database
//Sometimes there are erroros, where everything is 0
if humidity != 0 && int(rainTicks) != 0 {
weatherdata := WeatherData{Temperature: temperature, Humidity: humidity, Windspeed: windSpeed, Raining: (rain == "1"), RainTicks: int(rainTicks)}
db.Create(&weatherdata)
}
log.Printf("Temp: %.1f Humidity: %.0f WindSpeed: %.1f RainTicks: %.0f Rain: %b", temperature, humidity, windSpeed, rainTicks, currentRain)
}
/*
Calculates the fallen rain
1 rain tick means that 5ml water fell. the ticks are absolute and reset after reaching 4096 to 0
The area collecting the rain is 86.6cm² big
So we get the ticks and multiply it by 0.005, having the amount of L water that fell on the funnel
We then divide that by 0.00866m^2 to get the amount of water that fell on a square meter(Unit L/m^2 = mm)
1L on a square meter equals 1mm high water
So the formula is '(ticks * 0.005)/0.00866'
returns rain
*/
func CalculateRain(rainTicks int) (float64) {
//calculate fallen rain
//rain1h := float64(rain1hDelta) / (2 * 86.6) old and wrong formula
rain := ((float64(rainTicks) * 0.005) / 0.00866)
return rain
}
//ParseConfig does exactly what the name says...
func ParseConfig(configFile string) (Config, error) {
var cfg Config
err := gcfg.ReadFileInto(&cfg, configFile)
return cfg, err
}
func ArrayToString(data []int) string {
result := "["
for _, value := range data {
result += strconv.FormatInt(int64(value), 10) + ","
}
result += "]"
return result
}