7 Commits

Author SHA1 Message Date
9d253644bd bella mqtt quasi va 2019-10-28 00:20:21 +01:00
93846dbbea new source promoted 2019-10-27 22:56:24 +01:00
f781e24028 virtual board works 2019-10-27 22:51:00 +01:00
fb819a71c4 go on 2019-10-27 22:39:00 +01:00
82c33f9277 less stuck 2019-10-21 23:43:46 +02:00
c4567d780c mqtt mockup 2019-09-20 22:14:41 +02:00
56ad55b967 I'm stuck 2019-08-31 21:34:46 +02:00
25 changed files with 1090 additions and 172 deletions

View File

@@ -0,0 +1,18 @@
sonoff10/tele/LWT Online
sonoff10/cmnd/POWER (null)
sonoff10/tele/INFO1 {"Module":"Sonoff Basic","Version":"6.6.0(release-sonoff)","FallbackTopic":"cmnd/DVES_E4A1F5_fb/","GroupTopic":"sonoffs"}
sonoff10/tele/INFO2 {"WebServerMode":"Admin","Hostname":"sonoff10-0501","IPAddress":"192.168.107.105"}
sonoff10/tele/INFO3 {"RestartReason":"Software/System restart"}
sonoff10/stat/RESULT {"POWER":"ON"}
sonoff10/stat/POWER ON
homeassistant/switch/E4A1F5_RL_1/config {"name":"sonoff10","cmd_t":"~cmnd/POWER","stat_t":"~tele/STATE","val_tpl":"{{value_json.POWER}}","pl_off":"OFF","pl_on":"ON","avty_t":"~tele/LWT","pl_avail":"Online","pl_not_avail":"Offline","uniq_id":"E4A1F5_RL_1","device":{"identifiers":["E4A1F5"]},"~":"sonoff10/"}
homeassistant/sensor/E4A1F5_status/config {"name":"sonoff10 status","stat_t":"~HASS_STATE","avty_t":"~LWT","pl_avail":"Online","pl_not_avail":"Offline","json_attributes_topic":"~HASS_STATE","unit_of_meas":" ","val_tpl":"{{value_json['RSSI']}}","uniq_id":"E4A1F5_status","device":{"identifiers":["E4A1F5"],"name":"sonoff10","model":"Sonoff Basic","sw_version":"6.6.0(release-sonoff)","manufacturer":"Tasmota"},"~":"sonoff10/tele/"}
sonoff10/tele/STATE {"Time":"1970-01-01T00:00:10","Uptime":"0T00:00:10","Heap":15,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":23,"POWER":"ON","Wifi":{"AP":1,"SSId":"iot","BSSId":"02:9F:C2:F7:CD:A9","Channel":5,"RSSI":100,"LinkCount":1,"Downtime":"0T00:00:06"}}
sonoff10/stat/RESULT {"POWER":"ON"}
sonoff10/stat/POWER ON
sonoff10/tele/STATE {"Time":"2019-09-21T12:10:18","Uptime":"0T00:00:16","Heap":15,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"POWER":"ON","Wifi":{"AP":1,"SSId":"iot","BSSId":"02:9F:C2:F7:CD:A9","Channel":5,"RSSI":100,"LinkCount":1,"Downtime":"0T00:00:06"}}

3
poc/info.txt Normal file
View File

@@ -0,0 +1,3 @@
docker run --rm -ti -p 1883:1883 alpine:latest sh -c "apk -U add mosquitto && mosquitto"

37
poc/mqtt.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"fmt"
"time"
MQTT "github.com/eclipse/paho.mqtt.golang"
)
func main() {
opts := MQTT.NewClientOptions()
opts.AddBroker("tcp://127.0.0.1:1883")
opts.SetClientID("ortobio")
opts.SetUsername("DVES_USER")
opts.SetPassword("DVES_PASS")
opts.SetCleanSession(false)
client := MQTT.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
fmt.Println("Sample Publisher Started")
qos := 0
for i := 0; i < 3; i++ {
fmt.Println("---- doing publish ----", i)
token := client.Publish("ciaouno", byte(qos), false, fmt.Sprintf("%d", i))
token.Wait()
time.Sleep(300 * time.Millisecond)
}
client.Disconnect(250)
fmt.Println("Sample Publisher Disconnected")
}

116
poc/mqtt2.go Normal file
View File

