{"id":16408695,"url":"https://github.com/creativeprojects/gopenhab","last_synced_at":"2025-04-22T09:30:58.878Z","repository":{"id":57581409,"uuid":"363919585","full_name":"creativeprojects/gopenhab","owner":"creativeprojects","description":"Write your openHAB rules in Go","archived":false,"fork":false,"pushed_at":"2024-02-21T22:16:54.000Z","size":3564,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-02-22T22:43:43.198Z","etag":null,"topics":["go","golang","openhab","openhab-rules"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/creativeprojects.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2021-05-03T12:18:27.000Z","updated_at":"2024-04-14T21:37:18.855Z","dependencies_parsed_at":"2024-04-14T21:47:32.642Z","dependency_job_id":null,"html_url":"https://github.com/creativeprojects/gopenhab","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/creativeprojects%2Fgopenhab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/creativeprojects%2Fgopenhab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/creativeprojects%2Fgopenhab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/creativeprojects%2Fgopenhab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/creativeprojects","download_url":"https://codeload.github.com/creativeprojects/gopenhab/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223893165,"owners_count":17220834,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","openhab","openhab-rules"],"created_at":"2024-10-11T06:17:33.703Z","updated_at":"2024-11-09T23:02:58.710Z","avatar_url":"https://github.com/creativeprojects.png","language":"Go","readme":"[![Go Reference](https://pkg.go.dev/badge/github.com/creativeprojects/gopenhab.svg)](https://pkg.go.dev/github.com/creativeprojects/gopenhab)\n[![Build](https://github.com/creativeprojects/gopenhab/actions/workflows/build.yml/badge.svg)](https://github.com/creativeprojects/gopenhab/actions/workflows/build.yml)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=creativeprojects_gopenhab\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=creativeprojects_gopenhab)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=creativeprojects_gopenhab\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=creativeprojects_gopenhab)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=creativeprojects_gopenhab\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=creativeprojects_gopenhab)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=creativeprojects_gopenhab\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=creativeprojects_gopenhab)\n\n# gopenHAB\n\nWrite your openHAB rules in Go. The power of openHAB rules with the simplicity of Go.\n\nI had some existing code written in Go that I needed to connect to openHAB.\nMy first thought was to make a REST API to make this external system accessible from openHAB, but after fiddling with openHAB DSL rules and trying Jython scripts, I realized the best thing was to actually connect my system to the openHAB event bus and replicate a rule system, all in Go.\n\nIn theory, everything you can do in a DSL rule or in Jython should be available.\n\nNotes:\n- I do fully use it on my home automation platform: I did move all my DSL rules to gopenhab.\n- This is still work in progress: you might need some events that are not yet implemented, or some features that are not yet available.\n\nHere's an example of what you can do with it right now:\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/creativeprojects/gopenhab/event\"\n\t\"github.com/creativeprojects/gopenhab/openhab\"\n)\n\nfunc main() {\n\topenhab.SetDebugLog(log.Default())\n\tclient := openhab.NewClient(openhab.Config{\n\t\tURL: \"http://localhost:8080\",\n\t})\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Connected to openHAB events\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tlog.Printf(\"SYSTEM EVENT: client connected\")\n\t\t},\n\t\topenhab.Debounce(1*time.Minute, openhab.OnConnect()),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Disconnected from openHAB events\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tlog.Print(\"SYSTEM EVENT: client disconnected\")\n\t\t},\n\t\topenhab.Debounce(10*time.Second, openhab.OnDisconnect()),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Receiving item command\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tif ev, ok := e.(event.ItemReceivedCommand); ok {\n\t\t\t\tlog.Printf(\"COMMAND EVENT: Back_Garden_Lighting_Switch received command %+v\", ev.Command)\n\t\t\t}\n\t\t},\n\t\topenhab.OnItemReceivedCommand(\"Back_Garden_Lighting_Switch\", nil),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Receiving ON command\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tlog.Print(\"COMMAND EVENT: Back_Garden_Lighting_Switch switched ON\")\n\t\t},\n\t\topenhab.OnItemReceivedCommand(\"Back_Garden_Lighting_Switch\", openhab.SwitchON),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Receiving OFF command\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tlog.Print(\"COMMAND EVENT: Back_Garden_Lighting_Switch switched OFF\")\n\t\t},\n\t\topenhab.OnItemReceivedCommand(\"Back_Garden_Lighting_Switch\", openhab.SwitchOFF),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Receiving updated state\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tif ev, ok := e.(event.ItemReceivedState); ok {\n\t\t\t\tlog.Printf(\"STATE EVENT: Back_Garden_Lighting_Switch received state %+v\", ev.State)\n\t\t\t}\n\t\t},\n\t\topenhab.OnItemReceivedState(\"Back_Garden_Lighting_Switch\", nil),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Receiving ON state\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tlog.Printf(\"STATE EVENT: Back_Garden_Lighting_Switch state is now ON\")\n\t\t},\n\t\topenhab.OnItemReceivedState(\"Back_Garden_Lighting_Switch\", openhab.SwitchON),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Receiving OFF state\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tlog.Printf(\"STATE EVENT: Back_Garden_Lighting_Switch state is now OFF\")\n\t\t},\n\t\topenhab.OnItemReceivedState(\"Back_Garden_Lighting_Switch\", openhab.SwitchOFF),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{Name: \"Receiving state changed\"},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\tif ev, ok := e.(event.ItemStateChanged); ok {\n\t\t\t\tlog.Printf(\"STATE CHANGED EVENT: Back_Garden_Lighting_Switch changed to state %+v\", ev.NewState)\n\t\t\t}\n\t\t},\n\t\topenhab.OnItemStateChanged(\"Back_Garden_Lighting_Switch\"),\n\t)\n\n\tclient.AddRule(\n\t\topenhab.RuleData{\n\t\t\tName: \"Test rule\",\n\t\t},\n\t\tfunc(client *openhab.Client, ruleData openhab.RuleData, e event.Event) {\n\t\t\titem, err := client.GetItem(\"Back_Garden_Lighting_Switch\")\n\t\t\tif err != nil {\n\t\t\t\tlog.Print(err)\n\t\t\t}\n\n\t\t\t_, err = item.SendCommandWait(openhab.SwitchON, 2*time.Second)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"sending command: %s\", err)\n\t\t\t}\n\t\t\ttime.Sleep(4 * time.Second)\n\n\t\t\t_, err = item.SendCommandWait(openhab.SwitchOFF, 2*time.Second)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"sending command: %s\", err)\n\t\t\t}\n\t\t},\n\t\topenhab.OnTimeCron(\"*/10 * * ? * *\"),\n\t)\n\tclient.Start()\n}\n\n\n```\n\n# Unit test your rules\n\nTo be able to run some unit tests I created a *mock* openHAB server, which can trigger events and can keep items in memory. This is work in progress but you can use it to test your rules.\n\n## How to test a simple event\n\nImagine you have a function `calculateZoneTemperature` that takes an array of values coming from the `Context` and sends an average temperature to an output item. The context of the function will be as such:\n\n```go\n\nopenhab.RuleData{\n\tName: \"Calculate average\",\n\tContext: zoneContext{\n\t\tname:   \"test-zone\",\n\t\tconfig: ZoneConfiguration{Output: \"ZoneTemperature\", Sensors: []string{\"temperature1\", \"temperature2\"}},\n\t},\n},\n```\n\nImagine the `calculateAverage` function simply sends the result to the item in output configuration, from the example the name of the item is `ZoneTemperature`.\n\nHere's how you can test it with the openHAB mock server:\n\n```go\nfunc TestCalculateZoneTemperature(t *testing.T) {\n\tconst temperatureItem1 = \"temperature1\"\n\tconst temperatureItem2 = \"temperature2\"\n\tconst averageTemperatureItem = \"ZoneTemperature\"\n\n\t// Create the openHAB mock server that will publish events from changes coming from the API calls\n\tserver := openhabtest.NewServer(openhabtest.Config{Log: t, SendEventsFromAPI: true})\n\tdefer server.Close()\n\n\t// setup all 3 items in the mock server\n\tserver.SetItem(api.Item{\n\t\tName:  averageTemperatureItem,\n\t\tType:  \"Number\",\n\t\tState: \"0.0\",\n\t})\n\tserver.SetItem(api.Item{\n\t\tName:  temperatureItem1,\n\t\tType:  \"Number\",\n\t\tState: \"10.0\",\n\t})\n\tserver.SetItem(api.Item{\n\t\tName:  temperatureItem2,\n\t\tType:  \"Number\",\n\t\tState: \"10.0\",\n\t})\n\n\t// create a client that connects to our mock server\n\tclient := openhab.NewClient(openhab.Config{URL: server.URL()})\n\n\t// standard rule to calculate the average\n\tclient.AddRule(\n\t\topenhab.RuleData{\n\t\t\tName: \"Calculate average\",\n\t\t\tContext: zoneContext{\n\t\t\t\tname:   \"test-zone\",\n\t\t\t\tconfig: ZoneConfiguration{Output: averageTemperatureItem, Sensors: []string{temperatureItem1, temperatureItem2}},\n\t\t\t},\n\t\t},\n\t\tcalculateZoneTemperature, // this is the code to test\n\t\topenhab.OnItemReceivedState(temperatureItem1, nil),\n\t\topenhab.OnItemReceivedState(temperatureItem2, nil),\n\t)\n\n\t// testing rule to verify the calculation\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tclient.AddRule(\n\t\topenhab.RuleData{\n\t\t\tName: \"Test rule\",\n\t\t},\n\t\tfunc(client openhab.RuleClient, ruleData openhab.RuleData, e event.Event) {\n\t\t\t// test is finished after receiving this event\n\t\t\tdefer wg.Done()\n\n\t\t\tev, ok := e.(event.ItemReceivedCommand)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"expected event to be of type ItemReceivedCommand\")\n\t\t\t}\n\t\t\tassert.Equal(t, \"10.5\", ev.Command)\n\t\t},\n\t\topenhab.OnItemReceivedCommand(averageTemperatureItem, nil),\n\t)\n\n\t// start the client in the background so we can send events to it\n\tgo func() {\n\t\tclient.Start()\n\t}()\n\n\t// make sure the client is ready (it surely needs less than that)\n\ttime.Sleep(10 * time.Millisecond)\n\t// we simulate openhab receiving an event: temperature2 item received a new value of 11 degrees, brrrr!\n\tserver.Event(event.NewItemReceivedState(temperatureItem2, \"Number\", \"11.0\"))\n\n\twg.Wait()\n\tclient.Stop()\n}\n```\n\n# TODO\n\n- Handle all state types. Handled for now are `String`, `Switch`, `Number`, `DateTime`.\n- Add triggers for more events. All `item` events have triggers, and some `thing` events (but not all)\n- Ability to update rules\n- Handle more events on the openhab test server (typically, `things` are not supported yet)\n\n# Limitations of the mock openHAB server for testing\n\n- When the server receives a `command` event, it doesn't follow with any corresponding `state` event. You need to publish the `state` events manually if you need them in your tests.\n\n# Compatibility\n\nThe library supports API version 3 to 6.\n\nAt some point in time, this library was tested (and running in production) with these versions of openHAB:\n- 2.5\n- 3.0\n- 3.1\n- 3.2\n- 3.3\n- 3.4\n- 4.0\n- 4.1\n\n# Integration tests\n\nI do have some integrations tests running against a real openHAB server (currently 4.1.2). The server is running an exact copy of my home configuration, with a script sending mock states to the server via MQTT.\n\nFor reliability testing, I also have a Toxiproxy between openHAB and gopenhab.\n\nI might publish some of these tests at some point.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcreativeprojects%2Fgopenhab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcreativeprojects%2Fgopenhab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcreativeprojects%2Fgopenhab/lists"}