Skip to content

Commit

Permalink
Habemus a simple chart! WIP #15
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfireman committed Jul 24, 2018
1 parent 211cf6a commit cc4687b
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 31 deletions.
14 changes: 13 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 66 additions & 8 deletions server/status/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,30 @@ func (db *DB) StoreWeather(ts time.Time, s weather.State) error {
return db.store(ts, weatherField, toStore(s))
}

// FetchWeather fetches a time range of weather temperatures (which do not include forecasts).
func (db *DB) FetchWeather(start time.Time, finish time.Time) ([]weather.State, error) {
iter := db.iterRange(start, finish, weatherField)
var ret []weather.State
var d weatherDocument
for iter.Next(&d) {
ret = append(ret, fromStore(d.Value, d.Timestamp))
}
if err := iter.Close(); err != nil {
if err == mgo.ErrNotFound {
return ret, nil
}
return nil, fmt.Errorf("Error fetching weather: %q", err)
}
return ret, nil
}

// StoreWeatherForecast updates the StatusDB with the new information about the weather forecast. This call
// assumes the weather.State.Timestamp is a future timestamp, so it overrides whichever information is
// associated to it (it should be none).
func (db *DB) StoreWeatherForecast(states ...weather.State) error {
bulk := db.collection.Bulk()
for _, s := range states {
ts := time.Unix(s.Timestamp, 0)
db.bulkStore(bulk, ts, forecastField, toStore(s))
db.bulkStore(bulk, s.Timestamp, forecastField, toStore(s))
}
_, err := bulk.Run()
return err
Expand All @@ -51,6 +67,19 @@ func (db *DB) StoreBedroomTemperature(ts time.Time, temp float32) error {
return db.store(time.Now(), bedroomField, temp)
}

func (db *DB) iterRange(start time.Time, finish time.Time, typez string) *mgo.Iter {
startUTC := start.In(time.UTC)
finishUTC := finish.In(time.UTC)
return db.collection.Find(
bson.M{
timestampIndexField: bson.M{
"$gte": startUTC,
"$lte": finishUTC,
},
"type": typez,
}).Iter()
}

// Fan returns a Fan instance.
func (db *DB) Fan() *Fan {
return &Fan{db}
Expand Down Expand Up @@ -127,22 +156,51 @@ func toStore(s weather.State) weatherState {
}
}

func fromStore(s weatherState, hour time.Time) weather.State {
return weather.State{
Description: weather.Description{
Text: s.Description.Text,
Icon: s.Description.Icon,
},
Wind: weather.Wind{
Speed: s.Wind.Speed,
Direction: s.Wind.Direction,
},
Temp: s.Temp,
Humidity: s.Humidity,
Rain: s.Rain,
Cloudiness: s.Cloudiness,
Timestamp: hour,
}
}

// weatherState stores the complete information about the weather at a certain time.
type weatherState struct {
Description weatherDescription `bson:"description,omitempty"`
Wind wind `bson:"wind,omitempty"`
Temp float32 `bson:"temp,omitempty"` // Temperature, Celsius
Humidity float32 `bson:"humidity,omitempty"` // Humidity, %
Rain float32 `bson:"rain,omitempty"` // Rain volume for the last hours
Cloudiness float32 `bson:"cloudiness,omitempty"` // Cloudiness, %
Temp float64 `bson:"temp,omitempty"` // Temperature, Celsius
Humidity float64 `bson:"humidity,omitempty"` // Humidity, %
Rain float64 `bson:"rain,omitempty"` // Rain volume for the last hours
Cloudiness float64 `bson:"cloudiness,omitempty"` // Cloudiness, %
}

type wind struct {
Speed float32 `bson:"speed,omitempty"` // Wind speed, meter/sec
Direction float32 `bson:"deg,omitempty"` // Wind direction, degrees (meteorological)
Speed float64 `bson:"speed,omitempty"` // Wind speed, meter/sec
Direction float64 `bson:"deg,omitempty"` // Wind direction, degrees (meteorological)
}

type weatherDescription struct {
Text string `bson:"text,omitempty"` // Weather condition within the group
Icon string `bson:"icon,omitempty"` // Weather icon id
}

type bedroomTemp float64

type bedroomTempDocument struct {
Value bedroomTemp `bson:"value,omitempty"`
}

type weatherDocument struct {
Timestamp time.Time `bson:"timestamp_hour,omitempty"`
Value weatherState `bson:"value,omitempty"`
}
6 changes: 3 additions & 3 deletions server/weather/owm_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func toState(owmResp owmCurrWeatherResponse) State {
Humidity: round(owmResp.Main.Humidity),
Rain: round(owmResp.Rain.ThreeHours),
Cloudiness: round(owmResp.Clouds.All),
Timestamp: owmResp.DT,
Timestamp: time.Unix(owmResp.DT, 0),
}
}