@@ -0,0 +1,116 @@
package main
import (
"flag"
"fmt"
"os"
MQTT "github.com/eclipse/paho.mqtt.golang"
)
/*
Options:
[-help] Display help
[-a pub|sub] Action pub (publish) or sub (subscribe)
[-m <message>] Payload to send
[-n <number>] Number of messages to send or receive
[-q 0|1|2] Quality of Service
[-clean] CleanSession (true if -clean is present)
[-id <clientid>] CliendID
[-user <user>] User
[-password <password>] Password
[-broker <uri>] Broker URI
[-topic <topic>] Topic
[-store <path>] Store Directory
*/
func main() {
topic := flag.String("topic", "", "The topic name to/from which to publish/subscribe")
broker := flag.String("broker", "tcp://iot.eclipse.org:1883", "The broker URI. ex: tcp://10.10.1.1:1883")
password := flag.String("password", "", "The password (optional)")
user := flag.String("user", "", "The User (optional)")
id := flag.String("id", "testgoid", "The ClientID (optional)")
cleansess := flag.Bool("clean", false, "Set Clean Session (default false)")
qos := flag.Int("qos", 0, "The Quality of Service 0,1,2 (default 0)")
num := flag.Int("num", 1, "The number of messages to publish or subscribe (default 1)")
payload := flag.String("message", "", "The message text to publish (default empty)")
action := flag.String("action", "", "Action publish or subscribe (required)")
store := flag.String("store", ":memory:", "The Store Directory (default use memory store)")
flag.Parse()
if *action != "pub" && *action != "sub" {
fmt.Println("Invalid setting for -action, must be pub or sub")
return
}
if *topic == "" {
fmt.Println("Invalid setting for -topic, must not be empty")
return
}
fmt.Printf("Sample Info:\n")
fmt.Printf("\taction: %s\n", *action)
fmt.Printf("\tbroker: %s\n", *broker)
fmt.Printf("\tclientid: %s\n", *id)
fmt.Printf("\tuser: %s\n", *user)
fmt.Printf("\tpassword: %s\n", *password)
fmt.Printf("\ttopic: %s\n", *topic)
fmt.Printf("\tmessage: %s\n", *payload)
fmt.Printf("\tqos: %d\n", *qos)
fmt.Printf("\tcleansess: %v\n", *cleansess)
fmt.Printf("\tnum: %d\n", *num)
fmt.Printf("\tstore: %s\n", *store)
opts := MQTT.NewClientOptions()
opts.AddBroker(*broker)
opts.SetClientID(*id)
opts.SetUsername(*user)
opts.SetPassword(*password)
opts.SetCleanSession(*cleansess)
if *store != ":memory:" {
opts.SetStore(MQTT.NewFileStore(*store))
}
if *action == "pub" {
client := MQTT.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
fmt.Println("Sample Publisher Started")
for i := 0; i < *num; i++ {
fmt.Println("---- doing publish ----")
token := client.Publish(*topic, byte(*qos), false, *payload)
token.Wait()
}
client.Disconnect(250)
fmt.Println("Sample Publisher Disconnected")
} else {
receiveCount := 0
choke := make(chan [2]string)
opts.SetDefaultPublishHandler(func(client MQTT.Client, msg MQTT.Message) {
choke <- [2]string{msg.Topic(), string(msg.Payload())}
})
client := MQTT.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
if token := client.Subscribe(*topic, byte(*qos), nil); token.Wait() && token.Error() != nil {
fmt.Println(token.Error())
os.Exit(1)
}
for receiveCount < *num {
incoming := <-choke
fmt.Printf("RECEIVED TOPIC: %s MESSAGE: %s\n", incoming[0], incoming[1])
receiveCount++
}
client.Disconnect(250)
fmt.Println("Sample Subscriber Disconnected")
}
}

83
source/board.go Normal file
View File

@@ -0,0 +1,83 @@
package main
// BoardType Status
const (
BoardTypeDummy uint = 0
BoardTypeGPIO uint = 1
BoardTypeI2CGPIO uint = 2
BoardTypeI2CADC uint = 3
)
// Board def
type Board struct {
ChannelCount uint `json:"channelcount"`
ID string `json:"id"`
Name string `json:"name"`
Type uint `json:"type"`
Bus uint `json:"bus"`
Address uint `json:"address"`
dummyValue map[uint]bool
}
// ChannelName - return the name of a channel, useful for onboard GPIO
func (b *Board) ChannelName(num uint) string {
return string(num)
}
// PowerON def
func (b *Board) PowerON(num uint) error {
switch b.Type {
case BoardTypeDummy:
if b.dummyValue == nil {
b.dummyValue = make(map[uint]bool)
}
b.dummyValue[num] = true
return nil
}
return nil
}
// PowerOFF def
func (b *Board) PowerOFF(num uint) error {
switch b.Type {
case BoardTypeDummy:
if b.dummyValue == nil {
b.dummyValue = make(map[uint]bool)
}
b.dummyValue[num] = false
return nil
}
return nil
}
// PowerToggle def
func (b *Board) PowerToggle(num uint) error {
switch b.Type {
case BoardTypeDummy:
if b.dummyValue == nil {
b.dummyValue = make(map[uint]bool)
}
v, _ := b.PowerStatus(num)
b.dummyValue[num] = !v
return nil
}
return nil
}
// PowerStatus def
func (b *Board) PowerStatus(num uint) (bool, error) {
switch b.Type {
case BoardTypeDummy:
if b.dummyValue == nil {
b.dummyValue = make(map[uint]bool)
}
val, ok := b.dummyValue[num]
if !ok {
b.dummyValue[num] = false
val = false
}
return val, nil
}
return false, nil
}

