After some misadventure and blind alleys I have an efficient and reliable approach to panelising PCBs in Eagle with v-scoring (v-cuts) to aid in board separation. I am using Eagle 9.6.2 (free version) and send PCBs to Seeed Studio.
The starting point is the Sparkfun-Panelizer ULP. Thanks for open-sourcing this, Sparkfun; I couldn’t have “got there” otherwise. Unfortunately, this completely bombed for me due to its failure to cope with space characters in file paths. This is easily fixed once diagnosed. Towards the end of the ULP, change the line which says sprintf(s, “SCRIPT %s;\n”, scriptFile); to say sprintf(s, “SCRIPT ‘%s’;\n”, scriptFile);. See that I added single quotation marks; what a difference two characters can make in progamming!
A second issue with the Sparkfun ULP is that component names get changed because the Eagle PASTE operation prevents name collision by assigning the next available number. The upshot is that each panelised board gets a different set of component names printed. Arghh! This does make sense if you think about these as being unique identifiers, as Eagle sees the whole composite as “one board”. I got around this using a tool already within Eagle.
The final issue is that the Sparkfun ULP was made for their scenario and there are lots of things which are irrelevant and the default settings need changing each time. I also didn’t want to adopt their workflow concerning design rules and CAM processor config. So I streamlined the script for me (I could do more; there is some cruft remaining). Here it is: Panelizer2
The Procedure
1. Prepare a New Layer with Component Names
From the board in Eagle: Tools > Panelize.
This does not actually panelize anything, but generates a script by iterating through all the components. The result of executing the script is that the component names are written into layers 125 (aka “_tNames” for top components) and 126 (aka “_bNames”) for bottom components). Since these are just written as text, they no longer need to follow the unique naming rule; they are not names!
2. Run the Panelizer ULP
If the Sparkfun ULP works for you, go ahead. There is a tutorial on maker.pro. Or maybe you have spaces in your file paths and can use it with the fix noted above. Alternatively, if you are using Seeed Studio, try using my hacked version.
Run a DRC now!
3. Run a Modified CAM Process
The upshot of step 1 is that we have component names in four layers (two for the top and two for the bottom) and the ones which your usual CAM process rules will put into the silk-screen layer are the wrong ones. The change required is quite simple. Start by taking a copy of the “.cam” file and open it in a plain text editor (Notepad will do, but I prefer “Notepad++”).
You are looking for the occurrence of 25 or 26 within a “layers” structure. For example, a typical setup for a top silk-screen would include the placement and names and define this using:
“layers”: [
21,
25
]
Simply change the 25 to 125 (i.e. _tNames) and 26 to 126.
Then just run your modified CAM file in the CAM Processor.
Notes Concerning Seeed Studio
I’ve found them fast (accounting for shipping from China), cheap, and (so far) of good quality. I use the basic PCB service, which has a 100mm x 100mm standard board and panelisation of identical boards attracts no additional fee. There are some rules concerning panelisation which the revised ULP adopts.
Continuous monitoring with the Open Energy Monitor EmonPi hardare us now possible (since Feb 2023), see: https://docs.openenergymonitor.org/emonpi/firmware.html. This gives a more accurate measurement of power than sampling at time intervals.
Unfortunately, the process is incompletely documented there and the forum contains several threads which contain errors, distractions, and diversions alongside the information to make the firmware update work. I certainly had to spend time disentangling things to arrive at what is actually a simple procedure, although the forum was a source of some trepidation. Also: do not be tempted into the compilation process described on that firmware web page.
NB: what follows is based on my EmonPi setup. I believe some details will be different for EmonTX + EmonBase setup.
First, ensure that your system is up to date using Setup > Admin > Update: Full Update. This does not change the firmware, but will change what you see available in the Update Firmware Only section. Once updated, you can select options in the drop-down lists in the Update Firmware Only. My hardware is an emonPi and I opted for the “RFM68 LowPowerLabs” radio format (as recommended). Upon hitting the “Update Firmware” button, various messages will appear in the log.
Once it has completed, you will see that there are no updates in Setup > Inputs or Setup > EmonHub. This is because the baud rate and message format have changed. In Setup > EmonHub, use the “Edit config” button to fix this. Changes to two sections are required.
Under “[interfacers]” will be an entry which seems to have gone under different names at different times and/or different hardware configurations. If you have an emonPi, look for the section which contains “com_baud = 38400”, “pubchannels = ToEmonCMS”, and “baseid = 5”. Mine is identified as [[RFM2Pi]]. If you do not have an emonPi then a different baseid will apply. I only changed the com_baud from 38400 to 115200. Leave the rest alone!
Scroll down and find, under “[nodes]”, a section “[[5]]” (matches the baseid above). Leave the “nodename” entry as it is but modify the lines under “[[[rx]]]” to read:
Note that the scales line can be used to adjust the power values and Vrms which the emonPi records to accomodate systematic errors due to component tolerances; the text quoted above assumes that this calibration has not been done.
I found that simply saving the config and then using the “View log” button showed this worked. Some people state that EmonHub should be restarted, but I did not find this was needed.
Make a final check that things are working by visiting Setup > Inputs and looking at graphs, visualisation, your app etc. At this point, you might note that there are now two new attributes in the input data: E1 and E2. Unfortunately (again, argh!), the meaning of these (vs the pre-existing power1 and power2) is not properly documented and mentions in the forums are often roundly uninformative.
Announcing the first of my planned series of field guides to mining history (and some geology and other historical notes) for selected areas in the Peak District. The main download is a zip file which contains a written field guide, maps, and digital location data for GPS devices.
The first guide describes three itineraries near Castleton.
If you make use this guide, please let me know by adding a comment. You can also post corrections and suggestions (but please note I am unlikely to respond to requests for more information).
Unfortunately, simply setting up the QGIS cache (see this guide if you don’t know how) won’t cause caching of plain WMS responses (no matter what that guide says!), whereas WMTS or XYZ servers do get cached. This can be seen by using the “Debugging/Development Tools” panel to monitor network activity.
The reason for this is that the extent which is in view in QGIS determines the WMS response image coverage, so unless you are at exactly the same position and scale, there will be no “hit” on the cache.
The answer is to add the WMS layer using the “Data Source Manager” (shortcut Ctrl-L), rather than from the “Browser”. This offers you the option to set the tile size (width and height) to use. Choose a layer then “Add”. NB: once added, it doesn’t seem possible to change the tile size. This forces requests to discrete tiles, with 1 request for each. Now, so long as you do not change scale, panning back-and-forth will cause QGIS to pick up the cached tiles even if you don’t pan back to the same place, since the tile boundaries are defined in spatial coordinates.
I used a tile size of 500×500 with some hillshade based on 1m spatial resolution Lidar. Small tile sizes are not a good idea as the network latency will kill the performance on first load (this can be used to show that caching is working, e.g. use a tile size of 100×100 clearly shows the 1st load performance contrasted to the use of cached tiles).
The latest firmware of the Sonoff BasicR3 Wifi Switch now allows the device to be controlled through the DIY mode built into its firmware (v3.6.0) without any re-flashing with Tasmota firmware or fooling about with jumpers. The only obvious limitation for some people is that the “eWeLink” app and DIY access cannot both be used together; the switch is either in DIY mode or app-control mode. This is not an issue for me as I have no requirement for remote control from outside my LAN and don’t intend to use the app at all. In any case it would not be hard to either use a general purpose HTTP “shortcuts” app (e.g. HTTP Shortcuts!) for control from a phone from my LAN, or to write/run my own web app of a Raspberry Pi. I begin to digress… the point of this post is to make some notes for myself in a way which someone else might find useful (and adding some extra helpful comments). Much of what follows can be found on the web, but there are some holes, and I didn’t find the documentation quick and easy in parts.
Getting into DIY Mode
The key is to get the device into what the Sonoff User Manual calls “Compatible Pairing Mode”. The push button is used in two stages, with long (5s) holds first to get the LED signalling “..-” and then a continuous rapid flashing.
The switch is now acting as a WiFi access point with SSID (name) of “ITEAD-{device_id}”, where device_id will be something like “100136da83”. Connect to this network and then visit http://10.10.7.1 to access a web page which allows you to enter your own WiFi SSID and password. After a short while, the Sonoff device will have closed its access point and connected to your local network. It is now in DIY mode, and will stay this way when its power supply is disconnected and reconnected. The LED will signal “..”. You can still use the eWeLink app to add the switch to its device list, but this cancels the DIY mode. Also: once in DIY mode, you cannot access http://10.10.7.1, no matter how many times you force the device into making “ITEAD-{device_id}” available. The exception to this is if you power the switch up so it cannot connect to your local network. In which case, the device will eventually (20s) give up trying to connect and become eligible to be forced into Compatible Pairing Mode again.
Finding the Device on the Network
This is not as simple as it might be; it would have been neater if the http://10.10.7.1 web page reported its network name before disappearing. This is a bit messy.
Option 1 is to look at the client list in your router admin web interface. This is likely to require the “advanced” option. In my case, my modem-router does provide a client list, but rather confusingly the client name which is reported, and the MAC address have no obvious connection with the device_id revealed above. The client name is like “ESP_80DD76”, which is the standard way an ESP8266 (etc) chip reports its name, which takes the last 3 bytes of the MAC address prefixed by “ESP_”. If you can see something like this, you’ve found the IP address and can progress to the “Control the Switch” section, below, although you will have to trust that the default port is used (I see no reason why it would not be).
Option 2 is to use an mDNS browser. The essence of mDNS is a service which links device names to IP addresses and other info. MS Windows doesn’t seem to have an easy way to do this, but if you also run a Raspberry Pi it is not hard. Mac users can use Bonjour, there are Android Apps, and linux users (of course) can do as follows anyway. The service/tool for the Pi is Avahi. This should already be up and running, which can be confirmed using “service avahi-daemon status”. The mDNS browser requires the avahi-utils package, which was not on my Pi, but installed with “sudo apt-get install avahi-utils”. The simple command is not “avahi-browse –all –resolve”, which will reveal that the mDNS name is of the form “eWeLink_{device_id}.local”. The port entry will report which TCP port should be used to connect to the device, which should be the default of 8081. The txt entry reveals the device state if in DIY mode, or a base-64 string of gobbledy-gook if in eWeLink app mode.
Option 2a is an Android App. I tried one called Service Browser, but there are other mDNS browsers
Option 3 is to take advantage of the observation that the mDNS name is of the form “eWeLink_{device_id}.local”. This is fine so long as your method of controlling the switch can work with mDNS names, which Postman cannot (at present).
Caveat: there is no guarantee that your router will assign the same IP address. In my case, I operate a router which allows “address reservation”, so that several IoT devices I have get known IP addresses. Unless your router has this feature, beyond tinkering, you will have to resort to using mDNS-aware software. If writing your own in Python, it looks like the “zeroconf” package is suitable for dealing with mDNS names and not worrying about reliable IP addresses.
Control the Switch
I use Postman for manual testing, and Python for anything automated.
The documentation for interacting with the device is fairly good, and can be found at https://sonoff.tech/sonoff-diy-developer-documentation-basicr3-rfr3-mini-http-api/. This includes info on how to perform an OTA (“over the air”) firmware update, control how the device behaves when its power is restored (allowing you to choose whether it starts up off, on, or in its previous state), and to change the WiFI SSID and password which the device connects to, but I’m content with the info, on, off, and pulse options.
Note that all messages to the device are HTTP (not HTTPS) POST operations, with a JSON body (payload). Although the documentation includes “deviceid” in the JSON, this is completely redundant and can be omitted.
Summary:
Operation
URL
JSON Body
Info
http://{{ip_addr}}:{{port}}/zeroconf/info
{
"data": {}
}
On
http://{{ip_addr}}:{{port}}/zeroconf/switch
{
"data": {
"switch": "on"
}
}
Off
http://{{ip_addr}}:{{port}}/zeroconf/switch
{
"data": {
"switch": "off"
}
}
Pulse
http://{{ip_addr}}:{{port}}/zeroconf/pulse
{
"data": {
"pulse": "on",
"pulseWidth": 2000
}
}
Pulse changes the behaviour of “On” operations; if Pulse is on then an “On” operation will only last for pulseWidth milliseconds, after which the device will turn off again. The maximum duration is 1 hour. The JSON payload for Pulse can omit the “pulseWidth” to enable/disable the feature, but it is not possible to change the pulse duration by only specifying “pulseWidth”.
The HLK-LD015 is a 5GHz radar-based device to detect the presence of human beings, and similar moving objects, produced by Hi-Link. The documentation is a little sparse, especially with regards to the UART configuration procedure/commands (not even the baud rate is given!), but the device is ridiculously inexpensive. I paid £1.40 + tax and shipping from an AliExpress seller and the Hi-Link website price is just $1.65. After some sleuthing, I discovered that the “dist_radar_setting_tool” under Downloads on the LD010 prduct page will work with the LD015, although it isn’t clear whether this covers all the options. To use this, you will need something like a USB-Serial converter (I used by old FT232 breakout). Note that the board requires the use of 5V supply and logic. Remember to connect the sensor TX to the converter RX, and vice versa.
I suspect these are made for automatic light control as there is a photodiode and the output is a single I/O pin, and there is no UART output, unlike some of the higher end sensors.
The photodiode is supposed to suppress sensing if there is over threshold light, but the documention is not clear about whether this is enabled, and mentions enablement in software while not explaining that (I am still uninformed on this topic).
The setting tool is branded as “Airtouch” and the chip on the LD015 bears the Airtouch logo and the part number “5810S 2038DA”. Referring to the Airtouch website, this seems likely to be the AT5810 chip. Unfortunately, there is no useful data there.
Observations Using the “Radar Setting Tool”
The setting tool is a fairly thin wrapper around sending UART style serial messages to the sensor. See below for my decoding of the serial message structure.
The baud rate is 9600.
The pertinent settings for practical device use are:
“Distance”, although this seems not to be a distance measure at all; “threshold” might be better. A value of 0 gives the most sensitive performance, with switching occurring up to around 6m distance. Increasing the value quickly decreases sensitivity and by 6, you have to be right close! Oddly, values of 255 seem fairly sensitive, while the drop-down list has values 0-15. There is no documentation on what the values mean, or the valid range, but a “write” followed by a “read” returns the same value.
“LightOnTime” controls the duration (seconds) the OUT pin remains high after detection.
“Lux THR” appears to have no effect and a write followed by a read DOES NOT return the written value (a 0 is always returned).
It is also possible to enable/disable the radar, and to manually turn the OUT pin on and off. These might be useful for a practical micro-controller + sensor set-up.
Settings appear not to be saved across power-down.
Testing sensitivity was not quite as easy as initially expected, with the triggering distances often seeming to change for the same distance setting. I suspect this is due to the algorithm used to avoid false detection, and it SEEMed to be the case that the sensitivity was a little better just after a bit of fairly close approach (within 2m), lasting a little while and then tailing off. The device will trigger both walking towards and away from the sensor, but it is strictly a movement sensor.
Sniffing the Serial Messages
I used a Picoscope with UART decoding to capture and decode the signals sent by the setting tool and returned by the device.
In the following, the messages are all shown as hexadecimal.
The messages are framed as: {2 bytes of preamble} + {1 byte giving length} + {message} + {1 byte checksum}.
The preamble for messages to the sensor is 555A, while that from the sensor is 55A5.
The length is of the message + the checksum.
The checksum is computed by summing all the bytes [preceeding it] and using the least significant byte (i.e. sum taken modulo 256).
The message is composed of 1 command byte and from 0 to two data bytes. Double-byte data is sent least-significant byte first.
The “light on” (OUT pin high) and off command is 0A, and a single byte of 00 or 01 turns the output off or on, respectively. e.g. 55 5A 03 0A 01 BD turns the output on.
The radar on/off command is D1, and the pattern is the same. e.g. 55 5A 03 D1 00 83 turns the radar off.
The command bytes for “distance”, “light on time”, and “lux thr” vary according to whether a read or write is being undertaken. Distance has one data byte, while the others have two bytes of data.
Read distance command is 03 and write distance command is 02
Read light on time command is 05 and write is 04
Read and write lux thr commands are 07 and 06, respectively
Example: read light on time requires a send of 55 5A 02 05 B6 (note length = 2 this time) and leads to a reply of 55 A5 04 05 2C 01 30 (note length = 4 bytes and that the data is 012C hex = 300 decimal).
General Conclusions
This would make for quite a usable sensor for use with a 5V system such as the “traditional” Arduinos. The range is rather limited and the simple on/off output rather limiting, but for a smaller room or located close to a doorway, this would work well.
This is not a complete description of the background tech; there is plenty of info on the web about websockets, mqtt, and the javascript libraries.
The motivating idea behind this experiment is to be able to have a live-updating dashboard with the minimum set of dependencies of the “install X” kind. Since the EmonPi aleady has a MQTT broker, it provides the basis for feeding data to the dashboard. Websockets allows a “web page” (it can be a file viewed in your web browser) to send and receive data, and update the page, using JavaScript. This is not particularly difficult, but there are several steps which took a couple of evenings to research and put into practice, so here are some notes (for myself in the future, and anyone else who finds it!).
If you are looking for some “homework reading”, a decent place to start is Steve’s Internet Guide.
Make Mosquitto Listen for Websockets Connections
Mosquitto is the MQTT broker on the EmonPi. It is configured to listen for connections which employ the “mqtt:” protocol. It is possible to add a websockets listener (“ws:” protocol), with the conventional port 9001 assignment as follows:
SSH onto the EmonPi and navigate to /etc/mosquitto.
Modify the mosquitto.conf file to add “listener 9001” and “protocol websockets”, see below. I have also added explicit lines for the default mqtt listener on port 1883 in the interest of clarity, although I believe they are not required. You will need to use “sudo”. Alternatively, it should be possible to add a file to the “include_dir”.
Restart mosquitto using “sudo systemctl restart mosquitto.service”
It should now be possible to test two things: that the existing mqtt protocol listener, which is relied on to service data to EmonCMS, and that the websockets listener us “up”. I used “MQTT Explorer”, a free and simple client, which should be set up to not validate a certificate and to have an empty “Basepath” (it defaults to “ws”).
Create the Websockets Dashboard with HTML and JavaScript
My primary aim for this experiment is to be able to co-opt EmonPi to broker air quality data from a home-brew particulate matter, VOC, NOx, CO2 sensor combo, but I’m using the existing emon data to demonstrate the concept, which boils down to “guages” using the Google Charts toolkit, and scrolling line charts using Chart.js.
Here is the code to hack about with, based on snippets from various places, with modifications and updating to a recent. It should just live in a plain text file with a “.html” extension, and can be opened in your web browser. It is not beautiful but demonstrates the concept. There is some logging to the “console”, which is where error messages will also appear. Hit F12 on Firefox or Edge (or Chrome too I think) to find the console.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- This helps with viewing on mobile devices -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Real-Time Charts</title>
<!-- Google charts -->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<!-- Chart.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js" integrity="sha512-ElRFoEQdI5Ht6kZvyzXhYG9NqjtkmlkfYk0wr6wHxU9JEHakS7UJZNeml5ALk+8IKlU6jDgMabC3vkumRokgJA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- The Paho Javascript for MQTT over Websockets -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
<!-- Bootstrap CSS - can be removed but will help with the styling -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/css/bootstrap.min.css" integrity="sha512-SbiR/eusphKoMVVXysTKG/7VseWii+Y3FdHrt0EpKgpToZeemhqHeZeLWLhJutz/2ut2Vw1uQEj2MbRF+TVBUA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
</head>
<body>
<script type="text/javascript">
// load the google charts stuff and only after it is loaded should we start to set up the data acquisition
// otherwise, we find the charts library is being called upon before it exists!
google.charts.load('current', {'packages':['gauge']});
google.charts.setOnLoadCallback(doWhenReady);
function doWhenReady() {
// Create a client instance. NB clientid SHOULD BE DIFFERENT between browser clients; the following should work fine in a home environment.
var clientId = "client-" + Date.now().toString();
client = new Paho.MQTT.Client("192.168.1.1", 9001, clientId);
// set callback handlers
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
// connect the client
client.connect({onSuccess:onConnect, userName: "emonpi", password: "emonpimqtt2016"});
// called when the client connects
function onConnect() {
// Once a connection has been made, make subscriptions
console.log("onConnect");
client.subscribe("emon/emonpi/vrms");
client.subscribe("emon/emonpi/power1");
}
// called when the client loses its connection
function onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost: " + responseObject.errorMessage);
document.getElementById("status").innerText = "Lost connection due to: " + responseObject.errorMessage;
}
}
// called when a message arrives. Note that the full topic name is aka "destinationName"
function onMessageArrived(message) {
document.getElementById("status").innerHTML = "";
console.log("onMessageArrived: " + message.destinationName + " : " + message.payloadString);
if (message.destinationName == "emon/emonpi/vrms") {
// "+" converts string to numeric, which is then rounded by toFixed(), which returns a string!
var vrms = (+message.payloadString).toFixed(2);
// Google chart guage
vrmsGaugeData.setValue(0, 1, +vrms);
vrms_gauge.draw(vrmsGaugeData, vrmsOptions);
// scrolling line chart.js
if (vrmsChartData.data.labels.length === 20) {
vrmsChartData.data.labels.shift();
vrmsChartData.data.datasets[0].data.shift();
}
// we don't have the time in the mqtt message!
var timestamp = new Date().toLocaleTimeString();
vrmsChartData.data.labels.push(timestamp);
vrmsChartData.data.datasets[0].data.push(vrms);
lineChartVrms.update();
} else if (message.destinationName == "emon/emonpi/power1") {
var power1 = message.payloadString;
// Google
power1GaugeData.setValue(0, 1, +power1);
power1_gauge.draw(power1GaugeData, power1Options);
}
}
// ------------- Google Charts -----------
var vrmsGaugeData = new google.visualization.arrayToDataTable([
['Label', 'Value'],
['Vrms', 240]
]);
var power1GaugeData = google.visualization.arrayToDataTable([
['Label', 'Value'],
['Power', 0]
]);
var vrmsOptions = {
min : 200,
max : 260,
minorTicks : 5,
greenFrom : 230,
greenTo : 250,
majorTicks : ['200', '210', '220', '230', '240', '250', '260']
};
var power1Options = {
min : 0,
max : 5000,
minorTicks : 5,
majorTicks : ['0', '1kW', '2kW', '3kW', '4kW', '5kW']
};
var vrms_gauge = new google.visualization.Gauge(document.getElementById('vrms-gauge'));
var power1_gauge = new google.visualization.Gauge(document.getElementById('power1-gauge'));
vrms_gauge.draw(vrmsGaugeData, vrmsOptions);
power1_gauge.draw(power1GaugeData, power1Options);
// chart.js
var chartOptions = {
responsive: true,
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'nearest',
intersect: true
},
scales: {
xAxes: {
display: true,
scaleLabel: {
display: true,
labelString: 'Time'
}
},
yAxes: {
display: true,
scaleLabel: {
display: true,
labelString: 'Value'
}
}
}
};
// this allows us to have common chart options, while varying the scale in each chart
var vrmsChartOptions = structuredClone(chartOptions);
// use sensible range, which will be expanded if the data goes outside "suggested"
vrmsChartOptions.scales.yAxes.suggestedMin = 230;
vrmsChartOptions.scales.yAxes.suggestedMax = 250;
var vrmsChartData = {
type : 'line',
data : {
labels: [],
datasets : [{
label : 'Vrms',
backgroundColor : 'rgba(255, 136, 0, 0.5)',
borderColor : 'rgba(255, 136, 0, 1.0)',
fill: true,
data : []
}]
},
options : vrmsChartOptions
};
const lineChartVrms = new Chart('vrmsChart', vrmsChartData);
}
</script>
<h1>EmonPi Live Feed</h1>
<div class="container">
<div class="row">
<div id="vrms-gauge" style="width: 120px; height: 120px;"></div>
<div id="power1-gauge" style="width: 120px; height: 120px;"></div>
</div>
<div class="row" id="status">loading data...</div>
<div class="col-10">
<div class="card">
<div class="card-body">
<canvas id="vrmsChart"></canvas>
</div>
</div>
</div>
</div>
</body>
</html>
Don’t forget to change the IP address for the MQTT broker!
Serving the Dashboard
While the above HTML + JavaScript works fine as a file on your PC (so long as you have a network connection to acquire all those JavaScript libraries from), it can also be placed on the EmonPi.
I have opted to create a separate area from EmonCMS in the interest of avoiding too much risk of cock-up, future muddle, etc… The files in this separate area will all be “static” in the sense that they are just served to users as they are (no PHP etc). The dashboard page above IS static in this sense, since the JavaScript executes on your PC inside the web browser; all the EmonPi does is send it.
EmonCMS uses the Apache2 web server, so it is easy to make it listen on a different port (EmonCMS uses port 80, and I have chosen port 8001) using the following commands (in a SSH).
Create the place where the HTML file will live
cd /var/www
mkdir static
Check the directory ownership is correct using “ls -la”, which should show “drwxr-xr-x 2 pi pi 4096 Nov 27 23:02 static”.
If necessary “chown pi:pi static”.
Place the HTML + JavaScript file here (I suggest using the scp command, for example “scp dashboard.html pi@192.168.1.1:/var/www/static”).
Change the Apache2 config
There are two things to change, first to make apache2 listen on port 8001, and secondly to tell it to use the files in /var/www/static in connection with port 8001 requests.
cd /etc/apache2
sudo nano ports.conf
Add a single line “Listen 8001”, then save and exit.
For the second step, enter the “sites-enabled” directory. I chose to copy the existing emoncms.conf file, naming the copy “static.conf” and editing it to contain:
<VirtualHost *:8001>
ServerName localhost
ServerAdmin webmaster@localhost
DocumentRoot /var/www/static
# Virtual Host specific error log
ErrorLog /var/log/static/apache2-error.log
<Directory /var/www/static>
Options FollowSymLinks Indexes
AllowOverride All
DirectoryIndex index.html
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
The changes are fairly obvious, but note that I have also added “Indexes” against “Options”. This makes apache give a listing of all the files in “static” when I use the URL “http://192.168.1.1:8001”. If you only have one dashboard, then simply name it “index.html” and it will appear when that URL is used.
Make a directory for logs
Otherwise apache2 will not restart.
cd /var/log
mkdir static
Restart Apache2
To load the new config.
sudo systemctl restart apache2
Make sure EmonCMS is still working, then visit your new site! It should work adequately on a mobile phone, once rotated to landscape format.
I struggled to find information about this using web searches, so here is a condensed “how to”. The scenario I have is using a home-brew ESP8266 based device attached to my solar PV inverter which I want to relay definitive power output to my Open Energy Monitor via MQTT. This seems quite simple in principle, and is simple in practice, but seemingly not well documented.
First thing is to publish the data to an MQTT broker on the emonpi with a topic which starts “emon/{source}/{key}”, replacing {source} with a suitable name for the data source and {key} with the attribute name for the data being sent. In my case, I used “emon/solis/power” as the data source is the output power for my Solis PV inverter. The message payload is simply the data value. This immediately makes the published data appear on the “Inputs” screen of EmonCMS.
Two refinements/possibilities:
Send JSON
Rather than just a single value, send several in the same message. There are two ways to do this:
a) use a topic of form “emon/{source}”
b) use a topic of form “emon/{source}/{key}”
If the message payload sent to the MQTT topic “emon/solis/power” looks like {“ac”: 90, “dc”: 120}, option (a) creates EmonCMS inputs “ac” and “dc” under “solis”, while option (b) creates inputs called “power_ac” and “power_dc”.
Include a timestamp
To do this, simply include an extra field in the JSON called “time”, with a value which is the Unix time. If you are testing, the Unix time needs to be fairly close to the actual time (ignoring summer time) otherwise EmonCMS will indicate “inactive”, but it still captures the data.
Aside: I used the VSMqtt plugin for VSCode as the MQTT client, as I’m using the PlatformIO plugin to develop my ESP8266 code (using the Arduino libraries).
It is well known that, unlike a Raspberry Pi or old-school Arduino, the ESP8266 has some quirks which make using the broken-out pins D0-D8 non-trivial. Google/Bing/etc will easily confirm this. I have, however found lots of long winded, unclear, and conflicting information. This post is my aide memoir, shared in case anyone else might find it useful. The hardware platform is “HW-628 V1.1”, from one of the many cheap Chinese sellers on Alibaba. I’m using the Arduino libraries with PlatformIO (within VSCode), but would expect the same results using the Arduino IDE.
My baseline assumption is that inputs and outputs will normally be used in “active low” mode, with INPUT_PULLUP used as the pinMode for inputs. There are two problems which will easily be found without care: 1) [as outputs] some pins are driven low on boot, usually with quite a few pulses, which will cause an active low relay to stutter; 2) [as inputs] holding some pins low will block the booting.
Tests for the effect of booting (and flashing) on output states were undertaken using a logic analyser (an Open Bench Logic Sniffer with OLS software) with a weak pull-up on the pin under test. I am using the D0-D8 notation as marked on the NodeMCU board. The Arduino library pin numbers differ, but the mapping is widely published.
The following pins were found to be OK to use without any restrictions, as inputs or outputs: D0, D1, D2, D5, D6, D7. The only caveat for my hardware is that D0 is connected to one of the on-board LEDs (active low).
The following CANNOT be used as outputs (with the exception of LED signalling*) since they are affected on boot (and when flashing): D3, D4, D8. D8 was found to hold a low value during flashing and during and slightly after the reset pulse. D3 and D4 showed a burst of pulses. You might get away with using D8 in its “TX2” UART alter ego since the flash/boot glitch wont make a valid data frame. [* – the NodeMCU Devkit board I have does have an LED on D4 = GPIO2 and this can be used for signalling from programmes].
D3, D4, and D8 can be used as inputs but only with some care:
D3 and D4 can be used as active low inputs only AFTER the boot sequence is completed. Push-switch inputs would be safe. Anything which would pull these pins low on boot will block it. D4 is connected to the on-board LED which is marked “COM” (and is active low).
D8 can be used as an active high input only AFTER the boot sequence. Push-switch inputs would be safe. I would use this last as I don’t like circuits to contain a mix of active low and active high inputs; logic should normally be consistent to reduce bug risk.
Since these GPIOs have output functions during boot and flashing, if there is a chance that a push switch input would be operated during those states, the switch should connect to ground or supply via a current limiting resistor.
An aside: when setting pins as outputs with pinMode, it is worth setting the output value to HIGH with digitalWrite() BEFORE changing the mode from its default as an input. Otherwise, assuming active low working again, you will be likely to see an output glitch due to a default low state pertaining when the pinMode() is applied.
Digital elevation models are expressions of the surface of a planet as data. These, and the software to view or process the data, have become freely available over recent years. The software ranges from easily usable online viewers to PC-based tools requiring intermediate levels of IT skill. This all makes for an interesting and useful resource for people interested in mining history, whether as a casual interest or as a more focused amateur historian. This article seeks to provide an introduction to the topic and to illustrate what is possible. A shortened version is being published in the Peak District Mines Historical Society (PDMHS) members’ newsletter. If you find this article interesting, you may be interested in joining and participating in membership activities, but we also have public activities when pandemics allow.
I am avoiding technical detail, and not providing a “how to” guide; there is an abundance of information available on the web which should suffice, although a mining history focused “how to” guide may be created for PDMHS members if there is interest.
A Brief Introduction to Digital Elevation Models (and Lidar)
I am using “digital elevation model” (hereafter: DEM) as a generic term as well as two more specific terms: “digital terrain models” (DTM) and “digital surface models” (DSM). A DSMs give the surface with things on it – trees, buildings, walls, etc – whereas DTMs, in UK parlance, show the ground level (in the USA, a DTM is augmented with information about surface features such as rivers). Someone with an interest in forest canopies would normally be interested in a DSM, whereas we mining history people are much more likely to be interested in what is beneath the vegetation, hence in DTMs.
DEMs are usually created using the data from flying aircraft, with a considerable amount of sophisticated data processing to get from the raw data to usable DEM data. Radar has been used, although Lidar (an acronym for “light detection and ranging”) is the most likely source of DEMs which we will use. Lidar and radar use similar techniques: bouncing light or radio waves off objects and measuring the pulses which arrive at the sensor to “see” objects. Lidar equipment is extremely expensive. A cheaper alternative is to use photogrammetry, which entails taking numerous overlapping photographs from an aircraft, and using software to work out what the surface elevations must be to cause the observed changes between adjacent photographs. Photogrammetry can be achieved from professional grade unmanned aerial vehicles (aka “drones”). See, for example the 3D model of Magpie Mine, with online viewer, created by Peak Drone Imaging. Some specialist providers also fly Lidar drones.
What are DEMs Useful For?
Before listing what we can use DEMs for, it is fair to mention that even the higher-resolution images are no substitute for an experienced archaeologist on the ground. Such a person will see things, feel them under their feet, draw inferences from changes in vegetation, and observe traces of mineral, etc. Unfortunately such people are in short supply. Aerial photographs may also be more revealing than images created from DTMs, especially high resolution black and white images, but also when the character of vegetation is shown, although the best resolution of freely available aerial photographs is generally not good enough to compete with the latest Lidar-based DTMs.
Fieldwork (preparation) from your desk chair: before venturing out, 2m or higher resolution DTMs, can be a useful way of “seeing” what is there. This kind of prospecting can be really useful when the terrain is difficult. Alternatively, there may be places of interest on private property with no access.
Seeing though undergrowth is a major advantage of DTMs, making visible what would otherwise remain fairly well hidden.
Validating grid references and sketch maps; there are abundant imprecise locations from before the days of GPS, when sometimes an approximate 100m grid reference was the best that could be achieved. This also applies to British Geological Survey maps, which are often based on very old surveys and locate veins quite inaccurately.
Visualising the 3D character of the landscape is one nice application, and one which works even with 50m spatial resolution. This can be either quite subtle shading on a flat map, to given an impression of shadow, or a more virtual-reality-like presentation. Contour lines are really in this category too.
What Data is Available?
There are several good sources of freely-downloadable data (but with some licence terms):
Ordnance SurveyOS Terrain 50 is a DTM with a spatial resolution of 50m. It is not available as GeoTiff (see below), but in alternative grid-based formats and as contours.
NASA mapped almost the entire land surface of planet Earth in the Shuttle Radar Tomography Mission (SRTM) at 1 arc second spatial resolution, which is approximately 30m. You can browse maps online and download the data using the USGS Earth Explorer, and there is a SRTM downloader plugin for GQIS (see below).
The Environment Agency has surveyed many parts of England over the last 20 years, at a range of spatial resolutions between 2m and 0.25m. This work was particularly driven by a desire to model and predict flooding, so the smallest resolution surveys closely follow draining networks near large urban/suburban areas such as Sheffield. Since 2016 they have been undertaking the National Lidar Programme, which will complete its survey of the whole of England at 1m spatial resolution in 2021/2 (Lidar surveys are taken during the winter months while vegatation is largely dormant). The vertical accuracy is claimed to be below 0.15m and both DTM and DSM datasets are available. This is all available under an Open Government Licence. This is outstanding!
While the OS and NASA datasets are useful for visualising the physical geography, a spatial resolution of 1m or less is essential for picking up mining historical features other than large scale open-casting. Consequently, most of the rest of this article will look at the Environment Agency data, in particular the 1m National Lidar Programme data as it is available in nice square blocks, whereas the 0.5m and 0.25m surveys are quite limited in coverage and tightly follow streams and rivers.
Visualising DEMs
The simplest way of representing a DEM is as a square grid of altitudes, and this is the most likely way in which data is provided. The grid spacing, known as the spatial resolution, typically varies from around 100m down to 0.5m, or sometimes as small as 0.25m. A grid of elevations is also quite easy for computer programmes to process. The most favoured current format is GeoTiff, which is an extension of the Tag Image File Format (Tiff) to include data about the geospatial location which the data relates to. The simplest way of rendering a DEM for viewing is to have the elevation values correspond with different brightness values, from black (lowest elevation) to white (highest elevation), so GeoTiff is a neat solution. A bit of desk research will turn up lots of alternative file formats. Fortunately, most geospatial software can handle several of them.
Unfortunately, just opening one of these GeoTiff files on your PC will usually just give you what looks like a plain white image, but with the right software we can get something looking a little surreal or medical.
(All images are linked to a larger version)
We can do a bit better by creating a pseudo-colour image, where the elevations are mapped to a colour. The colour scheme can be semi-naturalistic like a traditional atlas, a simple grading from one colour to another via white, a rainbow or a garish scheme designed to highlight a particular elevation range.
One really effective technique is to generate “hill shading” by simulating light and shadow from an imaginary sun. If you visit the OS Terrain 50 web page, there is an interactive map where you can adjust the azimuth to alter the hill shading. Hillshade works well when overlaying a base map or aerial photography.
Grey-scale representation of a DTM. Monsal Head is at the top and Lathkill Dale at the bottom
Wye to Lathkill Pseudocolour DTM
Wye to Lathkill Hillshade Overlaying OpenStreetMap
Hill shading turns out to be quite effective at picking out even quite slight surface features with the higher resolution DEM data. When hill shading is used to augment a map, steep slopes can appear very dark, so people often use multi-directional hill shading, with three or more imaginary suns all shining at the same time. This sounds very unrealistic, but the images are pleasantly usable.
Some other ways of representing a DEM are as contours, which are not so easy to work with “as data” but super if you are map-making, and as the surfaces of 3D objects, which would be useful for creating photo-realistic images and “virtual reality” environments. This article will not look at 3D visualisations.
Visualising DEMs on the Web
My top recommendation is to use the Environment Agency Geomatics Team’s Lidar Composite viewer, but there are other services to explore via their Open Data Products page. This is a very interesting resource which includes clear information about Lidar, the accuracy of the data, and the way raw data is turned into a DTM or DSM. At the time of writing, the latest data which is included is the “2020 Composite”, but this will change as the National Lidar Programme progresses (link to catalogue of all completed and planned surveys).
Environment Agency Lidar Composite Viewer, with key controls annotated: Map layer, Base map, Spatial Resolution, and Placename Search.
At this point, I urge you to go and explore! The most important controls are the spatial resolution selection and the map layer. Once the Map layer control is opened, click on the little eye symbols to switch different effects on and off, or on the three dots to change the opacity of the layer. Experiment with hill shading, pseudo-colour, overlaying hill shade onto base maps (you will have to decrease the opacity of the hill shade), and looking at slope and aspect. The area shown in the image, above, is Grin Low, above Poole’s Cavern in Buxton, providing a dramatic illustration of the legacy of lime burning which is only partially evident as one walks around the woodland. The Thatch Marsh and Burbage Colliery area, not far to the West of Grin Low shows the causeways and pit locations quite clearly, as well as the pack horse hollow-way leading from the pits towards the lime kilns where much of the coal was consumed. The area and its history is thoroughly described in “Coal Mining near Buxton: Thatch Marsh, Orchard Common and Goyt’s Moss” by John Barnatt, in Mining History 19-2 (not currently online) which has several maps drawn from proper fieldwork, which make for an instructive comparison with Lidar armchair exploration.
An alternative viewer is the Environment Agency Survey Open Data Index Catalogue. This gives access to more datasets, including DSMs (although only in the 2017 Composite, not the 2019 version), but I don’t find it quite as pleasant to use.
For the More Adventurous – QGIS
QGIS (formerly Quantum GIS) is open source software with professional-level geographical information system (GIS) capabilities. Software with such power comes with challenges, and I would only recommend people who are confident IT users even taking a look. I hope those who are confident will be able to repeat the following examples, and are maybe motivated to learn more. There are a lot of resources explaining how to use QGIS on the web; just be aware that the latest version is QGIS 3, and many resources refer to the previous release. If you do download QGIS, choose the latest “long term release” version. I am going to use some more technical terms in this section, and will not explain them all, expecting readers can do their own web searches.
Before doing anything else, I recommend adding OpenStreetMap as a base map layer, if only so you know where you are! This is achieved by adding an “XYZ Tiles layer” (see this guide). The URL to add is “http://a.tile.openstreetmap.org/{z}/{x}/{y}.png” (without the quote marks, but with those curley brackets). There are lots of XYZ servers available – try a google search – but the Bing Maps satellite imagery service is a good companion to the OpenStreetMap: “http://ecn.t3.tiles.virtualearth.net/tiles/a{q}.jpeg?g=1“.
The starting point for all which follows is QGIS with a map in view and two panels – headed “Browser” and “Layers” – on the left hand side. If these panels are not visible, use menu View > Panels.
Getting Data for QGIS
I am only going to consider using Environment Agency data, for which there are two ways of using the DEMs in QGIS: downloading GeoTiff files, and accessing a WMS map tile server. Once set up, the WMS approach is quicker and easier but you get less control over how the DEM is rendered. Consequently, I suggest using the Defra Survey Data Download service. The workflow for using this site is generally: draw your area of interest on the map, click “Get Available Tiles”, then choose which dataset you want. Ignore the shapefile upload option; the icons for drawing your area are just beneath the upload grey box. This catalogue covers all the published datasets, with downloads being 5km x 5km squares. At 1m spatial resolution, that amounts to 5000 x 5000 = 25 million data points, so even these tiles are quite large files.
Once downloaded, you can just drag-and-drop the file into QGIS. If you do this, you will see that QGIS chooses a grey scale for each based on black = the lowest elevation and white = the highest elevation. This makes things look blocky if you load several 5km square tiles since each has a different min/max elevation. This can be fixed quite easily, but doesn’t matter for some uses (see below). I generally work with the OpenStreetMap and satellite imagery as the bottom layers and put DEM layers above (and the notes below assume this).
Playing with Hill Shading, Pseudo Colouring, and Overlaying Maps
For this section, we will work within a single 5km tile. I chose the SK16NE DTM from the 1m National Lidar Programme (aka DTM_1565) to work through what follows, which has Magpie Mine almost at the centre. The Magpie Mine is easily accessible and has been thoroughly surveyed and described, so it is a good place to see what Lidar data can (and cannot) reveal. Choose a place you know!
All of the following is achieved by double-clicking on the DTM layer in the “Layers” panel then choosing the “Symbology” option. When you open this up, it will not look exactly as below, because the default “Render type” is the rather boring “Singleband gray”. Change this to “Hillshade”. Also change “Zoomed in” to “Cubic”; if you don’t do this, the appearance when zoomed in looks a little bit like linen (this is an easy thing to forget). You must either “Apply” the changes or “OK” to close the properties window for the settings to take effect.
To begin with, “Multidirectional” will be un-ticked. In that state you can play around with the altitude and azimuth (compass direction) of the imaginary sun for which the shading is created. It will quickly become clear that different features are revealed for different angles, but that if you are interested in showing several features, there isn’t a good answer. This is where multidirectional hillshade comes in; it combines the effect of several simulated suns (3 for QGIS). Since the hillshade algorithm uses gradient and aspect to determine the shade of grey, it will work if you have several 5km tiles loaded.
To combine the hillshade with either the OpenStreetMap, change the “Blending mode” from “Normal” to “Multiply”. This merges the DTM and the map images so that the map appears to be draped over a 3D surface. I find this the best way to interpret features in relation to landscape features. A similar effect can be obtained with the Bing aerial photography layer, although if there are buildings and trees in the area of interest, it may be better to use a DSM instead. Combining the DTM and aerial photographs can often be very revealing since the photographs can show variations in vegetation or surface material in topographically indifferent ground, whereas the DTM reveals aspects which are indifferent in ground cover.
An alternative to hillshading is to set the “Render type” to “Singleband pseudocolor” (the TIFF images are “single band” because there is a single value for each pixel, which corresponds with the height, whereas colour photographs usually have three bands with separate red, green, and blue values per pixel). The magic button is “Classify“! There are lots of colour schemes to experiment with – change “Color ramp” – but most are horrible.
The Min/Max values can be useful to limit the colour range to the elevation range in a particular area of interest. A 10m range can nicely bring out spoil heaps and hollows. These values can also be used to force several 5km tiles to have the same colour scale, so to appear seamless.
Most of the other options will not be useful for general experimentation, but the brightness/contrast/saturation slides can be useful for preparing images for print.
Some tricks:
Right-click on the DTM layer and choose “Duplicate Layer”. Now set one of the two layers to be hillshade and one to be pseudocolour. This can really bring out relief.
Make your own multidirectional hillshade by duplicating the DTM layer 2 or more times and setting the azimuth separately on each layer. This can help to reveal features which are not showing nicely with the QGIS fixed azimuth angles.
Here is a composite image of Magpie Mine using the DTM mentioned above (so the buildings have been magically transported away). The spoil heap near the 1869 engine house and the reservoir are easily seen, as are the main veins outside the heavily re-modelled central area, but can you make out the four gin circles and the crushing circle? Gin/crushing circles are much easier to see on the ground than from a DTM when the terrain is quite flat. How about the covered flue from the square chimney to the long engine house, the slime ponds and dressing area, and the straight tramroad from Dirty Redsoil into the centre of the main site? In this case, adding the pseudocolour actually makes it harder to make out the more subtle features where the changes in slope and aspect are more significant than changes in elevation.
Composite of Magpie Mine comprising OpenStreetMap base map overlain by a multidirectional hillshade and a single band pseudocolor constrained to the altitude range of 315m to 325m. (Click to open larger image)
QGIS can also generate contour lines from DTMs; change the “Render type” again. The contour options are fairly self-explanatory and work well down to as low as 1m interval with the National Lidar Programme data. The “index contour” can be used to make every 5th, 10th, etc contour be styled differently. The “input downscaling” setting default of 4 usually works well; this smooths out the contour lines to make them less jittery as the limits of the spatial resolution (and to a lesser degree the vertical accuracy) come into play. Larger values make for smoother contours.
I find there isn’t a single magic setting which works for all cases; different sites and different exploratory questions indicate different settings, but there is enough variety and power in what QGIS provides that there is usually a satisfactory approach, within the limits of the data.
Creating Terrain Profiles
Since the DEM encodes height on a grid, it is possible to construct profiles. The standard QGIS plugin “Profile tool” is the easiest way of doing this and there are a few more advanced plugins available. The tool is quite self-explanatory, so here is an example without a “how to”. The area around Wass’ Level (also known as Moorhigh New Level) on Maury Rake is in an accessible grassland location just South of the old Midland Railway bed through Millers Dale. A visit to the site allows the spoil heaps and settling ponds to be easily seen along with the presumed collapsed level at around 245m elevation, at the edge of the woodland. The Lidar image reveals some interesting features up-slope which cannot be seen from the open area but the elevation helpfully reveals enough to motivate a bit of struggling through the undergrowth. Could the feature at 255m be evidence for the excavation from the surface of a winze down from Wass’ Level to the now-lost Upper Level (destroyed when the railway was build just above it)? The paper entitled “The Maury and Burfoot Mines, Taddington and Brushfield, Derbyshire” by John Barnatt & Chris Heathcote in Mining History 15-3 describes the site and its history.
Aside: the vertical line near the right is just an artefact of having used two DEM tiles which have been separately hill-shaded; the hillshade algorithm can’t work out what the slope/aspect is at the edge of a tile. This can be avoided by combining the tiles into a single layer in QGIS, which should be done for publication but I’ve left it in here as there is educational value in showing and explaining the issue.
The Area Around Wass’ Level (SK149730) on Maury Rake, Millers Dale. (Click for larger image)
Some Oddments…
Once you have got to grips with the basics, there are numerous plugins and processing algorithms to experiment with. The Visibility Analysis plugin uses the DTM to determine those parts of a map which show what can and cannot be seen from a vantage point. There are algorithms which will calculate surface slope and aspect, or roughness (Processing Toolbox > Raster Terrain Analysis); these values can then me mapped to colours using the same technique as above. Mapping slope to pseudocolour does a better job of revealing gin circles than hillshade when the terrain is quite level.
Using a WMS (Web Map Service) is generally more convenient than adding 5km square Tiff tiles in QGIS but the available services provide a combined hillshaded and pseudocoloured image and only a DTM is currently available (things might change). The separate services for 2m and 1m DTM (etc) are:
The following URL may be added as a WMS to discover the extent of coverage without the DEM: https://environment.data.gov.uk/spatialdata/survey-index-files/wms . NB this will not work in a web browser, but the same source can be accessed using an online service via web browser.
The elevation data is available (as opposed to a hillshaded and pseudocoloured image), but only using the ArcGIS Image Server protocol. In QGIS this requires the use of the “ArcGIS ImageServer Connector” plugin, which works but the image redraws rather slowly. The URL to use is of the form: https://environment.data.gov.uk/image/rest/services/SURVEY/LIDAR_Composite_2m_DTM_2020_Elevation/ImageServer.
We can hope that, once the National Lidar Programme has concluded, the WMS provision will improve, along with the rather confusing array of data download, WMS, and online map viewers. Quite a few things changed during the drafting of this article.