├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Kconfig ├── LICENSE ├── README.md ├── assets ├── DS_SX1261-2_V1.2.pdf ├── DS_SX1276-7-8-9_W_APP_V7.pdf ├── LoRa126X_160mW_Low_Power_Consumption_Wireless_Transceiver_Module_V2.1.pdf ├── LoRa1276_datasheet.pdf ├── LoRa127X_100mW_LoRa_Wireless_Transceiver_Module_V3.0.pdf ├── Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf ├── bh1750fvi-e-186247.pdf ├── bst-bme280-ds002.pdf └── qrcode.png ├── component.mk ├── examples ├── application_example │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── rebar.config │ └── src │ │ ├── myapp.app.src │ │ ├── myapp_app.erl │ │ ├── myapp_sup.erl │ │ └── myapp_worker.erl ├── bh1750_example │ ├── LICENSE │ ├── README.md │ ├── rebar.config │ └── src │ │ ├── bh1750_example.app.src │ │ └── bh1750_example.erl ├── bme280_example │ ├── LICENSE │ ├── README.md │ ├── rebar.config │ └── src │ │ ├── bme280_example.app.src │ │ └── bme280_example.erl ├── gen_tcp_server_example │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── rebar.config │ └── src │ │ ├── config.erl-template │ │ ├── gen_tcp_server_example.app.src │ │ └── gen_tcp_server_example.erl ├── httpd_example │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── priv │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── js │ │ │ ├── app.js │ │ │ └── lib │ │ │ │ ├── backbone-1.3.3-min.js │ │ │ │ ├── backbone-min.map │ │ │ │ ├── jquery-3.1.1-min.js │ │ │ │ ├── underscore-1.8.3-min.js │ │ │ │ └── underscore-min.map │ │ └── w3.css │ ├── rebar.config │ └── src │ │ ├── config.erl-template │ │ ├── httpd_example.app.src │ │ └── httpd_example.erl ├── ledc_pwm_example │ ├── LICENSE │ ├── README.md │ ├── rebar.config │ └── src │ │ ├── ledc_pwm_example.app.src │ │ └── ledc_pwm_example.erl ├── lora_example │ ├── LICENSE │ ├── README.md │ ├── rebar.config │ └── src │ │ ├── config.erl │ │ ├── lora_example.app.src │ │ ├── lora_receiver.erl │ │ └── lora_sender.erl ├── lora_node_example │ ├── LICENSE │ ├── README.md │ ├── rebar.config │ └── src │ │ ├── config.erl │ │ ├── lora_client.erl │ │ ├── lora_node_example.app.src │ │ └── lora_server.erl └── sht3x_example │ ├── LICENSE │ ├── README.md │ ├── rebar.config │ └── src │ ├── sht3x_example.app.src │ └── sht3x_example.erl ├── include ├── httpd.hrl ├── ledc.hrl ├── logger.hrl └── trace.hrl ├── markdown ├── bh1750.md ├── bme280.md ├── components.md ├── httpd.md ├── ledc_pwm.md ├── lora.md └── sht3x.md ├── nifs ├── atomvm_lib.c └── include │ └── atomvm_lib.h ├── rebar.config └── src ├── atomvm_lib.app.src ├── atomvm_lib.erl ├── avm_application.erl ├── avm_application_controller.erl ├── avm_env.erl ├── bh1750.erl ├── bme280.erl ├── codec.erl ├── diag.erl ├── gen_tcp_server.erl ├── httpd.erl ├── httpd_api_handler.erl ├── httpd_cmd_api_handler.erl ├── httpd_env_api_handler.erl ├── httpd_file_handler.erl ├── httpd_handler.erl ├── httpd_ota_handler.erl ├── httpd_stats_api_handler.erl ├── httpd_ws_handler.erl ├── i2c_bus.erl ├── init.erl ├── json_encoder.erl ├── ledc_pwm.erl ├── lora.erl ├── lora_node.erl ├── lora_sx126x.erl ├── lora_sx127x.erl ├── map_utils.erl ├── rational.erl ├── sht3x.erl └── sx126x_cmd.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | logs 15 | _build 16 | .idea 17 | *.iml 18 | .vscode/** 19 | rebar3.crashdump 20 | rebar.lock 21 | *~ 22 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/.gitmodules -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of AtomVM. 3 | # 4 | # Copyright 2022 Fred Dushin 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 19 | # 20 | 21 | set(ATOMVM_LIB_COMPONENT_SRCS 22 | "nifs/atomvm_lib.c" 23 | ) 24 | 25 | idf_component_register( 26 | SRCS ${ATOMVM_LIB_COMPONENT_SRCS} 27 | INCLUDE_DIRS "nifs/include" 28 | PRIV_REQUIRES "libatomvm" "avm_sys" "mbedtls" 29 | ) 30 | 31 | idf_build_set_property( 32 | LINK_OPTIONS "-Wl,--whole-archive ${CMAKE_CURRENT_BINARY_DIR}/lib${COMPONENT_NAME}.a -Wl,--no-whole-archive" 33 | APPEND 34 | ) 35 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | menu "ATOMVM_LIB Configuration" 2 | 3 | config AVM_LIB_ENABLE 4 | bool "Enable AtomVM LIB driver" 5 | default y 6 | help 7 | Use this parameter to enable or disable the AtomVM LIB driver. 8 | 9 | config RTC_MEMORY_SIZE 10 | int "RTC Memory Size (in bytes)" 11 | default 0 12 | help 13 | Use this parameter to set the size of the RTC memory buffer. 14 | 15 | endmenu 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `atomvm_lib` 2 | 3 | This repository contains a collection of useful modules for developing programs for the AtomVM platform. 4 | 5 | Many of these modules are "optional" and are therefore not a part of the AtomVM core libraries. 6 | 7 | # Getting Started 8 | 9 | The best way to use this library is to include it in your rebar3 project's `rebar.config` as a dependency: 10 | 11 | {deps, [ 12 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 13 | ]}. 14 | 15 | Make sure to also include the `atomvm_rebar3_plugin`, so that you can generate AtomVM packbeam files and flash them to your ESP32 device. 16 | 17 | {plugins, [ 18 | atomvm_rebar3_plugin 19 | ]}. 20 | 21 | You can then use the `packbeam` and `esp32_flash` targets to upload your application to a device. 22 | 23 | Some of the modules in this library make use of AtomVM components, which are native-C extensions to the AtomVM virtual machine. For more information about these components and how to build them into the AtomVM virtual machines, see: 24 | 25 | * [`atomvm_lib` Components](markdown/components.md) 26 | 27 | # `atomvm_lib` modules 28 | 29 | The `atomvm_lib` library includes the following features: 30 | 31 | * [BME280](markdown/bme280.md) (Temperature, humidity, and pressure sensor) 32 | * [SHT3X](markdown/sht3x.md) (Temperature and humidity sensor) 33 | * [BH1750](markdown/bh1750.md) (Luminosity sensor) 34 | * [LEDC PWM](markdown/ledc_pwm.md) 35 | * [LoRa](markdown/lora.md) (SX127X transceiver) 36 | * [HTTPd](markdown/httpd.md) (HTTP daemon) 37 | -------------------------------------------------------------------------------- /assets/DS_SX1261-2_V1.2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/DS_SX1261-2_V1.2.pdf -------------------------------------------------------------------------------- /assets/DS_SX1276-7-8-9_W_APP_V7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/DS_SX1276-7-8-9_W_APP_V7.pdf -------------------------------------------------------------------------------- /assets/LoRa126X_160mW_Low_Power_Consumption_Wireless_Transceiver_Module_V2.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/LoRa126X_160mW_Low_Power_Consumption_Wireless_Transceiver_Module_V2.1.pdf -------------------------------------------------------------------------------- /assets/LoRa1276_datasheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/LoRa1276_datasheet.pdf -------------------------------------------------------------------------------- /assets/LoRa127X_100mW_LoRa_Wireless_Transceiver_Module_V3.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/LoRa127X_100mW_LoRa_Wireless_Transceiver_Module_V3.0.pdf -------------------------------------------------------------------------------- /assets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf -------------------------------------------------------------------------------- /assets/bh1750fvi-e-186247.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/bh1750fvi-e-186247.pdf -------------------------------------------------------------------------------- /assets/bst-bme280-ds002.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/bst-bme280-ds002.pdf -------------------------------------------------------------------------------- /assets/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/assets/qrcode.png -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS := nifs/include 2 | COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive 3 | COMPONENT_SRCDIRS := nifs 4 | CXXFLAGS += -fno-rtti 5 | -------------------------------------------------------------------------------- /examples/application_example/.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | logs 15 | _build 16 | .idea 17 | *.iml 18 | rebar3.crashdump 19 | *~ 20 | -------------------------------------------------------------------------------- /examples/application_example/README.md: -------------------------------------------------------------------------------- 1 | application_example 2 | =================== 3 | 4 | This example program illustrates how to build and deploy an OTP application into AtomVM. 5 | 6 | You can deploy an OTP application into AtomVM by implementing your application using [OTP design principles](https://www.erlang.org/doc/design_principles/users_guide.html), including implementations of the [application](https://www.erlang.org/doc/man/application.html), [supervisor](https://www.erlang.org/doc/man/supervisor.html), and various `gen_` behaviors (e.g., [gen_server](https://www.erlang.org/doc/man/gen_server.html), [gen_statem](https://www.erlang.org/doc/man/gen_statem.html), and [gen_event](https://www.erlang.org/doc/man/gen_event.html)). 7 | 8 | In addition, you can deploy your application and its dependencies (as defined in the application specification file) automatically into AtomVM without having to write a `start` entrypoint. 9 | 10 | Instead, all you need to do is using the `init` module as the start module, and all applications and their dependencies will be automatically loaded for you. 11 | 12 | In this example, we create an OTP `application`, containing a `supervisor` that supervises a single worker process. This worker simply sends a `tick` message to itself once a second. After 5 ticks, the process will exit with a `boom`. The supervisor will restart the process, and the ticks will continue. 13 | 14 | # Building the example application 15 | 16 | Build the packbeam for this application, specifying `init` as the start module. 17 | 18 | $ rebar3 packbeam -p -s init 19 | 20 | > Note that `init` is not an application that is defined in this example program, but instead is defined in the AtomVM (`atomvm_lib`) framework. 21 | 22 | If you have the [packbeam](https://github.com/atomvm/atomvm_packbeam) executable available, you can list the contents of the generated AVM file: 23 | 24 | $ packbeam list _build/default/lib/myapp.avm 25 | init.beam * [1832] 26 | myapp_worker.beam [740] 27 | myapp_sup.beam [704] 28 | myapp_app.beam [564] 29 | myapp/priv/application.bin [280] 30 | init/priv/start.boot [56] 31 | avm_env.beam [4980] 32 | avm_application_controller.beam [3800] 33 | avm_application.beam [812] 34 | atomvm_lib/priv/application.bin [712] 35 | 36 | # Running the example application 37 | 38 | You can run this example program on the `generic_unix` and `esp32` platforms. 39 | 40 | ## Running on the `generic_unix` platform 41 | 42 | Use the `atomvm` executable to run the generated AVM file: 43 | 44 | $ atomvm _build/default/lib/myapp.avm 45 | Starting myapp_app ... 46 | Starting myapp_sup ... 47 | Application myapp started 48 | tick 49 | tick 50 | tick 51 | tick 52 | tick 53 | CRASH 54 | ====== 55 | pid: <0.4.0> 56 | 57 | Stacktrace: 58 | [{myapp_worker,handle_info,2,[{file,".../examples/application_example/src/myapp_worker.erl"},{line,59}]},{gen_server,loop,2,[{file,".../AtomVM/libs/estdlib/src/gen_server.erl"},{line,399}]}] 59 | 60 | cp: #CP 61 | 62 | x[0]: exit 63 | x[1]: boom 64 | x[2]: {2,2,178,2,[{3,2284},{13,131}]} 65 | 66 | Stack 67 | ------ 68 | 69 | #CP 70 | [] 71 | [] 72 | {state,5} 73 | [] 74 | [] 75 | {state,undefined,myapp_worker,{state,5}} 76 | #CP 77 | 78 | 79 | Registers 80 | ---------- 81 | x[0]: exit 82 | x[1]: boom 83 | x[2]: {2,2,178,2,[{3,2284},{13,131}]} 84 | x[3]: [] 85 | x[4]: [] 86 | x[5]: [] 87 | x[6]: [] 88 | x[7]: [] 89 | x[8]: [] 90 | x[9]: [] 91 | x[10]: [] 92 | x[11]: [] 93 | x[12]: [] 94 | x[13]: [] 95 | x[14]: [] 96 | x[15]: [] 97 | 98 | 99 | Mailbox 100 | -------- 101 | 102 | 103 | **End Of Crash Report** 104 | 105 | ## Running on the `esp32` platform 106 | 107 | You can flash the application to your ESP32 device by using the `esp32_flash` target: 108 | 109 | $ rebar3 esp32_flash -p /dev/ttyUSB0 110 | ... 111 | 112 | You can then attach to the console using `minicom` or equivalent serial program: 113 | 114 | I (0) cpu_start: Starting scheduler on APP CPU. 115 | 116 | ########################################################### 117 | 118 | ### ######## ####### ## ## ## ## ## ## 119 | ## ## ## ## ## ### ### ## ## ### ### 120 | ## ## ## ## ## #### #### ## ## #### #### 121 | ## ## ## ## ## ## ### ## ## ## ## ### ## 122 | ######### ## ## ## ## ## ## ## ## ## 123 | ## ## ## ## ## ## ## ## ## ## ## 124 | ## ## ## ####### ## ## ### ## ## 125 | 126 | ########################################################### 127 | 128 | I (849) AtomVM: Starting AtomVM revision 0.6.0-dev+git.0c549a28 129 | I (859) AtomVM: Loaded BEAM partition main.avm at address 0x210000 (size=1048576 bytes) 130 | I (869) otp_socket: Initialized AtomVM socket. 131 | I (879) atomvm_nvs_reset: NVS Reset task is running. (pin=0 invert_pin=false) 132 | I (889) AtomVM: Found startup beam init.beam 133 | I (889) AtomVM: Loaded BEAM partition lib.avm at address 0x1d0000 (size=262144 bytes) 134 | I (899) AtomVM: Starting init.beam... 135 | --- 136 | Starting myapp_app ... 137 | Starting myapp_sup ... 138 | Application myapp started 139 | tick 140 | tick 141 | tick 142 | ... 143 | -------------------------------------------------------------------------------- /examples/application_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [{atomvm_lib, {git, "https://github.com/fadushin/atomvm_lib.git", {branch, "master"}}}]}. 3 | {plugins, [ 4 | atomvm_rebar3_plugin 5 | ]}. 6 | -------------------------------------------------------------------------------- /examples/application_example/src/myapp.app.src: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2022 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | { 18 | application, myapp, [ 19 | {description, "My AtomVM application"}, 20 | {vsn, "0.1.0"}, 21 | {registered, []}, 22 | {applications, [ 23 | kernel, 24 | stdlib 25 | ]}, 26 | {env,[]}, 27 | {mod, {myapp_app, []}}, 28 | {modules, []}, 29 | {licenses, ["Apache 2.0"]}, 30 | {links, []} 31 | ] 32 | }. 33 | -------------------------------------------------------------------------------- /examples/application_example/src/myapp_app.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2022 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(myapp_app). 18 | 19 | -export([start/2, stop/1]). 20 | 21 | start(_Type, Args) -> 22 | io:format("Starting myapp_app ...~n"), 23 | myapp_sup:start(Args). 24 | 25 | stop(_State) -> 26 | myapp_sup:stop_children(). 27 | -------------------------------------------------------------------------------- /examples/application_example/src/myapp_sup.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2022 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(myapp_sup). 18 | 19 | -export([start/1, init/1]). 20 | 21 | start(Args) -> 22 | io:format("Starting myapp_sup ...~n"), 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, Args). 24 | 25 | %% 26 | %% supervisor implementation 27 | %% 28 | 29 | init(_Args) -> 30 | {ok, { 31 | {one_for_one, 1, 1}, [ 32 | { 33 | myapp_worker, 34 | {myapp_worker, start_link, []}, 35 | permanent, 36 | brutal_kill, 37 | worker, 38 | [] 39 | } 40 | ] 41 | } 42 | }. 43 | -------------------------------------------------------------------------------- /examples/application_example/src/myapp_worker.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2022 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(myapp_worker). 18 | 19 | -export([start_link/0]). 20 | 21 | -behavior(gen_server). 22 | -export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). 23 | 24 | %% 25 | %% api 26 | %% 27 | 28 | start_link() -> 29 | gen_server:start_link(?MODULE, [], []). 30 | 31 | %% 32 | %% gen_server implementation 33 | %% 34 | 35 | -record(state, { 36 | counter = 0 37 | }). 38 | 39 | %% @hidden 40 | init([]) -> 41 | send_tick(), 42 | {ok, #state{}}. 43 | 44 | %% @hidden 45 | handle_cast(_Request, State) -> 46 | {noreply, State}. 47 | 48 | %% @hidden 49 | handle_call(_Request, _From, State) -> 50 | {reply, {error, unimplemented}, State}. 51 | 52 | %% @hidden 53 | handle_info(tick, #state{counter = 5} = State) -> 54 | io:format("boom!~n"), 55 | exit(boom), 56 | {noreply, State}; 57 | handle_info(tick, State) -> 58 | io:format("tick~n"), 59 | send_tick(), 60 | {noreply, State#state{counter = State#state.counter + 1}}; 61 | handle_info(_Msg, State) -> 62 | {noreply, State}. 63 | 64 | %% @hidden 65 | terminate(_Reason, _State) -> 66 | ok. 67 | 68 | %% 69 | %% internal implementation 70 | %% 71 | 72 | %% @private 73 | send_tick() -> 74 | erlang:send_after(1000, self(), tick). 75 | -------------------------------------------------------------------------------- /examples/bh1750_example/README.md: -------------------------------------------------------------------------------- 1 | # `bh1750_example` 2 | 3 | Welcome to the `bh1750_example` AtomVM application. 4 | 5 | This example application will drive a BH1750 luminosity sensor attached to an ESP32 device using the 2-wire I2C interface and print luminosity values in [lux](https://en.wikipedia.org/wiki/Lux) to the console. 6 | 7 | For this application, you will need: 8 | 9 | * An ESP32 device, flashed with the [AtomVM](https://github.com/bettio/AtomVM) image (including the VM and core libraries), and capable of connecting via UART to your development machine; 10 | * A BH1750 device, typically marketed as an integrated development board; 11 | * The [`esptool.py`](https://github.com/espressif/esptool) tool (for flashing); 12 | * The [`git`](https://git-scm.com) version control tool; 13 | * [Erlang/OTP 21](https://www.erlang.org) or higher, along with [`rebar3`](https://www.rebar3.org); 14 | * A serial monitor program of your choice (e.g, [`minicom`](https://en.wikipedia.org/wiki/Minicom)) 15 | 16 | While the [IDF SDK](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) and required toolchains are not required, they may make life a little easier. 17 | 18 | ## Getting Started 19 | 20 | ### Connection 21 | 22 | Use of the BH1750 will typically require connecting some kind of development board, which includes the BH1750 integrated circuit, along with additional passive components, to you ESP32 device. 23 | 24 | Connect the VCC and GND pins to a power source and ground (e.g., on your ESP32). 25 | 26 | Connect the SDA and SCL pins on two selected GPIO pins on your ESP32 device (e.g., pin 21 and 22). 27 | 28 | +-----------+ +-------------+ 29 | | VCC +-------------------------+ +3.3v | 30 | | GND +-------------------------+ GND | 31 | | SDA +-------------------------+ GPIO 21 | 32 | | SCL +-------------------------+ GPIO 22 | 33 | +-----------+ +-------------+ 34 | BH1750 ESP32 35 | 36 | > Note. The example program assumes the SDA pin is connected to the ESP32 GPIO pin 21 and the SCL pin is connected to the ESP32 GPIO pin 22. If you need to use different pins, make sure to change the example program to reflect your requirements. 37 | 38 | ### Build Instructions 39 | 40 | To build and flash this application to your ESP32 device, issue the `esp32_flash` target to the `rebar3` command, and optionally specify the device port and baud rate, if they do not match the defaults. 41 | 42 | > Note. For information about the `esp32_flash` target, see the [`atomvm_rebar3_plugin`](https://github.com/atomvm/atomvm_rebar3_plugin) instructions. 43 | 44 | shell$ rebar3 esp32_flash -p /dev/tty.usbserial-01940306 45 | ===> Verifying dependencies... 46 | ===> App atomvm_lib is a checkout dependency and cannot be locked. 47 | ===> Analyzing applications... 48 | ===> Compiling atomvm_lib 49 | ===> Analyzing applications... 50 | ===> Compiling bh1750_example 51 | ===> AVM file written to : bh1750_example.avm 52 | ===> esptool.py --chip esp32 --port /dev/tty.usbserial-01940306 --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 .../atomvm_lib/examples/bh1750_example/_build/default/lib/bh1750_example.avm 53 | 54 | esptool.py v2.1 55 | Connecting........_ 56 | Chip is ESP32D0WDQ6 (revision 1) 57 | Uploading stub... 58 | Running stub... 59 | Stub running... 60 | Configuring flash size... 61 | Auto-detected Flash size: 4MB 62 | Wrote 16384 bytes at 0x00210000 in 1.4 seconds (91.7 kbit/s)... 63 | Hash of data verified. 64 | 65 | Leaving... 66 | Hard resetting... 67 | 68 | Connect to the device using the USB port (e.g., via `minicom`), and you should see something like: 69 | 70 | Found AVM partition: size: 1048576, address: 0x210000 71 | Booting file mapped at: 0x3f430000, size: 1048576 72 | Found AVM partition: size: 1048576, address: 0x110000 73 | Starting: bh1750_example.beam... 74 | --- 75 | Luminosity: 153.22lx 76 | Luminosity: 106.66lx 77 | Luminosity: 107.50lx 78 | Luminosity: 107.50lx 79 | Luminosity: 107.50lx 80 | Luminosity: 106.66lx 81 | Luminosity: 107.50lx 82 | Luminosity: 107.50lx 83 | Luminosity: 90.0lx 84 | Luminosity: 74.16lx 85 | ... 86 | -------------------------------------------------------------------------------- /examples/bh1750_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | {atomvm_rebar3_plugin, [ 9 | {packbeam, [prune]} 10 | ]}. 11 | -------------------------------------------------------------------------------- /examples/bh1750_example/src/bh1750_example.app.src: -------------------------------------------------------------------------------- 1 | {application, bh1750_example, [ 2 | {description, "An OTP library"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | {licenses, ["Apache 2.0"]}, 11 | {links, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /examples/bh1750_example/src/bh1750_example.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2020 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(bh1750_example). 18 | 19 | -export([start/0]). 20 | 21 | start() -> 22 | {ok, I2CBus} = i2c_bus:start( 23 | #{ 24 | sda => 21, 25 | scl => 22 26 | } 27 | ), 28 | Mode = continuous, %% change to continuous to receive continuous readings 29 | {ok, BH} = bh1750:start(I2CBus, [{mode, Mode}]), 30 | case Mode of 31 | one_time -> 32 | one_time_loop(BH); 33 | continuous -> 34 | continuous_loop(BH) 35 | end. 36 | 37 | one_time_loop(BH) -> 38 | case bh1750:take_reading(BH) of 39 | {ok, Reading} -> 40 | io:format("Luminosity: ~slx\n", [Reading]); 41 | ErrorT -> 42 | io:format("Error taking reading temperature: ~p~n", [ErrorT]) 43 | end, 44 | timer:sleep(1000), 45 | one_time_loop(BH). 46 | 47 | continuous_loop(BH) -> 48 | receive 49 | Reading -> 50 | io:format("Luminosity: ~p\n", [Reading]) 51 | end, 52 | continuous_loop(BH). 53 | -------------------------------------------------------------------------------- /examples/bme280_example/README.md: -------------------------------------------------------------------------------- 1 | # `bme280_example` 2 | 3 | Welcome to the `bme280_example` AtomVM application. 4 | 5 | This example application will drive a BME280 temperature, pressure, and humidity sensor attached to an ESP32 device using the 2-wire I2C interface and print readings to the console. 6 | 7 | For this application, you will need: 8 | 9 | * An ESP32 device, flashed with the [AtomVM](https://github.com/bettio/AtomVM) image (including the VM and core libraries), and capable of connecting via UART to your development machine; 10 | * A BME280 device, typically marketed as an integrated development board; 11 | * The [`esptool.py`](https://github.com/espressif/esptool) tool (for flashing); 12 | * The [`git`](https://git-scm.com) version control tool; 13 | * [Erlang/OTP 21](https://www.erlang.org) or higher, along with [`rebar3`](https://www.rebar3.org); 14 | * A serial monitor program of your choice (e.g, [`minicom`](https://en.wikipedia.org/wiki/Minicom)) 15 | 16 | While the [IDF SDK](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) and required toolchains are not required, they may make life a little easier. 17 | 18 | ## Getting Started 19 | 20 | ### Connection 21 | 22 | Use of the BME280 will typically require connecting some kind of development board, which includes the BH1750 integrated circuit, along with additional passive components, to you ESP32 device. 23 | 24 | Connect the VCC and GND pins to a power source and ground (e.g., on your ESP32). 25 | 26 | Connect the SDA and SCL pins on two selected GPIO pins on your ESP32 device (e.g., pin 21 and 22). 27 | 28 | +-----------+ +-------------+ 29 | | VCC +-------------------------+ +3.3v | 30 | | GND +-------------------------+ GND | 31 | | SDA +-------------------------+ GPIO 21 | 32 | | SCL +-------------------------+ GPIO 22 | 33 | +-----------+ +-------------+ 34 | BME280 ESP32 35 | 36 | > Note. The example program assumes the SDA pin is connected to the ESP32 GPIO pin 21 and the SCL pin is connected to the ESP32 GPIO pin 22. If you need to use different pins, make sure to change the example program to reflect your requirements. 37 | 38 | ### Build Instructions 39 | 40 | To build and flash this application to your ESP32 device, issue the `esp32_flash` target to the `rebar3` command, and optionally specify the device port and baud rate, if they do not match the defaults. 41 | 42 | > Note. For information about the `esp32_flash` target, see the [`atomvm_rebar3_plugin`](https://github.com/atomvm/atomvm_rebar3_plugin) instructions. 43 | 44 | shell$ rebar3 esp32_flash -p /dev/ttyUSB0 -b 115200 45 | ===> Fetching atomvm_rebar3_plugin 46 | ===> Fetching packbeam (from {git,"https://github.com/atomvm/atomvm_packbeam.git", 47 | {branch,"master"}}) 48 | ===> Analyzing applications... 49 | ===> Compiling atomvm_rebar3_plugin 50 | ===> Compiling packbeam 51 | ===> Verifying dependencies... 52 | ===> App atomvm_lib is a checkout dependency and cannot be locked. 53 | ===> Analyzing applications... 54 | ===> Compiling atomvm_lib 55 | ===> Analyzing applications... 56 | ===> Compiling bme280_example 57 | ===> AVM file written to : atomvm_lib.avm 58 | ===> AVM file written to : bme280_example.avm 59 | ===> esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 .../atomvm_lib/examples/bme280_example/_build/default/lib/bme280_example.avm 60 | 61 | esptool.py v2.1 62 | Connecting........_____. 63 | Chip is ESP32D0WDQ6 (revision (unknown 0xa)) 64 | Uploading stub... 65 | Running stub... 66 | Stub running... 67 | Configuring flash size... 68 | Auto-detected Flash size: 4MB 69 | Wrote 16384 bytes at 0x00210000 in 1.4 seconds (91.4 kbit/s)... 70 | Hash of data verified. 71 | 72 | Leaving... 73 | Hard resetting... 74 | 75 | Connect to the device using the USB port (e.g., via `minicom`), and you should see something like: 76 | 77 | Found AVM partition: size: 1048576, address: 0x210000 78 | Booting file mapped at: 0x3f430000, size: 1048576 79 | Found AVM partition: size: 1048576, address: 0x110000 80 | Starting: bme280_example.beam... 81 | --- 82 | Temperature: 2.47500000000000000000e+01C, Pressure: 1.01286000000000001364e+03hPa, Humidity: 3.33222656250000000000e+01%RH 83 | Temperature: 2.47300000000000004263e+01C, Pressure: 1.01283000000000004093e+03hPa, Humidity: 3.34482421875000000000e+01%RH 84 | Temperature: 2.47199999999999988631e+01C, Pressure: 1.01282000000000005002e+03hPa, Humidity: 3.34482421875000000000e+01%RH 85 | ... 86 | -------------------------------------------------------------------------------- /examples/bme280_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | {atomvm_rebar3_plugin, [ 9 | {packbeam, [prune]} 10 | ]}. 11 | -------------------------------------------------------------------------------- /examples/bme280_example/src/bme280_example.app.src: -------------------------------------------------------------------------------- 1 | {application, bme280_example, [ 2 | {description, "An OTP library"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | {licenses, ["Apache 2.0"]}, 11 | {links, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /examples/bme280_example/src/bme280_example.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2020 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(bme280_example). 18 | 19 | -export([start/0]). 20 | 21 | start() -> 22 | {ok, I2CBus} = i2c_bus:start(#{ 23 | sda => 21, 24 | scl => 22 25 | }), 26 | {ok, BME} = bme280:start(I2CBus), 27 | loop(BME). 28 | 29 | loop(BME) -> 30 | case bme280:take_reading(BME) of 31 | {ok, Reading} -> 32 | {Temperature, Pressure, Humidity} = Reading, 33 | io:format("Temperature: ~pC, Pressure: ~phPa, Humidity: ~p%RH~n", [ 34 | Temperature, Pressure, Humidity 35 | ]); 36 | ErrorT -> 37 | io:format("Error taking reading temperature: ~p~n", [ErrorT]) 38 | end, 39 | timer:sleep(5000), 40 | loop(BME). 41 | -------------------------------------------------------------------------------- /examples/gen_tcp_server_example/.gitignore: -------------------------------------------------------------------------------- 1 | src/config.erl 2 | -------------------------------------------------------------------------------- /examples/gen_tcp_server_example/README.md: -------------------------------------------------------------------------------- 1 | # gen_tcp_server Example Program 2 | -------------------------------------------------------------------------------- /examples/gen_tcp_server_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | -------------------------------------------------------------------------------- /examples/gen_tcp_server_example/src/config.erl-template: -------------------------------------------------------------------------------- 1 | % 2 | % This file is part of AtomVM. 3 | % 4 | % Copyright 2022 Fred Dushin 5 | % 6 | % Licensed under the Apache License, Version 2.0 (the "License"); 7 | % you may not use this file except in compliance with the License. 8 | % You may obtain a copy of the License at 9 | % 10 | % http://www.apache.org/licenses/LICENSE-2.0 11 | % 12 | % Unless required by applicable law or agreed to in writing, software 13 | % distributed under the License is distributed on an "AS IS" BASIS, 14 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | % See the License for the specific language governing permissions and 16 | % limitations under the License. 17 | % 18 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 19 | % 20 | -module(config). 21 | 22 | -export([get/0]). 23 | 24 | %% Copy this file to config.erl and edit to your satisfaction 25 | 26 | get() -> 27 | #{ 28 | sta => [ 29 | {ssid, "myssid"}, 30 | {psk, "mypassword"} 31 | ] 32 | }. 33 | -------------------------------------------------------------------------------- /examples/gen_tcp_server_example/src/gen_tcp_server_example.app.src: -------------------------------------------------------------------------------- 1 | {application, gen_tcp_server_example, [ 2 | {description, "An OTP library"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | {licenses, ["Apache 2.0"]}, 11 | {links, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /examples/gen_tcp_server_example/src/gen_tcp_server_example.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2021 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(gen_tcp_server_example). 18 | 19 | -export([start/0]). 20 | 21 | -behavior(gen_tcp_server). 22 | -export([init/1, handle_receive/3, handle_tcp_closed/2]). 23 | 24 | start() -> 25 | ok = maybe_start_network(atomvm:platform()), 26 | 27 | {ok, Pid} = gen_tcp_server:start(1342, #{{socket, reuseaddr} => true, {socket, linger} => #{onoff => true, linger => 0}}, ?MODULE, []), 28 | io:format("Started gen_tcp_server ~p on port ~p~n", [Pid, 1342]), 29 | timer:sleep(infinity). 30 | 31 | init(_) -> 32 | {ok, undefined}. 33 | 34 | handle_receive(Socket, <<"sleep\n">>, State) -> 35 | io:format("Received sleep from ~p. sleeping ...~n", [Socket]), 36 | timer:sleep(5000), 37 | io:format("Done with sleep. replying with ok packet~n"), 38 | {reply, <<"ok\n">>, State}; 39 | handle_receive(Socket, Packet, State) -> 40 | io:format("Received ~p from ~p. Echoing back...~n", [Packet, Socket]), 41 | {reply, Packet, State}. 42 | 43 | handle_tcp_closed(Socket, _State) -> 44 | io:format("Socekt closed: ~p~n", [Socket]), 45 | ok. 46 | 47 | %% 48 | %% Internal functions 49 | %% 50 | 51 | %% @private 52 | maybe_start_network(esp32) -> 53 | Config = maps:get(sta, config:get()), 54 | case network:wait_for_sta(Config, 30000) of 55 | {ok, {Address, Netmask, Gateway}} -> 56 | io:format( 57 | "Acquired IP address: ~p Netmask: ~p Gateway: ~p~n", 58 | [Address, Netmask, Gateway] 59 | ), 60 | ok; 61 | Error -> 62 | io:format("An error occurred starting network: ~p~n", [Error]), 63 | Error 64 | end; 65 | maybe_start_network(_Platform) -> 66 | ok. 67 | -------------------------------------------------------------------------------- /examples/httpd_example/.gitignore: -------------------------------------------------------------------------------- 1 | src/config.erl 2 | -------------------------------------------------------------------------------- /examples/httpd_example/README.md: -------------------------------------------------------------------------------- 1 | # atomvm_app 2 | 3 | Welcome to the atomvm_app AtomVM application. 4 | 5 | To build and flash this application to your ESP32 device, issue the `esp32_flash` target 6 | 7 | shell$ rebar3 esp32_flash 8 | -------------------------------------------------------------------------------- /examples/httpd_example/priv/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomvm/atomvm_lib/453c4f40c26db67446a8f30c5ceebf0ede41ad2d/examples/httpd_example/priv/favicon.ico -------------------------------------------------------------------------------- /examples/httpd_example/priv/index.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | AtomVM Web Console 19 | 69 | 70 | 71 |
72 | 129 | 130 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /examples/httpd_example/priv/js/app.js: -------------------------------------------------------------------------------- 1 | // 2 | // # ---------------------------------------------------------------------------- 3 | // # "THE BEER-WARE LICENSE" (Revision 42): 4 | // # wrote this file. You are hereby granted permission to 5 | // # copy, modify, or mutilate this file without restriction. If you create a 6 | // # work derived from this file, you may optionally include a copy of this notice, 7 | // # for which I would be most grateful, but you are not required to do so. 8 | // # If we meet some day, and you think this stuff is worth it, you can buy me a 9 | // # beer in return. Fred Dushin 10 | // # ---------------------------------------------------------------------------- 11 | // 12 | 13 | var SystemModel = Backbone.Model.extend({ 14 | url: '/api/system_info', 15 | defaults: { 16 | platform: null, 17 | system_architecture: null, 18 | word_size: null, 19 | atomvm_version: null, 20 | esp32_chip_info: null, 21 | esp_idf_version: null, 22 | chip_model: null, 23 | chip_cores: null, 24 | chip_features: null, 25 | chip_revision: null 26 | }, 27 | 28 | parse: function(data) { 29 | data["chip_model"] = data.esp32_chip_info["model"]; 30 | data["chip_cores"] = data.esp32_chip_info["cores"]; 31 | data["chip_features"] = data.esp32_chip_info["features"]; 32 | data["chip_revision"] = data.esp32_chip_info["revision"]; 33 | return data; 34 | } 35 | }); 36 | var system_model = new SystemModel(); 37 | 38 | var SystemView = Backbone.View.extend({ 39 | el: '#system-view', 40 | template: _.template($('#system-tmpl').html()), 41 | 42 | initialize: function() { 43 | this.listenTo(this.model, 'sync change', this.render); 44 | this.model.fetch(); 45 | this.render(); 46 | }, 47 | 48 | render: function() { 49 | var html = this.template(this.model.toJSON()); 50 | this.$el.html(html); 51 | return this; 52 | } 53 | }); 54 | var system_view = new SystemView({model: system_model}) 55 | 56 | var MemoryModel = Backbone.Model.extend({ 57 | url: '/api/memory', 58 | defaults: { 59 | atom_count: null, 60 | port_count: null, 61 | process_count: null, 62 | esp32_free_heap_size: null, 63 | esp32_largest_free_block: null, 64 | esp32_minimum_free_size: null 65 | }, 66 | 67 | parse: function(data) { 68 | return data; 69 | } 70 | }); 71 | var memory_model = new MemoryModel(); 72 | 73 | var MemoryView = Backbone.View.extend({ 74 | el: '#memory-view', 75 | template: _.template($('#memory-tmpl').html()), 76 | 77 | initialize: function() { 78 | this.listenTo(this.model, 'sync change', this.render); 79 | this.model.fetch(); 80 | this.render(); 81 | }, 82 | 83 | render: function() { 84 | var html = this.template(this.model.toJSON()); 85 | this.$el.html(html); 86 | return this; 87 | } 88 | }); 89 | var memory_view = new MemoryView({model: memory_model}) 90 | 91 | var get_websocket_url = function() { 92 | var hostname = window.location.hostname; 93 | var port = window.location.port; 94 | return "ws://" + hostname + ":" + port + "/ws"; 95 | } 96 | 97 | var create_websocket = function() { 98 | var ws = new WebSocket(get_websocket_url(), []); 99 | ws.onmessage = function (event) { 100 | console.log("Received WS data: " + event.data); 101 | var data = JSON.parse(event.data); 102 | for (var key in memory_model.attributes) { 103 | if (data[key]) { 104 | memory_model.set(key, data[key]); 105 | } 106 | } 107 | } 108 | 109 | ws.onopen = function (event) { 110 | console.log("opened connection: " + event); 111 | } 112 | 113 | ws.onclose = function (event) { 114 | console.log("closed connection: " + event); 115 | webSocket = create_websocket(); 116 | } 117 | return ws; 118 | } 119 | 120 | var webSocket = create_websocket(); 121 | -------------------------------------------------------------------------------- /examples/httpd_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | {atomvm_rebar3_plugin, [ 9 | {packbeam, [prune]} 10 | ]}. 11 | -------------------------------------------------------------------------------- /examples/httpd_example/src/config.erl-template: -------------------------------------------------------------------------------- 1 | % 2 | % This file is part of AtomVM. 3 | % 4 | % Copyright 2022 Fred Dushin 5 | % 6 | % Licensed under the Apache License, Version 2.0 (the "License"); 7 | % you may not use this file except in compliance with the License. 8 | % You may obtain a copy of the License at 9 | % 10 | % http://www.apache.org/licenses/LICENSE-2.0 11 | % 12 | % Unless required by applicable law or agreed to in writing, software 13 | % distributed under the License is distributed on an "AS IS" BASIS, 14 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | % See the License for the specific language governing permissions and 16 | % limitations under the License. 17 | % 18 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 19 | % 20 | -module(config). 21 | 22 | -export([get/0]). 23 | 24 | %% Copy this file to config.erl and edit to your satisfaction 25 | 26 | get() -> 27 | #{ 28 | sta => [ 29 | {ssid, "myssid"}, 30 | {psk, "mypassword"} 31 | ] 32 | }. 33 | -------------------------------------------------------------------------------- /examples/httpd_example/src/httpd_example.app.src: -------------------------------------------------------------------------------- 1 | {application, httpd_example, [ 2 | {description, "An OTP library"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | {licenses, ["Apache 2.0"]}, 11 | {links, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /examples/httpd_example/src/httpd_example.erl: -------------------------------------------------------------------------------- 1 | % 2 | % Copyright 2022 Fred Dushin 3 | % 4 | % Licensed under the Apache License, Version 2.0 (the "License"); 5 | % you may not use this file except in compliance with the License. 6 | % You may obtain a copy of the License at 7 | % 8 | % http://www.apache.org/licenses/LICENSE-2.0 9 | % 10 | % Unless required by applicable law or agreed to in writing, software 11 | % distributed under the License is distributed on an "AS IS" BASIS, 12 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | % See the License for the specific language governing permissions and 14 | % limitations under the License. 15 | % 16 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 17 | % 18 | 19 | -module(httpd_example). 20 | 21 | -behavior(httpd_api_handler). 22 | -export([start/0, handle_api_request/4, handle_ws_init/3, handle_ws_message/2]). 23 | 24 | -export([init_handler/2, handle_http_req/2]). 25 | 26 | start() -> 27 | ok = maybe_start_network(atomvm:platform()), 28 | 29 | Config = [ 30 | {[<<"api">>], #{ 31 | handler => httpd_api_handler, 32 | handler_config => #{ 33 | module => ?MODULE 34 | } 35 | }}, 36 | {[<<"ws">>], #{ 37 | handler => httpd_ws_handler, 38 | handler_config => #{ 39 | module => ?MODULE 40 | } 41 | }}, 42 | {[<<"ota">>], #{ 43 | handler => ?MODULE 44 | }}, 45 | {[], #{ 46 | handler => httpd_file_handler, 47 | handler_config => #{ 48 | app => ?MODULE 49 | } 50 | }} 51 | ], 52 | 53 | io:format("Starting httpd on port 8080 ...~n", []), 54 | case httpd:start(8080, Config) of 55 | {ok, _Pid} -> 56 | io:format("httpd started.~n", []), 57 | timer:sleep(infinity); 58 | Error -> 59 | io:format("An error occurred: ~p~n", [Error]) 60 | end. 61 | 62 | %% 63 | %% API Handler implementation 64 | %% 65 | 66 | handle_api_request(get, [<<"system_info">>], HttpRequest, _Args) -> 67 | Socket = maps:get(socket, HttpRequest), 68 | {ok, #{addr := Host, port := Port}} = socket:peername(Socket), 69 | io:format("GET system_info request from ~p:~p~n", [Host, Port]), 70 | {ok, #{ 71 | platform => atomvm:platform(), 72 | word_size => erlang:system_info(wordsize), 73 | system_architecture => erlang:system_info(system_architecture), 74 | atomvm_version => erlang:system_info(atomvm_version), 75 | esp32_chip_info => get_esp32_chip_info(), 76 | esp_idf_version => 77 | case erlang:system_info(esp_idf_version) of 78 | undefined -> "n/a"; 79 | Version -> list_to_binary(Version) 80 | end 81 | }}; 82 | handle_api_request(get, [<<"memory">>], HttpRequest, _Args) -> 83 | Socket = maps:get(socket, HttpRequest), 84 | {ok, #{addr := Host, port := Port}} = socket:peername(Socket), 85 | io:format("GET memory request from ~p:~p~n", [Host, Port]), 86 | {ok, get_memory_data()}; 87 | handle_api_request(Method, Path, _HttpRequest, _Args) -> 88 | io:format("ERROR! Unsupported method ~p or path ~p~n", [Method, Path]), 89 | not_found. 90 | 91 | get_memory_data() -> 92 | #{ 93 | atom_count => erlang:system_info(atom_count), 94 | process_count => erlang:system_info(process_count), 95 | port_count => erlang:system_info(port_count), 96 | esp32_free_heap_size => erlang:system_info(esp32_free_heap_size), 97 | esp32_largest_free_block => erlang:system_info(esp32_largest_free_block), 98 | esp32_minimum_free_size => erlang:system_info(esp32_minimum_free_size) 99 | }. 100 | 101 | %% 102 | %% HTTP request handler implementation (for OTA) 103 | %% 104 | 105 | -define(TARGET_PARTITION, <<"app2.avm">>). 106 | -define(ATOMVM_NAMESPACE, atomvm). 107 | -define(BOOT_TARGET_PARTITION_KEY, boot_partition). 108 | 109 | -record(state, { 110 | offset = 0 111 | }). 112 | 113 | %% @hidden 114 | init_handler(_PathPrefix, _HandlerConfig) -> 115 | {ok, #state{}}. 116 | 117 | %% @hidden 118 | handle_http_req(#{method := post} = HttpRequest, State) -> 119 | #{ 120 | headers := Headers, 121 | body := Body 122 | } = HttpRequest, 123 | Offset = State#state.offset, 124 | case Offset of 125 | 0 -> 126 | io:format("Erasing partition ~p~n", [?TARGET_PARTITION]), 127 | esp:partition_erase_range(?TARGET_PARTITION, 0); 128 | _ -> 129 | ok 130 | end, 131 | BodyLen = erlang:byte_size(Body), 132 | NewOffset = Offset + BodyLen, 133 | ContentLength = get_content_length(Headers), 134 | case NewOffset < ContentLength of 135 | true -> 136 | io:format("Offset: ~p ContentLength: ~p BodyLen: ~p~n", [Offset, ContentLength, BodyLen]), 137 | ok = esp:partition_write(?TARGET_PARTITION, Offset, Body), 138 | io:format("Wrote ~p bytes at offset ~p to partition ~p.~n", [BodyLen, Offset, ?TARGET_PARTITION]), 139 | NewState = State#state{offset = NewOffset}, 140 | {noreply, NewState}; 141 | false -> 142 | io:format("Request complete.~n"), 143 | ok = esp:partition_write(?TARGET_PARTITION, Offset, Body), 144 | io:format("Wrote ~p bytes at offset ~p to partition ~p.~n", [BodyLen, Offset, ?TARGET_PARTITION]), 145 | ok = esp:nvs_set_binary(?ATOMVM_NAMESPACE, ?BOOT_TARGET_PARTITION_KEY, ?TARGET_PARTITION), 146 | io:format("Set boot partition to ~p~n", [?TARGET_PARTITION]), 147 | {close, <<"ok">>} 148 | end; 149 | handle_http_req(_HttpRequest, _State) -> 150 | {error, internal_server_error}. 151 | 152 | get_content_length(Headers) -> 153 | %% TODO handle case 154 | erlang:binary_to_integer(maps:get(<<"Content-Length">>, Headers, <<"0">>)). 155 | 156 | %% 157 | %% WebSocket handlers 158 | %% 159 | 160 | handle_ws_init(WebSocket, _Path, _Args) -> 161 | io:format("Initializing websocket pid=~p~n", [self()]), 162 | spawn(fun() -> update_loop(WebSocket, get_memory_data()) end), 163 | {ok, undefined}. 164 | 165 | handle_ws_message(<<"ping">>, State) -> 166 | {reply, <<"pong">>, State}; 167 | handle_ws_message(Message, State) -> 168 | io:format("Received message from web socket. Message: ~p~n", [Message]), 169 | {noreply, State}. 170 | 171 | update_loop(WebSocket, LastMemoryData) -> 172 | LatestMemoryData = get_memory_data(), 173 | timer:sleep(5000), 174 | % erlang:garbage_collect(), 175 | NewMemoryData = get_difference(LastMemoryData, LatestMemoryData), 176 | case NewMemoryData of 177 | [] -> 178 | ok; 179 | _ -> 180 | Binary = iolist_to_binary(json_encoder:encode(NewMemoryData)), 181 | io:format("Sending websocket message to client ~p ... ", [Binary]), 182 | httpd_ws_handler:send(WebSocket, Binary), 183 | io:format("sent.~n") 184 | end, 185 | update_loop(WebSocket, LatestMemoryData). 186 | 187 | %% 188 | %% Internal functions 189 | %% 190 | %% 191 | get_difference(Map1, Map2) -> 192 | maps:fold( 193 | fun(Key, Value, Accum) -> 194 | case maps:get(Key, Map2, undefined) of 195 | undefined -> 196 | [{Key, Value} | Accum]; 197 | Value -> 198 | Accum; 199 | NewValue -> 200 | [{Key, NewValue} | Accum] 201 | end 202 | end, 203 | [], 204 | Map1 205 | ). 206 | 207 | get_esp32_chip_info() -> 208 | case erlang:system_info(esp32_chip_info) of 209 | Info when is_map(Info) -> 210 | maps:to_list(Info); 211 | _ -> 212 | [{features, undefined}, {cores, undefined}, {revision, undefined}, {model, undefined}] 213 | end. 214 | 215 | %% @private 216 | maybe_start_network(esp32) -> 217 | Config = maps:get(sta, config:get()), 218 | case network:wait_for_sta(Config, 30000) of 219 | {ok, {Address, Netmask, Gateway}} -> 220 | io:format( 221 | "Acquired IP address: ~p Netmask: ~p Gateway: ~p~n", 222 | [Address, Netmask, Gateway] 223 | ), 224 | ok; 225 | Error -> 226 | io:format("An error occurred starting network: ~p~n", [Error]), 227 | Error 228 | end; 229 | maybe_start_network(_Platform) -> 230 | ok. 231 | -------------------------------------------------------------------------------- /examples/ledc_pwm_example/README.md: -------------------------------------------------------------------------------- 1 | # `bme280_example` 2 | 3 | Welcome to the `ledc_pwm_example` AtomVM application. 4 | 5 | This example application illustrates use of the high-level LEDC PWM interface. 6 | 7 | For this application, you will need: 8 | 9 | * An ESP32 device, flashed with the [AtomVM](https://github.com/bettio/AtomVM) image (including the VM and core libraries), and capable of connecting via UART to your development machine; 10 | * An LED to connect to your ESP32 device (if not already available on your development board); 11 | * The [`esptool.py`](https://github.com/espressif/esptool) tool (for flashing); 12 | * The [`git`](https://git-scm.com) version control tool; 13 | * [Erlang/OTP 21](https://www.erlang.org) or higher, along with [`rebar3`](https://www.rebar3.org); 14 | * A serial monitor program of your choice (e.g, [`minicom`](https://en.wikipedia.org/wiki/Minicom)) 15 | 16 | While the [IDF SDK](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) and required toolchains are not required, they may make life a little easier. 17 | 18 | ## Getting Started 19 | 20 | ### Connection 21 | 22 | Many development boards already have an LED attached to GPIO2. 23 | 24 | However, if you are using a base ESP32, build a circuit containing an LED and 1k resistor. 25 | 26 | +-------------+ +-----------+ 27 | | | | | 28 | | GND +--------+ /\ LED 29 | | GPIO 2 +--------+ ---- 30 | | | | | 31 | +-------------+ +---\/\/\/---+ 32 | ESP32 1k resistor 33 | 34 | > Note. The example program assumes the LED is attached to GPIO2. If you need to use different pins, make sure to change the example program to reflect your requirements. 35 | 36 | ### Build Instructions 37 | 38 | To build and flash this application to your ESP32 device, issue the `esp32_flash` target to the `rebar3` command, and optionally specify the device port and baud rate, if they do not match the defaults. 39 | 40 | > Note. For information about the `esp32_flash` target, see the [`atomvm_rebar3_plugin`](https://github.com/atomvm/atomvm_rebar3_plugin) instructions. 41 | 42 | shell$ rebar3 esp32_flash -p /dev/ttyUSB0 -b 115200 43 | ===> Fetching atomvm_rebar3_plugin 44 | ===> Fetching packbeam (from {git,"https://github.com/atomvm/atomvm_packbeam.git", 45 | {branch,"master"}}) 46 | ===> Analyzing applications... 47 | ===> Compiling atomvm_rebar3_plugin 48 | ===> Compiling packbeam 49 | ===> Verifying dependencies... 50 | ===> App atomvm_lib is a checkout dependency and cannot be locked. 51 | ===> Analyzing applications... 52 | ===> Compiling atomvm_lib 53 | ===> Analyzing applications... 54 | ===> Compiling bme280_example 55 | ===> AVM file written to : atomvm_lib.avm 56 | ===> AVM file written to : bme280_example.avm 57 | ===> esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 .../atomvm_lib/examples/bme280_example/_build/default/lib/bme280_example.avm 58 | 59 | esptool.py v2.1 60 | Connecting........_____. 61 | Chip is ESP32D0WDQ6 (revision (unknown 0xa)) 62 | Uploading stub... 63 | Running stub... 64 | Stub running... 65 | Configuring flash size... 66 | Auto-detected Flash size: 4MB 67 | Wrote 16384 bytes at 0x00210000 in 1.4 seconds (91.4 kbit/s)... 68 | Hash of data verified. 69 | 70 | Leaving... 71 | Hard resetting... 72 | 73 | Connect to the device using the USB port (e.g., via `minicom`), and you should see something like: 74 | 75 | Found AVM partition: size: 1048576, address: 0x210000 76 | Booting file mapped at: 0x3f430000, size: 1048576 77 | Found AVM partition: size: 1048576, address: 0x110000 78 | Starting: bme280_example.beam... 79 | --- 80 | Temperature: 18.77C, Pressure: 1012.45hPa, Humidity: 30.47%RH 81 | Temperature: 18.85C, Pressure: 1012.49hPa, Humidity: 30.25%RH 82 | Temperature: 18.93C, Pressure: 1012.42hPa, Humidity: 30.17%RH 83 | Temperature: 18.99C, Pressure: 1012.40hPa, Humidity: 30.43%RH 84 | ... 85 | -------------------------------------------------------------------------------- /examples/ledc_pwm_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | -------------------------------------------------------------------------------- /examples/ledc_pwm_example/src/ledc_pwm_example.app.src: -------------------------------------------------------------------------------- 1 | {application, ledc_pwm_example, [ 2 | {description, "An OTP library"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | {licenses, ["Apache 2.0"]}, 11 | {links, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /examples/ledc_pwm_example/src/ledc_pwm_example.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2020 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module (ledc_pwm_example). 18 | 19 | -export([start/0]). 20 | 21 | start() -> 22 | ok = ledc_pwm:start(), 23 | 24 | Freq = 5000, 25 | {ok, Timer} = ledc_pwm:create_timer(Freq), 26 | io:format("Timer: ~p~n", [Timer]), 27 | 28 | {ok, Channel1} = ledc_pwm:create_channel(Timer, 18), 29 | {ok, Channel2} = ledc_pwm:create_channel(Timer, 19), 30 | io:format("Channel1: ~p~n", [Channel1]), 31 | io:format("Channel2: ~p~n", [Channel2]), 32 | 33 | spawn(fun() -> fade_loop(Channel1) end), 34 | spawn(fun() -> step_loop(Channel2, 0, 10) end), 35 | loop_forever(). 36 | 37 | fade_loop(Channel) -> 38 | FadeupMs = 1500, 39 | ok = ledc_pwm:fade(Channel, 100, FadeupMs), 40 | timer:sleep(FadeupMs), 41 | 42 | FadeDownMs = 3000, 43 | ok = ledc_pwm:fade(Channel, 0, FadeDownMs), 44 | timer:sleep(FadeDownMs), 45 | 46 | fade_loop(Channel). 47 | 48 | step_loop(Channel, Dutyp, _Incr) when Dutyp < 0 -> 49 | step_loop(Channel, 0, 10); 50 | step_loop(Channel, Dutyp, _Incr) when Dutyp > 100 -> 51 | step_loop(Channel, 100, -10); 52 | step_loop(Channel, Dutyp, Incr) -> 53 | ok = ledc_pwm:set_dutyp(Channel, Dutyp), 54 | timer:sleep(1000), 55 | step_loop(Channel, Dutyp + Incr, Incr). 56 | 57 | loop_forever() -> 58 | timer:sleep(10000), 59 | loop_forever(). 60 | -------------------------------------------------------------------------------- /examples/lora_example/README.md: -------------------------------------------------------------------------------- 1 | # `lora_example` 2 | 3 | Welcome to the `lora_example` AtomVM application(s). 4 | 5 | This example application illustrates use of the Lora interface to send and receive messages between two ESP32 devices, each of which should be connected to a LoRa transceiver. Currently, the SemTech SX127x and SX126x devices are supported. 6 | 7 | For this application, you will need: 8 | 9 | * Two LoRa transceivers (Semtech SX127x or SX126x); 10 | * Two ESP32 devices, flashed with the [AtomVM](https://github.com/bettio/AtomVM) image (including the VM and core libraries), and capable of connecting via UART to your development machine; 11 | * The [`esptool.py`](https://github.com/espressif/esptool) tool (for flashing); 12 | * The [`git`](https://git-scm.com) version control tool; 13 | * [Erlang/OTP](https://www.erlang.org) 21, 22, or 23, along with [`rebar3`](https://www.rebar3.org); 14 | * A serial monitor program of your choice (e.g, [`minicom`](https://en.wikipedia.org/wiki/Minicom)) 15 | 16 | While the [IDF SDK](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) and required tool-chains are not required, they may make life a little easier. 17 | 18 | > Note. These instructions assume you have flashed the AtomVM virtual machine and Erlang libraries to your ESP32 device. For information about flashing ESP32 devices with the AtomVM virtual machine and libraries, consult the [AtomVM documentation](https://doc.atomvm.net). 19 | 20 | ## Getting Started 21 | 22 | Connect each of your LoRa transceivers to your respective ESP32 devices. Use the 4-wire SPI interface to connect your LoRa modem and ESP32 as follows: 23 | 24 | | LoRa Pin | ESP32 Pin | 25 | |----------|-----------| 26 | | `GND` | `GND` | 27 | | `MISO` | `GPIO 19` | 28 | | `MOSI` | `GPIO 27` | 29 | | `SCLK` | `GPIO 5` | 30 | | `CS` (`NSS` on some boards) | `GPIO 18` | 31 | | `IRQ` (`DPIO0` on SX127x, `DPIO1` on SX126x) | `GPIO 26` | 32 | | `RESET` | `GPIO 14` | 33 | | `BUSY` (SX126x only) | `GPIO 22` | 34 | | `VCC` | `+3.3v` | 35 | 36 | > Note. If you are using an integrated development board with an included LoRa module, consult the data sheet for your development board to identify the correct pin mappings. 37 | 38 | The following diagram illustrates the connected devices: 39 | 40 | \\\ // 41 | o ||| air || o 42 | | /// \\ | 43 | +-------------+ +-----------+ | | +-----------+ +-----------+ 44 | | GND +--------+ GND +-----+ +----+ GND +-------+ GND | 45 | | GPIO 19 +--------+ MISO | antenna antenna | MISO +-------+ GPIO 19 | 46 | | GPIO 27 +--------+ MOSI | | MOSI +-------+ GPIO 27 | 47 | | GPIO 5 +--------+ SCLK | | SCLK +-------+ GPIO 5 | 48 | | GPIO 18 +--------+ NCC | | NCC +-------+ GPIO 18 | 49 | | GPIO 26 +--------+ DPIO0 | | DPIO0 +-------+ GPIO 26 | 50 | | GPIO 14 +--------+ RESET | | RESET +-------+ GPIO 14 | 51 | | GPIO 22 +--------+ BUSY | | BUSY +-------+ GPIO 22 | 52 | | +3.3v +--------+ VCC | | VCC +-------+ +3.3v | 53 | | | | | | | | | 54 | +-------------+ +-----------+ +-----------+ +-----------+ 55 | ESP32 LoRa LoRa ESP32 56 | transceiver transceiver 57 | 58 | LoRa Sender LoRa Receiver 59 | 60 | In the example program, the LoRa sender will broadcast a simple string `AtomVM I`, where `I` is a sequence number starting at 0. The LoRa receiver will print the received message to the console. 61 | 62 | ### Build Instructions 63 | 64 | We will build and flash the application for the sender and receiver in several steps. 65 | 66 | First, let's start by compiling the source code: 67 | 68 | shell$ rebar3 compile 69 | ... 70 | ===> Verifying dependencies... 71 | ===> Analyzing applications... 72 | ===> Compiling atomvm_lib 73 | ===> Analyzing applications... 74 | ===> Compiling lora_example 75 | 76 | #### Build and Flash LoRa Receiver 77 | 78 | Let's build and flash the LoRa receiver application on to one of your ESP32 devices. 79 | 80 | We will first create a pruned packbeam file, with the `lora_receiver` as the entry-point into the application: 81 | 82 | shell$ rebar3 packbeam --prune --start lora_receiver 83 | ... 84 | ===> Verifying dependencies... 85 | ===> Analyzing applications... 86 | ===> Compiling atomvm_lib 87 | ===> Analyzing applications... 88 | ===> Compiling lora_example 89 | ===> AVM file written to : lora_example.avm 90 | 91 | Next, we will flash the example program to your ESP32 device (replace `tty.usbserial.deviceA` with the device used for your LoRa receiver): 92 | 93 | shell$ rebar3 esp32_flash -p /dev/tty.usbserial.deviceA 94 | ... 95 | ===> esptool.py --chip esp32 --port /dev/tty.usbserial.deviceA --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 .../atomvm_lib/examples/lora_example/_build/default/lib/lora_example.avm 96 | 97 | esptool.py v2.1 98 | Connecting........_____. 99 | Chip is ESP32D0WDQ6 (revision (unknown 0xa)) 100 | Uploading stub... 101 | Running stub... 102 | Stub running... 103 | Configuring flash size... 104 | Auto-detected Flash size: 4MB 105 | Wrote 16384 bytes at 0x00210000 in 1.4 seconds (91.4 kbit/s)... 106 | Hash of data verified. 107 | 108 | Leaving... 109 | Hard resetting... 110 | 111 | Connect to the device using the USB port (e.g., via `minicom`), and you should see something like: 112 | 113 | Found AVM partition: size: 1048576, address: 0x210000 114 | Booting file mapped at: 0x3f430000, size: 1048576 115 | Found AVM partition: size: 262144, address: 0x1d0000 116 | Starting: lora_receiver.beam... 117 | --- 118 | Lora started. Waiting to receive messages... 119 | 120 | #### Build and Flash LoRa Sender 121 | 122 | Let's build and flash the LoRa sender application on to the other one of your ESP32 devices. 123 | 124 | We will first create a pruned packbeam file, with the `lora_sender` as the entry-point into the application (don't forget to clean the build first): 125 | 126 | shell$ rebar3 clean 127 | shell$ rebar3 packbeam --force --prune --start lora_sender 128 | ... 129 | ===> Verifying dependencies... 130 | ===> Analyzing applications... 131 | ===> Compiling atomvm_lib 132 | ===> Analyzing applications... 133 | ===> Compiling lora_example 134 | ===> AVM file written to : lora_example.avm 135 | 136 | Next, we will flash the example program to your ESP32 device (replace `tty.usbserial.deviceB` with the device used for your LoRa sender): 137 | 138 | shell$ rebar3 esp32_flash -p /dev/tty.usbserial.deviceB 139 | ... 140 | ===> esptool.py --chip esp32 --port /dev/tty.usbserial.deviceB --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 .../atomvm_lib/examples/lora_example/_build/default/lib/lora_example.avm 141 | 142 | esptool.py v2.1 143 | Connecting........_____. 144 | Chip is ESP32D0WDQ6 (revision (unknown 0xa)) 145 | Uploading stub... 146 | Running stub... 147 | Stub running... 148 | Configuring flash size... 149 | Auto-detected Flash size: 4MB 150 | Wrote 16384 bytes at 0x00210000 in 1.4 seconds (91.4 kbit/s)... 151 | Hash of data verified. 152 | 153 | Leaving... 154 | Hard resetting... 155 | 156 | Connect to the device using the USB port (e.g., via `minicom`), and you should see something like: 157 | 158 | Found AVM partition: size: 1048576, address: 0x210000 159 | Booting file mapped at: 0x3f430000, size: 1048576 160 | Found AVM partition: size: 262144, address: 0x1d0000 161 | Starting: lora_sender.beam... 162 | --- 163 | Lora started. Sending messages... 164 | Sent [<<"AtomVM ">>,"0"] 165 | Sent [<<"AtomVM ">>,"1"] 166 | Sent [<<"AtomVM ">>,"2"] 167 | Sent [<<"AtomVM ">>,"3"] 168 | Sent [<<"AtomVM ">>,"4"] 169 | Sent [<<"AtomVM ">>,"5"] 170 | ... 171 | 172 | On the console connected to your receiver, you should see: 173 | 174 | Received Packet: "AtomVM 0"; QoS: #{rssi => -75,snr => 13} 175 | Received Packet: "AtomVM 1"; QoS: #{rssi => -76,snr => 13} 176 | Received Packet: "AtomVM 2"; QoS: #{rssi => -78,snr => 13} 177 | Received Packet: "AtomVM 3"; QoS: #{rssi => -77,snr => 13} 178 | Received Packet: "AtomVM 4"; QoS: #{rssi => -77,snr => 13} 179 | Received Packet: "AtomVM 5"; QoS: #{rssi => -81,snr => 13} 180 | 181 | If all goes according to plan, congratulations -- your two ESP32 devices are communicating using LoRa modulation! 182 | -------------------------------------------------------------------------------- /examples/lora_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | -------------------------------------------------------------------------------- /examples/lora_example/src/config.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2022 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(config). 18 | 19 | -export([lora_config/1]). 20 | 21 | -define(DEVICE_NAME, my_device). 22 | 23 | -spec lora_config(Device :: sx127x | sx126x) -> map(). 24 | lora_config(Device) -> #{ 25 | spi => spi:open(spi_config(Device)), 26 | device_name => ?DEVICE_NAME, 27 | device => Device, 28 | irq => 26, 29 | busy => 22, 30 | reset => 14 31 | }. 32 | 33 | %% @private 34 | spi_config(Device) -> #{ 35 | bus_config => #{ 36 | miso_io_num => 19, 37 | mosi_io_num => 27, 38 | sclk_io_num => 5 39 | }, 40 | device_config => #{ 41 | ?DEVICE_NAME => #{ 42 | address_len_bits => case Device of sx127x -> 8; _ -> 0 end, 43 | spi_clock_hz => 1000000, 44 | mode => 0, 45 | spi_cs_io_num => 18 46 | } 47 | } 48 | }. 49 | -------------------------------------------------------------------------------- /examples/lora_example/src/lora_example.app.src: -------------------------------------------------------------------------------- 1 | {application, lora_example, [ 2 | {description, "An OTP library"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | {licenses, ["Apache 2.0"]}, 11 | {links, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /examples/lora_example/src/lora_receiver.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2021 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(lora_receiver). 18 | 19 | -export([start/0]). 20 | 21 | start() -> 22 | LoraConfig = config:lora_config(sx127x), 23 | {ok, _Lora} = lora:start(LoraConfig#{receive_handler => fun handle_receive/3}), 24 | io:format("Lora started. Waiting to receive messages...~n"), 25 | timer:sleep(infinity). 26 | 27 | handle_receive(_Lora, Packet, QoS) -> 28 | io:format("Received Packet: ~p; QoS: ~p~n", [Packet, QoS]). 29 | -------------------------------------------------------------------------------- /examples/lora_example/src/lora_sender.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2021 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(lora_sender). 18 | 19 | -export([start/0]). 20 | 21 | start() -> 22 | LoraConfig = config:lora_config(sx127x), 23 | {ok, Lora} = lora:start(LoraConfig), 24 | io:format("Lora started. Sending messages...~n"), 25 | loop(Lora, 0). 26 | 27 | loop(Lora, I) -> 28 | Payload = [<<"AtomVM ">>, integer_to_list(I)], 29 | try 30 | case lora:broadcast(Lora, Payload) of 31 | ok -> 32 | io:format("Sent ~p~n", [Payload]); 33 | Error -> 34 | io:format("Error sending: ~p~n", [Error]) 35 | end 36 | catch 37 | exit:timeout -> 38 | io:format("Timed out broadcasting ~p~n", [Payload]) 39 | end, 40 | timer:sleep(1000), 41 | loop(Lora, I + 1). 42 | -------------------------------------------------------------------------------- /examples/lora_node_example/README.md: -------------------------------------------------------------------------------- 1 | # `lora_example` 2 | 3 | Welcome to the `lora_example` AtomVM application(s). 4 | 5 | This example application illustrates use of the Lora interface to send and receive messages between two ESP32 devices, each of which should be connected to a LoRa transceiver. 6 | 7 | For this application, you will need: 8 | 9 | * Two LoRa transceivers; 10 | * Two ESP32 devices, flashed with the [AtomVM](https://github.com/bettio/AtomVM) image (including the VM and core libraries), and capable of connecting via UART to your development machine; 11 | * The [`esptool.py`](https://github.com/espressif/esptool) tool (for flashing); 12 | * The [`git`](https://git-scm.com) version control tool; 13 | * [Erlang/OTP 21](https://www.erlang.org) or higher, along with [`rebar3`](https://www.rebar3.org); 14 | * A serial monitor program of your choice (e.g, [`minicom`](https://en.wikipedia.org/wiki/Minicom)) 15 | 16 | While the [IDF SDK](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) and required tool-chains are not required, they may make life a little easier. 17 | 18 | > Note. These instructions assume you have flashed the AtomVM virtual machine and Erlang libraries to your ESP32 device. For information about flashing ESP32 devices with the AtomVM virtual machine and libraries, consult the AtomVM documentation. 19 | 20 | ## Getting Started 21 | 22 | Connect each of your LoRa transceivers to your respective ESP32 devices. Use the 4-wire SPI interface to connect your LoRa modem and ESP32 as follows: 23 | 24 | | LoRa Pin | ESP32 Pin | 25 | |----------|-----------| 26 | | `GND` | `GND` | 27 | | `SCLK` | `GPIO 14` | 28 | | `MISO` | `GPIO 12` | 29 | | `MOSI` | `GPIO 13` | 30 | | `NCC` | `GPIO 18` | 31 | | `DPIO0` | `GPIO 26` | 32 | | `VCC` | `+3.3v` | 33 | 34 | The following diagram illustrates the connected devices: 35 | 36 | \\\ // 37 | o ||| air || o 38 | | /// \\ | 39 | +-------------+ +-----------+ | | +-----------+ +-----------+ 40 | | GND +--------+ GND +-----+ +----+ GND +-------+ GND | 41 | | GPIO 14 +--------+ SCLK | antenna antenna | SCLK +-------+ GPIO 14 | 42 | | GPIO 12 +--------+ MISO | | MISO +-------+ GPIO 12 | 43 | | GPIO 13 +--------+ MOSI | | MOSI +-------+ GPIO 13 | 44 | | GPIO 18 +--------+ NCC | | NCC +-------+ GPIO 18 | 45 | | GPIO 26 +--------+ DPIO0 | | DPIO0 +-------+ GPIO 26 | 46 | | +3.3v +--------+ VCC | | VCC +-------+ +3.3v | 47 | | | | | | | | | 48 | +-------------+ +-----------+ +-----------+ +-----------+ 49 | ESP32 LoRa LoRa ESP32 50 | transceiver transceiver 51 | 52 | LoRa Sender LoRa Receiver 53 | 54 | In the example program, the LoRa sender will broadcast a simple string `AtomVM I`, where `I` is a sequence number starting at 0. The LoRa receiver will print the received message to the console. 55 | 56 | ### Build Instructions 57 | 58 | We will build and flash the application for the sender and receiver in several steps. 59 | 60 | First, let's start by compiling the source code: 61 | 62 | shell$ rebar3 compile 63 | ... 64 | ===> Verifying dependencies... 65 | ===> Analyzing applications... 66 | ===> Compiling atomvm_lib 67 | ===> Analyzing applications... 68 | ===> Compiling lora_example 69 | 70 | #### Build and Flash LoRa Receiver 71 | 72 | Let's build and flash the LoRa receiver application on to one of your ESP32 devices. 73 | 74 | We will first create a pruned packbeam file, with the `lora_receiver` as the entry-point into the application: 75 | 76 | shell$ rebar3 packbeam --prune --start lora_receiver 77 | ... 78 | ===> Verifying dependencies... 79 | ===> Analyzing applications... 80 | ===> Compiling atomvm_lib 81 | ===> Analyzing applications... 82 | ===> Compiling lora_example 83 | ===> AVM file written to : lora_example.avm 84 | 85 | Next, we will flash the example program to your ESP32 device (replace `tty.usbserial.deviceA` with the device used for your LoRa receiver): 86 | 87 | shell$ rebar3 esp32_flash -p /dev/tty.usbserial.deviceA 88 | ... 89 | ===> esptool.py --chip esp32 --port /dev/tty.usbserial.deviceA --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 .../atomvm_lib/examples/lora_example/_build/default/lib/lora_example.avm 90 | 91 | esptool.py v2.1 92 | Connecting........_____. 93 | Chip is ESP32D0WDQ6 (revision (unknown 0xa)) 94 | Uploading stub... 95 | Running stub... 96 | Stub running... 97 | Configuring flash size... 98 | Auto-detected Flash size: 4MB 99 | Wrote 16384 bytes at 0x00210000 in 1.4 seconds (91.4 kbit/s)... 100 | Hash of data verified. 101 | 102 | Leaving... 103 | Hard resetting... 104 | 105 | Connect to the device using the USB port (e.g., via `minicom`), and you should see something like: 106 | 107 | Found AVM partition: size: 1048576, address: 0x210000 108 | Booting file mapped at: 0x3f430000, size: 1048576 109 | Found AVM partition: size: 262144, address: 0x1d0000 110 | Starting: lora_receiver.beam... 111 | --- 112 | Lora started. Waiting to receive messages... 113 | 114 | #### Build and Flash LoRa Sender 115 | 116 | Let's build and flash the LoRa sender application on to the other one of your ESP32 devices. 117 | 118 | We will first create a pruned packbeam file, with the `lora_sender` as the entry-point into the application (don't forget to clean the build first): 119 | 120 | shell$ rebar3 clean 121 | shell$ rebar3 packbeam --prune --start lora_sender 122 | ... 123 | ===> Verifying dependencies... 124 | ===> Analyzing applications... 125 | ===> Compiling atomvm_lib 126 | ===> Analyzing applications... 127 | ===> Compiling lora_example 128 | ===> AVM file written to : lora_example.avm 129 | 130 | Next, we will flash the example program to your ESP32 device (replace `tty.usbserial.deviceB` with the device used for your LoRa sender): 131 | 132 | shell$ rebar3 esp32_flash -p /dev/tty.usbserial.deviceB 133 | ... 134 | ===> esptool.py --chip esp32 --port /dev/tty.usbserial.deviceB --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 .../atomvm_lib/examples/lora_example/_build/default/lib/lora_example.avm 135 | 136 | esptool.py v2.1 137 | Connecting........_____. 138 | Chip is ESP32D0WDQ6 (revision (unknown 0xa)) 139 | Uploading stub... 140 | Running stub... 141 | Stub running... 142 | Configuring flash size... 143 | Auto-detected Flash size: 4MB 144 | Wrote 16384 bytes at 0x00210000 in 1.4 seconds (91.4 kbit/s)... 145 | Hash of data verified. 146 | 147 | Leaving... 148 | Hard resetting... 149 | 150 | Connect to the device using the USB port (e.g., via `minicom`), and you should see something like: 151 | 152 | Found AVM partition: size: 1048576, address: 0x210000 153 | Booting file mapped at: 0x3f430000, size: 1048576 154 | Found AVM partition: size: 262144, address: 0x1d0000 155 | Starting: lora_sender.beam... 156 | --- 157 | Lora started. Sending messages... 158 | Sent [<<"AtomVM ">>,"0"] 159 | Sent [<<"AtomVM ">>,"1"] 160 | Sent [<<"AtomVM ">>,"2"] 161 | Sent [<<"AtomVM ">>,"3"] 162 | Sent [<<"AtomVM ">>,"4"] 163 | Sent [<<"AtomVM ">>,"5"] 164 | ... 165 | 166 | On the console connected to your receiver, you should see: 167 | 168 | Received Packet: "AtomVM 0"; QoS: #{rssi => -75,snr => 13} 169 | Received Packet: "AtomVM 1"; QoS: #{rssi => -76,snr => 13} 170 | Received Packet: "AtomVM 2"; QoS: #{rssi => -78,snr => 13} 171 | Received Packet: "AtomVM 3"; QoS: #{rssi => -77,snr => 13} 172 | Received Packet: "AtomVM 4"; QoS: #{rssi => -77,snr => 13} 173 | Received Packet: "AtomVM 5"; QoS: #{rssi => -81,snr => 13} 174 | 175 | If all goes according to plan, congratulations -- your two ESP32 devices are communicating using LoRa modulation! 176 | -------------------------------------------------------------------------------- /examples/lora_node_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | -------------------------------------------------------------------------------- /examples/lora_node_example/src/config.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2022 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(config). 18 | 19 | -export([lora_config/1]). 20 | 21 | -define(DEVICE_NAME, my_device). 22 | 23 | -spec lora_config(Device :: sx127x | sx126x) -> map(). 24 | lora_config(Device) -> #{ 25 | spi => spi:open(spi_config(Device)), 26 | device_name => ?DEVICE_NAME, 27 | device => Device, 28 | irq => 26, 29 | busy => 22, 30 | reset => 21 31 | }. 32 | 33 | %% @private 34 | spi_config(Device) -> #{ 35 | bus_config => #{ 36 | miso_io_num => 15, 37 | mosi_io_num => 13, 38 | sclk_io_num => 14 39 | }, 40 | device_config => #{ 41 | ?DEVICE_NAME => #{ 42 | address_len_bits => case Device of sx127x -> 8; _ -> 0 end, 43 | spi_clock_hz => 1000000, 44 | mode => 0, 45 | spi_cs_io_num => 18 46 | } 47 | } 48 | }. 49 | -------------------------------------------------------------------------------- /examples/lora_node_example/src/lora_client.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2021 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(lora_client). 18 | 19 | -export([start/0]). 20 | 21 | start() -> 22 | LoraNodeConfig = #{ 23 | lora => config:lora_config(sx126x) 24 | }, 25 | {ok, LoraNode} = lora_node:start(joe, LoraNodeConfig), 26 | io:format("Lora started. Sending hello to robert...~n"), 27 | loop(LoraNode, 0). 28 | 29 | loop(LoraNode, I) -> 30 | try 31 | io:format("Calling robert with message {hello, ~p} ... ", [I]), 32 | case lora_node:call(LoraNode, robert, {hello, I}) of 33 | {hello, Who} -> 34 | io:format("Received hello back from ~p~n", [Who]); 35 | Error -> 36 | io:format("Error sendingto robert: ~p~n", [Error]) 37 | end 38 | after 39 | ok = timer:sleep(1000) 40 | end, 41 | loop(LoraNode, I + 1). 42 | -------------------------------------------------------------------------------- /examples/lora_node_example/src/lora_node_example.app.src: -------------------------------------------------------------------------------- 1 | {application, lora_node_example, [ 2 | {description, "An OTP library"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | {licenses, ["Apache 2.0"]}, 11 | {links, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /examples/lora_node_example/src/lora_server.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2021 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(lora_server). 18 | 19 | -export([start/0]). 20 | 21 | start() -> 22 | LoraNodeConfig = #{ 23 | lora => config:lora_config(sx127x), 24 | call_handler => fun handle_call/2 25 | }, 26 | {ok, _LoraNode} = lora_node:start(robert, LoraNodeConfig), 27 | 28 | io:format("Lora server started. Waiting to receive requests...~n"), 29 | loop_forever(). 30 | 31 | handle_call({hello, I} = _Message, Context) -> 32 | From = maps:get(from, Context), 33 | io:format("Received {hello, ~p} from ~p...~n", [I, From]), 34 | {hello, From}; 35 | handle_call(_Message, _Context) -> 36 | buzz_off. 37 | 38 | loop_forever() -> 39 | receive 40 | halt -> ok 41 | after 100000 -> 42 | loop_forever() 43 | end. 44 | -------------------------------------------------------------------------------- /examples/sht3x_example/README.md: -------------------------------------------------------------------------------- 1 | # `sht3x_example` 2 | 3 | Welcome to the `sht3x_example` AtomVM application. 4 | 5 | This example application will drive a SHT3x temperature and humidity sensor attached to an ESP32 device using the 2-wire I2C interface and print readings to the console. 6 | 7 | For this application, you will need: 8 | 9 | * An ESP32 device, flashed with the [AtomVM](https://github.com/bettio/AtomVM) image (including the VM and core libraries), and capable of connecting via UART to your development machine; 10 | * An SHT3x device, typically marketed as an integrated development board; 11 | * The [`esptool.py`](https://github.com/espressif/esptool) tool (for flashing); 12 | * The [`git`](https://git-scm.com) version control tool; 13 | * [Erlang/OTP 21](https://www.erlang.org) or higher, along with [`rebar3`](https://www.rebar3.org); 14 | * A serial monitor program of your choice (e.g, [`minicom`](https://en.wikipedia.org/wiki/Minicom)) 15 | 16 | While the [IDF SDK](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) and required tool chains are not required, they may make life a little easier. 17 | 18 | ## Getting Started 19 | 20 | ### Connection 21 | 22 | Use of the SHT3x will typically require connecting some kind of development board, which includes the SHT3x integrated circuit integrated circuit, along with additional passive components, to you ESP32 device. 23 | 24 | Connect the VCC and GND pins to a power source and ground (e.g., on your ESP32). 25 | 26 | Connect the SDA and SCL pins on two selected GPIO pins on your ESP32 device (e.g., pin 21 and 22). 27 | 28 | +-----------+ +-------------+ 29 | | VCC +-------------------------+ +3.3v | 30 | | GND +-------------------------+ GND | 31 | | SDA +-------------------------+ GPIO 21 | 32 | | SCL +-------------------------+ GPIO 22 | 33 | +-----------+ +-------------+ 34 | SHT3X ESP32 35 | 36 | > Note. The example program assumes the SDA pin is connected to the ESP32 GPIO pin 21 and the SCL pin is connected to the ESP32 GPIO pin 22. If you need to use different pins, make sure to change the example program to reflect your requirements. 37 | 38 | ### Build Instructions 39 | 40 | To build and flash this application to your ESP32 device, issue the `esp32_flash` target to the `rebar3` command, and optionally specify the device port and baud rate, if they do not match the defaults. 41 | 42 | > Note. For information about the `esp32_flash` target, see the [`atomvm_rebar3_plugin`](https://github.com/atomvm/atomvm_rebar3_plugin) instructions. 43 | 44 | shell$ rebar3 esp32_flash -p /dev/ttyUSB0 -b 115200 45 | ===> Fetching atomvm_rebar3_plugin 46 | ===> Fetching packbeam (from {git,"https://github.com/atomvm/atomvm_packbeam.git", 47 | {branch,"master"}}) 48 | ===> Analyzing applications... 49 | ===> Compiling atomvm_rebar3_plugin 50 | ===> Compiling packbeam 51 | ===> Verifying dependencies... 52 | ===> App atomvm_lib is a checkout dependency and cannot be locked. 53 | ===> Analyzing applications... 54 | ===> Compiling atomvm_lib 55 | ===> Analyzing applications... 56 | ===> Compiling sht3x_example 57 | ===> AVM file written to : atomvm_lib.avm 58 | ===> AVM file written to : sht3x_example.avm 59 | ===> esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 .../atomvm_lib/examples/sht3x_example/_build/default/lib/sht3x_example.avm 60 | 61 | esptool.py v2.1 62 | Connecting........_____. 63 | Chip is ESP32D0WDQ6 (revision (unknown 0xa)) 64 | Uploading stub... 65 | Running stub... 66 | Stub running... 67 | Configuring flash size... 68 | Auto-detected Flash size: 4MB 69 | Wrote 16384 bytes at 0x00210000 in 1.4 seconds (91.4 kbit/s)... 70 | Hash of data verified. 71 | 72 | Leaving... 73 | Hard resetting... 74 | 75 | Connect to the device using the USB port (e.g., via `minicom`), and you should see something like: 76 | 77 | Found AVM partition: size: 1048576, address: 0x210000 78 | Booting file mapped at: 0x3f430000, size: 1048576 79 | Found AVM partition: size: 1048576, address: 0x110000 80 | Starting: sht3x_example.beam... 81 | --- 82 | Temperature: {19,{79,1000}}, Humidity: {31,{91,100}} 83 | Temperature: {19,{76,1000}}, Humidity: {31,{90,100}} 84 | Temperature: {19,{79,1000}}, Humidity: {31,{91,100}} 85 | Temperature: {19,{81,1000}}, Humidity: {31,{92,100}} 86 | Temperature: {19,{89,1000}}, Humidity: {31,{90,100}} 87 | ... 88 | -------------------------------------------------------------------------------- /examples/sht3x_example/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | {atomvm_lib, {git, "https://github.com/atomvm/atomvm_lib.git", {branch, "master"}}} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | {atomvm_rebar3_plugin, [ 9 | {packbeam, [prune]} 10 | ]}. 11 | -------------------------------------------------------------------------------- /examples/sht3x_example/src/sht3x_example.app.src: -------------------------------------------------------------------------------- 1 | {application, sht3x_example, [ 2 | {description, "An OTP library"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, stdlib 7 | ]}, 8 | {env,[]}, 9 | {modules, []}, 10 | {licenses, ["Apache 2.0"]}, 11 | {links, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /examples/sht3x_example/src/sht3x_example.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2020 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(sht3x_example). 18 | 19 | -export([start/0]). 20 | 21 | start() -> 22 | {ok, I2CBus} = i2c_bus:start(#{ 23 | sda => 21, 24 | scl => 22 25 | }), 26 | {ok, SHT} = sht3x:start_link(I2CBus), 27 | loop(SHT). 28 | 29 | loop(SHT) -> 30 | case sht3x:take_reading(SHT) of 31 | {ok, {Temperature, Humidity}} -> 32 | io:format("Temperature: ~p, Humidity: ~p~n", [Temperature, Humidity]); 33 | Error -> 34 | io:format("Error taking reading: ~p~n", [Error]) 35 | end, 36 | timer:sleep(5000), 37 | loop(SHT). 38 | -------------------------------------------------------------------------------- /include/httpd.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | 18 | -define(INTERNAL_SERVER_ERROR, 500). 19 | -define(BAD_REQUEST, 400). 20 | -define(NOT_FOUND, 404). 21 | -define(OK, 200). 22 | -define(CONTINUE, 100). 23 | -define(SWITCHING_PROTOCOLS, 101). 24 | -------------------------------------------------------------------------------- /include/ledc.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | 18 | -define(LEDC_HIGH_SPEED_MODE, 0). 19 | -define(LEDC_LOW_SPEED_MODE, 1). 20 | -define(LEDC_SPEED_MODE_MAX, 2). 21 | 22 | -define(LEDC_TIMER_0, 0). 23 | -define(LEDC_TIMER_1, 1). 24 | -define(LEDC_TIMER_2, 2). 25 | -define(LEDC_TIMER_3, 3). 26 | -define(LEDC_TIMER_MAX, 4). 27 | 28 | -define(LEDC_CHANNEL_0, 0). 29 | -define(LEDC_CHANNEL_1, 1). 30 | -define(LEDC_CHANNEL_2, 2). 31 | -define(LEDC_CHANNEL_3, 3). 32 | -define(LEDC_CHANNEL_4, 4). 33 | -define(LEDC_CHANNEL_5, 5). 34 | -define(LEDC_CHANNEL_6, 6). 35 | -define(LEDC_CHANNEL_7, 7). 36 | -define(LEDC_CHANNEL_MAX, 8). 37 | 38 | -define(LEDC_TIMER_1_BIT, 1). 39 | -define(LEDC_TIMER_2_BIT, 2). 40 | -define(LEDC_TIMER_3_BIT, 3). 41 | -define(LEDC_TIMER_4_BIT, 4). 42 | -define(LEDC_TIMER_5_BIT, 5). 43 | -define(LEDC_TIMER_6_BIT, 6). 44 | -define(LEDC_TIMER_7_BIT, 7). 45 | -define(LEDC_TIMER_8_BIT, 8). 46 | -define(LEDC_TIMER_9_BIT, 9). 47 | -define(LEDC_TIMER_10_BIT, 10). 48 | -define(LEDC_TIMER_11_BIT, 11). 49 | -define(LEDC_TIMER_12_BIT, 12). 50 | -define(LEDC_TIMER_13_BIT, 13). 51 | -define(LEDC_TIMER_14_BIT, 14). 52 | -define(LEDC_TIMER_15_BIT, 15). 53 | -define(LEDC_TIMER_16_BIT, 16). 54 | -define(LEDC_TIMER_17_BIT, 17). 55 | -define(LEDC_TIMER_18_BIT, 18). 56 | -define(LEDC_TIMER_19_BIT, 19). 57 | -define(LEDC_TIMER_20_BIT, 20). 58 | -define(LEDC_TIMER_BIT_MAX, 21). 59 | 60 | -define(LEDC_FADE_NO_WAIT, 0). 61 | -define(LEDC_FADE_WAIT_DONE, 1). 62 | -define(LEDC_FADE_MAX, 2). 63 | -------------------------------------------------------------------------------- /include/logger.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | 18 | -define(LOG_INFO(Format, Args), avm_logger:log({?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, ?LINE}, info, {Format, Args})). 19 | -define(LOG_WARNING(Format, Args), avm_logger:log({?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, ?LINE}, warning, {Format, Args})). 20 | -define(LOG_ERROR(Format, Args), avm_logger:log({?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, ?LINE}, error, {Format, Args})). 21 | -define(LOG_DEBUG(Format, Args), avm_logger:log({?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, ?LINE}, debug, {Format, Args})). 22 | -------------------------------------------------------------------------------- /include/trace.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | 18 | -ifdef(TRACE_ENABLED). 19 | -define(TRACE(Format, Args), io:format("~p ~p [~p:~p/~p:~p] " ++ Format ++ "~n", [erlang:system_time(millisecond), self(), ?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, ?LINE | Args])). 20 | -else. 21 | -define(TRACE(Format, Args), ok). 22 | -endif. 23 | -------------------------------------------------------------------------------- /markdown/bh1750.md: -------------------------------------------------------------------------------- 1 | 2 | # BH1750 Driver 3 | 4 | The Rohm Semiconductor [BH1750](https://www.mouser.com/datasheet/2/348/bh1750fvi-e-186247.pdf) is a compact illumination sensor, capable of reading [lux](https://en.wikipedia.org/wiki/Lux) values with 16 bit resolution. The BH1750 can be integrated directly into your circuit designs, but is more typlically supplied via a development board for integration with your projects. 5 | 6 | The BH1750 supports the 2-pin I2C interface, and the BH1750 AtomVM driver makes use of this interface to communicate with the sensor to yeild lux measurements. Users may select any supported I2C GPIO pins on your device to integrate with the sensor. 7 | 8 | The BH1750 Driver supports two modes of operation: 9 | 10 | * One-time measurements -- call the driver to take a measurement, and process the result. The sensor will go into sleep mode after taking a reading. Ideal for power-sensitive applications. 11 | * Continuous measurements -- Initialize the driver to supply a continuous stream of measurements at a specified interval. This mode of operation is only recommended for applications that have a reliable power source. 12 | 13 | The BH1750 Driver also supports 3 levels of resolution: 14 | 15 | * (High) 1 lux resolution in the range `{1..65535}` 16 | * (High2) .5 lux resolution in the range `TBD` 17 | * (Low) 4 lux resolution in the range `TBD` 18 | 19 | Finally, the sensitivity of the BH1750 can be adjusted, which can be especially useful for low-light scenarios. 20 | 21 | Currently, the BH1750 driver is only tested on the ESP32 platform. 22 | 23 | # Programming Manual 24 | 25 | The BH1750 Driver is designed to provide a simple, easy to use interface for taking luminosity measurements for your AtomVM application. This section describes the BH1750 driver API, as encapsulated in the `bh1750` Erlang module. 26 | 27 | ## Lifecycle 28 | 29 | An instance of the BH1750 driver is created using an instance of the I2CBus service. Start by initializing an I2Cbus using the `i2c_bus` module, specifying the `sda` and `scl` pins in a `map` structure: 30 | 31 | {ok, I2CBus} = i2c_bus:start(#{ 32 | sda => 21, 33 | scl => 22 34 | }), 35 | 36 | You can then use the `bh1750:start/1` and `bh1750:start/2` functions to start an instance of the BH1750 driver: 37 | 38 | {ok BH} = bh1750:start(I2CBus), 39 | ... 40 | 41 | The return value includes a reference to the driver, which is used in subsequent operations. 42 | 43 | The arity-2 version of the `start` function allows additional parameters to be specified, in a manner described in more detail below. 44 | 45 | > Note. The I2CBus instance may be used to initialize other I2C devices that are multiplexed on the same I2C bus. 46 | 47 | To delete an instance of the driver, use the `bh1750:stop/1` function. 48 | 49 | > Note. This function is not well tested and its use may result in a memory leak. 50 | 51 | ## One-Time vs Continuous Mode 52 | 53 | The BH1750 driver can operate in one-time or continuos mode. In one-time mode, the application must poll the driver for a reading, whereas in continuous mode, the driver will periodically send the owning process a reading as a message. 54 | 55 | The advantage to one-time mode is that the BH1750 sensor will enter sleep mode once a reading is taken, so it is ideal for low-power applications. In continuous mode, the device will continue to draw power. 56 | 57 | The default operational mode (without explicit configuration) is one-time. 58 | 59 | To configure continuous mode, add the tuple `{mode, continuous}` to the `Options` properties list to the `bh1750:start/2` function. By default, in continuous mode, the driver will send a reading to the process that started the driver every 5 seconds. 60 | 61 | To specify a different process to which to send messages, add the tuple `{owner, Pid}` to the `Options` parameter to the `bh1750:start/2` function, where `Pid` is the erlang process id of the owning process. 62 | 63 | To specify a different update interval for continuous mode, add the tuple `{update_interval_ms, UpdateIntervalMs}` to the `Options` parameter to the `bh1750:start/2` function, where `UpdateIntervalMs` is the desired update interval (in milliseconds). 64 | 65 | > Note. In continuous mode, the update interval will include an additional 16-120 millisecond delay, depending on the specified resolution and sensitivity, described in more detail below. 66 | 67 | Specifying a value outside of the above range will result in a `bardarg` exception. 68 | 69 | ## Readings 70 | 71 | Readings are obtained in one-time mode via a call to the `bh1750:take_reading/0` function. In continuous mode, readings are sent periodically to the process that "owns" the driver instance, in a manner described above. 72 | 73 | Readings are represented as a tuple containing an integral and fractional part, where the fractional part is a tuple containing a numerator and denominator. The denominator is always a power of 10, e.g., 74 | 75 | {153, {33, 100}} 76 | 77 | ## Resolution 78 | 79 | Resolution of BH1750 readings can be controlled by adding the tuple `{resolution, R}` to the `Options` parameter to the `bh1750:start/3` function, where `R` can take the following atom values: 80 | 81 | * `low` -- 4 lux resolution 82 | * `high` (default) -- 1 lux resolution 83 | * `high2` -- 0.5 lux resolution 84 | 85 | Specifying a value outside of the above range will result in a `bardarg` exception. 86 | 87 | ## Sensitivity 88 | 89 | The sensitivity of the BH1750 can be controlled by adding the tuple `{mtreg, S}` to the `Options` parameter to the `bh1750:start/3` function, where `S` can take be an integer value between 31 and 254 (inclusive). The default value for this parameter is 69. 90 | 91 | Specifying a value outside of the above range will result in a `bardarg` exception. 92 | 93 | # BH1750 Example 94 | 95 | The BH1750 driver includes an example program illustrating use of the BH1750 driver. See the [README](../examples/bh1750_example/README.md) for information about how to build, flash, and run this exmaple program. 96 | -------------------------------------------------------------------------------- /markdown/bme280.md: -------------------------------------------------------------------------------- 1 | ## BME280 driver 2 | 3 | The Bosch Sensortec BME280 is a small sensor that can read temperature, humidity, and atmospheric pressure. The chipset supports I2C and SPI interfaces. This driver uses the AtomVM I2C interface for communicating with the BME280. This means you can take temperature, barometric pressure, and humidity readings using two GPIO pins on your ESP32. 4 | 5 | Developers interact with this driver by starting an instance, specifying pins for the I2C data and clock pins. Starting an instance of the driver yeilds a reference that can be used in subsequent calls. 6 | 7 | The primary operation in this module is the take_reading/1 function, which takes a reference to a BME280 driver, and returns a reading expressed as a tuple containing the temperature (in degrees celsius), atmospheric pressure (in hecto-pascals) and relative humidity (as a percentage). 8 | 9 | Functions for reading the BME280 chip ide and version, as well as doing a soft reset of the device, are also supported. 10 | 11 | > Note. The BME280 sensor is a fairly dynamic sensor and can be used for many different applications (e.g., weather collection, gaming, drones, etc). The primary use-case for this driver is weather collection, which is assumed to be a low frequency operation. Some of the BME280 applications may require additional support in this driver, which would be relatively straightforward to support in future versions. 12 | 13 | Further information about the Bosch Sensortec BME280 can be found in the reference documentation: 14 | 15 | https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf 16 | 17 | # Programming Manual 18 | 19 | The BME280 Driver is designed to provide a simple, easy to use interface for taking temperature, humidity, and atmospheric pressure measurements for your AtomVM application. This section describes the BME280 driver API, as encapsulated in the `bme280` Erlang module. 20 | 21 | ## Lifecycle 22 | 23 | An instance of the BME280 driver is created using an instance of the I2CBus service. Start by initializing an I2Cbus using the `i2c_bus` module, specifying the `sda` and `scl` pins in a `map` structure: 24 | 25 | {ok, I2CBus} = i2c_bus:start(#{ 26 | sda => 21, 27 | scl => 22 28 | }), 29 | 30 | You can then use the `bme280:start/1` and `bme280:start/2` functions to start an instance of the BME280 driver: 31 | 32 | {ok, BME} = bme280:start(I2CBus), 33 | ... 34 | 35 | The return value includes a reference to the driver, which is used in subsequent operations. 36 | 37 | > Note. The I2CBus instance may be used to initialize other I2C devices that are multiplexed on the same I2C bus. 38 | 39 | The arity-2 version of the `start` function allows additional parameters to be specified, in a manner described in more detail below. 40 | 41 | To delete an instance of the driver, use the `bme280:stop/1` function. 42 | 43 | > Note. This function is not well tested and its use may result in a memory leak. 44 | 45 | ## Readings 46 | 47 | Readings are obtained via a call to the `bme280:take_reading/0` function. 48 | 49 | Readings are represented as a tuple containing the temperature (in degrees celcius), atmospheric pressure (in [hectopascals](https://en.wikipedia.org/wiki/Pascal_(unit))), and relative humidity, as a percentage. Each value contains an integral and fractional part, to two digits of precision. 50 | 51 | {{20, 88}, {1018, 30}, {23, 31}} 52 | 53 | ## Oversampling 54 | 55 | You may specify an oversampling rate for each data point (temperature, atmospheric pressure, or humidity) to reduce noise by adding the tuple `{*_oversampling, Value)}` to the `Options` properties list to the `bme280:start/2` function, where `*_oversampling` can range over: 56 | 57 | * `temp_oversampling` 58 | * `pressure_oversampling` 59 | * `humidity_oversampling` 60 | 61 | and where `Value` can take the following atomic values: 62 | 63 | * `ignore` -- skip measurement of this data point 64 | * `x1` -- x1 oversampling 65 | * `x2` -- x2 oversampling 66 | * `x4` (default) -- x4 oversampling 67 | * `x8` -- x8 oversampling 68 | * any other value -- x16 oversampling 69 | 70 | # BME280 Example 71 | 72 | The BME280 driver includes an example program illustrating use of the BME280 driver. See the [README](../examples/bme280_example/README.md) for information about how to build, flash, and run this exmaple program. 73 | -------------------------------------------------------------------------------- /markdown/components.md: -------------------------------------------------------------------------------- 1 | # `atomvm_lib` Components 2 | 3 | The `atomvm_lib` repository includes mostly Erlang APIs that provide added functionality to the AtomVM virtual machine. Many of these APIs can be used on a standard AtomVM image. 4 | 5 | However, some of these libraries make use of native-C extensions to the AtomVM virtual machine on the ESP32. These extensions are implemented as ESP IDF SDK "components", which are collections of native C code that can be compiled and linked into the AtomVM ESP32 image. Because the ESP IDF SDK and tool-chain does not make use of dynamic linkage, these components must be known and configured at _build_ time. 6 | 7 | This page provides instructions for building and linking `atomvm_lib` components into an AtomVM image, so that they can be used in your applications. 8 | 9 | ## Prerequisites 10 | 11 | * The `git` source control tool 12 | * GNU Make 13 | * [IDF SDK](https://docs.espressif.com/projects/esp-idf/en/release-v3.3/index.html) and tool chain, and its pre-requisite software. 14 | * [AtomVM](https://github.com/bettio/AtomVM) source tree, and its pre-requisite software. 15 | 16 | > Note. These instructions assume you have downloaded the AtomVM virtual machine and have the required software needed to build the VM, including the IDF SDK and tool chain. Instructions for building AtomVM are outside of the scope of this document. For information about how to build AtomVM targeted for the ESP32, see the [AtomVM Implementors Guide](http://doc.atomvm.net). 17 | 18 | ## `atomvm_lib` Build Instructions 19 | 20 | This section describes how to build `atomvm_lib` components into the AtomVM virtual machine. In this section, we use `` to refer to the top level of the AtomVM source tree. 21 | 22 | Start by cloning the `atomvm_lib` repository into the `src/platforms/esp32/components` directory of the AtomVM source tree: 23 | 24 | shell$ cd /src/platforms/esp32/components 25 | shell$ git clone git@github.com:atomvm/atomvm_lib.git 26 | ... 27 | 28 | If you have not already built AtomVM, you can issue the `make` command from the `src/platforms/esp32` directory of the AtomVM source tree: 29 | 30 | shell$ cd /src/platforms/esp32 31 | shell$ make 32 | ... 33 | 34 | This step will compile the AtomVM sources, as well as the `atomvm_lib` sources. However, it will not link any of the `atomvm_lib` components into the AtomVM virtual machine image. 35 | 36 | This step should create a new AtomVM image, with the `atomvm_lib` components linked into it. 37 | 38 | Now that you have built an AtomVM VM image containing `atomvm_lib` components, you can now flash the AtomVM image to your device. 39 | 40 | > Note. Flashing the AtomVM image, containing the AtomVM VM and core Erlang libraries is outside of the scope of this document. For information about how to create an AtomVM image and flash it to an ESP32, see the [AtomVM Implementors Guide](http://doc.atomvm.net). 41 | -------------------------------------------------------------------------------- /markdown/ledc_pwm.md: -------------------------------------------------------------------------------- 1 | ## LEDC PWM 2 | 3 | The `ledc_pwm` module provides abstractions and state management around the low-level `ledc` API provided by AtomVM, and helps to mitigate common errors with use of the low-level APIs. 4 | 5 | The API is more "object-oriented", in that it separates the notion of a timer and channel into separate referencible entities, making it easier to manage the lifecycle of these objects. 6 | 7 | Furthermore, the high-level API manages the relationship between a frequency set on a timer, the duty cycle resolution defined by that frequency, and the duty cycle, as set by the user. As the details between this values is hidden, the user can set a frequency for a timer, and then specify the duty cycle as a percentage (a value between 0 and 100, inclusive), so that he or she does not need to manually compute which duty cycles are appropriate for which frequency, as one needs to do in the low-level API. 8 | 9 | # Programming Manual 10 | 11 | TODO 12 | 13 | ### Sample Code 14 | 15 | The following code illustrates use of the high-level API: 16 | 17 | %% create a 5khz timer 18 | Freq = 5000. 19 | {ok, Timer} = ledc_pwm:create_timer(Freq). 20 | 21 | %% bind pin 2 to this timer in a channel 22 | Pin = 2. 23 | {ok, Channel} = ledc_pwm:create_channel(Timer, Pin). 24 | 25 | io:format("Frequency(hz): ~p Duty(%): ~p~n", [ledc_pwm:get_freq_hz(Channel), ledc_pwm:get_dutyp(Channel)]). 26 | 27 | %% set the duty cycle to 0%, and fade up to 100% over 5 seconds 28 | TargetDutyp = 100. 29 | FadeMs = 5000. 30 | ledc_pwm:set_dutyp(Channel, 0). 31 | ledc_pwm:fade(Channel, TargetDutyp, FadeMs). 32 | 33 | 34 | # LEDC PWM Example 35 | 36 | The LEDC PWM example program illustrates use of the LEDC PWM API. See the [README](../examples/ledc_pwm_example/README.md) for information about how to build, flash, and run this exmaple program. 37 | -------------------------------------------------------------------------------- /markdown/sht3x.md: -------------------------------------------------------------------------------- 1 | ## SHT3x driver 2 | 3 | The [Sensirion](https://www.sensirion.com) [SHT3x](https://www.sensirion.com/en/environmental-sensors/humidity-sensors/digital-humidity-sensors-for-various-applications/) is a small sensor that can read temperature and humidity with a fairly high degree of accuracy. The chipset supports the I2C interfaces, with varying levels of accuracy. This driver uses the AtomVM I2C interface for communicating with the SHT31. 4 | This means you can take temperature and humidity readings using two GPIO pins on your ESP32. 5 | 6 | Developers interact with this driver by starting an instance, specifying an I2CBus instance through which I2C commands will be serialized. Starting an instance of the driver yields a reference that can be used as input to subsequent function calls in the module. 7 | 8 | The SHT3x driver support two modes of operation: 9 | 10 | 1. **One-Shot mode** (ideal for low-lower applications) Applications make explicit calls to the module to obtain a measurement. 11 | 1. (Currently unimplemented) **Periodic mode** (ideal for continuous monitoring) Applications configure the device to collect measurements at periodic intervals (1/2 sec, 1s, 2s, 4s, or 10s), and to report the measurements back to the application, either via a callback function or a message delivered to a specified process. 12 | 13 | 14 | > Note. The SHT31 sensor is a fairly dynamic sensor and can be used for many different applications (e.g., weather collection, gaming, drones, etc). The primary use-case for this driver is weather collection, which is assumed to be a low frequency operation. Some of the SHT31 applications may require additional support in this driver, which would be relatively straightforward to support in future versions. 15 | 16 | Further information about the Sensirion SHT3x can be found in the [SHT3x datasheet](../assets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf). 17 | 18 | # Programming Manual 19 | 20 | The SHT3x Driver is designed to provide a simple, easy to use interface for taking temperature and humidity measurements for your AtomVM application. This section describes the SHT3x driver API, as encapsulated in the `sht3x` Erlang module. 21 | 22 | ## Lifecycle 23 | 24 | An instance of the SHT3x driver is created using an instance of the I2CBus service. Start by initializing an I2Cbus using the `i2c_bus` module, specifying the `sda` and `scl` pins in a `map` structure: 25 | 26 | {ok, I2CBus} = i2c_bus:start(#{ 27 | sda => 21, 28 | scl => 22 29 | }), 30 | 31 | You can then use the `sht3x:start[_link]/1` or `sht3x:start[_link]/2` functions to start an instance of the SHT3x driver: 32 | 33 | {ok, BME} = sht3x:start_link(I2CBus), 34 | ... 35 | 36 | The return value includes a reference to the driver, which is used in subsequent operations. 37 | 38 | > Note. The I2CBus instance may be used to initialize other I2C devices that are multiplexed on the same I2C bus. 39 | 40 | The arity-2 version of the `start[_link]` function allows additional parameters to be specified, in a manner described in more detail below. 41 | 42 | To stop an instance of the driver and free any resources in use, use the `sht3x:stop/1` function. 43 | 44 | ## Configuration 45 | 46 | The SHT3x driver can be configured via an optional `map` structure passed in to the `start[_link]/2` function. 47 | 48 | 49 | | Key | Value | Default | Description | 50 | |-----|-------|---------|-------------| 51 | | `repeatability` | `high \| medium \| low` | `high` | The repeatability of the measurement. Higher repeatability will result in less noise in the measurement, more time to take the measurement, and higher current consumption. | 52 | | `clock_stretching` | `enabled \| disabled` | `disabled` | Whether I2C standard clock stretching is enabled. | 53 | 54 | ## Single-Shot Readings 55 | 56 | Single-shot readings are obtained via a call to the `sht3x:take_reading/0` function. 57 | 58 | Readings are represented as a tuple containing the temperature (in degrees celsius) and relative humidity, as a percentage. Each value contains an integral and fractional part, with the fractional part expressed as a ratio. 59 | 60 | The SHT31 supports a 0.01% relative humidity resolution, and a 0.015C temperature resolution. See the [SHT3x datasheet](../assets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) for information about other models of this sensor. 61 | 62 | For example, 63 | 64 | %% erlang 65 | {ok, {Temperature, Humidity}} = sht3x:take_reading(SHT), 66 | io:format("Temperature: ~p, Humidity: ~p~n", [Temperature, Humidity]), 67 | ... 68 | 69 | prints something like the following to the console: 70 | 71 | Temperature: {19,{79,1000}}, Humidity: {31,{91,100}} 72 | 73 | I.e., 19.079C, 31.91% relative humidity. 74 | 75 | ## Periodic Readings 76 | 77 | TODO (currently unimplemented) 78 | 79 | ## Soft Reset 80 | 81 | TODO (currently unimplemented) 82 | 83 | ## Heater 84 | 85 | TODO (currently unimplemented) 86 | 87 | # SHT3x Example 88 | 89 | The SHT3x driver includes an example program illustrating use of the SHT3x driver. See the [README](../examples/sht3x_example/README.md) for information about how to build, flash, and run this example program. 90 | -------------------------------------------------------------------------------- /nifs/atomvm_lib.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 dushin.net 3 | // All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | // #define ENABLE_TRACE 37 | #include "trace.h" 38 | 39 | #define TAG "atomvm_lib" 40 | 41 | RTC_DATA_ATTR size_t data_len = 0; 42 | RTC_DATA_ATTR char *data[CONFIG_RTC_MEMORY_SIZE]; 43 | 44 | static term nif_set_rtc_memory(Context *ctx, int argc, term argv[]) 45 | { 46 | UNUSED(argc); 47 | 48 | term binary = argv[0]; 49 | VALIDATE_VALUE(binary, term_is_binary); 50 | 51 | size_t binary_len = term_binary_size(binary); 52 | if (CONFIG_RTC_MEMORY_SIZE < binary_len) { 53 | RAISE_ERROR(BADARG_ATOM); 54 | } 55 | data_len = binary_len; 56 | memcpy(data, term_binary_data(binary), binary_len); 57 | 58 | return OK_ATOM; 59 | } 60 | 61 | static term nif_get_rtc_memory(Context *ctx, int argc, term argv[]) 62 | { 63 | UNUSED(argc); 64 | UNUSED(argv); 65 | 66 | if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(data_len)) != MEMORY_GC_OK)) { 67 | RAISE_ERROR(OUT_OF_MEMORY_ATOM); 68 | } 69 | 70 | return term_from_literal_binary(data, data_len, &ctx->heap, ctx->global); 71 | } 72 | 73 | #define MAC_LENGTH 6 74 | 75 | static term nif_get_mac(Context *ctx, int argc, term argv[]) 76 | { 77 | UNUSED(argc); 78 | UNUSED(argv); 79 | 80 | uint8_t mac[MAC_LENGTH]; 81 | esp_efuse_mac_get_default(mac); 82 | 83 | if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(2 * MAC_LENGTH)) != MEMORY_GC_OK)) { 84 | RAISE_ERROR(OUT_OF_MEMORY_ATOM); 85 | } 86 | char buf[2 * MAC_LENGTH + 1]; 87 | snprintf(buf, 2 * MAC_LENGTH + 1, 88 | "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 89 | 90 | return term_from_literal_binary(buf, 2 * MAC_LENGTH, &ctx->heap, ctx->global); 91 | } 92 | 93 | #define SHA1_LEN 20 94 | 95 | static term nif_sha1(Context *ctx, int argc, term argv[]) 96 | { 97 | UNUSED(argc); 98 | term binary = argv[0]; 99 | VALIDATE_VALUE(binary, term_is_binary); 100 | 101 | if (UNLIKELY(memory_ensure_free(ctx, term_binary_heap_size(SHA1_LEN)) != MEMORY_GC_OK)) { 102 | RAISE_ERROR(OUT_OF_MEMORY_ATOM); 103 | } 104 | term ret = term_create_uninitialized_binary(SHA1_LEN, &ctx->heap, ctx->global); 105 | binary = argv[0]; 106 | 107 | int res = mbedtls_sha1_ret((const unsigned char *) term_binary_data(binary), term_binary_size(binary), (unsigned char *) term_binary_data(ret)); 108 | if (res != 0) { 109 | RAISE_ERROR(BADARG_ATOM); 110 | } 111 | 112 | return ret; 113 | } 114 | 115 | static term nif_set_time_of_day(Context *ctx, int argc, term argv[]) 116 | { 117 | UNUSED(argc); 118 | 119 | VALIDATE_VALUE(argv[0], term_is_any_integer); 120 | 121 | avm_int64_t ms_since_unix_epoch = term_maybe_unbox_int64(argv[0]); 122 | 123 | TRACE("ms_since_unix_epoch: %lli\n", ms_since_unix_epoch); 124 | 125 | struct timeval tp = { 126 | .tv_sec = ms_since_unix_epoch / 1000, 127 | .tv_usec = (ms_since_unix_epoch % 1000) * 1000 128 | }; 129 | struct timezone tz = { 130 | .tz_minuteswest = 0, 131 | .tz_dsttime = 0 132 | }; 133 | int res = settimeofday(&tp, &tz); 134 | if (res != 0) { 135 | if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { 136 | RAISE_ERROR(OUT_OF_MEMORY_ATOM); 137 | } 138 | term error = term_alloc_tuple(2, &ctx->heap); 139 | term_put_tuple_element(error, 0, ERROR_ATOM); 140 | term_put_tuple_element(error, 1, term_from_int(errno)); 141 | return RAISE_ERROR(error); 142 | } else { 143 | return OK_ATOM; 144 | } 145 | } 146 | 147 | static const struct Nif set_rtc_memory_nif = 148 | { 149 | .base.type = NIFFunctionType, 150 | .nif_ptr = nif_set_rtc_memory 151 | }; 152 | static const struct Nif get_rtc_memory_nif = 153 | { 154 | .base.type = NIFFunctionType, 155 | .nif_ptr = nif_get_rtc_memory 156 | }; 157 | static const struct Nif get_mac_nif = 158 | { 159 | .base.type = NIFFunctionType, 160 | .nif_ptr = nif_get_mac 161 | }; 162 | static const struct Nif sha1_nif = 163 | { 164 | .base.type = NIFFunctionType, 165 | .nif_ptr = nif_sha1 166 | }; 167 | static const struct Nif set_time_of_day_nif = 168 | { 169 | .base.type = NIFFunctionType, 170 | .nif_ptr = nif_set_time_of_day 171 | }; 172 | 173 | 174 | // 175 | // Component Nif Entrypoints 176 | // 177 | 178 | void atomvm_lib_init(GlobalContext *global) 179 | { 180 | // no-op 181 | } 182 | 183 | const struct Nif *atomvm_lib_get_nif(const char *nifname) 184 | { 185 | TRACE("Locating nif %s ...", nifname); 186 | if (strcmp("atomvm_lib:set_rtc_memory/1", nifname) == 0) { 187 | TRACE("Resolved platform nif %s ...\n", nifname); 188 | return &set_rtc_memory_nif; 189 | } 190 | if (strcmp("atomvm_lib:get_rtc_memory/0", nifname) == 0) { 191 | TRACE("Resolved platform nif %s ...\n", nifname); 192 | return &get_rtc_memory_nif; 193 | } 194 | if (strcmp("atomvm_lib:get_mac/0", nifname) == 0) { 195 | TRACE("Resolved platform nif %s ...\n", nifname); 196 | return &get_mac_nif; 197 | } 198 | if (strcmp("atomvm_lib:sha1/1", nifname) == 0) { 199 | TRACE("Resolved platform nif %s ...\n", nifname); 200 | return &sha1_nif; 201 | } 202 | if (strcmp("atomvm_lib:set_time_of_day/1", nifname) == 0) { 203 | TRACE("Resolved platform nif %s ...\n", nifname); 204 | return &set_time_of_day_nif; 205 | } 206 | return NULL; 207 | } 208 | 209 | #include 210 | #ifdef CONFIG_AVM_LIB_ENABLE 211 | REGISTER_NIF_COLLECTION(atomvm_lib, atomvm_lib_init, NULL, atomvm_lib_get_nif) 212 | #endif 213 | -------------------------------------------------------------------------------- /nifs/include/atomvm_lib.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 dushin.net 3 | // All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | #ifndef __ATOMVM_LIB_H 19 | #define __ATOMVM_LIB_H 20 | 21 | #include 22 | #include 23 | 24 | void atomvm_lib_init(GlobalContext *global); 25 | const struct Nif *atomvm_lib_get_nif(const char *nifname); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [ 3 | %{eavmlib, "0.1.0"} 4 | ]}. 5 | {plugins, [ 6 | atomvm_rebar3_plugin 7 | ]}. 8 | -------------------------------------------------------------------------------- /src/atomvm_lib.app.src: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | { 18 | application, atomvm_lib, [ 19 | {description, "An OTP library for developing programs for the AtomVM platform"}, 20 | {vsn, "0.1.0"}, 21 | {registered, []}, 22 | {applications, [kernel, stdlib]}, 23 | {env,[]}, 24 | {modules, []}, 25 | {licenses, ["Apache 2.0"]}, 26 | {links, []} 27 | ] 28 | }. 29 | -------------------------------------------------------------------------------- /src/atomvm_lib.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(atomvm_lib). 18 | 19 | -export([set_rtc_memory/1, get_rtc_memory/0, random/2, sleep_forever/0, to_hex/2, to_hex/1, set_date_time/1, set_date_time/2, set_time_of_day/1]). 20 | 21 | -type year() :: integer(). 22 | -type month() :: 1..12. 23 | -type day() :: 1..31. 24 | -type hour() :: 0..23. 25 | -type minute() :: 0..59. 26 | -type second() :: 0..59. 27 | -type millisecond() :: 0..999. 28 | 29 | -type date() :: {year(), month(), day()}. 30 | -type time() :: {hour(), minute(), second(), millisecond()}. 31 | -type date_time() :: {date(), time()}. 32 | 33 | %%----------------------------------------------------------------------------- 34 | %% @param Data binary data to store 35 | %% @returns ok 36 | %% @doc Store a blob in RTC memory. 37 | %% 38 | %% This operation will store data in RTC memory. This memory will 39 | %% be preserved during a sleep operation, but will be cleared once 40 | %% the device restarts. 41 | %% 42 | %% The input binary data must be no larger than the the value set 43 | %% in configuration at build time of the AtomVM binary. (By default, 44 | %% the maximum binary size is 0 bytes. You may adjust this value 45 | %% via `make menuconfig' when building the AtomVM image.) An attempt 46 | %% to store a blob larger than the maximum allowable size will result 47 | %% in a `badarg' exception. 48 | %% @end 49 | %%----------------------------------------------------------------------------- 50 | -spec set_rtc_memory(Data::binary()) -> ok. 51 | set_rtc_memory(_Data) -> 52 | throw(nif_error). 53 | 54 | %%----------------------------------------------------------------------------- 55 | %% @returns data stored in RTC memory, or the empty binary (`<<"">>'), if 56 | %% nothing has been stored. 57 | %% @doc Retrieve a blob stored in RTC memory. 58 | %% 59 | %% This operation will retrieve data stored in RTC memory. This memory will 60 | %% be preserved during a sleep operation, but will be cleared once 61 | %% the device restarts. 62 | %% @end 63 | %%----------------------------------------------------------------------------- 64 | -spec get_rtc_memory() -> binary(). 65 | get_rtc_memory() -> 66 | throw(nif_error). 67 | 68 | %%----------------------------------------------------------------------------- 69 | %% @returns random 32-bit integer between `Lower' and `Upper'. 70 | %% @doc Returns a random 32-bit integer value between `Lower' and `Upper'. 71 | %% 72 | %% Bother `Lower' and `Upper' must be integers and `Lower' must be less than `Upper'. 73 | %% @end 74 | %%----------------------------------------------------------------------------- 75 | -spec random(Lower::integer(), Upper::integer()) -> integer(). 76 | random(Lower, Upper) when is_integer(Lower), is_integer(Upper), Lower < Upper -> 77 | R = atomvm:random(), 78 | P = case R < 0 of true -> -R; _ -> R end, 79 | Lower + (P rem (Upper - Lower)); 80 | random(_,_) -> 81 | throw(badarg). 82 | 83 | 84 | %%----------------------------------------------------------------------------- 85 | %% @doc Sleep forever. This function does not halt. 86 | %% @end 87 | %%----------------------------------------------------------------------------- 88 | sleep_forever() -> 89 | timer:sleep(24*60*60*1000), 90 | sleep_forever(). 91 | 92 | 93 | %%----------------------------------------------------------------------------- 94 | %% @returns hex representation of I, as a string. 95 | %% @doc Returns the hex representation of I, as a string. 96 | %% @end 97 | %%----------------------------------------------------------------------------- 98 | -spec to_hex(I::integer()|binary()) -> string(). 99 | to_hex(I) when is_integer(I) -> 100 | to_hex(I, 1); 101 | to_hex(B) when is_binary(B) -> 102 | lists:flatten([ "0x" ++ to_hex(I) ++ "," || I <- erlang:binary_to_list(B)]). 103 | 104 | 105 | %%----------------------------------------------------------------------------- 106 | %% @returns hex representation of I, as a string. 107 | %% @doc Returns the hex representation of I, as a string. 108 | %% @end 109 | %%----------------------------------------------------------------------------- 110 | -spec to_hex(I::integer, Bytes::non_neg_integer()) -> string(). 111 | to_hex(I, Bytes) when is_integer(I) -> 112 | to_hex(I, Bytes * 2, []). 113 | 114 | %% @private 115 | to_hex(0, K, Accum) -> 116 | maybe_pad(K, Accum); 117 | to_hex(I, K, Accum) -> 118 | Quartet = I band 16#F, 119 | to_hex(I bsr 4, K - 1, [hex_char(Quartet) | Accum]). 120 | 121 | %% @private 122 | maybe_pad(0, Accum) -> 123 | Accum; 124 | maybe_pad(K, Accum) -> 125 | maybe_pad(K - 1, [$0 | Accum]). 126 | 127 | %% @private 128 | hex_char(16#0) -> $0; 129 | hex_char(16#1) -> $1; 130 | hex_char(16#2) -> $2; 131 | hex_char(16#3) -> $3; 132 | hex_char(16#4) -> $4; 133 | hex_char(16#5) -> $5; 134 | hex_char(16#6) -> $6; 135 | hex_char(16#7) -> $7; 136 | hex_char(16#8) -> $8; 137 | hex_char(16#9) -> $9; 138 | hex_char(16#A) -> $A; 139 | hex_char(16#B) -> $B; 140 | hex_char(16#C) -> $C; 141 | hex_char(16#D) -> $D; 142 | hex_char(16#E) -> $E; 143 | hex_char(16#F) -> $F. 144 | 145 | %% 146 | %% @param DateTime Date and Time to set 147 | %% @return `ok | {error, Reason :: term()}' 148 | %% @doc Set the system time. 149 | %% 150 | %% Equivalent to `set_date_time(DateTime, 0)' 151 | %% @end 152 | %% 153 | -spec set_date_time(DateTime :: date_time()) -> ok. 154 | set_date_time(DateTime) -> 155 | set_date_time(DateTime, 0). 156 | 157 | %% 158 | %% @param DateTime Date and Time to set 159 | %% @param Millisecond Millisecond granularity not encapsulated in DateTime. 160 | %% @return `ok | {error, Reason :: term()}' 161 | %% @doc Set the system time. 162 | %% 163 | %% This function sets the system time to the specified date and time (at 164 | %% millisecond granularity). The specified date may not be before the 165 | %% UNIX epoch (Jan 1, 1970). Coordinates are all in UTC. 166 | %% 167 | %% Note. Some systems may require special permissions to call this function. 168 | %% @end 169 | %% 170 | -spec set_date_time(DateTime :: date_time(), Millisecond :: 0..999) -> ok. 171 | set_date_time({{Year, Month, Day}, {Hour, Minute, Second}} = DateTime, Millisecond) 172 | when is_integer(Year) andalso Year >= 1970 173 | andalso is_integer(Month) andalso Month >= 1 andalso Month =< 12 174 | andalso is_integer(Day) andalso Day >= 1 andalso Day =< 31 175 | andalso is_integer(Hour) andalso Hour >= 0 andalso Hour =< 24 176 | andalso is_integer(Minute) andalso Minute >= 0 andalso Minute =< 59 177 | andalso is_integer(Second) andalso Second >= 0 andalso Second =< 59 178 | andalso is_integer(Millisecond) andalso Millisecond >= 0 andalso Millisecond =< 999 179 | -> 180 | ?MODULE:set_time_of_day(seconds_since_epoch(DateTime) * 1000 + Millisecond). 181 | 182 | %% 183 | %% @param MsSinceUnixEpoch Milliseconds since the UNIX epoch 184 | %% @param Millisecond Millisecond granularity not encapsulated in DateTime. 185 | %% @return `ok | {error, Reason :: term()}' 186 | %% @doc Set the system time. 187 | %% 188 | %% This function sets the system time to the specified number of milliseconds 189 | %% after the UNIX epoch (Jan 1, 1970). Coordinates are all in UTC. 190 | %% 191 | %% Note. Some systems may require special permissions to call this function. 192 | %% @end 193 | %% 194 | -spec set_time_of_day(MsSinceUnixEpoch :: non_neg_integer()) -> ok. 195 | set_time_of_day(_MsSinceUnixEpoch) -> 196 | throw(nif_error). 197 | 198 | %% @private 199 | seconds_since_epoch({{Year, Month, Day}, {Hour, Minute, Second}}) -> 200 | YDay = day_of_year(Month, Day) - 1, 201 | AdjYear = Year - 1900, 202 | %% https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15 203 | Second + Minute * 60 + Hour * 3600 + YDay * 86400 + 204 | (AdjYear - 70) * 31536000 + ((AdjYear - 69) div 4) * 86400 - 205 | ((AdjYear - 1) div 100) * 86400 + ((AdjYear + 299) div 400) * 86400. 206 | 207 | %% @private 208 | day_of_year(1, Day) when Day =< 31 -> 209 | Day; 210 | day_of_year(2, Day) when Day =< 28 -> 211 | 31 + Day; 212 | day_of_year(3, Day) when Day =< 31 -> 213 | 31 + 28 + Day; 214 | day_of_year(4, Day) when Day =< 30 -> 215 | 31 + 28 + 31 + Day; 216 | day_of_year(5, Day) when Day =< 31 -> 217 | 31 + 28 + 31 + 30 + Day; 218 | day_of_year(6, Day) when Day =< 30 -> 219 | 31 + 28 + 31 + 30 + 31 + Day; 220 | day_of_year(7, Day) when Day =< 31 -> 221 | 31 + 28 + 31 + 30 + 31 + 30 + Day; 222 | day_of_year(8, Day) when Day =< 31 -> 223 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + Day; 224 | day_of_year(9, Day) when Day =< 30 -> 225 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + Day; 226 | day_of_year(10, Day) when Day =< 31 -> 227 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + Day; 228 | day_of_year(11, Day) when Day =< 30 -> 229 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + Day; 230 | day_of_year(12, Day) when Day =< 31 -> 231 | 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + Day; 232 | day_of_year(_Month, _Day) -> 233 | error(badarg). 234 | -------------------------------------------------------------------------------- /src/avm_application.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(avm_application). 18 | 19 | -export([ 20 | start/1, stop/1, get_env/2, get_env/3, set_env/3, set_env/4, ensure_all_started/1 21 | ]). 22 | 23 | 24 | start(Application) -> 25 | avm_application_controller:start_application(Application). 26 | 27 | stop(Application) -> 28 | avm_application_controller:stop_application(Application). 29 | 30 | get_env(Application, Key) -> 31 | avm_env:get_env(Application, Key). 32 | 33 | get_env(Application, Key, Default) -> 34 | avm_env:get_env(Application, Key, Default). 35 | 36 | set_env(Application, Key, Value) -> 37 | avm_env:set_env(Application, Key, Value). 38 | 39 | set_env(Application, Key, Value, Opts) -> 40 | avm_env:set_env(Application, Key, Value, Opts). 41 | 42 | ensure_all_started(Application) -> 43 | avm_application_controller:ensure_all_started(Application). 44 | % avm_application_controller:start_application(Application). 45 | -------------------------------------------------------------------------------- /src/codec.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2021 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(codec). 18 | 19 | -export([encode/2]). 20 | 21 | encode(0, hex) -> 22 | "00"; 23 | encode(Value, hex) when 0 < Value -> 24 | hex_encode(Value, []). 25 | 26 | 27 | %% @private 28 | hex_encode(0, Accum) -> 29 | Accum; 30 | hex_encode(Value, Accum) -> 31 | Octet = Value band 16#FF, 32 | hex_encode(Value bsr 8, hex_encode_octet(Octet) ++ Accum). 33 | 34 | %% @private 35 | hex_encode_octet(Octet) -> 36 | hex_encode((Octet band 16#F0) bsr 4) ++ hex_encode(Octet band 16#0F). 37 | 38 | %% @private 39 | hex_encode(N) when 0 =< N andalso N =< 9 -> integer_to_list(N); 40 | hex_encode(16#0A) -> "A"; 41 | hex_encode(16#0B) -> "B"; 42 | hex_encode(16#0C) -> "C"; 43 | hex_encode(16#0D) -> "D"; 44 | hex_encode(16#0E) -> "E"; 45 | hex_encode(16#0F) -> "F". 46 | -------------------------------------------------------------------------------- /src/diag.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2021 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(diag). 18 | 19 | -export([print_all_proc_info/0, print_proc_info/0, print_proc_info/1, get_all_proc_info/0, get_proc_info/0, get_proc_info/1]). 20 | 21 | 22 | print_all_proc_info() -> 23 | Procs = erlang:processes(), 24 | io:format("num_procs: ~p~n", [length(Procs)]), 25 | [print_proc_info(Proc) || Proc <- Procs], 26 | io:format("esp32_free_heap_size: ~p~n", [erlang:system_info(esp32_free_heap_size)]), 27 | io:format("refc_binary_info: ~p~n", [erlang:system_info(refc_binary_info)]), 28 | ok. 29 | 30 | print_proc_info() -> 31 | print_proc_info(self()). 32 | 33 | print_proc_info(Proc) -> 34 | io:format("pid: ~p~n", [Proc]), 35 | io:format(" heap_size: ~p~n", [erlang:process_info(Proc, heap_size)]), 36 | io:format(" stack_size: ~p~n", [erlang:process_info(Proc, stack_size)]), 37 | io:format(" message_queue_len: ~p~n", [erlang:process_info(Proc, message_queue_len)]), 38 | io:format(" memory: ~p~n", [erlang:process_info(Proc, memory)]), 39 | ok. 40 | 41 | get_all_proc_info() -> 42 | FreeHeapSize = erlang:system_info(esp32_free_heap_size), 43 | % RefcBinaryInfo = erlang:system_info(refc_binary_info), 44 | #{ 45 | procs => [get_proc_info(Proc) || Proc <- erlang:processes()], 46 | esp32_free_heap_size => FreeHeapSize 47 | % ,refc_binary_info => RefcBinaryInfo 48 | }. 49 | 50 | get_proc_info() -> 51 | get_proc_info(self()). 52 | 53 | get_proc_info(Proc) -> 54 | {_, HeapSize} = erlang:process_info(Proc, heap_size), 55 | {_, StackSize} = erlang:process_info(Proc, stack_size), 56 | {_, MessageQueueLen} = erlang:process_info(Proc, message_queue_len), 57 | {_, Memory} = erlang:process_info(Proc, memory), 58 | #{ 59 | proc => Proc, 60 | heap_size => HeapSize, 61 | stack_size => StackSize, 62 | message_queue_len => MessageQueueLen, 63 | memory => Memory 64 | }. 65 | -------------------------------------------------------------------------------- /src/gen_tcp_server.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2022 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | 18 | -module(gen_tcp_server). 19 | 20 | -export([start/4, start/3, start_link/4, start_link/3, stop/1]). 21 | 22 | -behaviour(gen_server). 23 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). 24 | 25 | %% 26 | %% gen_tcp_server behavior 27 | %% 28 | 29 | -callback init(Args :: term()) -> 30 | {ok, State :: term()} | {stop, Reason :: term()}. 31 | 32 | -callback handle_receive(Socket :: term(), Packet :: binary(), State :: term()) -> 33 | {reply, Packet :: iolist(), NewState :: term()} | {noreply, NewState :: term()} | {close, Packet :: iolist()} | close. 34 | 35 | -callback handle_tcp_closed(Socket :: term(), State :: term()) -> ok. 36 | 37 | % -define(TRACE_ENABLED, true). 38 | -include_lib("atomvm_lib/include/trace.hrl"). 39 | 40 | -record(state, { 41 | handler, 42 | handler_state 43 | }). 44 | 45 | -define(DEFAULT_BIND_OPTIONS, #{ 46 | family => inet, 47 | addr => any 48 | }). 49 | -define(DEFAULT_SOCKET_OPTIONS, #{}). 50 | 51 | %% 52 | %% API 53 | %% 54 | 55 | start(BindOptions, Handler, Args) -> 56 | start(BindOptions, ?DEFAULT_SOCKET_OPTIONS, Handler, Args). 57 | 58 | start(BindOptions, SocketOptions, Handler, Args) -> 59 | gen_server:start(?MODULE, {maps:merge(?DEFAULT_BIND_OPTIONS, BindOptions), SocketOptions, Handler, Args}, []). 60 | 61 | start_link(BindOptions, Handler, Args) -> 62 | start_link(BindOptions, ?DEFAULT_SOCKET_OPTIONS, Handler, Args). 63 | 64 | start_link(BindOptions, SocketOptions, Handler, Args) -> 65 | gen_server:start_link(?MODULE, {maps:merge(?DEFAULT_BIND_OPTIONS, BindOptions), SocketOptions, Handler, Args}, []). 66 | 67 | stop(Server) -> 68 | gen_server:stop(Server). 69 | 70 | %% 71 | %% gen_server implementation 72 | %% 73 | 74 | %% @hidden 75 | init({BindOptions, SocketOptions, Handler, Args}) -> 76 | Self = self(), 77 | case socket:open(inet, stream, tcp) of 78 | {ok, Socket} -> 79 | ok = set_socket_options(Socket, SocketOptions), 80 | case socket:bind(Socket, BindOptions) of 81 | ok -> 82 | case socket:listen(Socket) of 83 | ok -> 84 | spawn(fun() -> accept(Self, Socket) end), 85 | case Handler:init(Args) of 86 | {ok, HandlerState} -> 87 | {ok, #state{handler = Handler, handler_state = HandlerState}}; 88 | HandlerError -> 89 | try_close(Socket), 90 | {stop, {handler_error, HandlerError}} 91 | end; 92 | ListenError -> 93 | try_close(Socket), 94 | {stop, {listen_error, ListenError}} 95 | end; 96 | BindError -> 97 | try_close(Socket), 98 | {stop, {bind_error, BindError}} 99 | end; 100 | OpenError -> 101 | {stop, {open_error, OpenError}} 102 | end; 103 | init({Socket, Handler, Args}) -> 104 | Self = self(), 105 | case Handler:init(Args) of 106 | {ok, HandlerState} -> 107 | spawn(fun() -> loop(Self, Socket) end), 108 | {ok, #state{handler = Handler, handler_state = HandlerState}}; 109 | HandlerError -> 110 | {stop, {handler_error, HandlerError}} 111 | end. 112 | 113 | %% @hidden 114 | handle_call(_From, _Request, State) -> 115 | {noreply, State}. 116 | 117 | %% @hidden 118 | handle_cast(_Msg, State) -> 119 | {noreply, State}. 120 | 121 | %% @hidden 122 | handle_info({tcp_closed, Socket}, State) -> 123 | ?TRACE("TCP Socket closed ~p", [Socket]), 124 | #state{handler=Handler, handler_state=HandlerState} = State, 125 | NewHandlerState = Handler:handle_tcp_closed(Socket, HandlerState), 126 | {noreply, State#state{handler_state=NewHandlerState}}; 127 | handle_info({tcp, Socket, Packet}, State) -> 128 | #state{handler=Handler, handler_state=HandlerState} = State, 129 | ?TRACE("received packet: len(~p) from ~p", [erlang:byte_size(Packet), socket:peername(Socket)]), 130 | case Handler:handle_receive(Socket, Packet, HandlerState) of 131 | {reply, ResponsePacket, ResponseState} -> 132 | ?TRACE("Sending reply to endpoint ~p", [socket:peername(Socket)]), 133 | try_send(Socket, ResponsePacket), 134 | {noreply, State#state{handler_state=ResponseState}}; 135 | {noreply, ResponseState} -> 136 | ?TRACE("no reply", []), 137 | {noreply, State#state{handler_state=ResponseState}}; 138 | {close, ResponsePacket} -> 139 | ?TRACE("Sending reply to endpoint ~p and closing socket: ~p", [socket:peername(Socket), Socket]), 140 | try_send(Socket, ResponsePacket), 141 | % timer:sleep(500), 142 | try_close(Socket), 143 | {noreply, State}; 144 | close -> 145 | ?TRACE("Closing socket ~p", [Socket]), 146 | try_close(Socket), 147 | {noreply, State}; 148 | _SomethingElse -> 149 | ?TRACE("Unexpected response from handler ~p: ~p", [Handler, SomethingElse]), 150 | try_close(Socket), 151 | {noreply, State} 152 | end; 153 | handle_info(Info, State) -> 154 | io:format("Received spurious info msg: ~p~n", [Info]), 155 | {noreply, State}. 156 | 157 | %% @hidden 158 | terminate(_Reason, _State) -> 159 | ok. 160 | 161 | %% 162 | %% internal functions 163 | %% 164 | 165 | %% @private 166 | try_send(Socket, Packet) when is_binary(Packet) -> 167 | ?TRACE( 168 | "Trying to send binary packet data to socket ~p. Packet (or len): ~p", [ 169 | Socket, case byte_size(Packet) < 32 of true -> Packet; _ -> byte_size(Packet) end 170 | ]), 171 | case socket:send(Socket, Packet) of 172 | ok -> 173 | ?TRACE("sent.", []), 174 | ok; 175 | {ok, Rest} -> 176 | ?TRACE("sent. remaining: ~p", [Rest]), 177 | try_send(Socket, Rest); 178 | Error -> 179 | io:format("Send failed due to error ~p~n", [Error]) 180 | end; 181 | try_send(Socket, Char) when is_integer(Char) -> 182 | %% TODO handle unicode 183 | ?TRACE("Sending char ~p as ~p", [Char, <>]), 184 | try_send(Socket, <>); 185 | try_send(Socket, List) when is_list(List) -> 186 | case is_string(List) of 187 | true -> 188 | try_send(Socket, list_to_binary(List)); 189 | _ -> 190 | try_send_iolist(Socket, List) 191 | end. 192 | 193 | try_send_iolist(_Socket, []) -> 194 | ok; 195 | try_send_iolist(Socket, [H | T]) -> 196 | try_send(Socket, H), 197 | try_send_iolist(Socket, T). 198 | 199 | is_string([]) -> 200 | true; 201 | is_string([H | T]) when is_integer(H) -> 202 | is_string(T); 203 | is_string(_) -> 204 | false. 205 | 206 | %% @private 207 | try_close(Socket) -> 208 | case socket:close(Socket) of 209 | ok -> 210 | ok; 211 | Error -> 212 | io:format("Close failed due to error ~p~n", [Error]) 213 | end. 214 | 215 | %% @private 216 | set_socket_options(Socket, SocketOptions) -> 217 | maps:fold( 218 | fun(Option, Value, Accum) -> 219 | erlang:display({setopt, Socket, Option, Value}), 220 | ok = socket:setopt(Socket, Option, Value), 221 | Accum 222 | end, 223 | ok, 224 | SocketOptions 225 | ). 226 | 227 | %% @private 228 | accept(ControllingProcess, ListenSocket) -> 229 | ?TRACE("pid ~p Waiting for connection on ~p ...", [self(), socket:sockname(ListenSocket)]), 230 | case socket:accept(ListenSocket) of 231 | {ok, Connection} -> 232 | ?TRACE("Accepted connection from ~p", [socket:peername(Connection)]), 233 | spawn(fun() -> accept(ControllingProcess, ListenSocket) end), 234 | loop(ControllingProcess, Connection); 235 | _Error -> 236 | ?TRACE("Error accepting connection: ~p", [Error]) 237 | end. 238 | 239 | 240 | %% @private 241 | loop(ControllingProcess, Connection) -> 242 | case socket:recv(Connection) of 243 | {ok, Data} -> 244 | ?TRACE("Received data ~p on connection ~p", [Data, Connection]), 245 | ControllingProcess ! {tcp, Connection, Data}, 246 | loop(ControllingProcess, Connection); 247 | {error, closed} -> 248 | ?TRACE("Peer closed connection ~p", [Connection]), 249 | ControllingProcess ! {tcp_closed, Connection}, 250 | ok; 251 | {error, _SomethingElse} -> 252 | ?TRACE("Some other error occurred ~p", [Connection]), 253 | try_close(Connection) 254 | end. 255 | -------------------------------------------------------------------------------- /src/httpd_api_handler.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(httpd_api_handler). 18 | 19 | -behavior(httpd_handler). 20 | -export([handler_module/0, init_handler/2, handle_http_req/2]). 21 | 22 | -type json_term() :: #{atom() := true | false | atom() | integer() | string() | binary() | list(json_term()) | json_term()}. 23 | 24 | -callback handle_api_request(Method :: httpd:method(), PathSuffix :: httpd:path(), HttpRequest :: httpd:http_request(), Args :: term()) -> 25 | {ok, Reply :: json_term()} | 26 | {close, Reply :: json_term()} | 27 | bad_request | internal_server_error | 28 | term(). 29 | 30 | % -define(TRACE_ENABLED, true). 31 | -include_lib("atomvm_lib/include/trace.hrl"). 32 | 33 | %% @hidden 34 | handler_module() -> 35 | ?MODULE. 36 | 37 | -record(state, { 38 | path_suffix, 39 | handler_config 40 | }). 41 | 42 | %% @hidden 43 | init_handler(PathSuffix, HandlerConfig) -> 44 | {ok, #state{path_suffix = PathSuffix, handler_config = HandlerConfig}}. 45 | 46 | %% @hidden 47 | handle_http_req(HttpRequest, State) -> 48 | #{ 49 | method := Method 50 | } = HttpRequest, 51 | PathSuffix = State#state.path_suffix, 52 | HandlerConfig = State#state.handler_config, 53 | ?TRACE("Method: ~p PathSuffix: ~p HttpRequest: ~p HandlerConfig: ~p", [Method, PathSuffix, HttpRequest, HandlerConfig]), 54 | Module = maps:get(module, HandlerConfig), 55 | Args = maps:get(args, HandlerConfig, undefined), 56 | ?TRACE("Module: ~p Args: ~p", [Module, Args]), 57 | case Module of 58 | Mod when is_atom(Mod) -> 59 | case Mod:handle_api_request(Method, PathSuffix, HttpRequest, Args) of 60 | ok -> 61 | {close, #{"Content-Type" => "text/plain"}, "ok"}; 62 | {ok, Reply} when is_atom(Reply) -> 63 | {close, #{"Content-Type" => "text/plain"}, atom_to_list(Reply)}; 64 | {ok, Reply} when is_list(Reply) -> 65 | {close, #{"Content-Type" => "text/plain"}, Reply}; 66 | {ok, Reply} when is_map(Reply) -> 67 | ?TRACE("Encoding reply ~p", [Reply]), 68 | Body = json_encoder:encode(Reply), 69 | ?TRACE("JSON Body: ~p", [Body]), 70 | {close, #{"Content-Type" => "application/json"}, Body}; 71 | {close, Reply} -> 72 | ?TRACE("Encoding reply ~p", [Reply]), 73 | Body = json_encoder:encode(Reply), 74 | ?TRACE("JSON Body: ~p", [Body]), 75 | {close, #{"Content-Type" => "application/json"}, Body}; 76 | Error -> 77 | Error 78 | end; 79 | Fun when is_function(Fun) -> 80 | case Fun(Method, PathSuffix, HttpRequest, Args) of 81 | {ok, Reply, State} -> 82 | ?TRACE("Encoding reply ~p", [Reply]), 83 | Body = json_encoder:encode(Reply), 84 | {close, #{"Content-Type" => "application/json"}, Body}; 85 | Error -> 86 | Error 87 | end; 88 | SomethingElse -> 89 | io:format("Bad Config. Expected module or function, but got ~p~n", [SomethingElse]), 90 | {error, internal_server_error} 91 | end. 92 | -------------------------------------------------------------------------------- /src/httpd_cmd_api_handler.erl: -------------------------------------------------------------------------------- 1 | % 2 | % Copyright 2022 Fred Dushin 3 | % 4 | % Licensed under the Apache License, Version 2.0 (the "License"); 5 | % you may not use this file except in compliance with the License. 6 | % You may obtain a copy of the License at 7 | % 8 | % http://www.apache.org/licenses/LICENSE-2.0 9 | % 10 | % Unless required by applicable law or agreed to in writing, software 11 | % distributed under the License is distributed on an "AS IS" BASIS, 12 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | % See the License for the specific language governing permissions and 14 | % limitations under the License. 15 | % 16 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 17 | % 18 | 19 | -module(httpd_cmd_api_handler). 20 | 21 | -behavior(httpd_api_handler). 22 | -export([handle_api_request/4]). 23 | 24 | % -define(TRACE_ENABLED, true). 25 | -include_lib("atomvm_lib/include/trace.hrl"). 26 | 27 | %% 28 | %% API Handler implementation 29 | %% 30 | 31 | handle_api_request(post, [<<"restart">>], _HttpRequest, _Args) -> 32 | ?TRACE("POST restart request", []), 33 | %% TODO parameterize 34 | spawn(fun() -> restart(3000) end), 35 | ok; 36 | 37 | handle_api_request(Method, Path, _HttpRequest, _Args) -> 38 | io:format("ERROR! Unsupported method ~p or path ~p~n", [Method, Path]), 39 | not_found. 40 | 41 | %% 42 | %% Internal Functions 43 | %% 44 | 45 | restart(SleepMs) -> 46 | timer:sleep(SleepMs), 47 | esp:restart(). 48 | -------------------------------------------------------------------------------- /src/httpd_env_api_handler.erl: -------------------------------------------------------------------------------- 1 | % 2 | % Copyright 2022 Fred Dushin 3 | % 4 | % Licensed under the Apache License, Version 2.0 (the "License"); 5 | % you may not use this file except in compliance with the License. 6 | % You may obtain a copy of the License at 7 | % 8 | % http://www.apache.org/licenses/LICENSE-2.0 9 | % 10 | % Unless required by applicable law or agreed to in writing, software 11 | % distributed under the License is distributed on an "AS IS" BASIS, 12 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | % See the License for the specific language governing permissions and 14 | % limitations under the License. 15 | % 16 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 17 | % 18 | 19 | -module(httpd_env_api_handler). 20 | 21 | -behavior(httpd_api_handler). 22 | -export([handle_api_request/4]). 23 | 24 | % -define(TRACE_ENABLED, true). 25 | -include_lib("atomvm_lib/include/trace.hrl"). 26 | 27 | %% 28 | %% API Handler implementation 29 | %% 30 | 31 | handle_api_request(get, [Application, Param | Rest], _HttpRequest, _Args) -> 32 | ?TRACE("Application: ~p Param: ~p, Rest: ~p", [Application, Param, Rest]), 33 | 34 | ApplicationAtom = bin_to_atom(Application), 35 | ParamAtom = bin_to_atom(Param), 36 | Result = case avm_application:get_env(ApplicationAtom, ParamAtom) of 37 | undefined -> 38 | undefined; 39 | {ok, Value} -> 40 | find_value_in_path(Value, Rest) 41 | end, 42 | case Result of 43 | undefined -> 44 | {error, not_found}; 45 | _ -> 46 | {ok, Result} 47 | end; 48 | 49 | handle_api_request(post, [Application, Param | Rest], HttpRequest, _Args) -> 50 | ?TRACE("Application: ~p Param: ~p, Rest: ~p", [Application, Param, Rest]), 51 | 52 | QueryParams = maps:get(query_params, HttpRequest, #{}), 53 | ?TRACE("QueryParams: ~p", [QueryParams]), 54 | 55 | ApplicationAtom = bin_to_atom(Application), 56 | ParamAtom = bin_to_atom(Param), 57 | 58 | NewValue = create_value(Rest, QueryParams, #{}), 59 | ?TRACE("NewValue: ~p", [NewValue]), 60 | MergedValue = case avm_application:get_env(ApplicationAtom, ParamAtom) of 61 | undefined -> 62 | NewValue; 63 | {ok, OldValue} -> 64 | ?TRACE("merging OldValue: ~p NewValue: ~p", [OldValue, NewValue]), 65 | map_utils:deep_maps_merge(OldValue, NewValue) 66 | end, 67 | 68 | ?TRACE("QueryParams: ~p MergedValue: ~p", [QueryParams, MergedValue]), 69 | ok = avm_application:set_env(ApplicationAtom, ParamAtom, MergedValue, [{persistent, true}]); 70 | 71 | handle_api_request(delete, [Application, Param | Rest], _HttpRequest, _Args) -> 72 | ?TRACE("Application: ~p Param: ~p, Rest: ~p", [Application, Param, Rest]), 73 | 74 | ApplicationAtom = bin_to_atom(Application), 75 | ParamAtom = bin_to_atom(Param), 76 | Result = case avm_application:get_env(ApplicationAtom, ParamAtom) of 77 | undefined -> 78 | undefined; 79 | {ok, Env} -> 80 | %% TODO memory leak 81 | Path = [bin_to_atom(P) || P <- Rest], 82 | ?TRACE("Removing path ~p from env ~p", [Path, Env]), 83 | map_utils:remove_entry_in_path(Env, Path) 84 | end, 85 | case Result of 86 | undefined -> 87 | {error, not_found}; 88 | NewEnv -> 89 | ?TRACE("NewEnv: ~p", [NewEnv]), 90 | avm_application:set_env(ApplicationAtom, ParamAtom, NewEnv), 91 | ok 92 | end; 93 | 94 | handle_api_request(Method, Path, _HttpRequest, _Args) -> 95 | io:format("ERROR! Unsupported method ~p or path ~p~n", [Method, Path]), 96 | {error, not_found}. 97 | 98 | find_value_in_path(Map, []) -> 99 | Map; 100 | find_value_in_path(Value, [H | T]) when is_map(Value) -> 101 | %% TODO binary to atom here is bad 102 | case maps:get(bin_to_atom(H), Value, undefined) of 103 | undefined -> 104 | undefined; 105 | V -> 106 | find_value_in_path(V, T) 107 | end; 108 | find_value_in_path(_Value, _Path) -> 109 | undefined. 110 | 111 | bin_to_atom(Bin) -> 112 | list_to_atom(binary_to_list(Bin)). 113 | 114 | create_value([], QueryParams, Accum) -> 115 | maps:merge(Accum, QueryParams); 116 | create_value([H | T], QueryParams, Accum) -> 117 | %% TODO binary to atom here is bad 118 | #{bin_to_atom(H) => create_value(T, QueryParams, Accum)}. 119 | -------------------------------------------------------------------------------- /src/httpd_file_handler.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(httpd_file_handler). 18 | 19 | -export([handler_module/0, init_handler/2, handle_http_req/2]). 20 | -behavior(httpd_handler). 21 | 22 | % -define(TRACE_ENABLED, true). 23 | -include_lib("atomvm_lib/include/trace.hrl"). 24 | 25 | -record(state, { 26 | path_suffix, 27 | handler_config 28 | }). 29 | 30 | %% @hidden 31 | handler_module() -> 32 | ?MODULE. 33 | 34 | %% @hidden 35 | init_handler(PathSuffix, HandlerConfig) -> 36 | {ok, #state{path_suffix = PathSuffix, handler_config = HandlerConfig}}. 37 | 38 | %% @hidden 39 | handle_http_req(#{method := get} = _HttpRequest, State) -> 40 | HandlerConfig = State#state.handler_config, 41 | App = maps:get(app, HandlerConfig), 42 | PathSuffix = State#state.path_suffix, 43 | FullPath = join("/", lists:reverse(PathSuffix)), 44 | ?TRACE("App: ~p PathSuffix: ~p FullPath: ~p", [App, PathSuffix, FullPath]), 45 | case atomvm:read_priv(App, FullPath) of 46 | undefined -> 47 | {error, not_found}; 48 | Data -> 49 | {close, #{"Content-Type" => get_content_type(lists:reverse(PathSuffix))}, Data} 50 | end; 51 | handle_http_req(_HttpRequest, _HandlerConfig) -> 52 | {error, internal_server_error}. 53 | 54 | %% @private 55 | join(Separator, Path) -> 56 | join(Separator, Path, []). 57 | 58 | %% @private 59 | join(_Separator, [], Accum) -> 60 | Accum; 61 | join(Separator, [H|T], []) -> 62 | join(Separator, T, binary_to_list(H)); 63 | join(Separator, [H|T], Accum) -> 64 | join(Separator, T, binary_to_list(H) ++ Separator ++ Accum). 65 | 66 | %% @private 67 | get_content_type([Filename|_]) -> 68 | case get_suffix(Filename) of 69 | "html" -> 70 | "text/html"; 71 | "css" -> 72 | "text/css"; 73 | "js" -> 74 | "text/javascript"; 75 | _ -> 76 | "application/octet-streeam" 77 | end. 78 | 79 | get_suffix(Filename) -> 80 | case string:split(binary_to_list(Filename), ".") of 81 | [_Basename, Suffix] -> 82 | Suffix; 83 | _ -> 84 | undefined 85 | end. 86 | -------------------------------------------------------------------------------- /src/httpd_handler.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | 18 | %% 19 | %% @doc This module defined the `handle_http_req/4' callback function. 20 | %% 21 | %% The `handle_http_req/4' callback function is used to implment HTTP handlers for the `httpd' module. 22 | %% @end 23 | %% 24 | 25 | -module(httpd_handler). 26 | 27 | -type http_method() :: get | post | put | delete. 28 | -type http_headers() :: #{binary() => binary()}. 29 | -type http_path() :: list(atom()). 30 | -type http_request() :: #{ 31 | method => http_method(), 32 | path => http_path(), 33 | headers => http_headers(), 34 | body => binary() 35 | }. 36 | 37 | %% 38 | %% 39 | %% 40 | -callback init_handler(PathPrefix :: http_path(), HandlerConfig :: map()) -> 41 | {ok, State :: term()} | 42 | {error, Reason :: term()}. 43 | 44 | %% 45 | %% 46 | %% 47 | -callback handle_http_req(HttpRequest :: http_request(), State :: term()) -> 48 | {noreply, State :: term()} | 49 | {reply, Reply :: term(), State :: term()} | 50 | {reply, Headers :: http_headers(), Reply :: term(), State :: term()} | 51 | close | 52 | {close, Reply :: term()} | 53 | {close, Headers :: http_headers(), Reply :: term()} | 54 | {error, not_found | bad_request | internal_server_error | term()}. 55 | -------------------------------------------------------------------------------- /src/httpd_ota_handler.erl: -------------------------------------------------------------------------------- 1 | % 2 | % Copyright 2022 Fred Dushin 3 | % 4 | % Licensed under the Apache License, Version 2.0 (the "License"); 5 | % you may not use this file except in compliance with the License. 6 | % You may obtain a copy of the License at 7 | % 8 | % http://www.apache.org/licenses/LICENSE-2.0 9 | % 10 | % Unless required by applicable law or agreed to in writing, software 11 | % distributed under the License is distributed on an "AS IS" BASIS, 12 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | % See the License for the specific language governing permissions and 14 | % limitations under the License. 15 | % 16 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 17 | % 18 | 19 | -module(httpd_ota_handler). 20 | 21 | -export([init_handler/2, handle_http_req/2]). 22 | 23 | % -define(TRACE_ENABLED, true). 24 | -include_lib("atomvm_lib/include/trace.hrl"). 25 | 26 | %% 27 | %% HTTP request handler implementation (for OTA) 28 | %% 29 | 30 | -define(TARGET_PARTITION, <<"app2.avm">>). 31 | -define(ATOMVM_NAMESPACE, atomvm). 32 | -define(BOOT_TARGET_PARTITION_KEY, boot_partition). 33 | 34 | -record(state, { 35 | offset = 0 36 | }). 37 | 38 | %% @hidden 39 | init_handler(_PathPrefix, _HandlerConfig) -> 40 | {ok, #state{}}. 41 | 42 | %% @hidden 43 | handle_http_req(#{method := post} = HttpRequest, State) -> 44 | #{ 45 | headers := Headers, 46 | body := Body 47 | } = HttpRequest, 48 | Offset = State#state.offset, 49 | case Offset of 50 | 0 -> 51 | io:format("Erasing partition ~p~n", [?TARGET_PARTITION]), 52 | esp:partition_erase_range(?TARGET_PARTITION, 0); 53 | _ -> 54 | ok 55 | end, 56 | BodyLen = erlang:byte_size(Body), 57 | NewOffset = Offset + BodyLen, 58 | ContentLength = get_content_length(Headers), 59 | case NewOffset < ContentLength of 60 | true -> 61 | io:format("Offset: ~p ContentLength: ~p BodyLen: ~p~n", [Offset, ContentLength, BodyLen]), 62 | ok = esp:partition_write(?TARGET_PARTITION, Offset, Body), 63 | io:format("Wrote ~p bytes at offset ~p to partition ~p.~n", [BodyLen, Offset, ?TARGET_PARTITION]), 64 | NewState = State#state{offset = NewOffset}, 65 | {noreply, NewState}; 66 | false -> 67 | io:format("Request complete.~n"), 68 | ok = esp:partition_write(?TARGET_PARTITION, Offset, Body), 69 | io:format("Wrote ~p bytes at offset ~p to partition ~p.~n", [BodyLen, Offset, ?TARGET_PARTITION]), 70 | ok = esp:nvs_set_binary(?ATOMVM_NAMESPACE, ?BOOT_TARGET_PARTITION_KEY, ?TARGET_PARTITION), 71 | io:format("Set boot partition to ~p~n", [?TARGET_PARTITION]), 72 | {close, <<"ok">>} 73 | end; 74 | handle_http_req(_HttpRequest, _State) -> 75 | {error, internal_server_error}. 76 | 77 | get_content_length(Headers) -> 78 | %% TODO handle case 79 | erlang:binary_to_integer(maps:get(<<"Content-Length">>, Headers, <<"0">>)). 80 | -------------------------------------------------------------------------------- /src/httpd_stats_api_handler.erl: -------------------------------------------------------------------------------- 1 | % 2 | % Copyright 2022 Fred Dushin 3 | % 4 | % Licensed under the Apache License, Version 2.0 (the "License"); 5 | % you may not use this file except in compliance with the License. 6 | % You may obtain a copy of the License at 7 | % 8 | % http://www.apache.org/licenses/LICENSE-2.0 9 | % 10 | % Unless required by applicable law or agreed to in writing, software 11 | % distributed under the License is distributed on an "AS IS" BASIS, 12 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | % See the License for the specific language governing permissions and 14 | % limitations under the License. 15 | % 16 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 17 | % 18 | 19 | -module(httpd_stats_api_handler). 20 | 21 | -behavior(httpd_api_handler). 22 | -export([handle_api_request/4]). 23 | 24 | % -define(TRACE_ENABLED, true). 25 | -include_lib("atomvm_lib/include/trace.hrl"). 26 | 27 | %% 28 | %% API Handler implementation 29 | %% 30 | 31 | handle_api_request(get, [<<"system">>], _HttpRequest, _Args) -> 32 | ?TRACE("GET system request", []), 33 | {ok, get_system_info()}; 34 | 35 | handle_api_request(get, [<<"memory">>], _HttpRequest, _Args) -> 36 | ?TRACE("GET memory request", []), 37 | {ok, get_memory()}; 38 | 39 | handle_api_request(Method, Path, _HttpRequest, _Args) -> 40 | io:format("ERROR! Unsupported method ~p or path ~p~n", [Method, Path]), 41 | not_found. 42 | 43 | %% 44 | %% Internal Functions 45 | %% 46 | 47 | get_system_info() -> 48 | #{ 49 | platform => atomvm:platform(), 50 | word_size => erlang:system_info(wordsize), 51 | system_architecture => erlang:system_info(system_architecture), 52 | atomvm_version => erlang:system_info(atomvm_version), 53 | esp32_chip_info => get_esp32_chip_info(), 54 | esp_idf_version => list_to_binary(erlang:system_info(esp_idf_version)) 55 | }. 56 | 57 | get_esp32_chip_info() -> 58 | case erlang:system_info(esp32_chip_info) of 59 | undefined -> 60 | undefined; 61 | %% TODO remove old API 62 | {esp32, Features, Cores, Revision} -> 63 | [{features, Features}, {cores, Cores}, {revision, Revision}, {model, undefined}]; 64 | Info when is_map(Info) -> 65 | maps:to_list(Info); 66 | _ -> 67 | unknown 68 | end. 69 | 70 | get_memory() -> 71 | #{ 72 | atom_count => erlang:system_info(atom_count), 73 | process_count => erlang:system_info(process_count), 74 | port_count => erlang:system_info(port_count), 75 | esp32_free_heap_size => erlang:system_info(esp32_free_heap_size), 76 | esp32_largest_free_block => erlang:system_info(esp32_largest_free_block), 77 | esp32_minimum_free_size => erlang:system_info(esp32_minimum_free_size) 78 | }. 79 | -------------------------------------------------------------------------------- /src/httpd_ws_handler.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(httpd_ws_handler). 18 | 19 | %% internal API only called by httpd_ws_handler 20 | -export([start/3, stop/1, handle_web_socket_message/2]). 21 | %% API used by implementors of the httpd_ws_handler behavior 22 | -export([send/2]). 23 | 24 | -behavior(gen_server). 25 | -export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). 26 | 27 | % -define(TRACE_ENABLED, true). 28 | -include_lib("atomvm_lib/include/trace.hrl"). 29 | 30 | 31 | -type websocket() :: term(). 32 | 33 | -export_type([websocket/0]). 34 | 35 | %% 36 | %% httpd_ws_handler behavior 37 | %% 38 | 39 | -callback handle_ws_init(WebSocket :: websocket(), Path :: httpd:path(), Args :: term()) -> 40 | {ok, State :: term()} | 41 | term(). 42 | 43 | -callback handle_ws_message(PayloadData :: binary(), State :: term()) -> 44 | {reply, Reply :: iolist(), NewState :: term()} | 45 | {noreply, NewState :: term()} | 46 | {close, Reply :: iolist(), NewState :: term()} | 47 | {close, NewState :: term()} | 48 | term(). 49 | 50 | %% 51 | %% API 52 | %% 53 | 54 | %% @hidden 55 | start(Socket, Path, Config) -> 56 | gen_server:start(?MODULE, {Socket, Path, Config}, []). 57 | 58 | %% @hidden 59 | stop(WebSocket) -> 60 | gen_server:stop(WebSocket). 61 | 62 | 63 | %% @hidden 64 | handle_web_socket_message(WebSocket, Packet) -> 65 | gen_server:cast(WebSocket, {message, Packet}). 66 | 67 | send(WebSocket, Packet) -> 68 | case self() of 69 | WebSocket -> 70 | throw(badarg); 71 | _ -> 72 | gen_server:call(WebSocket, {send, Packet}) 73 | end. 74 | 75 | 76 | %% 77 | %% gen_server implementation 78 | %% 79 | 80 | -record(state, { 81 | socket, 82 | handler_module, 83 | handler_state 84 | }). 85 | 86 | %% @hidden 87 | init({Socket, Path, Config}) -> 88 | ?TRACE("Started WebSocket using socket ~p with Config ~p", [Socket, Config]), 89 | case maps:get(module, Config, undefined) of 90 | undefined -> 91 | {stop, bad_config}; 92 | HandlerModule -> 93 | case HandlerModule:handle_ws_init(self(), Path, maps:get(args, Config, undefined)) of 94 | {ok, HandlerState} -> 95 | {ok, #state{socket=Socket, handler_module=HandlerModule, handler_state=HandlerState}}; 96 | Error -> 97 | {stop, Error} 98 | end 99 | end. 100 | 101 | %% @hidden 102 | handle_cast({message, Packet}, State) -> 103 | #state{ 104 | socket = Socket, 105 | handler_module = HandlerModule, 106 | handler_state = HandlerState 107 | } = State, 108 | ?TRACE("WebSocket received packet ~p", [Packet]), 109 | case parse_frame(Packet) of 110 | {ok, PayloadData} -> 111 | ?TRACE("HandlerModule ~p; PayloadData ~p", [HandlerModule, PayloadData]), 112 | case HandlerModule:handle_ws_message(PayloadData, HandlerState) of 113 | {reply, Reply, NewHandlerState} -> 114 | ?TRACE("Handled WS payload. NewHandlerState: ~p", [NewHandlerState]), 115 | do_send(Socket, Reply, text), 116 | {noreply, State#state{handler_state = NewHandlerState}}; 117 | {noreply, NewHandlerState} -> 118 | ?TRACE("Handled WS payload. NewHandlerState: ~p", [NewHandlerState]), 119 | {noreply, State#state{handler_state = NewHandlerState}}; 120 | HandleModleError -> 121 | ?TRACE("HandleModleError: ~p", [HandleModleError]), 122 | socket:close(Socket), 123 | {stop, HandleModleError, State} 124 | end; 125 | empty_payload -> 126 | ?TRACE("Empty payload.", []), 127 | {noreply, State}; 128 | ParseFrameError -> 129 | ?TRACE("ParseFrameError: ~p", [ParseFrameError]), 130 | socket:close(Socket), 131 | {stop, ParseFrameError, State} 132 | end. 133 | 134 | %% @hidden 135 | handle_call({send, Packet}, _From, State) -> 136 | ?TRACE("Sending packet ~p", [Packet]), 137 | Reply = do_send(State#state.socket, Packet, text), 138 | {reply, Reply, State}. 139 | 140 | %% @hidden 141 | handle_info(_Msg, State) -> 142 | {noreply, State}. 143 | 144 | %% @hidden 145 | terminate(_Reason, _State) -> 146 | ok. 147 | 148 | 149 | %% 150 | %% internal implementation 151 | %% 152 | 153 | %% @private 154 | parse_frame(<<0,0,0,0,0,0,0,0,0,0>>) -> 155 | empty_payload; 156 | parse_frame(Packet) -> 157 | try 158 | <<_FinOpcode:8, MaskLen:8, Rest/binary>> = Packet, 159 | % Fin = (FinOpcode band 16#80) bsr 7, 160 | % Opcode = FinOpcode band 16#0F, 161 | Mask = (MaskLen band 16#80) bsr 7, 162 | PayloadLen = MaskLen band 16#7F, 163 | % <> = Packet, 164 | % ?TRACE("FinOpcode: ~p, Fin: ~p, Opcode: ~p, MaskLen: ~p, Mask: ~p, PayloadLen: ~p, Rest: ~p", [FinOpcode, Fin, Opcode, MaskLen, Mask, PayloadLen, Rest]), 165 | ?TRACE("Fin: ~p, Opcode: ~p, Mask: ~p, PayloadLen: ~p, Rest: ~p", [Fin, Opcode, Mask, PayloadLen, Rest]), 166 | case PayloadLen of 167 | 0 -> 168 | {ok, <<"">>}; 169 | 126 -> 170 | case Mask of 171 | 1 -> 172 | <> = Rest, 173 | <> = Rest2, 174 | ?TRACE("MaskingKey: ~p, MaskedPayload: ~p", [MaskingKey, MaskedPayload]), 175 | {ok, unmask(MaskingKey, MaskedPayload)}; 176 | _ -> 177 | <> = Rest, 178 | {ok, <>} 179 | end; 180 | 127 -> 181 | case Mask of 182 | 1 -> 183 | <> = Rest, 184 | <> = Rest2, 185 | ?TRACE("MaskingKey: ~p, MaskedPayload: ~p", [MaskingKey, MaskedPayload]), 186 | {ok, unmask(MaskingKey, MaskedPayload)}; 187 | _ -> 188 | <> = Rest, 189 | {ok, <>} 190 | end; 191 | _ -> 192 | case Mask of 193 | 1 -> 194 | <> = Rest, 195 | ?TRACE("MaskingKey: ~p, MaskedPayload: ~p", [MaskingKey, MaskedPayload]), 196 | {ok, unmask(MaskingKey, MaskedPayload)}; 197 | _ -> 198 | {ok, Rest} 199 | end 200 | end 201 | catch 202 | _:Error -> 203 | ?TRACE("Error in parse_frame: ~p", [Error]), 204 | {error, Error} 205 | end. 206 | 207 | %% @private 208 | unmask(MaskingKey, MaskedPayload) -> 209 | unmask(MaskingKey, MaskedPayload, 0, []). 210 | 211 | unmask(_MaskingKey, <<"">>, _I, Accum) -> 212 | % ?TRACE("unmasked Accum: ~p", [Accum]), 213 | list_to_binary(lists:reverse(Accum)); 214 | unmask(MaskingKey, <>, I, Accum) -> 215 | MaskingOctet = octet(MaskingKey, I rem 4), 216 | % ?TRACE("H: ~p, MaskingOctet: ~p", [H, MaskingOctet]), 217 | unmask(MaskingKey, T, I + 1, [MaskingOctet bxor H | Accum]). 218 | 219 | %% @private 220 | octet(<>, 0) -> 221 | First; 222 | octet(<<_:1/binary, Second:8, _/binary>>, 1) -> 223 | Second; 224 | octet(<<_:2/binary, Third:8, _/binary>>, 2) -> 225 | Third; 226 | octet(<<_:3/binary, Fourth:8, _/binary>>, 3) -> 227 | Fourth. 228 | 229 | %% @private 230 | do_send(Socket, Packet, Mode) -> 231 | FramedPacket = frame(Packet, Mode), 232 | ?TRACE("Framed packet: [~s]", [atomvm_lib:to_hex(FramedPacket)]), 233 | socket:send(Socket, FramedPacket). 234 | 235 | %% @private 236 | frame(Packet, Mode) when is_list(Packet) -> 237 | frame(iolist_to_binary(Packet), Mode); 238 | frame(Packet, Mode) when is_binary(Packet) -> 239 | Fin = 16#80, 240 | Opcode = case Mode of text -> 16#01; binary -> 16#02; _ -> 16#01 end, 241 | FinOpcode = Fin bor Opcode, 242 | PayloadLen = erlang:byte_size(Packet), 243 | case {PayloadLen =< 125, PayloadLen =< 65536} of 244 | {true, _} -> 245 | NoMask = 16#7F, 246 | MaskLen = NoMask band PayloadLen, 247 | <>; 248 | {false, true} -> 249 | NoMask = 16#7F, 250 | MaskLen = NoMask band 126, 251 | <>; 252 | {false, false} -> 253 | NoMask = 16#7F, 254 | MaskLen = NoMask band 127, 255 | <> 256 | end. 257 | -------------------------------------------------------------------------------- /src/i2c_bus.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | 18 | %%----------------------------------------------------------------------------- 19 | %% @doc 20 | %% @end 21 | %%----------------------------------------------------------------------------- 22 | -module(i2c_bus). 23 | 24 | -behaviour(gen_server). 25 | 26 | -export([start/1, start_link/1, stop/1, enqueue/3, write_bytes/3, write_bytes/4, read_bytes/3, read_bytes/4]). 27 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 28 | -export_type([i2c_bus/0]). 29 | 30 | % -define(TRACE_ENABLED, true). 31 | -include_lib("atomvm_lib/include/trace.hrl"). 32 | 33 | -type pin() :: non_neg_integer(). 34 | -type address() :: non_neg_integer(). 35 | -type register() :: non_neg_integer(). 36 | -type freq_hz() :: non_neg_integer(). 37 | -type options() :: #{ 38 | sda => pin(), 39 | scl => pin(), 40 | freq_hz => freq_hz() 41 | }. 42 | -type i2c_bus() :: pid(). 43 | -type operation() :: fun((port(), address()) -> any()). 44 | -type operations() :: [operation()]. 45 | 46 | -define(DEFAULT_OPTIONS, #{freq_hz => 400000}). 47 | 48 | -record(state, { 49 | i2c 50 | }). 51 | 52 | %%----------------------------------------------------------------------------- 53 | %% @param Options 54 | %% @returns {ok, i2c_bus()} on success, or {error, Reason}, on failure 55 | %% @doc Start the I2C Bus. 56 | %% @end 57 | %%----------------------------------------------------------------------------- 58 | -spec start(Options::options()) -> {ok, i2c_bus()} | {error, Reason::term()}. 59 | start(Options) -> 60 | gen_server:start(?MODULE, maybe_add_defaults(Options), []). 61 | 62 | %%----------------------------------------------------------------------------- 63 | %% @param Options 64 | %% @returns {ok, i2c_bus()} on success, or {error, Reason}, on failure 65 | %% @doc Start the I2C Bus. 66 | %% @end 67 | %%----------------------------------------------------------------------------- 68 | -spec start_link(Options::options()) -> {ok, i2c_bus()} | {error, Reason::term()}. 69 | start_link(Options) -> 70 | gen_server:start_link(?MODULE, maybe_add_defaults(Options), []). 71 | 72 | %%----------------------------------------------------------------------------- 73 | %% @param Bus a reference to the I2C Bus instance created via start 74 | %% @returns ok if successful; {error, Reason}, otherwise 75 | %% @doc Stop the I2C Bus. 76 | %% 77 | %% Note. This function is not well tested and its use may result in a memory leak. 78 | %% @end 79 | %%----------------------------------------------------------------------------- 80 | -spec stop(Bus::i2c_bus()) -> ok | {error, Reason::term()}. 81 | stop(Bus) -> 82 | gen_server:stop(Bus). 83 | 84 | %%----------------------------------------------------------------------------- 85 | %% @param Bus a reference to the Bus instance created via start 86 | %% @returns ok if successful; {error, Reason}, otherwise 87 | %% @doc 88 | %% @end 89 | %%----------------------------------------------------------------------------- 90 | -spec enqueue(Bus::i2c_bus(), Address::address(), Operations::operations()) -> ok | {error, Reason::term()}. 91 | enqueue(Bus, Address, Operations) -> 92 | gen_server:call(Bus, {enqueue, Address, Operations}). 93 | 94 | %%----------------------------------------------------------------------------- 95 | %% @param Bus a reference to the Bus instance created via start 96 | %% @returns ok if successful; {error, Reason}, otherwise 97 | %% @doc 98 | %% @end 99 | %%----------------------------------------------------------------------------- 100 | -spec write_bytes(Bus::i2c_bus(), Address::address(), Bytes::binary()) -> ok | {error, Reason::term()}. 101 | write_bytes(Bus, Address, Bytes) -> 102 | gen_server:call(Bus, {write_bytes, Address, Bytes}). 103 | 104 | %%----------------------------------------------------------------------------- 105 | %% @param Bus a reference to the Bus instance created via start 106 | %% @returns ok if successful; {error, Reason}, otherwise 107 | %% @doc 108 | %% @end 109 | %%----------------------------------------------------------------------------- 110 | -spec write_bytes(Bus::i2c_bus(), Address::address(), Register::register(), Bytes::binary()) -> ok | {error, Reason::term()}. 111 | write_bytes(Bus, Address, Register, Bytes) -> 112 | gen_server:call(Bus, {write_bytes, Address, Register, Bytes}). 113 | 114 | %%----------------------------------------------------------------------------- 115 | %% @param Bus a reference to the Bus instance created via start 116 | %% @returns ok if successful; {error, Reason}, otherwise 117 | %% @doc 118 | %% @end 119 | %%----------------------------------------------------------------------------- 120 | -spec read_bytes(Bus::i2c_bus(), Address::address(), Count:: non_neg_integer()) -> {ok, binary()} | {error, Reason :: term()}. 121 | read_bytes(Bus, Address, Count) -> 122 | gen_server:call(Bus, {read_bytes, Address, Count}). 123 | 124 | %%----------------------------------------------------------------------------- 125 | %% @param Bus a reference to the Bus instance created via start 126 | %% @returns ok if successful; {error, Reason}, otherwise 127 | %% @doc 128 | %% @end 129 | %%----------------------------------------------------------------------------- 130 | -spec read_bytes(Bus::i2c_bus(), Address::address(), Register::register(), Count:: non_neg_integer()) -> {ok, binary()} | {error, Reason :: term()}. 131 | read_bytes(Bus, Address, Register, Count) -> 132 | gen_server:call(Bus, {read_bytes, Address, Register, Count}). 133 | 134 | %% 135 | %% gen_server API 136 | %% 137 | 138 | %% @hidden 139 | init(Options) -> 140 | I2C = i2c:open([ 141 | {clock_speed_hz, maps:get(freq_hz, Options)} 142 | | maps:to_list(Options) 143 | ]), 144 | ?TRACE("I2C opened. Options: ~p I2C: ~p", [Options, I2C]), 145 | State = #state{ 146 | i2c = I2C 147 | }, 148 | {ok, State}. 149 | 150 | %% @hidden 151 | handle_call({enqueue, Address, Operations}, _From, State) -> 152 | Reply = try_enqueue_operations(State#state.i2c, Address, Operations), 153 | {reply, Reply, State}; 154 | handle_call({write_bytes, Address, Bytes}, _From, State) -> 155 | ?TRACE("Writing bytes ~p to address ~p i2c ~p", [Bytes, Address, State#state.i2c]), 156 | Reply = i2c:write_bytes(State#state.i2c, Address, Bytes), 157 | ?TRACE("Reply: ~p", [Reply]), 158 | {reply, Reply, State}; 159 | handle_call({write_bytes, Address, Register, Bytes}, _From, State) -> 160 | ?TRACE("Writing bytes ~p to address ~p register ~p i2c ~p", [Bytes, Address, Register, State#state.i2c]), 161 | Reply = i2c:write_bytes(State#state.i2c, Address, Register, Bytes), 162 | ?TRACE("Reply: ~p", [Reply]), 163 | {reply, Reply, State}; 164 | handle_call({read_bytes, Address, Count}, _From, State) -> 165 | ?TRACE("Reading bytes off address ~p count ~p i2c ~p", [Address, Count, State#state.i2c]), 166 | Reply = i2c:read_bytes(State#state.i2c, Address, Count), 167 | ?TRACE("Reply: ~p", [Reply]), 168 | {reply, Reply, State}; 169 | handle_call({read_bytes, Address, Register, Count}, _From, State) -> 170 | ?TRACE("Reading bytes off address ~p register ~p count ~p i2c ~p", [Address, Register, Count, State#state.i2c]), 171 | Reply = i2c:read_bytes(State#state.i2c, Address, Register, Count), 172 | ?TRACE("Reply: ~p", [Reply]), 173 | {reply, Reply, State}; 174 | handle_call(Request, _From, State) -> 175 | {reply, {error, {unknown_request, Request}}, State}. 176 | 177 | %% @hidden 178 | handle_cast(_Msg, State) -> 179 | {noreply, State}. 180 | 181 | %% @hidden 182 | handle_info(_Info, State) -> 183 | {noreply, State}. 184 | 185 | %% @hidden 186 | terminate(_Reason, State) -> 187 | io:format("Closing I2C ... ~n"), 188 | i2c:close(State#state.i2c), 189 | ok. 190 | 191 | %% @hidden 192 | code_change(_OldVsn, State, _Extra) -> 193 | {ok, State}. 194 | 195 | %% 196 | %% Internal functions 197 | %% 198 | 199 | 200 | %% @private 201 | maybe_add_defaults(Options) -> 202 | maps:merge(?DEFAULT_OPTIONS, Options). 203 | 204 | %% @private 205 | try_enqueue_operations(I2C, Address, Operations) -> 206 | ?TRACE("Enqueing on I2C ~p at address ~p", [I2C, Address]), 207 | case i2c:begin_transmission(I2C, Address) of 208 | ok -> 209 | try 210 | lists:foreach( 211 | fun(Operation) -> 212 | ?TRACE("Executing on I2C ~p at address ~p", [I2C, Address]), 213 | Operation(I2C, Address) 214 | end, 215 | Operations 216 | ) 217 | catch 218 | _:E -> 219 | {error, E} 220 | after 221 | i2c:end_transmission(I2C) 222 | end; 223 | E -> 224 | {error, E} 225 | end. 226 | -------------------------------------------------------------------------------- /src/init.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(init). 18 | 19 | -export([ 20 | start/0, stop/0 21 | ]). 22 | 23 | % -define(TRACE_ENABLED, true). 24 | -include_lib("atomvm_lib/include/trace.hrl"). 25 | 26 | %% @hidden 27 | start() -> 28 | ?TRACE("init:start/0", []), 29 | case atomvm:read_priv(?MODULE, "start.boot") of 30 | Bin when is_binary(Bin) -> 31 | ?TRACE("Found start.boot for init module", []), 32 | case start_boot(binary_to_term(Bin)) of 33 | ok -> 34 | ?TRACE("init booted", []), 35 | timer:sleep(infinity); %% TODO 36 | Error -> 37 | io:format("ERROR! Boot failure: ~p~n", [Error]), 38 | {error, {boot_failure, Error}} 39 | end; 40 | undefined -> 41 | io:format("ERROR! Missing boot file. (~p/priv/start.boot)~n", [?MODULE]), 42 | {error, missing_boot_file}; 43 | _ -> 44 | io:format("ERROR! Invalid boot file. (~p/priv/start.boot)~n", [?MODULE]), 45 | {error, invalid_boot_file} 46 | end. 47 | 48 | %% @hidden 49 | stop() -> 50 | %% TODO 51 | avm_application_controller:stop(). 52 | 53 | %% @private 54 | start_boot(BootSpec) -> 55 | ?TRACE("start_boot(~p)", [BootSpec]), 56 | case BootSpec of 57 | {boot, _Version, Spec} when is_map(Spec) -> 58 | boot(Spec); 59 | _ -> 60 | {error, {invalid_boot_spec, not_map}} 61 | end. 62 | 63 | %% @private 64 | boot(Spec) -> 65 | ?TRACE("boot(~p)", [Spec]), 66 | case maps:get(applications, Spec, undefined) of 67 | Applications when is_list(Applications) -> 68 | ?TRACE("Starting applications ~p", [Applications]), 69 | {ok, _Pid} = avm_application_controller:start_link(), 70 | ?TRACE("Application controller started", []), 71 | try 72 | lists:foreach( 73 | fun(Application) -> 74 | ?TRACE("Ensuring that all applications are being started for ~p", [Application]), 75 | case avm_application:ensure_all_started(Application) of 76 | ok -> 77 | io:format("Application ~p started~n", [Application]), 78 | ok; 79 | Error -> 80 | io:format("Error! Failed to ensure applications started for ~p~n", [Application]), 81 | throw({error, {application_start_failure, Application, Error}}) 82 | end 83 | end, 84 | Applications 85 | ) 86 | catch 87 | C:E:S -> 88 | io:format("Error! An exception occurred when attempting to ensure all applications started. Spec=~p Class=~p Exception=~p Stacktrace=~p~n", [Spec, C, E, S]) 89 | end; 90 | _ -> 91 | io:format("Error! Applications in spec file is not a list or does not exist! Spec=~p~n", [Spec]), 92 | {error, applications_not_list} 93 | end. 94 | -------------------------------------------------------------------------------- /src/json_encoder.erl: -------------------------------------------------------------------------------- 1 | % 2 | % This file is part of AtomVM. 3 | % 4 | % Copyright 2018-2022 Davide Bettio 5 | % 6 | % Licensed under the Apache License, Version 2.0 (the "License"); 7 | % you may not use this file except in compliance with the License. 8 | % You may obtain a copy of the License at 9 | % 10 | % http://www.apache.org/licenses/LICENSE-2.0 11 | % 12 | % Unless required by applicable law or agreed to in writing, software 13 | % distributed under the License is distributed on an "AS IS" BASIS, 14 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | % See the License for the specific language governing permissions and 16 | % limitations under the License. 17 | % 18 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 19 | % 20 | 21 | % 22 | % This file is part of AtomVM. 23 | % 24 | % Copyright 2019 Davide Bettio 25 | % 26 | % Licensed under the Apache License, Version 2.0 (the "License"); 27 | % you may not use this file except in compliance with the License. 28 | % You may obtain a copy of the License at 29 | % 30 | % http://www.apache.org/licenses/LICENSE-2.0 31 | % 32 | % Unless required by applicable law or agreed to in writing, software 33 | % distributed under the License is distributed on an "AS IS" BASIS, 34 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35 | % See the License for the specific language governing permissions and 36 | % limitations under the License. 37 | % 38 | % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later 39 | % 40 | 41 | -module(json_encoder). 42 | -export([encode/1]). 43 | 44 | encode(false) -> 45 | <<"false">>; 46 | encode(true) -> 47 | <<"true">>; 48 | encode(nil) -> 49 | <<"nil">>; 50 | encode(null) -> 51 | <<"null">>; 52 | encode(undefined) -> 53 | <<"null">>; 54 | encode(Value) when is_atom(Value) -> 55 | [$", erlang:atom_to_binary(Value, latin1), $"]; 56 | encode(Value) when is_binary(Value) -> 57 | [$", Value, $"]; 58 | encode(Value) when is_float(Value) -> 59 | erlang:float_to_binary(Value, [{decimals, 32}, compact]); 60 | encode(Value) when is_integer(Value) -> 61 | erlang:integer_to_binary(Value); 62 | encode(Value) when is_map(Value) -> 63 | encode_map(Value); 64 | encode(V) -> 65 | case is_string(V) of 66 | true -> 67 | [$",erlang:list_to_binary(V), $"]; 68 | _ -> 69 | encode(V, []) 70 | end. 71 | 72 | encode([{_K, _V} | _T] = L, []) -> 73 | encode(L, ${); 74 | encode([{Key, Value} | []], Acc) -> 75 | Encoded = [$", encode_key(Key), "\":", encode(Value), $}], 76 | [Acc | Encoded]; 77 | encode([{Key, Value} | Tail], Acc) -> 78 | Encoded = [$", encode_key(Key), "\":", encode(Value), $,], 79 | encode(Tail, [Acc | Encoded]); 80 | encode([_V | _T] = L, []) -> 81 | encode(L, $[); 82 | encode([Value | []], Acc) -> 83 | Encoded = [encode(Value), $]], 84 | [Acc | Encoded]; 85 | encode([Value | Tail], Acc) -> 86 | Encoded = [encode(Value), $,], 87 | encode(Tail, [Acc | Encoded]). 88 | 89 | encode_key(Key) when is_atom(Key) -> 90 | erlang:atom_to_binary(Key, latin1); 91 | encode_key(Key) when is_binary(Key) -> 92 | Key. 93 | 94 | is_string([]) -> 95 | true; 96 | is_string([H|T]) when is_integer(H) andalso 0 =< H andalso H =< 255 -> 97 | is_string(T); 98 | is_string(_) -> 99 | false. 100 | 101 | encode_map(Map) -> 102 | iterate_entries(maps:next(maps:iterator(Map)), 0, "{"). 103 | 104 | %% NB. Output is an iolist, so try to avoid gratuitous copying of data 105 | iterate_entries(none, _K, Accum) -> 106 | [[Accum] | [$}]]; 107 | iterate_entries({Key, Value, Iterator}, K, Accum) -> 108 | MaybeComma = 109 | case K of 110 | 0 -> 111 | ""; 112 | _ -> 113 | ", " 114 | end, 115 | Encoded = [MaybeComma, $", encode_key(Key), "\": ", encode(Value)], 116 | iterate_entries(maps:next(Iterator), K + 1, [[Accum] | Encoded]). 117 | -------------------------------------------------------------------------------- /src/lora.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2021 dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(lora). 18 | 19 | %%% 20 | %%% @doc 21 | %%% An SPI driver for the LoRa (SX127X) chipset. 22 | %%% 23 | %%% This module can be used to send and receive messages using LoRa modulation. 24 | %%% Currently, this module only supports point-to-point communications. This 25 | %%% module does not support LoRaWAN. 26 | %%% 27 | %%% References 28 | %%% SemTech SX127x data sheet: https://semtech.my.salesforce.com/sfc/p/#E0000000JelG/a/2R0000001Rbr/6EfVZUorrpoKFfvaF_Fkpgp5kzjiNyiAbqcpqh9qSjE 29 | %%% Python implementation: https://github.com/lemariva/uPyLoRaWAN 30 | %%% 31 | %%% @end 32 | 33 | -export([start/1, stop/1, broadcast/2, sleep/1]). 34 | %% debugging 35 | -export([dump_registers/1]). 36 | 37 | % -define(TRACE_ENABLED, true). 38 | -include_lib("atomvm_lib/include/trace.hrl"). 39 | 40 | -type lora() :: any(). 41 | -type message() :: iodata(). 42 | 43 | -type frequency() :: freq_169mhz | freq_433mhz | freq_868mhz | freq_915mhz | non_neg_integer(). 44 | -type bandwidth() :: bw_7_8khz | bw_10_4khz | bw_15_6khz | bw_20_8khz | bw_31_25khz | bw_41_7khz | bw_62_5khz 45 | | bw_125khz | bw_250khz | bw_500khz. 46 | -type tx_power() :: 2..17. 47 | -type spreading_factor() :: sf_5 | sf_6 | sf_7 | sf_8 | sf_9 | sf_10 | sf_11 | sf_12 | 5..12. 48 | -type ldro() :: on | off. 49 | -type preamble_length() :: 6..65535. 50 | -type lna_gain() :: lna_1 | lna_2 | lna_3 | lna_4 | lna_5 | lna_6 | auto. 51 | 52 | -type coding_rate() :: cr_4_5 | cr_4_6 | cr_4_7 | cr_4_8. 53 | -type header_mode() :: implicit | explicit. 54 | -type device() :: sx127x | sx126x. 55 | 56 | -type config() :: #{ 57 | device => device(), 58 | device_name => atom(), 59 | frequency => frequency(), 60 | bandwidth => bandwidth(), 61 | tx_power => tx_power(), 62 | spreading_factor => spreading_factor(), 63 | preamble_length => preamble_length(), 64 | lna_gain => lna_gain(), 65 | coding_rate => coding_rate(), 66 | ldro => ldro(), 67 | header_mode => header_mode(), 68 | sync_word => non_neg_integer(), 69 | enable_crc => boolean(), 70 | invert_iq => boolean(), 71 | binary => boolean() 72 | }. 73 | 74 | -define(DEFAULT_CONFIG, #{ 75 | device => sx127x, 76 | frequency => freq_915mhz, 77 | bandwidth => bw_125khz, 78 | tx_power => 2, 79 | spreading_factor => 7, 80 | preamble_length => 8, 81 | lna_gain => auto, 82 | coding_rate => cr_4_8, 83 | header_mode => explicit, 84 | sync_word => 16#12, 85 | enable_crc => true, 86 | invert_iq => false, 87 | binary => true 88 | }). 89 | 90 | 91 | %%% 92 | %%% Public API 93 | %%% 94 | 95 | -spec start(config()) -> {ok, lora()} | {error, Reason::term()}. 96 | start(Config) -> 97 | ?TRACE("Config: ~p", [Config]), 98 | SPI = get_or_load_spi(maps:get(spi, Config)), 99 | NewConfig = verify_config(maps:merge(?DEFAULT_CONFIG, Config#{spi => SPI})), 100 | ?TRACE("NewConfig: ~p", [NewConfig]), 101 | Module = get_module(NewConfig), 102 | case Module:start(NewConfig) of 103 | {ok, Impl} -> 104 | {ok, {Module, Impl}}; 105 | E -> E 106 | end. 107 | 108 | -spec stop(Lora::lora()) -> ok. 109 | stop({Module, Impl}) -> 110 | Module:stop(Impl). 111 | 112 | -spec broadcast(Lora::lora(), Message::message()) -> {ok, Length::non_neg_integer()} | {error, Reason::term()}. 113 | broadcast({Module, Impl}, Message) -> 114 | Data = erlang:iolist_to_binary(Message), 115 | Module:broadcast(Impl, Data). 116 | 117 | -spec sleep(Lora::lora()) -> ok. 118 | sleep({Module, Impl}) -> 119 | Module:sleep(Impl). 120 | 121 | %% @hidden 122 | dump_registers({_Module, Impl}) -> 123 | ?TRACE("Calling dump_registers", []), 124 | gen_server:call(Impl, dump_registers). 125 | 126 | %%% 127 | %%% internal functions 128 | %%% 129 | 130 | %% @private 131 | verify_config(Config) -> 132 | %% TODO 133 | Config. 134 | 135 | %% @private 136 | get_module(Config) -> 137 | %% Note. In order to get r 138 | Device = maps:get(device, Config), 139 | case Device of 140 | sx127x -> 141 | lora_sx127x; 142 | sx126x -> 143 | lora_sx126x; 144 | Unknown -> 145 | throw({unsupported_device, Unknown}) 146 | end. 147 | 148 | %% @private 149 | get_or_load_spi(SPI) when is_pid(SPI) -> 150 | SPI; 151 | get_or_load_spi(SPIConfig) when is_map(SPIConfig) -> 152 | get_or_load_spi(maps:to_list(SPIConfig)); 153 | get_or_load_spi(SPIConfig) when is_list(SPIConfig) -> 154 | spi:open(SPIConfig). 155 | -------------------------------------------------------------------------------- /src/map_utils.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(map_utils). 18 | 19 | -export([deep_map_to_proplist/1, deep_maps_merge/2, remove_entry_in_path/2]). 20 | 21 | %% convert a map of maps to a proplist of proplists 22 | deep_map_to_proplist(Map) when is_map(Map) -> 23 | iterate_entries(maps:next(maps:iterator(Map)), []); 24 | deep_map_to_proplist(Term) -> 25 | Term. 26 | 27 | iterate_entries(none, Accum) -> 28 | Accum; 29 | iterate_entries({Key, Value, Iterator}, Accum) -> 30 | iterate_entries(maps:next(Iterator), [{Key, deep_map_to_proplist(Value)} | Accum]). 31 | 32 | %% merge a map of maps 33 | deep_maps_merge(Map1, Map2) -> 34 | iterate_entries( 35 | maps:next(maps:iterator(Map1)), 36 | Map2, 37 | Map2 38 | ). 39 | 40 | %% @private 41 | iterate_entries(none, _Map2, Accum) -> 42 | Accum; 43 | iterate_entries({Key1, Value1, Iterator1}, Map2, Accum) -> 44 | NewAccum = case maps:get(Key1, Map2, undefined) of 45 | undefined -> 46 | Accum#{Key1 => Value1}; 47 | Value2 when is_map(Value1) andalso is_map(Value2) -> 48 | Accum#{Key1 => deep_maps_merge(Value1, Value2)}; 49 | Value2 -> 50 | Accum#{Key1 => Value2} 51 | end, 52 | iterate_entries(maps:next(Iterator1), Map2, NewAccum). 53 | 54 | %% return the result of removing the entry in Map defined by Path, 55 | %% or `undefined', if the path cannot be found in the map. The 56 | %% path must reflect a valid path from the root map to a map 57 | %% key in the input map. The type of the entries in the path 58 | %% must match the keys used in the input map. 59 | %% 60 | %% Example: Map = #{a => #{b => c, d => #{e => f, g => #{h => i}}}} 61 | %% Path = [a, d, e] -> #{a, #{b => c, d => #{g => #{h => i}}}} 62 | %% Path = [a, d, g, h] -> #{a => #{b => c, d => #{e => f, g => #{}}}} 63 | %% Path = [a, b] -> #{a => #{d => #{e => f, g => #{h => i}}}} 64 | %% Path = [a, d, e, f] -> undefined 65 | %% Path = [a, d] -> #{a, #{b => c}} 66 | %% Path = [a, c] -> undefined 67 | %% Path = [a] -> #{} 68 | %% Path = [] -> Map 69 | remove_entry_in_path(Map, []) when is_map(Map) -> 70 | Map; 71 | remove_entry_in_path(Map, [H]) when is_map(Map) -> 72 | case maps:is_key(H, Map) of 73 | false -> 74 | undefined; 75 | _ -> 76 | maps:remove(H, Map) 77 | end; 78 | remove_entry_in_path(Map, [H | T]) when is_map(Map) -> 79 | case maps:get(H, Map, undefined) of 80 | undefined -> 81 | undefined; 82 | V -> 83 | case remove_entry_in_path(V, T) of 84 | undefined -> 85 | undefined; 86 | VPrime -> 87 | Map#{H := VPrime} 88 | end 89 | end; 90 | remove_entry_in_path(_Map, _Path) -> 91 | undefined. 92 | -------------------------------------------------------------------------------- /src/rational.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) dushin.net 3 | %% All rights reserved. 4 | %% 5 | %% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %% you may not use this file except in compliance with the License. 7 | %% You may obtain a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, software 12 | %% distributed under the License is distributed on an "AS IS" BASIS, 13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %% See the License for the specific language governing permissions and 15 | %% limitations under the License. 16 | %% 17 | -module(rational). 18 | 19 | -export([add/2, subtract/2, multiply/2, divide/2, normalize/1, simplify/1, reduce/1, to_decimal/2, round/1]). 20 | 21 | -type numerator() :: integer(). 22 | -type denominator() :: integer(). 23 | -type fraction() :: {numerator(), denominator()}. 24 | -type composite() :: {integer(), fraction()}. 25 | -type rational() :: integer() | fraction() | composite(). 26 | 27 | %% 28 | %% @param A rational 29 | %% @param B rational 30 | %% @return A + B as a (possibly top-heavy) fraction. 31 | %% 32 | -spec add(A::rational(), B::rational()) -> fraction(). 33 | add(A, B) -> 34 | add_rational(normalize(A), normalize(B)). 35 | 36 | %% @private 37 | add_rational({N1, D1}, {N2, D2}) -> 38 | {N1 * D2 + N2 * D1, D1 * D2}. 39 | 40 | %% 41 | %% @param A rational 42 | %% @param B rational 43 | %% @return A - B as a (possibly top-heavy) fraction. 44 | %% 45 | subtract(A, B) -> 46 | subtract_rational(normalize(A), normalize(B)). 47 | 48 | %% @private 49 | subtract_rational({N1, D1}, {N2, D2}) -> 50 | {N1 * D2 - N2 * D1, D1 * D2}. 51 | 52 | %% 53 | %% @param A rational 54 | %% @param B rational 55 | %% @return A * B as a (possibly top-heavy) fraction. 56 | %% 57 | multiply(A, B) -> 58 | multiply_rational(normalize(A), normalize(B)). 59 | 60 | %% @private 61 | multiply_rational({N1, D1}, {N2, D2}) -> 62 | {N1 * N2, D1 * D2}. 63 | 64 | %% 65 | %% @param A rational 66 | %% @param B rational 67 | %% @return A / B as a (possibly top-heavy) fraction. 68 | %% 69 | divide(A, B) -> 70 | divide_rational(normalize(A), normalize(B)). 71 | 72 | %% @private 73 | divide_rational({0, _}, _) -> 74 | 0; 75 | divide_rational(_, {0, _}) -> 76 | undefined; 77 | divide_rational({N1, D1}, {N2, D2}) -> 78 | {N1 * D2, D1 * N2}. 79 | 80 | %% 81 | %% @param R rational 82 | %% @return (possibly top-heavy) fraction. 83 | %% 84 | -spec normalize(R::rational()) -> fraction(). 85 | normalize(X) when is_integer(X) -> {X, 1}; 86 | normalize({I, {N, D}}) when is_integer(I) andalso is_integer(N) andalso is_integer(D) -> 87 | {I * D + N, D}; 88 | normalize({N, D} = R) when is_integer(N) andalso is_integer(D) -> 89 | R. 90 | 91 | %% 92 | %% @param X (possibly top-heavy) fraction 93 | %% @return simplified rational, a fraction, integer or composite. 94 | %% 95 | -spec simplify(F::fraction()) -> rational(). 96 | % simplify({N, 1}) -> 97 | % N; 98 | % simplify({N, N}) -> 99 | % 1; 100 | % simplify({0, D}) -> 101 | % 0; 102 | % simplify({_N, 0}) -> 103 | % undefined; 104 | simplify({N, D}) when is_integer(D) andalso N > D -> 105 | case N rem D of 106 | 0 -> 107 | {N div D, {0, D}}; 108 | R -> 109 | {N div D, reduce({R, D})} 110 | end; 111 | simplify(X) -> 112 | X. 113 | 114 | %% 115 | %% @param X (possibly top-heavy) fraction 116 | %% @return reduced fraction (possibly divided by gcd). 117 | %% 118 | -spec reduce(F::fraction()) -> fraction(). 119 | reduce({N, D}) when is_integer(N) andalso is_integer(D) -> 120 | case gcd(N, D) of 121 | 1 -> 122 | {N, D}; 123 | G -> 124 | {N div G, D div G} 125 | end. 126 | 127 | %% @private 128 | gcd(A, B) when B > A -> 129 | gcd(B, A); 130 | gcd(A, B) -> 131 | case A rem B of 132 | 0 -> B; 133 | R -> 134 | gcd(B, R) 135 | end. 136 | 137 | %% 138 | %% @param F (possibly top-heavy) fraction 139 | %% @param P desired precision 140 | %% @return equivalent fraction whose denominator is 10^{Precision}. 141 | %% @doc Note that no rounding is performed on the last digit, 142 | %% and that in general the returned fraction is an estimate. 143 | %% 144 | -spec to_decimal(F::fraction(), P::non_neg_integer()) -> rational(). 145 | to_decimal(N, Precision) when is_integer(N) -> 146 | {N, {0, pow(10, Precision)}}; 147 | to_decimal({N, D}, Precision) when is_integer(N) andalso is_integer(D) -> 148 | case simplify({N, D}) of 149 | {I, {N1, D1}} -> 150 | {I, to_decimal({N1, D1}, Precision)}; 151 | {N1, D1} -> 152 | Digits = long_division(D1, N1, Precision, []), 153 | {to_number(Digits), pow(10, Precision)}; 154 | undefined -> 155 | undefined; 156 | N -> 157 | {N, {0, 10}} 158 | end. 159 | 160 | %% @private 161 | long_division(_D, _N1, 0, Accum) -> 162 | Accum; 163 | long_division(D, N1, Precision, Accum) -> 164 | N2 = N1 * 10, 165 | X = N2 div D, 166 | long_division(D, N2 - X * D, Precision - 1, [X | Accum]). 167 | 168 | %% 169 | %% @param R rational 170 | %% @return Rounded off integer 171 | %% 172 | -spec round(R::rational()) -> integer(). 173 | round({I, {N, D} = F}) when is_integer(I) andalso is_integer(N) andalso is_integer(D) -> 174 | I + ?MODULE:round(F); 175 | round({N, D}) when is_integer(N) andalso is_integer(D) -> 176 | case N < (D bsr 1) of 177 | true -> 0; 178 | _ -> 1 179 | end; 180 | round(I) when is_integer(I) -> 181 | I. 182 | 183 | %% @private 184 | to_number(Digits) -> 185 | to_number(Digits, 1, 0). 186 | 187 | %% @private 188 | to_number([], _I, Accum) -> 189 | Accum; 190 | to_number([D|T], I, Accum) -> 191 | to_number(T, I*10, D * I + Accum). 192 | 193 | %% @private 194 | pow(_B, 0) -> 195 | 1; 196 | pow(B, N) -> 197 | B * pow(B, N - 1). 198 | --------------------------------------------------------------------------------