45
source/boardlink.go Normal file
View File

@@ -0,0 +1,45 @@
package main
// Boardlink def
type Boardlink struct {
BoardID string `json:"boardid"`
Channel uint `json:"channel"`
board *Board
}
// Board def
func (bl Boardlink) Board() *Board {
if bl.board == nil {
for i := range TheConfig.Boards {
if TheConfig.Boards[i].ID == bl.BoardID {
bl.board = TheConfig.Boards[i]
break
}
}
}
return bl.board
}
// PowerON def
func (bl Boardlink) PowerON() error {
b := bl.Board()
return b.PowerON(bl.Channel)
}
// PowerOFF def
func (bl Boardlink) PowerOFF() error {
b := bl.Board()
return b.PowerOFF(bl.Channel)
}
// PowerToggle def
func (bl Boardlink) PowerToggle() error {
b := bl.Board()
return b.PowerToggle(bl.Channel)
}
// PowerStatus def
func (bl Boardlink) PowerStatus() (bool, error) {
b := bl.Board()
return b.PowerStatus(bl.Channel)
}

View File

@@ -1,20 +1,45 @@
package main package main
import ( import (
"log" "encoding/json"
"io/ioutil"
"github.com/spf13/viper"
) )
func readConfig() { // Configuration def
viper.SetConfigName("openpdu.yaml") // name of config file (without extension) type Configuration struct {
viper.SetConfigType("yaml") Hostname string `json:"hostname"`
viper.AddConfigPath("/etc/openpdu/") // path to look for the config file in Outlets map[(uint)]Outlet `json:"outlets"`
viper.AddConfigPath(".") // optionally look for config in the working directory Boards []*Board `json:"boards"`
err := viper.ReadInConfig() // Find and read the config file MQTT MQTTConfig `json:"mqtt"`
if err != nil { // Handle errors reading the config file
log.Printf("Fatal error config file: %s \n", err)
} }
viper.SetDefault("hostname", "openpdu") // TheConfig def
var TheConfig Configuration
func loadConfig(filename string) (Configuration, error) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return Configuration{}, err
}
var c Configuration
err = json.Unmarshal(bytes, &c)
if err != nil {
return Configuration{}, err
}
return c, nil
}
func saveConfig(c Configuration, filename string) error {
bytes, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(filename, bytes, 0644)
}
func writeConfig() error {
return saveConfig(TheConfig, "t1.json")
} }

View File

@@ -1,27 +1,38 @@
package main package main
import ( // Dictionary definition
// "encoding/json"
"log"
// "periph.io/x/periph"
"github.com/spf13/viper"
)
// Dictionary aaa
type Dictionary map[string]interface{} type Dictionary map[string]interface{}
func initOutlets() {
for _, o := range TheConfig.Outlets {
o.PowerInitial()
}
}
func main() { func main() {
var err error
readConfig() var c1 Configuration
nam := viper.Get("hostname") c1 = createMockConfig()
log.Printf("hostname: %v\n", nam)
initI2C() err = saveConfig(c1, "t.json")
if err != nil {
panic(err)
}
TheConfig, err = loadConfig("t.json")
if err != nil {
panic(err)
}
err = saveConfig(TheConfig, "t1.json")
if err != nil {
panic(err)
}
startServer() startServer()
} }
// https://github.com/ColorlibHQ/AdminLTE/archive/v2.4.17.tar.gz // https://github.com/ColorlibHQ/AdminLTE/archive/v2.4.17.tar.gz

101
source/mock.go Normal file
View File

