Skip to content

Commit

Permalink
WIP: Add HorizonBoxBee (#171)
Browse files Browse the repository at this point in the history
Add HorizonBoxBee
  • Loading branch information
dschmidt authored and muesli committed Sep 27, 2017
1 parent ed05072 commit 7f1664f
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 0 deletions.
158 changes: 158 additions & 0 deletions bees/horizonboxbee/horizonboxbee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright (C) 2017 Dominik Schmidt
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Dominik Schmidt <[email protected]>
*/

package horizonboxbee

import (
"github.com/PuerkitoBio/goquery"
"github.com/muesli/beehive/bees"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"
)

type HorizonBoxBeeState struct {
online bool
ip string
}

type HorizonBoxBee struct {
bees.Bee

address string
user string
password string
interval int

State HorizonBoxBeeState
eventChan chan bees.Event
}

func (mod *HorizonBoxBee) poll() {
// Form data
v := url.Values{}
v.Set("username", mod.user)
v.Set("password", mod.password)
v.Set("page", "login")

req, err := http.NewRequest("POST", "http://"+mod.address+"/cgi-bin/sendResult.cgi?section=login", strings.NewReader(v.Encode()))
if err != nil {
mod.LogErrorf("http.NewRequest() error: %v", err)
return
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")

cookieJar, _ := cookiejar.New(nil)
c := &http.Client{
Jar: cookieJar,
}

resp, err := c.Do(req)
if err != nil {
mod.LogErrorf("Could not connect to %s: %v", mod.address, err)
return
}

state := HorizonBoxBeeState{}
doc, err := goquery.NewDocumentFromResponse(resp)
doc.Find("table").Each(func(i int, s *goquery.Selection) {
s.Find("tr").Each(func(j int, t *goquery.Selection) {
// Unfortunately keys here are translated, thus we rely on the order instead of keys
// key := t.Find("td").First().Text()
value := t.Find("td").Last().Text()

switch j {
case 8:
state.online = false
if value == "Enabled" {
state.online = true
}
case 9:
state.ip = value
}
})
})

if mod.State.online != state.online {
mod.announceOnlineStateChange(state.online)
}

if mod.State.ip != state.ip {
mod.announceIpChange(state.ip)
}

mod.State = state
}

func (mod *HorizonBoxBee) announceIpChange(ip string) {
event := bees.Event{
Bee: mod.Name(),
Name: "external_ip_change",
Options: []bees.Placeholder{
{
Name: "new_external_ip",
Type: "address",
Value: ip,
},
},
}
mod.eventChan <- event
}

func (mod *HorizonBoxBee) announceOnlineStateChange(online bool) {
event := bees.Event{
Bee: mod.Name(),
Name: "connection_status_change",
Options: []bees.Placeholder{
{
Name: "online",
Type: "bool",
Value: online,
},
},
}
mod.eventChan <- event
}

// Run executes the Bee's event loop.
func (mod *HorizonBoxBee) Run(cin chan bees.Event) {
mod.eventChan = cin
for {
select {
case <-mod.SigChan:
return

case <-time.After(time.Duration(mod.interval) * time.Second):
mod.poll()
}
}
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *HorizonBoxBee) ReloadOptions(options bees.BeeOptions) {
mod.SetOptions(options)

options.Bind("address", &mod.address)
options.Bind("user", &mod.user)
options.Bind("password", &mod.password)
options.Bind("interval", &mod.interval)
}
132 changes: 132 additions & 0 deletions bees/horizonboxbee/horizonboxbeefactory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2017 Dominik Schmidt
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Dominik Schmidt <[email protected]>
*/

package horizonboxbee

import (
"github.com/muesli/beehive/bees"
)

// HorizonBoxBeeFactory is a factory for HorizonBoxBees.
type HorizonBoxBeeFactory struct {
bees.BeeFactory
}

// New returns a new Bee instance configured with the supplied options.
func (factory *HorizonBoxBeeFactory) New(name, description string, options bees.BeeOptions) bees.BeeInterface {
bee := HorizonBoxBee{
Bee: bees.NewBee(name, factory.ID(), description, options),
}
bee.ReloadOptions(options)

return &bee
}

// ID returns the ID of this Bee.
func (factory *HorizonBoxBeeFactory) ID() string {
return "horizonboxbee"
}

// Name returns the name of this Bee.
func (factory *HorizonBoxBeeFactory) Name() string {
return "HorizonBox"
}

// Description returns the description of this Bee.
func (factory *HorizonBoxBeeFactory) Description() string {
return "A bee observing the state of a UnityMedia HorizonBox"
}

// // Image returns the filename of an image for this Bee.
// func (factory *HorizonBoxBeeFactory) Image() string {
// return factory.ID() + ".png"
// }

// LogoColor returns the preferred logo background color (used by the admin interface).
func (factory *HorizonBoxBeeFactory) LogoColor() string {
return "#212727"
}

func (factory *HorizonBoxBeeFactory) Options() []bees.BeeOptionDescriptor {
opts := []bees.BeeOptionDescriptor{
{
Name: "address",
Description: "Address of the HorizonBox, eg: 192.168.192.1",
Type: "address",
Mandatory: true,
},
{
Name: "user",
Description: "Username to login to the HorizonBox",
Type: "string",
Mandatory: true,
},
{
Name: "password",
Description: "Password to login to the HorizonBox",
Type: "password",
Mandatory: true,
},
{
Name: "interval",
Description: "State polling interval in seconds",
Type: "int",
Default: 60,
Mandatory: true,
},
}
return opts
}

// Events describes the available events provided by this Bee.
func (factory *HorizonBoxBeeFactory) Events() []bees.EventDescriptor {
events := []bees.EventDescriptor{
{
Namespace: factory.Name(),
Name: "connection_status_change",
Description: "The internet connection changed",
Options: []bees.PlaceholderDescriptor{
{
Name: "online",
Description: "The new connection status",
Type: "bool",
},
},
},
{
Namespace: factory.Name(),
Name: "external_ip_change",
Description: "The external ip changed",
Options: []bees.PlaceholderDescriptor{
{
Name: "new_external_ip",
Description: "The new external ip",
Type: "address",
},
},
},
}
return events
}

func init() {
f := HorizonBoxBeeFactory{}
bees.RegisterFactory(&f)
}
1 change: 1 addition & 0 deletions hives.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
_ "github.com/muesli/beehive/bees/fsnotifybee"
_ "github.com/muesli/beehive/bees/githubbee"
_ "github.com/muesli/beehive/bees/gitterbee"
_ "github.com/muesli/beehive/bees/horizonboxbee"
_ "github.com/muesli/beehive/bees/htmlextractbee"
_ "github.com/muesli/beehive/bees/httpbee"
_ "github.com/muesli/beehive/bees/huebee"
Expand Down

0 comments on commit 7f1664f

Please sign in to comment.