diff --git a/examples/cc26xx/Makefile b/examples/cc26xx/Makefile new file mode 100644 index 000000000..f23d49fe3 --- /dev/null +++ b/examples/cc26xx/Makefile @@ -0,0 +1,9 @@ +DEFINES+=PROJECT_CONF_H=\"project-conf.h\" +CONTIKI_PROJECT = cc26xx-demo + +all: $(CONTIKI_PROJECT) + +CONTIKI_WITH_IPV6 = 1 + +CONTIKI = ../.. +include $(CONTIKI)/Makefile.include diff --git a/examples/cc26xx/Makefile.target b/examples/cc26xx/Makefile.target new file mode 100644 index 000000000..15890aa6a --- /dev/null +++ b/examples/cc26xx/Makefile.target @@ -0,0 +1 @@ +TARGET = srf06-cc26xx diff --git a/examples/cc26xx/README.md b/examples/cc26xx/README.md new file mode 100644 index 000000000..bd1be685b --- /dev/null +++ b/examples/cc26xx/README.md @@ -0,0 +1,15 @@ +CC26xx Demo +=========== +This example demonstrates basic functionality for the two supported CC26xx +boards. More specifically, the example demonstrates: + +* How to take sensor readings +* How to use buttons and the reed relay (triggered by holding a magnet near S3 + on the SensorTag). +* How to keep a power domain powered and a peripheral clocked under low power + operation +* How to send out BLE advertisements. The device will periodically send out BLE + beacons with the platform name as payload. Those beacons/BLE ADV packets can + be captured with any BLE capable device. Two such applications for iOS are the + TI Multitool and the TI Sensortag app. They can be found in the Apple App + Store. If you have a BLE-capable Mac, you can also use LightBlue for OS X. diff --git a/examples/cc26xx/cc26xx-demo.c b/examples/cc26xx/cc26xx-demo.c new file mode 100644 index 000000000..83f78e869 --- /dev/null +++ b/examples/cc26xx/cc26xx-demo.c @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +/** + * \addtogroup cc26xx-platforms + * @{ + * + * \defgroup cc26xx-examples CC26xx Example Projects + * + * Example projects for CC26xx-based platforms. + * @{ + * + * \defgroup cc26xx-demo CC26xx Demo Project + * + * Example project demonstrating the CC26xx platforms + * + * This example will work for the following boards: + * - srf06-cc26xx: SmartRF06EB + CC26XX EM + * - sensortag-cc26xx: CC26XX sensortag + * + * By default, the example will build for the srf06-cc26xx board. To switch + * between platforms: + * - make clean + * - make BOARD=sensortag-cc26xx savetarget + * + * or + * + * make BOARD=srf06-cc26xx savetarget + * + * This is an IPv6/RPL-enabled example. Thus, if you have a border router in + * your installation (same RDC layer, same PAN ID and RF channel), you should + * be able to ping6 this demo node. + * + * This example also demonstrates CC26xx BLE operation. The process starts + * the BLE beacon daemon (implemented in the RF driver). The daemon will + * send out a BLE beacon periodically. Use any BLE-enabled application (e.g. + * LightBlue on OS X or the TI BLE Multitool smartphone app) and after a few + * seconds the cc26xx device will be discovered. + * + * - etimer/clock : Every CC26XX_DEMO_LOOP_INTERVAL clock ticks the LED defined + * as CC26XX_DEMO_LEDS_PERIODIC will toggle and the device + * will print out readings from some supported sensors + * - sensors : Some sensortag sensors are read asynchronously (see sensor + * documentation). For those, this example will print out + * readings in a staggered fashion at a random interval + * - Buttons : CC26XX_DEMO_SENSOR_1 button will toggle CC26XX_DEMO_LEDS_BUTTON + * - CC26XX_DEMO_SENSOR_2 turns on LEDS_REBOOT and causes a + * watchdog reboot + * - The remaining buttons will just print something + * - The example also shows how to retrieve the duration of a + * button press (in ticks). The driver will generate a + * sensors_changed event upon button release + * - UART : Receiving an entire line of text over UART (ending + * in \\r) will cause CC26XX_DEMO_LEDS_SERIAL_IN to toggle + * This also demonstrates how a code module can influence + * low-power operation: In this example we keep the UART on + * and capable to RX even with the chip in deep sleep. + * see keep_uart_on() and the UART driver + * - Reed Relay : Will toggle the sensortag buzzer on/off + * + * @{ + * + * \file + * Example demonstrating the cc26xx platforms + */ +#include "contiki.h" +#include "sys/etimer.h" +#include "sys/ctimer.h" +#include "dev/leds.h" +#include "dev/serial-line.h" +#include "dev/watchdog.h" +#include "random.h" +#include "button-sensor.h" +#include "batmon-sensor.h" +#include "board-peripherals.h" +#include "lpm.h" +#include "cc26xx-rf.h" + +#include "ti-lib.h" + +#include +#include +/*---------------------------------------------------------------------------*/ +#define CC26XX_DEMO_LOOP_INTERVAL (CLOCK_SECOND * 20) +#define CC26XX_DEMO_LEDS_PERIODIC LEDS_YELLOW +#define CC26XX_DEMO_LEDS_BUTTON LEDS_RED +#define CC26XX_DEMO_LEDS_SERIAL_IN LEDS_ORANGE +#define CC26XX_DEMO_LEDS_REBOOT LEDS_ALL +/*---------------------------------------------------------------------------*/ +#define CC26XX_DEMO_SENSOR_NONE (void *)0xFFFFFFFF + +#define CC26XX_DEMO_SENSOR_1 &button_left_sensor +#define CC26XX_DEMO_SENSOR_2 &button_right_sensor + +#if BOARD_SENSORTAG +#define CC26XX_DEMO_SENSOR_3 CC26XX_DEMO_SENSOR_NONE +#define CC26XX_DEMO_SENSOR_4 CC26XX_DEMO_SENSOR_NONE +#define CC26XX_DEMO_SENSOR_5 &reed_relay_sensor +#else +#define CC26XX_DEMO_SENSOR_3 &button_up_sensor +#define CC26XX_DEMO_SENSOR_4 &button_down_sensor +#define CC26XX_DEMO_SENSOR_5 &button_select_sensor +#endif +/*---------------------------------------------------------------------------*/ +static struct etimer et; +/*---------------------------------------------------------------------------*/ +PROCESS(cc26xx_demo_process, "cc26xx demo process"); +AUTOSTART_PROCESSES(&cc26xx_demo_process); +/*---------------------------------------------------------------------------*/ +#if BOARD_SENSORTAG +/*---------------------------------------------------------------------------*/ +/* + * Update sensor readings in a staggered fashion every SENSOR_READING_PERIOD + * ticks + a random interval between 0 and SENSOR_READING_RANDOM ticks + */ +#define SENSOR_READING_PERIOD (CLOCK_SECOND * 20) +#define SENSOR_READING_RANDOM (CLOCK_SECOND << 4) + +static struct ctimer bmp_timer, opt_timer, sht_timer, tmp_timer, mpu_timer; +/*---------------------------------------------------------------------------*/ +static void init_bmp_reading(void *not_used); +static void init_opt_reading(void *not_used); +static void init_sht_reading(void *not_used); +static void init_tmp_reading(void *not_used); +static void init_mpu_reading(void *not_used); +/*---------------------------------------------------------------------------*/ +static void +print_mpu_reading(int reading) +{ + if(reading < 0) { + printf("-"); + reading = -reading; + } + + printf("%d.%02d", reading / 100, reading % 100); +} +/*---------------------------------------------------------------------------*/ +static void +get_bmp_reading() +{ + int value; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + value = bmp_280_sensor.value(BMP_280_SENSOR_TYPE_PRESS); + if(value != CC26XX_SENSOR_READING_ERROR) { + printf("BAR: Pressure=%d.%02d hPa\n", value / 100, value % 100); + } else { + printf("BAR: Pressure Read Error\n"); + } + + value = bmp_280_sensor.value(BMP_280_SENSOR_TYPE_TEMP); + if(value != CC26XX_SENSOR_READING_ERROR) { + printf("BAR: Temp=%d.%02d C\n", value / 100, value % 100); + } else { + printf("BAR: Temperature Read Error\n"); + } + + SENSORS_DEACTIVATE(bmp_280_sensor); + + ctimer_set(&bmp_timer, next, init_bmp_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +get_tmp_reading() +{ + int value; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + value = tmp_007_sensor.value(TMP_007_SENSOR_TYPE_ALL); + + if(value == CC26XX_SENSOR_READING_ERROR) { + printf("TMP: Ambient Read Error\n"); + return; + } + + value = tmp_007_sensor.value(TMP_007_SENSOR_TYPE_AMBIENT); + printf("TMP: Ambient=%d.%03d C\n", value / 1000, value % 1000); + + value = tmp_007_sensor.value(TMP_007_SENSOR_TYPE_OBJECT); + printf("TMP: Object=%d.%03d C\n", value / 1000, value % 1000); + + SENSORS_DEACTIVATE(tmp_007_sensor); + + ctimer_set(&tmp_timer, next, init_tmp_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +get_sht_reading() +{ + int value; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + value = sht_21_sensor.value(SHT_21_SENSOR_TYPE_TEMP); + if(value != CC26XX_SENSOR_READING_ERROR) { + printf("SHT: Temp=%d.%02d C\n", value / 100, value % 100); + } else { + printf("SHT: Temp Read Error\n"); + } + + value = sht_21_sensor.value(SHT_21_SENSOR_TYPE_HUMIDITY); + if(value != CC26XX_SENSOR_READING_ERROR) { + printf("SHT: Humidity=%d.%02d %%RH\n", value / 100, value % 100); + } else { + printf("SHT: Humidity Read Error\n"); + } + + ctimer_set(&sht_timer, next, init_sht_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +get_light_reading() +{ + int value; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + value = opt_3001_sensor.value(0); + if(value != CC26XX_SENSOR_READING_ERROR) { + printf("OPT: Light=%d.%02d lux\n", value / 100, value % 100); + } else { + printf("OPT: Light Read Error\n"); + } + + SENSORS_DEACTIVATE(opt_3001_sensor); + + ctimer_set(&opt_timer, next, init_opt_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +get_mpu_reading() +{ + int value; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + printf("MPU Gyro: X="); + value = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_GYRO_X); + print_mpu_reading(value); + printf(" deg/sec\n"); + + printf("MPU Gyro: Y="); + value = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_GYRO_Y); + print_mpu_reading(value); + printf(" deg/sec\n"); + + printf("MPU Gyro: Z="); + value = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_GYRO_Z); + print_mpu_reading(value); + printf(" deg/sec\n"); + + printf("MPU Acc: X="); + value = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_ACC_X); + print_mpu_reading(value); + printf(" G\n"); + + printf("MPU Acc: Y="); + value = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_ACC_Y); + print_mpu_reading(value); + printf(" G\n"); + + printf("MPU Acc: Z="); + value = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_ACC_Z); + print_mpu_reading(value); + printf(" G\n"); + + SENSORS_DEACTIVATE(mpu_9250_sensor); + + ctimer_set(&mpu_timer, next, init_mpu_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +init_bmp_reading(void *not_used) +{ + SENSORS_ACTIVATE(bmp_280_sensor); +} +/*---------------------------------------------------------------------------*/ +static void +init_opt_reading(void *not_used) +{ + SENSORS_ACTIVATE(opt_3001_sensor); +} +/*---------------------------------------------------------------------------*/ +static void +init_sht_reading(void *not_used) +{ + SENSORS_ACTIVATE(sht_21_sensor); +} +/*---------------------------------------------------------------------------*/ +static void +init_tmp_reading(void *not_used) +{ + SENSORS_ACTIVATE(tmp_007_sensor); +} +/*---------------------------------------------------------------------------*/ +static void +init_mpu_reading(void *not_used) +{ + mpu_9250_sensor.configure(SENSORS_ACTIVE, MPU_9250_SENSOR_TYPE_ALL); +} +#endif +/*---------------------------------------------------------------------------*/ +static void +get_sync_sensor_readings(void) +{ + int value; + + printf("-----------------------------------------\n"); + + value = batmon_sensor.value(BATMON_SENSOR_TYPE_TEMP); + printf("Bat: Temp=%d.%02d C (%08x)\n", value >> 2, + (value & 0x00000003) * 25, value); + + value = batmon_sensor.value(BATMON_SENSOR_TYPE_VOLT); + printf("Bat: Volt=%d mV\n", (value * 125) >> 5); + + return; +} +/*---------------------------------------------------------------------------*/ +static void +init_sensors(void) +{ +#if BOARD_SENSORTAG + SENSORS_ACTIVATE(reed_relay_sensor); +#endif + + SENSORS_ACTIVATE(batmon_sensor); +} +/*---------------------------------------------------------------------------*/ +static void +init_sensor_readings(void) +{ +#if BOARD_SENSORTAG + SENSORS_ACTIVATE(sht_21_sensor); + SENSORS_ACTIVATE(tmp_007_sensor); + SENSORS_ACTIVATE(opt_3001_sensor); + SENSORS_ACTIVATE(bmp_280_sensor); + + init_mpu_reading(NULL); +#endif +} +/*---------------------------------------------------------------------------*/ +static lpm_power_domain_lock_t lock; +/*---------------------------------------------------------------------------*/ +/* + * In order to maintain UART input operation: + * - Keep the uart clocked in sleep and deep sleep + * - Keep the serial PD on in deep sleep + */ +static void +keep_uart_on(void) +{ + /* Keep the serial PD on */ + lpm_pd_lock_obtain(&lock, PRCM_DOMAIN_SERIAL); + + /* Keep the UART clock on during Sleep and Deep Sleep */ + ti_lib_prcm_peripheral_sleep_enable(PRCM_PERIPH_UART0); + ti_lib_prcm_peripheral_deep_sleep_enable(PRCM_PERIPH_UART0); + ti_lib_prcm_load_set(); + while(!ti_lib_prcm_load_get()); +} +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(cc26xx_demo_process, ev, data) +{ + + PROCESS_BEGIN(); + + printf("CC26XX demo\n"); + + init_sensors(); + + /* Init the BLE advertisement daemon */ + cc26xx_rf_ble_beacond_config(0, BOARD_STRING); + cc26xx_rf_ble_beacond_start(); + + etimer_set(&et, CC26XX_DEMO_LOOP_INTERVAL); + get_sync_sensor_readings(); + init_sensor_readings(); + + keep_uart_on(); + + while(1) { + + PROCESS_YIELD(); + + if(ev == PROCESS_EVENT_TIMER) { + if(data == &et) { + leds_toggle(CC26XX_DEMO_LEDS_PERIODIC); + + get_sync_sensor_readings(); + + etimer_set(&et, CC26XX_DEMO_LOOP_INTERVAL); + } + } else if(ev == sensors_event) { + if(data == CC26XX_DEMO_SENSOR_1) { + printf("Left: Pin %d, press duration %d clock ticks\n", + (CC26XX_DEMO_SENSOR_1)->value(BUTTON_SENSOR_VALUE_STATE), + (CC26XX_DEMO_SENSOR_1)->value(BUTTON_SENSOR_VALUE_DURATION)); + + if((CC26XX_DEMO_SENSOR_1)->value(BUTTON_SENSOR_VALUE_DURATION) > + CLOCK_SECOND) { + printf("Long button press!\n"); + } + + leds_toggle(CC26XX_DEMO_LEDS_BUTTON); + } else if(data == CC26XX_DEMO_SENSOR_2) { + leds_on(CC26XX_DEMO_LEDS_REBOOT); + watchdog_reboot(); + } else if(data == CC26XX_DEMO_SENSOR_3) { + printf("Up\n"); + } else if(data == CC26XX_DEMO_SENSOR_4) { + printf("Down\n"); + } else if(data == CC26XX_DEMO_SENSOR_5) { +#if BOARD_SENSORTAG + if(buzzer_state()) { + buzzer_stop(); + } else { + buzzer_start(1000); + } + } else if(ev == sensors_event && data == &bmp_280_sensor) { + get_bmp_reading(); + } else if(ev == sensors_event && data == &opt_3001_sensor) { + get_light_reading(); + } else if(ev == sensors_event && data == &sht_21_sensor) { + get_sht_reading(); + } else if(ev == sensors_event && data == &tmp_007_sensor) { + get_tmp_reading(); + } else if(ev == sensors_event && data == &mpu_9250_sensor) { + get_mpu_reading(); +#else + printf("Sel: Pin %d, press duration %d clock ticks\n", + button_select_sensor.value(BUTTON_SENSOR_VALUE_STATE), + button_select_sensor.value(BUTTON_SENSOR_VALUE_DURATION)); +#endif + } + } else if(ev == serial_line_event_message) { + leds_toggle(CC26XX_DEMO_LEDS_SERIAL_IN); + } + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +/** + * @} + * @} + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/Makefile b/examples/cc26xx/cc26xx-web-demo/Makefile new file mode 100644 index 000000000..622156a4d --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/Makefile @@ -0,0 +1,24 @@ +DEFINES+=PROJECT_CONF_H=\"project-conf.h\" + +all: cc26xx-web-demo + +REST_RESOURCES_DIR = ./resources + +REST_RESOURCES_FILES += res-leds.c res-toggle-leds.c res-device.c +REST_RESOURCES_FILES += res-sensors.c res-ble-advd.c + +PROJECTDIRS += $(REST_RESOURCES_DIR) +PROJECT_SOURCEFILES += $(REST_RESOURCES_FILES) + +PROJECT_SOURCEFILES += cetic-6lbr-client.c coap-server.c net-uart.c mqtt-client.c +PROJECT_SOURCEFILES += httpd-simple.c + +CONTIKI_WITH_IPV6 = 1 + +# REST Engine shall use Erbium CoAP implementation +APPS += er-coap +APPS += rest-engine +APPS += mqtt + +CONTIKI=../../.. +include $(CONTIKI)/Makefile.include diff --git a/examples/cc26xx/cc26xx-web-demo/Makefile.target b/examples/cc26xx/cc26xx-web-demo/Makefile.target new file mode 100644 index 000000000..15890aa6a --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/Makefile.target @@ -0,0 +1 @@ +TARGET = srf06-cc26xx diff --git a/examples/cc26xx/cc26xx-web-demo/README.md b/examples/cc26xx/cc26xx-web-demo/README.md new file mode 100644 index 000000000..194336a56 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/README.md @@ -0,0 +1,169 @@ +CC26xx Web Demo Readme +====================== +This demo project combines a number of web-based applications aiming to +demonstrate the CC26xx capability. The applications are: + +* A network-based UART +* A client for [6lbr](http://cetic.github.io/6lbr/) +* A CoAP server +* An MQTT client +* A web server which can be used to display sensor readings but also to + configure MQTT functionality + +The example has been configured to run for both CC26xx-based boards: i) The +SensorTag 2.0 and ii) The Srf06EB with a CC26xx EM mounted on it. + +To build the example for the Srf, simply run `make`. To build for the tag, +run `make BOARD=sensortag`. Do not forget to `make clean` when switching +between the two platforms. + +You can disable some of those individual components by changing the respective +defines in `project-conf.h`. For instance, to disable the CoAP functionality, +set `#define CC26XX_WEB_DEMO_CONF_COAP_SERVER 0`. The web server cannot be +disabled, all other aforementioned applications can. + +Network UART (net-uart) +----------------------- +This example only makes sense if you are using the Srf or if you have taken +the sensortag out of its case and you have it connected over JTAG to the Srf. + +The net-uart does two things: + +* When you type a string to the UART console, the string will be sent verbatim + to a remote UDP port 7777 listener. This can be for example a netcat listener + on a linux or OS X PC: + `nc -6ulkw 1 7777` +* The net-uart also listens to UDP port 7777 and when it receives a string over + UDP, it will print it verbatim over UART. + +The example will initially send packets to a hard-coded IPv6 address. This can +be changed very easily by entering a different IPv6 address to the console. +Thus, when the serial input string is an IPv6 address, it will not be sent as +any other string would, but instead it will configure the example to send to a +different remote address. This new IPv6 address is not persistent across +device reboots. + +6lbr Client +----------- +This will periodically send a UDP packet to your 6lbr, containing network +information, which will be used by 6lbr to construct a network graph. To see +this in action, fire up a browser and navigate to the 6lbr web page. The +default address is http://[bbbb::100]. Once the page loads, click the 'sensors' +tab, as per the image below. + +![6lbr](img/6lbr-web.png) + +CoAP Server +----------- +For this functionality to work, you will need to install the +[Copper (Cu)](https://addons.mozilla.org/en-US/firefox/addon/copper-270430/) +addon to your browser. + +From the sensors tab in the 6lbr web page, click the 'coap' link in the line +corresponding to your CC26xx device. Once the addon fires up, select +".well-known/core" in the left pane and then hit the 'Get' button at the top. + +![CoAP Resources](img/coap-resources.png) + +The Device will respond with a list of all available CoAP resources. This list +will be different between the Srf and the SensorTag. The screenshot below shows +a (partial) list of resources exported by the SensorTag CoAP server. Select +a resource on the left pane and hit 'Get' to retrieve its value. Select +`lt/g` and hit 'Post' to toggle the green LED, `lt/r` for the red one. + +You can also use CoAP to enable/disable BLE advertisements! Select +`dev/ble_advd` and then hit the "Outgoing" button in the payload panel. Type in +the desired payload, which can be: + +* `mode=on|off` +* `name=` +* `interval=` + +or a combination of both delimited with an amp. For example, you can set as +payload `mode=on&name=My CC26xx Device 4&interval=5`. Once you have set the +payload, hit either the POST or PUT button. + +Bear in mind that you must set `name` at least once before enabling BLE +advertisements. If you fail to do so, the RF will refuse to enter BLE mode and +the CoAP engine will return 4.03 forbidden. The values of `name` and `interval` +persist across BLE on/off cycles, so you only have to set them once. The values +do _not_ persist through device powercycles. + +HTTPD +----- +Back on the 6lbr page, hit the 'web' link corresponding to your device. This +will take you to a web page served by the CC26xx. The HTTPD serves two pages: + +* index.html: Provides sensor readings and network information +* config.html: Can be used to configure the MQTT client (more below) + +In the navigation bar at the top there is also a third link, which will take +you directly to your device's page on IBM's quickstart service. + +IBM Quickstart / MQTT Client +---------------------------- +The MQTT client can be used to: + +* Publish sensor readings to an MQTT broker. +* Subscribe to a topic and as a result receive commands from an MQTT broker + +The device will try to connect to IBM's quickstart over NAT64, so you will +need a NAT64 gateway in your network to make this work. A guide on how to +setup NAT64 is out of scope here. If this is not an option for you, you can +configure the device to publish to a local MQTT broker over end-to-end IPv6. +See below on how to change the destination broker's address. + +By default the device will publish readings to IBM's quickstart service. The +publish messages include sensor readings but also some other information such +as device uptime in seconds and a message sequence number. Click the "IBM +Quickstart" link in the web page to go directly to your device's page +on Quickstart. After a few seconds, you will see something like this: + +![A SensorTag on IBM Quickstart](img/quickstart-sensortag.png) + +Sensor readings are only published if they have changed since the previous +reading (BatMon is an exception and always gets published). Additionally, you +can turn on/off individual readings from the config.html web page, as per the +figure below. + +![Sensor Readings Configuration](img/sensor-readings-config.png) + +Some of the MQTT client functionality can be configured even further: + +* You can change the broker IP and port. This is useful if you want to use your + own MQTT broker instead of IBM's quickstart. The example has been tested + successfully with [mosquitto](http://mosquitto.org/) +* You can change the publish interval. Recommended values are 10secs or higher. + You will not be allowed to set this to anything less than 5 seconds. +* If you want to use IBM's cloud service with a registered device, change + 'Org ID' and provide an 'Auth Token', which acts as a 'password', but bear in + mind that it gets transported in clear text, both over the web configuration + page as well as inside MQTT messages. +* The remaining configuration options are related to the content of MQTT + messages and in general you won't have to modify them. + +For the SensorTag, changes to the MQTT configuration get saved in external +flash and persist across device restarts. The same does not hold true for +Srf+EM builds. + +You can also subscribe to topics and receive commands, but this will only +work if you use "Org ID" != 'quickstart'. Thus, if you provide a different +Org ID (do not forget the auth token!), the device will subscribe to: + +`iot-2/cmd/+/fmt/json` + +You can then use this to toggle LEDs or to turn the buzzer on and off. +The buzzer is only available on the SensorTag. To do this, you can for example +use mosquitto client to publish to `iot-2/cmd/leds/fmt/json`. So, to turn +the buzzer on, you would do this: + +`mosquitto_pub -h -m "1" -t iot-2/cmd/buzz/fmt/json` + +Where `broker IP` should be replaced with the IP address of your mosquitto +broker (the one where you device has subscribed). Replace `-m "1'` with `-m "0"` +to turn the buzzer back off. Replace `buzz` with `leds` in the topic to change +the state of the LED. + +Bear in mind that, even though the topic suggests that messages are of json +format, they are in fact not. This was done in order to avoid linking a json +parser into the firmware. diff --git a/examples/cc26xx/cc26xx-web-demo/cc26xx-web-demo.c b/examples/cc26xx/cc26xx-web-demo/cc26xx-web-demo.c new file mode 100644 index 000000000..91f921b9d --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/cc26xx-web-demo.c @@ -0,0 +1,893 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * Main module for the CC26XX web demo. Activates on-device resources, + * takes sensor readings periodically and caches them for all other modules + * to use. + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "contiki-net.h" +#include "rest-engine.h" +#include "board-peripherals.h" +#include "lib/sensors.h" +#include "lib/list.h" +#include "sys/process.h" +#include "button-sensor.h" +#include "batmon-sensor.h" +#include "httpd-simple.h" +#include "cc26xx-web-demo.h" +#include "mqtt-client.h" +#include "coap-server.h" + +#include +#include +#include +/*---------------------------------------------------------------------------*/ +PROCESS_NAME(cetic_6lbr_client_process); +PROCESS(cc26xx_web_demo_process, "CC26XX Web Demo"); +/*---------------------------------------------------------------------------*/ +/* + * Update sensor readings in a staggered fashion every SENSOR_READING_PERIOD + * ticks + a random interval between 0 and SENSOR_READING_RANDOM ticks + */ +#define SENSOR_READING_PERIOD (CLOCK_SECOND * 20) +#define SENSOR_READING_RANDOM (CLOCK_SECOND << 4) + +struct ctimer batmon_timer; + +#if BOARD_SENSORTAG +struct ctimer bmp_timer, sht_timer, tmp_timer, opt_timer, mpu_timer; +#endif +/*---------------------------------------------------------------------------*/ +/* Provide visible feedback via LEDS while searching for a network */ +#define NO_NET_LED_DURATION (CC26XX_WEB_DEMO_NET_CONNECT_PERIODIC >> 1) + +static struct etimer et; +static struct ctimer ct; +/*---------------------------------------------------------------------------*/ +process_event_t cc26xx_web_demo_publish_event; +process_event_t cc26xx_web_demo_config_loaded_event; +process_event_t cc26xx_web_demo_load_config_defaults; +/*---------------------------------------------------------------------------*/ +/* Saved settings on flash: store, offset, magic */ +#define CONFIG_FLASH_OFFSET 0 +#define CONFIG_MAGIC 0xCC265001 + +cc26xx_web_demo_config_t cc26xx_web_demo_config; +/*---------------------------------------------------------------------------*/ +/* A cache of sensor values. Updated periodically or upon key press */ +LIST(sensor_list); +/*---------------------------------------------------------------------------*/ +/* The objects representing sensors used in this demo */ +#define DEMO_SENSOR(name, type, descr, xml_element, form_field, units) \ + cc26xx_web_demo_sensor_reading_t name##_reading = \ + { NULL, 0, 0, descr, xml_element, form_field, units, type, 1, 1 } + +/* CC26xx sensors */ +DEMO_SENSOR(batmon_temp, CC26XX_WEB_DEMO_SENSOR_BATMON_TEMP, + "Battery Temp", "battery-temp", "batmon_temp", + CC26XX_WEB_DEMO_UNIT_TEMP); +DEMO_SENSOR(batmon_volt, CC26XX_WEB_DEMO_SENSOR_BATMON_VOLT, + "Battery Volt", "battery-volt", "batmon_volt", + CC26XX_WEB_DEMO_UNIT_VOLT); + +/* Sensortag sensors */ +#if BOARD_SENSORTAG +DEMO_SENSOR(bmp_pres, CC26XX_WEB_DEMO_SENSOR_BMP_PRES, + "Air Pressure", "air-pressure", "bmp_pres", + CC26XX_WEB_DEMO_UNIT_PRES); +DEMO_SENSOR(bmp_temp, CC26XX_WEB_DEMO_SENSOR_BMP_TEMP, + "Air Temp", "air-temp", "bmp_temp", + CC26XX_WEB_DEMO_UNIT_TEMP); +DEMO_SENSOR(sht_temp, CC26XX_WEB_DEMO_SENSOR_SHT_TEMP, + "SHT Temp", "sht-temp", "sht_temp", + CC26XX_WEB_DEMO_UNIT_TEMP); +DEMO_SENSOR(sht_hum, CC26XX_WEB_DEMO_SENSOR_SHT_HUMIDITY, + "SHT Humidity", "sht-humidity", "sht_hum", + CC26XX_WEB_DEMO_UNIT_HUMIDITY); +DEMO_SENSOR(tmp_amb, CC26XX_WEB_DEMO_SENSOR_TMP_AMBIENT, + "Ambient Temp", "ambient-temp", "tmp_amb", + CC26XX_WEB_DEMO_UNIT_TEMP); +DEMO_SENSOR(tmp_obj, CC26XX_WEB_DEMO_SENSOR_TMP_OBJECT, + "Object Temp", "object-temp", "tmp_obj", + CC26XX_WEB_DEMO_UNIT_TEMP); +DEMO_SENSOR(opt, CC26XX_WEB_DEMO_SENSOR_OPT_LIGHT, + "Light", "light", "light", + CC26XX_WEB_DEMO_UNIT_LIGHT); + +/* MPU Readings */ +DEMO_SENSOR(mpu_acc_x, CC26XX_WEB_DEMO_SENSOR_MPU_ACC_X, + "Acc X", "acc-x", "acc_x", + CC26XX_WEB_DEMO_UNIT_ACC); +DEMO_SENSOR(mpu_acc_y, CC26XX_WEB_DEMO_SENSOR_MPU_ACC_Y, + "Acc Y", "acc-y", "acc_y", + CC26XX_WEB_DEMO_UNIT_ACC); +DEMO_SENSOR(mpu_acc_z, CC26XX_WEB_DEMO_SENSOR_MPU_ACC_Z, + "Acc Z", "acc-z", "acc_z", + CC26XX_WEB_DEMO_UNIT_ACC); + +DEMO_SENSOR(mpu_gyro_x, CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_X, + "Gyro X", "gyro-x", "gyro_x", + CC26XX_WEB_DEMO_UNIT_GYRO); +DEMO_SENSOR(mpu_gyro_y, CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_Y, + "Gyro Y", "gyro-y", "gyro_y", + CC26XX_WEB_DEMO_UNIT_GYRO); +DEMO_SENSOR(mpu_gyro_z, CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_Z, + "Gyro Z", "gyro-z", "gyro_Z", + CC26XX_WEB_DEMO_UNIT_GYRO); +#endif +/*---------------------------------------------------------------------------*/ +#if BOARD_SENSORTAG +static void init_bmp_reading(void *data); +static void init_light_reading(void *data); +static void init_sht_reading(void *data); +static void init_tmp_reading(void *data); +static void init_mpu_reading(void *data); +#endif +/*---------------------------------------------------------------------------*/ +static void +publish_led_off(void *d) +{ + leds_off(CC26XX_WEB_DEMO_STATUS_LED); +} +/*---------------------------------------------------------------------------*/ +static void +save_config() +{ + /* Dump current running config to flash */ +#if BOARD_SENSORTAG + int rv; + cc26xx_web_demo_sensor_reading_t *reading = NULL; + + rv = ext_flash_open(); + + if(!rv) { + printf("Could not open flash to save config\n"); + ext_flash_close(); + return; + } + + rv = ext_flash_erase(CONFIG_FLASH_OFFSET, sizeof(cc26xx_web_demo_config_t)); + + if(!rv) { + printf("Error erasing flash\n"); + } else { + cc26xx_web_demo_config.magic = CONFIG_MAGIC; + cc26xx_web_demo_config.len = sizeof(cc26xx_web_demo_config_t); + cc26xx_web_demo_config.sensors_bitmap = 0; + + for(reading = list_head(sensor_list); + reading != NULL; + reading = list_item_next(reading)) { + if(reading->publish) { + cc26xx_web_demo_config.sensors_bitmap |= (1 << reading->type); + } + } + + rv = ext_flash_write(CONFIG_FLASH_OFFSET, sizeof(cc26xx_web_demo_config_t), + (uint8_t *)&cc26xx_web_demo_config); + if(!rv) { + printf("Error saving config\n"); + } + } + + ext_flash_close(); +#endif +} +/*---------------------------------------------------------------------------*/ +static void +load_config() +{ +#if BOARD_SENSORTAG + /* Read from flash into a temp buffer */ + cc26xx_web_demo_config_t tmp_cfg; + cc26xx_web_demo_sensor_reading_t *reading = NULL; + + int rv = ext_flash_open(); + + if(!rv) { + printf("Could not open flash to load config\n"); + ext_flash_close(); + return; + } + + rv = ext_flash_read(CONFIG_FLASH_OFFSET, sizeof(tmp_cfg), + (uint8_t *)&tmp_cfg); + + ext_flash_close(); + + if(!rv) { + printf("Error loading config\n"); + return; + } + + if(tmp_cfg.magic == CONFIG_MAGIC && tmp_cfg.len == sizeof(tmp_cfg)) { + memcpy(&cc26xx_web_demo_config, &tmp_cfg, sizeof(cc26xx_web_demo_config)); + } + + for(reading = list_head(sensor_list); + reading != NULL; + reading = list_item_next(reading)) { + if(cc26xx_web_demo_config.sensors_bitmap & (1 << reading->type)) { + reading->publish = 1; + } else { + reading->publish = 0; + snprintf(reading->converted, CC26XX_WEB_DEMO_CONVERTED_LEN, "\"N/A\""); + } + } +#endif +} +/*---------------------------------------------------------------------------*/ +/* Don't start everything here, we need to dictate order of initialisation */ +AUTOSTART_PROCESSES(&cc26xx_web_demo_process); +/*---------------------------------------------------------------------------*/ +int +cc26xx_web_demo_ipaddr_sprintf(char *buf, uint8_t buf_len, + const uip_ipaddr_t *addr) +{ + uint16_t a; + uint8_t len = 0; + int i, f; + for(i = 0, f = 0; i < sizeof(uip_ipaddr_t); i += 2) { + a = (addr->u8[i] << 8) + addr->u8[i + 1]; + if(a == 0 && f >= 0) { + if(f++ == 0) { + len += snprintf(&buf[len], buf_len - len, "::"); + } + } else { + if(f > 0) { + f = -1; + } else if(i > 0) { + len += snprintf(&buf[len], buf_len - len, ":"); + } + len += snprintf(&buf[len], buf_len - len, "%x", a); + } + } + + return len; +} +/*---------------------------------------------------------------------------*/ +const cc26xx_web_demo_sensor_reading_t * +cc26xx_web_demo_sensor_lookup(int sens_type) +{ + cc26xx_web_demo_sensor_reading_t *reading = NULL; + + for(reading = list_head(sensor_list); + reading != NULL; + reading = list_item_next(reading)) { + if(reading->type == sens_type) { + return reading; + } + } + + return NULL; +} +/*---------------------------------------------------------------------------*/ +const cc26xx_web_demo_sensor_reading_t * +cc26xx_web_demo_sensor_first() +{ + return list_head(sensor_list); +} +/*---------------------------------------------------------------------------*/ +void +cc26xx_web_demo_restore_defaults(void) +{ + cc26xx_web_demo_sensor_reading_t *reading = NULL; + + leds_on(LEDS_ALL); + + for(reading = list_head(sensor_list); + reading != NULL; + reading = list_item_next(reading)) { + reading->publish = 1; + } + +#if CC26XX_WEB_DEMO_MQTT_CLIENT + process_post_synch(&mqtt_client_process, + cc26xx_web_demo_load_config_defaults, NULL); +#endif + +#if CC26XX_WEB_DEMO_NET_UART + process_post_synch(&net_uart_process, cc26xx_web_demo_load_config_defaults, + NULL); +#endif + + save_config(); + + leds_off(LEDS_ALL); +} +/*---------------------------------------------------------------------------*/ +static int +defaults_post_handler(char *key, int key_len, char *val, int val_len) +{ + if(key_len != strlen("defaults") || + strncasecmp(key, "defaults", strlen("defaults")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + cc26xx_web_demo_restore_defaults(); + + return HTTPD_SIMPLE_POST_HANDLER_OK; +} +/*---------------------------------------------------------------------------*/ +static int +sensor_readings_handler(char *key, int key_len, char *val, int val_len) +{ + cc26xx_web_demo_sensor_reading_t *reading = NULL; + int rv; + + for(reading = list_head(sensor_list); + reading != NULL; + reading = list_item_next(reading)) { + if(key_len == strlen(reading->form_field) && + strncmp(reading->form_field, key, strlen(key)) == 0) { + + rv = atoi(val); + + /* Be pedantic: only accept 0 and 1, not just any non-zero value */ + if(rv == 0) { + reading->publish = 0; + snprintf(reading->converted, CC26XX_WEB_DEMO_CONVERTED_LEN, "\"N/A\""); + } else if(rv == 1) { + reading->publish = 1; + } else { + return HTTPD_SIMPLE_POST_HANDLER_ERROR; + } + + return HTTPD_SIMPLE_POST_HANDLER_OK; + } + } + + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; +} +/*---------------------------------------------------------------------------*/ +HTTPD_SIMPLE_POST_HANDLER(sensor, sensor_readings_handler); +HTTPD_SIMPLE_POST_HANDLER(defaults, defaults_post_handler); +/*---------------------------------------------------------------------------*/ +static void +get_batmon_reading(void *data) +{ + int value; + char *buf; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + if(batmon_temp_reading.publish) { + value = batmon_sensor.value(BATMON_SENSOR_TYPE_TEMP); + if(value != CC26XX_SENSOR_READING_ERROR) { + batmon_temp_reading.raw = value; + + buf = batmon_temp_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d.%02d", value >> 2, + (value & 0x00000003) * 25); + } + } + + if(batmon_volt_reading.publish) { + value = batmon_sensor.value(BATMON_SENSOR_TYPE_VOLT); + if(value != CC26XX_SENSOR_READING_ERROR) { + batmon_volt_reading.raw = value; + + buf = batmon_volt_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d", (value * 125) >> 5); + } + } + + ctimer_set(&batmon_timer, next, get_batmon_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +#if BOARD_SENSORTAG +/*---------------------------------------------------------------------------*/ +static void +compare_and_update(cc26xx_web_demo_sensor_reading_t *reading) +{ + if(reading->last == reading->raw) { + reading->changed = 0; + } else { + reading->last = reading->raw; + reading->changed = 1; + } +} +/*---------------------------------------------------------------------------*/ +static void +print_mpu_reading(int reading, char *buf) +{ + char *loc_buf = buf; + + if(reading < 0) { + sprintf(loc_buf, "-"); + reading = -reading; + loc_buf++; + } + + sprintf(loc_buf, "%d.%02d", reading / 100, reading % 100); +} +/*---------------------------------------------------------------------------*/ +static void +get_bmp_reading() +{ + int value; + char *buf; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + if(bmp_pres_reading.publish) { + value = bmp_280_sensor.value(BMP_280_SENSOR_TYPE_PRESS); + if(value != CC26XX_SENSOR_READING_ERROR) { + bmp_pres_reading.raw = value; + + compare_and_update(&bmp_pres_reading); + + buf = bmp_pres_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d.%02d", value / 100, + value % 100); + } + } + + if(bmp_temp_reading.publish) { + value = bmp_280_sensor.value(BMP_280_SENSOR_TYPE_TEMP); + if(value != CC26XX_SENSOR_READING_ERROR) { + bmp_temp_reading.raw = value; + + compare_and_update(&bmp_temp_reading); + + buf = bmp_temp_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d.%02d", value / 100, + value % 100); + } + } + + SENSORS_DEACTIVATE(bmp_280_sensor); + + ctimer_set(&bmp_timer, next, init_bmp_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +get_tmp_reading() +{ + int value; + char *buf; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + if(tmp_amb_reading.publish || tmp_obj_reading.publish) { + if(tmp_007_sensor.value(TMP_007_SENSOR_TYPE_ALL) == + CC26XX_SENSOR_READING_ERROR) { + + SENSORS_DEACTIVATE(tmp_007_sensor); + ctimer_set(&tmp_timer, next, init_tmp_reading, NULL); + } + } + + if(tmp_amb_reading.publish) { + value = tmp_007_sensor.value(TMP_007_SENSOR_TYPE_AMBIENT); + tmp_amb_reading.raw = value; + + compare_and_update(&tmp_amb_reading); + + buf = tmp_amb_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d.%03d", value / 1000, + value % 1000); + } + + if(tmp_obj_reading.publish) { + value = tmp_007_sensor.value(TMP_007_SENSOR_TYPE_OBJECT); + tmp_obj_reading.raw = value; + + compare_and_update(&tmp_obj_reading); + + buf = tmp_obj_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d.%03d", value / 1000, + value % 1000); + } + + SENSORS_DEACTIVATE(tmp_007_sensor); + + ctimer_set(&tmp_timer, next, init_tmp_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +get_sht_reading() +{ + int value; + char *buf; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + if(sht_temp_reading.publish) { + value = sht_21_sensor.value(SHT_21_SENSOR_TYPE_TEMP); + if(value != CC26XX_SENSOR_READING_ERROR) { + sht_temp_reading.raw = value; + + compare_and_update(&sht_temp_reading); + + buf = sht_temp_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d.%02d", value / 100, + value % 100); + } + } + + if(sht_hum_reading.publish) { + value = sht_21_sensor.value(SHT_21_SENSOR_TYPE_HUMIDITY); + if(value != CC26XX_SENSOR_READING_ERROR) { + sht_hum_reading.raw = value; + + compare_and_update(&sht_hum_reading); + + buf = sht_hum_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d.%02d", value / 100, + value % 100); + } + } + + ctimer_set(&sht_timer, next, init_sht_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +get_light_reading() +{ + int value; + char *buf; + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + + value = opt_3001_sensor.value(0); + + SENSORS_DEACTIVATE(opt_3001_sensor); + + if(value != CC26XX_SENSOR_READING_ERROR) { + opt_reading.raw = value; + + compare_and_update(&opt_reading); + + buf = opt_reading.converted; + memset(buf, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + snprintf(buf, CC26XX_WEB_DEMO_CONVERTED_LEN, "%d.%02d", value / 100, + value % 100); + } + + ctimer_set(&opt_timer, next, init_light_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +get_mpu_reading() +{ + clock_time_t next = SENSOR_READING_PERIOD + + (random_rand() % SENSOR_READING_RANDOM); + int raw; + + if(mpu_gyro_x_reading.publish) { + raw = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_GYRO_X); + if(raw != CC26XX_SENSOR_READING_ERROR) { + mpu_gyro_x_reading.raw = raw; + } + } + + if(mpu_gyro_y_reading.publish) { + raw = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_GYRO_Y); + if(raw != CC26XX_SENSOR_READING_ERROR) { + mpu_gyro_y_reading.raw = raw; + } + } + + if(mpu_gyro_z_reading.publish) { + raw = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_GYRO_Z); + if(raw != CC26XX_SENSOR_READING_ERROR) { + mpu_gyro_z_reading.raw = raw; + } + } + + if(mpu_acc_x_reading.publish) { + raw = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_ACC_X); + if(raw != CC26XX_SENSOR_READING_ERROR) { + mpu_acc_x_reading.raw = raw; + } + } + + if(mpu_acc_y_reading.publish) { + raw = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_ACC_Y); + if(raw != CC26XX_SENSOR_READING_ERROR) { + mpu_acc_y_reading.raw = raw; + } + } + + if(mpu_acc_z_reading.publish) { + raw = mpu_9250_sensor.value(MPU_9250_SENSOR_TYPE_ACC_Z); + if(raw != CC26XX_SENSOR_READING_ERROR) { + mpu_acc_z_reading.raw = raw; + } + } + + SENSORS_DEACTIVATE(mpu_9250_sensor); + + if(mpu_gyro_x_reading.publish) { + compare_and_update(&mpu_gyro_x_reading); + memset(mpu_gyro_x_reading.converted, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + print_mpu_reading(mpu_gyro_x_reading.raw, mpu_gyro_x_reading.converted); + } + + if(mpu_gyro_y_reading.publish) { + compare_and_update(&mpu_gyro_y_reading); + memset(mpu_gyro_y_reading.converted, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + print_mpu_reading(mpu_gyro_y_reading.raw, mpu_gyro_y_reading.converted); + } + + if(mpu_gyro_z_reading.publish) { + compare_and_update(&mpu_gyro_z_reading); + memset(mpu_gyro_z_reading.converted, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + print_mpu_reading(mpu_gyro_z_reading.raw, mpu_gyro_z_reading.converted); + } + + if(mpu_acc_x_reading.publish) { + compare_and_update(&mpu_acc_x_reading); + memset(mpu_acc_x_reading.converted, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + print_mpu_reading(mpu_acc_x_reading.raw, mpu_acc_x_reading.converted); + } + + if(mpu_acc_y_reading.publish) { + compare_and_update(&mpu_acc_y_reading); + memset(mpu_acc_y_reading.converted, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + print_mpu_reading(mpu_acc_y_reading.raw, mpu_acc_y_reading.converted); + } + + if(mpu_acc_z_reading.publish) { + compare_and_update(&mpu_acc_z_reading); + memset(mpu_acc_z_reading.converted, 0, CC26XX_WEB_DEMO_CONVERTED_LEN); + print_mpu_reading(mpu_acc_z_reading.raw, mpu_acc_z_reading.converted); + } + + /* We only use the single timer */ + ctimer_set(&mpu_timer, next, init_mpu_reading, NULL); +} +/*---------------------------------------------------------------------------*/ +static void +init_tmp_reading(void *data) +{ + if(tmp_amb_reading.publish || tmp_obj_reading.publish) { + SENSORS_ACTIVATE(tmp_007_sensor); + } else { + ctimer_set(&tmp_timer, CLOCK_SECOND, init_tmp_reading, NULL); + } +} +/*---------------------------------------------------------------------------*/ +static void +init_bmp_reading(void *data) +{ + if(bmp_pres_reading.publish || bmp_temp_reading.publish) { + SENSORS_ACTIVATE(bmp_280_sensor); + } else { + ctimer_set(&bmp_timer, CLOCK_SECOND, init_bmp_reading, NULL); + } +} +/*---------------------------------------------------------------------------*/ +static void +init_sht_reading(void *data) +{ + if(sht_hum_reading.publish || sht_temp_reading.publish) { + SENSORS_ACTIVATE(sht_21_sensor); + } else { + ctimer_set(&sht_timer, CLOCK_SECOND, init_sht_reading, NULL); + } +} +/*---------------------------------------------------------------------------*/ +static void +init_light_reading(void *data) +{ + if(opt_reading.publish) { + SENSORS_ACTIVATE(opt_3001_sensor); + } else { + ctimer_set(&opt_timer, CLOCK_SECOND, init_light_reading, NULL); + } +} +/*---------------------------------------------------------------------------*/ +static void +init_mpu_reading(void *data) +{ + int readings_bitmap = 0; + + if(mpu_acc_x_reading.publish || mpu_acc_y_reading.publish || + mpu_acc_z_reading.publish) { + readings_bitmap |= MPU_9250_SENSOR_TYPE_ACC; + } + + if(mpu_gyro_x_reading.publish || mpu_gyro_y_reading.publish || + mpu_gyro_z_reading.publish) { + readings_bitmap |= MPU_9250_SENSOR_TYPE_GYRO; + } + + if(readings_bitmap) { + mpu_9250_sensor.configure(SENSORS_ACTIVE, readings_bitmap); + } else { + ctimer_set(&mpu_timer, CLOCK_SECOND, init_mpu_reading, NULL); + } +} +#endif +/*---------------------------------------------------------------------------*/ +static void +init_sensor_readings(void) +{ + /* + * Make a first pass and get all initial sensor readings. This will also + * trigger periodic value updates + */ + get_batmon_reading(NULL); + +#if BOARD_SENSORTAG + init_bmp_reading(NULL); + init_light_reading(NULL); + init_sht_reading(NULL); + init_tmp_reading(NULL); + init_mpu_reading(NULL); +#endif /* BOARD_SENSORTAG */ + + return; +} +/*---------------------------------------------------------------------------*/ +static void +init_sensors(void) +{ + + list_add(sensor_list, &batmon_temp_reading); + list_add(sensor_list, &batmon_volt_reading); + SENSORS_ACTIVATE(batmon_sensor); + +#if BOARD_SENSORTAG + list_add(sensor_list, &bmp_pres_reading); + list_add(sensor_list, &bmp_temp_reading); + + list_add(sensor_list, &tmp_obj_reading); + list_add(sensor_list, &tmp_amb_reading); + + list_add(sensor_list, &opt_reading); + + list_add(sensor_list, &sht_hum_reading); + list_add(sensor_list, &sht_temp_reading); + + list_add(sensor_list, &mpu_acc_x_reading); + list_add(sensor_list, &mpu_acc_y_reading); + list_add(sensor_list, &mpu_acc_z_reading); + list_add(sensor_list, &mpu_gyro_x_reading); + list_add(sensor_list, &mpu_gyro_y_reading); + list_add(sensor_list, &mpu_gyro_z_reading); + + SENSORS_ACTIVATE(reed_relay_sensor); +#endif +} +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(cc26xx_web_demo_process, ev, data) +{ + PROCESS_BEGIN(); + + printf("CC26XX Web Demo Process\n"); + + init_sensors(); + + cc26xx_web_demo_publish_event = process_alloc_event(); + cc26xx_web_demo_config_loaded_event = process_alloc_event(); + cc26xx_web_demo_load_config_defaults = process_alloc_event(); + + /* Start all other (enabled) processes first */ + process_start(&httpd_simple_process, NULL); +#if CC26XX_WEB_DEMO_COAP_SERVER + process_start(&coap_server_process, NULL); +#endif + +#if CC26XX_WEB_DEMO_6LBR_CLIENT + process_start(&cetic_6lbr_client_process, NULL); +#endif + +#if CC26XX_WEB_DEMO_MQTT_CLIENT + process_start(&mqtt_client_process, NULL); +#endif + +#if CC26XX_WEB_DEMO_NET_UART + process_start(&net_uart_process, NULL); +#endif + + /* + * Now that processes have set their own config default values, set our + * own defaults and restore saved config from flash... + */ + cc26xx_web_demo_config.sensors_bitmap = 0xFFFFFFFF; /* all on by default */ + load_config(); + + /* + * Notify all other processes (basically the ones in this demo) that the + * configuration has been loaded from flash, in case they care + */ + process_post(PROCESS_BROADCAST, cc26xx_web_demo_config_loaded_event, NULL); + + init_sensor_readings(); + + httpd_simple_register_post_handler(&sensor_handler); + httpd_simple_register_post_handler(&defaults_handler); + + etimer_set(&et, CC26XX_WEB_DEMO_NET_CONNECT_PERIODIC); + + /* + * Update all sensor readings on a configurable sensors_event + * (e.g a button press / or reed trigger) + */ + while(1) { + if(ev == sensors_event && data == CC26XX_WEB_DEMO_SENSOR_READING_TRIGGER) { + if((CC26XX_WEB_DEMO_SENSOR_READING_TRIGGER)->value( + BUTTON_SENSOR_VALUE_DURATION) > CLOCK_SECOND * 5) { + printf("Restoring defaults!\n"); + cc26xx_web_demo_restore_defaults(); + } else { + init_sensor_readings(); + + process_post(PROCESS_BROADCAST, cc26xx_web_demo_publish_event, NULL); + } + } else if(ev == PROCESS_EVENT_TIMER && etimer_expired(&et)) { + if(uip_ds6_get_global(ADDR_PREFERRED) == NULL) { + leds_on(CC26XX_WEB_DEMO_STATUS_LED); + ctimer_set(&ct, NO_NET_LED_DURATION, publish_led_off, NULL); + etimer_set(&et, CC26XX_WEB_DEMO_NET_CONNECT_PERIODIC); + } + } else if(ev == httpd_simple_event_new_config) { + save_config(); +#if BOARD_SENSORTAG + } else if(ev == sensors_event && data == &bmp_280_sensor) { + get_bmp_reading(); + } else if(ev == sensors_event && data == &opt_3001_sensor) { + get_light_reading(); + } else if(ev == sensors_event && data == &sht_21_sensor) { + get_sht_reading(); + } else if(ev == sensors_event && data == &tmp_007_sensor) { + get_tmp_reading(); + } else if(ev == sensors_event && data == &mpu_9250_sensor) { + get_mpu_reading(); +#endif + } + + PROCESS_YIELD(); + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +/** + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/cc26xx-web-demo.h b/examples/cc26xx/cc26xx-web-demo/cc26xx-web-demo.h new file mode 100644 index 000000000..89bf91d2b --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/cc26xx-web-demo.h @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \addtogroup cc26xx-examples + * @{ + * + * \defgroup cc26xx-web-demo CC26xx Web Demo + * @{ + * + * An example demonstrating: + * * how to use a CC26XX-powered node in a deployment driven by a 6LBR + * * how to expose on-device sensors as CoAP resources + * * how to build a small web page which reports networking and sensory data + * * how to configure functionality through the aforementioned web page using + * HTTP POST requests + * * a network-based UART + * + * \file + * Main header file for the CC26XX web demo. + */ +/*---------------------------------------------------------------------------*/ +#ifndef CC26XX_WEB_DEMO_H_ +#define CC26XX_WEB_DEMO_H_ +/*---------------------------------------------------------------------------*/ +#include "dev/leds.h" +#include "sys/process.h" +#include "mqtt-client.h" +#include "net-uart.h" + +#include +/*---------------------------------------------------------------------------*/ +#ifdef CC26XX_WEB_DEMO_CONF_MQTT_CLIENT +#define CC26XX_WEB_DEMO_MQTT_CLIENT CC26XX_WEB_DEMO_CONF_MQTT_CLIENT +#else +#define CC26XX_WEB_DEMO_MQTT_CLIENT 1 +#endif + +#ifdef CC26XX_WEB_DEMO_CONF_6LBR_CLIENT +#define CC26XX_WEB_DEMO_6LBR_CLIENT CC26XX_WEB_DEMO_CONF_6LBR_CLIENT +#else +#define CC26XX_WEB_DEMO_6LBR_CLIENT 1 +#endif + +#ifdef CC26XX_WEB_DEMO_CONF_COAP_SERVER +#define CC26XX_WEB_DEMO_COAP_SERVER CC26XX_WEB_DEMO_CONF_COAP_SERVER +#else +#define CC26XX_WEB_DEMO_COAP_SERVER 1 +#endif + +#ifdef CC26XX_WEB_DEMO_CONF_NET_UART +#define CC26XX_WEB_DEMO_NET_UART CC26XX_WEB_DEMO_CONF_NET_UART +#else +#define CC26XX_WEB_DEMO_NET_UART 1 +#endif +/*---------------------------------------------------------------------------*/ +/* User configuration */ +/* Take a sensor reading on button press */ +#define CC26XX_WEB_DEMO_SENSOR_READING_TRIGGER &button_left_sensor + +/* Payload length of ICMPv6 echo requests used to measure RSSI with def rt */ +#define CC26XX_WEB_DEMO_ECHO_REQ_PAYLOAD_LEN 20 + +#if BOARD_SENSORTAG +/* Force an MQTT publish on sensor event */ +#define CC26XX_WEB_DEMO_MQTT_PUBLISH_TRIGGER &reed_relay_sensor +#else +#define CC26XX_WEB_DEMO_MQTT_PUBLISH_TRIGGER &button_right_sensor +#endif + +#define CC26XX_WEB_DEMO_STATUS_LED LEDS_GREEN +/*---------------------------------------------------------------------------*/ +/* A timeout used when waiting to connect to a network */ +#define CC26XX_WEB_DEMO_NET_CONNECT_PERIODIC (CLOCK_SECOND >> 3) +/*---------------------------------------------------------------------------*/ +/* Default configuration values */ +#define CC26XX_WEB_DEMO_DEFAULT_ORG_ID "quickstart" +#define CC26XX_WEB_DEMO_DEFAULT_TYPE_ID "cc26xx" +#define CC26XX_WEB_DEMO_DEFAULT_EVENT_TYPE_ID "status" +#define CC26XX_WEB_DEMO_DEFAULT_SUBSCRIBE_CMD_TYPE "+" +#define CC26XX_WEB_DEMO_DEFAULT_BROKER_PORT 1883 +#define CC26XX_WEB_DEMO_DEFAULT_PUBLISH_INTERVAL (30 * CLOCK_SECOND) +#define CC26XX_WEB_DEMO_DEFAULT_KEEP_ALIVE_TIMER 60 +#define CC26XX_WEB_DEMO_DEFAULT_RSSI_MEAS_INTERVAL (CLOCK_SECOND * 30) +/*---------------------------------------------------------------------------*/ +/* + * You normally won't have to change anything from here onwards unless you are + * extending the example + */ +/*---------------------------------------------------------------------------*/ +/* Sensor types */ +#define CC26XX_WEB_DEMO_SENSOR_BATMON_TEMP 0 +#define CC26XX_WEB_DEMO_SENSOR_BATMON_VOLT 1 +#define CC26XX_WEB_DEMO_SENSOR_BMP_PRES 2 +#define CC26XX_WEB_DEMO_SENSOR_BMP_TEMP 3 +#define CC26XX_WEB_DEMO_SENSOR_TMP_AMBIENT 4 +#define CC26XX_WEB_DEMO_SENSOR_TMP_OBJECT 5 +#define CC26XX_WEB_DEMO_SENSOR_SHT_TEMP 6 +#define CC26XX_WEB_DEMO_SENSOR_SHT_HUMIDITY 7 +#define CC26XX_WEB_DEMO_SENSOR_OPT_LIGHT 8 +#define CC26XX_WEB_DEMO_SENSOR_MPU_ACC_X 9 +#define CC26XX_WEB_DEMO_SENSOR_MPU_ACC_Y 10 +#define CC26XX_WEB_DEMO_SENSOR_MPU_ACC_Z 11 +#define CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_X 12 +#define CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_Y 13 +#define CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_Z 14 +/*---------------------------------------------------------------------------*/ +extern process_event_t cc26xx_web_demo_publish_event; +extern process_event_t cc26xx_web_demo_config_loaded_event; +extern process_event_t cc26xx_web_demo_load_config_defaults; +/*---------------------------------------------------------------------------*/ +#define CC26XX_WEB_DEMO_UNIT_TEMP "C" +#define CC26XX_WEB_DEMO_UNIT_VOLT "mV" +#define CC26XX_WEB_DEMO_UNIT_PRES "hPa" +#define CC26XX_WEB_DEMO_UNIT_HUMIDITY "%RH" +#define CC26XX_WEB_DEMO_UNIT_LIGHT "lux" +#define CC26XX_WEB_DEMO_UNIT_ACC "G" +#define CC26XX_WEB_DEMO_UNIT_GYRO "deg per sec" +/*---------------------------------------------------------------------------*/ +/* A data type for sensor readings, internally stored in a linked list */ +#define CC26XX_WEB_DEMO_CONVERTED_LEN 12 + +typedef struct cc26xx_web_demo_sensor_reading { + struct cc26xx_web_demo_sensor_reading *next; + int raw; + int last; + const char *descr; + const char *xml_element; + const char *form_field; + char *units; + uint8_t type; + uint8_t publish; + uint8_t changed; + char converted[CC26XX_WEB_DEMO_CONVERTED_LEN]; +} cc26xx_web_demo_sensor_reading_t; +/*---------------------------------------------------------------------------*/ +/* Global configuration */ +typedef struct cc26xx_web_demo_config_s { + uint32_t magic; + int len; + uint32_t sensors_bitmap; + mqtt_client_config_t mqtt_config; + net_uart_config_t net_uart; +} cc26xx_web_demo_config_t; + +extern cc26xx_web_demo_config_t cc26xx_web_demo_config; +/*---------------------------------------------------------------------------*/ +/** + * \brief Performs a lookup for a reading of a specific type of sensor + * \param sens_type CC26XX_WEB_DEMO_SENSOR_BATMON_TEMP... + * \return A pointer to the reading data structure or NULL + */ +const cc26xx_web_demo_sensor_reading_t *cc26xx_web_demo_sensor_lookup(int sens_type); + +/** + * \brief Returns the first available sensor reading + * \return A pointer to the reading data structure or NULL + */ +const cc26xx_web_demo_sensor_reading_t *cc26xx_web_demo_sensor_first(void); + +/** + * \brief Print an IPv6 address into a buffer + * \param buf A pointer to the buffer where this function will print the IPv6 + * address + * \param buf_len the length of the buffer + * \param addr A pointer to the IPv6 address + * \return The number of bytes written to the buffer + * + * It is the caller's responsibility to allocate enough space for buf + */ +int cc26xx_web_demo_ipaddr_sprintf(char *buf, uint8_t buf_len, + const uip_ipaddr_t *addr); + +/** + * \brief Resets the example to a default configuration + */ +void cc26xx_web_demo_restore_defaults(void); +/*---------------------------------------------------------------------------*/ +#endif /* CC26XX_WEB_DEMO_H_ */ +/*---------------------------------------------------------------------------*/ +/** + * @} + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/cetic-6lbr-client.c b/examples/cc26xx/cc26xx-web-demo/cetic-6lbr-client.c new file mode 100644 index 000000000..68a890390 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/cetic-6lbr-client.c @@ -0,0 +1,197 @@ +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * An implementation of a 6LBR UDP client. Is used to populate the 6LBR + * web server's 'sensors' tab + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "contiki-lib.h" +#include "contiki-net.h" +#include "net/rpl/rpl.h" +#include "net/ip/uip.h" + +#include +#include +/*---------------------------------------------------------------------------*/ +#define DEBUG 0 +#include "net/ip/uip-debug.h" +/*---------------------------------------------------------------------------*/ +#ifndef CETIC_6LBR_NODE_INFO_PORT +#define CETIC_6LBR_NODE_INFO_PORT 3000 +#endif + +#define MAX_PAYLOAD_LEN 40 +#define MSG_INTERVAL (60 * CLOCK_SECOND) +/*---------------------------------------------------------------------------*/ +static struct uip_udp_conn *client_conn = NULL; +static struct etimer et; +static uip_ip6addr_t dest_addr; +/*---------------------------------------------------------------------------*/ +PROCESS(cetic_6lbr_client_process, "6LBR Client Process"); +/*---------------------------------------------------------------------------*/ +static void +tcpip_handler(void) +{ + char *str; + + if(uip_newdata()) { + str = uip_appdata; + str[uip_datalen()] = '\0'; + PRINTF("Response from the server: '%s'\n", str); + } +} +/*---------------------------------------------------------------------------*/ +static char * +add_ipaddr(char *buf, const uip_ipaddr_t *addr) +{ + uint16_t a; + unsigned int i; + int f; + char *p = buf; + + for(i = 0, f = 0; i < sizeof(uip_ipaddr_t); i += 2) { + a = (addr->u8[i] << 8) + addr->u8[i + 1]; + if(a == 0 && f >= 0) { + if(f++ == 0) { + p += sprintf(p, "::"); + } + } else { + if(f > 0) { + f = -1; + } else if(i > 0) { + p += sprintf(p, ":"); + } + p += sprintf(p, "%04x", a); + } + } + return p; +} +/*---------------------------------------------------------------------------*/ +static void +timeout_handler(void) +{ + static int seq_id; + char buf[MAX_PAYLOAD_LEN]; + int i; + uip_ip6addr_t *globaladdr = NULL; + uint16_t dest_port = CETIC_6LBR_NODE_INFO_PORT; + int has_dest = 0; + rpl_dag_t *dag; + + uip_ds6_addr_t *addr_desc = uip_ds6_get_global(ADDR_PREFERRED); + if(addr_desc != NULL) { + globaladdr = &addr_desc->ipaddr; + dag = rpl_get_any_dag(); + if(dag) { + uip_ipaddr_copy(&dest_addr, globaladdr); + memcpy(&dest_addr.u8[8], &dag->dag_id.u8[8], sizeof(uip_ipaddr_t) / 2); + has_dest = 1; + } + } + + if(has_dest) { + if(client_conn == NULL) { + PRINTF("UDP-CLIENT: address destination: "); + PRINT6ADDR(&dest_addr); + PRINTF("\n"); + client_conn = udp_new(&dest_addr, UIP_HTONS(dest_port), NULL); + + if(client_conn != NULL) { + PRINTF("Created a connection with the server "); + PRINT6ADDR(&client_conn->ripaddr); + PRINTF(" local/remote port %u/%u\n", + UIP_HTONS(client_conn->lport), UIP_HTONS(client_conn->rport)); + } else { + PRINTF("Could not open connection\n"); + } + } else { + if(memcmp(&client_conn->ripaddr, &dest_addr, sizeof(uip_ipaddr_t)) != 0) { + PRINTF("UDP-CLIENT: new address destination: "); + PRINT6ADDR(&dest_addr); + PRINTF("\n"); + uip_udp_remove(client_conn); + client_conn = udp_new(&dest_addr, UIP_HTONS(dest_port), NULL); + if(client_conn != NULL) { + PRINTF("Created a connection with the server "); + PRINT6ADDR(&client_conn->ripaddr); + PRINTF(" local/remote port %u/%u\n", + UIP_HTONS(client_conn->lport), UIP_HTONS(client_conn->rport)); + } else { + PRINTF("Could not open connection\n"); + } + } + } + if(client_conn != NULL) { + PRINTF("Client sending to: "); + PRINT6ADDR(&client_conn->ripaddr); + i = sprintf(buf, "%d | ", ++seq_id); + dag = rpl_get_any_dag(); + if(dag && dag->instance->def_route) { + add_ipaddr(buf + i, &dag->instance->def_route->ipaddr); + } else { + sprintf(buf + i, "(null)"); + } + PRINTF(" (msg: %s)\n", buf); + uip_udp_packet_send(client_conn, buf, strlen(buf)); + } else { + PRINTF("No connection created\n"); + } + } else { + PRINTF("No address configured\n"); + } +} +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(cetic_6lbr_client_process, ev, data) +{ + + PROCESS_BEGIN(); + + printf("6LBR Client Process\n"); + + memset(&dest_addr, 0, sizeof(uip_ipaddr_t)); + + etimer_set(&et, MSG_INTERVAL); + while(1) { + PROCESS_YIELD(); + if(etimer_expired(&et)) { + timeout_handler(); + etimer_set(&et, MSG_INTERVAL); + } else if(ev == tcpip_event) { + tcpip_handler(); + } + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +/** + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/coap-server.c b/examples/cc26xx/cc26xx-web-demo/coap-server.c new file mode 100644 index 000000000..65d748707 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/coap-server.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * A CC26XX-specific CoAP server + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "contiki-net.h" +#include "rest-engine.h" +#include "board-peripherals.h" +#include "dev/cc26xx-rf.h" + +#include +#include +#include +/*---------------------------------------------------------------------------*/ +/* Common resources */ +extern resource_t res_leds; + +extern resource_t res_batmon_temp; +extern resource_t res_batmon_volt; + +extern resource_t res_device_sw; +extern resource_t res_device_hw; +extern resource_t res_device_uptime; +extern resource_t res_device_cfg_reset; + +#if CC26XX_RF_BLE_SUPPORT +extern resource_t res_ble_advd; +#endif + +/* Board-specific resources */ +#if BOARD_SENSORTAG +extern resource_t res_bmp280_temp; +extern resource_t res_bmp280_press; +extern resource_t res_tmp007_amb; +extern resource_t res_tmp007_obj; +extern resource_t res_sht21_temp; +extern resource_t res_sht21_hum; +extern resource_t res_opt3001_light; +extern resource_t res_mpu_acc_x; +extern resource_t res_mpu_acc_y; +extern resource_t res_mpu_acc_z; +extern resource_t res_mpu_gyro_x; +extern resource_t res_mpu_gyro_y; +extern resource_t res_mpu_gyro_z; +extern resource_t res_toggle_red; +extern resource_t res_toggle_green; +#else +extern resource_t res_toggle_red; +extern resource_t res_toggle_green; +extern resource_t res_toggle_orange; +extern resource_t res_toggle_yellow; +#endif +/*---------------------------------------------------------------------------*/ +const char *coap_server_not_found_msg = "Resource not found"; +const char *coap_server_supported_msg = "Supported:" + "text/plain," + "application/json," + "application/xml"; +/*---------------------------------------------------------------------------*/ +static void +start_board_resources(void) +{ +#if BOARD_SENSORTAG + rest_activate_resource(&res_bmp280_temp, "sen/bar/temp"); + rest_activate_resource(&res_bmp280_press, "sen/bar/pres"); + rest_activate_resource(&res_tmp007_amb, "sen/tmp/amb"); + rest_activate_resource(&res_tmp007_obj, "sen/tmp/obj"); + rest_activate_resource(&res_sht21_temp, "sen/sht/t"); + rest_activate_resource(&res_sht21_hum, "sen/sht/h"); + rest_activate_resource(&res_opt3001_light, "sen/opt/light"); + rest_activate_resource(&res_mpu_acc_x, "sen/mpu/acc/x"); + rest_activate_resource(&res_mpu_acc_y, "sen/mpu/acc/y"); + rest_activate_resource(&res_mpu_acc_z, "sen/mpu/acc/z"); + rest_activate_resource(&res_mpu_gyro_x, "sen/mpu/gyro/x"); + rest_activate_resource(&res_mpu_gyro_y, "sen/mpu/gyro/y"); + rest_activate_resource(&res_mpu_gyro_z, "sen/mpu/gyro/z"); + rest_activate_resource(&res_leds, "lt"); + rest_activate_resource(&res_toggle_green, "lt/g"); + rest_activate_resource(&res_toggle_red, "lt/r"); +#elif BOARD_SMARTRF06EB + rest_activate_resource(&res_leds, "lt"); + rest_activate_resource(&res_toggle_red, "lt/r"); + rest_activate_resource(&res_toggle_yellow, "lt/y"); + rest_activate_resource(&res_toggle_green, "lt/g"); + rest_activate_resource(&res_toggle_orange, "lt/o"); +#endif +} +/*---------------------------------------------------------------------------*/ +PROCESS(coap_server_process, "CC26XX CoAP Server"); +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(coap_server_process, ev, data) +{ + PROCESS_BEGIN(); + + printf("CC26XX CoAP Server\n"); + + /* Initialize the REST engine. */ + rest_init_engine(); + + rest_activate_resource(&res_batmon_temp, "sen/batmon/temp"); + rest_activate_resource(&res_batmon_volt, "sen/batmon/voltage"); + + rest_activate_resource(&res_device_hw, "dev/mdl/hw"); + rest_activate_resource(&res_device_sw, "dev/mdl/sw"); + rest_activate_resource(&res_device_uptime, "dev/uptime"); + rest_activate_resource(&res_device_cfg_reset, "dev/cfg_reset"); + +#if CC26XX_RF_BLE_SUPPORT + rest_activate_resource(&res_ble_advd, "dev/ble_advd"); +#endif + + start_board_resources(); + + /* Define application-specific events here. */ + while(1) { + PROCESS_WAIT_EVENT(); + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +/** + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/coap-server.h b/examples/cc26xx/cc26xx-web-demo/coap-server.h new file mode 100644 index 000000000..7399597c8 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/coap-server.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * Header file for the CC26xx web demo CoAP functionality + */ +/*---------------------------------------------------------------------------*/ +#include "sys/process.h" +/*---------------------------------------------------------------------------*/ +#ifndef COAP_SERVER_H_ +#define COAP_SERVER_H_ +/*---------------------------------------------------------------------------*/ +extern const char *coap_server_not_found_msg; +extern const char *coap_server_supported_msg; +/*---------------------------------------------------------------------------*/ +PROCESS_NAME(coap_server_process); +/*---------------------------------------------------------------------------*/ +#endif /* COAP_SERVER_H_ */ +/*---------------------------------------------------------------------------*/ +/** + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/httpd-simple.c b/examples/cc26xx/cc26xx-web-demo/httpd-simple.c new file mode 100644 index 000000000..281567acf --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/httpd-simple.c @@ -0,0 +1,1298 @@ +/* + * Copyright (c) 2010, Swedish Institute of Computer Science. + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * A simple web server which displays network and sensor information + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "httpd-simple.h" +#include "net/ipv6/uip-ds6-route.h" +#include "batmon-sensor.h" +#include "lib/sensors.h" +#include "lib/list.h" +#include "cc26xx-web-demo.h" +#include "mqtt-client.h" +#include "net-uart.h" + +#include +#include +#include +#include +#include +#include +#include +#include +/*---------------------------------------------------------------------------*/ +#define SEND_STRING(s, str) PSOCK_SEND(s, (uint8_t *)str, strlen(str)) +/*---------------------------------------------------------------------------*/ +#define CONNS 2 +#define CONTENT_LENGTH_MAX 256 +#define STATE_WAITING 0 +#define STATE_OUTPUT 1 +#define IPADDR_BUF_LEN 64 +/*---------------------------------------------------------------------------*/ +#define RETURN_CODE_OK 0 +#define RETURN_CODE_NF 1 /* Not Found */ +#define RETURN_CODE_SU 2 /* Service Unavailable */ +#define RETURN_CODE_BR 3 /* Bad Request */ +#define RETURN_CODE_LR 4 /* Length Required */ +#define RETURN_CODE_TL 5 /* Content Length too Large */ +/*---------------------------------------------------------------------------*/ +/* POST request machine states */ +#define PARSE_POST_STATE_INIT 0 +#define PARSE_POST_STATE_MORE 1 +#define PARSE_POST_STATE_READING_KEY 2 +#define PARSE_POST_STATE_READING_VAL 3 +#define PARSE_POST_STATE_ERROR 0xFFFFFFFF +/*---------------------------------------------------------------------------*/ +#define PARSE_POST_BUF_SIZES 64 + +/* Last byte always used to null terminate */ +#define PARSE_POST_MAX_POS (PARSE_POST_BUF_SIZES - 2) + +static char key[PARSE_POST_BUF_SIZES]; +static char val_escaped[PARSE_POST_BUF_SIZES]; +static char val[PARSE_POST_BUF_SIZES]; +static int key_len; +static int val_len; +static int state; +/*---------------------------------------------------------------------------*/ +/* Stringified min/max intervals */ +#define STRINGIFY(x) XSTR(x) +#define XSTR(x) #x + +#define RSSI_INT_MAX STRINGIFY(MQTT_CLIENT_RSSI_MEASURE_INTERVAL_MAX) +#define RSSI_INT_MIN STRINGIFY(MQTT_CLIENT_RSSI_MEASURE_INTERVAL_MIN) +#define PUB_INT_MAX STRINGIFY(MQTT_CLIENT_PUBLISH_INTERVAL_MAX) +#define PUB_INT_MIN STRINGIFY(MQTT_CLIENT_PUBLISH_INTERVAL_MIN) +/*---------------------------------------------------------------------------*/ +/* + * We can only handle a single POST request at a time. Since a second POST + * request cannot interrupt us while obtaining a lock, we don't really need + * this lock to be atomic. + * + * An HTTP connection will first request a lock before it starts processing + * a POST request. We maintain a global lock which is either NULL or points + * to the http conn which currently has the lock + */ +static struct httpd_state *lock; +/*---------------------------------------------------------------------------*/ +PROCESS(httpd_simple_process, "CC26XX Web Server"); +/*---------------------------------------------------------------------------*/ +#define ISO_nl 0x0A +#define ISO_space 0x20 +#define ISO_slash 0x2F +#define ISO_amp 0x26 +#define ISO_column 0x3A +#define ISO_equal 0x3D +/*---------------------------------------------------------------------------*/ +#define HTTP_200_OK "HTTP/1.0 200 OK\r\n" +#define HTTP_302_FO "HTTP/1.0 302 Found\r\n" +#define HTTP_400_BR "HTTP/1.0 400 Bad Request\r\n" +#define HTTP_404_NF "HTTP/1.0 404 Not Found\r\n" +#define HTTP_411_LR "HTTP/1.0 411 Length Required\r\n" +#define HTTP_413_TL "HTTP/1.0 413 Request Entity Too Large\r\n" +#define HTTP_503_SU "HTTP/1.0 503 Service Unavailable\r\n" +#define CONN_CLOSE "Connection: close\r\n" +/*---------------------------------------------------------------------------*/ +#define SECTION_TAG "div" +#define SECTION_OPEN "<" SECTION_TAG ">" +#define SECTION_CLOSE "" + +#define CONTENT_OPEN "
"
+#define CONTENT_CLOSE "
" +/*---------------------------------------------------------------------------*/ +#define REQUEST_TYPE_GET 1 +#define REQUEST_TYPE_POST 2 +/*---------------------------------------------------------------------------*/ +static const char *NOT_FOUND = "" + "
" + "

404 - file not found

" + "
" + "" + ""; +/*---------------------------------------------------------------------------*/ +/* Page template */ +static const char http_doctype[] = ""; +static const char http_header_200[] = HTTP_200_OK; +static const char http_header_302[] = HTTP_302_FO; +static const char http_header_400[] = HTTP_400_BR; +static const char http_header_404[] = HTTP_404_NF; +static const char http_header_411[] = HTTP_411_LR; +static const char http_header_413[] = HTTP_413_TL; +static const char http_header_503[] = HTTP_503_SU; +static const char http_get[] = "GET "; +static const char http_post[] = "POST "; +static const char http_index_html[] = "/index.html"; +static const char http_html_start[] = ""; +static const char *http_header_srv_str[] = { + "Server: Contiki, ", + BOARD_STRING "\r\n", + NULL +}; + +static const char *http_header_redir_location[] = { + "Location: /config.html\r\n", + NULL +}; + +static const char *http_header_con_close[] = { + CONN_CLOSE, + NULL +}; + +static const char *http_config_css[] = { + "", + NULL +}; +static const char http_head_charset[] = ""; +static const char http_title_start[] = ""; +static const char http_title_end[] = ""; +static const char http_head_end[] = ""; +static const char http_body_start[] = ""; +static const char http_bottom[] = ""; +/*---------------------------------------------------------------------------*/ +static const char http_content_type_html[] = "text/html"; +static const char http_content_type_plain[] = "text/plain"; +/*---------------------------------------------------------------------------*/ +/* For the config page */ +static const char config_div_left[] = "
"; +static const char config_div_right[] = "
"; +static const char config_div_close[] = "
"; +/*---------------------------------------------------------------------------*/ +typedef struct page { + struct page *next; + char *filename; + char *title; +} page_t; + +static page_t http_index_page = { + NULL, + "index.html", + "Index", +}; + +static page_t http_dev_cfg_page = { + NULL, + "config.html", + "Device Config", +}; + +#if CC26XX_WEB_DEMO_NET_UART +static page_t http_net_cfg_page = { + NULL, + "net.html", + "Net-UART Config", +}; +#endif + +#if CC26XX_WEB_DEMO_MQTT_CLIENT +static page_t http_mqtt_cfg_page = { + NULL, + "mqtt.html", + "MQTT/IBM Cloud Config", +}; +#endif +/*---------------------------------------------------------------------------*/ +#define IBM_QUICKSTART_LINK_LEN 128 +static char http_mqtt_a[IBM_QUICKSTART_LINK_LEN]; +/*---------------------------------------------------------------------------*/ +static uint16_t numtimes; +static const httpd_simple_post_handler_t *handler; +/*---------------------------------------------------------------------------*/ +static uint8_t config_ok; +process_event_t httpd_simple_event_new_config; +/*---------------------------------------------------------------------------*/ +struct httpd_state; +typedef char (*httpd_simple_script_t)(struct httpd_state *s); + +struct httpd_state { + char buf[HTTPD_SIMPLE_MAIN_BUF_SIZE]; + char tmp_buf[TMP_BUF_SIZE]; + struct timer timer; + struct psock sin, sout; + int blen; + const char **ptr; + const cc26xx_web_demo_sensor_reading_t *reading; + const page_t *page; + uip_ds6_route_t *r; + uip_ds6_nbr_t *nbr; + httpd_simple_script_t script; + int content_length; + int tmp_buf_len; + int tmp_buf_copied; + char filename[HTTPD_PATHLEN]; + char inputbuf[HTTPD_INBUF_LEN]; + struct pt outputpt; + struct pt generate_pt; + struct pt top_matter_pt; + char state; + char request_type; + char return_code; +}; +/*---------------------------------------------------------------------------*/ +LIST(post_handlers); +LIST(pages_list); +MEMB(conns, struct httpd_state, CONNS); +/*---------------------------------------------------------------------------*/ +#define HEX_TO_INT(x) (isdigit(x) ? x - '0' : x - 'W') +static size_t +url_unescape(const char *src, size_t srclen, char *dst, size_t dstlen) +{ + size_t i, j; + int a, b; + + for(i = j = 0; i < srclen && j < dstlen - 1; i++, j++) { + if(src[i] == '%' && isxdigit(*(unsigned char *)(src + i + 1)) + && isxdigit(*(unsigned char *)(src + i + 2))) { + a = tolower(*(unsigned char *)(src + i + 1)); + b = tolower(*(unsigned char *)(src + i + 2)); + dst[j] = ((HEX_TO_INT(a) << 4) | HEX_TO_INT(b)) & 0xff; + i += 2; + } else if(src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + + dst[j] = '\0'; + + return i == srclen; +} +/*---------------------------------------------------------------------------*/ +void +httpd_simple_register_post_handler(httpd_simple_post_handler_t *h) +{ + list_add(post_handlers, h); +} +/*---------------------------------------------------------------------------*/ +static void +get_neighbour_state_text(char *buf, uint8_t state) +{ + switch(state) { + case NBR_INCOMPLETE: + memcpy(buf, "INCOMPLETE", strlen("INCOMPLETE")); + break; + case NBR_REACHABLE: + memcpy(buf, "REACHABLE", strlen("REACHABLE")); + break; + case NBR_STALE: + memcpy(buf, "STALE", strlen("STALE")); + break; + case NBR_DELAY: + memcpy(buf, "DELAY", strlen("DELAY")); + break; + case NBR_PROBE: + memcpy(buf, "NBR_PROBE", strlen("NBR_PROBE")); + break; + } +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(enqueue_chunk(struct httpd_state *s, uint8_t immediate, + const char *format, ...)) +{ + va_list ap; + + PSOCK_BEGIN(&s->sout); + + va_start(ap, format); + + s->tmp_buf_len = vsnprintf(s->tmp_buf, TMP_BUF_SIZE, format, ap); + + va_end(ap); + + if(s->blen + s->tmp_buf_len < HTTPD_SIMPLE_MAIN_BUF_SIZE) { + /* Enough space for the entire chunk. Copy over */ + memcpy(&s->buf[s->blen], s->tmp_buf, s->tmp_buf_len); + s->blen += s->tmp_buf_len; + } else { + memcpy(&s->buf[s->blen], s->tmp_buf, HTTPD_SIMPLE_MAIN_BUF_SIZE - s->blen); + s->tmp_buf_copied = HTTPD_SIMPLE_MAIN_BUF_SIZE - s->blen; + s->blen = HTTPD_SIMPLE_MAIN_BUF_SIZE; + PSOCK_SEND(&s->sout, (uint8_t *)s->buf, s->blen); + s->blen = 0; + if(s->tmp_buf_copied < s->tmp_buf_len) { + memcpy(s->buf, &s->tmp_buf[s->tmp_buf_copied], + s->tmp_buf_len - s->tmp_buf_copied); + s->blen += s->tmp_buf_len - s->tmp_buf_copied; + } + } + + if(immediate != 0 && s->blen > 0) { + PSOCK_SEND(&s->sout, (uint8_t *)s->buf, s->blen); + s->blen = 0; + } + + PSOCK_END(&s->sout); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(generate_top_matter(struct httpd_state *s, const char *title, + const char **css)) +{ + + PT_BEGIN(&s->top_matter_pt); + + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_doctype)); + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_html_start)); + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_title_start)); + + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, title)); + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_title_end)); + + if(css != NULL) { + for(s->ptr = css; *(s->ptr) != NULL; s->ptr++) { + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, *(s->ptr))); + } + } + + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_head_charset)); + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_head_end)); + PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_body_start)); + + /* Links */ + PT_WAIT_THREAD(&s->top_matter_pt, + enqueue_chunk(s, 0, SECTION_OPEN "

")); + + s->page = list_head(pages_list); + PT_WAIT_THREAD(&s->top_matter_pt, + enqueue_chunk(s, 0, "[ %s ]", + s->page->filename, s->page->title)); + + for(s->page = s->page->next; s->page != NULL; s->page = s->page->next) { + PT_WAIT_THREAD(&s->top_matter_pt, + enqueue_chunk(s, 0, " | [ %s ]", + s->page->filename, s->page->title)); + } + +#if CC26XX_WEB_DEMO_MQTT_CLIENT + PT_WAIT_THREAD(&s->top_matter_pt, + enqueue_chunk(s, 0, " | %s", http_mqtt_a)); +#endif + PT_WAIT_THREAD(&s->top_matter_pt, + enqueue_chunk(s, 0, "

" SECTION_CLOSE)); + + PT_END(&s->top_matter_pt); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(generate_index(struct httpd_state *s)) +{ + char ipaddr_buf[IPADDR_BUF_LEN]; /* Intentionally on stack */ + + PT_BEGIN(&s->generate_pt); + + /* Generate top matter (doctype, title, nav links etc) */ + PT_WAIT_THREAD(&s->generate_pt, + generate_top_matter(s, http_index_page.title, NULL)); + + /* ND Cache */ + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, SECTION_OPEN "Neighbors" CONTENT_OPEN)); + + for(s->nbr = nbr_table_head(ds6_neighbors); s->nbr != NULL; + s->nbr = nbr_table_next(ds6_neighbors, s->nbr)) { + + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "\n")); + + memset(ipaddr_buf, 0, IPADDR_BUF_LEN); + cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, &s->nbr->ipaddr); + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf)); + + memset(ipaddr_buf, 0, IPADDR_BUF_LEN); + get_neighbour_state_text(ipaddr_buf, s->nbr->state); + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, " %s", ipaddr_buf)); + } + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE)); + + /* Default Route */ + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + SECTION_OPEN "Default Route" CONTENT_OPEN)); + + memset(ipaddr_buf, 0, IPADDR_BUF_LEN); + cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, + uip_ds6_defrt_choose()); + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE)); + + /* Routes */ + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, SECTION_OPEN "Routes" CONTENT_OPEN)); + + for(s->r = uip_ds6_route_head(); s->r != NULL; + s->r = uip_ds6_route_next(s->r)) { + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "\n")); + + memset(ipaddr_buf, 0, IPADDR_BUF_LEN); + cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, &s->r->ipaddr); + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, " / %u via ", s->r->length)); + + memset(ipaddr_buf, 0, IPADDR_BUF_LEN); + cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, + uip_ds6_route_nexthop(s->r)); + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + ", lifetime=%lus", s->r->state.lifetime)); + } + + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, + CONTENT_CLOSE SECTION_CLOSE)); + + /* Sensors */ + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, SECTION_OPEN "Sensors" CONTENT_OPEN)); + + for(s->reading = cc26xx_web_demo_sensor_first(); + s->reading != NULL; s->reading = s->reading->next) { + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "\n%s = %s %s", s->reading->descr, + s->reading->publish ? s->reading->converted : "N/A", + s->reading->units)); + } + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE)); + + /* Footer */ + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_OPEN)); + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "Page hits: %u
", + ++numtimes)); + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "Uptime: %lu secs
", + clock_seconds())); + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_CLOSE)); + + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom)); + + PT_END(&s->generate_pt); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(generate_config(struct httpd_state *s)) +{ + PT_BEGIN(&s->generate_pt); + + /* Generate top matter (doctype, title, nav links etc) */ + PT_WAIT_THREAD(&s->generate_pt, + generate_top_matter(s, http_dev_cfg_page.title, + http_config_css)); + + /* Sensor Settings */ + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "

Sensors

")); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "
generate_pt, + enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); + + for(s->reading = cc26xx_web_demo_sensor_first(); + s->reading != NULL; s->reading = s->reading->next) { + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%s%s:%s%s", config_div_left, + s->reading->descr, config_div_close, + config_div_right)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "generate_pt, + enqueue_chunk(s, 0, "title=\"On\" name=\"%s\"%s>", + s->reading->form_field, + s->reading->publish ? " Checked" : "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "generate_pt, + enqueue_chunk(s, 0, "title=\"Off\" name=\"%s\"%s>%s", + s->reading->form_field, + s->reading->publish ? "" : " Checked", + config_div_close)); + } + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "
")); + + /* Actions */ + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "

Actions

")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "
generate_pt, + enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "
")); + + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom)); + + PT_END(&s->generate_pt); +} +/*---------------------------------------------------------------------------*/ +#if CC26XX_WEB_DEMO_MQTT_CLIENT +static +PT_THREAD(generate_mqtt_config(struct httpd_state *s)) +{ + PT_BEGIN(&s->generate_pt); + + /* Generate top matter (doctype, title, nav links etc) */ + PT_WAIT_THREAD(&s->generate_pt, + generate_top_matter(s, http_mqtt_cfg_page.title, + http_config_css)); + + /* MQTT client settings */ + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "

%s

", http_mqtt_cfg_page.title)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "
generate_pt, + enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sType ID:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%s\" ", + cc26xx_web_demo_config.mqtt_config.type_id)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "name=\"type_id\">%s", config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sOrg ID:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%s\" ", + cc26xx_web_demo_config.mqtt_config.org_id)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "name=\"org_id\">%s", config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sAuth Token:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"\" ")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "name=\"auth_token\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sCommand Type:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%s\" ", + cc26xx_web_demo_config.mqtt_config.cmd_type)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "name=\"cmd_type\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sEvent Type ID:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%s\" ", + cc26xx_web_demo_config.mqtt_config.event_type_id)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "name=\"event_type_id\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sInterval (secs):%s", + config_div_left, config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%lu\" ", + (clock_time_t) + (cc26xx_web_demo_config.mqtt_config.pub_interval + / CLOCK_SECOND))); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "min=\"" PUB_INT_MIN "\" " + "max=\"" PUB_INT_MAX "\" " + "name=\"interval\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sBroker IP:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%s\" ", + cc26xx_web_demo_config.mqtt_config.broker_ip)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "name=\"broker_ip\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sBroker Port:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%d\" ", + cc26xx_web_demo_config.mqtt_config.broker_port)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "min=\"1\" max=\"65535\" " + "name=\"broker_port\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sRSSI Interval (secs):%s", + config_div_left, config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%lu\" ", + (clock_time_t) + (cc26xx_web_demo_config.mqtt_config.def_rt_ping_interval + / CLOCK_SECOND))); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "min=\"" RSSI_INT_MIN "\" " + "max=\"" RSSI_INT_MAX "\" " + "name=\"ping_interval\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "
")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "
generate_pt, + enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "
")); + + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom)); + + PT_END(&s->generate_pt); +} +#endif +/*---------------------------------------------------------------------------*/ +#if CC26XX_WEB_DEMO_NET_UART +static +PT_THREAD(generate_net_uart_config(struct httpd_state *s)) +{ + + PT_BEGIN(&s->generate_pt); + + /* Generate top matter (doctype, title, nav links etc) */ + PT_WAIT_THREAD(&s->generate_pt, + generate_top_matter(s, http_net_cfg_page.title, + http_config_css)); + + /* Net-UART settings */ + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "

