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 lxd
Then step through a bunch of questions, just using defaults work fine:
sudo lxd init
So 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
:
lxc launch ubuntu:17.04 nim
We can now see it’s running:
lxc list
And we can get a root shell inside it:
lxc exec nim -- bash
But better to login properly as the ubuntu
user:
lxc exec nim -- su --login ubuntu
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 gcc
Then 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:$PATH
And 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:release
Create 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.
mkdir moni && cd moni
nimble init
Now, let’s add some more lines to moni.nimble
, starting with these three in the top section:
binDir = "bin"
bin = @["moni"]
skipExt = @["nim"]
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:
requires "docopt"
The full file should now look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
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:
nimble build
And we can then run the binary:
./bin/moni
If we supply a topic and payload we can see default values for options:
./bin/moni topic payload
We 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:
nim c moni.nim
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
:
requires "https://github.com/barnybug/nim-mqtt"
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
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
connect
proc 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
publish
proc we see thediscard
statement, 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 build
And 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 information
Oops! 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 make
Then we can build and install Paho C from source:
1 2 3 4 5 6 |
|
Let’s try building again:
1 2 |
|
And finally we can hopefully publish via MQTT, let’s try it once more:
1
|
|
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!