On this page
In the eCraft2Learn project which Evothings is participating in we were looking at various ways Nim is an awesome programming language and this article is a whirlwind-copy-paste-into-your-terminal-as-you-read-thing to show how you install Nim, write a small program, compile it and package it in a very short time.
To spice it up, for no specific reason at all, we are doing it all inside a Linux Container - a fast virtual environment to work in. It’s just a nice way to have a clean environment and to ensure that you as a reader see the same results as I do.
You can of course just skip the part on LXC and go directly to Nim fun. :)
Get going with LXC
NOTE: The following presumes you are on a Ubuntu box, virtual should work fine.
Linux Containers let’s us run an isolated full Linux system inside a Linux host, kinda like KVM/Virtualbox but much more lightweight, similar to Docker. Contrary to Docker though, LXC is not constrained to a single process. Instead it behaves like a full VM which is much more what I want!
LXD is then a REST based daemon sitting on top of LXC that also gives us nice CLI tools operating against the daemon. See this nice blog article series on LXD version 2.0. Let’s install LXD and get us a clean spanking new Ubuntu 17.04 environment!
NOTE: More detailed steps are found here and a cheat sheet
sudo apt install lxd lxd-client zfsutils-linux newgrp lxdThen step through a bunch of questions, just using defaults work fine:
sudo lxd initSo that dance felt long, but… it was worth it!
Fire up a fresh Ubuntu 17.04
Now we can fire up a fresh Ubuntu, say version 17.04, and call it nim:
We can now see it’s running:
lxc listAnd we can get a root shell inside it:
lxc exec nim -- bashBut better to login properly as the ubuntu user:
Install Nim
Today the preferred way to install Nim on Linux is to use choosenim, a neat toolchain multiplexer which makes it easy to switch between different versions of the Nim compiler. First we install GCC though, needed by choosenim:
sudo apt install gccThen we can do the dance to install choosenim and nim:
curl https://nim-lang.org/choosenim/init.sh -sSf | sh echo "export PATH=~/.nimble/bin:\$PATH" >> ~/.bashrc export PATH=~/.nimble/bin:$PATHAnd we should have the Nim compiler in our path:
ubuntu@nim:~$ nim --version Nim Compiler Version 0.17.2 (2017-09-07) [Linux: amd64] Copyright (c) 2006-2017 by Andreas Rumpf git hash: 811fbdafd958443ddac98ad58c77245860b38620 active boot switches: -d:releaseCreate a Nim program
Allright! Time to make a small Nim program called “moni” - don’t ask why. First create a directory to work in, obviously we should use git etc, but I leave that to you. We also run nimble init to get a skeleton of a so called .nimble file. Nimble is the “npm” of the Nim ecosystem. And a nimble file is similar to packages.json for npm.
Now, let’s add some more lines to moni.nimble, starting with these three in the top section:
This tells nimble that this package produces binaries and will put them in the directory bin when building. We also tell it that we have a list of binaries, the syntax for a seq in Nim, which is a dynamic array, looks like @[ a, b, ... c ]. So we add "moni" to that list, the executable’s name.
Finally we also tell Nimble that when this package later is installed, skip installing all .nim files, since we are not making a Nim library, we only want the compiled executable to be installed.
Let’s also add a dependency called docopt which is a really nice Nim library for parsing command line arguments, to the bottom list of dependencies:
The full file should now look like this:
# Package
version = "0.1.0"
author = "Göran Krampe"
description = "A simple MQTT publisher tool."
license = "MIT"
binDir = "bin"
bin = @["moni"]
skipExt = @["nim"]
# Dependencies
requires "nim >= 0.17.2"
requires "docopt"
Ok, and finally, let’s write some code. To begin with the program will just parse out arguments, and can show help, save this as moni.nim:
import docopt
let help = """
moni - A simple MQTT publisher tool.
Usage:
moni [-u username] [-p password] [-s mqtturl] <topic> <payload>
moni (-h | --help)
moni (-v | --version)
Options:
-u username Set username [default: test].
-p password Set password [default: test].
-s mqtturl Set URL for the MQTT server [default: tcp://localhost:1883]
-h --help Show this screen.
-v --version Show version.
"""
var args = docopt(help, version = "0.1.0")
# Get parameters
let username = $args["-u"]
let password = $args["-p"]
let mqtturl = $args["-s"]
let topic = $args["<topic>"]
let payload = $args["<payload>"]
echo "Username: " & $username
echo "Password: " & $password
echo "Server: " & $mqtturl
echo "Topic: " & $topic
echo "Payload: " & $payload
quit
Time to compile it!
If we want nimble to suck down dependencies automatically for us, then we build using nimble, it will use the moni.nimble to figure out what to do:
And we can then run the binary:
./bin/moniIf we supply a topic and payload we can see default values for options:
./bin/moni topic payloadWe can also compile the moni.nim file directly, simply using the nim compiler - but that would have failed initially since we didn’t have the docopt dependency installed. But do try it now:
The nim compiler will however put the binary in your current directory, not in bin.
Ok, let’s get serious and add some real MQTT code into this. First add a dependency in moni.nimble to the Nim wrapper of the PAHO MQTT C library, by adding the following line at the bottom of moni.nimble:
So with nimble we can require using direct URLs to git or mercurial repositories as well, we are not limited to the pulished known packages in the Nimble catalog. Then make the code look like this instead:
import docopt, mqtt, MQTTClient
let help = """
moni - A simple MQTT publisher tool.
Usage:
moni [-u username] [-p password] [-s mqtturl] <topic> <payload>
moni (-h | --help)
moni (-v | --version)
Options:
-u username Set username [default: test].
-p password Set password [default: test].
-s mqtturl Set URL for the MQTT server [default: tcp://localhost:1883]
-h --help Show this screen.
-v --version Show version.
"""
var args = docopt(help, version = "0.1.0")
# Get parameters
let username = $args["-u"]
let password = $args["-p"]
let mqtturl = $args["-s"]
let topic = $args["<topic>"]
let payload = $args["<payload>"]
# Print them
echo "Username: " & $username
echo "Password: " & $password
echo "Server: " & $mqtturl
echo "Topic: " & $topic
echo "Payload: " & $payload
const ClientId = "nim-mqtt-pub"
proc connect(username, password, mqtturl: string): MQTTClient =
## Connect to MQTT server
result = newClient(mqtturl, ClientId, MQTTPersistenceType.None)
var connectOptions = newConnectOptions()
connectOptions.username = username
connectOptions.password = password
result.connect(connectOptions)
proc disconnect(client: MQTTClient) =
## Disconnect the client
client.disconnect(1000)
client.destroy()
proc publish(client: MQTTClient, topic, payload: string) =
## Publish a payload on a topic
discard client.publish(topic, payload, QOS.AtMostOnce, false)
try:
var client = connect(username, password, mqtturl)
client.publish(topic, payload)
client.disconnect()
echo "Payload sent"
except MQTTError:
quit("MQTT exception: " & getCurrentExceptionMsg(), QuitFailure)
quit(QuitSuccess)
A few quick remarks about the code:
- When you see
$something, that’s Nim’s way of sayingsomething.toString() - When you see
&that’s string concatenation. - You also see three procs that we later call inside the try: block. A proc is just another name for function.
- In the
connectproc there is a variable calledresult. It’s an implicit variable available in all procs that have a return value and represents the thing that will be returned. - In the
publishproc we see thediscardstatement, it’s used to “throw away” return values that we ignore, it has to be done explicitly in Nim or the compiler will complain. - If all goes well we quit with success, otherwise with the exception message and failure.
Then we build again:
nimble buildAnd let’s try running it aginst a public demo broker:
./bin/moni -s tcp://broker.hivemq.com:1883 sensor/99 '{"temp": 25.4, "flow": 0.7}' could not load: libpaho-mqtt3c.so compile with -d:nimDebugDlOpen for more informationOops! Ok, so the MQTT wrapper library needs the C library of course! And it’s not available as a deb, so let’s get our hands dirty:
sudo apt install libssl-dev makeThen we can build and install Paho C from source:
cd ~
git clone https://github.com/eclipse/paho.mqtt.c.git
cd paho.mqtt.c
make
sudo make install
sudo ldconfig
Let’s try building again:
cd ~/moni
nimble build
And finally we can hopefully publish via MQTT, let’s try it once more:
./bin/moni -s tcp://broker.hivemq.com:1883 cucumber/99 '{"temp": 25.4, "flow": 0.7}'
If it ends with “Payload sent” we are all good! We just sent a JSON payload to the cucumber/99 topic.
HiveMQ accepts anonymous connections on the test broker so we don’t need to specify username/password. In order to verify that the above actually worked, you can point your browser to http://www.hivemq.com/demos/websocket-client/ and connect on port 8000 to broker.hivemq.com, then add topic subscription to “cucumber/#” and run the above command once more. If all works you should see the message appear!
Now, to round things off we can install this little program too, locally for your user inside the LXC container that is. :) You just run nimble install and then we have it in our path.
Ok, that’s all folks - you are now a Nim hacker!