From 7116d3239c587b81ae22058d5c7af69eebfaedd4 Mon Sep 17 00:00:00 2001 From: Chteufleur Date: Sun, 1 Nov 2015 16:07:35 +0100 Subject: [PATCH] Add relay management --- controllers/relay.go | 78 ++++++++++++++++++++ controllers/sensors.go | 3 + models/relay/relay.go | 126 ++++++++++++++++++++++++++++++++ models/variables/variables.go | 12 ++- routers/router.go | 6 ++ static/css/jumbotron-narrow.css | 36 +++++++++ static/css/style.css | 21 ++++++ static/img/bulbOff.png | Bin 0 -> 3705 bytes static/img/bulbOn.png | Bin 0 -> 5196 bytes views/base/head.html | 1 + views/base/navbar.html | 7 ++ views/index.tpl | 2 + views/relay.tpl | 102 ++++++++++++++++++++++++++ views/sensors.tpl | 36 ++++++--- views/temp.tpl | 4 + 15 files changed, 421 insertions(+), 13 deletions(-) create mode 100644 controllers/relay.go create mode 100644 models/relay/relay.go create mode 100644 static/css/jumbotron-narrow.css create mode 100644 static/css/style.css create mode 100644 static/img/bulbOff.png create mode 100644 static/img/bulbOn.png create mode 100644 views/relay.tpl diff --git a/controllers/relay.go b/controllers/relay.go new file mode 100644 index 0000000..b420f62 --- /dev/null +++ b/controllers/relay.go @@ -0,0 +1,78 @@ +package controllers + +import ( + "github.com/astaxie/beego" + "github.com/astaxie/beego/httplib" + + "datahouse/models/variables" + "datahouse/models/relay" + "strings" +) + + +type ViewRelayController struct { + beego.Controller +} + +func (c *ViewRelayController) Prepare() { + c.Data["IsViewRelay"] = true; + c.Data["version"] = variables.Version +} + +func (c *ViewRelayController) Get() { + c.Data["relays"] = relay.GetAllRelay() + + mac := c.Ctx.Input.Param(":sensor") + r := relay.GetRelayByMac(mac) + if r.Id != 0 { + c.Data["isRelaySelected"] = true + c.Data["relayMac"] = r.Mac + if r.Description != "" { + c.Data["relayDescription"] = r.Description + } else { + c.Data["relayDescription"] = r.Mac + } + } + + c.TplNames = "relay.tpl" +} + + +func (c *ViewRelayController) Post() { + mac := c.Ctx.Input.Param(":sensor") + r := relay.GetRelayByMac(mac) + ret := "" + if r.Id != 0 { + ret += r.Mac+"/" + if r.IpAddress != "" { + getRep, err := httplib.Get("http://"+r.IpAddress+"/status").String() + if err == nil { + ret += getRep + } else { + relay.UpdateSensorIpAddress(r.Mac, "") + } + } + } + c.Ctx.Output.Body([]byte(ret)) +} + + + + + + +type AddRelayController struct { + beego.Controller +} + +func (c *AddRelayController) Prepare() { + c.Data["version"] = variables.Version +} + +func (c *AddRelayController) Get() { + ip := strings.Split(c.Ctx.Request.RemoteAddr, ":")[0] + mac := c.Ctx.Input.Param(":sensor") + relay.UpdateSensorIpAddress(mac, ip) + + c.Ctx.Output.Body([]byte("OK")) +} diff --git a/controllers/sensors.go b/controllers/sensors.go index aa058f4..e760a46 100644 --- a/controllers/sensors.go +++ b/controllers/sensors.go @@ -3,6 +3,7 @@ package controllers import ( "github.com/astaxie/beego" + "datahouse/models/relay" "datahouse/models/sensor" "datahouse/models/temperature" "datahouse/models/variables" @@ -27,8 +28,10 @@ func (c *SensorsController) Prepare() { func (c *SensorsController) Get() { sensors := sensor.GetAllSensor() + relays := relay.GetAllRelay() c.Data["sensors"] = sensors + c.Data["relays"] = relays c.TplNames = "sensors.tpl" } diff --git a/models/relay/relay.go b/models/relay/relay.go new file mode 100644 index 0000000..2b5aff9 --- /dev/null +++ b/models/relay/relay.go @@ -0,0 +1,126 @@ +package relay + +import ( + "github.com/astaxie/beego/orm" + + "datahouse/models/database" + "datahouse/models/utils" +) + + + +type RelayTable struct { + Id int64 + Mac string + Description string + IpAddress string +} + +func init() { + orm.RegisterModel(new(RelayTable)) +} + + + +func GetRelayByMac(relayMac string) *RelayTable { + o := orm.NewOrm() + o.Using(database.Alias) + + ret := new(RelayTable) + var maps []orm.Params + _, err := o.QueryTable(new(RelayTable)).Filter("Mac", relayMac).Values(&maps) + if err == nil { + for _, m := range maps { + ret.Id = utils.GetInt(m, "Id") + ret.Mac = utils.GetString(m, "Mac") + ret.Description = utils.GetString(m, "Description") + ret.IpAddress = utils.GetString(m, "IpAddress") + break + } + } + + return ret +} + +func GetAllRelay() []RelayTable { + o := orm.NewOrm() + o.Using(database.Alias) + + var ret []RelayTable + var maps []orm.Params + _, err := o.QueryTable(new(RelayTable)).Values(&maps) + if err == nil { + for _, m := range maps { + r := new(RelayTable) + r.Id = utils.GetInt(m, "Id") + r.Mac = utils.GetString(m, "Mac") + r.Description = utils.GetString(m, "Description") + r.IpAddress = utils.GetString(m, "IpAddress") + + ret = append(ret, *r) + } + } + return ret +} + +func GetRelay(id int64) *RelayTable { + o := orm.NewOrm() + o.Using(database.Alias) + + var ret = new(RelayTable) + var maps []orm.Params + _, err := o.QueryTable(new(RelayTable)).Filter("Id", id).Values(&maps) + if err == nil { + for _, m := range maps { + ret.Id = utils.GetInt(m, "Id") + ret.Mac = utils.GetString(m, "Mac") + ret.Description = utils.GetString(m, "Description") + ret.IpAddress = utils.GetString(m, "IpAddress") + } + } + return ret +} + +func UpdateSensorDescription(mac, description string) { + o := orm.NewOrm() + o.Using(database.Alias) + + s := GetRelayByMac(mac) + if o.Read(s) == nil { + s.Description = description + o.Update(s) + } else { + AddRelay(mac, description, "") + } +} + +func UpdateSensorIpAddress(mac, ip string) { + o := orm.NewOrm() + o.Using(database.Alias) + + s := GetRelayByMac(mac) + if o.Read(s) == nil { + s.IpAddress = ip + o.Update(s) + } else { + AddRelay(mac, "", ip) + } +} + +func AddRelay(mac, desc, ip string) { + o := orm.NewOrm() + o.Using(database.Alias) + _, _ = o.Insert(&RelayTable{Mac: mac, Description: desc, IpAddress: ip}) +} + +func DeleteSensorByMac(mac string) { + o := orm.NewOrm() + o.Using(database.Alias) + o.Delete(&RelayTable{Mac: mac}) +} + +func DeleteSensor(id int64) { + o := orm.NewOrm() + o.Using(database.Alias) + o.Delete(&RelayTable{Id: id}) +} diff --git a/models/variables/variables.go b/models/variables/variables.go index e406fe3..a0954ae 100644 --- a/models/variables/variables.go +++ b/models/variables/variables.go @@ -4,12 +4,20 @@ const ( Version = "0.0.3" SessionName = "Session_Data_House" + + sensorMacRegex = ":sensor([0-9A-Fa-f:]+)" ) var ( RootRoute = "/" - AddTempRoute = "/add/temp/:sensor([0-9A-Fa-f:]+)/:val([0-9]+)" - ViewTempRoute = "/view/temp" + + AddTempRoute = "/add/temp/"+sensorMacRegex+"/:val([0-9]+)" + AddRelayRoute = "/add/relay/"+sensorMacRegex + + ViewTempRoute = "/view/temp" + ViewRelaysRoute = "/view/relay" + ViewRelayRoute = "/view/relay/"+sensorMacRegex + SensorsRoute = "/sensors" LoginRoute = "/login" UserRoute = "/user" diff --git a/routers/router.go b/routers/router.go index a85bf15..caf4a34 100644 --- a/routers/router.go +++ b/routers/router.go @@ -8,8 +8,14 @@ import ( func init() { beego.Router(variables.RootRoute, &controllers.MainController{}) + beego.Router(variables.AddTempRoute, &controllers.AddTempController{}) + beego.Router(variables.AddRelayRoute, &controllers.AddRelayController{}) + beego.Router(variables.ViewTempRoute, &controllers.ViewTempController{}) + beego.Router(variables.ViewRelaysRoute, &controllers.ViewRelayController{}) + beego.Router(variables.ViewRelayRoute, &controllers.ViewRelayController{}) + beego.Router(variables.SensorsRoute, &controllers.SensorsController{}) beego.Router(variables.LoginRoute, &controllers.LoginController{}) beego.Router(variables.UserRoute, &controllers.UserController{}) diff --git a/static/css/jumbotron-narrow.css b/static/css/jumbotron-narrow.css new file mode 100644 index 0000000..3b80b6b --- /dev/null +++ b/static/css/jumbotron-narrow.css @@ -0,0 +1,36 @@ +/* Main marketing message and sign up button */ +.jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; +} +.jumbotron .btn { + padding: 14px 24px; + font-size: 21px; +} + +/* Supporting marketing content */ +.marketing { + margin: 40px 0; +} +.marketing p + h4 { + margin-top: 28px; +} + +/* Responsive: Portrait tablets and up */ +@media screen and (min-width: 768px) { + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-right: 0; + padding-left: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } +} diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..67f4168 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,21 @@ +/* make sidebar nav vertical */ +@media (min-width: 768px) { + .sidebar-nav .navbar .navbar-collapse { + padding: 0; + max-height: none; + } + .sidebar-nav .navbar ul { + float: none; + } + .sidebar-nav .navbar ul:not { + display: block; + } + .sidebar-nav .navbar li { + float: none; + display: block; + } + .sidebar-nav .navbar li a { + padding-top: 12px; + padding-bottom: 12px; + } +} diff --git a/static/img/bulbOff.png b/static/img/bulbOff.png new file mode 100644 index 0000000000000000000000000000000000000000..4de0ba58dc9a93def2ea57c85565642489bd8f1c GIT binary patch literal 3705 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