Drupal 11实现通过REST服务控制LED灯颜色全流程解析

Drupal 11实现通过REST服务控制LED灯颜色全流程解析

Drupal 11: Controlling LED Lights Using A REST Service

继我关于Pimoroni Plasma 2350 W的文章之后,我决定利用其Wifi接口做一些有趣的事情,使其连接到Drupal网站

Pimoroni的Plasma 2350 W固件附带了一个示例,该示例连接到一个免费API以随机更新颜色。当我看到它运行时,我意识到将其修改为从Drupal REST服务中获取数据应该不会太难。

在Drupal中创建RESTful服务非常容易,只需要一个类即可。我所需要做的就是创建一个表单,用于控制REST服务中选择的颜色。

在本文中,我们将探讨创建一个包含RESTful接口的Drupal模块,我们将使用Plasma 2350 W连接到该接口以更新灯光的颜色。

设置表单

为了能够设置LED灯的颜色,我需要创建一个专门用于此目的的表单。

为了将颜色保存到系统中,我们将使用 state 服务。这是一个方便的键值服务,它允许我们将简单的值写入数据库。这个服务是存储那些不属于Drupal配置系统的值的好方法。理想情况下,你希望这些值如果不存在,系统可以轻松重新创建。因此,颜色设置是 state 服务的理想候选对象。

使用注入此服务来设置表单相当简单,但我们还可以通过抽象出 state 本身的获取和设置方法来简化表单集成。

以下是表单类的基本结构。

  
    namespace Drupal\hashbangcode_plasma\Form;

    use Drupal\Core\Form\FormBase;
    use Drupal\Core\Form\FormStateInterface;
    use Drupal\Core\State\StateInterface;
    use Symfony\Component\DependencyInjection\ContainerInterface;

    class SetColourForm extends FormBase {

      /**
       * The state service.
       *
       * @var \Drupal\Core\State\StateInterface
       */
      protected StateInterface $state;

      public static function create(ContainerInterface $container) {
        $instance = new static();
        $instance->state = $container->get('state');
        return $instance;
      }

      /**
       * Get the colour from the state service.
       *
       * @return string
       *   The colour.
       */
      public function getColourState() {
        return $this->state->get('hashbangcode_plasma.colour', '#ffffff');
      }

      /**
       * Set the colour in the state interface.
       *
       * @param string $colour
       *   The colour to set.
       */
      public function setColourState($colour) {
        $this->state->set('hashbangcode_plasma.colour', $colour);
      }

      public function buildForm(array $form, FormStateInterface $form_state) {
        // Add form here.
      }

      public function submitForm(array &$form, FormStateInterface $form_state) {
        // Add submit here.
      }

    } 
  

