使用 Serverless 和颜色显示天气

Avatar of Burke Holland
Burke Holland

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

我喜欢慢跑。有时天气很冷。有时天气很冷,但看起来并不冷。阳光普照,鸟儿鸣叫。然后你穿着短裤和 T 恤走到户外,意识到你大约只有 2 分钟的时间才能避免着凉。

我决定用一个灯泡来解决这个问题,根据外面的温度显示特定的颜色。它的效果比我预期的要好,这说明了一些问题,因为通常情况下,事情都不会像我想的那样发展。

这是一个很有趣的项目,因为它本质上是一个在定时器上运行的托管服务,因此它是 Serverless 的完美用例。

现在你可能在想,“嗯,直接查看天气预报不是更容易吗?” 当然,确实更容易,但那样我就没有借口买一个昂贵的灯泡或写一篇包含“Serverless”这个词的文章了。

所以让我们看看如何构建你自己的天气灯泡。最终代码并不复杂,但它确实有一些值得注意的有趣部分。顺便说一句,我有没有提到它是 Serverless 的?

构建天气灯泡

首先,你需要一个灯泡。没有灯泡就没有天气灯泡。把“灯泡”这个词大声念 10 次,你会发现它是一个多么奇怪的词。灯泡,灯泡,灯泡——看到了吧?很奇怪。

我使用的是 LIFX Mini Color。它并不*太*贵,但更重要的是,它有一个开放的 API。

该 API 有两种身份验证方法。第一种包含“OAuth”这个词,我为让你读到它而感到抱歉。别担心,有一种更简单的方法,不需要涉及 OAu…. 那个不言而喻的东西。

第二种方法是在 LIFX 中注册一个应用程序。你会得到一个密钥,你只需要在任何 HTTP 请求中传递该密钥即可。这就是我用于此演示的方法。

例如,如果我们想将灯泡颜色更改为蓝色,我们可以将color: blue传递到/state端点。

该 API 支持几种不同的颜色格式,包括命名颜色(如红色、蓝色)、十六进制值、RGB、开尔文、色调亮度和饱和度。这一点很重要,因为它会影响到这个项目中最难的部分:将温度转换为颜色。

用颜色表示温度

如果你曾经看过天气预报,你就会熟悉气象学在天气图上用颜色表示天气状况的方式。

通常,这样做是为了可视化降水。你可能在天气图上见过那条不祥的绿色风暴带正向你袭来,而你则试图弄清楚是否应该躲进浴缸,因为你正处于龙卷风的路径上。或者也许这只是我们这些身处 美国龙卷风走廊 的不幸灵魂。

颜色也用于表示温度。这正是我想用灯泡做的。困难的是,似乎没有标准化的方式来做到这一点。有些地图将其显示为带状的纯色。在这种情况下,蓝色可能表示 0℉ – 32℉ 的范围。

其他地图则将其显示为渐变尺度,这更精确。这正是我想要的天气灯泡的效果。

我第一次尝试解决这个问题是简单地谷歌搜索“温度颜色尺度”以及该搜索词的其他各种变体。我得到了很多关于开尔文的信息。

开尔文是颜色温度的表示。从字面上看。对于任何光源(灯泡、太阳等),光源的实际温度都会影响其发出的光的颜色。火焰燃烧发出黄红色的光。火焰越热,颜色就越接近白色。因此有句谚语,“白热”。所以如果有人说“红热”,你可以在所有人面前纠正他们,因为谁不喜欢一个喜欢抬杠的人呢?

LIFX 灯泡支持开尔文,所以你可能会认为这会奏效。毕竟,这是开尔文温标……

问题在于,颜色变化不足,因为这些不是实际的颜色,而是光源根据其“温度”发出的色彩。以下是 LIFX 应用程序附带的开尔文色轮。

在灯泡上,这些颜色几乎无法区分。这并不是我想要的。

这让我不得不尝试将颜色转换为十六进制、RGB 或其他格式。这很难,因为你从哪里开始?我花了很长时间调整从冷色蓝色(0, 0, 255)到热色红色(255, 0, 0)之间的 RGB 比例值。就在这时,我突然意识到也许 HSL 会是一种更好的方法。为什么?因为色相更容易理解。

色相

色相是在 0 到 360 之间的颜色表示。这就是为什么我们经常看到颜色以轮的形式表示(360°)。这是一个过于简单的概括,但除非你想让我开始谈论波长,否则我们就用这个定义吧。

色相色轮如下所示……

如果我们将其展开,就更容易推理了。

我们准备将温度转换为颜色。首先我们需要确定一个温度范围。我选择了 0℉ 到 100℉。我们不能处理无限的温度颜色组合。数字可以无限延伸,颜色却不能。当我们的灯泡始终是亮红色时,温度只能达到 100℉。寒冷也是如此。

如果浅蓝色代表 0℉,我可以在色相尺度的 200 标记处开始。红色将代表 100℉。你可以看到红色位于两个极端,所以我可以向左或向右移动,具体取决于我想用什么颜色来表示温度。它与实际天气程序中使用的颜色并不相同,但谁在乎呢?显然我不在乎。

我选择向右移动,因为左边没有粉色,而粉色是我最喜欢的颜色。我也觉得粉色比绿色更能代表温暖。绿色代表雨和龙卷风。

现在我们可以根据温度反推出色相了。准备好了吗?我们开始吧。

假设外面是清爽的 50℉。

如果 100℉ 是我们所能达到的最高温度(360),而 0℉ 是最低温度(200),那么我们的颜色尺度就有 160 个点。为了弄清楚我们需要在这个 160 个点的范围内移动到哪里,我们可以将当前温度除以 100℉ 的上限,这将给出我们需要在范围内移动的确切百分比,即 50%。如果我们在 160 个点的范围内移动 50%,那么我们就位于 80 处。由于我们从 200 开始,因此我们的色相为 280。

这听起来很复杂,但这仅仅是因为数学中的文字题很**糟糕**。以下是代码最终的样子……

let hue = 200 + (160 * ( temperature / 100 ));

好的!我们根据色相得到了一个动态颜色尺度,你可能不知道,我们可以像传递命名颜色一样简单地将色相传递给 LIFX。

现在我们只需要找出当前温度是多少,反推出色相,并每隔几分钟执行一次。Serverless,我们来了!

Serverless 定时器函数

Serverless 风靡一时。它就像曾经的 HTML5 一样:它是什么并不重要,重要的是你是否知道这个词并且不害怕在博客文章中使用它。

在此示例中,我们将使用 Azure Functions,因为它支持定时器触发器,并且我们可以在使用 VS Code 部署之前在本地测试这些定时器触发器。Serverless 中让我感到恼火的一件事是,当我无法 在本地调试它 时。

使用 Azure Functions Core ToolsAzure Functions VS Code 扩展,我可以创建一个新的 Serverless 项目并选择一个定时器触发器。

Azure Functions 中的定时器触发器被指定为 Cron 表达式。别担心,我也不知道那是什么。

Cron 表达式允许你非常精确地定义间隔。Cron 将时间分解为秒、分、时、日、月、年。因此,如果你想每秒、每分钟、每小时、每天、每月、每年运行一次,你的表达式将如下所示……

* * * * * *

如果你想每天 10:15 运行一次,则表达式将如下所示……

* 15 10 * * *

如果你想每 5 分钟运行一次(这是 Azure 的默认设置),则可以通过说“当分钟数可以被 5 整除时”来指定。

0 */5 * * * *

出于此函数的目的,我们将其设置为 2 分钟。

我使用 2 分钟的间隔,因为这是我们可以免费调用天气 API 的频率 💰。

从 DarkSky 获取天气预报

DarkSky 有一个很棒的天气 API,你可以免费每天调用 1000 次。如果一天有 1440 分钟(确实有),这意味着我们可以每天每 1.44 分钟调用一次 DarkSky 并保持在免费范围内。我只是四舍五入到 2 分钟,因为温度变化没有那么快。

当我们调用 DarkSky API 时,我们的函数是这样的。我的所有令牌、密钥、纬度和经度设置都在环境变量中,因此它们没有硬编码。这些设置在 local.settings.json 文件中。我使用 axios 进行 HTTP 请求,因为它是一个神奇、神奇的包。

const axios = require('axios');

module.exports = function (context, myTimer) {
  // build up the DarkSky endpoint
  let endpoint = `${process.env.DS_API}/${process.env.DS_SECRET}/${process.env.LAT}, 
                  ${process.env.LNG}`;
  
  // use axios to call DarkSky for weather
  axios
    .get(endpoint)
    .then(response => {
      let temp = Math.round(response.data.currently.temperature);
    
      // TODO: Set the color of the LIFX bulb
    })
    .catch(err => {
      context.log(err.message);
    });
};

现在我有了温度,我需要调用 LIFX API。你猜怎么着,有人已经创建了一个名为 lifx-http-api 的 npm 包来执行此操作。这就是你热爱 JavaScript 的原因。

设置灯泡色调

天气结果返回后,我需要使用 LIFX API 实例并调用 setState 方法。此方法返回一个 promise,这意味着我们需要嵌套 promise。嵌套 promise 会变得难以控制,可能会让我们回到回调地狱,这正是我们最初使用 promise 想要避免的事情。

相反,我们将处理第一个 promise,然后返回 Promise.all,我们可以在另一个顶层的 then 中处理它。这只是防止我们嵌套 then 语句。

记住孩子们,promise 只是在社会上可以接受的回调。

const axios = require('axios');
const LIFX = require('lifx-http-api');

let client = new LIFX({
  bearerToken: process.env.LIFX_TOKEN
});

module.exports = function (context, myTimer) {
  // build up the DarkSky endpoint
  let endpoint = <code>${process.env.DS_API}/${process.env.DS_SECRET}/${
    process.env.LAT
    },${process.env.LNG}<code>;
    
  // use axios to call DarkSky for weather
  axios
    .get(endpoint)
    .then(response => {
      let temp = Math.round(response.data.currently.temperature);
      
      // make sure the temp isn't above 100 because that's as high as we can go
      temp = temp < 100 ? temp : 100;
      
      // determine the hue
      let hue = 200 + (160 * (temp / 100));

      // return Promise.all so we can resolve at the top level
      return Promise.all([
        data,
        client.setState('all', { color: <code>hue:${hue}<code> })
      ]);
    })
    .then(result => {
      // result[0] contains the darksky result
      // result[1] contains the LIFX result
      context.log(result[1]);
    })
    .catch(err => {
      context.log(err.message);
    });
};

现在我们可以本地运行这个东西,并观察我们的计时器执行它的操作。

就是这样!让我们部署它。

部署天气灯泡

我可以从 VS Code 扩展创建新的 Functions 项目。

我可以右键单击它以“在门户中打开”,在那里我可以定义部署源,以便它从 Github 中提取我的代码并进行部署。这是理想的选择,因为现在每当我将更改推送到 Github 时,我的应用程序都会自动重新部署。

万岁,天气灯泡!

现在只需坐下来欣赏天气灯泡的柔和光芒!为什么要查看实际温度,当你可以查看这种美丽的热粉色时?

你能根据本文中的知识猜测温度是多少吗?发表评论并猜得最接近的人将获得我赠送的免费 LIFX 灯泡(因为我❤️你们所有人),或者如果您在 美国以外,则支付灯泡费用(约 40 美元)。

您可以从Github获取此项目的所有代码。