%s

", http_net_cfg_page.title)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "
generate_pt, + enqueue_chunk(s, 0, "method=\"post\" enctype=\"")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" ")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">")); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sRemote IPv6:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%s\" ", + cc26xx_web_demo_config.net_uart.remote_address)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "name=\"net_uart_ip\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sRemote Port:%s", config_div_left, + config_div_close)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%sgenerate_pt, + enqueue_chunk(s, 0, "value=\"%u\" ", + cc26xx_web_demo_config.net_uart.remote_port)); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "min=\"1\" max=\"65535\" " + "name=\"net_uart_port\">%s", + config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "%s%s:%s%s", config_div_left, + "Enable", config_div_close, + config_div_right)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "generate_pt, + enqueue_chunk(s, 0, "title=\"On\" name=\"net_uart_on\"%s>", + cc26xx_web_demo_config.net_uart.enable ? + " Checked" : "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "generate_pt, + enqueue_chunk(s, 0, "title=\"Off\" name=\"net_uart_on\"" + "%s>%s", + cc26xx_web_demo_config.net_uart.enable ? + "" : " Checked", config_div_close)); + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, + "")); + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "
")); + + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom)); + + PT_END(&s->generate_pt); +} +#endif +/*---------------------------------------------------------------------------*/ +static void +lock_obtain(struct httpd_state *s) +{ + if(lock == NULL) { + lock = s; + } +} +/*---------------------------------------------------------------------------*/ +static void +lock_release(struct httpd_state *s) +{ + if(lock == s) { + lock = NULL; + } +} +/*---------------------------------------------------------------------------*/ +static void +parse_post_request_chunk(char *buf, int buf_len, int last_chunk) +{ + int i; + int finish; + + for(i = 0; i < buf_len; i++) { + switch(state) { + case PARSE_POST_STATE_INIT: + state = PARSE_POST_STATE_MORE; + /* continue */ + case PARSE_POST_STATE_MORE: + memset(key, 0, PARSE_POST_BUF_SIZES); + memset(val, 0, PARSE_POST_BUF_SIZES); + memset(val_escaped, 0, PARSE_POST_BUF_SIZES); + key_len = 0; + val_len = 0; + state = PARSE_POST_STATE_READING_KEY; + /* continue */ + case PARSE_POST_STATE_READING_KEY: + if(buf[i] == ISO_equal) { + state = PARSE_POST_STATE_READING_VAL; + } else if(buf[i] == ISO_amp) { + /* Don't accept an amp while reading a key */ + state = PARSE_POST_STATE_ERROR; + } else { + /* Make sure we don't overshoot key's boundary */ + if(key_len <= PARSE_POST_MAX_POS) { + key[key_len] = buf[i]; + key_len++; + } else { + /* Not enough space for the key. Abort */ + state = PARSE_POST_STATE_ERROR; + } + } + break; + case PARSE_POST_STATE_READING_VAL: + finish = 0; + if(buf[i] == ISO_amp) { + finish = 1; + } else if(buf[i] == ISO_equal) { + /* Don't accept an '=' while reading a val */ + state = PARSE_POST_STATE_ERROR; + } else { + /* Make sure we don't overshoot key's boundary */ + if(val_len <= PARSE_POST_MAX_POS) { + val[val_len] = buf[i]; + val_len++; + /* Last character of the last chunk */ + if((i == buf_len - 1) && (last_chunk == 1)) { + finish = 1; + } + } else { + /* Not enough space for the value. Abort */ + state = PARSE_POST_STATE_ERROR; + } + } + + if(finish == 1) { + /* + * Done reading a key=value pair, either because we encountered an amp + * or because we reached the end of the message body. + * + * First, unescape the value. + * + * Then invoke handlers. We will bail out with PARSE_POST_STATE_ERROR, + * unless the key-val gets correctly processed + */ + url_unescape(val, val_len, val_escaped, PARSE_POST_BUF_SIZES); + val_len = strlen(val_escaped); + + for(handler = list_head(post_handlers); handler != NULL; + handler = list_item_next((void *)handler)) { + if(handler->handler != NULL) { + finish = handler->handler(key, key_len, val_escaped, val_len); + } + if(finish == HTTPD_SIMPLE_POST_HANDLER_ERROR) { + state = PARSE_POST_STATE_ERROR; + break; + } else if(finish == HTTPD_SIMPLE_POST_HANDLER_OK) { + /* Restart the state machine to expect the next pair */ + state = PARSE_POST_STATE_MORE; + + /* + * At least one handler returned OK, therefore we must generate a + * new config event when we're done. + */ + config_ok = 1; + break; + } + /* Else, continue */ + } + } + break; + case PARSE_POST_STATE_ERROR: + /* If we entered the error state earlier, do nothing */ + return; + default: + break; + } + } +} +/*---------------------------------------------------------------------------*/ +static httpd_simple_script_t +get_script(const char *name) +{ + if(strlen(name) == 10 && strncmp(name, "index.html", 10) == 0) { + return generate_index; + } else if(strlen(name) == 11 && strncmp(name, "config.html", 11) == 0) { + return generate_config; +#if CC26XX_WEB_DEMO_MQTT_CLIENT + } else if(strlen(name) == 9 && strncmp(name, "mqtt.html", 9) == 0) { + return generate_mqtt_config; +#endif +#if CC26XX_WEB_DEMO_NET_UART + } else if(strlen(name) == 8 && strncmp(name, "net.html", 8) == 0) { + return generate_net_uart_config; +#endif + } + + return NULL; +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(send_string(struct httpd_state *s, const char *str)) +{ + PSOCK_BEGIN(&s->sout); + + SEND_STRING(&s->sout, str); + + PSOCK_END(&s->sout); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(send_headers(struct httpd_state *s, const char *statushdr, + const char *content_type, const char **additional)) +{ + PT_BEGIN(&s->generate_pt); + + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, statushdr)); + + for(s->ptr = http_header_srv_str; *(s->ptr) != NULL; s->ptr++) { + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, *(s->ptr))); + } + + if(additional) { + for(s->ptr = additional; *(s->ptr) != NULL; s->ptr++) { + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, *(s->ptr))); + } + } + + PT_WAIT_THREAD(&s->generate_pt, + enqueue_chunk(s, 0, "Content-type: %s; ", content_type)); + + PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, "charset=UTF-8\r\n\r\n")); + + PT_END(&s->generate_pt); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(handle_output(struct httpd_state *s)) +{ + PT_BEGIN(&s->outputpt); + + s->script = NULL; + + PT_INIT(&s->generate_pt); + PT_INIT(&s->top_matter_pt); + + if(s->request_type == REQUEST_TYPE_POST) { + if(s->return_code == RETURN_CODE_OK) { + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_302, + http_content_type_plain, + http_header_redir_location)); + } else if(s->return_code == RETURN_CODE_LR) { + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_411, + http_content_type_plain, + http_header_con_close)); + PT_WAIT_THREAD(&s->outputpt, send_string(s, "Content-Length Required\n")); + } else if(s->return_code == RETURN_CODE_TL) { + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_413, + http_content_type_plain, + http_header_con_close)); + PT_WAIT_THREAD(&s->outputpt, send_string(s, "Content-Length too Large\n")); + } else if(s->return_code == RETURN_CODE_SU) { + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_503, + http_content_type_plain, + http_header_con_close)); + PT_WAIT_THREAD(&s->outputpt, send_string(s, "Service Unavailable\n")); + } else { + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_400, + http_content_type_plain, + http_header_con_close)); + PT_WAIT_THREAD(&s->outputpt, send_string(s, "Bad Request\n")); + } + } else if(s->request_type == REQUEST_TYPE_GET) { + s->script = get_script(&s->filename[1]); + if(s->script == NULL) { + strncpy(s->filename, "/notfound.html", sizeof(s->filename)); + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_404, + http_content_type_html, + http_header_con_close)); + PT_WAIT_THREAD(&s->outputpt, + send_string(s, NOT_FOUND)); + uip_close(); + PT_EXIT(&s->outputpt); + } else { + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200, + http_content_type_html, + http_header_con_close)); + PT_WAIT_THREAD(&s->outputpt, s->script(s)); + } + } + s->script = NULL; + PSOCK_CLOSE(&s->sout); + PT_END(&s->outputpt); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(handle_input(struct httpd_state *s)) +{ + PSOCK_BEGIN(&s->sin); + + PSOCK_READTO(&s->sin, ISO_space); + + if(strncasecmp(s->inputbuf, http_get, 4) == 0) { + s->request_type = REQUEST_TYPE_GET; + PSOCK_READTO(&s->sin, ISO_space); + + if(s->inputbuf[0] != ISO_slash) { + PSOCK_CLOSE_EXIT(&s->sin); + } + + if(s->inputbuf[1] == ISO_space) { + strncpy(s->filename, http_index_html, sizeof(s->filename)); + } else { + s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0; + strncpy(s->filename, s->inputbuf, sizeof(s->filename)); + } + } else if(strncasecmp(s->inputbuf, http_post, 5) == 0) { + s->request_type = REQUEST_TYPE_POST; + /* POST: Read out the rest of the line and ignore it */ + PSOCK_READTO(&s->sin, ISO_nl); + + /* + * Start parsing headers. We look for Content-Length and ignore everything + * else until we hit the start of the message body. + * + * We will return 411 if the client doesn't send Content-Length and 413 + * if Content-Length is too high + */ + s->content_length = 0; + s->return_code = RETURN_CODE_LR; + do { + s->inputbuf[PSOCK_DATALEN(&s->sin)] = 0; + /* We anticipate a content length */ + if((PSOCK_DATALEN(&s->sin) > 14) && + strncasecmp(s->inputbuf, "Content-Length:", 15) == 0) { + char *val_start = &s->inputbuf[15]; + s->content_length = atoi(val_start); + + /* So far so good */ + s->return_code = RETURN_CODE_OK; + } + PSOCK_READTO(&s->sin, ISO_nl); + } while(PSOCK_DATALEN(&s->sin) != 2); + + /* + * Done reading headers. + * Reject content length greater than CONTENT_LENGTH_MAX bytes + */ + if(s->content_length > CONTENT_LENGTH_MAX) { + s->content_length = 0; + s->return_code = RETURN_CODE_TL; + } + + if(s->return_code == RETURN_CODE_OK) { + /* Acceptable Content Length. Try to obtain a lock */ + lock_obtain(s); + + if(lock == s) { + state = PARSE_POST_STATE_INIT; + } else { + s->return_code = RETURN_CODE_SU; + } + } + + /* Parse the message body, unless we have detected an error. */ + while(s->content_length > 0 && lock == s && + s->return_code == RETURN_CODE_OK) { + PSOCK_READBUF_LEN(&s->sin, s->content_length); + s->content_length -= PSOCK_DATALEN(&s->sin); + + /* Parse the message body */ + parse_post_request_chunk(s->inputbuf, PSOCK_DATALEN(&s->sin), + (s->content_length == 0)); + if(state == PARSE_POST_STATE_ERROR) { + /* Could not parse: Bad Request and stop parsing */ + s->return_code = RETURN_CODE_BR; + } + } + + /* + * Done. If our return code is OK but the state machine is not in + * STATE_MORE, it means that the message body ended half-way reading a key + * or value. Set 'Bad Request' + */ + if(s->return_code == RETURN_CODE_OK && state != PARSE_POST_STATE_MORE) { + s->return_code = RETURN_CODE_BR; + } + + /* If the flag is set, we had at least 1 configuration value accepted */ + if(config_ok) { + process_post(PROCESS_BROADCAST, httpd_simple_event_new_config, NULL); + } + config_ok = 0; + + lock_release(s); + } else { + PSOCK_CLOSE_EXIT(&s->sin); + } + + s->state = STATE_OUTPUT; + + while(1) { + PSOCK_READTO(&s->sin, ISO_nl); + } + + PSOCK_END(&s->sin); +} +/*---------------------------------------------------------------------------*/ +static void +handle_connection(struct httpd_state *s) +{ + handle_input(s); + if(s->state == STATE_OUTPUT) { + handle_output(s); + } +} +/*---------------------------------------------------------------------------*/ +static void +appcall(void *state) +{ + struct httpd_state *s = (struct httpd_state *)state; + + if(uip_closed() || uip_aborted() || uip_timedout()) { + if(s != NULL) { + s->script = NULL; + s->blen = 0; + s->tmp_buf_len = 0; + memb_free(&conns, s); + } + } else if(uip_connected()) { + s = (struct httpd_state *)memb_alloc(&conns); + if(s == NULL) { + uip_abort(); + return; + } + tcp_markconn(uip_conn, s); + PSOCK_INIT(&s->sin, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1); + PSOCK_INIT(&s->sout, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1); + PT_INIT(&s->outputpt); + s->script = NULL; + s->state = STATE_WAITING; + timer_set(&s->timer, CLOCK_SECOND * 10); + handle_connection(s); + } else if(s != NULL) { + if(uip_poll()) { + if(timer_expired(&s->timer)) { + uip_abort(); + s->script = NULL; + memb_free(&conns, s); + } + } else { + timer_restart(&s->timer); + } + handle_connection(s); + } else { + uip_abort(); + } +} +/*---------------------------------------------------------------------------*/ +static void +init(void) +{ + tcp_listen(UIP_HTONS(80)); + memb_init(&conns); + + list_add(pages_list, &http_index_page); + list_add(pages_list, &http_dev_cfg_page); + +#if CC26XX_WEB_DEMO_NET_UART + list_add(pages_list, &http_net_cfg_page); +#endif + +#if CC26XX_WEB_DEMO_MQTT_CLIENT + list_add(pages_list, &http_mqtt_cfg_page); +#endif +} +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(httpd_simple_process, ev, data) +{ + PROCESS_BEGIN(); + + printf("CC26XX Web Server\n"); + + httpd_simple_event_new_config = process_alloc_event(); + + init(); + + snprintf(http_mqtt_a, IBM_QUICKSTART_LINK_LEN, + "[ IBM Quickstart ]", + linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1], + linkaddr_node_addr.u8[2], linkaddr_node_addr.u8[5], + linkaddr_node_addr.u8[6], linkaddr_node_addr.u8[7]); + + while(1) { + PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); + appcall(data); + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +/** + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/httpd-simple.h b/examples/cc26xx/cc26xx-web-demo/httpd-simple.h new file mode 100644 index 000000000..c7631adb1 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/httpd-simple.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010, Swedish Institute of Computer Science. + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ +/*---------------------------------------------------------------------------*/ +/** + * \file + * Header file for the HTTPD of the cc26xx web demo example. + * \author + * Adam Dunkels + * Niclas Finne + * Joakim Eriksson + * Texas Instruments Incorporated - http://www.ti.com/ + */ +/*---------------------------------------------------------------------------*/ +#ifndef HTTPD_SIMPLE_H_ +#define HTTPD_SIMPLE_H_ +/*---------------------------------------------------------------------------*/ +#include "contiki-net.h" +#include "sys/process.h" +#include "cc26xx-web-demo.h" +/*---------------------------------------------------------------------------*/ +/* Ideally a multiple of TCP_MSS */ +#ifdef HTTPD_SIMPLE_CONF_MAIN_BUF_SIZE +#define HTTPD_SIMPLE_MAIN_BUF_SIZE HTTPD_SIMPLE_CONF_BUF_SIZE +#else +#define HTTPD_SIMPLE_MAIN_BUF_SIZE UIP_TCP_MSS +#endif +/*---------------------------------------------------------------------------*/ +#define HTTPD_PATHLEN 16 +#define HTTPD_INBUF_LEN (HTTPD_PATHLEN + 10) + +#define TMP_BUF_SIZE (UIP_TCP_MSS + 1) +/*---------------------------------------------------------------------------*/ +/* POST request handlers */ +#define HTTPD_SIMPLE_POST_HANDLER_OK 1 +#define HTTPD_SIMPLE_POST_HANDLER_UNKNOWN 0 +#define HTTPD_SIMPLE_POST_HANDLER_ERROR 0xFFFFFFFF + +/** + * \brief Datatype for a handler which can process incoming POST requests + * \param key The configuration key to be updated + * \param key_len The length of the key argument + * \param val The new configuration value for key + * \param val_len The length of the value argument + * + * \return 1: HTTPD_SIMPLE_POST_HANDLER_OK if the function can handle the + * request, HTTPD_SIMPLE_POST_HANDLER_UNKNOWN if it does not know how to handle + * it. HTTPD_SIMPLE_POST_HANDLER_ERROR if it does know how to handle it but + * the request was malformed. + */ +typedef struct httpd_simple_post_handler { + struct httpd_simple_post_handler *next; + int (*handler)(char *key, int key_len, char *val, int val_len); +} httpd_simple_post_handler_t; + +/* Declare a handler */ +#define HTTPD_SIMPLE_POST_HANDLER(name, fp) \ + httpd_simple_post_handler_t name##_handler = { NULL, fp } + +/** + * \brief Register a handler for POST requests + * \param h A pointer to the handler structure + */ +void httpd_simple_register_post_handler(httpd_simple_post_handler_t *h); +/*---------------------------------------------------------------------------*/ +/* + * An event generated by the HTTPD when a new configuration request has been + * received + */ +extern process_event_t httpd_simple_event_new_config; +/*---------------------------------------------------------------------------*/ +PROCESS_NAME(httpd_simple_process); +/*---------------------------------------------------------------------------*/ +#endif /* HTTPD_SIMPLE_H_ */ diff --git a/examples/cc26xx/cc26xx-web-demo/img/6lbr-web.png b/examples/cc26xx/cc26xx-web-demo/img/6lbr-web.png new file mode 100644 index 000000000..5308c412b Binary files /dev/null and b/examples/cc26xx/cc26xx-web-demo/img/6lbr-web.png differ diff --git a/examples/cc26xx/cc26xx-web-demo/img/coap-resources.png b/examples/cc26xx/cc26xx-web-demo/img/coap-resources.png new file mode 100644 index 000000000..c71c934fe Binary files /dev/null and b/examples/cc26xx/cc26xx-web-demo/img/coap-resources.png differ diff --git a/examples/cc26xx/cc26xx-web-demo/img/quickstart-sensortag.png b/examples/cc26xx/cc26xx-web-demo/img/quickstart-sensortag.png new file mode 100644 index 000000000..a7ce7dabe Binary files /dev/null and b/examples/cc26xx/cc26xx-web-demo/img/quickstart-sensortag.png differ diff --git a/examples/cc26xx/cc26xx-web-demo/img/sensor-readings-config.png b/examples/cc26xx/cc26xx-web-demo/img/sensor-readings-config.png new file mode 100644 index 000000000..79c8e61e3 Binary files /dev/null and b/examples/cc26xx/cc26xx-web-demo/img/sensor-readings-config.png differ diff --git a/examples/cc26xx/cc26xx-web-demo/img/well-known-core.png b/examples/cc26xx/cc26xx-web-demo/img/well-known-core.png new file mode 100644 index 000000000..93552e502 Binary files /dev/null and b/examples/cc26xx/cc26xx-web-demo/img/well-known-core.png differ diff --git a/examples/cc26xx/cc26xx-web-demo/mqtt-client.c b/examples/cc26xx/cc26xx-web-demo/mqtt-client.c new file mode 100644 index 000000000..4fa0736ef --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/mqtt-client.c @@ -0,0 +1,975 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * MQTT/IBM cloud service client for the CC26XX web demo. + */ +/*---------------------------------------------------------------------------*/ +#include "contiki-conf.h" +#include "rpl/rpl-private.h" +#include "mqtt.h" +#include "net/rpl/rpl.h" +#include "net/ip/uip.h" +#include "net/ipv6/uip-icmp6.h" +#include "net/ipv6/sicslowpan.h" +#include "sys/etimer.h" +#include "sys/ctimer.h" +#include "lib/sensors.h" +#include "button-sensor.h" +#include "board-peripherals.h" +#include "cc26xx-web-demo.h" +#include "dev/leds.h" +#include "mqtt-client.h" +#include "httpd-simple.h" + +#include +#include +/*---------------------------------------------------------------------------*/ +/* + * IBM server: messaging.quickstart.internetofthings.ibmcloud.com + * (184.172.124.189) mapped in an NAT64 (prefix 64:ff9b::/96) IPv6 address + * Note: If not able to connect; lookup the IP address again as it may change. + * + * If the node has a broker IP setting saved on flash, this value here will + * get ignored + */ +static const char *broker_ip = "0064:ff9b:0000:0000:0000:0000:b8ac:7cbd"; +/*---------------------------------------------------------------------------*/ +/* + * A timeout used when waiting for something to happen (e.g. to connect or to + * disconnect) + */ +#define STATE_MACHINE_PERIODIC (CLOCK_SECOND >> 1) +/*---------------------------------------------------------------------------*/ +/* Provide visible feedback via LEDS during various states */ +/* When connecting to broker */ +#define CONNECTING_LED_DURATION (CLOCK_SECOND >> 3) + +/* Each time we try to publish */ +#define PUBLISH_LED_ON_DURATION (CLOCK_SECOND) +/*---------------------------------------------------------------------------*/ +/* Connections and reconnections */ +#define RETRY_FOREVER 0xFF +#define RECONNECT_INTERVAL (CLOCK_SECOND * 2) + +/* + * Number of times to try reconnecting to the broker. + * Can be a limited number (e.g. 3, 10 etc) or can be set to RETRY_FOREVER + */ +#define RECONNECT_ATTEMPTS RETRY_FOREVER +#define CONNECTION_STABLE_TIME (CLOCK_SECOND * 5) +#define NEW_CONFIG_WAIT_INTERVAL (CLOCK_SECOND * 20) +static struct timer connection_life; +static uint8_t connect_attempt; +/*---------------------------------------------------------------------------*/ +/* Various states */ +static uint8_t state; +#define MQTT_CLIENT_STATE_INIT 0 +#define MQTT_CLIENT_STATE_REGISTERED 1 +#define MQTT_CLIENT_STATE_CONNECTING 2 +#define MQTT_CLIENT_STATE_CONNECTED 3 +#define MQTT_CLIENT_STATE_PUBLISHING 4 +#define MQTT_CLIENT_STATE_DISCONNECTED 5 +#define MQTT_CLIENT_STATE_NEWCONFIG 6 +#define MQTT_CLIENT_STATE_CONFIG_ERROR 0xFE +#define MQTT_CLIENT_STATE_ERROR 0xFF +/*---------------------------------------------------------------------------*/ +/* Maximum TCP segment size for outgoing segments of our socket */ +#define MQTT_CLIENT_MAX_SEGMENT_SIZE 32 +/*---------------------------------------------------------------------------*/ +/* + * Buffers for Client ID and Topic. + * Make sure they are large enough to hold the entire respective string + * + * d:quickstart:status:EUI64 is 32 bytes long + * iot-2/evt/status/fmt/json is 25 bytes + * We also need space for the null termination + */ +#define BUFFER_SIZE 64 +static char client_id[BUFFER_SIZE]; +static char pub_topic[BUFFER_SIZE]; +static char sub_topic[BUFFER_SIZE]; +/*---------------------------------------------------------------------------*/ +/* + * The main MQTT buffers. + * We will need to increase if we start publishing more data. + */ +#define APP_BUFFER_SIZE 512 +static struct mqtt_connection conn; +static char app_buffer[APP_BUFFER_SIZE]; +/*---------------------------------------------------------------------------*/ +#define QUICKSTART "quickstart" +/*---------------------------------------------------------------------------*/ +static struct mqtt_message *msg_ptr = 0; +static struct etimer publish_periodic_timer; +static struct ctimer ct; +static char *buf_ptr; +static uint16_t seq_nr_value = 0; +/*---------------------------------------------------------------------------*/ +static uip_ip6addr_t def_route; +/*---------------------------------------------------------------------------*/ +/* Parent RSSI functionality */ +static struct uip_icmp6_echo_reply_notification echo_reply_notification; +static struct etimer echo_request_timer; +int def_rt_rssi = 0; +/*---------------------------------------------------------------------------*/ +const static cc26xx_web_demo_sensor_reading_t *reading; +/*---------------------------------------------------------------------------*/ +mqtt_client_config_t *conf; +/*---------------------------------------------------------------------------*/ +PROCESS(mqtt_client_process, "CC26XX MQTT Client"); +/*---------------------------------------------------------------------------*/ +static void +publish_led_off(void *d) +{ + leds_off(CC26XX_WEB_DEMO_STATUS_LED); +} +/*---------------------------------------------------------------------------*/ +static void +new_net_config(void) +{ + /* + * We got a new configuration over the net. + * + * Disconnect from the current broker and stop the periodic timer. + * + * When the source of the new configuration is done, we will get notified + * via an event. + */ + if(state == MQTT_CLIENT_STATE_NEWCONFIG) { + return; + } + + state = MQTT_CLIENT_STATE_NEWCONFIG; + + etimer_stop(&publish_periodic_timer); + mqtt_disconnect(&conn); +} +/*---------------------------------------------------------------------------*/ +static int +org_id_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + if(key_len != strlen("org_id") || + strncasecmp(key, "org_id", strlen("org_id")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + if(val_len > MQTT_CLIENT_CONFIG_ORG_ID_LEN) { + /* Ours but bad value */ + rv = HTTPD_SIMPLE_POST_HANDLER_ERROR; + } else { + memset(conf->org_id, 0, MQTT_CLIENT_CONFIG_ORG_ID_LEN); + memcpy(conf->org_id, val, val_len); + + rv = HTTPD_SIMPLE_POST_HANDLER_OK; + } + + new_net_config(); + + return rv; +} +/*---------------------------------------------------------------------------*/ +static int +type_id_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + if(key_len != strlen("type_id") || + strncasecmp(key, "type_id", strlen("type_id")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + if(val_len > MQTT_CLIENT_CONFIG_TYPE_ID_LEN) { + /* Ours but bad value */ + rv = HTTPD_SIMPLE_POST_HANDLER_ERROR; + } else { + memset(conf->type_id, 0, MQTT_CLIENT_CONFIG_TYPE_ID_LEN); + memcpy(conf->type_id, val, val_len); + + rv = HTTPD_SIMPLE_POST_HANDLER_OK; + } + + new_net_config(); + + return rv; +} +/*---------------------------------------------------------------------------*/ +static int +event_type_id_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + if(key_len != strlen("event_type_id") || + strncasecmp(key, "event_type_id", strlen("event_type_id")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + if(val_len > MQTT_CLIENT_CONFIG_EVENT_TYPE_ID_LEN) { + /* Ours but bad value */ + rv = HTTPD_SIMPLE_POST_HANDLER_ERROR; + } else { + memset(conf->event_type_id, 0, MQTT_CLIENT_CONFIG_EVENT_TYPE_ID_LEN); + memcpy(conf->event_type_id, val, val_len); + + rv = HTTPD_SIMPLE_POST_HANDLER_OK; + } + + new_net_config(); + + return rv; +} +/*---------------------------------------------------------------------------*/ +static int +cmd_type_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + if(key_len != strlen("cmd_type") || + strncasecmp(key, "cmd_type", strlen("cmd_type")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + if(val_len > MQTT_CLIENT_CONFIG_CMD_TYPE_LEN) { + /* Ours but bad value */ + rv = HTTPD_SIMPLE_POST_HANDLER_ERROR; + } else { + memset(conf->cmd_type, 0, MQTT_CLIENT_CONFIG_CMD_TYPE_LEN); + memcpy(conf->cmd_type, val, val_len); + + rv = HTTPD_SIMPLE_POST_HANDLER_OK; + } + + new_net_config(); + + return rv; +} +/*---------------------------------------------------------------------------*/ +static int +auth_token_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + if(key_len != strlen("auth_token") || + strncasecmp(key, "auth_token", strlen("auth_token")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + if(val_len > MQTT_CLIENT_CONFIG_AUTH_TOKEN_LEN) { + /* Ours but bad value */ + rv = HTTPD_SIMPLE_POST_HANDLER_ERROR; + } else { + memset(conf->auth_token, 0, MQTT_CLIENT_CONFIG_AUTH_TOKEN_LEN); + memcpy(conf->auth_token, val, val_len); + + rv = HTTPD_SIMPLE_POST_HANDLER_OK; + } + + new_net_config(); + + return rv; +} +/*---------------------------------------------------------------------------*/ +static int +interval_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = 0; + + if(key_len != strlen("interval") || + strncasecmp(key, "interval", strlen("interval")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + rv = atoi(val); + + if(rv < MQTT_CLIENT_PUBLISH_INTERVAL_MIN || + rv > MQTT_CLIENT_PUBLISH_INTERVAL_MAX) { + return HTTPD_SIMPLE_POST_HANDLER_ERROR; + } + + conf->pub_interval = rv * CLOCK_SECOND; + + return HTTPD_SIMPLE_POST_HANDLER_OK; +} +/*---------------------------------------------------------------------------*/ +static int +port_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = 0; + + if(key_len != strlen("broker_port") || + strncasecmp(key, "broker_port", strlen("broker_port")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + rv = atoi(val); + + if(rv <= 65535 && rv > 0) { + conf->broker_port = rv; + } else { + return HTTPD_SIMPLE_POST_HANDLER_ERROR; + } + + new_net_config(); + + return HTTPD_SIMPLE_POST_HANDLER_OK; +} +/*---------------------------------------------------------------------------*/ +static int +ip_addr_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + + if(key_len != strlen("broker_ip") || + strncasecmp(key, "broker_ip", strlen("broker_ip")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + if(val_len > MQTT_CLIENT_CONFIG_IP_ADDR_STR_LEN) { + /* Ours but bad value */ + rv = HTTPD_SIMPLE_POST_HANDLER_ERROR; + } else { + memset(conf->broker_ip, 0, MQTT_CLIENT_CONFIG_IP_ADDR_STR_LEN); + memcpy(conf->broker_ip, val, val_len); + + rv = HTTPD_SIMPLE_POST_HANDLER_OK; + } + + new_net_config(); + + return rv; +} +/*---------------------------------------------------------------------------*/ +static int +reconnect_post_handler(char *key, int key_len, char *val, int val_len) +{ + if(key_len != strlen("reconnect") || + strncasecmp(key, "reconnect", strlen("reconnect")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + new_net_config(); + + return HTTPD_SIMPLE_POST_HANDLER_OK; +} +/*---------------------------------------------------------------------------*/ +static int +ping_interval_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = 0; + + if(key_len != strlen("ping_interval") || + strncasecmp(key, "ping_interval", strlen("ping_interval")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + rv = atoi(val); + + if(rv < MQTT_CLIENT_RSSI_MEASURE_INTERVAL_MIN || + rv > MQTT_CLIENT_RSSI_MEASURE_INTERVAL_MAX) { + return HTTPD_SIMPLE_POST_HANDLER_ERROR; + } + + conf->def_rt_ping_interval = rv * CLOCK_SECOND; + + return HTTPD_SIMPLE_POST_HANDLER_OK; +} +/*---------------------------------------------------------------------------*/ +HTTPD_SIMPLE_POST_HANDLER(org_id, org_id_post_handler); +HTTPD_SIMPLE_POST_HANDLER(type_id, type_id_post_handler); +HTTPD_SIMPLE_POST_HANDLER(event_type_id, event_type_id_post_handler); +HTTPD_SIMPLE_POST_HANDLER(cmd_type, cmd_type_post_handler); +HTTPD_SIMPLE_POST_HANDLER(auth_token, auth_token_post_handler); +HTTPD_SIMPLE_POST_HANDLER(ip_addr, ip_addr_post_handler); +HTTPD_SIMPLE_POST_HANDLER(port, port_post_handler); +HTTPD_SIMPLE_POST_HANDLER(interval, interval_post_handler); +HTTPD_SIMPLE_POST_HANDLER(reconnect, reconnect_post_handler); +HTTPD_SIMPLE_POST_HANDLER(ping_interval, ping_interval_post_handler); +/*---------------------------------------------------------------------------*/ +static void +echo_reply_handler(uip_ipaddr_t *source, uint8_t ttl, uint8_t *data, + uint16_t datalen) +{ + if(uip_ip6addr_cmp(source, uip_ds6_defrt_choose())) { + def_rt_rssi = sicslowpan_get_last_rssi(); + } +} +/*---------------------------------------------------------------------------*/ +static void +pub_handler(const char *topic, uint16_t topic_len, const uint8_t *chunk, + uint16_t chunk_len) +{ + DBG("Pub Handler: topic='%s' (len=%u), chunk_len=%u\n", topic, topic_len, + chunk_len); + + /* If we don't like the length, ignore */ + if(topic_len != 23 || chunk_len != 1) { + printf("Incorrect topic or chunk len. Ignored\n"); + return; + } + + /* If the format != json, ignore */ + if(strncmp(&topic[topic_len - 4], "json", 4) != 0) { + printf("Incorrect format\n"); + } + + if(strncmp(&topic[10], "leds", 4) == 0) { + if(chunk[0] == '1') { + leds_on(LEDS_RED); + } else if(chunk[0] == '0') { + leds_off(LEDS_RED); + } + return; + } + +#if BOARD_SENSORTAG + if(strncmp(&topic[10], "buzz", 4) == 0) { + if(chunk[0] == '1') { + buzzer_start(1000); + } else if(chunk[0] == '0') { + buzzer_stop(); + } + return; + } +#endif +} +/*---------------------------------------------------------------------------*/ +static void +mqtt_event(struct mqtt_connection *m, mqtt_event_t event, void *data) +{ + switch(event) { + case MQTT_EVENT_CONNECTED: { + DBG("APP - Application has a MQTT connection\n"); + timer_set(&connection_life, CONNECTION_STABLE_TIME); + state = MQTT_CLIENT_STATE_CONNECTED; + break; + } + case MQTT_EVENT_DISCONNECTED: { + DBG("APP - MQTT Disconnect. Reason %u\n", *((mqtt_event_t *)data)); + + /* Do nothing if the disconnect was the result of an incoming config */ + if(state != MQTT_CLIENT_STATE_NEWCONFIG) { + state = MQTT_CLIENT_STATE_DISCONNECTED; + process_poll(&mqtt_client_process); + } + break; + } + case MQTT_EVENT_PUBLISH: { + msg_ptr = data; + + /* Implement first_flag in publish message? */ + if(msg_ptr->first_chunk) { + msg_ptr->first_chunk = 0; + DBG("APP - Application received a publish on topic '%s'. Payload " + "size is %i bytes. Content:\n\n", + msg_ptr->topic, msg_ptr->payload_length); + } + + pub_handler(msg_ptr->topic, strlen(msg_ptr->topic), msg_ptr->payload_chunk, + msg_ptr->payload_length); + break; + } + case MQTT_EVENT_SUBACK: { + DBG("APP - Application is subscribed to topic successfully\n"); + break; + } + case MQTT_EVENT_UNSUBACK: { + DBG("APP - Application is unsubscribed to topic successfully\n"); + break; + } + case MQTT_EVENT_PUBACK: { + DBG("APP - Publishing complete.\n"); + break; + } + default: + DBG("APP - Application got a unhandled MQTT event: %i\n", event); + break; + } +} +/*---------------------------------------------------------------------------*/ +static int +construct_pub_topic(void) +{ + int len = snprintf(pub_topic, BUFFER_SIZE, "iot-2/evt/%s/fmt/json", + conf->event_type_id); + + /* len < 0: Error. Len >= BUFFER_SIZE: Buffer too small */ + if(len < 0 || len >= BUFFER_SIZE) { + printf("Pub Topic: %d, Buffer %d\n", len, BUFFER_SIZE); + return 0; + } + + return 1; +} +/*---------------------------------------------------------------------------*/ +static int +construct_sub_topic(void) +{ + int len = snprintf(sub_topic, BUFFER_SIZE, "iot-2/cmd/%s/fmt/json", + conf->cmd_type); + + /* len < 0: Error. Len >= BUFFER_SIZE: Buffer too small */ + if(len < 0 || len >= BUFFER_SIZE) { + printf("Sub Topic: %d, Buffer %d\n", len, BUFFER_SIZE); + return 0; + } + + return 1; +} +/*---------------------------------------------------------------------------*/ +static int +construct_client_id(void) +{ + int len = snprintf(client_id, BUFFER_SIZE, "d:%s:%s:%02x%02x%02x%02x%02x%02x", + conf->org_id, conf->type_id, + linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1], + linkaddr_node_addr.u8[2], linkaddr_node_addr.u8[5], + linkaddr_node_addr.u8[6], linkaddr_node_addr.u8[7]); + + /* len < 0: Error. Len >= BUFFER_SIZE: Buffer too small */ + if(len < 0 || len >= BUFFER_SIZE) { + printf("Client ID: %d, Buffer %d\n", len, BUFFER_SIZE); + return 0; + } + + return 1; +} +/*---------------------------------------------------------------------------*/ +static void +update_config(void) +{ + if(construct_client_id() == 0) { + /* Fatal error. Client ID larger than the buffer */ + state = MQTT_CLIENT_STATE_CONFIG_ERROR; + return; + } + + if(construct_sub_topic() == 0) { + /* Fatal error. Topic larger than the buffer */ + state = MQTT_CLIENT_STATE_CONFIG_ERROR; + return; + } + + if(construct_pub_topic() == 0) { + /* Fatal error. Topic larger than the buffer */ + state = MQTT_CLIENT_STATE_CONFIG_ERROR; + return; + } + + /* Reset the counter */ + seq_nr_value = 0; + + state = MQTT_CLIENT_STATE_INIT; + + /* + * Schedule next timer event ASAP + * + * If we entered an error state then we won't do anything when it fires. + * + * Since the error at this stage is a config error, we will only exit this + * error state if we get a new config. + */ + etimer_set(&publish_periodic_timer, 0); + + return; +} +/*---------------------------------------------------------------------------*/ +static int +init_config() +{ + /* Populate configuration with default values */ + memset(conf, 0, sizeof(mqtt_client_config_t)); + + memcpy(conf->org_id, CC26XX_WEB_DEMO_DEFAULT_ORG_ID, 11); + memcpy(conf->type_id, CC26XX_WEB_DEMO_DEFAULT_TYPE_ID, 7); + memcpy(conf->event_type_id, CC26XX_WEB_DEMO_DEFAULT_EVENT_TYPE_ID, 7); + memcpy(conf->broker_ip, broker_ip, strlen(broker_ip)); + memcpy(conf->cmd_type, CC26XX_WEB_DEMO_DEFAULT_SUBSCRIBE_CMD_TYPE, 1); + + conf->broker_port = CC26XX_WEB_DEMO_DEFAULT_BROKER_PORT; + conf->pub_interval = CC26XX_WEB_DEMO_DEFAULT_PUBLISH_INTERVAL; + conf->def_rt_ping_interval = CC26XX_WEB_DEMO_DEFAULT_RSSI_MEAS_INTERVAL; + + return 1; +} +/*---------------------------------------------------------------------------*/ +static void +register_http_post_handlers(void) +{ + httpd_simple_register_post_handler(&org_id_handler); + httpd_simple_register_post_handler(&type_id_handler); + httpd_simple_register_post_handler(&event_type_id_handler); + httpd_simple_register_post_handler(&cmd_type_handler); + httpd_simple_register_post_handler(&auth_token_handler); + httpd_simple_register_post_handler(&interval_handler); + httpd_simple_register_post_handler(&port_handler); + httpd_simple_register_post_handler(&ip_addr_handler); + httpd_simple_register_post_handler(&reconnect_handler); + httpd_simple_register_post_handler(&ping_interval_handler); +} +/*---------------------------------------------------------------------------*/ +static void +subscribe(void) +{ + /* Publish MQTT topic in IBM quickstart format */ + mqtt_status_t status; + + status = mqtt_subscribe(&conn, NULL, sub_topic, MQTT_QOS_LEVEL_0); + + DBG("APP - Subscribing!\n"); + if(status == MQTT_STATUS_OUT_QUEUE_FULL) { + DBG("APP - Tried to subscribe but command queue was full!\n"); + } +} +/*---------------------------------------------------------------------------*/ +static void +publish(void) +{ + /* Publish MQTT topic in IBM quickstart format */ + int len; + int remaining = APP_BUFFER_SIZE; + + seq_nr_value++; + + buf_ptr = app_buffer; + + len = snprintf(buf_ptr, remaining, + "{" + "\"d\":{" + "\"myName\":\"%s\"," + "\"Seq #\":%d," + "\"Uptime (sec)\":%lu", + BOARD_STRING, seq_nr_value, clock_seconds()); + + if(len < 0 || len >= remaining) { + printf("Buffer too short. Have %d, need %d + \\0\n", remaining, len); + return; + } + + remaining -= len; + buf_ptr += len; + + /* Put our Default route's string representation in a buffer */ + char def_rt_str[64]; + memset(def_rt_str, 0, sizeof(def_rt_str)); + cc26xx_web_demo_ipaddr_sprintf(def_rt_str, sizeof(def_rt_str), + uip_ds6_defrt_choose()); + + len = snprintf(buf_ptr, remaining, ",\"Def Route\":\"%s\",\"RSSI (dBm)\":%d", + def_rt_str, def_rt_rssi); + + if(len < 0 || len >= remaining) { + printf("Buffer too short. Have %d, need %d + \\0\n", remaining, len); + return; + } + remaining -= len; + buf_ptr += len; + + memcpy(&def_route, uip_ds6_defrt_choose(), sizeof(uip_ip6addr_t)); + + for(reading = cc26xx_web_demo_sensor_first(); + reading != NULL; reading = reading->next) { + if(reading->publish && reading->raw != CC26XX_SENSOR_READING_ERROR) { + len = snprintf(buf_ptr, remaining, + ",\"%s (%s)\":%s", reading->descr, reading->units, + reading->converted); + + if(len < 0 || len >= remaining) { + printf("Buffer too short. Have %d, need %d + \\0\n", remaining, len); + return; + } + remaining -= len; + buf_ptr += len; + } + } + + len = snprintf(buf_ptr, remaining, "}}"); + + if(len < 0 || len >= remaining) { + printf("Buffer too short. Have %d, need %d + \\0\n", remaining, len); + return; + } + + mqtt_publish(&conn, NULL, pub_topic, (uint8_t *)app_buffer, + strlen(app_buffer), MQTT_QOS_LEVEL_0, MQTT_RETAIN_OFF); + + DBG("APP - Publish!\n"); +} +/*---------------------------------------------------------------------------*/ +static void +connect_to_broker(void) +{ + /* Connect to MQTT server */ + mqtt_connect(&conn, conf->broker_ip, conf->broker_port, + conf->pub_interval * 3); + + state = MQTT_CLIENT_STATE_CONNECTING; +} +/*---------------------------------------------------------------------------*/ +static void +ping_parent(void) +{ + if(uip_ds6_get_global(ADDR_PREFERRED) == NULL) { + return; + } + + uip_icmp6_send(uip_ds6_defrt_choose(), ICMP6_ECHO_REQUEST, 0, + CC26XX_WEB_DEMO_ECHO_REQ_PAYLOAD_LEN); +} +/*---------------------------------------------------------------------------*/ +static void +state_machine(void) +{ + switch(state) { + case MQTT_CLIENT_STATE_INIT: + /* If we have just been configured register MQTT connection */ + mqtt_register(&conn, &mqtt_client_process, client_id, mqtt_event, + MQTT_CLIENT_MAX_SEGMENT_SIZE); + + /* + * If we are not using the quickstart service (thus we are an IBM + * registered device), we need to provide user name and password + */ + if(strncasecmp(conf->org_id, QUICKSTART, strlen(conf->org_id)) != 0) { + if(strlen(conf->auth_token) == 0) { + printf("User name set, but empty auth token\n"); + state = MQTT_CLIENT_STATE_ERROR; + break; + } else { + mqtt_set_username_password(&conn, "use-token-auth", + conf->auth_token); + } + } + + /* _register() will set auto_reconnect. We don't want that. */ + conn.auto_reconnect = 0; + connect_attempt = 1; + + /* + * Wipe out the default route so we'll republish it every time we switch to + * a new broker + */ + memset(&def_route, 0, sizeof(def_route)); + + state = MQTT_CLIENT_STATE_REGISTERED; + DBG("Init\n"); + /* Continue */ + case MQTT_CLIENT_STATE_REGISTERED: + if(uip_ds6_get_global(ADDR_PREFERRED) != NULL) { + /* Registered and with a public IP. Connect */ + DBG("Registered. Connect attempt %u\n", connect_attempt); + ping_parent(); + connect_to_broker(); + } + etimer_set(&publish_periodic_timer, CC26XX_WEB_DEMO_NET_CONNECT_PERIODIC); + return; + break; + case MQTT_CLIENT_STATE_CONNECTING: + leds_on(CC26XX_WEB_DEMO_STATUS_LED); + ctimer_set(&ct, CONNECTING_LED_DURATION, publish_led_off, NULL); + /* Not connected yet. Wait */ + DBG("Connecting (%u)\n", connect_attempt); + break; + case MQTT_CLIENT_STATE_CONNECTED: + /* Don't subscribe unless we are a registered device */ + if(strncasecmp(conf->org_id, QUICKSTART, strlen(conf->org_id)) == 0) { + DBG("Using 'quickstart': Skipping subscribe\n"); + state = MQTT_CLIENT_STATE_PUBLISHING; + } + /* Continue */ + case MQTT_CLIENT_STATE_PUBLISHING: + /* If the timer expired, the connection is stable. */ + if(timer_expired(&connection_life)) { + /* + * Intentionally using 0 here instead of 1: We want RECONNECT_ATTEMPTS + * attempts if we disconnect after a successful connect + */ + connect_attempt = 0; + } + + if(mqtt_ready(&conn) && conn.out_buffer_sent) { + /* Connected. Publish */ + if(state == MQTT_CLIENT_STATE_CONNECTED) { + subscribe(); + state = MQTT_CLIENT_STATE_PUBLISHING; + } else { + leds_on(CC26XX_WEB_DEMO_STATUS_LED); + ctimer_set(&ct, PUBLISH_LED_ON_DURATION, publish_led_off, NULL); + publish(); + } + etimer_set(&publish_periodic_timer, conf->pub_interval); + + DBG("Publishing\n"); + /* Return here so we don't end up rescheduling the timer */ + return; + } else { + /* + * Our publish timer fired, but some MQTT packet is already in flight + * (either not sent at all, or sent but not fully ACKd). + * + * This can mean that we have lost connectivity to our broker or that + * simply there is some network delay. In both cases, we refuse to + * trigger a new message and we wait for TCP to either ACK the entire + * packet after retries, or to timeout and notify us. + */ + DBG("Publishing... (MQTT state=%d, q=%u)\n", conn.state, + conn.out_queue_full); + } + break; + case MQTT_CLIENT_STATE_DISCONNECTED: + DBG("Disconnected\n"); + if(connect_attempt < RECONNECT_ATTEMPTS || + RECONNECT_ATTEMPTS == RETRY_FOREVER) { + /* Disconnect and backoff */ + clock_time_t interval; + mqtt_disconnect(&conn); + connect_attempt++; + + interval = connect_attempt < 3 ? RECONNECT_INTERVAL << connect_attempt : + RECONNECT_INTERVAL << 3; + + DBG("Disconnected. Attempt %u in %lu ticks\n", connect_attempt, interval); + + etimer_set(&publish_periodic_timer, interval); + + state = MQTT_CLIENT_STATE_REGISTERED; + return; + } else { + /* Max reconnect attempts reached. Enter error state */ + state = MQTT_CLIENT_STATE_ERROR; + DBG("Aborting connection after %u attempts\n", connect_attempt - 1); + } + break; + case MQTT_CLIENT_STATE_NEWCONFIG: + /* Only update config after we have disconnected */ + if(conn.state == MQTT_CONN_STATE_NOT_CONNECTED) { + update_config(); + DBG("New config\n"); + + /* update_config() scheduled next pass. Return */ + return; + } + break; + case MQTT_CLIENT_STATE_CONFIG_ERROR: + /* Idle away. The only way out is a new config */ + printf("Bad configuration.\n"); + return; + case MQTT_CLIENT_STATE_ERROR: + default: + leds_on(CC26XX_WEB_DEMO_STATUS_LED); + /* + * 'default' should never happen. + * + * If we enter here it's because of some error. Stop timers. The only thing + * that can bring us out is a new config event + */ + printf("Default case: State=0x%02x\n", state); + return; + } + + /* If we didn't return so far, reschedule ourselves */ + etimer_set(&publish_periodic_timer, STATE_MACHINE_PERIODIC); +} +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(mqtt_client_process, ev, data) +{ + + PROCESS_BEGIN(); + + printf("CC26XX MQTT Client Process\n"); + + conf = &cc26xx_web_demo_config.mqtt_config; + if(init_config() != 1) { + PROCESS_EXIT(); + } + + register_http_post_handlers(); + + update_config(); + + def_rt_rssi = 0x8000000; + uip_icmp6_echo_reply_callback_add(&echo_reply_notification, + echo_reply_handler); + etimer_set(&echo_request_timer, conf->def_rt_ping_interval); + + /* Main loop */ + while(1) { + + PROCESS_YIELD(); + + if(ev == sensors_event && data == CC26XX_WEB_DEMO_MQTT_PUBLISH_TRIGGER) { + if(state == MQTT_CLIENT_STATE_ERROR) { + connect_attempt = 1; + state = MQTT_CLIENT_STATE_REGISTERED; + } + } + + if(ev == httpd_simple_event_new_config) { + /* + * Schedule next pass in a while. When HTTPD sends us this event, it is + * also in the process of sending the config page. Wait a little before + * reconnecting, so as to not cause congestion. + */ + etimer_set(&publish_periodic_timer, NEW_CONFIG_WAIT_INTERVAL); + } + + if((ev == PROCESS_EVENT_TIMER && data == &publish_periodic_timer) || + ev == PROCESS_EVENT_POLL || + ev == cc26xx_web_demo_publish_event || + (ev == sensors_event && data == CC26XX_WEB_DEMO_MQTT_PUBLISH_TRIGGER)) { + state_machine(); + } + + if(ev == PROCESS_EVENT_TIMER && data == &echo_request_timer) { + ping_parent(); + etimer_set(&echo_request_timer, conf->def_rt_ping_interval); + } + + if(ev == cc26xx_web_demo_load_config_defaults) { + init_config(); + etimer_set(&publish_periodic_timer, NEW_CONFIG_WAIT_INTERVAL); + } + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +/** + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/mqtt-client.h b/examples/cc26xx/cc26xx-web-demo/mqtt-client.h new file mode 100644 index 000000000..b4b50085f --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/mqtt-client.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * Header file for the CC26xx web demo MQTT client functionality + */ +/*---------------------------------------------------------------------------*/ +#ifndef MQTT_CLIENT_H_ +#define MQTT_CLIENT_H_ +/*---------------------------------------------------------------------------*/ +#define MQTT_CLIENT_CONFIG_ORG_ID_LEN 32 +#define MQTT_CLIENT_CONFIG_TYPE_ID_LEN 32 +#define MQTT_CLIENT_CONFIG_AUTH_TOKEN_LEN 32 +#define MQTT_CLIENT_CONFIG_EVENT_TYPE_ID_LEN 32 +#define MQTT_CLIENT_CONFIG_CMD_TYPE_LEN 8 +#define MQTT_CLIENT_CONFIG_IP_ADDR_STR_LEN 64 +/*---------------------------------------------------------------------------*/ +#define MQTT_CLIENT_RSSI_MEASURE_INTERVAL_MAX 86400 /* secs: 1 day */ +#define MQTT_CLIENT_RSSI_MEASURE_INTERVAL_MIN 5 /* secs */ +#define MQTT_CLIENT_PUBLISH_INTERVAL_MAX 86400 /* secs: 1 day */ +#define MQTT_CLIENT_PUBLISH_INTERVAL_MIN 5 /* secs */ +/*---------------------------------------------------------------------------*/ +PROCESS_NAME(mqtt_client_process); +/*---------------------------------------------------------------------------*/ +/** + * \brief Data structure declaration for the MQTT client configuration + */ +typedef struct mqtt_client_config { + char org_id[MQTT_CLIENT_CONFIG_ORG_ID_LEN]; + char type_id[MQTT_CLIENT_CONFIG_TYPE_ID_LEN]; + char auth_token[MQTT_CLIENT_CONFIG_AUTH_TOKEN_LEN]; + char event_type_id[MQTT_CLIENT_CONFIG_EVENT_TYPE_ID_LEN]; + char broker_ip[MQTT_CLIENT_CONFIG_IP_ADDR_STR_LEN]; + char cmd_type[MQTT_CLIENT_CONFIG_CMD_TYPE_LEN]; + clock_time_t pub_interval; + int def_rt_ping_interval; + uint16_t broker_port; +} mqtt_client_config_t; +/*---------------------------------------------------------------------------*/ +#endif /* MQTT_CLIENT_H_ */ +/*---------------------------------------------------------------------------*/ +/** + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/net-uart.c b/examples/cc26xx/cc26xx-web-demo/net-uart.c new file mode 100644 index 000000000..96463aee0 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/net-uart.c @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * A process which receives data over UART and transmits them over UDP + * to a pre-defined IPv6 address and port. It also listens on the same UDP + * port for messages, which it prints out over UART. + * + * For this example to work, you will have to modify the destination IPv6 + * address by adjusting the set_dest_addr() macro below. + * + * To listen on your linux or OS X box: + * nc -6ulkw 1 REMOTE_PORT + * + * (REMOTE_PORT should be the actual value of the define below, e.g. 7777) + * + * Once netcat is up and listening, type something to the CC26xx's terminal + * Bear in mind that the datagram will only be sent after a 0x0a (LF) char + * has been received. Therefore, if you are on Win, do NOT use PuTTY for + * this purpose, since it does not send 0x0a as part of the line end. On + * Win XP use hyperterm. On Win 7 use some other software (e.g. Tera Term, + * which can be configured to send CRLF on enter keystrokes). + * + * To send data in the other direction from your linux or OS X box: + * + * nc -6u \ REMOTE_PORT + */ +/*---------------------------------------------------------------------------*/ +#include "contiki-conf.h" +#include "sys/process.h" +#include "dev/serial-line.h" +#include "net/ip/uip.h" +#include "net/ip/uip-udp-packet.h" +#include "net/ip/uiplib.h" +#include "lpm.h" +#include "net-uart.h" +#include "httpd-simple.h" + +#include "ti-lib.h" + +#include +#include +#include +#include +/*---------------------------------------------------------------------------*/ +#define DEBUG DEBUG_NONE +#include "net/ip/uip-debug.h" +/*---------------------------------------------------------------------------*/ +#define REMOTE_PORT 7777 +#define MAX_MSG_SIZE 100 + +#define set_dest_addr() uip_ip6addr(&remote_addr, \ + 0xBBBB, 0x0000, 0x0000, 0x0000, \ + 0x3E07, 0x54FF, 0xFE74, 0x4885); +/*---------------------------------------------------------------------------*/ +#define ADDRESS_CONVERSION_OK 1 +#define ADDRESS_CONVERSION_ERROR 0 +/*---------------------------------------------------------------------------*/ +#ifndef MIN +#define MIN(n, m) (((n) < (m)) ? (n) : (m)) +#endif +/*---------------------------------------------------------------------------*/ +static struct uip_udp_conn *udp_conn = NULL; + +static uint8_t buffer[MAX_MSG_SIZE]; +static uint8_t msg_len; +static uip_ip6addr_t remote_addr; +/*---------------------------------------------------------------------------*/ +#define IPV6_ADDR_STR_LEN 64 +/*---------------------------------------------------------------------------*/ +PROCESS(net_uart_process, "Net UART Process"); +/*---------------------------------------------------------------------------*/ +/* + * \brief Attempts to convert a string representation of an IPv6 address to a + * numeric one. + * \param buf The buffer with the string to be converted. + * \return ADDRESS_CONVERSION_OK or ADDRESS_CONVERSION_ERROR + * + * ToDo: Add support for NAT64 conversion in case the incoming address is a v4 + * This is now supported in the current master, so when we pull it in this will + * be very straightforward. + */ +static int +set_new_ip_address(char *buf) +{ + /* + * uiplib_ip6addrconv will immediately start writing into the supplied buffer + * even if it subsequently fails. Thus, pass an intermediate buffer + */ + uip_ip6addr_t tmp_addr; + + int rv = uiplib_ip6addrconv(buf, &tmp_addr); + + if(rv == ADDRESS_CONVERSION_OK) { + /* Conversion OK, copy to our main buffer */ + memcpy(&remote_addr, &tmp_addr, sizeof(remote_addr)); + + PRINTF("Updated remote address "); + PRINT6ADDR(&remote_addr); + PRINTF("\n"); + } + + return rv; +} +/*---------------------------------------------------------------------------*/ +static void +net_input(void) +{ + if(uip_newdata()) { + memset(buffer, 0, MAX_MSG_SIZE); + msg_len = MIN(uip_datalen(), MAX_MSG_SIZE - 1); + + /* Copy data */ + memcpy(buffer, uip_appdata, msg_len); + printf("%s", (char *)buffer); + } + + return; +} +/*---------------------------------------------------------------------------*/ +/* + * In order to maintain UART input operation: + * - Keep the uart clocked in sleep and deep sleep + * - Keep the serial PD on in deep sleep + */ +static lpm_power_domain_lock_t lock; +/*---------------------------------------------------------------------------*/ +static void +release_uart(void) +{ + /* Release serial PD lock */ + lpm_pd_lock_release(&lock); + + /* Let the UART turn off during Sleep and Deep Sleep */ + ti_lib_prcm_peripheral_sleep_disable(PRCM_PERIPH_UART0); + ti_lib_prcm_peripheral_deep_sleep_disable(PRCM_PERIPH_UART0); + ti_lib_prcm_load_set(); + while(!ti_lib_prcm_load_get()); +} +/*---------------------------------------------------------------------------*/ +static void +keep_uart_on(void) +{ + /* Keep the serial PD on */ + lpm_pd_lock_obtain(&lock, PRCM_DOMAIN_SERIAL); + + /* Keep the UART clock on during Sleep and Deep Sleep */ + ti_lib_prcm_peripheral_sleep_enable(PRCM_PERIPH_UART0); + ti_lib_prcm_peripheral_deep_sleep_enable(PRCM_PERIPH_UART0); + ti_lib_prcm_load_set(); + while(!ti_lib_prcm_load_get()); +} +/*---------------------------------------------------------------------------*/ +static int +remote_port_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv; + + if(key_len != strlen("net_uart_port") || + strncasecmp(key, "net_uart_port", strlen("net_uart_port")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + rv = atoi(val); + + if(rv <= 65535 && rv > 0) { + cc26xx_web_demo_config.net_uart.remote_port = (uint16_t)rv; + } else { + return HTTPD_SIMPLE_POST_HANDLER_ERROR; + } + + return HTTPD_SIMPLE_POST_HANDLER_OK; +} +/*---------------------------------------------------------------------------*/ +static int +remote_ipv6_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv = HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + + if(key_len != strlen("net_uart_ip") || + strncasecmp(key, "net_uart_ip", strlen("net_uart_ip")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + if(val_len > IPV6_ADDR_STR_LEN) { + /* Ours but bad value */ + rv = HTTPD_SIMPLE_POST_HANDLER_ERROR; + } else { + if(set_new_ip_address(val)) { + memset(cc26xx_web_demo_config.net_uart.remote_address, 0, + NET_UART_IP_ADDR_STRLEN); + memcpy(cc26xx_web_demo_config.net_uart.remote_address, val, val_len); + rv = HTTPD_SIMPLE_POST_HANDLER_OK; + } + } + + return rv; +} +/*---------------------------------------------------------------------------*/ +static int +on_off_post_handler(char *key, int key_len, char *val, int val_len) +{ + int rv; + + if(key_len != strlen("net_uart_on") || + strncasecmp(key, "net_uart_on", strlen("net_uart_on")) != 0) { + /* Not ours */ + return HTTPD_SIMPLE_POST_HANDLER_UNKNOWN; + } + + rv = atoi(val); + + /* Be pedantic: only accept 0 and 1, not just any non-zero value */ + if(rv == 0) { + cc26xx_web_demo_config.net_uart.enable = 0; + release_uart(); + } else if(rv == 1) { + cc26xx_web_demo_config.net_uart.enable = 1; + keep_uart_on(); + } else { + return HTTPD_SIMPLE_POST_HANDLER_ERROR; + } + + return HTTPD_SIMPLE_POST_HANDLER_OK; +} +/*---------------------------------------------------------------------------*/ +HTTPD_SIMPLE_POST_HANDLER(remote_port, remote_port_post_handler); +HTTPD_SIMPLE_POST_HANDLER(remote_ipv6, remote_ipv6_post_handler); +HTTPD_SIMPLE_POST_HANDLER(on_off, on_off_post_handler); +/*---------------------------------------------------------------------------*/ +static void +set_config_defaults(void) +{ + /* Set a hard-coded destination address to start with */ + set_dest_addr(); + + /* Set config defaults */ + cc26xx_web_demo_ipaddr_sprintf(cc26xx_web_demo_config.net_uart.remote_address, + NET_UART_IP_ADDR_STRLEN, &remote_addr); + cc26xx_web_demo_config.net_uart.remote_port = REMOTE_PORT; + cc26xx_web_demo_config.net_uart.enable = 1; +} +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(net_uart_process, ev, data) +{ + PROCESS_BEGIN(); + + printf("CC26XX Net UART Process\n"); + + set_config_defaults(); + + udp_conn = udp_new(NULL, UIP_HTONS(0), NULL); + udp_bind(udp_conn, UIP_HTONS(REMOTE_PORT)); + + if(udp_conn == NULL) { + printf("No UDP connection available, exiting the process!\n"); + PROCESS_EXIT(); + } + + httpd_simple_register_post_handler(&remote_port_handler); + httpd_simple_register_post_handler(&remote_ipv6_handler); + httpd_simple_register_post_handler(&on_off_handler); + + while(1) { + + PROCESS_YIELD(); + + if(ev == serial_line_event_message) { + /* + * If the message contains a new IP address, save it and go back to + * waiting. + */ + if(set_new_ip_address((char *)data) == ADDRESS_CONVERSION_ERROR) { + /* Not an IP address in the message. Send to current destination */ + memset(buffer, 0, MAX_MSG_SIZE); + + /* We need to add a line feed, thus never fill the entire buffer */ + msg_len = MIN(strlen(data), MAX_MSG_SIZE - 1); + memcpy(buffer, data, msg_len); + + /* Add a line feed */ + buffer[msg_len] = 0x0A; + msg_len++; + + uip_udp_packet_sendto( + udp_conn, buffer, msg_len, &remote_addr, + UIP_HTONS(cc26xx_web_demo_config.net_uart.remote_port)); + } + } else if(ev == tcpip_event) { + net_input(); + } else if(ev == cc26xx_web_demo_config_loaded_event) { + /* + * New config. Check if it's possible to update the remote address. + * The port will have been updated already + */ + set_new_ip_address(cc26xx_web_demo_config.net_uart.remote_address); + + if(cc26xx_web_demo_config.net_uart.enable == 1) { + keep_uart_on(); + } + } else if(ev == cc26xx_web_demo_load_config_defaults) { + set_config_defaults(); + } + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +/** + * @} + * @} + */ diff --git a/examples/cc26xx/cc26xx-web-demo/net-uart.h b/examples/cc26xx/cc26xx-web-demo/net-uart.h new file mode 100644 index 000000000..5c4201672 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/net-uart.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef NET_UART_H_ +#define NET_UART_H_ +/*---------------------------------------------------------------------------*/ +#include "net/ip/uip.h" + +#include +/*---------------------------------------------------------------------------*/ +#define NET_UART_IP_ADDR_STRLEN 64 +/*---------------------------------------------------------------------------*/ +PROCESS_NAME(net_uart_process); +/*---------------------------------------------------------------------------*/ +typedef struct net_uart_config_s { + char remote_address[NET_UART_IP_ADDR_STRLEN]; + uint16_t remote_port; + uint8_t enable; +} net_uart_config_t; +/*---------------------------------------------------------------------------*/ +#endif /* NET_UART_H_ */ +/*---------------------------------------------------------------------------*/ diff --git a/examples/cc26xx/cc26xx-web-demo/project-conf.h b/examples/cc26xx/cc26xx-web-demo/project-conf.h new file mode 100644 index 000000000..d49a7b3e9 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/project-conf.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +#ifndef PROJECT_CONF_H_ +#define PROJECT_CONF_H_ +/*---------------------------------------------------------------------------*/ +/* Change to match your configuration */ +#define NETSTACK_CONF_RDC contikimac_driver +#define IEEE802154_CONF_PANID 0xABCD +#define CC26XX_RF_CONF_CHANNEL 25 +#define CC26XX_MODEL_CONF_CPU_VARIANT 2650 /* CC2650 */ +#define CC26XX_RF_CONF_BLE_SUPPORT 1 /* Only available with CC2650 */ +/*---------------------------------------------------------------------------*/ +/* Enable/Disable Components of this Demo */ +#define CC26XX_WEB_DEMO_CONF_MQTT_CLIENT 1 +#define CC26XX_WEB_DEMO_CONF_6LBR_CLIENT 1 +#define CC26XX_WEB_DEMO_CONF_COAP_SERVER 1 +#define CC26XX_WEB_DEMO_CONF_NET_UART 1 +/*---------------------------------------------------------------------------*/ +/* Shrink the size of the uIP buffer, routing table and ND cache */ +#define UIP_CONF_BUFFER_SIZE 900 +#define NBR_TABLE_CONF_MAX_NEIGHBORS 8 +#define UIP_CONF_MAX_ROUTES 8 +/*---------------------------------------------------------------------------*/ +#endif /* PROJECT_CONF_H_ */ +/*---------------------------------------------------------------------------*/ diff --git a/examples/cc26xx/cc26xx-web-demo/resources/res-ble-advd.c b/examples/cc26xx/cc26xx-web-demo/resources/res-ble-advd.c new file mode 100644 index 000000000..0604c7ac9 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/resources/res-ble-advd.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2015, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * CoAP resource to start/stop/configure BLE advertisements + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "rest-engine.h" +#include "er-coap.h" +#include "dev/cc26xx-rf.h" + +#include +#include +/*---------------------------------------------------------------------------*/ +#define BLE_NAME_BUF_LEN 32 +/*---------------------------------------------------------------------------*/ +const char *forbidden_payload = "Name to advertise unspecified.\n" + "Use name= in the request"; +/*---------------------------------------------------------------------------*/ +static void +res_ble_post_put_handler(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + size_t len = 0; + const char *text = NULL; + char name[BLE_NAME_BUF_LEN]; + int success = 0; + int rv; + + memset(name, 0, BLE_NAME_BUF_LEN); + + len = REST.get_post_variable(request, "name", &text); + + if(len > 0 && len < BLE_NAME_BUF_LEN) { + memcpy(name, text, len); + cc26xx_rf_ble_beacond_config(0, name); + success = 1; + } + + len = REST.get_post_variable(request, "interval", &text); + + rv = atoi(text); + + if(rv > 0) { + cc26xx_rf_ble_beacond_config((clock_time_t)(rv * CLOCK_SECOND), NULL); + success = 1; + } + + len = REST.get_post_variable(request, "mode", &text); + + if(len) { + if(strncmp(text, "on", len) == 0) { + if(cc26xx_rf_ble_beacond_start()) { + success = 1; + } else { + REST.set_response_status(response, REST.status.FORBIDDEN); + REST.set_response_payload(response, forbidden_payload, + strlen(forbidden_payload)); + return; + } + } else if(strncmp(text, "off", len) == 0) { + cc26xx_rf_ble_beacond_stop(); + success = 1; + } else { + success = 0; + } + } + + if(!success) { + REST.set_response_status(response, REST.status.BAD_REQUEST); + } +} +/*---------------------------------------------------------------------------*/ +RESOURCE(res_ble_advd, + "title=\"BLE advd config: POST/PUT name=&mode=on|off" + "&interval=\";rt=\"Control\"", + NULL, + res_ble_post_put_handler, + res_ble_post_put_handler, + NULL); +/*---------------------------------------------------------------------------*/ +/** @} */ diff --git a/examples/cc26xx/cc26xx-web-demo/resources/res-device.c b/examples/cc26xx/cc26xx-web-demo/resources/res-device.c new file mode 100644 index 000000000..84cef8e24 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/resources/res-device.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * CoAP resource handler for CC26XX software and hardware version + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "rest-engine.h" +#include "er-coap.h" +#include "sys/clock.h" +#include "cc26xx-model.h" +#include "coap-server.h" +#include "cc26xx-web-demo.h" + +#include +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_hw(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + unsigned int accept = -1; + + REST.get_header_accept(request, &accept); + + if(accept == -1 || accept == REST.type.TEXT_PLAIN) { + REST.set_header_content_type(response, REST.type.TEXT_PLAIN); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "%s on CC%u", BOARD_STRING, + CC26XX_MODEL_CPU_VARIANT); + + REST.set_response_payload(response, (uint8_t *)buffer, strlen((char *)buffer)); + } else if(accept == REST.type.APPLICATION_JSON) { + REST.set_header_content_type(response, REST.type.APPLICATION_JSON); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "{\"HW Ver\":\"%s on CC%u\"}", + BOARD_STRING, CC26XX_MODEL_CPU_VARIANT); + + REST.set_response_payload(response, buffer, strlen((char *)buffer)); + } else if(accept == REST.type.APPLICATION_XML) { + REST.set_header_content_type(response, REST.type.APPLICATION_XML); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, + "", BOARD_STRING, + CC26XX_MODEL_CPU_VARIANT); + + REST.set_response_payload(response, buffer, strlen((char *)buffer)); + } else { + REST.set_response_status(response, REST.status.NOT_ACCEPTABLE); + REST.set_response_payload(response, coap_server_supported_msg, + strlen(coap_server_supported_msg)); + } +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_sw(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + unsigned int accept = -1; + + REST.get_header_accept(request, &accept); + + if(accept == -1 || accept == REST.type.TEXT_PLAIN) { + REST.set_header_content_type(response, REST.type.TEXT_PLAIN); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "%s", CONTIKI_VERSION_STRING); + + REST.set_response_payload(response, (uint8_t *)buffer, strlen((char *)buffer)); + } else if(accept == REST.type.APPLICATION_JSON) { + REST.set_header_content_type(response, REST.type.APPLICATION_JSON); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "{\"SW Ver\":\"%s\"}", + CONTIKI_VERSION_STRING); + + REST.set_response_payload(response, buffer, strlen((char *)buffer)); + } else if(accept == REST.type.APPLICATION_XML) { + REST.set_header_content_type(response, REST.type.APPLICATION_XML); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, + "", CONTIKI_VERSION_STRING); + + REST.set_response_payload(response, buffer, strlen((char *)buffer)); + } else { + REST.set_response_status(response, REST.status.NOT_ACCEPTABLE); + REST.set_response_payload(response, coap_server_supported_msg, + strlen(coap_server_supported_msg)); + } +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_uptime(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + unsigned int accept = -1; + + REST.get_header_accept(request, &accept); + + if(accept == -1 || accept == REST.type.TEXT_PLAIN) { + REST.set_header_content_type(response, REST.type.TEXT_PLAIN); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "%lu", clock_seconds()); + + REST.set_response_payload(response, (uint8_t *)buffer, strlen((char *)buffer)); + } else if(accept == REST.type.APPLICATION_JSON) { + REST.set_header_content_type(response, REST.type.APPLICATION_JSON); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "{\"uptime\":%lu}", + clock_seconds()); + + REST.set_response_payload(response, buffer, strlen((char *)buffer)); + } else if(accept == REST.type.APPLICATION_XML) { + REST.set_header_content_type(response, REST.type.APPLICATION_XML); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, + "", clock_seconds()); + + REST.set_response_payload(response, buffer, strlen((char *)buffer)); + } else { + REST.set_response_status(response, REST.status.NOT_ACCEPTABLE); + REST.set_response_payload(response, coap_server_supported_msg, + strlen(coap_server_supported_msg)); + } +} +/*---------------------------------------------------------------------------*/ +static void +res_post_handler_cfg_reset(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + cc26xx_web_demo_restore_defaults(); +} +/*---------------------------------------------------------------------------*/ +RESOURCE(res_device_sw, + "title=\"Software version\";rt=\"text\"", + res_get_handler_sw, + NULL, + NULL, + NULL); +/*---------------------------------------------------------------------------*/ +RESOURCE(res_device_uptime, + "title=\"Uptime\";rt=\"seconds\"", + res_get_handler_uptime, + NULL, + NULL, + NULL); +/*---------------------------------------------------------------------------*/ +RESOURCE(res_device_hw, + "title=\"Hardware version\";rt=\"text\"", + res_get_handler_hw, + NULL, + NULL, + NULL); +/*---------------------------------------------------------------------------*/ +RESOURCE(res_device_cfg_reset, + "title=\"Reset Device Config: POST\";rt=\"Control\"", + NULL, res_post_handler_cfg_reset, NULL, NULL); +/*---------------------------------------------------------------------------*/ +/** @} */ diff --git a/examples/cc26xx/cc26xx-web-demo/resources/res-leds.c b/examples/cc26xx/cc26xx-web-demo/resources/res-leds.c new file mode 100644 index 000000000..2a86ad25b --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/resources/res-leds.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2013, Institute for Pervasive Computing, ETH Zurich + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * CoAP resource handler for the CC26xx LEDs. Slightly modified copy of + * the one found in Contiki's original CoAP example. + * \author + * Matthias Kovatsch (original) + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "rest-engine.h" +#include "dev/leds.h" + +#include +/*---------------------------------------------------------------------------*/ +static void +res_post_put_handler(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + size_t len = 0; + const char *color = NULL; + const char *mode = NULL; + uint8_t led = 0; + int success = 1; + + if((len = REST.get_query_variable(request, "color", &color))) { + if(strncmp(color, "r", len) == 0) { + led = LEDS_RED; + } else if(strncmp(color, "g", len) == 0) { + led = LEDS_GREEN; +#if BOARD_SMARTRF06EB + } else if(strncmp(color, "y", len) == 0) { + led = LEDS_YELLOW; + } else if(strncmp(color, "o", len) == 0) { + led = LEDS_ORANGE; +#endif + } else { + success = 0; + } + } else { + success = 0; + } + + if(success && (len = REST.get_post_variable(request, "mode", &mode))) { + if(strncmp(mode, "on", len) == 0) { + leds_on(led); + } else if(strncmp(mode, "off", len) == 0) { + leds_off(led); + } else { + success = 0; + } + } else { + success = 0; + } + + if(!success) { + REST.set_response_status(response, REST.status.BAD_REQUEST); + } +} +/*---------------------------------------------------------------------------*/ +/* + * A simple actuator example, depending on the color query parameter and post + * variable mode, corresponding led is activated or deactivated + */ +#if BOARD_SENSORTAG +#define RESOURCE_PARAMS "r|g" +#elif BOARD_SMARTRF06EB +#define RESOURCE_PARAMS "r|g|y|o" +#endif + +RESOURCE(res_leds, + "title=\"LEDs: ?color=" RESOURCE_PARAMS ", POST/PUT mode=on|off\";rt=\"Control\"", + NULL, + res_post_put_handler, + res_post_put_handler, + NULL); +/*---------------------------------------------------------------------------*/ +/** @} */ diff --git a/examples/cc26xx/cc26xx-web-demo/resources/res-sensors.c b/examples/cc26xx/cc26xx-web-demo/resources/res-sensors.c new file mode 100644 index 000000000..fecfca994 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/resources/res-sensors.c @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * CoAP resource handler for the Sensortag-CC26xx sensors + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "rest-engine.h" +#include "er-coap.h" +#include "cc26xx-web-demo.h" +#include "coap-server.h" + +#include +#include +#include +/*---------------------------------------------------------------------------*/ +/* + * Generic resource handler for any sensor in this example. Ultimately gets + * called by all handlers and populates the CoAP response + */ +static void +res_get_handler_all(int sens_type, void *request, void *response, + uint8_t *buffer, uint16_t preferred_size, int32_t *offset) +{ + unsigned int accept = -1; + const cc26xx_web_demo_sensor_reading_t *reading; + + reading = cc26xx_web_demo_sensor_lookup(sens_type); + + if(reading == NULL) { + REST.set_response_status(response, REST.status.NOT_FOUND); + REST.set_response_payload(response, coap_server_not_found_msg, + strlen(coap_server_not_found_msg)); + return; + } + + REST.get_header_accept(request, &accept); + + if(accept == -1 || accept == REST.type.TEXT_PLAIN) { + REST.set_header_content_type(response, REST.type.TEXT_PLAIN); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "%s", reading->converted); + + REST.set_response_payload(response, (uint8_t *)buffer, + strlen((char *)buffer)); + } else if(accept == REST.type.APPLICATION_JSON) { + REST.set_header_content_type(response, REST.type.APPLICATION_JSON); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, "{\"%s\":%s}", + reading->descr, reading->converted); + + REST.set_response_payload(response, buffer, strlen((char *)buffer)); + } else if(accept == REST.type.APPLICATION_XML) { + REST.set_header_content_type(response, REST.type.APPLICATION_XML); + snprintf((char *)buffer, REST_MAX_CHUNK_SIZE, + "<%s val=\"%s\" unit=\"%s\"/>", reading->xml_element, + reading->converted, reading->units); + + REST.set_response_payload(response, buffer, strlen((char *)buffer)); + } else { + REST.set_response_status(response, REST.status.NOT_ACCEPTABLE); + REST.set_response_payload(response, coap_server_supported_msg, + strlen(coap_server_supported_msg)); + } +} +/*---------------------------------------------------------------------------*/ +/* BatMon resources and handler: Temperature, Voltage */ +static void +res_get_handler_batmon_temp(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_BATMON_TEMP, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_batmon_volt(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_BATMON_VOLT, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +RESOURCE(res_batmon_temp, "title=\"Battery Temp\";rt=\"C\"", + res_get_handler_batmon_temp, NULL, NULL, NULL); +/*---------------------------------------------------------------------------*/ +RESOURCE(res_batmon_volt, "title=\"Battery Voltage\";rt=\"mV\"", + res_get_handler_batmon_volt, NULL, NULL, NULL); +/*---------------------------------------------------------------------------*/ +#if BOARD_SENSORTAG +/*---------------------------------------------------------------------------*/ +/* MPU resources and handler: Accelerometer and Gyro */ +static void +res_get_handler_mpu_acc_x(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_MPU_ACC_X, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_mpu_acc_y(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_MPU_ACC_Y, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_mpu_acc_z(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_MPU_ACC_Z, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_mpu_gyro_x(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_X, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_mpu_gyro_y(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_Y, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_mpu_gyro_z(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_MPU_GYRO_Z, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +RESOURCE(res_mpu_acc_x, "title=\"Acc X\";rt=\"G\"", res_get_handler_mpu_acc_x, + NULL, NULL, NULL); +RESOURCE(res_mpu_acc_y, "title=\"Acc Y\";rt=\"G\"", res_get_handler_mpu_acc_y, + NULL, NULL, NULL); +RESOURCE(res_mpu_acc_z, "title=\"Acc Z\";rt=\"G\"", res_get_handler_mpu_acc_z, + NULL, NULL, NULL); + +RESOURCE(res_mpu_gyro_x, "title=\"Gyro X\";rt=\"deg/sec\"", + res_get_handler_mpu_gyro_x, NULL, NULL, NULL); +RESOURCE(res_mpu_gyro_y, "title=\"Gyro Y\";rt=\"deg/sec\"", + res_get_handler_mpu_gyro_y, NULL, NULL, NULL); +RESOURCE(res_mpu_gyro_z, "title=\"Gyro Z\";rt=\"deg/sec\"", + res_get_handler_mpu_gyro_z, NULL, NULL, NULL); +/*---------------------------------------------------------------------------*/ +/* TMP sensor resources and handlers: Object, Ambient */ +static void +res_get_handler_obj_temp(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_TMP_OBJECT, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_amb_temp(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_TMP_AMBIENT, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +RESOURCE(res_tmp007_obj, "title=\"Temperature (Object)\";rt=\"C\"", + res_get_handler_obj_temp, NULL, NULL, NULL); + +RESOURCE(res_tmp007_amb, "title=\"Temperature (Ambient)\";rt=\"C\"", + res_get_handler_amb_temp, NULL, NULL, NULL); +/*---------------------------------------------------------------------------*/ +/* BMP sensor resources: Temperature, Pressure */ +static void +res_get_handler_bmp_temp(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_BMP_TEMP, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_bmp_press(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_BMP_PRES, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +RESOURCE(res_bmp280_temp, "title=\"Barometer (Temperature)\";rt=\"C\"", + res_get_handler_bmp_temp, NULL, NULL, NULL); + +RESOURCE(res_bmp280_press, + "title=\"Barometer (Pressure)\";rt=\"hPa (hectopascal / millibar)\"", + res_get_handler_bmp_press, NULL, NULL, NULL); +/*---------------------------------------------------------------------------*/ +/* SHT21 sensor resources and handler: Temperature, Pressure */ +static void +res_get_handler_sht_temp(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_SHT_TEMP, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +static void +res_get_handler_sht_humidity(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_SHT_HUMIDITY, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +RESOURCE(res_sht21_temp, "title=\"Temperature\";rt=\"C\"", + res_get_handler_sht_temp, NULL, NULL, NULL); + +RESOURCE(res_sht21_hum, "title=\"Humidity\";rt=\"%RH\"", + res_get_handler_sht_humidity, NULL, NULL, NULL); +/*---------------------------------------------------------------------------*/ +/* Illuminance resources and handler */ +static void +res_get_handler_opt(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + res_get_handler_all(CC26XX_WEB_DEMO_SENSOR_OPT_LIGHT, request, response, + buffer, preferred_size, offset); +} +/*---------------------------------------------------------------------------*/ +RESOURCE(res_opt3001_light, "title=\"Illuminance\";rt=\"Lux\"", + res_get_handler_opt, NULL, NULL, NULL); +/*---------------------------------------------------------------------------*/ +#endif /* BOARD_SENSORTAG */ +/*---------------------------------------------------------------------------*/ +/** @} */ diff --git a/examples/cc26xx/cc26xx-web-demo/resources/res-toggle-leds.c b/examples/cc26xx/cc26xx-web-demo/resources/res-toggle-leds.c new file mode 100644 index 000000000..2fb424a03 --- /dev/null +++ b/examples/cc26xx/cc26xx-web-demo/resources/res-toggle-leds.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2013, Institute for Pervasive Computing, ETH Zurich + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + */ +/*---------------------------------------------------------------------------*/ +/** + * \addtogroup cc26xx-web-demo + * @{ + * + * \file + * CoAP resource to toggle LEDs. Slightly modified copy of the one found + * in Contiki's original CoAP example. + * \author + * Matthias Kovatsch (original) + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "rest-engine.h" +#include "dev/leds.h" + +#include +/*---------------------------------------------------------------------------*/ +static void +res_post_handler_red(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + leds_toggle(LEDS_RED); +} +/*---------------------------------------------------------------------------*/ +static void +res_post_handler_green(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + leds_toggle(LEDS_GREEN); +} +/*---------------------------------------------------------------------------*/ +/* Toggles the red led */ +RESOURCE(res_toggle_red, + "title=\"Red LED\";rt=\"Control\"", + NULL, + res_post_handler_red, + NULL, + NULL); + +/* Toggles the green led */ +RESOURCE(res_toggle_green, + "title=\"Green LED\";rt=\"Control\"", + NULL, + res_post_handler_green, + NULL, + NULL); +/*---------------------------------------------------------------------------*/ +/* An additional 2 LEDs on the Srf */ +#if BOARD_SMARTRF06EB +/*---------------------------------------------------------------------------*/ +static void +res_post_handler_yellow(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + leds_toggle(LEDS_YELLOW); +} +/*---------------------------------------------------------------------------*/ +static void +res_post_handler_orange(void *request, void *response, uint8_t *buffer, + uint16_t preferred_size, int32_t *offset) +{ + leds_toggle(LEDS_ORANGE); +} +/*---------------------------------------------------------------------------*/ +/* Toggles the yellow led */ +RESOURCE(res_toggle_yellow, + "title=\"Yellow LED\";rt=\"Control\"", + NULL, + res_post_handler_yellow, + NULL, + NULL); + +/* Toggles the orange led */ +RESOURCE(res_toggle_orange, + "title=\"Orange LED\";rt=\"Control\"", + NULL, + res_post_handler_orange, + NULL, + NULL); +#endif +/*---------------------------------------------------------------------------*/ +/** @} */ diff --git a/examples/cc26xx/project-conf.h b/examples/cc26xx/project-conf.h new file mode 100644 index 000000000..97bb52591 --- /dev/null +++ b/examples/cc26xx/project-conf.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +#ifndef PROJECT_CONF_H_ +#define PROJECT_CONF_H_ +/*---------------------------------------------------------------------------*/ +/* Disable button shutdown functionality */ +#define BUTTON_SENSOR_CONF_ENABLE_SHUTDOWN 0 +/*---------------------------------------------------------------------------*/ +/* Change to match your configuration */ +#define NETSTACK_CONF_RDC contikimac_driver +#define IEEE802154_CONF_PANID 0xABCD +#define CC26XX_RF_CONF_CHANNEL 25 +#define CC26XX_MODEL_CONF_CPU_VARIANT 2650 /* CC2650 */ +#define CC26XX_RF_CONF_BLE_SUPPORT 1 /* Only available with CC2650 */ +/*---------------------------------------------------------------------------*/ +#endif /* PROJECT_CONF_H_ */ +/*---------------------------------------------------------------------------*/