Expand Down Expand Up @@ -137,6 +137,6 @@ type weather struct {
// round receives a float64, rounds it to 4 most signigicant digits (max) and returns it as
// float32. Mostly used to decrease massive (unnecessary) precision of float64 and thus
// to decrease storage requirements.
func round(f float64) float32 {
return float32(math.Round(f/0.0001) * 0.0001)
func round(f float64) float64 {
return float64(math.Round(f/0.0001) * 0.0001)
}
16 changes: 9 additions & 7 deletions server/weather/struct.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package weather

import "time"

// State stores the complete information about the weather at a certain time.
type State struct {
Timestamp int64 // Timestamp in unix UTC
Timestamp time.Time // Timestamp in unix UTC
Description Description // Icon and other text describing the weather state
Wind Wind // Wind description
Temp float32 // Temperature, Celsius
Humidity float32 // Humidity, %
Rain float32 // Rain volume for the last hours
Cloudiness float32 // Cloudiness, %
Temp float64 // Temperature, Celsius
Humidity float64 // Humidity, %
Rain float64 // Rain volume for the last hours
Cloudiness float64 // Cloudiness, %
}

// Wind stores information about the wind.
type Wind struct {
Speed float32 `bson:"speed,omitempty"` // Wind speed, meter/sec
Direction float32 `bson:"deg,omitempty"` // Wind direction, degrees (meteorological)
Speed float64 `bson:"speed,omitempty"` // Wind speed, meter/sec
Direction float64 `bson:"deg,omitempty"` // Wind direction, degrees (meteorological)
}

// Description stores overall information to describe the weather. That include text, images and so on.
Expand Down
2 changes: 2 additions & 0 deletions server/web/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
web
public/js/graph.js
public/js/graph.js.map
1 change: 1 addition & 0 deletions server/web/graph/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
graph
58 changes: 58 additions & 0 deletions server/web/graph/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

//go:generate gopherjs build main.go -o ../public/js/graph.js -m

import (
"bytes"
"encoding/json"
"net/http"
"strconv"

"github.com/danielfireman/temp-to-go/server/weather"

charts "github.com/cnguy/gopherjs-frappe-charts"
)

type weatherResponse struct {
Weather []weather.State `json:"weather,omitempty"`
}

func main() {
resp, err := http.Get("http://localhost:8080/restricted/weather")
if err != nil {
println(err)
return
}
if resp.StatusCode != 200 {
println("StatusCode:", resp.StatusCode)
return
}
defer resp.Body.Close()

buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
println(buf.String())

var wr weatherResponse
if err := json.Unmarshal(buf.Bytes(), &wr); err != nil {
println(err)
return
}
var temps []float64
var labels []string
for _, s := range wr.Weather {
d := s.Timestamp.Hour()
labels = append(labels, strconv.Itoa(d))
temps = append(temps, s.Temp)
}

chartData := charts.NewChartData()
chartData.Labels = labels
chartData.Datasets = []*charts.Dataset{
charts.NewDataset(
"Weather",
temps,
)}

_ = charts.NewLineChart("#chart", chartData).Render()
}
13 changes: 11 additions & 2 deletions server/web/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"time"

"github.com/danielfireman/temp-to-go/server/status"
Expand Down Expand Up @@ -63,15 +64,23 @@ func main() {
// Public Routes.
e.File("/", filepath.Join(publicHTML, "index.html"))
e.File("/favicon.ico", filepath.Join(publicHTML, "favicon.ico"))
e.Static("/public", publicHTML)
e.Static("/", publicHTML)
//e.Static("/js", filepath.Join(publicHTML, "js"))
e.POST("/indoortemp", bedroomapi.TempHandlerFunc(key, sdb))
e.POST("/login", loginHandlerFunc(userPasswd))

// Routes which should only be accessed after login.
restricted := e.Group(restrictedPath, restrictedMiddleware)
restricted := e.Group(restrictedPath, restrictedMiddleware, middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://mybedroom.live"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
Skipper: func(c echo.Context) bool {
return strings.HasPrefix(c.Request().Host, "localhost")
},
}))
restricted.GET("", mainRestrictedHandlerFunc(fan))
restricted.POST("/fan", fanHandlerFunc(fan))
restricted.POST("/logout", logoutHandler)
restricted.GET("/weather", weatherHandlerFunc(sdb))

// Starting server.
port := os.Getenv("PORT")
Expand Down
29 changes: 19 additions & 10 deletions server/web/public/templates/main.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
{{define "main"}}
<html>

<head>
<meta charset="utf-8">
<title>Welcome to temp-to-go</title>
</head>

<body>
<h1>Welcome!</h1>
<form method="post" action="/restricted/logout">
<button type="submit">Logout</button>
</form>
<hr>
Current Fan Status: <b>{{.Speed}}</b>
Current Fan Status:
<b>{{.Speed}}</b>
<form method="post" action={{.Action}}>
Update Status:
{{range .Opts}}
<input type="radio" name={{.Name}} value={{.Value}} checked>{{.Label}}
{{end}}
Update Status: {{range .Opts}}
<input type="radio" name={{.Name}} value={{.Value}} checked>{{.Label}} {{end}}
<button type="submit">Submit</button>
</form>
</form>
<hr>
<br>
<form method="post" action="/restricted/logout">
<button type="submit">Logout</button>
</form>
<b>Charts</b>
<div id="chart"></div>

<!-- Imports (must stay at the end) -->
<script src="https://unpkg.com/[email protected]/dist/frappe-charts.min.iife.js"></script>
<script src="/../js/graph.js" data-cover></script>
</body>

</html>
Expand Down
26 changes: 26 additions & 0 deletions server/web/weather_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"net/http"
"time"

"github.com/danielfireman/temp-to-go/server/status"
"github.com/danielfireman/temp-to-go/server/weather"
"github.com/labstack/echo"
)

func weatherHandlerFunc(db *status.DB) echo.HandlerFunc {
return func(c echo.Context) error {
var resp weatherResponse
var err error
resp.Weather, err = db.FetchWeather(time.Now().Add(-24*time.Hour), time.Now())
if err != nil {
return c.NoContent(http.StatusInternalServerError)
}
return c.JSON(http.StatusOK, resp)
}
}

type weatherResponse struct {
Weather []weather.State `json:"weather,omitempty"`
}

0 comments on commit cc4687b

Please sign in to comment.