Рендеринг Vue на встроенный ЖК-экран

внешний интерфейс Vue.js
Рендеринг Vue на встроенный ЖК-экран

предисловие

Я смотрел Big Sprite раньше.Render React на встроенный ЖК-экранЯ нахожу это очень интересным: React можно отображать на встроенном ЖК-экране, так можно ли использовать Vue? Итак, что мы будем делать в этой статье:

Как следует из названия, это визуализация Vue на встроенный ЖК-экран. Используемый здесь ЖК-экран — это SSD1306 с большим 0,96-дюймовым разрешением 128x64. Для рендеринга Vue на ЖК-экран нам также нужен мост, который должен иметь возможность управлять ЖК-экраном и запускать код. Возможности аппаратной стыковки Raspberry Pi и программируемость, естественно, имеют это условие. Последний вопрос: какие технологии мы используем для достижения этой цели?

vue-ssd1306

Здесь я выбрал Node.js. причина:

  • Закон Этвуда: «Любое приложение, которое можно написать на JavaScript, в конечном итоге будет написано на JavaScript».
  • Вождение оборудования У меня есть одна строка Node.jsnpm installИди мир. 🐶

vue-ssd1306

Эту интересную практику можно разбить на следующие этапы:

  • Запуск Vue на Node.js
  • Raspberry Pi подключен к экранному чипу
  • Node.js управляет оборудованием

Talk is cheap,Let's Go!!!

кросс-энд рендеринг

Будь то лозунг «Учись один раз, пиши где угодно», заявленный React Native на основе React, или слоган «Пиши один раз, работай везде», заявленный Weex на основе Vue, они по существу подчеркивают свои возможности кросс-конечного рендеринга. Так что же такое кросс-энд рендеринг?

React: ReactNative Taro ...

Vue: Weex UniApp ...

Появились всевозможные интерфейсные фреймворки, и фронтенд-инженеры жаловались, что не могут учиться~

Боссы смеялись и смеялись, когда увидели это, список приложений, оценку внешнего интерфейса, список апплетов, ласточку внешнего интерфейса, PC/H5, обморок внешнего интерфейса. скр~

Принципы этих кросс-платформенных фреймворков в основном одинаковы. В качестве DSL выбран Vue/React, а фреймворк DSL используется в качестве стандарта для компиляции на каждом конце. Во время выполнения каждый конец использует свой собственный механизм рендеринга (Render Engines). ) для рендеринга и базовый механизм рендеринга. Вам не нужно заботиться о синтаксисе и стратегии обновления DSL верхнего уровня, вам нужно иметь дело только со структурой узла и инструкциями рендеринга, единообразно определенными в JS Framework. Именно из-за абстракции этого слоя рендеринга возможна кроссплатформенность/фреймворк.

vue-ssd1306

И Vue, и React теперь реализуют собственные рендереры, поэтому давайте кратко представим их:

React Reconciler

React16 использует новый Reconciler, который использует внутреннюю архитектуру Fiber.react-reconcilerМодули основаны на новой реализации Reconciler версии 16, которая предоставляет возможность создавать собственные средства визуализации React.

const Reconciler = require("react-reconciler");

const HostConfig = {
  // You'll need to implement some methods here.
  // See below for more information and examples.
};

const MyRenderer = Reconciler(HostConfig);

const RendererPublicAPI = {
  render(element, container, callback) {
    // Call MyRenderer.updateContainer() to schedule changes on the roots.
    // See ReactDOM, React Native, or React ART for practical examples.
  },
};

module.exports = RendererPublicAPI;

Vue createRenderer

vue3 предоставляетcreateRender API, давайте создадим собственный рендерер.

Функция createRenderer принимает два общих параметра: HostNode и HostElement, соответствующие типам Node и Element в среде размещения. Пользовательские средства визуализации могут передавать типы, зависящие от платформы, например:

import { createRenderer } from 'vue'
const { render, createApp } = createRenderer<Node, Element>({
  patchProp,
  ...nodeOps
})

vue-ssd1306

Запуск Vue на Node.js

SFC To JS

<template>
  <text x="0" y="0">Hello Vue</text>
  <text x="0" y="20">{{ time }}</text>
  <text x="0" y="40">Hi SSD3306</text>
</template>
<script>
import { defineComponent, ref, toRefs, onMounted } from "vue";
import dayjs from "dayjs";
export default defineComponent({
  setup() {
    const time = ref(dayjs().format("hh:mm:ss"));
    onMounted(() => {
      setInterval(() => {
        time.value = dayjs().format("hh:mm:ss");
      }, 800);
    });
    return {
      ...toRefs({
        time,
      }),
    };
  },
});
</script>

Чтобы отобразить Vue на ЖК-экране, нам сначала нужно включить Vue для работы на Node.js, но вышеупомянутый SFC не может быть распознан Node.js, это всего лишь спецификация программирования Vue, который является диалектом. Итак, что нам нужно сделать, это сначала преобразовать SFC в js. Здесь я использую пакет Rollup для преобразования SFC в JS (соответствующая конфигурация здесь не многословна, вставьте еепортал). На данный момент Node.js может успешно запускать упакованный код js, чего недостаточно.В настоящее время обновление состояния компонента Vue не может быть синхронизировано с Node.js.

