Ampelmännchen是东德的一个流行符号,每个街角的行人交通灯上都有它的身影。甚至有一家总部位于柏林的零售连锁店也受到了他们的设计启发。
我在跳蚤市场发现了一组二手 Ampelmann 信号,我想通过手机控制它们。如果您也想这样做,请继续阅读!
健康警告:本项目使用220V市电。我不是电工。请您自行承担遵循这些说明的风险。
我想建造一些具有柏林美学的东西,并且让来我家的游客与之互动会很有趣。不幸的是,今年我们的游客数量严重下降,但仍然希望 2021 年能有良好的反响……?
本地网络上的每个设备都有自己的私有 IP 地址(例如192.168.1.20
)。某些路由器(例如 FritzBox)还允许您通过本地主机名进行浏览,因此也可以通过mydevice.fritz.box
访问192.168.1.20
。对于交通灯,设备主机名是traffic-light
,因此我们可以通过http://traffic-light.fritz.box
访问它。
Web 应用程序是一个非常简单的响应式单页应用程序。它显示:
代码位于/webapp目录下。不需要外部依赖项,因为它只依赖于标准浏览器功能,例如 CSS 转换、WebSockets 和 XHR。您可以在此处预览正在运行的应用程序,尽管它不会控制任何内容,因为您当然不在我的局域网上。如果您从本地网络(例如http://traffic-light.fritz.box
访问它,它将完全正常工作。
加载页面后,应用程序发出 GET 请求以查找/api/status
处的当前状态,然后在端口81
上打开与服务器的 WebSocket 连接。后续状态更新将始终通过 WebSocket 进行,以保持多个客户端同步。每次 websocket 事件到达时,我们都会将更改应用于单个全局state
对象。不久之后, updateScreen()
方法将这些更改应用到 DOM。
启动时,我们还会检测用户是否使用移动设备或桌面设备,以处理触摸事件或单击事件。我们实际上使用touchend
事件向服务器发送命令,因为这在 iPhone X 上执行更可靠。从屏幕底部向上滑动退出 Safari 会触发touchstart
事件,因此无法在不打开的情况下退出应用程序绿灯亮了!
最后,我们希望尽可能减少服务器的负载。请记住,ESP8266 运行在80MHz处理器上,RAM 仅约50kB 。它不是一个强大的设备。因此,当浏览器处于非活动状态时,我们会断开 websocket 的连接。当选项卡或浏览器重新打开时,我们再次检查状态,并重新连接 WebSocket。
ESP8266 正忙于处理 API 请求和计时代码,因此它没有必要的资源来服务 Web 应用程序本身。此外,如果我每次想要应用更新时都需要物理连接到硬件,那么对网络应用程序进行外观更改也很困难。
webapp的index.html遵循单页应用原则,一切都应该由Javascript渲染,使得HTML内容本身非常小。像550 字节小。其他所有内容均由客户端浏览器加载,无需进一步调用服务器。因此,该 Web 应用程序实际上完全托管在 GitHub Pages(一个免费的静态网站托管工具)上。点击/index.html
实际上向 GitHub 页面发出代理请求,并将结果返回到客户端浏览器。
现在我们可以更改 web 应用程序中的任何内容,并且服务器不受影响。伟大的!嗯,差不多……
此 Web 应用程序的大部分代码都在 CSS 和 JS 文件中,而不是在index.html
本身中。浏览器会在重新请求加载的文件之前将其缓存一段不确定的时间。如果index.html没有改变,但我们部署了新的JS版本,我们的客户如何知道他们需要加载新的JS版本?
当我们将代码的任何新版本推送到 git master
分支时,会运行一个 GitHub Action,该操作会执行到 GitHub Pages 的部署,该页面实际上是向公众提供的。这里的技巧是在index.html
中将后缀?version=latest
添加到我们自己的 CSS 和 JS 文件的末尾。在将内容复制到gh-pages
分支之前,该操作使用命令sed
将“ latest
”替换为变量$GITHUB_SHA
的值,这实际上是master
分支上的最后一次提交 ID。 (例如像b43200422c4f5da6dd70676456737e5af46cb825
这样的值)。
然后,下次客户端访问 web 应用程序时,浏览器将在?version=
之后看到一个新的不同值,并请求新的、更新的 JS 或 CSS 文件,该文件尚未缓存。
请参阅traffic-light-controller.ino
中的setup(void)
方法和 Arduino 代码部分,了解其实际工作原理。
我决定同时使用 REST 和 WebSocket。 REST 主要由客户端用来控制服务器。 WebSocket 用于向客户端广播状态信息。有很多像 Postman 这样的工具可以让你轻松地尝试 REST API,所以我发现这更方便。
HTTP API:请参阅此处的 Swagger 文档。
WebSocket API: WebSocket 连接发送 JSON Blob,Web 应用程序使用这些 Blob 来更新其内部状态。 Websocket 事件可以包含一个或多个要更新的字段。包含环境信息的示例可能如下所示:
{
"redTemperature" : 21.6 ,
"greenTemperature" : 22.7 ,
"greenHumidity" : 55 ,
"redHumidity" : 59
}
当前没有数据通过 websocket 从客户端发送到服务器,尽管这是可能的。
arduino 代码全部位于一个文件中,其中包含解释性注释。
它从一组引脚位置定义、库导入以及 HTTP 内容类型和响应代码值等硬编码值开始。接下来是一组可以在运行时更改的变量,所有变量都带有下划线前缀。这里还初始化了一些对象,包括 Web 服务器、Web 套接字服务器、WiFi 客户端和温度传感器。 “系统时钟”由_currentMillis
字段维护。
启动后, setup(void)
方法运行。完成一些引脚设置后,它会为 REST 端点创建必要的映射,并启动服务器侦听客户端请求。 loop(void)
方法负责其他一切。每个周期它都会处理任何待处理的网络请求,更新节奏周期,并在必要时读取传感器。如果我们处于聚会模式,它将设置当前的闪光/脉冲状态。
节奏(用于派对模式)被硬编码为在RHYTHM_PATTERN
字段中播放序列,但理论上它可以在运行时更改为其他任何内容。每次调用rhythm()
方法时,我们都会使用当前的_bpm
和_currentMillis
值来计算出我们应该处于模式中的位置。这存储在_rhythmStep
字段中。
在节奏模式期间,有时两个继电器实际上都关闭。但由于这些灯是白炽灯泡,它们不会立即启动或停止发光。看起来灯泡需要大约 1.7 秒才能完全打开或关闭。因此,通过在模式中添加一段时间,使两者都关闭,我们最终会在灯泡升温和冷却时得到温和的脉冲模式。
在partyFlash()
方法中,我们获取当前应该显示的模式项(或者两者都将被关闭),并使用适当的参数调用lightSwitch(...)
。 lightSwitch(...)
依次调用sendToWebSocketClients(...)
以便所有连接的客户端都更新为新状态。
如果用户只需单击其中一盏灯即可将其打开或关闭,则过程类似,但作为 REST 请求进行处理。调用handleX
方法之一,该方法验证请求,然后调用lightSwitch(...)
。
我们会不频繁地检查两个机柜的温度,并通过 WebSocket 将其发送给所有客户端。目前,这仅用于提供信息,但可用于在温度超过某个安全限制时禁用灯光。
感谢 @mrcosta 帮助审阅本文。