VueJs, Vuetify y Axios permiten crear una interfaz web para un ESP32 con API REST y estética Material Design.
El ejemplo está orientado a ESP32. En muchos casos también puede adaptarse a ESP8266 cambiando librerías y algunos detalles de pines.
La arquitectura combina tres piezas: el ESP32 como backend que sirve el API REST, Axios como cliente HTTP en el navegador y VueJs/Vuetify para construir la interfaz.
Nuestra aplicación podría ser un buen ejemplo de arquitectura pero… reconozcamos que era fea como ella sola. ¡Pero esto está a punto de cambiar! Para ello vamos a incorporar a nuestro coctel Vuetify, que ya vimos en esta entrada.

El objetivo es conseguir una funcionalidad parecida a la anterior, es decir una demo que muestre cómo realizar las distintas acciones a través del API Rest, pero con esta estética más moderna y adecuada.
El código del backend, es decir, del ESP32, mantiene la misma estructura que en los ejemplos anteriores de API REST. Aquí nos centraremos en la parte del frontend.
Lo que va a cambiar es la página web servida.
El fichero index.html pasa a ser
<!DOCTYPE html>
<html>
<head>
<title>ESP32 Vuetify + Axios</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Desde CDN -->
<!--<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">-->
<!--<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">-->
<link href="./vendor/google-fonts.css" rel="stylesheet">
<link href="./vendor/vuetify.min.css" rel="stylesheet">
</head>
<body>
<div id="app">
<v-app id="inspire">
<v-container style="max-width: 600px">
<v-form>
<v-container>
<v-layout row>
<v-flex xs12 sm6 md3>
<v-btn color="info" @click="getAll">Get All</v-btn>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12 sm6 md3>
<v-btn color="info" @click="getById">Get</v-btn>
</v-flex>
<v-flex xs12 sm6 md3>
<v-text-field v-model.number="getId" label="Id" solo type="number"></v-text-field>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12 sm6 md3>
<v-btn color="info" @click="updateById">Update</v-btn>
</v-flex>
<v-flex xs12 sm6 md3>
<v-text-field v-model.number="updateId" label="Id" solo type="number"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md3>
<v-text-field v-model="updateText" label="Text" solo></v-text-field>
</v-flex>
</v-layout>
</v-container>
</v-form>
<v-divider class="mb-3"></v-divider>
<v-text-field v-model="newItem" label="Add new item" solo @keydown.enter="create">
<v-fade-transition slot="append">
<v-icon v-if="newItem" @click="create">add_circle</v-icon>
</v-fade-transition>
</v-text-field>
<v-card>
<v-toolbar color="cyan" dark>
<v-toolbar-title>Total: {{ items.length }}</v-toolbar-title>
</v-toolbar>
<v-fade-transition class="py-0" group tag="v-list">
<template v-for="(item, i) in items">
<v-divider v-if="i !== 0" :key="`${i}-divider`" ></v-divider>
<v-list-tile :key="`${i}-${item.text}`">
<v-list-tile-action>
<v-list-tile-title v-text="item.id"></v-list-tile-title>
</v-list-tile-action>
<v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-text="item.text"></v-list-tile-title>
</v-list-tile-content>
</v-list-tile-action>
<v-spacer></v-spacer>
<div>
<v-btn color="error" v-model="item" @click="remove(item)">Delete</v-btn>
</div>
</v-list-tile>
</template>
</v-fade-transition>
<v-snackbar v-model="snackbar" :bottom="true" :timeout="timeout">
{{ text }}
<v-btn color="pink" flat @click="snackbar = false">
Close
</v-btn>
</v-snackbar>
</v-card>
</v-container>
</v-app>
</div>
<!-- Desde CDN -->
<!--<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>-->
<!--<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.js"></script>-->
<!--<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>-->
<script type="text/javascript" src="./vendor/vue.min.js"></script>
<script type="text/javascript" src="./vendor/vuetify.min.js"></script>
<script type="text/javascript" src="./vendor/axios.min.js"></script>
<script type="text/javascript" src="./js/API.js"></script>
<script type="text/javascript" src="./js/app.js"></script>
</body>
</html>
El fichero JavaScript de la página también debe modificarse para adaptarse al nuevo Framework Vuetify, quedando así.
new Vue({
el: '#app',
data: () => ({
items: [
{
id: 0,
done: false,
text: 'Init 0'
}
],
newItem: null,
getId: null,
updateId: null,
updateText: null,
snackbar: false,
timeout: 1500,
text: '',
lastId : 0,
}),
computed: {
},
methods: {
create () {
lastId = Math.max(...this.items.map(d => d.id));
this.items.push({
id: ++this.lastId,
done: false,
text: this.newItem
})
API_post(this.newItem)
this.newItem = null
},
getAll() {
this.text = "Getting all"
this.snackbar = true
API_getAll()
lastId = Math.max(...this.items.map(d => d.id));
this.items.push({id: ++this.lastId, text: 'Loaded all '})
this.items.push({id: ++this.lastId, text: 'Loaded all '})
},
getById() {
if(this.getId == null) return;
if(((typeof this.items.find(x => x.id === this.getId) !== 'undefined'))) return;
this.text = "Getting by Id " + this.getId
this.snackbar = true
API_get(this.getId)
this.items.push({id: this.getId, text: 'Loaded by Id'})
},
updateById(id) {
if(this.updateId == null || this.updateId == null) return;
this.text = "Updating " + this.updateId + " with " + this.updateText
this.snackbar = true
API_put(this.updateId, this.updateText)
this.items.find(x => x.id === this.updateId).text = this.updateText
},
remove(item) {
this.text = "Deleting " + item.id
this.snackbar = true
API_delete(item.id)
this.items.splice(this.items.indexOf(item), 1)
}
}
})
Resultado
Subimos nuestros contenidos al SPIFFS como estamos acostumbrados, y pulsamos los distintos botones para comprobar que todo funciona correctamente en la consola de desarrollo del navegador.

Así como que llegan correctamente al ESP32 como backend, a través del terminal del puerto serie.

Con esto hemos integrado API REST, JSON, Axios, Vue y Vuetify en una demo funcional y bastante más agradable de usar. La misma estructura se puede adaptar a paneles de control, configuradores o pequeñas interfaces de mantenimiento para proyectos con ESP32.
Descarga el código
Todo el código de esta entrada está disponible para su descarga en Github.
Versión para el ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples
Versión para el ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples

