├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── components ├── embear_logger │ └── CMakeLists.txt ├── http_parser │ └── CMakeLists.txt ├── iota_cclient │ ├── CMakeLists.txt │ ├── gen_hash_container.sh │ └── init.sh ├── keccak │ ├── CMakeLists.txt │ ├── init.sh │ └── keccak_58b20ec99f8a891913d8cf0ea350d05b6fb3ae41.patch ├── qrcodegen │ └── CMakeLists.txt └── uthash │ └── CMakeLists.txt ├── image ├── IOTA Cashier.png ├── cashier_init.jpg ├── cashier_standby.jpg └── iota_cashier.plantuml ├── init.sh ├── main ├── CMakeLists.txt ├── Kconfig.projbuild ├── cashier.c ├── cashier.h ├── component.mk └── main.c └── sdkconfig.defaults /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | ColumnLimit: 120 5 | ... 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sdkconfig 2 | sdkconfig.old 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "components/embear_logger/logger"] 2 | path = components/embear_logger/logger 3 | url = https://github.com/embear/logger.git 4 | branch = v4.0.x 5 | [submodule "components/uthash/uthash"] 6 | path = components/uthash/uthash 7 | url = https://github.com/troydhanson/uthash.git 8 | [submodule "components/http_parser/http_parser"] 9 | path = components/http_parser/http_parser 10 | url = https://github.com/nodejs/http-parser.git 11 | [submodule "components/keccak/keccak"] 12 | path = components/keccak/keccak 13 | url = https://github.com/XKCP/XKCP.git 14 | [submodule "components/iota_cclient/entangled"] 15 | path = components/iota_cclient/entangled 16 | url = https://github.com/iotaledger/entangled.git 17 | [submodule "components/qrcodegen/qrcodegen"] 18 | path = components/qrcodegen/qrcodegen 19 | url = https://github.com/nayuki/QR-Code-generator.git 20 | [submodule "components/ST7735"] 21 | path = components/ST7735 22 | url = https://github.com/oopsmonk/esp32_lib_st7735.git 23 | -------------------------------------------------------------------------------- /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(iota-cashier) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sam Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := hello-world 7 | 8 | include $(IDF_PATH)/make/project.mk 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IOTA Cashier 2 | 3 | A payment device powered by IOTA CClient on ESP32. 4 | 5 | It generates a QR code from an unspent address for costumers paying IOTA tokens, after the owner withdrawal tokens from this address the device will seek for next unspent address and refresh QR code, it prevents to reuse an address. 6 | 7 | ## Flowchart 8 | 9 | ![](https://github.com/oopsmonk/iota_esp32_cashier/raw/master/image/IOTA%20Cashier.png) 10 | 11 | ## Demonstration 12 | 13 | **Receiving tokens** 14 | [![](http://img.youtube.com/vi/Vp9J2ntikcc/0.jpg)](http://www.youtube.com/watch?v=Vp9J2ntikcc) 15 | 16 | **Updating the QR code after a withdraw** 17 | [![](http://img.youtube.com/vi/a_qEPlbzrig/0.jpg)](http://www.youtube.com/watch?v=a_qEPlbzrig) 18 | 19 | ## Requirements 20 | 21 | * [ESP32-DevKitC V4](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-devkitc.html#functional-description) 22 | 23 | ## ESP32 build system setup 24 | 25 | Please follow documentations to setup your toolchain and development framework. 26 | 27 | Linux and MacOS: 28 | * [xtensa-esp32 toolchain(Linux)](https://docs.espressif.com/projects/esp-idf/en/v3.3.1/get-started-cmake/linux-setup.html) 29 | * [xtensa-esp32 toolchain(MacOS)](https://docs.espressif.com/projects/esp-idf/en/v3.3.1/get-started-cmake/macos-setup.html) 30 | * [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/v3.3.1/get-started-cmake/index.html#linux-and-macos) 31 | 32 | Windows: 33 | * [xtensa-esp32 toolchain](https://docs.espressif.com/projects/esp-idf/en/v3.3.1/get-started-cmake/windows-setup.html#standard-setup-of-toolchain-for-windows-cmake) 34 | * [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/v3.3.1/get-started-cmake/index.html#windows-command-prompt) 35 | 36 | **Notice: We use the ESP-IDF v3.3.1** 37 | 38 | ``` 39 | git clone -b v3.3.1 --recursive https://github.com/espressif/esp-idf.git 40 | ``` 41 | 42 | Now, you can test your develop environment via the [hello_world](https://github.com/espressif/esp-idf/tree/release/v3.2/examples/get-started/hello_world) project. 43 | 44 | ```shell 45 | cd ~/esp 46 | cp -r $IDF_PATH/examples/get-started/hello_world . 47 | idf.py menuconfig 48 | idf.py build 49 | idf.py -p /dev/ttyUSB0 flash && idf.py -p /dev/ttyUSB0 monitor 50 | ``` 51 | 52 | The output would be something like: 53 | 54 | ```shell 55 | I (0) cpu_start: App cpu up. 56 | I (184) heap_init: Initializing. RAM available for dynamic allo 57 | cation: 58 | I (191) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM 59 | I (197) heap_init: At 3FFB2EF8 len 0002D108 (180 KiB): DRAM 60 | I (204) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM 61 | I (210) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM 62 | I (216) heap_init: At 40089560 len 00016AA0 (90 KiB): IRAM 63 | I (223) cpu_start: Pro cpu start user code 64 | I (241) cpu_start: Starting scheduler on PRO CPU. 65 | I (0) cpu_start: Starting scheduler on APP CPU. 66 | Hello world! 67 | This is ESP32 chip with 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 4MB external flash 68 | Restarting in 10 seconds... 69 | Restarting in 9 seconds... 70 | ``` 71 | 72 | You can press `Ctrl` + `]` to exit the monitor and ready for the next setup. 73 | 74 | ## Building and flashing to ESP32 75 | 76 | ### Step 1: cloning repository 77 | 78 | ```shell 79 | git clone --recursive https://github.com/oopsmonk/iota_esp32_cashier.git 80 | ``` 81 | 82 | Or (if you didn't put the `--recursive` command during clone) 83 | 84 | ```shell 85 | git clone https://github.com/oopsmonk/iota_esp32_cashier.git 86 | cd iota_esp32_cashier 87 | git submodule update --init --recursive 88 | ``` 89 | 90 | ### Step 2: initializing components 91 | 92 | The `init.sh` helps us to generate files and switch to the right branch for the components. 93 | 94 | Linux and MacOS: 95 | 96 | ```shell 97 | cd iota_esp32_cashier 98 | bash ./init.sh 99 | ``` 100 | 101 | Windows: use **Git Bash** to run the command above. 102 | 103 | ### Step 3: Configuration 104 | 105 | In this step, you need to set up the WiFi, SNTP, IRI node, and a receiver. 106 | 107 | ``` 108 | idf.py menuconfig 109 | # WiFi SSID & Password 110 | [IOTA Cashier] -> [WiFi] 111 | 112 | # SNTP Client 113 | [IOTA Cashier] -> [SNTP] 114 | 115 | # Default IRI node 116 | [IOTA Cashier] -> [IRI Node] 117 | 118 | # The time of monitoring 119 | [IOTA Cashier] -> (30) Monitor interval (s) 120 | 121 | # Do you wanna update address automatically? 122 | [IOTA Cashier] -> [ ] Auto refresh address 123 | 124 | # Enable LCD driver? 125 | [IOTA Cashier] -> [ ] Support LCD 126 | 127 | # LCD driver config 128 | [Component config] -> ST7735 Configuration 129 | ``` 130 | 131 | You can check configures in `sdkconfig` file. 132 | 133 | Please make sure you assigned the receiver(`CONFIG_MSG_RECEIVER`), Here is an example for your configuration: 134 | 135 | **LCD support with auto address update** 136 | ```shell 137 | CONFIG_WIFI_SSID="YOUR_SSID" 138 | CONFIG_WIFI_PASSWORD="YOUR_PWD" 139 | CONFIG_SNTP_SERVER="pool.ntp.org" 140 | CONFIG_SNTP_TZ="CST-8" 141 | CONFIG_IRI_NODE_URI="nodes.thetangle.org" 142 | CONFIG_IRI_NODE_PORT=443 143 | CONFIG_ENABLE_HTTPS=y 144 | CONFIG_INTERVAL=30 145 | CONFIG_ADDRESS_REFRESH=y 146 | # IOTA Seed, it's a 81 characters string 147 | CONFIG_IOTA_SEED="SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9SEED9S" 148 | # IOTA security level, could be 1, 2, or 3. 149 | CONFIG_IOTA_SECURITY=2 150 | # The start index of finding an unspent address. 151 | CONFIG_IOTA_ADDRESS_START_INDEX=0 152 | CONFIG_FTF_LCD=y 153 | CONFIG_ST7735_BL_PIN=17 154 | CONFIG_USE_COLOR_RBG565=y 155 | # CONFIG_USE_COLOR_RGB565 is not set 156 | CONFIG_ST7735_HOST_VSPI=y 157 | # CONFIG_ST7735_HOST_HSPI is not set 158 | ``` 159 | 160 | **LCD support without auto address update** 161 | ```shell 162 | CONFIG_WIFI_SSID="YOUR_SSID" 163 | CONFIG_WIFI_PASSWORD="YOUR_PWD" 164 | CONFIG_SNTP_SERVER="pool.ntp.org" 165 | CONFIG_SNTP_TZ="CST-8" 166 | CONFIG_IRI_NODE_URI="nodes.thetangle.org" 167 | CONFIG_IRI_NODE_PORT=443 168 | CONFIG_ENABLE_HTTPS=y 169 | CONFIG_INTERVAL=30 170 | CONFIG_ADDRESS_REFRESH= 171 | # Address with checksum, it's a 90 characters string 172 | CONFIG_IOTA_RECEIVER="RECEIVER9CHECHSUM9RECEIVER9CHECHSUM9RECEIVER9CHECHSUM9RECEIVER9CHECHSUM9RECEIVER9CHECHSUM9" 173 | CONFIG_FTF_LCD=y 174 | CONFIG_ST7735_BL_PIN=17 175 | CONFIG_USE_COLOR_RBG565=y 176 | CONFIG_USE_COLOR_RGB565= 177 | CONFIG_ST7735_HOST_VSPI=y 178 | CONFIG_ST7735_HOST_HSPI= 179 | ``` 180 | 181 | The `CONFIG_SNTP_TZ` follows the [POSIX Timezone string](https://github.com/nayarsystems/posix_tz_db/blob/master/zones.json) 182 | 183 | ### Step 4: Build & Run 184 | 185 | ```shell 186 | idf.py build 187 | idf.py -p /dev/ttyUSB0 flash && idf.py -p /dev/ttyUSB0 monitor 188 | ``` 189 | 190 | Output: 191 | ```shell 192 | I (4310) main: WiFi Connected 193 | I (4310) main: IRI Node: nodes.thetangle.org, port: 443, HTTPS:True 194 | I (4380) main: Initializing SNTP: pool.ntp.org, Timezone: CST-8 195 | I (4390) main: Waiting for system time to be set... (1/10) 196 | I (6390) main: The current date/time is: Wed Oct 2 17:06:29 2019 197 | I (6450) cashier: Get unspent address from 5 198 | E (11380) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time: 199 | E (11380) task_wdt: - IDLE0 (CPU 0) 200 | E (11380) task_wdt: Tasks currently running: 201 | E (11380) task_wdt: CPU 0: main 202 | E (11380) task_wdt: CPU 1: IDLE1 203 | E (21940) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time: 204 | E (21940) task_wdt: - IDLE0 (CPU 0) 205 | E (21940) task_wdt: Tasks currently running: 206 | E (21940) task_wdt: CPU 0: main 207 | E (21940) task_wdt: CPU 1: IDLE1 208 | Get balance [6]RECEIVER9ADDRESS9RECEIVER9ADDRESS9RECEIVER9ADDRESS9RECEIVER9ADDRESS9RECEIVER9ADDR 209 | I (29960) main: Initial balance: 4800i, interval 30 210 | Get balance [6]RECEIVER9ADDRESS9RECEIVER9ADDRESS9RECEIVER9ADDRESS9RECEIVER9ADDRESS9RECEIVER9ADDR 211 | = 4800i 212 | ``` 213 | 214 | `Ctrl` + `]` to exit. 215 | 216 | 217 | ## LCD Wiring Diagram 218 | 219 | **ESP32 VSPI** 220 | ![](https://github.com/oopsmonk/esp32_lib_st7735/raw/master/image/ESP32-ST7735-Wiring-VSPI.jpg) 221 | 222 | **ESP32 HVSPI** 223 | ![](https://github.com/oopsmonk/esp32_lib_st7735/raw/master/image/ESP32-ST7735-Wiring-HSPI.jpg) 224 | 225 | 226 | ## Troubleshooting 227 | 228 | `CONFIG_IOTA_RECEIVER` or `CONFIG_IOTA_SEED` is not set or is invalid: 229 | ```shell 230 | I (0) cpu_start: Starting scheduler on APP CPU. 231 | E (3443) main: please set a valid hash(CONFIG_IOTA_RECEIVER or CONFIG_IOTA_SEED) in sdkconfig! 232 | I (3443) main: Restarting in 5 seconds... 233 | I (4443) main: Restarting in 4 seconds... 234 | I (5443) main: Restarting in 3 seconds... 235 | ``` 236 | 237 | `CONFIG_MAIN_TASK_STACK_SIZE` is too small, you need to enlarge it: 238 | ```shell 239 | ***ERROR*** A stack overflow in task main has been detected. 240 | abort() was called at PC 0x4008af7c on core 0 241 | ``` 242 | -------------------------------------------------------------------------------- /components/embear_logger/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCS 2 | logger/src/logger.c 3 | ) 4 | 5 | set(COMPONENT_ADD_INCLUDEDIRS "${CMAKE_CURRENT_LIST_DIR}/logger/include") 6 | 7 | register_component() -------------------------------------------------------------------------------- /components/http_parser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCS 2 | http_parser/http_parser.c 3 | ) 4 | 5 | set(COMPONENT_ADD_INCLUDEDIRS "${CMAKE_CURRENT_LIST_DIR}/http_parser") 6 | 7 | register_component() -------------------------------------------------------------------------------- /components/iota_cclient/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CClient for ESP32 platfrom 2 | 3 | set(ENTANGLED_DIR entangled) 4 | set(UTILS_DIR ${ENTANGLED_DIR}/utils) 5 | set(COMMON_DIR ${ENTANGLED_DIR}/common) 6 | set(CCLIENT_DIR ${ENTANGLED_DIR}/cclient) 7 | set(HASH_CONTAINERS_DIR ${COMPONENT_PATH}/${UTILS_DIR}/containers/hash) 8 | 9 | set(COMPONENT_PRIV_INCLUDEDIRS ${ENTANGLED_DIR}) 10 | # common/errors 11 | set(COMMON_ERROR 12 | ${COMMON_DIR}/errors.c 13 | ) 14 | # utils 15 | set(UTILS 16 | ${UTILS_DIR}/time.c 17 | ${UTILS_DIR}/logger_helper.c 18 | ${UTILS_DIR}/char_buffer.c 19 | ${UTILS_DIR}/memset_safe.c 20 | ) 21 | # trinary 22 | set(COMMON_TRINARY 23 | ${COMMON_DIR}/trinary/add.c 24 | ${COMMON_DIR}/trinary/flex_trit.c 25 | ${COMMON_DIR}/trinary/ptrit_incr.c 26 | ${COMMON_DIR}/trinary/trit_byte.c 27 | ${COMMON_DIR}/trinary/trit_long.c 28 | ${COMMON_DIR}/trinary/trit_tryte.c 29 | ${COMMON_DIR}/trinary/tryte_ascii.c 30 | ${COMMON_DIR}/trinary/tryte_long.c 31 | ) 32 | #http client 33 | set(HTTP_CLIENT 34 | ${CCLIENT_DIR}/http/http.c 35 | ${CCLIENT_DIR}/http/socket.c 36 | ${CCLIENT_DIR}/service.c 37 | ) 38 | #hash container 39 | set(HASH_CONTAINERS 40 | ${HASH_CONTAINERS_DIR}/hash_array.c 41 | ${HASH_CONTAINERS_DIR}/hash27_queue.c 42 | ${HASH_CONTAINERS_DIR}/hash81_queue.c 43 | ${HASH_CONTAINERS_DIR}/hash243_queue.c 44 | ${HASH_CONTAINERS_DIR}/hash6561_queue.c 45 | ${HASH_CONTAINERS_DIR}/hash8019_queue.c 46 | ${HASH_CONTAINERS_DIR}/hash27_stack.c 47 | ${HASH_CONTAINERS_DIR}/hash81_stack.c 48 | ${HASH_CONTAINERS_DIR}/hash243_stack.c 49 | ${HASH_CONTAINERS_DIR}/hash6561_stack.c 50 | ${HASH_CONTAINERS_DIR}/hash8019_stack.c 51 | ) 52 | # common curl 53 | set(COMMON_CURL 54 | ${COMMON_DIR}/crypto/curl-p/const.c 55 | ${COMMON_DIR}/crypto/curl-p/curl_p.c 56 | ${COMMON_DIR}/crypto/curl-p/digest.c 57 | ) 58 | set(COMMON_KERL 59 | ${COMMON_DIR}/crypto/kerl/bigint.c 60 | ${COMMON_DIR}/crypto/kerl/converter.c 61 | ${COMMON_DIR}/crypto/kerl/kerl.c 62 | ${COMMON_DIR}/crypto/kerl/hash.c 63 | ) 64 | set(COMMON_HELPERS 65 | ${COMMON_DIR}/helpers/checksum.c 66 | ${COMMON_DIR}/helpers/digest.c 67 | ${COMMON_DIR}/helpers/sign.c 68 | ) 69 | set(COMMON_MODEL 70 | ${COMMON_DIR}/model/bundle.c 71 | ${COMMON_DIR}/model/transaction.c 72 | ${COMMON_DIR}/model/transfer.c 73 | ) 74 | set(COMMON_SIGN 75 | ${COMMON_DIR}/crypto/iss/v1/iss_curl.c 76 | ${COMMON_DIR}/crypto/iss/v1/iss_kerl.c 77 | ${COMMON_DIR}/crypto/iss/normalize.c 78 | ) 79 | 80 | set(COMMON_SRC 81 | ${COMMON_ERROR} 82 | ${COMMON_TRINARY} 83 | ${COMMON_CURL} 84 | ${COMMON_KERL} 85 | ${COMMON_HELPERS} 86 | ${COMMON_MODEL} 87 | ${COMMON_SIGN} 88 | ) 89 | 90 | #json serialization 91 | set(JSON_SERIALIZER_JSON_DIR ${CCLIENT_DIR}/serialization/json) 92 | set(JSON_SERIALIZER_JSON 93 | ${JSON_SERIALIZER_JSON_DIR}/add_neighbors.c 94 | ${JSON_SERIALIZER_JSON_DIR}/attach_to_tangle.c 95 | ${JSON_SERIALIZER_JSON_DIR}/broadcast_transactions.c 96 | ${JSON_SERIALIZER_JSON_DIR}/check_consistency.c 97 | ${JSON_SERIALIZER_JSON_DIR}/error.c 98 | ${JSON_SERIALIZER_JSON_DIR}/find_transactions.c 99 | ${JSON_SERIALIZER_JSON_DIR}/get_balances.c 100 | ${JSON_SERIALIZER_JSON_DIR}/get_inclusion_states.c 101 | ${JSON_SERIALIZER_JSON_DIR}/get_missing_transactions.c 102 | ${JSON_SERIALIZER_JSON_DIR}/get_neighbors.c 103 | ${JSON_SERIALIZER_JSON_DIR}/get_node_api_conf.c 104 | ${JSON_SERIALIZER_JSON_DIR}/get_node_info.c 105 | ${JSON_SERIALIZER_JSON_DIR}/get_tips.c 106 | ${JSON_SERIALIZER_JSON_DIR}/get_transactions_to_approve.c 107 | ${JSON_SERIALIZER_JSON_DIR}/get_trytes.c 108 | ${JSON_SERIALIZER_JSON_DIR}/helpers.c 109 | ${JSON_SERIALIZER_JSON_DIR}/json_serializer.c 110 | ${JSON_SERIALIZER_JSON_DIR}/logger.c 111 | ${JSON_SERIALIZER_JSON_DIR}/remove_neighbors.c 112 | ${JSON_SERIALIZER_JSON_DIR}/store_transactions.c 113 | ${JSON_SERIALIZER_JSON_DIR}/were_addresses_spent_from.c 114 | ) 115 | #request 116 | set(API_REQUEST_DIR ${CCLIENT_DIR}/request) 117 | set(API_REQUEST 118 | ${API_REQUEST_DIR}/add_neighbors.c 119 | ${API_REQUEST_DIR}/attach_to_tangle.c 120 | ${API_REQUEST_DIR}/broadcast_transactions.c 121 | ${API_REQUEST_DIR}/check_consistency.c 122 | ${API_REQUEST_DIR}/find_transactions.c 123 | ${API_REQUEST_DIR}/get_balances.c 124 | ${API_REQUEST_DIR}/get_inclusion_states.c 125 | ${API_REQUEST_DIR}/get_transactions_to_approve.c 126 | ${API_REQUEST_DIR}/get_trytes.c 127 | ${API_REQUEST_DIR}/remove_neighbors.c 128 | ${API_REQUEST_DIR}/store_transactions.c 129 | ${API_REQUEST_DIR}/were_addresses_spent_from.c 130 | ) 131 | set(API_RESPONSE_DIR ${CCLIENT_DIR}/response) 132 | set(API_RESPONSE 133 | ${API_RESPONSE_DIR}/add_neighbors.c 134 | ${API_RESPONSE_DIR}/attach_to_tangle.c 135 | ${API_RESPONSE_DIR}/check_consistency.c 136 | ${API_RESPONSE_DIR}/error.c 137 | ${API_RESPONSE_DIR}/find_transactions.c 138 | ${API_RESPONSE_DIR}/get_balances.c 139 | ${API_RESPONSE_DIR}/get_inclusion_states.c 140 | ${API_RESPONSE_DIR}/get_missing_transactions.c 141 | ${API_RESPONSE_DIR}/get_neighbors.c 142 | ${API_RESPONSE_DIR}/get_node_info.c 143 | ${API_RESPONSE_DIR}/get_tips.c 144 | ${API_RESPONSE_DIR}/get_transactions_to_approve.c 145 | ${API_RESPONSE_DIR}/get_trytes.c 146 | ${API_RESPONSE_DIR}/remove_neighbors.c 147 | ${API_RESPONSE_DIR}/were_addresses_spent_from.c 148 | ) 149 | 150 | set(CCLIENT_API_CORE_DIR ${CCLIENT_DIR}/api/core) 151 | set(CCLIENT_CORE 152 | ${CCLIENT_API_CORE_DIR}/get_inclusion_states.c 153 | ${CCLIENT_API_CORE_DIR}/get_node_info.c 154 | ${CCLIENT_API_CORE_DIR}/get_neighbors.c 155 | ${CCLIENT_API_CORE_DIR}/core_init.c 156 | ${CCLIENT_API_CORE_DIR}/get_transactions_to_approve.c 157 | ${CCLIENT_API_CORE_DIR}/find_transactions.c 158 | ${CCLIENT_API_CORE_DIR}/logger.c 159 | ${CCLIENT_API_CORE_DIR}/attach_to_tangle.c 160 | ${CCLIENT_API_CORE_DIR}/store_transactions.c 161 | ${CCLIENT_API_CORE_DIR}/get_balances.c 162 | ${CCLIENT_API_CORE_DIR}/remove_neighbors.c 163 | ${CCLIENT_API_CORE_DIR}/add_neighbors.c 164 | ${CCLIENT_API_CORE_DIR}/broadcast_transactions.c 165 | ${CCLIENT_API_CORE_DIR}/check_consistency.c 166 | ${CCLIENT_API_CORE_DIR}/get_tips.c 167 | ${CCLIENT_API_CORE_DIR}/get_trytes.c 168 | ${CCLIENT_API_CORE_DIR}/were_addresses_spent_from.c 169 | ${CCLIENT_API_CORE_DIR}/get_node_api_conf.c 170 | ) 171 | set(CCLIENT_API_EXTENDED_DIR ${CCLIENT_DIR}/api/extended) 172 | set(CCLIENT_EXTENDED 173 | ${CCLIENT_API_EXTENDED_DIR}/send_transfer.c 174 | ${CCLIENT_API_EXTENDED_DIR}/extended_init.c 175 | ${CCLIENT_API_EXTENDED_DIR}/get_account_data.c 176 | ${CCLIENT_API_EXTENDED_DIR}/broadcast_bundle.c 177 | ${CCLIENT_API_EXTENDED_DIR}/get_new_address.c 178 | ${CCLIENT_API_EXTENDED_DIR}/is_promotable.c 179 | ${CCLIENT_API_EXTENDED_DIR}/send_trytes.c 180 | ${CCLIENT_API_EXTENDED_DIR}/find_transaction_objects.c 181 | ${CCLIENT_API_EXTENDED_DIR}/traverse_bundle.c 182 | ${CCLIENT_API_EXTENDED_DIR}/logger.c 183 | ${CCLIENT_API_EXTENDED_DIR}/replay_bundle.c 184 | ${CCLIENT_API_EXTENDED_DIR}/store_and_broadcast.c 185 | ${CCLIENT_API_EXTENDED_DIR}/get_inputs.c 186 | ${CCLIENT_API_EXTENDED_DIR}/prepare_transfers.c 187 | ${CCLIENT_API_EXTENDED_DIR}/get_latest_inclusion.c 188 | ${CCLIENT_API_EXTENDED_DIR}/get_bundle.c 189 | ${CCLIENT_API_EXTENDED_DIR}/promote_transaction.c 190 | ${CCLIENT_API_EXTENDED_DIR}/get_transaction_objects.c 191 | ) 192 | # cclient 193 | set(CCLIENT 194 | ${JSON_SERIALIZER_JSON} 195 | ${API_REQUEST} 196 | ${API_RESPONSE} 197 | ${CCLIENT_CORE} 198 | ${CCLIENT_EXTENDED} 199 | ) 200 | 201 | set(COMPONENT_SRCS 202 | ${HASH_CONTAINERS} 203 | ${COMMON_SRC} 204 | ${UTILS} 205 | ${HTTP_CLIENT} 206 | ${CCLIENT} 207 | ) 208 | 209 | set(COMPONENT_ADD_INCLUDEDIRS ${CMAKE_CURRENT_LIST_DIR}/${ENTANGLED_DIR}) 210 | # local components 211 | set(COMPONENT_REQUIRES 212 | embear_logger 213 | uthash 214 | http_parser 215 | keccak 216 | mbedtls 217 | ) 218 | 219 | # esp-idf compoments 220 | set(COMPONENT_PRIV_REQUIRES 221 | json 222 | ) 223 | 224 | register_component() 225 | 226 | # CClient debug 227 | if(CONFIG_CCLIENT_DEBUG) 228 | add_definitions(-DDEBUG) 229 | endif() 230 | 231 | # flex_trit encoding 232 | if(CONFIG_ONE_TRIT_PER_BYTE) 233 | add_definitions(-DFLEX_TRIT_ENCODING_1_TRITS_PER_BYTE) 234 | elseif(CONFIG_THREE_TRIT_PER_BYTE) 235 | add_definitions(-DFLEX_TRIT_ENCODING_3_TRITS_PER_BYTE) 236 | elseif(CONFIG_FOUR_TRIT_PER_BYTE) 237 | add_definitions(-DFLEX_TRIT_ENCODING_4_TRITS_PER_BYTE) 238 | elseif(CONFIG_FIVE_TRIT_PER_BYTE) 239 | add_definitions(-DFLEX_TRIT_ENCODING_5_TRITS_PER_BYTE) 240 | endif() 241 | -------------------------------------------------------------------------------- /components/iota_cclient/gen_hash_container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | HASH_SIZE_LIST=("27" "81" "243" "6561" "8019") 3 | TYPE_LIST=("queue" "stack") 4 | 5 | HASH_TEMPLATE_DIR="./entangled/utils/containers/hash" 6 | for HASH_TYPE in ${TYPE_LIST[@]}; do 7 | for HASH_FILE in ${HASH_TEMPLATE_DIR}/hash_${HASH_TYPE}*.tpl; do 8 | FILE_NAME=$(basename "${HASH_FILE}" .tpl) 9 | for HASH_SIZE in ${HASH_SIZE_LIST[@]}; do 10 | HASH_FILE_NAME=${FILE_NAME/hash/hash${HASH_SIZE}} 11 | echo "cp ${HASH_FILE} ${HASH_TEMPLATE_DIR}/${HASH_FILE_NAME}" 12 | cp ${HASH_FILE} ${HASH_TEMPLATE_DIR}/${HASH_FILE_NAME} 13 | if [[ "$OSTYPE" == "darwin" ]]; then 14 | sed -i '' -e "s/{SIZE}/${HASH_SIZE}/g" "${HASH_TEMPLATE_DIR}/${HASH_FILE_NAME}" 15 | else 16 | sed -i -e "s/{SIZE}/${HASH_SIZE}/g" "${HASH_TEMPLATE_DIR}/${HASH_FILE_NAME}" 17 | fi 18 | done 19 | done 20 | done 21 | -------------------------------------------------------------------------------- /components/iota_cclient/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /bin/bash ./gen_hash_container.sh 3 | -------------------------------------------------------------------------------- /components/keccak/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCS 2 | keccak/lib/low/KeccakP-1600/Reference/KeccakP-1600-reference.c 3 | keccak/lib/high/Keccak/KeccakSpongeWidth1600.c 4 | keccak/lib/high/Keccak/FIPS202/KeccakHash.c 5 | ) 6 | 7 | set(COMPONENT_ADD_INCLUDEDIRS 8 | ${CMAKE_CURRENT_LIST_DIR}/keccak/lib/common 9 | ${CMAKE_CURRENT_LIST_DIR}/keccak/lib/low/KeccakP-1600/Reference 10 | ${CMAKE_CURRENT_LIST_DIR}/keccak/lib/high/Keccak 11 | ) 12 | 13 | register_component() -------------------------------------------------------------------------------- /components/keccak/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd ./keccak 3 | git apply ../keccak_58b20ec99f8a891913d8cf0ea350d05b6fb3ae41.patch 4 | 5 | -------------------------------------------------------------------------------- /components/keccak/keccak_58b20ec99f8a891913d8cf0ea350d05b6fb3ae41.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lib/high/Keccak/FIPS202/KeccakHash.c b/lib/high/Keccak/FIPS202/KeccakHash.c 2 | index 4bc5b31..d7b1544 100644 3 | --- a/lib/high/Keccak/FIPS202/KeccakHash.c 4 | +++ b/lib/high/Keccak/FIPS202/KeccakHash.c 5 | @@ -21,7 +21,7 @@ HashReturn Keccak_HashInitialize(Keccak_HashInstance *instance, unsigned int rat 6 | HashReturn result; 7 | 8 | if (delimitedSuffix == 0) 9 | - return FAIL; 10 | + return KECCAK_FAIL; 11 | result = (HashReturn)KeccakWidth1600_SpongeInitialize(&instance->sponge, rate, capacity); 12 | if (result != SUCCESS) 13 | return result; 14 | @@ -73,6 +73,6 @@ HashReturn Keccak_HashFinal(Keccak_HashInstance *instance, BitSequence *hashval) 15 | HashReturn Keccak_HashSqueeze(Keccak_HashInstance *instance, BitSequence *data, BitLength databitlen) 16 | { 17 | if ((databitlen % 8) != 0) 18 | - return FAIL; 19 | + return KECCAK_FAIL; 20 | return (HashReturn)KeccakWidth1600_SpongeSqueeze(&instance->sponge, data, databitlen/8); 21 | } 22 | diff --git a/lib/high/Keccak/FIPS202/KeccakHash.h b/lib/high/Keccak/FIPS202/KeccakHash.h 23 | index 782f144..0ae96f0 100644 24 | --- a/lib/high/Keccak/FIPS202/KeccakHash.h 25 | +++ b/lib/high/Keccak/FIPS202/KeccakHash.h 26 | @@ -26,7 +26,7 @@ typedef unsigned char BitSequence; 27 | typedef size_t BitLength; 28 | #endif 29 | 30 | -typedef enum { SUCCESS = 0, FAIL = 1, BAD_HASHLEN = 2 } HashReturn; 31 | +typedef enum { SUCCESS = 0, KECCAK_FAIL = 1, KECCAK_BAD_HASHLEN = 2 } HashReturn; 32 | 33 | typedef struct { 34 | KeccakWidth1600_SpongeInstance sponge; 35 | -------------------------------------------------------------------------------- /components/qrcodegen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCS 2 | "qrcodegen/c/qrcodegen.c" 3 | ) 4 | 5 | set(COMPONENT_ADD_INCLUDEDIRS "${CMAKE_CURRENT_LIST_DIR}/qrcodegen/c") 6 | 7 | register_component() 8 | -------------------------------------------------------------------------------- /components/uthash/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_ADD_INCLUDEDIRS "${CMAKE_CURRENT_LIST_DIR}/uthash/src") 2 | 3 | register_component() -------------------------------------------------------------------------------- /image/IOTA Cashier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oopsmonk/iota_esp32_cashier/4ff583509961e592731934c37aa76692bd72209e/image/IOTA Cashier.png -------------------------------------------------------------------------------- /image/cashier_init.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oopsmonk/iota_esp32_cashier/4ff583509961e592731934c37aa76692bd72209e/image/cashier_init.jpg -------------------------------------------------------------------------------- /image/cashier_standby.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oopsmonk/iota_esp32_cashier/4ff583509961e592731934c37aa76692bd72209e/image/cashier_standby.jpg -------------------------------------------------------------------------------- /image/iota_cashier.plantuml: -------------------------------------------------------------------------------- 1 | ' IOTA Cashier 2 | @startuml 3 | title IOTA Cashier 4 | 5 | start 6 | partition Initialization { 7 | repeat 8 | -LCD initialization 9 | -WiFi initialization 10 | -Checks Seed or Reciver Address 11 | -Update time via SNTP 12 | -Gets an unspent address 13 | -Gets balance 14 | repeat while ( ) is (Any fails) 15 | } 16 | 17 | partition Monitoring { 18 | repeat 19 | if (Balance increased) then ( ) 20 | :Refreash LCD; 21 | elseif (Balance decreased) then ( ) 22 | :Gets next unspent address; 23 | :Refreash LCD; 24 | elseif (Balance remained) then ( ) 25 | :Nothing; 26 | endif 27 | repeat while (Gets balance) is ( ) 28 | } 29 | @enduml -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | COMPONENTS_DIR="$PWD/components" 3 | for COMPONECT in ${COMPONENTS_DIR}/*; do 4 | INIT_SCRIPT=${COMPONECT}/init.sh 5 | if [ -f ${INIT_SCRIPT} ]; then 6 | echo "init $COMPONECT" 7 | cd ${COMPONECT} 8 | /bin/bash ./init.sh 9 | cd - 10 | fi 11 | done -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCS "main.c" "cashier.c") 2 | set(COMPONENT_ADD_INCLUDEDIRS "") 3 | 4 | set(COMPONENT_ADD_INCLUDEDIRS ${CMAKE_CURRENT_LIST_DIR}) 5 | 6 | set(COMPONENT_PRIV_REQUIRES 7 | embear_logger 8 | uthash 9 | http_parser 10 | keccak 11 | iota_cclient 12 | mbedtls 13 | qrcodegen 14 | ST7735 15 | ) 16 | 17 | set(COMPONENT_REQUIRES console spi_flash nvs_flash) 18 | 19 | register_component() 20 | 21 | # flex_trit encoding 22 | if(CONFIG_ONE_TRIT_PER_BYTE) 23 | add_definitions(-DFLEX_TRIT_ENCODING_1_TRITS_PER_BYTE) 24 | elseif(CONFIG_THREE_TRIT_PER_BYTE) 25 | add_definitions(-DFLEX_TRIT_ENCODING_3_TRITS_PER_BYTE) 26 | elseif(CONFIG_FOUR_TRIT_PER_BYTE) 27 | add_definitions(-DFLEX_TRIT_ENCODING_4_TRITS_PER_BYTE) 28 | elseif(CONFIG_FIVE_TRIT_PER_BYTE) 29 | add_definitions(-DFLEX_TRIT_ENCODING_5_TRITS_PER_BYTE) 30 | endif() 31 | -------------------------------------------------------------------------------- /main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | 2 | menu "IOTA Cashier" 3 | 4 | menu "WiFi" 5 | config WIFI_SSID 6 | string "WiFi SSID" 7 | default "myssid" 8 | help 9 | SSID (network name) for the example to connect to. 10 | 11 | config WIFI_PASSWORD 12 | string "WiFi Password" 13 | default "mypassword" 14 | help 15 | WiFi password (WPA or WPA2) for the example to use. 16 | endmenu 17 | 18 | menu "SNTP" 19 | config SNTP_SERVER 20 | string "SNTP server" 21 | default "pool.ntp.org" 22 | help 23 | Endpoint of Network Time Protocol server. 24 | 25 | config SNTP_TZ 26 | string "Timezone" 27 | default "CST-8" 28 | help 29 | POSIX timezone. Ref: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.json 30 | endmenu 31 | 32 | menu "IRI Node" 33 | config IRI_NODE_URI 34 | string "IRI Node URI" 35 | default "nodes.thetangle.org" 36 | help 37 | IRI uri for the example to use. 38 | 39 | config IRI_NODE_PORT 40 | int "Port Number of IRI Node" 41 | default 443 42 | help 43 | IRI port for the example to use. 44 | 45 | config ENABLE_HTTPS 46 | bool "Use HTTPS" 47 | default y 48 | 49 | endmenu 50 | 51 | config INTERVAL 52 | int "Monitor interval(s)" 53 | default 30 54 | help 55 | Monitoring interval in seconds. 56 | 57 | config ADDRESS_REFRESH 58 | bool "Auto refresh address" 59 | default n 60 | help 61 | Refresh address if it's a spent address. 62 | 63 | config IOTA_SEED 64 | string "Seed" 65 | default "" 66 | depends on ADDRESS_REFRESH 67 | help 68 | IOTA Seed 69 | 70 | config IOTA_SECURITY 71 | int "Security Level" 72 | default 2 73 | range 1 3 74 | depends on ADDRESS_REFRESH 75 | help 76 | the security level(1, 2, or 3) for addresses generation 77 | 78 | config IOTA_ADDRESS_START_INDEX 79 | int "Start Address" 80 | default 0 81 | depends on ADDRESS_REFRESH 82 | help 83 | the start index for finding an unspent address. 84 | 85 | if !ADDRESS_REFRESH 86 | config IOTA_RECEIVER 87 | string "Receiver address" 88 | default "" 89 | help 90 | Monitor the balance on this address. 91 | endif 92 | 93 | config FTF_LCD 94 | bool "Support LCD" 95 | default n 96 | help 97 | Support QR Code on LCD. 98 | 99 | config CCLIENT_DEBUG 100 | bool "Enable DEBUG in CClient" 101 | default n 102 | 103 | choice FLEX_TRIT_ENCODING 104 | prompt "flex_trit encoding" 105 | default THREE_TRIT_PER_BYTE 106 | help 107 | flex_trit encoding for the trinary module. 108 | 109 | config ONE_TRIT_PER_BYTE 110 | bool "1 trit per byte" 111 | config THREE_TRIT_PER_BYTE 112 | bool "3 trits per byte" 113 | config FOUR_TRIT_PER_BYTE 114 | bool "4 trits per byte" 115 | config FIVE_TRIT_PER_BYTE 116 | bool "5 trits per byte" 117 | endchoice 118 | 119 | endmenu 120 | -------------------------------------------------------------------------------- /main/cashier.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "esp_log.h" 9 | #include "freertos/FreeRTOS.h" 10 | #include "freertos/task.h" 11 | 12 | #include "cashier.h" 13 | #include "common/helpers/checksum.h" 14 | #include "qrcodegen.h" 15 | #include "sdkconfig.h" 16 | 17 | static char const *TAG = "cashier"; 18 | 19 | //================CClient Setup============= 20 | static iota_client_service_t g_cclient; 21 | // address with checksum 22 | static flex_trit_t unspent_addr[NUM_FLEX_TRITS_ADDRESS]; 23 | static uint64_t unspent_index = 0; 24 | 25 | static char const *amazon_ca1_pem = 26 | "-----BEGIN CERTIFICATE-----\r\n" 27 | "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\r\n" 28 | "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\r\n" 29 | "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\r\n" 30 | "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\r\n" 31 | "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\r\n" 32 | "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\r\n" 33 | "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\r\n" 34 | "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\r\n" 35 | "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\r\n" 36 | "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\r\n" 37 | "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\r\n" 38 | "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\r\n" 39 | "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\r\n" 40 | "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\r\n" 41 | "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\r\n" 42 | "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\r\n" 43 | "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\r\n" 44 | "rqXRfboQnoZsG4q5WTP468SQvvG5\r\n" 45 | "-----END CERTIFICATE-----\r\n"; 46 | 47 | retcode_t update_receiver_address() { 48 | #ifdef CONFIG_ADDRESS_REFRESH 49 | retcode_t ret = RC_ERROR; 50 | flex_trit_t seed[FLEX_TRIT_SIZE_243]; 51 | address_opt_t opt = {.security = CONFIG_IOTA_SECURITY, .start = CONFIG_IOTA_ADDRESS_START_INDEX, .total = UINT64_MAX}; 52 | 53 | if (unspent_index != 0) { 54 | opt.start = unspent_index; 55 | } 56 | ESP_LOGI(TAG, "Get unspent address from %" PRIu64 "\n", opt.start); 57 | 58 | if (flex_trits_from_trytes(seed, NUM_TRITS_HASH, (tryte_t const *)CONFIG_IOTA_SEED, NUM_TRYTES_HASH, 59 | NUM_TRYTES_HASH) != 0) { 60 | // seeking for an unspent address 61 | if ((ret = iota_client_get_unspent_address(&g_cclient, seed, opt, unspent_addr, &unspent_index) != RC_OK)) { 62 | ESP_LOGE(TAG, "Get unspent address failed.\n"); 63 | } 64 | } else { 65 | ESP_LOGE(TAG, "flex_trits converting failed.\n"); 66 | ret = RC_CCLIENT_FLEX_TRITS; 67 | } 68 | return ret; 69 | #else 70 | if (flex_trits_from_trytes(unspent_addr, NUM_TRITS_ADDRESS, (tryte_t const *)CONFIG_IOTA_RECEIVER, NUM_TRYTES_ADDRESS, 71 | NUM_TRYTES_ADDRESS) == 0) { 72 | ESP_LOGE(TAG, "flex_trits converting failed.\n"); 73 | return RC_CCLIENT_FLEX_TRITS; 74 | } 75 | return RC_OK; 76 | #endif 77 | } 78 | 79 | // initialize CClient lib and get an unused address. 80 | retcode_t init_iota_client() { 81 | g_cclient.http.path = "/"; 82 | g_cclient.http.content_type = "application/json"; 83 | g_cclient.http.accept = "application/json"; 84 | g_cclient.http.host = CONFIG_IRI_NODE_URI; 85 | g_cclient.http.port = CONFIG_IRI_NODE_PORT; 86 | g_cclient.http.api_version = 1; 87 | #ifdef CONFIG_ENABLE_HTTPS 88 | g_cclient.http.ca_pem = amazon_ca1_pem; 89 | #else 90 | g_cclient.http.ca_pem = NULL; 91 | #endif 92 | g_cclient.serializer_type = SR_JSON; 93 | iota_client_core_init(&g_cclient); 94 | 95 | return update_receiver_address(); 96 | } 97 | 98 | uint64_t get_balance() { 99 | retcode_t ret_code = RC_OK; 100 | get_balances_req_t *balance_req = get_balances_req_new(); 101 | get_balances_res_t *balance_res = get_balances_res_new(); 102 | uint64_t balance = 0; 103 | 104 | if (!balance_req || !balance_res) { 105 | ESP_LOGE(TAG, "Error: OOM\n"); 106 | goto done; 107 | } 108 | 109 | if ((ret_code = hash243_queue_push(&balance_req->addresses, unspent_addr)) != RC_OK) { 110 | ESP_LOGE(TAG, "Error: Adding hash to list failed!\n"); 111 | goto done; 112 | } 113 | 114 | balance_req->threshold = 100; 115 | #ifdef CONFIG_ADDRESS_REFRESH 116 | printf("Get balance [%" PRIu64 "]", unspent_index); 117 | flex_trit_print(unspent_addr, NUM_TRITS_ADDRESS); 118 | printf("\n"); 119 | #endif 120 | 121 | if ((ret_code = iota_client_get_balances(&g_cclient, balance_req, balance_res)) == RC_OK) { 122 | balance = get_balances_res_balances_at(balance_res, 0); 123 | } 124 | 125 | done: 126 | if (ret_code != RC_OK) { 127 | ESP_LOGE(TAG, "get_balance: %s\n", error_2_string(ret_code)); 128 | } 129 | get_balances_req_free(&balance_req); 130 | get_balances_res_free(&balance_res); 131 | return balance; 132 | } 133 | 134 | //===========End of CClient================= 135 | 136 | //===========LCD and QR code================ 137 | 138 | #ifdef CONFIG_FTF_LCD 139 | // text buffer for display 140 | static char lcd_text[32] = {}; 141 | #endif 142 | 143 | void lcd_init() { 144 | #ifdef CONFIG_FTF_LCD 145 | st7735_init(); 146 | #endif 147 | } 148 | 149 | void lcd_fill_screen(int16_t color) { 150 | #ifdef CONFIG_FTF_LCD 151 | st7735_fill_screen(COLOR_WHITE); 152 | #endif 153 | } 154 | 155 | void lcd_print(int16_t x, int16_t y, int16_t color, const char *fmt, ...) { 156 | #ifdef CONFIG_FTF_LCD 157 | va_list arg_list; 158 | va_start(arg_list, fmt); 159 | vsprintf(lcd_text, fmt, arg_list); 160 | va_end(arg_list); 161 | st7735_draw_string(x, y, lcd_text, color, COLOR_WHITE, 1); 162 | #endif 163 | } 164 | 165 | void show_balace(uint64_t balance) { 166 | #ifdef CONFIG_FTF_LCD 167 | // show balance on LCD 168 | if (balance > 1000000000000) { 169 | sprintf(lcd_text, "%-3.2fTi", (float)balance / 1000000000000); 170 | } else if (balance > 1000000000) { 171 | sprintf(lcd_text, "%-3.2fGi", (float)balance / 1000000000); 172 | } else if (balance > 1000000) { 173 | sprintf(lcd_text, "%-3.2fMi", (float)balance / 1000000); 174 | } else if (balance > 1000) { 175 | sprintf(lcd_text, "%-3.2fKi", (float)balance / 1000); 176 | } else { 177 | sprintf(lcd_text, "%-3di", (int)balance); 178 | } 179 | st7735_draw_string(2, 1, lcd_text, COLOR_BLUE, COLOR_WHITE, 2); 180 | #endif 181 | } 182 | 183 | void lcd_draw_qrcode() { 184 | #ifdef CONFIG_FTF_LCD 185 | int element_size = 2; 186 | int qr_version = 10; 187 | // qr code (x,y) offset 188 | int offset_x = 8, offset_y = 30; 189 | size_t qr_buff_len = qrcodegen_BUFFER_LEN_FOR_VERSION(qr_version); 190 | uint8_t qr0[qr_buff_len]; 191 | uint8_t tempBuffer[qr_buff_len]; 192 | 193 | #ifdef CONFIG_ADDRESS_REFRESH 194 | size_t sum_len = 9; 195 | char *checksum = NULL; 196 | tryte_t address_string[NUM_TRYTES_ADDRESS + sum_len + 1]; 197 | flex_trits_to_trytes(address_string, NUM_TRYTES_ADDRESS, unspent_addr, NUM_TRITS_ADDRESS, NUM_TRITS_ADDRESS); 198 | 199 | checksum = iota_checksum((char *)address_string, NUM_TRYTES_ADDRESS, sum_len); 200 | memcpy(address_string + NUM_TRYTES_ADDRESS, checksum, sum_len); 201 | free(checksum); 202 | checksum = NULL; 203 | address_string[NUM_TRYTES_ADDRESS + sum_len] = '\0'; 204 | bool ok = qrcodegen_encodeText((char *)address_string, tempBuffer, qr0, qrcodegen_Ecc_MEDIUM, qr_version, qr_version, 205 | qrcodegen_Mask_AUTO, true); 206 | #else 207 | bool ok = qrcodegen_encodeText(CONFIG_IOTA_RECEIVER, tempBuffer, qr0, qrcodegen_Ecc_MEDIUM, qr_version, qr_version, 208 | qrcodegen_Mask_AUTO, true); 209 | #endif 210 | 211 | if (ok) { 212 | int size = qrcodegen_getSize(qr0); 213 | for (int y = 0; y < size; y++) { 214 | for (int x = 0; x < size; x++) { 215 | if (qrcodegen_getModule(qr0, x, y)) { 216 | st7735_rect(x * element_size + offset_x, y * element_size + offset_y, element_size, element_size, 217 | COLOR_BLACK); 218 | } else { 219 | st7735_rect(x * element_size + offset_x, y * element_size + offset_y, element_size, element_size, 220 | COLOR_WHITE); 221 | } 222 | } 223 | } 224 | } 225 | #endif 226 | } 227 | 228 | //===========End of LCD and QR code========= 229 | -------------------------------------------------------------------------------- /main/cashier.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ST7735.h" 4 | #include "cclient/api/core/core_api.h" 5 | #include "cclient/api/extended/extended_api.h" 6 | 7 | retcode_t init_iota_client(); 8 | uint64_t get_balance(); 9 | retcode_t update_receiver_address(); 10 | 11 | void lcd_init(); 12 | void lcd_fill_screen(int16_t color); 13 | void lcd_print(int16_t x, int16_t y, int16_t color, const char *fmt, ...); 14 | void lcd_draw_qrcode(); 15 | 16 | void show_balace(uint64_t balance); -------------------------------------------------------------------------------- /main/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 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | IOTA cashier who monitoring the balance of an address. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "driver/rtc_io.h" 13 | #include "esp_log.h" 14 | #include "freertos/FreeRTOS.h" 15 | #include "freertos/task.h" 16 | #include "nvs_flash.h" 17 | #include "rom/uart.h" 18 | 19 | // sntp 20 | #include "lwip/apps/sntp.h" 21 | #include "lwip/err.h" 22 | 23 | // wifi 24 | #include "esp_event_loop.h" 25 | #include "esp_wifi.h" 26 | #include "freertos/event_groups.h" 27 | 28 | #include "cashier.h" 29 | 30 | // The BOOT butten on board, push on LOW. 31 | #define WAKE_UP_GPIO GPIO_NUM_0 32 | // ESP32-DevKitC V4 onboard LED 33 | #define BLINK_GPIO GPIO_NUM_2 34 | 35 | static char const *TAG = "main"; 36 | 37 | static EventGroupHandle_t wifi_event_group; 38 | 39 | /* The event group allows multiple bits for each event, 40 | but we only care about one event - are we connected 41 | to the AP with an IP? */ 42 | const static int CONNECTED_BIT = BIT0; 43 | 44 | // log previous balance 45 | uint64_t latest_balance = 0; 46 | 47 | static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) { 48 | switch (event->event_id) { 49 | case SYSTEM_EVENT_STA_START: 50 | esp_wifi_connect(); 51 | break; 52 | case SYSTEM_EVENT_STA_GOT_IP: 53 | xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); 54 | break; 55 | case SYSTEM_EVENT_STA_DISCONNECTED: 56 | /* This is a workaround as ESP32 WiFi libs don't currently 57 | auto-reassociate. */ 58 | esp_wifi_connect(); 59 | xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); 60 | break; 61 | default: 62 | break; 63 | } 64 | return ESP_OK; 65 | } 66 | 67 | static void wifi_conn_init(void) { 68 | tcpip_adapter_init(); 69 | wifi_event_group = xEventGroupCreate(); 70 | ESP_ERROR_CHECK(esp_event_loop_init(wifi_event_handler, NULL)); 71 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 72 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 73 | ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); 74 | wifi_config_t wifi_config = { 75 | .sta = 76 | { 77 | .ssid = CONFIG_WIFI_SSID, 78 | .password = CONFIG_WIFI_PASSWORD, 79 | }, 80 | }; 81 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 82 | ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); 83 | ESP_ERROR_CHECK(esp_wifi_start()); 84 | } 85 | 86 | static void initialize_nvs() { 87 | esp_err_t err = nvs_flash_init(); 88 | if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { 89 | ESP_ERROR_CHECK(nvs_flash_erase()); 90 | err = nvs_flash_init(); 91 | } 92 | ESP_ERROR_CHECK(err); 93 | } 94 | 95 | static void restart_in(int second) { 96 | for (int i = second; i >= 0; i--) { 97 | ESP_LOGI(TAG, "Restarting in %d seconds...", i); 98 | vTaskDelay(1000 / portTICK_PERIOD_MS); 99 | } 100 | ESP_LOGI(TAG, "Restarting now.\n"); 101 | fflush(stdout); 102 | esp_restart(); 103 | } 104 | 105 | static void update_time() { 106 | // init sntp 107 | ESP_LOGI(TAG, "Initializing SNTP: %s, Timezone: %s", CONFIG_SNTP_SERVER, CONFIG_SNTP_TZ); 108 | sntp_setoperatingmode(SNTP_OPMODE_POLL); 109 | sntp_setservername(0, CONFIG_SNTP_SERVER); 110 | sntp_init(); 111 | 112 | // wait for time to be set 113 | time_t now = 0; 114 | struct tm timeinfo = {0}; 115 | int retry = 0; 116 | const int retry_count = 10; 117 | while (timeinfo.tm_year < (2018 - 1900) && ++retry < retry_count) { 118 | ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count); 119 | vTaskDelay(2000 / portTICK_PERIOD_MS); 120 | time(&now); 121 | localtime_r(&now, &timeinfo); 122 | } 123 | 124 | if (timeinfo.tm_year < (2018 - 1900)) { 125 | ESP_LOGE(TAG, "Sync SNPT failed..."); 126 | lcd_print(1, 10, COLOR_RED, "Get time failed."); 127 | restart_in(5); 128 | } 129 | 130 | // set timezone 131 | char strftime_buf[32]; 132 | setenv("TZ", CONFIG_SNTP_TZ, 1); 133 | tzset(); 134 | localtime_r(&now, &timeinfo); 135 | strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); 136 | ESP_LOGI(TAG, "The current date/time is: %s", strftime_buf); 137 | } 138 | 139 | // validats the address or seed length in the configure file. 140 | void check_receiver_address() { 141 | size_t hash_len = 0; 142 | #ifdef CONFIG_ADDRESS_REFRESH 143 | hash_len = strlen(CONFIG_IOTA_SEED); 144 | #else 145 | hash_len = strlen(CONFIG_IOTA_RECEIVER); 146 | #endif 147 | if (!(hash_len == HASH_LENGTH_TRYTE || hash_len == HASH_LENGTH_TRYTE + 9)) { 148 | lcd_print(1, 4, COLOR_RED, "Invalid hash"); 149 | lcd_print(1, 6, COLOR_RED, "Restart in 5s"); 150 | ESP_LOGE(TAG, "please set a valid hash(CONFIG_IOTA_RECEIVER or CONFIG_IOTA_SEED) in sdkconfig!"); 151 | restart_in(5); 152 | } 153 | } 154 | 155 | void monitor_receiver_address() { 156 | uint64_t curr_balance = get_balance(); 157 | if (curr_balance > latest_balance) { 158 | // the balance has increased. 159 | printf("\033[0;34m+ %" PRIu64 "i\033[0m\n", curr_balance - latest_balance); 160 | show_balace(curr_balance); 161 | } else if (curr_balance == latest_balance) { 162 | // the balance has remained the same. 163 | printf("= %" PRIu64 "i\n", latest_balance); 164 | } else { 165 | printf("\033[1;31m- %" PRIu64 "i\033[0m\n", latest_balance - curr_balance); 166 | // this address has become a spent address, seeking for a new unspent one. 167 | lcd_fill_screen(COLOR_WHITE); 168 | lcd_print(1, 4, COLOR_RED, "Update Address..."); 169 | update_receiver_address(); 170 | curr_balance = get_balance(); 171 | lcd_draw_qrcode(); 172 | show_balace(curr_balance); 173 | } 174 | latest_balance = curr_balance; 175 | } 176 | 177 | void app_main() { 178 | // init GPIO 179 | gpio_pad_select_gpio(BLINK_GPIO); 180 | gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT); 181 | gpio_set_level(BLINK_GPIO, 1); 182 | 183 | // init lcd 184 | lcd_init(); 185 | lcd_fill_screen(COLOR_WHITE); 186 | 187 | lcd_print(1, 2, COLOR_BLACK, "Checking hash..."); 188 | check_receiver_address(); 189 | 190 | initialize_nvs(); 191 | 192 | lcd_print(1, 4, COLOR_BLACK, "Init WiFi..."); 193 | // init wifi 194 | wifi_conn_init(); 195 | 196 | /* Wait for the callback to set the CONNECTED_BIT in the event group. */ 197 | xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); 198 | ESP_LOGI(TAG, "WiFi Connected"); 199 | ESP_LOGI(TAG, "IRI Node: %s, port: %d, HTTPS:%s\n", CONFIG_IRI_NODE_URI, CONFIG_IRI_NODE_PORT, 200 | CONFIG_ENABLE_HTTPS ? "True" : "False"); 201 | 202 | lcd_print(1, 6, COLOR_BLACK, "SSID: %s", CONFIG_WIFI_SSID); 203 | // get time from sntp 204 | update_time(); 205 | 206 | // init cclient 207 | lcd_print(1, 8, COLOR_BLACK, "Init Address..."); 208 | if (init_iota_client() != RC_OK) { 209 | lcd_print(1, 10, COLOR_RED, "Initial client failed"); 210 | lcd_print(1, 12, COLOR_RED, "Restart in 5s"); 211 | ESP_LOGE(TAG, "initial client or unable to find an unused address."); 212 | restart_in(5); 213 | } 214 | 215 | latest_balance = get_balance(); 216 | ESP_LOGI(TAG, "Initial balance: %" PRIu64 "i, interval %d", latest_balance, CONFIG_INTERVAL); 217 | 218 | lcd_print(1, 10, COLOR_BLUE, "Ready to go..."); 219 | vTaskDelay(2 * 1000 / portTICK_PERIOD_MS); 220 | lcd_fill_screen(COLOR_WHITE); 221 | 222 | lcd_draw_qrcode(); 223 | show_balace(latest_balance); 224 | 225 | while (1) { 226 | vTaskDelay(CONFIG_INTERVAL * 1000 / portTICK_PERIOD_MS); 227 | monitor_receiver_address(); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_MAIN_TASK_STACK_SIZE=20480 2 | --------------------------------------------------------------------------------