initial commit
This commit is contained in:
34
internal/Config/Config.go
Normal file
34
internal/Config/Config.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package Config
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"io/ioutil"
|
||||
"rpiMqttControl/internal/MqttService"
|
||||
"rpiMqttControl/internal/PinControlService"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
PinControlConfig PinControlService.PinControlConfig `yaml:"pin-control-config"`
|
||||
MqttConfig MqttService.MqttServiceConfig `yaml:"mqtt-config"`
|
||||
}
|
||||
|
||||
func NewConfig() Config {
|
||||
return Config{
|
||||
PinControlConfig: PinControlService.NewPinControlConfig(),
|
||||
MqttConfig: MqttService.NewMqttServiceConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewConfigFromYamlFile(configFile string) (*Config, error) {
|
||||
config := NewConfig()
|
||||
if data, err := ioutil.ReadFile(configFile); err == nil {
|
||||
if err := yaml.Unmarshal(data, &config); err == nil {
|
||||
return &config, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
17
internal/MqttService/Helpers.go
Normal file
17
internal/MqttService/Helpers.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package MqttService
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PinNameFromTopic(topic string) (string,error) {
|
||||
topicComponents := strings.Split(topic, "/")
|
||||
if len(topicComponents) >= 2 {
|
||||
gpioCh := topicComponents[len(topicComponents)-2]
|
||||
return gpioCh, nil
|
||||
|
||||
} else {
|
||||
return "", errors.New("invalid topic")
|
||||
}
|
||||
}
|
||||
102
internal/MqttService/MqttService.go
Normal file
102
internal/MqttService/MqttService.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package MqttService
|
||||
|
||||
import (
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"rpiMqttControl/internal/PinControlService"
|
||||
"time"
|
||||
)
|
||||
|
||||
type OnCompletionHandler func()
|
||||
|
||||
type MqttService struct {
|
||||
mqttClient mqtt.Client
|
||||
quit chan struct{}
|
||||
config MqttServiceConfig
|
||||
pinService *PinControlService.PinControlService
|
||||
}
|
||||
|
||||
func (m *MqttService) pinEventCallback(pinName string, pinState PinControlService.PinState) {
|
||||
topic := m.config.TopicPrefix + "/gpio/" + pinName + "/event"
|
||||
token := m.mqttClient.Publish(topic, 1, false, string(pinState))
|
||||
go m._asyncWait(token, nil)
|
||||
}
|
||||
|
||||
func (m *MqttService) pinCyclicCallback(pinName string, pinState PinControlService.PinState) {
|
||||
topic := m.config.TopicPrefix + "/gpio/" + pinName + "/value"
|
||||
token := m.mqttClient.Publish(topic, 1, false, string(pinState))
|
||||
go m._asyncWait(token, nil)
|
||||
}
|
||||
func (m *MqttService) controlCallback(client mqtt.Client, message mqtt.Message) {
|
||||
if pinNo, err := PinNameFromTopic(message.Topic()); err == nil {
|
||||
if err := m.pinService.Command(pinNo, PinControlService.PinCommand(message.Payload())); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
func (m *MqttService) Stop() {
|
||||
m.quit <- struct{}{}
|
||||
m.mqttClient.Disconnect(100)
|
||||
}
|
||||
|
||||
func (m *MqttService) _asyncWait(token mqtt.Token, onComplete OnCompletionHandler) {
|
||||
for {
|
||||
select {
|
||||
case <-token.Done():
|
||||
if err := token.Error(); err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
if onComplete != nil {
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
return
|
||||
case <-m.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func (m *MqttService) _subscribe() {
|
||||
topic := m.config.TopicPrefix + "/gpio/+/control"
|
||||
token := m.mqttClient.Subscribe(topic, 1, m.controlCallback)
|
||||
|
||||
go m._asyncWait(token, nil)
|
||||
}
|
||||
|
||||
func (m *MqttService) Start() {
|
||||
token := m.mqttClient.Connect()
|
||||
go m._asyncWait(token, func() {
|
||||
m._subscribe()
|
||||
})
|
||||
}
|
||||
|
||||
func NewMqttService(config MqttServiceConfig, pinService *PinControlService.PinControlService) MqttService {
|
||||
clientOptions := mqtt.NewClientOptions()
|
||||
clientOptions.AddBroker(config.BrokerAddress)
|
||||
|
||||
if config.Username != "" {
|
||||
clientOptions.Username = config.Username
|
||||
clientOptions.Password = config.Password
|
||||
}
|
||||
|
||||
clientOptions.SetConnectRetry(true)
|
||||
clientOptions.SetConnectRetryInterval(10 * time.Second)
|
||||
clientOptions.SetAutoReconnect(config.AutoReconnect)
|
||||
clientOptions.SetConnectTimeout(time.Duration(config.ConnectTimeoutMs) * time.Millisecond)
|
||||
|
||||
mqttClient := mqtt.NewClient(clientOptions)
|
||||
|
||||
m := MqttService{
|
||||
mqttClient: mqttClient,
|
||||
quit: make(chan struct{}, 1),
|
||||
pinService: pinService,
|
||||
config: config,
|
||||
}
|
||||
|
||||
pinService.OnCycleCallback = m.pinCyclicCallback
|
||||
pinService.OnChangeCallback = m.pinEventCallback
|
||||
|
||||
return m
|
||||
}
|
||||
21
internal/MqttService/MqttServiceConfig.go
Normal file
21
internal/MqttService/MqttServiceConfig.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package MqttService
|
||||
|
||||
type MqttServiceConfig struct {
|
||||
BrokerAddress string `yaml:"broker-address"`
|
||||
TopicPrefix string `yaml:"topic-prefix"`
|
||||
AutoReconnect bool `yaml:"auto-reconnect"`
|
||||
ConnectTimeoutMs int `yaml:"connect-timeout-ms"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
|
||||
}
|
||||
|
||||
func NewMqttServiceConfig() MqttServiceConfig {
|
||||
return MqttServiceConfig{
|
||||
BrokerAddress: "tcp://localhost:1883",
|
||||
TopicPrefix: "/rpicontrol",
|
||||
AutoReconnect: true,
|
||||
ConnectTimeoutMs: 10000,
|
||||
|
||||
}
|
||||
}
|
||||
101
internal/PinControlService/Pin.go
Normal file
101
internal/PinControlService/Pin.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package PinControlService
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stianeikeland/go-rpio"
|
||||
)
|
||||
|
||||
type Pin struct {
|
||||
Id int
|
||||
Name string
|
||||
Direction PinDirection
|
||||
PullConfig PinPull
|
||||
InitialState PinCommand
|
||||
PinHandle rpio.Pin
|
||||
SendPollingEvents bool
|
||||
SendChangeEvents bool
|
||||
|
||||
}
|
||||
|
||||
func NewPin(config PinConfig) Pin {
|
||||
p := Pin{
|
||||
Direction: config.Direction,
|
||||
PullConfig: config.PullConfig,
|
||||
Name: config.Name,
|
||||
Id: config.PinNumber,
|
||||
PinHandle: rpio.Pin(config.PinNumber),
|
||||
}
|
||||
|
||||
if config.SendPollingEvents != nil {
|
||||
p.SendPollingEvents = *config.SendPollingEvents
|
||||
} else {
|
||||
p.SendPollingEvents = true
|
||||
}
|
||||
|
||||
if config.SendChangeEvents != nil {
|
||||
p.SendChangeEvents = *config.SendChangeEvents
|
||||
} else {
|
||||
p.SendChangeEvents = true
|
||||
}
|
||||
|
||||
if config.InitialState != "" {
|
||||
p.InitialState = PinCommand(config.InitialState)
|
||||
} else {
|
||||
p.InitialState = Off
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Pin) State() PinState {
|
||||
if state := p.PinHandle.Read(); state == rpio.High {
|
||||
return StateOn
|
||||
} else {
|
||||
return StateOff
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pin) Command(cmd PinCommand) error{
|
||||
log.Debugf("try to send command %s for pin %s (pin no: %d)", cmd, p.Name, p.Id)
|
||||
if p.Direction != Output {
|
||||
return errors.New("pin is not an output")
|
||||
}
|
||||
|
||||
if cmd == On {
|
||||
p.PinHandle.High()
|
||||
} else if cmd == Off {
|
||||
p.PinHandle.Low()
|
||||
} else if cmd == Toggle {
|
||||
p.PinHandle.Toggle()
|
||||
} else {
|
||||
return errors.New("unknown command")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pin) Configure() {
|
||||
if p.Direction == Input {
|
||||
p.PinHandle.Input()
|
||||
} else if p.Direction == Output {
|
||||
p.PinHandle.Output()
|
||||
_ = p.Command(p.InitialState)
|
||||
}
|
||||
|
||||
p.PinHandle.Detect(rpio.AnyEdge)
|
||||
|
||||
if p.PullConfig == PullUp {
|
||||
p.PinHandle.PullUp()
|
||||
} else if p.PullConfig == PullDown {
|
||||
p.PinHandle.PullDown()
|
||||
} else if p.PullConfig == PullOff {
|
||||
p.PinHandle.PullOff()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (p *Pin) Changed() bool {
|
||||
return p.PinHandle.EdgeDetected()
|
||||
}
|
||||
|
||||
25
internal/PinControlService/PinControlConfig.go
Normal file
25
internal/PinControlService/PinControlConfig.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package PinControlService
|
||||
|
||||
type PinConfig struct {
|
||||
PinNumber int `yaml:"number"`
|
||||
Name string `yaml:"name"`
|
||||
Direction PinDirection `yaml:"direction"`
|
||||
PullConfig PinPull `yaml:"pull-config"`
|
||||
InitialState PinCommand `yaml:"initial-state"`
|
||||
SendPollingEvents *bool `yaml:"send-polling-events"`
|
||||
SendChangeEvents *bool `yaml:"send-change-events"`
|
||||
|
||||
}
|
||||
|
||||
type PinControlConfig struct {
|
||||
GpioPins []PinConfig `yaml:"gpio-pins"`
|
||||
PollingTimeMs int `yaml:"polling-time-ms"`
|
||||
|
||||
}
|
||||
|
||||
func NewPinControlConfig() PinControlConfig {
|
||||
return PinControlConfig{
|
||||
GpioPins: make([]PinConfig, 0),
|
||||
PollingTimeMs: 100,
|
||||
}
|
||||
}
|
||||
94
internal/PinControlService/PinControlService.go
Normal file
94
internal/PinControlService/PinControlService.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package PinControlService
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stianeikeland/go-rpio"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type PinControlService struct {
|
||||
Pins map[string]Pin
|
||||
timer* time.Ticker
|
||||
exit chan bool
|
||||
OnChangeCallback PinCallback
|
||||
OnCycleCallback PinCallback
|
||||
|
||||
}
|
||||
|
||||
|
||||
func (p*PinControlService) AddPin(
|
||||
config PinConfig) {
|
||||
pin := NewPin(config)
|
||||
p.Pins[pin.Name] = pin
|
||||
|
||||
}
|
||||
|
||||
func (p*PinControlService) Command(pinName string, command PinCommand) error {
|
||||
if pin, found := p.Pins[pinName]; found == false {
|
||||
return errors.New("pin not configured")
|
||||
} else {
|
||||
return pin.Command(command)
|
||||
}
|
||||
}
|
||||
|
||||
func (p*PinControlService) Start() {
|
||||
if err := rpio.Open(); err != nil {
|
||||
log.Fatal(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _,v := range p.Pins {
|
||||
v.Configure()
|
||||
}
|
||||
go p._task()
|
||||
}
|
||||
func (p*PinControlService) Stop() {
|
||||
p.exit <- true
|
||||
}
|
||||
|
||||
func (p*PinControlService) _task() {
|
||||
for {
|
||||
select {
|
||||
case <- p.timer.C:
|
||||
for pinName, pin := range p.Pins {
|
||||
log.Debug("timer event")
|
||||
if pin.Changed() {
|
||||
log.Debugf("detected pin change for pin %s (pin no %d)", pin.Name, pin.Id)
|
||||
if pin.SendChangeEvents && p.OnChangeCallback != nil {
|
||||
p.OnChangeCallback(pinName, pin.State())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if pin.SendPollingEvents && p.OnCycleCallback != nil {
|
||||
p.OnCycleCallback(pinName, pin.State())
|
||||
}
|
||||
}
|
||||
case <- p.exit:
|
||||
log.Debug("stop timer")
|
||||
p.timer.Stop()
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
func NewPinControl(config *PinControlConfig) (*PinControlService, error) {
|
||||
p := PinControlService{
|
||||
Pins: make(map[string]Pin),
|
||||
exit: make(chan bool,1),
|
||||
timer: time.NewTicker(time.Duration(config.PollingTimeMs) * time.Millisecond)}
|
||||
|
||||
for _, pinConfig := range config.GpioPins {
|
||||
p.AddPin(pinConfig)
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
23
internal/PinControlService/Types.go
Normal file
23
internal/PinControlService/Types.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package PinControlService
|
||||
|
||||
type PinCommand string
|
||||
type PinState string
|
||||
type PinDirection string
|
||||
type PinPull string
|
||||
type PinCallback func(pinName string, state PinState)
|
||||
|
||||
const (
|
||||
On PinCommand = "ON"
|
||||
Off PinCommand = "OFF"
|
||||
Toggle PinCommand = "TOGGLE"
|
||||
|
||||
StateOn PinState = "ON"
|
||||
StateOff PinState = "OFF"
|
||||
|
||||
Input PinDirection = "Input"
|
||||
Output PinDirection = "Output"
|
||||
|
||||
PullUp PinPull = "PULL_UP"
|
||||
PullDown PinPull = "PULL_DOWN"
|
||||
PullOff PinPull = "PULL_OFF"
|
||||
)
|
||||
Reference in New Issue
Block a user