forked from OpenPDU/openpdu
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d253644bd | |||
| 93846dbbea | |||
| f781e24028 | |||
| fb819a71c4 | |||
| 82c33f9277 | |||
| c4567d780c | |||
| 56ad55b967 |
18
poc/homeassistant_autodiscover.txt
Normal file
18
poc/homeassistant_autodiscover.txt
Normal 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
3
poc/info.txt
Normal 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
37
poc/mqtt.go
Normal 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
116
poc/mqtt2.go
Normal 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
83
source/board.go
Normal 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
45
source/boardlink.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
|
// TheConfig def
|
||||||
|
var TheConfig Configuration
|
||||||
|
|
||||||
|
func loadConfig(filename string) (Configuration, error) {
|
||||||
|
bytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return Configuration{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.SetDefault("hostname", "openpdu")
|
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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
101
source/mock.go
Normal 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
13
source/mqtt.go
Normal 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
49
source/outlet.go
Normal 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
34
source/web.go
Normal 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
129
source/webjson.go
Normal 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
66
source/webpages.go
Normal 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",
|
||||||
|
})
|
||||||
|
}
|
||||||
113
source/webui.go
113
source/webui.go
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
@@ -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 () {
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
242
templates/settings_mqtt.html
Normal file
242
templates/settings_mqtt.html
Normal 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>
|
||||||
|
|
||||||
@@ -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
42
v.go
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user