├── .gitignore ├── CMakeLists.txt ├── Makefile ├── components ├── duktape │ ├── CMakeLists.txt │ ├── Kconfig │ ├── component.mk │ ├── config │ │ ├── esp32.yaml │ │ ├── esp32_glue.h │ │ ├── gen.sh │ │ └── readme.md │ ├── duktape.c │ ├── esp32_glue.c │ └── include │ │ ├── duk_config.h │ │ └── duktape.h ├── lora │ ├── CMakeLists.txt │ ├── Kconfig │ ├── component.mk │ ├── include │ │ └── lora.h │ ├── lora.c │ └── readme.md └── websocket_server │ ├── CMakeLists.txt │ ├── Kconfig │ ├── component.mk │ ├── include │ └── websocket.h │ └── websocket.c ├── docs ├── BLE.md ├── Readme.md ├── crypto.md ├── examples │ ├── ble_echo_server.md │ ├── cryptotest.md │ ├── filesystem.md │ ├── lorawanscan.md │ └── test.md ├── filesystem.md ├── fluxboard.jpeg ├── fluxboard.md ├── fluxboard_0.4.pdf ├── huzzah.jpeg ├── huzzah.md ├── lora.md ├── lorawanlib.md ├── platform.md ├── timer.md ├── util.md └── webservice.md ├── main ├── CMakeLists.txt ├── ble_server.c ├── ble_support.c ├── board.c ├── button_service.c ├── component.mk ├── duk_crypto.c ├── duk_fs.c ├── duk_main.c ├── duk_util.c ├── include │ ├── ble_server.h │ ├── ble_support.h │ ├── board.h │ ├── button_service.h │ ├── duk_crypto.h │ ├── duk_fs.h │ ├── duk_helpers.h │ ├── duk_main.h │ ├── duk_util.h │ ├── led_service.h │ ├── log.h │ ├── lora_main.h │ ├── platform.h │ ├── queue.h │ ├── record.h │ ├── udp_service.h │ ├── util.h │ ├── version │ ├── vfs_config.h │ ├── web_service.h │ └── wifi_service.h ├── led_service.c ├── lora_main.c ├── main.c ├── platform.c ├── record.c ├── udp_service.c ├── util.c ├── web_service.c └── wifi_service.c ├── partitions.csv ├── scripts └── json2mddoc.py ├── sdkconfig ├── spiffs_image ├── ble_echo_server.js ├── cryptotest.js ├── filesystem_test.js ├── index.html ├── lorawanlib.js ├── lorawanscan.js ├── main.js ├── recovery.js ├── test.js ├── timer.js ├── util.js └── wifimgnt.js └── test ├── Makefile ├── jstest.c └── queue.c /.gitignore: -------------------------------------------------------------------------------- 1 | main/include/version.h 2 | build/*** 3 | test/*_test 4 | sdkconfig.old 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(fluxnode) 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | cat main/include/version |tr -d '\n' >main/include/version.h 4 | git rev-parse --short HEAD |tr -d '\n' >>main/include/version.h 5 | echo '"' >>main/include/version.h 6 | idf.py build 7 | 8 | .PHONY: monitor 9 | monitor: 10 | idf.py monitor 11 | 12 | .PHONY: flash 13 | flash: 14 | idf.py flash monitor 15 | 16 | .PHONY: docs 17 | docs: 18 | ./scripts/json2mddoc.py spiffs_image/timer.js >docs/timer.md 19 | ./scripts/json2mddoc.py spiffs_image/lorawanlib.js >docs/lorawanlib.md 20 | ./scripts/json2mddoc.py spiffs_image/util.js >docs/util.md 21 | ./scripts/json2mddoc.py main/duk_fs.c >docs/filesystem.md 22 | ./scripts/json2mddoc.py main/lora_main.c >docs/lora.md 23 | ./scripts/json2mddoc.py main/web_service.c >docs/webservice.md 24 | ./scripts/json2mddoc.py main/platform.c >docs/platform.md 25 | ./scripts/json2mddoc.py main/duk_crypto.c >docs/crypto.md 26 | 27 | .PHONY: test 28 | test: 29 | cd test; make 30 | 31 | .PHONY: clean 32 | clean: 33 | rm -rf build 34 | 35 | .PHONY: onboardtest 36 | onboardtest: 37 | @echo "\n192.168.4.1 is the default IP for Wifi AP\n" 38 | curl http://192.168.4.1/control?setload=/cryptotest.js 39 | curl http://192.168.4.1/control?reset 40 | -------------------------------------------------------------------------------- /components/duktape/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCDIRS 2 | "." 3 | ) 4 | set(COMPONENT_ADD_INCLUDEDIRS 5 | "include" 6 | ) 7 | 8 | register_component() 9 | -------------------------------------------------------------------------------- /components/duktape/Kconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crmulliner/fluxnode/fb106e7c86a3e7f67eb6b3fe713136b1cf38ca78/components/duktape/Kconfig -------------------------------------------------------------------------------- /components/duktape/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | -------------------------------------------------------------------------------- /components/duktape/config/esp32.yaml: -------------------------------------------------------------------------------- 1 | DUK_USE_BUFFEROBJECT_SUPPORT: true 2 | DUK_USE_ENCODING_BUILTINS: true 3 | DUK_USE_DUKTAPE_BUILTIN: true 4 | DUK_USE_HEX_SUPPORT: true 5 | DUK_USE_DATE_GET_NOW: esp32_duktape_get_now 6 | DUK_USE_GET_RANDOM_DOUBLE: esp32_duktape_random_double 7 | 8 | DUK_USE_VERBOSE_ERRORS: true 9 | DUK_USE_TRACEBACKS: true 10 | DUK_USE_AUGMENT_ERROR_CREATE: true 11 | DUK_USE_FUNC_FILENAME_PROPERTY: true 12 | DUK_USE_PC2LINE: true 13 | 14 | #DUK_USE_REFERENCE_COUNTING: false 15 | #DUK_USE_DOUBLE_LINKED_HEAP: false 16 | 17 | DUK_USE_ROM_STRINGS: true 18 | DUK_USE_ROM_OBJECTS: true 19 | DUK_USE_ROM_GLOBAL_INHERIT: true 20 | 21 | DUK_USE_FAST_REFCOUNT_DEFAULT: true 22 | DUK_USE_BASE64_SUPPORT: true 23 | 24 | -------------------------------------------------------------------------------- /components/duktape/config/esp32_glue.h: -------------------------------------------------------------------------------- 1 | #ifndef __ESP32_GLUE_H__ 2 | #define __ESP32_GLUE_H__ 3 | 4 | #include 5 | #include 6 | 7 | duk_double_t esp32_duktape_get_now(); 8 | 9 | duk_double_t esp32_duktape_random_double(); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /components/duktape/config/gen.sh: -------------------------------------------------------------------------------- 1 | python ./tools/configure.py \ 2 | --rom-support \ 3 | --rom-auto-lightfunc \ 4 | --config-metadata config/ \ 5 | --source-directory src-input \ 6 | --option-file config/examples/low_memory.yaml \ 7 | --option-file esp32.yaml \ 8 | --fixup-file esp32_glue.h \ 9 | --output-directory flux-duk 10 | -------------------------------------------------------------------------------- /components/duktape/config/readme.md: -------------------------------------------------------------------------------- 1 | # Duktape Config 2 | 3 | - clone duktape repo 4 | - copy config directory to the duktape directory (the one you just checked out) 5 | - run gen.sh 6 | 7 | now you will have `flux-duk` that contains the duktape.c/h files confgiured for Fluxn0de -------------------------------------------------------------------------------- /components/duktape/esp32_glue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | 7 | #ifndef __JSTEST__ 8 | #include "freertos/FreeRTOS.h" 9 | #include "driver/rtc_io.h" 10 | #endif 11 | 12 | #include 13 | 14 | duk_double_t esp32_duktape_get_now() 15 | { 16 | struct timeval tv; 17 | gettimeofday(&tv, NULL); 18 | duk_double_t ret = floor((double)tv.tv_sec * 1000.0 + (double)tv.tv_usec / 1000.0); 19 | return ret; 20 | } 21 | 22 | duk_double_t esp32_duktape_random_double() 23 | { 24 | #ifndef __JSTEST__ 25 | uint32_t r = esp_random(); 26 | duk_double_t rnd = r; 27 | return rnd; 28 | #else 29 | return 42; 30 | #endif 31 | } 32 | -------------------------------------------------------------------------------- /components/lora/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCS 2 | "lora.c" 3 | ) 4 | set(COMPONENT_ADD_INCLUDEDIRS 5 | "include" 6 | ) 7 | 8 | register_component() 9 | -------------------------------------------------------------------------------- /components/lora/Kconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crmulliner/fluxnode/fb106e7c86a3e7f67eb6b3fe713136b1cf38ca78/components/lora/Kconfig -------------------------------------------------------------------------------- /components/lora/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | -------------------------------------------------------------------------------- /components/lora/include/lora.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Improvements Copyright: Collin Mulliner 3 | * 4 | * based on: https://github.com/Inteform/esp32-lora-library 5 | * and since that is based https://github.com/sandeepmistry/arduino-LoRa 6 | * the license is: MIT 7 | */ 8 | 9 | #ifndef __LORA_H__ 10 | #define __LORA_H__ 11 | 12 | #define LORA_IRQ_ENABLE 1 13 | #define LORA_IRQ_DISABLE 0 14 | 15 | #define LORA_MSG_MAX_SIZE 255 16 | 17 | typedef void (*lora_isr_t)(void *); 18 | 19 | void lora_config_dio(const int gpio_dio0, const int gpio_dio1, const int gpio_dio2); 20 | void lora_config(const int gpio_cs, const int gpio_rst, const int gpio_miso, const int gpio_mosi, const int gpio_sck); 21 | int lora_init(void); 22 | void lora_reset(void); 23 | 24 | void lora_explicit_header_mode(void); 25 | void lora_implicit_header_mode(int size); 26 | 27 | void lora_idle(void); 28 | void lora_sleep(void); 29 | void lora_receive(void); 30 | 31 | void lora_set_tx_power(int level); 32 | void lora_set_frequency(const double frequency); 33 | void lora_set_spreading_factor(int sf); 34 | void lora_set_bandwidth(long sbw); 35 | void lora_set_coding_rate(int denominator); 36 | void lora_set_preamble_length(long length); 37 | void lora_set_sync_word(int sw); 38 | void lora_enable_crc(void); 39 | void lora_disable_crc(void); 40 | void lora_send_packet(uint8_t *buf, const int size); 41 | int lora_receive_packet(uint8_t *buf, const int size); 42 | int lora_received(void); 43 | int lora_packet_rssi(void); 44 | float lora_packet_snr(void); 45 | void lora_dump_registers(void); 46 | void lora_get_settings(int *bw, int *cr, int *sf); 47 | void lora_set_gain(uint8_t gain); 48 | 49 | void lora_disable_invert_iq(); 50 | void lora_enable_invert_iq(); 51 | void lora_calibrate(); 52 | 53 | int lora_install_irq_recv(lora_isr_t isr_handler); 54 | int lora_uninstall_irq_recv(); 55 | int lora_enable_irq_recv(int enable); 56 | void lora_send_packet_irq(uint8_t *buf, int size); 57 | 58 | int lora_install_irq_fhss(lora_isr_t isr_handler); 59 | int lora_uninstall_irq_fhss(); 60 | int lora_enable_irq_fhss(int enable); 61 | void lora_fhss_sethops(const int hops); 62 | int lora_fhss_handle(const double *fhtable, const int fhtable_size); 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /components/lora/readme.md: -------------------------------------------------------------------------------- 1 | # LoRa 2 | 3 | based on: https://github.com/Inteform/esp32-lora-library 4 | and since that is based https://github.com/sandeepmistry/arduino-LoRa 5 | the license is: MIT 6 | 7 | This library contains some changes and additions: 8 | - GPIOs can be configured at runtime 9 | - FHSS (hopping support) 10 | - non-polling operation using interrupts 11 | - improved frequency calculation 12 | -------------------------------------------------------------------------------- /components/websocket_server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCDIRS 2 | "." 3 | ) 4 | set(COMPONENT_ADD_INCLUDEDIRS 5 | "include" 6 | ) 7 | 8 | set(COMPONENT_REQUIRES 9 | mbedtls 10 | ) 11 | 12 | register_component() 13 | -------------------------------------------------------------------------------- /components/websocket_server/Kconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crmulliner/fluxnode/fb106e7c86a3e7f67eb6b3fe713136b1cf38ca78/components/websocket_server/Kconfig -------------------------------------------------------------------------------- /components/websocket_server/component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS := include 2 | COMPONENT_SRCDIRS := . 3 | -------------------------------------------------------------------------------- /components/websocket_server/include/websocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | * 4 | * based on: https://github.com/ThomasBarth/WebSockets-on-the-ESP32/blob/master/main/WebSocket_Task.c 5 | * license: MIT 6 | */ 7 | 8 | #ifndef _WEBSOCKET_H_ 9 | #define _WEBSOCKET_H_ 10 | 11 | typedef struct 12 | { 13 | uint32_t payload_length; 14 | char *payload; 15 | } websocket_frame_t; 16 | 17 | // return = 0 for success, return != 0 will close connection 18 | typedef int websocket_server_new_frame_callback(websocket_frame_t *, const void *); 19 | 20 | // 1 = connected, 0 = disconnected 21 | typedef void websocket_server_conninfo_callback(int); 22 | 23 | typedef int (*_log_printf_func_t)(const char *, ...); 24 | 25 | typedef struct 26 | { 27 | uint16_t port; 28 | 29 | websocket_server_new_frame_callback *callback; 30 | void *callback_data; 31 | websocket_server_conninfo_callback *conninfo_callback; 32 | 33 | unsigned long server_timeout; 34 | unsigned long client_timeout; 35 | 36 | _log_printf_func_t log_printf; 37 | } websocket_server_config_t; 38 | 39 | /* 40 | * websocket_server_config *ws_server_cfg = malloc(sizeof(websocket_server_config_t)); 41 | * ws_server_cfg->port = 1337; 42 | * ws_server_cfg->callback = my_callback; 43 | * ws_server_cfg->callback_data = NULL; // set callback data 44 | * ws_server_cfg->conninfo_callback = NULL; 45 | * websocket_start(ws_server_cfg); 46 | */ 47 | 48 | int websocket_send(const char *data, const size_t length); 49 | int websocket_start(websocket_server_config_t *ws_server_cfg); 50 | void websocket_stop(); 51 | int websocket_is_connected(); 52 | char *websocket_get_client_ip(); 53 | 54 | //#define WS_DEBUG 1 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /components/websocket_server/websocket.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | * 4 | * based on: https://github.com/ThomasBarth/WebSockets-on-the-ESP32/blob/master/main/WebSocket_Task.c 5 | * license: MIT 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include "freertos/FreeRTOS.h" 12 | #include "esp32/sha.h" 13 | #include "esp_system.h" 14 | #include "mbedtls/base64.h" 15 | #include "lwip/api.h" 16 | 17 | #include "websocket.h" 18 | 19 | typedef enum 20 | { 21 | WS_OP_CONNECT = 0x0, 22 | WS_OP_TEXT = 0x1, 23 | WS_OP_BIN = 0x2, 24 | WS_OP_CLOSE = 0x8, 25 | WS_OP_PING = 0x9, 26 | WS_OP_PONG = 0xA 27 | } websocket_opcodes_t; 28 | 29 | typedef struct __attribute__((__packed__)) 30 | { 31 | uint8_t opcode : 4; 32 | uint8_t reserved : 3; 33 | uint8_t FIN : 1; 34 | uint8_t payload_len : 7; 35 | uint8_t mask : 1; 36 | } websocket_frame_header_t; 37 | 38 | typedef struct __attribute__((__packed__)) 39 | { 40 | uint8_t opcode : 4; 41 | uint8_t reserved : 3; 42 | uint8_t FIN : 1; 43 | uint8_t payload_len : 7; 44 | uint8_t mask : 1; 45 | uint16_t len; 46 | } websocket_frame_header_16len_t; 47 | 48 | typedef struct __attribute__((__packed__)) 49 | { 50 | uint8_t opcode : 4; 51 | uint8_t reserved : 3; 52 | uint8_t FIN : 1; 53 | uint8_t payload_len : 7; 54 | uint8_t mask : 1; 55 | uint32_t len; 56 | } websocket_frame_header_32len_t; 57 | 58 | static struct netconn *websocket_conn; 59 | struct netconn *server_conn; 60 | static int websocket_server_running; 61 | static char client_ip_address[16]; 62 | 63 | const char websocket_sec_key[] = "Sec-WebSocket-Key:"; 64 | const char websocket_sec_con_key[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 65 | const char websocket_server_header[] = "HTTP/1.1 101 Switching Protocols \r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %.*s\r\n\r\n"; 66 | 67 | #define KEY_DIGEST_SIZE_MAX 24 68 | #define SHA1_DIGEST_LEN 20 69 | #define BASE64_DIGEST_LEN 42 70 | #define TWOBYTE_LEN 2 71 | #define FOURBYTE_LEN 4 72 | 73 | char *websocket_get_client_ip() 74 | { 75 | return client_ip_address; 76 | } 77 | 78 | static void conninfo_notify(websocket_server_config_t *cfg, int status) 79 | { 80 | if (cfg->conninfo_callback) 81 | { 82 | cfg->conninfo_callback(status); 83 | } 84 | } 85 | 86 | static int websocket_handshake(struct netconn *conn, websocket_server_config_t *cfg) 87 | { 88 | struct netbuf *inbuf; 89 | 90 | int err = netconn_recv(conn, &inbuf); 91 | 92 | if (err != ERR_OK) 93 | { 94 | #ifdef WS_DEBUG 95 | cfg->log_printf("%s: recv failed, err = %d\n", __func__, err); 96 | #endif 97 | return err; 98 | } 99 | 100 | char *buf; 101 | uint16_t buf_len; 102 | 103 | err = netbuf_data(inbuf, (void **)&buf, &buf_len); 104 | if (err != ERR_OK) 105 | { 106 | #ifdef WS_DEBUG 107 | cfg->log_printf("%s: recv data, err = %d\n", __func__, err); 108 | #endif 109 | return err; 110 | } 111 | 112 | char *seckey = strstr(buf, websocket_sec_key); 113 | 114 | if (seckey == NULL) 115 | { 116 | netbuf_delete(inbuf); 117 | return ERR_CONN; 118 | } 119 | 120 | char *handshake = malloc(sizeof(websocket_sec_con_key) + KEY_DIGEST_SIZE_MAX); 121 | if (handshake == NULL) 122 | { 123 | netbuf_delete(inbuf); 124 | return ERR_MEM; 125 | } 126 | memset(handshake, 0, sizeof(websocket_sec_con_key) + KEY_DIGEST_SIZE_MAX); 127 | 128 | int hs_len = 0; 129 | for (hs_len = 0; hs_len < KEY_DIGEST_SIZE_MAX; hs_len++) 130 | { 131 | if (*(seckey + sizeof(websocket_sec_key) + hs_len) == '\r' || *(seckey + sizeof(websocket_sec_key) + hs_len) == '\n') 132 | { 133 | break; 134 | } 135 | handshake[hs_len] = *(seckey + sizeof(websocket_sec_key) + hs_len); 136 | } 137 | memcpy(&handshake[hs_len], websocket_sec_con_key, sizeof(websocket_sec_con_key)); 138 | netbuf_delete(inbuf); 139 | 140 | unsigned char *digest = malloc(SHA1_DIGEST_LEN + 1); 141 | 142 | // some bug here?? 143 | esp_sha(SHA1, (unsigned char *)handshake, strlen(handshake), (unsigned char *)digest); 144 | 145 | char *base64digest = malloc(BASE64_DIGEST_LEN); 146 | size_t base64len = 0; 147 | memset(base64digest, 0, BASE64_DIGEST_LEN); 148 | mbedtls_base64_encode((unsigned char *)base64digest, BASE64_DIGEST_LEN, (size_t *)&base64len, (unsigned char *)digest, SHA1_DIGEST_LEN); 149 | 150 | free(digest); 151 | free(handshake); 152 | 153 | handshake = malloc(sizeof(websocket_server_header) + base64len + 1); 154 | if (handshake == NULL) 155 | { 156 | free(base64digest); 157 | return ERR_MEM; 158 | } 159 | 160 | sprintf(handshake, websocket_server_header, base64len, base64digest); 161 | free(base64digest); 162 | 163 | err = netconn_write(conn, handshake, strlen(handshake), NETCONN_COPY); 164 | if (err != ERR_OK) 165 | { 166 | #ifdef WS_DEBUG 167 | cfg->log_printf("%s: write failed, err = %d\n", __func__, err); 168 | #endif 169 | } 170 | free(handshake); 171 | 172 | // connection is established 173 | return err; 174 | } 175 | 176 | static int websocket_serve(struct netconn *conn, websocket_server_config_t *cfg) 177 | { 178 | struct netbuf *inbuf; 179 | 180 | while (1) 181 | { 182 | int res = netconn_recv(conn, &inbuf); 183 | 184 | if (res == ERR_TIMEOUT && websocket_server_running) 185 | { 186 | #ifdef WS_DEBUG 187 | cfg->log_printf("%s: recv timeout\n", __func__); 188 | #endif 189 | continue; 190 | } 191 | 192 | if (res != ERR_OK || websocket_server_running == 0) { 193 | #ifdef WS_DEBUG 194 | cfg->log_printf("%s: recv error (disconnecting)\n", __func__); 195 | #endif 196 | break; 197 | } 198 | 199 | char *buf; 200 | uint16_t buf_len; 201 | int err; 202 | 203 | err = netbuf_data(inbuf, (void **)&buf, &buf_len); 204 | if (err != ERR_OK) 205 | { 206 | #ifdef WS_DEBUG 207 | cfg->log_printf("%s: data, error = %d\n", __func__, err); 208 | #endif 209 | break; 210 | } 211 | 212 | websocket_frame_header_16len_t *hdr16 = (websocket_frame_header_16len_t *)buf; 213 | websocket_frame_header_32len_t *hdr32 = (websocket_frame_header_32len_t *)buf; 214 | 215 | if (hdr32->opcode == WS_OP_CLOSE) 216 | { 217 | #ifdef WS_DEBUG 218 | cfg->log_printf("%s: opcode = CLOSE\n", __func__); 219 | #endif 220 | break; 221 | } 222 | 223 | uint32_t payload_len = hdr32->payload_len; 224 | char *mask = (buf + sizeof(websocket_frame_header_t)); 225 | if (payload_len == 127) 226 | { 227 | payload_len = ntohl(hdr32->len); 228 | mask += FOURBYTE_LEN; 229 | } 230 | else if (payload_len == 126) 231 | { 232 | payload_len = ntohs(hdr16->len); 233 | mask += TWOBYTE_LEN; 234 | } 235 | 236 | #ifdef WS_DEBUG 237 | cfg->log_printf("%s: payload len: %d\n", __func__, payload_len); 238 | #endif 239 | 240 | char *payload = malloc(payload_len + 1); 241 | if (payload == NULL) 242 | { 243 | #ifdef WS_DEBUG 244 | cfg->log_printf("%s: payload = malloc failed\n", __func__); 245 | #endif 246 | break; 247 | } 248 | 249 | // check if content is masked 250 | if (hdr32->mask) 251 | { 252 | for (int i = 0; i < payload_len; i++) 253 | { 254 | payload[i] = (mask + 4)[i] ^ mask[i % 4]; 255 | } 256 | } 257 | else 258 | { 259 | memcpy(payload, mask, payload_len); 260 | } 261 | 262 | // terminate string 263 | payload[payload_len] = 0; 264 | 265 | if ((payload != NULL) && (hdr32->opcode != WS_OP_TEXT)) 266 | { 267 | #ifdef WS_DEBUG 268 | cfg->log_printf("%s: payload AND opcode bad = %d\n", __func__, hdr32->opcode); 269 | #endif 270 | free(payload); 271 | // I guess we only support TXT 272 | break; 273 | } 274 | 275 | websocket_frame_t frame; 276 | frame.payload_length = payload_len; 277 | frame.payload = payload; 278 | 279 | int status = cfg->callback(&frame, cfg->callback_data); 280 | // free payload if callback didn't free it 281 | if (frame.payload != NULL) 282 | { 283 | free(frame.payload); 284 | } 285 | if (status != ERR_OK) 286 | { 287 | #ifdef WS_DEBUG 288 | cfg->log_printf("%s: callback returned: %d\n", __func__, status); 289 | #endif 290 | break; 291 | } 292 | 293 | // free inbuf 294 | netbuf_delete(inbuf); 295 | } 296 | 297 | netbuf_delete(inbuf); 298 | netconn_close(conn); 299 | netconn_delete(conn); 300 | 301 | return 1; 302 | } 303 | 304 | static void websocket_server_task(void *p) 305 | { 306 | websocket_server_config_t *cfg = (websocket_server_config_t *)p; 307 | 308 | websocket_conn = NULL; 309 | 310 | server_conn = netconn_new(NETCONN_TCP); 311 | if (server_conn == NULL) 312 | { 313 | cfg->log_printf("%s: netconn_new failed FATAL\n", __func__); 314 | return; 315 | } 316 | netconn_bind(server_conn, NULL, cfg->port); 317 | netconn_listen(server_conn); 318 | 319 | #ifdef WS_DEBUG 320 | cfg->log_printf("%s: listing on port=%d\n", __func__, cfg->port); 321 | #endif 322 | 323 | // set timeout, otherwise this blocks forever and we can't stop 324 | netconn_set_recvtimeout(server_conn, cfg->server_timeout); 325 | 326 | while (1) 327 | { 328 | int accepted = netconn_accept(server_conn, &websocket_conn); 329 | if (accepted != ERR_OK && websocket_server_running) 330 | { 331 | #ifdef WS_DEBUG 332 | cfg->log_printf("%s: accept timeout\n", __func__); 333 | #endif 334 | continue; 335 | } 336 | 337 | if (websocket_server_running == 0) 338 | { 339 | break; 340 | } 341 | 342 | #ifdef WS_DEBUG 343 | cfg->log_printf("%s: accepted \n", __func__); 344 | #endif 345 | 346 | // get client IP address 347 | ip_addr_t ip; 348 | u16_t port; 349 | netconn_getaddr(websocket_conn, &ip, &port, 0); 350 | sprintf(client_ip_address, "%s", ipaddr_ntoa(&ip)); 351 | 352 | // set timeout, otherwise this blocks forever and we can't stop 353 | netconn_set_recvtimeout(websocket_conn, cfg->client_timeout); 354 | 355 | if (websocket_handshake(websocket_conn, cfg) != 0) 356 | { 357 | #ifdef WS_DEBUG 358 | cfg->log_printf("%s: handshake failed\n", __func__); 359 | #endif 360 | continue; 361 | } 362 | #ifdef WS_DEBUG 363 | cfg->log_printf("%s: handshake success\n", __func__); 364 | #endif 365 | conninfo_notify(cfg, 1); 366 | websocket_serve(websocket_conn, cfg); 367 | conninfo_notify(cfg, 0); 368 | client_ip_address[0] = 0; 369 | websocket_conn = NULL; 370 | } 371 | 372 | netconn_close(server_conn); 373 | netconn_delete(server_conn); 374 | websocket_server_running = 2; 375 | client_ip_address[0] = 0; 376 | 377 | #ifdef WS_DEBUG 378 | cfg->log_printf("%s: stopped\n", __func__); 379 | #endif 380 | 381 | vTaskDelete(NULL); 382 | } 383 | 384 | int websocket_is_connected() 385 | { 386 | return websocket_conn != NULL; 387 | } 388 | 389 | int websocket_send(const char *data, const size_t length) 390 | { 391 | if (websocket_conn == NULL) 392 | { 393 | return ERR_CONN; 394 | } 395 | 396 | uint8_t buf[sizeof(websocket_frame_header_32len_t)]; 397 | websocket_frame_header_16len_t *hdr16 = (websocket_frame_header_16len_t *)buf; 398 | websocket_frame_header_16len_t *hdr32 = (websocket_frame_header_16len_t *)buf; 399 | 400 | hdr32->FIN = 0x1; 401 | size_t hdr_len = sizeof(websocket_frame_header_t); 402 | if (length > 0xffff) 403 | { 404 | hdr32->payload_len = 127; 405 | hdr32->len = ntohl(length); 406 | hdr_len += FOURBYTE_LEN; 407 | } 408 | else if (length > 125 && length <= 0xffff) 409 | { 410 | hdr16->payload_len = 126; 411 | hdr16->len = ntohs(length); 412 | hdr_len += TWOBYTE_LEN; 413 | } 414 | else 415 | { 416 | hdr16->payload_len = length; 417 | } 418 | hdr16->mask = 0; 419 | hdr16->reserved = 0; 420 | hdr16->opcode = WS_OP_TEXT; 421 | 422 | int res = netconn_write(websocket_conn, buf, hdr_len, NETCONN_COPY); 423 | 424 | if (res != ERR_OK) 425 | { 426 | return res; 427 | } 428 | 429 | return netconn_write(websocket_conn, data, length, NETCONN_COPY); 430 | } 431 | 432 | int websocket_start(websocket_server_config_t *ws_server_cfg) 433 | { 434 | 435 | if (ws_server_cfg->log_printf == NULL) 436 | { 437 | ws_server_cfg->log_printf = printf; 438 | } 439 | 440 | websocket_server_running = 1; 441 | return xTaskCreate(&websocket_server_task, "websocket_server", 2048, ws_server_cfg, 5, NULL); 442 | } 443 | 444 | void websocket_stop() 445 | { 446 | websocket_server_running = 0; 447 | if (websocket_conn != NULL) 448 | { 449 | // TODO: disconnect client if connected 450 | } 451 | 452 | // wait until task has completed 453 | while (websocket_server_running != 2) 454 | { 455 | vTaskDelay(100); 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /docs/BLE.md: -------------------------------------------------------------------------------- 1 | # BLE 2 | 3 | Fluxn0de implements two services: battery level service and a non standard service that 4 | allows exchanging frames similar to a websocket connection. 5 | 6 | ## BLE 'Websocket' 7 | 8 | The BLE 'websocket' provides a websocket like service to send and receive data frames. 9 | Each frame starts with 16-bit length (network byte order) indicator followed by the number of bytes indicated 10 | by the length. 11 | 12 | ### Service UUIDs 13 | 14 | The general service UUID is: `0xF700` 15 | 16 | Send data to the Fluxn0de via UUID: `0xF701` 17 | Fluxn0de receives data via unconfirmed writes. It does **NOT** support 18 | prepare/execute. 19 | 20 | Receive data from Fluxn0de via UUID: `0xF702` 21 | Fluxn0de sends data via unconfirmed notifications. 22 | 23 | 24 | ## BLE Battery Service 25 | 26 | Provides the standardized battery service using UUID `0x180F` 27 | -------------------------------------------------------------------------------- /docs/Readme.md: -------------------------------------------------------------------------------- 1 | # Fluxn0de 2 | 3 | Fluxn0de is: 4 | - a tool to explore and prototype LoRa and LoRaWAN applications 5 | - designed to run on battery powered devices 6 | - designed to run on [ESP32 based boards](#boards) 7 | - running JavaScript application (Fluxn0de uses the [Duktape](https://duktape.org/) runtime) 8 | - designed with web application in mind as the main interface (http server + websocket server implementation) 9 | - [BLE](BLE.md) service that (kind of) emulates a websocket connection 10 | - BLE battery level service 11 | 12 | ## JavaScript API 13 | - [Platform](platform.md) Control Wifi, BLE, LEDs, JavaScript runtime,... 14 | - [LoRa](lora.md) LoRa modem API 15 | - [Crypto](crypto.md) Crypto API (tailored towards LoRaWAN) 16 | - [FileSystem](filesystem.md) Access files on the flash filesystem 17 | 18 | ## HTTP API 19 | - [WebService](webservice.md) HTTP API to interact with the webserver (if Wifi is enabled) 20 | 21 | ## BLE 22 | - [BLE](BLE.md) Fluxn0de BLE services 23 | 24 | ## JavaScript Libraries 25 | - [Timer](timer.md) Timer library that provides basic timer functionality 26 | - [LoRaWan](lorawanlib.md) Basic LoRaWAN library 27 | - [Util](util.md) Utility library 28 | 29 | ## Boards 30 | - [Adafruit Huzzah](huzzah.md) 31 | - [Fluxboard](fluxboard.md) 32 | 33 | ## - Getting Started - 34 | 35 | Ideally you have build ESP32 software before and are familiar with the ESP32 SDK 36 | and tools such as `idf.py`. 37 | 38 | ### Config 39 | 40 | Before you flash your Fluxn0de configure the wifi parameters 41 | in [recovery.js](../spiffs_image/recovery.js) and [test.js](../spiffs_image/test.js). 42 | If you forget to do that you can always connect to the Wifi network that is automatically created by Fluxn0de. 43 | The recovery SSID is `fluxn0de` and the password is `fluxn0de`. Recovery is indicated by a blinking red LED. 44 | `192.168.4.1` is the default IP for Fluxn0de in Wifi AP mode. 45 | 46 | ### Building & Flashing 47 | 48 | Setup: 49 | - [Install ESP32 SDK](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) 50 | - check that `idf.py` is in your path 51 | 52 | Build: 53 | - `make` 54 | 55 | Flash (and connect serial console): 56 | - `make flash` 57 | 58 | Connect serial console (and reboot): 59 | - `make monitor` 60 | 61 | ### Examples 62 | 63 | All examples use the [HTTP API](webservice.md) to control the Fluxn0de from a computer. Make sure to take a look. 64 | Check that you have `curl` installed (all examples rely on curl) 65 | 66 | There are a number of examples applications that are installed by default. From easiest to hardest: 67 | 68 | - [test.js](examples/test.md) basic demo app 69 | - [Crypto Test](examples/cryptotest.md) crypto API tests 70 | - [LoRaWanScan](examples/lorawanscan.md) LoRaWAN demo app 71 | - [BLE echo server](examples/ble_echo_server.md) BLE echo server 72 | - [FileSystem Test](examples/filesystem.md) FileSystem API tests 73 | 74 | ### Advanced 75 | 76 | The two JavaScript applications [main.js](../spiffs_image/main.js) and [recovery.js](../spiffs_image/recovery.js) 77 | have special meaning for Fluxn0de. `main.js` is always loaded on startup / reset of the JavaScript runtime. 78 | The idea behind this is that you can load a different application from `main.js` or overwrite `main.js` with your application 79 | to have it start on reset. `recovery.js` is a fallback for when an error crashes the current application. `recovery.js` will be executed 80 | and you will have a chance to use the [HTTP API](webservice.md) to fix your error without flashing. Recovery will enable wifi if it was not already enabled. 81 | 82 | Only modify `recovery.js` if you really think you know what you are doing (otherwise you will be flashing). 83 | -------------------------------------------------------------------------------- /docs/crypto.md: -------------------------------------------------------------------------------- 1 | # Crypto 2 | 3 | Documentation for the Crypto API. 4 | 5 | Some code is based on: https://github.com/nkolban/duktape-esp32.git 6 | under the Apache 2.0 license. 7 | 8 | Most of the crypto APIs use Plain Buffers (https://wiki.duktape.org/howtobuffers2x). 9 | 10 | ## Methods 11 | 12 | - [aes128CbcDec](#aes128cbcdeckeyivdatadata_len) 13 | - [aes128CbcEnc](#aes128cbcenckeyivdatadata_len) 14 | - [aes128Cmac](#aes128cmackeydatacmac) 15 | - [aes128EcbDec](#aes128ecbdeckeydata) 16 | - [aes128EcbEnc](#aes128ecbenckeydata) 17 | - [fillRandom](#fillrandomdata) 18 | 19 | --- 20 | 21 | ## aes128CbcDec(key,iv,data,data_len) 22 | 23 | Decrypt using AES 128 in CBC mode. 24 | 25 | - key 26 | 27 | type: plain buffer 28 | 29 | decryption key 16 bytes 30 | 31 | - iv 32 | 33 | type: plain buffer 34 | 35 | IV 16 bytes 36 | 37 | - data 38 | 39 | type: plain buffer 40 | 41 | data to be decrypted, decryption will be in place and overwrite data. 42 | 43 | - data_len 44 | 45 | type: uint 46 | 47 | length of data 48 | 49 | **Returns:** boolean status 50 | 51 | ``` 52 | 53 | ``` 54 | 55 | ## aes128CbcEnc(key,iv,data,data_len) 56 | 57 | Encrypt using AES 128 in CBC mode. 58 | 59 | - key 60 | 61 | type: plain buffer 62 | 63 | encryption key 16 bytes 64 | 65 | - iv 66 | 67 | type: plain buffer 68 | 69 | IV 16 bytes 70 | 71 | - data 72 | 73 | type: plain buffer 74 | 75 | data to be encrypted, encryption will be in place and overwrite data. Must be be large enough to hold encrypted data. 76 | 77 | - data_len 78 | 79 | type: uint 80 | 81 | length of data 82 | 83 | **Returns:** boolean status 84 | 85 | ``` 86 | 87 | ``` 88 | 89 | ## aes128Cmac(key,data,cmac) 90 | 91 | Compute the AES 128 CMAC of the data. 92 | 93 | - key 94 | 95 | type: plain buffer 96 | 97 | key 16 bytes 98 | 99 | - data 100 | 101 | type: plain buffer 102 | 103 | data to authenticate 104 | 105 | - cmac 106 | 107 | type: plain buffer 108 | 109 | CMAC 16 bytes 110 | 111 | **Returns:** boolean status 112 | 113 | ``` 114 | key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 115 | data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 116 | output = Uint8Array.allocPlain(16); 117 | Crypto.aes128Cmac(key, data, output); 118 | print(Duktape.enc('hex', output)); 119 | 120 | ``` 121 | 122 | ## aes128EcbDec(key,data) 123 | 124 | Decrypt using AES 128 in ECB mode. 125 | 126 | - key 127 | 128 | type: plain buffer 129 | 130 | decryption key 16 bytes 131 | 132 | - data 133 | 134 | type: plain buffer 135 | 136 | data to be decrypted, decryption will be in place and overwrite data. 137 | 138 | **Returns:** boolean status 139 | 140 | ``` 141 | key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 142 | data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 143 | Crypto.aes128EcbDec(key, data); 144 | 145 | ``` 146 | 147 | ## aes128EcbEnc(key,data) 148 | 149 | Encrypt using AES 128 in ECB mode. 150 | 151 | - key 152 | 153 | type: plain buffer 154 | 155 | encryption key 16 bytes 156 | 157 | - data 158 | 159 | type: plain buffer 160 | 161 | data to be encrypted, encryption will be in place and overwrite data. 162 | 163 | **Returns:** boolean status 164 | 165 | ``` 166 | key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 167 | data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 168 | Crypto.aes128EcbEnc(key, data); 169 | 170 | ``` 171 | 172 | ## fillRandom(data) 173 | 174 | Fill buffer with random bytes from the hardware random generator. 175 | 176 | - data 177 | 178 | type: plain buffer 179 | 180 | buffer to fill with random bytes 181 | 182 | ``` 183 | output = Uint8Array.allocPlain(16); 184 | Crypto.fillRandom(output); 185 | print(Duktape.enc('hex', output)); 186 | 187 | ``` 188 | 189 | -------------------------------------------------------------------------------- /docs/examples/ble_echo_server.md: -------------------------------------------------------------------------------- 1 | # BLE Echo Server 2 | 3 | The BLE echo server does exactly what you think it does. 4 | 5 | The echo server will accept the first BLE bonding request. 6 | Everything sent to the [BLE 'websocket' service](../BLE.md) 7 | will be sent back. Checkout: [ble_echo_server.js](../../spiffs_image/ble_echo_server.js) 8 | 9 | ## Running it 10 | 11 | Commands (replace 192.168.4.1 with the IP of your Fluxn0de): 12 | 13 | ``` 14 | curl http://192.168.4.1/control?setload=/ble_echo_server.js 15 | curl http://192.168.4.1/control?reset 16 | ``` 17 | 18 | Now you should see Fluxn0de BLE advertisement packets. 19 | 20 | Connect to the BLE service and start sending data. 21 | 22 | ## BLE Battery Service 23 | 24 | If you have a BLE device that can query standard BLE services 25 | such as the battery service you should be able to get a reading from Fluxn0de. 26 | -------------------------------------------------------------------------------- /docs/examples/cryptotest.md: -------------------------------------------------------------------------------- 1 | # Cryptotest 2 | 3 | This is a simple application to test the [Crypto API](../crypto.md). This will test AES-128 ECB, CMAC via AES-128, and the 4 | random number generator. Checkout: [cryptotest.js](../../spiffs_image/cryptotest.js). 5 | 6 | # Running it 7 | 8 | Commands (replace 192.168.4.1 with the IP of your Fluxn0de): 9 | 10 | ``` 11 | curl http://192.168.4.1/control?setload=/cryptotest.js 12 | curl http://192.168.4.1/control?reset 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/examples/filesystem.md: -------------------------------------------------------------------------------- 1 | # FileSystem Test 2 | 3 | filesystem_test.js is a simple application that shows you how 4 | to use the [FileSystem](../filesystem.md) API. Checkout: [filesystem_test.js](../../spiffs_image/filesystem_test.js). 5 | 6 | # Running it 7 | 8 | Commands (replace 192.168.4.1 with the IP of your Fluxn0de): 9 | 10 | ``` 11 | curl http://192.168.4.1/control?setload=/filesystem_test.js 12 | curl http://192.168.4.1/control?reset 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/examples/lorawanscan.md: -------------------------------------------------------------------------------- 1 | # LoRaWanScan 2 | 3 | This example requires some experience with LoRaWAN and 4 | The Things Network (TTN). 5 | 6 | This example shows how to use: 7 | - LoRa API 8 | - LoRaWan Library 9 | - Timer Library 10 | 11 | By default this demo will receive and decode ClassB Beacons. 12 | No TTN account is required for this. 13 | 14 | To run the full LoRaWANScan example edit lorawanscan.js 15 | and set the mode to 0 and add the device details from your TTN account. 16 | 17 | Requirements: 18 | - [TTN](https://www.thethingsnetwork.org/) account 19 | 20 | The idea behind this example is the following. 21 | A LoRaWAN gateway only listens on a few channels (frequencies). 22 | The LoRaWANScan will cycle thru every channel and send a small 23 | message with the ACK flag set. After sending the message 24 | it will listen on the corresponding down link channel and 25 | record if an ACK was received. At the end it will print all channels ACKs where received on. 26 | 27 | The example is not very useful but provides you with a demo 28 | of two way communication LoRaWAN. 29 | 30 | ## Running it 31 | 32 | Set the DeviceAddr, NetworkKey, and AppKey in [lorawanscan.js](../../spiffs_image/lorawanscan.js) and upload lorawanscan.js to your Fluxn0de. 33 | Make sure to disable the frame counter. 34 | 35 | Commands (replace 192.168.4.1 with the IP of your Fluxn0de): 36 | 37 | ``` 38 | curl http://192.168.4.1/file?lorawanscan.js --data-binary @spiffs_image/lorawanscan.js 39 | curl http://192.168.4.1/control?setload=/lorawanscan.js 40 | curl http://192.168.4.1/control?reset 41 | ``` -------------------------------------------------------------------------------- /docs/examples/test.md: -------------------------------------------------------------------------------- 1 | # Test 2 | 3 | test.js is a simple application that shows you how 4 | to use the [Platform](../platform.md) API and do basic 5 | websocket communication between JavaScript running 6 | in the browser and the JavaScript running on the Fluxn0de. 7 | 8 | Make sure to edit [test.js](../../spiffs_image/test.js) and 9 | configure your wifi SSID and password. You should do this before 10 | flashing the your Fluxn0de (or if you forgot you can use the Fluxn0de recovery wifi access point to upload your modified version of test.js). 11 | 12 | Take a look at the browser code in [index.html](../../spiffs_image/index.html). 13 | 14 | # Running it 15 | 16 | `192.168.4.1` is the default IP for Fluxn0de in Wifi AP mode. 17 | 18 | In a web browser navigate [http://192.168.4.1/](http://192.168.4.1/) and open the JavaScript console. 19 | 20 | You should see things happening in the JavaScript console. 21 | -------------------------------------------------------------------------------- /docs/filesystem.md: -------------------------------------------------------------------------------- 1 | # FileSystem 2 | 3 | Documentation for the FileSystem API. 4 | 5 | Most of the code is either directly copied from or based on: https://github.com/nkolban/duktape-esp32.git 6 | under the Apache 2.0 license. 7 | 8 | ## Methods 9 | 10 | - [close](#closefd) 11 | - [fsSizeTotal](#fssizetotal) 12 | - [fsSizeUsed](#fssizeused) 13 | - [fstat](#fstatfd) 14 | - [listDir](#listdir) 15 | - [open](#openpathflags) 16 | - [read](#readfdbufferbufferoffsetmaxread) 17 | - [seek](#seekfdoffsetwhence) 18 | - [stat](#statpath) 19 | - [unlink](#unlinkpath) 20 | - [write](#writefdbufferbufferoffsetlength) 21 | - [write ](#write fdstring) 22 | 23 | --- 24 | 25 | ## close(fd) 26 | 27 | Close file descriptor. 28 | 29 | - fd 30 | 31 | type: int 32 | 33 | file descriptor 34 | 35 | ``` 36 | 37 | ``` 38 | 39 | ## fsSizeTotal() 40 | 41 | Get total size of the flash filesystem. 42 | 43 | **Returns:** size in bytes of the filesystem 44 | 45 | ``` 46 | 47 | ``` 48 | 49 | ## fsSizeUsed() 50 | 51 | Get used number of bytes of the flash filesystem. 52 | 53 | **Returns:** bytes used of the filesystem 54 | 55 | ``` 56 | 57 | ``` 58 | 59 | ## fstat(fd) 60 | 61 | Stat a file descriptor but inly return size. 62 | 63 | - fd 64 | 65 | type: int 66 | 67 | file descriptor 68 | 69 | **Returns:** {'size': file_size} 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | ## listDir() 76 | 77 | List files in the filesystem. 78 | 79 | **Returns:** array of strings (e.g., ['filename', 'otherfilename']) 80 | 81 | ``` 82 | 83 | ``` 84 | 85 | ## open(path,flags) 86 | 87 | Open file. 88 | 89 | - path 90 | 91 | type: string 92 | 93 | file path 94 | 95 | - flags 96 | 97 | type: string 98 | 99 | flags ('r', 'w', 'a', ...) 100 | 101 | **Returns:** file descriptor (or -1 for error) 102 | 103 | ``` 104 | 105 | ``` 106 | 107 | ## read(fd,buffer,bufferOffset,maxRead) 108 | 109 | Read from a file descriptor. 110 | 111 | - fd 112 | 113 | type: int 114 | 115 | file descriptor 116 | 117 | - buffer 118 | 119 | type: Plain Buffer 120 | 121 | buffer 122 | 123 | - bufferOffset 124 | 125 | type: uint 126 | 127 | offset into the buffer 128 | 129 | - maxRead 130 | 131 | type: uint 132 | 133 | max num bytes to read 134 | 135 | **Returns:** number of bytes read 136 | 137 | ``` 138 | 139 | ``` 140 | 141 | ## seek(fd,offset,whence) 142 | 143 | seek to a specific offset in the file. This implements lseek(2) 144 | 145 | - fd 146 | 147 | type: uint 148 | 149 | file descriptor 150 | 151 | - offset 152 | 153 | type: uint 154 | 155 | offset 156 | 157 | - whence 158 | 159 | type: int 160 | 161 | whence 0 = SET, 1 = CUR, 2 = END 162 | 163 | **Returns:** current position in the file 164 | 165 | ``` 166 | FileSystem.seek(fp, 0, 2); // seek to end of file 167 | 168 | ``` 169 | 170 | ## stat(path) 171 | 172 | Stat a file path but inly return size. 173 | 174 | - path 175 | 176 | type: string 177 | 178 | filepath 179 | 180 | **Returns:** {'size': file_size} 181 | 182 | ``` 183 | 184 | ``` 185 | 186 | ## unlink(path) 187 | 188 | Unlink (delete) file. 189 | 190 | - path 191 | 192 | type: int 193 | 194 | filepath 195 | 196 | ``` 197 | 198 | ``` 199 | 200 | ## write(fd,buffer,bufferOffset,length) 201 | 202 | Write to file descriptor. 203 | 204 | - fd 205 | 206 | type: uint 207 | 208 | file descriptor 209 | 210 | - buffer 211 | 212 | type: Plain Buffer 213 | 214 | data 215 | 216 | - bufferOffset 217 | 218 | type: uint 219 | 220 | offset in the buffer (optional) 221 | 222 | - length 223 | 224 | type: uint 225 | 226 | length (optional) 227 | 228 | **Returns:** num bytes written 229 | 230 | ``` 231 | 232 | ``` 233 | 234 | ## write (fd,string) 235 | 236 | Write string to file descriptor. 237 | 238 | - fd 239 | 240 | type: uint 241 | 242 | file descriptor 243 | 244 | - string 245 | 246 | type: string 247 | 248 | data 249 | 250 | **Returns:** num bytes written 251 | 252 | ``` 253 | 254 | ``` 255 | 256 | -------------------------------------------------------------------------------- /docs/fluxboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crmulliner/fluxnode/fb106e7c86a3e7f67eb6b3fe713136b1cf38ca78/docs/fluxboard.jpeg -------------------------------------------------------------------------------- /docs/fluxboard.md: -------------------------------------------------------------------------------- 1 | # Fluxboard 2 | 3 | Fluxboard is a purpose designed board to run Fluxn0de. 4 | 5 | Fluxboard specs: 6 | - ESP32 WRoom with 16MB 7 | - HopeRF RFM95W 8 | - 2 LEDs (red + green) 9 | - 32khz oscillator as external clock source 10 | - button 11 | - battery charging circuit 12 | - battery charging detection (GPIO) 13 | - battery measurement circuit (with low drain) 14 | - USB (power) connected detection (GPIO) 15 | - ESP32 Uart2 PINs exposed on backside 16 | - GPIO 22,23 (I2C) exposed on backside 17 | - GPIO 25 exposed on backside 18 | - GND + 3.3V exposed on backside 19 | 20 | # Board 21 | 22 | [Fluxboard v0.4 schematics](fluxboard_0.4.pdf) 23 | 24 | Two fluxboards, the board's full PINout documented on the backside. 25 | 26 | ![Fluxboard](fluxboard.jpeg) 27 | -------------------------------------------------------------------------------- /docs/fluxboard_0.4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crmulliner/fluxnode/fb106e7c86a3e7f67eb6b3fe713136b1cf38ca78/docs/fluxboard_0.4.pdf -------------------------------------------------------------------------------- /docs/huzzah.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crmulliner/fluxnode/fb106e7c86a3e7f67eb6b3fe713136b1cf38ca78/docs/huzzah.jpeg -------------------------------------------------------------------------------- /docs/huzzah.md: -------------------------------------------------------------------------------- 1 | # Huzzah 2 | 3 | The Huzzah board basically just combines two Adadfruit boards into one. 4 | 5 | - ESP32 [Huzzah32](https://www.adafruit.com/product/3405) board 6 | - LoRa [Adafruit LoRa Radio FeatherWing - RFM95W 900 MHz - RadioFruit](https://www.adafruit.com/product/3231) (this is the 900Mhz version) 7 | 8 | Huzzah32 specs: 9 | - 4MB flash 10 | - 1 LED 11 | - battery charging circuit 12 | - battery charge measurement (drains battery) 13 | - all GPIOs exposed 14 | 15 | ## LoRa Board wiring 16 | 17 | The LoRa feather has to be wired up in the following way: 18 | - RST to pin D 19 | - CS to pin E 20 | - IRQ/DIO0 to pin C 21 | - DIO1 to pin B 22 | - DIO2 to pin A 23 | 24 | ## Combining the Boards 25 | 26 | Basically stack the Huzzah32 on top of the LoRa board so that the LoRa antenna is on the opposite side of the USB connector. 27 | Also solder an antenna or antenna connector. 28 | 29 | ## Boards 30 | 31 | Left to right: LoRa feather (with wiring), Huzzah32, Huzzah+LoRaFeather with antenna connector (top view, bottom view - with some of the wires solder on the bottom side). 32 | 33 | ![huzzah](huzzah.jpeg) 34 | -------------------------------------------------------------------------------- /docs/lora.md: -------------------------------------------------------------------------------- 1 | # LoRa 2 | 3 | Documentation for the LoRa modem API. 4 | 5 | The library supports every feature needed for LoRaWAN. 6 | Additionally it supports Frequency hopping spread spectrum (FHSS), see: [setHopping](#sethoppinghopshopfreqs). 7 | The library has been tested on [RFM95](https://www.hoperf.com/modules/lora/RFM95.html). 8 | 9 | 10 | ## Methods 11 | 12 | - [loraIdle](#loraidle) 13 | - [loraReceive](#lorareceive) 14 | - [loraSleep](#lorasleep) 15 | - [sendPacket](#sendpacketpacket_bytes) 16 | - [setBW](#setbwbw) 17 | - [setCR](#setcrcr) 18 | - [setCRC](#setcrccrc) 19 | - [setFrequency](#setfrequencyfreq) 20 | - [setHopping](#sethoppinghopshopfreqs) 21 | - [setIQMode](#setiqmodeiq_invert) 22 | - [setPayloadLen](#setpayloadlenlength) 23 | - [setPreambleLen](#setpreamblelenlength) 24 | - [setSF](#setsfsf) 25 | - [setSyncWord](#setsyncwordsyncword) 26 | - [setTxPower](#settxpowerlevel) 27 | 28 | --- 29 | 30 | ## loraIdle() 31 | 32 | Set LoRa modem to idle. 33 | 34 | ``` 35 | LoRa.loraIdle(); 36 | 37 | ``` 38 | 39 | ## loraReceive() 40 | 41 | Set LoRa modem to Receive.Packets will arrive via OnEvent().The type of EventData is plain buffer, see: https://wiki.duktape.org/howtobuffers2x 42 | 43 | ``` 44 | function OnStart() { 45 | LoRa.loraReceive(); 46 | } 47 | function OnEvent(evt) { 48 | if (evt.EventType == 1) { 49 | pkt = evt.EventData; 50 | rssi = evt.LoRaRSSI; 51 | snr = evt.LoRaSNR; 52 | timestamp = evt.TimeStamp; 53 | } 54 | } 55 | 56 | ``` 57 | 58 | ## loraSleep() 59 | 60 | Set LoRa modem to sleep. Lowest power consumption. 61 | 62 | ``` 63 | LoRa.loraSleep(); 64 | 65 | ``` 66 | 67 | ## sendPacket(packet_bytes) 68 | 69 | Send a LoRa packet. LoRa.loraReceive() has to be called before sending. The modem can be put into idle or sleep right after sendPacket returns. For plain buffers see: https://wiki.duktape.org/howtobuffers2x 70 | 71 | - packet_bytes 72 | 73 | type: Plain Buffer 74 | 75 | packet bytes length 1-255 76 | 77 | ``` 78 | LoRa.sendPacket(Uint8Array.plainOf('Hello')); 79 | 80 | ``` 81 | 82 | ## setBW(bw) 83 | 84 | Set the bandwidth. 85 | 86 | - bw 87 | 88 | type: uint 89 | 90 | bandwidth 7.8E3 - 5000E3 91 | 92 | **Returns:** boolean status 93 | 94 | ``` 95 | LoRa.setBW(125E3); 96 | 97 | ``` 98 | 99 | ## setCR(cr) 100 | 101 | Set the coding rate. 102 | 103 | - cr 104 | 105 | type: uint 106 | 107 | coding rate 5 - 8 108 | 109 | **Returns:** boolean status 110 | 111 | ``` 112 | LoRa.setCR(5); 113 | 114 | ``` 115 | 116 | ## setCRC(crc) 117 | 118 | Enable / Disable packet CRC. 119 | 120 | - crc 121 | 122 | type: boolean 123 | 124 | enable = True 125 | 126 | **Returns:** boolean status 127 | 128 | ``` 129 | LoRa.setCRC(true); 130 | 131 | ``` 132 | 133 | ## setFrequency(freq) 134 | 135 | Set the frequency. 136 | 137 | - freq 138 | 139 | type: double 140 | 141 | 902.0 - 928.0 (US-915) 142 | 143 | **Returns:** boolean status 144 | 145 | ``` 146 | LoRa.setFrequency(902.3); 147 | 148 | ``` 149 | 150 | ## setHopping(hops,hopfreqs) 151 | 152 | Configure the frequency hopping functionality. Disable frequency hopping by setting hops to `0`. 153 | 154 | - hops 155 | 156 | type: uint 157 | 158 | number of hops, 0 = don't use frequency hopping 159 | 160 | - hopfreqs 161 | 162 | type: double[] 163 | 164 | the hopping frequencies, needs to contain at least 1 entry if hops > 0 165 | 166 | **Returns:** boolean status 167 | 168 | ``` 169 | // 5 hops 170 | LoRa.setHopping(5, [905,906,907,908,909]); 171 | // disable hopping 172 | LoRa.setHopping(0, []); 173 | 174 | ``` 175 | 176 | ## setIQMode(iq_invert) 177 | 178 | Enable / Disable IQ invert. 179 | 180 | - iq_invert 181 | 182 | type: boolean 183 | 184 | enable IQ invert 185 | 186 | ``` 187 | LoRa.setIQMode(true); 188 | 189 | ``` 190 | 191 | ## setPayloadLen(length) 192 | 193 | Set payload length. If length is set to 0 the header will contain the payload length for each packet. 194 | 195 | - length 196 | 197 | type: uint 198 | 199 | 0 - 255 200 | 201 | **Returns:** boolean status 202 | 203 | ``` 204 | LoRa.setPayloadLen(0); 205 | 206 | ``` 207 | 208 | ## setPreambleLen(length) 209 | 210 | Set the length of the preamble in symbols. 211 | 212 | - length 213 | 214 | type: uint 215 | 216 | number of symbols: 0 - 65335 217 | 218 | **Returns:** boolean status 219 | 220 | ``` 221 | LoRa.setPreambleLen(8); 222 | 223 | ``` 224 | 225 | ## setSF(sf) 226 | 227 | Set the spreading factor. 228 | 229 | - sf 230 | 231 | type: uint 232 | 233 | spreading factor: 6 - 12 234 | 235 | **Returns:** boolean status 236 | 237 | ``` 238 | LoRa.setSF(7); 239 | 240 | ``` 241 | 242 | ## setSyncWord(syncword) 243 | 244 | Set the LoRa sync word. 245 | 246 | - syncword 247 | 248 | type: uint8 249 | 250 | sync word 251 | 252 | ``` 253 | LoRa.setSyncWord(0x42); 254 | 255 | ``` 256 | 257 | ## setTxPower(level) 258 | 259 | Set LoRa modem TX power level. 260 | 261 | - level 262 | 263 | type: uint 264 | 265 | power level 2 - 17 (17 = PA boost). 266 | 267 | **Returns:** boolean status 268 | 269 | ``` 270 | LoRa.setTxPower(17); 271 | 272 | ``` 273 | 274 | -------------------------------------------------------------------------------- /docs/lorawanlib.md: -------------------------------------------------------------------------------- 1 | # LoraWanPacket 2 | 3 | This is a very basic LoRaWAN library. 4 | 5 | Requirements: util.js 6 | 7 | `Platform.loadLibrary('/util.js');` 8 | 9 | Supported features: 10 | - Activation Method: ABP 11 | - send confirmed / un-confirmed message 12 | - receive confirmed / un-confirmed message 13 | - encode/decode ACK message 14 | - decoding MAC commands (FOpts) 15 | - send FOpts 16 | 17 | Unsupported: 18 | - Activation Method: OTAA 19 | 20 | ## Methods 21 | 22 | - [LoraWanPacket](#lorawanpacketdevaddrnwkkeyappkey) 23 | - [LoraWanPacket.ackPacket](#lorawanpacket.ackpacketfportfcnt) 24 | - [LoraWanPacket.confirmedDown](#lorawanpacket.confirmeddownpayloadfportfcntfopts) 25 | - [LoraWanPacket.confirmedUp](#lorawanpacket.confirmeduppayloadfportfcntfopts) 26 | - [LoraWanPacket.foptsDecode](#lorawanpacket.foptsdecodedata) 27 | - [LoraWanPacket.makePacketFromPayLoad](#lorawanpacket.makepacketfrompayloadpayloadpkttypefportfcrtlfcntfoptsdir) 28 | - [LoraWanPacket.parseDownPacket](#lorawanpacket.parsedownpacketpacket) 29 | - [LoraWanPacket.unConfirmedDown](#lorawanpacket.unconfirmeddownpayloadfportfcntfopts) 30 | - [LoraWanPacket.unConfirmedUp](#lorawanpacket.unconfirmeduppayloadfportfcntfopts) 31 | - [loraWanBeaconDecode](#lorawanbeacondecodepktregion) 32 | - [loraWanBeaconGetNextChannel](#lorawanbeacongetnextchannel) 33 | - [loraWanBeaconListen](#lorawanbeaconlistenchannelregion) 34 | - [loraWanChannel915](#lorawanchannel915channeldirection) 35 | - [loraWanUpDownChannel915](#lorawanupdownchannel915channel) 36 | 37 | --- 38 | 39 | ## LoraWanPacket(devAddr,nwkKey,appKey) 40 | 41 | Create a LoraWanPacket instance using device address, network key, and application key. 42 | All packet operations will use the address and keys to encode and decode packets. 43 | 44 | Configure the frame counter length by setting member variable fcntLen to 2 for 16bit and 4 fore 32bit. 45 | The default is 16bit. The length is not used by the library, the user has to manage the fcnt. 46 | 47 | The instance will contain the following members: 48 | ``` 49 | { 50 | devAddr: plainBuffer, 51 | nwkKey: plainBuffer, 52 | appKey: plainBuffer, 53 | fcntLen: int, 54 | makePacketFromPayLoad: function, 55 | unConfirmedUp: function, 56 | confirmedUp: function, 57 | ackPacket: function, 58 | parseDownPacket: function, 59 | } 60 | ``` 61 | 62 | 63 | - devAddr 64 | 65 | type: plain buffer 66 | 67 | device address 68 | 69 | - nwkKey 70 | 71 | type: plain buffer 72 | 73 | network key 74 | 75 | - appKey 76 | 77 | type: plain buffer 78 | 79 | application key 80 | 81 | **Returns:** LoraWanPacket object 82 | 83 | ``` 84 | addr = hexToBin('AABBCCDD'); 85 | appkey = hexToBin('0011223344556677889900aabbccddee'); 86 | nwkkey = hexToBin('ffeeddccbbaa00998877665544332211'); 87 | var lwp = LoraWanPacket(addr, nwkkey, appkey); 88 | 89 | ``` 90 | 91 | ## LoraWanPacket.ackPacket(fport,fcnt) 92 | 93 | Create a UpACK packet. 94 | 95 | - fport 96 | 97 | type: uint 98 | 99 | fport 100 | 101 | - fcnt 102 | 103 | type: uint 104 | 105 | frame counter 106 | 107 | **Returns:** loraWan packet as a plain buffer 108 | 109 | ``` 110 | pkt = lwp.ackPacket(1, 1); 111 | 112 | ``` 113 | 114 | ## LoraWanPacket.confirmedDown(payload,fport,fcnt,fopts) 115 | 116 | Create a ConfirmedDown packet. 117 | 118 | - payload 119 | 120 | type: plain buffer 121 | 122 | payload 123 | 124 | - fport 125 | 126 | type: uint 127 | 128 | fport 129 | 130 | - fcnt 131 | 132 | type: uint 133 | 134 | frame counter 135 | 136 | - fopts 137 | 138 | type: plain buffer 139 | 140 | fopts 141 | 142 | **Returns:** loraWan packet as a plain buffer 143 | 144 | ``` 145 | var enc = new TextEncoder(); 146 | var payload = enc.encode('hi'); 147 | var vport = 1; 148 | var fcnt = 1; 149 | pkt = lwp.confirmedDown(payload, fport, fcnt, []); 150 | 151 | ``` 152 | 153 | ## LoraWanPacket.confirmedUp(payload,fport,fcnt,fopts) 154 | 155 | Create a ConfirmedUp packet. 156 | 157 | - payload 158 | 159 | type: plain buffer 160 | 161 | payload 162 | 163 | - fport 164 | 165 | type: uint 166 | 167 | fport 168 | 169 | - fcnt 170 | 171 | type: uint 172 | 173 | frame counter 174 | 175 | - fopts 176 | 177 | type: plain buffer 178 | 179 | fopts 180 | 181 | **Returns:** loraWan packet as a plain buffer 182 | 183 | ``` 184 | var enc = new TextEncoder(); 185 | var payload = enc.encode('hi'); 186 | var vport = 1; 187 | var fcnt = 1; 188 | pkt = lwp.confirmedUp(payload, fport, fcnt, []); 189 | 190 | ``` 191 | 192 | ## LoraWanPacket.foptsDecode(data) 193 | 194 | parse FOpts to get MAC commands (not all commands are fully decoded) 195 | 196 | - data 197 | 198 | type: plain buffer 199 | 200 | fopts 201 | 202 | **Returns:** array of MAC commands 203 | 204 | ``` 205 | var mac = lwp.foptsDecode(pkt.fopts); 206 | // print MAC command name 207 | print(mac[0].cmd + ' 208 | '); 209 | 210 | ``` 211 | 212 | ## LoraWanPacket.makePacketFromPayLoad(payload,pkttype,fport,fcrtl,fcnt,fopts,dir) 213 | 214 | Create a LoraWan packet. 215 | 216 | - payload 217 | 218 | type: plain buffer 219 | 220 | payload 221 | 222 | - pkttype 223 | 224 | type: uint8 225 | 226 | packet type 227 | 228 | - fport 229 | 230 | type: uint 231 | 232 | fport 233 | 234 | - fcrtl 235 | 236 | type: plain buffer 237 | 238 | fcrtl 239 | 240 | - fcnt 241 | 242 | type: uint16 243 | 244 | frame counter 245 | 246 | - fopts 247 | 248 | type: plain buffer 249 | 250 | fopts 251 | 252 | - dir 253 | 254 | type: int 255 | 256 | direction 0 = up, 1 = down 257 | 258 | **Returns:** loraWan packet as a plain buffer 259 | 260 | ``` 261 | var enc = new TextEncoder(); 262 | var payload = enc.encode('hi'); 263 | var vport = 1; 264 | var fcnt = 1; 265 | // make un-confirmed Up 266 | pkt = lwp.makePacketFromPayLoad(payload, 0x40, fport, 0, fcnt, [], 0); 267 | 268 | ``` 269 | 270 | ## LoraWanPacket.parseDownPacket(packet) 271 | 272 | Decode a received LoraWan `down` packet. 273 | 274 | The decoded object packet contains the following elements: 275 | ``` 276 | { 277 | error: boolean, 278 | devAddr: plainBuffer, 279 | confirmed: boolean, 280 | payload: plainBuffer, 281 | fport: int, 282 | fcnt: int, 283 | fopts: plainBuffer, 284 | ack: boolean, 285 | fpending: boolean, 286 | adr: boolean, 287 | adrackreq: boolean, 288 | micVerified: boolean, 289 | } 290 | ``` 291 | 292 | 293 | - packet 294 | 295 | type: plain buffer 296 | 297 | packet 298 | 299 | **Returns:** decoded packet object 300 | 301 | ``` 302 | decodedPkt = lwp.parseDownPacket(pkt) 303 | if (!decodedPkt.micVerified) { 304 | print('packet integrity check failed\n'); 305 | } 306 | 307 | ``` 308 | 309 | ## LoraWanPacket.unConfirmedDown(payload,fport,fcnt,fopts) 310 | 311 | Create a UnConfirmedDown packet. 312 | 313 | - payload 314 | 315 | type: plain buffer 316 | 317 | payload 318 | 319 | - fport 320 | 321 | type: uint 322 | 323 | fport 324 | 325 | - fcnt 326 | 327 | type: uint 328 | 329 | frame counter 330 | 331 | - fopts 332 | 333 | type: plain buffer 334 | 335 | fopts 336 | 337 | **Returns:** loraWan packet as a plain buffer 338 | 339 | ``` 340 | var enc = new TextEncoder(); 341 | var payload = enc.encode('hi'); 342 | var vport = 1; 343 | var fcnt = 1; 344 | pkt = lwp.unConfirmedDown(payload, fport, fcnt, []); 345 | 346 | ``` 347 | 348 | ## LoraWanPacket.unConfirmedUp(payload,fport,fcnt,fopts) 349 | 350 | Create a UnConfirmedUp packet. 351 | 352 | - payload 353 | 354 | type: plain buffer 355 | 356 | payload 357 | 358 | - fport 359 | 360 | type: uint 361 | 362 | fport 363 | 364 | - fcnt 365 | 366 | type: uint 367 | 368 | frame counter 369 | 370 | - fopts 371 | 372 | type: plain buffer 373 | 374 | fopts 375 | 376 | **Returns:** loraWan packet as a plain buffer 377 | 378 | ``` 379 | var enc = new TextEncoder(); 380 | var payload = enc.encode('hi'); 381 | var vport = 1; 382 | var fcnt = 1; 383 | pkt = lwp.unConfirmedUp(payload, fport, fcnt, []); 384 | 385 | ``` 386 | 387 | ## loraWanBeaconDecode(pkt,region) 388 | 389 | Decode a LoRaWAN Class B beacon. 390 | 391 | The beacon time uses GPS time and is 17 seconds ahead of UTC. 392 | See: https://www.usno.navy.mil/USNO/time/master-clock/leap-seconds 393 | 394 | The beacon object contains the following members: 395 | ``` 396 | { 397 | error: boolean, 398 | msg: string, // if error == true 399 | region: int, 400 | time: uint, // UTC 401 | time_stamp: uint, // time stamp from beacon 402 | time_crc: boolean, 403 | info: uint8, 404 | info_crc: boolean, 405 | lat: int, 406 | lon: int, 407 | } 408 | ``` 409 | 410 | 411 | - pkt 412 | 413 | type: plain buffer 414 | 415 | packet buffer 416 | 417 | - region 418 | 419 | type: int 420 | 421 | LoRaWAN region: 0 = us-915 422 | 423 | **Returns:** Beacon object 424 | 425 | ``` 426 | var beacon = loraWanBeaconDecode(pkt, 0) 427 | print(beacon.time); 428 | 429 | ``` 430 | 431 | ## loraWanBeaconGetNextChannel() 432 | 433 | Get the channel number for the next beacon and seconds until the beacon arrives on the channel. 434 | This relies on the device having a accurate system time. 435 | 436 | The next channel object contains the following members: 437 | 438 | ``` 439 | { 440 | channel: int, // next channel a beacon will arrive 441 | nextBeaconSeconds: int, // number of seconds before beacon arrives 442 | } 443 | ``` 444 | 445 | 446 | **Returns:** next channel object 447 | 448 | ``` 449 | var nextBeaconChannel = loraWanBeaconGetNextChannel(); 450 | print(nextBeaconChannel.channel); 451 | 452 | ``` 453 | 454 | ## loraWanBeaconListen(channel,region) 455 | 456 | Configure the LoRa modem for receiving class B beacons on a specific channel. 457 | 458 | 459 | - channel 460 | 461 | type: int 462 | 463 | channel 0-7 464 | 465 | - region 466 | 467 | type: int 468 | 469 | LoRaWAN region: 0 = us-915 470 | 471 | **Returns:** boolean 472 | 473 | ``` 474 | loraWanBeaconListen(0, 0); 475 | 476 | ``` 477 | 478 | ## loraWanChannel915(channel,direction) 479 | 480 | Get the US-915 frequency for a given channel and direction 481 | 482 | - channel 483 | 484 | type: int 485 | 486 | 0 - 71 487 | 488 | - direction 489 | 490 | type: int 491 | 492 | 0 = up, 1 = down 493 | 494 | **Returns:** frequency 495 | 496 | ``` 497 | var freq = loraWanChannel915(1, 0); 498 | 499 | ``` 500 | 501 | ## loraWanUpDownChannel915(channel) 502 | 503 | Get the up, down, and 2nd down frequency for a given channel in US-915. 504 | The channel object contains the following members: 505 | ``` 506 | { 507 | channel: uint, 508 | sf: uint, 509 | bw: uint, 510 | upFreq: double, 511 | downFreq: double, 512 | down2Freq: double, 513 | } 514 | ``` 515 | 516 | 517 | - channel 518 | 519 | type: int 520 | 521 | 0 - 71 522 | 523 | **Returns:** channel object for given channel 524 | 525 | ``` 526 | var loraRadioSettings = loraWanUpDownChannel915(1); 527 | 528 | ``` 529 | 530 | -------------------------------------------------------------------------------- /docs/platform.md: -------------------------------------------------------------------------------- 1 | # Platform 2 | 3 | Documentation for the Platform API. 4 | 5 | Every application needs to contain three functions that are called by the runtime. 6 | 7 | ## OnStart() 8 | Is called once when the application is started. 9 | OnStart can be used to initialize your application. 10 | 11 | ## OnEvent(event) 12 | OnEvent is called to deliver events to the application. 13 | Events such as LoRa packet was received, a websocket/ble frame was received, 14 | a button was pressed. 15 | 16 | **Event Object** 17 | 18 | The event object has a number of members. 19 | EventType is always present. The other members are only present for 20 | their specific event type. 21 | 22 | ``` 23 | Event = { 24 | EventType: uint, 25 | EventData: Uint8Array.plain(), 26 | LoRaRSSI: int, 27 | LoRaSNR: int, 28 | TimeStamp: uint, 29 | NumPress: uint, 30 | } 31 | ``` 32 | 33 | **EventType (int)** identifies the type of event. 34 | ui refer to messages received from either 35 | the websocket connection or the BLE connection. 36 | ui_connect/ui_disconnect refers to the UI connection 37 | being established or torn down. 38 | 39 | ``` 40 | function EventName(event) { 41 | var et = ['lora', 'ui', 'ui_connected', 'ui_disconnected', 'button']; 42 | return et[event.EventType]; 43 | } 44 | ``` 45 | 46 | **EventData (Plain Buffer)** contains the event payload 47 | in the case of ui or lora events. See: https://wiki.duktape.org/howtobuffers2x 48 | 49 | **LoRaRSSI (uint)** is set for lora events 50 | and indicates the RSSI of the packet. 51 | 52 | **LoRaSNR (uint)** is set for lora events 53 | and indicates the SNR of the packet. 54 | 55 | **TimeStamp (uint)** is set for lora events 56 | and indicates the time of when the packet was received. 57 | 58 | **NumPress (uint)** is set for button events 59 | and indicates how often the button was pressed within the 3 seconds 60 | frame after the first press. 61 | 62 | ## OnTimer() 63 | is called after the timeout configured via Platform.setTimer() has expired. 64 | 65 | ## Example 66 | 67 | A basic application. 68 | 69 | ``` 70 | function OnStart() { 71 | 72 | } 73 | 74 | function OnEvent(event) { 75 | // incoming websocket / BLE data 76 | if (event.EventType == 1) { 77 | var dec = new TextDecoder(); 78 | // parse JSON object 79 | var cmd = JSON.parse(dec.decode(evt.EventData)); 80 | } 81 | } 82 | 83 | ``` 84 | 85 | ## Member Variables 86 | - BoardName (name of the board) 87 | - Version (fluxnode software version) 88 | - ButtonNum (number of buttons: 0-1) 89 | - ButtonPressedDuringBoot (was button pressed during boot: 0 = no, 1 = yes) 90 | - LEDNum (number of LEDs: 0-2) 91 | - FlashSize (size of flash chip in bytes) 92 | 93 | Example: 94 | ``` 95 | print('running on: ' + Platform.BoardName); 96 | ``` 97 | 98 | ## Duktape/ESP32 Notes 99 | 100 | ### Random Numbers 101 | 102 | The ESP32 provides a [hardware random number generator](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system.html#_CPPv410esp_randomv). 103 | We use the ESP32's hw random number generator as Duktape's 104 | random number generator. When you call `Math.random()` you get 105 | the output of the ESP32 hw random generator. 106 | 107 | 108 | ## Methods 109 | 110 | - [bleBondAllow](#blebondallowbond) 111 | - [bleBondGetDevices](#blebondgetdevices) 112 | - [bleBondRemove](#blebondremovebaddr) 113 | - [bleGetDeviceAddr](#blegetdeviceaddr) 114 | - [bleSetPasscode](#blesetpasscodepass) 115 | - [getBatteryMVolt](#getbatterymvolt) 116 | - [getBatteryPercent](#getbatterypercent) 117 | - [getBatteryStatus](#getbatterystatus) 118 | - [getBootime](#getbootime) 119 | - [getClientConnected](#getclientconnected) 120 | - [getClientID](#getclientid) 121 | - [getConnectivity](#getconnectivity) 122 | - [getFreeHeap](#getfreeheap) 123 | - [getFreeInternalHeap](#getfreeinternalheap) 124 | - [getLocalIP](#getlocalip) 125 | - [getUSBStatus](#getusbstatus) 126 | - [gpioRead](#gpioreadgpionum) 127 | - [gpioWrite](#gpiowritegpionumvalue) 128 | - [isButtonPressed](#isbuttonpressed) 129 | - [loadLibrary](#loadlibraryfilename) 130 | - [print](#printvar) 131 | - [reboot](#reboot) 132 | - [reset](#reset) 133 | - [sendEvent](#sendeventevent_typedata) 134 | - [setConnectivity](#setconnectivitycon) 135 | - [setLED](#setledled_idonoff) 136 | - [setLEDBlink](#setledblinkled_idratebrightness) 137 | - [setLoadFileName](#setloadfilenamefilename) 138 | - [setSystemTime](#setsystemtimetime) 139 | - [setTimer](#settimertimeout) 140 | - [stopLEDBlink](#stopledblinkled_id) 141 | - [wifiConfigure](#wificonfiguressidpasswordmodeweb_mode) 142 | - [wifiGetMacAddr](#wifigetmacaddr) 143 | 144 | --- 145 | 146 | ## bleBondAllow(bond) 147 | 148 | Set BLE bond mode. 149 | 150 | - bond 151 | 152 | type: uint 153 | 154 | allow = 1, disallow = 0 155 | 156 | **Returns:** boolean status 157 | 158 | ``` 159 | // allow the next BLE bonding attempt 160 | Platform.bleBondAllow(1); 161 | 162 | ``` 163 | 164 | ## bleBondGetDevices() 165 | 166 | Get BLE addresses that are currently bonded. This returns a string array. 167 | 168 | **Returns:** array of strings 169 | 170 | ``` 171 | var devs = Platform.bleBondGetDevices(); 172 | print(devs); 173 | 174 | ``` 175 | 176 | ## bleBondRemove(baddr) 177 | 178 | Remove bonding for a BLE MAC. 179 | 180 | - baddr 181 | 182 | type: string 183 | 184 | BLE MAC to remove 185 | 186 | **Returns:** boolean status 187 | 188 | ``` 189 | Platform.bleBondRemove('00:11:22:33:44:55'); 190 | 191 | ``` 192 | 193 | ## bleGetDeviceAddr() 194 | 195 | Get BLE local device address 196 | 197 | **Returns:** string BLE address 198 | 199 | ``` 200 | var dev = Platform.bleGetDeviceAddr(); 201 | 202 | ``` 203 | 204 | ## bleSetPasscode(pass) 205 | 206 | Set BLE passcode. 207 | 208 | - pass 209 | 210 | type: uint 211 | 212 | BLE pin 213 | 214 | **Returns:** boolean status 215 | 216 | ``` 217 | Platform.bleSetPasscode(1234); 218 | 219 | ``` 220 | 221 | ## getBatteryMVolt() 222 | 223 | Get the battery charge level in mV. 224 | 225 | **Returns:** number 226 | 227 | ``` 228 | Platform.getBatteryMVolt(); 229 | 230 | ``` 231 | 232 | ## getBatteryPercent() 233 | 234 | Get the battery charge level in percent. 235 | 236 | **Returns:** number 237 | 238 | ``` 239 | Platform.getBatteryPercent(); 240 | 241 | ``` 242 | 243 | ## getBatteryStatus() 244 | 245 | Get the battery charging status. 0 = draining, 1 = charging 246 | 247 | **Returns:** number 248 | 249 | ``` 250 | if (Platform.getBatteryStatus() == 1) { 251 | print('battery is charging\n'); 252 | } 253 | 254 | ``` 255 | 256 | ## getBootime() 257 | 258 | get boot time 259 | 260 | **Returns:** time of system boot up 261 | 262 | ``` 263 | 264 | ``` 265 | 266 | ## getClientConnected() 267 | 268 | Get the UI client status. This is either the websocket or the BLE connection. 0 = disconnected, 1 = connected 269 | 270 | **Returns:** number 271 | 272 | ``` 273 | if (Platform.getClientConnected() == 1) { 274 | print('client is connected\n'); 275 | } 276 | 277 | ``` 278 | 279 | ## getClientID() 280 | 281 | Get the ID of the client. Either an IP address or a BLE MAC, 'none' no client connected. 282 | 283 | **Returns:** string 284 | 285 | ``` 286 | print('client ID is: ' + Platform.getClientID() + '\n'); 287 | 288 | ``` 289 | 290 | ## getConnectivity() 291 | 292 | Get the connectivity mode. 0 = wifi/ble off, 1 = wifi, 2 = BLE. 293 | 294 | **Returns:** number 295 | 296 | ``` 297 | if (Platform.getConnectivity() == 0) { 298 | print('disconnected from the world\n'); 299 | } 300 | 301 | ``` 302 | 303 | ## getFreeHeap() 304 | 305 | Returns number of free bytes on the heap. 306 | 307 | **Returns:** number 308 | 309 | ``` 310 | print('we got ' + Platform.getFreeHeap()/1024 + 'K free heap'); 311 | 312 | ``` 313 | 314 | ## getFreeInternalHeap() 315 | 316 | Returns number of free bytes on the internal heap. 317 | 318 | **Returns:** number 319 | 320 | ``` 321 | print('we got ' + Platform.getFreeInternalHeap()/1024 + 'K free internal heap'); 322 | 323 | ``` 324 | 325 | ## getLocalIP() 326 | 327 | Get the local IP of the board. If connectivity is Wifi. 328 | 329 | **Returns:** string 330 | 331 | ``` 332 | print('my IP is: ' + Platform.getLocalIP() + '\n'); 333 | 334 | ``` 335 | 336 | ## getUSBStatus() 337 | 338 | Get the the USB connection status. 0 = disconnected, 1 = connected 339 | 340 | **Returns:** number 341 | 342 | ``` 343 | Platform.getUSBStats(); 344 | 345 | ``` 346 | 347 | ## gpioRead(gpionum) 348 | 349 | Read from GPIO. This also configures the given GPIO as an input GPIO. Use with caution! 350 | 351 | - gpionum 352 | 353 | type: uint 354 | 355 | GPIO number 356 | 357 | **Returns:** number 358 | 359 | ``` 360 | Platform.gpioRead(14); 361 | 362 | ``` 363 | 364 | ## gpioWrite(gpionum,value) 365 | 366 | Write to GPIO. This also configures the given GPIO as an output GPIO. Use with caution! 367 | 368 | - gpionum 369 | 370 | type: uint 371 | 372 | GPIO number 373 | 374 | - value 375 | 376 | type: uint 377 | 378 | 0 or 1 379 | 380 | ``` 381 | Platform.gpioWrite(15, 1); 382 | 383 | ``` 384 | 385 | ## isButtonPressed() 386 | 387 | Returns True if the button is pressed while this function is called. 388 | 389 | **Returns:** boolean status 390 | 391 | ``` 392 | if (Platform.isButtonPressed()) { 393 | print('somebody is pressing the button right now'); 394 | } 395 | 396 | ``` 397 | 398 | ## loadLibrary(filename) 399 | 400 | Load JavaScript code into current application. 401 | 402 | - filename 403 | 404 | type: string 405 | 406 | filename to load 407 | 408 | **Returns:** boolean status 409 | 410 | ``` 411 | Platform.loadLibrary('/timer.js'); 412 | 413 | ``` 414 | 415 | ## print(var) 416 | 417 | Print to the console. Note: Print is not part of the Platform namespace. 418 | 419 | - var 420 | 421 | type: var 422 | 423 | multiple arguments 424 | 425 | ``` 426 | print('Hi\n'); 427 | 428 | ``` 429 | 430 | ## reboot() 431 | 432 | Reboot the board. 433 | 434 | ``` 435 | Platform.reboot(); 436 | 437 | ``` 438 | 439 | ## reset() 440 | 441 | Reset the JavaScript runtime. This will try to load and run `main.js`. If `main.js` can't be run, try to run `recovery.js`. The idea behind `recovery.js` is that you have a fallback application that will allow you to recover from a error without power cycle and/or flashing the board. 442 | 443 | ``` 444 | Platform.reset(); 445 | 446 | ``` 447 | 448 | ## sendEvent(event_type,data) 449 | 450 | Send event to the connected UI client. Only UI event (1) is currently supported. 451 | 452 | - event_type 453 | 454 | type: uint 455 | 456 | 1 = UI event (send to websocket or BLE) 457 | 458 | - data 459 | 460 | type: plain buffer 461 | 462 | data to send (see: https://wiki.duktape.org/howtobuffers2x) 463 | 464 | **Returns:** boolean status 465 | 466 | ``` 467 | var x = { 468 | test: 'test string', 469 | num: 1337, 470 | }; 471 | Platform.sendEvent(1, Uint8Array.allocPlain(JSON.stringify(x))); 472 | 473 | ``` 474 | 475 | ## setConnectivity(con) 476 | 477 | Set connectivity. 0 = off, 1 = wifi, 2 = BLE. 478 | 479 | - con 480 | 481 | type: uint 482 | 483 | connectivity mode 484 | 485 | **Returns:** boolean status 486 | 487 | ``` 488 | // switch to BLE 489 | Platform.setConnectivity(2); 490 | 491 | ``` 492 | 493 | ## setLED(led_id,onoff) 494 | 495 | Set an LED on/off. 496 | 497 | - led_id 498 | 499 | type: uint 500 | 501 | LED id (0-1) 502 | 503 | - onoff 504 | 505 | type: uint 506 | 507 | on (1) / off (0) 508 | 509 | ``` 510 | Platform.setLED(0, 1); 511 | 512 | ``` 513 | 514 | ## setLEDBlink(led_id,rate,brightness) 515 | 516 | Set an LED to blink at a given rate and brightness. 517 | 518 | - led_id 519 | 520 | type: uint 521 | 522 | LED id (0-1) 523 | 524 | - rate 525 | 526 | type: uint 527 | 528 | 0-4 529 | 530 | - brightness 531 | 532 | type: double 533 | 534 | 0.0-1.0 535 | 536 | ``` 537 | Platform.setLEDBlink(0, 2, 0.5); 538 | 539 | ``` 540 | 541 | ## setLoadFileName(filename) 542 | 543 | Set the file to be loaded the next time reset() is called. 544 | 545 | - filename 546 | 547 | type: string 548 | 549 | filename to load 550 | 551 | ``` 552 | // stop current application and load test.js 553 | Platform.setLoadFileName('/test.js'); 554 | Platform.reset(); 555 | 556 | ``` 557 | 558 | ## setSystemTime(time) 559 | 560 | Set the system time. 561 | 562 | - time 563 | 564 | type: uint 565 | 566 | time (unix epoch) 567 | 568 | ``` 569 | Platform.setSystemTime(1580515200); 570 | 571 | ``` 572 | 573 | ## setTimer(timeout) 574 | 575 | Set timeout in milliseconds after which OnTimer() is called. The shortest timer is `10` milliseconds. Setting a timer of 0 will disable the timer. 576 | 577 | - timeout 578 | 579 | type: uint 580 | 581 | milliseconds, 0 = disable timer 582 | 583 | **Returns:** boolean status 584 | 585 | ``` 586 | // timer will go off in 5 seconds 587 | Platform.setTimer(5000); 588 | 589 | function OnTimer() { 590 | print('timer was called\n'); 591 | } 592 | 593 | ``` 594 | 595 | ## stopLEDBlink(led_id) 596 | 597 | Stop an LED blinking. 598 | 599 | - led_id 600 | 601 | type: uint 602 | 603 | LED id (0-1) 604 | 605 | ``` 606 | Platform.stopLEDBlink(0); 607 | 608 | ``` 609 | 610 | ## wifiConfigure(ssid,password,mode,web_mode) 611 | 612 | Configure Wifi. The web_mode can be used to lock down the web API. 613 | 614 | - ssid 615 | 616 | type: string 617 | 618 | WIFI SSID 619 | 620 | - password 621 | 622 | type: string 623 | 624 | WPA password (min 8 characters) 625 | 626 | - mode 627 | 628 | type: uint 629 | 630 | 0 = AP, 1 = client 631 | 632 | - web_mode 633 | 634 | type: uint 635 | 636 | webserver API readonly = 1, read/write = 0 637 | 638 | **Returns:** boolean status 639 | 640 | ``` 641 | Platform.setConnectivity(1); 642 | // start WIFI AP and set web mode to R/W 643 | var success = Platform.wifiConfigure('fluxnode','fluxulf', 0, 1); 644 | if (success == false) { 645 | print('wifi start failed, check SSID and password\n'); 646 | } 647 | 648 | ``` 649 | 650 | ## wifiGetMacAddr() 651 | 652 | Get WIFI MAC address 653 | 654 | **Returns:** string MAC address 655 | 656 | ``` 657 | var dev = Platform.wifiGetMacAddr(); 658 | 659 | ``` 660 | 661 | -------------------------------------------------------------------------------- /docs/timer.md: -------------------------------------------------------------------------------- 1 | # Timer 2 | 3 | This is a basic timer library. 4 | 5 | The library is designed around `OnTimer()` and `Platform.setTimeout()`. 6 | An application that uses this library should **NOT** have its own implementation 7 | of `OnTimer()`. 8 | 9 | The shortest timeout/interval is 10 milliseconds. 10 | 11 | ## Methods 12 | 13 | - [OnTimer](#ontimer) 14 | - [cancelTimer](#canceltimertimerid) 15 | - [intervalTimeout](#intervaltimeoutfuncinterval) 16 | - [setTimeout](#settimeoutfuncdelay) 17 | 18 | --- 19 | 20 | ## OnTimer() 21 | 22 | `OnTimer()` function that is provided by this library. 23 | 24 | When using this library the main application cannot contain a OnTimer() function. 25 | 26 | 27 | ``` 28 | 29 | ``` 30 | 31 | ## cancelTimer(timerID) 32 | 33 | Delete timer. 34 | 35 | - timerID 36 | 37 | type: timerID 38 | 39 | ID returned by setTimeout or intervalTimer 40 | 41 | ``` 42 | var id = setTimer(x, 1000); 43 | cancelTimer(id); 44 | 45 | ``` 46 | 47 | ## intervalTimeout(func,interval) 48 | 49 | Call function repeatedly in the given interval (in milliseconds) until canceled. 50 | 51 | - func 52 | 53 | type: function 54 | 55 | function to call when the timer expires 56 | 57 | - interval 58 | 59 | type: uint 60 | 61 | interval in milliseconds 62 | 63 | **Returns:** timer ID 64 | 65 | ``` 66 | // call function x every 5 seconds 67 | intervalTimeout(x, 5000); 68 | 69 | ``` 70 | 71 | ## setTimeout(func,delay) 72 | 73 | Timer to call a function after a given delay (in milliseconds). 74 | 75 | - func 76 | 77 | type: function 78 | 79 | function to call when the timer expires 80 | 81 | - delay 82 | 83 | type: uint 84 | 85 | timeout in milliseconds 86 | 87 | **Returns:** timer ID 88 | 89 | ``` 90 | // 5000 second timer 91 | setTimeout(function(){print('timer\n');}, 5000); 92 | 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /docs/util.md: -------------------------------------------------------------------------------- 1 | # Util 2 | 3 | A simple utility library. 4 | 5 | ## Methods 6 | 7 | - [arrayEqual](#arrayequalab) 8 | - [binToHex](#bintohexbin) 9 | - [copyIndexLen](#copyindexlennumbernumbernumbernumbernumber) 10 | - [crc16](#crc16buf) 11 | - [fromTwosComplement](#fromtwoscomplementtwoscomplementnumberbytes) 12 | - [hexToBin](#hextobinhex) 13 | - [htonl](#htonlnumber) 14 | - [htons](#htonsnumber) 15 | - [reverse](#reversebin) 16 | 17 | --- 18 | 19 | ## arrayEqual(a,b) 20 | 21 | compare two arrays 22 | 23 | - a 24 | 25 | type: array 26 | 27 | a 28 | 29 | - b 30 | 31 | type: array 32 | 33 | b 34 | 35 | **Returns:** boolean 36 | 37 | ``` 38 | 39 | ``` 40 | 41 | ## binToHex(bin) 42 | 43 | convert a plain buffer to a hex string 44 | 45 | - bin 46 | 47 | type: plainbuffer 48 | 49 | binary buffer 50 | 51 | **Returns:** string 52 | 53 | ``` 54 | 55 | ``` 56 | 57 | ## copyIndexLen(number,number,number,number,number) 58 | 59 | copy len bytes from src at src index to dst at dst index 60 | 61 | - number 62 | 63 | type: array 64 | 65 | src 66 | 67 | - number 68 | 69 | type: int 70 | 71 | src index 72 | 73 | - number 74 | 75 | type: array 76 | 77 | dst 78 | 79 | - number 80 | 81 | type: int 82 | 83 | dst index 84 | 85 | - number 86 | 87 | type: int 88 | 89 | len 90 | 91 | **Returns:** 92 | 93 | ``` 94 | 95 | ``` 96 | 97 | ## crc16(buf) 98 | 99 | calculate crc16 of the buffer 100 | 101 | - buf 102 | 103 | type: bytearray 104 | 105 | buffer 106 | 107 | **Returns:** uint16 108 | 109 | ``` 110 | 111 | ``` 112 | 113 | ## fromTwosComplement(twosComplement,numberBytes) 114 | 115 | convert twos complement to int 116 | 117 | - twosComplement 118 | 119 | type: bytearray 120 | 121 | twos complement 122 | 123 | - numberBytes 124 | 125 | type: int 126 | 127 | number of bytes in towsComplement 128 | 129 | **Returns:** int 130 | 131 | ``` 132 | 133 | ``` 134 | 135 | ## hexToBin(hex) 136 | 137 | convert hex string to a plain buffer 138 | 139 | - hex 140 | 141 | type: string 142 | 143 | hex string 144 | 145 | **Returns:** plain buffer 146 | 147 | ``` 148 | 149 | ``` 150 | 151 | ## htonl(number) 152 | 153 | convert host to network byte order (uint32) 154 | 155 | - number 156 | 157 | type: uint32 158 | 159 | uint32 160 | 161 | **Returns:** uint32 162 | 163 | ``` 164 | 165 | ``` 166 | 167 | ## htons(number) 168 | 169 | convert host to network byte order (uint16) 170 | 171 | - number 172 | 173 | type: uint16 174 | 175 | uint16 176 | 177 | **Returns:** uint16 178 | 179 | ``` 180 | 181 | ``` 182 | 183 | ## reverse(bin) 184 | 185 | reverse the bytes in a plain buffer 186 | 187 | - bin 188 | 189 | type: plainbuffer 190 | 191 | binary buffer 192 | 193 | **Returns:** plain buffer 194 | 195 | ``` 196 | 197 | ``` 198 | 199 | -------------------------------------------------------------------------------- /docs/webservice.md: -------------------------------------------------------------------------------- 1 | # Web Service 2 | 3 | The Web Service provides a minimal HTTP server to implement a web application. 4 | The web service includes a websocket server that allows for one connection. 5 | The websocket connection is transparently provided to the JavaScript 6 | runtime via the OnEvent() function. 7 | 8 | The websocket server is listening on port `8888`. 9 | 10 | `192.168.4.1` is the default IP for Fluxn0de in Wifi AP mode. 11 | 12 | The `web_mode` parameter of Platform.wifiConfigure() allows to disable `/control` and 13 | POST to `/file`. 14 | 15 | ## API 16 | 17 | The API will not allow to read and write files that start with `_`. 18 | This allows the device to store `private` data. 19 | 20 | ### URL: / 21 | 22 | - GET will read /index.html 23 | 24 | ### /file?\ 25 | 26 | - GET to read \ 27 | - POST to write to \ 28 | 29 | ### /control?\ 30 | 31 | - GET to execute the \ 32 | 33 | Commands: 34 | - **reset** (reset the JavaScript runtime, same as Platform.reset()) 35 | - **setload=\** (set the load file, same as Platform.setLoadFileName(filename)) 36 | - **reboot** (reboot the board, same as Platform.reboot()) 37 | - **deletefile=\** (delete \, same as FileSystem.unlink(filename)) 38 | 39 | Example: 40 | ``` 41 | 42 | Upload test42.js to the board. 43 | $ http://192.168.4.1/file?test42.js --data-binary @test42.js 44 | OK 45 | 46 | Set /test42.js as the application to run 47 | $ http://192.168.4.1/control?setload=/test42.js 48 | OK 49 | 50 | Reset JavaScript runtime and run test42.js 51 | $ curl http://192.168.4.1/control?reset 52 | OK 53 | 54 | ``` 55 | 56 | 57 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS 2 | "main.c" 3 | "duk_util.c" 4 | "wifi_service.c" 5 | "duk_fs.c" 6 | "duk_main.c" 7 | "web_service.c" 8 | "util.c" 9 | "platform.c" 10 | "led_service.c" 11 | "lora_main.c" 12 | "button_service.c" 13 | "ble_server.c" 14 | "ble_support.c" 15 | "record.c" 16 | "duk_crypto.c" 17 | "board.c" 18 | "udp_service.c" 19 | INCLUDE_DIRS 20 | "include" 21 | "." 22 | ) 23 | 24 | spiffs_create_partition_image(storage ../spiffs_image FLASH_IN_PROJECT) 25 | -------------------------------------------------------------------------------- /main/ble_support.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | 7 | #include "freertos/FreeRTOS.h" 8 | #include "freertos/task.h" 9 | #include "freertos/event_groups.h" 10 | #include "esp_system.h" 11 | #include "esp_bt.h" 12 | #include "esp_gap_ble_api.h" 13 | #include "esp_gatts_api.h" 14 | #include "esp_bt_defs.h" 15 | #include "esp_bt_main.h" 16 | 17 | #include "log.h" 18 | 19 | char *esp_key_type_to_str(esp_ble_key_type_t key_type) 20 | { 21 | char *key_str = NULL; 22 | switch (key_type) 23 | { 24 | case ESP_LE_KEY_NONE: 25 | key_str = "ESP_LE_KEY_NONE"; 26 | break; 27 | case ESP_LE_KEY_PENC: 28 | key_str = "ESP_LE_KEY_PENC"; 29 | break; 30 | case ESP_LE_KEY_PID: 31 | key_str = "ESP_LE_KEY_PID"; 32 | break; 33 | case ESP_LE_KEY_PCSRK: 34 | key_str = "ESP_LE_KEY_PCSRK"; 35 | break; 36 | case ESP_LE_KEY_PLK: 37 | key_str = "ESP_LE_KEY_PLK"; 38 | break; 39 | case ESP_LE_KEY_LLK: 40 | key_str = "ESP_LE_KEY_LLK"; 41 | break; 42 | case ESP_LE_KEY_LENC: 43 | key_str = "ESP_LE_KEY_LENC"; 44 | break; 45 | case ESP_LE_KEY_LID: 46 | key_str = "ESP_LE_KEY_LID"; 47 | break; 48 | case ESP_LE_KEY_LCSRK: 49 | key_str = "ESP_LE_KEY_LCSRK"; 50 | break; 51 | default: 52 | key_str = "INVALID BLE KEY TYPE"; 53 | break; 54 | } 55 | 56 | return key_str; 57 | } 58 | 59 | char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req) 60 | { 61 | char *auth_str = NULL; 62 | switch (auth_req) 63 | { 64 | case ESP_LE_AUTH_NO_BOND: 65 | auth_str = "ESP_LE_AUTH_NO_BOND"; 66 | break; 67 | case ESP_LE_AUTH_BOND: 68 | auth_str = "ESP_LE_AUTH_BOND"; 69 | break; 70 | case ESP_LE_AUTH_REQ_MITM: 71 | auth_str = "ESP_LE_AUTH_REQ_MITM"; 72 | break; 73 | case ESP_LE_AUTH_REQ_BOND_MITM: 74 | auth_str = "ESP_LE_AUTH_REQ_BOND_MITM"; 75 | break; 76 | case ESP_LE_AUTH_REQ_SC_ONLY: 77 | auth_str = "ESP_LE_AUTH_REQ_SC_ONLY"; 78 | break; 79 | case ESP_LE_AUTH_REQ_SC_BOND: 80 | auth_str = "ESP_LE_AUTH_REQ_SC_BOND"; 81 | break; 82 | case ESP_LE_AUTH_REQ_SC_MITM: 83 | auth_str = "ESP_LE_AUTH_REQ_SC_MITM"; 84 | break; 85 | case ESP_LE_AUTH_REQ_SC_MITM_BOND: 86 | auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND"; 87 | break; 88 | default: 89 | auth_str = "INVALID BLE AUTH REQ"; 90 | break; 91 | } 92 | 93 | return auth_str; 94 | } 95 | -------------------------------------------------------------------------------- /main/board.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "freertos/FreeRTOS.h" 9 | #include "freertos/task.h" 10 | #include "driver/gpio.h" 11 | #include 12 | #include "esp_adc_cal.h" 13 | #include "soc/rtc.h" 14 | #include "esp_pm.h" 15 | #include "esp_sleep.h" 16 | 17 | #include "log.h" 18 | #include "util.h" 19 | #include "board.h" 20 | 21 | //#define UTIL_DEBUG_2 1 22 | 23 | static void flux_batt_en_toggle(int on); 24 | static uint32_t flux_battery_measure_mvolt(); 25 | static void flux_board_init(); 26 | 27 | static uint32_t huzzah_battery_measure_mvolt(); 28 | static void huzzah_board_init(); 29 | 30 | static board_config_t boards[NUM_BOARDS] = { 31 | { 32 | "huzzah", 33 | // led 0 1 34 | 13, 35 | -1, 36 | // cs, rst 37 | 33, 38 | 27, 39 | // miso, mosi, sck 40 | 19, 41 | 18, 42 | 5, 43 | // dio 0 1 2 44 | 32, 45 | 15, 46 | 14, 47 | // bat 48 | -1, 49 | -1, 50 | -1, 51 | // usb con 52 | -1, 53 | // button 54 | -1, 55 | huzzah_battery_measure_mvolt, 56 | NULL, 57 | huzzah_board_init, 58 | }, 59 | { 60 | "fluxn0de", 61 | // led 0 1 62 | 27, 63 | 15, 64 | // cs, rst, 65 | 21, 66 | 4, 67 | // miso, mosi, sck 68 | 19, 69 | 18, 70 | 5, 71 | // dio 0 1 2 72 | 34, 73 | 39, 74 | 36, 75 | // bat 76 | -1, 77 | 26, 78 | 2, 79 | // usb con 80 | 13, 81 | // button 82 | 14, 83 | flux_battery_measure_mvolt, 84 | flux_batt_en_toggle, 85 | flux_board_init, 86 | }}; 87 | 88 | static board_config_t *this_board = NULL; 89 | static board_type_t board_type = -1; 90 | 91 | static uint32_t huzzah_battery_measure_mvolt() 92 | { 93 | esp_adc_cal_characteristics_t *adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t)); 94 | esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, adc_chars); 95 | 96 | adc1_config_width(ADC_WIDTH_BIT_12); 97 | adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11); 98 | 99 | uint32_t val = adc1_get_raw(ADC1_CHANNEL_7); 100 | uint32_t voltage = esp_adc_cal_raw_to_voltage(val, adc_chars); 101 | 102 | // huzzah esp32 has a 2x divider between voltage PIN and GPIO 103 | voltage = voltage * 2; 104 | #ifdef UTIL_DEBUG_2 105 | logprintf("%s: %d mV\n", __func__, voltage); 106 | #endif 107 | return voltage; 108 | } 109 | 110 | static void huzzah_board_init() 111 | { 112 | // disable external clock otherwise SPI doesn't work 113 | rtc_clk_32k_enable(false); 114 | // 32 is LoRa IRQ 115 | esp_sleep_enable_ext1_wakeup(GPIO_SEL_32, ESP_EXT1_WAKEUP_ANY_HIGH); 116 | } 117 | 118 | static void flux_board_init() 119 | { 120 | // 34 is LoRa IRQ 121 | // 14 is button 122 | esp_sleep_enable_ext1_wakeup(GPIO_SEL_34 | GPIO_SEL_14, ESP_EXT1_WAKEUP_ANY_HIGH); 123 | } 124 | 125 | static void flux_batt_en_toggle(int on) 126 | { 127 | gpio_pad_select_gpio(boards[BOARD_TYPE_FLUX].bat_en_gpio); 128 | gpio_set_direction(boards[BOARD_TYPE_FLUX].bat_en_gpio, GPIO_MODE_OUTPUT); 129 | if (on) 130 | { 131 | adc_power_on(); 132 | gpio_set_level(boards[BOARD_TYPE_FLUX].bat_en_gpio, 1); 133 | } 134 | else 135 | { 136 | gpio_set_level(boards[BOARD_TYPE_FLUX].bat_en_gpio, 0); 137 | adc_power_off(); 138 | } 139 | } 140 | 141 | static uint32_t flux_battery_measure_mvolt_internal(uint32_t *valout) 142 | { 143 | esp_adc_cal_characteristics_t *adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t)); 144 | esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_0, ADC_WIDTH_BIT_12, 1100, adc_chars); 145 | 146 | adc1_config_width(ADC_WIDTH_BIT_12); 147 | adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_0); 148 | 149 | uint32_t val = adc1_get_raw(ADC1_CHANNEL_7); 150 | uint32_t voltage = esp_adc_cal_raw_to_voltage(val, adc_chars); 151 | 152 | if (valout) 153 | { 154 | *valout = val; 155 | } 156 | 157 | // fluxn0de has a 11x divider on the voltage 158 | #ifdef UTIL_DEBUG_2 159 | logprintf("%s: %d %d mV\n", __func__, val, voltage * 11); 160 | #endif 161 | return voltage * 11; 162 | } 163 | 164 | static uint32_t flux_battery_measure_mvolt() 165 | { 166 | return flux_battery_measure_mvolt_internal(NULL); 167 | } 168 | 169 | // --- board detection --- 170 | 171 | #define BATT_EN_GPIO 26 172 | #define CHARGE_USB_GPIO 2 173 | #define USB_CON_GPIO 13 174 | 175 | /* 176 | * This function tries to detect the board it is running on. 177 | * Right now detection is based on battery volt measurement. 178 | * The fluxboard has an enable GPIO that needs to be set to 179 | * 1 in order to feed the battery voltage to the ADC. Without it 180 | * the fluxboard will read 0 volt while the HUZZAH board will 181 | * have some value other than 0. 182 | * 183 | */ 184 | static board_type_t board_detect() 185 | { 186 | gpio_pad_select_gpio(BATT_EN_GPIO); 187 | gpio_set_direction(BATT_EN_GPIO, GPIO_MODE_OUTPUT); 188 | gpio_set_level(BATT_EN_GPIO, 0); 189 | adc_power_on(); 190 | uint32_t val; 191 | flux_battery_measure_mvolt_internal(&val); 192 | adc_power_off(); 193 | if (val == 0) 194 | { 195 | board_type = BOARD_TYPE_FLUX; 196 | flux_batt_en_toggle(0); 197 | return board_type; 198 | } 199 | board_type = BOARD_TYPE_HUZZAH; 200 | return board_type; 201 | } 202 | 203 | // END --- board detection --- 204 | 205 | board_type_t get_board_type() 206 | { 207 | if (board_type != -1) 208 | { 209 | return board_type; 210 | } 211 | board_type = board_detect(); 212 | return board_type; 213 | } 214 | 215 | board_config_t *get_board_config() 216 | { 217 | if (this_board) 218 | { 219 | return this_board; 220 | } 221 | this_board = &boards[get_board_type()]; 222 | return this_board; 223 | } 224 | -------------------------------------------------------------------------------- /main/button_service.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "freertos/FreeRTOS.h" 9 | #include "freertos/task.h" 10 | #include "freertos/timers.h" 11 | #include "soc/gpio_struct.h" 12 | #include "driver/gpio.h" 13 | 14 | #include "log.h" 15 | #include "board.h" 16 | #include "button_service.h" 17 | #include "util.h" 18 | 19 | struct button_service_t 20 | { 21 | int button_press_count; 22 | unsigned long presses[BUTTON_PRESS_NUM_MAX]; 23 | int press_idx; 24 | 25 | TimerHandle_t button_timer; 26 | int timer_started; 27 | 28 | int gpio; 29 | 30 | button_service_callback *cb; 31 | void *cb_data; 32 | 33 | unsigned long timeout_msec; 34 | }; 35 | 36 | static struct button_service_t *button1; 37 | 38 | static void start_timer() 39 | { 40 | BaseType_t wake; 41 | xTimerStartFromISR(button1->button_timer, &wake); 42 | if (wake) 43 | { 44 | // wake timerservice 45 | portYIELD_FROM_ISR(); 46 | } 47 | } 48 | 49 | static IRAM_ATTR void button_isr(void *pdata) 50 | { 51 | if (!button1->timer_started) 52 | { 53 | button1->timer_started = 1; 54 | start_timer(); 55 | } 56 | 57 | if (button1->press_idx < BUTTON_PRESS_NUM_MAX - 1) 58 | { 59 | button1->presses[button1->press_idx] = xTaskGetTickCountFromISR(); 60 | button1->press_idx++; 61 | } 62 | } 63 | 64 | static void button_callback(void *pdata) 65 | { 66 | button1->button_press_count = 1; 67 | unsigned long last = button1->presses[0]; 68 | for (int i = 1; i < button1->press_idx; i++) 69 | { 70 | if ((button1->presses[i] - last) > BUTTON_PRESS_MIN_MS) 71 | { 72 | button1->button_press_count++; 73 | last = button1->presses[i]; 74 | } 75 | } 76 | button1->press_idx = 0; 77 | 78 | button1->cb(button1->button_press_count, button1->timeout_msec, button1->cb_data); 79 | button1->button_press_count = 0; 80 | } 81 | 82 | void button_service_set_cb(button_service_callback cb, void *cb_data) 83 | { 84 | button1->cb = cb; 85 | button1->cb_data = cb_data; 86 | } 87 | 88 | void button_server_set_timeout(unsigned long timeout_msec) 89 | { 90 | button1->timeout_msec = timeout_msec; 91 | } 92 | 93 | void button_service_start(button_service_callback cb, void *cb_data) 94 | { 95 | board_config_t *board = get_board_config(); 96 | 97 | button1 = malloc(sizeof(struct button_service_t)); 98 | button1->press_idx = 0; 99 | button1->timer_started = 0; 100 | button1->timeout_msec = BUTTON_SERVICE_DEFAULT_TIMEOUT; 101 | 102 | button_service_set_cb(cb, cb_data); 103 | 104 | button1->gpio = board->button_gpio; 105 | 106 | button1->button_timer = xTimerCreate("button1", pdMS_TO_TICKS(button1->timeout_msec), pdFALSE, NULL, button_callback); 107 | 108 | gpio_set_direction(button1->gpio, GPIO_MODE_INPUT); 109 | gpio_set_intr_type(button1->gpio, GPIO_INTR_POSEDGE); 110 | gpio_intr_enable(button1->gpio); 111 | 112 | gpio_isr_handler_add(button1->gpio, button_isr, (void *)NULL); 113 | } 114 | 115 | void button_service_enable(int enable) 116 | { 117 | if (enable) 118 | { 119 | button1->timer_started = 0; 120 | button1->button_press_count = 0; 121 | return; 122 | } 123 | button1->timer_started = 1; 124 | } 125 | 126 | void button_service_stop() 127 | { 128 | gpio_intr_disable(button1->gpio); 129 | gpio_isr_handler_remove(button1->gpio); 130 | } 131 | 132 | int button_service_ispressed() 133 | { 134 | board_config_t *board = get_board_config(); 135 | if (board->button_gpio != -1) 136 | { 137 | gpio_set_direction(board->button_gpio, GPIO_MODE_INPUT); 138 | return gpio_get_level(board->button_gpio); 139 | } 140 | return 0; 141 | } 142 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crmulliner/fluxnode/fb106e7c86a3e7f67eb6b3fe713136b1cf38ca78/main/component.mk -------------------------------------------------------------------------------- /main/duk_crypto.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include "sdkconfig.h" 11 | #include "mbedtls/cmac.h" 12 | #include "mbedtls/aes.h" 13 | 14 | #include 15 | 16 | #include "log.h" 17 | #include "duk_helpers.h" 18 | 19 | /* 20 | * based on: https://github.com/nkolban/duktape-esp32.git 21 | * license: Apache 2.0 22 | */ 23 | 24 | /* jsondoc 25 | { 26 | "class": "Crypto", 27 | "longtext": " 28 | Documentation for the Crypto API. 29 | 30 | Some code is based on: https://github.com/nkolban/duktape-esp32.git 31 | under the Apache 2.0 license. 32 | 33 | Most of the crypto APIs use Plain Buffers (https://wiki.duktape.org/howtobuffers2x). 34 | " 35 | } 36 | */ 37 | 38 | #define AES_BLOCK_SIZE 16 39 | 40 | /* jsondoc 41 | { 42 | "name": "aes128EcbEnc", 43 | "args": [ 44 | {"name": "key", "vtype": "plain buffer", "text": "encryption key 16 bytes"}, 45 | {"name": "data", "vtype": "plain buffer", "text": "data to be encrypted, encryption will be in place and overwrite data."} 46 | ], 47 | "text": "Encrypt using AES 128 in ECB mode.", 48 | "return": "boolean status", 49 | "example": " 50 | key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 51 | data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 52 | Crypto.aes128EcbEnc(key, data); 53 | " 54 | } 55 | */ 56 | static duk_ret_t aes_128_ecb_encrypt(duk_context *ctx) 57 | { 58 | duk_size_t keySize; 59 | uint8_t *key = duk_require_buffer_data(ctx, 0, &keySize); 60 | 61 | duk_size_t dataSize; 62 | uint8_t *data = duk_require_buffer_data(ctx, 1, &dataSize); 63 | 64 | int ret = 0; 65 | mbedtls_aes_context aes_ctx; 66 | 67 | mbedtls_aes_init(&aes_ctx); 68 | 69 | ret = mbedtls_aes_setkey_enc(&aes_ctx, key, 128); 70 | 71 | if (ret < 0) 72 | { 73 | mbedtls_aes_free(&aes_ctx); 74 | duk_push_boolean(ctx, 0); 75 | return 1; 76 | } 77 | 78 | ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT, data, data); 79 | 80 | mbedtls_aes_free(&aes_ctx); 81 | 82 | duk_push_boolean(ctx, ret == 0); 83 | return 1; 84 | } 85 | 86 | /* jsondoc 87 | { 88 | "name": "aes128EcbDec", 89 | "args": [ 90 | {"name": "key", "vtype": "plain buffer", "text": "decryption key 16 bytes"}, 91 | {"name": "data", "vtype": "plain buffer", "text": "data to be decrypted, decryption will be in place and overwrite data."} 92 | ], 93 | "text": "Decrypt using AES 128 in ECB mode.", 94 | "return": "boolean status", 95 | "example": " 96 | key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 97 | data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 98 | Crypto.aes128EcbDec(key, data); 99 | " 100 | } 101 | */ 102 | static duk_ret_t aes_128_ecb_decrypt(duk_context *ctx) 103 | { 104 | duk_size_t keySize; 105 | uint8_t *key = duk_require_buffer_data(ctx, 0, &keySize); 106 | 107 | duk_size_t dataSize; 108 | uint8_t *data = duk_require_buffer_data(ctx, 1, &dataSize); 109 | 110 | int ret = 0; 111 | mbedtls_aes_context aes_ctx; 112 | 113 | mbedtls_aes_init(&aes_ctx); 114 | 115 | ret = mbedtls_aes_setkey_dec(&aes_ctx, key, 128); 116 | 117 | if (ret < 0) 118 | { 119 | mbedtls_aes_free(&aes_ctx); 120 | duk_push_boolean(ctx, 0); 121 | return 1; 122 | } 123 | 124 | ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_DECRYPT, data, data); 125 | 126 | mbedtls_aes_free(&aes_ctx); 127 | 128 | duk_push_boolean(ctx, ret == 0); 129 | return 1; 130 | } 131 | 132 | /* jsondoc 133 | { 134 | "name": "aes128CbcEnc", 135 | "args": [ 136 | {"name": "key", "vtype": "plain buffer", "text": "encryption key 16 bytes"}, 137 | {"name": "iv", "vtype": "plain buffer", "text": "IV 16 bytes"}, 138 | {"name": "data", "vtype": "plain buffer", "text": "data to be encrypted, encryption will be in place and overwrite data. Must be be large enough to hold encrypted data."}, 139 | {"name": "data_len", "vtype": "uint", "text": "length of data"} 140 | ], 141 | "return": "boolean status", 142 | "text": "Encrypt using AES 128 in CBC mode.", 143 | "example": " 144 | " 145 | } 146 | */ 147 | static duk_ret_t aes_128_cbc_encrypt(duk_context *ctx) 148 | { 149 | duk_size_t keySize; 150 | uint8_t *key = duk_require_buffer_data(ctx, 0, &keySize); 151 | 152 | duk_size_t ivSize; 153 | uint8_t *iv = duk_require_buffer_data(ctx, 1, &ivSize); 154 | 155 | duk_size_t dataSize; 156 | uint8_t *data = duk_require_buffer_data(ctx, 2, &dataSize); 157 | 158 | int data_len = duk_require_int(ctx, 3); 159 | 160 | int ret = 0; 161 | mbedtls_aes_context aes_ctx; 162 | uint8_t cbc[AES_BLOCK_SIZE]; 163 | 164 | mbedtls_aes_init(&aes_ctx); 165 | 166 | ret = mbedtls_aes_setkey_enc(&aes_ctx, key, 128); 167 | 168 | if (ret < 0) 169 | { 170 | mbedtls_aes_free(&aes_ctx); 171 | duk_push_boolean(ctx, 0); 172 | return 1; 173 | } 174 | 175 | memcpy(cbc, iv, AES_BLOCK_SIZE); 176 | 177 | ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, data_len, cbc, data, data); 178 | 179 | mbedtls_aes_free(&aes_ctx); 180 | 181 | duk_push_boolean(ctx, ret == 0); 182 | return 1; 183 | } 184 | 185 | /* jsondoc 186 | { 187 | "name": "aes128CbcDec", 188 | "args": [ 189 | {"name": "key", "vtype": "plain buffer", "text": "decryption key 16 bytes"}, 190 | {"name": "iv", "vtype": "plain buffer", "text": "IV 16 bytes"}, 191 | {"name": "data", "vtype": "plain buffer", "text": "data to be decrypted, decryption will be in place and overwrite data."}, 192 | {"name": "data_len", "vtype": "uint", "text": "length of data"} 193 | ], 194 | "return": "boolean status", 195 | "text": "Decrypt using AES 128 in CBC mode.", 196 | "example": " 197 | " 198 | } 199 | */ 200 | static duk_ret_t aes_128_cbc_decrypt(duk_context *ctx) 201 | { 202 | duk_size_t keySize; 203 | uint8_t *key = duk_require_buffer_data(ctx, 0, &keySize); 204 | 205 | duk_size_t ivSize; 206 | uint8_t *iv = duk_require_buffer_data(ctx, 1, &ivSize); 207 | 208 | duk_size_t dataSize; 209 | uint8_t *data = duk_require_buffer_data(ctx, 2, &dataSize); 210 | 211 | int data_len = duk_require_int(ctx, 3); 212 | 213 | int ret = 0; 214 | mbedtls_aes_context aes_ctx; 215 | uint8_t cbc[AES_BLOCK_SIZE]; 216 | 217 | mbedtls_aes_init(&aes_ctx); 218 | 219 | ret = mbedtls_aes_setkey_dec(&aes_ctx, key, 128); 220 | 221 | if (ret < 0) 222 | { 223 | mbedtls_aes_free(&aes_ctx); 224 | duk_push_boolean(ctx, 0); 225 | return 1; 226 | } 227 | 228 | memcpy(cbc, iv, AES_BLOCK_SIZE); 229 | 230 | ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, data_len, cbc, data, data); 231 | 232 | mbedtls_aes_free(&aes_ctx); 233 | 234 | duk_push_boolean(ctx, ret == 0); 235 | return 1; 236 | } 237 | 238 | /* jsondoc 239 | { 240 | "name": "aes128Cmac", 241 | "args": [ 242 | {"name": "key", "vtype": "plain buffer", "text": "key 16 bytes"}, 243 | {"name": "data", "vtype": "plain buffer", "text": "data to authenticate"}, 244 | {"name": "cmac", "vtype": "plain buffer", "text": "CMAC 16 bytes"} 245 | ], 246 | "text": "Compute the AES 128 CMAC of the data.", 247 | "return": "boolean status", 248 | "example": " 249 | key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 250 | data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 251 | output = Uint8Array.allocPlain(16); 252 | Crypto.aes128Cmac(key, data, output); 253 | print(Duktape.enc('hex', output)); 254 | " 255 | } 256 | */ 257 | static duk_ret_t aes_128_cmac(duk_context *ctx) 258 | { 259 | duk_size_t keySize; 260 | uint8_t *key = duk_require_buffer_data(ctx, 0, &keySize); 261 | 262 | duk_size_t dataSize; 263 | uint8_t *data = duk_require_buffer_data(ctx, 1, &dataSize); 264 | 265 | duk_size_t outSize; 266 | uint8_t *output = duk_require_buffer_data(ctx, 2, &outSize); 267 | 268 | int ret = 0; 269 | 270 | mbedtls_cipher_info_t *cipher_info = (mbedtls_cipher_info_t *)mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB); 271 | 272 | // key size must be in bits 273 | ret = mbedtls_cipher_cmac(cipher_info, key, keySize * 8, data, dataSize, output); 274 | 275 | duk_push_boolean(ctx, ret == 0); 276 | return 1; 277 | } 278 | 279 | /* jsondoc 280 | { 281 | "name": "fillRandom", 282 | "args": [ 283 | {"name": "data", "vtype": "plain buffer", "text": "buffer to fill with random bytes"} 284 | ], 285 | "text": "Fill buffer with random bytes from the hardware random generator.", 286 | "example": " 287 | output = Uint8Array.allocPlain(16); 288 | Crypto.fillRandom(output); 289 | print(Duktape.enc('hex', output)); 290 | " 291 | } 292 | */ 293 | static duk_ret_t fill_random(duk_context *ctx) 294 | { 295 | duk_size_t dataSize; 296 | uint8_t *data = duk_require_buffer_data(ctx, 1, &dataSize); 297 | esp_fill_random(data, dataSize); 298 | return 0; 299 | } 300 | 301 | static duk_function_list_entry crypto_funcs[] = { 302 | {"aes128CbcEnc", aes_128_cbc_encrypt, 4}, 303 | {"aes128CbcDec", aes_128_cbc_decrypt, 4}, 304 | {"aes128EcbEnc", aes_128_ecb_encrypt, 2}, 305 | {"aes128EcbDec", aes_128_ecb_decrypt, 2}, 306 | {"aes128Cmac", aes_128_cmac, 3}, 307 | {"fillRandom", fill_random, 1}, 308 | {NULL, NULL, 0}, 309 | }; 310 | 311 | void crypto_register(duk_context *ctx) 312 | { 313 | duk_push_global_object(ctx); 314 | duk_push_object(ctx); 315 | 316 | duk_put_function_list(ctx, -1, crypto_funcs); 317 | duk_put_prop_string(ctx, -2, "Crypto"); 318 | duk_pop(ctx); 319 | } 320 | -------------------------------------------------------------------------------- /main/duk_fs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include "sdkconfig.h" 11 | #include "esp_spiffs.h" 12 | 13 | #include 14 | 15 | #include "vfs_config.h" 16 | #include "duk_helpers.h" 17 | 18 | /* 19 | * based on: https://github.com/nkolban/duktape-esp32.git 20 | * license: Apache 2.0 21 | */ 22 | 23 | /* jsondoc 24 | { 25 | "class": "FileSystem", 26 | "longtext": " 27 | Documentation for the FileSystem API. 28 | 29 | Most of the code is either directly copied from or based on: https://github.com/nkolban/duktape-esp32.git 30 | under the Apache 2.0 license. 31 | " 32 | } 33 | */ 34 | 35 | /** 36 | * Convert a string to posix open() flags. 37 | * "r" - O_RDONLY 38 | * "w" - O_WRONLY 39 | */ 40 | static int stringToPosixFlags(const char *flags) 41 | { 42 | int posixFlags = 0; 43 | if (strcmp(flags, "r") == 0) 44 | { 45 | posixFlags = O_RDONLY; 46 | } 47 | if (strcmp(flags, "r+") == 0) 48 | { 49 | posixFlags = O_RDWR; 50 | } 51 | if (strcmp(flags, "w") == 0) 52 | { 53 | posixFlags = O_WRONLY | O_TRUNC | O_CREAT; 54 | } 55 | if (strcmp(flags, "w+") == 0) 56 | { 57 | posixFlags = O_RDWR | O_TRUNC | O_CREAT; 58 | } 59 | if (strcmp(flags, "a") == 0) 60 | { 61 | posixFlags = O_WRONLY | O_APPEND | O_CREAT; 62 | } 63 | if (strcmp(flags, "a+") == 0) 64 | { 65 | posixFlags = O_RDWR | O_APPEND | O_CREAT; 66 | } 67 | return posixFlags; 68 | } 69 | 70 | /* jsondoc 71 | { 72 | "name": "write", 73 | "args": [ 74 | {"name": "fd", "vtype": "uint", "text": "file descriptor"}, 75 | {"name": "buffer", "vtype": "Plain Buffer", "text": "data"}, 76 | {"name": "bufferOffset", "vtype": "uint", "text": "offset in the buffer (optional)"}, 77 | {"name": "length", "vtype": "uint", "text": "length (optional)"} 78 | ], 79 | "text": "Write to file descriptor.", 80 | "return": "num bytes written", 81 | "example": " 82 | " 83 | } 84 | */ 85 | 86 | /* jsondoc 87 | { 88 | "name": "write ", 89 | "args": [ 90 | {"name": "fd", "vtype": "uint", "text": "file descriptor"}, 91 | {"name": "string", "vtype": "string", "text": "data"} 92 | ], 93 | "return": "num bytes written", 94 | "text": "Write string to file descriptor.", 95 | "example": " 96 | " 97 | } 98 | */ 99 | static duk_ret_t js_fs_write(duk_context *ctx) 100 | { 101 | int fd; 102 | duk_size_t bufferSize; 103 | uint8_t *bufPtr; 104 | int rc; 105 | duk_size_t offset = 0; // Default offset is 0. 106 | duk_size_t length; 107 | 108 | fd = duk_get_int(ctx, 0); 109 | if (duk_is_buffer(ctx, 1)) 110 | { 111 | bufPtr = duk_require_buffer_data(ctx, 1, &bufferSize); 112 | // Unless specified, the length is all of the buffer 113 | length = bufferSize; 114 | 115 | if (duk_is_number(ctx, 2)) 116 | { 117 | offset = duk_get_int(ctx, 2); 118 | if (duk_is_number(ctx, 3)) 119 | { 120 | length = duk_get_int(ctx, 3); 121 | if (length > bufferSize) 122 | { 123 | printf("Specified length is > buffer size\n"); 124 | length = bufferSize; 125 | } // length > bufferSize 126 | } // We have a length 127 | } // We have an offset 128 | } // Data is buffer 129 | else 130 | { 131 | // Handle the case where the data type is a string. 132 | bufPtr = (uint8_t *)duk_get_string(ctx, 1); 133 | length = bufferSize = strlen((char *)bufPtr); 134 | } 135 | if (length > (bufferSize - offset)) 136 | { 137 | #ifdef FS_DEBUG_1 138 | printf("Specified length + offset is > buffer size\n"); 139 | #endif 140 | length = bufferSize - offset; 141 | } 142 | rc = write(fd, bufPtr + offset, length); 143 | 144 | duk_push_int(ctx, rc); 145 | return 1; 146 | } 147 | 148 | /* jsondoc 149 | { 150 | "name": "open", 151 | "args": [ 152 | {"name": "path", "vtype": "string", "text": "file path"}, 153 | {"name": "flags", "vtype": "string", "text": "flags ('r', 'w', 'a', ...)"} 154 | ], 155 | "return": "file descriptor (or -1 for error)", 156 | "text": "Open file.", 157 | "example": " 158 | " 159 | } 160 | */ 161 | static duk_ret_t js_fs_open(duk_context *ctx) 162 | { 163 | #ifdef FS_DEBUG_1 164 | printf("js_fs_open\n"); 165 | #endif 166 | 167 | // Get the path 168 | const char *path = duk_get_string(ctx, 0); 169 | 170 | if (path == NULL) 171 | { 172 | #ifdef FS_DEBUG_1 173 | printf("js_fs_open: Unable to get path\n"); 174 | #endif 175 | duk_push_int(ctx, -1); 176 | return 1; 177 | } 178 | 179 | // Get the flags 180 | const char *flags = duk_get_string(ctx, 1); 181 | 182 | if (flags == NULL) 183 | { 184 | #ifdef FS_DEBUG_1 185 | printf("js_fs_open: Unable to get flags\n"); 186 | #endif 187 | duk_push_int(ctx, -1); 188 | return 1; 189 | } 190 | #ifdef FS_DEBUG_1 191 | printf(" - Path to open is '%s' and flags are '%s'\n", path, flags); 192 | #endif 193 | int posixOpenFlags = stringToPosixFlags(flags); 194 | 195 | int fd = open(path, posixOpenFlags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); 196 | // Return the open file descriptor 197 | duk_push_int(ctx, fd); 198 | 199 | #ifdef FS_DEBUG_1 200 | printf("js_fs_open fd: %d\n", fd); 201 | #endif 202 | 203 | return 1; 204 | } 205 | 206 | /* jsondoc 207 | { 208 | "name": "close", 209 | "args": [ 210 | {"name": "fd", "vtype": "int", "text": "file descriptor"} 211 | ], 212 | "text": "Close file descriptor.", 213 | "example": " 214 | " 215 | } 216 | */ 217 | static duk_ret_t js_fs_close(duk_context *ctx) 218 | { 219 | int fd = duk_require_int(ctx, 0); 220 | close(fd); 221 | return 0; 222 | } 223 | 224 | /* jsondoc 225 | { 226 | "name": "fstat", 227 | "args": [ 228 | {"name": "fd", "vtype": "int", "text": "file descriptor"} 229 | ], 230 | "return": "{'size': file_size}", 231 | "text": "Stat a file descriptor but inly return size.", 232 | "example": " 233 | " 234 | } 235 | */ 236 | static duk_ret_t js_fs_fstat(duk_context *ctx) 237 | { 238 | struct stat statBuf; 239 | int fd = duk_require_int(ctx, 0); 240 | int rc = fstat(fd, &statBuf); 241 | if (rc == -1) 242 | { 243 | #ifdef FS_DEBUG_1 244 | printf("Error from stat of fd %d: %d %s\n", fd, errno, strerror(errno)); 245 | #endif 246 | statBuf.st_size = 0; 247 | } 248 | duk_push_object(ctx); 249 | duk_push_int(ctx, statBuf.st_size); 250 | duk_put_prop_string(ctx, -2, "size"); 251 | return 1; 252 | } 253 | 254 | /* jsondoc 255 | { 256 | "name": "seek", 257 | "args": [ 258 | {"name": "fd", "vtype": "uint", "text": "file descriptor"}, 259 | {"name": "offset", "vtype": "uint", "text": "offset"}, 260 | {"name": "whence", "vtype": "int", "text": "whence 0 = SET, 1 = CUR, 2 = END"} 261 | ], 262 | "return": "current position in the file", 263 | "text": "seek to a specific offset in the file. This implements lseek(2)", 264 | "example": " 265 | FileSystem.seek(fp, 0, 2); // seek to end of file 266 | " 267 | } 268 | */ 269 | static duk_ret_t js_fs_seek(duk_context *ctx) 270 | { 271 | int fd = duk_require_int(ctx, 0); 272 | size_t offset = duk_require_uint(ctx, 1); 273 | int whence = duk_require_int(ctx, 2); 274 | switch (whence) 275 | { 276 | case 0: 277 | whence = SEEK_SET; 278 | break; 279 | case 1: 280 | whence = SEEK_CUR; 281 | break; 282 | case 2: 283 | whence = SEEK_END; 284 | break; 285 | } 286 | 287 | offset = lseek(fd, offset, whence); 288 | duk_push_int(ctx, offset); 289 | return 1; 290 | } 291 | 292 | /* jsondoc 293 | { 294 | "name": "stat", 295 | "args": [ 296 | {"name": "path", "vtype": "string", "text": "filepath"} 297 | ], 298 | "return": "{'size': file_size}", 299 | "text": "Stat a file path but inly return size.", 300 | "example": " 301 | " 302 | } 303 | */ 304 | static duk_ret_t js_fs_stat(duk_context *ctx) 305 | { 306 | struct stat statBuf; 307 | const char *path = duk_require_string(ctx, 0); 308 | #ifdef FS_DEBUG_1 309 | printf("js_fs_stat: %s\n", path); 310 | #endif 311 | int rc = stat(path, &statBuf); 312 | if (rc == -1) 313 | { 314 | #ifdef FS_DEBUG_1 315 | printf("js_fs_stat: Error from stat of file %s: %d %s\n", path, errno, strerror(errno)); 316 | #endif 317 | statBuf.st_size = 0; 318 | } 319 | duk_push_object(ctx); 320 | duk_push_int(ctx, statBuf.st_size); 321 | duk_put_prop_string(ctx, -2, "size"); 322 | #ifdef FS_DEBUG_1 323 | printf("js_fs_statS: %s - size: %ld\n", path, statBuf.st_size); 324 | #endif 325 | return 1; 326 | } 327 | 328 | /* jsondoc 329 | { 330 | "name": "read", 331 | "args": [ 332 | {"name": "fd", "vtype": "int", "text": "file descriptor"}, 333 | {"name": "buffer", "vtype": "Plain Buffer", "text": "buffer"}, 334 | {"name": "bufferOffset", "vtype": "uint", "text": "offset into the buffer"}, 335 | {"name": "maxRead", "vtype": "uint", "text": "max num bytes to read"} 336 | ], 337 | "return": "number of bytes read", 338 | "text": "Read from a file descriptor.", 339 | "example": " 340 | " 341 | } 342 | */ 343 | static duk_ret_t js_fs_read(duk_context *ctx) 344 | { 345 | #ifdef FS_DEBUG_1 346 | printf("js_fs_read\n"); 347 | #endif 348 | //esp32_duktape_dump_value_stack(ctx); 349 | int fd = duk_require_int(ctx, 0); 350 | 351 | size_t writeOffset = duk_require_int(ctx, 2); 352 | 353 | size_t maxToRead = duk_require_int(ctx, 3); 354 | 355 | ssize_t sizeRead = 0; 356 | duk_size_t bufferSize; 357 | 358 | uint8_t *bufPtr = duk_require_buffer_data(ctx, 1, &bufferSize); 359 | 360 | if (bufPtr == NULL) 361 | { 362 | #ifdef FS_DEBUG_1 363 | printf("Failed to get the buffer pointer\n"); 364 | #endif 365 | } 366 | #ifdef FS_DEBUG_1 367 | printf("Buffer pointer size returned as %d\n", (int)bufferSize); 368 | #endif 369 | 370 | // Length can't be <= 0. 371 | // FIX ... PERFORM CHECK HERE 372 | 373 | // Check that writeOffset is within range. If the buffer is "buffer.length" bytes in size 374 | // then the writeOffset must be < buffer.length 375 | if (writeOffset >= bufferSize) 376 | { 377 | #ifdef FS_DEBUG_1 378 | 379 | printf("Invalid writeOffset\n"); 380 | #endif 381 | } 382 | 383 | // Position can't be < 0 384 | // FIX ... PERFORM CHECK HERE 385 | 386 | // if length > buffer.length -> length = buffer.length 387 | if (maxToRead > bufferSize) 388 | { 389 | maxToRead = bufferSize; 390 | } 391 | 392 | // Read the data from the underlying file. 393 | sizeRead = read(fd, bufPtr + writeOffset, maxToRead); 394 | if (sizeRead < 0) 395 | { 396 | #ifdef FS_DEBUG_1 397 | printf("js_fs_read: read() error: %d %s\n", errno, strerror(errno)); 398 | #endif 399 | sizeRead = 0; 400 | } 401 | 402 | duk_push_int(ctx, sizeRead); // Push the size onto the value stack. 403 | #ifdef FS_DEBUG_1 404 | printf("js_fs_read: sizeRead: %d\n", (int)sizeRead); 405 | #endif 406 | return 1; 407 | } 408 | 409 | /* jsondoc 410 | { 411 | "name": "unlink", 412 | "args": [ 413 | {"name": "path", "vtype": "int", "text": "filepath"} 414 | ], 415 | "text": "Unlink (delete) file.", 416 | "example": " 417 | " 418 | } 419 | */ 420 | static duk_ret_t js_fs_unlink(duk_context *ctx) 421 | { 422 | const char *path = duk_require_string(ctx, 0); 423 | unlink(path); 424 | return 0; 425 | } 426 | 427 | /* jsondoc 428 | { 429 | "name": "listDir", 430 | "args": [ 431 | ], 432 | "return": "array of strings (e.g., ['filename', 'otherfilename'])", 433 | "text": "List files in the filesystem.", 434 | "example": " 435 | " 436 | } 437 | */ 438 | static duk_ret_t js_fs_spiffsDir(duk_context *ctx) 439 | { 440 | duk_push_array(ctx); // Push a new empty array onto the stack. 441 | DIR *d; 442 | struct dirent *dir; 443 | 444 | d = opendir(BASE_PATH); 445 | if (d != NULL) 446 | { 447 | int i = 0; 448 | while ((dir = readdir(d)) != NULL) 449 | { 450 | // Skip if "." or ".." 451 | if (strcmp(dir->d_name, ".") == 0 || 452 | strcmp(dir->d_name, "..") == 0) 453 | { 454 | continue; 455 | } 456 | duk_push_object(ctx); 457 | duk_push_string(ctx, dir->d_name); 458 | duk_put_prop_string(ctx, -2, "name"); 459 | duk_put_prop_index(ctx, -2, i); 460 | i++; 461 | } 462 | closedir(d); 463 | } 464 | return 1; 465 | } 466 | 467 | /* jsondoc 468 | { 469 | "name": "fsSizeTotal", 470 | "args": [ 471 | ], 472 | "return": "size in bytes of the filesystem", 473 | "text": "Get total size of the flash filesystem.", 474 | "example": " 475 | " 476 | } 477 | */ 478 | static int fs_size_total(duk_context *ctx) 479 | { 480 | size_t total = 0; 481 | size_t used = 0; 482 | esp_err_t ret = esp_spiffs_info(NULL, &total, &used); 483 | if (ret != ESP_OK) 484 | { 485 | duk_push_number(ctx, -1); 486 | return 1; 487 | } 488 | duk_push_number(ctx, total); 489 | return 1; 490 | } 491 | 492 | /* jsondoc 493 | { 494 | "name": "fsSizeUsed", 495 | "args": [ 496 | ], 497 | "return": "bytes used of the filesystem", 498 | "text": "Get used number of bytes of the flash filesystem.", 499 | "example": " 500 | " 501 | } 502 | */ 503 | static int fs_size_used(duk_context *ctx) 504 | { 505 | size_t total = 0; 506 | size_t used = 0; 507 | esp_err_t ret = esp_spiffs_info(NULL, &total, &used); 508 | if (ret != ESP_OK) 509 | { 510 | duk_push_number(ctx, -1); 511 | return 1; 512 | } 513 | duk_push_number(ctx, used); 514 | return 1; 515 | } 516 | 517 | static duk_function_list_entry fs_funcs[] = { 518 | {"close", js_fs_close, 1}, 519 | {"fstat", js_fs_fstat, 1}, 520 | {"seek", js_fs_seek, 3}, 521 | {"open", js_fs_open, 2}, 522 | {"read", js_fs_read, 4}, 523 | {"listDir", js_fs_spiffsDir, 0}, 524 | {"stat", js_fs_stat, 1}, 525 | {"unlink", js_fs_unlink, 1}, 526 | {"write", js_fs_write, 4}, 527 | {"fsSizeTotal", fs_size_total, 0}, 528 | {"fsSizeUsed", fs_size_used, 0}, 529 | {NULL, NULL, 0}, 530 | }; 531 | 532 | duk_ret_t duk_fs_register(duk_context *ctx) 533 | { 534 | duk_push_global_object(ctx); 535 | duk_push_object(ctx); 536 | 537 | duk_put_function_list(ctx, -1, fs_funcs); 538 | duk_put_prop_string(ctx, -2, "FileSystem"); 539 | duk_pop(ctx); 540 | 541 | return 0; 542 | } 543 | -------------------------------------------------------------------------------- /main/duk_main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "freertos/FreeRTOS.h" 10 | #include "freertos/task.h" 11 | #include "freertos/event_groups.h" 12 | #include "esp_sleep.h" 13 | #include "driver/rtc_io.h" 14 | #include "soc/sens_periph.h" 15 | #include "soc/rtc.h" 16 | #include "freertos/queue.h" 17 | #include "freertos/semphr.h" 18 | 19 | #include 20 | 21 | #include "log.h" 22 | #include "vfs_config.h" 23 | #include "queue.h" 24 | #include "duk_fs.h" 25 | #include "duk_util.h" 26 | #include "lora_main.h" 27 | #include "duk_helpers.h" 28 | #include "duk_main.h" 29 | #include "platform.h" 30 | #include "web_service.h" 31 | #include "duk_crypto.h" 32 | #include "button_service.h" 33 | #include "board.h" 34 | #include "util.h" 35 | 36 | //#define DUK_MAIN_DEBUG 1 37 | //#define TIMER_DEBUG 1 38 | 39 | typedef struct event_msg_t event_msg_t; 40 | typedef event_msg_t *event_msg_ptr_t; 41 | struct event_msg_t 42 | { 43 | event_msg_ptr_t next; 44 | event_msg_ptr_t prev; 45 | 46 | event_msg_type msg_type; 47 | event_direction_type msg_direction; 48 | 49 | int rssi; 50 | int snr; 51 | time_t ts; 52 | uint8_t *payload; 53 | size_t payload_len; 54 | }; 55 | 56 | struct duk_globals_t 57 | { 58 | // duktape engine 59 | duk_context *ctx; 60 | int reset; 61 | 62 | // timer related 63 | unsigned int delay; 64 | unsigned int last_ticks; 65 | unsigned long int wake_up_timeMS; 66 | 67 | // task 68 | TaskHandle_t duk_main_task_handle; 69 | 70 | work_queue_t *event_queue; 71 | // function to send to UI client 72 | ui_msg_send_func *send_func; 73 | 74 | int load_index; 75 | char *load_file; 76 | }; 77 | 78 | #define MS_PER_TICK 10 79 | 80 | #define LOAD_FILES_NUM 2 81 | const char *load_files[LOAD_FILES_NUM] = { 82 | BASE_PATH "/main.js", 83 | BASE_PATH "/recovery.js", 84 | }; 85 | 86 | static struct duk_globals_t *g; 87 | 88 | static void duk_main_wake() 89 | { 90 | xTaskNotifyGive(g->duk_main_task_handle); 91 | } 92 | 93 | void duk_main_set_load_file(char *fname) 94 | { 95 | if (g->load_file) 96 | { 97 | free(g->load_file); 98 | g->load_file = NULL; 99 | } 100 | g->load_file = fname; 101 | } 102 | 103 | static int send_event(duk_context *ctx, event_msg_ptr_t event) 104 | { 105 | int result = 1; 106 | duk_push_global_object(ctx); 107 | duk_get_prop_string(ctx, -1, "OnEvent"); 108 | 109 | duk_push_object(ctx); 110 | duk_push_number(ctx, event->msg_type); 111 | duk_put_prop_string(ctx, -2, "EventType"); 112 | 113 | if (event->payload_len && event->payload != NULL) 114 | { 115 | uint8_t *buf = (uint8_t *)duk_push_fixed_buffer(ctx, event->payload_len); 116 | memcpy(buf, event->payload, event->payload_len); 117 | duk_put_prop_string(ctx, -2, "EventData"); 118 | } 119 | if (event->msg_type == LORA_MSG) 120 | { 121 | duk_push_number(ctx, event->rssi); 122 | duk_put_prop_string(ctx, -2, "LoRaRSSI"); 123 | duk_push_number(ctx, event->snr); 124 | duk_put_prop_string(ctx, -2, "LoRaSNR"); 125 | } 126 | if (event->ts) 127 | { 128 | duk_push_number(ctx, event->ts); 129 | duk_put_prop_string(ctx, -2, "TimeStamp"); 130 | } 131 | 132 | if (event->msg_type == BUTTON_PRESSED) 133 | { 134 | duk_push_number(ctx, event->payload_len); 135 | duk_put_prop_string(ctx, -2, "NumPress"); 136 | } 137 | 138 | duk_insert(ctx, -1); 139 | if (duk_pcall(ctx, 1 /*nargs*/) != 0) 140 | { 141 | #ifdef DUK_MAIN_DEBUG 142 | logprintf("%s: eval failed: '%s'\n", __func__, duk_safe_to_string(ctx, -1)); 143 | #endif 144 | result = 0; 145 | } 146 | duk_pop(ctx); 147 | return result; 148 | } 149 | 150 | #define MAX_DELAY (100 * 60 * 60 * 24) // 24h 151 | 152 | static int timer_set_check() 153 | { 154 | g->last_ticks = xTaskGetTickCount(); 155 | unsigned long int delay = g->wake_up_timeMS / MS_PER_TICK; 156 | delay = delay * 0.99; // wake up after 99% of the delay time has passed 157 | delay = delay == 0 ? 1 : delay; // minimal delay of 1 tick 158 | delay = delay > MAX_DELAY ? MAX_DELAY : delay; 159 | g->delay = delay; 160 | #ifdef TIMER_DEBUG 161 | logprintf("%s: delay: %d\n", __func__, g->delay); 162 | #endif 163 | return 1; 164 | } 165 | 166 | static int timer_check() 167 | { 168 | #if 0 169 | uint64_t tm = rtc_time_get(); 170 | logprintf("%s: tm: %lld\n", __func__, tm); 171 | #endif 172 | 173 | if (g->wake_up_timeMS == 0) 174 | { 175 | #ifdef TIMER_DEBUG 176 | logprintf("wake_up_timeMS == 0\n"); 177 | #endif 178 | g->delay = portMAX_DELAY; 179 | g->last_ticks = 0; 180 | return 0; 181 | } 182 | 183 | unsigned long int cur = xTaskGetTickCount(); 184 | unsigned long int ticks_passed = cur - g->last_ticks; 185 | #ifdef TIMER_DEBUG 186 | logprintf("ticks passed %ld\n", ticks_passed); 187 | #endif 188 | if (ticks_passed == 0) 189 | { 190 | #ifdef TIMER_DEBUG 191 | logprintf("ticks_passed == 0\n"); 192 | #endif 193 | return 0; 194 | } 195 | // ticks_passed is now MS passed 196 | ticks_passed *= MS_PER_TICK; 197 | // we hit the timer or passed it 198 | if (ticks_passed >= g->wake_up_timeMS) 199 | { 200 | // clear timer 201 | g->last_ticks = 0; 202 | g->wake_up_timeMS = 0; 203 | // timer expired 204 | return 1; 205 | } 206 | // update wake up 207 | g->wake_up_timeMS = g->wake_up_timeMS - ticks_passed; 208 | timer_set_check(); 209 | return 0; 210 | } 211 | 212 | int duk_main_set_wake_up_time(unsigned long int wake_up_in_MS) 213 | { 214 | g->wake_up_timeMS = wake_up_in_MS; 215 | #ifdef TIMER_DEBUG 216 | logprintf("set wake up time: %ld\n", g->wake_up_timeMS); 217 | #endif 218 | timer_set_check(); 219 | return 1; 220 | } 221 | 222 | void duk_main_set_reset(int rst) 223 | { 224 | g->reset = rst; 225 | } 226 | 227 | void duk_main_set_send_func(ui_msg_send_func *func) 228 | { 229 | g->send_func = func; 230 | } 231 | 232 | int duk_main_add_full_event(event_msg_type msg_type, const event_direction_type direction, uint8_t *payload, const size_t len, const int rssi, const int snr, const time_t ts) 233 | { 234 | event_msg_ptr_t m = malloc(sizeof(event_msg_t)); 235 | m->next = NULL; 236 | m->prev = NULL; 237 | m->msg_type = msg_type; 238 | m->msg_direction = direction; 239 | m->payload = payload; 240 | m->ts = ts; 241 | m->rssi = rssi; 242 | m->snr = snr; 243 | m->payload_len = len; 244 | if (m->payload_len == 0 && m->payload != NULL) 245 | { 246 | m->payload_len = strlen((char *)payload); 247 | } 248 | WORK_QUEUE_RECV_ADD(g->event_queue, m); 249 | duk_main_wake(); 250 | return 1; 251 | } 252 | 253 | int duk_main_add_event(event_msg_type msg_type, event_direction_type direction, uint8_t *payload, size_t len) 254 | { 255 | return duk_main_add_full_event(msg_type, direction, payload, len, 0, 0, 0); 256 | } 257 | 258 | static void work_queue_delete(work_queue_t *q) 259 | { 260 | for (;;) 261 | { 262 | event_msg_ptr_t msg = NULL; 263 | WORK_QUEUE_RECV_GET(g->event_queue, msg); 264 | if (msg != NULL) 265 | { 266 | if (msg->payload) 267 | { 268 | free(msg->payload); 269 | } 270 | free(msg); 271 | } 272 | else 273 | { 274 | break; 275 | } 276 | } 277 | } 278 | 279 | static void duk_init() 280 | { 281 | #ifdef DUK_MAIN_DEBUG 282 | logprintf("%s: start\n", __func__); 283 | #endif 284 | 285 | load: 286 | if (g->ctx != NULL) 287 | { 288 | duk_destroy_heap(g->ctx); 289 | } 290 | g->ctx = duk_create_heap_default(); 291 | // clear queue 292 | work_queue_delete(g->event_queue); 293 | 294 | g->delay = portMAX_DELAY; 295 | g->wake_up_timeMS = 0; 296 | g->reset = 0; 297 | 298 | duk_util_register(g->ctx); 299 | platform_register(g->ctx); 300 | duk_fs_register(g->ctx); 301 | lora_main_register(g->ctx); 302 | crypto_register(g->ctx); 303 | 304 | char *load_name = g->load_file; 305 | if (load_name == NULL) 306 | { 307 | load_name = (char *)load_files[g->load_index]; 308 | } 309 | 310 | #ifdef DUK_MAIN_DEBUG 311 | logprintf("%s: loading %s\n", __func__, load_name); 312 | #endif 313 | if (duk_util_load_and_run(g->ctx, load_name, "OnStart();") == 0) 314 | { 315 | g->load_index++; 316 | g->load_index = g->load_index % LOAD_FILES_NUM; 317 | 318 | if (g->load_file == load_name) 319 | { 320 | free(g->load_file); 321 | g->load_file = NULL; 322 | } 323 | 324 | goto load; 325 | } 326 | else 327 | { 328 | g->load_index = 0; 329 | } 330 | if (g->load_file == load_name) 331 | { 332 | free(g->load_file); 333 | g->load_file = NULL; 334 | } 335 | } 336 | 337 | static void duktape_task(void *ignored) 338 | { 339 | g->duk_main_task_handle = xTaskGetCurrentTaskHandle(); 340 | #ifdef DUK_MAIN_DEBUG 341 | logprintf("%s started\n", __func__); 342 | #endif 343 | 344 | // trigger init 345 | g->reset = 1; 346 | 347 | for (;;) 348 | { 349 | while (g->reset) 350 | { 351 | duk_init(); 352 | } 353 | 354 | int notify = ulTaskNotifyTake(pdTRUE, g->delay); 355 | // time out 356 | if (notify == 0) 357 | { 358 | } 359 | 360 | // process event_queue, timers 361 | for (;;) 362 | { 363 | if (timer_check()) 364 | { 365 | duk_util_run(g->ctx, "OnTimer();"); 366 | } 367 | 368 | event_msg_ptr_t msg = NULL; 369 | WORK_QUEUE_RECV_GET(g->event_queue, msg); 370 | if (msg == NULL) 371 | { 372 | // back to sleep 373 | break; 374 | } 375 | 376 | if (msg->msg_direction == INCOMING) 377 | { 378 | send_event(g->ctx, msg); 379 | } 380 | else 381 | { 382 | if (msg->msg_type == UI_MSG) 383 | { 384 | if (g->send_func) 385 | { 386 | g->send_func(msg->payload, msg->payload_len); 387 | } 388 | } 389 | else 390 | { 391 | #ifdef DUK_MAIN_DEBUG 392 | logprintf("%s: got OUTGOING event that is not UI_MSG\n", __func__); 393 | #endif 394 | } 395 | } 396 | if (msg->payload) 397 | { 398 | free(msg->payload); 399 | } 400 | free(msg); 401 | } 402 | } 403 | } 404 | 405 | static int control_callback(int id, char *data, size_t data_len) 406 | { 407 | #ifdef DUK_MAIN_DEBUG 408 | logprintf("%s: control callback cmd = %d\n", __func__, id); 409 | #endif 410 | switch (id) 411 | { 412 | case CMD_RESET: 413 | { 414 | g->reset = 1; 415 | duk_main_wake(); 416 | } 417 | break; 418 | case CMD_SET_LOAD: 419 | { 420 | if (data != NULL) 421 | { 422 | duk_main_set_load_file(strdup(data)); 423 | } 424 | } 425 | break; 426 | case CMD_REBOOT: 427 | { 428 | esp_restart(); 429 | } 430 | break; 431 | } 432 | return 1; 433 | } 434 | 435 | static int button_callback(int presses, unsigned long int timeout, void *cb_data) 436 | { 437 | // re-enable button 438 | button_service_enable(1); 439 | duk_main_add_event(BUTTON_PRESSED, INCOMING, NULL, presses); 440 | return 1; 441 | } 442 | 443 | void duk_main_start() 444 | { 445 | #ifdef DUK_MAIN_DEBUG 446 | logprintf("%s started\n", __func__); 447 | #endif 448 | 449 | platform_init(); 450 | lora_main_start(); 451 | 452 | board_config_t *board = get_board_config(); 453 | 454 | if (board->button_gpio != -1) 455 | { 456 | #ifdef DUK_MAIN_DEBUG 457 | logprintf("%s: button service active\n", __func__); 458 | #endif 459 | button_service_start(button_callback, NULL); 460 | } 461 | 462 | webserver_set_control_func(control_callback); 463 | 464 | g = malloc(sizeof(struct duk_globals_t)); 465 | g->load_file = NULL; 466 | g->load_index = 0; 467 | g->send_func = NULL; 468 | g->event_queue = NULL; 469 | g->event_queue = malloc(sizeof(work_queue_t)); 470 | WORK_QUEUE_INIT(g->event_queue); 471 | g->ctx = NULL; 472 | 473 | xTaskCreatePinnedToCore(&duktape_task, "duktape_task", 16 * 1024, NULL, 5, NULL, tskNO_AFFINITY); 474 | } 475 | -------------------------------------------------------------------------------- /main/duk_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "duk_helpers.h" 10 | #include "log.h" 11 | 12 | #define DUK_UTIL_DEBUG 1 13 | 14 | static duk_ret_t native_print(duk_context *ctx) 15 | { 16 | duk_push_string(ctx, " "); 17 | duk_insert(ctx, 0); 18 | duk_join(ctx, duk_get_top(ctx) - 1); 19 | printf("%s", duk_safe_to_string(ctx, -1)); 20 | return 0; 21 | } 22 | 23 | void duk_util_register(duk_context *ctx) 24 | { 25 | duk_push_c_function(ctx, native_print, DUK_VARARGS); 26 | duk_put_global_string(ctx, "print"); 27 | } 28 | 29 | int duk_util_run(duk_context *ctx, const char *func_str) 30 | { 31 | int result = 1; 32 | if (duk_peval_string(ctx, func_str) != 0) 33 | { 34 | #ifdef DUK_UTIL_DEBUG 35 | logprintf("%s: eval failed: %s\n", __func__, duk_safe_to_string(ctx, -1)); 36 | #endif 37 | result = 0; 38 | } 39 | #if 0 40 | else 41 | { 42 | logprintf("%s: result is: %s\n", __func__, duk_get_string(ctx, -1)); 43 | } 44 | #endif 45 | duk_pop(ctx); 46 | return result; 47 | } 48 | 49 | int duk_util_call_with_buf(duk_context *ctx, const char *func_name, const uint8_t *inbuf, const size_t len) 50 | { 51 | int result = 1; 52 | duk_push_global_object(ctx); 53 | duk_get_prop_string(ctx, -1, func_name); 54 | uint8_t *buf = (uint8_t *)duk_push_fixed_buffer(ctx, len); 55 | memcpy(buf, inbuf, len); 56 | duk_insert(ctx, -1); 57 | if (duk_pcall(ctx, 1 /*nargs*/) != 0) 58 | { 59 | result = 0; 60 | #ifdef DUK_UTIL_DEBUG 61 | logprintf("%s: eval failed: %s\n", __func__, duk_safe_to_string(ctx, -1)); 62 | #endif 63 | } 64 | duk_pop(ctx); 65 | return result; 66 | } 67 | 68 | char *duk_util_load_file(const char *fname) 69 | { 70 | FILE *fp = fopen(fname, "r"); 71 | if (fp == NULL) 72 | { 73 | logprintf("%s: can't open: %s\n", __func__, fname); 74 | return 0; 75 | } 76 | fseek(fp, 0, SEEK_END); 77 | long int fsize = ftell(fp); 78 | fseek(fp, 0, SEEK_SET); 79 | 80 | char *buff = malloc(fsize + 1); 81 | if (buff == NULL) 82 | { 83 | fclose(fp); 84 | #ifdef DUK_UTIL_DEBUG 85 | logprintf("%s: can't allocate %ld bytes\n", __func__, fsize); 86 | #endif 87 | return NULL; 88 | } 89 | int num = fread(buff, 1, fsize, fp); 90 | fclose(fp); 91 | buff[fsize - 1] = 0; 92 | if (num < fsize) 93 | { 94 | #ifdef DUK_UTIL_DEBUG 95 | logprintf("%s: only read %d out of %ld bytes\n", __func__, num, fsize); 96 | #endif 97 | free(buff); 98 | return NULL; 99 | } 100 | return buff; 101 | } 102 | 103 | int duk_util_load_and_run(duk_context *ctx, const char *fname, const char *func_call) 104 | { 105 | char *buff = duk_util_load_file(fname); 106 | if (buff == NULL) 107 | { 108 | return 0; 109 | } 110 | int result = duk_util_run(ctx, buff); 111 | free(buff); 112 | if (!result) 113 | return result; 114 | if (func_call) 115 | return duk_util_run(ctx, func_call); 116 | return 1; 117 | } 118 | -------------------------------------------------------------------------------- /main/include/ble_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _BLE_SERVER_H_ 6 | #define _BLE_SERVER_H_ 7 | 8 | #include "esp_gap_ble_api.h" 9 | 10 | // return = 0 for success, return != 0 will close connection 11 | typedef int ble_server_receive_callback(const uint8_t *buf, size_t len); 12 | 13 | // returns 0-100 14 | typedef int ble_server_battery_percent(); 15 | 16 | // 1 = connected, 0 = disconnected 17 | typedef void ble_server_conninfo_callback(int); 18 | 19 | void ble_server_set_receive_callback(ble_server_receive_callback *func); 20 | void ble_server_set_battery_percent(ble_server_battery_percent *func); 21 | 22 | int ble_server_send(const uint8_t *buf, const size_t len); 23 | int ble_server_is_connected(); 24 | 25 | void ble_server_accept_bonding(int status); 26 | void ble_support_remove_bonded_device(esp_bd_addr_t *bd_addr); 27 | int ble_support_num_bonded_devices(); 28 | 29 | char* ble_server_get_client_addr(); 30 | void ble_server_set_conninfo_callback(ble_server_conninfo_callback *cb); 31 | void ble_server_set_passkey(uint32_t pkey); 32 | void ble_support_get_bonded_devices(esp_ble_bond_dev_t **dev_list, int *num); 33 | 34 | void ble_server_stop(); 35 | int ble_server_start(); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /main/include/ble_support.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _BLE_SUPPORT_H_ 6 | #define _BLE_SUPPORT_H_ 7 | 8 | char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req); 9 | char *esp_key_type_to_str(esp_ble_key_type_t key_type); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /main/include/board.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef __BOARD_H__ 6 | #define __BOARD_H__ 7 | 8 | typedef uint32_t board_battery_measure_func(); 9 | typedef void board_battery_measure_toggle_func(int); 10 | typedef void board_init_func(); 11 | 12 | typedef enum { 13 | BOARD_TYPE_HUZZAH = 0, 14 | BOARD_TYPE_FLUX, 15 | BOARD_TYPE_LAST 16 | } board_type_t; 17 | 18 | #define NUM_BOARDS BOARD_TYPE_LAST 19 | 20 | typedef struct 21 | { 22 | char boardname[16]; 23 | 24 | int led0_gpio; 25 | int led1_gpio; 26 | 27 | int lora_cs_gpio; 28 | int lora_rst_gpio; 29 | 30 | int lora_miso_gpio; 31 | int lora_mosi_gpio; 32 | int lora_sck_gpio; 33 | 34 | int lora_dio0_gpio; 35 | int lora_dio1_gpio; 36 | int lora_dio2_gpio; 37 | 38 | int bat_adc_gpio; 39 | int bat_en_gpio; 40 | int bat_charge_gpio; 41 | 42 | int usb_con_gpio; 43 | 44 | int button_gpio; 45 | 46 | board_battery_measure_func *board_battery_measure; 47 | board_battery_measure_toggle_func *board_battery_measure_toggle; 48 | board_init_func *board_init; 49 | 50 | } board_config_t; 51 | 52 | 53 | board_type_t get_board_type(); 54 | board_config_t *get_board_config(); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /main/include/button_service.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _BUTTON_SERVICE_H_ 6 | #define _BUTTON_SERVICE_H_ 7 | 8 | typedef int button_service_callback(int presses, unsigned long timeout_msec, void *data); 9 | 10 | void button_service_set_cb(button_service_callback cb, void *cb_data); 11 | void button_server_set_timeout(unsigned long timeout_msec); 12 | void button_service_start(button_service_callback cb, void *cb_data); 13 | void button_service_stop(); 14 | void button_service_enable(int enable); 15 | int button_service_ispressed(); 16 | 17 | // we record num presses over this time (MS) 18 | #define BUTTON_SERVICE_DEFAULT_TIMEOUT 3000 19 | #define BUTTON_PRESS_NUM_MAX 11 20 | // only one press within this time 21 | #define BUTTON_PRESS_MIN_MS pdMS_TO_TICKS(300) 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /main/include/duk_crypto.h: -------------------------------------------------------------------------------- 1 | /* 2 | * based on: https://github.com/nkolban/duktape-esp32.git 3 | * license: Apache 2.0 4 | */ 5 | 6 | #ifndef __duk_crypto_h__ 7 | #define __duk_crypto_h__ 8 | 9 | #include 10 | 11 | void crypto_register(duk_context *ctx); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /main/include/duk_fs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * based on: https://github.com/nkolban/duktape-esp32.git 3 | * license: Apache 2.0 4 | */ 5 | 6 | #ifndef __duk_fs_h__ 7 | #define __duk_fs_h__ 8 | 9 | #include 10 | 11 | duk_ret_t duk_fs_register(duk_context *ctx); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /main/include/duk_helpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * based on: https://github.com/nkolban/duktape-esp32.git 3 | * license: Apache 2.0 4 | */ 5 | 6 | #ifndef __duk_helpers_h__ 7 | #define __duk_helpers_h__ 8 | 9 | #define ADD_FUNCTION(FUNCTION_NAME_STRING, FUNCTION_NAME, PARAM_COUNT) \ 10 | duk_push_c_function(ctx, FUNCTION_NAME, PARAM_COUNT); \ 11 | duk_put_prop_string(ctx, -2, FUNCTION_NAME_STRING) 12 | 13 | #define ADD_STRING(STR_NAME, STR_VALUE) \ 14 | duk_push_string(ctx, STR_VALUE); \ 15 | duk_put_prop_string(ctx, -2, STR_NAME) 16 | 17 | #define ADD_NUMBER(NUM_NAME, NUM_VALUE) \ 18 | duk_push_int(ctx, NUM_VALUE); \ 19 | duk_put_prop_string(ctx, -2, NUM_NAME) 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /main/include/duk_main.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef __duk_main_h__ 6 | #define __duk_main_h__ 7 | 8 | #include 9 | 10 | #include "freertos/FreeRTOS.h" 11 | 12 | typedef enum 13 | { 14 | LORA_MSG = 0, 15 | UI_MSG, 16 | UI_CONNECTED, 17 | UI_DISCONNECTED, 18 | BUTTON_PRESSED, 19 | // not implemented yet 20 | USB_CONNECTED, 21 | USB_DISCONNECTED, 22 | BATT_CHARGING, 23 | BATT_DRAINING, 24 | } event_msg_type; 25 | 26 | typedef enum 27 | { 28 | INCOMING = 0, 29 | OUTGOING = 1, 30 | } event_direction_type; 31 | 32 | typedef int ui_msg_send_func(const uint8_t *buffer, const size_t len); 33 | 34 | int duk_main_add_full_event(event_msg_type msg_type, const event_direction_type direction, uint8_t *payload, const size_t len, const int rssi, const int snr, const time_t ts); 35 | int duk_main_add_event(event_msg_type msg_type, event_direction_type direction, uint8_t *payload, size_t len); 36 | void duk_main_start(); 37 | void duk_main_set_send_func(ui_msg_send_func *func); 38 | void duk_main_set_reset(int rst); 39 | int duk_main_set_wake_up_time(unsigned long int wake_up_timeMS); 40 | void duk_main_set_load_file(char *fname); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /main/include/duk_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef __duk_util_h__ 6 | #define __duk_util_h__ 7 | 8 | #include 9 | 10 | void duk_util_register(duk_context *ctx); 11 | 12 | int duk_util_run(duk_context *ctx, const char *func_str); 13 | int duk_util_call_with_buf(duk_context *ctx, const char *func_name, const uint8_t *inbuf, const size_t len); 14 | int duk_util_load_and_run(duk_context *ctx, const char *fname, const char *func_call); 15 | char *duk_util_load_file(const char *fname); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /main/include/led_service.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _LED_SERVICE_H_ 6 | #define _LED_SERVICE_H_ 7 | 8 | enum BLINK_T { 9 | SLOW = 0, 10 | MEDIUM, 11 | FAST, 12 | NOT 13 | }; 14 | 15 | enum LED_ID_T { 16 | RED = 0, 17 | GREEN 18 | }; 19 | 20 | void led_set(enum LED_ID_T id, int mode); 21 | 22 | // ID = 0-1, speed = BLINK_T, brigtness = 0.0-1.0 23 | void led_service_blink_led(enum LED_ID_T id, enum BLINK_T speed, float brightness); 24 | void led_service_blink_led_stop(enum LED_ID_T id); 25 | 26 | void led_service_init(int red, int green); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /main/include/log.h: -------------------------------------------------------------------------------- 1 | #ifndef __log_h__ 2 | #define __log_h__ 3 | 4 | #define logprintf printf 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /main/include/lora_main.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _LORA_MAIN_H_ 6 | #define _LORA_MAIN_H_ 7 | 8 | #include 9 | 10 | #define LORA_MSG_MAX_SIZE 255 11 | 12 | int lora_main_register(duk_context *ctx); 13 | int lora_main_start(); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /main/include/platform.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef __platform_h__ 6 | #define __platform_h__ 7 | 8 | #include 9 | 10 | void platform_register(duk_context *ctx); 11 | void platform_init(); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /main/include/queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _QUEUE_H_ 6 | #define _QUEUE_H_ 7 | 8 | typedef struct 9 | { 10 | void *send_queue; 11 | void *recv_queue; 12 | SemaphoreHandle_t send_queue_mutex; 13 | SemaphoreHandle_t recv_queue_mutex; 14 | } work_queue_t; 15 | 16 | #define WORK_QUEUE_INIT(q) \ 17 | do \ 18 | { \ 19 | q->send_queue = NULL; \ 20 | q->recv_queue = NULL; \ 21 | q->send_queue_mutex = xSemaphoreCreateMutex(); \ 22 | q->recv_queue_mutex = xSemaphoreCreateMutex(); \ 23 | } while (0) 24 | 25 | #define QUEUE_DL_APPEND(head, entry) \ 26 | do \ 27 | { \ 28 | if (head) \ 29 | { \ 30 | typeof(entry) _tmp = head; \ 31 | while (_tmp->next != NULL) \ 32 | { \ 33 | _tmp = _tmp->next; \ 34 | } \ 35 | entry->prev = _tmp; \ 36 | _tmp->next = entry; \ 37 | } \ 38 | else \ 39 | { \ 40 | head = (typeof(head))entry; \ 41 | } \ 42 | } while (0) 43 | 44 | // -- RECV -- 45 | 46 | // get length of queue - do we need to add locking? 47 | #define WORK_QUEUE_RECV_LEN(q, q_length, entry) \ 48 | do \ 49 | { \ 50 | q_length = 0; \ 51 | typeof(entry) _q_l_head = q->recv_queue; \ 52 | while (_q_l_head != NULL) \ 53 | { \ 54 | q_length++; \ 55 | _q_l_head = _q_l_head->next; \ 56 | } \ 57 | } while (0) 58 | 59 | // append item at end 60 | #define WORK_QUEUE_RECV_ADD(q, node) \ 61 | do \ 62 | { \ 63 | xSemaphoreTake(q->recv_queue_mutex, portMAX_DELAY); \ 64 | QUEUE_DL_APPEND(q->recv_queue, node); \ 65 | xSemaphoreGive(q->recv_queue_mutex); \ 66 | } while (0) 67 | 68 | // add item as new head 69 | #define WORK_QUEUE_RECV_INSERT_HEAD(q, node) \ 70 | do \ 71 | { \ 72 | xSemaphoreTake(q->recv_queue_mutex, portMAX_DELAY); \ 73 | typeof(node) __head = q->recv_queue; \ 74 | q->recv_queue = node; \ 75 | if (__head) \ 76 | QUEUE_DL_APPEND(q->recv_queue, __head); \ 77 | xSemaphoreGive(q->recv_queue_mutex); \ 78 | } while (0) 79 | 80 | // get first item 81 | #define WORK_QUEUE_RECV_GET(q, item) \ 82 | do \ 83 | { \ 84 | xSemaphoreTake(q->recv_queue_mutex, portMAX_DELAY); \ 85 | item = (typeof(item))q->recv_queue; \ 86 | if (item != NULL) \ 87 | { \ 88 | if (item->next == NULL) \ 89 | { \ 90 | q->recv_queue = NULL; \ 91 | } \ 92 | else \ 93 | { \ 94 | q->recv_queue = (void *)item->next; \ 95 | } \ 96 | } \ 97 | xSemaphoreGive(q->recv_queue_mutex); \ 98 | } while (0) 99 | 100 | // take head and all attached nodes (emptys queue) 101 | #define WORK_QUEUE_RECV_TAKE_HEAD(q, head) \ 102 | do \ 103 | { \ 104 | xSemaphoreTake(q->recv_queue_mutex, portMAX_DELAY); \ 105 | head = q->recv_queue; \ 106 | q->recv_queue = NULL; \ 107 | xSemaphoreGive(q->recv_queue_mutex); \ 108 | } while (0) 109 | 110 | // -- SEND -- 111 | 112 | // append item at end 113 | #define WORK_QUEUE_SEND_ADD(q, node) \ 114 | do \ 115 | { \ 116 | xSemaphoreTake(q->send_queue_mutex, portMAX_DELAY); \ 117 | QUEUE_DL_APPEND(q->send_queue, node); \ 118 | xSemaphoreGive(q->send_queue_mutex); \ 119 | } while (0) 120 | 121 | // add item as new head 122 | #define WORK_QUEUE_SEND_INSERT_HEAD(q, node) \ 123 | do \ 124 | { \ 125 | xSemaphoreTake(q->send_queue_mutex, portMAX_DELAY); \ 126 | typeof(node) __head = q->send_queue; \ 127 | q->send_queue = node; \ 128 | if (__head) \ 129 | QUEUE_DL_APPEND(q->send_queue, __head); \ 130 | xSemaphoreGive(q->send_queue_mutex); \ 131 | } while (0) 132 | 133 | // get first item 134 | #define WORK_QUEUE_SEND_GET(q, item) \ 135 | do \ 136 | { \ 137 | xSemaphoreTake(q->send_queue_mutex, portMAX_DELAY); \ 138 | item = (typeof(item))q->send_queue; \ 139 | if (item != NULL) \ 140 | { \ 141 | if (item->next == NULL) \ 142 | { \ 143 | q->send_queue = NULL; \ 144 | } \ 145 | else \ 146 | { \ 147 | q->send_queue = (void *)item->next; \ 148 | } \ 149 | } \ 150 | xSemaphoreGive(q->send_queue_mutex); \ 151 | } while (0) 152 | 153 | // take head and all attached nodes (emptys queue) 154 | #define WORK_QUEUE_SEND_TAKE_HEAD(q, head) \ 155 | do \ 156 | { \ 157 | xSemaphoreTake(q->send_queue_mutex, portMAX_DELAY); \ 158 | head = q->send_queue; \ 159 | q->send_queue = NULL; \ 160 | xSemaphoreGive(q->send_queue_mutex); \ 161 | } while (0) 162 | 163 | #endif 164 | -------------------------------------------------------------------------------- /main/include/record.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _RECORD_H_ 6 | #define _RECORD_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | typedef int send_func(const uint8_t *buf, const size_t len, const void *data); 13 | 14 | struct record_t 15 | { 16 | size_t recordSize; 17 | send_func *send; 18 | void *send_data; 19 | int lenbytes; 20 | size_t len; 21 | size_t cur; 22 | uint8_t *buf; 23 | }; 24 | 25 | void record_add(struct record_t *r, const uint8_t *buf, const size_t len); 26 | size_t record_len(struct record_t *r); 27 | uint8_t *record_get(struct record_t *r); 28 | int record_complete(struct record_t *r); 29 | int record_send(struct record_t *r, const uint8_t *buf, const size_t len); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /main/include/udp_service.h: -------------------------------------------------------------------------------- 1 | #ifndef __UDP_SERVICE__ 2 | #define __UDP_SERVICE__ 3 | 4 | typedef int udp_service_cb_func(uint8_t *buf, size_t len); 5 | 6 | struct udp_service_type 7 | { 8 | int udp_srv_sock; 9 | int running; 10 | 11 | udp_service_cb_func *callback; 12 | }; 13 | 14 | int udp_server_start(int port, udp_service_cb_func *callback;); 15 | int udp_server_stop(); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /main/include/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _UTIL_H_ 6 | #define _UTIL_H_ 7 | 8 | // precent 0-100 9 | int battery_percent(); 10 | float battery_mvolt(); 11 | 12 | int usb_isconnected(); 13 | int batt_charging(); 14 | 15 | void util_init(); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /main/include/version: -------------------------------------------------------------------------------- 1 | #define FLUXNODE_VERSION "0.1.0- -------------------------------------------------------------------------------- /main/include/vfs_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef __vfs_config_h__ 6 | #define __vfs_config_h__ 7 | 8 | #define BASE_PATH "" 9 | #define FS_MAX_NUM_FILES 10 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /main/include/web_service.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef __web_service_h__ 6 | #define __web_service_h__ 7 | 8 | typedef enum { 9 | CMD_NONE = 0, 10 | CMD_RESET, 11 | CMD_SET_LOAD, 12 | CMD_REBOOT, 13 | CMD_DELETE_FILE, 14 | } control_cmd_t; 15 | 16 | void webserver_stop(); 17 | int webserver_start(int read_only); 18 | 19 | typedef int webserver_control_function_t(int, char*, size_t); 20 | 21 | void webserver_set_control_func(webserver_control_function_t *func); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /main/include/wifi_service.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #ifndef _WIFI_SERVICE_H_ 6 | #define _WIFI_SERVICE_H_ 7 | 8 | typedef enum 9 | { 10 | wifi_mode_ap = 0, 11 | wifi_mode_sta, 12 | } wifi_service_mode_t; 13 | 14 | void wifi_stop(); 15 | int wifi_start(wifi_service_mode_t mode, char *ssid, char *password); 16 | char *wifi_service_get_ip(); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /main/led_service.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "freertos/FreeRTOS.h" 9 | #include "freertos/task.h" 10 | #include "driver/ledc.h" 11 | #include "esp_sleep.h" 12 | #include "driver/rtc_io.h" 13 | #include "soc/rtc.h" 14 | #include "esp_pm.h" 15 | 16 | #include "driver/uart.h" 17 | #include "esp32/rom/uart.h" 18 | 19 | #include "log.h" 20 | #include "led_service.h" 21 | 22 | static int green_led_gpio = -1; 23 | static int red_led_gpio = -1; 24 | 25 | static void led_cfg(int id, int hz) 26 | { 27 | int gpio; 28 | int timer_num; 29 | int channel; 30 | 31 | switch (id) 32 | { 33 | case RED: 34 | gpio = red_led_gpio; 35 | timer_num = 1; 36 | channel = LEDC_CHANNEL_0; 37 | break; 38 | case GREEN: 39 | gpio = green_led_gpio; 40 | timer_num = 2; 41 | channel = LEDC_CHANNEL_1; 42 | break; 43 | default: 44 | return; 45 | } 46 | 47 | ledc_timer_config_t ledc_timer = { 48 | .duty_resolution = LEDC_TIMER_13_BIT, 49 | .freq_hz = hz, 50 | .speed_mode = LEDC_LOW_SPEED_MODE, 51 | .timer_num = timer_num, 52 | .clk_cfg = LEDC_USE_RTC8M_CLK, 53 | }; 54 | ledc_timer_config(&ledc_timer); 55 | 56 | ledc_channel_config_t ledc_channel = { 57 | .channel = channel, 58 | .duty = 0, 59 | .gpio_num = gpio, 60 | .speed_mode = LEDC_LOW_SPEED_MODE, 61 | .hpoint = 0, 62 | .timer_sel = timer_num, 63 | }; 64 | ledc_channel_config(&ledc_channel); 65 | } 66 | 67 | static void internal_led_set(enum LED_ID_T id, int value) 68 | { 69 | int channel; 70 | 71 | switch (id) 72 | { 73 | case RED: 74 | channel = LEDC_CHANNEL_0; 75 | break; 76 | case GREEN: 77 | channel = LEDC_CHANNEL_1; 78 | break; 79 | default: 80 | return; 81 | } 82 | ledc_set_duty(LEDC_LOW_SPEED_MODE, channel, value); 83 | ledc_update_duty(LEDC_LOW_SPEED_MODE, channel); 84 | } 85 | 86 | void led_service_blink_led(enum LED_ID_T id, enum BLINK_T speed, float brightness) 87 | { 88 | int hz; 89 | int value; 90 | 91 | switch (speed) 92 | { 93 | case SLOW: 94 | hz = 2; 95 | break; 96 | case MEDIUM: 97 | hz = 5; 98 | break; 99 | case FAST: 100 | hz = 10; 101 | break; 102 | default: 103 | hz = 100; 104 | break; 105 | } 106 | 107 | led_cfg(id, hz); 108 | 109 | if (brightness > 1.0 || brightness < 0.0) 110 | { 111 | brightness = 1.0; 112 | } 113 | 114 | value = 8192.0 * brightness; 115 | 116 | #ifdef LS_DEBUG 117 | logprintf("%s: value = %d\n", __func__, value); 118 | #endif 119 | internal_led_set(id, value); 120 | } 121 | 122 | void led_service_blink_led_stop(enum LED_ID_T id) 123 | { 124 | ledc_stop(LEDC_LOW_SPEED_MODE, id == RED ? LEDC_CHANNEL_0 : LEDC_CHANNEL_1, 0); 125 | } 126 | 127 | #if 0 128 | /* 129 | * pattern is 0 = both off, G = green on, R = red on, 1 = both on, '_' = 100ms pause 130 | * 131 | * pattern = blink pattern, num = number of times to play it in a row 132 | */ 133 | void led_service_pattern_blink(const char *pattern, int num) 134 | { 135 | gpio_pad_select_gpio(red_led_gpio); 136 | gpio_set_direction(red_led_gpio, GPIO_MODE_OUTPUT); 137 | gpio_pad_select_gpio(green_led_gpio); 138 | gpio_set_direction(green_led_gpio, GPIO_MODE_OUTPUT); 139 | 140 | int pattern_len = strlen(pattern); 141 | 142 | while (num > 0) 143 | { 144 | int i; 145 | for (i = 0; i < pattern_len; i++) 146 | { 147 | switch (pattern[i]) 148 | { 149 | case '0': 150 | gpio_set_level(red_led_gpio, 0); 151 | gpio_set_level(green_led_gpio, 0); 152 | break; 153 | case 'G': 154 | gpio_set_level(red_led_gpio, 0); 155 | gpio_set_level(green_led_gpio, 1); 156 | break; 157 | case 'R': 158 | gpio_set_level(red_led_gpio, 1); 159 | gpio_set_level(green_led_gpio, 0); 160 | break; 161 | case '_': 162 | vTaskDelay(100 / portTICK_PERIOD_MS); 163 | break; 164 | case '1': 165 | gpio_set_level(red_led_gpio, 1); 166 | gpio_set_level(green_led_gpio, 1); 167 | break; 168 | } 169 | } 170 | num--; 171 | } 172 | } 173 | #endif 174 | 175 | void led_set(enum LED_ID_T id, int mode) 176 | { 177 | int led = red_led_gpio; 178 | if (id == GREEN) 179 | { 180 | led = green_led_gpio; 181 | } 182 | if (led == -1) 183 | { 184 | return; 185 | } 186 | gpio_set_level(led, mode); 187 | } 188 | 189 | void led_service_init(int red, int green) 190 | { 191 | green_led_gpio = green; 192 | red_led_gpio = red; 193 | 194 | if (green_led_gpio != -1) 195 | { 196 | gpio_pad_select_gpio(green_led_gpio); 197 | gpio_set_direction(green_led_gpio, GPIO_MODE_OUTPUT); 198 | } 199 | 200 | if (red_led_gpio != -1) 201 | { 202 | gpio_pad_select_gpio(red_led_gpio); 203 | gpio_set_direction(red_led_gpio, GPIO_MODE_OUTPUT); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "freertos/FreeRTOS.h" 9 | #include "driver/rtc_io.h" 10 | 11 | #include "esp_spiffs.h" 12 | #include "nvs_flash.h" 13 | #include "driver/gpio.h" 14 | #include "soc/rtc.h" 15 | #include "esp_pm.h" 16 | #include "esp_sleep.h" 17 | #include "soc/sens_periph.h" 18 | #include "bootloader_random.h" 19 | 20 | #include "duk_main.h" 21 | #include "board.h" 22 | #include "vfs_config.h" 23 | #include "log.h" 24 | #include 25 | 26 | time_t boottime; 27 | 28 | void app_main() 29 | { 30 | board_config_t *board = get_board_config(); 31 | 32 | esp_err_t ret = nvs_flash_init(); 33 | ESP_ERROR_CHECK(ret); 34 | 35 | esp_vfs_spiffs_conf_t conf = { 36 | .base_path = BASE_PATH, 37 | .partition_label = NULL, 38 | .max_files = FS_MAX_NUM_FILES, 39 | .format_if_mount_failed = true}; 40 | ret = esp_vfs_spiffs_register(&conf); 41 | ESP_ERROR_CHECK(ret); 42 | 43 | // general ISR service 44 | gpio_install_isr_service(0); 45 | 46 | // enable random source 47 | bootloader_random_enable(); 48 | 49 | // board specific init, e.g. power managenent and wakeup 50 | board->board_init(); 51 | 52 | boottime = time(0); 53 | 54 | esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); 55 | // enable auto sleep 56 | esp_pm_config_esp32_t pm_config = { 57 | .max_freq_mhz = 80, 58 | .min_freq_mhz = 40, 59 | .light_sleep_enable = true}; 60 | ESP_ERROR_CHECK(esp_pm_configure(&pm_config)); 61 | 62 | duk_main_start(); 63 | } 64 | -------------------------------------------------------------------------------- /main/record.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef RECORD_TEST 11 | #include "log.h" 12 | #endif 13 | #include "record.h" 14 | 15 | void record_add(struct record_t *r, const uint8_t *buf, const size_t len) 16 | { 17 | if (r->len == 0) 18 | { 19 | if (r->lenbytes == 2) 20 | { 21 | r->len = (buf[0] | (buf[1] << 8)); 22 | #ifdef RECORD_DEBUG_2 23 | logprintf("%s: %d\n", __func__, r->len); 24 | #endif 25 | } 26 | r->buf = malloc(r->len); 27 | memcpy(r->buf, buf + 2, len - 2); 28 | r->cur = (len - 2); 29 | } 30 | else 31 | { 32 | if (r->len >= r->cur + len) 33 | { 34 | memcpy(r->buf + r->cur, buf, len); 35 | r->cur += len; 36 | } 37 | } 38 | #ifdef RECORD_DEBUG_2 39 | logprintf("%s: rec cur = %d\n", __func__, r->cur); 40 | #endif 41 | } 42 | 43 | size_t record_len(struct record_t *r) 44 | { 45 | return r->len; 46 | } 47 | 48 | int record_complete(struct record_t *r) 49 | { 50 | return r->len == r->cur; 51 | } 52 | 53 | unsigned char *record_get(struct record_t *r) 54 | { 55 | uint8_t *buf = r->buf; 56 | r->buf = NULL; 57 | r->len = 0; 58 | r->cur = 0; 59 | return buf; 60 | } 61 | 62 | int record_send(struct record_t *r, const uint8_t *buf, const size_t len) 63 | { 64 | size_t total_len = len + r->lenbytes; 65 | int numRecords = total_len / r->recordSize; 66 | if (total_len % r->recordSize > 0) 67 | { 68 | numRecords++; 69 | } 70 | #ifdef RECORD_DEBUG_2 71 | logprintf("%s: rec %d\n", __func__, numRecords); 72 | #endif 73 | 74 | int alloc_size = r->recordSize; 75 | if (len + 2 < alloc_size) 76 | { 77 | alloc_size = len + 2; 78 | } 79 | uint8_t *data = malloc(alloc_size); 80 | data[0] = (len & 0xff); 81 | data[1] = ((len >> 8) & 0xff); 82 | memcpy(&data[2], buf, alloc_size - 2); 83 | int res = r->send(data, alloc_size, r->send_data); 84 | free(data); 85 | if (res != 0) 86 | { 87 | return res; 88 | } 89 | 90 | size_t cur = alloc_size - 2; 91 | size_t cur_len = r->recordSize; 92 | 93 | for (int i = 1; i < numRecords; i++) 94 | { 95 | if (cur + cur_len > len) 96 | { 97 | cur_len = len - cur; 98 | } 99 | #ifdef RECORD_DEBUG_2 100 | logprintf("%s: %d %d\n", __func__, cur, cur_len); 101 | #endif 102 | int res = r->send(buf + cur, cur_len, r->send_data); 103 | if (res != 0) 104 | { 105 | return res; 106 | } 107 | cur += cur_len; 108 | } 109 | return 0; 110 | } 111 | 112 | #ifdef RECORD_TEST 113 | 114 | #include 115 | 116 | struct test_t 117 | { 118 | struct record_t *r; 119 | char *test; 120 | }; 121 | 122 | int snd(const uint8_t *b, const size_t len, const void *d) 123 | { 124 | struct test_t *t = (struct test_t *)d; 125 | 126 | record_add(t->r, b, len); 127 | if (record_complete(t->r)) 128 | { 129 | size_t l = record_len(t->r); 130 | uint8_t *m = record_get(t->r); 131 | printf("%ld '%s' expected '%s'\n", l, m, t->test); 132 | assert(strcmp(m, t->test) == 0); 133 | free(m); 134 | } 135 | } 136 | 137 | int main() 138 | { 139 | struct test_t t; 140 | 141 | struct record_t r; 142 | r.lenbytes = 2; 143 | r.recordSize = 10; 144 | r.len = 0; 145 | r.cur = 0; 146 | r.buf = 0; 147 | r.send_data = &t; 148 | r.send = snd; 149 | 150 | t.r = &r; 151 | 152 | char *buf = strdup("hi hallo you what up beer? yeah?"); 153 | t.test = buf; 154 | record_send(&r, buf, strlen(buf)); 155 | buf = strdup("hi"); 156 | t.test = buf; 157 | record_send(&r, buf, strlen(buf)); 158 | buf = strdup("12345678"); 159 | t.test = buf; 160 | record_send(&r, buf, strlen(buf)); 161 | buf = strdup("1234567890"); 162 | t.test = buf; 163 | record_send(&r, buf, strlen(buf)); 164 | } 165 | #endif 166 | -------------------------------------------------------------------------------- /main/udp_service.c: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "freertos/event_groups.h" 4 | #include "esp_system.h" 5 | #include "esp_wifi.h" 6 | 7 | #include "lwip/err.h" 8 | #include "lwip/sys.h" 9 | 10 | #include "lwip/err.h" 11 | #include "lwip/sockets.h" 12 | #include "lwip/sys.h" 13 | #include 14 | #include 15 | 16 | #include "udp_service.h" 17 | 18 | struct udp_service_type udp_server; 19 | 20 | static void udp_server_task(void *p) 21 | { 22 | struct udp_service_type *data = (struct udp_service_type *)p; 23 | 24 | uint8_t *buf = malloc(2048); 25 | while (data->running) 26 | { 27 | size_t rb = recv(data->udp_srv_sock, buf, 2048, 0); 28 | if (data->callback != NULL) 29 | { 30 | data->callback(buf, rb); 31 | } 32 | } 33 | free(buf); 34 | } 35 | 36 | int udp_server_start(int port, udp_service_cb_func *callback) 37 | { 38 | struct sockaddr_in srv_addr; 39 | 40 | srv_addr.sin_addr.s_addr = inet_addr("0.0.0.0"); 41 | srv_addr.sin_family = AF_INET; 42 | srv_addr.sin_port = htons(port); 43 | 44 | int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); 45 | if (sock == -1) 46 | { 47 | return 0; 48 | } 49 | if (bind(sock, (struct sockaddr *)&srv_addr, sizeof(struct sockaddr_in)) == -1) 50 | { 51 | return 0; 52 | } 53 | 54 | udp_server.udp_srv_sock = sock; 55 | udp_server.running = 1; 56 | udp_server.callback = callback; 57 | 58 | return xTaskCreate(&udp_server_task, "udp_server_task", 2048, &udp_server, 5, NULL); 59 | } 60 | 61 | int udp_server_stop() 62 | { 63 | udp_server.running = 0; 64 | close(udp_server.udp_srv_sock); 65 | return 1; 66 | } 67 | -------------------------------------------------------------------------------- /main/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "freertos/FreeRTOS.h" 9 | #include "freertos/task.h" 10 | #include "driver/gpio.h" 11 | #include 12 | #include "esp_adc_cal.h" 13 | 14 | #include "log.h" 15 | #include "util.h" 16 | #include "board.h" 17 | 18 | //#define UTIL_DEBUG_1 1 19 | 20 | #define BATTERY_VOLT_MIN 3300.0 21 | #define BATTERY_VOLT_MAX 4200.0 22 | #define BATTERY_MEASURE_NUM 10 23 | float battery_mvolt() 24 | { 25 | board_config_t *board = get_board_config(); 26 | 27 | if (board->board_battery_measure == NULL) 28 | { 29 | return 0; 30 | } 31 | 32 | if (board->board_battery_measure_toggle) 33 | { 34 | board->board_battery_measure_toggle(1); 35 | } 36 | 37 | float mvolt = 0; 38 | for (int i = 0; i < BATTERY_MEASURE_NUM; i++) 39 | { 40 | mvolt += (float)board->board_battery_measure(); 41 | } 42 | 43 | if (board->board_battery_measure_toggle) 44 | { 45 | board->board_battery_measure_toggle(0); 46 | } 47 | mvolt = mvolt / (float)BATTERY_MEASURE_NUM; 48 | return mvolt; 49 | } 50 | 51 | int battery_percent() 52 | { 53 | float mvolt = battery_mvolt(); 54 | #ifdef UTIL_DEBUG_1 55 | logprintf("%s: %d mV\n", __func__, (int)mvolt); 56 | #endif 57 | float volt = (mvolt - BATTERY_VOLT_MIN) / (BATTERY_VOLT_MAX - BATTERY_VOLT_MIN); 58 | volt = volt * 100.0; 59 | return volt > 100 ? 100 : volt; 60 | } 61 | 62 | int batt_charging() 63 | { 64 | board_config_t *board = get_board_config(); 65 | 66 | if (board->bat_charge_gpio == -1) 67 | { 68 | return 0; 69 | } 70 | gpio_pad_select_gpio(board->bat_charge_gpio); 71 | gpio_set_direction(board->bat_charge_gpio, GPIO_MODE_INPUT); 72 | return !gpio_get_level(board->bat_charge_gpio); 73 | } 74 | 75 | int usb_isconnected() 76 | { 77 | board_config_t *board = get_board_config(); 78 | 79 | if (board->usb_con_gpio == -1) 80 | { 81 | return 0; 82 | } 83 | gpio_pad_select_gpio(board->usb_con_gpio); 84 | gpio_set_direction(board->usb_con_gpio, GPIO_MODE_INPUT); 85 | return gpio_get_level(board->usb_con_gpio); 86 | } 87 | 88 | void util_init() 89 | { 90 | // configure GPIOs 91 | } 92 | -------------------------------------------------------------------------------- /main/web_service.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "freertos/FreeRTOS.h" 11 | #include "freertos/task.h" 12 | #include "freertos/event_groups.h" 13 | #include "esp_sleep.h" 14 | #include "driver/rtc_io.h" 15 | #include "soc/sens_periph.h" 16 | #include "soc/rtc.h" 17 | #include "freertos/queue.h" 18 | #include "esp_spiffs.h" 19 | #include "nvs_flash.h" 20 | #include 21 | 22 | #include "log.h" 23 | #include "vfs_config.h" 24 | #include "web_service.h" 25 | 26 | //#define WEBSERV_DEBUG 1 27 | 28 | /* jsondoc 29 | { 30 | "class": "Web Service", 31 | "longtext": " 32 | The Web Service provides a minimal HTTP server to implement a web application. 33 | The web service includes a websocket server that allows for one connection. 34 | The websocket connection is transparently provided to the JavaScript 35 | runtime via the OnEvent() function. 36 | 37 | The websocket server is listening on port `8888`. 38 | 39 | `192.168.4.1` is the default IP for Fluxn0de in Wifi AP mode. 40 | 41 | The `web_mode` parameter of Platform.wifiConfigure() allows to disable `/control` and 42 | POST to `/file`. 43 | 44 | ## API 45 | 46 | The API will not allow to read and write files that start with `_`. 47 | This allows the device to store `private` data. 48 | 49 | ### URL: / 50 | 51 | - GET will read /index.html 52 | 53 | ### /file?\\ 54 | 55 | - GET to read \\ 56 | - POST to write to \\ 57 | 58 | ### /control?\\ 59 | 60 | - GET to execute the \\ 61 | 62 | Commands: 63 | - **reset** (reset the JavaScript runtime, same as Platform.reset()) 64 | - **setload=\\** (set the load file, same as Platform.setLoadFileName(filename)) 65 | - **reboot** (reboot the board, same as Platform.reboot()) 66 | - **deletefile=\\** (delete \\, same as FileSystem.unlink(filename)) 67 | 68 | Example: 69 | ``` 70 | 71 | Upload test42.js to the board. 72 | $ http://192.168.4.1/file?test42.js --data-binary @test42.js 73 | OK 74 | 75 | Set /test42.js as the application to run 76 | $ http://192.168.4.1/control?setload=/test42.js 77 | OK 78 | 79 | Reset JavaScript runtime and run test42.js 80 | $ curl http://192.168.4.1/control?reset 81 | OK 82 | 83 | ``` 84 | 85 | " 86 | } 87 | */ 88 | 89 | #define FS_BASE "/" 90 | #define BUF_SIZE 1024 91 | #define FILE_URI "/file" 92 | #define CONTROL_URI "/control" 93 | #define PROTECTED_FILE '_' 94 | 95 | struct control_name_t 96 | { 97 | char name[16]; 98 | control_cmd_t id; 99 | }; 100 | 101 | static struct control_name_t control_names[] = { 102 | {"reset", CMD_RESET}, 103 | {"setload", CMD_SET_LOAD}, 104 | {"reboot", CMD_REBOOT}, 105 | {"deletefile", CMD_DELETE_FILE}, 106 | {"", CMD_NONE}, 107 | }; 108 | 109 | static int running = 0; 110 | static httpd_handle_t server; 111 | static webserver_control_function_t *control_func = NULL; 112 | 113 | void webserver_set_control_func(webserver_control_function_t *func) 114 | { 115 | control_func = func; 116 | } 117 | 118 | static esp_err_t post_handler(httpd_req_t *req) 119 | { 120 | size_t url_len = httpd_req_get_url_query_len(req); 121 | char *url = malloc(url_len + 1); 122 | httpd_req_get_url_query_str(req, url, url_len + 1); 123 | #ifdef WEBSERV_DEBUG 124 | logprintf("%s POST '%s'\n", __func__, url); 125 | #endif 126 | 127 | if (url[0] == PROTECTED_FILE) 128 | { 129 | free(url); 130 | httpd_resp_sendstr(req, "Access Denied"); 131 | return ESP_FAIL; 132 | } 133 | 134 | char *fname = malloc(sizeof(FS_BASE) + url_len + 1); 135 | sprintf(fname, "%s%s", FS_BASE, url); 136 | free(url); 137 | 138 | FILE *fp = fopen(fname, "w+"); 139 | if (fp == NULL) 140 | { 141 | #ifdef WEBSERV_DEBUG 142 | logprintf("%s: can't create file '%s'\n", __func__, fname); 143 | #endif 144 | free(fname); 145 | httpd_resp_sendstr(req, "FileCreateError"); 146 | return ESP_FAIL; 147 | } 148 | else 149 | { 150 | #ifdef WEBSERV_DEBUG 151 | logprintf("%s: writing %s with %d bytes\n", __func__, fname, req->content_len); 152 | #endif 153 | } 154 | free(fname); 155 | 156 | char *buf = malloc(BUF_SIZE); 157 | int bread = 0; 158 | for (;;) 159 | { 160 | int ret = httpd_req_recv(req, buf, BUF_SIZE); 161 | if (ret <= 0) 162 | { // 0 return value indicates connection closed 163 | // Check if timeout occurred 164 | if (ret == HTTPD_SOCK_ERR_TIMEOUT) 165 | { 166 | httpd_resp_send_408(req); 167 | } 168 | free(buf); 169 | fclose(fp); 170 | httpd_resp_sendstr(req, "Error"); 171 | return ESP_FAIL; 172 | } 173 | fwrite(buf, ret, 1, fp); 174 | bread += ret; 175 | if (bread >= req->content_len) 176 | { 177 | // add a new line at the end of the file, for the Duktape parser 178 | fwrite("\n", 1, 1, fp); 179 | break; 180 | } 181 | } 182 | fclose(fp); 183 | free(buf); 184 | httpd_resp_sendstr(req, "OK"); 185 | 186 | return ESP_OK; 187 | } 188 | 189 | static esp_err_t get_file_by_name(httpd_req_t *req, const char *name) 190 | { 191 | char *fname = malloc(sizeof(FS_BASE) + strlen(name) + 1); 192 | sprintf(fname, "%s%s", FS_BASE, name); 193 | #ifdef WEBSERV_DEBUG 194 | logprintf("%s read file '%s'\n", __func__, fname); 195 | #endif 196 | FILE *fp = fopen(fname, "r"); 197 | free(fname); 198 | if (fp == NULL) 199 | { 200 | #ifdef WEBSERV_DEBUG 201 | logprintf("%s: file does not exist\n", __func__); 202 | #endif 203 | httpd_resp_sendstr(req, "NoSuchFileError"); 204 | return ESP_FAIL; 205 | } 206 | 207 | char *buf = malloc(BUF_SIZE); 208 | for (;;) 209 | { 210 | int bs = fread(buf, 1, BUF_SIZE, fp); 211 | int ret = httpd_resp_send_chunk(req, buf, bs); 212 | // 0 return value indicates connection closed 213 | if (ret <= 0) 214 | { 215 | if (ret != ESP_OK) 216 | { 217 | #ifdef WEBSERV_DEBUG 218 | logprintf("%s: send error\n", __func__); 219 | #endif 220 | free(buf); 221 | fclose(fp); 222 | httpd_resp_sendstr(req, "Error"); 223 | return ESP_FAIL; 224 | } 225 | } 226 | if (bs == 0) 227 | { 228 | break; 229 | } 230 | else if (bs < BUF_SIZE) 231 | { 232 | httpd_resp_send_chunk(req, buf, 0); 233 | break; 234 | } 235 | } 236 | fclose(fp); 237 | free(buf); 238 | 239 | return ESP_OK; 240 | } 241 | 242 | static esp_err_t get_handler(httpd_req_t *req) 243 | { 244 | size_t url_len = httpd_req_get_url_query_len(req); 245 | char *url = malloc(url_len + 1); 246 | httpd_req_get_url_query_str(req, url, url_len + 1); 247 | #ifdef WEBSERV_DEBUG 248 | logprintf("%s GET '%s'\n", __func__, url); 249 | #endif 250 | if (url[0] == PROTECTED_FILE) 251 | { 252 | free(url); 253 | httpd_resp_sendstr(req, "Access Denied"); 254 | return ESP_FAIL; 255 | } 256 | 257 | esp_err_t res = get_file_by_name(req, url); 258 | free(url); 259 | return res; 260 | } 261 | 262 | static esp_err_t control_handler(httpd_req_t *req) 263 | { 264 | size_t url_len = httpd_req_get_url_query_len(req); 265 | char *url = malloc(url_len + 1); 266 | httpd_req_get_url_query_str(req, url, url_len + 1); 267 | #ifdef WEBSERV_DEBUG 268 | logprintf("%s GET '%s'\n", __func__, url); 269 | #endif 270 | int n = 0; 271 | char *data = NULL; 272 | size_t data_len = 0; 273 | int res = 0; 274 | 275 | int idx = 0; 276 | for (;;) 277 | { 278 | if (control_names[idx].id == 0) 279 | { 280 | break; 281 | } 282 | if (strncmp(control_names[idx].name, url, strlen(control_names[idx].name)) == 0) 283 | { 284 | n = control_names[idx].id; 285 | break; 286 | } 287 | idx++; 288 | } 289 | if (n == CMD_SET_LOAD) 290 | { 291 | if (strlen(url) > strlen(control_names[idx].name) + 1) 292 | { 293 | data = url + strlen(control_names[idx].name) + 1; 294 | #ifdef WEBSERV_DEBUG 295 | logprintf("%s: setload data = %s\n", __func__, data); 296 | #endif 297 | } 298 | } 299 | else if (n == CMD_DELETE_FILE) 300 | { 301 | if (strlen(url) > strlen(control_names[idx].name) + 1) 302 | { 303 | data = url + strlen(control_names[idx].name) + 1; 304 | #ifdef WEBSERV_DEBUG 305 | logprintf("%s: delete file data = %s\n", __func__, data); 306 | #endif 307 | // delete file 308 | char *fname = malloc(strlen(data) + strlen(FS_BASE) + 1); 309 | sprintf(fname, "%s%s", FS_BASE, data); 310 | res = unlink(fname); 311 | free(fname); 312 | 313 | // set to none to skip callback 314 | n = CMD_NONE; 315 | } 316 | } 317 | if (n != CMD_NONE && control_func != NULL) 318 | { 319 | control_func(n, data, data_len); 320 | } 321 | free(url); 322 | if (res == 0) 323 | httpd_resp_sendstr(req, "OK"); 324 | else 325 | httpd_resp_sendstr(req, "Error"); 326 | return ESP_OK; 327 | } 328 | 329 | static esp_err_t get_index_handler(httpd_req_t *req) 330 | { 331 | return get_file_by_name(req, "index.html"); 332 | } 333 | 334 | static const httpd_uri_t post_file = { 335 | .uri = FILE_URI, 336 | .method = HTTP_POST, 337 | .handler = post_handler, 338 | }; 339 | 340 | static const httpd_uri_t get_file = { 341 | .uri = FILE_URI, 342 | .method = HTTP_GET, 343 | .handler = get_handler, 344 | }; 345 | 346 | static const httpd_uri_t index_file = { 347 | .uri = "/", 348 | .method = HTTP_GET, 349 | .handler = get_index_handler, 350 | }; 351 | 352 | static const httpd_uri_t control_file = { 353 | .uri = CONTROL_URI, 354 | .method = HTTP_GET, 355 | .handler = control_handler, 356 | }; 357 | 358 | static esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err) 359 | { 360 | #ifdef WEBSERV_DEBUG 361 | logprintf("%s: not found: %s\n", __func__, req->uri); 362 | #endif 363 | httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Not found"); 364 | return ESP_FAIL; 365 | } 366 | 367 | int webserver_start(int read_only) 368 | { 369 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 370 | 371 | #ifdef WEBSERV_DEBUG 372 | logprintf("%s: Starting server on port: %d\n", __func__, config.server_port); 373 | #endif 374 | if (httpd_start(&server, &config) == ESP_OK) 375 | { 376 | httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, http_404_error_handler); 377 | if (read_only == 0) 378 | { 379 | httpd_register_uri_handler(server, &post_file); 380 | httpd_register_uri_handler(server, &control_file); 381 | } 382 | httpd_register_uri_handler(server, &get_file); 383 | httpd_register_uri_handler(server, &index_file); 384 | running = 1; 385 | return 1; 386 | } 387 | #ifdef WEBSERV_DEBUG 388 | logprintf("%s: Error starting server!\n", __func__); 389 | #endif 390 | return 0; 391 | } 392 | 393 | void webserver_stop() 394 | { 395 | if (running) 396 | { 397 | running = 0; 398 | httpd_stop(server); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /main/wifi_service.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "freertos/FreeRTOS.h" 9 | #include "freertos/task.h" 10 | #include "freertos/event_groups.h" 11 | #include "esp_system.h" 12 | #include "esp_wifi.h" 13 | #include "esp_event.h" 14 | #include "esp_log.h" 15 | 16 | #include "log.h" 17 | #include "wifi_service.h" 18 | 19 | //#define WIFI_DEBUG 1 20 | 21 | #define STATION_MAXIMUM_RETRY 3 22 | #define AP_MAX_STA_CONN 1 23 | 24 | /* The event group allows multiple bits for each event, but we only care about two events: 25 | * - we are connected to the AP with an IP 26 | * - we failed to connect after the maximum amount of retries */ 27 | #define WIFI_CONNECTED_BIT BIT0 28 | #define WIFI_FAIL_BIT BIT1 29 | 30 | static EventGroupHandle_t s_wifi_event_group; 31 | static int s_retry_num = 0; 32 | static int wifi_mode = 0; 33 | // network interface 34 | static esp_netif_t *net; 35 | 36 | static char ip_address[16] = {0}; 37 | 38 | char *wifi_service_get_ip() 39 | { 40 | return ip_address; 41 | } 42 | 43 | static void station_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) 44 | { 45 | if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) 46 | { 47 | esp_wifi_connect(); 48 | } 49 | else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) 50 | { 51 | if (s_retry_num < STATION_MAXIMUM_RETRY) 52 | { 53 | esp_wifi_connect(); 54 | s_retry_num++; 55 | #ifdef WIFI_DEBUG 56 | logprintf("retry to connect to the AP\n"); 57 | #endif 58 | } 59 | else 60 | { 61 | xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); 62 | } 63 | #ifdef WIFI_DEBUG 64 | logprintf("connect to the AP fail\n"); 65 | #endif 66 | } 67 | else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) 68 | { 69 | ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; 70 | 71 | memset(ip_address, 0, sizeof(ip_address)); 72 | sprintf(ip_address, "%d.%d.%d.%d", IP2STR(&event->ip_info.ip)); 73 | 74 | #ifdef WIFI_DEBUG 75 | logprintf("IP: %s\n", ip_address); 76 | #endif 77 | 78 | s_retry_num = 0; 79 | xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); 80 | } 81 | } 82 | 83 | static int wifi_init_station(char *ssid, char *password) 84 | { 85 | s_wifi_event_group = xEventGroupCreate(); 86 | 87 | net = esp_netif_create_default_wifi_sta(); 88 | 89 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 90 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 91 | 92 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &station_event_handler, NULL)); 93 | ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &station_event_handler, NULL)); 94 | 95 | wifi_config_t wifi_config = { 96 | .sta = { 97 | .ssid = "", 98 | .password = "", 99 | }, 100 | }; 101 | strcpy((char *)wifi_config.sta.ssid, ssid); 102 | strcpy((char *)wifi_config.sta.password, password); 103 | 104 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 105 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); 106 | ESP_ERROR_CHECK(esp_wifi_start()); 107 | 108 | #ifdef WIFI_DEBUG 109 | logprintf("wifi_init_sta finished\n"); 110 | #endif 111 | 112 | /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum 113 | * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */ 114 | EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, 115 | WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, 116 | pdFALSE, 117 | pdFALSE, 118 | portMAX_DELAY); 119 | 120 | int ret = 0; 121 | /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually 122 | * happened. */ 123 | if (bits & WIFI_CONNECTED_BIT) 124 | { 125 | #ifdef WIFI_DEBUG 126 | logprintf("connected to ap SSID: '%s' password: '%s'\n", ssid, password); 127 | #endif 128 | ret = 1; 129 | } 130 | else if (bits & WIFI_FAIL_BIT) 131 | { 132 | #ifdef WIFI_DEBUG 133 | logprintf("Failed to connect to SSID: '%s' password: '%s'\n", ssid, password); 134 | #endif 135 | } 136 | else 137 | { 138 | #ifdef WIFI_DEBUG 139 | logprintf("Wifi UNEXPECTED EVENT\n"); 140 | #endif 141 | } 142 | 143 | // we should need this in order to stay connected? 144 | ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &station_event_handler)); 145 | ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &station_event_handler)); 146 | vEventGroupDelete(s_wifi_event_group); 147 | 148 | return ret; 149 | } 150 | 151 | static void ap_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) 152 | { 153 | if (event_id == WIFI_EVENT_AP_STACONNECTED) 154 | { 155 | wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data; 156 | #ifdef WIFI_DEBUG 157 | logprintf("station " MACSTR " join, AID=%d", MAC2STR(event->mac), event->aid); 158 | #endif 159 | } 160 | else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) 161 | { 162 | wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; 163 | #ifdef WIFI_DEBUG 164 | logprintf("station " MACSTR " leave, AID=%d", MAC2STR(event->mac), event->aid); 165 | #endif 166 | } 167 | } 168 | 169 | static int wifi_init_softap(char *ssid, char *password) 170 | { 171 | net = esp_netif_create_default_wifi_ap(); 172 | 173 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 174 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 175 | 176 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &ap_event_handler, NULL)); 177 | 178 | wifi_config_t wifi_config = { 179 | .ap = { 180 | .ssid = "", 181 | .ssid_len = strlen(ssid), 182 | .password = "", 183 | .max_connection = AP_MAX_STA_CONN, 184 | .authmode = WIFI_AUTH_WPA_WPA2_PSK}, 185 | }; 186 | strcpy((char *)wifi_config.ap.ssid, ssid); 187 | strcpy((char *)wifi_config.ap.password, password); 188 | 189 | if (strlen(password) == 0) 190 | { 191 | wifi_config.ap.authmode = WIFI_AUTH_OPEN; 192 | } 193 | 194 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); 195 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config)); 196 | ESP_ERROR_CHECK(esp_wifi_start()); 197 | 198 | #ifdef WIFI_DEBUG 199 | logprintf("wifi_init_softap finished. SSID: '%s' password: '%s'\n", ssid, password); 200 | #endif 201 | return 1; 202 | } 203 | 204 | int wifi_start(wifi_service_mode_t mode, char *ssid, char *password) 205 | { 206 | #ifdef WIFI_DEBUG 207 | logprintf("%s\n", __func__); 208 | #endif 209 | 210 | wifi_mode = mode; 211 | 212 | ESP_ERROR_CHECK(esp_netif_init()); 213 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 214 | 215 | if (mode == wifi_mode_ap) 216 | { 217 | // AP mode we know our IP 218 | strcpy(ip_address, "192.168.4.1"); 219 | wifi_init_softap(ssid, password); 220 | return 1; 221 | } 222 | else 223 | { 224 | return wifi_init_station(ssid, password); 225 | } 226 | } 227 | 228 | void wifi_stop() 229 | { 230 | if (wifi_mode == wifi_mode_sta) 231 | { 232 | ESP_ERROR_CHECK(esp_wifi_disconnect()); 233 | vTaskDelay(100); 234 | } 235 | else 236 | { 237 | // TODO: fix AP mode stop 238 | vTaskDelay(100); 239 | } 240 | ESP_ERROR_CHECK(esp_wifi_stop()); 241 | vTaskDelay(100); 242 | ESP_ERROR_CHECK(esp_wifi_deinit()); 243 | vTaskDelay(100); 244 | 245 | esp_netif_destroy(net); 246 | vTaskDelay(100); 247 | esp_netif_deinit(); 248 | vTaskDelay(100); 249 | 250 | esp_event_loop_delete_default(); 251 | vTaskDelay(100); 252 | } 253 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x6000, 3 | phy_init, data, phy, 0xf000, 0x1000, 4 | factory, app, factory, 0x10000, 2M, 5 | storage, data, spiffs, , 0xF0000, 6 | -------------------------------------------------------------------------------- /scripts/json2mddoc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | 3 | import json 4 | import sys 5 | 6 | def getFunctions(fname): 7 | fp = open(fname, "r") 8 | if fp == None: 9 | return None 10 | line = " " 11 | isdoc = False 12 | example = False 13 | outdoc = [] 14 | cd = "" 15 | while line != None and len(line) > 0: 16 | line = fp.readline() 17 | if "/* jsondoc" in line: 18 | isdoc = True 19 | continue 20 | if "*/" in line and isdoc: 21 | isdoc = False 22 | outdoc.append(cd) 23 | cd = "" 24 | continue 25 | if isdoc: 26 | cd += line.strip('\n') 27 | if "\"example\":" in line or "\"longtext\":" in line: 28 | example = True 29 | continue 30 | if example: 31 | if line[0] == '"': 32 | example = False 33 | else: 34 | cd += "\\n" 35 | #print outdoc 36 | return outdoc 37 | 38 | def sortfuncs(fcns): 39 | sfcn = [] 40 | fn = {} 41 | out = [] 42 | for f in fcns: 43 | fcn = None 44 | try: 45 | fcn = json.loads(f) 46 | except: 47 | sys.stderr.write(f+"\n") 48 | sys.exit(1) 49 | if "class" in fcn: 50 | pass 51 | else: 52 | fn[fcn["name"]] = fcn 53 | sfcn.append(fcn["name"]) 54 | sfcn.sort() 55 | for f in fcns: 56 | if "class" in f: 57 | out.append(json.loads(f)) 58 | break 59 | for f in sfcn: 60 | out.append(fn[f]) 61 | return out 62 | 63 | def printfuncs(fncs): 64 | idx = [] 65 | for fcn in fncs: 66 | if "class" in fcn: 67 | continue 68 | fn = fcn['name'] 69 | for a in fcn['args']: 70 | fn += a['name'] 71 | idx.append((fcn["name"],fn)) 72 | for fcn in fncs: 73 | if "class" in fcn: 74 | print("# " + fcn['class'] + "\n") 75 | if "longtext" in fcn: 76 | print(fcn['longtext']) 77 | else: 78 | print(fcn['text']) 79 | if len(fncs) > 1: 80 | print("## Methods\n") 81 | for fn,f in idx: 82 | print("- [" + fn + "](#" + f.lower() + ")") 83 | print("\n---\n") 84 | continue 85 | fn = fcn['name'] 86 | fn += "(" 87 | for a in fcn['args']: 88 | fn += a['name'] + "," 89 | if fn[len(fn)-1] == ",": 90 | fn = fn[0:len(fn)-1] 91 | fn += ")" 92 | print "## " + fn + "\n" 93 | if "longtext" in fcn: 94 | print(fcn['longtext'] + "\n") 95 | else: 96 | print(fcn['text'] + "\n") 97 | for a in fcn['args']: 98 | print("- " + a["name"] + "\n") 99 | print(" type: " + a["vtype"] + "\n") 100 | print(" " + a['text'] + "\n") 101 | if "return" in fcn: 102 | print("**Returns:** " + fcn['return'] + "\n") 103 | print("```\n"+fcn["example"]+"\n```\n") 104 | 105 | 106 | x = getFunctions(sys.argv[1]) 107 | #for p in x: 108 | # print(p) 109 | #printfuncs(x) 110 | 111 | sx = sortfuncs(x) 112 | printfuncs(sx) 113 | -------------------------------------------------------------------------------- /spiffs_image/ble_echo_server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | function OnStart() { 6 | Platform.setConnectivity(2); 7 | // allow next bonding request 8 | Platform.bleBondAllow(1); 9 | print("BLE echo server...\n"); 10 | } 11 | 12 | function OnEvent(evt) { 13 | if (evt.EventType == 1) { 14 | print("data received " + evt.EventData.length + " bytes\n"); 15 | // send back data we just received (echo service) 16 | Platform.sendEvent(1, evt.EventData); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spiffs_image/cryptotest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | function OnStart() { 6 | print("------ cryptotest.js ------\n"); 7 | 8 | var key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 9 | var data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 10 | var ciphertext = Duktape.dec('hex', '3AD77BB40D7A3660A89ECAF32466EF97'); 11 | Crypto.aes128EcbEnc(key, data); 12 | 13 | print("aes128EcbEnc: "); 14 | if (!arrayEqual(data,ciphertext)) { 15 | print("failed\n"); 16 | } 17 | else { 18 | print("success\n"); 19 | } 20 | 21 | ciphertext = Duktape.dec('hex', '3AD77BB40D7A3660A89ECAF32466EF97'); 22 | data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 23 | key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 24 | Crypto.aes128EcbDec(key, ciphertext); 25 | 26 | print("aes128EcbDec: "); 27 | if (!arrayEqual(data,ciphertext)) { 28 | print("failed\n"); 29 | } 30 | else { 31 | print("success\n"); 32 | } 33 | 34 | key = Duktape.dec('hex', '2b7e151628aed2a6abf7158809cf4f3c'); 35 | data = Duktape.dec('hex', '6bc1bee22e409f96e93d7e117393172a'); 36 | var cmac = Duktape.dec('hex', '070a16b46b4d4144f79bdd9dd04a287c'); 37 | var output = Uint8Array.allocPlain(16); 38 | Crypto.aes128Cmac(key, data, output); 39 | 40 | print("aes128Cmac: "); 41 | if (!arrayEqual(output,cmac)) { 42 | print("failed\n"); 43 | } 44 | else { 45 | print("success\n"); 46 | } 47 | 48 | print("random numbers\n"); 49 | var i = 0; 50 | for (i = 0; i < 10; i++) { 51 | print("rand: " + Math.random() + "\n"); 52 | } 53 | } 54 | 55 | function OnEvent(event) { 56 | } 57 | 58 | function arrayEqual(a, b) { 59 | if (a.length != b.length) { 60 | return false; 61 | } 62 | for (var i = 0; i < a.length; i++) { 63 | if (a[i] != b[i]) { 64 | return false; 65 | } 66 | } 67 | return true; 68 | } 69 | -------------------------------------------------------------------------------- /spiffs_image/filesystem_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | function OnStart() { 6 | bla = { 7 | test_name: "write read", 8 | number: 32 9 | }; 10 | 11 | wr(JSON.stringify(bla)); 12 | var x = rd(); 13 | print("data: "); 14 | print(x); 15 | print("\n"); 16 | 17 | var dir = FileSystem.listDir(); 18 | print(JSON.stringify(dir)); 19 | print("\n"); 20 | 21 | var st = FileSystem.stat("fs_test.json"); 22 | print("size: "+ JSON.stringify(st)); 23 | print("\n"); 24 | 25 | seektest(); 26 | 27 | FileSystem.unlink("fs_test.json"); 28 | var fp = FileSystem.open("fs_test.json", "r"); 29 | if (fp != -1) { 30 | print("open should return -1\n"); 31 | } 32 | 33 | dir = FileSystem.listDir(); 34 | print(JSON.stringify(dir)); 35 | print("\n"); 36 | } 37 | 38 | function seektest() { 39 | var fp = FileSystem.open("fs_test.json", "r"); 40 | var fplen = FileSystem.seek(fp, 0, 2); 41 | print("size: " + fplen + "\n"); 42 | var data = new Uint8Array(1); 43 | FileSystem.seek(fp, fplen-1, 0); 44 | FileSystem.read(fp, data, 0, 1); 45 | if (String.fromCharCode(data[0]) == '}') { 46 | print("seek test success\n"); 47 | } 48 | FileSystem.close(fp); 49 | } 50 | 51 | function rd() { 52 | var fp = FileSystem.open("fs_test.json", "r"); 53 | var data = new Uint8Array(1024); 54 | FileSystem.read(fp, data, 0, 1024); 55 | FileSystem.close(fp); 56 | var dec = new TextDecoder(); 57 | return dec.decode(data); 58 | } 59 | 60 | function wr(data) { 61 | var fp = FileSystem.open("fs_test.json", "w"); 62 | FileSystem.write(fp, data); 63 | FileSystem.close(fp); 64 | } 65 | 66 | function OnEvent(evt) { 67 | print("event\n"); 68 | } 69 | -------------------------------------------------------------------------------- /spiffs_image/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fluxn0de 5 | 6 | 7 | 8 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /spiffs_image/lorawanscan.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | var wifi_ssid = ""; 6 | var wifi_pass = ""; 7 | 8 | var DeviceAddrHex = "260CA79F"; 9 | var AppKeyHex = "DE894188CC2A67CFBDABA74BF2F3D839"; 10 | var NwkKeyHex = "DEBBB7A1904014AAB8E068140BE2931E"; 11 | 12 | 13 | var mode = 2; // 0 = scan, 1 = listen for class B beacon, 2 = listen on RX2 (class C) 14 | 15 | // --- do not change below --- 16 | var scanState = {}; 17 | var packetCallback = processLora; 18 | 19 | function OnStart() { 20 | 21 | Platform.loadLibrary("/wifimgnt.js"); 22 | Platform.loadLibrary("/lorawanlib.js"); 23 | Platform.loadLibrary("/timer.js"); 24 | Platform.loadLibrary("/util.js"); 25 | 26 | startWifi(wifi_ssid, wifi_pass); 27 | 28 | if (mode == 0) { 29 | if (AppKeyHex == "" || NwkKeyHex == "" || DeviceAddrHex == "") { 30 | print("\n\nConfiguration Needed!\n\nSet DeviceAddr, AppKey, and NwkKey from your LoraWAN console!!\n\n"); 31 | print("Only ABP is supported!\n\n") 32 | return; 33 | } 34 | 35 | startScan(); 36 | packetCallback = processLora; 37 | } 38 | 39 | if (mode == 1) { 40 | print("Listening for Beacons...\n"); 41 | scanState = { channel: -1 }; 42 | if (new Date().getTime() > __gpsEpoch) { 43 | var bChan = loraWanBeaconGetNextChannel(); 44 | print(JSON.stringify(bChan) + "\n"); 45 | scanState.channel = bChan.channel - 1; 46 | } 47 | listenNext(); 48 | packetCallback = processListen; 49 | } 50 | 51 | if (mode == 2) { 52 | print("Listen Rx2\n"); 53 | var addr = hexToBin(DeviceAddrHex); 54 | var appkey = hexToBin(AppKeyHex); 55 | var nwkkey = hexToBin(NwkKeyHex); 56 | 57 | scanState = { 58 | lp: LoraWanPacket(addr, nwkkey, appkey), 59 | channel: -1, 60 | channel_max: 71, 61 | answers: [], 62 | }; 63 | packetCallback = processDownlink; 64 | listenRX2(); 65 | } 66 | } 67 | 68 | function startScan() { 69 | print("Start scanning...\n"); 70 | var addr = hexToBin(DeviceAddrHex); 71 | var appkey = hexToBin(AppKeyHex); 72 | var nwkkey = hexToBin(NwkKeyHex); 73 | 74 | scanState = { 75 | lp: LoraWanPacket(addr, nwkkey, appkey), 76 | channel: -1, 77 | channel_max: 71, 78 | answers: [], 79 | }; 80 | 81 | setTimeout(scanNext, 5000); 82 | } 83 | 84 | function scanNext() { 85 | print("scanNext...\n"); 86 | if (scanState.channel < scanState.channel_max) { 87 | scanState.channel++; 88 | var enc = new TextEncoder(); 89 | var pkt = scanState.lp.confirmedUp(enc.encode("ping"), 1, scanState.channel, []); 90 | sendUp(pkt, scanState.channel); 91 | if (scanState.channel <= scanState.channel_max) { 92 | setTimeout(scanNext, 5000); 93 | } else { 94 | setTimeout(scanNext, 7000); 95 | } 96 | } else { 97 | print("scan done, answers on " + scanState.answers.length + " channels:"); 98 | print(JSON.stringify(scanState.answers) + "\n"); 99 | } 100 | } 101 | 102 | function processLora(pkt) { 103 | var res = scanState.lp.parseDownPacket(pkt); 104 | if (res.error == false && res.ack == true && res.micVerified == true) { 105 | print("got response for channel: " + scanState.channel + "\n"); 106 | scanState.answers.push(scanState.channel); 107 | print(JSON.stringify(res) + "\n"); 108 | } 109 | } 110 | 111 | function sendUp(pkt, channel) { 112 | var chans = loraWanUpDownChannel915(channel); 113 | print("sending on: " + chans.upFreq + "\n"); 114 | LoRa.loraIdle(); 115 | if (!LoRa.setFrequency(chans.upFreq)) { 116 | print("send freq error\n"); 117 | } 118 | 119 | LoRa.setBW(chans.bw); 120 | LoRa.setSF(chans.sf); 121 | LoRa.setTxPower(17); 122 | LoRa.setPreambleLen(8); 123 | LoRa.setIQMode(false); 124 | LoRa.setSyncWord(0x34); 125 | LoRa.setCRC(true); 126 | LoRa.loraReceive(); 127 | LoRa.sendPacket(Uint8Array.plainOf(pkt)); 128 | 129 | LoRa.loraIdle(); 130 | LoRa.setBW(500E3); 131 | LoRa.setIQMode(true); 132 | print("listening on: " + chans.downFreq + "\n"); 133 | if (!LoRa.setFrequency(chans.downFreq)) { 134 | print("recv freq error\n"); 135 | } 136 | LoRa.loraReceive(); 137 | } 138 | 139 | function processListen(pkt) { 140 | print("Channel: " + scanState.channel + "\n"); 141 | print("Data: " + binToHex(pkt) + "\n"); 142 | var beacon = loraWanBeaconDecode(pkt, 0); 143 | if (beacon.time_crc) { 144 | Platform.setSystemTime(beacon.time + 1); 145 | } 146 | print(JSON.stringify(beacon) + "\n"); 147 | print(new Date() + "\n"); 148 | listenNext(); 149 | } 150 | 151 | function listenRX2() { 152 | var chans = loraWanUpDownChannel915(0); 153 | LoRa.loraIdle(); 154 | 155 | LoRa.setSF(8); 156 | LoRa.setTxPower(1); 157 | LoRa.setPreambleLen(8); 158 | LoRa.setSyncWord(0x34); 159 | LoRa.setCRC(true); 160 | LoRa.setBW(500E3); 161 | LoRa.setIQMode(true); 162 | print("Rx2 listening on: " + chans.down2Freq + "\n"); 163 | if (!LoRa.setFrequency(chans.down2Freq)) { 164 | print("recv freq error\n"); 165 | } 166 | LoRa.loraReceive(); 167 | } 168 | 169 | function processDownlink(pkt) { 170 | print("Data: " + binToHex(pkt) + "\n"); 171 | var dp = scanState.lp.parseDownPacket(pkt); 172 | print(JSON.stringify(dp) + "\n"); 173 | } 174 | 175 | function listenNext() { 176 | if (scanState.channel < 7) { 177 | scanState.channel++; 178 | } else { 179 | scanState.channel = 0; 180 | } 181 | loraWanBeaconListen(scanState.channel, 0); 182 | } 183 | 184 | function OnEvent(event) { 185 | print(JSON.stringify(event) + "\n"); 186 | if (event.EventType == 0) { 187 | packetCallback(event.EventData); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /spiffs_image/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | /* change to load a different application */ 6 | var app = "/test.js"; 7 | 8 | function OnStart() { 9 | print("BoardName: " + Platform.BoardName + "\n"); 10 | print("HeapFree: " + Platform.getFreeHeap()/1000 + " KB\n"); 11 | print("loading: " + app + "\n"); 12 | Platform.setLoadFileName(app); 13 | Platform.reset(); 14 | print("reseting runtime\n"); 15 | } 16 | 17 | function OnTimer() { 18 | } 19 | 20 | function OnEvent(event) { 21 | print(JSON.stringify(event)); 22 | } 23 | -------------------------------------------------------------------------------- /spiffs_image/recovery.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | /* set clientSSID and clientPassword to connect to a Wifi AP in recovery */ 6 | var clientSSID = ""; 7 | var clientPassword = ""; 8 | 9 | function APRecovery() { 10 | var wifi_ssid = "fluxn0de"; 11 | var wifi_pass = "fluxn0de"; 12 | print("\n\nFlux starting recovery WIFI AP SSID: '" + wifi_ssid + "' Password: '" + wifi_pass + "'\n\n"); 13 | Platform.setConnectivity(1); 14 | if (Platform.wifiConfigure(wifi_ssid, wifi_pass, 0, 0)) { 15 | print("FluxN0de IP: " + Platform.getLocalIP() + "\n"); 16 | } else { 17 | print("\n\nWifi AP error, password length < 8?\n\n"); 18 | } 19 | } 20 | 21 | function ClientRecovery() { 22 | if (clientSSID != "") { 23 | print("Client Recovery\n"); 24 | Platform.setConnectivity(1); 25 | Platform.wifiConfigure(clientSSID, clientPassword, 1, 0); 26 | return true; 27 | } 28 | print("\n\nSet clientSSID and clientPassword to use ClientRecovery\n\n"); 29 | return false; 30 | } 31 | 32 | function OnStart() { 33 | if (Platform.getConnectivity() != 1) { 34 | if (!ClientRecovery()) { 35 | APRecovery(); 36 | } 37 | } 38 | print("--- Recovery ---\n"); 39 | Platform.setLEDBlink(0, 1, 0.5); 40 | } 41 | 42 | function OnEvent(event) { 43 | print(JSON.stringify(event)); 44 | /* reboot when button is pressed */ 45 | if (event.EventType == 4) { 46 | Platform.reboot(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spiffs_image/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | 6 | var wifi_ssid = ""; 7 | var wifi_pass = ""; 8 | 9 | var logs = []; 10 | 11 | function OnStart() { 12 | print("NOW running: test.js\n"); 13 | 14 | Platform.loadLibrary("/wifimgnt.js"); 15 | startWifi(wifi_ssid, wifi_pass); 16 | 17 | if (Platform.ButtonNum == 0) { 18 | Platform.loadLibrary("/timer.js"); 19 | intervalTimer(sendInfo, 7000); 20 | } else { 21 | print("Press Button!\n"); 22 | } 23 | } 24 | 25 | function sendInfo() { 26 | var d = new Date(); 27 | var x = { 28 | cmd: "info", 29 | battery_percent: Platform.getBatteryPercent(), 30 | battery_mv: Math.floor(Platform.getBatteryMVolt()), 31 | usb_connected: Platform.getUSBStatus(), 32 | battery_charging: Platform.getBatteryStatus(), 33 | heap_free: Platform.getFreeHeap(), 34 | heap_internal_free: Platform.getFreeInternalHeap(), 35 | fs_total: FileSystem.fsSizeTotal(), 36 | fs_used: FileSystem.fsSizeUsed(), 37 | local_ip: Platform.getLocalIP(), 38 | connectivity: Platform.getConnectivity(), 39 | client_id: Platform.getClientID(), 40 | boardname: Platform.BoardName, 41 | system_time: d.getTime(), 42 | boot_time: Platform.getBoottime(), 43 | wifi_mac: Platform.wifiGetMacAddr(), 44 | ble_addr: Platform.bleGetDeviceAddr(), 45 | flash_size: Platform.FlashSize, 46 | }; 47 | print(JSON.stringify(x) + "\n"); 48 | Platform.sendEvent(1, Uint8Array.allocPlain(JSON.stringify(x))); 49 | } 50 | 51 | function OnEvent(evt) { 52 | if (EventName(evt) == "button") { 53 | sendInfo(); 54 | return; 55 | } 56 | if (EventName(evt) == "ui") { 57 | var dec = new TextDecoder(); 58 | var cmd = JSON.parse(dec.decode(evt.EventData)); 59 | switch (cmd.cmd) { 60 | case "set_system_time": 61 | Platform.setSystemTime(cmd.time); 62 | break; 63 | default: 64 | print("command '" + cmd.cmd + "' not supported\n"); 65 | break; 66 | } 67 | return; 68 | } 69 | } 70 | 71 | function log(line) { 72 | logs.push(line); 73 | sendLogs(); 74 | } 75 | 76 | function sendLogs() { 77 | if (Platform.getClientConnected()) { 78 | for (var i = 0; i < logs.length; i++) { 79 | var x = JSON.stringify({ cmd: "log", event: logs[i] }); 80 | Platform.sendEvent(1, Uint8Array.allocPlain(x)); 81 | } 82 | logs = []; 83 | } 84 | } 85 | 86 | function EventName(event) { 87 | var et = ['lora', 'ui', 'ui_connected', 'ui_disconnected', 'button']; 88 | return et[event.EventType]; 89 | } 90 | -------------------------------------------------------------------------------- /spiffs_image/timer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright: Collin Mulliner 3 | */ 4 | 5 | /* jsondoc 6 | { 7 | "class": "Timer", 8 | "longtext":" 9 | This is a basic timer library. 10 | 11 | The library is designed around `OnTimer()` and `Platform.setTimeout()`. 12 | An application that uses this library should **NOT** have its own implementation 13 | of `OnTimer()`. 14 | 15 | The shortest timeout/interval is 10 milliseconds. 16 | " 17 | } 18 | */ 19 | 20 | __Timers = { 21 | timers: [], 22 | nextID: 1, 23 | }; 24 | 25 | 26 | /* jsondoc 27 | { 28 | "name": "setTimeout", 29 | "args": [ 30 | {"name": "func", "vtype": "function", "text": "function to call when the timer expires"}, 31 | {"name": "delay", "vtype": "uint", "text": "timeout in milliseconds"} 32 | ], 33 | "return": "timer ID", 34 | "text": "Timer to call a function after a given delay (in milliseconds).", 35 | "example": " 36 | // 5000 second timer 37 | setTimeout(function(){print('timer\\n');}, 5000); 38 | " 39 | } 40 | */ 41 | function setTimeout(func, delay) { 42 | var id = __setTimeoutInternal(func, delay, false, -1); 43 | __installTimer(); 44 | return id; 45 | } 46 | 47 | /* jsondoc 48 | { 49 | "name": "cancelTimer", 50 | "args": [ {"name": "timerID", "vtype": "timerID", "text": "ID returned by setTimeout or intervalTimer"} ], 51 | "text": "Delete timer.", 52 | "example": " 53 | var id = setTimer(x, 1000); 54 | cancelTimer(id); 55 | " 56 | } 57 | */ 58 | function cancelTimer(id) { 59 | var idx = __timersFindIdxId(id); 60 | if (idx != -1) { 61 | __timersRemoveAt(idx); 62 | } 63 | __installTimer(); 64 | } 65 | 66 | /* jsondoc 67 | { 68 | "name": "intervalTimeout", 69 | "args": [ 70 | {"name": "func", "vtype": "function", "text": "function to call when the timer expires"}, 71 | {"name": "interval", "vtype": "uint", "text": "interval in milliseconds"} 72 | ], 73 | "return": "timer ID", 74 | "text": "Call function repeatedly in the given interval (in milliseconds) until canceled.", 75 | "example": " 76 | // call function x every 5 seconds 77 | intervalTimeout(x, 5000); 78 | " 79 | } 80 | */ 81 | function intervalTimer(func, interval) { 82 | var id = __setTimeoutInternal(func, interval, true, -1); 83 | __installTimer(); 84 | return id; 85 | } 86 | 87 | /* jsondoc 88 | { 89 | "name": "OnTimer", 90 | "args": [], 91 | "longtext": " 92 | `OnTimer()` function that is provided by this library. 93 | 94 | When using this library the main application cannot contain a OnTimer() function. 95 | ", 96 | "example": " 97 | " 98 | } 99 | */ 100 | function OnTimer() { 101 | var expired = []; 102 | var d = new Date(); 103 | var ts = d.getTime(); 104 | for (var i = 0; i < __Timers.timers.length; i++) { 105 | if (ts >= __Timers.timers[i].expire) { 106 | expired.push(__Timers.timers[i].id); 107 | __Timers.timers[i].func(); 108 | } 109 | if (ts < __Timers.timers[i].expire) { 110 | break; 111 | } 112 | } 113 | for (var i = 0; i < expired.length; i++) { 114 | var idx = __timersFindIdxId(expired[i]); 115 | var rt = __timersRemoveAt(idx); 116 | if (rt.interval) { 117 | __setTimeoutInternal(rt.func, rt.delay, true, rt.id); 118 | } 119 | } 120 | __installTimer(); 121 | } 122 | 123 | function __installTimer() { 124 | if (__Timers.timers.length == 0) { 125 | Platform.setTimer(0); 126 | return; 127 | } 128 | var d = new Date(); 129 | var ts = d.getTime(); 130 | var diff = __Timers.timers[0].expire - ts; 131 | //print("diff: " + diff); 132 | Platform.setTimer(diff); 133 | } 134 | 135 | function __timersFindIdx(expire) { 136 | for (var i = 0; i < __Timers.timers.length; i++) { 137 | if (__Timers.timers[i].expire >= expire) { 138 | return i; 139 | } 140 | } 141 | return __Timers.timers.length; 142 | } 143 | 144 | function __timersFindIdxId(id) { 145 | for (var i = 0; i < __Timers.timers.length; i++) { 146 | if (__Timers.timers[i].id == id) { 147 | return i; 148 | } 149 | } 150 | return -1; 151 | } 152 | 153 | function __timersInsertAt(t, idx) { 154 | if (idx == 0) { 155 | __Timers.timers.unshift(t); 156 | } 157 | else if (idx == __Timers.timers.length) { 158 | __Timers.timers.push(t); 159 | } else { 160 | var ts = __Timers.timers.slice(0, idx); 161 | var te = __Timers.timers.slice(idx, __Timers.timers.length); 162 | __Timers.timers = ts; 163 | __Timers.timers.push(t); 164 | __Timers.timers = ts.concat(te); 165 | } 166 | } 167 | 168 | function __timersRemoveAt(idx) { 169 | if (idx == 0) { 170 | return __Timers.timers.shift(); 171 | } 172 | else if (idx == __Timers.timers.length) { 173 | return __Timers.timers.pop(); 174 | } 175 | var rt = __Timers.timers[idx]; 176 | var ts = __Timers.timers.slice(0, idx); 177 | var te = __Timers.timers.slice(idx + 1, __Timers.timers.length); 178 | __Timers.timers = ts; 179 | __Timers.timers = ts.concat(te); 180 | return rt; 181 | } 182 | 183 | function __setTimeoutInternal(func, delay, interval, ID) { 184 | var d = new Date(); 185 | 186 | var t = { 187 | func: func, 188 | expire: d.getTime() + delay , 189 | delay: delay, 190 | id: ID != -1 ? ID : __Timers.nextID++, 191 | interval: interval, 192 | }; 193 | 194 | var idx = __timersFindIdx(t.expire); 195 | __timersInsertAt(t, idx); 196 | return t.id; 197 | } 198 | -------------------------------------------------------------------------------- /spiffs_image/util.js: -------------------------------------------------------------------------------- 1 | /* jsondoc 2 | { 3 | "class": "Util", 4 | "longtext":" 5 | A simple utility library. 6 | " 7 | } 8 | */ 9 | 10 | var __crcTable = [0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 11 | 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 12 | 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 13 | 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 14 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 15 | 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 16 | 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 17 | 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 18 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 19 | 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 20 | 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 21 | 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 22 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 23 | 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 24 | 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 25 | 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 26 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 27 | 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 28 | 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 29 | 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 30 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 31 | 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 32 | 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 33 | 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 34 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 35 | 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 36 | 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 37 | 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 38 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 39 | 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 40 | 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 41 | 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 42 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 43 | 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 44 | 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 45 | 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 46 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 47 | 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 48 | 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 49 | 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 50 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 51 | 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 52 | 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0]; 53 | 54 | /* jsondoc 55 | { 56 | "name": "crc16", 57 | "args": [ 58 | {"name": "buf", "vtype": "bytearray", "text": "buffer"} 59 | ], 60 | "return": "uint16", 61 | "text": "calculate crc16 of the buffer", 62 | "example": " 63 | " 64 | } 65 | */ 66 | function crc16(buf) { 67 | var crc = 0x0000; 68 | var j, i; 69 | for (i = 0; i < buf.length; i++) { 70 | var c = buf[i]; 71 | if (c > 255) { 72 | throw new RangeError(); 73 | } 74 | j = (c ^ (crc >> 8)) & 0xFF; 75 | crc = __crcTable[j] ^ (crc << 8); 76 | } 77 | return ((crc ^ 0x0) & 0xFFFF); 78 | } 79 | 80 | /* jsondoc 81 | { 82 | "name": "fromTwosComplement", 83 | "args": [ 84 | {"name": "twosComplement", "vtype": "bytearray", "text": "twos complement"}, 85 | {"name": "numberBytes", "vtype": "int", "text": "number of bytes in towsComplement"} 86 | ], 87 | "return": "int", 88 | "text": "convert twos complement to int", 89 | "example": " 90 | " 91 | } 92 | */ 93 | function fromTwosComplement(twosComplement, numberBytes) { 94 | var numberBits = (numberBytes || 1) * 8; 95 | if (twosComplement < 0 || twosComplement > (1 << numberBits) - 1) { 96 | return 0; 97 | } 98 | if (twosComplement <= Math.pow(2, numberBits - 1) - 1) { 99 | return twosComplement; 100 | } 101 | return -(((~twosComplement) & ((1 << numberBits) - 1)) + 1); 102 | } 103 | 104 | /* jsondoc 105 | { 106 | "name": "arrayEqual", 107 | "args": [ 108 | {"name": "a", "vtype": "array", "text": "a"}, 109 | {"name": "b", "vtype": "array", "text": "b"} 110 | ], 111 | "return": "boolean", 112 | "text": "compare two arrays", 113 | "example": " 114 | " 115 | } 116 | */ 117 | function arrayEqual(a, b) { 118 | if (a.length != b.length) { 119 | return false; 120 | } 121 | for (var i = 0; i < a.length; i++) { 122 | if (a[i] != b[i]) { 123 | return false; 124 | } 125 | } 126 | return true; 127 | } 128 | 129 | /* jsondoc 130 | { 131 | "name": "hexToBin", 132 | "args": [ 133 | {"name": "hex", "vtype": "string", "text": "hex string"} 134 | ], 135 | "return": "plain buffer", 136 | "text": "convert hex string to a plain buffer", 137 | "example": " 138 | " 139 | } 140 | */ 141 | function hexToBin(hexStr) { 142 | return Duktape.dec('hex', hexStr); 143 | } 144 | 145 | /* jsondoc 146 | { 147 | "name": "binToHex", 148 | "args": [ 149 | {"name": "bin", "vtype": "plainbuffer", "text": "binary buffer"} 150 | ], 151 | "return": "string", 152 | "text": "convert a plain buffer to a hex string", 153 | "example": " 154 | " 155 | } 156 | */ 157 | function binToHex(bin) { 158 | return Duktape.enc('hex', bin); 159 | } 160 | 161 | /* jsondoc 162 | { 163 | "name": "reverse", 164 | "args": [ 165 | {"name": "bin", "vtype": "plainbuffer", "text": "binary buffer"} 166 | ], 167 | "return": "plain buffer", 168 | "text": "reverse the bytes in a plain buffer", 169 | "example": " 170 | " 171 | } 172 | */ 173 | function reverse(input) { 174 | var output = new Uint8Array(input.length); 175 | for (var i = 0; i < input.length; i++) { 176 | output[i] = input[input.length - (i + 1)]; 177 | } 178 | return output; 179 | } 180 | 181 | /* jsondoc 182 | { 183 | "name": "htons", 184 | "args": [ 185 | {"name": "number", "vtype": "uint16", "text": "uint16"} 186 | ], 187 | "return": "uint16", 188 | "text": "convert host to network byte order (uint16)", 189 | "example": " 190 | " 191 | } 192 | */ 193 | function htons(num) { 194 | return (num >> 8) | ((num & 0xff) << 8); 195 | } 196 | 197 | /* jsondoc 198 | { 199 | "name": "htonl", 200 | "args": [ 201 | {"name": "number", "vtype": "uint32", "text": "uint32"} 202 | ], 203 | "return": "uint32", 204 | "text": "convert host to network byte order (uint32)", 205 | "example": " 206 | " 207 | } 208 | */ 209 | function htonl(num) { 210 | print("htonl\n"); 211 | return (num >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24); 212 | } 213 | 214 | /* jsondoc 215 | { 216 | "name": "copyIndexLen", 217 | "args": [ 218 | {"name": "number", "vtype": "array", "text": "src"}, 219 | {"name": "number", "vtype": "int", "text": "src index"}, 220 | {"name": "number", "vtype": "array", "text": "dst"}, 221 | {"name": "number", "vtype": "int", "text": "dst index"}, 222 | {"name": "number", "vtype": "int", "text": "len"} 223 | ], 224 | "return": "", 225 | "text": "copy len bytes from src at src index to dst at dst index", 226 | "example": " 227 | " 228 | } 229 | */ 230 | function copyIndexLen(src, sidx, dst, didx, len) { 231 | for (var i = 0; i < len; i++) { 232 | dst[didx+i] = src[sidx+i]; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /spiffs_image/wifimgnt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple wifi management for the examples. 3 | */ 4 | 5 | function startWifi(ssid, pass) { 6 | if (Platform.getConnectivity() != 0) { 7 | print("Wifi already configured\n"); 8 | return; 9 | } 10 | 11 | if (ssid == "" || pass == "") { 12 | wifiAP(); 13 | return; 14 | } 15 | 16 | print("Connecting to Wifi Network: '" + ssid + "'\n"); 17 | Platform.setConnectivity(1); 18 | if (!Platform.wifiConfigure(ssid, pass, 1, 0)) { 19 | print("falling back to AP mode...\n"); 20 | wifiAP(); 21 | return; 22 | } 23 | print("FluxN0de IP: " + Platform.getLocalIP() + "\n"); 24 | } 25 | 26 | function wifiAP() { 27 | var wifi_ssid = "fluxn0de"; 28 | var wifi_pass = "fluxn0de"; 29 | print("\n\nFluxN0de starting WIFI AP SSID: " + wifi_ssid + " Password: " + wifi_pass + "\n\n"); 30 | Platform.setConnectivity(1); 31 | if (Platform.wifiConfigure(wifi_ssid, wifi_pass, 0, 0)) { 32 | print("FluxN0de IP: " + Platform.getLocalIP() + "\n"); 33 | Platform.setLEDBlink(0, 1, 0.5); 34 | } else { 35 | print("\n\nWifi AP error, password length < 8?\n\n"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | all: record queue 2 | 3 | .PHONY: record 4 | record: 5 | gcc -I ../main/include -DRECORD_TEST ../main/record.c -o record_test 6 | ./record_test >/dev/null 2>&1 7 | 8 | .PHONY: queue 9 | queue: 10 | gcc -I ../main/include queue.c -o queue_test 11 | ./queue_test >/dev/null 2>&1 12 | 13 | jstest: 14 | gcc -D__JSTEST__ -o jstest jstest.c ../main/duk_util.c ../components/duktape/esp32_glue.c ../components/duktape/duktape.c -I ../main/include -I ../components/duktape/include -lm 15 | -------------------------------------------------------------------------------- /test/jstest.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "duk_util.h" 6 | 7 | duk_context *ctx; 8 | 9 | int main(int argc, char **argv) 10 | { 11 | 12 | ctx = duk_create_heap_default(); 13 | duk_util_register(ctx); 14 | 15 | duk_util_load_and_run(ctx, argv[1], "OnStart();"); 16 | } 17 | -------------------------------------------------------------------------------- /test/queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // --- START - mock mutex and semaphore functions 7 | int xSemaphoreCreateMutex() 8 | { 9 | return 0; 10 | } 11 | 12 | int xSemaphoreGive(int arg) 13 | { 14 | return 0; 15 | } 16 | 17 | int xSemaphoreTake(int arg1, int arg2) 18 | { 19 | return 0; 20 | } 21 | 22 | #define portMAX_DELAY 0 23 | #define SemaphoreHandle_t int 24 | #define xSemaphoreCreateBinary xSemaphoreCreateMutex 25 | 26 | // --- END - mock mutex and semaphore functions 27 | 28 | #include "queue.h" 29 | 30 | typedef struct lm_lora_msg_t lm_lora_msg_t; 31 | typedef lm_lora_msg_t *lm_lora_msg_ptr_t; 32 | struct lm_lora_msg_t 33 | { 34 | lm_lora_msg_ptr_t next; 35 | lm_lora_msg_ptr_t prev; 36 | int data; 37 | }; 38 | 39 | int main() 40 | { 41 | work_queue_t queue; 42 | work_queue_t *q = &queue; 43 | 44 | lm_lora_msg_ptr_t fn; 45 | int q_len; 46 | 47 | WORK_QUEUE_INIT(q); 48 | assert(q->recv_queue == NULL); 49 | 50 | lm_lora_msg_ptr_t n = malloc(sizeof(lm_lora_msg_t)); 51 | n->next = NULL; 52 | n->prev = NULL; 53 | 54 | // add 55 | WORK_QUEUE_RECV_ADD(q, n); 56 | assert(q->recv_queue != NULL); 57 | 58 | WORK_QUEUE_RECV_LEN(q, q_len, fn); 59 | assert(q_len == 1); 60 | 61 | // take head 62 | lm_lora_msg_ptr_t head; 63 | WORK_QUEUE_RECV_TAKE_HEAD(q, head); 64 | assert(q->recv_queue == NULL); 65 | assert(head != NULL); 66 | 67 | WORK_QUEUE_RECV_LEN(q, q_len, fn); 68 | assert(q_len == 0); 69 | 70 | WORK_QUEUE_RECV_INSERT_HEAD(q, head); 71 | assert(q->recv_queue == head); 72 | 73 | WORK_QUEUE_RECV_LEN(q, q_len, fn); 74 | assert(q_len == 1); 75 | 76 | // take head, add node, re-insert head 77 | WORK_QUEUE_RECV_TAKE_HEAD(q, head); 78 | assert(q->recv_queue == NULL); 79 | assert(head != NULL); 80 | 81 | lm_lora_msg_ptr_t n1 = malloc(sizeof(lm_lora_msg_t)); 82 | n1->next = NULL; 83 | n1->prev = NULL; 84 | 85 | WORK_QUEUE_RECV_ADD(q, n1); 86 | assert(q->recv_queue == n1); 87 | 88 | WORK_QUEUE_RECV_INSERT_HEAD(q, head); 89 | assert(q->recv_queue == head); 90 | 91 | WORK_QUEUE_RECV_LEN(q, q_len, fn); 92 | assert(q_len == 2); 93 | } --------------------------------------------------------------------------------