-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
142 lines (133 loc) · 4.55 KB
/
main.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
// gcloud-wifi-geolocation is a Go web application hosted on Google Cloud Standard App Engine that
// renders realtime temperature sensor data and geolocation on a map. The sensor data and geolocation
// are pushed via HTTPS from thethings.io Cloud Code Trigger "forward_geolocate" and
// Cloud Code Function "geolocate". The map is rendered using Mapbox GL JS.
// For privacy, users are required to specify the Device ID when viewing the app. Adapted from
// https://github.com/GoogleCloudPlatform/golang-samples/blob/master/appengine/go11x/helloworld/helloworld.go
// https://github.com/GoogleCloudPlatform/golang-samples/blob/master/appengine_flexible/pubsub/pubsub.go
// https://docs.mapbox.com/mapbox-gl-js/example/3d-buildings/
package main
import (
"encoding/json"
"fmt"
"log"
"math"
"net/http"
"os"
"sync"
)
// DeviceState contains the state of the device: temperature (deg C), location and geolocation accuracy (in metres).
// The fields must start with an uppercase letter because these fields will be exported for JSON deserialisation.
type DeviceState struct {
Device string `json:"device"` // When we serialise or deserialise to JSON, we will use the "json" names.
Tmp float64 `json:"tmp"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Accuracy float64 `json:"accuracy"`
}
var (
// deviceStates is the threadsafe map that maps device ID to device state. It needs to be threadsafe because we will reading and writing concurrently.
deviceStates sync.Map
)
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/pull", pullHandler)
http.HandleFunc("/push", pushHandler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
log.Printf("Defaulting to port %s", port)
}
log.Printf("Listening on port %s", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}
func pullHandler(w http.ResponseWriter, r *http.Request) {
device := r.URL.Query().Get("device")
if device == "" {
// Decode the received JSON.
msg := &DeviceState{}
if err := json.NewDecoder(r.Body).Decode(msg); err != nil {
http.Error(w, fmt.Sprintf("Could not decode body: %v", err), http.StatusBadRequest)
return
}
device = msg.Device
if device == "" {
http.Error(w, "missing device", http.StatusBadRequest)
return
}
}
enc := json.NewEncoder(w)
m := map[string]float64{}
result, ok := deviceStates.Load(device)
if ok {
// State exists for the device. Return the state.
state := result.(*DeviceState)
if !math.IsNaN(state.Tmp) {
m["tmp"] = state.Tmp
}
if !math.IsNaN(state.Latitude) {
m["latitude"] = state.Latitude
}
if !math.IsNaN(state.Longitude) {
m["longitude"] = state.Longitude
}
if !math.IsNaN(state.Accuracy) {
m["accuracy"] = state.Accuracy
}
fmt.Printf("pull state `%s`: tmp=%f, lat=%f, lng=%f, acc=%f\n", state.Device, state.Tmp, state.Latitude, state.Longitude, state.Accuracy)
} else {
fmt.Printf("no state `%s`\n", device)
}
enc.Encode(m)
}
func pushHandler(w http.ResponseWriter, r *http.Request) {
// TODO: Verify the token.
/*
if r.URL.Query().Get("token") != token {
http.Error(w, "Bad token", http.StatusBadRequest)
}
*/
// Decode the received JSON.
msg := &DeviceState{"", math.NaN(), math.NaN(), math.NaN(), math.NaN()}
if err := json.NewDecoder(r.Body).Decode(msg); err != nil {
http.Error(w, fmt.Sprintf("Could not decode body: %v", err), http.StatusBadRequest)
return
}
if msg.Device == "" {
http.Error(w, "Missing device", http.StatusBadRequest)
return
}
// Fetch the current state for the device.
result, loaded := deviceStates.LoadOrStore(msg.Device, msg)
if !loaded {
// State does not exist. We have already stored the message so do nothing.
fmt.Printf("new state `%s`: tmp=%f, lat=%f, lng=%f, acc=%f\n", msg.Device, msg.Tmp, msg.Latitude, msg.Longitude, msg.Accuracy)
} else {
// State already exists. We copy the received message into the current state.
state := result.(*DeviceState)
if !math.IsNaN(msg.Tmp) {
state.Tmp = msg.Tmp
}
if !math.IsNaN(msg.Latitude) {
state.Latitude = msg.Latitude
}
if !math.IsNaN(msg.Longitude) {
state.Longitude = msg.Longitude
}
if !math.IsNaN(msg.Accuracy) {
state.Accuracy = msg.Accuracy
}
fmt.Printf("updated state `%s`: tmp=%f, lat=%f, lng=%f, acc=%f\n", state.Device, state.Tmp, state.Latitude, state.Longitude, state.Accuracy)
}
fmt.Fprint(w, "\"OK\"")
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
fmt.Fprint(w, "Hello, World!")
return
}
http.NotFound(w, r)
}
/*
gcloud app logs tail -s default & ; gcloud app browse ; fg
*/