ITADN
brianmay/robotica-rust
brianmay/robotica-rust · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

robotica-rust

使用异步任务来操作异步事件。

快速入门

这仍然需要撰写。

理由

我的物联网控制之旅经历了 6 个主要解决方案:

  1. Robotica/Python version. I can't remember much about this.
  2. Robotica/Elixir version. Basically a scheduler with device plugins and RPI3 based GUI.
  3. Node Red
  4. Penguin Nodes - Elixir solution.
  5. Home Assistant
  6. Robotica Rust (this project) - Rust solution.

Robotica/Elixir 版本

Elixir 是一门优秀的语言。我喜欢它的函数式特性。我喜欢它的并发检查。

不幸的是,其类型检查目前非常糟糕。在 CI 中使用它既缓慢又笨拙。而且它会漏掉声明类型与实际类型之间明显的错误。因此,我在维护现有代码时受到阻碍,因为我不知道各个函数接受的数据类型是什么。

语言中正在进行改进类型检查的工作。但在那之前,我目前更倾向于使用 Rust。

Node Red

Node red 起初看起来确实令人印象深刻。拥有大量插件。

但随后我发现它非常具有限制性和局限性。特别是,一个节点只能接受一个输入。这被视为一种特性,并且包括子流程。当然,你可以使用诸如连接器之类的块,但在实践中,这会增加很多复杂性。而且通常我只是想在子流程中再次将其拆分。

我也希望能将状态保存在每个节点中,而不是仅为整个流程设置全局值。

因此,我为处理一盏灯编写了完美的流程,然后我不得不为家里的每一盏其他灯复制粘贴它。如果我进行了更改,我不得不对每一盏灯重复这些更改。我讨厌这一点。

此外,我发现我尝试过的两个独立编写的插件存在生命周期问题。也就是说,如果点击按钮重新部署,node-red 会关闭所有现有对象并创建新对象。但某些插件会保留对旧对象的引用,用于各种回调。这可能导致令人困惑的行为。维护者对此感到困惑。对代码和所有内容的详细检查看起来都正常。没有可用的工具来诊断此类问题。我只能通过提出“如果……会怎样?”类型的问题并不断向维护者施压来解决它们。我也讨厌这一点。

我觉得“在我这里能运行”的态度很普遍。人们会编写插件来解决他们的问题,而对修复其插件中的 bug 并不太感兴趣。这也可能是因为他们觉得无法获得支持/帮助来解决超出他们能力范围的问题。

在我看来,基于 JavaScript 的 Node Red 设计感觉不是一个可靠且可信系统的坚实基础。

I also feel nervous whenever I make changes directly to a live system with no provision for testing before deploying or saving a history of what has changed in git.

企鹅节点

来源

由于 node red 存在的问题,我决定用 Elixir 重写。我的重写版本具有一些有趣的功能:

  • 节点由 Elixir 进程表示。
  • 一套确保每个节点拥有唯一 id 的机制,该 id 在节点重启和重新排序后依然有效。
  • 每个节点可以拥有状态、定时器等。
  • 如果进程死亡,它会在 Elixir 集群中的任意节点上重启。
  • 节点可以从任意数量的源节点接收消息,并向一个或多个目标节点发送消息。
  • 部署可以通过逐个移除 Kubernetes pods 来实现零停机时间,节点会在现有的 pods 上重启。

我发现了一个 mqtt 问题。如果我从一个 mqtt 主题取消订阅并再次订阅,服务器不会发送最新的保留消息。因此,无法知道该主题的状态。我目前通过不允许取消订阅操作来规避这个问题,因此我们总是能获取到最新的状态。

我还遇到了另一个问题。节点在新的 pod 上重启后,会丢失其状态。没有保证它会从源节点收到消息,从而获得节点的正确状态。

起初,我尝试运行一个类似分布式 etcd 的系统来保存不同进程的状态。但我在使其正常工作方面遇到了问题,最终改为将状态保存/恢复到 postgresql 数据库中。

这运行得很好。但这是一个复杂的解决方案,我对它没有太多信心。举个例子,我注意到我忘记为其中一个节点实现保存状态的代码。这应该很容易,对吧?但保存状态要求数据必须是可序列化的。而系统的无类型设计意味着无法保证数据是可序列化的。如果我实现了这一点,我会因为将此用于不可序列化的数据而面临崩溃的风险吗?是现在还是将来?

我尝试编写代码对输入和输出进行类型检查,但这效果并不好。而且所有类型检查都是在运行时而非编译时进行的。我最初尝试在编译时生成节点和输入/输出的图,但当该配置包含回调函数时,这失败了。