这里需要注意的主要一点是,state 服务有一个 get() 方法,它允许我们设置一个默认值。在上面的代码中,这个默认值被设置为纯白色(即 #ffffff)。

这个项目的表单只需要接受颜色并允许提交表单。有趣的是,Drupal自带了一个 “color” 字段类型,它是HTML输入类型 “color” 的包装器。这意味着我们不需要包含任何额外的库来实现颜色选择功能,我们只需要添加一个具有适当类型的字段即可。

  
    public function buildForm(array $form, FormStateInterface $form_state) {
      $form['colour'] = [
        '#type' => 'color',
        '#title' => $this->t('Colour'),
        '#default_value' => $this->getColourState(),
        '#required' => TRUE,
      ];

      $form['submit'] = [
        '#type' => 'submit',
        '#value' => 'Set Colour',
      ];

      return $form;
    }
  

HTML的 “color” 字段类型是一个有趣的小知识,虽然它被广泛支持,并且允许通过一个简单的HTML元素进行颜色选择,但我在不同的设备和浏览器上至少看到了3种不同的输入界面。

表单的提交处理程序非常简单。我们只需要从表单状态中提取颜色值,使用我们一开始设置的 setColourState() 方法将其写入 state 服务,然后重新构建表单,以便在页面重新加载后,新的值会显示在表单中。

  
    public function submitForm(array &$form, FormStateInterface $form_state) {
      $colour = $form_state->getValue('colour');
      $this->setColourState($colour);
      $form_state->setRebuild();
    }
  

表单基本就是这样,但我们可以使用Drupal的洪水服务(flood service)来防止滥用,从而对其进行一些改进。

一、洪水防护

我不希望的情况是,表单上线后,人们开始每几秒钟就更改一次颜色来滥用它。幸运的是,Drupal有一个内置的名为 “flood” 的服务,它使我们能够通过跟踪在给定时间段内的提交次数,并将其与阈值进行比较,来防止表单被滥用。

为了实现这一点,我们需要像添加 state 服务一样,将洪水服务添加到表单中。

  
    /**
     * The flood service.
     *
     * @var \Drupal\Core\Flood\FloodInterface
     */
    protected FloodInterface $flood;

    public static function create(ContainerInterface $container) {
      $instance = new static();
      $instance->state = $container->get('state');
      $instance->flood = $container->get('flood');
      return $instance;
    }
  

然后,我们更新提交处理程序,以便当用户更改颜色时,我们向洪水服务注册他们的交互。在这种情况下,我们使用用户的IP地址来注册这种交互。

  
    public function submitForm(array &$form, FormStateInterface $form_state) {
      $colour = $form_state->getValue('colour');
      $this->setColourState($colour);
      $form_state->setRebuild();

      // Register flood handler.
      $floodIdentifier = $this->getRequest()->getClientIp();
      $this->flood->register('hashbangcode_plasma.plasma_flood_protection', self::ABUSE_WINDOW, $floodIdentifier);
    }
  

有了这些之后,我们可以为表单添加一个验证处理程序,以检查洪水阈值的状态。如果用户的提交触发了洪水阈值(设置为10分钟内30次),那么我们将拒绝提交,并向用户显示一条错误消息。

  
    public function validateForm(array &$form, FormStateInterface $form_state) {
      parent::validateForm($form, $form_state);

      $floodIdentifier = $this->getRequest()->getClientIp();
      if (!$this->flood->isAllowed('hashbangcode_plasma.plasma_flood_protection', 30, 610, $floodIdentifier)) {
        $form_state->setErrorByName('', $this->t('Too many uses of this form from your IP address. This address is temporarily banned. Try again later.'));
      }
    }
  

如果你想了解更多关于这方面的内容,我之前写过一篇关于 Drupal中洪水系统 的文章,那篇文章中有关于这个系统的更多详细信息。

现在我们有了一个(受保护的)表单,接下来我们可以构建一个REST资源,以便将颜色显示为JSON提要。

创建REST资源

Drupal的API系统非常强大,并且自带了用于格式和认证机制的插件。对于这个项目,我们只需要JSON格式化器和基本的 “cookie” 认证提供程序,它允许对资源进行简单的匿名访问。

要定义一个RESTful API接口,我们首先需要创建一个插件类。这个类将设置端点,并创建为接口提供动力所需的代码。此外,由于我们的颜色存储在 state 服务中,我们将使用依赖注入将该服务注入到类中。

  
    /**
     * Provides a resource for database watchdog log entries.
     */
    #[RestResource(
      id: "plasma",
      label: new TranslatableMarkup("Plasma resource"),
      uri_paths: [
        "canonical" => "/plasma/[REDACTED]",
      ]
    )]
    class PlasmaResource extends ResourceBase {
    }
  

我们希望为这个资源定义一个GET方法,因此我们需要创建一个名为 get() 的方法,该方法将由REST服务自动触发。这个方法实际上非常简单,我们将响应负载定义为一个数组,并向其中添加一个名为 “colour” 的字段。然后,我们使用 state 服务从表单设置的状态中提取颜色,或者默认使用纯白色。

通常,当我们从REST资源返回响应时,我们会创建一个新的 Drupal\rest\ResourceResponse 对象,它会处理将有效负载转换为JSON字符串的所有上游格式化操作。但对于这个项目,这种响应类型并不合适,因为它会被Drupal缓存。由于Plasma 2350 W向REST端点发出匿名请求(这样我们就不必在设备上处理认证),表单中所做的更新将不会被获取到。

经过一些实验,我发现对于将被匿名用户使用的RESTful资源,关闭缓存的最有效方法是返回一个新的 Drupal\rest\ModifiedResourceResponse 对象。它的作用与 ResourceResponse 相同,但假设每个响应都是唯一的,因此会防止响应被缓存。我看到很多人建议将缓存的最大年龄设置为0,但这对匿名流量不起作用。

以下是 get() 方法的内容。

  
    public function get() {
      $colour = [
        'colour' => $this->state->get('hashbangcode_plasma.colour', '#ffffff'),
      ];

      // Do not cache the response.
      return new ModifiedResourceResponse($colour, 200);
    }
  

REST资源将以200状态码响应,并返回一个小的JSON有效负载。

为了完整起见,以下是完整的REST插件类。

  
    <?php

    namespace Drupal\hashbangcode_plasma\Plugin\rest\resource;

    use Drupal\Core\State\StateInterface;
    use Drupal\Core\StringTranslation\TranslatableMarkup;
    use Drupal\rest\Attribute\RestResource;
    use Drupal\rest\ModifiedResourceResponse;
    use Drupal\rest\Plugin\ResourceBase;
    use Symfony\Component\DependencyInjection\ContainerInterface;

    /**
     * Provides a resource for database watchdog log entries.
     */
    #[RestResource(
      id: "plasma",
      label: new TranslatableMarkup("Plasma resource"),
      uri_paths: [
        "canonical" => "/plasma/[REDACTED]",
      ]
    )]
    class PlasmaResource extends ResourceBase {

      /**
       * The state service.
       *
       * @var \Drupal\Core\State\StateInterface
       */
      protected StateInterface $state;

      public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        $instance = new static(
          $configuration,
          $plugin_id,
          $plugin_definition,
          $container->getParameter('serializer.formats'),
          $container->get('logger.factory')->get('rest')
        );
        $instance->state = $container->get('state');
        return $instance;
      }

      public function get() {
        $colours = [
          'colour' => $this->state->get('hashbangcode_plasma.colour', '#ffffff'),
        ];

        // Do not cache the response.
        return new ModifiedResourceResponse($colours, 200);
      }

    }
  

有了这个之后,我们现在需要启用该资源,因为所有REST资源默认都是禁用的。

Drupal没有内置的界面来启用或配置REST资源,但我们可以使用 REST UI模块 来轻松启用Plasma资源,并设置它将使用的方法、格式和认证类型。安装REST UI模块后,我们可以启用Plasma资源,并允许它接受GET请求,现在看起来是这样的。

这将生成一个配置实体,我们可以将其导出并写入Drupal配置目录,然后进行上游部署。

  
    langcode: en
    status: true
    dependencies:
      module:
        - hashbangcode_plasma
        - serialization
        - user
    id: plasma
    plugin_id: plasma
    granularity: resource
    configuration:
      methods:
        - GET
      formats:
        - json
      authentication:
        - cookie
  

当然,在没有为资源设置权限的情况下,我们无法访问RESTful接口,所以最后一步是允许匿名用户访问Plasma资源。我在文章中对资源的实际路径进行了模糊处理,但一旦访问,我们会看到以下输出。

  
    {"colour":"#ffffff"}
  

通过颜色表单更改此颜色并刷新REST资源,我们将看到新的颜色。

  
    {"colour":"#19bbbe"}
  

现在Drupal中的所有设置都已完成,接下来让我们看看如何使用MicroPython从API中获取颜色并更改灯光的颜色。

编写MicroPython代码

正如我之前提到的,Pimoroni的Plasma 2350 W固件附带了一个 MicroPython脚本,该脚本连接到一个包含颜色的免费API。每120秒,MicroPython会连接到这个API,将找到的十六进制代码转换为RGB值,并更新LED灯的颜色。

我需要做的是更新使用的URL,并减少更新之间的时间间隔。这只是在脚本中更改URL变量的问题。

  
    URL = "https://www.hashbangcode.com/plasma/[REDACTED]"
  

我们的自定义JSON提要使用的字段与原始提要不同,所以我们只需要更新从提要中提取该值的代码。

  
    # extract hex colour from the data
    hex = j["colour"]
  

以下是完整的脚本,它与原始脚本非常相似,但我稍微简化了一些内容。

  
    import time

    import urequests
    from ezwifi import connect
    

联系我们

提供基于Drupal的门户网站、电子商务网站、移动应用开发及托管服务

长按加微信
长风云微信
长按关注公众号
长风云公众号