最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 将 Vue 渲染到嵌入式液晶屏

    正文概述 掘金(null仔)   2020-12-07   490

    前言

    之前看了雪碧大佬的将 React 渲染到嵌入式液晶屏觉得很有意思,React能被渲染到嵌入式液晶屏,那Vue是不是也可以呢?所以本文我们要做的就是:

    如标题所示,就是将Vue渲染到嵌入式液晶屏。这里使用的液晶屏是0.96 寸大128x64分辨率的SSD1306。要将Vue渲染到液晶屏,我们还需要一个桥梁,它必须具备控制液晶屏及运行代码的能力。而树莓派的硬件对接能力和可编程性天然就具备这个条件。最后一个问题来了,我们用什么技术来实现呢?

    将 Vue 渲染到嵌入式液晶屏

    这里我选择了 Node.js。原因:

    • Atwood 定律:“任何可以使用 JavaScript 来编写的应用,最终会由 JavaScript 编写。” ?
    • 驱动硬件我大 Node.js 一行npm install走天下。 ?

    将 Vue 渲染到嵌入式液晶屏

    这个有趣的实践可拆分为这几个步骤:

    • 在 Node.js 运行 Vue
    • 树莓派连接屏幕芯片
    • Node.js 驱动硬件

    Talk is cheap,Let's Go!!!

    跨端渲染

    无论是 基于 React 的 React Native 宣称的「Learn Once, Write Anywhere」,还是基于 Vue 的 Weex 宣称的「Write Once, Run Everywhere」口号,本质上强调的都是它们跨端渲染的能力。那什么是跨端渲染呢?

    React: ReactNative Taro ...

    Vue: Weex UniApp ...

    各种五花八门的前端框架纷纷袭来,前端工程师们纷纷抱怨学不动了~

    老板们看到纷纷笑嘻嘻, App 单,前端分,小程序单,前端吞,PC/H5,前端昏。skr~

    这些跨平台框架原理其实都大同小异,选定 Vue/React 作为 DSL,以这个 DSL 框架为标准在各端分别编译,在运行时,各端使用各自的渲染引擎(Render Engines)进行渲染,底层渲染引擎中不必关心上层 DSL 的语法和更新策略,只需要处理 JS Framework 中统一定义的节点结构和渲染指令。也正是因为这一渲染层的抽象,使得跨平台/框架成为了可能。

    将 Vue 渲染到嵌入式液晶屏

    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 渲染到嵌入式液晶屏

    在 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 渲染到嵌入式液晶屏

    注意:请一定以屏幕的实际引脚编号为准。

    • 屏幕 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 渲染到嵌入式液晶屏

    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 渲染到嵌入式液晶屏

    参考

    将 React 渲染到嵌入式液晶屏

    在树莓派上使用 SSD1306 OLED 屏幕

    结语

    完整代码已上传到 Github,如果你觉得这个实践对你有启发/帮助,点个 star 吧~

    Vue 已经成功渲染到嵌入式液晶屏了,那下一步是不是可以考虑接个摇杆写个贪吃蛇游戏了~哈哈哈,这很"Javascript"。

    "阅读式"的学习使我犯困,所以我更倾向通过一些有趣的实践吸收知识。如果你和我一样爱折腾,欢迎关注~


    起源地下载网 » 将 Vue 渲染到嵌入式液晶屏

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元