@@ -0,0 +1,101 @@
package main
func createMockConfig() Configuration {
b1 := new(Board)
b1.ID = "47e41dc9-4a14-4b79-8644-d7442a15cb50"
b1.Name = "Virtual IO"
b1.Type = BoardTypeDummy
b1.ChannelCount = 40
b2 := new(Board)
b2.ID = "6561df75-bf93-43f5-82ac-9b3dda081961"
b2.Name = "Internal GPIO"
b2.Type = BoardTypeGPIO
b2.ChannelCount = 40
b3 := new(Board)
b3.Bus = 1
b3.Address = 0x29
b3.ID = "79690164-214f-41b0-93f9-e910dd54f323"
b3.Name = "bordo1"
b3.Type = BoardTypeI2CGPIO
b3.ChannelCount = 8
b4 := new(Board)
b4.Bus = 1
b4.Address = 0x27
b4.ID = "93f446d8-59e4-4abd-8bf7-e31cd80bc713"
b4.Name = "bordo2"
b4.Type = BoardTypeI2CADC
b4.ChannelCount = 4
return Configuration{
Hostname: "maramao",
MQTT: MQTTConfig{
BrokerIP: "192.168.2.190",
BrokerPort: "1883",
ClientID: "openpdu-123",
Username: "DVES_USER",
Password: "DVES_PASS",
CleanSession: false,
Topic: "openpdu/ok",
HomeAssistant: true,
},
Boards: []*Board{b1, b2, b3, b4},
Outlets: map[(uint)]Outlet{
0: Outlet{
Name: "uscita 0",
Location: "port 1 dx",
HasPowerMeter: true,
Command: Boardlink{
BoardID: b1.ID,
Channel: 0,
},
PowerMeter: Boardlink{
BoardID: b4.ID,
Channel: 0,
},
},
1: Outlet{
Name: "uscita 1",
Location: "port 1 sx",
HasPowerMeter: true,
Command: Boardlink{
BoardID: b1.ID,
Channel: 1,
},
PowerMeter: Boardlink{
BoardID: b4.ID,
Channel: 1,
},
},
2: Outlet{
Name: "uscita 2",
Location: "port 2 dx",
HasPowerMeter: false,
Command: Boardlink{
BoardID: "79690164-214f-41b0-93f9-e910dd54f323",
Channel: 2,
},
},
3: Outlet{
Name: "uscita 5v 1",
Location: "usb avanti 1",
HasPowerMeter: false,
Command: Boardlink{
BoardID: "47e41dc9-4a14-4b79-8644-d7442a15cb50",
Channel: 21,
},
},
4: Outlet{
Name: "uscita 5v 2",
Location: "usb avanti 2",
HasPowerMeter: false,
Command: Boardlink{
BoardID: "47e41dc9-4a14-4b79-8644-d7442a15cb50",
Channel: 22,
},
},
},
}
}

13
source/mqtt.go Normal file
View File

@@ -0,0 +1,13 @@
package main
// MQTTConfig def
type MQTTConfig struct {
BrokerIP string `json:"ip"`
BrokerPort string `json:"port"`
ClientID string `json:"clientid"`
Username string `json:"username"`
Password string `json:"password"`
CleanSession bool `json:"cleansession"`
Topic string `json:"topic"`
HomeAssistant bool `json:"homeassistant"`
}

49
source/outlet.go Normal file
View File

@@ -0,0 +1,49 @@
package main
// Outlet Initial Status
const (
OutletInitialStatusOFF uint = 0
OutletInitialStatusON uint = 1
OutletInitialStatusLAST uint = 2 // if hardware supported
)
// Outlet def
type Outlet struct {
Name string `json:"name"`
Location string `json:"location"`
HasPowerMeter bool `json:"haspowermeter"`
InitialStatus uint `json:"initialstatus"`
Command Boardlink `json:"command"`
PowerMeter Boardlink `json:"powermeter"`
}
// PowerON def
func (o Outlet) PowerON() error {
return o.Command.PowerON()
}
// PowerOFF def
func (o Outlet) PowerOFF() error {
return o.Command.PowerOFF()
}
// PowerToggle def
func (o Outlet) PowerToggle() error {
return o.Command.PowerToggle()
}
// PowerInitial def
func (o Outlet) PowerInitial() error {
switch o.InitialStatus {
case OutletInitialStatusOFF:
return o.Command.PowerOFF()
case OutletInitialStatusON:
return o.Command.PowerON()
}
return nil
}
// PowerStatus def
func (o Outlet) PowerStatus() (bool, error) {
return o.Command.PowerStatus()
}

34
source/web.go Normal file
View File

@@ -0,0 +1,34 @@
package main
import (
"log"
"net/http"
"github.com/go-macaron/binding"
"github.com/go-macaron/pongo2"
"gopkg.in/macaron.v1"
)
func startServer() {
m := macaron.Classic()
m.Use(pongo2.Pongoer())
m.Use(macaron.Static("static"))
// m.Get("/", myHandler)
m.Get("/", statusPage)
m.Get("/json/status", jsonStatus)
m.Post("/json/outlet/:outlet/on", jsonOutletPowerON)
m.Post("/json/outlet/:outlet/off", jsonOutletPowerOFF)
m.Post("/json/outlet/:outlet/toggle", jsonOutletPowerToggle)
m.Get("/settings/mqtt", webGETSettingsMQTT)
m.Post("/json/settings/mqtt", binding.Bind(SettingsMQTTForm{}), webPOSTSettingsMQTT)
// m.Post("/settings/mqtt", webPOSTSettingsMQTT)
m.Get("/boards", func(ctx *macaron.Context) {
ctx.HTML(200, "boards") // 200 is the response code.
})
log.Println("Server is running...")
log.Println(http.ListenAndServe("0.0.0.0:4000", m))
}

