Skip to main content

River Level Monitoring with LoRaWAN and DesignSpark Metrics Part 2: Sensor Installation and Dashboard Build

sensor above the River Calder

Installing the sensor above the River Calder and building out a dashboard.


In part one of this series we took a look at the Dragino LDDS75-8 LoRaWAN distance sensor and connected it to The Things Network to retrieve sensor data. We then produced a Python script that would bridge between TTN’s MQTT broker and DesignSpark Metrics.

In this article, we’ll be modifying our Python script to make it modular with a configuration file, installing the sensor in the final position with a custom mounting bracket and building a more suitable dashboard on DesignSpark Metrics.

Python Script Modifications

Our initial Python script used a set of hard-coded values for connecting to an MQTT broker and DesignSpark Metrics, as well as only reporting the measured distance rather than a calculated depth.

with open(config_file) as fh:
	config = toml.loads(

MQTT_BROKER = config['mqtt']['broker']
MQTT_PORT = config['mqtt']['port']
MQTT_USERNAME = config['mqtt']['username']
MQTT_PASSWORD = config['mqtt']['password']
MQTT_TOPIC = config['mqtt']['topic']

DSM_INSTANCE = config['dsm']['instance']
DSM_KEY = config['dsm']['key']
DSM_URL = config['dsm']['url']

To make the script more flexible, parts were rewritten to take configuration from a TOML file. Information read from the file includes MQTT broker connection details, DesignSpark Metrics connection details, a set of additional labels to be included with the river level readings and a depth measurement to the riverbed where the sensor is installed.

broker = ""
port = 1883
username = ""
password = ""
topic = ""

instance = ""
key = ""
url = ""

river = ""

empty_depth = 5.25

Within the TOML file is an optional parameter [labels] that can be used to supply additional data labels which are appended onto the metrics pushed to the cloud. For example, a label could be used to group a number of sensors by river name.

A parameter called “empty_depth” has to be provided — this is the height of the sensor from the bed of the river which is then used to calculate the water level. The resulting water level value is rounded to three decimal places (the stated accuracy of the LDDS75 sensor is ±(1cm+S*0.3%) where S is the measured distance).

if distanceMeasurement != -1:

	river_height = round(empty_depth - distanceMeasurement, 3)

	if river_height < 0:
		river_height = 0


	print("Invalid distance data received, skipping data post")

In part one, we modified the Javascript payload formatter on The Things Network to return a distance value of -1 should an error occur. Our Python script was modified to check for this value and then not publish a data point should it occur, only emitting a log statement warning that invalid distance data was received.

if river_height < 0:
	river_height = 0

To ensure the depth could never be below zero (in case of measurement inaccuracies in setting the empty depth) a simple `if` statement checks and corrects a negative value.

metricData = {"name": "battery", 


status = writer.write(metricData)

We opted to include battery voltage in the data published to DesignSpark Metrics, so that a general overview of sensor health could be produced. This was easily done, only requiring another variable to be declared that pulled the relevant data point from the “decoded_payload” object. 

Changes to the script were tested by pointing the MQTT client at a local broker and sending sample data captured during initial sensor testing. We also ensured that the script would successfully add labels to the data object by checking DesignSpark Metrics.

Sensor Installation

Before we could install the sensor in its final position, a bracket needed to be designed. As our primary mounting method would be on a pole extending from a wall, the bracket needs to accommodate U-bolts.

Designing Sensor Bracket

The bracket went through two revisions to achieve a design we were happy with, with the electronics enclosure mounted above the U-bolts and then a small tongue to hold the ultrasonic transducer. Initially, the design was a lot larger, but this was reduced to help minimise wind loading on the sensor, which could potentially damage the sensor and mount.

Eight-millimetre slots were used to accommodate a wide range of pole sizes, along with three five-millimetre holes to accept screws to provide mounting flexibility.

The ultrasonic sensor must be unwired from the electronics enclosure before installation, then rewired — a slight annoyance that could be solved by adding a suitably IP-rated connector in place of the cable gland on the box.

prototype mounting bracket

A prototype mounting bracket was laser-cut from a sheet of five-millimetre acrylic and bent into shape to ensure fitment of all the parts. Eventually, this will be replaced by a metal bracket but will suffice for initial installation.

Sensor installed on bracket above river

A suitable bracketry arrangement was bolted to a wall in our desired measurement location, the sensor was installed and then a depth measurement taken from the bottom of the sensor horn to the river bed. 

It is worth noting that the maximum depth to the river bed was checked before installation of the sensor and accompanying brackets to ensure the 7500mm detection distance was not exceeded.

Dashboard Modifications

Dashboard for water level sensor - no data

With the sensor installed, we moved to modifying the dashboard. The existing dash only consisted of a graph of water level and the sensor RSSI.

Dashboard for water level sensor - battery voltage

To display battery voltage a gauge visualisation was utilised. We set up a “red zone” at 3V which changes the colour of the graph and text. To get the red threshold to display on the gauge itself, a minimum and maximum value have to be set — we set these at 0 and 4 respectively.

The existing RSSI figure was kept as-is, only shrunk down to fit in neater with the other indicators.

Graph showing water level

To be able to set a suitable warning threshold on our graph, we consulted the Environment Agency’s “Check for Flooding” service and looked at the nearest monitoring station to our site.

The warning point that EA has set is at 1.5m which they advise “property flooding is possible above this level”. This seems like a suitable place to set our threshold at, although this can easily be modified in future or additional thresholds added. 

For example, near our sensor location is a raised riverbank and a threshold could be added at the point the river would exceed the bank.

Setting the threshold

Setting a threshold is as easy as expanding the “Thresholds” group in the right-hand sidebar, then setting an appropriate value. Multiple thresholds with different colours can be stacked on top of each other. For example, an amber warning could be created and then a red warning at a higher level.

Once thresholds have been configured these can be used to trigger alerts, providing an alerting method has been previously set up.

Dashboard - Complete

With the appropriate visualisations in place, we declared our dashboard complete.

In Conclusion

In this article we’ve taken a look at the modifications performed to our Python script to support a configuration file, plus some other additions to help make the code more robust against invalid sensor readings.

We also looked at the design of the mounting bracket, installed the sensor and took a river depth measurement ready for plugging into the configuration file. Our dashboard was then reconfigured to have thresholds set on the river level, as well as metrics for both RSSI and battery voltage.

Potential future modifications for the dashboard could include building a complete new dashboard that displays the level of a river across multiple sensors that all have a “river_name” label set, as well as setting up alerting rules for when the level crosses thresholds.

Engineer of mechanical and electronic things by day, and a designer of rather amusing, rather terrible electric "vehicles" by night.
DesignSpark Electrical Logolinkedin