將 Vue 渲染到嵌入式液晶屏

image

前言

以前看了雪碧大佬的將 React 渲染到嵌入式液晶屏以爲頗有意思,React能被渲染到嵌入式液晶屏,那Vue是否是也能夠呢?因此本文咱們要作的就是: html

如標題所示,就是將Vue渲染到嵌入式液晶屏。這裏使用的液晶屏是0.96 寸大128x64分辨率的SSD1306。要將Vue渲染到液晶屏,咱們還須要一個橋樑,它必須具有控制液晶屏及運行代碼的能力。而樹莓派的硬件對接能力和可編程性自然就具有這個條件。最後一個問題來了,咱們用什麼技術來實現呢?前端

vue-ssd1306

這裏我選擇了 Node.js。緣由:vue

  • Atwood 定律:「任何能夠使用 JavaScript 來編寫的應用,最終會由 JavaScript 編寫。」 🐶
  • 驅動硬件我大 Node.js 一行npm install走天下。 🐶

vue-ssd1306

這個有趣的實踐可拆分爲這幾個步驟:node

  • 在 Node.js 運行 Vue
  • 樹莓派鏈接屏幕芯片
  • Node.js 驅動硬件

Talk is cheap,Let's Go!!!react

跨端渲染

不管是 基於 React 的 React Native 宣稱的「Learn Once, Write Anywhere」,仍是基於 Vue 的 Weex 宣稱的「Write Once, Run Everywhere」口號,本質上強調的都是它們跨端渲染的能力。那什麼是跨端渲染呢?git

React: ReactNative Taro ...github

Vue: Weex UniApp ...web

各類五花八門的前端框架紛紛襲來,前端工程師們紛紛抱怨學不動了~npm

老闆們看到紛紛笑嘻嘻, App 單,前端分,小程序單,前端吞,PC/H5,前端昏。skr~編程

這些跨平臺框架原理其實都大同小異,選定 Vue/React 做爲 DSL,以這個 DSL 框架爲標準在各端分別編譯,在運行時,各端使用各自的渲染引擎(Render Engines)進行渲染,底層渲染引擎中沒必要關心上層 DSL 的語法和更新策略,只須要處理 JS Framework 中統必定義的節點結構和渲染指令。也正是由於這一渲染層的抽象,使得跨平臺/框架成爲了可能。

vue-ssd1306

Vue 和 React 如今都實現了自定義渲染器,下面咱們簡單介紹一下:

React Reconciler

React16 採用新的 Reconciler,內部採用了 Fiber 的架構。react-reconciler模塊正是基於 v16 的新 Reconciler 實現,它提供了建立 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,對應於宿主環境中的 節點 和 元素 類型。
自定義渲染器能夠傳入特定於平臺的類型,以下所示:

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

vue-ssd1306

在 Node.js 上運行 Vue

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 渲染器默認實現了 Web 平臺 DOM 編程接口,將 Virtual DOM 渲染爲真實 DOM。可是這個渲染器只能運行在瀏覽器中,不具有跨平臺能力。因此咱們必須重寫 nodeOps 相關鉤子函數,實現對應宿主環境元素的增刪改查操做。接下來咱們定義一個適配器,來實現相關邏輯。

Adapter

在實現前,咱們先來理一下咱們要實現的邏輯:

  • 建立元素實例 (create)
  • 將元素實例插入容器,由容器進行管理 (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);
}

樹莓派鏈接屏幕芯片

SSD1306 OLED

OLED,即有機發光二極管( Organic Light Emitting Diode)。是一種液晶顯示屏。而 SSD1306 就是一種 OLED 驅動芯片。ssd1306 自己支持多種總線驅動方式:6800/8080 並口、SPI 及 IIC 接口方式。這裏咱們選擇 IIC 接口方式進行通訊,理由很簡單: 1. 接線簡單方便(兩根線就能夠驅動 OLED) 2.輪子好找...缺點就是 IIC 傳輸數據效率太慢了,刷新率只有 10FPS 不到。而 SPI 刷新率最大能達到 2200FPS。

硬件接線

IIC 僅須要 4 根線就能夠,其中 2 根是電源,另外 2 根是 SDA 和 SCL。咱們使用 IIC-1 接口。下面是樹莓派的 GPIO 引腳圖。

vue-ssd1306

注意:請必定以屏幕的實際引腳編號爲準。

  • 屏幕 VCC 接樹莓派 1 號引腳。- 3.3v 電源
  • 屏幕 GND 接樹莓派 9 號引腳。- 地線
  • 屏幕 SDA 接樹莓派 3 號引腳。- IIC 通訊中爲數據管腳
  • 屏幕 SCL 接樹莓派 5 號引腳。- IIC 通訊中爲時鐘管腳

樹莓派啓用 I2C

1.安裝工具包

sudo apt-get install -y i2c-tools

2.啓用 I2C

  • sudo raspi-config
  • 選擇 Interfacing Options
  • Enable I2C

3.檢查設備掛載狀態

i2c-tools 提供的 i2cdetect 命令能夠查看掛載設備

sudo i2cdetect -y 1

Node.js 驅動硬件

Node.js Lib

咱們先來看幾個 Node.js 庫,看完你會不得不感嘆~任何能夠使用 JavaScript 來編寫的應用,最....

johnny-five

Johnnt-Five 是一個支持 JavaScript 語言編程的機器人和 IOT 開發平臺,基於 Firmata 協議。Firmata 是計算機軟件和微控制器之間的一種通訊協議。使用它,咱們能夠很簡單的架起樹莓派和屏幕芯片之間的橋樑。

vue-ssd1306

raspi-io

Raspi IO 是一個爲 Johnny-Five Node.js 機器人平臺提供的 I/O 插件,該插件使 Johnny-Five 可以控制一個 Raspberry Pi 上的硬件。

oled-font-5x7

5x7 oled 字體庫,將字符轉爲 16 進制編碼,讓 oled 程序可以識別。用於繪製文字。

oled-js

📺 兼容 johnny-five 的 oled 支持庫 (johnny-five 自己並不支持 oled),提供了操做 oled 的 API。

驅動程序實現

// 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

參考

將 React 渲染到嵌入式液晶屏

在樹莓派上使用 SSD1306 OLED 屏幕

結語

完整代碼已上傳到 Github,若是你以爲這個實踐對你有啓發/幫助,點個 star 吧~

Vue 已經成功渲染到嵌入式液晶屏了,那下一步是否是能夠考慮接個搖桿寫個貪吃蛇遊戲了~哈哈哈,這很"Javascript"。

"閱讀式"的學習使我犯困,因此我更傾向經過一些有趣的實踐吸取知識。若是你和我同樣愛折騰,歡迎關注

相關文章
相關標籤/搜索