Controlling Syncthing rate limits using Home Assistant

Contents

Introduction

Syncthing is a fantastic open-source file synchronization application that I use to keep folders in sync between my home and remote servers. For larger transfers, Syncthing will max out my home internet connection, which can make streaming or browsing a bit of a pain.

While Syncthing does support rate limiting of transfers, it can be frustrating to run through the user authentication and change the limit manually every time. For my use-case, a simple toggle switch somewhere with a predefined rate-limit would be ideal.

Enter Home Assistant, my home automation and control platform of choice. I use my Home Assistant dashboard as a control panel for the whole house, and it is always easily accessible. While Home Assistant has a massive library of Integrations, there is not yet any official support for Syncthing.

In this guide we will be using Home Assistant and Node-RED (via the Community Add-On) to build a small control panel that lets us enable/disable rate limiting and monitor our current sync progress.

The final rate limiting control panel

Syncthing Configuration

The first piece of information we will need is your API key, which can be found by opening your Syncthing settings panel.

The main Syncthing settings panel

Next, we will need the ID of the folder you would like to monitor. You can find this by clicking on the folder on the Syncthing homepage and noting down the “Folder ID” field.

The Syncthing folder pane

Once you have noted these down, we can move on to configuring Node-RED.

Node-RED Flow

While Home Assistant has a REST Sensor that can be used to pull in values from a RESTful API, I was unable to find an easy way to modify the contents of a received payload and fire off a POST request to the same endpoint. Instead, we will be using Node-RED to achieve this using the flow below.

