diff --git a/examples/emcute/Makefile b/examples/emcute/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..a8b49a81d7647696177751d538c119313a762cdc --- /dev/null +++ b/examples/emcute/Makefile @@ -0,0 +1,44 @@ +# name of your application +APPLICATION = emcute + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-mega2560 arduino-uno \ + chronos msb-430 msb-430h nucleo-f030 nucleo-f070 \ + nucleo-f072 nucleo-f334 nucleo32-f031 nucleo32-f303 \ + nucleo32-f042 stm32f0discovery telosb \ + waspmote-pro weio wsn430-v1_3b wsn430-v1_4 z1 + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +# Specify the mandatory networking modules for IPv6 and UDP +USEMODULE += gnrc_sock_udp +USEMODULE += gnrc_ipv6_default +# Include MQTT-SN +USEMODULE += emcute +# Add also the shell, some shell commands +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps +# For testing we also include the ping6 command and some stats +USEMODULE += gnrc_icmpv6_echo + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +CFLAGS += -DDEVELHELP + +# Comment this out to join RPL DODAGs even if DIOs do not contain +# DODAG Configuration Options (see the doc for more info) +# CFLAGS += -DGNRC_RPL_DODAG_CONF_OPTIONAL_ON_JOIN + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/emcute/README.md b/examples/emcute/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9493646339740b8159e9c287ac130703e674ec7e --- /dev/null +++ b/examples/emcute/README.md @@ -0,0 +1,97 @@ +## About +This application demonstrates the usage of the emCute (MQTT-SN) module in RIOT. + +## Setup +For using this example, two prerequisites have to be fullfilled: + +1. You need a running MQTT broker that supports MQTT-SN or a running MQTT-SN + gateway that is connected to a running MQTT broker +2. Your RIOT node needs to be able to speak to that broker/gateway + + +### Setting up a broker +In general, any MQTT-SN capable broker or broker/gateway setup will do. +Following a quick instruction on how-to setup the Mosquitto Real Simple Message +Broker: + +1. Get the RSMB here: https://github.com/eclipse/mosquitto.rsmb: +``` +git clone https://github.com/eclipse/mosquitto.rsmb.git +``` + +2. Go into the source folder and build the RSMB +``` +cd mosquitto.rsmb/rsmb/src +make +``` + +3. Create a config file. In this case we run the RSMB as MQTT and MQTT-SN + capable broker, using port 1885 for MQTT-SN and 1886 for MQTT and enabling + IPv6, so save the following to `config.conf`: +``` +# add some debug output +trace_output protocol + +# listen for MQTT-SN traffic on UDP port 1885 +listener 1885 INADDR_ANY mqtts + ipv6 true + +# listen to MQTT connections on tcp port 1886 +listener 1886 INADDR_ANY + ipv6 true +``` + +4. Start the broker: +``` +./broker_mqtts config.conf +``` + +You can refer to +https://rawgit.com/MichalFoksa/rsmb/master/rsmb/doc/gettingstarted.htm for more +configuration options. + + +### Setting up RIOT `native` +When running this example under native, we must configure some global addresses, +as the RSMB doesn't seems to be able to handle link-local addresses. So for a +single RIOT native instance, we can do the following: + +1. Setup `tap` and `tabbr` devices using RIOT's `tapsetup` script: +``` +./RIOTDIR/dist/tools/tapsetup/tapsetup +``` + +2. Assign a site-global prefix to the `tabbr0` interface (the name could be + different on OSX etc): +``` +sudo ip a a fec0:affe::1/64 dev tapbr0 +``` + +3. Assign a site-global address with the same prefix to the RIOT `native` + instance: +``` +ifconfig 5 add fec0:affe::99 +``` + + +## Usage +This example maps all available MQTT-SN functions to shell commands. Simply type +`help` to see the available commands. The most important steps are explained +below: + +- To connect to a broker, use the `con` command: +``` +con fec0:affe::1 1885 +``` + +- To subscribe to a topic, run `sub` with the topic name as parameter, e.g. +``` +sub hello/world +``` + +- For publishing, use the `pub` command: +``` +pub hello/world "One more beer, please." +``` + +That's it, happy publishing! diff --git a/examples/emcute/main.c b/examples/emcute/main.c new file mode 100644 index 0000000000000000000000000000000000000000..9184a8c1074b21a97e38b1d4c096aa42970c71bd --- /dev/null +++ b/examples/emcute/main.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2015 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup examples + * @{ + * + * @file + * @brief Example application for demonstrating the RIOT network stack + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "shell.h" +#include "msg.h" +#include "net/emcute.h" +#include "net/ipv6/addr.h" + +#define EMCUTE_PORT (1883U) +#define EMCUTE_ID ("gertrud") +#define EMCUTE_PRIO (THREAD_PRIORITY_MAIN - 1) + +#define NUMOFSUBS (16U) +#define TOPIC_MAXLEN (64U) + +static char stack[THREAD_STACKSIZE_DEFAULT]; +static msg_t queue[8]; + +static emcute_sub_t subscriptions[NUMOFSUBS]; +static char topics[NUMOFSUBS][TOPIC_MAXLEN]; + +static void *emcute_thread(void *arg) +{ + (void)arg; + emcute_run(EMCUTE_PORT, EMCUTE_ID); + return NULL; /* should never be reached */ +} + +static void on_pub(const emcute_topic_t *topic, void *data, size_t len) +{ + char *in = (char *)data; + + printf("### got publication for topic '%s' [%i] ###\n", + topic->name, (int)topic->id); + for (size_t i = 0; i < len; i++) { + printf("%c", in[i]); + } + puts(""); +} + +static unsigned get_qos(const char *str) +{ + int qos = atoi(str); + switch (qos) { + case 1: return EMCUTE_QOS_1; + case 2: return EMCUTE_QOS_2; + default: return EMCUTE_QOS_0; + } +} + +static int cmd_con(int argc, char **argv) +{ + sock_udp_ep_t gw = { .family = AF_INET6, .port = EMCUTE_PORT }; + char *topic = NULL; + char *message = NULL; + size_t len = 0; + + if (argc < 2) { + printf("usage: %s <ipv6 addr> [port] [<will topic> <will message>]\n", + argv[0]); + return 1; + } + + /* parse address */ + if (ipv6_addr_from_str((ipv6_addr_t *)&gw.addr.ipv6, argv[1]) == NULL) { + printf("error parsing IPv6 address\n"); + return 1; + } + + if (argc >= 3) { + gw.port = (uint16_t)atoi(argv[2]); + } + if (argc >= 5) { + topic = argv[3]; + message = argv[4]; + len = strlen(message); + } + + if (emcute_con(&gw, true, topic, message, len, 0) != EMCUTE_OK) { + printf("error: unable to connect to [%s]:%i\n", argv[1], (int)gw.port); + } + printf("Successfully connected to gateway at [%s]:%i\n", + argv[1], (int)gw.port); + + return 0; +} + +static int cmd_discon(int argc, char **argv) +{ + (void)argc; + (void)argv; + + int res = emcute_discon(); + if (res == EMCUTE_NOGW) { + puts("error: not connected to any broker"); + return 1; + } + else if (res != EMCUTE_OK) { + puts("error: unable to disconnect"); + return 1; + } + puts("Disconnect successful"); + return 0; +} + +static int cmd_pub(int argc, char **argv) +{ + emcute_topic_t t; + unsigned flags = EMCUTE_QOS_0; + + if (argc < 3) { + printf("usage: %s <topic name> <data> [QoS level]\n", argv[0]); + return 1; + } + + /* parse QoS level */ + if (argc >= 4) { + flags |= get_qos(argv[3]); + } + + printf("pub with topic: %s and name %s and flags 0x%02x\n", argv[1], argv[2], (int)flags); + + /* step 1: get topic id */ + t.name = argv[1]; + if (emcute_reg(&t) != EMCUTE_OK) { + puts("error: unable to obtain topic ID"); + return 1; + } + + /* step 2: publish data */ + if (emcute_pub(&t, argv[2], strlen(argv[2]), flags) != EMCUTE_OK) { + printf("error: unable to publish data to topic '%s [%i]'\n", + t.name, (int)t.id); + return 1; + } + + printf("Published %i bytes to topic '%s [%i]'\n", + (int)strlen(argv[2]), t.name, t.id); + + return 0; +} + +static int cmd_sub(int argc, char **argv) +{ + unsigned flags = EMCUTE_QOS_0; + + if (argc < 2) { + printf("usage: %s <topic name> [QoS level]\n", argv[0]); + return 1; + } + + if (strlen(argv[1]) > TOPIC_MAXLEN) { + puts("error: topic name exceeds maximum possible size"); + return 1; + } + if (argc >= 3) { + flags |= get_qos(argv[2]); + } + + /* find empty subscription slot */ + unsigned i = 0; + for (; (i < NUMOFSUBS) && (subscriptions[i].topic.id != 0); i++) {} + if (i == NUMOFSUBS) { + puts("error: no memory to store new subscriptions"); + return 1; + } + + subscriptions[i].cb = on_pub; + strcpy(topics[i], argv[1]); + subscriptions[i].topic.name = topics[i]; + if (emcute_sub(&subscriptions[i], flags) != EMCUTE_OK) { + printf("error: unable to subscribe to %s\n", argv[1]); + return 1; + } + + printf("Now subscribed to %s\n", argv[1]); + return 0; +} + +static int cmd_unsub(int argc, char **argv) +{ + if (argc < 2) { + printf("usage %s <topic name>\n", argv[0]); + return 1; + } + + /* find subscriptions entry */ + for (unsigned i = 0; i < NUMOFSUBS; i++) { + if (subscriptions[i].topic.name && + (strcmp(subscriptions[i].topic.name, argv[1]) == 0)) { + if (emcute_unsub(&subscriptions[i]) == EMCUTE_OK) { + memset(&subscriptions[i], 0, sizeof(emcute_sub_t)); + printf("Unsubscribed from '%s'\n", argv[1]); + } + else { + printf("Unsubscription form '%s' failed\n", argv[1]); + } + return 0; + } + } + + printf("error: no subscription for topic '%s' found\n", argv[1]); + return 1; +} + +static int cmd_will(int argc, char **argv) +{ + if (argc < 3) { + printf("usage %s <will topic name> <will message content>\n", argv[0]); + return 1; + } + + if (emcute_willupd_topic(argv[1], 0) != EMCUTE_OK) { + puts("error: unable to update the last will topic"); + return 1; + } + if (emcute_willupd_msg(argv[2], strlen(argv[2])) != EMCUTE_OK) { + puts("error: unable to update the last will message"); + return 1; + } + + puts("Successfully updated last will topic and message"); + return 0; +} + +static const shell_command_t shell_commands[] = { + { "con", "connect to MQTT broker", cmd_con }, + { "discon", "disconnect from the current broker", cmd_discon }, + { "pub", "publish something", cmd_pub }, + { "sub", "subscribe topic", cmd_sub }, + { "unsub", "unsubscribe from topic", cmd_unsub }, + { "will", "register a last will", cmd_will }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + puts("MQTT-SN example application\n"); + puts("Type 'help' to get started. Have a look at the README.md for more" + "information."); + + /* the main thread needs a msg queue to be able to run `ping6`*/ + msg_init_queue(queue, (sizeof(queue) / sizeof(msg_t))); + + /* initialize our subscription buffers */ + memset(subscriptions, 0, (NUMOFSUBS * sizeof(emcute_sub_t))); + + /* start the emcute thread */ + thread_create(stack, sizeof(stack), EMCUTE_PRIO, 0, + emcute_thread, NULL, "emcute"); + + /* start shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should be never reached */ + return 0; +}