├── .github └── workflows │ └── gitleaks.yml ├── .gitignore ├── .media ├── mbmd.png ├── mqtt-topics.png ├── victron_meter.png └── vrm_portal.png ├── LICENSE ├── Makefile ├── README.md ├── bin ├── arm │ └── bridge │ │ └── sdm630-bridge ├── sdm630-bridge └── startup.sh ├── go.mod ├── go.sum ├── main.go └── sdm630-bridge.go /.github/workflows/gitleaks.yml: -------------------------------------------------------------------------------- 1 | name: gitleaks 2 | on: [pull_request, push, workflow_dispatch] 3 | jobs: 4 | scan: 5 | name: gitleaks 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 11 | - uses: gitleaks/gitleaks-action@v2 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE}} # Only required for Organizations, not personal accounts. 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | -------------------------------------------------------------------------------- /.media/mbmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormmurdoc/victron_sdm630_bridge/094b4c6b43d0a6fb736c480a98f939baf31db798/.media/mbmd.png -------------------------------------------------------------------------------- /.media/mqtt-topics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormmurdoc/victron_sdm630_bridge/094b4c6b43d0a6fb736c480a98f939baf31db798/.media/mqtt-topics.png -------------------------------------------------------------------------------- /.media/victron_meter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormmurdoc/victron_sdm630_bridge/094b4c6b43d0a6fb736c480a98f939baf31db798/.media/victron_meter.png -------------------------------------------------------------------------------- /.media/vrm_portal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormmurdoc/victron_sdm630_bridge/094b4c6b43d0a6fb736c480a98f939baf31db798/.media/vrm_portal.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Patrick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o bin/sdm630-bridge main.go 3 | 4 | run: 5 | go run main.go 6 | 7 | compile: 8 | echo "Compiling for ARM OS (Venus)" 9 | GOOS=linux GOARCH=arm GOARM=7 go build -o bin/arm/bridge/sdm630-bridge main.go 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Victron Eastron SDM630 Bridge (Alternative to the EM24) 2 | 3 | This small program emulates the Energy Meter (EM24) in a 4 | Victron ESS System. It reads values from an existing MQTT 5 | Broker (in my case MBMD) and publishes the result on dbus 6 | as if it were the SDM630 meter. 7 | 8 | ![Victron Overview](./.media/victron_meter.png) 9 | 10 | Use this at your own risk, I have no association with Victron 11 | or Eastron and am providing this for anyone who already has 12 | these components and wants to play around with this. 13 | 14 | I use this privately, and it works in my timezone, your 15 | results may vary. 16 | 17 | __Update:__ Having owned a Multiplus II now, I can confirm 18 | that it works flawlessly with the ESS! 19 | 20 | Special Thanks to Sean (mitchese) who did most of the work for the shm-et340. 21 | Mostly of the code is forked from his repo. You can find it here: 22 | [Repo](https://github.com/mitchese/shm-et340) 23 | 24 | VRM Portal 25 | 26 | ![Victron VRM Portal](./.media/vrm_portal.png) 27 | 28 | ## Table of content 29 | 30 | - [Tested Venus OS Version](#tested-venus-os-version) 31 | - [Setup](#setup) 32 | - [Install MBMD](#install-mbmd) 33 | - [Configuration](#configuration) 34 | - [Change Default Configuration](#change-default-configuration) 35 | - [Compiling from source](#compiling-from-source) 36 | - [Update go modules](#update-go-modules) 37 | - [Copy the file to your Venus OS device (e.g. CerboGX)](#copy-the-file-to-your-venus-os-device-eg-cerbogx) 38 | - [Start the program](#start-the-program) 39 | - [Autostart on Venus OS](#autostart-on-venus-os) 40 | - [Create rc.local file](#create-rclocal-file) 41 | - [Create the startup script](#create-the-startup-script) 42 | - [Victron Grid Meter Values](#victron-grid-meter-values) 43 | - [ToDo](#todo) 44 | 45 | ## Tested Venus OS Version 46 | 47 | - 18.03.2022 v2.84 48 | - 10.02.2023 v2.92 49 | 50 | ## Setup 51 | 52 | ### Install MBMD 53 | 54 | To load the data from the EASTRON power meter into your MQTT Broker, 55 | please use the mbmd program. You can find more information here: 56 | [Volkzaehler/mbmd](https://github.com/volkszaehler/mbmd) 57 | 58 | ![MBMD Frontend](./.media/mbmd.png) 59 | 60 | Here is an example of how the SDM630 data looks in the broker: 61 | 62 | ![MQTT-Topics](./.media/mqtt-topics.png) 63 | 64 | example mbmd autostart added in `/data/bridge/startup.sh`: 65 | 66 | ```bash 67 | #!/bin/bash 68 | while true; do 69 | /data/bridge/mbmd run -a IpOfModbusToTcpOrModbusToUsbPath:502 \ 70 | --rtu -d='sdm:1' --mqtt-broker='127.0.0.1:1883' \ 71 | --mqtt-topic='stromzaehler' & 72 | /data/bridge/sdm630-bridge --broker=127.0.0.1 --port=1883 \ 73 | --topic='stromzaehler/#' --client-id='grid-meter-bridge' 74 | sleep 1 75 | done 76 | ``` 77 | 78 | ## Configuration 79 | 80 | ### Change Default Configuration 81 | 82 | You can use CLI flags to set the proper values for your setup but 83 | if you want you can also change the defaults in the `./main.go` file: 84 | 85 | ```golang 86 | var ( 87 | broker = "192.168.1.119" 88 | brokerPort = 1883 89 | topic = "stromzaehler/#" 90 | clientId = "sdm630-bridge" 91 | username = "user" 92 | password = "pass" 93 | ) 94 | ``` 95 | 96 | ### Compiling from source 97 | 98 | To compile this for the Venus GX (an Arm 7 processor), you can 99 | easily cross-compile with the following: 100 | 101 | ```golang 102 | `GOOS=linux GOARCH=arm GOARM=7 go build -o bin/arm/bridge/sdm630-bridge main.go` 103 | ``` 104 | 105 | You can compile it also with the make command: 106 | 107 | ```golang 108 | make compile 109 | ``` 110 | 111 | ### Update go modules 112 | 113 | ```shell 114 | go get -u && go mod tidy 115 | ``` 116 | 117 | ### Copy the file to your Venus OS device (e.g. CerboGX) 118 | 119 | You will need SSH access to your Venus GX device. You can find 120 | more information here: [Venus OS: Enable SSH](https://www.victronenergy.com/live/ccgx:root_access#set_access_level_to_superuser) 121 | 122 | ```shell 123 | scp -rp ./bin/arm/bridge root@CerboGX:/data/ 124 | ``` 125 | 126 | ### Start the program 127 | 128 | Login in via ssh to your Venus Device: 129 | 130 | ```shell 131 | ssh root@CerboGX 132 | ``` 133 | 134 | Start the program: 135 | 136 | ```shell 137 | cd /data/bridge 138 | ./sdm630-bridge --broker=192.168.1.119 --port=1883 \ 139 | --username=user \ 140 | --password="pass" \ 141 | --topic="stromzaehler/#" \ 142 | --client-id="grid-meter-bridge" 143 | ``` 144 | 145 | ### Autostart on Venus OS 146 | 147 | The only directory that is unaffected by an update is the /data directory. 148 | If there is an executable file with the name rc.local, it will be executed 149 | when the system is started. This makes it possible to start the 150 | sdm630-bridge automatically. 151 | 152 | ### Create rc.local file 153 | 154 | Login into the system via ssh. Create a file with the following command: 155 | 156 | ```shell 157 | vi /data/rc.local 158 | ``` 159 | 160 | Add the following content: 161 | 162 | ```shell 163 | #!/bin/bash 164 | sleep 20 && /data/bridge/startup.sh > /data/bridge/sdm630-bridge.log 2>&1 & 165 | ``` 166 | 167 | Save the file and make them executable: 168 | 169 | ```shell 170 | chmod +x /data/rc.local 171 | ``` 172 | 173 | ### Create the startup script 174 | 175 | ```shell 176 | cd /data/bridge/ 177 | vi startup.sh 178 | ``` 179 | 180 | Paste the following content into the startup script, remember to change 181 | the CLI arguments to suit your environment: 182 | 183 | ```shell 184 | #!/bin/sh 185 | while true; do 186 | /data/bridge/sdm630-bridge --broker=192.168.1.119 \ 187 | --port=1883 --username=user --password="pass" \ 188 | --topic="stromzaehler/#" --client-id="grid-meter-bridge" 189 | sleep 1 190 | done 191 | ``` 192 | 193 | Save the file and make them executable: 194 | 195 | ```shell 196 | chmod +x /data/bridge/startup.sh 197 | ``` 198 | 199 | Reboot the system and check if the process come up. 200 | 201 | ```shell 202 | ps | grep sdm630 203 | ``` 204 | 205 | ### Victron Grid Meter Values 206 | 207 | [Source Victron](https://github.com/victronenergy/venus/wiki/dbus#grid-meter) 208 | 209 | ```shell 210 | com.victronenergy.grid 211 | 212 | /Ac/Energy/Forward <- kWh - bought energy (total of all phases) 213 | /Ac/Energy/Reverse <- kWh - sold energy (total of all phases) 214 | /Ac/Power <- W - total of all phases, real power 215 | 216 | /Ac/Current <- A AC - Deprecated 217 | /Ac/Voltage <- V AC - Deprecated 218 | 219 | /Ac/L1/Current <- A AC 220 | /Ac/L1/Energy/Forward <- kWh - bought 221 | /Ac/L1/Energy/Reverse <- kWh - sold 222 | /Ac/L1/Power <- W, real power 223 | /Ac/L1/Voltage <- V AC 224 | /Ac/L2/* <- same as L1 225 | /Ac/L3/* <- same as L1 226 | /DeviceType 227 | /ErrorCode 228 | ``` 229 | 230 | ## ToDo 231 | 232 | - [ ] Check Update process -> [ccgx:root_access](https://www.victronenergy.com/live/ccgx:root_access) 233 | -------------------------------------------------------------------------------- /bin/arm/bridge/sdm630-bridge: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormmurdoc/victron_sdm630_bridge/094b4c6b43d0a6fb736c480a98f939baf31db798/bin/arm/bridge/sdm630-bridge -------------------------------------------------------------------------------- /bin/sdm630-bridge: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormmurdoc/victron_sdm630_bridge/094b4c6b43d0a6fb736c480a98f939baf31db798/bin/sdm630-bridge -------------------------------------------------------------------------------- /bin/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | while true; do 3 | /data/home/root/sdm630-bridge 4 | sleep 1 5 | done 6 | 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module sdm630-bridge.go 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/eclipse/paho.mqtt.golang v1.4.3 7 | github.com/godbus/dbus v4.1.0+incompatible 8 | github.com/godbus/dbus/v5 v5.1.0 9 | github.com/gorilla/websocket v1.5.1 // indirect 10 | github.com/sirupsen/logrus v1.9.3 11 | golang.org/x/net v0.21.0 // indirect 12 | golang.org/x/sync v0.6.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= 5 | github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= 6 | github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= 7 | github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= 8 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 9 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 10 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 11 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 12 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 16 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 18 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 19 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 20 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 21 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 22 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 23 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 24 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 25 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 26 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 27 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 28 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 29 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 30 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 31 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 32 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 33 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 34 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 35 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 36 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 37 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 38 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= 40 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 49 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= 52 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 53 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 54 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 55 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 56 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 57 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 58 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 59 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 61 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 62 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 63 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 64 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 65 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 66 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 67 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 68 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 69 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 70 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 71 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 72 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 73 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 75 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 76 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | mqtt "github.com/eclipse/paho.mqtt.golang" 13 | "github.com/godbus/dbus/introspect" 14 | "github.com/godbus/dbus/v5" 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | /* Configuration */ 19 | var ( 20 | broker = "192.168.1.119" 21 | brokerPort = 1883 22 | topic = "stromzaehler/#" 23 | clientId = "sdm630-bridge" 24 | username = "user" 25 | password = "pass" 26 | ) 27 | 28 | var P1 float64 = 0.00 29 | var P2 float64 = 0.00 30 | var P3 float64 = 0.00 31 | var psum float64 = 0.00 32 | var psum_update bool = true 33 | var value_correction bool = false 34 | var conn, err = dbus.SystemBus() 35 | 36 | type singlePhase struct { 37 | voltage float32 // Volts: 230,0 38 | curent float32 // Amps: 8,3 39 | power float32 // Watts: 1909 40 | forward float64 // kWh, purchased power 41 | reverse float64 // kWh, sold power 42 | } 43 | 44 | const intro = ` 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ` + introspect.IntrospectDataString + ` ` 61 | 62 | type objectpath string 63 | 64 | var victronValues = map[int]map[objectpath]dbus.Variant{ 65 | // 0: This will be used to store the VALUE variant 66 | 0: map[objectpath]dbus.Variant{}, 67 | // 1: This will be used to store the STRING variant 68 | 1: map[objectpath]dbus.Variant{}, 69 | } 70 | 71 | func (f objectpath) GetValue() (dbus.Variant, *dbus.Error) { 72 | log.Debug("GetValue() called for ", f) 73 | log.Debug("...returning ", victronValues[0][f]) 74 | return victronValues[0][f], nil 75 | } 76 | func (f objectpath) GetText() (string, *dbus.Error) { 77 | log.Debug("GetText() called for ", f) 78 | log.Debug("...returning ", victronValues[1][f]) 79 | // Why does this end up ""SOMEVAL"" ... trim it I guess 80 | return strings.Trim(victronValues[1][f].String(), "\""), nil 81 | } 82 | 83 | func init() { 84 | lvl, ok := os.LookupEnv("LOG_LEVEL") 85 | if !ok { 86 | lvl = "info" 87 | } 88 | 89 | ll, err := log.ParseLevel(lvl) 90 | if err != nil { 91 | ll = log.DebugLevel 92 | } 93 | 94 | log.SetLevel(ll) 95 | } 96 | 97 | func main() { 98 | // Parse command line arguments 99 | flag.StringVar(&broker, "broker", broker, "MQTT broker address") 100 | flag.IntVar(&brokerPort, "port", brokerPort, "MQTT broker port") 101 | flag.StringVar(&topic, "topic", topic, "MQTT topic prefix") 102 | flag.StringVar(&clientId, "client-id", clientId, "MQTT client id") 103 | flag.StringVar(&username, "username", username, "MQTT username") 104 | flag.StringVar(&password, "password", password, "MQTT password") 105 | flag.Parse() 106 | 107 | // Need to implement following paths: 108 | // https://github.com/victronenergy/venus/wiki/dbus#grid-meter 109 | // also in system.py 110 | victronValues[0]["/Connected"] = dbus.MakeVariant(1) 111 | victronValues[1]["/Connected"] = dbus.MakeVariant("1") 112 | 113 | victronValues[0]["/CustomName"] = dbus.MakeVariant("Grid meter") 114 | victronValues[1]["/CustomName"] = dbus.MakeVariant("Grid meter") 115 | 116 | victronValues[0]["/DeviceInstance"] = dbus.MakeVariant(30) 117 | victronValues[1]["/DeviceInstance"] = dbus.MakeVariant("30") 118 | 119 | // also in system.py 120 | victronValues[0]["/DeviceType"] = dbus.MakeVariant(71) 121 | victronValues[1]["/DeviceType"] = dbus.MakeVariant("71") 122 | 123 | victronValues[0]["/ErrorCode"] = dbus.MakeVariantWithSignature(0, dbus.SignatureOf(123)) 124 | victronValues[1]["/ErrorCode"] = dbus.MakeVariant("0") 125 | 126 | victronValues[0]["/FirmwareVersion"] = dbus.MakeVariant(2) 127 | victronValues[1]["/FirmwareVersion"] = dbus.MakeVariant("2") 128 | 129 | // also in system.py 130 | victronValues[0]["/Mgmt/Connection"] = dbus.MakeVariant("/dev/ttyUSB0") 131 | victronValues[1]["/Mgmt/Connection"] = dbus.MakeVariant("/dev/ttyUSB0") 132 | 133 | victronValues[0]["/Mgmt/ProcessName"] = dbus.MakeVariant("/opt/color-control/dbus-cgwacs/dbus-cgwacs") 134 | victronValues[1]["/Mgmt/ProcessName"] = dbus.MakeVariant("/opt/color-control/dbus-cgwacs/dbus-cgwacs") 135 | 136 | victronValues[0]["/Mgmt/ProcessVersion"] = dbus.MakeVariant("1.8.0") 137 | victronValues[1]["/Mgmt/ProcessVersion"] = dbus.MakeVariant("1.8.0") 138 | 139 | victronValues[0]["/Position"] = dbus.MakeVariantWithSignature(0, dbus.SignatureOf(123)) 140 | victronValues[1]["/Position"] = dbus.MakeVariant("0") 141 | 142 | // also in system.py 143 | victronValues[0]["/ProductId"] = dbus.MakeVariant(45058) 144 | victronValues[1]["/ProductId"] = dbus.MakeVariant("45058") 145 | 146 | // also in system.py 147 | victronValues[0]["/ProductName"] = dbus.MakeVariant("Grid meter") 148 | victronValues[1]["/ProductName"] = dbus.MakeVariant("Grid meter") 149 | 150 | victronValues[0]["/Serial"] = dbus.MakeVariant("BP98305081235") 151 | victronValues[1]["/Serial"] = dbus.MakeVariant("BP98305081235") 152 | 153 | // Provide some initial values... note that the values must be a valid formt otherwise dbus_systemcalc.py exits like this: 154 | // @400000005ecc11bf3782b374 File "/opt/victronenergy/dbus-systemcalc-py/dbus_systemcalc.py", line 386, in _handletimertick 155 | // @400000005ecc11bf37aa251c self._updatevalues() 156 | // @400000005ecc11bf380e74cc File "/opt/victronenergy/dbus-systemcalc-py/dbus_systemcalc.py", line 678, in _updatevalues 157 | // @400000005ecc11bf383ab4ec c = _safeadd(c, p, pvpower) 158 | // @400000005ecc11bf386c9674 File "/opt/victronenergy/dbus-systemcalc-py/sc_utils.py", line 13, in safeadd 159 | // @400000005ecc11bf387b28ec return sum(values) if values else None 160 | // @400000005ecc11bf38b2bb7c TypeError: unsupported operand type(s) for +: 'int' and 'unicode' 161 | // 162 | victronValues[0]["/Ac/L1/Power"] = dbus.MakeVariant(0.0) 163 | victronValues[1]["/Ac/L1/Power"] = dbus.MakeVariant("0 W") 164 | victronValues[0]["/Ac/L2/Power"] = dbus.MakeVariant(0.0) 165 | victronValues[1]["/Ac/L2/Power"] = dbus.MakeVariant("0 W") 166 | victronValues[0]["/Ac/L3/Power"] = dbus.MakeVariant(0.0) 167 | victronValues[1]["/Ac/L3/Power"] = dbus.MakeVariant("0 W") 168 | 169 | victronValues[0]["/Ac/L1/Voltage"] = dbus.MakeVariant(230) 170 | victronValues[1]["/Ac/L1/Voltage"] = dbus.MakeVariant("230 V") 171 | victronValues[0]["/Ac/L2/Voltage"] = dbus.MakeVariant(230) 172 | victronValues[1]["/Ac/L2/Voltage"] = dbus.MakeVariant("230 V") 173 | victronValues[0]["/Ac/L3/Voltage"] = dbus.MakeVariant(230) 174 | victronValues[1]["/Ac/L3/Voltage"] = dbus.MakeVariant("230 V") 175 | 176 | victronValues[0]["/Ac/L1/Current"] = dbus.MakeVariant(0.0) 177 | victronValues[1]["/Ac/L1/Current"] = dbus.MakeVariant("0 A") 178 | victronValues[0]["/Ac/L2/Current"] = dbus.MakeVariant(0.0) 179 | victronValues[1]["/Ac/L2/Current"] = dbus.MakeVariant("0 A") 180 | victronValues[0]["/Ac/L3/Current"] = dbus.MakeVariant(0.0) 181 | victronValues[1]["/Ac/L3/Current"] = dbus.MakeVariant("0 A") 182 | 183 | victronValues[0]["/Ac/L1/Energy/Forward"] = dbus.MakeVariant(0.0) 184 | victronValues[1]["/Ac/L1/Energy/Forward"] = dbus.MakeVariant("0 kWh") 185 | victronValues[0]["/Ac/L2/Energy/Forward"] = dbus.MakeVariant(0.0) 186 | victronValues[1]["/Ac/L2/Energy/Forward"] = dbus.MakeVariant("0 kWh") 187 | victronValues[0]["/Ac/L3/Energy/Forward"] = dbus.MakeVariant(0.0) 188 | victronValues[1]["/Ac/L3/Energy/Forward"] = dbus.MakeVariant("0 kWh") 189 | 190 | victronValues[0]["/Ac/L1/Energy/Reverse"] = dbus.MakeVariant(0.0) 191 | victronValues[1]["/Ac/L1/Energy/Reverse"] = dbus.MakeVariant("0 kWh") 192 | victronValues[0]["/Ac/L2/Energy/Reverse"] = dbus.MakeVariant(0.0) 193 | victronValues[1]["/Ac/L2/Energy/Reverse"] = dbus.MakeVariant("0 kWh") 194 | victronValues[0]["/Ac/L3/Energy/Reverse"] = dbus.MakeVariant(0.0) 195 | victronValues[1]["/Ac/L3/Energy/Reverse"] = dbus.MakeVariant("0 kWh") 196 | 197 | basicPaths := []dbus.ObjectPath{ 198 | "/Connected", 199 | "/CustomName", 200 | "/DeviceInstance", 201 | "/DeviceType", 202 | "/ErrorCode", 203 | "/FirmwareVersion", 204 | "/Mgmt/Connection", 205 | "/Mgmt/ProcessName", 206 | "/Mgmt/ProcessVersion", 207 | "/Position", 208 | "/ProductId", 209 | "/ProductName", 210 | "/Serial", 211 | } 212 | 213 | updatingPaths := []dbus.ObjectPath{ 214 | "/Ac/L1/Power", 215 | "/Ac/L2/Power", 216 | "/Ac/L3/Power", 217 | "/Ac/L1/Voltage", 218 | "/Ac/L2/Voltage", 219 | "/Ac/L3/Voltage", 220 | "/Ac/L1/Current", 221 | "/Ac/L2/Current", 222 | "/Ac/L3/Current", 223 | "/Ac/L1/Energy/Forward", 224 | "/Ac/L2/Energy/Forward", 225 | "/Ac/L3/Energy/Forward", 226 | "/Ac/L1/Energy/Reverse", 227 | "/Ac/L2/Energy/Reverse", 228 | "/Ac/L3/Energy/Reverse", 229 | } 230 | 231 | defer conn.Close() 232 | 233 | // Some of the victron stuff requires it be called grid.cgwacs... using the only known valid value (from the simulator) 234 | // This can _probably_ be changed as long as it matches com.victronenergy.grid.cgwacs_* 235 | reply, err := conn.RequestName("com.victronenergy.grid.cgwacs_ttyUSB0_di30_mb1", 236 | dbus.NameFlagDoNotQueue) 237 | if err != nil { 238 | log.Panic("Something went horribly wrong in the dbus connection") 239 | panic(err) 240 | } 241 | 242 | if reply != dbus.RequestNameReplyPrimaryOwner { 243 | log.Panic("name cgwacs_ttyUSB0_di30_mb1 already taken on dbus.") 244 | os.Exit(1) 245 | } 246 | 247 | for i, s := range basicPaths { 248 | log.Debug("Registering dbus basic path #", i, ": ", s) 249 | conn.Export(objectpath(s), s, "com.victronenergy.BusItem") 250 | conn.Export(introspect.Introspectable(intro), s, "org.freedesktop.DBus.Introspectable") 251 | } 252 | 253 | for i, s := range updatingPaths { 254 | log.Debug("Registering dbus update path #", i, ": ", s) 255 | conn.Export(objectpath(s), s, "com.victronenergy.BusItem") 256 | conn.Export(introspect.Introspectable(intro), s, "org.freedesktop.DBus.Introspectable") 257 | } 258 | 259 | log.Info("Successfully connected to dbus and registered as a meter... Commencing reading of the SDM630 meter") 260 | 261 | // MQTT Subscripte 262 | opts := mqtt.NewClientOptions() 263 | opts.AddBroker(fmt.Sprintf("tcp://%s:%d", broker, brokerPort)) 264 | opts.SetClientID(clientId) 265 | opts.SetUsername(username) 266 | opts.SetPassword(password) 267 | opts.SetDefaultPublishHandler(messagePubHandler) 268 | opts.OnConnect = connectHandler 269 | opts.OnConnectionLost = connectLostHandler 270 | client := mqtt.NewClient(opts) 271 | if token := client.Connect(); token.Wait() && token.Error() != nil { 272 | panic(token.Error()) 273 | } 274 | sub(client) 275 | // Infinite loop 276 | for true { 277 | // fmt.Println("Infinite Loop entered") 278 | time.Sleep(time.Second) 279 | } 280 | 281 | // This is a forever loop^^ 282 | panic("Error: We terminated.... how did we ever get here?") 283 | } 284 | 285 | /* MQTT Subscribe Function */ 286 | func sub(client mqtt.Client) { 287 | topic := topic 288 | token := client.Subscribe(topic, 1, nil) 289 | token.Wait() 290 | log.Info("Subscribed to topic: " + topic) 291 | } 292 | 293 | /* MQTT Publish Function */ 294 | func publish(client mqtt.Client) { 295 | num := 10 296 | for i := 0; i < num; i++ { 297 | text := fmt.Sprintf("Message %d", i) 298 | token := client.Publish("topic/test", 0, false, text) 299 | token.Wait() 300 | time.Sleep(time.Second) 301 | } 302 | } 303 | 304 | /* Write dbus Values to Victron handler */ 305 | func updateVariant(value float64, unit string, path string) { 306 | emit := make(map[string]dbus.Variant) 307 | emit["Text"] = dbus.MakeVariant(fmt.Sprintf("%.2f", value) + unit) 308 | emit["Value"] = dbus.MakeVariant(float64(value)) 309 | victronValues[0][objectpath(path)] = emit["Value"] 310 | victronValues[1][objectpath(path)] = emit["Text"] 311 | conn.Emit(dbus.ObjectPath(path), "com.victronenergy.BusItem.PropertiesChanged", emit) 312 | } 313 | 314 | /* Convert binary to float64 */ 315 | func bin2Float64(bin string) float64 { 316 | foostring := string(bin) 317 | result, err := strconv.ParseFloat(foostring, 64) 318 | if err != nil { 319 | panic(err) 320 | } 321 | return result 322 | } 323 | 324 | /* Called if connection is established */ 325 | var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) { 326 | log.Info(fmt.Sprintf("Connected to broker %s:%d", broker, brokerPort)) 327 | } 328 | 329 | /* Called if connection is lost */ 330 | var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) { 331 | log.Info(fmt.Sprintf("Connect lost: %v", err)) 332 | os.Exit(1) 333 | } 334 | 335 | /* Search for string with regex */ 336 | func ContainString(searchstring string, str string) bool { 337 | var obj bool 338 | 339 | obj, err = regexp.MatchString(searchstring, str) 340 | 341 | if err != nil { 342 | panic(err) 343 | } 344 | 345 | return obj 346 | } 347 | 348 | /* MQTT Subscribe Handler */ 349 | var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) { 350 | 351 | log.Debug(fmt.Sprintf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())) 352 | value_correction = false 353 | 354 | // Power L1 355 | if ContainString(".*Power/L1$", msg.Topic()) { 356 | P1 = bin2Float64(string(msg.Payload())) 357 | // if (P1 > 0) { 358 | // updateVariant(float64(P1), "W", "/Ac/L1/Power") 359 | // log.Debug(fmt.Sprintf("L1 Power: %.3f W" ,P1)) 360 | // psum_update=true 361 | // } else { 362 | // value_correction=true 363 | // log.Info(fmt.Sprintf("Rückeinspeisung L1: %.3f W" ,P1)) 364 | // updateVariant(float64(0.00), "W", "/Ac/L1/Power") 365 | // } 366 | // 367 | updateVariant(float64(P1), "W", "/Ac/L1/Power") 368 | } 369 | 370 | // Power L2 371 | if ContainString(".*Power/L2$", msg.Topic()) { 372 | P2 = bin2Float64(string(msg.Payload())) 373 | // if (P2 > 0) { 374 | // updateVariant(float64(P2), "W", "/Ac/L2/Power") 375 | // log.Debug(fmt.Sprintf("L2 Power: %.3f W" ,P2)) 376 | // psum_update=true 377 | // } else { 378 | // value_correction=true 379 | // log.Info(fmt.Sprintf("Rückeinspeisung L2: %.3f W" ,P2)) 380 | // updateVariant(float64(0.00), "W", "/Ac/L2/Power") 381 | // } 382 | updateVariant(float64(P2), "W", "/Ac/L2/Power") 383 | } 384 | 385 | // Power L3 386 | if ContainString(".*Power/L3$", msg.Topic()) { 387 | P3 = bin2Float64(string(msg.Payload())) 388 | // if (P3 > 0) { 389 | // updateVariant(float64(P3), "W", "/Ac/L3/Power") 390 | // log.Debug(fmt.Sprintf("L3 Power: %.3f W" ,P3)) 391 | // psum_update=true 392 | // } else { 393 | // value_correction=true 394 | // log.Info(fmt.Sprintf("Rückeinspeisung L3: %.3f W" ,P3)) 395 | // updateVariant(float64(0.00), "W", "/Ac/L3/Power") 396 | // } 397 | updateVariant(float64(P3), "W", "/Ac/L3/Power") 398 | } 399 | // Summe aller drei Phasen 400 | // if psum_update { 401 | // psum := psum + (P1+P2+P3) 402 | // if psum < 0 { 403 | // log.Info(fmt.Sprintf("Kein Verbrauch: %d W", psum)) 404 | // updateVariant(float64(0.00), "W", "/Ac/L1/Power") 405 | // updateVariant(float64(0.00), "W", "/Ac/L2/Power") 406 | // updateVariant(float64(0.00), "W", "/Ac/L3/Power") 407 | // } 408 | // psum_update=false 409 | 410 | // // FIXME 411 | // if value_correction { 412 | // psum := (P1+P2+P3)/3 413 | // updateVariant(float64(psum), "W", "/Ac/L1/Power") 414 | // updateVariant(float64(psum), "W", "/Ac/L2/Power") 415 | // updateVariant(float64(psum), "W", "/Ac/L3/Power") 416 | // log.Info(fmt.Sprintf("Phasensumme wurde korrigiert! %.2f W" ,psum)) 417 | // } 418 | // log.Debug(fmt.Sprintf("Summe aller Phasen: %.2f W" ,psum)) 419 | // } 420 | 421 | // /Ac/Energy/Forward <- kWh - bought energy (total of all phases) 422 | if ContainString(".*/Import$", msg.Topic()) { 423 | IP := bin2Float64(string(msg.Payload())) 424 | updateVariant(float64(IP), "kWh", "/Ac/Energy/Forward") 425 | log.Debug(fmt.Sprintf("Import Power: %.3f kWh", IP)) 426 | } 427 | 428 | // /Ac/Energy/Reverse <- kWh - sold energy (total of all phases) 429 | if ContainString(".*/Export$", msg.Topic()) { 430 | EP := bin2Float64(string(msg.Payload())) 431 | updateVariant(float64(EP), "kWh", "/Ac/Energy/Reverse") 432 | log.Debug(fmt.Sprintf("Export Power: %.3f kWh", EP)) 433 | } 434 | 435 | // /Ac/Power <- W - total of all phases, real power 436 | if ContainString(".*/Power$", msg.Topic()) { 437 | TP := bin2Float64(string(msg.Payload())) 438 | updateVariant(float64(TP), "W", "/Ac/Power") 439 | log.Debug(fmt.Sprintf("Total Power: %.3f W", TP)) 440 | } 441 | 442 | // /Ac/L1/Current <- A AC 443 | if ContainString(".*/Current/L1$", msg.Topic()) { 444 | CL1 := bin2Float64(string(msg.Payload())) 445 | updateVariant(float64(CL1), "A", "/Ac/L1/Current") 446 | log.Debug(fmt.Sprintf("Current L1: %.3f A", CL1)) 447 | } 448 | 449 | // /Ac/L2/Current <- A AC 450 | if ContainString(".*/Current/L2$", msg.Topic()) { 451 | CL2 := bin2Float64(string(msg.Payload())) 452 | updateVariant(float64(CL2), "A", "/Ac/L2/Current") 453 | log.Debug(fmt.Sprintf("Current L2: %.3f A", CL2)) 454 | } 455 | 456 | // /Ac/L3/Current <- A AC 457 | if ContainString(".*/Current/L3$", msg.Topic()) { 458 | CL3 := bin2Float64(string(msg.Payload())) 459 | updateVariant(float64(CL3), "A", "/Ac/L3/Current") 460 | log.Debug(fmt.Sprintf("Current L3: %.3f A", CL3)) 461 | } 462 | 463 | // /Ac/L1/Voltage <- V AC 464 | if ContainString(".*/Voltage/L1$", msg.Topic()) { 465 | VL1 := bin2Float64(string(msg.Payload())) 466 | updateVariant(float64(VL1), "V", "/Ac/L1/Voltage") 467 | log.Debug(fmt.Sprintf("Voltage L1: %.3f V", VL1)) 468 | } 469 | 470 | // /Ac/L2/Voltage <- V AC 471 | if ContainString(".*/Voltage/L2$", msg.Topic()) { 472 | VL2 := bin2Float64(string(msg.Payload())) 473 | updateVariant(float64(VL2), "V", "/Ac/L2/Voltage") 474 | log.Debug(fmt.Sprintf("Voltage L2: %.3f V", VL2)) 475 | } 476 | 477 | // /Ac/L3/Voltage <- V AC 478 | if ContainString(".*/Voltage/L3$", msg.Topic()) { 479 | VL3 := bin2Float64(string(msg.Payload())) 480 | updateVariant(float64(VL3), "V", "/Ac/L3/Voltage") 481 | log.Debug(fmt.Sprintf("Voltage L3: %.3f V", VL3)) 482 | } 483 | 484 | // /Ac/L1/Energy/Forward <- kWh - bought 485 | if ContainString(".*/Sum/L1$", msg.Topic()) { 486 | SL1 := bin2Float64(string(msg.Payload())) 487 | updateVariant(float64(SL1), "kWh", "/Ac/L1/Energy/Forward") 488 | log.Debug(fmt.Sprintf("Energy Forward L1: %.3f kWh", SL1)) 489 | } 490 | 491 | // /Ac/L2/Energy/Forward <- kWh - bought 492 | if ContainString(".*/Sum/L2$", msg.Topic()) { 493 | SL2 := bin2Float64(string(msg.Payload())) 494 | updateVariant(float64(SL2), "kWh", "/Ac/L2/Energy/Forward") 495 | log.Debug(fmt.Sprintf("Energy Forward L2: %.3f kWh", SL2)) 496 | } 497 | // /Ac/L3/Energy/Forward <- kWh - bought 498 | if ContainString(".*/Sum/L3$", msg.Topic()) { 499 | SL3 := bin2Float64(string(msg.Payload())) 500 | updateVariant(float64(SL3), "kWh", "/Ac/L3/Energy/Forward") 501 | log.Debug(fmt.Sprintf("Energy Forward L3: %.3f kWh", SL3)) 502 | } 503 | 504 | // /Ac/L1/Energy/Reverse <- kWh - bought 505 | if ContainString(".*/Export/L1$", msg.Topic()) { 506 | EL1 := bin2Float64(string(msg.Payload())) 507 | updateVariant(float64(EL1), "kWh", "/Ac/L1/Energy/Reverse") 508 | log.Debug(fmt.Sprintf("Energy Reverse L1: %.3f kWh", EL1)) 509 | } 510 | 511 | // /Ac/L2/Energy/Reverse <- kWh - bought 512 | if ContainString(".*/Export/L2$", msg.Topic()) { 513 | EL2 := bin2Float64(string(msg.Payload())) 514 | updateVariant(float64(EL2), "kWh", "/Ac/L2/Energy/Reverse") 515 | log.Debug(fmt.Sprintf("Energy Reverse L2: %.3f kWh", EL2)) 516 | } 517 | // /Ac/L3/Energy/Reverse <- kWh - bought 518 | if ContainString(".*/Export/L3$", msg.Topic()) { 519 | EL3 := bin2Float64(string(msg.Payload())) 520 | updateVariant(float64(EL3), "kWh", "/Ac/L3/Energy/Reverse") 521 | log.Debug(fmt.Sprintf("Energy Reverse L3: %.3f kWh", EL3)) 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /sdm630-bridge.go: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormmurdoc/victron_sdm630_bridge/094b4c6b43d0a6fb736c480a98f939baf31db798/sdm630-bridge.go --------------------------------------------------------------------------------