15. Interface and Application Programming
Assignments
Here you can find a recording of the lecture from the 30th of April.
This week's assignments and learning outcomes, see here:
Group assignment:
- Compare as many tool options as possible.
- Document your work on the group work page and reflect on your individual page what you learned
You can find the documentation for our group assignments here.
Individual assignment:
- Write an application for the embedded board that you made that interfaces a user with an input and/or output device(s)
Questions to be answered/from Nueval:
Have you answered these questions?
- Linked to the group assignment page.
- Documented your process.
- Explained the UI that you made and how you did it.
- Explained how your application communicates with your embedded microcontroller board.
- Explained any problems you encountered and how you fixed them.
- Included original source code (or a screenshot of the app code if that's not possible).
- Included a ‘hero shot’ of your application running & communicating with your board.
Link to group page
Here you can find the documentation of our group assignment.
Hero shots
Turning the globe
This video shows how I used templates in D3, mixed different codes and added information on longitude and latitude points. The globe can be rotated in all directions by grabbing it with the mouse and moving it. The red dots are the latitude and longitude points that I added to a .csv file to display the travels of an imaginary Humpback whale. The website can be found here. P.S. Note that in this project I was not communicating with a microcontroller.
Turning the globe
Summary
This week I looked into interface and application programming. I compared different tools...
Work process detail
Group assignment
Link to group page
You can find the documentation for our group assignments here.
Individual assignment
Copyright
Note that I used three templates when working with D3, as described her below, and all of them are made by Mike Bostock. You can read about the copyright here. The copyright permission is also signed by Ricky Reusser, but his name is not mentioned in the templates. I added the full text from the copyright permission with files at the bottom of this page.
D3 basics
D3
I chose to look at D3 and it's possibilities. D3 is a free, open source online editor that has broad variety of templates for visualizing data. It is low-level JavaScript based library and it offers multiple ways of displaying all kinds of information. I like the possibilities of D3 so much and could have spent many days checking out different templates if I had the time, but this is all new to me and everything I did took me a very long time. It is highly likely that I will try out more of D3's tools.
Animation and interaction
You can use animation like D3-timer, D3-ease and D3-transition.
You can also use different types of interaction like D3-brush, D3-dispatch, D3-zoom and D3-drag.
Observable inputs
You can use observable inputs like D3-timer, D3-ease and D3-transition
Many examples
You can check out many examples in the D3 gallery.
Fork an example
If you want to work with an example, you can Fork the example into your project folder. You choose if you want your project to be public or unlisted, for a team or just for yourself.
Beginning to work
You need to sign up before working with 3D. You create workspaces and in them you create notebooks. Here below you can see an overview of a workspace and the notebooks created in it. To create a new notebook you click on the plus in the top right corner. From there you can choose to work in a blank notebook or use a template.
Templates
There is a wide variety of templates to work with.
Help panel and database
There are many ways to find information when you begin working with D3. When you are working in a notebook you can click on a question mark to open help panel. You can also search for more information on the website, f.ex. in this summary of data.
My projects
An example forked into projects folder
What is a versor dragging?
The template, that I will tell you about here below, is based on versor dragging. It means that you can rotate the globe with a mouse. This method was introduced by Jason Davies and Mike Bostock. You can read more about it here.
Template showing World airports
I found a template showing World airports Voronoi by Mike Bostock. You can see another view of it here. I forked this example into my projects folder and began editing it.
Here below you can see the original code. I split the code into codeblocks, just like it is setup on the original. I wrote what I deleted (with hashtags). Below the code you can see what changed in the image.
chart = {
const context = DOM.context2d(width, height);
const path = d3.geoPath(mutable projection, context).pointRadius(1.5);
function render() {
context.clearRect(0, 0, width, height);
context.beginPath();
path(graticule);
context.lineWidth = 0.5;
context.strokeStyle = "#aaa";
context.stroke();
context.beginPath(); # I deleted this
path(mesh); # I deleted this
context.lineWidth = 0.5; # I deleted this
context.strokeStyle = "#000"; # I deleted this
context.stroke(); # I deleted this
context.beginPath();
path(sphere);
context.lineWidth = 1.5;
context.strokeStyle = "#000";
context.stroke();
context.beginPath();
path({type: "MultiPoint", coordinates: points});
context.fillStyle = "#f00";
context.fill();
}
function dragged() {
mutable projection = mutable projection;
render();
}
return d3.select(context.canvas)
.call(drag(mutable projection).on("drag.render", dragged))
.call(render)
.node();
}
mutable projection = d3.geoOrthographic()
.fitExtent([[1, 1], [width - 1, height - 1]], sphere)
.rotate([0, -30])
points = (await FileAttachment("airports.csv").csv({typed: true})).map(({longitude, latitude}) => [longitude, latitude])
After deleting mesh
I wanted to get rid of the lines that connected the dots, so I deleted the part about mesh (see where I marked it in the code here above). Se here below in these two images how the map changed
Finding out how to delete airports and add location of whale
I wondered how I could change the information on all these red dots, which marked airports, and change them into dots that showed the location of the Humpback whale. I noticed that there was a symbol for file attachments. There I found a .csv file with the longitude and latitude for all these airports. I do not know how to create a .csv file, so I decided to download the file, make a copy, delete all information and fill in new ones.
Latitude and longitude for whale
Now I had one problem. The information here I got from Marine and freshwater research institute did not include gps or latitude and longitude. It only showed a paths that the whales had travelled.
Travels of Humpback whales, Marine and freshwater research institute, 2025.
Chosen path
I decided to use the path that the yellow arrow is pointing at. The path is short and that is most likely because of the battery running out or the gps tracking tool falling off, but according to the information here the migration routes lead south. Some humpback whales travel to West Africa and some of them travel all the way to the Caribbean. The path I chose looks as if the whale is travelling in the direction to the Caribbean, maybe to the Dominican Republic. I choose to imagine that the whale had travelled all this way, so my project will not be based on exact information. It will be a story of a humpback whale that might have travelled from the East of Iceland to the Dominic Republic.
Comparing path to Google maps
I compared the path to Google maps and clicked on the location on Google maps to see the longitude and latitude.
Writing longitude and latitude in .csv file
When I opened the .csv file that I downloaded, it opened up as an excel file. I saw an notification warning that maybe not all I used a copy of the .csv file, deleted the airport information and wrote my longitude and latitude for the points where the whale was located.
Changing .csv files
I deleted the .csv file with the airport information and added the file with the longitude and latitude points for the whale. The dots appeared on the map but I noticed that the dots were not in the right places. I had the latitude and the longitude mixed, so I changed this in the .csv file. Then the dots appeared in the right places. Now I only needed to find out how to add countries to the globe. You can see the globe here.
Using information from a template to add to project
Template showing World map
I found another template with the World map by Mike Bostock. I wanted to use the countries showed on it and add it to the map with the airports, because there were no countries on it.
Mixed code for two maps that did not unite
Mix of two codes
Here is a link to a map that I tried to merge together, but it did not work.
I mixed codes from this template and from this template showing World airports Voronoi by Mike Bostock. Here you can see the mixture:
chart = {
const context = DOM.context2d(width, height);
const path = d3.geoPath(mutable projection, context).pointRadius(1.5);
function render() {
context.clearRect(0, 0, width, height);
context.beginPath();
path(graticule);
context.lineWidth = 0.5;
context.strokeStyle = "#aaa";
context.stroke();
context.beginPath(); # I deleted this
path(mesh); # I deleted this
context.lineWidth = 0.5; # I deleted this
context.strokeStyle = "#000"; # I deleted this
context.stroke(); # I deleted this
context.beginPath();
path(sphere);
context.lineWidth = 1.5;
context.strokeStyle = "#000";
context.stroke();
context.beginPath();
path({type: "MultiPoint", coordinates: points});
context.fillStyle = "#f00";
context.fill();
}
function dragged() {
mutable projection = mutable projection;
render();
}
return d3.select(context.canvas)
.call(drag(mutable projection).on("drag.render", dragged))
.call(render)
.node();
}
map = {
const context = DOM.context2d(width, height);
const path = d3.geoPath(projection, context);
context.save();
context.beginPath(), path(outline), context.clip(), context.fillStyle = "#fff", context.fillRect(0, 0, width, height);
context.beginPath(), path(graticule), context.strokeStyle = "#ccc", context.stroke();
context.beginPath(), path(land), context.fillStyle = "#000", context.fill();
context.restore();
context.beginPath(), path(outline), context.strokeStyle = "#000", context.stroke();
return context.canvas;
path({type: "MultiPoint", coordinates: points});
context.fillStyle = "#f00";
context.fill();
context.beginPath();
path({type: "MultiPoint", coordinates: points});
context.fillStyle = "#f00";
context.fill();
}
mutable projection = d3.geoOrthographic()
.fitExtent([[1, 1], [width - 1, height - 1]], sphere)
.rotate([0, -30])
points = (await FileAttachment("Whales@1.csv").csv({typed: true})).map(({longitude, latitude}) => [longitude, latitude])
Rotating world map with Humpback whale's travels marked on it
Template showing rotating World map
Since my previous experiments did not work I decided to keep on looking. I found one more example by Mike Bostock that showed Earth and by grabbing it, you can rotate it. I wanted to see if I could add points that show the travels of a Humpback whale. I did not change much this time. I added the .csv file with the longitude and langitude of points that show examples of the whale's travels.
You can see the project with the added longitude and latitude points here.
Code with changes
Code with added longitude and latitude points
Here you can see the original code from here and how I added points with certain longitude and latitude that is an attached .csv file. Look at the hashtags to see what I added.
chart = {
const context = DOM.context2d(width, height);
const path = d3.geoPath(projection, context);
function render(land) {
context.clearRect(0, 0, width, height);
context.beginPath(), path(sphere), context.fillStyle = "#fff", context.fill();
context.beginPath(), path(land), context.fillStyle = "#000", context.fill();
context.beginPath(), path(sphere), context.stroke();
# I added the next four lines from [template](https://observablehq.com/@d3/world-airports-voronoi) showing World airports Voronoi by Mike Bostock
context.beginPath();
path({type: "MultiPoint", coordinates: points});
context.fillStyle = "#f00";
context.fill();
}
return d3.select(context.canvas)
.call(drag(projection)
.on("drag.render", () => render(land110))
.on("end.render", () => render(land50)))
.call(() => render(land50))
.node();
}
function drag(projection) {
let v0, q0, r0, a0, l;
function pointer(event, that) {
const t = d3.pointers(event, that);
if (t.length !== l) {
l = t.length;
if (l > 1) a0 = Math.atan2(t[1][1] - t[0][1], t[1][0] - t[0][0]);
dragstarted.apply(that, [event, that]);
}
// For multitouch, average positions and compute rotation.
if (l > 1) {
const x = d3.mean(t, p => p[0]);
const y = d3.mean(t, p => p[1]);
const a = Math.atan2(t[1][1] - t[0][1], t[1][0] - t[0][0]);
return [x, y, a];
}
return t[0];
}
function dragstarted({x, y}) {
v0 = versor.cartesian(projection.invert([x, y]));
q0 = versor(r0 = projection.rotate());
}
function dragged(event) {
const v1 = versor.cartesian(projection.rotate(r0).invert([event.x, event.y]));
const delta = versor.delta(v0, v1);
let q1 = versor.multiply(q0, delta);
// For multitouch, compose with a rotation around the axis.
const p = pointer(event, this);
if (p[2]) {
const d = (p[2] - a0) / 2;
const s = -Math.sin(d);
const c = Math.sign(Math.cos(d));
q1 = versor.multiply([Math.sqrt(1 - s * s), 0, 0, c * s], q1);
}
projection.rotate(versor.rotation(q1));
// In vicinity of the antipode (unstable) of q0, restart.
if (delta[0] < 0.7) dragstarted.apply(this, [event, this]);
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged);
}
height = {
const [[x0, y0], [x1, y1]] = d3.geoPath(projection.fitWidth(width, sphere)).bounds(sphere);
const dy = Math.ceil(y1 - y0), l = Math.min(Math.ceil(x1 - x0), dy);
projection.scale(projection.scale() * (l - 1) / l).precision(0.2);
return dy;
}
land50 = FileAttachment("land-50m.json").json().then(world => topojson.feature(world, world.objects.land))
land110 = FileAttachment("land-110m.json").json().then(world => topojson.feature(world, world.objects.land))
points = (await FileAttachment("Whales.csv").csv({typed: true})).map(({longitude, latitude}) => [longitude, latitude]) # I added this from [template](https://observablehq.com/@d3/world-airports-voronoi) showing World airports Voronoi by Mike Bostock and swithced the original .csv file with an .csv file with my latitude and longitude points for the whale
Video showing how the globe can be turned around
Turning the globe
This video shows how the globe can be rotated in all directions by grabbing it with the mouse and moving it. The red dots are the latitude and longitude points that I added to a .csv file to display the travels of an imaginary Humpback whale. The website can be found here.
Turning the globe
Communicating with my embedded microcontroller
Can you interact with D3js.org?
I really wanted to find a way for my microcontroller to interact with the globe in the video above. I checked out different things and I found out that when you add a codeblock in D3, it is possible to choose a command that fetches information from HTTPS. I wondered if I could make the Raspberry Pi Pico W send information to a website that this command could fetch!
Trying to use RPP to serve a website
Blynk basics
I found these directions and tried to follow them, hoping that I could use the website to connect the RPP and D3. It was easy to create a HTML document and open it online, but when it came to letting the RPP serve the website it did not work. I copied the codes from the website, so it was exactly the same, but I got error notifications on some of the commands. I decided to quit and look for something else.
Blynk
Blynk basics
A few months ago I heard someone mention Blynk and I decided to create an account, but I never got to try it out. So, when I saw that Blynk was on the list of user interfaces, I decided to check it out. According to this site, Blynk is an IoT cloud platform and it is based on low-code. It works as an online editor and also as an app. It is possible to control different IoT devices, f.ex. devices that connect through satellite. In Blynk, you can also gather data and view it on a dashboard. Here you can see an overview of the developer zone in Blynk:
Dashboard
Here you can see the dashboard.
New template
I created a new template.
Datastream
I found information on how to set up a datastream here. I followed them and set up two types of datastream. One of them was virtual pin datastream and it can be used to send random values from the device. The other one was an enumerable datastream and it can be used to send a value from mobile or web dashboards to a certain device.
Controlling widgets
I found information on how to control widgets here.
Installing hardware
When I planned on connecting my Raspberry Pi Pico W to Blynk, I ran into endless problems. I found a Quickstart template on my dashboard that led me through a process step by step. Here you can see the directions on how to use the quickstart.. I chose Raspberry Pi Pico as my microcontroller. Then I chose Wifi.
Choosing IDE
In the next step I chose Other, because I planned on using Thonny.
Installing library in Thonny
I installed a Blynk library for Thonny from here.
Installing library in Arduino IDE
I also decided to install a Blynk library in Arduino IDE, just to be ready for anything. It was done by clicking on Tools, Manage libraries, search for Blynk and install all. When I opened the Blynk examples, it seemed to have many possibilities.
Failure
In the next step I followed the directions when I added information, such as the name of my wifi and the wifi key. When everything was ready the directions told me to copy the code and paste it into my programmer. I pasted it into Thonny and waited for a connection, but it did not happen.
Info
I unplugged the microcontroller and plugged it in again. Then I tried to find directions on how to use Arduino IDE to connect a microcontroller. I found information here by Elen . She seems to have had problems just like me, but this code from Blynk worked for her:
#define BLYNK_FIRMWARE_VERSION "0.1.0"
#define BLYNK_PRINT Serial
// libraries and defines the WiFi credentials and Blynk authorization token
#define BLYNK_TEMPLATE_ID "TMPL62FAZkgPN"
#define BLYNK_TEMPLATE_NAME "Fading light ESP12F"
#define BLYNK_AUTH_TOKEN "Syz7_B8V-5aH91ZoqOg00eoDAbIrP383"
#include <ESP8266WiFi.h> // wifi library for esp8266-esp12
#include <BlynkSimpleEsp8266.h>
char ssid[] = "****";
char pass[] = "****";
// Blynk widgets and variables used in the sketch.
WidgetLED led1(V0); // Virtual pin in Blynk
const int ledPin = 2; // gpio2 pin on the board
BlynkTimer timer; //variable is used to create a timer that runs every 300 milliseconds.
//function is called when the microcontroller connects to the Blynk server.
BLYNK_CONNECTED() {
Blynk.syncVirtual(V0); // synchronizes the state of V0 Blynk widget with the microcontroller.
int value = millis() / 1000;
Blynk.virtualWrite(V0, value); //sending value in elapsed time
// You can send any value at any time.
// Please don't send more that 10 values per second.
}
// This function will be called every time Slider Widget writes values to the Virtual Pin V0
BLYNK_WRITE(V0) {
int sliderValue = param.asInt(); // assign incoming value from V0 to variable
Serial.print("V0 Slider value is: "); // print on serial monitor the value from the widget
Serial.println(sliderValue);
analogWrite(ledPin, sliderValue); // LED pin gets the value from the slider
delay(30);
}
void setup() {
Serial.begin(9600); // initializes the serial connection
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass); // sets the wifi connection.
pinMode(ledPin, OUTPUT); // Led pin set output
}
void loop() {
Blynk.run(); // runs the Blynk and timer functions, allowing the microcontroller to communicate
timer.run(); //with the Blynk server and update the state of the Blynk widgets.
}
Failure
I changed the wifi information, wrote which version of library I was using and took away the BlynkSimpleEsp8266 library and ESP8266WiFi.h wifi. At this point I did not know what to do and the code did not work. I looked into Blynk examples from the Blynk library that I installed and I could not see the Raspberry Pi Pico on the list. I could choose the RPP as always, by choosing Tools and then Board, but Blynk did not seem to emphasize the use of Raspberry Pi Pico in the examples.
Info
I had installed a Blynk library for Thonny from here. Then took another look at that page and ran this code from the same page. This is the code:
import BlynkLib
# Initialize Blynk
blynk = BlynkLib.Blynk('YourAuthToken')
# Register Virtual Pins
@blynk.VIRTUAL_WRITE(1)
def my_write_handler(value):
print('Current V1 value: {}'.format(value))
@blynk.VIRTUAL_READ(2)
def my_read_handler():
# this widget will show some time in seconds..
blynk.virtual_write(2, int(time.time()))
while True:
blynk.run()
Failed to connect Raspberry Pi Pico to Blynk
Failure
The Blynk logo appeared in the shell, and that made me happy, but also a notification that said: OSError: -6. When I checked, the RPP had not managed to connect to Blynk.
I have been trying to solve problems on my own lately, because I want to be independent when facing problems in Fab Academy, but at this point I was starting to ask others if they knew the solution to this. Unfortunately, that did not help either.
Another attempt
Installing library in Thonny
I found another directions here in the Blynk community here, written by the co-founder of Blynk. In these directions it is recommended to install Node.js. I tried to download it but I was notified that my organization does not accept it.
Learning outcome
Files
- Original file by Mike Bostock from here with longitude and latitude for airports
Original file from Mike Bostok
- File with longitude and latitude mixed up
- File with correct longitude and latitude
Correct longitude and latitude
Copyright permission - full text
Copyright
Note that I used three templates when working with D3 and all of them are made by Mike Bostock. You can read about the copyright here. The copyright permission is also signed by Ricky Reusser, but his name is not mentioned in the templates. Here is the full text:
Copyright permission - full text
Copyright 2013-2021 Mike Bostock
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
MIT License for https://github.com/scijs/integrate-adaptive-simpson
The MIT License (MIT)
Copyright 2015 Ricky Reusser
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.