como-usar-vuetify-con-esp8266

Cómo usar Vuetify con ESP32

  • 4 min

Vuetify permite aplicar Material Design a aplicaciones VueJS servidas desde un ESP32.

El ejemplo está orientado a ESP32. En muchos casos también puede adaptarse a ESP8266 cambiando librerías y algunos detalles de pines.

Cuando construimos una interfaz web para el ESP32, VueJS nos ayuda a organizar la lógica de la aplicación. Vuetify añade una capa de componentes visuales con estética Material Design.

La idea es conseguir una interfaz más agradable sin tener que escribir todos los estilos y componentes a mano.

Aquí es donde entra en juego Vuetify, un framework que ya vimos en su día, que aporta un montón de componentes para realizar aplicaciones en VueJS con un interface agradable, moderno, y que no te den ganas de arrancarte los ojos cuando lo veas.

Al igual que la anterior, esta entrada de introducción al framework va a ser muy sencilla. Únicamente nos vamos a centrar en presentarlo, y hacer una pequeña App “hola mundo”, y verificar que funciona correctamente.

Nuestro programa principal sigue siendo sencillo y apenas necesita cambios.

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>

#include "config.h"  // Sustituir con datos de vuestra red
#include "Server.hpp"
#include "ESP32_Utils.hpp"

void setup(void)
{
  Serial.begin(115200);
  SPIFFS.begin();
  
  ConnectWiFi_STA();
  
  InitServer();
}

void loop(void)
{
}
Copied!

Así como la declaración del servidor en el fichero “Server.hpp”,

AsyncWebServer server(80);

void InitServer()
{
  server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");

  server.onNotFound([](AsyncWebServerRequest *request) {
    request->send(400, "text/plain", "Not found");
  });

  server.begin();
    Serial.println("HTTP server started");
}
Copied!

Lo que va a cambiar es el lado del cliente,

Nuestro fichero ‘index.html’ es más largo que el anterior. Pero es el “precio” de tener una estética mejor… más líneas de código.

<!DOCTYPE html>
<html>
<head>
  <title>ESP32 Vuetify</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: 500px">
      <v-text-field
        v-model="task"
        label="What are you working on?"
        solo
        @keydown.enter="create"
      >
        <v-fade-transition slot="append">
          <v-icon
            v-if="task"
            @click="create"
          >
            add_circle
          </v-icon>
        </v-fade-transition>
      </v-text-field>
  
      <h2 class="display-1 success--text pl-3">
        Tasks:&nbsp;
        <v-fade-transition leave-absolute>
          <span :key="`tasks-${tasks.length}`">
            {{ tasks.length }}
          </span>
        </v-fade-transition>
      </h2>
  
      <v-divider class="mt-3"></v-divider>
  
      <v-layout
        my-1
        align-center
      >
        <strong class="mx-3 info--text text--darken-3">
          Remaining: {{ remainingTasks }}
        </strong>
  
        <v-divider vertical></v-divider>
  
        <strong class="mx-3 black--text">
          Completed: {{ completedTasks }}
        </strong>
  
        <v-spacer></v-spacer>
  
        <v-progress-circular
          :value="progress"
          class="mr-2"
        ></v-progress-circular>
      </v-layout>
  
      <v-divider class="mb-3"></v-divider>
  
      <v-card v-if="tasks.length > 0">
        <v-slide-y-transition
          class="py-0"
          group
          tag="v-list"
        >
          <template v-for="(task, i) in tasks">
            <v-divider
              v-if="i !== 0"
              :key="`${i}-divider`"
            ></v-divider>
  
            <v-list-tile :key="`${i}-${task.text}`">
              <v-list-tile-action>
                <v-checkbox
                  v-model="task.done"
                  color="info darken-3"
                >
                  <div
                    slot="label"
                    :class="task.done && 'grey--text' || 'text--primary'"
                    class="ml-3"
                    v-text="task.text"
                  ></div>
                </v-checkbox>
              </v-list-tile-action>
  
              <v-spacer></v-spacer>
  
              <v-scroll-x-transition>
                <v-icon
                  v-if="task.done"
                  color="success"
                >
                  check
                </v-icon>
              </v-scroll-x-transition>
            </v-list-tile>
          </template>
        </v-slide-y-transition>
      </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 type="text/javascript" src="./vendor/vue.min.js"></script>
  <script type="text/javascript" src="./vendor/vuetify.min.js"></script>

  <script src="./js/app.js"></script>
</body>
</html>
Copied!

Por su parte, la definición de la aplicación en Vue también ha cambiado, para adaptarse a los requisitos de Vuetify, pasando a ser,

new Vue({
    el: '#app',
    data: () => ({
      tasks: [
        {
          done: false,
          text: 'Foobar'
        },
        {
          done: false,
          text: 'Fizzbuzz'
        }
      ],
      task: null
    }),
  
    computed: {
      completedTasks () {
        return this.tasks.filter(task => task.done).length
      },
      progress () {
        return this.completedTasks / this.tasks.length * 100
      },
      remainingTasks () {
        return this.tasks.length - this.completedTasks
      }
    },
  
    methods: {
      create () {
        this.tasks.push({
          done: false,
          text: this.task
        })
  
        this.task = null
      }
    }
  })
Copied!

Resultado

Subimos todo al ESP32 y cargamos la página en el navegador. Volvemos a tener una interfaz con estética Material Design, agradable y bonita, a la vez que mantenemos las ventajas que nos aporta VueJS.

esp8266-vuetify-resultado

Con esto podemos construir interfaces más cuidadas para proyectos con ESP32, manteniendo la lógica declarativa de VueJS y aprovechando los componentes ya preparados de Vuetify.

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github.

github-full

Versión para el ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Versión para el ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples