diff --git a/.travis.yml b/.travis.yml index b22d9bb..dfb0fda 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,13 @@ language: c sudo: false env: # Target commit for https://github.com/pfalcon/esp-open-sdk/ - OPENSDK_COMMIT=cd1d336 + OPENSDK_COMMIT=9bd3aba CROSS_ROOT="${HOME}/toolchain-${OPENSDK_COMMIT}" CROSS_BINDIR="${CROSS_ROOT}/bin" ESPTOOL2_COMMIT=ec0e2c7 ESPTOOL2_DIR="${HOME}/esptool2-${ESPTOOL2_COMMIT}" PATH=${PATH}:${CROSS_BINDIR}:${ESPTOOL2_DIR} + MAKE_CMD="make WARNINGS_AS_ERRORS=1 -C examples/ build-examples CROSS=\"ccache xtensa-lx106-elf-\"" cache: directories: - ${CROSS_ROOT} @@ -57,5 +58,7 @@ before_install: script: - cd ${TRAVIS_BUILD_DIR} # Remove ssid_config requirement for examples - - sed -i "s%#warning%//#warning%" include/ssid_config.h - - make -C examples/ build-examples CROSS="ccache xtensa-lx106-elf-" V=1 + - sed -i "s%#error%//#error%" include/ssid_config.h + # Try building everything w/ non-verbose output (to avoid 4MB log limit), but if it fails then + # rerun with V=1 so the error part is output verbosely + - ${MAKE_CMD} || ( ${MAKE_CMD} V=1 ) diff --git a/FreeRTOS/Source/portable/esp8266/port.c b/FreeRTOS/Source/portable/esp8266/port.c index fc452f0..21a0d55 100644 --- a/FreeRTOS/Source/portable/esp8266/port.c +++ b/FreeRTOS/Source/portable/esp8266/port.c @@ -178,9 +178,6 @@ void xPortSysTickHandle (void) //OpenNMI(); } -static bool sdk_compat_initialised; -void sdk_compat_initialise(void); - /* * See header file for description. */ @@ -189,14 +186,6 @@ portBASE_TYPE xPortStartScheduler( void ) _xt_isr_attach(INUM_SOFT, SV_ISR); _xt_isr_unmask(BIT(INUM_SOFT)); - /* ENORMOUS HACK: Call the sdk_compat_initialise() function. - This can be removed happily once we have open source startup code. - */ - if(!sdk_compat_initialised) { - sdk_compat_initialised = true; - sdk_compat_initialise(); - } - /* Initialize system tick timer interrupt and schedule the first tick. */ _xt_isr_attach(INUM_TICK, sdk__xt_timer_int); _xt_isr_unmask(BIT(INUM_TICK)); diff --git a/README.md b/README.md index f709216..50edf61 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Please note that this project is released with a [Contributor Code of Conduct](h ## Quick Start -* Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/), build it with `make STANDALONE=n`, then edit your PATH and add the generated toolchain `bin` directory. The path will be something like `/path/to/esp-open-sdk/xtensa-lx106-elf/bin`. (Despite the similar name esp-open-sdk has different maintainers - but we think it's fantastic!) +* Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/), build it with `make toolchain esptool libhal STANDALONE=n`, then edit your PATH and add the generated toolchain `bin` directory. The path will be something like `/path/to/esp-open-sdk/xtensa-lx106-elf/bin`. (Despite the similar name esp-open-sdk has different maintainers - but we think it's fantastic!) (Other toolchains may also work, as long as a gcc cross-compiler is available on the PATH and libhal (and libhal headers) are compiled and available to gcc. The proprietary Tensilica "xcc" compiler will probably not work.) diff --git a/common.mk b/common.mk index 7706a15..a9ecd69 100644 --- a/common.mk +++ b/common.mk @@ -75,6 +75,9 @@ FLAVOR ?= release # or debug # Compiler names, etc. assume gdb CROSS ?= xtensa-lx106-elf- +# Path to the filteroutput.py tool +FILTEROUTPUT ?= $(ROOT)/utils/filteroutput.py + AR = $(CROSS)ar CC = $(CROSS)gcc CPP = $(CROSS)cpp @@ -106,8 +109,14 @@ ENTRY_SYMBOL ?= call_user_start # but compiled code size will come down a small amount.) SPLIT_SECTIONS ?= 1 +# Set this to 1 to have all compiler warnings treated as errors (and stop the +# build). This is recommended whenever you are working on code which will be +# submitted back to the main project, as all submitted code will be expected to +# compile without warnings to be accepted. +WARNINGS_AS_ERRORS ?= 0 + # Common flags for both C & C++_ -C_CXX_FLAGS ?= -Wall -Werror -Wl,-EL -nostdlib $(EXTRA_C_CXX_FLAGS) +C_CXX_FLAGS ?= -Wall -Wl,-EL -nostdlib $(EXTRA_C_CXX_FLAGS) # Flags for C only CFLAGS ?= $(C_CXX_FLAGS) -std=gnu99 $(EXTRA_CFLAGS) # Flags for C++ only @@ -118,6 +127,10 @@ CPPFLAGS += -mlongcalls -mtext-section-literals LDFLAGS = -nostdlib -Wl,--no-check-sections -L$(BUILD_DIR)sdklib -L$(ROOT)lib -u $(ENTRY_SYMBOL) -Wl,-static -Wl,-Map=$(BUILD_DIR)$(PROGRAM).map $(EXTRA_LDFLAGS) +ifeq ($(WARNINGS_AS_ERRORS),1) + C_CXX_FLAGS += -Werror +endif + ifeq ($(SPLIT_SECTIONS),1) C_CXX_FLAGS += -ffunction-sections -fdata-sections LDFLAGS += -Wl,-gc-sections @@ -375,7 +388,7 @@ size: $(PROGRAM_OUT) $(Q) $(CROSS)size --format=sysv $(PROGRAM_OUT) test: flash - screen $(ESPPORT) 115200 + $(FILTEROUTPUT) --port $(ESPPORT) --baud 115200 --elf $(PROGRAM_OUT) # the rebuild target is written like this so it can be run in a parallel build # environment without causing weird side effects diff --git a/core/app_main.c b/core/app_main.c index 631b2bb..5e02667 100644 --- a/core/app_main.c +++ b/core/app_main.c @@ -41,8 +41,6 @@ void user_init(void); #define RTCMEM_BACKUP_PHY_VER 31 #define RTCMEM_SYSTEM_PP_VER 62 -#define halt() while (1) {} - extern uint32_t _bss_start; extern uint32_t _bss_end; @@ -85,7 +83,6 @@ static void IRAM set_spi0_divisor(uint32_t divisor); static void zero_bss(void); static void init_networking(uint8_t *phy_info, uint8_t *mac_addr); static void init_g_ic(void); -static void dump_excinfo(void); static void user_start_phase2(void); static void dump_flash_sector(uint32_t start_sector, uint32_t length); static void dump_flash_config_sectors(uint32_t start_sector); @@ -102,11 +99,11 @@ static void IRAM get_otp_mac_address(uint8_t *buf) { if (!(otp_flags & 0x8000)) { //FIXME: do we really need this check? printf("Firmware ONLY supports ESP8266!!!\n"); - halt(); + abort(); } if (otp_id0 == 0 && otp_id1 == 0) { printf("empty otp\n"); - halt(); + abort(); } if (otp_flags & 0x1000) { // If bit 12 is set, it indicates that the vendor portion of the MAC @@ -147,23 +144,6 @@ static void IRAM set_spi0_divisor(uint32_t divisor) { SPI(0).CTRL0 = SET_FIELD(SPI(0).CTRL0, SPI_CTRL0_CLOCK, clkdiv); } -// .text+0x148 -void IRAM sdk_user_fatal_exception_handler(void) { - if (!sdk_NMIIrqIsOn) { - vPortEnterCritical(); - do { - DPORT.DPORT0 &= 0xffffffe0; - } while (DPORT.DPORT0 & 0x00000001); - } - Cache_Read_Disable(); - Cache_Read_Enable(0, 0, 1); - dump_excinfo(); - uart_flush_txfifo(0); - uart_flush_txfifo(1); - sdk_system_restart_in_nmi(); - halt(); -} - static void IRAM default_putc(char c) { uart_putc(0, c); @@ -277,7 +257,7 @@ static void zero_bss(void) { static void init_networking(uint8_t *phy_info, uint8_t *mac_addr) { if (sdk_register_chipv6_phy(phy_info)) { printf("FATAL: sdk_register_chipv6_phy failed"); - halt(); + abort(); } uart_set_baud(0, 74906); uart_set_baud(1, 74906); @@ -333,37 +313,6 @@ static void init_g_ic(void) { } } -// .Lfunc008 -- .irom0.text+0x2a0 -static void dump_excinfo(void) { - uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1; - uint32_t excinfo[8]; - - RSR(exccause, exccause); - printf("Fatal exception (%d): \n", (int)exccause); - RSR(epc1, epc1); - RSR(epc2, epc2); - RSR(epc3, epc3); - RSR(excvaddr, excvaddr); - RSR(depc, depc); - RSR(excsave1, excsave1); - printf("%s=0x%08x\n", "epc1", epc1); - printf("%s=0x%08x\n", "epc2", epc2); - printf("%s=0x%08x\n", "epc3", epc3); - printf("%s=0x%08x\n", "excvaddr", excvaddr); - printf("%s=0x%08x\n", "depc", depc); - printf("%s=0x%08x\n", "excsave1", excsave1); - sdk_system_rtc_mem_read(0, excinfo, 32); // Why? - excinfo[0] = 2; - excinfo[1] = exccause; - excinfo[2] = epc1; - excinfo[3] = epc2; - excinfo[4] = epc3; - excinfo[5] = excvaddr; - excinfo[6] = depc; - excinfo[7] = excsave1; - sdk_system_rtc_mem_write(0, excinfo, 32); -} - // .irom0.text+0x398 void sdk_wdt_init(void) { WDT.CTRL &= ~WDT_CTRL_ENABLE; @@ -408,6 +357,9 @@ void sdk_user_init_task(void *params) { vTaskDelete(NULL); } +extern void (*__init_array_start)(void); +extern void (*__init_array_end)(void); + // .Lfunc009 -- .irom0.text+0x5b4 static void user_start_phase2(void) { uint8_t *buf; @@ -445,6 +397,13 @@ static void user_start_phase2(void) { } init_networking(phy_info, sdk_info.sta_mac_addr); free(phy_info); + + // Call gcc constructor functions + void (**ctor)(void); + for ( ctor = &__init_array_start; ctor != &__init_array_end; ++ctor) { + (*ctor)(); + } + tcpip_init(NULL, NULL); sdk_wdt_init(); xTaskCreate(sdk_user_init_task, (signed char *)"uiT", 1024, 0, 14, &sdk_xUserTaskHandle); diff --git a/core/debug_dumps.c b/core/debug_dumps.c new file mode 100644 index 0000000..15396ac --- /dev/null +++ b/core/debug_dumps.c @@ -0,0 +1,175 @@ +/* Code for dumping status/debug output/etc, including fatal + * exception handling & abort implementation. + * + * Part of esp-open-rtos + * + * Partially reverse engineered from MIT licensed Espressif RTOS SDK Copyright (C) Espressif Systems. + * Additions Copyright (C) 2015 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE + */ +#include +#include +#include +#include + +#include "debug_dumps.h" +#include "common_macros.h" +#include "xtensa_ops.h" +#include "esp/rom.h" +#include "esp/uart.h" +#include "espressif/esp_common.h" +#include "sdk_internal.h" + +/* Forward declarations */ +static void IRAM fatal_handler_prelude(void); +/* Inner parts of crash handlers marked noinline to ensure they don't inline into IRAM. */ +static void __attribute__((noinline)) __attribute__((noreturn)) fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack); +static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_inner(uint32_t *caller, uint32_t *sp); + +/* fatal_exception_handler called from any unhandled user exception + * + * (similar to a hard fault on other processor architectures) + * + * This function is run from IRAM, but the majority of the handler + * runs from flash after fatal_handler_prelude ensures it is mapped + * safely. + */ +void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack) { + fatal_handler_prelude(); + fatal_exception_handler_inner(sp, registers_saved_on_stack); +} + +/* Abort implementation + * + * Replaces the weak-linked abort implementation provided by newlib libc. + * + * Disable interrupts, enable flash mapping, dump stack & caller + * address, restart. + * + * This function is run from IRAM, but the majority of the abort + * handler runs from flash after fatal_handler_prelude ensures it is + * mapped safely. + * + */ +void IRAM abort(void) { + uint32_t *sp, *caller; + RETADDR(caller); + /* abort() caller is one instruction before our return address */ + caller = (uint32_t *)((intptr_t)caller - 3); + SP(sp); + fatal_handler_prelude(); + abort_handler_inner(caller, sp); +} + +/* Dump exception information from special function registers */ +static void dump_excinfo(void) { + uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1; + uint32_t excinfo[8]; + + RSR(exccause, exccause); + printf("Fatal exception (%d): \n", (int)exccause); + RSR(epc1, epc1); + RSR(epc2, epc2); + RSR(epc3, epc3); + RSR(excvaddr, excvaddr); + RSR(depc, depc); + RSR(excsave1, excsave1); + printf("%s=0x%08x\n", "epc1", epc1); + printf("%s=0x%08x\n", "epc2", epc2); + printf("%s=0x%08x\n", "epc3", epc3); + printf("%s=0x%08x\n", "excvaddr", excvaddr); + printf("%s=0x%08x\n", "depc", depc); + printf("%s=0x%08x\n", "excsave1", excsave1); + sdk_system_rtc_mem_read(0, excinfo, 32); // Why? + excinfo[0] = 2; + excinfo[1] = exccause; + excinfo[2] = epc1; + excinfo[3] = epc2; + excinfo[4] = epc3; + excinfo[5] = excvaddr; + excinfo[6] = depc; + excinfo[7] = excsave1; + sdk_system_rtc_mem_write(0, excinfo, 32); +} + +/* dump stack memory (frames above sp) to stdout + + There's a lot of smart stuff we could do while dumping stack + but for now we just dump what looks like our stack region. + + Probably dumps more memory than it needs to, the first instance of + 0xa5a5a5a5 probably constitutes the end of our stack. +*/ +void dump_stack(uint32_t *sp) { + printf("\nStack: SP=%p\n", sp); + for(uint32_t *p = sp; p < sp + 32; p += 4) { + if((intptr_t)p >= 0x3fffc000) { + break; /* approximate end of RAM */ + } + printf("%p: %08x %08x %08x %08x\n", p, p[0], p[1], p[2], p[3]); + if(p[0] == 0xa5a5a5a5 && p[1] == 0xa5a5a5a5 + && p[2] == 0xa5a5a5a5 && p[3] == 0xa5a5a5a5) { + break; /* FreeRTOS uses this pattern to mark untouched stack space */ + } + } +} + +/* Dump normal registers that were stored above 'sp' + by the exception handler preamble +*/ +void dump_registers_in_exception_handler(uint32_t *sp) { + uint32_t excsave1; + uint32_t *saved = sp - (0x50 / sizeof(uint32_t)); + printf("Registers:\n"); + RSR(excsave1, excsave1); + printf("a0 %08x ", excsave1); + printf("a1 %08x ", (intptr_t)sp); + for(int a = 2; a < 14; a++) { + printf("a%-2d %08x%c", a, saved[a+3], a == 3 || a == 7 || a == 11 ? '\n':' '); + } + printf("SAR %08x\n", saved[0x13]); +} + + +/* Prelude ensures exceptions/NMI off and flash is mapped, allowing + calls to non-IRAM functions. +*/ +static void IRAM fatal_handler_prelude(void) { + if (!sdk_NMIIrqIsOn) { + vPortEnterCritical(); + do { + DPORT.DPORT0 &= 0xffffffe0; + } while (DPORT.DPORT0 & 0x00000001); + } + Cache_Read_Disable(); + Cache_Read_Enable(0, 0, 1); +} + +/* Main part of fatal exception handler, is run from flash to save + some IRAM. +*/ +static void fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) { + dump_excinfo(); + if (sp) { + if (registers_saved_on_stack) { + dump_registers_in_exception_handler(sp); + } + dump_stack(sp); + } + uart_flush_txfifo(0); + uart_flush_txfifo(1); + sdk_system_restart_in_nmi(); + while(1) {} +} + +/* Main part of abort handler, can be run from flash to save some + IRAM. +*/ +static void abort_handler_inner(uint32_t *caller, uint32_t *sp) { + printf("abort() invoked at %p.\n", caller); + dump_stack(sp); + uart_flush_txfifo(0); + uart_flush_txfifo(1); + sdk_system_restart_in_nmi(); + while(1) {} +} diff --git a/core/esp_interrupts.c b/core/esp_interrupts.c index 4fd68b7..b3aefff 100644 --- a/core/esp_interrupts.c +++ b/core/esp_interrupts.c @@ -9,6 +9,8 @@ _xt_isr isr[16]; +bool esp_in_isr; + void IRAM _xt_isr_attach(uint8_t i, _xt_isr func) { isr[i] = func; @@ -20,6 +22,8 @@ void IRAM _xt_isr_attach(uint8_t i, _xt_isr func) */ uint16_t IRAM _xt_isr_handler(uint16_t intset) { + esp_in_isr = true; + /* WDT has highest priority (occasional WDT resets otherwise) */ if(intset & BIT(INUM_WDT)) { _xt_clear_ints(BIT(INUM_WDT)); @@ -35,5 +39,7 @@ uint16_t IRAM _xt_isr_handler(uint16_t intset) intset -= mask; } + esp_in_isr = false; + return 0; } diff --git a/core/exception_vectors.S b/core/exception_vectors.S index c1f1761..000e742 100644 --- a/core/exception_vectors.S +++ b/core/exception_vectors.S @@ -68,7 +68,9 @@ DebugExceptionVector: .type DebugExceptionVector, @function wsr a0, excsave2 - call0 sdk_user_fatal_exception_handler + mov a2, a1 + movi a3, 0 + call0 fatal_exception_handler rfi 2 .org VecBase + 0x20 @@ -81,7 +83,9 @@ KernelExceptionVector: .type KernelExceptionVector, @function break 1, 0 - call0 sdk_user_fatal_exception_handler + mov a2, a1 + movi a3, 0 + call0 fatal_exception_handler rfe .org VecBase + 0x50 @@ -98,7 +102,9 @@ DoubleExceptionVector: .type DoubleExceptionVector, @function break 1, 4 - call0 sdk_user_fatal_exception_handler + mov a2, a1 + movi a3, 0 + call0 fatal_exception_handler /* Reset vector at offset 0x80 is unused, as vecbase gets reset to mask ROM * vectors on chip reset. */ @@ -251,11 +257,13 @@ LoadStoreErrorHandler: * will have correct values */ wsr a0, sar l32i a0, sp, 0 - l32i a2, sp, 0x08 + /*l32i a2, sp, 0x08*/ l32i a3, sp, 0x0c l32i a4, sp, 0x10 rsr a1, excsave1 - call0 sdk_user_fatal_exception_handler + mov a2, a1 + movi a3, 0 + call0 fatal_exception_handler .balign 4 .LSE_assign_a1: @@ -515,7 +523,9 @@ UserExceptionHandler: .literal_position .LUserFailOtherExceptionCause: break 1, 1 - call0 sdk_user_fatal_exception_handler + addi a2, a1, 0x50 /* UserExceptionHandler pushes stack down 0x50 */ + movi a3, 1 + call0 fatal_exception_handler /* _xt_user_exit is pushed onto the stack as part of the user exception handler, restores same set registers which were saved there and returns from exception */ diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h new file mode 100644 index 0000000..809b87a --- /dev/null +++ b/core/include/debug_dumps.h @@ -0,0 +1,22 @@ +/* Functions for dumping status/debug output/etc, including fatal + * exception handling. + * + * Part of esp-open-rtos + * + * Copyright (C) 2015-2016 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE + */ +#ifndef _DEBUG_DUMPS_H +#define _DEBUG_DUMPS_H +#include + +/* Dump stack memory starting from stack pointer address sp. */ +void dump_stack(uint32_t *sp); + +/* Called from exception_vectors.S when a fatal exception occurs. + + Probably not useful to be called in other contexts. +*/ +void __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack); + +#endif diff --git a/core/include/xtensa_ops.h b/core/include/xtensa_ops.h index 2fdc0f8..25252f6 100644 --- a/core/include/xtensa_ops.h +++ b/core/include/xtensa_ops.h @@ -25,4 +25,19 @@ #define ESYNC() asm volatile ( "esync" ) #define DSYNC() asm volatile ( "dsync" ) + +/* Read stack pointer to variable. + * + * Note that the compiler will push a stack frame (minimum 16 bytes) + * in the prelude of a C function that calls any other functions. + */ +#define SP(var) asm volatile ("mov %0, a1" : "=r" (var)); + +/* Read the function return address to a variable. + * + * Depends on the containing function being simple enough that a0 is + * being used as a working register. + */ +#define RETADDR(var) asm volatile ("mov %0, a0" : "=r" (var)) + #endif /* _XTENSA_OPS_H */ diff --git a/core/newlib_syscalls.c b/core/newlib_syscalls.c index b414481..0d4b95e 100644 --- a/core/newlib_syscalls.c +++ b/core/newlib_syscalls.c @@ -25,7 +25,7 @@ IRAM caddr_t _sbrk_r (struct _reent *r, int incr) if (heap_end + incr > stack_ptr) { _write (1, "_sbrk: Heap collided with stack\n", 32); - while(1) {} + abort(); } */ heap_end += incr; diff --git a/core/sdk_compat.c b/core/sdk_compat.c index b0d6789..1e60b85 100644 --- a/core/sdk_compat.c +++ b/core/sdk_compat.c @@ -17,20 +17,6 @@ void IRAM *zalloc(size_t nbytes) return calloc(1, nbytes); } -extern void (*__init_array_start)(void); -extern void (*__init_array_end)(void); - -/* Do things which should be done as part of the SDK startup code, but aren't. - TODO: Move into app_main.c -*/ -void sdk_compat_initialise() -{ - /* Call C++ constructors or C functions marked with __attribute__((constructor)) */ - void (**p)(void); - for ( p = &__init_array_start; p != &__init_array_end; ++p) - (*p)(); -} - /* UART RX function from Espressif SDK internals. * * Not part of published API. diff --git a/examples/cpp_01_tasks/Makefile b/examples/cpp_01_tasks/Makefile index f9254cf..0953d08 100644 --- a/examples/cpp_01_tasks/Makefile +++ b/examples/cpp_01_tasks/Makefile @@ -1,5 +1,4 @@ # Simple makefile for simple example PROGRAM=cpp_01_tasks -OTA=0 EXTRA_COMPONENTS=extras/cpp_support include ../../common.mk diff --git a/examples/ds18b20_broadcaster/ds18b20_broadcaster.c b/examples/ds18b20_broadcaster/ds18b20_broadcaster.c index fe12055..def94fd 100644 --- a/examples/ds18b20_broadcaster/ds18b20_broadcaster.c +++ b/examples/ds18b20_broadcaster/ds18b20_broadcaster.c @@ -19,15 +19,14 @@ // DS18B20 driver #include "ds18b20/ds18b20.h" -// Onewire init -#include "onewire/onewire.h" void broadcast_temperature(void *pvParameters) { uint8_t amount = 0; - uint8_t sensors = 2; - ds_sensor_t t[sensors]; + uint8_t sensors = 1; + ds18b20_addr_t addrs[sensors]; + float results[sensors]; // Use GPIO 13 as one wire pin. uint8_t GPIO_FOR_ONE_WIRE = 13; @@ -36,8 +35,6 @@ void broadcast_temperature(void *pvParameters) // Broadcaster part err_t err; - // Initialize one wire bus. - onewire_init(GPIO_FOR_ONE_WIRE); while(1) { @@ -66,18 +63,17 @@ void broadcast_temperature(void *pvParameters) for(;;) { // Search all DS18B20, return its amount and feed 't' structure with result data. - amount = ds18b20_read_all(GPIO_FOR_ONE_WIRE, t); + amount = ds18b20_scan_devices(GPIO_FOR_ONE_WIRE, addrs, sensors); if (amount < sensors){ printf("Something is wrong, I expect to see %d sensors \nbut just %d was detected!\n", sensors, amount); } - for (int i = 0; i < amount; ++i) + ds18b20_measure_and_read_multi(GPIO_FOR_ONE_WIRE, addrs, sensors, results); + for (int i = 0; i < sensors; ++i) { - int intpart = (int)t[i].value; - int fraction = (int)((t[i].value - intpart) * 100); - // Multiple "" here is just to satisfy compiler and don`t raise 'hex escape sequence out of range' warning. - sprintf(msg, "Sensor %d report: %d.%02d ""\xC2""\xB0""C\n",t[i].id, intpart, fraction); + // ("\xC2\xB0" is the degree character (U+00B0) in UTF-8) + sprintf(msg, "Sensor %08x%08x reports: %f \xC2\xB0""C\n", (uint32_t)(addrs[i] >> 32), (uint32_t)addrs[i], results[i]); printf("%s", msg); struct netbuf* buf = netbuf_new(); diff --git a/examples/ds18b20_onewire/ds18b20_onewire.c b/examples/ds18b20_onewire/ds18b20_onewire.c index 78d13bf..b9b7655 100644 --- a/examples/ds18b20_onewire/ds18b20_onewire.c +++ b/examples/ds18b20_onewire/ds18b20_onewire.c @@ -1,59 +1,78 @@ -/* ds18b20 - Retrieves temperature from ds18b20 sensors and print it out. +/* ds18b20_onewire.c - Retrieves readings from one or more DS18B20 temperature + * sensors, and prints the results to stdout. * * This sample code is in the public domain., */ -#include "espressif/esp_common.h" -#include "esp/uart.h" #include "FreeRTOS.h" #include "task.h" -#include "timers.h" -#include "queue.h" +#include "esp/uart.h" -// DS18B20 driver #include "ds18b20/ds18b20.h" -// Onewire init -#include "onewire/onewire.h" -void print_temperature(void *pvParameters) -{ - int delay = 500; - uint8_t amount = 0; - // Declare amount of sensors - uint8_t sensors = 2; - ds_sensor_t t[sensors]; - - // Use GPIO 13 as one wire pin. - uint8_t GPIO_FOR_ONE_WIRE = 13; - - onewire_init(GPIO_FOR_ONE_WIRE); +#define SENSOR_GPIO 13 +#define MAX_SENSORS 8 +#define RESCAN_INTERVAL 8 +#define LOOP_DELAY_MS 250 + +void print_temperature(void *pvParameters) { + ds18b20_addr_t addrs[MAX_SENSORS]; + float temps[MAX_SENSORS]; + int sensor_count; + // There is no special initialization required before using the ds18b20 + // routines. However, we make sure that the internal pull-up resistor is + // enabled on the GPIO pin so that one can connect up a sensor without + // needing an external pull-up (Note: The internal (~47k) pull-ups of the + // ESP8266 do appear to work, at least for simple setups (one or two sensors + // connected with short leads), but do not technically meet the pull-up + // requirements from the DS18B20 datasheet and may not always be reliable. + // For a real application, a proper 4.7k external pull-up resistor is + // recommended instead!) + + gpio_set_pullup(SENSOR_GPIO, true, true); + while(1) { - // Search all DS18B20, return its amount and feed 't' structure with result data. - amount = ds18b20_read_all(GPIO_FOR_ONE_WIRE, t); + // Every RESCAN_INTERVAL samples, check to see if the sensors connected + // to our bus have changed. + sensor_count = ds18b20_scan_devices(SENSOR_GPIO, addrs, MAX_SENSORS); - if (amount < sensors){ - printf("Something is wrong, I expect to see %d sensors \nbut just %d was detected!\n", sensors, amount); - } + if (sensor_count < 1) { + printf("\nNo sensors detected!\n"); + } else { + printf("\n%d sensors detected:\n", sensor_count); + // If there were more sensors found than we have space to handle, + // just report the first MAX_SENSORS.. + if (sensor_count > MAX_SENSORS) sensor_count = MAX_SENSORS; - for (int i = 0; i < amount; ++i) - { - int intpart = (int)t[i].value; - int fraction = (int)((t[i].value - intpart) * 100); - // Multiple "" here is just to satisfy compiler and don`t raise 'hex escape sequence out of range' warning. - printf("Sensor %d report: %d.%02d ""\xC2""\xB0""C\n",t[i].id, intpart, fraction); + // Do a number of temperature samples, and print the results. + for (int i = 0; i < RESCAN_INTERVAL; i++) { + ds18b20_measure_and_read_multi(SENSOR_GPIO, addrs, sensor_count, temps); + for (int j = 0; j < sensor_count; j++) { + // The DS18B20 address is a 64-bit integer, but newlib-nano + // printf does not support printing 64-bit values, so we + // split it up into two 32-bit integers and print them + // back-to-back to make it look like one big hex number. + uint32_t addr0 = addrs[j] >> 32; + uint32_t addr1 = addrs[j]; + float temp_c = temps[j]; + float temp_f = (temp_c * 1.8) + 32; + printf(" Sensor %08x%08x reports %f deg C (%f deg F)\n", addr0, addr1, temp_c, temp_f); + } + printf("\n"); + + // Wait for a little bit between each sample (note that the + // ds18b20_measure_and_read_multi operation already takes at + // least 750ms to run, so this is on top of that delay). + vTaskDelay(LOOP_DELAY_MS / portTICK_RATE_MS); + } } - printf("\n"); - vTaskDelay(delay / portTICK_RATE_MS); } } -void user_init(void) -{ +void user_init(void) { uart_set_baud(0, 115200); - printf("SDK version:%s\n", sdk_system_get_sdk_version()); - xTaskCreate(&print_temperature, (signed char *)"print_temperature", 256, NULL, 2, NULL); } diff --git a/examples/experiments/unaligned_load/unaligned_load.c b/examples/experiments/unaligned_load/unaligned_load.c index 258b4d0..4244804 100644 --- a/examples/experiments/unaligned_load/unaligned_load.c +++ b/examples/experiments/unaligned_load/unaligned_load.c @@ -305,7 +305,7 @@ static void test_system_interaction() } uint32_t ticks = xTaskGetTickCount() - start; printf("Timer interaction test PASSED after %dms.\n", ticks*portTICK_RATE_MS); - while(1) {} + abort(); } /* The following "sanity tests" are designed to try to execute every code path diff --git a/examples/http_get_mbedtls/cert.c b/examples/http_get_mbedtls/cert.c index f8ab11f..7acc438 100644 --- a/examples/http_get_mbedtls/cert.c +++ b/examples/http_get_mbedtls/cert.c @@ -1,43 +1,44 @@ -/* This is the root certificate for the CA trust chain of +/* This is the CA certificate for the CA trust chain of www.howsmyssl.com in PEM format, as dumped via: openssl s_client -showcerts -connect www.howsmyssl.com:443 #include #include +/* + 1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3 + i:/O=Digital Signature Trust Co./CN=DST Root CA X3 + */ const char *server_root_cert = "-----BEGIN CERTIFICATE-----\r\n" -"MIIFNzCCBB+gAwIBAgISAfl7TPw6Mf/F6VCBc5eaHA7kMA0GCSqGSIb3DQEBCwUA\r\n" -"MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD\r\n" -"ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMTAeFw0xNTEyMzAwNzQ0MDBaFw0x\r\n" -"NjAzMjkwNzQ0MDBaMBwxGjAYBgNVBAMTEXd3dy5ob3dzbXlzc2wuY29tMIIBIjAN\r\n" -"BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArKF7WzSrDPinQhd9mVfoW5u46/TC\r\n" -"fbYKR3MEryetUSeQuwuXFj2xO6+a/JQ99UC3Eq+9s8uFdx1zFFS6qfW+HXBwGWVf\r\n" -"ajKEyIcXUJZCRn7aWpTWZq6cuv4bZSv1QklGViQs8UCZifcN0A/mYrH7zHG2WDn2\r\n" -"fxNgA+nJBqbZPr1gP9hqGFCnX+dPR5WxtC9+Dv9Sx+wiOWVz/obVTCygdqqcpa5I\r\n" -"3/U9REVgO2VfT+xMty6NZTMCjTJ+GXZuB/BrMe9+ZmgWk0grJyqdrCxOCyK6B4g+\r\n" -"Fvs8WFRNTHdQnP3/NT5hPtreZ3nuY2YY7RbGFwUaBcvJwbbIqalpiQ1X7QIDAQAB\r\n" -"o4ICQzCCAj8wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr\r\n" -"BgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQMAe087CSp3PjgqyIYyWTR\r\n" -"a2aMnTAfBgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBwBggrBgEFBQcB\r\n" -"AQRkMGIwLwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmludC14MS5sZXRzZW5jcnlw\r\n" -"dC5vcmcvMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDEubGV0c2VuY3J5\r\n" -"cHQub3JnLzBNBgNVHREERjBEgg1ob3dzbXlzc2wuY29tgg1ob3dzbXl0bHMuY29t\r\n" -"ghF3d3cuaG93c215dGxzLmNvbYIRd3d3Lmhvd3NteXNzbC5jb20wgf4GA1UdIASB\r\n" -"9jCB8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpo\r\n" -"dHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlz\r\n" -"IENlcnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcg\r\n" -"UGFydGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmlj\r\n" -"YXRlIFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBv\r\n" -"c2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEALB2QrIrcxxFr81b6khy9LwVBpthL\r\n" -"2LSs0xWhA09gxHmPnVrqim3Wa9HFYRApSqtTQWSx58TO+MERXQ7eDX50k53oem+Q\r\n" -"gXn90HVDkR0jbV1CsAD5qL00kKOofAyGkUhFlO2hcRU0CIj7Z9HZB8xpYdZWLUSZ\r\n" -"BpgiXdf/YYRIwgx29GRQjhfl/H30fHyawY5SvquJuvAeEr7lxqAmEEg3a7J6ibHL\r\n" -"90zf5KMkXGyVsXqxLqrEXQKgTvpUMeP5iYAxE45R5GNmYS2jajyTi4XkWw4nEu4Q\r\n" -"dMTLuwxGz87KOwSSFOLU903hO3IIPvFIIMY6aVK2kAgQPNIqk9nFurTF9A==\r\n" +"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\r\n" +"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\r\n" +"DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\r\n" +"SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\r\n" +"GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\r\n" +"AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\r\n" +"q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\r\n" +"SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\r\n" +"Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\r\n" +"a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\r\n" +"/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\r\n" +"AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\r\n" +"CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\r\n" +"bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\r\n" +"c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\r\n" +"VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\r\n" +"ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\r\n" +"MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\r\n" +"Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\r\n" +"AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\r\n" +"uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\r\n" +"wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\r\n" +"X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\r\n" +"PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\r\n" +"KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\r\n" "-----END CERTIFICATE-----\r\n"; diff --git a/examples/http_get_mbedtls/http_get_mbedtls.c b/examples/http_get_mbedtls/http_get_mbedtls.c index f45fd54..7aad0e3 100644 --- a/examples/http_get_mbedtls/http_get_mbedtls.c +++ b/examples/http_get_mbedtls/http_get_mbedtls.c @@ -41,11 +41,11 @@ #include "mbedtls/error.h" #include "mbedtls/certs.h" -#define WEB_SERVER "howsmyssl.com" +#define WEB_SERVER "www.howsmyssl.com" #define WEB_PORT "443" #define WEB_URL "https://www.howsmyssl.com/a/check" -#define GET_REQUEST "GET "WEB_URL" HTTP/1.1\n\n" +#define GET_REQUEST "GET "WEB_URL" HTTP/1.1\nHost: "WEB_SERVER"\n\n" /* Root cert for howsmyssl.com, stored in cert.c */ extern const char *server_root_cert; @@ -115,7 +115,7 @@ void http_get_task(void *pvParameters) strlen(pers))) != 0) { printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -129,7 +129,7 @@ void http_get_task(void *pvParameters) if(ret < 0) { printf(" failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok (%d skipped)\n", ret); @@ -138,7 +138,7 @@ void http_get_task(void *pvParameters) if((ret = mbedtls_ssl_set_hostname(&ssl, WEB_SERVER)) != 0) { printf(" failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } /* diff --git a/examples/mqtt_client/Makefile b/examples/mqtt_client/Makefile new file mode 100644 index 0000000..0eff202 --- /dev/null +++ b/examples/mqtt_client/Makefile @@ -0,0 +1,3 @@ +PROGRAM=mqtt_client +EXTRA_COMPONENTS = extras/paho_mqtt_c +include ../../common.mk diff --git a/examples/mqtt_client/mqtt_client.c b/examples/mqtt_client/mqtt_client.c new file mode 100644 index 0000000..fdf86c3 --- /dev/null +++ b/examples/mqtt_client/mqtt_client.c @@ -0,0 +1,221 @@ +#include "espressif/esp_common.h" +#include "esp/uart.h" + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include + + +/* You can use http://test.mosquitto.org/ to test mqtt_client instead + * of setting up your own MQTT server */ +#define MQTT_HOST ("test.mosquitto.org") +#define MQTT_PORT 1883 + +#define MQTT_USER NULL +#define MQTT_PASS NULL + +xSemaphoreHandle wifi_alive; +xQueueHandle publish_queue; +#define PUB_MSG_LEN 16 + +static void beat_task(void *pvParameters) +{ + portTickType xLastWakeTime = xTaskGetTickCount(); + char msg[PUB_MSG_LEN]; + int count = 0; + + while (1) { + vTaskDelayUntil(&xLastWakeTime, 10000 / portTICK_RATE_MS); + printf("beat\r\n"); + snprintf(msg, PUB_MSG_LEN, "Beat %d\r\n", count++); + if (xQueueSend(publish_queue, (void *)msg, 0) == pdFALSE) { + printf("Publish queue overflow.\r\n"); + } + } +} + +static void topic_received(MessageData *md) +{ + int i; + MQTTMessage *message = md->message; + printf("Received: "); + for( i = 0; i < md->topic->lenstring.len; ++i) + printf("%c", md->topic->lenstring.data[ i ]); + + printf(" = "); + for( i = 0; i < (int)message->payloadlen; ++i) + printf("%c", ((char *)(message->payload))[i]); + + printf("\r\n"); +} + +static const char * get_my_id(void) +{ + // Use MAC address for Station as unique ID + static char my_id[13]; + static bool my_id_done = false; + int8_t i; + uint8_t x; + if (my_id_done) + return my_id; + if (!sdk_wifi_get_macaddr(STATION_IF, (uint8_t *)my_id)) + return NULL; + for (i = 5; i >= 0; --i) + { + x = my_id[i] & 0x0F; + if (x > 9) x += 7; + my_id[i * 2 + 1] = x + '0'; + x = my_id[i] >> 4; + if (x > 9) x += 7; + my_id[i * 2] = x + '0'; + } + my_id[12] = '\0'; + my_id_done = true; + return my_id; +} + +static void mqtt_task(void *pvParameters) +{ + int ret = 0; + struct Network network; + MQTTClient client = DefaultClient; + char mqtt_client_id[20]; + uint8_t mqtt_buf[100]; + uint8_t mqtt_readbuf[100]; + MQTTPacket_connectData data = MQTTPacket_connectData_initializer; + + NewNetwork( &network ); + memset(mqtt_client_id, 0, sizeof(mqtt_client_id)); + strcpy(mqtt_client_id, "ESP-"); + strcat(mqtt_client_id, get_my_id()); + + while(1) { + xSemaphoreTake(wifi_alive, portMAX_DELAY); + printf("%s: started\n\r", __func__); + printf("%s: (Re)connecting to MQTT server %s ... ",__func__, + MQTT_HOST); + ret = ConnectNetwork(&network, MQTT_HOST, MQTT_PORT); + if( ret ){ + printf("error: %d\n\r", ret); + taskYIELD(); + continue; + } + printf("done\n\r"); + NewMQTTClient(&client, &network, 5000, mqtt_buf, 100, + mqtt_readbuf, 100); + + data.willFlag = 0; + data.MQTTVersion = 3; + data.clientID.cstring = mqtt_client_id; + data.username.cstring = MQTT_USER; + data.password.cstring = MQTT_PASS; + data.keepAliveInterval = 10; + data.cleansession = 0; + printf("Send MQTT connect ... "); + ret = MQTTConnect(&client, &data); + if(ret){ + printf("error: %d\n\r", ret); + DisconnectNetwork(&network); + taskYIELD(); + continue; + } + printf("done\r\n"); + MQTTSubscribe(&client, "/esptopic", QOS1, topic_received); + xQueueReset(publish_queue); + + while(1){ + + char msg[PUB_MSG_LEN - 1] = "\0"; + while(xQueueReceive(publish_queue, (void *)msg, 0) == + pdTRUE){ + printf("got message to publish\r\n"); + MQTTMessage message; + message.payload = msg; + message.payloadlen = PUB_MSG_LEN; + message.dup = 0; + message.qos = QOS1; + message.retained = 0; + ret = MQTTPublish(&client, "/beat", &message); + if (ret != SUCCESS ){ + printf("error while publishing message: %d\n", ret ); + break; + } + } + + ret = MQTTYield(&client, 1000); + if (ret == DISCONNECTED) + break; + } + printf("Connection dropped, request restart\n\r"); + taskYIELD(); + } +} + +static void wifi_task(void *pvParameters) +{ + uint8_t status = 0; + uint8_t retries = 30; + struct sdk_station_config config = { + .ssid = WIFI_SSID, + .password = WIFI_PASS, + }; + + printf("WiFi: connecting to WiFi\n\r"); + sdk_wifi_set_opmode(STATION_MODE); + sdk_wifi_station_set_config(&config); + + while(1) + { + while ((status != STATION_GOT_IP) && (retries)){ + status = sdk_wifi_station_get_connect_status(); + printf("%s: status = %d\n\r", __func__, status ); + if( status == STATION_WRONG_PASSWORD ){ + printf("WiFi: wrong password\n\r"); + break; + } else if( status == STATION_NO_AP_FOUND ) { + printf("WiFi: AP not found\n\r"); + break; + } else if( status == STATION_CONNECT_FAIL ) { + printf("WiFi: connection failed\r\n"); + break; + } + vTaskDelay( 1000 / portTICK_RATE_MS ); + --retries; + } + if (status == STATION_GOT_IP) { + printf("WiFi: Connected\n\r"); + xSemaphoreGive( wifi_alive ); + taskYIELD(); + } + + while ((status = sdk_wifi_station_get_connect_status()) == STATION_GOT_IP) { + xSemaphoreGive( wifi_alive ); + taskYIELD(); + } + printf("WiFi: disconnected\n\r"); + sdk_wifi_station_disconnect(); + vTaskDelay( 1000 / portTICK_RATE_MS ); + } +} + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("SDK version:%s\n", sdk_system_get_sdk_version()); + + vSemaphoreCreateBinary(wifi_alive); + publish_queue = xQueueCreate(3, PUB_MSG_LEN); + xTaskCreate(&wifi_task, (int8_t *)"wifi_task", 256, NULL, 2, NULL); + xTaskCreate(&beat_task, (int8_t *)"beat_task", 256, NULL, 3, NULL); + xTaskCreate(&mqtt_task, (int8_t *)"mqtt_task", 1024, NULL, 4, NULL); +} diff --git a/examples/ota_basic/ota_basic.c b/examples/ota_basic/ota_basic.c index 9189f02..d55c03a 100644 --- a/examples/ota_basic/ota_basic.c +++ b/examples/ota_basic/ota_basic.c @@ -14,13 +14,15 @@ #include "ssid_config.h" #include "ota-tftp.h" -#include "rboot-ota.h" +#include "rboot-integration.h" +#include "rboot.h" +#include "rboot-api.h" void user_init(void) { uart_set_baud(0, 115200); - rboot_config_t conf = rboot_get_config(); + rboot_config conf = rboot_get_config(); printf("\r\n\r\nOTA Basic demo.\r\nCurrently running on flash slot %d / %d.\r\n\r\n", conf.current_rom, conf.count); diff --git a/examples/tls_server/tls_server.c b/examples/tls_server/tls_server.c index d253bc9..9030dc0 100644 --- a/examples/tls_server/tls_server.c +++ b/examples/tls_server/tls_server.c @@ -85,7 +85,7 @@ void tls_server_task(void *pvParameters) strlen(pers))) != 0) { printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -99,7 +99,7 @@ void tls_server_task(void *pvParameters) if(ret < 0) { printf(" failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); - while(1) {} /* todo: replace with abort() */ + abort(); } printf(" ok (%d skipped)\n", ret); @@ -109,7 +109,7 @@ void tls_server_task(void *pvParameters) if(ret != 0) { printf(" failed\n ! mbedtls_pk_parse_key returned - 0x%x\n\n", -ret); - while(1) { } /*todo: replace with abort() */ + abort(); } printf(" ok\n"); @@ -134,7 +134,7 @@ void tls_server_task(void *pvParameters) if( ( ret = mbedtls_ssl_conf_own_cert( &conf, &srvcert, &pkey ) ) != 0 ) { printf( " failed\n ! mbedtls_ssl_conf_own_cert returned %d\n\n", ret ); - while(1) { } + abort(); } mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); diff --git a/extras/ds18b20/ds18b20.c b/extras/ds18b20/ds18b20.c index dc028fc..c965b04 100644 --- a/extras/ds18b20/ds18b20.c +++ b/extras/ds18b20/ds18b20.c @@ -1,43 +1,47 @@ #include "FreeRTOS.h" #include "task.h" +#include "math.h" -#include "onewire/onewire.h" #include "ds18b20.h" -#define DS1820_WRITE_SCRATCHPAD 0x4E -#define DS1820_READ_SCRATCHPAD 0xBE -#define DS1820_COPY_SCRATCHPAD 0x48 -#define DS1820_READ_EEPROM 0xB8 -#define DS1820_READ_PWRSUPPLY 0xB4 -#define DS1820_SEARCHROM 0xF0 -#define DS1820_SKIP_ROM 0xCC -#define DS1820_READROM 0x33 -#define DS1820_MATCHROM 0x55 -#define DS1820_ALARMSEARCH 0xEC -#define DS1820_CONVERT_T 0x44 +#define DS18B20_WRITE_SCRATCHPAD 0x4E +#define DS18B20_READ_SCRATCHPAD 0xBE +#define DS18B20_COPY_SCRATCHPAD 0x48 +#define DS18B20_READ_EEPROM 0xB8 +#define DS18B20_READ_PWRSUPPLY 0xB4 +#define DS18B20_SEARCHROM 0xF0 +#define DS18B20_SKIP_ROM 0xCC +#define DS18B20_READROM 0x33 +#define DS18B20_MATCHROM 0x55 +#define DS18B20_ALARMSEARCH 0xEC +#define DS18B20_CONVERT_T 0x44 + +#define os_sleep_ms(x) vTaskDelay(((x) + portTICK_RATE_MS - 1) / portTICK_RATE_MS) uint8_t ds18b20_read_all(uint8_t pin, ds_sensor_t *result) { - - uint8_t addr[8]; + onewire_addr_t addr; + onewire_search_t search; uint8_t sensor_id = 0; - onewire_reset_search(pin); + + onewire_search_start(&search); - while(onewire_search(pin, addr)){ - uint8_t crc = onewire_crc8(addr, 7); - if (crc != addr[7]){ - printf("CRC check failed: %02X %02X\n", addr[7], crc); + while ((addr = onewire_search_next(&search, pin)) != ONEWIRE_NONE) { + uint8_t crc = onewire_crc8((uint8_t *)&addr, 7); + if (crc != (addr >> 56)){ + printf("CRC check failed: %02X %02X\n", (unsigned)(addr >> 56), crc); return 0; } onewire_reset(pin); onewire_select(pin, addr); - onewire_write(pin, DS1820_CONVERT_T, ONEWIRE_DEFAULT_POWER); + onewire_write(pin, DS18B20_CONVERT_T); + onewire_power(pin); vTaskDelay(750 / portTICK_RATE_MS); onewire_reset(pin); onewire_select(pin, addr); - onewire_write(pin, DS1820_READ_SCRATCHPAD, ONEWIRE_DEFAULT_POWER); + onewire_write(pin, DS18B20_READ_SCRATCHPAD); uint8_t get[10]; @@ -71,15 +75,15 @@ uint8_t ds18b20_read_all(uint8_t pin, ds_sensor_t *result) { float ds18b20_read_single(uint8_t pin) { onewire_reset(pin); + onewire_skip_rom(pin); + onewire_write(pin, DS18B20_CONVERT_T); - onewire_write(pin, DS1820_SKIP_ROM, ONEWIRE_DEFAULT_POWER); - onewire_write(pin, DS1820_CONVERT_T, ONEWIRE_DEFAULT_POWER); - + onewire_power(pin); vTaskDelay(750 / portTICK_RATE_MS); onewire_reset(pin); - onewire_write(pin, DS1820_SKIP_ROM, ONEWIRE_DEFAULT_POWER); - onewire_write(pin, DS1820_READ_SCRATCHPAD, ONEWIRE_DEFAULT_POWER); + onewire_skip_rom(pin); + onewire_write(pin, DS18B20_READ_SCRATCHPAD); uint8_t get[10]; @@ -106,3 +110,114 @@ float ds18b20_read_single(uint8_t pin) { return temperature; //printf("Got a DS18B20 Reading: %d.%02d\n", (int)temperature, (int)(temperature - (int)temperature) * 100); } + +bool ds18b20_measure(int pin, ds18b20_addr_t addr, bool wait) { + if (!onewire_reset(pin)) { + return false; + } + if (addr == DS18B20_ANY) { + onewire_skip_rom(pin); + } else { + onewire_select(pin, addr); + } + taskENTER_CRITICAL(); + onewire_write(pin, DS18B20_CONVERT_T); + // For parasitic devices, power must be applied within 10us after issuing + // the convert command. + onewire_power(pin); + taskEXIT_CRITICAL(); + + if (wait) { + os_sleep_ms(750); + onewire_depower(pin); + } + + return true; +} + +bool ds18b20_read_scratchpad(int pin, ds18b20_addr_t addr, uint8_t *buffer) { + uint8_t crc; + uint8_t expected_crc; + + if (!onewire_reset(pin)) { + return false; + } + if (addr == DS18B20_ANY) { + onewire_skip_rom(pin); + } else { + onewire_select(pin, addr); + } + onewire_write(pin, DS18B20_READ_SCRATCHPAD); + + for (int i = 0; i < 8; i++) { + buffer[i] = onewire_read(pin); + } + crc = onewire_read(pin); + + expected_crc = onewire_crc8(buffer, 8); + if (crc != expected_crc) { + printf("CRC check failed reading scratchpad: %02x %02x %02x %02x %02x %02x %02x %02x : %02x (expected %02x)\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], crc, expected_crc); + return false; + } + + return true; +} + +float ds18b20_read_temperature(int pin, ds18b20_addr_t addr) { + uint8_t scratchpad[8]; + int temp; + + if (!ds18b20_read_scratchpad(pin, addr, scratchpad)) { + return NAN; + } + + temp = scratchpad[1] << 8 | scratchpad[0]; + + return ((float)temp * 625.0)/10000; +} + +float ds18b20_measure_and_read(int pin, ds18b20_addr_t addr) { + if (!ds18b20_measure(pin, addr, true)) { + return NAN; + } + return ds18b20_read_temperature(pin, addr); +} + +bool ds18b20_measure_and_read_multi(int pin, ds18b20_addr_t *addr_list, int addr_count, float *result_list) { + if (!ds18b20_measure(pin, DS18B20_ANY, true)) { + for (int i=0; i < addr_count; i++) { + result_list[i] = NAN; + } + return false; + } + return ds18b20_read_temp_multi(pin, addr_list, addr_count, result_list); +} + +int ds18b20_scan_devices(int pin, ds18b20_addr_t *addr_list, int addr_count) { + onewire_search_t search; + onewire_addr_t addr; + int found = 0; + + onewire_search_start(&search); + while ((addr = onewire_search_next(&search, pin)) != ONEWIRE_NONE) { + if (found < addr_count) { + addr_list[found] = addr; + } + found++; + } + return found; +} + +bool ds18b20_read_temp_multi(int pin, ds18b20_addr_t *addr_list, int addr_count, float *result_list) { + bool result = true; + + for (int i = 0; i < addr_count; i++) { + result_list[i] = ds18b20_read_temperature(pin, addr_list[i]); + if (isnan(result_list[i])) { + result = false; + } + } + return result; +} + + diff --git a/extras/ds18b20/ds18b20.h b/extras/ds18b20/ds18b20.h index 594227b..04b4a53 100644 --- a/extras/ds18b20/ds18b20.h +++ b/extras/ds18b20/ds18b20.h @@ -1,6 +1,139 @@ #ifndef DRIVER_DS18B20_H_ #define DRIVER_DS18B20_H_ +#include "onewire/onewire.h" + +/** @file ds18b20.h + * + * Communicate with the DS18B20 family of one-wire temperature sensor ICs. + * + */ + +typedef onewire_addr_t ds18b20_addr_t; + +/** An address value which can be used to indicate "any device on the bus" */ +#define DS18B20_ANY ONEWIRE_NONE + +/** Find the addresses of all DS18B20 devices on the bus. + * + * Scans the bus for all devices and places their addresses in the supplied + * array. If there are more than `addr_count` devices on the bus, only the + * first `addr_count` are recorded. + * + * @param pin The GPIO pin connected to the DS18B20 bus + * @param addr_list A pointer to an array of ds18b20_addr_t values. This + * will be populated with the addresses of the found + * devices. + * @param addr_count Number of slots in the `addr_list` array. At most this + * many addresses will be returned. + * + * @returns The number of devices found. Note that this may be less than, + * equal to, or more than `addr_count`, depending on how many DS18B20 devices + * are attached to the bus. + */ +int ds18b20_scan_devices(int pin, ds18b20_addr_t *addr_list, int addr_count); + +/** Tell one or more sensors to perform a temperature measurement and + * conversion (CONVERT_T) operation. This operation can take up to 750ms to + * complete. + * + * If `wait=true`, this routine will automatically drive the pin high for the + * necessary 750ms after issuing the command to ensure parasitically-powered + * devices have enough power to perform the conversion operation (for + * non-parasitically-powered devices, this is not necessary but does not + * hurt). If `wait=false`, this routine will drive the pin high, but will + * then return immediately. It is up to the caller to wait the requisite time + * and then depower the bus using onewire_depower() or by issuing another + * command once conversion is done. + * + * @param pin The GPIO pin connected to the DS18B20 device + * @param addr The 64-bit address of the device on the bus. This can be set + * to ::DS18B20_ANY to send the command to all devices on the bus + * at the same time. + * @param wait Whether to wait for the necessary 750ms for the DS18B20 to + * finish performing the conversion before returning to the + * caller (You will normally want to do this). + * + * @returns `true` if the command was successfully issued, or `false` on error. + */ +bool ds18b20_measure(int pin, ds18b20_addr_t addr, bool wait); + +/** Read the value from the last CONVERT_T operation. + * + * This should be called after ds18b20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the DS18B20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18B20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * + * @returns The temperature in degrees Celsius, or NaN if there was an error. + */ +float ds18b20_read_temperature(int pin, ds18b20_addr_t addr); + +/** Read the value from the last CONVERT_T operation for multiple devices. + * + * This should be called after ds18b20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the DS18B20 bus + * @param addr_list A list of addresses for devices to read. + * @param addr_count The number of entries in `addr_list`. + * @param result_list An array of floats to hold the returned temperature + * values. It should have at least `addr_count` entries. + * + * @returns `true` if all temperatures were fetched successfully, or `false` + * if one or more had errors (the temperature for erroring devices will be + * returned as NaN). + */ +bool ds18b20_read_temp_multi(int pin, ds18b20_addr_t *addr_list, int addr_count, float *result_list); + +/** Perform a ds18b20_measure() followed by ds18b20_read_temperature() + * + * @param pin The GPIO pin connected to the DS18B20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18B20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * + * @returns The temperature in degrees Celsius, or NaN if there was an error. + */ +float ds18b20_measure_and_read(int pin, ds18b20_addr_t addr); + +/** Perform a ds18b20_measure() followed by ds18b20_read_temp_multi() + * + * @param pin The GPIO pin connected to the DS18B20 bus + * @param addr_list A list of addresses for devices to read. + * @param addr_count The number of entries in `addr_list`. + * @param result_list An array of floats to hold the returned temperature + * values. It should have at least `addr_count` entries. + * + * @returns `true` if all temperatures were fetched successfully, or `false` + * if one or more had errors (the temperature for erroring devices will be + * returned as NaN). + */ +bool ds18b20_measure_and_read_multi(int pin, ds18b20_addr_t *addr_list, int addr_count, float *result_list); + +/** Read the scratchpad data for a particular DS18B20 device. + * + * This is not generally necessary to do directly. It is done automatically + * as part of ds18b20_read_temperature(). + * + * @param pin The GPIO pin connected to the DS18B20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18B20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param buffer An 8-byte buffer to hold the read data. + * + * @returns `true` if the data was read successfully, or `false` on error. + */ +bool ds18b20_read_scratchpad(int pin, ds18b20_addr_t addr, uint8_t *buffer); + +// The following are obsolete/deprecated APIs + typedef struct { uint8_t id; float value; diff --git a/extras/mbedtls/mbedtls b/extras/mbedtls/mbedtls index 0a0c22e..a7ffc8f 160000 --- a/extras/mbedtls/mbedtls +++ b/extras/mbedtls/mbedtls @@ -1 +1 @@ -Subproject commit 0a0c22e0efcf2f8f71d7e16712f80b8f77326f72 +Subproject commit a7ffc8f7396573bec401e0afcc073137522d5305 diff --git a/extras/onewire/onewire.c b/extras/onewire/onewire.c index 3a946ba..159360b 100644 --- a/extras/onewire/onewire.c +++ b/extras/onewire/onewire.c @@ -1,206 +1,207 @@ #include "onewire.h" +#include "string.h" +#include "task.h" +#include "esp/gpio.h" -// global search state -static unsigned char ROM_NO[ONEWIRE_NUM][8]; -static uint8_t LastDiscrepancy[ONEWIRE_NUM]; -static uint8_t LastFamilyDiscrepancy[ONEWIRE_NUM]; -static uint8_t LastDeviceFlag[ONEWIRE_NUM]; +#define ONEWIRE_SELECT_ROM 0x55 +#define ONEWIRE_SKIP_ROM 0xcc +#define ONEWIRE_SEARCH 0xf0 -void onewire_init(uint8_t pin) -{ - gpio_enable(pin, GPIO_INPUT); - onewire_reset_search(pin); +// Waits up to `max_wait` microseconds for the specified pin to go high. +// Returns true if successful, false if the bus never comes high (likely +// shorted). +static inline bool _onewire_wait_for_bus(int pin, int max_wait) { + bool state; + for (int i = 0; i < ((max_wait + 4) / 5); i++) { + if (gpio_read(pin)) break; + sdk_os_delay_us(5); + } + state = gpio_read(pin); + // Wait an extra 1us to make sure the devices have an adequate recovery + // time before we drive things low again. + sdk_os_delay_us(1); + return state; } // Perform the onewire reset function. We will wait up to 250uS for // the bus to come high, if it doesn't then it is broken or shorted -// and we return a 0; +// and we return false; // -// Returns 1 if a device asserted a presence pulse, 0 otherwise. +// Returns true if a device asserted a presence pulse, false otherwise. // -uint8_t onewire_reset(uint8_t pin) -{ - uint8_t r; - uint8_t retries = 125; +bool onewire_reset(int pin) { + bool r; - noInterrupts(); - DIRECT_MODE_INPUT(pin); - interrupts(); - // wait until the wire is high... just in case - do { - if (--retries == 0) return 0; - delayMicroseconds(2); - } while ( !DIRECT_READ(pin)); + gpio_enable(pin, GPIO_OUT_OPEN_DRAIN); + gpio_write(pin, 1); + // wait until the wire is high... just in case + if (!_onewire_wait_for_bus(pin, 250)) return false; - noInterrupts(); - DIRECT_WRITE_LOW(pin); - DIRECT_MODE_OUTPUT(pin); // drive output low - interrupts(); - delayMicroseconds(480); - noInterrupts(); - DIRECT_MODE_INPUT(pin); // allow it to float - delayMicroseconds(70); - r = !DIRECT_READ(pin); - interrupts(); - delayMicroseconds(410); - return r; + gpio_write(pin, 0); + sdk_os_delay_us(480); + + taskENTER_CRITICAL(); + gpio_write(pin, 1); // allow it to float + sdk_os_delay_us(70); + r = !gpio_read(pin); + taskEXIT_CRITICAL(); + + // Wait for all devices to finish pulling the bus low before returning + if (!_onewire_wait_for_bus(pin, 410)) return false; + + return r; } -// Write a bit. Port and bit is used to cut lookup time and provide -// more certain timing. -// -static void onewire_write_bit(uint8_t pin, uint8_t v) -{ - if (v & 1) { - noInterrupts(); - DIRECT_WRITE_LOW(pin); - DIRECT_MODE_OUTPUT(pin); // drive output low - delayMicroseconds(10); - DIRECT_WRITE_HIGH(pin); // drive output high - interrupts(); - delayMicroseconds(55); - } else { - noInterrupts(); - DIRECT_WRITE_LOW(pin); - DIRECT_MODE_OUTPUT(pin); // drive output low - delayMicroseconds(65); - DIRECT_WRITE_HIGH(pin); // drive output high - interrupts(); - delayMicroseconds(5); - } +static bool _onewire_write_bit(int pin, bool v) { + if (!_onewire_wait_for_bus(pin, 10)) return false; + if (v) { + taskENTER_CRITICAL(); + gpio_write(pin, 0); // drive output low + sdk_os_delay_us(10); + gpio_write(pin, 1); // allow output high + taskEXIT_CRITICAL(); + sdk_os_delay_us(55); + } else { + taskENTER_CRITICAL(); + gpio_write(pin, 0); // drive output low + sdk_os_delay_us(65); + gpio_write(pin, 1); // allow output high + taskEXIT_CRITICAL(); + } + sdk_os_delay_us(1); + + return true; } -// Read a bit. Port and bit is used to cut lookup time and provide -// more certain timing. -// -static uint8_t onewire_read_bit(uint8_t pin) -{ - uint8_t r; +static int _onewire_read_bit(int pin) { + int r; - noInterrupts(); - DIRECT_MODE_OUTPUT(pin); - DIRECT_WRITE_LOW(pin); - delayMicroseconds(3); - DIRECT_MODE_INPUT(pin); // let pin float, pull up will raise - delayMicroseconds(10); - r = DIRECT_READ(pin); - interrupts(); - delayMicroseconds(53); - return r; + if (!_onewire_wait_for_bus(pin, 10)) return -1; + taskENTER_CRITICAL(); + gpio_write(pin, 0); + sdk_os_delay_us(2); + gpio_write(pin, 1); // let pin float, pull up will raise + sdk_os_delay_us(11); + r = gpio_read(pin); // Must sample within 15us of start + taskEXIT_CRITICAL(); + sdk_os_delay_us(48); + + return r; } -// Write a byte. The writing code uses the active drivers to raise the -// pin high, if you need power after the write (e.g. DS18S20 in -// parasite power mode) then set 'power' to 1, otherwise the pin will -// go tri-state at the end of the write to avoid heating in a short or -// other mishap. +// Write a byte. The writing code uses open-drain mode and expects the pullup +// resistor to pull the line high when not driven low. If you need strong +// power after the write (e.g. DS18B20 in parasite power mode) then call +// onewire_power() after this is complete to actively drive the line high. // -void onewire_write(uint8_t pin, uint8_t v, uint8_t power /* = 0 */) { - uint8_t bitMask; +bool onewire_write(int pin, uint8_t v) { + uint8_t bitMask; - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - onewire_write_bit(pin, (bitMask & v)?1:0); - } - if ( !power) { - noInterrupts(); - DIRECT_MODE_INPUT(pin); - DIRECT_WRITE_LOW(pin); - interrupts(); - } + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if (!_onewire_write_bit(pin, (bitMask & v))) { + return false; + } + } + return true; } -void onewire_write_bytes(uint8_t pin, const uint8_t *buf, uint16_t count, bool power /* = 0 */) { - uint16_t i; - for (i = 0 ; i < count ; i++) - onewire_write(pin, buf[i], ONEWIRE_DEFAULT_POWER); - if (!power) { - noInterrupts(); - DIRECT_MODE_INPUT(pin); - DIRECT_WRITE_LOW(pin); - interrupts(); - } +bool onewire_write_bytes(int pin, const uint8_t *buf, size_t count) { + size_t i; + + for (i = 0 ; i < count ; i++) { + if (!onewire_write(pin, buf[i])) { + return false; + } + } + return true; } // Read a byte // -uint8_t onewire_read(uint8_t pin) { - uint8_t bitMask; - uint8_t r = 0; +int onewire_read(int pin) { + uint8_t bitMask; + int r = 0; + int bit; - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - if (onewire_read_bit(pin)) r |= bitMask; - } - return r; + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + bit = _onewire_read_bit(pin); + if (bit < 0) { + return -1; + } else if (bit) { + r |= bitMask; + } + } + return r; } -void onewire_read_bytes(uint8_t pin, uint8_t *buf, uint16_t count) { - uint16_t i; - for (i = 0 ; i < count ; i++) - buf[i] = onewire_read(pin); +bool onewire_read_bytes(int pin, uint8_t *buf, size_t count) { + size_t i; + int b; + + for (i = 0 ; i < count ; i++) { + b = onewire_read(pin); + if (b < 0) return false; + buf[i] = b; + } + return true; } -// Do a ROM select -// -void onewire_select(uint8_t pin, const uint8_t rom[8]) -{ +bool onewire_select(int pin, onewire_addr_t addr) { uint8_t i; - onewire_write(pin, 0x55, ONEWIRE_DEFAULT_POWER); // Choose ROM + if (!onewire_write(pin, ONEWIRE_SELECT_ROM)) { + return false; + } - for (i = 0; i < 8; i++) onewire_write(pin, rom[i], ONEWIRE_DEFAULT_POWER); + for (i = 0; i < 8; i++) { + if (!onewire_write(pin, addr & 0xff)) { + return false; + } + addr >>= 8; + } + + return true; } -// Do a ROM skip -// -void onewire_skip(uint8_t pin) -{ - onewire_write(pin, 0xCC, ONEWIRE_DEFAULT_POWER); // Skip ROM +bool onewire_skip_rom(int pin) { + return onewire_write(pin, ONEWIRE_SKIP_ROM); } -void onewire_depower(uint8_t pin) -{ - noInterrupts(); - DIRECT_MODE_INPUT(pin); - interrupts(); +bool onewire_power(int pin) { + // Make sure the bus is not being held low before driving it high, or we + // may end up shorting ourselves out. + if (!_onewire_wait_for_bus(pin, 10)) return false; + + gpio_enable(pin, GPIO_OUTPUT); + gpio_write(pin, 1); + + return true; } -// You need to use this function to start a search again from the beginning. -// You do not need to do it for the first search, though you could. -// -void onewire_reset_search(uint8_t pin) -{ - // reset the search state - LastDiscrepancy[pin] = 0; - LastDeviceFlag[pin] = 0; - LastFamilyDiscrepancy[pin] = 0; - int i; - for(i = 7; ; i--) { - ROM_NO[pin][i] = 0; - if ( i == 0) break; - } +void onewire_depower(int pin) { + gpio_enable(pin, GPIO_OUT_OPEN_DRAIN); } -// Setup the search to find the device type 'family_code' on the next call -// to search(*newAddr) if it is present. -// -void onewire_target_search(uint8_t pin, uint8_t family_code) -{ - // set the search state to find SearchFamily type devices - ROM_NO[pin][0] = family_code; - uint8_t i; - for (i = 1; i < 8; i++) - ROM_NO[pin][i] = 0; - LastDiscrepancy[pin] = 64; - LastFamilyDiscrepancy[pin] = 0; - LastDeviceFlag[pin] = 0; +void onewire_search_start(onewire_search_t *search) { + // reset the search state + memset(search, 0, sizeof(*search)); } -// Perform a search. If this function returns a '1' then it has -// enumerated the next device and you may retrieve the ROM from the -// OneWire::address variable. If there are no devices, no further +void onewire_search_prefix(onewire_search_t *search, uint8_t family_code) { + uint8_t i; + + search->rom_no[0] = family_code; + for (i = 1; i < 8; i++) { + search->rom_no[i] = 0; + } + search->last_discrepancy = 64; + search->last_device_found = false; +} + +// Perform a search. If the next device has been successfully enumerated, its +// ROM address will be returned. If there are no devices, no further // devices, or something horrible happens in the middle of the -// enumeration then a 0 is returned. If a new device is found then -// its address is copied to newAddr. Use OneWire::reset_search() to +// enumeration then ONEWIRE_NONE is returned. Use OneWire::reset_search() to // start over. // // --- Replaced by the one from the Dallas Semiconductor web site --- @@ -210,129 +211,119 @@ void onewire_target_search(uint8_t pin, uint8_t family_code) // Return 1 : device found, ROM number in ROM_NO buffer // 0 : device not found, end of search // -uint8_t onewire_search(uint8_t pin, uint8_t *newAddr) -{ - uint8_t id_bit_number; - uint8_t last_zero, rom_byte_number, search_result; - uint8_t id_bit, cmp_id_bit; +onewire_addr_t onewire_search_next(onewire_search_t *search, int pin) { + //TODO: add more checking for read/write errors + uint8_t id_bit_number; + uint8_t last_zero, search_result; + int rom_byte_number; + uint8_t id_bit, cmp_id_bit; + onewire_addr_t addr; + unsigned char rom_byte_mask; + bool search_direction; - unsigned char rom_byte_mask, search_direction; - - // initialize for search - id_bit_number = 1; - last_zero = 0; - rom_byte_number = 0; - rom_byte_mask = 1; - search_result = 0; + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; - // if the last call was not the last one - if (!LastDeviceFlag[pin]) - { - // 1-Wire reset - if (!onewire_reset(pin)) - { - // reset the search - LastDiscrepancy[pin] = 0; - LastDeviceFlag[pin] = 0; - LastFamilyDiscrepancy[pin] = 0; - return 0; - } + // if the last call was not the last one + if (!search->last_device_found) { + // 1-Wire reset + if (!onewire_reset(pin)) { + // reset the search + search->last_discrepancy = 0; + search->last_device_found = false; + return ONEWIRE_NONE; + } - // issue the search command - onewire_write(pin, 0xF0, ONEWIRE_DEFAULT_POWER); + // issue the search command + onewire_write(pin, ONEWIRE_SEARCH); - // loop to do the search - do - { - // read a bit and its complement - id_bit = onewire_read_bit(pin); - cmp_id_bit = onewire_read_bit(pin); + // loop to do the search + do { + // read a bit and its complement + id_bit = _onewire_read_bit(pin); + cmp_id_bit = _onewire_read_bit(pin); - // check for no devices on 1-wire - if ((id_bit == 1) && (cmp_id_bit == 1)) - break; - else - { - // all devices coupled have 0 or 1 - if (id_bit != cmp_id_bit) - search_direction = id_bit; // bit write value for search - else - { - // if this discrepancy if before the Last Discrepancy - // on a previous next then pick the same as last time - if (id_bit_number < LastDiscrepancy[pin]) - search_direction = ((ROM_NO[pin][rom_byte_number] & rom_byte_mask) > 0); - else - // if equal to last pick 1, if not then pick 0 - search_direction = (id_bit_number == LastDiscrepancy[pin]); + // check for no devices on 1-wire + if ((id_bit < 0) || (cmp_id_bit < 0)) { + // Read error + break; + } else if ((id_bit == 1) && (cmp_id_bit == 1)) { + break; + } else { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) { + search_direction = id_bit; // bit write value for search + } else { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < search->last_discrepancy) { + search_direction = ((search->rom_no[rom_byte_number] & rom_byte_mask) > 0); + } else { + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == search->last_discrepancy); + } - // if 0 was picked then record its position in LastZero - if (search_direction == 0) - { - last_zero = id_bit_number; + // if 0 was picked then record its position in LastZero + if (!search_direction) { + last_zero = id_bit_number; + } + } - // check for Last discrepancy in family - if (last_zero < 9) - LastFamilyDiscrepancy[pin] = last_zero; - } + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction) { + search->rom_no[rom_byte_number] |= rom_byte_mask; + } else { + search->rom_no[rom_byte_number] &= ~rom_byte_mask; + } + + // serial number search direction write bit + _onewire_write_bit(pin, search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) { + // search successful so set last_discrepancy,last_device_found,search_result + search->last_discrepancy = last_zero; + + // check for last device + if (search->last_discrepancy == 0) { + search->last_device_found = true; } - // set or clear the bit in the ROM byte rom_byte_number - // with mask rom_byte_mask - if (search_direction == 1) - ROM_NO[pin][rom_byte_number] |= rom_byte_mask; - else - ROM_NO[pin][rom_byte_number] &= ~rom_byte_mask; + search_result = 1; + } + } - // serial number search direction write bit - onewire_write_bit(pin, search_direction); - - // increment the byte counter id_bit_number - // and shift the mask rom_byte_mask - id_bit_number++; - rom_byte_mask <<= 1; - - // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask - if (rom_byte_mask == 0) - { - rom_byte_number++; - rom_byte_mask = 1; - } - } - } - while(rom_byte_number < 8); // loop until through all ROM bytes 0-7 - - // if the search was successful then - if (!(id_bit_number < 65)) - { - // search successful so set LastDiscrepancy,LastDeviceFlag,search_result - LastDiscrepancy[pin] = last_zero; - - // check for last device - if (LastDiscrepancy[pin] == 0) - LastDeviceFlag[pin] = 1; - - search_result = 1; - } - } - - // if no device found then reset counters so next 'search' will be like a first - if (!search_result || !ROM_NO[pin][0]) - { - LastDiscrepancy[pin] = 0; - LastDeviceFlag[pin] = 0; - LastFamilyDiscrepancy[pin] = 0; - search_result = 0; - } - else - { - for (rom_byte_number = 0; rom_byte_number < 8; rom_byte_number++) - { - newAddr[rom_byte_number] = ROM_NO[pin][rom_byte_number]; - //printf("Ok I found something at %d - %x...\n",rom_byte_number, newAddr[rom_byte_number]); - } - } - return search_result; + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !search->rom_no[0]) { + search->last_discrepancy = 0; + search->last_device_found = false; + return ONEWIRE_NONE; + } else { + addr = 0; + for (rom_byte_number = 7; rom_byte_number >= 0; rom_byte_number--) { + addr = (addr << 8) | search->rom_no[rom_byte_number]; + } + //printf("Ok I found something at %08x%08x...\n", (uint32_t)(addr >> 32), (uint32_t)addr); + } + return addr; } // The 1-Wire CRC scheme is described in Maxim Application Note 27: @@ -371,41 +362,38 @@ static const uint8_t dscrc_table[] = { // compared to all those delayMicrosecond() calls. But I got // confused, so I use this table from the examples.) // -uint8_t onewire_crc8(const uint8_t *addr, uint8_t len) -{ - uint8_t crc = 0; +uint8_t onewire_crc8(const uint8_t *data, uint8_t len) { + uint8_t crc = 0; - while (len--) { - crc = pgm_read_byte(dscrc_table + (crc ^ *addr++)); - } - return crc; + while (len--) { + crc = pgm_read_byte(dscrc_table + (crc ^ *data++)); + } + return crc; } #else // // Compute a Dallas Semiconductor 8 bit CRC directly. // this is much slower, but much smaller, than the lookup table. // -uint8_t onewire_crc8(const uint8_t *addr, uint8_t len) -{ - uint8_t crc = 0; - - while (len--) { - uint8_t inbyte = *addr++; - uint8_t i; - for (i = 8; i; i--) { - uint8_t mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - if (mix) crc ^= 0x8C; - inbyte >>= 1; - } - } - return crc; +uint8_t onewire_crc8(const uint8_t *data, uint8_t len) { + uint8_t crc = 0; + + while (len--) { + uint8_t inbyte = *data++; + for (int i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; } #endif // Compute the 1-Wire CRC16 and compare it against the received CRC. // Example usage (reading a DS2408): - // // Put everything in a buffer so we can compute the CRC easily. +// // Put everything in a buffer so we can compute the CRC easily. // uint8_t buf[13]; // buf[0] = 0xF0; // Read PIO Registers // buf[1] = 0x88; // LSB address @@ -423,9 +411,8 @@ uint8_t onewire_crc8(const uint8_t *addr, uint8_t len) // *not* at a 16-bit integer. // @param crc - The crc starting value (optional) // @return 1, iff the CRC matches. -bool onewire_check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc) -{ - crc = ~onewire_crc16(input, len, crc); +bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv) { + uint16_t crc = ~onewire_crc16(input, len, crc_iv); return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; } @@ -441,8 +428,8 @@ bool onewire_check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inve // @param len - How many bytes to use. // @param crc - The crc starting value (optional) // @return The CRC16, as defined by Dallas Semiconductor. -uint16_t onewire_crc16(const uint8_t* input, uint16_t len, uint16_t crc) -{ +uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv) { + uint16_t crc = crc_iv; static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; @@ -463,4 +450,4 @@ uint16_t onewire_crc16(const uint8_t* input, uint16_t len, uint16_t crc) crc ^= cdata; } return crc; -} \ No newline at end of file +} diff --git a/extras/onewire/onewire.h b/extras/onewire/onewire.h index 1a0b15d..89823d7 100644 --- a/extras/onewire/onewire.h +++ b/extras/onewire/onewire.h @@ -4,135 +4,232 @@ #include // sdk_os_delay_us #include "FreeRTOS.h" -// 1 for keeping the parasitic power on H -#define ONEWIRE_DEFAULT_POWER 1 +/** @file onewire.h + * + * Routines to access devices using the Dallas Semiconductor 1-Wire(tm) + * protocol. + */ -// Maximum number of devices. -#define ONEWIRE_NUM 20 - -// You can exclude certain features from OneWire. In theory, this -// might save some space. In practice, the compiler automatically -// removes unused code (technically, the linker, using -fdata-sections -// and -ffunction-sections when compiling, and Wl,--gc-sections -// when linking), so most of these will not result in any code size -// reduction. Well, unless you try to use the missing features -// and redesign your program to not need them! ONEWIRE_CRC8_TABLE -// is the exception, because it selects a fast but large algorithm -// or a small but slow algorithm. - -// Select the table-lookup method of computing the 8-bit CRC -// by setting this to 1. The lookup table enlarges code size by -// about 250 bytes. It does NOT consume RAM (but did in very -// old versions of OneWire). If you disable this, a slower -// but very compact algorithm is used. +/** Select the table-lookup method of computing the 8-bit CRC + * by setting this to 1 during compilation. The lookup table enlarges code + * size by about 250 bytes. By default, a slower but very compact algorithm + * is used. + */ #ifndef ONEWIRE_CRC8_TABLE #define ONEWIRE_CRC8_TABLE 0 #endif -// Platform specific I/O definitions -#define noInterrupts portDISABLE_INTERRUPTS -#define interrupts portENABLE_INTERRUPTS -#define delayMicroseconds sdk_os_delay_us +/** Type used to hold all 1-Wire device ROM addresses (64-bit) */ +typedef uint64_t onewire_addr_t; -#define DIRECT_READ(pin) gpio_read(pin) -#define DIRECT_MODE_INPUT(pin) gpio_enable(pin, GPIO_INPUT) -#define DIRECT_MODE_OUTPUT(pin) gpio_enable(pin, GPIO_OUTPUT) -#define DIRECT_WRITE_LOW(pin) gpio_write(pin, 0) -#define DIRECT_WRITE_HIGH(pin) gpio_write(pin, 1) +/** Structure to contain the current state for onewire_search_next(), etc */ +typedef struct { + uint8_t rom_no[8]; + uint8_t last_discrepancy; + bool last_device_found; +} onewire_search_t; -void onewire_init(uint8_t pin); +/** ::ONEWIRE_NONE is an invalid ROM address that will never occur in a device + * (CRC mismatch), and so can be useful as an indicator for "no-such-device", + * etc. + */ +#define ONEWIRE_NONE ((onewire_addr_t)(0xffffffffffffffffLL)) -// Perform a 1-Wire reset cycle. Returns 1 if a device responds -// with a presence pulse. Returns 0 if there is no device or the -// bus is shorted or otherwise held low for more than 250uS -uint8_t onewire_reset(uint8_t pin); +/** Perform a 1-Wire reset cycle. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @returns `true` if at least one device responds with a presence pulse, + * `false` if no devices were detected (or the bus is shorted, etc) + */ +bool onewire_reset(int pin); -// Issue a 1-Wire rom select command, you do the reset first. -void onewire_select(uint8_t pin, const uint8_t rom[8]); +/** Issue a 1-Wire rom select command to select a particular device. + * + * It is necessary to call onewire_reset() before calling this function. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param addr The ROM address of the device to select + * + * @returns `true` if the "ROM select" command could be succesfully issued, + * `false` if there was an error. + */ +bool onewire_select(int pin, const onewire_addr_t addr); -// Issue a 1-Wire rom skip command, to address all on bus. -void onewire_skip(uint8_t pin); +/** Issue a 1-Wire "skip ROM" command to select *all* devices on the bus. + * + * It is necessary to call onewire_reset() before calling this function. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @returns `true` if the "skip ROM" command could be succesfully issued, + * `false` if there was an error. + */ +bool onewire_skip_rom(int pin); -// Write a byte. If 'power' is one then the wire is held high at -// the end for parasitically powered devices. You are responsible -// for eventually depowering it by calling depower() or doing -// another read or write. -void onewire_write(uint8_t pin, uint8_t v, uint8_t power); +/** Write a byte on the onewire bus. + * + * The writing code uses open-drain mode and expects the pullup resistor to + * pull the line high when not driven low. If you need strong power after the + * write (e.g. DS18B20 in parasite power mode) then call onewire_power() after + * this is complete to actively drive the line high. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param v The byte value to write + * + * @returns `true` if successful, `false` on error. + */ +bool onewire_write(int pin, uint8_t v); -void onewire_write_bytes(uint8_t pin, const uint8_t *buf, uint16_t count, bool power); +/** Write multiple bytes on the 1-Wire bus. + * + * See onewire_write() for more info. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param buf A pointer to the buffer of bytes to be written + * @param count Number of bytes to write + * + * @returns `true` if all bytes written successfully, `false` on error. + */ +bool onewire_write_bytes(int pin, const uint8_t *buf, size_t count); -// Read a byte. -uint8_t onewire_read(uint8_t pin); +/** Read a byte from a 1-Wire device. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @returns the read byte on success, negative value on error. + */ +int onewire_read(int pin); -void onewire_read_bytes(uint8_t pin, uint8_t *buf, uint16_t count); +/** Read multiple bytes from a 1-Wire device. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param buf A pointer to the buffer to contain the read bytes + * @param count Number of bytes to read + * + * @returns `true` on success, `false` on error. + */ +bool onewire_read_bytes(int pin, uint8_t *buf, size_t count); -// Write a bit. The bus is always left powered at the end, see -// note in write() about that. -// void onewire_write_bit(uint8_t pin, uint8_t v); +/** Actively drive the bus high to provide extra power for certain operations + * of parasitically-powered devices. + * + * For parasitically-powered devices which need more power than can be + * provided via the normal pull-up resistor, it may be necessary for some + * operations to drive the bus actively high. This function can be used to + * perform that operation. + * + * The bus can be depowered once it is no longer needed by calling + * onewire_depower(), or it will be depowered automatically the next time + * onewire_reset() is called to start another command. + * + * Note: Make sure the device(s) you are powering will not pull more current + * than the ESP8266 is able to supply via its GPIO pins (this is especially + * important when multiple devices are on the same bus and they are all + * performing a power-intensive operation at the same time (i.e. multiple + * DS18B20 sensors, which have all been given a "convert T" operation by using + * onewire_skip_rom())). + * + * Note: This routine will check to make sure that the bus is already high + * before driving it, to make sure it doesn't attempt to drive it high while + * something else is pulling it low (which could cause a reset or damage the + * ESP8266). + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @returns `true` on success, `false` on error. + */ +bool onewire_power(int pin); -// Read a bit. -// uint8_t onewire_read_bit(uint8_t pin); +/** Stop forcing power onto the bus. + * + * You only need to do this if you previously called onewire_power() to drive + * the bus high and now want to allow it to float instead. Note that + * onewire_reset() will also automatically depower the bus first, so you do + * not need to call this first if you just want to start a new operation. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + */ +void onewire_depower(int pin); -// Stop forcing power onto the bus. You only need to do this if -// you used the 'power' flag to write() or used a write_bit() call -// and aren't about to do another read or write. You would rather -// not leave this powered if you don't have to, just in case -// someone shorts your bus. -void onewire_depower(uint8_t pin); +/** Clear the search state so that it will start from the beginning on the next + * call to onewire_search_next(). + * + * @param search The onewire_search_t structure to reset. + */ +void onewire_search_start(onewire_search_t *search); -// Clear the search state so that if will start from the beginning again. -void onewire_reset_search(uint8_t pin); +/** Setup the search to search for devices with the specified "family code". + * + * @param search The onewire_search_t structure to update. + * @param family_code The "family code" to search for. + */ +void onewire_search_prefix(onewire_search_t *search, uint8_t family_code); -// Setup the search to find the device type 'family_code' on the next call -// to search(*newAddr) if it is present. -void onewire_target_search(uint8_t pin, uint8_t family_code); +/** Search for the next device on the bus. + * + * The order of returned device addresses is deterministic. You will always + * get the same devices in the same order. + * + * @returns the address of the next device on the bus, or ::ONEWIRE_NONE if + * there is no next address. ::ONEWIRE_NONE might also mean that the bus is + * shorted, there are no devices, or you have already retrieved all of them. + * + * It might be a good idea to check the CRC to make sure you didn't get + * garbage. + */ +onewire_addr_t onewire_search_next(onewire_search_t *search, int pin); -// Look for the next device. Returns 1 if a new address has been -// returned. A zero might mean that the bus is shorted, there are -// no devices, or you have already retrieved all of them. It -// might be a good idea to check the CRC to make sure you didn't -// get garbage. The order is deterministic. You will always get -// the same devices in the same order. -uint8_t onewire_search(uint8_t pin, uint8_t *newAddr); +/** Compute a Dallas Semiconductor 8 bit CRC. + * + * These are used in the ROM address and scratchpad registers to verify the + * transmitted data is correct. + */ +uint8_t onewire_crc8(const uint8_t *data, uint8_t len); -// Compute a Dallas Semiconductor 8 bit CRC, these are used in the -// ROM and scratchpad registers. -uint8_t onewire_crc8(const uint8_t *addr, uint8_t len); +/** Compute the 1-Wire CRC16 and compare it against the received CRC. + * + * Example usage (reading a DS2408): + * @code + * // Put everything in a buffer so we can compute the CRC easily. + * uint8_t buf[13]; + * buf[0] = 0xF0; // Read PIO Registers + * buf[1] = 0x88; // LSB address + * buf[2] = 0x00; // MSB address + * onewire_write_bytes(pin, buf, 3); // Write 3 cmd bytes + * onewire_read_bytes(pin, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + * if (!onewire_check_crc16(buf, 11, &buf[11])) { + * // TODO: Handle error. + * } + * @endcode + * + * @param input Array of bytes to checksum. + * @param len Number of bytes in `input` + * @param inverted_crc The two CRC16 bytes in the received data. + * This should just point into the received data, + * *not* at a 16-bit integer. + * @param crc_iv The crc starting value (optional) + * + * @returns `true` if the CRC matches, `false` otherwise. + */ +bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv); -// Compute the 1-Wire CRC16 and compare it against the received CRC. -// Example usage (reading a DS2408): -// // Put everything in a buffer so we can compute the CRC easily. -// uint8_t buf[13]; -// buf[0] = 0xF0; // Read PIO Registers -// buf[1] = 0x88; // LSB address -// buf[2] = 0x00; // MSB address -// WriteBytes(net, buf, 3); // Write 3 cmd bytes -// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 -// if (!CheckCRC16(buf, 11, &buf[11])) { -// // Handle error. -// } -// -// @param input - Array of bytes to checksum. -// @param len - How many bytes to use. -// @param inverted_crc - The two CRC16 bytes in the received data. -// This should just point into the received data, -// *not* at a 16-bit integer. -// @param crc - The crc starting value (optional) -// @return True, iff the CRC matches. -bool onewire_check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc); - -// Compute a Dallas Semiconductor 16 bit CRC. This is required to check -// the integrity of data received from many 1-Wire devices. Note that the -// CRC computed here is *not* what you'll get from the 1-Wire network, -// for two reasons: -// 1) The CRC is transmitted bitwise inverted. -// 2) Depending on the endian-ness of your processor, the binary -// representation of the two-byte return value may have a different -// byte order than the two bytes you get from 1-Wire. -// @param input - Array of bytes to checksum. -// @param len - How many bytes to use. -// @param crc - The crc starting value (optional) -// @return The CRC16, as defined by Dallas Semiconductor. -uint16_t onewire_crc16(const uint8_t* input, uint16_t len, uint16_t crc); +/** Compute a Dallas Semiconductor 16 bit CRC. + * + * This is required to check the integrity of data received from many 1-Wire + * devices. Note that the CRC computed here is *not* what you'll get from the + * 1-Wire network, for two reasons: + * 1. The CRC is transmitted bitwise inverted. + * 2. Depending on the endian-ness of your processor, the binary + * representation of the two-byte return value may have a different + * byte order than the two bytes you get from 1-Wire. + * + * @param input Array of bytes to checksum. + * @param len How many bytes are in `input`. + * @param crc_iv The crc starting value (optional) + * + * @returns the CRC16, as defined by Dallas Semiconductor. + */ +uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv); #endif diff --git a/extras/paho_mqtt_c/LICENSE.txt b/extras/paho_mqtt_c/LICENSE.txt new file mode 100644 index 0000000..a6d74fb --- /dev/null +++ b/extras/paho_mqtt_c/LICENSE.txt @@ -0,0 +1,32 @@ +Software License Agreement (BSD License) + +Copyright (c) 2015, Baoshi Zhu (www.ba0sh1.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: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* Neither the name of Baoshi Zhu nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission from me. + +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 OWNER 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. + +Part of this project is based on Eclipse Paho which is copyrighted by IBM corp. +Refer to the licensing information at the begining of corresponding source files. diff --git a/extras/paho_mqtt_c/MQTTClient.c b/extras/paho_mqtt_c/MQTTClient.c new file mode 100644 index 0000000..b964250 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTClient.c @@ -0,0 +1,551 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ +#include +#include +#include "MQTTClient.h" + +void NewMessageData(MessageData* md, MQTTString* aTopicName, MQTTMessage* aMessgage) { + md->topic = aTopicName; + md->message = aMessgage; +} + + +int getNextPacketId(MQTTClient *c) { + return c->next_packetid = (c->next_packetid == MAX_PACKET_ID) ? 1 : c->next_packetid + 1; +} + + +int sendPacket(MQTTClient* c, int length, Timer* timer) +{ + int rc = FAILURE, + sent = 0; + + while (sent < length && !expired(timer)) + { + rc = c->ipstack->mqttwrite(c->ipstack, &c->buf[sent], length, left_ms(timer)); + if (rc < 0) // there was an error writing the data + break; + sent += rc; + } + if (sent == length) + { + countdown(&(c->ping_timer), c->keepAliveInterval); // record the fact that we have successfully sent the packet + rc = SUCCESS; + } + else + rc = FAILURE; + return rc; +} + + +int decodePacket(MQTTClient* c, int* value, int timeout) +{ + unsigned char i; + int multiplier = 1; + int len = 0; + const int MAX_NO_OF_REMAINING_LENGTH_BYTES = 4; + + *value = 0; + do + { + int rc = MQTTPACKET_READ_ERROR; + + if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES) + { + rc = MQTTPACKET_READ_ERROR; /* bad data */ + goto exit; + } + rc = c->ipstack->mqttread(c->ipstack, &i, 1, timeout); + if (rc != 1) + goto exit; + *value += (i & 127) * multiplier; + multiplier *= 128; + } while ((i & 128) != 0); +exit: + return len; +} + + +int readPacket(MQTTClient* c, Timer* timer) +{ + int rc = FAILURE; + MQTTHeader header = {0}; + int len = 0; + int rem_len = 0; + + /* 1. read the header byte. This has the packet type in it */ + if (c->ipstack->mqttread(c->ipstack, c->readbuf, 1, left_ms(timer)) != 1) + goto exit; + + len = 1; + /* 2. read the remaining length. This is variable in itself */ + decodePacket(c, &rem_len, left_ms(timer)); + len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */ + + /* 3. read the rest of the buffer using a callback to supply the rest of the data */ + if (rem_len > 0 && (c->ipstack->mqttread(c->ipstack, c->readbuf + len, rem_len, left_ms(timer)) != rem_len)) + goto exit; + + header.byte = c->readbuf[0]; + rc = header.bits.type; +exit: + //dmsg_printf("readPacket=%d\r\n", rc); + return rc; +} + + +// assume topic filter and name is in correct format +// # can only be at end +// + and # can only be next to separator +char isTopicMatched(char* topicFilter, MQTTString* topicName) +{ + char* curf = topicFilter; + char* curn = topicName->lenstring.data; + char* curn_end = curn + topicName->lenstring.len; + + while (*curf && curn < curn_end) + { + if (*curn == '/' && *curf != '/') + break; + if (*curf != '+' && *curf != '#' && *curf != *curn) + break; + if (*curf == '+') + { // skip until we meet the next separator, or end of string + char* nextpos = curn + 1; + while (nextpos < curn_end && *nextpos != '/') + nextpos = ++curn + 1; + } + else if (*curf == '#') + curn = curn_end - 1; // skip until end of string + curf++; + curn++; + }; + + return (curn == curn_end) && (*curf == '\0'); +} + + +int deliverMessage(MQTTClient* c, MQTTString* topicName, MQTTMessage* message) +{ + int i; + int rc = FAILURE; + + // we have to find the right message handler - indexed by topic + for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) + { + if (c->messageHandlers[i].topicFilter != 0 && (MQTTPacket_equals(topicName, (char*)c->messageHandlers[i].topicFilter) || + isTopicMatched((char*)c->messageHandlers[i].topicFilter, topicName))) + { + if (c->messageHandlers[i].fp != NULL) + { + MessageData md; + NewMessageData(&md, topicName, message); + c->messageHandlers[i].fp(&md); + rc = SUCCESS; + } + } + } + + if (rc == FAILURE && c->defaultMessageHandler != NULL) + { + MessageData md; + NewMessageData(&md, topicName, message); + c->defaultMessageHandler(&md); + rc = SUCCESS; + } + + return rc; +} + + +int keepalive(MQTTClient* c) +{ + int rc = SUCCESS; + + if (c->keepAliveInterval == 0) + { + rc = SUCCESS; + goto exit; + } + + if (expired(&(c->ping_timer))) + { + if (c->ping_outstanding) + { + // if ping failure accumulated above MAX_FAIL_ALLOWED, the connection is broken + ++(c->fail_count); + if (c->fail_count >= MAX_FAIL_ALLOWED) + { + rc = DISCONNECTED; + goto exit; + } + } + else + { + Timer timer; + InitTimer(&timer); + countdown_ms(&timer, 1000); + c->ping_outstanding = 1; + int len = MQTTSerialize_pingreq(c->buf, c->buf_size); + if (len > 0) + sendPacket(c, len, &timer); + } + // re-arm ping counter + countdown(&(c->ping_timer), c->keepAliveInterval); + } + +exit: + return rc; +} + + +int cycle(MQTTClient* c, Timer* timer) +{ + // read the socket, see what work is due + unsigned short packet_type = readPacket(c, timer); + + int len = 0, + rc = SUCCESS; + + switch (packet_type) + { + case CONNACK: + case PUBACK: + case SUBACK: + break; + case PUBLISH: + { + MQTTString topicName; + MQTTMessage msg; + if (MQTTDeserialize_publish((unsigned char*)&msg.dup, (int*)&msg.qos, (unsigned char*)&msg.retained, (unsigned short*)&msg.id, &topicName, + (unsigned char**)&msg.payload, (int*)&msg.payloadlen, c->readbuf, c->readbuf_size) != 1) + goto exit; + deliverMessage(c, &topicName, &msg); + if (msg.qos != QOS0) + { + if (msg.qos == QOS1) + len = MQTTSerialize_ack(c->buf, c->buf_size, PUBACK, 0, msg.id); + else if (msg.qos == QOS2) + len = MQTTSerialize_ack(c->buf, c->buf_size, PUBREC, 0, msg.id); + if (len <= 0) + rc = FAILURE; + else + rc = sendPacket(c, len, timer); + if (rc == FAILURE) + goto exit; // there was a problem + } + break; + } + case PUBREC: + { + unsigned short mypacketid; + unsigned char dup, type; + if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) + rc = FAILURE; + else if ((len = MQTTSerialize_ack(c->buf, c->buf_size, PUBREL, 0, mypacketid)) <= 0) + rc = FAILURE; + else if ((rc = sendPacket(c, len, timer)) != SUCCESS) // send the PUBREL packet + rc = FAILURE; // there was a problem + if (rc == FAILURE) + goto exit; // there was a problem + break; + } + case PUBCOMP: + break; + case PINGRESP: + { + c->ping_outstanding = 0; + c->fail_count = 0; + } + break; + } + if (c->isconnected) + rc = keepalive(c); +exit: + if (rc == SUCCESS) + rc = packet_type; + return rc; +} + + +void NewMQTTClient(MQTTClient* c, Network* network, unsigned int command_timeout_ms, unsigned char* buf, size_t buf_size, unsigned char* readbuf, size_t readbuf_size) +{ + int i; + c->ipstack = network; + + for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) + c->messageHandlers[i].topicFilter = 0; + c->command_timeout_ms = command_timeout_ms; + c->buf = buf; + c->buf_size = buf_size; + c->readbuf = readbuf; + c->readbuf_size = readbuf_size; + c->isconnected = 0; + c->ping_outstanding = 0; + c->fail_count = 0; + c->defaultMessageHandler = NULL; + InitTimer(&(c->ping_timer)); +} + + +int MQTTYield(MQTTClient* c, int timeout_ms) +{ + int rc = SUCCESS; + Timer timer; + + InitTimer(&timer); + countdown_ms(&timer, timeout_ms); + while (!expired(&timer)) + { + rc = cycle(c, &timer); + // cycle could return 0 or packet_type or 65535 if nothing is read + // cycle returns DISCONNECTED only if keepalive() fails. + if (rc == DISCONNECTED) + break; + rc = SUCCESS; + } + return rc; +} + + +// only used in single-threaded mode where one command at a time is in process +int waitfor(MQTTClient* c, int packet_type, Timer* timer) +{ + int rc = FAILURE; + + do + { + if (expired(timer)) + break; // we timed out + } + while ((rc = cycle(c, timer)) != packet_type); + + return rc; +} + + +int MQTTConnect(MQTTClient* c, MQTTPacket_connectData* options) +{ + Timer connect_timer; + int rc = FAILURE; + MQTTPacket_connectData default_options = MQTTPacket_connectData_initializer; + int len = 0; + + InitTimer(&connect_timer); + countdown_ms(&connect_timer, c->command_timeout_ms); + + if (c->isconnected) // don't send connect packet again if we are already connected + goto exit; + + if (options == 0) + options = &default_options; // set default options if none were supplied + + c->keepAliveInterval = options->keepAliveInterval; + countdown(&(c->ping_timer), c->keepAliveInterval); + + if ((len = MQTTSerialize_connect(c->buf, c->buf_size, options)) <= 0) + goto exit; + if ((rc = sendPacket(c, len, &connect_timer)) != SUCCESS) // send the connect packet + goto exit; // there was a problem + + // this will be a blocking call, wait for the connack + if (waitfor(c, CONNACK, &connect_timer) == CONNACK) + { + unsigned char connack_rc = 255; + char sessionPresent = 0; + if (MQTTDeserialize_connack((unsigned char*)&sessionPresent, &connack_rc, c->readbuf, c->readbuf_size) == 1) + rc = connack_rc; + else + rc = FAILURE; + } + else + rc = FAILURE; + +exit: + if (rc == SUCCESS) + c->isconnected = 1; + return rc; +} + + +int MQTTSubscribe(MQTTClient* c, const char* topic, enum QoS qos, messageHandler handler) +{ + int rc = FAILURE; + Timer timer; + int len = 0; + MQTTString topicStr = MQTTString_initializer; + topicStr.cstring = (char *)topic; + + InitTimer(&timer); + countdown_ms(&timer, c->command_timeout_ms); + if (!c->isconnected) + goto exit; + + len = MQTTSerialize_subscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topicStr, (int*)&qos); + if (len <= 0) + goto exit; + if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet + { + goto exit; // there was a problem + } + + if (waitfor(c, SUBACK, &timer) == SUBACK) // wait for suback + { + int count = 0, grantedQoS = -1; + unsigned short mypacketid; + if (MQTTDeserialize_suback(&mypacketid, 1, &count, &grantedQoS, c->readbuf, c->readbuf_size) == 1) + rc = grantedQoS; // 0, 1, 2 or 0x80 + if (rc != 0x80) + { + int i; + for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) + { + if (c->messageHandlers[i].topicFilter == 0) + { + c->messageHandlers[i].topicFilter = topic; + c->messageHandlers[i].fp = handler; + rc = 0; + break; + } + } + } + } + else + rc = FAILURE; + +exit: + return rc; +} + + +int MQTTUnsubscribe(MQTTClient* c, const char* topicFilter) +{ + int rc = FAILURE; + Timer timer; + MQTTString topic = MQTTString_initializer; + topic.cstring = (char *)topicFilter; + int len = 0; + + InitTimer(&timer); + countdown_ms(&timer, c->command_timeout_ms); + + if (!c->isconnected) + goto exit; + + if ((len = MQTTSerialize_unsubscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic)) <= 0) + goto exit; + if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet + goto exit; // there was a problem + + if (waitfor(c, UNSUBACK, &timer) == UNSUBACK) + { + unsigned short mypacketid; // should be the same as the packetid above + if (MQTTDeserialize_unsuback(&mypacketid, c->readbuf, c->readbuf_size) == 1) + rc = 0; + } + else + rc = FAILURE; + +exit: + return rc; +} + + +int MQTTPublish(MQTTClient* c, const char* topic, MQTTMessage* message) +{ + int rc = FAILURE; + Timer timer; + MQTTString topicStr = MQTTString_initializer; + topicStr.cstring = (char *)topic; + int len = 0; + + InitTimer(&timer); + countdown_ms(&timer, c->command_timeout_ms); + + if (!c->isconnected) + goto exit; + + if (message->qos == QOS1 || message->qos == QOS2) + message->id = getNextPacketId(c); + + len = MQTTSerialize_publish(c->buf, c->buf_size, 0, message->qos, message->retained, message->id, + topicStr, (unsigned char*)message->payload, message->payloadlen); + if (len <= 0) + goto exit; + if ((rc = sendPacket(c, len, &timer)) != SUCCESS) // send the subscribe packet + { + goto exit; // there was a problem + } + + if (message->qos == QOS1) + { + if (waitfor(c, PUBACK, &timer) == PUBACK) + { + // We still can receive from broker, treat as recoverable + c->fail_count = 0; + unsigned short mypacketid; + unsigned char dup, type; + if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) + rc = FAILURE; + else + rc = SUCCESS; + } + else + { + rc = FAILURE; + } + + } + else if (message->qos == QOS2) + { + if (waitfor(c, PUBCOMP, &timer) == PUBCOMP) + { + // We still can receive from broker, treat as recoverable + c->fail_count = 0; + unsigned short mypacketid; + unsigned char dup, type; + if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) + rc = FAILURE; + else + rc = SUCCESS; + } + else + { + rc = FAILURE; + } + } + +exit: + return rc; +} + + +int MQTTDisconnect(MQTTClient* c) +{ + int rc = FAILURE; + Timer timer; // we might wait for incomplete incoming publishes to complete + int len = MQTTSerialize_disconnect(c->buf, c->buf_size); + + InitTimer(&timer); + countdown_ms(&timer, c->command_timeout_ms); + + if (len > 0) + rc = sendPacket(c, len, &timer); // send the disconnect packet + + c->isconnected = 0; + return rc; +} + diff --git a/extras/paho_mqtt_c/MQTTClient.h b/extras/paho_mqtt_c/MQTTClient.h new file mode 100644 index 0000000..f7ea424 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTClient.h @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#ifndef __MQTT_CLIENT_C_ +#define __MQTT_CLIENT_C_ + +#include "MQTTPacket.h" +#include "MQTTESP8266.h" + +#define MAX_PACKET_ID 65535 +#define MAX_MESSAGE_HANDLERS 5 +#define MAX_FAIL_ALLOWED 2 + +enum QoS { QOS0, QOS1, QOS2 }; + +// all failure return codes must be negative +enum returnCode {DISCONNECTED = -3, BUFFER_OVERFLOW = -2, FAILURE = -1, SUCCESS = 0 }; + +void NewTimer(Timer*); + +typedef struct _MQTTMessage +{ + enum QoS qos; + char retained; + char dup; + unsigned short id; + void *payload; + size_t payloadlen; +} MQTTMessage; + +typedef struct _MessageData +{ + MQTTString* topic; + MQTTMessage* message; +} MessageData; + +typedef void (*messageHandler)(MessageData*); + +struct _MQTTClient +{ + unsigned int next_packetid; + unsigned int command_timeout_ms; + size_t buf_size, readbuf_size; + unsigned char *buf; + unsigned char *readbuf; + unsigned int keepAliveInterval; + char ping_outstanding; + int fail_count; + int isconnected; + + struct MessageHandlers + { + const char* topicFilter; + void (*fp) (MessageData*); + } messageHandlers[MAX_MESSAGE_HANDLERS]; // Message handlers are indexed by subscription topic + + void (*defaultMessageHandler) (MessageData*); + + Network* ipstack; + Timer ping_timer; +}; + + +typedef struct _MQTTClient MQTTClient; + + +int MQTTConnect(MQTTClient* c, MQTTPacket_connectData* options); +int MQTTPublish(MQTTClient* c, const char* topic, MQTTMessage* message); +int MQTTSubscribe(MQTTClient* c, const char* topic, enum QoS qos, messageHandler handler); +int MQTTUnsubscribe(MQTTClient* c, const char* topic); +int MQTTDisconnect(MQTTClient* c); +int MQTTYield(MQTTClient* c, int timeout_ms); + +void NewMQTTClient(MQTTClient*, Network*, unsigned int, unsigned char*, size_t, unsigned char*, size_t); + +#define DefaultClient {0, 0, 0, 0, NULL, NULL, 0, 0, 0} + +#endif diff --git a/extras/paho_mqtt_c/MQTTConnect.h b/extras/paho_mqtt_c/MQTTConnect.h new file mode 100644 index 0000000..3d8addd --- /dev/null +++ b/extras/paho_mqtt_c/MQTTConnect.h @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTCONNECT_H_ +#define MQTTCONNECT_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + + +typedef union +{ + unsigned char all; /**< all connect flags */ +#if defined(REVERSED) + struct + { + unsigned int username : 1; /**< 3.1 user name */ + unsigned int password : 1; /**< 3.1 password */ + unsigned int willRetain : 1; /**< will retain setting */ + unsigned int willQoS : 2; /**< will QoS value */ + unsigned int will : 1; /**< will flag */ + unsigned int cleansession : 1; /**< clean session flag */ + unsigned int : 1; /**< unused */ + } bits; +#else + struct + { + unsigned int : 1; /**< unused */ + unsigned int cleansession : 1; /**< cleansession flag */ + unsigned int will : 1; /**< will flag */ + unsigned int willQoS : 2; /**< will QoS value */ + unsigned int willRetain : 1; /**< will retain setting */ + unsigned int password : 1; /**< 3.1 password */ + unsigned int username : 1; /**< 3.1 user name */ + } bits; +#endif +} MQTTConnectFlags; /**< connect flags byte */ + + + +/** + * Defines the MQTT "Last Will and Testament" (LWT) settings for + * the connect packet. + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTW. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** The LWT topic to which the LWT message will be published. */ + MQTTString topicName; + /** The LWT payload. */ + MQTTString message; + /** + * The retained flag for the LWT message (see MQTTAsync_message.retained). + */ + unsigned char retained; + /** + * The quality of service setting for the LWT message (see + * MQTTAsync_message.qos and @ref qos). + */ + char qos; +} MQTTPacket_willOptions; + + +#define MQTTPacket_willOptions_initializer { {'M', 'Q', 'T', 'W'}, 0, {NULL, {0, NULL}}, {NULL, {0, NULL}}, 0, 0 } + + +typedef struct +{ + /** The eyecatcher for this structure. must be MQTC. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** Version of MQTT to be used. 3 = 3.1 4 = 3.1.1 + */ + unsigned char MQTTVersion; + MQTTString clientID; + unsigned short keepAliveInterval; + unsigned char cleansession; + unsigned char willFlag; + MQTTPacket_willOptions will; + MQTTString username; + MQTTString password; +} MQTTPacket_connectData; + +typedef union +{ + unsigned char all; /**< all connack flags */ +#if defined(REVERSED) + struct + { + unsigned int sessionpresent : 1; /**< session present flag */ + unsigned int : 7; /**< unused */ + } bits; +#else + struct + { + unsigned int : 7; /**< unused */ + unsigned int sessionpresent : 1; /**< session present flag */ + } bits; +#endif +} MQTTConnackFlags; /**< connack flags byte */ + +#define MQTTPacket_connectData_initializer { {'M', 'Q', 'T', 'C'}, 0, 4, {NULL, {0, NULL}}, 60, 1, 0, \ + MQTTPacket_willOptions_initializer, {NULL, {0, NULL}}, {NULL, {0, NULL}} } + +DLLExport int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options); +DLLExport int MQTTDeserialize_connect(MQTTPacket_connectData* data, unsigned char* buf, int len); + +DLLExport int MQTTSerialize_connack(unsigned char* buf, int buflen, unsigned char connack_rc, unsigned char sessionPresent); +DLLExport int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen); + +DLLExport int MQTTSerialize_disconnect(unsigned char* buf, int buflen); +DLLExport int MQTTSerialize_pingreq(unsigned char* buf, int buflen); + +#endif /* MQTTCONNECT_H_ */ diff --git a/extras/paho_mqtt_c/MQTTConnectClient.c b/extras/paho_mqtt_c/MQTTConnectClient.c new file mode 100644 index 0000000..4c1e862 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTConnectClient.c @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ +#include +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + +/** + * Determines the length of the MQTT connect packet that would be produced using the supplied connect options. + * @param options the options to be used to build the connect packet + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_connectLength(MQTTPacket_connectData* options) +{ + int len = 0; + + FUNC_ENTRY; + + if (options->MQTTVersion == 3) + len = 12; /* variable depending on MQTT or MQIsdp */ + else if (options->MQTTVersion == 4) + len = 10; + + len += MQTTstrlen(options->clientID)+2; + if (options->willFlag) + len += MQTTstrlen(options->will.topicName)+2 + MQTTstrlen(options->will.message)+2; + if (options->username.cstring || options->username.lenstring.data) + len += MQTTstrlen(options->username)+2; + if (options->password.cstring || options->password.lenstring.data) + len += MQTTstrlen(options->password)+2; + + FUNC_EXIT_RC(len); + return len; +} + + +/** + * Serializes the connect options into the buffer. + * @param buf the buffer into which the packet will be serialized + * @param len the length in bytes of the supplied buffer + * @param options the options to be used to build the connect packet + * @return serialized length, or error if 0 + */ +int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + MQTTConnectFlags flags = {0}; + int len = 0; + int rc = -1; + + FUNC_ENTRY; + if (MQTTPacket_len(len = MQTTSerialize_connectLength(options)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = CONNECT; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, len); /* write remaining length */ + + if (options->MQTTVersion == 4) + { + writeCString(&ptr, "MQTT"); + writeChar(&ptr, (char) 4); + } + else + { + writeCString(&ptr, "MQIsdp"); + writeChar(&ptr, (char) 3); + } + + flags.all = 0; + flags.bits.cleansession = options->cleansession; + flags.bits.will = (options->willFlag) ? 1 : 0; + if (flags.bits.will) + { + flags.bits.willQoS = options->will.qos; + flags.bits.willRetain = options->will.retained; + } + + if (options->username.cstring || options->username.lenstring.data) + flags.bits.username = 1; + if (options->password.cstring || options->password.lenstring.data) + flags.bits.password = 1; + + writeChar(&ptr, flags.all); + writeInt(&ptr, options->keepAliveInterval); + writeMQTTString(&ptr, options->clientID); + if (options->willFlag) + { + writeMQTTString(&ptr, options->will.topicName); + writeMQTTString(&ptr, options->will.message); + } + if (flags.bits.username) + writeMQTTString(&ptr, options->username); + if (flags.bits.password) + writeMQTTString(&ptr, options->password); + + rc = ptr - buf; + + exit: FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Deserializes the supplied (wire) buffer into connack data - return code + * @param sessionPresent the session present flag returned (only for MQTT 3.1.1) + * @param connack_rc returned integer value of the connack return code + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param len the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + MQTTConnackFlags flags = {0}; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != CONNACK) + goto exit; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + if (enddata - curdata < 2) + goto exit; + + flags.all = readChar(&curdata); + *sessionPresent = flags.bits.sessionpresent; + *connack_rc = readChar(&curdata); + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a 0-length packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @param packettype the message type + * @return serialized length, or error if 0 + */ +int MQTTSerialize_zero(unsigned char* buf, int buflen, unsigned char packettype) +{ + MQTTHeader header = {0}; + int rc = -1; + unsigned char *ptr = buf; + + FUNC_ENTRY; + if (buflen < 2) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = packettype; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 0); /* write remaining length */ + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a disconnect packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @return serialized length, or error if 0 + */ +int MQTTSerialize_disconnect(unsigned char* buf, int buflen) +{ + return MQTTSerialize_zero(buf, buflen, DISCONNECT); +} + + +/** + * Serializes a disconnect packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pingreq(unsigned char* buf, int buflen) +{ + return MQTTSerialize_zero(buf, buflen, PINGREQ); +} diff --git a/extras/paho_mqtt_c/MQTTDeserializePublish.c b/extras/paho_mqtt_c/MQTTDeserializePublish.c new file mode 100644 index 0000000..21b7314 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTDeserializePublish.c @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ +#include +#include "StackTrace.h" +#include "MQTTPacket.h" +#include + +#define min(a, b) ((a < b) ? 1 : 0) + +/** + * Deserializes the supplied (wire) buffer into publish data + * @param dup returned integer - the MQTT dup flag + * @param qos returned integer - the MQTT QoS value + * @param retained returned integer - the MQTT retained flag + * @param packetid returned integer - the MQTT packet identifier + * @param topicName returned MQTTString - the MQTT topic in the publish + * @param payload returned byte buffer - the MQTT publish payload + * @param payloadlen returned integer - the length of the MQTT payload + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success + */ +int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName, + unsigned char** payload, int* payloadlen, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != PUBLISH) + goto exit; + *dup = header.bits.dup; + *qos = header.bits.qos; + *retained = header.bits.retain; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + if (!readMQTTLenString(topicName, &curdata, enddata) || + enddata - curdata < 0) /* do we have enough data to read the protocol version byte? */ + goto exit; + + if (*qos > 0) + *packetid = readInt(&curdata); + + *payloadlen = enddata - curdata; + *payload = curdata; + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Deserializes the supplied (wire) buffer into an ack + * @param packettype returned integer - the MQTT packet type + * @param dup returned integer - the MQTT dup flag + * @param packetid returned integer - the MQTT packet identifier + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_ack(unsigned char* packettype, unsigned char* dup, unsigned short* packetid, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + *dup = header.bits.dup; + *packettype = header.bits.type; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + if (enddata - curdata < 2) + goto exit; + *packetid = readInt(&curdata); + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + diff --git a/extras/paho_mqtt_c/MQTTESP8266.c b/extras/paho_mqtt_c/MQTTESP8266.c new file mode 100644 index 0000000..09e645b --- /dev/null +++ b/extras/paho_mqtt_c/MQTTESP8266.c @@ -0,0 +1,187 @@ +/** + ****************************************************************************** + * @file MQTTESP8266.c + * @author Baoshi + * @version 0.1 + * @date Sep 9, 2015 + * @brief Eclipse Paho ported to ESP8266 RTOS + * + ****************************************************************************** + * @copyright + * + * Copyright (c) 2015, Baoshi Zhu. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE.txt file. + * + * THIS SOFTWARE IS PROVIDED 'AS-IS', WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN NO EVENT WILL THE AUTHOR(S) BE HELD LIABLE FOR ANY DAMAGES + * ARISING FROM THE USE OF THIS SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "MQTTESP8266.h" + +char expired(Timer* timer) +{ + portTickType now = xTaskGetTickCount(); + int32_t left = timer->end_time - now; + return (left < 0); +} + + +void countdown_ms(Timer* timer, unsigned int timeout) +{ + portTickType now = xTaskGetTickCount(); + timer->end_time = now + timeout / portTICK_RATE_MS; +} + + +void countdown(Timer* timer, unsigned int timeout) +{ + countdown_ms(timer, timeout * 1000); +} + + +int left_ms(Timer* timer) +{ + portTickType now = xTaskGetTickCount(); + int32_t left = timer->end_time - now; + return (left < 0) ? 0 : left / portTICK_RATE_MS; +} + + +void InitTimer(Timer* timer) +{ + timer->end_time = 0; +} + + + +int mqtt_esp_read(Network* n, unsigned char* buffer, int len, int timeout_ms) +{ + struct timeval tv; + fd_set fdset; + int rc = 0; + int rcvd = 0; + FD_ZERO(&fdset); + FD_SET(n->my_socket, &fdset); + // It seems tv_sec actually means FreeRTOS tick + tv.tv_sec = timeout_ms / portTICK_RATE_MS; + tv.tv_usec = 0; + rc = select(n->my_socket + 1, &fdset, 0, 0, &tv); + if ((rc > 0) && (FD_ISSET(n->my_socket, &fdset))) + { + rcvd = recv(n->my_socket, buffer, len, 0); + } + else + { + // select fail + return -1; + } + return rcvd; +} + + +int mqtt_esp_write(Network* n, unsigned char* buffer, int len, int timeout_ms) +{ + struct timeval tv; + fd_set fdset; + int rc = 0; + + FD_ZERO(&fdset); + FD_SET(n->my_socket, &fdset); + // It seems tv_sec actually means FreeRTOS tick + tv.tv_sec = timeout_ms / portTICK_RATE_MS; + tv.tv_usec = 0; + rc = select(n->my_socket + 1, 0, &fdset, 0, &tv); + if ((rc > 0) && (FD_ISSET(n->my_socket, &fdset))) + { + rc = send(n->my_socket, buffer, len, 0); + } + else + { + // select fail + return -1; + } + return rc; +} + + + +void NewNetwork(Network* n) +{ + n->my_socket = -1; + n->mqttread = mqtt_esp_read; + n->mqttwrite = mqtt_esp_write; +} + +static int host2addr(const char *hostname , struct in_addr *in) +{ + struct addrinfo hints, *servinfo, *p; + struct sockaddr_in *h; + int rv; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + rv = getaddrinfo(hostname, 0 , &hints , &servinfo); + if (rv != 0) + { + return rv; + } + + // loop through all the results and get the first resolve + for (p = servinfo; p != 0; p = p->ai_next) + { + h = (struct sockaddr_in *)p->ai_addr; + in->s_addr = h->sin_addr.s_addr; + } + freeaddrinfo(servinfo); // all done with this structure + return 0; +} + + +int ConnectNetwork(Network* n, const char* host, int port) +{ + struct sockaddr_in addr; + int ret; + + if (host2addr(host, &(addr.sin_addr)) != 0) + { + return -1; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + n->my_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if( n->my_socket < 0 ) + { + // error + return -1; + } + ret = connect(n->my_socket, ( struct sockaddr *)&addr, sizeof(struct sockaddr_in)); + if( ret < 0 ) + { + // error + close(n->my_socket); + return ret; + } + + return ret; +} + + +int DisconnectNetwork(Network* n) +{ + close(n->my_socket); + n->my_socket = -1; + return 0; +} diff --git a/extras/paho_mqtt_c/MQTTESP8266.h b/extras/paho_mqtt_c/MQTTESP8266.h new file mode 100644 index 0000000..f29921e --- /dev/null +++ b/extras/paho_mqtt_c/MQTTESP8266.h @@ -0,0 +1,59 @@ +/** + ****************************************************************************** + * @file MQTTESP8266.h + * @author Baoshi + * @version 0.1 + * @date Sep 9, 2015 + * @brief Eclipse Paho ported to ESP8266 RTOS + * + ****************************************************************************** + * @copyright + * + * Copyright (c) 2015, Baoshi Zhu. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE.txt file. + * + * THIS SOFTWARE IS PROVIDED 'AS-IS', WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN NO EVENT WILL THE AUTHOR(S) BE HELD LIABLE FOR ANY DAMAGES + * ARISING FROM THE USE OF THIS SOFTWARE. + * + */ +#ifndef _MQTT_ESP8266_H_ +#define _MQTT_ESP8266_H_ + +#include +#include + +typedef struct Timer Timer; + +struct Timer +{ + portTickType end_time; +}; + +typedef struct Network Network; + +struct Network +{ + int my_socket; + int (*mqttread) (Network*, unsigned char*, int, int); + int (*mqttwrite) (Network*, unsigned char*, int, int); +}; + +char expired(Timer*); +void countdown_ms(Timer*, unsigned int); +void countdown(Timer*, unsigned int); +int left_ms(Timer*); + +void InitTimer(Timer*); + +int mqtt_esp_read(Network*, unsigned char*, int, int); +int mqtt_esp_write(Network*, unsigned char*, int, int); +void mqtt_esp_disconnect(Network*); + +void NewNetwork(Network* n); +int ConnectNetwork(Network* n, const char* host, int port); +int DisconnectNetwork(Network* n); + + +#endif /* _MQTT_ESP8266_H_ */ diff --git a/extras/paho_mqtt_c/MQTTFormat.h b/extras/paho_mqtt_c/MQTTFormat.h new file mode 100644 index 0000000..47b0c41 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTFormat.h @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#if !defined(MQTTFORMAT_H) +#define MQTTFORMAT_H + +#include "StackTrace.h" +#include "MQTTPacket.h" + +const char* MQTTPacket_getName(unsigned short packetid); +int MQTTStringFormat_connect(char* strbuf, int strbuflen, MQTTPacket_connectData* data); +int MQTTStringFormat_connack(char* strbuf, int strbuflen, unsigned char connack_rc, unsigned char sessionPresent); +int MQTTStringFormat_publish(char* strbuf, int strbuflen, unsigned char dup, int qos, unsigned char retained, + unsigned short packetid, MQTTString topicName, unsigned char* payload, int payloadlen); +int MQTTStringFormat_ack(char* strbuf, int strbuflen, unsigned char packettype, unsigned char dup, unsigned short packetid); +int MQTTStringFormat_subscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, int count, + MQTTString topicFilters[], int requestedQoSs[]); +int MQTTStringFormat_suback(char* strbuf, int strbuflen, unsigned short packetid, int count, int* grantedQoSs); +int MQTTStringFormat_unsubscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]); +char* MQTTFormat_toClientString(char* strbuf, int strbuflen, unsigned char* buf, int buflen); +char* MQTTFormat_toServerString(char* strbuf, int strbuflen, unsigned char* buf, int buflen); + +#endif diff --git a/extras/paho_mqtt_c/MQTTPacket.c b/extras/paho_mqtt_c/MQTTPacket.c new file mode 100644 index 0000000..eb36a50 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTPacket.c @@ -0,0 +1,409 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Sergio R. Caprile - non-blocking packet read functions for stream transport + *******************************************************************************/ +#include +#include "StackTrace.h" +#include "MQTTPacket.h" +#include + +/** + * Encodes the message length according to the MQTT algorithm + * @param buf the buffer into which the encoded data is written + * @param length the length to be encoded + * @return the number of bytes written to buffer + */ +int MQTTPacket_encode(unsigned char* buf, int length) +{ + int rc = 0; + + FUNC_ENTRY; + do + { + char d = length % 128; + length /= 128; + /* if there are more digits to encode, set the top bit of this digit */ + if (length > 0) + d |= 0x80; + buf[rc++] = d; + } while (length > 0); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Decodes the message length according to the MQTT algorithm + * @param getcharfn pointer to function to read the next character from the data source + * @param value the decoded length returned + * @return the number of bytes read from the socket + */ +int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value) +{ + unsigned char c; + int multiplier = 1; + int len = 0; +#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4 + + FUNC_ENTRY; + *value = 0; + do + { + int rc = MQTTPACKET_READ_ERROR; + + if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES) + { + rc = MQTTPACKET_READ_ERROR; /* bad data */ + goto exit; + } + rc = (*getcharfn)(&c, 1); + if (rc != 1) + goto exit; + *value += (c & 127) * multiplier; + multiplier *= 128; + } while ((c & 128) != 0); +exit: + FUNC_EXIT_RC(len); + return len; +} + + +int MQTTPacket_len(int rem_len) +{ + rem_len += 1; /* header byte */ + + /* now remaining_length field */ + if (rem_len < 128) + rem_len += 1; + else if (rem_len < 16384) + rem_len += 2; + else if (rem_len < 2097151) + rem_len += 3; + else + rem_len += 4; + return rem_len; +} + + +static unsigned char* bufptr; + +int bufchar(unsigned char* c, int count) +{ + int i; + + for (i = 0; i < count; ++i) + *c = *bufptr++; + return count; +} + + +int MQTTPacket_decodeBuf(unsigned char* buf, int* value) +{ + bufptr = buf; + return MQTTPacket_decode(bufchar, value); +} + + +/** + * Calculates an integer from two bytes read from the input buffer + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @return the integer value calculated + */ +int readInt(unsigned char** pptr) +{ + unsigned char* ptr = *pptr; + int len = 256*(*ptr) + (*(ptr+1)); + *pptr += 2; + return len; +} + + +/** + * Reads one character from the input buffer. + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @return the character read + */ +char readChar(unsigned char** pptr) +{ + char c = **pptr; + (*pptr)++; + return c; +} + + +/** + * Writes one character to an output buffer. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param c the character to write + */ +void writeChar(unsigned char** pptr, char c) +{ + **pptr = c; + (*pptr)++; +} + + +/** + * Writes an integer as 2 bytes to an output buffer. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param anInt the integer to write + */ +void writeInt(unsigned char** pptr, int anInt) +{ + **pptr = (unsigned char)(anInt / 256); + (*pptr)++; + **pptr = (unsigned char)(anInt % 256); + (*pptr)++; +} + + +/** + * Writes a "UTF" string to an output buffer. Converts C string to length-delimited. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param string the C string to write + */ +void writeCString(unsigned char** pptr, const char* string) +{ + int len = strlen(string); + writeInt(pptr, len); + memcpy(*pptr, string, len); + *pptr += len; +} + + +int getLenStringLen(char* ptr) +{ + int len = 256*((unsigned char)(*ptr)) + (unsigned char)(*(ptr+1)); + return len; +} + + +void writeMQTTString(unsigned char** pptr, MQTTString mqttstring) +{ + if (mqttstring.lenstring.len > 0) + { + writeInt(pptr, mqttstring.lenstring.len); + memcpy(*pptr, mqttstring.lenstring.data, mqttstring.lenstring.len); + *pptr += mqttstring.lenstring.len; + } + else if (mqttstring.cstring) + writeCString(pptr, mqttstring.cstring); + else + writeInt(pptr, 0); +} + + +/** + * @param mqttstring the MQTTString structure into which the data is to be read + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param enddata pointer to the end of the data: do not read beyond + * @return 1 if successful, 0 if not + */ +int readMQTTLenString(MQTTString* mqttstring, unsigned char** pptr, unsigned char* enddata) +{ + int rc = 0; + + FUNC_ENTRY; + /* the first two bytes are the length of the string */ + if (enddata - (*pptr) > 1) /* enough length to read the integer? */ + { + mqttstring->lenstring.len = readInt(pptr); /* increments pptr to point past length */ + if (&(*pptr)[mqttstring->lenstring.len] <= enddata) + { + mqttstring->lenstring.data = (char*)*pptr; + *pptr += mqttstring->lenstring.len; + rc = 1; + } + } + mqttstring->cstring = NULL; + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Return the length of the MQTTstring - C string if there is one, otherwise the length delimited string + * @param mqttstring the string to return the length of + * @return the length of the string + */ +int MQTTstrlen(MQTTString mqttstring) +{ + int rc = 0; + + if (mqttstring.cstring) + rc = strlen(mqttstring.cstring); + else + rc = mqttstring.lenstring.len; + return rc; +} + + +/** + * Compares an MQTTString to a C string + * @param a the MQTTString to compare + * @param bptr the C string to compare + * @return boolean - equal or not + */ +int MQTTPacket_equals(MQTTString* a, char* bptr) +{ + int alen = 0, + blen = 0; + char *aptr; + + if (a->cstring) + { + aptr = a->cstring; + alen = strlen(a->cstring); + } + else + { + aptr = a->lenstring.data; + alen = a->lenstring.len; + } + blen = strlen(bptr); + + return (alen == blen) && (strncmp(aptr, bptr, alen) == 0); +} + + +/** + * Helper function to read packet data from some source into a buffer + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param getfn pointer to a function which will read any number of bytes from the needed source + * @return integer MQTT packet type, or -1 on error + * @note the whole message must fit into the caller's buffer + */ +int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int)) +{ + int rc = -1; + MQTTHeader header = {0}; + int len = 0; + int rem_len = 0; + + /* 1. read the header byte. This has the packet type in it */ + if ((*getfn)(buf, 1) != 1) + goto exit; + + len = 1; + /* 2. read the remaining length. This is variable in itself */ + MQTTPacket_decode(getfn, &rem_len); + len += MQTTPacket_encode(buf + 1, rem_len); /* put the original remaining length back into the buffer */ + + /* 3. read the rest of the buffer using a callback to supply the rest of the data */ + if((rem_len + len) > buflen) + goto exit; + if ((*getfn)(buf + len, rem_len) != rem_len) + goto exit; + + header.byte = buf[0]; + rc = header.bits.type; +exit: + return rc; +} + +/** + * Decodes the message length according to the MQTT algorithm, non-blocking + * @param trp pointer to a transport structure holding what is needed to solve getting data from it + * @param value the decoded length returned + * @return integer the number of bytes read from the socket, 0 for call again, or -1 on error + */ +static int MQTTPacket_decodenb(MQTTTransport *trp) +{ + unsigned char c; + int rc = MQTTPACKET_READ_ERROR; + + FUNC_ENTRY; + if(trp->len == 0){ /* initialize on first call */ + trp->multiplier = 1; + trp->rem_len = 0; + } + do { + int frc; + if (++(trp->len) > MAX_NO_OF_REMAINING_LENGTH_BYTES) + goto exit; + if ((frc=(*trp->getfn)(trp->sck, &c, 1)) == -1) + goto exit; + if (frc == 0){ + rc = 0; + goto exit; + } + trp->rem_len += (c & 127) * trp->multiplier; + trp->multiplier *= 128; + } while ((c & 128) != 0); + rc = trp->len; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + +/** + * Helper function to read packet data from some source into a buffer, non-blocking + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param trp pointer to a transport structure holding what is needed to solve getting data from it + * @return integer MQTT packet type, 0 for call again, or -1 on error + * @note the whole message must fit into the caller's buffer + */ +int MQTTPacket_readnb(unsigned char* buf, int buflen, MQTTTransport *trp) +{ + int rc = -1, frc; + MQTTHeader header = {0}; + + switch(trp->state){ + default: + trp->state = 0; + /*FALLTHROUGH*/ + case 0: + /* read the header byte. This has the packet type in it */ + if ((frc=(*trp->getfn)(trp->sck, buf, 1)) == -1) + goto exit; + if (frc == 0) + return 0; + trp->len = 0; + ++trp->state; + /*FALLTHROUGH*/ + /* read the remaining length. This is variable in itself */ + case 1: + if((frc=MQTTPacket_decodenb(trp)) == MQTTPACKET_READ_ERROR) + goto exit; + if(frc == 0) + return 0; + trp->len = 1 + MQTTPacket_encode(buf + 1, trp->rem_len); /* put the original remaining length back into the buffer */ + if((trp->rem_len + trp->len) > buflen) + goto exit; + ++trp->state; + /*FALLTHROUGH*/ + case 2: + /* read the rest of the buffer using a callback to supply the rest of the data */ + if ((frc=(*trp->getfn)(trp->sck, buf + trp->len, trp->rem_len)) == -1) + goto exit; + if (frc == 0) + return 0; + trp->rem_len -= frc; + trp->len += frc; + if(trp->rem_len) + return 0; + + header.byte = buf[0]; + rc = header.bits.type; + break; + } + +exit: + trp->state = 0; + return rc; +} + diff --git a/extras/paho_mqtt_c/MQTTPacket.h b/extras/paho_mqtt_c/MQTTPacket.h new file mode 100644 index 0000000..c7909d9 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTPacket.h @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTPACKET_H_ +#define MQTTPACKET_H_ + +#if defined(__cplusplus) /* If this is a C++ compiler, use C linkage */ +extern "C" { +#endif + +#if defined(WIN32_DLL) || defined(WIN64_DLL) + #define DLLImport __declspec(dllimport) + #define DLLExport __declspec(dllexport) +#elif defined(LINUX_SO) + #define DLLImport extern + #define DLLExport __attribute__ ((visibility ("default"))) +#else + #define DLLImport + #define DLLExport +#endif + +enum errors +{ + MQTTPACKET_BUFFER_TOO_SHORT = -2, + MQTTPACKET_READ_ERROR = -1, + MQTTPACKET_READ_COMPLETE +}; + +enum msgTypes +{ + CONNECT = 1, CONNACK, PUBLISH, PUBACK, PUBREC, PUBREL, + PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK, + PINGREQ, PINGRESP, DISCONNECT +}; + +/** + * Bitfields for the MQTT header byte. + */ +typedef union +{ + unsigned char byte; /**< the whole byte */ +#if defined(REVERSED) + struct + { + unsigned int type : 4; /**< message type nibble */ + unsigned int dup : 1; /**< DUP flag bit */ + unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */ + unsigned int retain : 1; /**< retained flag bit */ + } bits; +#else + struct + { + unsigned int retain : 1; /**< retained flag bit */ + unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */ + unsigned int dup : 1; /**< DUP flag bit */ + unsigned int type : 4; /**< message type nibble */ + } bits; +#endif +} MQTTHeader; + +typedef struct +{ + int len; + char* data; +} MQTTLenString; + +typedef struct +{ + char* cstring; + MQTTLenString lenstring; +} MQTTString; + +#define MQTTString_initializer {NULL, {0, NULL}} + +int MQTTstrlen(MQTTString mqttstring); + +#include "MQTTConnect.h" +#include "MQTTPublish.h" +#include "MQTTSubscribe.h" +#include "MQTTUnsubscribe.h" +#include "MQTTFormat.h" + +int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char type, unsigned char dup, unsigned short packetid); +int MQTTDeserialize_ack(unsigned char* packettype, unsigned char* dup, unsigned short* packetid, unsigned char* buf, int buflen); + +int MQTTPacket_len(int rem_len); +int MQTTPacket_equals(MQTTString* a, char* b); + +int MQTTPacket_encode(unsigned char* buf, int length); +int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value); +int MQTTPacket_decodeBuf(unsigned char* buf, int* value); + +int readInt(unsigned char** pptr); +char readChar(unsigned char** pptr); +void writeChar(unsigned char** pptr, char c); +void writeInt(unsigned char** pptr, int anInt); +int readMQTTLenString(MQTTString* mqttstring, unsigned char** pptr, unsigned char* enddata); +void writeCString(unsigned char** pptr, const char* string); +void writeMQTTString(unsigned char** pptr, MQTTString mqttstring); + +DLLExport int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int)); + +typedef struct { + int (*getfn)(void *, unsigned char*, int); /* must return -1 for error, 0 for call again, or the number of bytes read */ + void *sck; /* pointer to whatever the system may use to identify the transport */ + int multiplier; + int rem_len; + int len; + char state; +}MQTTTransport; + +int MQTTPacket_readnb(unsigned char* buf, int buflen, MQTTTransport *trp); + +#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */ +} +#endif + + +#endif /* MQTTPACKET_H_ */ diff --git a/extras/paho_mqtt_c/MQTTPublish.h b/extras/paho_mqtt_c/MQTTPublish.h new file mode 100644 index 0000000..ffd8752 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTPublish.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTPUBLISH_H_ +#define MQTTPUBLISH_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid, + MQTTString topicName, unsigned char* payload, int payloadlen); + +DLLExport int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName, + unsigned char** payload, int* payloadlen, unsigned char* buf, int len); + +DLLExport int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid); +DLLExport int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid); +DLLExport int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid); + +#endif /* MQTTPUBLISH_H_ */ diff --git a/extras/paho_mqtt_c/MQTTSerializePublish.c b/extras/paho_mqtt_c/MQTTSerializePublish.c new file mode 100644 index 0000000..c3343c8 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTSerializePublish.c @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=453144 + *******************************************************************************/ +#include +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + + +/** + * Determines the length of the MQTT publish packet that would be produced using the supplied parameters + * @param qos the MQTT QoS of the publish (packetid is omitted for QoS 0) + * @param topicName the topic name to be used in the publish + * @param payloadlen the length of the payload to be sent + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_publishLength(int qos, MQTTString topicName, int payloadlen) +{ + int len = 0; + + len += 2 + MQTTstrlen(topicName) + payloadlen; + if (qos > 0) + len += 2; /* packetid */ + return len; +} + + +/** + * Serializes the supplied publish data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param dup integer - the MQTT dup flag + * @param qos integer - the MQTT QoS value + * @param retained integer - the MQTT retained flag + * @param packetid integer - the MQTT packet identifier + * @param topicName MQTTString - the MQTT topic in the publish + * @param payload byte buffer - the MQTT publish payload + * @param payloadlen integer - the length of the MQTT payload + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid, + MQTTString topicName, unsigned char* payload, int payloadlen) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_publishLength(qos, topicName, payloadlen)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.bits.type = PUBLISH; + header.bits.dup = dup; + header.bits.qos = qos; + header.bits.retain = retained; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeMQTTString(&ptr, topicName); + + if (qos > 0) + writeInt(&ptr, packetid); + + memcpy(ptr, payload, payloadlen); + ptr += payloadlen; + + rc = ptr - buf; + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Serializes the ack packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param type the MQTT packet type + * @param dup the MQTT dup flag + * @param packetid the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char packettype, unsigned char dup, unsigned short packetid) +{ + MQTTHeader header = {0}; + int rc = 0; + unsigned char *ptr = buf; + + FUNC_ENTRY; + if (buflen < 4) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.bits.type = packettype; + header.bits.dup = dup; + header.bits.qos = (packettype == PUBREL) ? 1 : 0; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */ + writeInt(&ptr, packetid); + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a puback packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBACK, 0, packetid); +} + + +/** + * Serializes a pubrel packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBREL, dup, packetid); +} + + +/** + * Serializes a pubrel packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBCOMP, 0, packetid); +} + + diff --git a/extras/paho_mqtt_c/MQTTSubscribe.h b/extras/paho_mqtt_c/MQTTSubscribe.h new file mode 100644 index 0000000..9b8511d --- /dev/null +++ b/extras/paho_mqtt_c/MQTTSubscribe.h @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTSUBSCRIBE_H_ +#define MQTTSUBSCRIBE_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[], int requestedQoSs[]); + +DLLExport int MQTTDeserialize_subscribe(unsigned char* dup, unsigned short* packetid, + int maxcount, int* count, MQTTString topicFilters[], int requestedQoSs[], unsigned char* buf, int len); + +DLLExport int MQTTSerialize_suback(unsigned char* buf, int buflen, unsigned short packetid, int count, int* grantedQoSs); + +DLLExport int MQTTDeserialize_suback(unsigned short* packetid, int maxcount, int* count, int grantedQoSs[], unsigned char* buf, int len); + + +#endif /* MQTTSUBSCRIBE_H_ */ diff --git a/extras/paho_mqtt_c/MQTTSubscribeClient.c b/extras/paho_mqtt_c/MQTTSubscribeClient.c new file mode 100644 index 0000000..a76a4d3 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTSubscribeClient.c @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ +#include +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + +/** + * Determines the length of the MQTT subscribe packet that would be produced using the supplied parameters + * @param count the number of topic filter strings in topicFilters + * @param topicFilters the array of topic filter strings to be used in the publish + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_subscribeLength(int count, MQTTString topicFilters[]) +{ + int i; + int len = 2; /* packetid */ + + for (i = 0; i < count; ++i) + len += 2 + MQTTstrlen(topicFilters[i]) + 1; /* length + topic + req_qos */ + return len; +} + + +/** + * Serializes the supplied subscribe data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied bufferr + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @param count - number of members in the topicFilters and reqQos arrays + * @param topicFilters - array of topic filter names + * @param requestedQoSs - array of requested QoS + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, int count, + MQTTString topicFilters[], int requestedQoSs[]) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = 0; + int i = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_subscribeLength(count, topicFilters)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = SUBSCRIBE; + header.bits.dup = dup; + header.bits.qos = 1; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeInt(&ptr, packetid); + + for (i = 0; i < count; ++i) + { + writeMQTTString(&ptr, topicFilters[i]); + writeChar(&ptr, requestedQoSs[i]); + } + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Deserializes the supplied (wire) buffer into suback data + * @param packetid returned integer - the MQTT packet identifier + * @param maxcount - the maximum number of members allowed in the grantedQoSs array + * @param count returned integer - number of members in the grantedQoSs array + * @param grantedQoSs returned array of integers - the granted qualities of service + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_suback(unsigned short* packetid, int maxcount, int* count, int grantedQoSs[], unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != SUBACK) + goto exit; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + if (enddata - curdata < 2) + goto exit; + + *packetid = readInt(&curdata); + + *count = 0; + while (curdata < enddata) + { + if (*count > maxcount) + { + rc = -1; + goto exit; + } + grantedQoSs[(*count)++] = readChar(&curdata); + } + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/extras/paho_mqtt_c/MQTTUnsubscribe.h b/extras/paho_mqtt_c/MQTTUnsubscribe.h new file mode 100644 index 0000000..2f8e829 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTUnsubscribe.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTUNSUBSCRIBE_H_ +#define MQTTUNSUBSCRIBE_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]); + +DLLExport int MQTTDeserialize_unsubscribe(unsigned char* dup, unsigned short* packetid, int max_count, int* count, MQTTString topicFilters[], + unsigned char* buf, int len); + +DLLExport int MQTTSerialize_unsuback(unsigned char* buf, int buflen, unsigned short packetid); + +DLLExport int MQTTDeserialize_unsuback(unsigned short* packetid, unsigned char* buf, int len); + +#endif /* MQTTUNSUBSCRIBE_H_ */ diff --git a/extras/paho_mqtt_c/MQTTUnsubscribeClient.c b/extras/paho_mqtt_c/MQTTUnsubscribeClient.c new file mode 100644 index 0000000..40b4733 --- /dev/null +++ b/extras/paho_mqtt_c/MQTTUnsubscribeClient.c @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ +#include +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + +/** + * Determines the length of the MQTT unsubscribe packet that would be produced using the supplied parameters + * @param count the number of topic filter strings in topicFilters + * @param topicFilters the array of topic filter strings to be used in the publish + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_unsubscribeLength(int count, MQTTString topicFilters[]) +{ + int i; + int len = 2; /* packetid */ + + for (i = 0; i < count; ++i) + len += 2 + MQTTstrlen(topicFilters[i]); /* length + topic*/ + return len; +} + + +/** + * Serializes the supplied unsubscribe data into the supplied buffer, ready for sending + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @param count - number of members in the topicFilters array + * @param topicFilters - array of topic filter names + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = -1; + int i = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_unsubscribeLength(count, topicFilters)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = UNSUBSCRIBE; + header.bits.dup = dup; + header.bits.qos = 1; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeInt(&ptr, packetid); + + for (i = 0; i < count; ++i) + writeMQTTString(&ptr, topicFilters[i]); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Deserializes the supplied (wire) buffer into unsuback data + * @param packetid returned integer - the MQTT packet identifier + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_unsuback(unsigned short* packetid, unsigned char* buf, int buflen) +{ + unsigned char type = 0; + unsigned char dup = 0; + int rc = 0; + + FUNC_ENTRY; + rc = MQTTDeserialize_ack(&type, &dup, packetid, buf, buflen); + if (type == UNSUBACK) + rc = 1; + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/extras/paho_mqtt_c/StackTrace.h b/extras/paho_mqtt_c/StackTrace.h new file mode 100644 index 0000000..517d437 --- /dev/null +++ b/extras/paho_mqtt_c/StackTrace.h @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - fix for bug #434081 + *******************************************************************************/ + +#ifndef STACKTRACE_H_ +#define STACKTRACE_H_ + +#define NOSTACKTRACE 1 + +#if defined(NOSTACKTRACE) +#define FUNC_ENTRY +#define FUNC_ENTRY_NOLOG +#define FUNC_ENTRY_MED +#define FUNC_ENTRY_MAX +#define FUNC_EXIT +#define FUNC_EXIT_NOLOG +#define FUNC_EXIT_MED +#define FUNC_EXIT_MAX +#define FUNC_EXIT_RC(x) +#define FUNC_EXIT_MED_RC(x) +#define FUNC_EXIT_MAX_RC(x) + +#else + +#if defined(WIN32) +#define inline __inline +#define FUNC_ENTRY StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MINIMUM) +#define FUNC_ENTRY_NOLOG StackTrace_entry(__FUNCTION__, __LINE__, -1) +#define FUNC_ENTRY_MED StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MEDIUM) +#define FUNC_ENTRY_MAX StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MAXIMUM) +#define FUNC_EXIT StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MINIMUM) +#define FUNC_EXIT_NOLOG StackTrace_exit(__FUNCTION__, __LINE__, -1) +#define FUNC_EXIT_MED StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MEDIUM) +#define FUNC_EXIT_MAX StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MAXIMUM) +#define FUNC_EXIT_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MINIMUM) +#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MEDIUM) +#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MAXIMUM) +#else +#define FUNC_ENTRY StackTrace_entry(__func__, __LINE__, TRACE_MINIMUM) +#define FUNC_ENTRY_NOLOG StackTrace_entry(__func__, __LINE__, -1) +#define FUNC_ENTRY_MED StackTrace_entry(__func__, __LINE__, TRACE_MEDIUM) +#define FUNC_ENTRY_MAX StackTrace_entry(__func__, __LINE__, TRACE_MAXIMUM) +#define FUNC_EXIT StackTrace_exit(__func__, __LINE__, NULL, TRACE_MINIMUM) +#define FUNC_EXIT_NOLOG StackTrace_exit(__func__, __LINE__, NULL, -1) +#define FUNC_EXIT_MED StackTrace_exit(__func__, __LINE__, NULL, TRACE_MEDIUM) +#define FUNC_EXIT_MAX StackTrace_exit(__func__, __LINE__, NULL, TRACE_MAXIMUM) +#define FUNC_EXIT_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MINIMUM) +#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MEDIUM) +#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MAXIMUM) + +void StackTrace_entry(const char* name, int line, int trace); +void StackTrace_exit(const char* name, int line, void* return_value, int trace); + +void StackTrace_printStack(FILE* dest); +char* StackTrace_get(unsigned long); + +#endif + +#endif + + + + +#endif /* STACKTRACE_H_ */ diff --git a/extras/paho_mqtt_c/component.mk b/extras/paho_mqtt_c/component.mk new file mode 100644 index 0000000..6ecf410 --- /dev/null +++ b/extras/paho_mqtt_c/component.mk @@ -0,0 +1,9 @@ +# Component makefile for extras/paho_mqtt_c + +# expected anyone using bmp driver includes it as 'paho_mqtt_c/MQTT*.h' +INC_DIRS += $(paho_mqtt_c_ROOT).. + +# args for passing into compile rule generation +paho_mqtt_c_SRC_DIR = $(paho_mqtt_c_ROOT) + +$(eval $(call component_compile_rules,paho_mqtt_c)) diff --git a/extras/rboot-ota/ota-tftp.c b/extras/rboot-ota/ota-tftp.c index f8140f6..d772927 100644 --- a/extras/rboot-ota/ota-tftp.c +++ b/extras/rboot-ota/ota-tftp.c @@ -22,7 +22,8 @@ #include #include "ota-tftp.h" -#include "rboot-ota.h" +#include "rboot.h" +#include "rboot-api.h" #define TFTP_FIRMWARE_FILE "firmware.bin" #define TFTP_OCTET_MODE "octet" /* non-case-sensitive */ @@ -112,7 +113,7 @@ static void tftp_task(void *listen_port) netbuf_delete(netbuf); /* Find next free slot - this requires flash unmapping so best done when no packets in flight */ - rboot_config_t conf; + rboot_config conf; conf = rboot_get_config(); int slot = (conf.current_rom + 1) % conf.count; diff --git a/extras/rboot-ota/rboot-api.c b/extras/rboot-ota/rboot-api.c new file mode 100644 index 0000000..7fe5340 --- /dev/null +++ b/extras/rboot-ota/rboot-api.c @@ -0,0 +1,214 @@ +////////////////////////////////////////////////// +// rBoot OTA and config API for ESP8266. +// Copyright 2015 Richard A Burton +// richardaburton@gmail.com +// See license.txt for license terms. +// OTA code based on SDK sample from Espressif. +////////////////////////////////////////////////// + +#include +#include +//#include +//#include + +// detect rtos sdk (not ideal method!) +#ifdef IRAM_ATTR +#define os_free(s) vPortFree(s) +#define os_malloc(s) pvPortMalloc(s) +#else +#include +#endif + +#ifdef RBOOT_INTEGRATION +#include +#endif + +#include "rboot-api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(BOOT_CONFIG_CHKSUM) || defined(BOOT_RTC_ENABLED) +// calculate checksum for block of data +// from start up to (but excluding) end +static uint8 calc_chksum(uint8 *start, uint8 *end) { + uint8 chksum = CHKSUM_INIT; + while(start < end) { + chksum ^= *start; + start++; + } + return chksum; +} +#endif + +// get the rboot config +rboot_config ICACHE_FLASH_ATTR rboot_get_config(void) { + rboot_config conf; + spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32*)&conf, sizeof(rboot_config)); + return conf; +} + +// write the rboot config +// preserves the contents of the rest of the sector, +// so the rest of the sector can be used to store user data +// updates checksum automatically (if enabled) +bool ICACHE_FLASH_ATTR rboot_set_config(rboot_config *conf) { + uint8 *buffer; + buffer = (uint8*)os_malloc(SECTOR_SIZE); + if (!buffer) { + //os_printf("No ram!\r\n"); + return false; + } + +#ifdef BOOT_CONFIG_CHKSUM + conf->chksum = calc_chksum((uint8*)conf, (uint8*)&conf->chksum); +#endif + + spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32*)((void*)buffer), SECTOR_SIZE); + memcpy(buffer, conf, sizeof(rboot_config)); + vPortEnterCritical(); + spi_flash_erase_sector(BOOT_CONFIG_SECTOR); + vPortExitCritical(); + taskYIELD(); + vPortEnterCritical(); + //spi_flash_write(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32*)((void*)buffer), SECTOR_SIZE); + spi_flash_write(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32*)((void*)buffer), SECTOR_SIZE); + vPortExitCritical(); + os_free(buffer); + return true; +} + +// get current boot rom +uint8 ICACHE_FLASH_ATTR rboot_get_current_rom(void) { + rboot_config conf; + conf = rboot_get_config(); + return conf.current_rom; +} + +// set current boot rom +bool ICACHE_FLASH_ATTR rboot_set_current_rom(uint8 rom) { + rboot_config conf; + conf = rboot_get_config(); + if (rom >= conf.count) return false; + conf.current_rom = rom; + return rboot_set_config(&conf); +} + +// create the write status struct, based on supplied start address +rboot_write_status ICACHE_FLASH_ATTR rboot_write_init(uint32 start_addr) { + rboot_write_status status = {0}; + status.start_addr = start_addr; + status.start_sector = start_addr / SECTOR_SIZE; + status.last_sector_erased = status.start_sector - 1; + //status.max_sector_count = 200; + //os_printf("init addr: 0x%08x\r\n", start_addr); + + return status; +} + +// function to do the actual writing to flash +// call repeatedly with more data (max len per write is the flash sector size (4k)) +bool ICACHE_FLASH_ATTR rboot_write_flash(rboot_write_status *status, uint8 *data, uint16 len) { + + bool ret = false; + uint8 *buffer; + int32 lastsect; + + if (data == NULL || len == 0) { + return true; + } + + // get a buffer + buffer = (uint8 *)os_malloc(len + status->extra_count); + if (!buffer) { + //os_printf("No ram!\r\n"); + return false; + } + + // copy in any remaining bytes from last chunk + memcpy(buffer, status->extra_bytes, status->extra_count); + // copy in new data + memcpy(buffer + status->extra_count, data, len); + + // calculate length, must be multiple of 4 + // save any remaining bytes for next go + len += status->extra_count; + status->extra_count = len % 4; + len -= status->extra_count; + memcpy(status->extra_bytes, buffer + len, status->extra_count); + + // check data will fit + //if (status->start_addr + len < (status->start_sector + status->max_sector_count) * SECTOR_SIZE) { + + // erase any additional sectors needed by this chunk + lastsect = ((status->start_addr + len) - 1) / SECTOR_SIZE; + while (lastsect > status->last_sector_erased) { + status->last_sector_erased++; + spi_flash_erase_sector(status->last_sector_erased); + } + + // write current chunk + //os_printf("write addr: 0x%08x, len: 0x%04x\r\n", status->start_addr, len); + if (spi_flash_write(status->start_addr, (uint32 *)((void*)buffer), len) == SPI_FLASH_RESULT_OK) { + ret = true; + status->start_addr += len; + } + //} + + os_free(buffer); + return ret; +} + +#ifdef BOOT_RTC_ENABLED +bool ICACHE_FLASH_ATTR rboot_get_rtc_data(rboot_rtc_data *rtc) { + if (system_rtc_mem_read(RBOOT_RTC_ADDR, rtc, sizeof(rboot_rtc_data))) { + return (rtc->chksum == calc_chksum((uint8*)rtc, (uint8*)&rtc->chksum)); + } + return false; +} + +bool ICACHE_FLASH_ATTR rboot_set_rtc_data(rboot_rtc_data *rtc) { + // calculate checksum + rtc->chksum = calc_chksum((uint8*)rtc, (uint8*)&rtc->chksum); + return system_rtc_mem_write(RBOOT_RTC_ADDR, rtc, sizeof(rboot_rtc_data)); +} + +bool ICACHE_FLASH_ATTR rboot_set_temp_rom(uint8 rom) { + rboot_rtc_data rtc; + // invalid data in rtc? + if (!rboot_get_rtc_data(&rtc)) { + // set basics + rtc.magic = RBOOT_RTC_MAGIC; + rtc.last_mode = MODE_STANDARD; + rtc.last_rom = 0; + } + // set next boot to temp mode with specified rom + rtc.next_mode = MODE_TEMP_ROM; + rtc.temp_rom = rom; + + return rboot_set_rtc_data(&rtc); +} + +bool ICACHE_FLASH_ATTR rboot_get_last_boot_rom(uint8 *rom) { + rboot_rtc_data rtc; + if (rboot_get_rtc_data(&rtc)) { + *rom = rtc.last_rom; + return true; + } + return false; +} + +bool ICACHE_FLASH_ATTR rboot_get_last_boot_mode(uint8 *mode) { + rboot_rtc_data rtc; + if (rboot_get_rtc_data(&rtc)) { + *mode = rtc.last_mode; + return true; + } + return false; +} +#endif + +#ifdef __cplusplus +} +#endif diff --git a/extras/rboot-ota/rboot-api.h b/extras/rboot-ota/rboot-api.h new file mode 100644 index 0000000..5a39b4d --- /dev/null +++ b/extras/rboot-ota/rboot-api.h @@ -0,0 +1,136 @@ +#ifndef __RBOOT_API_H__ +#define __RBOOT_API_H__ + +/** @defgroup rboot rBoot API + * @brief rBoot for ESP8266 API allows runtime code to access the rBoot configuration. + * Configuration may be read to use within the main firmware or updated to + * affect next boot behavior. + * @copyright 2015 Richard A Burton + * @author richardaburton@gmail.com + * @author OTA code based on SDK sample from Espressif + * @license See licence.txt for license terms. + * @{ +*/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Structure defining flash write status + * @note The user application should not modify the contents of this + * structure. + * @see rboot_write_flash +*/ +typedef struct { + uint32 start_addr; + uint32 start_sector; + //uint32 max_sector_count; + int32 last_sector_erased; + uint8 extra_count; + uint8 extra_bytes[4]; +} rboot_write_status; + +/** @brief Read rBoot configuration from flash + * @retval rboot_config Copy of the rBoot configuration + * @note Returns rboot_config (defined in rboot.h) allowing you to modify any values + * in it, including the ROM layout. +*/ +rboot_config ICACHE_FLASH_ATTR rboot_get_config(void); + +/** @brief Write rBoot configuration to flash memory + * @param conf pointer to a rboot_config structure containing configuration to save + * @retval bool True on success + * @note Saves the rboot_config structure back to configuration sector (BOOT_CONFIG_SECTOR) + * of the flash, while maintaining the contents of the rest of the sector. + * You can use the rest of this sector for your app settings, as long as you + * protect this structure when you do so. +*/ +bool ICACHE_FLASH_ATTR rboot_set_config(rboot_config *conf); + +/** @brief Get index of current ROM + * @retval uint8 Index of the current ROM + * @note Get the currently selected boot ROM (this will be the currently + * running ROM, as long as you haven't changed it since boot or rBoot + * booted the rom in temporary boot mode, see rboot_get_last_boot_rom). +*/ +uint8 ICACHE_FLASH_ATTR rboot_get_current_rom(void); + +/** @brief Set the index of current ROM + * @param rom The index of the ROM to use on next boot + * @retval bool True on success + * @note Set the current boot ROM, which will be used when next restarted. + * @note This function re-writes the whole configuration to flash memory (not just the current ROM index) +*/ +bool ICACHE_FLASH_ATTR rboot_set_current_rom(uint8 rom); + +/** @brief Initialise flash write process + * @param start_addr Address on the SPI flash to begin write to + * @note Call once before starting to pass data to write to flash memory with rboot_write_flash function. + * start_addr is the address on the SPI flash to write from. Returns a status structure which + * must be passed back on each write. The contents of the structure should not + * be modified by the calling code. +*/ +rboot_write_status ICACHE_FLASH_ATTR rboot_write_init(uint32 start_addr); + +/** @brief Write data to flash memory + * @param status Pointer to rboot_write_status structure defining the write status + * @param data Pointer to a block of uint8 data elements to be written to flash + * @param len Quantity of uint8 data elements to write to flash + * @note Call repeatedly to write data to the flash, starting at the address + * specified on the prior call to rboot_write_init. Current write position is + * tracked automatically. This method is likely to be called each time a packet + * of OTA data is received over the network. + * @note Call rboot_write_init before calling this function to get the rboot_write_status structure +*/ +bool ICACHE_FLASH_ATTR rboot_write_flash(rboot_write_status *status, uint8 *data, uint16 len); + +#ifdef BOOT_RTC_ENABLED +/** @brief Get rBoot status/control data from RTC data area + * @param rtc Pointer to a rboot_rtc_data structure to be populated + * @retval bool True on success, false if no data/invalid checksum (in which + * case do not use the contents of the structure) +*/ +bool ICACHE_FLASH_ATTR rboot_get_rtc_data(rboot_rtc_data *rtc); + +/** @brief Set rBoot status/control data in RTC data area + * @param rtc pointer to a rboot_rtc_data structure + * @retval bool True on success + * @note The checksum will be calculated automatically for you. +*/ +bool ICACHE_FLASH_ATTR rboot_set_rtc_data(rboot_rtc_data *rtc); + +/** @brief Set temporary rom for next boot + * @param rom Rom slot number for next boot + * @retval bool True on success + * @note This call will tell rBoot to temporarily boot the specified rom on + * the next boot. This is does not update the stored rBoot config on + * the flash, so after another reset it will boot back to the original + * rom. +*/ +bool ICACHE_FLASH_ATTR rboot_set_temp_rom(uint8 rom); + +/** @brief Get the last booted rom slot number + * @param rom Pointer to rom slot number variable to populate + * @retval bool True on success, false if no data/invalid checksum + * @note This will find the currently running rom, even if booted as a + * temporary rom. +*/ +bool ICACHE_FLASH_ATTR rboot_get_last_boot_rom(uint8 *rom); + +/** @brief Get the last boot mode + * @param mode Pointer to mode variable to populate + * @retval bool True on success, false if no data/invalid checksum + * @note This will indicate the type of boot: MODE_STANDARD, MODE_GPIO_ROM or + * MODE_TEMP_ROM. +*/ +bool ICACHE_FLASH_ATTR rboot_get_last_boot_mode(uint8 *mode); +#endif + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif diff --git a/extras/rboot-ota/rboot-ota.c b/extras/rboot-ota/rboot-integration.c similarity index 69% rename from extras/rboot-ota/rboot-ota.c rename to extras/rboot-ota/rboot-integration.c index c8b27c4..266d566 100644 --- a/extras/rboot-ota/rboot-ota.c +++ b/extras/rboot-ota/rboot-integration.c @@ -10,17 +10,10 @@ #include #include -#include "rboot-ota.h" +#include #define ROM_MAGIC_OLD 0xe9 #define ROM_MAGIC_NEW 0xea -#define CHECKSUM_INIT 0xef - -#if 0 -#define RBOOT_DEBUG(f_, ...) printf((f_), __VA_ARGS__) -#else -#define RBOOT_DEBUG(f_, ...) -#endif typedef struct __attribute__((packed)) { uint8_t magic; @@ -35,67 +28,6 @@ typedef struct __attribute__((packed)) { } section_header_t; -// get the rboot config -rboot_config_t rboot_get_config() { - rboot_config_t conf; - sdk_spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)&conf, sizeof(rboot_config_t)); - return conf; -} - -// write the rboot config -// preserves contents of rest of sector, so rest -// of sector can be used to store user data -// updates checksum automatically, if enabled -bool rboot_set_config(rboot_config_t *conf) { - uint8_t *buffer; -#ifdef RBOOT_CONFIG_CHKSUM - uint8_t chksum; - uint8_t *ptr; -#endif - - buffer = (uint8_t*)malloc(SECTOR_SIZE); - if (!buffer) { - printf("rboot_set_config: Failed to allocate sector buffer\r\n"); - return false; - } - -#ifdef BOOT_CONFIG_CHKSUM - chksum = CHKSUM_INIT; - for (ptr = (uint8_t*)conf; ptr < &conf->chksum; ptr++) { - chksum ^= *ptr; - } - conf->chksum = chksum; -#endif - - sdk_spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)buffer, SECTOR_SIZE); - memcpy(buffer, conf, sizeof(rboot_config_t)); - vPortEnterCritical(); - sdk_spi_flash_erase_sector(BOOT_CONFIG_SECTOR); - vPortExitCritical(); - taskYIELD(); - vPortEnterCritical(); - sdk_spi_flash_write(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)buffer, SECTOR_SIZE); - vPortExitCritical(); - free(buffer); - return true; -} - -// get current boot rom -uint8_t rboot_get_current_rom() { - rboot_config_t conf; - conf = rboot_get_config(); - return conf.current_rom; -} - -// set current boot rom -bool rboot_set_current_rom(uint8_t rom) { - rboot_config_t conf; - conf = rboot_get_config(); - if (rom >= conf.count) return false; - conf.current_rom = rom; - return rboot_set_config(&conf); -} - // Check that a valid-looking rboot image is found at this offset on the flash, and // takes up 'expected_length' bytes. bool rboot_verify_image(uint32_t offset, uint32_t expected_length, const char **error_message) @@ -120,7 +52,7 @@ bool rboot_verify_image(uint32_t offset, uint32_t expected_length, const char ** int remaining_sections = image_header.section_count; - uint8_t checksum = CHECKSUM_INIT; + uint8_t checksum = CHKSUM_INIT; while(remaining_sections > 0 && offset < end_offset) { diff --git a/extras/rboot-ota/rboot-integration.h b/extras/rboot-ota/rboot-integration.h new file mode 100644 index 0000000..00c1bcb --- /dev/null +++ b/extras/rboot-ota/rboot-integration.h @@ -0,0 +1,34 @@ +// The rboot project provides this file for making rboot fit other projects + +#ifndef __RBOOT_INTEGRATION_H__ +#define __RBOOT_INTEGRATION_H__ +#include +#include +#include +#include +#include +#include + +#define uint8 uint8_t +#define uint16 uint16_t +#define uint32 uint32_t +#define int32 int32_t +#define ICACHE_FLASH_ATTR +#define spi_flash_read sdk_spi_flash_read +#define spi_flash_erase_sector sdk_spi_flash_erase_sector +#define spi_flash_write sdk_spi_flash_write +#define os_malloc malloc +#define os_free free +#define system_rtc_mem_read sdk_system_rtc_mem_read +#define system_rtc_mem_write sdk_system_rtc_mem_write + +#if 0 +#define RBOOT_DEBUG(f_, ...) printf((f_), __VA_ARGS__) +#else +#define RBOOT_DEBUG(f_, ...) +#endif + +// Check that a valid-looking rboot image is found at this offset on the flash, and +// takes up 'expected_length' bytes. +bool rboot_verify_image(uint32_t offset, uint32_t expected_length, const char **error_message); +#endif // __RBOOT_INTEGRATION_H__ diff --git a/extras/rboot-ota/rboot-ota.h b/extras/rboot-ota/rboot-ota.h deleted file mode 100644 index 5586a30..0000000 --- a/extras/rboot-ota/rboot-ota.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef __RBOOT_OTA_H__ -#define __RBOOT_OTA_H__ -/* rboot-ota client API - * - * Ported from https://github.com/raburton/esp8266/ to esp-open-rtos - * - * BSD Licensed as per the file LICENSE in the top-level directory. - * Copyright (c) 2015 Richard A Burton & SuperHouse Pty Ltd - */ -#include -#include -#include - -/* rboot config block structure (stored in flash offset 0x1000) - * - * Structure taken from rboot.h revision a4724ede22 - * The version of rboot you're using has to match this structure - */ -typedef struct { - uint8_t magic; // our magic - uint8_t version; // config struct version - uint8_t mode; // boot loader mode - uint8_t current_rom; // currently selected rom - uint8_t gpio_rom; // rom to use for gpio boot - uint8_t count; // number of roms in use - uint8_t unused[2]; // padding - uint32_t roms[RBOOT_MAX_ROMS]; // flash addresses of the roms -#ifdef RBOOT_CONFIG_CHKSUM - uint8_t chksum; // config chksum -#endif -} rboot_config_t; - - -#define SECTOR_SIZE 0x1000 -#define BOOT_CONFIG_SECTOR 1 - -// timeout for the initial connect (in ms) -#define OTA_CONNECT_TIMEOUT 10000 - -// timeout for the download and flash to complete (in ms), once connected -#define OTA_DOWNLOAD_TIMEOUT 20000 - -#define UPGRADE_FLAG_IDLE 0x00 -#define UPGRADE_FLAG_START 0x01 -#define UPGRADE_FLAG_FINISH 0x02 - -#define FLASH_BY_ADDR 0xff - -rboot_config_t rboot_get_config(); -bool rboot_set_config(rboot_config_t *conf); -uint8_t rboot_get_current_rom(); -bool rboot_set_current_rom(uint8_t rom); -bool rboot_verify_image(uint32_t offset, uint32_t expected_length, const char **error_message); - -#endif diff --git a/extras/rboot-ota/rboot.h b/extras/rboot-ota/rboot.h new file mode 100644 index 0000000..e09aa7e --- /dev/null +++ b/extras/rboot-ota/rboot.h @@ -0,0 +1,111 @@ +#ifndef __RBOOT_H__ +#define __RBOOT_H__ + +////////////////////////////////////////////////// +// rBoot open source boot loader for ESP8266. +// Copyright 2015 Richard A Burton +// richardaburton@gmail.com +// See license.txt for license terms. +////////////////////////////////////////////////// + +#ifdef __cplusplus +extern "C" { +#endif + +#define RBOOT_INTEGRATION + +// uncomment to use only c code +// if you aren't using gcc you may need to do this +//#define BOOT_NO_ASM + +// uncomment to have a checksum on the boot config +//#define BOOT_CONFIG_CHKSUM + +// uncomment to enable big flash support (>1MB) +#define BOOT_BIG_FLASH + +// uncomment to enable 2 way communication between +// rBoot and the user app via the esp rtc data area +#define BOOT_RTC_ENABLED + +// uncomment to enable GPIO booting +//#define BOOT_GPIO_ENABLED + +// uncomment to include .irom0.text section in the checksum +// roms must be built with esptool2 using -iromchksum option +//#define BOOT_IROM_CHKSUM + +// uncomment to add a boot delay, allows you time to connect +// a terminal before rBoot starts to run and output messages +// value is in microseconds +//#define BOOT_DELAY_MICROS 2000000 + +// increase if required +#define MAX_ROMS 4 + +#define CHKSUM_INIT 0xef + +#define SECTOR_SIZE 0x1000 +#define BOOT_CONFIG_SECTOR 1 + +#define BOOT_CONFIG_MAGIC 0xe1 +#define BOOT_CONFIG_VERSION 0x01 + +#define MODE_STANDARD 0x00 +#define MODE_GPIO_ROM 0x01 +#define MODE_TEMP_ROM 0x02 + +#define RBOOT_RTC_MAGIC 0x2334ae68 +#define RBOOT_RTC_READ 1 +#define RBOOT_RTC_WRITE 0 +#define RBOOT_RTC_ADDR 64 + +#ifdef RBOOT_INTEGRATION +#include +#endif + +/** @brief Structure containing rBoot configuration + * @note ROM addresses must be multiples of 0x1000 (flash sector aligned). + * Without BOOT_BIG_FLASH only the first 8Mbit (1MB) of the chip will + * be memory mapped so ROM slots containing .irom0.text sections must + * remain below 0x100000. Slots beyond this will only be accessible via + * spi read calls, so use these for stored resources, not code. With + * BOOT_BIG_FLASH the flash will be mapped in chunks of 8MBit (1MB), so + * ROMs can be anywhere, but must not straddle two 8MBit (1MB) blocks. + * @ingroup rboot +*/ +typedef struct { + uint8 magic; ///< Our magic, identifies rBoot configuration - should be BOOT_CONFIG_MAGIC + uint8 version; ///< Version of configuration structure - should be BOOT_CONFIG_VERSION + uint8 mode; ///< Boot loader mode (MODE_STANDARD | MODE_GPIO_ROM) + uint8 current_rom; ///< Currently selected ROM (will be used for next standard boot) + uint8 gpio_rom; ///< ROM to use for GPIO boot (hardware switch) with mode set to MODE_GPIO_ROM + uint8 count; ///< Quantity of ROMs available to boot + uint8 unused[2]; ///< Padding (not used) + uint32 roms[MAX_ROMS]; ///< Flash addresses of each ROM +#ifdef BOOT_CONFIG_CHKSUM + uint8 chksum; ///< Checksum of this configuration structure (if BOOT_CONFIG_CHKSUM defined) +#endif +} rboot_config; + +#ifdef BOOT_RTC_ENABLED +/** @brief Structure containing rBoot status/control data + * @note This structure is used to, optionally, communicate between rBoot and + * the user app. It is stored in the ESP RTC data area. + * @ingroup rboot +*/ +typedef struct { + uint32 magic; ///< Magic, identifies rBoot RTC data - should be RBOOT_RTC_MAGIC + uint8 next_mode; ///< The next boot mode, defaults to MODE_STANDARD - can be set to MODE_TEMP_ROM + uint8 last_mode; ///< The last (this) boot mode - can be MODE_STANDARD, MODE_GPIO_ROM or MODE_TEMP_ROM + uint8 last_rom; ///< The last (this) boot rom number + uint8 temp_rom; ///< The next boot rom number when next_mode set to MODE_TEMP_ROM + uint8 chksum; ///< Checksum of this structure this will be updated for you passed to the API +} rboot_rtc_data; +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extras/rboot-ota/readme.txt b/extras/rboot-ota/readme.txt index 8721112..e5513e9 100644 --- a/extras/rboot-ota/readme.txt +++ b/extras/rboot-ota/readme.txt @@ -1,4 +1,4 @@ -*NOTE: This rboot-ota and the TFTP server ota-tftp.h are specific to esp-open-rtos. The below Makefile is from the upstream rboot-ota project.* +*NOTE: This rboot-ota and the TFTP server ota-tftp.h are specific to esp-open-rtos. The below Makefile is from the upstream rboot-ota project and the rboot code is taken from #75ca33b.* For more details on OTA in esp-open-rtos, see https://github.com/SuperHouse/esp-open-rtos/wiki/OTA-Update-Configuration diff --git a/include/ssid_config.h b/include/ssid_config.h index edad5a9..a13e09a 100644 --- a/include/ssid_config.h +++ b/include/ssid_config.h @@ -20,7 +20,7 @@ // https://www.kernel.org/pub/software/scm/git/docs/git-update-index.html // -#warning "You need to enter your wifi credentials in this file and follow the instructions here to keep the password safe from Github commits." +#error "You need to enter your wifi credentials in this file and follow the instructions here to keep the password safe from Github commits." #ifndef __SSID_CONFIG_H__ #define __SSID_CONFIG_H__ diff --git a/ld/common.ld b/ld/common.ld index 1413c1c..cc8a8ce 100644 --- a/ld/common.ld +++ b/ld/common.ld @@ -139,13 +139,34 @@ SECTIONS (except for libgcc which is matched above.) */ *(.literal .text .literal.* .text.*) + /* Anything explicitly marked as "irom" or "irom0" should go here */ + *(.irom.* .irom.*.* .irom0.*) + _irom0_text_end = ABSOLUTE(.); + + /* Temporary .rodata hacks start here, eventually all rodata will + be in irom by default */ /* mbedtls rodata */ *mbedtls.a:*.o(.rodata.* .rodata) /* actual certificate in example (TEMPORARY HACK) */ *:cert.o(.rodata.* .rodata) - /* Anything explicitly marked as "irom" or "irom0" should go here */ - *(.irom.* .irom.*.* .irom0.*) - _irom0_text_end = ABSOLUTE(.); + /* C++ constructor and destructor tables, properly ordered: */ + __init_array_start = ABSOLUTE(.); + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + __init_array_end = ABSOLUTE(.); + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + /* C++ exception handlers table: */ + __XT_EXCEPTION_DESCS__ = ABSOLUTE(.); + *(.xt_except_desc) + *(.gnu.linkonce.h.*) + __XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.); + *(.xt_except_desc_end) + } >irom0_0_seg :irom0_0_phdr .data : ALIGN(4) @@ -179,23 +200,6 @@ SECTIONS *(.gnu.version_r) *(.eh_frame) . = (. + 3) & ~ 3; - /* C++ constructor and destructor tables, properly ordered: */ - __init_array_start = ABSOLUTE(.); - KEEP (*crtbegin.o(.ctors)) - KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) - KEEP (*(SORT(.ctors.*))) - KEEP (*(.ctors)) - __init_array_end = ABSOLUTE(.); - KEEP (*crtbegin.o(.dtors)) - KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) - KEEP (*(SORT(.dtors.*))) - KEEP (*(.dtors)) - /* C++ exception handlers table: */ - __XT_EXCEPTION_DESCS__ = ABSOLUTE(.); - *(.xt_except_desc) - *(.gnu.linkonce.h.*) - __XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.); - *(.xt_except_desc_end) *(.dynamic) *(.gnu.version_d) . = ALIGN(4); /* this table MUST be 4-byte aligned */ @@ -230,7 +234,6 @@ SECTIONS } >dram0_0_seg :dram0_0_bss_phdr /* __stack = 0x3ffc8000; */ - .lit4 : ALIGN(4) { _lit4_start = ABSOLUTE(.); diff --git a/libc/xtensa-lx106-elf/lib/libc.a b/libc/xtensa-lx106-elf/lib/libc.a index 6a256cd..a43b5ed 100644 Binary files a/libc/xtensa-lx106-elf/lib/libc.a and b/libc/xtensa-lx106-elf/lib/libc.a differ diff --git a/libc/xtensa-lx106-elf/lib/libg.a b/libc/xtensa-lx106-elf/lib/libg.a index 6a256cd..4d3d50a 100644 Binary files a/libc/xtensa-lx106-elf/lib/libg.a and b/libc/xtensa-lx106-elf/lib/libg.a differ diff --git a/libc/xtensa-lx106-elf/lib/libm.a b/libc/xtensa-lx106-elf/lib/libm.a index 7d96963..2e9a859 100644 Binary files a/libc/xtensa-lx106-elf/lib/libm.a and b/libc/xtensa-lx106-elf/lib/libm.a differ diff --git a/lwip/include/arch/cc.h b/lwip/include/arch/cc.h index a09ea0c..04dab26 100644 --- a/lwip/include/arch/cc.h +++ b/lwip/include/arch/cc.h @@ -94,7 +94,7 @@ typedef int sys_prot_t; _Pragma("GCC diagnostic pop") \ } while(0) #define LWIP_PLATFORM_ASSERT(x) do { printf("Assertion \"%s\" failed at line %d in %s\n", \ - x, __LINE__, __FILE__); while(1) {} } while(0) + x, __LINE__, __FILE__); abort(); } while(0) #define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \ printf("Assertion \"%s\" failed at line %d in %s\n", message, __LINE__, __FILE__); \ diff --git a/lwip/sys_arch.c b/lwip/sys_arch.c index f625fc6..ad936e4 100644 --- a/lwip/sys_arch.c +++ b/lwip/sys_arch.c @@ -1,585 +1,592 @@ -/* - * Copyright (c) 2001-2003 Swedish Institute of Computer Science. - * 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. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 lwIP TCP/IP stack. - * - * Author: Adam Dunkels - * - */ - -//***************************************************************************** -// -// Include OS functionality. -// -//***************************************************************************** - -/* ------------------------ System architecture includes ----------------------------- */ -#include "arch/sys_arch.h" - -/* ------------------------ lwIP includes --------------------------------- */ -#include "lwip/opt.h" - -#include "lwip/err.h" -#include "lwip/debug.h" -#include "lwip/def.h" -#include "lwip/sys.h" -#include "lwip/mem.h" -#include "lwip/stats.h" - -/* Very crude mechanism used to determine if the critical section handling -functions are being called from an interrupt context or not. This relies on -the interrupt handler setting this variable manually. */ -portBASE_TYPE xInsideISR = pdFALSE; - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_new - *---------------------------------------------------------------------------* - * Description: - * Creates a new mailbox - * Inputs: - * int size -- Size of elements in the mailbox - * Outputs: - * sys_mbox_t -- Handle to new mailbox - *---------------------------------------------------------------------------*/ -err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize ) -{ -err_t xReturn = ERR_MEM; - - *pxMailBox = xQueueCreate( iSize, sizeof( void * ) ); - - if( *pxMailBox != NULL ) - { - xReturn = ERR_OK; - SYS_STATS_INC_USED( mbox ); - } - - return xReturn; -} - - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_free - *---------------------------------------------------------------------------* - * Description: - * Deallocates a mailbox. If there are messages still present in the - * mailbox when the mailbox is deallocated, it is an indication of a - * programming error in lwIP and the developer should be notified. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * Outputs: - * sys_mbox_t -- Handle to new mailbox - *---------------------------------------------------------------------------*/ -void sys_mbox_free( sys_mbox_t *pxMailBox ) -{ -unsigned long ulMessagesWaiting; - - ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox ); - configASSERT( ( ulMessagesWaiting == 0 ) ); - - #if SYS_STATS - { - if( ulMessagesWaiting != 0UL ) - { - SYS_STATS_INC( mbox.err ); - } - - SYS_STATS_DEC( mbox.used ); - } - #endif /* SYS_STATS */ - - vQueueDelete( *pxMailBox ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_post - *---------------------------------------------------------------------------* - * Description: - * Post the "msg" to the mailbox. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void *data -- Pointer to data to post - *---------------------------------------------------------------------------*/ -void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost ) -{ - while( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_mbox_trypost - *---------------------------------------------------------------------------* - * Description: - * Try to post the "msg" to the mailbox. Returns immediately with - * error if cannot. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void *msg -- Pointer to data to post - * Outputs: - * err_t -- ERR_OK if message posted, else ERR_MEM - * if not. - *---------------------------------------------------------------------------*/ -err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost ) -{ -err_t xReturn; -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( xInsideISR != pdFALSE ) - { - xReturn = xQueueSendFromISR( *pxMailBox, &pxMessageToPost, &xHigherPriorityTaskWoken ); - } - else - { - xReturn = xQueueSend( *pxMailBox, &pxMessageToPost, ( portTickType ) 0 ); - } - - if( xReturn == pdPASS ) - { - xReturn = ERR_OK; - } - else - { - /* The queue was already full. */ - xReturn = ERR_MEM; - SYS_STATS_INC( mbox.err ); - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_mbox_fetch - *---------------------------------------------------------------------------* - * Description: - * Blocks the thread until a message arrives in the mailbox, but does - * not block the thread longer than "timeout" milliseconds (similar to - * the sys_arch_sem_wait() function). The "msg" argument is a result - * parameter that is set by the function (i.e., by doing "*msg = - * ptr"). The "msg" parameter maybe NULL to indicate that the message - * should be dropped. - * - * The return values are the same as for the sys_arch_sem_wait() function: - * Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a - * timeout. - * - * Note that a function with a similar name, sys_mbox_fetch(), is - * implemented by lwIP. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void **msg -- Pointer to pointer to msg received - * u32_t timeout -- Number of milliseconds until timeout - * Outputs: - * u32_t -- SYS_ARCH_TIMEOUT if timeout, else number - * of milliseconds until received. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut ) -{ -void *pvDummy; -portTickType xStartTime, xEndTime, xElapsed; -unsigned long ulReturn; - - xStartTime = xTaskGetTickCount(); - - if( NULL == ppvBuffer ) - { - ppvBuffer = &pvDummy; - } - - if( ulTimeOut != 0UL ) - { - configASSERT( xInsideISR == ( portBASE_TYPE ) 0 ); - - if( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_RATE_MS ) ) - { - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - ulReturn = xElapsed; - } - else - { - /* Timed out. */ - *ppvBuffer = NULL; - ulReturn = SYS_ARCH_TIMEOUT; - } - } - else - { - while( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) ); - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - if( xElapsed == 0UL ) - { - xElapsed = 1UL; - } - - ulReturn = xElapsed; - } - - return ulReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_mbox_tryfetch - *---------------------------------------------------------------------------* - * Description: - * Similar to sys_arch_mbox_fetch, but if message is not ready - * immediately, we'll return with SYS_MBOX_EMPTY. On success, 0 is - * returned. - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * void **msg -- Pointer to pointer to msg received - * Outputs: - * u32_t -- SYS_MBOX_EMPTY if no messages. Otherwise, - * return ERR_OK. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer ) -{ -void *pvDummy; -unsigned long ulReturn; -long lResult; -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( ppvBuffer== NULL ) - { - ppvBuffer = &pvDummy; - } - - if( xInsideISR != pdFALSE ) - { - lResult = xQueueReceiveFromISR( *pxMailBox, &( *ppvBuffer ), &xHigherPriorityTaskWoken ); - } - else - { - lResult = xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL ); - } - - if( lResult == pdPASS ) - { - ulReturn = ERR_OK; - } - else - { - ulReturn = SYS_MBOX_EMPTY; - } - - return ulReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_new - *---------------------------------------------------------------------------* - * Description: - * Creates and returns a new semaphore. The "ucCount" argument specifies - * the initial state of the semaphore. - * NOTE: Currently this routine only creates counts of 1 or 0 - * Inputs: - * sys_mbox_t mbox -- Handle of mailbox - * u8_t ucCount -- Initial ucCount of semaphore (1 or 0) - * Outputs: - * sys_sem_t -- Created semaphore or 0 if could not create. - *---------------------------------------------------------------------------*/ -err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount ) -{ -err_t xReturn = ERR_MEM; - - vSemaphoreCreateBinary( ( *pxSemaphore ) ); - - if( *pxSemaphore != NULL ) - { - if( ucCount == 0U ) - { - xSemaphoreTake( *pxSemaphore, 1UL ); - } - - xReturn = ERR_OK; - SYS_STATS_INC_USED( sem ); - } - else - { - SYS_STATS_INC( sem.err ); - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_sem_wait - *---------------------------------------------------------------------------* - * Description: - * Blocks the thread while waiting for the semaphore to be - * signaled. If the "timeout" argument is non-zero, the thread should - * only be blocked for the specified time (measured in - * milliseconds). - * - * If the timeout argument is non-zero, the return value is the number of - * milliseconds spent waiting for the semaphore to be signaled. If the - * semaphore wasn't signaled within the specified time, the return value is - * SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore - * (i.e., it was already signaled), the function may return zero. - * - * Notice that lwIP implements a function with a similar name, - * sys_sem_wait(), that uses the sys_arch_sem_wait() function. - * Inputs: - * sys_sem_t sem -- Semaphore to wait on - * u32_t timeout -- Number of milliseconds until timeout - * Outputs: - * u32_t -- Time elapsed or SYS_ARCH_TIMEOUT. - *---------------------------------------------------------------------------*/ -u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout ) -{ -portTickType xStartTime, xEndTime, xElapsed; -unsigned long ulReturn; - - xStartTime = xTaskGetTickCount(); - - if( ulTimeout != 0UL ) - { - if( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_RATE_MS ) == pdTRUE ) - { - xEndTime = xTaskGetTickCount(); - xElapsed = (xEndTime - xStartTime) * portTICK_RATE_MS; - ulReturn = xElapsed; - } - else - { - ulReturn = SYS_ARCH_TIMEOUT; - } - } - else - { - while( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE ); - xEndTime = xTaskGetTickCount(); - xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; - - if( xElapsed == 0UL ) - { - xElapsed = 1UL; - } - - ulReturn = xElapsed; - } - - return ulReturn; -} - -/** Create a new mutex - * @param mutex pointer to the mutex to create - * @return a new mutex */ -err_t sys_mutex_new( sys_mutex_t *pxMutex ) -{ -err_t xReturn = ERR_MEM; - - *pxMutex = xSemaphoreCreateMutex(); - - if( *pxMutex != NULL ) - { - xReturn = ERR_OK; - SYS_STATS_INC_USED( mutex ); - } - else - { - SYS_STATS_INC( mutex.err ); - } - - return xReturn; -} - -/** Lock a mutex - * @param mutex the mutex to lock */ -void sys_mutex_lock( sys_mutex_t *pxMutex ) -{ - while( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS ); -} - -/** Unlock a mutex - * @param mutex the mutex to unlock */ -void sys_mutex_unlock(sys_mutex_t *pxMutex ) -{ - xSemaphoreGive( *pxMutex ); -} - - -/** Delete a semaphore - * @param mutex the mutex to delete */ -void sys_mutex_free( sys_mutex_t *pxMutex ) -{ - SYS_STATS_DEC( mutex.used ); - vQueueDelete( *pxMutex ); -} - - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_signal - *---------------------------------------------------------------------------* - * Description: - * Signals (releases) a semaphore - * Inputs: - * sys_sem_t sem -- Semaphore to signal - *---------------------------------------------------------------------------*/ -void sys_sem_signal( sys_sem_t *pxSemaphore ) -{ -portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; - - if( xInsideISR != pdFALSE ) - { - xSemaphoreGiveFromISR( *pxSemaphore, &xHigherPriorityTaskWoken ); - } - else - { - xSemaphoreGive( *pxSemaphore ); - } -} - -/*---------------------------------------------------------------------------* - * Routine: sys_sem_free - *---------------------------------------------------------------------------* - * Description: - * Deallocates a semaphore - * Inputs: - * sys_sem_t sem -- Semaphore to free - *---------------------------------------------------------------------------*/ -void sys_sem_free( sys_sem_t *pxSemaphore ) -{ - SYS_STATS_DEC(sem.used); - vQueueDelete( *pxSemaphore ); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_init - *---------------------------------------------------------------------------* - * Description: - * Initialize sys arch - *---------------------------------------------------------------------------*/ -void sys_init(void) -{ -} - -u32_t sys_now(void) -{ - return xTaskGetTickCount(); -} - -/*---------------------------------------------------------------------------* - * Routine: sys_thread_new - *---------------------------------------------------------------------------* - * Description: - * Starts a new thread with priority "prio" that will begin its - * execution in the function "thread()". The "arg" argument will be - * passed as an argument to the thread() function. The id of the new - * thread is returned. Both the id and the priority are system - * dependent. - * Inputs: - * char *name -- Name of thread - * void (* thread)(void *arg) -- Pointer to function to run. - * void *arg -- Argument passed into function - * int stacksize -- Required stack amount in bytes - * int prio -- Thread priority - * Outputs: - * sys_thread_t -- Pointer to per-thread timeouts. - *---------------------------------------------------------------------------*/ -sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void *pvArg, int iStackSize, int iPriority ) -{ -xTaskHandle xCreatedTask; -portBASE_TYPE xResult; -sys_thread_t xReturn; - - xResult = xTaskCreate( pxThread, ( signed char * ) pcName, iStackSize, pvArg, iPriority, &xCreatedTask ); - - if( xResult == pdPASS ) - { - xReturn = xCreatedTask; - } - else - { - xReturn = NULL; - } - - return xReturn; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_protect - *---------------------------------------------------------------------------* - * Description: - * This optional function does a "fast" critical region protection and - * returns the previous protection level. This function is only called - * during very short critical regions. An embedded system which supports - * ISR-based drivers might want to implement this function by disabling - * interrupts. Task-based systems might want to implement this by using - * a mutex or disabling tasking. This function should support recursive - * calls from the same task or interrupt. In other words, - * sys_arch_protect() could be called while already protected. In - * that case the return value indicates that it is already protected. - * - * sys_arch_protect() is only required if your port is supporting an - * operating system. - * Outputs: - * sys_prot_t -- Previous protection level (not used here) - *---------------------------------------------------------------------------*/ -sys_prot_t sys_arch_protect( void ) -{ - if( xInsideISR == pdFALSE ) - { - taskENTER_CRITICAL(); - } - return ( sys_prot_t ) 1; -} - -/*---------------------------------------------------------------------------* - * Routine: sys_arch_unprotect - *---------------------------------------------------------------------------* - * Description: - * This optional function does a "fast" set of critical region - * protection to the value specified by pval. See the documentation for - * sys_arch_protect() for more information. This function is only - * required if your port is supporting an operating system. - * Inputs: - * sys_prot_t -- Previous protection level (not used here) - *---------------------------------------------------------------------------*/ -void sys_arch_unprotect( sys_prot_t xValue ) -{ - (void) xValue; - if( xInsideISR == pdFALSE ) - { - taskEXIT_CRITICAL(); - } -} - -/* - * Prints an assertion messages and aborts execution. - */ -void sys_assert( const char *pcMessage ) -{ - (void) pcMessage; - - for (;;) - { - } -} -/*-------------------------------------------------------------------------* - * End of File: sys_arch.c - *-------------------------------------------------------------------------*/ - +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ + +//***************************************************************************** +// +// Include OS functionality. +// +//***************************************************************************** + +/* ------------------------ System architecture includes ----------------------------- */ +#include "arch/sys_arch.h" + +/* ------------------------ lwIP includes --------------------------------- */ +#include "lwip/opt.h" + +#include "lwip/err.h" +#include "lwip/debug.h" +#include "lwip/def.h" +#include "lwip/sys.h" +#include "lwip/mem.h" +#include "lwip/stats.h" + +extern bool esp_in_isr; + +/* Based on the default xInsideISR mechanism to determine + if an ISR is running. + + Doesn't support the possibility that LWIP functions are called from the NMI + handler (none are called from NMI when using current/SDK implementation.) +*/ +static inline bool is_inside_isr() +{ + return esp_in_isr; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_new + *---------------------------------------------------------------------------* + * Description: + * Creates a new mailbox + * Inputs: + * int size -- Size of elements in the mailbox + * Outputs: + * sys_mbox_t -- Handle to new mailbox + *---------------------------------------------------------------------------*/ +err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize ) +{ + err_t xReturn = ERR_MEM; + + *pxMailBox = xQueueCreate( iSize, sizeof( void * ) ); + + if( *pxMailBox != NULL ) + { + xReturn = ERR_OK; + SYS_STATS_INC_USED( mbox ); + } + + return xReturn; +} + + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_free + *---------------------------------------------------------------------------* + * Description: + * Deallocates a mailbox. If there are messages still present in the + * mailbox when the mailbox is deallocated, it is an indication of a + * programming error in lwIP and the developer should be notified. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * Outputs: + * sys_mbox_t -- Handle to new mailbox + *---------------------------------------------------------------------------*/ +void sys_mbox_free( sys_mbox_t *pxMailBox ) +{ +unsigned long ulMessagesWaiting; + + ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox ); + configASSERT( ( ulMessagesWaiting == 0 ) ); + + #if SYS_STATS + { + if( ulMessagesWaiting != 0UL ) + { + SYS_STATS_INC( mbox.err ); + } + + SYS_STATS_DEC( mbox.used ); + } + #endif /* SYS_STATS */ + + vQueueDelete( *pxMailBox ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_post + *---------------------------------------------------------------------------* + * Description: + * Post the "msg" to the mailbox. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void *data -- Pointer to data to post + *---------------------------------------------------------------------------*/ +void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost ) +{ + while( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_mbox_trypost + *---------------------------------------------------------------------------* + * Description: + * Try to post the "msg" to the mailbox. Returns immediately with + * error if cannot. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void *msg -- Pointer to data to post + * Outputs: + * err_t -- ERR_OK if message posted, else ERR_MEM + * if not. + *---------------------------------------------------------------------------*/ +err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost ) +{ +err_t xReturn; +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( is_inside_isr() != pdFALSE ) + { + xReturn = xQueueSendFromISR( *pxMailBox, &pxMessageToPost, &xHigherPriorityTaskWoken ); + } + else + { + xReturn = xQueueSend( *pxMailBox, &pxMessageToPost, ( portTickType ) 0 ); + } + + if( xReturn == pdPASS ) + { + xReturn = ERR_OK; + } + else + { + /* The queue was already full. */ + xReturn = ERR_MEM; + SYS_STATS_INC( mbox.err ); + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_mbox_fetch + *---------------------------------------------------------------------------* + * Description: + * Blocks the thread until a message arrives in the mailbox, but does + * not block the thread longer than "timeout" milliseconds (similar to + * the sys_arch_sem_wait() function). The "msg" argument is a result + * parameter that is set by the function (i.e., by doing "*msg = + * ptr"). The "msg" parameter maybe NULL to indicate that the message + * should be dropped. + * + * The return values are the same as for the sys_arch_sem_wait() function: + * Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a + * timeout. + * + * Note that a function with a similar name, sys_mbox_fetch(), is + * implemented by lwIP. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void **msg -- Pointer to pointer to msg received + * u32_t timeout -- Number of milliseconds until timeout + * Outputs: + * u32_t -- SYS_ARCH_TIMEOUT if timeout, else number + * of milliseconds until received. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut ) +{ +void *pvDummy; +portTickType xStartTime, xEndTime, xElapsed; +unsigned long ulReturn; + + xStartTime = xTaskGetTickCount(); + + if( NULL == ppvBuffer ) + { + ppvBuffer = &pvDummy; + } + + if( ulTimeOut != 0UL ) + { + configASSERT( is_inside_isr() == ( portBASE_TYPE ) 0 ); + + if( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_RATE_MS ) ) + { + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + ulReturn = xElapsed; + } + else + { + /* Timed out. */ + *ppvBuffer = NULL; + ulReturn = SYS_ARCH_TIMEOUT; + } + } + else + { + while( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) ); + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + if( xElapsed == 0UL ) + { + xElapsed = 1UL; + } + + ulReturn = xElapsed; + } + + return ulReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_mbox_tryfetch + *---------------------------------------------------------------------------* + * Description: + * Similar to sys_arch_mbox_fetch, but if message is not ready + * immediately, we'll return with SYS_MBOX_EMPTY. On success, 0 is + * returned. + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * void **msg -- Pointer to pointer to msg received + * Outputs: + * u32_t -- SYS_MBOX_EMPTY if no messages. Otherwise, + * return ERR_OK. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer ) +{ +void *pvDummy; +unsigned long ulReturn; +long lResult; +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( ppvBuffer== NULL ) + { + ppvBuffer = &pvDummy; + } + + if( is_inside_isr() != pdFALSE ) + { + lResult = xQueueReceiveFromISR( *pxMailBox, &( *ppvBuffer ), &xHigherPriorityTaskWoken ); + } + else + { + lResult = xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL ); + } + + if( lResult == pdPASS ) + { + ulReturn = ERR_OK; + } + else + { + ulReturn = SYS_MBOX_EMPTY; + } + + return ulReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_new + *---------------------------------------------------------------------------* + * Description: + * Creates and returns a new semaphore. The "ucCount" argument specifies + * the initial state of the semaphore. + * NOTE: Currently this routine only creates counts of 1 or 0 + * Inputs: + * sys_mbox_t mbox -- Handle of mailbox + * u8_t ucCount -- Initial ucCount of semaphore (1 or 0) + * Outputs: + * sys_sem_t -- Created semaphore or 0 if could not create. + *---------------------------------------------------------------------------*/ +err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount ) +{ +err_t xReturn = ERR_MEM; + + vSemaphoreCreateBinary( ( *pxSemaphore ) ); + + if( *pxSemaphore != NULL ) + { + if( ucCount == 0U ) + { + xSemaphoreTake( *pxSemaphore, 1UL ); + } + + xReturn = ERR_OK; + SYS_STATS_INC_USED( sem ); + } + else + { + SYS_STATS_INC( sem.err ); + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_sem_wait + *---------------------------------------------------------------------------* + * Description: + * Blocks the thread while waiting for the semaphore to be + * signaled. If the "timeout" argument is non-zero, the thread should + * only be blocked for the specified time (measured in + * milliseconds). + * + * If the timeout argument is non-zero, the return value is the number of + * milliseconds spent waiting for the semaphore to be signaled. If the + * semaphore wasn't signaled within the specified time, the return value is + * SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore + * (i.e., it was already signaled), the function may return zero. + * + * Notice that lwIP implements a function with a similar name, + * sys_sem_wait(), that uses the sys_arch_sem_wait() function. + * Inputs: + * sys_sem_t sem -- Semaphore to wait on + * u32_t timeout -- Number of milliseconds until timeout + * Outputs: + * u32_t -- Time elapsed or SYS_ARCH_TIMEOUT. + *---------------------------------------------------------------------------*/ +u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout ) +{ +portTickType xStartTime, xEndTime, xElapsed; +unsigned long ulReturn; + + xStartTime = xTaskGetTickCount(); + + if( ulTimeout != 0UL ) + { + if( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_RATE_MS ) == pdTRUE ) + { + xEndTime = xTaskGetTickCount(); + xElapsed = (xEndTime - xStartTime) * portTICK_RATE_MS; + ulReturn = xElapsed; + } + else + { + ulReturn = SYS_ARCH_TIMEOUT; + } + } + else + { + while( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE ); + xEndTime = xTaskGetTickCount(); + xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS; + + if( xElapsed == 0UL ) + { + xElapsed = 1UL; + } + + ulReturn = xElapsed; + } + + return ulReturn; +} + +/** Create a new mutex + * @param mutex pointer to the mutex to create + * @return a new mutex */ +err_t sys_mutex_new( sys_mutex_t *pxMutex ) +{ +err_t xReturn = ERR_MEM; + + *pxMutex = xSemaphoreCreateMutex(); + + if( *pxMutex != NULL ) + { + xReturn = ERR_OK; + SYS_STATS_INC_USED( mutex ); + } + else + { + SYS_STATS_INC( mutex.err ); + } + + return xReturn; +} + +/** Lock a mutex + * @param mutex the mutex to lock */ +void sys_mutex_lock( sys_mutex_t *pxMutex ) +{ + while( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS ); +} + +/** Unlock a mutex + * @param mutex the mutex to unlock */ +void sys_mutex_unlock(sys_mutex_t *pxMutex ) +{ + xSemaphoreGive( *pxMutex ); +} + + +/** Delete a semaphore + * @param mutex the mutex to delete */ +void sys_mutex_free( sys_mutex_t *pxMutex ) +{ + SYS_STATS_DEC( mutex.used ); + vQueueDelete( *pxMutex ); +} + + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_signal + *---------------------------------------------------------------------------* + * Description: + * Signals (releases) a semaphore + * Inputs: + * sys_sem_t sem -- Semaphore to signal + *---------------------------------------------------------------------------*/ +void sys_sem_signal( sys_sem_t *pxSemaphore ) +{ +portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; + + if( is_inside_isr() != pdFALSE ) + { + xSemaphoreGiveFromISR( *pxSemaphore, &xHigherPriorityTaskWoken ); + } + else + { + xSemaphoreGive( *pxSemaphore ); + } +} + +/*---------------------------------------------------------------------------* + * Routine: sys_sem_free + *---------------------------------------------------------------------------* + * Description: + * Deallocates a semaphore + * Inputs: + * sys_sem_t sem -- Semaphore to free + *---------------------------------------------------------------------------*/ +void sys_sem_free( sys_sem_t *pxSemaphore ) +{ + SYS_STATS_DEC(sem.used); + vQueueDelete( *pxSemaphore ); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_init + *---------------------------------------------------------------------------* + * Description: + * Initialize sys arch + *---------------------------------------------------------------------------*/ +void sys_init(void) +{ +} + +u32_t sys_now(void) +{ + return xTaskGetTickCount(); +} + +/*---------------------------------------------------------------------------* + * Routine: sys_thread_new + *---------------------------------------------------------------------------* + * Description: + * Starts a new thread with priority "prio" that will begin its + * execution in the function "thread()". The "arg" argument will be + * passed as an argument to the thread() function. The id of the new + * thread is returned. Both the id and the priority are system + * dependent. + * Inputs: + * char *name -- Name of thread + * void (* thread)(void *arg) -- Pointer to function to run. + * void *arg -- Argument passed into function + * int stacksize -- Required stack amount in bytes + * int prio -- Thread priority + * Outputs: + * sys_thread_t -- Pointer to per-thread timeouts. + *---------------------------------------------------------------------------*/ +sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void *pvArg, int iStackSize, int iPriority ) +{ +xTaskHandle xCreatedTask; +portBASE_TYPE xResult; +sys_thread_t xReturn; + + xResult = xTaskCreate( pxThread, ( signed char * ) pcName, iStackSize, pvArg, iPriority, &xCreatedTask ); + + if( xResult == pdPASS ) + { + xReturn = xCreatedTask; + } + else + { + xReturn = NULL; + } + + return xReturn; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_protect + *---------------------------------------------------------------------------* + * Description: + * This optional function does a "fast" critical region protection and + * returns the previous protection level. This function is only called + * during very short critical regions. An embedded system which supports + * ISR-based drivers might want to implement this function by disabling + * interrupts. Task-based systems might want to implement this by using + * a mutex or disabling tasking. This function should support recursive + * calls from the same task or interrupt. In other words, + * sys_arch_protect() could be called while already protected. In + * that case the return value indicates that it is already protected. + * + * sys_arch_protect() is only required if your port is supporting an + * operating system. + * Outputs: + * sys_prot_t -- Previous protection level (not used here) + *---------------------------------------------------------------------------*/ +sys_prot_t sys_arch_protect( void ) +{ + if( is_inside_isr() == pdFALSE ) + { + taskENTER_CRITICAL(); + } + return ( sys_prot_t ) 1; +} + +/*---------------------------------------------------------------------------* + * Routine: sys_arch_unprotect + *---------------------------------------------------------------------------* + * Description: + * This optional function does a "fast" set of critical region + * protection to the value specified by pval. See the documentation for + * sys_arch_protect() for more information. This function is only + * required if your port is supporting an operating system. + * Inputs: + * sys_prot_t -- Previous protection level (not used here) + *---------------------------------------------------------------------------*/ +void sys_arch_unprotect( sys_prot_t xValue ) +{ + (void) xValue; + if( is_inside_isr() == pdFALSE ) + { + taskEXIT_CRITICAL(); + } +} + +/* + * Prints an assertion messages and aborts execution. + */ +void sys_assert( const char *pcMessage ) +{ + (void) pcMessage; + + for (;;) + { + } +} +/*-------------------------------------------------------------------------* + * End of File: sys_arch.c + *-------------------------------------------------------------------------*/ diff --git a/utils/filteroutput.py b/utils/filteroutput.py new file mode 100755 index 0000000..954fd9a --- /dev/null +++ b/utils/filteroutput.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# +# A thin Python wrapper around addr2line, can monitor esp-open-rtos +# output and uses gdb to convert any suitable looking hex numbers +# found in the output into function and line numbers. +# +# Works with a serial port if the --port option is supplied. +# Otherwise waits for input on stdin. +# +import serial +import argparse +import re +import os +import os.path +import subprocess +import termios +import sys +import time + +# Try looking up anything in the executable address space +RE_EXECADDR = r"(0x)?40([0-9]|[a-z]){6}" + +def find_elf_file(): + out_files = [] + for top,_,files in os.walk('.', followlinks=False): + for f in files: + if f.endswith(".out"): + out_files.append(os.path.join(top,f)) + if len(out_files) == 1: + return out_files[0] + elif len(out_files) > 1: + print("Found multiple .out files: %s. Please specify one with the --elf option." % out_files) + else: + print("No .out file found under current directory. Please specify one with the --elf option.") + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser(description='esp-open-rtos output filter tool', prog='filteroutput') + parser.add_argument( + '--elf', '-e', + help="ELF file (*.out file) to load symbols from (if not supplied, will search for one)"), + parser.add_argument( + '--port', '-p', + help='Serial port to monitor (will monitor stdin if None)', + default=None) + parser.add_argument( + '--baud', '-b', + help='Baud rate for serial port', + type=int, + default=74880) + parser.add_argument( + '--reset-on-connect', '-r', + help='Reset ESP8266 (via DTR) on serial connect', + action='store_true') + + args = parser.parse_args() + + if args.elf is None: + args.elf = find_elf_file() + elif not os.path.exists(args.elf): + print("ELF file '%s' not found" % args.elf) + sys.exit(1) + + if args.port is not None: + print("Opening %s at %dbps..." % (args.port, args.baud)) + port = serial.Serial(args.port, baudrate=args.baud) + if args.reset_on_connect: + print("Resetting...") + port.setDTR(False) + time.sleep(0.1) + port.setDTR(True) + + else: + print("Reading from stdin...") + port = sys.stdin + # disable echo + try: + old_attr = termios.tcgetattr(sys.stdin.fileno()) + attr = termios.tcgetattr(sys.stdin.fileno()) + attr[3] = attr[3] & ~termios.ECHO + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attr) + except termios.error: + pass + + try: + while True: + line = port.readline() + if line == '': + break + print(line.strip()) + for match in re.finditer(RE_EXECADDR, line, re.IGNORECASE): + addr = match.group(0) + if not addr.startswith("0x"): + addr = "0x"+addr + # keeping addr2line and feeding it addresses on stdin didn't seem to work smoothly + addr2line = subprocess.check_output(["xtensa-lx106-elf-addr2line","-pfia","-e","%s" % args.elf, addr], cwd=".").strip() + if not addr2line.endswith(": ?? ??:0"): + print("\n%s\n" % addr2line.strip()) + finally: + if args.port is None: + # restore echo + termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_attr) + +if __name__ == "__main__": + main()