129
source/webjson.go Normal file
View File

@@ -0,0 +1,129 @@
package main
import (
"fmt"
"net/http"
"strconv"
"gopkg.in/macaron.v1"
)
func jsonStatus(ctx *macaron.Context) {
out := [][]string{}
for num, o := range TheConfig.Outlets {
pwr, _ := o.PowerStatus()
pwrstr := "0"
if pwr {
pwrstr = "1"
}
out = append(out, []string{fmt.Sprintf("%d", num), o.Name, pwrstr})
}
ctx.JSON(http.StatusOK, Dictionary{
"data": out,
})
}
func jsonOutletPowerToggle(ctx *macaron.Context) {
var outletnum64 uint64
var err error
outletnum64, err = strconv.ParseUint(ctx.Params(":outlet"), 10, 32)
if err != nil {
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "No outlet specified",
})
}
outletnum := uint(outletnum64)
outlet, exists := TheConfig.Outlets[outletnum]
if !exists {
// error: outlet doesn't exists
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "Outlet doesn't exists",
})
}
err = outlet.PowerToggle()
if err != nil {
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "Can't toggle power",
})
}
ctx.JSON(http.StatusOK, Dictionary{
"data": "ok",
})
}
func jsonOutletPowerON(ctx *macaron.Context) {
var outletnum64 uint64
var err error
outletnum64, err = strconv.ParseUint(ctx.Params(":outlet"), 10, 32)
if err != nil {
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "No outlet specified",
})
}
outletnum := uint(outletnum64)
outlet, exists := TheConfig.Outlets[outletnum]
if !exists {
// error: outlet doesn't exists
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "Outlet doesn't exists",
})
}
err = outlet.PowerON()
if err != nil {
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "Can't toggle power",
})
}
ctx.JSON(http.StatusOK, Dictionary{
"data": "ok",
})
}
func jsonOutletPowerOFF(ctx *macaron.Context) {
var outletnum64 uint64
var err error
outletnum64, err = strconv.ParseUint(ctx.Params(":outlet"), 10, 32)
if err != nil {
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "No outlet specified",
})
}
outletnum := uint(outletnum64)
outlet, exists := TheConfig.Outlets[outletnum]
if !exists {
// error: outlet doesn't exists
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "Outlet doesn't exists",
})
}
err = outlet.PowerOFF()
if err != nil {
ctx.JSON(http.StatusOK, Dictionary{
"data": "error",
"error": "Can't toggle power",
})
}
ctx.JSON(http.StatusOK, Dictionary{
"data": "ok",
})
}

66
source/webpages.go Normal file
View File

@@ -0,0 +1,66 @@
package main
import (
"net/http"
"gopkg.in/macaron.v1"
)
func statusPage(ctx *macaron.Context) {
var pluglist []Dictionary
for num, o := range TheConfig.Outlets {
pluglist = append(pluglist, Dictionary{"id": num, "description": o.Name})
}
ctx.Data["pluglist"] = pluglist
ctx.HTML(200, "status") // 200 is the response code.
}
//SettingsMQTTForm definition
type SettingsMQTTForm struct {
BrokerIP string `form:"brokerip" binding:"Required"`
BrokerPort string `form:"brokerport" binding:"Required"`
ClientID string `form:"clientid" binding:"Required"`
Username string `form:"username"`
Password string `form:"password"`
Topic string `form:"topic" binding:"Required"`
CleanSession bool `form:"cleansession"`
HomeAssistant bool `form:"homeassistant"`
}
func webGETSettingsMQTT(ctx *macaron.Context) {
ctx.Data["r_brokerip"] = TheConfig.MQTT.BrokerIP
ctx.Data["r_brokerport"] = TheConfig.MQTT.BrokerPort
ctx.Data["r_clientid"] = TheConfig.MQTT.ClientID
ctx.Data["r_username"] = TheConfig.MQTT.Username
ctx.Data["r_password"] = TheConfig.MQTT.Password
ctx.Data["r_cleansession"] = TheConfig.MQTT.CleanSession
ctx.Data["r_topic"] = TheConfig.MQTT.Topic
ctx.Data["r_homeassistant"] = TheConfig.MQTT.HomeAssistant
ctx.HTML(200, "settings_mqtt") // 200 is the response code.
}
func webPOSTSettingsMQTT(ctx *macaron.Context, f SettingsMQTTForm) {
TheConfig.MQTT.BrokerIP = f.BrokerIP
TheConfig.MQTT.BrokerPort = f.BrokerPort
TheConfig.MQTT.ClientID = f.ClientID
TheConfig.MQTT.Username = f.Username
TheConfig.MQTT.Password = f.Password
TheConfig.MQTT.Topic = f.Topic
// TODO: cleansession
// TODO: homeassistant
err := writeConfig()
if err != nil {
ctx.JSON(http.StatusOK, Dictionary{
"result": "error",
"error": err.Error(),
})
}
ctx.JSON(http.StatusOK, Dictionary{
"result": "ok",
})
}