我还添加了一个 Web 界面,使用了 Elixir live views。这是为了实时显示每个节点的日志。不幸的是,由于一些我不理解的原因,消息的负载——其实并没有那么多——对 Elixir 来说太重了。绝对的一切,每个进程都几乎完全停滞。我不得不关闭大部分日志记录,才能获得可接受的性能。我以为是日志占用了太多内存,但高的是 CPU,而不是内存使用率。

也许这并不是 live views 的一个好的使用场景。我在 Elixir lie view 进程中内存里存储了最近 n 条日志条目,这意味着如果日志太多,或者 Web 浏览器断开连接或重启,日志就不会被保存。

我仍然喜欢这个实现的一些特性。然而,我认为它过于复杂,因此我对它缺乏信心,也缺乏在不意外破坏东西的情况下进行修改的能力。

举个例子,最近我开始遇到一个问题,有时它不会发送“特斯拉已插上电源”的消息,而我完全不知道为什么。

Home Assistant

我应该更早地认真考虑这个。但我一直痴迷于“非此处发明”的倾向。事后看来,虽然 我并不完全喜欢基于 YAML 的语言,但它确实有一些优点,并且我尝试将这些 整合到我的项目中。

优点:

  • 自动记录状态。
  • 支持大量设备。
  • 自动检测和配置设备。
  • 能够检查实体的当前状态,而无需等待传入事件,这有助于简化操作。 这在 node-red 中是可能的,但您需要自行将值读取到全局变量中。
  • 实体具有类型信息。
  • 积极开发中。
  • 带有大量传感器用于观察的智能手机应用。
  • 基于 Python。

不足之处:

  • 安全模型薄弱。如果您想通过应用程序连接某人的手机,那么此人将获得系统中所有数据的完全访问权限。包括来自其他手机的数据。这在任何情况下都无法限制。登录——连接移动应用程序所必需——会授予对地图、日志和历史记录菜单项的完全访问权限。如果我家的每个人都能看到其他人在所有时间的所在位置,将会引发巨大的争吵。这是一个长期存在的问题 #7361
  • 自动化/脚本语言是一种基于 YAML 的丑陋语言,没有任何类型检查功能。
  • 更改实体类型不会导致已损坏的自动化/脚本以某种方式变得明显。
  • 自动化 GUI 虽然正在改进,但仍然难以可视化和操作代码结构。
  • 很容易在不知不觉中做出意外更改或破坏事物。
  • 虽然可以使用 git 跟踪某些文件的更改,但 GUI 界面不支持此功能。
  • 同样,将某些内容(例如通过 GUI 创建的实体)存储在 git 中也不容易。
  • 自动化与脚本的组织结构较差。例如,查看脚本时,无法判断其在哪里被使用。也许这是一个遗留脚本,不再使用?我应该删除它吗?无法将相关的实体/自动化/脚本组合在一起。
  • Home assistant 的日志非常冗长,会记录一些看似错误但实际上完全正常的事件。例如,由于脚本已在运行而未启动它,而这正是你所指示的行为。

虽然 Home Assistant 有一些优点,但我真正想要的是能够使用编译后的代码。能够使用标准工作流轻松提交到 git 的代码。在编译期间进行强类型检查的代码。在代码部署之前可以运行的测试。

Robotica Rust

是的,又一次重写。用 Rust。特性:

  • 设计要简单得多。
  • 使用 asyncio 任务。
  • 线程之间的类型检查在编译时强制执行。
  • 创建/维护/读取流程要容易得多。
  • 结构化为一个库,因此与我的设置不相关的部分可以由不同的项目共享。
  • 在意外失败时,希望退出以允许监控系统(例如 kubernetes)重启。
  • 已更新以减少发送与上一条消息完全相同的过多消息。

局限性:

  • 失去了自动重启任务的能力。如果一个任务失败,需要中止所有任务。
  • 一次只能运行一个副本。否则你会得到重复的出站事件。
  • 这仍然应该被视为 alpha 状态。也就是说,API 仍在开发中,可能会在没有通知的情况下更改。

仍待实现:

  • 二进制 crate 和库之间的拆分还需要更多工作。理想情况下,二进制 crate 应该只包含与我的设置相关的特定内容,但可能包含更多内容。

  • 日志记录还需要更多工作。目前还不确定如何处理。

  • 尚未尝试保存状态。希望这不会成为必需。当进程启动时,它会订阅 mqtt 并获取保留数据,目前这已足够。我有一些想法,但这意味着节点需要拥有唯一的 id,这很可能会增加复杂性。

  • 可用的构建模块支持不多。我仍然使用 home-assistant 处理大部分事务,并使用我较旧的基于 robotica elixir 的驱动程序处理其他事务。它们之间使用 MQTT 进行通信。

  • 轻松调试流程的工具。