OpenEmbedded, IOTA CClient and a Raspberry Pi
This is my third article for the IOTA community, the second in form of a tutorial. It presents the possibilities of the meta-iota OpenEmbedded layer with a hands-on tutorial that acts as a proof-of-concept. In the first tutorial, I talked about how to run IOTA cIRI on a BeagleBone Black with Yocto Project and OpenEmbedded. It showed how the meta-iota layer can help with the cross-compilation of an embeddable IOTA node.
This time, I want to focus on the client side of things. The repository c-iota-workshop has practical examples of how to use the CClient library to write C applications and interact with the Tangle. Although both the enTangled and the c-iota-workshop repositories provide the possibility of building with Bazel, which in theory allows cross-compilation for a Raspberry Pi, I want to show how to do that with BitBake. This tutorial can be easily translated into several different boards, as long as there is an OpenEmbedded BSP layer supporting it.
There are other repositories similar to c-iota-workshop, but in different programming languages. You can read more about them on Dave de Fijter’s article Learning by example: IOTA Workshops. I’ll soon publish tutorials based on the Go and Python examples too.
Introduction
First, let’s take a look at the contents of c-iota-workshop. It contains 6 examples, namely:
These examples show how C code can be used to write Client applications to interact with the Tangle. There are, however, some restrictions. Many parameters (such as SEED, RECEIVER_ADDR, ADDR_HASH and TLS_CERTIFICATE_PEM) are hard-coded into the source code, and real production applications should handle them differently (especially the seed, which is the most critical piece information of a client and should never be revealed). Nevertheless, these are still extremely useful references to understand how the CClient API should be manipulated.
Now, let’s take a look at the BitBake repice (c-iota-workshop_git.bb) that cross-compiles these examples:
SUMMARY = "C IOTA workshop"
DESCRIPTION = "Some simple examples to get you started on developing with IOTA using C."
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = " \
git://github.com/iota-community/c-iota-workshop.git \
"
SRCREV = "${AUTOREV}"
S = "${WORKDIR}/git"
DEPENDS = " libcclient uthash keccak logger"
do_compile(){
${CC} -I${S} ${CFLAGS} ${LDFLAGS} -o iota_c_hello_world examples/e01_hello_world.c iota_client_service/client_service.c -lcclient -lcommon -lcjson -lkeccak -lmbedtls -lmbedcrypto -lmbedx509 -lhttp_parser -llogger -pthread
${CC} -I${S} ${CFLAGS} ${LDFLAGS} -o iota_c_send_hello examples/e02_send_hello.c iota_client_service/client_service.c -lcclient -lcommon -lcjson -lkeccak -lmbedtls -lmbedcrypto -lmbedx509 -lhttp_parser -llogger -pthread -lm
${CC} -I${S} ${CFLAGS} ${LDFLAGS} -o iota_c_receive_hello examples/e03_receive_hello.c iota_client_service/client_service.c -lcclient -lcommon -lcjson -lkeccak -lmbedtls -lmbedcrypto -lmbedx509 -lhttp_parser -llogger -pthread
${CC} -I${S} ${CFLAGS} ${LDFLAGS} -o iota_c_generate_address examples/e04_generate_address.c iota_client_service/client_service.c -lcclient -lcommon -lcjson -lkeccak -lmbedtls -lmbedcrypto -lmbedx509 -lhttp_parser -llogger -pthread
${CC} -I${S} ${CFLAGS} ${LDFLAGS} -o iota_c_check_balance examples/e05_check_balance.c iota_client_service/client_service.c -lcclient -lcommon -lcjson -lkeccak -lmbedtls -lmbedcrypto -lmbedx509 -lhttp_parser -llogger -pthread
${CC} -I${S} ${CFLAGS} ${LDFLAGS} -o iota_c_send_tokens examples/e06_send_tokens.c iota_client_service/client_service.c -lcclient -lcommon -lcjson -lkeccak -lmbedtls -lmbedcrypto -lmbedx509 -lhttp_parser -llogger -pthread -lm
}
do_install(){
install -m 0755 -d ${D}${bindir}
install -m 0755 ${S}/iota_c_hello_world ${D}${bindir}
install -m 0755 ${S}/iota_c_send_hello ${D}${bindir}
install -m 0755 ${S}/iota_c_receive_hello ${D}${bindir}
install -m 0755 ${S}/iota_c_generate_address ${D}${bindir}
install -m 0755 ${S}/iota_c_check_balance ${D}${bindir}
install -m 0755 ${S}/iota_c_send_tokens ${D}${bindir}
}
Here, the variable DEPENDS
is telling BitBake what are the other recipes that need to be compiled before it starts with this one. That is necessary so the recipe’s sysroot is correctly set up with the appropriate libs and headers. The dependency list should actually be much bigger, but luckily libcclient_1.0.0.bb already declares most of them, and because our recipe depends on it, we don’t need to declare them again.
Then, the do_compile()
task is defined. We are not using Make, CMake, Bazel or any other tool to coordinate the compilation. Each one of the 6 examples is explicitly compiled with the invocation of ${CC}
, which is the BitBake variable that represents the C Cross Compiler. All the libraries are explicitly listed for linking.
Finally, the do_install()
task is responsible for placing the executable binaries into the destination directory, represented by the ${D}
variable. The ${bindir}
variable means that the binaries will reside in the /usr/bin
path of the final rootfs. The -m
parameter of the install
command is meant to set the appropriate file permissions (-rwxr-xr-x
) of each executable.
Building the image
Let’s get our hands dirty. Follow the next steps to build a image core-image-base
to the Raspberry Pi.
- Prepare your host for yocto development. Refer to Yocto’s official documentation for more details on setting up.
Assuming you are using Ubuntu (16.04 or 18.04):
$ sudo apt-get install gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath socat cpio python python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev xterm
2. Clone poky. We are going to use the warrior branch of the Yocto Releases:
$ mkdir ${HOME}/yocto
$ cd ${HOME}/yocto
$ git clone git://git.yoctoproject.org/poky -b warrior
3. Clone the other OE layers (warrior branch for meta-openembedded and meta-raspberry pi, tutorial-rpi for meta-iota):
$ git clone https://github.com/openembedded/meta-openembedded -b warrior
$ git clone https://github.com/agherzan/meta-raspberrypi -b warrior
$ git clone http://github.com/bernardoaraujor/meta-iota
4. Start the build environment:
$ source poky/oe-init-build-env
5. Inform BitBake about our layers by adding them to conf/bblayers.conf
:
...BBLAYERS ?= " \
${HOME}/yocto/poky/meta \
${HOME}/yocto/poky/meta-poky \
${HOME}/yocto/poky/meta-yocto-bsp \
${HOME}/yocto/meta-openembedded/meta-oe \
${HOME}/yocto/meta-openembedded/meta-networking \
${HOME}/yocto/meta-openembedded/meta-python \
${HOME}/yocto/meta-raspberrypi \
${HOME}/yocto/meta-iota/meta-tangle \
"
6. Add the following lines to the bottom of conf/local.conf
:
IMAGE_INSTALL_append = " c-iota-workshop"
The line above will make sure that the c-iota-workshop
package is included in the image.
Raspberry Pi 0, 1, 2 and CM will have UART console enabled by default. RaspberryPi 0 WiFi and 3 do not, so for those versions you need to add the following line to conf/local.conf
:
ENABLE_UART = "1"
7. Inform BitBake that we want to build for a Raspberry Pi by adding this line to conf/local.conf
:
MACHINE="raspberrypi{xxx}"
Here, you should set MACHINE
to match the version of your RPi board. Check meta-raspberrypi/conf/machine/ for the available options and find yours so you can replace {xxx}
with the appropriate value.
8. Start building a core-image-base
image.
$ bitbake core-image-base
9. Insert the SD card on your desktop/laptop. In my case it showed up as /dev/sdc
, might be something else for you. One way to find out is to use the lsblk
command before and after inserting the SD card. Note that my SD shows up as /dev/sdc
:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 238,5G 0 disk
├─sda2 8:2 0 1G 0 part
├─sda3 8:3 0 237,3G 0 part
└─sda1 8:1 0 200M 0 part /boot/efi
sdb 8:16 0 238,5G 0 disk
├─sdb2 8:18 0 230,1G 0 part /
├─sdb3 8:19 0 7,9G 0 part [SWAP]
└─sdb1 8:17 0 512M 0 part
$
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 238,5G 0 disk
├─sda2 8:2 0 1G 0 part
├─sda3 8:3 0 237,3G 0 part
└─sda1 8:1 0 200M 0 part /boot/efi
sdb 8:16 0 238,5G 0 disk
├─sdb2 8:18 0 230,1G 0 part /
├─sdb3 8:19 0 7,9G 0 part [SWAP]
└─sdb1 8:17 0 512M 0 part
sdc 8:32 1 14,4G 0 disk
└─sdc1 8:33 1 14,4G 0 part
10. Finally, flash the compiled image into an SD card. Replace the{xxx}
in the commands below with the appropriate version of your board.
$ cd ${HOME}/yocto/build/tmp/deploy/images/raspberrypi{xxx}/
$ sudo dd bs=4M if=core-image-base-raspberrypi{xxx}.rpi-sdimg of=/dev/sdc
$ sync
11. I assume you have a FTDI USB-RS232 adapter (or equivalent), and that it shows up as /dev/ttyUSB0
. Insert the SD card on the board, connect the FTDI to the RPi’s UART pins and start minicom:
$ sudo minicom -D /dev/ttyUSB0 -b 115200
12. Power the board and watch the kernel boot. Now you are inside the RPi’s Linux! Log in with username root
. Connect an Ethernet cable from your LAN router into the board.
Make sure that the RPi has access to the internet:
# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=53 time=17.122 ms
64 bytes from 8.8.8.8: seq=1 ttl=53 time=16.575 ms
Running the examples
1. We are finally ready to run the CClient examples! Let’s start with the classical first example:
# iota_c_hello_world
appName IRI
appVersion 1.8.2-RELEASE
latestMilestone AXZ9DSAXNYPYSIZHMUFG9WYQCYAT9DBVBOOPNZBMEZBECHQSINJKKJTDJNJUBWQIWOAEQSLUUYZF99999
latestMilestoneIndex 1267123
milestoneStratIndex 1226954
neighbors 3
packetsQueueSize 0
time 1575581363444
tips 3257
transactionsToRequest 0
So that’s the Hello World application printing out information about the node (174.141.204.79
) that was defined in config.h of the c-iota-workshop repository.
2. Next, let’s run the Send Hello example:
# iota_c_send_hello
send transfer OK
bundle hash: 9HSPLDGFI9RZCPKB9K9PEXOFKPGAM9OLIFOIKIG9QTKCZFWBBRCQATUXJ9XHFDHCHEUJXSILTCQYOJTGA
Transaction was sent.
Here we are sending a transaction to RECEIVER_ADDR
. We are not sending any IOTA tokens, but the beautiful thing about the Tangle is that we can also send data through the transactions.
We can head to a Tangle explorer and paste the bundle hash that we got in the output:
Then, we can click on the transaction hash and get to:
There we can see the tag HELLOWORLD99999999999999
and if you scroll down to the Message field and check “Text”, you will find the message You did it!
just like it was defined in the C code.
3. Let’s now run the Receive Hello example:
# iota_c_receive_hello
find transaction count: 31
dump first transaction:
value = 0, curr_index = 0, last_index = 0
addr: 999999999999999999999999999999999999999999999999999999999999999999999999999999999
data:
Transaction is received.
This example fetches and prints transactions for a given address. Note that the ADDR_HASH
variable has the same value as the RECEIVER_ADDR
variable from the previous example.
Unfortunately, recent changes to the CClient library introduced a bug that makes this example behave a little different than expected. The output prints a null address (just 9s), and the data field is empty.
Soon I will try to find some time to debug it and make this example fully functional again. Any help is more than appreciated!
4. Now let us create new addresses with the seed hard-coded in the Generate Address example. You can read more about seeds here, and about address generation here. Both links will take you into the official IOTA documentation.
# iota_c_generate_address
unused: SHVKEXYQLWCKLXZZXESTHTLPCNFQYVLUNDY9NPNAGBFSPLAKLTPTUUJZVSHGMKUMCCMJXQXPRABIGTAQX
[0]: SHVKEXYQLWCKLXZZXESTHTLPCNFQYVLUNDY9NPNAGBFSPLAKLTPTUUJZVSHGMKUMCCMJXQXPRABIGTAQX
Addresses generated.
Remember, this is just an illustrative example! Seeds should never be exposed in plaintext or source code! This code is just meant to show how CClient is used to generate addresses from the seed, and real production scenarios must handle seeds with the appropriate security measures.
5. Let’s run the example from the Check Balances code:
# iota_c_check_balance
balances: [ 0 ]
reference: RZOBVARXAZGQEBMEDINPMKOEYTYHXAOVLULQAXVUHTBJBMMSEN9WTCHDUDMZNXPREXPNKA9EGQRHZ9999
Check balances done.
Here, the address being checked is the same from examples 2, 3 and 4. There’s 0 IOTAs on that address, as we can see from the output.
6. The last example is Send Tokens. This would send IOTAs to the same destination address from examples 2 and 3. Unfortunately this code also needs some debugging at the moment.
# iota_c_send_tokens
send transfer Checking balance failed
Send tokens done with error code: 1044
Conclusion
There you go! There will be more tutorials like this soon, based on different boards. Although the Raspberry Pi is a great board and broadly adopted in the maker community, I also want to target harware that’s used on industrial settings, such as STMicroelectronics STM32MP157C-DK2 and Toradex Colibri iMX6. Nevertheless, it’s always good to remember that the tutorials should translate to any other board supported by the Yocto Project and OpenEmbedded.
I feel the need to apologize for the two examples (3 and 6) that still need some debugging. I had to make the difficult decision between publishing the article the way it is, or keeping it on the backlog and only publish it after I made sure all the examples were running 100%. The problem is that my backlog is already full of things to be done, and I feel the article still provides a valuable insight into how the Yocto Project can be used to cross compile IOTA Client applications.
If you have any questions or feedback, feel free to write a comment below, DM me on Discord (Bernardo [IF]#8478) or send me an email (bernardo.araujo@iota.org).