Create Custom Renderer

Обновление состояния компонента Нам нужно уведомить Node.js об обновлении и отображении содержимого ЖК-экрана, нам нужно создать пользовательскую «стратегию обновления». Здесь нам нужно использовать пользовательский рендерер, о котором мы упоминали ранее: createRenderer API. Ниже мы кратко представляем наше родственное использование:

// index.js
// 自定义渲染器
import { createApp } from "./renderer.js";
// 组件
import App from "./App.vue";
// 容器
function getContainer() {
  // ...
}
// 创建渲染器,将组件挂载到容器上
createApp(App).mount(getContainer());
// renderer.js

import { createRenderer } from "vue";
// 定义渲染器,传入自定义nodeOps
const render = createRenderer({
  // 创建元素
  createElement(type) {},
  // 插入元素
  insert(el, parent) {},
  // props更新
  patchProp(el, key, preValue, nextValue) {},
  // 设置元素文本
  setElementText(node, text) {},
  // 以下忽略,有兴趣的童鞋可自行了解
  remove(el) {},
  createText(type) {},
  parentNode(node) {},
  nextSibling(nide) {},
});

export function createApp(root) {
  return render.createApp(root);
}

Рендерер vue по умолчанию реализует программный интерфейс DOM веб-платформы и визуализирует виртуальный DOM в реальный DOM. Но этот рендерер может работать только в браузере и не имеет кроссплатформенных возможностей. Следовательно, мы должны переписать связанные с nodeOps функции-ловушки, чтобы реализовать операции добавления, удаления, изменения и запроса соответствующих элементов среды хоста. Затем мы определяем адаптер для реализации соответствующей логики.

Adapter

Перед реализацией давайте взглянем на логику, которую мы хотим реализовать:

  • Создать экземпляр элемента (создать)

  • Вставьте экземпляр элемента в контейнер, управляемый контейнером (insert)

  • При изменении состояния уведомить контейнер об обновлении (update)

// adapter.js

// 文本元素
export class Text {
  constructor(parent) {
    // 提供一个父节点用于寻址调用更新 (前面提到状态更新由容器进行)
    this.parent = parent;
  }
  // 元素绘制,这里需要实现文本元素渲染逻辑
  draw(text) {
    console.log(text);
  }
}
// 适配器
export class Adapter {
  constructor() {
    // 装载容器
    this.children = [];
  }
  // 装载子元素
  append(child) {
    this.children.push(child);
  }
  // 元素状态更新
  update(node, text) {
    // 找到目标渲染进行绘制
    const target = this.children.find((child) => child === node);
    target.draw(text);
  }
  clear() {}
}
// 容器 === 适配器实例
export function getContainer() {
  return new Adapter();
}

Хорошо, базовый адаптер готов, давайте реализуем рендерер.

Renderer Abstract

import { createRenderer } from "vue";

import { Text } from "./adapter";
let uninitialized = [];
const render = createRenderer({
  // 创建元素,实例化Text
  createElement(type) {
    switch (type) {
      case "text":
        return new Text();
    }
  },
  // 插入元素,调用适配器方法进行装载统一管理
  insert(el, parent) {
    if (el instanceof Text) {
      el.parent = parent;
      parent.append(el);
      uninitialized.map(({ node, text }) => el.parent.update(node, text));
    }
    return el;
  },
  // props更新
  patchProp(el, key, preValue, nextValue) {
    el[key] = nextValue;
  },
  // 文本更新,重新绘制
  setElementText(node, text) {
    if (node.parent) {
      console.log(text);
      node.parent.clear(node);
      node.parent.update(node, text);
    } else {
      uninitialized.push({ node, text });
    }
  },
  remove(el) {},
  createText(type) {},
  parentNode(node) {},
  nextSibling(nide) {},
});

export function createApp(root) {
  return render.createApp(root);
}

Raspberry Pi подключен к экранному чипу

SSD1306 OLED

OLED, а именно органический светоизлучающий диод (Organic Light Emitting Diode). Это жидкокристаллический дисплей. SSD1306 — это микросхема драйвера OLED. Сам ssd1306 поддерживает различные методы управления шиной: параллельный порт 6800/8080, методы интерфейса SPI и IIC. Здесь мы выбираем интерфейс IIC для связи, причина очень проста: 1. Проводка проста и удобна (два провода могут управлять OLED) 2. Колеса легко найти... Недостаток в том, что передача данных IIC эффективность слишком низкая, а частота обновления составляет всего 10 кадров в секунду. Частота обновления SPI может достигать максимум 2200 кадров в секунду.

аппаратная проводка

Для IIC требуется всего 4 провода, 2 из которых — питание, а остальные 2 — SDA и SCL. Мы используем интерфейс IIC-1. Ниже приведена схема контактов GPIO Raspberry Pi.

vue-ssd1306

