Home

Awesome

基于 Rust + SlintUI 实现的跨全平台的小电视项目

简介

这是一个使用 Rust 开发的跨平台,仅单个按键交互,具备蜂鸣器发声能力,分辨率在 240x240 尺寸大小的彩屏小电视项目。

功能列表

后台管理

项目展示

TODO

编译运行

ESP32C3

cd app/esp32c3-impl
cargo run -r

桌面端

cd app/desktop-impl
cargo run

浏览器端

cd app/wasm-impl
make release
make serve

方案说明

支持平台

app 中的各个 crate 介绍

画布系统

画布系统提供了一系列绘图 API,可在屏幕上自定义绘制任何复杂图形,为投屏,PC 性能监视器提供了基础设施。

天气 API

天气 API 使用和风天气,对接了用于查询地理位置的 GeoApi,免费天气的 DevApi,付费的 API(TODO)。 由于和风天气强制使用 https 和 gzip 压缩,故在 ESP32C3 上引入了常用证书库,引入了 libflate crate 对响应进行解压,故相对于非 gzip 压缩和未加密的 http 请求而言,更消耗 ESP32C3 上的内存资源。

内存使用

Boot 页面下,双击按键可打开屏幕上悬浮的性能监视窗口。可用于实时检测剩余内存,剩余可分配的最大内存空闲块,FPS 计数器等信息。

由于 ESP32C3 内存资源有限,目前进入首页获取完成天气数据之后,剩余可用内存为 57KB 左右,最大连续空闲内存为 36KB 左右,在播放 MIDI 音频时候更占据大量的内存资源,仍需持续优化内存资源占用。

消息的内存占用

以下是截至目前各个平台运行时单个消息体的栈空间内存占用(不包含具有堆内存分配的数据结构在堆上占据的内存大小)

运行平台ESP32C3Linux x86_64
Message40B64B
MessageWithHeader72B120B
MessageQueueItem88B144B

内存优化

消息通信机制的设计

整套程序采用消息传递机制完成整个 app 框架的设计,各个组件仅通过消息进行相互耦合实现通信,各个通信节点称为Node,通过枚举NodeName可以唯一标识一个组件。平台无关的组件放置在 app-core 中,平台相关的组件放置在各个平台的实现中。所有消息均实现了serde::Serializeserde::Deserialize,故可天然通过 http 或 mqtt 传输 app 内的任意消息,使得 RPC 调用程序内的任意功能成为一个天然的可能,无需专门编写复杂的接口适配,同时这也为分布式 app 的可能性奠定了基础,app 内的各个组件节点可以分布工作在其他远程机器之上。

实际上本项目的实现实际上可以和 ROS 或微服务中的一些机制进行对比:

本项目ROS微服务
一对多通信broadcast_global/broadcast_topic 广播机制话题通信机制消息队列
一对一同步通信sync_call/async_call 消息调用机制服务通信机制RPC 通信
参数配置基于 sync_call 的 KV Storage 模块参数服务器机制配置中心
消息传输格式本地消息通信直接内存访问,远程通过 serde 以 JSON 形式传输ROS 消息序列化格式json/protobuf/...

App 中消息通信机制的一些基本概念

节点 Node 一个节点实际上就作为 app 内的一个组件,它可以向其他组件发送消息也可以接收来自其他组件的消息。

发送源 发送源通常使用from: NodeName来标识消息从哪个Node发出,Schedular是一个特殊的Node标识调度器消息。

发送目标 MessageTo

消息处理结果 HandleResult 当一个消息被处理完成后,需要反馈一个消息处理结果,目前定义了三种处理结果:

同步消息调用

Context 中的 sync_call(ctx, message) -> HandleResult 为同步消息调用,组件间的通信实际上就是直接的函数调用。

同步调用的优点:

  1. 简化组件的使用,可使得编码风格形成更加自然的业务流程的顺序调用。

同步调用的缺点:

注意这种调用可能会造成一些问题,如:

  1. 两个组件互相循环通信时可能造成对同一个变量的两次可变借用从而使程序崩溃或获取两次锁造成死锁。
  2. 组件相互通信可能导致程序陷入死循环,其他所有消息均无法正常调度,程序将死机。
  3. 消息处理必须是短时间内可直接执行完毕的消息,调用完成后必须立刻返回 Finish 或 Discard 状态,禁止返回 Pending 状态。

异步消息调用

Context 中的 async_call(ctx, message, FnOnce(HandleResult)) 为异步消息调用,实际上是发送一个异步消息到调度器的调度队列中进行调度,某一时刻若消息完成则通过回调函数异步通知发送者消息执行完毕。

异步调用的优点:

  1. 两个组件循环通信本质上都是调度器在两个节点上依次消息调度,即使组件陷入长时间的消息循环,甚至无限循环,其他消息依旧可以正常调度。
  2. 可以天然表达异步耗时任务的调用,可以使用 Pending 状态表明该消息需要等待一段时间后才能返回结果。

异步调用的缺点:

  1. 组件调用将必须以回调的形式接收结果,影响了代码风格。 当然若将 Rust 的 async 机制对接到调度器,可大大简化异步消息的代码风格(TODO)。