├── .editorconfig ├── .env ├── .github └── workflows │ ├── build-release.yml │ └── build.yml ├── .gitignore ├── .run └── Main.run.xml ├── LICENSE ├── README.md ├── app ├── Dockerfile ├── config │ └── config.go ├── fritzbox │ └── fritzbox.go ├── go.mod ├── go.sum ├── main.go ├── mqtt │ └── client.go └── start.sh ├── build.sh ├── config-example.json ├── dashboard.json ├── deploy.sh ├── production ├── config │ └── .gitignore └── docker-compose.yaml └── renovate.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | 13 | [*.yml] 14 | indent_size = 2 15 | 16 | [*.md] 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VERSION=latest 2 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Build release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseType: 7 | description: Release type 8 | required: true 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | - major 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | permissions: 20 | contents: write 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: philipparndt/get-release-number@v3 26 | id: next 27 | with: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | releaseType: ${{ github.event.inputs.releaseType }} 30 | 31 | - uses: actions/setup-go@v5 32 | with: 33 | go-version: '1.22.5' 34 | 35 | - name: Build 36 | working-directory: app 37 | run: | 38 | go build . 39 | 40 | - name: Set up QEMU 41 | uses: docker/setup-qemu-action@v3 42 | 43 | - name: Set up Docker Buildx 44 | uses: docker/setup-buildx-action@v3 45 | 46 | - name: Login to DockerHub 47 | uses: docker/login-action@v3 48 | with: 49 | username: ${{ secrets.DOCKERHUB_USERNAME }} 50 | password: ${{ secrets.DOCKERHUB_TOKEN }} 51 | 52 | - name: Build docker container and push 53 | id: docker_build 54 | uses: docker/build-push-action@v6 55 | env: 56 | RELEASE_VERSION: ${{ steps.next.outputs.version }} 57 | with: 58 | context: ./app 59 | file: ./app/Dockerfile 60 | platforms: linux/amd64,linux/arm/v7,linux/arm64 61 | push: true 62 | tags: | 63 | pharndt/fritzboxmqtt:latest 64 | pharndt/fritzboxmqtt:${{env.RELEASE_VERSION}} 65 | 66 | - uses: ncipollo/release-action@v1 67 | with: 68 | name: ${{ steps.next.outputs.version }} 69 | tag: ${{ steps.next.outputs.version }} 70 | body: | 71 | Docker tag: `pharndt/fritzboxmqtt:${{ steps.next.outputs.version }}` 72 | 73 | Changes in this Release 74 | - Dependency update 75 | - ... 76 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Application 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version: '1.22.5' 18 | 19 | - name: Build 20 | working-directory: app 21 | run: | 22 | go build . 23 | 24 | - name: Build docker container and push 25 | id: docker_build 26 | uses: docker/build-push-action@v6 27 | env: 28 | RELEASE_VERSION: ${{ steps.next.outputs.version }} 29 | with: 30 | context: ./app 31 | file: ./app/Dockerfile 32 | platforms: linux/amd64 33 | push: false 34 | tags: | 35 | pharndt/fritzboxmqtt:latest 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | 11 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 12 | !/.mvn/wrapper/maven-wrapper.jar 13 | 14 | config.json 15 | .metadata 16 | .recommenders 17 | .sonarlint 18 | .settings 19 | *.iml 20 | logs/ 21 | config.json 22 | .idea 23 | 24 | build_internal/ 25 | node_modules/ 26 | dist/ 27 | -------------------------------------------------------------------------------- /.run/Main.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fritzbox-to-mqtt-gw 2 | 3 | [![mqtt-smarthome](https://img.shields.io/badge/mqtt-smarthome-blue.svg)](https://github.com/mqtt-smarthome/mqtt-smarthome) 4 | 5 | Convert the FritzBox tr-064 data to MQTT messages. 6 | 7 | ## Example configuration 8 | 9 | ```json 10 | { 11 | "mqtt": { 12 | "url": "tcp://192.168.2.2:1883", 13 | "client-id": "fritzbox-mqtt-gw", 14 | "username": "mqtt-username", 15 | "password": "mqtt-password", 16 | "retain": true, 17 | "topic": "internet/connection" 18 | }, 19 | "fritzbox": { 20 | "polling-interval": 60, 21 | "host": "192.168.2.1", 22 | "username": "fritzbox-username", 23 | "password": "fritzbox-password", 24 | "message": [ 25 | { 26 | "service": "urn:dslforum-org:service:WANCommonInterfaceConfig:1", 27 | "action": "GetTotalBytesSent", 28 | "values": [ 29 | { "name": "TotalBytesSent" } 30 | ] 31 | }, 32 | { 33 | "service": "urn:dslforum-org:service:WANCommonInterfaceConfig:1", 34 | "action": "GetTotalBytesReceived", 35 | "values": [ 36 | { "name": "TotalBytesReceived" } 37 | ] 38 | }, 39 | { 40 | "service": "urn:dslforum-org:service:WANIPConnection:1", 41 | "action": "GetInfo", 42 | "values": [ 43 | { "name": "Uptime" }, 44 | { "name": "ConnectionStatus", "mapEnum": { "Connected": 1, "__default": 0 } } 45 | ] 46 | }, 47 | { 48 | "service": "urn:dslforum-org:service:Hosts:1", 49 | "action": "GetSpecificHostEntry", 50 | "args": [ 51 | { "name": "NewMACAddress", "value": "12:34:56:78:9A:BC" } 52 | ], 53 | "alias": "host1" 54 | } 55 | ] 56 | } 57 | } 58 | ``` 59 | 60 | ## Example message 61 | 62 | ```json 63 | { 64 | "ConnectionStatus": 0, 65 | "ConnectionTrigger": "AlwaysOn", 66 | "ConnectionType": "IP_Routed", 67 | "DNSEnabled": true, 68 | "DNSOverrideAllowed": true, 69 | "DNSServers": "xx:xx:xx:xx:xx, ...", 70 | "Enable": true, 71 | "ExternalIPAddress": "xx.xx.xx.xx", 72 | "LastConnectionError": "ERROR_NONE", 73 | "MACAddress": "xx:xx:xx:xx:xx", 74 | "NATEnabled": true, 75 | "Name": "internet", 76 | "PossibleConnectionTypes": "IP_Routed, IP_Bridged", 77 | "RSIPAvailable": false, 78 | "RouteProtocolRx": "Off", 79 | "TotalBytesReceived": 812730036094, 80 | "TotalBytesSent": 164014571233, 81 | "Uptime": 3801160, 82 | "host1.Active": true, 83 | "host1.AddressSource": "DHCP", 84 | "host1.HostName": "PC-xx-xx-xx-xx", 85 | "host1.IPAddress": "xx.xx.xx.xx", 86 | "host1.InterfaceType": "Ethernet", 87 | "host1.LeaseTimeRemaining": 0 88 | } 89 | ``` 90 | 91 | # Upgrade to 5.x 92 | 93 | 5.x is a major upgrade 94 | - reimplemented using go for lower memory footprint 95 | - configuration has changed slightly 96 | 97 | The configuration has been changed and the message format is much more flexible now. 98 | You can now specify the properties of the message that will be sent. 99 | 100 | # build 101 | 102 | build the docker container using `build.sh` 103 | 104 | # run 105 | 106 | copy the `config-example.json` to `/production/config/config.json` 107 | 108 | ``` 109 | cd ./production 110 | docker-compose up -d 111 | ``` 112 | 113 | Grafana Dashboard adapted from: 114 | https://blog.butenostfreesen.de/2018/10/11/Fritz-Box-Monitoring-mit-Grafana-und-Raspberry/ 115 | -------------------------------------------------------------------------------- /app/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the application from source 2 | FROM golang:1.23 AS build-stage 3 | 4 | WORKDIR /app 5 | 6 | COPY go.mod go.sum ./ 7 | RUN go mod download 8 | 9 | COPY . ./ 10 | 11 | RUN CGO_ENABLED=0 GOOS=linux go build -o /fritzbox-to-mqtt-gw 12 | 13 | # Run the tests in the container 14 | FROM build-stage AS run-test-stage 15 | RUN go test -v ./... 16 | 17 | # Deploy the application binary into a lean image 18 | FROM gcr.io/distroless/base-debian11 AS build-release-stage 19 | 20 | WORKDIR / 21 | 22 | COPY --from=build-stage /fritzbox-to-mqtt-gw /fritzbox-to-mqtt-gw 23 | 24 | USER nonroot:nonroot 25 | 26 | ENTRYPOINT ["/fritzbox-to-mqtt-gw", "/var/lib/fritzbox-to-mqtt-gw/config.json"] 27 | -------------------------------------------------------------------------------- /app/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/philipparndt/go-logger" 6 | "github.com/philipparndt/mqtt-gateway/config" 7 | "os" 8 | ) 9 | 10 | type ValueMapping struct { 11 | Name string `json:"name"` 12 | MapEnum map[string]interface{} `json:"mapEnum,omitempty"` 13 | } 14 | 15 | type Argument struct { 16 | Name string `json:"name"` 17 | Value string `json:"value"` 18 | } 19 | 20 | type Fritzbox struct { 21 | PollingInterval int `json:"polling-interval"` 22 | Host string `json:"host"` 23 | Username string `json:"username"` 24 | Password string `json:"password"` 25 | BoxType string `json:"box-type"` 26 | Message []struct { 27 | Service string `json:"service"` 28 | Action string `json:"action"` 29 | Args []Argument `json:"args"` 30 | Values []ValueMapping `json:"values"` 31 | Alias string `json:"alias,omitempty"` 32 | } `json:"message"` 33 | } 34 | 35 | type Port struct { 36 | Name string `json:"name"` 37 | Port string `json:"port"` 38 | } 39 | 40 | type Config struct { 41 | MQTT config.MQTTConfig `json:"mqtt"` 42 | Fritzbox Fritzbox `json:"fritzbox"` 43 | LogLevel string `json:"loglevel,omitempty"` 44 | } 45 | 46 | func (cfg Config) Validate() { 47 | for _, msg := range cfg.Fritzbox.Message { 48 | if len(msg.Args) > 1 { 49 | panic("Currently only one argument is supported check configuration for " + msg.Service + " " + msg.Action) 50 | } 51 | } 52 | } 53 | 54 | func LoadConfig(file string) (Config, error) { 55 | data, err := os.ReadFile(file) 56 | if err != nil { 57 | logger.Error("Error reading config file", err) 58 | return Config{}, err 59 | } 60 | 61 | data = config.ReplaceEnvVariables(data) 62 | 63 | // Create a Config object 64 | var cfg Config 65 | 66 | // Unmarshal the JSON data into the Config object 67 | err = json.Unmarshal(data, &cfg) 68 | if err != nil { 69 | logger.Error("Unmarshalling JSON:", err) 70 | return Config{}, err 71 | } 72 | 73 | if cfg.LogLevel == "" { 74 | cfg.LogLevel = "info" 75 | } 76 | 77 | cfg.Validate() 78 | 79 | return cfg, nil 80 | } 81 | -------------------------------------------------------------------------------- /app/fritzbox/fritzbox.go: -------------------------------------------------------------------------------- 1 | package fritzbox 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/mqtt-home/fritzbox-to-mqtt-gw/config" 7 | "github.com/philipparndt/go-logger" 8 | "github.com/sberk42/fritzbox_exporter/fritzbox_upnp" 9 | ) 10 | 11 | func JsonFromDataMap(dataMap map[string]interface{}) string { 12 | // Marshal the map into a JSON byte slice 13 | jsonBytes, err := json.Marshal(dataMap) 14 | if err != nil { 15 | // Handle the error (optional: return an empty string or an error message) 16 | fmt.Println("Error marshaling JSON:", err) 17 | return "" 18 | } 19 | 20 | // Convert the byte slice to a string and return it 21 | return string(jsonBytes) 22 | } 23 | 24 | func mapEnumValue(mapping []config.ValueMapping, key string, value interface{}) interface{} { 25 | for _, msgValue := range mapping { 26 | if key == msgValue.Name { 27 | for mapKey, mapValue := range msgValue.MapEnum { 28 | if value == mapKey { 29 | value = mapValue 30 | break 31 | } else if mapKey == "__default" { 32 | value = mapValue 33 | } 34 | } 35 | } 36 | } 37 | return value 38 | } 39 | 40 | func LoadData(cfg config.Config) map[string]interface{} { 41 | 42 | services, err := fritzbox_upnp.LoadServices( 43 | "http://"+cfg.Fritzbox.Host+":49000", 44 | cfg.Fritzbox.Username, 45 | cfg.Fritzbox.Password, 46 | false) 47 | if err != nil { 48 | logger.Error("Failed loading services", err) 49 | return nil 50 | } 51 | 52 | dataMap := make(map[string]interface{}) 53 | 54 | for _, service := range services.Services { 55 | logger.Debug("Service: ", service.ServiceType) 56 | for _, action := range service.Actions { 57 | logger.Debug(" Action: ", action.Name) 58 | } 59 | 60 | for _, msg := range cfg.Fritzbox.Message { 61 | if msg.Service == service.ServiceType { 62 | for _, action := range service.Actions { 63 | if action.Name == msg.Action { 64 | actionArg := fritzbox_upnp.ActionArgument{ 65 | Name: "", 66 | } 67 | 68 | if len(msg.Args) > 0 { 69 | actionArg = fritzbox_upnp.ActionArgument{ 70 | Name: msg.Args[0].Name, 71 | Value: msg.Args[0].Value, 72 | } 73 | } 74 | 75 | result, err := action.Call(&actionArg) 76 | if err == nil { 77 | for key, value := range result { 78 | keyName := key 79 | if msg.Alias != "" { 80 | keyName = msg.Alias + "." + key 81 | } 82 | 83 | dataMap[keyName] = mapEnumValue(msg.Values, key, value) 84 | } 85 | } else { 86 | logger.Error("Error calling action", err) 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | return dataMap 95 | } 96 | -------------------------------------------------------------------------------- /app/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mqtt-home/fritzbox-to-mqtt-gw 2 | 3 | go 1.22.6 4 | 5 | toolchain go1.23.2 6 | 7 | require ( 8 | github.com/philipparndt/go-logger v1.0.0 9 | github.com/philipparndt/mqtt-gateway v1.3.0 10 | github.com/sberk42/fritzbox_exporter v0.0.0-20240828163925-4ff6c1a79919 11 | ) 12 | 13 | require ( 14 | github.com/eclipse/paho.mqtt.golang v1.4.3 // indirect 15 | github.com/gorilla/websocket v1.5.0 // indirect 16 | golang.org/x/net v0.23.0 // indirect 17 | golang.org/x/sync v0.3.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /app/go.sum: -------------------------------------------------------------------------------- 1 | github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= 2 | github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= 3 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 4 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | github.com/philipparndt/go-logger v1.0.0 h1:5khKl1q8q6FH74S1wF5KPl6DvdZnRdPKqKbBPQDr9P4= 6 | github.com/philipparndt/go-logger v1.0.0/go.mod h1:4mWf8eNH82GhKl5byAFwXcux9WPNuaePaPaAFbgjHXg= 7 | github.com/philipparndt/mqtt-gateway v1.3.0 h1:QeJ0XIEnlzH4Qm7vqAQN3WTrI45UcPxlcQFcTrCXvn8= 8 | github.com/philipparndt/mqtt-gateway v1.3.0/go.mod h1:N6LC57xFs/u41Xc5csPHTACm/00GyOyMgIC+oOqp32Y= 9 | github.com/sberk42/fritzbox_exporter v0.0.0-20240409154313-846e73fa6fac h1:62a3NZoSSQ70PnvuLVbVzfC2XsxCzRfe5lLaWpWbp04= 10 | github.com/sberk42/fritzbox_exporter v0.0.0-20240409154313-846e73fa6fac/go.mod h1:fsdBVW7VERDYzGEuqvJRVUp+0rO8fo1vxdxu0ZVk20g= 11 | github.com/sberk42/fritzbox_exporter v0.0.0-20240828163925-4ff6c1a79919 h1:FrXXqJDmFz+9lNrRTMjoZm34+szEQiagGO/CXdLu8Mw= 12 | github.com/sberk42/fritzbox_exporter v0.0.0-20240828163925-4ff6c1a79919/go.mod h1:NfPGQ73gieQqzCbWeChPTouWbt/BloYFIfw8EbgebYE= 13 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 14 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 15 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 16 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 17 | -------------------------------------------------------------------------------- /app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/mqtt-home/fritzbox-to-mqtt-gw/config" 6 | "github.com/mqtt-home/fritzbox-to-mqtt-gw/fritzbox" 7 | "github.com/mqtt-home/fritzbox-to-mqtt-gw/mqtt" 8 | "github.com/philipparndt/go-logger" 9 | config2 "github.com/philipparndt/mqtt-gateway/config" 10 | mqtt2 "github.com/philipparndt/mqtt-gateway/mqtt" 11 | "os" 12 | "os/signal" 13 | "syscall" 14 | "time" 15 | ) 16 | 17 | func PublishJSON(cfg config2.MQTTConfig, data any) { 18 | jsonData, err := json.Marshal(data) 19 | if err != nil { 20 | logger.Error("Error marshaling to JSON", err) 21 | } else { 22 | mqtt2.PublishAbsolute(cfg.Topic, string(jsonData), cfg.Retain) 23 | } 24 | } 25 | 26 | func mainLoop(cfg config.Config) { 27 | for { 28 | dataMap := fritzbox.LoadData(cfg) 29 | logger.Debug("DataMap: ", fritzbox.JsonFromDataMap(dataMap)) 30 | PublishJSON(cfg.MQTT, dataMap) 31 | 32 | time.Sleep(time.Duration(cfg.Fritzbox.PollingInterval) * time.Second) 33 | } 34 | } 35 | 36 | func main() { 37 | if len(os.Args) < 2 { 38 | logger.Error("No config file specified") 39 | os.Exit(1) 40 | } 41 | 42 | configFile := os.Args[1] 43 | logger.Info("Config file", configFile) 44 | cfg, err := config.LoadConfig(configFile) 45 | if err != nil { 46 | logger.Error("Failed loading config", err) 47 | return 48 | } 49 | 50 | logger.SetLevel(cfg.LogLevel) 51 | mqtt.Start(cfg.MQTT) 52 | 53 | go mainLoop(cfg) 54 | 55 | logger.Info("Application is now ready. Press Ctrl+C to quit.") 56 | 57 | quitChannel := make(chan os.Signal, 1) 58 | signal.Notify(quitChannel, syscall.SIGINT, syscall.SIGTERM) 59 | <-quitChannel 60 | 61 | logger.Info("Received quit signal") 62 | } 63 | -------------------------------------------------------------------------------- /app/mqtt/client.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "github.com/philipparndt/mqtt-gateway/config" 5 | "github.com/philipparndt/mqtt-gateway/mqtt" 6 | ) 7 | 8 | func Start(config config.MQTTConfig) { 9 | mqtt.Start(config, "fb_mqtt") 10 | } 11 | -------------------------------------------------------------------------------- /app/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PWD=$(pwd) 4 | config="$PWD/../production/config/config.json" 5 | 6 | go run . "$config" 7 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/app" 3 | 4 | docker build -t pharndt/fritzboxmqtt . 5 | -------------------------------------------------------------------------------- /config-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "mqtt": { 3 | "url": "tcp://192.168.2.2:1883", 4 | "client-id": "fritzbox-mqtt-gw", 5 | "username": "mqtt-username", 6 | "password": "mqtt-password", 7 | "retain": true, 8 | "topic": "internet/connection" 9 | }, 10 | "fritzbox": { 11 | "polling-interval": 60, 12 | "host": "192.168.2.1", 13 | "username": "fritzbox-username", 14 | "password": "fritzbox-password", 15 | "box-type": "dsl", 16 | "message": [ 17 | { 18 | "service": "urn:dslforum-org:service:WANCommonInterfaceConfig:1", 19 | "action": "GetTotalBytesSent", 20 | "values": [ 21 | { "name": "TotalBytesSent" } 22 | ] 23 | }, 24 | { 25 | "service": "urn:dslforum-org:service:WANCommonInterfaceConfig:1", 26 | "action": "GetTotalBytesReceived", 27 | "values": [ 28 | { "name": "TotalBytesReceived" } 29 | ] 30 | }, 31 | { 32 | "service": "urn:dslforum-org:service:WANIPConnection:1", 33 | "action": "GetInfo", 34 | "values": [ 35 | { "name": "Uptime" }, 36 | { "name": "ConnectionStatus", "mapEnum": { "Connected": 1, "__default": 0 } } 37 | ] 38 | } 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "description": "Monitor FRITZ!Box routers.", 16 | "editable": true, 17 | "gnetId": 713, 18 | "graphTooltip": 2, 19 | "id": 7, 20 | "links": [], 21 | "panels": [ 22 | { 23 | "cacheTimeout": null, 24 | "colorBackground": true, 25 | "colorValue": false, 26 | "colors": [ 27 | "rgba(245, 54, 54, 0.9)", 28 | "rgba(237, 129, 40, 0.89)", 29 | "#1f78c1" 30 | ], 31 | "datasource": null, 32 | "format": "none", 33 | "gauge": { 34 | "maxValue": 100, 35 | "minValue": 0, 36 | "show": false, 37 | "thresholdLabels": false, 38 | "thresholdMarkers": true 39 | }, 40 | "gridPos": { 41 | "h": 4, 42 | "w": 4, 43 | "x": 0, 44 | "y": 0 45 | }, 46 | "id": 10, 47 | "interval": null, 48 | "links": [], 49 | "mappingType": 1, 50 | "mappingTypes": [ 51 | { 52 | "name": "value to text", 53 | "value": 1 54 | }, 55 | { 56 | "name": "range to text", 57 | "value": 2 58 | } 59 | ], 60 | "maxDataPoints": 100, 61 | "nullPointMode": "connected", 62 | "nullText": null, 63 | "options": {}, 64 | "postfix": "", 65 | "postfixFontSize": "50%", 66 | "prefix": "", 67 | "prefixFontSize": "50%", 68 | "rangeMaps": [ 69 | { 70 | "from": "null", 71 | "text": "N/A", 72 | "to": "null" 73 | } 74 | ], 75 | "sparkline": { 76 | "fillColor": "rgba(31, 118, 189, 0.18)", 77 | "full": false, 78 | "lineColor": "rgb(31, 120, 193)", 79 | "show": false 80 | }, 81 | "tableColumn": "", 82 | "targets": [ 83 | { 84 | "dsType": "influxdb", 85 | "groupBy": [ 86 | { 87 | "params": [ 88 | "$__interval" 89 | ], 90 | "type": "time" 91 | } 92 | ], 93 | "measurement": "mqtt_consumer", 94 | "orderByTime": "ASC", 95 | "policy": "default", 96 | "refId": "A", 97 | "resultFormat": "time_series", 98 | "select": [ 99 | [ 100 | { 101 | "params": [ 102 | "NewConnectionStatus" 103 | ], 104 | "type": "field" 105 | }, 106 | { 107 | "params": [], 108 | "type": "last" 109 | } 110 | ] 111 | ], 112 | "tags": [ 113 | { 114 | "key": "topic", 115 | "operator": "=", 116 | "value": "internet/connection/dsl" 117 | } 118 | ], 119 | "target": "" 120 | } 121 | ], 122 | "thresholds": "1,1", 123 | "title": "Connection Status", 124 | "type": "singlestat", 125 | "valueFontSize": "80%", 126 | "valueMaps": [ 127 | { 128 | "op": "=", 129 | "text": "N/A", 130 | "value": "null" 131 | }, 132 | { 133 | "op": "=", 134 | "text": "Disconnected", 135 | "value": "0" 136 | }, 137 | { 138 | "op": "=", 139 | "text": "Connected", 140 | "value": "1" 141 | } 142 | ], 143 | "valueName": "current" 144 | }, 145 | { 146 | "cacheTimeout": null, 147 | "colorBackground": true, 148 | "colorValue": false, 149 | "colors": [ 150 | "rgba(245, 54, 54, 0.9)", 151 | "rgba(237, 129, 40, 0.89)", 152 | "#1f78c1" 153 | ], 154 | "datasource": "influx-mqttdata", 155 | "format": "none", 156 | "gauge": { 157 | "maxValue": 100, 158 | "minValue": 0, 159 | "show": false, 160 | "thresholdLabels": false, 161 | "thresholdMarkers": true 162 | }, 163 | "gridPos": { 164 | "h": 4, 165 | "w": 4, 166 | "x": 4, 167 | "y": 0 168 | }, 169 | "id": 9, 170 | "interval": null, 171 | "links": [], 172 | "mappingType": 1, 173 | "mappingTypes": [ 174 | { 175 | "name": "value to text", 176 | "value": 1 177 | }, 178 | { 179 | "name": "range to text", 180 | "value": 2 181 | } 182 | ], 183 | "maxDataPoints": 100, 184 | "nullPointMode": "connected", 185 | "nullText": null, 186 | "options": {}, 187 | "postfix": "", 188 | "postfixFontSize": "50%", 189 | "prefix": "", 190 | "prefixFontSize": "50%", 191 | "rangeMaps": [ 192 | { 193 | "from": "null", 194 | "text": "N/A", 195 | "to": "null" 196 | } 197 | ], 198 | "sparkline": { 199 | "fillColor": "rgba(31, 118, 189, 0.18)", 200 | "full": false, 201 | "lineColor": "rgb(31, 120, 193)", 202 | "show": false 203 | }, 204 | "tableColumn": "", 205 | "targets": [ 206 | { 207 | "dsType": "influxdb", 208 | "groupBy": [ 209 | { 210 | "params": [ 211 | "$__interval" 212 | ], 213 | "type": "time" 214 | } 215 | ], 216 | "measurement": "mqtt_consumer", 217 | "orderByTime": "ASC", 218 | "policy": "default", 219 | "refId": "A", 220 | "resultFormat": "time_series", 221 | "select": [ 222 | [ 223 | { 224 | "params": [ 225 | "NewPhysicalLinkStatus" 226 | ], 227 | "type": "field" 228 | }, 229 | { 230 | "params": [], 231 | "type": "last" 232 | } 233 | ] 234 | ], 235 | "tags": [ 236 | { 237 | "key": "topic", 238 | "operator": "=", 239 | "value": "internet/connection/dsl" 240 | } 241 | ], 242 | "target": "" 243 | } 244 | ], 245 | "thresholds": "1,1", 246 | "title": "DSL Link Status", 247 | "type": "singlestat", 248 | "valueFontSize": "80%", 249 | "valueMaps": [ 250 | { 251 | "op": "=", 252 | "text": "N/A", 253 | "value": "null" 254 | }, 255 | { 256 | "op": "=", 257 | "text": "Disconnected", 258 | "value": "0" 259 | }, 260 | { 261 | "op": "=", 262 | "text": "Connected", 263 | "value": "1" 264 | } 265 | ], 266 | "valueName": "current" 267 | }, 268 | { 269 | "cacheTimeout": null, 270 | "colorBackground": false, 271 | "colorValue": false, 272 | "colors": [ 273 | "rgba(245, 54, 54, 0.9)", 274 | "rgba(237, 129, 40, 0.89)", 275 | "rgba(50, 172, 45, 0.97)" 276 | ], 277 | "datasource": "influx-mqttdata", 278 | "format": "dtdurations", 279 | "gauge": { 280 | "maxValue": 100, 281 | "minValue": 0, 282 | "show": false, 283 | "thresholdLabels": false, 284 | "thresholdMarkers": true 285 | }, 286 | "gridPos": { 287 | "h": 4, 288 | "w": 4, 289 | "x": 8, 290 | "y": 0 291 | }, 292 | "id": 13, 293 | "interval": null, 294 | "links": [], 295 | "mappingType": 1, 296 | "mappingTypes": [ 297 | { 298 | "name": "value to text", 299 | "value": 1 300 | }, 301 | { 302 | "name": "range to text", 303 | "value": 2 304 | } 305 | ], 306 | "maxDataPoints": 100, 307 | "nullPointMode": "connected", 308 | "nullText": null, 309 | "options": {}, 310 | "postfix": "", 311 | "postfixFontSize": "50%", 312 | "prefix": "", 313 | "prefixFontSize": "50%", 314 | "rangeMaps": [ 315 | { 316 | "from": "null", 317 | "text": "N/A", 318 | "to": "null" 319 | } 320 | ], 321 | "sparkline": { 322 | "fillColor": "rgba(31, 118, 189, 0.18)", 323 | "full": false, 324 | "lineColor": "rgb(31, 120, 193)", 325 | "show": false 326 | }, 327 | "tableColumn": "", 328 | "targets": [ 329 | { 330 | "dsType": "influxdb", 331 | "groupBy": [ 332 | { 333 | "params": [ 334 | "$__interval" 335 | ], 336 | "type": "time" 337 | } 338 | ], 339 | "measurement": "mqtt_consumer", 340 | "orderByTime": "ASC", 341 | "policy": "default", 342 | "refId": "A", 343 | "resultFormat": "time_series", 344 | "select": [ 345 | [ 346 | { 347 | "params": [ 348 | "NewUptime" 349 | ], 350 | "type": "field" 351 | }, 352 | { 353 | "params": [], 354 | "type": "last" 355 | } 356 | ] 357 | ], 358 | "tags": [ 359 | { 360 | "key": "topic", 361 | "operator": "=", 362 | "value": "internet/connection/dsl" 363 | } 364 | ], 365 | "target": "" 366 | } 367 | ], 368 | "thresholds": "1,1", 369 | "title": "Uptime", 370 | "type": "singlestat", 371 | "valueFontSize": "80%", 372 | "valueMaps": [ 373 | { 374 | "op": "=", 375 | "text": "N/A", 376 | "value": "null" 377 | } 378 | ], 379 | "valueName": "current" 380 | }, 381 | { 382 | "cacheTimeout": null, 383 | "colorBackground": false, 384 | "colorValue": false, 385 | "colors": [ 386 | "rgba(245, 54, 54, 0.9)", 387 | "rgba(237, 129, 40, 0.89)", 388 | "#1f78c1" 389 | ], 390 | "datasource": "influx-mqttdata", 391 | "decimals": 1, 392 | "format": "bps", 393 | "gauge": { 394 | "maxValue": 51392000, 395 | "minValue": 0, 396 | "show": true, 397 | "thresholdLabels": false, 398 | "thresholdMarkers": false 399 | }, 400 | "gridPos": { 401 | "h": 4, 402 | "w": 6, 403 | "x": 12, 404 | "y": 0 405 | }, 406 | "id": 11, 407 | "interval": null, 408 | "links": [], 409 | "mappingType": 1, 410 | "mappingTypes": [ 411 | { 412 | "name": "value to text", 413 | "value": 1 414 | }, 415 | { 416 | "name": "range to text", 417 | "value": 2 418 | } 419 | ], 420 | "maxDataPoints": 100, 421 | "nullPointMode": "connected", 422 | "nullText": null, 423 | "options": {}, 424 | "postfix": "", 425 | "postfixFontSize": "50%", 426 | "prefix": "", 427 | "prefixFontSize": "50%", 428 | "rangeMaps": [ 429 | { 430 | "from": "null", 431 | "text": "N/A", 432 | "to": "null" 433 | } 434 | ], 435 | "sparkline": { 436 | "fillColor": "rgba(31, 118, 189, 0.18)", 437 | "full": false, 438 | "lineColor": "rgb(31, 120, 193)", 439 | "show": false 440 | }, 441 | "tableColumn": "", 442 | "targets": [ 443 | { 444 | "dsType": "influxdb", 445 | "groupBy": [ 446 | { 447 | "params": [ 448 | "$__interval" 449 | ], 450 | "type": "time" 451 | } 452 | ], 453 | "measurement": "fritzbox_value", 454 | "orderByTime": "ASC", 455 | "policy": "default", 456 | "refId": "A", 457 | "resultFormat": "time_series", 458 | "select": [ 459 | [ 460 | { 461 | "params": [ 462 | "value" 463 | ], 464 | "type": "field" 465 | }, 466 | { 467 | "params": [], 468 | "type": "last" 469 | } 470 | ] 471 | ], 472 | "tags": [ 473 | { 474 | "key": "type_instance", 475 | "operator": "=", 476 | "value": "receiverate" 477 | } 478 | ], 479 | "target": "" 480 | } 481 | ], 482 | "thresholds": "1,1", 483 | "title": "Current Download", 484 | "type": "singlestat", 485 | "valueFontSize": "50%", 486 | "valueMaps": [ 487 | { 488 | "op": "=", 489 | "text": "N/A", 490 | "value": "null" 491 | } 492 | ], 493 | "valueName": "current" 494 | }, 495 | { 496 | "cacheTimeout": null, 497 | "colorBackground": false, 498 | "colorValue": false, 499 | "colors": [ 500 | "rgba(245, 54, 54, 0.9)", 501 | "rgba(237, 129, 40, 0.89)", 502 | "#e24d42" 503 | ], 504 | "datasource": "influx-mqttdata", 505 | "decimals": 1, 506 | "format": "bps", 507 | "gauge": { 508 | "maxValue": 10048000, 509 | "minValue": 0, 510 | "show": true, 511 | "thresholdLabels": false, 512 | "thresholdMarkers": false 513 | }, 514 | "gridPos": { 515 | "h": 4, 516 | "w": 6, 517 | "x": 18, 518 | "y": 0 519 | }, 520 | "id": 12, 521 | "interval": null, 522 | "links": [], 523 | "mappingType": 1, 524 | "mappingTypes": [ 525 | { 526 | "name": "value to text", 527 | "value": 1 528 | }, 529 | { 530 | "name": "range to text", 531 | "value": 2 532 | } 533 | ], 534 | "maxDataPoints": 100, 535 | "nullPointMode": "connected", 536 | "nullText": null, 537 | "options": {}, 538 | "postfix": "", 539 | "postfixFontSize": "50%", 540 | "prefix": "", 541 | "prefixFontSize": "50%", 542 | "rangeMaps": [ 543 | { 544 | "from": "null", 545 | "text": "N/A", 546 | "to": "null" 547 | } 548 | ], 549 | "sparkline": { 550 | "fillColor": "rgba(31, 118, 189, 0.18)", 551 | "full": false, 552 | "lineColor": "rgb(31, 120, 193)", 553 | "show": false 554 | }, 555 | "tableColumn": "", 556 | "targets": [ 557 | { 558 | "dsType": "influxdb", 559 | "groupBy": [ 560 | { 561 | "params": [ 562 | "$__interval" 563 | ], 564 | "type": "time" 565 | } 566 | ], 567 | "measurement": "fritzbox_value", 568 | "orderByTime": "ASC", 569 | "policy": "default", 570 | "refId": "A", 571 | "resultFormat": "time_series", 572 | "select": [ 573 | [ 574 | { 575 | "params": [ 576 | "value" 577 | ], 578 | "type": "field" 579 | }, 580 | { 581 | "params": [], 582 | "type": "last" 583 | } 584 | ] 585 | ], 586 | "tags": [ 587 | { 588 | "key": "type_instance", 589 | "operator": "=", 590 | "value": "sendrate" 591 | } 592 | ], 593 | "target": "" 594 | } 595 | ], 596 | "thresholds": "1,1", 597 | "title": "Current Upload", 598 | "type": "singlestat", 599 | "valueFontSize": "50%", 600 | "valueMaps": [ 601 | { 602 | "op": "=", 603 | "text": "N/A", 604 | "value": "null" 605 | } 606 | ], 607 | "valueName": "current" 608 | }, 609 | { 610 | "cacheTimeout": null, 611 | "colorBackground": false, 612 | "colorValue": false, 613 | "colors": [ 614 | "rgba(245, 54, 54, 0.9)", 615 | "rgba(237, 129, 40, 0.89)", 616 | "rgba(50, 172, 45, 0.97)" 617 | ], 618 | "datasource": "influx-mqttdata", 619 | "decimals": 1, 620 | "format": "decbytes", 621 | "gauge": { 622 | "maxValue": 100, 623 | "minValue": 0, 624 | "show": false, 625 | "thresholdLabels": false, 626 | "thresholdMarkers": true 627 | }, 628 | "gridPos": { 629 | "h": 4, 630 | "w": 6, 631 | "x": 0, 632 | "y": 4 633 | }, 634 | "id": 3, 635 | "interval": null, 636 | "links": [], 637 | "mappingType": 1, 638 | "mappingTypes": [ 639 | { 640 | "name": "value to text", 641 | "value": 1 642 | }, 643 | { 644 | "name": "range to text", 645 | "value": 2 646 | } 647 | ], 648 | "maxDataPoints": 100, 649 | "nullPointMode": "connected", 650 | "nullText": null, 651 | "options": {}, 652 | "postfix": "", 653 | "postfixFontSize": "50%", 654 | "prefix": "", 655 | "prefixFontSize": "50%", 656 | "rangeMaps": [ 657 | { 658 | "from": "null", 659 | "text": "N/A", 660 | "to": "null" 661 | } 662 | ], 663 | "sparkline": { 664 | "fillColor": "rgba(31, 118, 189, 0.18)", 665 | "full": false, 666 | "lineColor": "rgb(31, 120, 193)", 667 | "show": true 668 | }, 669 | "tableColumn": "", 670 | "targets": [ 671 | { 672 | "dsType": "influxdb", 673 | "groupBy": [ 674 | { 675 | "params": [ 676 | "$__interval" 677 | ], 678 | "type": "time" 679 | }, 680 | { 681 | "params": [ 682 | "null" 683 | ], 684 | "type": "fill" 685 | } 686 | ], 687 | "measurement": "fritzbox_value", 688 | "orderByTime": "ASC", 689 | "policy": "default", 690 | "query": "SELECT cumulative_sum(non_negative_difference(last(\"NewTotalBytesReceived\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter GROUP BY time($__interval)", 691 | "rawQuery": true, 692 | "refId": "A", 693 | "resultFormat": "time_series", 694 | "select": [ 695 | [ 696 | { 697 | "params": [ 698 | "value" 699 | ], 700 | "type": "field" 701 | }, 702 | { 703 | "params": [], 704 | "type": "mean" 705 | } 706 | ] 707 | ], 708 | "tags": [ 709 | { 710 | "key": "type_instance", 711 | "operator": "=", 712 | "value": "totalbytesreceived" 713 | } 714 | ], 715 | "target": "" 716 | } 717 | ], 718 | "thresholds": "", 719 | "title": "Total Download", 720 | "type": "singlestat", 721 | "valueFontSize": "80%", 722 | "valueMaps": [ 723 | { 724 | "op": "=", 725 | "text": "N/A", 726 | "value": "null" 727 | } 728 | ], 729 | "valueName": "current" 730 | }, 731 | { 732 | "cacheTimeout": null, 733 | "colorBackground": false, 734 | "colorValue": false, 735 | "colors": [ 736 | "rgba(245, 54, 54, 0.9)", 737 | "rgba(237, 129, 40, 0.89)", 738 | "rgba(50, 172, 45, 0.97)" 739 | ], 740 | "datasource": "influx-mqttdata", 741 | "decimals": 1, 742 | "format": "decbytes", 743 | "gauge": { 744 | "maxValue": 100, 745 | "minValue": 0, 746 | "show": false, 747 | "thresholdLabels": false, 748 | "thresholdMarkers": true 749 | }, 750 | "gridPos": { 751 | "h": 4, 752 | "w": 6, 753 | "x": 6, 754 | "y": 4 755 | }, 756 | "id": 8, 757 | "interval": null, 758 | "links": [], 759 | "mappingType": 1, 760 | "mappingTypes": [ 761 | { 762 | "name": "value to text", 763 | "value": 1 764 | }, 765 | { 766 | "name": "range to text", 767 | "value": 2 768 | } 769 | ], 770 | "maxDataPoints": 100, 771 | "nullPointMode": "connected", 772 | "nullText": null, 773 | "options": {}, 774 | "postfix": "", 775 | "postfixFontSize": "50%", 776 | "prefix": "", 777 | "prefixFontSize": "50%", 778 | "rangeMaps": [ 779 | { 780 | "from": "null", 781 | "text": "N/A", 782 | "to": "null" 783 | } 784 | ], 785 | "sparkline": { 786 | "fillColor": "rgba(137, 15, 2, 0.18)", 787 | "full": false, 788 | "lineColor": "#e24d42", 789 | "show": true 790 | }, 791 | "tableColumn": "", 792 | "targets": [ 793 | { 794 | "dsType": "influxdb", 795 | "groupBy": [ 796 | { 797 | "params": [ 798 | "$__interval" 799 | ], 800 | "type": "time" 801 | }, 802 | { 803 | "params": [ 804 | "null" 805 | ], 806 | "type": "fill" 807 | } 808 | ], 809 | "measurement": "fritzbox_value", 810 | "orderByTime": "ASC", 811 | "policy": "default", 812 | "query": "SELECT cumulative_sum(non_negative_difference(last(\"NewTotalBytesSent\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter GROUP BY time($__interval)", 813 | "rawQuery": true, 814 | "refId": "A", 815 | "resultFormat": "time_series", 816 | "select": [ 817 | [ 818 | { 819 | "params": [ 820 | "value" 821 | ], 822 | "type": "field" 823 | }, 824 | { 825 | "params": [], 826 | "type": "mean" 827 | } 828 | ] 829 | ], 830 | "tags": [ 831 | { 832 | "key": "type_instance", 833 | "operator": "=", 834 | "value": "totalbytesreceived" 835 | } 836 | ], 837 | "target": "" 838 | } 839 | ], 840 | "thresholds": "", 841 | "title": "Total Upload", 842 | "type": "singlestat", 843 | "valueFontSize": "80%", 844 | "valueMaps": [ 845 | { 846 | "op": "=", 847 | "text": "N/A", 848 | "value": "null" 849 | } 850 | ], 851 | "valueName": "current" 852 | }, 853 | { 854 | "cacheTimeout": null, 855 | "colorBackground": false, 856 | "colorValue": false, 857 | "colors": [ 858 | "rgba(245, 54, 54, 0.9)", 859 | "rgba(237, 129, 40, 0.89)", 860 | "rgba(50, 172, 45, 0.97)" 861 | ], 862 | "datasource": "influx-mqttdata", 863 | "decimals": 1, 864 | "format": "decbytes", 865 | "gauge": { 866 | "maxValue": 100, 867 | "minValue": 0, 868 | "show": false, 869 | "thresholdLabels": false, 870 | "thresholdMarkers": true 871 | }, 872 | "gridPos": { 873 | "h": 4, 874 | "w": 6, 875 | "x": 12, 876 | "y": 4 877 | }, 878 | "hideTimeOverride": false, 879 | "id": 15, 880 | "interval": null, 881 | "links": [], 882 | "mappingType": 1, 883 | "mappingTypes": [ 884 | { 885 | "name": "value to text", 886 | "value": 1 887 | }, 888 | { 889 | "name": "range to text", 890 | "value": 2 891 | } 892 | ], 893 | "maxDataPoints": 100, 894 | "nullPointMode": "connected", 895 | "nullText": null, 896 | "options": {}, 897 | "postfix": "", 898 | "postfixFontSize": "50%", 899 | "prefix": "", 900 | "prefixFontSize": "50%", 901 | "rangeMaps": [ 902 | { 903 | "from": "null", 904 | "text": "N/A", 905 | "to": "null" 906 | } 907 | ], 908 | "sparkline": { 909 | "fillColor": "rgba(31, 118, 189, 0.18)", 910 | "full": false, 911 | "lineColor": "rgb(31, 120, 193)", 912 | "show": true 913 | }, 914 | "tableColumn": "", 915 | "targets": [ 916 | { 917 | "dsType": "influxdb", 918 | "groupBy": [ 919 | { 920 | "params": [ 921 | "$__interval" 922 | ], 923 | "type": "time" 924 | }, 925 | { 926 | "params": [ 927 | "null" 928 | ], 929 | "type": "fill" 930 | } 931 | ], 932 | "measurement": "fritzbox_value", 933 | "orderByTime": "ASC", 934 | "policy": "default", 935 | "query": "SELECT cumulative_sum(non_negative_difference(last(\"NewTotalBytesReceived\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter\nGROUP BY time($__interval) tz('Europe/Berlin')", 936 | "rawQuery": true, 937 | "refId": "A", 938 | "resultFormat": "time_series", 939 | "select": [ 940 | [ 941 | { 942 | "params": [ 943 | "value" 944 | ], 945 | "type": "field" 946 | }, 947 | { 948 | "params": [], 949 | "type": "mean" 950 | } 951 | ] 952 | ], 953 | "tags": [ 954 | { 955 | "key": "type_instance", 956 | "operator": "=", 957 | "value": "totalbytesreceived" 958 | } 959 | ], 960 | "target": "" 961 | } 962 | ], 963 | "thresholds": "", 964 | "timeFrom": "1d", 965 | "timeShift": null, 966 | "title": "Last 24h Download", 967 | "type": "singlestat", 968 | "valueFontSize": "80%", 969 | "valueMaps": [ 970 | { 971 | "op": "=", 972 | "text": "N/A", 973 | "value": "null" 974 | } 975 | ], 976 | "valueName": "current" 977 | }, 978 | { 979 | "cacheTimeout": null, 980 | "colorBackground": false, 981 | "colorValue": false, 982 | "colors": [ 983 | "rgba(245, 54, 54, 0.9)", 984 | "rgba(237, 129, 40, 0.89)", 985 | "rgba(50, 172, 45, 0.97)" 986 | ], 987 | "datasource": "influx-mqttdata", 988 | "decimals": 1, 989 | "format": "decbytes", 990 | "gauge": { 991 | "maxValue": 100, 992 | "minValue": 0, 993 | "show": false, 994 | "thresholdLabels": false, 995 | "thresholdMarkers": true 996 | }, 997 | "gridPos": { 998 | "h": 4, 999 | "w": 6, 1000 | "x": 18, 1001 | "y": 4 1002 | }, 1003 | "id": 16, 1004 | "interval": null, 1005 | "links": [], 1006 | "mappingType": 1, 1007 | "mappingTypes": [ 1008 | { 1009 | "name": "value to text", 1010 | "value": 1 1011 | }, 1012 | { 1013 | "name": "range to text", 1014 | "value": 2 1015 | } 1016 | ], 1017 | "maxDataPoints": 100, 1018 | "nullPointMode": "connected", 1019 | "nullText": null, 1020 | "options": {}, 1021 | "postfix": "", 1022 | "postfixFontSize": "50%", 1023 | "prefix": "", 1024 | "prefixFontSize": "50%", 1025 | "rangeMaps": [ 1026 | { 1027 | "from": "null", 1028 | "text": "N/A", 1029 | "to": "null" 1030 | } 1031 | ], 1032 | "sparkline": { 1033 | "fillColor": "rgba(137, 15, 2, 0.18)", 1034 | "full": false, 1035 | "lineColor": "#e24d42", 1036 | "show": true 1037 | }, 1038 | "tableColumn": "", 1039 | "targets": [ 1040 | { 1041 | "dsType": "influxdb", 1042 | "groupBy": [ 1043 | { 1044 | "params": [ 1045 | "$__interval" 1046 | ], 1047 | "type": "time" 1048 | }, 1049 | { 1050 | "params": [ 1051 | "null" 1052 | ], 1053 | "type": "fill" 1054 | } 1055 | ], 1056 | "measurement": "fritzbox_value", 1057 | "orderByTime": "ASC", 1058 | "policy": "default", 1059 | "query": "SELECT cumulative_sum(non_negative_difference(last(\"NewTotalBytesSent\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter\nGROUP BY time($__interval) tz('Europe/Berlin')", 1060 | "rawQuery": true, 1061 | "refId": "A", 1062 | "resultFormat": "time_series", 1063 | "select": [ 1064 | [ 1065 | { 1066 | "params": [ 1067 | "value" 1068 | ], 1069 | "type": "field" 1070 | }, 1071 | { 1072 | "params": [], 1073 | "type": "mean" 1074 | } 1075 | ] 1076 | ], 1077 | "tags": [ 1078 | { 1079 | "key": "type_instance", 1080 | "operator": "=", 1081 | "value": "totalbytesreceived" 1082 | } 1083 | ], 1084 | "target": "" 1085 | } 1086 | ], 1087 | "thresholds": "", 1088 | "timeFrom": "1d", 1089 | "title": "Last 24h Upload", 1090 | "type": "singlestat", 1091 | "valueFontSize": "80%", 1092 | "valueMaps": [ 1093 | { 1094 | "op": "=", 1095 | "text": "N/A", 1096 | "value": "null" 1097 | } 1098 | ], 1099 | "valueName": "current" 1100 | }, 1101 | { 1102 | "aliasColors": { 1103 | "Download": "#1f78c1", 1104 | "Upload": "#e24d42", 1105 | "fritzbox_value.non_negative_derivative": "#ba43a9", 1106 | "fritzbox_value.non_negative_difference": "#e24d42" 1107 | }, 1108 | "bars": true, 1109 | "dashLength": 10, 1110 | "dashes": false, 1111 | "datasource": null, 1112 | "fill": 1, 1113 | "fillGradient": 0, 1114 | "gridPos": { 1115 | "h": 7, 1116 | "w": 12, 1117 | "x": 0, 1118 | "y": 8 1119 | }, 1120 | "hiddenSeries": false, 1121 | "id": 2, 1122 | "interval": "10s", 1123 | "legend": { 1124 | "alignAsTable": false, 1125 | "avg": false, 1126 | "current": false, 1127 | "hideEmpty": false, 1128 | "hideZero": false, 1129 | "max": false, 1130 | "min": false, 1131 | "rightSide": false, 1132 | "show": true, 1133 | "total": false, 1134 | "values": false 1135 | }, 1136 | "lines": false, 1137 | "linewidth": 1, 1138 | "links": [], 1139 | "nullPointMode": "null", 1140 | "options": { 1141 | "dataLinks": [] 1142 | }, 1143 | "percentage": false, 1144 | "pointradius": 5, 1145 | "points": false, 1146 | "renderer": "flot", 1147 | "seriesOverrides": [], 1148 | "spaceLength": 10, 1149 | "stack": false, 1150 | "steppedLine": false, 1151 | "targets": [ 1152 | { 1153 | "alias": "Download", 1154 | "dsType": "influxdb", 1155 | "groupBy": [ 1156 | { 1157 | "params": [ 1158 | "24h" 1159 | ], 1160 | "type": "time" 1161 | } 1162 | ], 1163 | "hide": false, 1164 | "measurement": "fritzbox_value", 1165 | "orderByTime": "ASC", 1166 | "policy": "default", 1167 | "query": "SELECT non_negative_difference(last(cumulative_sum)) FROM (\nSELECT cumulative_sum(non_negative_difference(last(\"NewTotalBytesReceived\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter GROUP BY time($__interval)\n) WHERE $timeFilter GROUP BY time(1d) tz('Europe/Berlin')", 1168 | "rawQuery": true, 1169 | "refId": "F", 1170 | "resultFormat": "time_series", 1171 | "select": [ 1172 | [ 1173 | { 1174 | "params": [ 1175 | "value" 1176 | ], 1177 | "type": "field" 1178 | }, 1179 | { 1180 | "params": [], 1181 | "type": "max" 1182 | }, 1183 | { 1184 | "params": [ 1185 | "10s" 1186 | ], 1187 | "type": "non_negative_derivative" 1188 | } 1189 | ] 1190 | ], 1191 | "tags": [ 1192 | { 1193 | "key": "type_instance", 1194 | "operator": "=", 1195 | "value": "totalbytesreceived" 1196 | } 1197 | ], 1198 | "target": "" 1199 | }, 1200 | { 1201 | "alias": "Upload", 1202 | "dsType": "influxdb", 1203 | "groupBy": [ 1204 | { 1205 | "params": [ 1206 | "24h" 1207 | ], 1208 | "type": "time" 1209 | } 1210 | ], 1211 | "hide": false, 1212 | "measurement": "fritzbox_value", 1213 | "orderByTime": "ASC", 1214 | "policy": "default", 1215 | "query": "SELECT non_negative_difference(last(cumulative_sum)) FROM (\nSELECT cumulative_sum(non_negative_difference(last(\"NewTotalBytesSent\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter GROUP BY time($__interval)\n) WHERE $timeFilter GROUP BY time(1d) tz('Europe/Berlin')", 1216 | "rawQuery": true, 1217 | "refId": "A", 1218 | "resultFormat": "time_series", 1219 | "select": [ 1220 | [ 1221 | { 1222 | "params": [ 1223 | "value" 1224 | ], 1225 | "type": "field" 1226 | }, 1227 | { 1228 | "params": [], 1229 | "type": "max" 1230 | }, 1231 | { 1232 | "params": [ 1233 | "10s" 1234 | ], 1235 | "type": "non_negative_derivative" 1236 | } 1237 | ] 1238 | ], 1239 | "tags": [ 1240 | { 1241 | "key": "type_instance", 1242 | "operator": "=", 1243 | "value": "totalbytesreceived" 1244 | } 1245 | ], 1246 | "target": "" 1247 | } 1248 | ], 1249 | "thresholds": [], 1250 | "timeFrom": null, 1251 | "timeRegions": [], 1252 | "timeShift": null, 1253 | "title": "Daily Traffic", 1254 | "tooltip": { 1255 | "shared": true, 1256 | "sort": 0, 1257 | "value_type": "individual" 1258 | }, 1259 | "type": "graph", 1260 | "xaxis": { 1261 | "buckets": null, 1262 | "mode": "time", 1263 | "name": null, 1264 | "show": true, 1265 | "values": [] 1266 | }, 1267 | "yaxes": [ 1268 | { 1269 | "decimals": 2, 1270 | "format": "decbytes", 1271 | "label": "", 1272 | "logBase": 1, 1273 | "max": null, 1274 | "min": null, 1275 | "show": true 1276 | }, 1277 | { 1278 | "format": "decbytes", 1279 | "label": null, 1280 | "logBase": 1, 1281 | "max": null, 1282 | "min": null, 1283 | "show": true 1284 | } 1285 | ], 1286 | "yaxis": { 1287 | "align": false, 1288 | "alignLevel": null 1289 | } 1290 | }, 1291 | { 1292 | "aliasColors": { 1293 | "download": "#1F78C1", 1294 | "upload": "#EA6460" 1295 | }, 1296 | "annotate": { 1297 | "enable": false 1298 | }, 1299 | "bars": true, 1300 | "dashLength": 10, 1301 | "dashes": false, 1302 | "datasource": "influx-mqttdata", 1303 | "editable": true, 1304 | "fill": 0, 1305 | "fillGradient": 0, 1306 | "grid": {}, 1307 | "gridPos": { 1308 | "h": 7, 1309 | "w": 12, 1310 | "x": 12, 1311 | "y": 8 1312 | }, 1313 | "hiddenSeries": false, 1314 | "id": 17, 1315 | "interval": "1h", 1316 | "legend": { 1317 | "avg": false, 1318 | "current": true, 1319 | "max": false, 1320 | "min": false, 1321 | "show": true, 1322 | "total": false, 1323 | "values": true 1324 | }, 1325 | "lines": false, 1326 | "linewidth": 1, 1327 | "links": [], 1328 | "nullPointMode": "null", 1329 | "options": { 1330 | "dataLinks": [] 1331 | }, 1332 | "percentage": false, 1333 | "pointradius": 5, 1334 | "points": false, 1335 | "renderer": "flot", 1336 | "resolution": 100, 1337 | "scale": 1, 1338 | "seriesOverrides": [], 1339 | "spaceLength": 10, 1340 | "stack": false, 1341 | "steppedLine": true, 1342 | "targets": [ 1343 | { 1344 | "alias": "download", 1345 | "dsType": "influxdb", 1346 | "fields": [ 1347 | { 1348 | "func": "mean", 1349 | "name": "value" 1350 | } 1351 | ], 1352 | "groupBy": [ 1353 | { 1354 | "params": [ 1355 | "$interval" 1356 | ], 1357 | "type": "time" 1358 | }, 1359 | { 1360 | "params": [ 1361 | "null" 1362 | ], 1363 | "type": "fill" 1364 | } 1365 | ], 1366 | "groupByTags": [], 1367 | "measurement": "fritzbox_value", 1368 | "orderByTime": "ASC", 1369 | "policy": "default", 1370 | "query": "SELECT non_negative_difference(last(cumulative_sum)) FROM (\nSELECT cumulative_sum(non_negative_difference(last(\"NewTotalBytesReceived\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter GROUP BY time($__interval)\n) WHERE $timeFilter GROUP BY time($__interval) tz('Europe/Berlin')", 1371 | "rawQuery": true, 1372 | "refId": "A", 1373 | "resultFormat": "time_series", 1374 | "select": [ 1375 | [ 1376 | { 1377 | "params": [ 1378 | "value" 1379 | ], 1380 | "type": "field" 1381 | }, 1382 | { 1383 | "params": [], 1384 | "type": "mean" 1385 | } 1386 | ] 1387 | ], 1388 | "tags": [ 1389 | { 1390 | "key": "type_instance", 1391 | "operator": "=", 1392 | "value": "totalbytesreceived" 1393 | } 1394 | ], 1395 | "target": "alias(summarize(nonNegativeDerivative(collectd.squirrel.fritzbox.bytes-totalbytesreceived, 0), '1h', 'sum'), 'download')" 1396 | }, 1397 | { 1398 | "alias": "upload", 1399 | "dsType": "influxdb", 1400 | "fields": [ 1401 | { 1402 | "func": "mean", 1403 | "name": "value" 1404 | } 1405 | ], 1406 | "fill": "null", 1407 | "groupBy": [ 1408 | { 1409 | "params": [ 1410 | "$interval" 1411 | ], 1412 | "type": "time" 1413 | }, 1414 | { 1415 | "params": [ 1416 | "null" 1417 | ], 1418 | "type": "fill" 1419 | } 1420 | ], 1421 | "groupByTags": [], 1422 | "hide": false, 1423 | "measurement": "fritzbox_value", 1424 | "orderByTime": "ASC", 1425 | "policy": "default", 1426 | "query": "SELECT non_negative_difference(last(cumulative_sum)) FROM (\nSELECT cumulative_sum(non_negative_difference(last(\"NewTotalBytesSent\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter GROUP BY time($__interval)\n) WHERE $timeFilter GROUP BY time($__interval) tz('Europe/Berlin')", 1427 | "rawQuery": true, 1428 | "refId": "B", 1429 | "resultFormat": "time_series", 1430 | "select": [ 1431 | [ 1432 | { 1433 | "params": [ 1434 | "value" 1435 | ], 1436 | "type": "field" 1437 | }, 1438 | { 1439 | "params": [], 1440 | "type": "mean" 1441 | } 1442 | ] 1443 | ], 1444 | "tags": [ 1445 | { 1446 | "key": "type_instance", 1447 | "operator": "=", 1448 | "value": "totalbytessent" 1449 | } 1450 | ], 1451 | "target": "alias(summarize(nonNegativeDerivative(collectd.squirrel.fritzbox.bytes-totalbytessent,0),'1h','sum'),'upload')" 1452 | } 1453 | ], 1454 | "thresholds": [], 1455 | "timeFrom": null, 1456 | "timeRegions": [], 1457 | "timeShift": null, 1458 | "title": "Current Traffic", 1459 | "tooltip": { 1460 | "msResolution": false, 1461 | "query_as_alias": true, 1462 | "shared": true, 1463 | "sort": 0, 1464 | "value_type": "cumulative" 1465 | }, 1466 | "type": "graph", 1467 | "xaxis": { 1468 | "buckets": null, 1469 | "mode": "time", 1470 | "name": null, 1471 | "show": true, 1472 | "values": [] 1473 | }, 1474 | "yaxes": [ 1475 | { 1476 | "format": "decbytes", 1477 | "logBase": 1, 1478 | "max": null, 1479 | "min": 0, 1480 | "show": true 1481 | }, 1482 | { 1483 | "format": "short", 1484 | "logBase": 1, 1485 | "max": null, 1486 | "min": null, 1487 | "show": true 1488 | } 1489 | ], 1490 | "yaxis": { 1491 | "align": false, 1492 | "alignLevel": null 1493 | }, 1494 | "zerofill": true 1495 | }, 1496 | { 1497 | "columns": [], 1498 | "datasource": "influx-mqttdata", 1499 | "fontSize": "100%", 1500 | "gridPos": { 1501 | "h": 8, 1502 | "w": 12, 1503 | "x": 0, 1504 | "y": 15 1505 | }, 1506 | "id": 14, 1507 | "links": [], 1508 | "options": {}, 1509 | "pageSize": null, 1510 | "scroll": true, 1511 | "showHeader": true, 1512 | "sort": { 1513 | "col": 0, 1514 | "desc": true 1515 | }, 1516 | "styles": [ 1517 | { 1518 | "alias": "Time", 1519 | "dateFormat": "MMMM D, YYYY LT", 1520 | "pattern": "Time", 1521 | "type": "date" 1522 | }, 1523 | { 1524 | "alias": "", 1525 | "colorMode": null, 1526 | "colors": [ 1527 | "rgba(245, 54, 54, 0.9)", 1528 | "rgba(237, 129, 40, 0.89)", 1529 | "rgba(50, 172, 45, 0.97)" 1530 | ], 1531 | "decimals": 2, 1532 | "pattern": "/.*/", 1533 | "thresholds": [], 1534 | "type": "number", 1535 | "unit": "decbytes" 1536 | } 1537 | ], 1538 | "targets": [ 1539 | { 1540 | "alias": "Download", 1541 | "dsType": "influxdb", 1542 | "groupBy": [ 1543 | { 1544 | "params": [ 1545 | "1d" 1546 | ], 1547 | "type": "time" 1548 | } 1549 | ], 1550 | "measurement": "fritzbox_value", 1551 | "orderByTime": "ASC", 1552 | "policy": "default", 1553 | "query": "SELECT non_negative_difference(last(cumulative_sum)) FROM (\nSELECT cumulative_sum(non_negative_difference(last(\"NewBytesReceived\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter GROUP BY time($__interval)\n) WHERE $timeFilter GROUP BY time(1d) tz('Europe/Berlin')", 1554 | "rawQuery": true, 1555 | "refId": "A", 1556 | "resultFormat": "time_series", 1557 | "select": [ 1558 | [ 1559 | { 1560 | "params": [ 1561 | "value" 1562 | ], 1563 | "type": "field" 1564 | }, 1565 | { 1566 | "params": [], 1567 | "type": "max" 1568 | }, 1569 | { 1570 | "params": [ 1571 | "10s" 1572 | ], 1573 | "type": "non_negative_derivative" 1574 | } 1575 | ] 1576 | ], 1577 | "tags": [ 1578 | { 1579 | "key": "type_instance", 1580 | "operator": "=", 1581 | "value": "totalbytesreceived" 1582 | } 1583 | ], 1584 | "target": "" 1585 | }, 1586 | { 1587 | "alias": "Upload", 1588 | "dsType": "influxdb", 1589 | "groupBy": [ 1590 | { 1591 | "params": [ 1592 | "1d" 1593 | ], 1594 | "type": "time" 1595 | } 1596 | ], 1597 | "measurement": "fritzbox_value", 1598 | "orderByTime": "ASC", 1599 | "policy": "default", 1600 | "query": "SELECT non_negative_difference(last(cumulative_sum)) FROM (\nSELECT cumulative_sum(non_negative_difference(last(\"NewBytesSent\"))) \nFROM \"mqtt_consumer\" WHERE (\"topic\" = 'internet/connection/dsl') AND $timeFilter GROUP BY time($__interval)\n) WHERE $timeFilter GROUP BY time(1d) tz('Europe/Berlin')", 1601 | "rawQuery": true, 1602 | "refId": "B", 1603 | "resultFormat": "time_series", 1604 | "select": [ 1605 | [ 1606 | { 1607 | "params": [ 1608 | "value" 1609 | ], 1610 | "type": "field" 1611 | }, 1612 | { 1613 | "params": [], 1614 | "type": "max" 1615 | }, 1616 | { 1617 | "params": [ 1618 | "10s" 1619 | ], 1620 | "type": "non_negative_derivative" 1621 | } 1622 | ] 1623 | ], 1624 | "tags": [ 1625 | { 1626 | "key": "type_instance", 1627 | "operator": "=", 1628 | "value": "totalbytesreceived" 1629 | } 1630 | ], 1631 | "target": "" 1632 | } 1633 | ], 1634 | "title": "Daily Traffic", 1635 | "transform": "timeseries_to_columns", 1636 | "type": "table" 1637 | }, 1638 | { 1639 | "aliasColors": { 1640 | "downstream": "#1F78C1", 1641 | "downstream max": "#0A437C", 1642 | "upstream": "#EA6460", 1643 | "upstream max": "#890F02" 1644 | }, 1645 | "annotate": { 1646 | "enable": false 1647 | }, 1648 | "bars": false, 1649 | "dashLength": 10, 1650 | "dashes": false, 1651 | "datasource": null, 1652 | "editable": true, 1653 | "fill": 1, 1654 | "fillGradient": 0, 1655 | "grid": {}, 1656 | "gridPos": { 1657 | "h": 8, 1658 | "w": 12, 1659 | "x": 12, 1660 | "y": 15 1661 | }, 1662 | "hiddenSeries": false, 1663 | "id": 18, 1664 | "legend": { 1665 | "avg": false, 1666 | "current": true, 1667 | "max": false, 1668 | "min": false, 1669 | "show": true, 1670 | "total": false, 1671 | "values": true 1672 | }, 1673 | "lines": true, 1674 | "linewidth": 1, 1675 | "links": [], 1676 | "nullPointMode": "connected", 1677 | "options": { 1678 | "dataLinks": [] 1679 | }, 1680 | "percentage": false, 1681 | "pointradius": 5, 1682 | "points": false, 1683 | "renderer": "flot", 1684 | "resolution": 100, 1685 | "scale": 1, 1686 | "seriesOverrides": [], 1687 | "spaceLength": 10, 1688 | "stack": false, 1689 | "steppedLine": false, 1690 | "targets": [ 1691 | { 1692 | "alias": "downstream", 1693 | "dsType": "influxdb", 1694 | "fields": [ 1695 | { 1696 | "func": "mean", 1697 | "name": "value" 1698 | } 1699 | ], 1700 | "groupBy": [ 1701 | { 1702 | "params": [ 1703 | "$__interval" 1704 | ], 1705 | "type": "time" 1706 | }, 1707 | { 1708 | "params": [ 1709 | "null" 1710 | ], 1711 | "type": "fill" 1712 | } 1713 | ], 1714 | "groupByTags": [], 1715 | "hide": false, 1716 | "measurement": "mqtt_consumer", 1717 | "orderByTime": "ASC", 1718 | "policy": "default", 1719 | "query": "SELECT mean(value) FROM \"fritzbox_value\" WHERE \"type_instance\" = 'receiverate' AND $timeFilter GROUP BY time($interval)", 1720 | "refId": "A", 1721 | "resultFormat": "time_series", 1722 | "select": [ 1723 | [ 1724 | { 1725 | "params": [ 1726 | "NewDownstreamCurrRate" 1727 | ], 1728 | "type": "field" 1729 | }, 1730 | { 1731 | "params": [], 1732 | "type": "mean" 1733 | } 1734 | ] 1735 | ], 1736 | "tags": [ 1737 | { 1738 | "key": "topic", 1739 | "operator": "=", 1740 | "value": "internet/connection/dsl" 1741 | } 1742 | ], 1743 | "target": "alias(collectd.squirrel.fritzbox.bitrate-receiverate,'downstream')" 1744 | }, 1745 | { 1746 | "alias": "downstream max", 1747 | "dsType": "influxdb", 1748 | "fields": [ 1749 | { 1750 | "func": "mean", 1751 | "name": "value" 1752 | } 1753 | ], 1754 | "groupBy": [ 1755 | { 1756 | "params": [ 1757 | "$__interval" 1758 | ], 1759 | "type": "time" 1760 | }, 1761 | { 1762 | "params": [ 1763 | "null" 1764 | ], 1765 | "type": "fill" 1766 | } 1767 | ], 1768 | "groupByTags": [], 1769 | "measurement": "mqtt_consumer", 1770 | "orderByTime": "ASC", 1771 | "policy": "default", 1772 | "query": "SELECT mean(value) FROM \"fritzbox_value\" WHERE \"type_instance\" = 'downstreammax' AND $timeFilter GROUP BY time($interval)", 1773 | "refId": "B", 1774 | "resultFormat": "time_series", 1775 | "select": [ 1776 | [ 1777 | { 1778 | "params": [ 1779 | "NewDownstreamMaxRate" 1780 | ], 1781 | "type": "field" 1782 | }, 1783 | { 1784 | "params": [], 1785 | "type": "mean" 1786 | } 1787 | ] 1788 | ], 1789 | "tags": [ 1790 | { 1791 | "key": "topic", 1792 | "operator": "=", 1793 | "value": "internet/connection/dsl" 1794 | } 1795 | ], 1796 | "target": "alias(collectd.squirrel.fritzbox.bitrate-downstreammax,'downstream max')" 1797 | }, 1798 | { 1799 | "alias": "upstream", 1800 | "dsType": "influxdb", 1801 | "fields": [ 1802 | { 1803 | "func": "mean", 1804 | "name": "value" 1805 | } 1806 | ], 1807 | "groupBy": [ 1808 | { 1809 | "params": [ 1810 | "$__interval" 1811 | ], 1812 | "type": "time" 1813 | }, 1814 | { 1815 | "params": [ 1816 | "null" 1817 | ], 1818 | "type": "fill" 1819 | } 1820 | ], 1821 | "groupByTags": [], 1822 | "hide": false, 1823 | "measurement": "mqtt_consumer", 1824 | "orderByTime": "ASC", 1825 | "policy": "default", 1826 | "query": "SELECT mean(value) FROM \"fritzbox_value\" WHERE \"type_instance\" = 'sendrate' AND $timeFilter GROUP BY time($interval)", 1827 | "refId": "C", 1828 | "resultFormat": "time_series", 1829 | "select": [ 1830 | [ 1831 | { 1832 | "params": [ 1833 | "NewUpstreamCurrRate" 1834 | ], 1835 | "type": "field" 1836 | }, 1837 | { 1838 | "params": [], 1839 | "type": "mean" 1840 | } 1841 | ] 1842 | ], 1843 | "tags": [ 1844 | { 1845 | "key": "topic", 1846 | "operator": "=", 1847 | "value": "internet/connection/dsl" 1848 | } 1849 | ], 1850 | "target": "alias(collectd.squirrel.fritzbox.bitrate-sendrate,'upstream')" 1851 | }, 1852 | { 1853 | "alias": "upstream max", 1854 | "dsType": "influxdb", 1855 | "fields": [ 1856 | { 1857 | "func": "mean", 1858 | "name": "value" 1859 | } 1860 | ], 1861 | "groupBy": [ 1862 | { 1863 | "params": [ 1864 | "$__interval" 1865 | ], 1866 | "type": "time" 1867 | }, 1868 | { 1869 | "params": [ 1870 | "null" 1871 | ], 1872 | "type": "fill" 1873 | } 1874 | ], 1875 | "groupByTags": [], 1876 | "measurement": "mqtt_consumer", 1877 | "orderByTime": "ASC", 1878 | "policy": "default", 1879 | "query": "SELECT mean(value) FROM \"fritzbox_value\" WHERE \"type_instance\" = 'upstreammax' AND $timeFilter GROUP BY time($interval)", 1880 | "refId": "D", 1881 | "resultFormat": "time_series", 1882 | "select": [ 1883 | [ 1884 | { 1885 | "params": [ 1886 | "NewUpstreamMaxRate" 1887 | ], 1888 | "type": "field" 1889 | }, 1890 | { 1891 | "params": [], 1892 | "type": "mean" 1893 | } 1894 | ] 1895 | ], 1896 | "tags": [ 1897 | { 1898 | "key": "topic", 1899 | "operator": "=", 1900 | "value": "internet/connection/dsl" 1901 | } 1902 | ], 1903 | "target": "alias(collectd.squirrel.fritzbox.bitrate-upstreammax,'upstream max')" 1904 | } 1905 | ], 1906 | "thresholds": [], 1907 | "timeFrom": null, 1908 | "timeRegions": [], 1909 | "timeShift": null, 1910 | "title": "Bandwidth", 1911 | "tooltip": { 1912 | "msResolution": false, 1913 | "query_as_alias": true, 1914 | "shared": true, 1915 | "sort": 0, 1916 | "value_type": "cumulative" 1917 | }, 1918 | "type": "graph", 1919 | "xaxis": { 1920 | "buckets": null, 1921 | "mode": "time", 1922 | "name": null, 1923 | "show": true, 1924 | "values": [] 1925 | }, 1926 | "yaxes": [ 1927 | { 1928 | "format": "Kbits", 1929 | "logBase": 1, 1930 | "max": null, 1931 | "min": "0", 1932 | "show": true 1933 | }, 1934 | { 1935 | "format": "Kbits", 1936 | "logBase": 1, 1937 | "max": null, 1938 | "min": null, 1939 | "show": true 1940 | } 1941 | ], 1942 | "yaxis": { 1943 | "align": false, 1944 | "alignLevel": null 1945 | }, 1946 | "zerofill": true 1947 | } 1948 | ], 1949 | "refresh": "1m", 1950 | "schemaVersion": 21, 1951 | "style": "dark", 1952 | "tags": [], 1953 | "templating": { 1954 | "list": [] 1955 | }, 1956 | "time": { 1957 | "from": "now-7d", 1958 | "to": "now" 1959 | }, 1960 | "timepicker": { 1961 | "refresh_intervals": [ 1962 | "5s", 1963 | "10s", 1964 | "30s", 1965 | "1m", 1966 | "5m", 1967 | "15m", 1968 | "30m", 1969 | "1h", 1970 | "2h", 1971 | "1d" 1972 | ], 1973 | "time_options": [ 1974 | "5m", 1975 | "15m", 1976 | "1h", 1977 | "6h", 1978 | "12h", 1979 | "24h", 1980 | "2d", 1981 | "7d", 1982 | "30d" 1983 | ] 1984 | }, 1985 | "timezone": "", 1986 | "title": "FRITZ!Box Router Status", 1987 | "uid": "000000013", 1988 | "version": 18 1989 | } 1990 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker push pharndt/fritzboxmqtt:latest 3 | -------------------------------------------------------------------------------- /production/config/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | -------------------------------------------------------------------------------- /production/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | fritzboxmqtt: 5 | hostname: fritzboxmqtt 6 | image: pharndt/fritzboxmqtt 7 | volumes: 8 | - ./config:/var/lib/fritzbox-to-mqtt-gw:ro 9 | restart: always 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | --------------------------------------------------------------------------------