A tiny Erlang/Elixir VM for microcontrollers
`whoami`
“Typical” AtomVM user
Footnotes, caveats, get out of jail free card
$2.50 Ali Express
make
or cmake
esptool.py
(flashing over USB/serial)* With limitations
spawn
, !
, receive
* Not supported on all platforms
erlang
, timer
, lists
, proplists
gen_server
, gen_statem
gen_tcp
, gen_udp
, inet
io
, io_lib
network_fsm
, http_server
atomvm
, esp
, nvs
gpio
, ledc_pwm
, dht
Use | Type | Size |
---|---|---|
nvs | data | 24k |
phy_init | data | 4k |
atomvm | app | 1M |
main.app | data | 1M |
shell$ erlc foo.erl
shell$ erlc bar.erl
shell$ PackBEAM \
-a foo.avm foo.beam bar.beam \
.../eavmlib.avm \
.../estdlib.avm
Generates an AVM file containing BEAM files from command line args
shell$ esptool.py \
--chip esp32 \
--port /dev/ttyusb0 \
write_flash \
0x110000 \
foo.avm
Flash the AVM file to the start of the main.app
partition (address 0x110000)
AVM file written to main.app
partition; contains beam files in sequence.
-module(hello).
-export([start/0]).
start() ->
io:format("Hello World!~n").
All AVM programs have a start
entrypoint.
-module (blinky).
-export([start/0]).
start() ->
GPIO = gpio:open(),
gpio:set_direction(GPIO, 2, output),
loop(GPIO, 1).
loop(GPIO, Val) ->
gpio:set_level(GPIO, 2, Val),
io:format("Set pin 2 to ~p~n", [Val]),
timer:sleep(1000),
loop(GPIO, 1 - Val).
The “Hello World” of IoT!
-module(pingpong).
-export([start/0]).
start() ->
GPIO = gpio:open(),
gpio:set_direction(GPIO, 2, output),
Pong = spawn(fun() -> pong(GPIO) end),
ping(GPIO, Pong).
ping(GPIO, Pong) ->
Pong ! {ping, self()},
receive
pong ->
gpio:set_level(GPIO, 2, 1),
timer:sleep(1000),
ping(GPIO, Pong)
end.
pong(GPIO) ->
receive
{ping, Pid} ->
gpio:set_level(GPIO, 2, 0),
timer:sleep(1000),
Pid ! pong,
pong(GPIO)
end.
-module (dht_demo).
-export([start/0]).
start() ->
{ok, DHT11} = dht:start(21, dht11),
loop(DHT11).
loop(DHT11) ->
take_measurement(DHT11),
timer:sleep(10000),
loop(DHT11).
take_measurement(DHT) ->
case dht:measure(DHT) of
{ok, Measurement} ->
{Temp, TempFractional, Hum, HumFractional} = Measurement,
io:format(
"Temperature: ~p.~pC Humidity: ~p.~p%~n", [
Temp, TempFractional, Hum, HumFractional]
);
Error ->
io:format("Error taking measurement: ~p~n", [Error])
end.
-module(wifi_demo).
-export([start/0]).
-include("logger.hrl").
start() ->
case atomvm:platform() of
esp32 ->
start_wifi();
_ -> ok
end,
run().
start_wifi() ->
case network_fsm:wait_for_sta() of
{ok, {Address, Netmask, Gateway}} ->
?LOG_INFO(
"IP address: ~s Netmask: ~s Gateway: ~s", [
avm_util:address_to_string(Address),
avm_util:address_to_string(Netmask),
avm_util:address_to_string(Gateway)
]
);
Error ->
?LOG_ERROR("An error occurred starting network: ~p", [Error])
end.
run() ->
avm_util:sleep_forever().
-module(httpd_demo).
-export([start/0, handle_api_request/4]).
-include("logger.hrl").
start() ->
case atomvm:platform() of
esp32 ->
start_wifi();
_ -> ok
end,
run().
start_wifi() ->
case network_fsm:wait_for_sta([{sntp, "pool.ntp.org"}]) of
{ok, {Address, Netmask, Gateway}} ->
?LOG_INFO(
"IP address: ~s Netmask: ~s Gateway: ~s", [
avm_util:address_to_string(Address),
avm_util:address_to_string(Netmask),
avm_util:address_to_string(Gateway)
]
);
Error ->
?LOG_ERROR("An error occurred starting network: ~p", [Error])
end.
-record(opts, {dht, gpio, pin}).
run() ->
GPIO = gpio:open(),
gpio:set_direction(GPIO, 2, output),
{ok, DHT11} = dht:start(21, dht11),
Opts = #opts{dht=DHT11, gpio=GPIO, pin=2},
Config = [{["api"], api_handler, {?MODULE, Opts}}],
?LOG_INFO("Starting httpd on port 8080", []),
case httpd:start(8080, Config) of
{ok, _Pid} ->
?LOG_INFO("httpd started.", []),
avm_util:sleep_forever();
Error ->
?LOG_ERROR("An error occurred: ~p", [Error])
end.
%%
%% API Handler implementation
%%
handle_api_request(get, ["temp"], HttpRequest, #opts{dht=DHT11}) ->
Socket = proplists:get_value(socket, proplists:get_value(tcp, HttpRequest)),
{ok, {Host, _Port}} = inet:peername(Socket),
?LOG_INFO("Temperature request from ~s", [avm_util:address_to_string(Host)]),
{ok, {Temp, TempFractional, Hum, HumFractional}} = dht:measure(DHT11),
{ok, [
{temp, Temp},
{temp_fractional, TempFractional},
{hum, Hum},
{hum_fractional, HumFractional}
]};
handle_api_request(post, ["led"], HttpRequest, #opts{gpio=GPIO, pin=Pin}) ->
Socket = proplists:get_value(socket, proplists:get_value(tcp, HttpRequest)),
{ok, {Host, _Port}} = inet:peername(Socket),
QueryParams = proplists:get_value(query_params, HttpRequest),
case proplists:get_value("led", QueryParams) of
"on" ->
?LOG_INFO("Turning on LED from ~s", [avm_util:address_to_string(Host)]),
{ok, gpio:set_level(GPIO, Pin, 1)};
"off" ->
?LOG_INFO("Turning off LED from ~s", [avm_util:address_to_string(Host)]),
{ok, gpio:set_level(GPIO, Pin, 0)};
_ ->
bad_request
end;
handle_api_request(Method, Path, _HttpRequest, _HandlerOpts) ->
?LOG_ERROR("Unsupported method ~p and path ~p", [Method, Path]),
not_found.
Laptop
shell$ curl -i -X GET "http://atomvm:8080/api/temp"
HTTP/1.1 200 OK
Server: atomvm-httpd
Content-Type: "application/json"
{"temp":22,"temp_fractional":9,"hum":43,"hum_fractional":0}
ESP32 Console
2020-05-03T23:24:21.000 [httpd_demo:handle_api_request/4:52] <0.8.0> info: Temperature request from 192.168.211.13
Laptop
shell$ curl -i -X POST "http://atomvm:8080/api/led?led=on"
HTTP/1.1 200 OK
Server: atomvm-httpd
Content-Type: "application/json"
"ok"
ESP32 Console
2020-05-03T23:26:42.000 [httpd_demo:handle_api_request/4:66] <0.10.0> info: Turning on LED from 192.168.211.13
Laptop
shell$ curl -i -X POST "http://atomvm:8080/api/led?led=off"
HTTP/1.1 200 OK
Server: atomvm-httpd
Content-Type: "application/json"
"ok"
ESP32 Console
2020-05-03T23:27:26.000 [httpd_demo:handle_api_request/4:69] <0.12.0> info: Turning off LED from 192.168.211.13
Laptop
shell$ curl -i -X POST "http://atomvm:8080/api/led?foo=bar"
HTTP/1.1 400 BAD_REQUEST
Server: atomvm-httpd
Content-Type: "text/html"
Error: bad_request
ESP32 Console
2020-05-03T23:27:57.000 [httpd:handle_error/3:174] <0.14.0> error: error in httpd. StatusCode=400 Error=bad_request
Laptop
shell$ curl -i -X GET "http://atomvm:8080/api/led"
HTTP/1.1 404 NOT_FOUND
Server: atomvm-httpd
Content-Type: "text/html"
Error: not_found
ESP32 Console
2020-05-03T23:28:29.000 [httpd_demo:handle_api_request/4:75] <0.16.0> error: Unsupported method get and path ["led"]
2020-05-03T23:28:29.000 [httpd:handle_error/3:174] <0.16.0> error: error in httpd. StatusCode=404 Error=not_found