Skip to content

feat: addition of the APIRequest example in Elixir folder #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions elixir/APIRequest/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
6 changes: 6 additions & 0 deletions elixir/APIRequest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
_build/*
_checkouts/*
src/config.erl
rebar.lock
*.avm
**.beam
9 changes: 9 additions & 0 deletions elixir/APIRequest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# `APIRequest` Application

Welcome to the `APIRequest` AtomVM application.

The `APIRequest` AtomVM application connects to a wifi, and when the connection is established retrieves and prints on console a GET request over https.
Copy link
Collaborator

@UncleGrumpy UncleGrumpy Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could change this to "...is established makes a GET request over https and prints the result to the console." Reading this I half expected the ESP32 to be the server getting the API request from a client, and printing the actual request on a the console.

That would be a strange example, but there were enough details left out to make it hard to be certain which details were missing, especially for non-native English speakers. ;-)


For more information about programming on the AtomVM platform, see the [AtomVM Programmers Guide](https://www.atomvm.net/doc/master/programmers-guide.html).

For general information about building and executing Elixir AtomVM example programs, see the Elixir example program [README](../README.md).
151 changes: 151 additions & 0 deletions elixir/APIRequest/lib/APIRequest.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#
# This file is part of AtomVM.
#
# Copyright 2018 Davide Bettio <[email protected]>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You wrote this, you should give yourself credit here ;-)

#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
#

defmodule APIRequest do
def start() do
:io.format(~c"starting API Request application~n")
spawn(fn -> start_network() end)

case wait_for_network() do
:ok ->
:io.format(~c"Connected to WiFi! Making HTTP request...~n")

{:error, reason} ->
:io.format(~c"Failed to connect to WiFi: ~p~n", [reason])
end
end

defp start_network() do
:io.format(~c"Starting network...~n")

config = [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could choose to make this a demo, but if you would like to keep it as a simple example instead, I recommend using : wait_for_sta/1 (or /2) and not use the callbacks. The goal for the examples is to keep them to a very limited scope. If you would like to submit a separate PR with an Elixir example for connecting to WiFi and using the callbacks (like the Erlang wifi example) that would be a very welcome addition too!

sta: [
connected: fn ->
:io.format(~c"WiFi connected~n")
spawn(fn -> make_request() end)
end,
got_ip: fn info -> :io.format(~c"Got IP: ~p~n", [info]) end,
disconnected: fn -> :io.format(~c"WiFi disconnected~n") end,
# Edit these values for your network:
ssid: ~c"SSID_NAME",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want hard coded credentials in the app. If someone makes a PR to update this PR in the future (maybe to accommodate AtomVM API updates) it would be easy for someone to accidentally push their credentials to GitHub. These values should be read from a config file (which a template should be provided for... I.E. config.exs-template) that should be edited by end users a renamed to the actual config file name (config.exs). The real configuration file should be added to .gitignore so credentials can never be accidentally pushed to GitHub.

psk: ~c"SSID_PASSWORD"
]
]

case verify_platform(:atomvm.platform()) do
:ok ->
:io.format(~c"Starting network...~n")

case :network.start(config) do
{:ok, _pid} ->
:io.format(~c"Network started.~n")
# Keep the network process alive
Process.sleep(:infinity)

error ->
error
end

error ->
error
end
end

defp verify_platform(:esp32), do: :ok
defp verify_platform(platform), do: {:error, {:unsupported_platform, platform}}

defp wait_for_network() do
:io.format(~c"Waiting for network...~n")

receive do
{:network, :got_ip, _info} -> :ok
after
30_000 -> {:error, :timeout}
end
end

defp make_request() do
:io.format(~c"Making HTTPS request to api.sunrisesunset.io API...~n")

ssl_opts = [{:active, false}, {:verify, :verify_none}]
ok = :ssl.start()

case :ahttp_client.connect(:https, ~c"api.sunrisesunset.io", 443, ssl_opts) do
{:ok, conn} ->
case :ahttp_client.request(conn, ~c"GET", ~c"/json?lat=38.907192&lng=-77.036873", [], nil) do
{:ok, conn, ref} ->
:io.format(~c"Connection established, receiving response~n")
receive_response(conn, ref, [])

error ->
:io.format(~c"Request failed: ~p~n", [error])
end

error ->
:io.format(~c"Connection failed: ~p~n", [error])
end

ok = :ssl.stop()
end

defp receive_response(conn, ref, acc) do
case :ahttp_client.recv(conn, 0) do
{:ok, conn, responses} ->
process_responses(conn, ref, responses, acc)

error ->
:io.format(~c"Receive failed: ~p~n", [error])
end
end

defp process_responses(conn, ref, [], acc) do
:ahttp_client.close(conn)

case acc do
[data | _] ->
:io.format(~c"Data: ~p~n", [data])

_ ->
:ok
end
end

defp process_responses(conn, ref, [{:status, ref, status} | rest], acc) do
:io.format(~c"Status: ~p~n", [status])
process_responses(conn, ref, rest, acc)
end

defp process_responses(conn, ref, [{:header, ref, {name, value}} | rest], acc) do
:io.format(~c"Header: ~s: ~s~n", [name, value])
process_responses(conn, ref, rest, acc)
end

defp process_responses(conn, ref, [{:data, ref, data} | rest], acc) do
process_responses(conn, ref, rest, [data | acc])
end

defp process_responses(conn, ref, [{:done, ref} | rest], acc) do
process_responses(conn, ref, rest, acc)
end

defp process_responses(conn, ref, [_response | rest], acc) do
process_responses(conn, ref, rest, acc)
end
end
31 changes: 31 additions & 0 deletions elixir/APIRequest/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
defmodule APIRequest.MixProject do
use Mix.Project

def project do
[
app: :APIRequest,
version: "0.1.0",
elixir: "~> 1.13",
start_permanent: Mix.env() == :prod,
deps: deps(),
atomvm: [
start: APIRequest,
flash_offset: 0x250000
]
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:exatomvm, git: "https://github.com/atomvm/ExAtomVM/"}
]
end
end
2 changes: 1 addition & 1 deletion elixir/Blinky/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Blinky.MixProject do
deps: deps(),
atomvm: [
start: Blinky,
flash_offset: 0x210000
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make this correction in a separate PR.

flash_offset: 0x250000
]
]
end
Expand Down
2 changes: 1 addition & 1 deletion elixir/HelloWorld/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule HelloWorld.MixProject do
deps: deps(),
atomvm: [
start: HelloWorld,
flash_offset: 0x210000
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in a separate PR (it can be together with the above correction) we just want to keep the new additions in a different commit from the corrections to other examples. It makes the commit log cleaner and more useful.

flash_offset: 0x250000
]
]
end
Expand Down