Conexión Remota
En esta sección se mostrará como conectar un celular Android o iPhone al ESP32 y poner enviar comandos via Wi-fi Access Point.
Conexión a través de Wi-Fi
Es posible crear un Access Point directamente desde un ESP32 para facilitar su conexión a dispositivos. Esta funcionalidad permite que cualquier dispositivo conectado al Access Point pueda acceder al servidor hosteado en el ESP32 a través de navegador.
Dependencias de AtomVM
Para obtener acceso a la librería httpd de Erlang, es necesario incluir atomvm_lib en el archivo mix.exs.
defp deps do
[
{:exatomvm, git: "https://github.com/atomvm/ExAtomVM/"},
{:atomvm_lib, git: "https://github.com/atomvm/atomvm_lib/"}
]
end
Ejemplo
A continuación, se incluye un módulo de ejemplo que implementa httpd para servir archivos y una API desde un servidor hosteado en la ESP32.
defmodule ApTest do
@behaviour :httpd_api_handler
@port 8080
def start() do
ap_config = [
ssid: "robot test",
psk: "passpass",
ap_started: fn -> :io.format('Punto de acceso iniciado en 192.168.4.1~n') end
]
{:ok, _network_pid} = :network.start(ap: ap_config)
Process.sleep(1000)
httpd_config = [
# handler para ruta /api
{[<<"api">>],
%{
handler: :httpd_api_handler,
handler_config: %{
module: __MODULE__
}
}},
# handler de archivos para ruta /, expone archivos en directorio /priv
{[],
%{
handler: :httpd_file_handler,
handler_config: %{
app: :serial_test
}
}}
]
{:ok, _httpd_pid} = :httpd.start_link(@port, httpd_config)
:io.format('Servidor web iniciado en http://192.168.4.1:8080~n')
:timer.sleep(:infinity)
end
def handle_api_request(:post, [<<"move">>], http_request, _args) do
direction = http_request.body
move_robot(direction)
{:ok, %{status: "ok"}}
end
def handle_api_request(method, path, _http_request, _args) do
:io.format('Petición no soportada: ~p ~p~n', [method, path])
:not_found
end
defp move_robot(direction) do
:io.format('Comando de movimiento recibido: ~p~n', [direction])
# aqui iría el código para mover el robot
end
end
También es necesario incluir un archivo index.html en el directorio /priv del proyecto (se debe crear si no existe), para acceder a la API a través de una interfaz.
<!DOCTYPE html>
<html>
<head>
<title>Control del Robot</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root {
--bg: #0f1220;
--panel: #1a1e35;
--btn: #2a2f55;
--btn-hover: #3a4090;
--btn-active: #4c53c7;
--text: #e8eaff;
--danger: #c0392b;
}
body {
margin: 0;
min-height: 100vh;
font-family: system-ui, sans-serif;
background: radial-gradient(circle at top, #1a1e35, #0f1220);
color: var(--text);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
h1 {
margin-bottom: 24px;
font-weight: 600;
}
.controls {
background: var(--panel);
padding: 20px;
border-radius: 20px;
display: grid;
grid-template-columns: repeat(3, 80px);
gap: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
}
.controls button {
width: 80px;
height: 80px;
border-radius: 18px;
border: none;
background: linear-gradient(180deg, var(--btn), #1f2340);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
0 6px 0 #14172b,
0 10px 20px rgba(0,0,0,0.4);
transition: transform 0.05s ease, box-shadow 0.05s ease, background 0.15s;
}
.controls button:hover {
background: linear-gradient(180deg, var(--btn-hover), #2b3090);
}
.controls button:active {
transform: translateY(4px);
box-shadow:
0 2px 0 #14172b,
0 4px 10px rgba(0,0,0,0.4);
background: linear-gradient(180deg, var(--btn-active), #3a40b0);
}
.controls svg {
width: 36px;
height: 36px;
fill: var(--text);
}
.forward { grid-column: 2; }
.left { grid-column: 1; }
.stop {
grid-column: 2;
background: linear-gradient(180deg, var(--danger), #8e1f17);
}
.right { grid-column: 3; }
.back { grid-column: 2; }
.stop:hover {
background: linear-gradient(180deg, #e74c3c, #a93226);
}
.status {
margin-top: 24px;
font-size: 14px;
opacity: 0.9;
}
</style>
</head>
<body>
<h1>Control del Robot</h1>
<div class="controls">
<button class="forward" onclick="moveRobot('forward')">
<svg viewBox="0 0 24 24">
<path d="M12 4l-8 8h5v8h6v-8h5z"/>
</svg>
</button>
<button class="left" onclick="moveRobot('left')">
<svg viewBox="0 0 24 24">
<path d="M4 12l8 8v-5h8v-6h-8v-5z"/>
</svg>
</button>
<button class="stop" onclick="moveRobot('stop')">
<svg viewBox="0 0 24 24">
<rect x="6" y="6" width="12" height="12"/>
</svg>
</button>
<button class="right" onclick="moveRobot('right')">
<svg viewBox="0 0 24 24">
<path d="M20 12l-8-8v5h-8v6h8v5z"/>
</svg>
</button>
<button class="back" onclick="moveRobot('back')">
<svg viewBox="0 0 24 24">
<path d="M12 20l8-8h-5v-8h-6v8h-5z"/>
</svg>
</button>
</div>
<div id="status" class="status">Estado: Listo</div>
<script>
function moveRobot(direction) {
document.getElementById("status").innerText = "Estado: Enviando comando..."";
fetch("/api/move", {
method: "POST",
headers: { "Content-Type": "text/html" },
body: direction
})
.then(r => r.json())
.then(data => {
document.getElementById("status").innerText = "Estado: "" + data.status;
})
.catch(() => {
document.getElementById("status").innerText = "Estado: Error de conexión";
});
}
</script>
</body>
</html>
Para acceder a la API, el dispositivo primero se debe conectar a la red Wi-Fi llamada robot test, y luego acceder a 192.168.4.1:8080/index.html.