From b29b5971915eb91ac70cb9fc91be634c12d11b77 Mon Sep 17 00:00:00 2001 From: lauorez Date: Fri, 26 Dec 2025 14:28:38 +0100 Subject: [PATCH 1/6] adjusted the makefile to work on windows and (hopefully still) linux bcs im a filthy windows user --- Makefile | 70 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 1f5e7c0..9532885 100755 --- a/Makefile +++ b/Makefile @@ -1,23 +1,49 @@ -DIST=dist/ -APPNAME=halsecur +DIST := dist +APPNAME := halsecur -GOLANGCILINT_VERSION=v2.7.2 -GOSEC_VERSION=v2.22.11 -VULNCHECK_VERSION=latest +GOLANGCILINT_VERSION := v2.7.2 +GOSEC_VERSION := v2.22.11 +VULNCHECK_VERSION := latest -all: clean build +LDFLAGS := -X bisecur/version.Version=?version? -X bisecur/version.BuildDate=?date? + +ifeq ($(OS),Windows_NT) + SHELL := cmd.exe + SHELLFLAGS := /C + + EXE := .exe + DEVNULL := NUL + + # Use cmd built-ins / Windows tools + MKDIR_P = if not exist "$(DIST)" mkdir "$(DIST)" + RM_RF = if exist "$(DIST)" rmdir /S /Q "$(DIST)" + WHERE = where +else + EXE := + DEVNULL := /dev/null + + MKDIR_P = mkdir -p "$(DIST)" + RM_RF = rm -rf "$(DIST)" + WHERE = which +endif + +OUT := $(DIST)/$(APPNAME)$(EXE) + +# --- Targets --- +.PHONY: all env clean lint-env lint lint-fix test test-short build build-linux build-docker + +all: clean build build-linux env: - mkdir -p ${DIST} + @$(MKDIR_P) clean: - rm -rf ${DIST} + @$(RM_RF) lint-env: - ( which gosec &>/dev/zero && gosec --version | grep -qs $(GOSEC_VERSION) ) || go install github.com/securego/gosec/v2/cmd/gosec@$(GOSEC_VERSION) - ( which golangci-lint &>/dev/zero && golangci-lint --version | grep -qs $(GOLANGCILINT_VERSION) ) || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCILINT_VERSION) - ( which govulncheck &>/dev/zero ) || go install golang.org/x/vuln/cmd/govulncheck@$(VULNCHECK_VERSION) - # go install github.com/goreleaser/goreleaser/v2@latest + @$(WHERE) gosec >$(DEVNULL) 2>&1 && gosec --version | grep -qs "$(GOSEC_VERSION)" || go install github.com/securego/gosec/v2/cmd/gosec@$(GOSEC_VERSION) + @$(WHERE) golangci-lint >$(DEVNULL) 2>&1 && golangci-lint --version | grep -qs "$(GOLANGCILINT_VERSION)" || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCILINT_VERSION) + @$(WHERE) govulncheck >$(DEVNULL) 2>&1 || go install golang.org/x/vuln/cmd/govulncheck@$(VULNCHECK_VERSION) lint: lint-env golangci-lint --timeout 10m -v run ./... @@ -28,13 +54,25 @@ lint-fix: lint-env golangci-lint run -v --fix ./... test: test-short - go test ${VENDOR} ./... + go test $(VENDOR) ./... test-short: - go test ${VENDOR} -race -short + go test $(VENDOR) -race -short ./... build: env - CGO_ENABLED=0 go build -ldflags "-X 'bisecur/version.Version=?version?' -X 'bisecur/version.BuildDate=?date?'" -v -o ${DIST}${APPNAME} . +ifeq ($(OS),Windows_NT) + @set CGO_ENABLED=0&& go build -ldflags "$(LDFLAGS)" -v -o "$(OUT)" . +else + CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -v -o "$(OUT)" . +endif + +build-linux: env +ifeq ($(OS),Windows_NT) + + @set CGO_ENABLED=0&& @set GOARCH=amd64&& @set GOOS=linux&& go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)" . +else + CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)" . +endif build-docker: env build - docker build --build-arg TARGETPLATFORM='./' --build-arg VERSION=$(shell git describe --tags --always) -t bisecur/halsecur:latest -f Dockerfile ${DIST} + docker build --build-arg TARGETPLATFORM='./' --build-arg VERSION=$(shell git describe --tags --always) -t bisecur/halsecur:latest -f Dockerfile "$(DIST)" From bf6c70de54cc3dc0566a2ee50cf595fd7b08acc7 Mon Sep 17 00:00:00 2001 From: lauorez Date: Fri, 26 Dec 2025 19:18:13 +0100 Subject: [PATCH 2/6] added build-windows build-linux seperately with build running both. both building targets run on windows and linux (i think) --- Makefile | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 9532885..3ad8680 100755 --- a/Makefile +++ b/Makefile @@ -14,7 +14,6 @@ ifeq ($(OS),Windows_NT) EXE := .exe DEVNULL := NUL - # Use cmd built-ins / Windows tools MKDIR_P = if not exist "$(DIST)" mkdir "$(DIST)" RM_RF = if exist "$(DIST)" rmdir /S /Q "$(DIST)" WHERE = where @@ -32,7 +31,7 @@ OUT := $(DIST)/$(APPNAME)$(EXE) # --- Targets --- .PHONY: all env clean lint-env lint lint-fix test test-short build build-linux build-docker -all: clean build build-linux +all: clean build env: @$(MKDIR_P) @@ -59,19 +58,20 @@ test: test-short test-short: go test $(VENDOR) -race -short ./... -build: env +build: env build-linux build-windows + +build-linux: env ifeq ($(OS),Windows_NT) - @set CGO_ENABLED=0&& go build -ldflags "$(LDFLAGS)" -v -o "$(OUT)" . + @set CGO_ENABLED=0&& @set GOARCH=amd64&& @set GOOS=linux&& go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)" . else - CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -v -o "$(OUT)" . + CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)" . endif -build-linux: env +build-windows: env ifeq ($(OS),Windows_NT) - - @set CGO_ENABLED=0&& @set GOARCH=amd64&& @set GOOS=linux&& go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)" . + @set CGO_ENABLED=0&& @set GOARCH=amd64&& @set GOOS=windows&& go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)$(EXE)" . else - CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)" . + CGO_ENABLED=0 GOARCH=amd64 GOOS=windows go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)$(EXE)" . endif build-docker: env build From 7eb116b7449121e3302fe04958cd4c7b2de51fa8 Mon Sep 17 00:00:00 2001 From: lauorez Date: Sat, 27 Dec 2025 11:09:26 +0100 Subject: [PATCH 3/6] screw the stupid EXE variable, hardcoding the extension will do i guess :D --- Makefile | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 3ad8680..3cbc78d 100755 --- a/Makefile +++ b/Makefile @@ -11,14 +11,12 @@ ifeq ($(OS),Windows_NT) SHELL := cmd.exe SHELLFLAGS := /C - EXE := .exe DEVNULL := NUL MKDIR_P = if not exist "$(DIST)" mkdir "$(DIST)" RM_RF = if exist "$(DIST)" rmdir /S /Q "$(DIST)" WHERE = where else - EXE := DEVNULL := /dev/null MKDIR_P = mkdir -p "$(DIST)" @@ -26,8 +24,6 @@ else WHERE = which endif -OUT := $(DIST)/$(APPNAME)$(EXE) - # --- Targets --- .PHONY: all env clean lint-env lint lint-fix test test-short build build-linux build-docker @@ -69,9 +65,9 @@ endif build-windows: env ifeq ($(OS),Windows_NT) - @set CGO_ENABLED=0&& @set GOARCH=amd64&& @set GOOS=windows&& go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)$(EXE)" . + @set CGO_ENABLED=0&& @set GOARCH=amd64&& @set GOOS=windows&& go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME).exe" . else - CGO_ENABLED=0 GOARCH=amd64 GOOS=windows go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME)$(EXE)" . + CGO_ENABLED=0 GOARCH=amd64 GOOS=windows go build -ldflags "$(LDFLAGS)" -v -o "$(DIST)/$(APPNAME).exe" . endif build-docker: env build From a42e7dc2e3c750d61177b16d9a8299fdef159b77 Mon Sep 17 00:00:00 2001 From: lauorez Date: Sun, 28 Dec 2025 12:32:25 +0100 Subject: [PATCH 4/6] change doPeriodicRequest flag to doorStatusSupported. if not supported, publish a button instead of a cover / garage entity to HA, because the open stop close logic can't be applied without door status. include the "PRESS" command handling for the button entity case. --- cli/cmd/consts.go | 2 +- cli/cmd/homeAssistant.go | 8 ++++---- cli/homeAssistant/actions.go | 11 +++++++++++ cli/homeAssistant/client.go | 19 ++++++++++++------- cli/homeAssistant/messages.go | 8 ++++++-- cli/homeAssistant/topics.go | 9 ++++++--- 6 files changed, 40 insertions(+), 17 deletions(-) diff --git a/cli/cmd/consts.go b/cli/cmd/consts.go index 55ff237..6a96dc3 100755 --- a/cli/cmd/consts.go +++ b/cli/cmd/consts.go @@ -32,5 +32,5 @@ const ( LogoutCmdName = "logout" PingCmdName = "ping" HomeAssistantCmdName = "ha" - ArgDoPeriodicRequests = "doPeriodicRequests" + ArgDoorStatusSupported = "doorStatusSupported" ) diff --git a/cli/cmd/homeAssistant.go b/cli/cmd/homeAssistant.go index 5afd6f7..9845ae1 100755 --- a/cli/cmd/homeAssistant.go +++ b/cli/cmd/homeAssistant.go @@ -26,7 +26,7 @@ func init() { mqttTelePeriod time.Duration mqttTelePeriodFast time.Duration devicePort int - doPeriodicRequests bool + doorStatusSupported bool ) haCmd := &cobra.Command{ @@ -53,7 +53,7 @@ func init() { mqttTelePeriod = viper.GetDuration(ArgMqttTelePeriodName) mqttTelePeriodFast = viper.GetDuration(ArgMqttTelePeriodFastName) devicePort = viper.GetInt(ArgDevicePortName) - doPeriodicRequests = viper.GetBool(ArgDoPeriodicRequests) + doorStatusSupported = viper.GetBool(ArgDoorStatusSupported) mqttClientId := fmt.Sprintf("clientId_%s", deviceMac) @@ -67,7 +67,7 @@ func init() { cli.Log, localMac, mac, username, password, host, port, token, mqttServerName, mqttClientId, mqttServerPort, mqttServerTls, mqttServerTlsValidaton, mqttBaseTopic, mqttDeviceName, mqttUserName, mqttPassword, mqttTelePeriod, mqttTelePeriodFast, - byte(devicePort), doPeriodicRequests, + byte(devicePort), doorStatusSupported, ) if err != nil { cli.Log.Fatalf("%v", err) @@ -94,7 +94,7 @@ func init() { haCmd.Flags().DurationVarP(&mqttTelePeriod, ArgMqttTelePeriodName, "e", 15*time.Second, "Frequency of device state publish") haCmd.Flags().DurationVarP(&mqttTelePeriodFast, ArgMqttTelePeriodFastName, "f", 5*time.Second, "Frequency of device state publish when door might be moving") haCmd.Flags().IntVar(&devicePort, ArgDevicePortName, 0, "Port number of the door") - haCmd.Flags().BoolVar(&doPeriodicRequests, ArgDoPeriodicRequests, true, "Whether periodic status requests should be sent or not") + haCmd.Flags().BoolVar(&doorStatusSupported, ArgDoorStatusSupported, true, "Whether the controlled door supports door status (opening state) or not") flag.Parse() err := viper.BindPFlags(haCmd.Flags()) if err != nil { diff --git a/cli/homeAssistant/actions.go b/cli/homeAssistant/actions.go index 0268696..7076c97 100644 --- a/cli/homeAssistant/actions.go +++ b/cli/homeAssistant/actions.go @@ -88,6 +88,17 @@ func (ha *HomeAssistanceMqttClient) setStateBisecurMultiCall(count int) error { return nil } +func (ha *HomeAssistanceMqttClient) impuls() any { + ha.log.Info("Sending impuls...") + + err := ha.setStateMultiCall(1) + if err != nil { + return fmt.Errorf("failed to send impuls. %v", err) + } + + return nil +} + func (ha *HomeAssistanceMqttClient) openDoor() error { ha.log.Info("Opening door...") diff --git a/cli/homeAssistant/client.go b/cli/homeAssistant/client.go index a18426d..778bdca 100644 --- a/cli/homeAssistant/client.go +++ b/cli/homeAssistant/client.go @@ -46,12 +46,12 @@ type HomeAssistanceMqttClient struct { log *logrus.Logger mqttClient mqtt.Client requestFastUpdate time.Time - doPeriodicRequests bool + doorStatusSupported bool } func NewHomeAssistanceMqttClient(log *logrus.Logger, localMac [6]byte, deviceMac [6]byte, deviceUsername string, devicePassword string, host string, port int, token uint32, mqttServerName string, mqttClientId string, mqttServerPort int, mqttServerTls bool, mqttServerTlsValidaton bool, mqttBaseTopic string, - mqttDeviceName string, mqttUserName string, mqttPassword string, mqttTelePeriod time.Duration, mqttTelePeriodFast time.Duration, devicePort byte, doPeriodicRequests bool) (*HomeAssistanceMqttClient, error) { + mqttDeviceName string, mqttUserName string, mqttPassword string, mqttTelePeriod time.Duration, mqttTelePeriodFast time.Duration, devicePort byte, doorStatusSupported bool) (*HomeAssistanceMqttClient, error) { ha := &HomeAssistanceMqttClient{ localMac: localMac, @@ -75,7 +75,7 @@ func NewHomeAssistanceMqttClient(log *logrus.Logger, localMac [6]byte, deviceMac devicePort: devicePort, log: log, requestFastUpdate: time.UnixMicro(0), // initial value must be in the past - doPeriodicRequests: doPeriodicRequests, + doorStatusSupported: doorStatusSupported, } return ha, nil @@ -121,6 +121,11 @@ func (ha *HomeAssistanceMqttClient) Start() error { if err != nil { ha.log.Errorf("failed to stop door. %v", err) } + case "PRESS": + err := ha.impuls() + if err != nil { + ha.log.Errorf("failed to do impuls. %v", err) + } } } @@ -133,7 +138,7 @@ func (ha *HomeAssistanceMqttClient) Start() error { protocol = "tls" } brokerUrl := fmt.Sprintf("%s://%s:%d", protocol, ha.mqttServerName, ha.mqttServerPort) - ha.log.Debugf("MQTT Broken url: %s", brokerUrl) + ha.log.Debugf("MQTT Broker url: %s", brokerUrl) opts.AddBroker(brokerUrl) opts.SetClientID(ha.mqttClientId) @@ -217,7 +222,7 @@ out: ha.log.Infof("Exiting") break out case <-ticker.C: - if ha.doPeriodicRequests { + if ha.doorStatusSupported { err := ha.doorStatus() if err != nil { ha.log.Errorf("failed to publish current door status. %v", err) @@ -231,7 +236,7 @@ out: continue } - if ha.doPeriodicRequests { + if ha.doorStatusSupported { err := ha.doorStatus() if err != nil { ha.log.Errorf("failed to publish current door status. %v", err) @@ -332,7 +337,7 @@ func (ha *HomeAssistanceMqttClient) PublishDiscoveryMessage() error { return fmt.Errorf("failed to generate discovery message. %v", err) } - mqttToken := ha.mqttClient.Publish(ha.getDiscoveryTopic(), qosAtLeastOnce, true, discoveryMsg) + mqttToken := ha.mqttClient.Publish(ha.getDiscoveryTopic(!ha.doorStatusSupported), qosAtLeastOnce, true, discoveryMsg) if mqttToken.Wait() && mqttToken.Error() != nil { return fmt.Errorf("failed to publish discovery message. %v", mqttToken.Error()) } diff --git a/cli/homeAssistant/messages.go b/cli/homeAssistant/messages.go index 8fcae20..ba4398b 100644 --- a/cli/homeAssistant/messages.go +++ b/cli/homeAssistant/messages.go @@ -8,6 +8,10 @@ import ( func (ha *HomeAssistanceMqttClient) getDiscoveryMessage() (string, error) { name := ha.getUniqueObjectId() uniqueId := ha.getUniqueObjectId() + device_class := `"device_class": "garage",` + if !ha.doorStatusSupported { + device_class = "" + } commandTopic := ha.getSetPositionTopic() positionTopic := ha.getPositionTopicName() @@ -15,7 +19,7 @@ func (ha *HomeAssistanceMqttClient) getDiscoveryMessage() (string, error) { { "name": "%s", "unique_id": "%s", - "device_class": "garage", + %s "command_topic": "%s", "position_topic": "%s", "device": { @@ -27,7 +31,7 @@ func (ha *HomeAssistanceMqttClient) getDiscoveryMessage() (string, error) { "payload_not_available": "%s" }` - message := fmt.Sprintf(messageTemplate, name, uniqueId, commandTopic, positionTopic, uniqueId, name, ha.getAvailabilityTopic(), ha.getAvabilityMessage(true), ha.getAvabilityMessage(false)) + message := fmt.Sprintf(messageTemplate, name, uniqueId, device_class, commandTopic, positionTopic, uniqueId, name, ha.getAvailabilityTopic(), ha.getAvabilityMessage(true), ha.getAvabilityMessage(false)) return message, nil } diff --git a/cli/homeAssistant/topics.go b/cli/homeAssistant/topics.go index 6c25523..602305f 100644 --- a/cli/homeAssistant/topics.go +++ b/cli/homeAssistant/topics.go @@ -21,10 +21,13 @@ func (ha *HomeAssistanceMqttClient) getGetStateTopicName() string { return fmt.Sprintf("%s/%s/state", ha.mqttBaseTopic, ha.mqttDeviceName) } -func (ha *HomeAssistanceMqttClient) getDiscoveryTopic() string { +func (ha *HomeAssistanceMqttClient) getDiscoveryTopic(impulsCommandOnly bool) string { ////[/]/config - - return fmt.Sprintf("homeassistant/cover/halsecur/%s/config", ha.getUniqueObjectId()) + entity_type := "cover" + if impulsCommandOnly { + entity_type = "button" + } + return fmt.Sprintf("homeassistant/%s/halsecur/%s/config", entity_type, ha.getUniqueObjectId()) } func (ha *HomeAssistanceMqttClient) getUniqueObjectId() string { From 8ee7dcd5956bc0baad831d299799ab4be86b853c Mon Sep 17 00:00:00 2001 From: lauorez Date: Sun, 28 Dec 2025 15:33:24 +0100 Subject: [PATCH 5/6] multidoor support --- cli/cmd/consts.go | 1 + cli/cmd/homeAssistant.go | 8 +-- cli/homeAssistant/actions.go | 46 +++++++------- cli/homeAssistant/client.go | 109 ++++++++++++++++++++-------------- cli/homeAssistant/messages.go | 12 ++-- cli/homeAssistant/topics.go | 28 ++++----- 6 files changed, 111 insertions(+), 93 deletions(-) diff --git a/cli/cmd/consts.go b/cli/cmd/consts.go index 6a96dc3..51472b6 100755 --- a/cli/cmd/consts.go +++ b/cli/cmd/consts.go @@ -33,4 +33,5 @@ const ( PingCmdName = "ping" HomeAssistantCmdName = "ha" ArgDoorStatusSupported = "doorStatusSupported" + ArgDevicePortsName = "devicePorts" ) diff --git a/cli/cmd/homeAssistant.go b/cli/cmd/homeAssistant.go index 9845ae1..ec10209 100755 --- a/cli/cmd/homeAssistant.go +++ b/cli/cmd/homeAssistant.go @@ -25,7 +25,7 @@ func init() { mqttPassword string mqttTelePeriod time.Duration mqttTelePeriodFast time.Duration - devicePort int + devicePorts []int doorStatusSupported bool ) @@ -52,7 +52,7 @@ func init() { mqttPassword = viper.GetString(ArgMqttPasswordName) mqttTelePeriod = viper.GetDuration(ArgMqttTelePeriodName) mqttTelePeriodFast = viper.GetDuration(ArgMqttTelePeriodFastName) - devicePort = viper.GetInt(ArgDevicePortName) + devicePorts = viper.GetIntSlice(ArgDevicePortsName) doorStatusSupported = viper.GetBool(ArgDoorStatusSupported) mqttClientId := fmt.Sprintf("clientId_%s", deviceMac) @@ -67,7 +67,7 @@ func init() { cli.Log, localMac, mac, username, password, host, port, token, mqttServerName, mqttClientId, mqttServerPort, mqttServerTls, mqttServerTlsValidaton, mqttBaseTopic, mqttDeviceName, mqttUserName, mqttPassword, mqttTelePeriod, mqttTelePeriodFast, - byte(devicePort), doorStatusSupported, + devicePorts, doorStatusSupported, ) if err != nil { cli.Log.Fatalf("%v", err) @@ -93,7 +93,7 @@ func init() { haCmd.Flags().StringVarP(&mqttDeviceName, ArgMqttDeviceNameName, "n", "garage", "Name of the local device in MQTT messages") haCmd.Flags().DurationVarP(&mqttTelePeriod, ArgMqttTelePeriodName, "e", 15*time.Second, "Frequency of device state publish") haCmd.Flags().DurationVarP(&mqttTelePeriodFast, ArgMqttTelePeriodFastName, "f", 5*time.Second, "Frequency of device state publish when door might be moving") - haCmd.Flags().IntVar(&devicePort, ArgDevicePortName, 0, "Port number of the door") + haCmd.Flags().IntSliceVar(&devicePorts, ArgDevicePortsName, []int{}, "Port numbers of the doors") haCmd.Flags().BoolVar(&doorStatusSupported, ArgDoorStatusSupported, true, "Whether the controlled door supports door status (opening state) or not") flag.Parse() err := viper.BindPFlags(haCmd.Flags()) diff --git a/cli/homeAssistant/actions.go b/cli/homeAssistant/actions.go index 7076c97..49cf0f1 100644 --- a/cli/homeAssistant/actions.go +++ b/cli/homeAssistant/actions.go @@ -59,12 +59,12 @@ func (ha *HomeAssistanceMqttClient) forceReLogin() error { return nil } -func (ha *HomeAssistanceMqttClient) setStateMultiCall(count int) error { - return ha.setStateBisecurMultiCall(count) +func (ha *HomeAssistanceMqttClient) setStateMultiCall(count int, devicePort byte) error { + return ha.setStateBisecurMultiCall(count, devicePort) //return mockDoor.SetStateMockMultiCall(count) } -func (ha *HomeAssistanceMqttClient) setStateBisecurMultiCall(count int) error { +func (ha *HomeAssistanceMqttClient) setStateBisecurMultiCall(count int, devicePort byte) error { const delayDuration = 1500 * time.Millisecond for i := 0; i < count; i++ { @@ -75,7 +75,7 @@ func (ha *HomeAssistanceMqttClient) setStateBisecurMultiCall(count int) error { return fmt.Errorf("auto login failed. %v", err) } - err = bisecur.SetState(ha.localMac, ha.deviceMac, ha.host, ha.port, ha.devicePort, ha.token) + err = bisecur.SetState(ha.localMac, ha.deviceMac, ha.host, ha.port, devicePort, ha.token) if err != nil { return fmt.Errorf("failed to get door status. %v", err) } @@ -88,10 +88,10 @@ func (ha *HomeAssistanceMqttClient) setStateBisecurMultiCall(count int) error { return nil } -func (ha *HomeAssistanceMqttClient) impuls() any { +func (ha *HomeAssistanceMqttClient) impuls(devicePort byte) any { ha.log.Info("Sending impuls...") - err := ha.setStateMultiCall(1) + err := ha.setStateMultiCall(1, devicePort) if err != nil { return fmt.Errorf("failed to send impuls. %v", err) } @@ -99,34 +99,34 @@ func (ha *HomeAssistanceMqttClient) impuls() any { return nil } -func (ha *HomeAssistanceMqttClient) openDoor() error { +func (ha *HomeAssistanceMqttClient) openDoor(devicePort byte) error { ha.log.Info("Opening door...") - direction, position, err := ha.getDoorStatus() + direction, position, err := ha.getDoorStatus(devicePort) if err != nil { return fmt.Errorf("failed to get door status. %v", err) } switch direction { case utils.CLOSING: - err := ha.setStateMultiCall(2) + err := ha.setStateMultiCall(2, devicePort) if err != nil { return fmt.Errorf("failed to set state. %v", err) } case utils.STOPPED, utils.OPEN, utils.CLOSED: if position < 100 { // check if door is not already fully open - err := ha.setStateMultiCall(1) + err := ha.setStateMultiCall(1, devicePort) if err != nil { return fmt.Errorf("failed to set state. %v", err) } - newDirection, _, err := ha.getDoorStatus() + newDirection, _, err := ha.getDoorStatus(devicePort) if err != nil { return fmt.Errorf("failed to get door status to confirm it is moving into the right direction. %v", err) } if newDirection != utils.OPENING && newDirection != utils.OPEN { // check if door needs to be reversed - err := ha.setStateMultiCall(2) + err := ha.setStateMultiCall(2, devicePort) if err != nil { return fmt.Errorf("failed to reverse moving direction. %v", err) } @@ -143,34 +143,34 @@ func (ha *HomeAssistanceMqttClient) openDoor() error { return nil } -func (ha *HomeAssistanceMqttClient) closeDoor() error { +func (ha *HomeAssistanceMqttClient) closeDoor(devicePort byte) error { ha.log.Info("Closing door...") - direction, position, err := ha.getDoorStatus() + direction, position, err := ha.getDoorStatus(devicePort) if err != nil { return fmt.Errorf("failed to get door status. %v", err) } switch direction { case utils.OPENING: - err := ha.setStateMultiCall(2) // stop then reverse + err := ha.setStateMultiCall(2, devicePort) // stop then reverse if err != nil { return fmt.Errorf("failed to set state. %v", err) } case utils.STOPPED, utils.OPEN: if position > 0 { // check if door is not already fully closed - err := ha.setStateMultiCall(1) + err := ha.setStateMultiCall(1, devicePort) if err != nil { return fmt.Errorf("failed to set state. %v", err) } - newDirection, _, err := ha.getDoorStatus() + newDirection, _, err := ha.getDoorStatus(devicePort) if err != nil { return fmt.Errorf("failed to get door status to confirm it is moving into the right direction. %v", err) } if newDirection != utils.CLOSING && newDirection != utils.CLOSED { // check if door needs to be reversed - err := ha.setStateMultiCall(2) + err := ha.setStateMultiCall(2, devicePort) if err != nil { return fmt.Errorf("failed to reverse moving direction. %v", err) } @@ -187,16 +187,16 @@ func (ha *HomeAssistanceMqttClient) closeDoor() error { return nil } -func (ha *HomeAssistanceMqttClient) stopDoor() error { +func (ha *HomeAssistanceMqttClient) stopDoor(devicePort byte) error { ha.log.Infof("Stopping door...") - direction, _, err := ha.getDoorStatus() + direction, _, err := ha.getDoorStatus(devicePort) if err != nil { return fmt.Errorf("failed to get door status. %v", err) } if direction == utils.OPENING || direction == utils.CLOSING { // anything which means moving door - err := ha.setStateMultiCall(1) + err := ha.setStateMultiCall(1, devicePort) if err != nil { return fmt.Errorf("failed to stop the door. %v", err) } @@ -209,7 +209,7 @@ func (ha *HomeAssistanceMqttClient) stopDoor() error { return nil } -func (ha *HomeAssistanceMqttClient) getDoorStatus() (direction string, position int, err error) { +func (ha *HomeAssistanceMqttClient) getDoorStatus(devicePort byte) (direction string, position int, err error) { /* status="{\"StateInPercent\":0,\"DesiredStateInPercent\":0,\"ErrorResponse\":false,\"AutoClose\":false,\"DriveTime\":0, \"Gk\":257,\"Hcp\":{\"PositionOpen\":false,\"PositionClose\":true,\"OptionRelais\":false,\"LightBarrier\":false, @@ -229,7 +229,7 @@ func (ha *HomeAssistanceMqttClient) getDoorStatus() (direction string, position var status *payload.HmGetTransitionResponse err = utils.RetryAlways(utils.RetryCount, func() error { var err2 error - status, err2 = bisecur.GetStatus(ha.localMac, ha.deviceMac, ha.host, ha.port, ha.devicePort, ha.token) + status, err2 = bisecur.GetStatus(ha.localMac, ha.deviceMac, ha.host, ha.port, devicePort, ha.token) if err2.Error() == "PERMISSION_DENIED" { // TODO don't like string comparisons so should be refactored somehow while relogin also should be make more generic (think of other commands) // Does it make sense to force relogin after a PERMISSION_DENIED error? diff --git a/cli/homeAssistant/client.go b/cli/homeAssistant/client.go index 778bdca..911fb22 100644 --- a/cli/homeAssistant/client.go +++ b/cli/homeAssistant/client.go @@ -10,6 +10,8 @@ import ( "log" "os" "os/signal" + "strconv" + "strings" "syscall" "time" @@ -42,7 +44,7 @@ type HomeAssistanceMqttClient struct { mqttPassword string mqttTelePeriod time.Duration mqttTelePeriodFast time.Duration - devicePort byte + devicePorts []int log *logrus.Logger mqttClient mqtt.Client requestFastUpdate time.Time @@ -51,7 +53,7 @@ type HomeAssistanceMqttClient struct { func NewHomeAssistanceMqttClient(log *logrus.Logger, localMac [6]byte, deviceMac [6]byte, deviceUsername string, devicePassword string, host string, port int, token uint32, mqttServerName string, mqttClientId string, mqttServerPort int, mqttServerTls bool, mqttServerTlsValidaton bool, mqttBaseTopic string, - mqttDeviceName string, mqttUserName string, mqttPassword string, mqttTelePeriod time.Duration, mqttTelePeriodFast time.Duration, devicePort byte, doorStatusSupported bool) (*HomeAssistanceMqttClient, error) { + mqttDeviceName string, mqttUserName string, mqttPassword string, mqttTelePeriod time.Duration, mqttTelePeriodFast time.Duration, devicePorts []int, doorStatusSupported bool) (*HomeAssistanceMqttClient, error) { ha := &HomeAssistanceMqttClient{ localMac: localMac, @@ -72,7 +74,7 @@ func NewHomeAssistanceMqttClient(log *logrus.Logger, localMac [6]byte, deviceMac mqttPassword: mqttPassword, mqttTelePeriod: mqttTelePeriod, mqttTelePeriodFast: mqttTelePeriodFast, - devicePort: devicePort, + devicePorts: devicePorts, log: log, requestFastUpdate: time.UnixMicro(0), // initial value must be in the past doorStatusSupported: doorStatusSupported, @@ -103,26 +105,31 @@ func (ha *HomeAssistanceMqttClient) Start() error { homeAssistantSetPossitionMessagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) { ha.log.Debugf("Received set position message: %s from topic: %s", msg.Payload(), msg.Topic()) + devicePort, err := strconv.Atoi(strings.Split(msg.Topic(), "/")[2]) // extract device port from topic + if err != nil { + ha.log.Errorf("failed to parse device port. %v", err) + return + } command := string(msg.Payload()) switch command { case "CLOSE": - err := ha.closeDoor() + err := ha.closeDoor(byte(devicePort)) if err != nil { ha.log.Errorf("failed to close door. %v", err) } case "OPEN": - err := ha.openDoor() + err := ha.openDoor(byte(devicePort)) if err != nil { ha.log.Errorf("failed to open door. %v", err) } case "STOP": - err := ha.stopDoor() + err := ha.stopDoor(byte(devicePort)) if err != nil { ha.log.Errorf("failed to stop door. %v", err) } case "PRESS": - err := ha.impuls() + err := ha.impuls(byte(devicePort)) if err != nil { ha.log.Errorf("failed to do impuls. %v", err) } @@ -151,8 +158,10 @@ func (ha *HomeAssistanceMqttClient) Start() error { opts.SetTLSConfig(tlsConfig) opts.SetAutoReconnect(true) - // Configure offline availability message - opts.SetWill(ha.getAvailabilityTopic(), ha.getAvabilityMessage(false), qosAtLeastOnce, true) + // Configure offline availability messages + for _, devicePort := range ha.devicePorts { + opts.SetWill(ha.getAvailabilityTopic(devicePort), ha.getAvabilityMessage(false), qosAtLeastOnce, true) + } ha.mqttClient = mqtt.NewClient(opts) mqttToken := ha.mqttClient.Connect() @@ -162,16 +171,18 @@ func (ha *HomeAssistanceMqttClient) Start() error { defer func() { ha.log.Debugf("Disconnecting from MQTT server") - err := ha.PublishAvabilityMessage(false) - if err != nil { - ha.log.Errorf("failed to publish availability message (offline). %v", err) + for _, devicePort := range ha.devicePorts { + err := ha.PublishAvabilityMessage(false, devicePort) + if err != nil { + ha.log.Errorf("failed to publish availability message (offline). %v", err) + } } ha.mqttClient.Disconnect(250) ha.log.Infof("Disconnected from MQTT server") - err = ha.LogoutBisecur() + err := ha.LogoutBisecur() if err != nil { ha.log.Errorf("Error logging out of bisecur") } else { @@ -182,21 +193,23 @@ func (ha *HomeAssistanceMqttClient) Start() error { // Subscribe to home assistant's status topic (get notification when HA restarts) ha.mqttClient.Subscribe(utils.HomeAssistantStatusTopic, 0, homeAssistantStatusMessagePubHandler) - // Subscribe to topic for receiving commands - setPositionTopicName := ha.getSetPositionTopic() - ha.log.Debugf("Subscribing to topic: %s", setPositionTopicName) - ha.mqttClient.Subscribe(setPositionTopicName, 0, homeAssistantSetPossitionMessagePubHandler) + for _, devicePort := range ha.devicePorts { + // Subscribe to topics for receiving commands + setPositionTopicName := ha.getSetPositionTopic(devicePort) + ha.log.Debugf("Subscribing to topic: %s", setPositionTopicName) + ha.mqttClient.Subscribe(setPositionTopicName, 0, homeAssistantSetPossitionMessagePubHandler) - // Publish discovery message - err := ha.PublishDiscoveryMessage() - if err != nil { - ha.log.Errorf("failed to publish discovery message. %v", err) - } + // Publish discovery messages + err := ha.PublishDiscoveryMessage(devicePort) + if err != nil { + ha.log.Errorf("failed to publish discovery message. %v", err) + } - // Configure availability - err = ha.PublishAvabilityMessage(true) - if err != nil { - ha.log.Errorf("failed to publish availability message (online). %v", err) + // Configure availabilities + err = ha.PublishAvabilityMessage(true, int(devicePort)) + if err != nil { + ha.log.Errorf("failed to publish availability message (online). %v", err) + } } mockDoor.StartTicker() @@ -223,10 +236,12 @@ out: break out case <-ticker.C: if ha.doorStatusSupported { - err := ha.doorStatus() - if err != nil { - ha.log.Errorf("failed to publish current door status. %v", err) - continue + for _, devicePort := range ha.devicePorts { + err := ha.doorStatus(byte(devicePort)) + if err != nil { + ha.log.Errorf("failed to publish current door status. %v", err) + continue + } } } else { ha.log.Debug("Status request skipped due to disable periodic requests.") @@ -237,10 +252,12 @@ out: } if ha.doorStatusSupported { - err := ha.doorStatus() - if err != nil { - ha.log.Errorf("failed to publish current door status. %v", err) - continue + for _, devicePort := range ha.devicePorts { + err := ha.doorStatus(byte(devicePort)) + if err != nil { + ha.log.Errorf("failed to publish current door status. %v", err) + continue + } } } else { ha.log.Debug("Status request skipped due to disable periodic requests.") @@ -259,11 +276,11 @@ func (ha *HomeAssistanceMqttClient) requestFastDootStatus() bool { return ha.requestFastUpdate.After(time.Now()) // check if fast update timeout is still not reached } -func (ha *HomeAssistanceMqttClient) doorStatus() error { +func (ha *HomeAssistanceMqttClient) doorStatus(devicePort byte) error { ha.log.Tracef("Publish current door status") startTs := time.Now() - direction, position, err := ha.getDoorStatus() + direction, position, err := ha.getDoorStatus(devicePort) endTs := time.Now() ha.log.Debugf("Get door status took %v", endTs.Sub(startTs)) if err != nil { @@ -283,7 +300,7 @@ func (ha *HomeAssistanceMqttClient) doorStatus() error { state = utils.OPEN } - err = ha.PublishCurrentDoorStatus(position, direction, state) + err = ha.PublishCurrentDoorStatus(position, direction, state, int(devicePort)) if err != nil { return fmt.Errorf("failed to publish current door status. %v", err) } @@ -291,18 +308,18 @@ func (ha *HomeAssistanceMqttClient) doorStatus() error { return nil } -func (ha *HomeAssistanceMqttClient) PublishCurrentDoorStatus(position int, direction string, state string) error { - mqttToken := ha.mqttClient.Publish(ha.getGetStateTopicName(), qosAtLeastOnce, false, state) +func (ha *HomeAssistanceMqttClient) PublishCurrentDoorStatus(position int, direction string, state string, devicePort int) error { + mqttToken := ha.mqttClient.Publish(ha.getGetStateTopicName(devicePort), qosAtLeastOnce, false, state) if mqttToken.Wait() && mqttToken.Error() != nil { return fmt.Errorf("failed to publish discovery message. %v", mqttToken.Error()) } - mqttToken = ha.mqttClient.Publish(ha.getPositionTopicName(), qosAtLeastOnce, false, fmt.Sprintf("%d", position)) + mqttToken = ha.mqttClient.Publish(ha.getPositionTopicName(devicePort), qosAtLeastOnce, false, fmt.Sprintf("%d", position)) if mqttToken.Wait() && mqttToken.Error() != nil { return fmt.Errorf("failed to publish discovery message. %v", mqttToken.Error()) } - mqttToken = ha.mqttClient.Publish(ha.getDirectionTopicName(), qosAtLeastOnce, false, direction) + mqttToken = ha.mqttClient.Publish(ha.getDirectionTopicName(devicePort), qosAtLeastOnce, false, direction) if mqttToken.Wait() && mqttToken.Error() != nil { return fmt.Errorf("failed to publish discovery message. %v", mqttToken.Error()) } @@ -312,7 +329,7 @@ func (ha *HomeAssistanceMqttClient) PublishCurrentDoorStatus(position int, direc return nil } -func (ha *HomeAssistanceMqttClient) PublishAvabilityMessage(online bool) error { +func (ha *HomeAssistanceMqttClient) PublishAvabilityMessage(online bool, devicePort int) error { var message string if online { @@ -321,7 +338,7 @@ func (ha *HomeAssistanceMqttClient) PublishAvabilityMessage(online bool) error { message = utils.OFFLINE } - mqttToken := ha.mqttClient.Publish(ha.getAvailabilityTopic(), qosAtLeastOnce, true, message) + mqttToken := ha.mqttClient.Publish(ha.getAvailabilityTopic(devicePort), qosAtLeastOnce, true, message) if mqttToken.Wait() && mqttToken.Error() != nil { return fmt.Errorf("failed to publish avability message. %v", mqttToken.Error()) } @@ -331,13 +348,13 @@ func (ha *HomeAssistanceMqttClient) PublishAvabilityMessage(online bool) error { return nil } -func (ha *HomeAssistanceMqttClient) PublishDiscoveryMessage() error { - discoveryMsg, err := ha.getDiscoveryMessage() +func (ha *HomeAssistanceMqttClient) PublishDiscoveryMessage(devicePort int) error { + discoveryMsg, err := ha.getDiscoveryMessage(devicePort) if err != nil { return fmt.Errorf("failed to generate discovery message. %v", err) } - mqttToken := ha.mqttClient.Publish(ha.getDiscoveryTopic(!ha.doorStatusSupported), qosAtLeastOnce, true, discoveryMsg) + mqttToken := ha.mqttClient.Publish(ha.getDiscoveryTopic(!ha.doorStatusSupported, devicePort), qosAtLeastOnce, true, discoveryMsg) if mqttToken.Wait() && mqttToken.Error() != nil { return fmt.Errorf("failed to publish discovery message. %v", mqttToken.Error()) } diff --git a/cli/homeAssistant/messages.go b/cli/homeAssistant/messages.go index ba4398b..7fb90c9 100644 --- a/cli/homeAssistant/messages.go +++ b/cli/homeAssistant/messages.go @@ -5,15 +5,15 @@ import ( "fmt" ) -func (ha *HomeAssistanceMqttClient) getDiscoveryMessage() (string, error) { - name := ha.getUniqueObjectId() - uniqueId := ha.getUniqueObjectId() +func (ha *HomeAssistanceMqttClient) getDiscoveryMessage(devicePort int) (string, error) { + name := ha.getUniqueObjectId(devicePort) + uniqueId := ha.getUniqueObjectId(devicePort) device_class := `"device_class": "garage",` if !ha.doorStatusSupported { device_class = "" } - commandTopic := ha.getSetPositionTopic() - positionTopic := ha.getPositionTopicName() + commandTopic := ha.getSetPositionTopic(devicePort) + positionTopic := ha.getPositionTopicName(devicePort) messageTemplate := ` { @@ -31,7 +31,7 @@ func (ha *HomeAssistanceMqttClient) getDiscoveryMessage() (string, error) { "payload_not_available": "%s" }` - message := fmt.Sprintf(messageTemplate, name, uniqueId, device_class, commandTopic, positionTopic, uniqueId, name, ha.getAvailabilityTopic(), ha.getAvabilityMessage(true), ha.getAvabilityMessage(false)) + message := fmt.Sprintf(messageTemplate, name, uniqueId, device_class, commandTopic, positionTopic, uniqueId, name, ha.getAvailabilityTopic(devicePort), ha.getAvabilityMessage(true), ha.getAvabilityMessage(false)) return message, nil } diff --git a/cli/homeAssistant/topics.go b/cli/homeAssistant/topics.go index 602305f..0414d81 100644 --- a/cli/homeAssistant/topics.go +++ b/cli/homeAssistant/topics.go @@ -5,36 +5,36 @@ import ( "fmt" ) -func (ha *HomeAssistanceMqttClient) getPositionTopicName() string { - return fmt.Sprintf("%s/%s/position", ha.mqttBaseTopic, ha.mqttDeviceName) +func (ha *HomeAssistanceMqttClient) getPositionTopicName(devicePort int) string { + return fmt.Sprintf("%s/%d/position", ha.mqttBaseTopic, devicePort) } -func (ha *HomeAssistanceMqttClient) getSetPositionTopic() string { - return fmt.Sprintf("%s/cmnd/%s/position", ha.mqttBaseTopic, ha.mqttDeviceName) +func (ha *HomeAssistanceMqttClient) getSetPositionTopic(devicePort int) string { + return fmt.Sprintf("%s/cmnd/%d/position", ha.mqttBaseTopic, devicePort) } -func (ha *HomeAssistanceMqttClient) getDirectionTopicName() string { - return fmt.Sprintf("%s/%s/direction", ha.mqttBaseTopic, ha.mqttDeviceName) +func (ha *HomeAssistanceMqttClient) getDirectionTopicName(devicePort int) string { + return fmt.Sprintf("%s/%d/direction", ha.mqttBaseTopic, devicePort) } -func (ha *HomeAssistanceMqttClient) getGetStateTopicName() string { - return fmt.Sprintf("%s/%s/state", ha.mqttBaseTopic, ha.mqttDeviceName) +func (ha *HomeAssistanceMqttClient) getGetStateTopicName(devicePort int) string { + return fmt.Sprintf("%s/%d/state", ha.mqttBaseTopic, devicePort) } -func (ha *HomeAssistanceMqttClient) getDiscoveryTopic(impulsCommandOnly bool) string { +func (ha *HomeAssistanceMqttClient) getDiscoveryTopic(impulsCommandOnly bool, devicePort int) string { ////[/]/config entity_type := "cover" if impulsCommandOnly { entity_type = "button" } - return fmt.Sprintf("homeassistant/%s/halsecur/%s/config", entity_type, ha.getUniqueObjectId()) + return fmt.Sprintf("homeassistant/%s/halsecur/%s/config", entity_type, ha.getUniqueObjectId(devicePort)) } -func (ha *HomeAssistanceMqttClient) getUniqueObjectId() string { +func (ha *HomeAssistanceMqttClient) getUniqueObjectId(devicePort int) string { deviceMacStr := hex.EncodeToString(ha.deviceMac[:]) - return fmt.Sprintf("%s%d", deviceMacStr, ha.devicePort) + return fmt.Sprintf("%s%d", deviceMacStr, devicePort) } -func (ha *HomeAssistanceMqttClient) getAvailabilityTopic() string { - return fmt.Sprintf("%s/%s/availability", ha.mqttBaseTopic, ha.mqttDeviceName) +func (ha *HomeAssistanceMqttClient) getAvailabilityTopic(devicePort int) string { + return fmt.Sprintf("%s/%d/availability", ha.mqttBaseTopic, devicePort) } From d857e80de5e4d411440e18759cb8cf12734b6c16 Mon Sep 17 00:00:00 2001 From: lauorez Date: Thu, 1 Jan 2026 18:33:24 +0100 Subject: [PATCH 6/6] remove redundant parameter in getDiscoveryTopic() --- cli/homeAssistant/client.go | 2 +- cli/homeAssistant/topics.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/homeAssistant/client.go b/cli/homeAssistant/client.go index 911fb22..7142921 100644 --- a/cli/homeAssistant/client.go +++ b/cli/homeAssistant/client.go @@ -354,7 +354,7 @@ func (ha *HomeAssistanceMqttClient) PublishDiscoveryMessage(devicePort int) erro return fmt.Errorf("failed to generate discovery message. %v", err) } - mqttToken := ha.mqttClient.Publish(ha.getDiscoveryTopic(!ha.doorStatusSupported, devicePort), qosAtLeastOnce, true, discoveryMsg) + mqttToken := ha.mqttClient.Publish(ha.getDiscoveryTopic(devicePort), qosAtLeastOnce, true, discoveryMsg) if mqttToken.Wait() && mqttToken.Error() != nil { return fmt.Errorf("failed to publish discovery message. %v", mqttToken.Error()) } diff --git a/cli/homeAssistant/topics.go b/cli/homeAssistant/topics.go index 0414d81..bfa7a44 100644 --- a/cli/homeAssistant/topics.go +++ b/cli/homeAssistant/topics.go @@ -21,10 +21,10 @@ func (ha *HomeAssistanceMqttClient) getGetStateTopicName(devicePort int) string return fmt.Sprintf("%s/%d/state", ha.mqttBaseTopic, devicePort) } -func (ha *HomeAssistanceMqttClient) getDiscoveryTopic(impulsCommandOnly bool, devicePort int) string { +func (ha *HomeAssistanceMqttClient) getDiscoveryTopic(devicePort int) string { ////[/]/config entity_type := "cover" - if impulsCommandOnly { + if !ha.doorStatusSupported { entity_type = "button" } return fmt.Sprintf("homeassistant/%s/halsecur/%s/config", entity_type, ha.getUniqueObjectId(devicePort))