diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3abfe86 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +install/* diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..52af4f3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/rpiMqttControl.iml b/.idea/rpiMqttControl.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/rpiMqttControl.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c8c450b --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +BINARY_NAME=rpicontrol +GIT_COMMIT=$(shell git rev-list -1 HEAD --abbrev-commit) +VERSION=1.0.0 + +get: + go get . +build_rpi: get + GOARCH=arm GOOS=linux go build -ldflags "-X main.GitCommit=${GIT_COMMIT} -X main.Version=${VERSION} -X main.Arch=${GOARCH}" -o ${BINARY_NAME} main.go +build_linux: get + GOARCH=amd64 GOOS=linux go build -ldflags "-X main.GitCommit=${GIT_COMMIT} -X main.Version=${VERSION} -X main.Arch=${GOARCH}" -o ${BINARY_NAME} main.go + +run: + ./${BINARY_NAME} -config rpicontrol.conf.example -log info + +build_and_run: build run + +clean: + go clean + -rm ${BINARY_NAME} + -rm -fr install + -rm -f rpicontrol-${VERSION}.tar.gz +pack: + mkdir -p install/etc/rpicontrol + mkdir -p install/usr/lib/systemd/system + mkdir -p install/usr/bin + cp rpicontrol.conf.example install/etc/rpicontrol/rpicontrol.conf.example + cp rpicontrol.service install/usr/lib/systemd/system/rpicontrol.service + cp rpicontrol install/usr/bin + tar cfvz rpicontrol-${VERSION}.tar.gz -C "install/" . + +deploy: + tar xfvz rpicontrol-${VERSION}.tar.gz -C / + systemctl daemon-reload + systemctl start rpicontrol + diff --git a/README.md b/README.md index 1215521..99c2f0e 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,115 @@ Raspberry MQTT Control ===================== -Description ---------------- -This is a linux daemon application for Raspberry PI, which receives control commands via MQTT to remotely control IO ports of Raspberry. -Application is fully configurable with YAML config file. +## Description +This is a linux daemon application for Raspberry PI, which receives control commands via MQTT to set GPIO ports of Raspberry. +As MQTT is widely supported within different home automation systems, this daemon application should be quite easily be integrated, for example in [OpenHAB](https://www.openhab.org/) +For further information, how to configure and use OpenHAB MQTT Binding, please see [here](https://www.openhab.org/addons/bindings/mqtt.generic/) + +## Usage +This application is fully controlled via MQTT Pub/Sub mechanism. +- To test the behavior, install and start an MQTT broker the linux daemon *rpicontrol*, as described in chapter _Installation_. +- As soon as everything is running, you can test the switch of output pins via following command: + + `mosquitto_pub -h localhost -t /rpicontrol/gpio/out1/control -m ON` + + This command will set the configured GPIO pin with name *out1* to state _ON_. + State _OFF_ will also work of course. + + - this command refers to example config as described below. If you have configured different pin names, broker address, or topic prefix, + you have of course to adapt the command according to your configuration. +- In order to see current states of all configured GPIO pins, you can subscribe via wildcard to the MQTT topic: + + `mosquitto_sub -h localhost -t "/rpicontrol/#" -v` + - Again, this command refers to example config as described below. If you have configured different pin names, broker address, or topic prefix, you have of course to adapt the command according to your configuration. + + + + +## Installation +- Install and configure an appropriate MQTT broker either locally or on your network. + for this, the open-source broker [mosquitto](https://mosquitto.org/) will be a good choice. + For installing and configuring mosquitto for your platform, please see the mosquitto docs. + +- untar the package tarball with following command: + + `sudo tar xfvz -C / rpicontrol-a.b.c.tar.gz`, where _a.b.c_ represents the version of rpicontrol + +- use the example config file as initial configuration + + ``` + cp /etc/rpicontrol/rpicontrol.conf.example /etc/rpicontrol/rpicontrol.conf + ``` +- edit the configuration file `/etc/rpicontrol/rpicontrol.conf` to your needs, see chapter _Configuration_ + +- reload systemd, enable and start service: + + ``` + sudo systemctl daemon-reload + sudo systemctl enable rpicontrol.service + sudo systemctl start rpicontrol.service + ``` +## Configuration -Installation and Usage -------------------------- -tbd. +### Example Configuration +Configuration file has YAML syntax, and shall have following structure. +Please note that optional values are not needed for configuration, see _Available Parameters_ -Compiling ---------- -tbd. \ No newline at end of file +``` +mqtt-config: + broker-address: "tcp://localhost:1883" + connect-timeout-ms: 10000 + connect-retry-interval-ms: 10000 + max-reconnect-interval-ms: 10000 + topic-prefix: "/rpicontrol" + username: "rpicontrol" + password: "my_password" + +pin-control-config: + gpio-pins: + - number: 17 + name: out1 + direction: Output + pull-config: PullOff + send-polling-events: false + send-change-events: true + initial-state: "OFF" + + polling-time-ms: 100 +``` +### Available Parameters + +- **mqtt-config**: section for configuring MQTT Broker Connection + - **broker-address**: URL for MQTT Broker. Example Value: "tcp://localhost:1883" + - **connect-timeout-ms**: (optional) timeout in milliseconds for connection + - **connect-retry-interval-ms**: (optional) connection retry interval in milliseconds + - **max-reconnect-interval-ms**: (optional) max connection retry interval time in milliseconds + - **topic-prefix**: (optional) prefix for MQTT topic + - **username**: (optional) username for MQTT connection + - **password**: (optional) password for MQTT connection + +- **pin-control-config**: section for configuring GPIO Pins that shall be used + - **gpio-pins**: list of GPIO pins + - **number**: number of GPIO pin, see [Raspberry Pi: GPIO - General Purpose Input Output](https://www.elektronik-kompendium.de/sites/raspberry-pi/2002191.htm) + - **name**: custom name for the pin (used for MQTT topic). + - **direction**: configure GPIO direction. Available Values: Input, Output + - **pull-config**: configure GPIO pull-up/down resistor. Available Values: + - PullDown: pull-down resistor + - PullUp: pull-up resistor + - PullOff: no resistor + - **send-polling-events**: (optional) send pin state via MQTT on every polling event. Available Values: + - true: enable send on polling event + - false (default): disable send on polling event + - **send-change-events**: (optional) send pin state via MQTT on a change event. Available Values: + - true: enable send on change events + - false (default): disable send on change events + - **initial-state**: (optional) set of initial state of output pin at start of application. Available Values: + - ON: initial state set to ON at startup + - OFF: initial state set to OFF at startup + - [not set]: do not set initial state at startup + - **polling-time-ms**: (optional) interval in milliseconds (default: 100), for cyclically polling the current state of all pins. + + diff --git a/go.mod b/go.mod index 142a2d4..aea4869 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.17 require ( github.com/eclipse/paho.mqtt.golang v1.3.5 github.com/sirupsen/logrus v1.8.1 + github.com/stianeikeland/go-rpio v4.2.0+incompatible gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) require ( github.com/gorilla/websocket v1.4.2 // indirect - github.com/stianeikeland/go-rpio v4.2.0+incompatible // indirect golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect ) diff --git a/internal/PinControlService/Pin.go b/internal/PinControlService/Pin.go index 49a2383..740b478 100644 --- a/internal/PinControlService/Pin.go +++ b/internal/PinControlService/Pin.go @@ -10,7 +10,7 @@ type Pin struct { Name string Direction PinDirection PullConfig PinPull - InitialState PinCommand + InitialState *PinCommand PinHandle HardwarePinInterface SendPollingEvents bool SendChangeEvents bool @@ -37,11 +37,7 @@ func NewPin(config PinConfig) Pin { p.SendChangeEvents = true } - if config.InitialState != "" { - p.InitialState = PinCommand(config.InitialState) - } else { - p.InitialState = Off - } + p.InitialState = config.InitialState return p } @@ -80,8 +76,11 @@ func (p *Pin) Configure() { } else if p.Direction == Output { log.Infof("configuring pin %s (pin no: %d) as Output", p.Name, p.Id) p.PinHandle.Output() - log.Infof("set initial state \"%s\" for pin %s (pin no: %d)", p.InitialState, p.Name, p.Id) - _ = p.Command(p.InitialState) + if p.InitialState != nil { + log.Infof("set initial state \"%s\" for pin %s (pin no: %d)", p.InitialState, p.Name, p.Id) + _ = p.Command(*p.InitialState) + } + } p.PinHandle.Detect(AnyEdge) @@ -92,6 +91,8 @@ func (p *Pin) Configure() { p.PinHandle.PullDown() } else if p.PullConfig == PullOff { p.PinHandle.PullOff() + } else { + log.Errorf("unknown config value \"%s\" for pull-config", p.PullConfig) } } diff --git a/internal/PinControlService/PinControlConfig.go b/internal/PinControlService/PinControlConfig.go index 792bda1..b8ba27f 100644 --- a/internal/PinControlService/PinControlConfig.go +++ b/internal/PinControlService/PinControlConfig.go @@ -1,25 +1,23 @@ 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"` - + 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"` - + GpioPins []PinConfig `yaml:"gpio-pins"` + PollingTimeMs int `yaml:"polling-time-ms"` } func NewPinControlConfig() PinControlConfig { return PinControlConfig{ - GpioPins: make([]PinConfig, 0), + GpioPins: make([]PinConfig, 0), PollingTimeMs: 100, } } diff --git a/internal/PinControlService/Types.go b/internal/PinControlService/Types.go index 10a855c..c353605 100644 --- a/internal/PinControlService/Types.go +++ b/internal/PinControlService/Types.go @@ -7,8 +7,8 @@ type PinPull string type PinCallback func(pinName string, state PinState) const ( - On PinCommand = "ON" - Off PinCommand = "OFF" + On PinCommand = "ON" + Off PinCommand = "OFF" Toggle PinCommand = "TOGGLE" StateOn PinState = "ON" @@ -17,7 +17,7 @@ const ( Input PinDirection = "Input" Output PinDirection = "Output" - PullUp PinPull = "PULL_UP" - PullDown PinPull = "PULL_DOWN" - PullOff PinPull = "PULL_OFF" + PullUp PinPull = "PullUp" + PullDown PinPull = "PullDown" + PullOff PinPull = "PullOff" ) diff --git a/main.go b/main.go index 811270b..ce5287f 100644 --- a/main.go +++ b/main.go @@ -10,12 +10,26 @@ import ( "rpiMqttControl/internal/PinControlService" ) +var ( + Version string = "0.0.0" + GitCommit string = "000000" +) + func main() { + versionStr := Version + "-" + GitCommit flagConfig := flag.String("config", "/etc/rpicontrol/rpicontrol.conf", "path to config file") flagLogLevel := flag.String("log", "info", "set log level for console output") + flagVersion := flag.Bool("version", false, "output version and exit") flag.Parse() + if *flagVersion { + print(versionStr) + os.Exit(0) + } + + log.Infof("starting rpicontrol Version %s", versionStr) + if level, err := log.ParseLevel(*flagLogLevel); err != nil { log.SetLevel(log.WarnLevel) log.Warnf("could not set log level. %s", err.Error()) diff --git a/rpicontrol.config.example b/rpicontrol.conf.example similarity index 82% rename from rpicontrol.config.example rename to rpicontrol.conf.example index 9d32ad6..cf4b86b 100644 --- a/rpicontrol.config.example +++ b/rpicontrol.conf.example @@ -10,5 +10,7 @@ pin-control-config: name: out1 direction: Output pull-config: PullOff + send-polling-events: false + send-change-events: true polling-time-ms: 100 diff --git a/rpicontrol.service b/rpicontrol.service index e239435..e1436ba 100644 --- a/rpicontrol.service +++ b/rpicontrol.service @@ -9,16 +9,12 @@ Group=root Restart=on-failure RestartSec=10 -startLimitIntervalSec=60 WorkingDirectory=/usr/bin ExecStart=/usr/bin/rpicontrol # make sure log directory exists and owned by syslog -PermissionsStartOnly=true -StandardOutput=syslog -StandardError=syslog SyslogIdentifier=rpicontrol [Install] -WantedBy=multi-user.target \ No newline at end of file +WantedBy=multi-user.target