The Node-RED flow
[{"id":"a37931fd.2c5168","type":"tab","label":"Syncthing Rate Limit","disabled":false,"info":""},{"id":"f0fb11fd.0cd388","type":"http request","z":"a37931fd.2c5168","name":"","method":"GET","ret":"obj","paytoqs":false,"url":"http://ServerIPAddress:8384/rest/system/config","tls":"","persist":false,"proxy":"","authType":"","x":1030,"y":260,"wires":[["72fd4fc8.b5c9f8"]]},{"id":"35c91ab0.3483e6","type":"function","z":"a37931fd.2c5168","name":"Set request authentication headers","func":"msg.headers = {};\nmsg.headers['X-API-Key'] = 'YourAPIKeyHere';\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":360,"wires":[["c503bb57.9f42d"]]},{"id":"72fd4fc8.b5c9f8","type":"function","z":"a37931fd.2c5168","name":"Turn rate limit on","func":"msg.payload['options']['maxRecvKbps'] = 512;\nreturn msg;","outputs":1,"noerr":0,"x":1230,"y":260,"wires":[["fdcb51dd.e94768"]]},{"id":"c1e8b3e2.71f9f8","type":"server-state-changed","z":"a37931fd.2c5168","name":"On Rate Limit Switch Change","server":"dc61337d.58b1c","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"switch.syncthing_rate_limit","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"x":240,"y":360,"wires":[["35c91ab0.3483e6"]]},{"id":"c6ee1233.354d9","type":"ha-entity","z":"a37931fd.2c5168","name":"Rate Limit Switch","server":"dc61337d.58b1c","version":1,"debugenabled":false,"outputs":2,"entityType":"switch","config":[{"property":"name","value":"syncthing_rate_limit"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"payload","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","x":210,"y":460,"wires":[[],[]]},{"id":"c503bb57.9f42d","type":"switch","z":"a37931fd.2c5168","name":"Switch by State","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"on","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":800,"y":360,"wires":[["f0fb11fd.0cd388"],["f30f2996.e375a"]]},{"id":"f30f2996.e375a","type":"http request","z":"a37931fd.2c5168","name":"","method":"GET","ret":"obj","paytoqs":false,"url":"http://ServerIPAddress:8384/rest/system/config","tls":"","persist":false,"proxy":"","authType":"","x":1030,"y":460,"wires":[["d79bfe81.456018"]]},{"id":"d79bfe81.456018","type":"function","z":"a37931fd.2c5168","name":"Turn rate limit off","func":"msg.payload['options']['maxRecvKbps'] = 0;\nreturn msg;","outputs":1,"noerr":0,"x":1230,"y":460,"wires":[["556c19fb.8af3"]]},{"id":"b4596b4a.7e44c","type":"http request","z":"a37931fd.2c5168","name":"","method":"POST","ret":"obj","paytoqs":false,"url":"http://ServerIPAddress:8384/rest/system/config","tls":"","persist":false,"proxy":"","authType":"","x":1750,"y":260,"wires":[[]]},{"id":"fdcb51dd.e94768","type":"function","z":"a37931fd.2c5168","name":"Set request authentication headers","func":"msg.headers = {};\nmsg.headers['X-API-Key'] = 'YourAPIKeyHere';\nreturn msg;","outputs":1,"noerr":0,"x":1500,"y":260,"wires":[["b4596b4a.7e44c"]]},{"id":"685f0344.65d7c4","type":"http request","z":"a37931fd.2c5168","name":"","method":"POST","ret":"obj","paytoqs":false,"url":"http://ServerIPAddress:8384/rest/system/config","tls":"","persist":false,"proxy":"","authType":"","x":1750,"y":460,"wires":[[]]},{"id":"556c19fb.8af3","type":"function","z":"a37931fd.2c5168","name":"Set request authentication headers","func":"msg.headers = {};\nmsg.headers['X-API-Key'] = 'YourAPIKeyHere';\nreturn msg;","outputs":1,"noerr":0,"x":1500,"y":460,"wires":[["685f0344.65d7c4"]]},{"id":"d65dcea4.71f8c","type":"comment","z":"a37931fd.2c5168","name":"On","info":"","x":850,"y":300,"wires":[]},{"id":"fc5a5c21.e5c078","type":"comment","z":"a37931fd.2c5168","name":"Off","info":"","x":850,"y":420,"wires":[]},{"id":"cd2f0b76.be8cb","type":"comment","z":"a37931fd.2c5168","name":"GET current configuration","info":"","x":1050,"y":360,"wires":[]},{"id":"dc9eb39f.49e198","type":"comment","z":"a37931fd.2c5168","name":"POST new configuration","info":"","x":1750,"y":360,"wires":[]},{"id":"dc61337d.58b1c","type":"server","z":"","name":"Home Assistant","addon":true}]

There are one or two things that will probably need to be edited in the above flow to work in your setup:

You can also edit the Turn rate limit on node to adjust the limit that will be applied when you turn toggle the switch. I currently have this set to 512 KiB/s.

This flow does the following:

You should now be able to deploy this flow and access the new switch.syncthing_rate_limit entity in Home Assistant.

Home Assistant Configuration

The last piece of the puzzle is to create a sensor which can monitor our current sync progress. This will be accomplished using the Command Line sensor.

Add the following to your configuration.yaml file:

sensor:
  - platform: command_line
    command: "curl -X GET -H \"X-API-Key: YourAPIKeyHere\" http://ServerIPAddress:8384/rest/db/status?folder=\"FolderID\""
    name: syncthing_sync_remaining
    value_template: '{{ (float(value_json.needBytes) / 1073741824) | round(2) }}'
    scan_interval: 600
    unit_of_measurement: "GiB"

This sensor will use the curl command to fire a GET request to the Syncthing /rest/db/status endpoint for your folder every 10 minutes. It then converts the needBytes field to GiB, and rounds it to two decimal places. This will be exposed to Home Assistant under sensor.syncthing_sync_remaining.

You can now go ahead and add your new entities to Home Assistant - I like to place my syncthing_sync_remaining entity in a conditional card that checks for a value of 0.0. This allows the card to be hidden when not needed.

Conclusions

Using the above configuration, you can integrate Syncthing into home assistant to create some simple controls. You can take this further and use the entities that you create for automations, like pausing or applying rate limiting to Syncthing if a media player is busy streaming.

Feel free to leave any questions in the comments below.

comments powered by Disqus