View File

@@ -1,113 +0,0 @@
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"github.com/go-macaron/pongo2"
"gopkg.in/macaron.v1"
)
func statusPage(ctx *macaron.Context) {
ctx.Data["pluglist"] = []Dictionary{
{"id": 1, "description": "p1"},
{"id": 2, "description": "p2"},
{"id": 3, "description": "p3"},
{"id": 4, "description": "p4"},
{"id": 5, "description": "p5"},
{"id": 6, "description": "p6"},
{"id": 7, "description": "p7"},
{"id": 8, "description": "p8"},
}
ctx.HTML(200, "status") // 200 is the response code.
}
func jsonStatus(ctx *macaron.Context) {
p0, err := MyBoard.channelStatus(0)
if err != nil {
log.Fatal(err)
}
p1, err := MyBoard.channelStatus(1)
if err != nil {
log.Fatal(err)
}
p2, err := MyBoard.channelStatus(2)
if err != nil {
log.Fatal(err)
}
p3, err := MyBoard.channelStatus(3)
if err != nil {
log.Fatal(err)
}
p4, err := MyBoard.channelStatus(4)
if err != nil {
log.Fatal(err)
}
p5, err := MyBoard.channelStatus(5)
if err != nil {
log.Fatal(err)
}
p6, err := MyBoard.channelStatus(6)
if err != nil {
log.Fatal(err)
}
p7, err := MyBoard.channelStatus(7)
if err != nil {
log.Fatal(err)
}
ctx.JSON(http.StatusOK, Dictionary{
"data": [][]string{
{"0", "p0", fmt.Sprint(p0)},
{"1", "p1", fmt.Sprint(p1)},
{"2", "p2", fmt.Sprint(p2)},
{"3", "p3", fmt.Sprint(p3)},
{"4", "p4", fmt.Sprint(p4)},
{"5", "p5", fmt.Sprint(p5)},
{"6", "p6", fmt.Sprint(p6)},
{"7", "p7", fmt.Sprint(p7)},
},
})
}
func jsonOutletToggle(ctx *macaron.Context) {
id, err := strconv.ParseUint(ctx.Params(":id"), 10, 64)
if err != nil {
log.Fatal(err)
}
err = MyBoard.channelToggle(uint(id))
if err != nil {
log.Fatal(err)
}
ctx.JSON(http.StatusOK, Dictionary{
"data": "ok",
})
}
func startServer() {
m := macaron.Classic()
m.Use(pongo2.Pongoer())
m.Use(macaron.Static("static"))
// m.Get("/", myHandler)
m.Get("/", statusPage)
m.Get("/json/status", jsonStatus)
m.Post("/json/outlet/:id/toggle", jsonOutletToggle)
m.Get("/boards", func(ctx *macaron.Context) {
ctx.HTML(200, "boards") // 200 is the response code.
})
log.Println("Server is running...")
log.Println(http.ListenAndServe("0.0.0.0:4000", m))
}

View File