Примечание. Обратитесь к фактическому номеру контакта на экране.

  • Экран VCC подключен к контакту 1 Raspberry Pi. - блок питания 3,3 В

  • Экран GND подключен к контакту 9 Raspberry Pi. - заземляющий провод

  • Экран SDA подключен к контакту 3 Raspberry Pi. - Контакты данных в связи IIC

  • Экран SCL подключен к контакту 5 Raspberry Pi. - Тактовый вывод в связи IIC

Raspberry Pi поддерживает I2C

1. Установите инструментарий

sudo apt-get install -y i2c-tools

2. Включите I2C

  • sudo raspi-config

  • Выберите параметры взаимодействия

  • Enable I2C

3. Проверьте статус подключения устройства

Команда i2cdetect, предоставляемая i2c-tools, может просматривать смонтированное устройство.

sudo i2cdetect -y 1

Node.js управляет оборудованием

Node.js Lib

Давайте сначала взглянем на несколько библиотек Node.js.После прочтения вам придется вздохнуть ~ любое приложение, которое можно написать на JavaScript, самое...

johnny-five

Johnnt-Five — это платформа для разработки роботов и Интернета вещей, поддерживающая программирование на языке JavaScript на основе протокола Firmata. Firmata — это протокол связи между компьютерным программным обеспечением и микроконтроллерами. Используя его, мы можем легко построить мост между Raspberry Pi и чипом экрана.

vue-ssd1306

raspi-io

Raspi IO — это подключаемый модуль ввода-вывода для робототехнической платформы Johnny-Five Node.js, который позволяет Johnny-Five управлять оборудованием на Raspberry Pi.

oled-font-5x7

Библиотека шрифтов 5x7 oled, преобразование символов в шестнадцатеричное кодирование, чтобы oled-программы могли распознавать. Используется для рисования текста.

oled-js

📺 Совместим с библиотекой поддержки oled от johnny-five (сам johnny-five не поддерживает oled), предоставляя API для управления oled.

реализация драйвера

// oled.js
const five = require("johnny-five");
const Raspi = require("raspi-io").RaspiIO;
const font = require("oled-font-5x7");
const Oled = require("oled-js");
const OPTS = {
  width: 128, // 分辨率  0.96寸 ssd1306 128*64
  height: 64, // 分辨率
  address: 0x3c, // 控制输入地址,ssd1306 默认为0x3c
};
class OledService {
  constructor() {
    this.oled = null;
  }
  /**
   * 初始化: 创建一个Oled实例
   * 创建后,我们就可以通过操作Oled实例来控制屏幕了
   */
  init() {
    const board = new five.Board({
      io: new Raspi(),
    });
    // 监听程序退出,关闭屏幕
    board.on("exit", () => {
      this.oled && this.remove();
    });
    return new Promise((resolve, reject) => {
      board.on("ready", () => {
        // Raspberry Pi connect SSD 1306
        this.oled = new Oled(board, five, OPTS);
        // 打开屏幕显示
        this.oled.turnOnDisplay();
        resolve();
      });
    });
  }
  // 绘制文字
  drawText({ text, x, y }) {
    // 重置光标位置
    this.oled.setCursor(+x, +y);
    // 绘制文字
    this.oled.writeString(font, 2, text, 1, true, 2);
  }
  clear({ x, y }) {
    this.oled.setCursor(+x, +y);
  }
  // 刷新屏幕
  update() {
    this.oled.update();
  }
  remove() {
    // 关闭显示
    this.oled.turnOffDisplay();
    this.oled = null;
  }
}
export function oledService() {
  return new OledService();
}

Затем мы можем вызвать программу oled в адаптере для рендеринга экрана~

// index.js
import { createApp } from "./renderer.js";
import { getContainer } from "./adapter";
import { oledService } from "./oled";
import App from "./App.vue";
const oledIns = oledService();
oledIns.init().then(() => {
  createApp(App).mount(getContainer(oledIns));
});

// adapter.js
export class Text {
  constructor(parent) {
    this.parent = parent;
  }
  draw(ints, opts) {
    ints.drawText(opts);
    ints.update();
  }
}

export class Adapter {
  constructor(oledIns) {
    this.children = [];
    this.oled = oledIns;
  }
  append(child) {
    this.children.push(child);
  }
  update(node, text) {
    const target = this.children.find((child) => child === node);
    target.draw(this.oled, {
      text,
      x: node.x,
      y: node.y,
    });
  }
  clear(opts) {
    this.oled.clear(opts);
  }
}
export function getContainer(oledIns) {
  return new Adapter(oledIns);
}

На этом этапе вы можете успешно осветить экран, давайте посмотрим на эффект~

Показать результаты

vue-ssd1306

Ссылаться на

Render React на встроенный ЖК-экран

Использование OLED-экрана SSD1306 на Raspberry Pi

Эпилог

Полный код загружен наGithub, если вы считаете, что эта практика вдохновляет/помогает вам, нажмитеstarНу~

Vue был успешно отрендерен на встроенный ЖК-экран, так что не могли бы вы на следующем этапе написать игру про змейку с помощью джойстика?

Обучение «чтению» вызывает у меня сонливость, поэтому я предпочитаю впитывать знания через какую-нибудь интересную практику. Если вы любите бросать, как я, добро пожаловатьобрати внимание на~