@@ -72,7 +72,7 @@ func (b I2CBoard) channelToggle(ch uint) error {
return nil return nil
} }
func initI2C() { func init() {
// Make sure periph is initialized. // Make sure periph is initialized.
if _, err := host.Init(); err != nil { if _, err := host.Init(); err != nil {
log.Fatal(err) log.Fatal(err)
@@ -101,14 +101,4 @@ func initI2C() {
MyBoard.i2cdev = *mydevice MyBoard.i2cdev = *mydevice
MyBoard.data = make([]byte, (MyBoard.channels-1)/8+1) MyBoard.data = make([]byte, (MyBoard.channels-1)/8+1)
go func() {
var i uint
for i = 0; i < 8; i++ {
v, err := MyBoard.channelStatus(i)
if err != nil {
log.Fatal(err)
}
log.Printf("Channel %d status: %v", i, v)
}
}()
} }

View File

@@ -82,8 +82,8 @@
<!-- DataTables --> <!-- DataTables -->
<script src="../../bower_components/datatables.net/js/jquery.dataTables.min.js"></script> <script src="/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="../../bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script> <script src="/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<script> <script>
$(function () { $(function () {

View File

@@ -4,16 +4,16 @@
<!-- Tell the browser to be responsive to screen width --> <!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.7 --> <!-- Bootstrap 3.3.7 -->
<link rel="stylesheet" href="../../bower_components/bootstrap/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
<!-- Font Awesome --> <!-- Font Awesome -->
<link rel="stylesheet" href="../../bower_components/font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="/bower_components/font-awesome/css/font-awesome.min.css">
<!-- Ionicons --> <!-- Ionicons -->
<link rel="stylesheet" href="../../bower_components/Ionicons/css/ionicons.min.css"> <link rel="stylesheet" href="/bower_components/Ionicons/css/ionicons.min.css">
<!-- Theme style --> <!-- Theme style -->
<link rel="stylesheet" href="adminlte/css/AdminLTE.min.css"> <link rel="stylesheet" href="/adminlte/css/AdminLTE.min.css">
<!-- AdminLTE Skins. Choose a skin from the css/skins <!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. --> folder instead of downloading all of them to reduce the load. -->
<link rel="stylesheet" href="adminlte/css/skins/_all-skins.min.css"> <link rel="stylesheet" href="/adminlte/css/skins/_all-skins.min.css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
@@ -23,4 +23,4 @@
<![endif]--> <![endif]-->
<!-- Google Font --> <!-- Google Font -->
<link rel="stylesheet" href="../../googlefonts/fonts.css"> <link rel="stylesheet" href="/googlefonts/fonts.css">

View File

@@ -1,10 +1,10 @@
<!-- jQuery 3 --> <!-- jQuery 3 -->
<script src="../../bower_components/jquery/dist/jquery.min.js"></script> <script src="/bower_components/jquery/dist/jquery.min.js"></script>
<!-- Bootstrap 3.3.7 --> <!-- Bootstrap 3.3.7 -->
<script src="../../bower_components/bootstrap/dist/js/bootstrap.min.js"></script> <script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<!-- Slimscroll --> <!-- Slimscroll -->
<script src="../../bower_components/jquery-slimscroll/jquery.slimscroll.min.js"></script> <script src="/bower_components/jquery-slimscroll/jquery.slimscroll.min.js"></script>
<!-- FastClick --> <!-- FastClick -->
<script src="../../bower_components/fastclick/lib/fastclick.js"></script> <script src="/bower_components/fastclick/lib/fastclick.js"></script>
<!-- AdminLTE App --> <!-- AdminLTE App -->
<script src="adminlte/js/adminlte.min.js"></script> <script src="/adminlte/js/adminlte.min.js"></script>

View File

@@ -1,6 +1,6 @@
<header class="main-header"> <header class="main-header">
<!-- Logo --> <!-- Logo -->
<a href="../../index2.html" class="logo"> <a href="/" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels --> <!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini">O<b>P</b></span> <span class="logo-mini">O<b>P</b></span>
<!-- logo for regular state and mobile devices --> <!-- logo for regular state and mobile devices -->

View File

@@ -26,19 +26,46 @@
</a> </a>
</li> </li>
{% if pageselected == "settings" || pageselected == "settings/mqtt" %}
<li class="treeview menu-open">
{% else %}
<li class="treeview"> <li class="treeview">
{% endif %}
<a href="#"> <a href="#">
<i class="fa fa-gears"></i> <span>Settings</span> <i class="fa fa-gears"></i> <span>Settings</span>
<span class="pull-right-container"> <span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i> <i class="fa fa-angle-left pull-right"></i>
</span> </span>
</a> </a>
<ul class="treeview-menu">
<li><a href="/settings/lan"><i class="fa fa-circle-o"></i> LAN</a></li> {% if pageselected == "settings" || pageselected == "settings/mqtt" %}
<li><a href="/settings/mqtt"><i class="fa fa-circle-o"></i> MQTT</a></li> <ul class="treeview-menu" style="display: block;">
<li><a href="/settings/syslog"><i class="fa fa-circle-o"></i> syslog</a></li> {% else %}
<li><a href="/settings/backup"><i class="fa fa-circle-o"></i> backup</a></li> <ul class="treeview-menu" style="display: none;">
<li><a href="/settings/restore"><i class="fa fa-circle-o"></i> restore</a></li> {% endif %}
<li>
<a href="/settings/lan"><i class="fa fa-circle-o"></i> LAN</a>
</li>
{% if pageselected == "settings/mqtt" %}
<li class="active">
{% else %}
<li>
{% endif %}
<a href="/settings/mqtt"><i class="fa fa-circle-o"></i> MQTT</a>
</li>
<li>
<a href="/settings/syslog"><i class="fa fa-circle-o"></i> syslog</a>
</li>
<li>
<a href="/settings/backup"><i class="fa fa-circle-o"></i> backup</a>
</li>
<li>
<a href="/settings/restore"><i class="fa fa-circle-o"></i> restore</a>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>

View File

@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html>
<head>
{% include "common/common-head.html" %}
</head>
<body class="hold-transition skin-blue sidebar-mini">
<div class="wrapper">
{% include "common/page-header.html" %}
{% with pageselected="settings/mqtt" %}
{% include "common/sidebar-menu.html" %}
{% endwith %}
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
MQTT configuration
</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="row" id="notification-success" style="display: none;">
<div class="col-md-12">
<div class="box box-success box-solid">
<div class="box-header with-border">
<h3 class="box-title">Settings saved!</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="remove"><i
class="fa fa-times"></i></button>
</div>
<!-- /.box-tools -->
</div>
<!-- /.box-header -->
<div class="box-body">
The body of the box
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<!-- /.col -->
</div>
<div class="row" id="notification-error" style="display: none;">
<div class="col-md-12">
<div class="box box-danger box-solid">
<div class="box-header with-border">
<h3 class="box-title">Error saving settings!</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="remove"><i
class="fa fa-times"></i></button>
</div>
<!-- /.box-tools -->
</div>
<!-- /.box-header -->
<div class="box-body">
The body of the box
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<!-- /.col -->
</div>
<div class="row">
<!-- left column -->
<div class="col-md-12">
<!-- general form elements -->
<div class="box box-primary">
<!-- form start -->
<form role="form" id="theform" class="form-horizontal">
<div class="box-body">
<div class="form-group">
<label class="col-sm-2 control-label" for="brokerip">Broker IP address</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="brokerip" placeholder="MQTT broker IP address or hostname" value="{{ r_brokerip }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="brokerport">Broker port</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="brokerport" placeholder="MQTT broker port" value="{{ r_brokerport }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="clientid">MQTT Client ID</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="clientid" placeholder="MQTT client ID" value="{{ r_clientid }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="username">Username</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="username" placeholder="MQTT broker username" value="{{ r_username }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="password">Password</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="password" placeholder="MQTT broker password" value="{{ r_password }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="topic">Topic</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="topic" placeholder="MQTT topic" value="{{ r_topic }}">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<!-- value !!!! -->
<input type="checkbox" id="cleansession"> Clean session
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<!-- value !!!! -->
<input type="checkbox" id="homeassistant"> HomeAssistant auto discovery
</label>
</div>
</div>
</div>
</div>
<!-- /.box-body -->
<div class="box-footer">
<button type="submit" class="btn btn-primary pull-right">Save</button>
</div>
</form>
</div>
<!-- /.box -->
</div>
<!--/.col (right) -->
</div>
<!-- /.row -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
{% include "common/footer.html" %}
</div>
<!-- ./wrapper -->
{% include "common/common-js.html" %}
<script>
$(function () {
$('#brokerip').select();
$('#theform').on( 'submit', function (e) {
e.preventDefault();
$.ajax({
url: '/json/settings/mqtt',
type: 'post',
data: {
'brokerip': $('#brokerip').val(),
'brokerport': $('#brokerport').val(),
'clientid': $('#clientid').val(),
'username': $('#username').val(),
'password': $('#password').val(),
'topic': $('#topic').val(),
'cleansession': $('#cleansession').val(),
'homeassistant': $('#homeassistant').val(),
},
})
.done(function(r) {
if (r.result=='ok') {
$('#notification-success .box-body').text('MQTT settings saved.');
$("#notification-success").show("fast");
setTimeout(function(){
$("#notification-success").fadeOut("slow");
},1000);
} else {
$('#notification-error .box-body').text('Error saving MQTT settings.');
$("#notification-error").show("fast");
setTimeout(function(){
$("#notification-error").fadeOut("slow");
},5000);
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
var responseText = jQuery.parseJSON(jqXHR.responseText);
$('#notification-error .box-body').text('Something went wrong. Settings not saved.');
$("#notification-error").show("fast");
setTimeout(function(){
$("#notification-error").fadeOut("slow");
},5000);
});
});
})
</script>
</body>
</html>

View File

@@ -65,8 +65,8 @@
{% include "common/common-js.html" %} {% include "common/common-js.html" %}
<!-- DataTables --> <!-- DataTables -->
<script src="../../bower_components/datatables.net/js/jquery.dataTables.min.js"></script> <script src="/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
<script src="../../bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script> <script src="/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<script> <script>
$(function () { $(function () {

42
v.go Normal file
View File

@@ -0,0 +1,42 @@
package main
import "github.com/spf13/viper"
// Board def
type Board struct {
ChannelCount uint `json:"channelcount"`
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
}
// Configuration def
type Configuration struct {
Hostname string `json:"hostname"`
// Outlets map[(uint)]Outlet `json:"outlets"`
Boards []Board `json:"boards"`
// MQTT MQTTConfig `json:"mqtt"`
}
// Boards definition
var Boards []Board
func main() {
var err error
viper.SetConfigName("v")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
mock()
}
func mock() {
b := Board{
ID: "6561df75-bf93-43f5-82ac-9b3dda081961",
Name: "Internal GPIO",
Type: "GPIORelayBoard",
ChannelCount: 40,
}
Boards = append(Boards, b)
}