物联网毕业设计 树莓派智能捡垃圾机器人 - 机器视觉 单片机

269 篇文章 42 订阅
269 篇文章 9 订阅


0 前言

🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。

为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是

🚩 基于树莓派的智能捡垃圾机器人

🥇学长这里给一个题目综合评分(每项满分5分)

  • 难度系数:5分
  • 工作量:3分
  • 创新点:5分

1 简介

通过这款自动驾驶机器人,可进行物体检测识别和监控垃圾。它还部署了视频流和跌倒检测系统。
使用 Edge Impulse 构建了一个神经网络模型,用于在这三类下通过物体检测来检测垃圾:

  • 瓶子(玻璃和塑料)
  • 罐(金属)
  • 包装(塑料、纸、纸板等)

Edge Impulse 还可以在连接到 Raspberry Pi 后提供实时视频流。因此,无需创建网络摄像头服务器 (Motion) 即可使用 Raspberry Pi 为该项目进行直播。

2 主要器件

  • 树莓派4
  • RPLIDAR A1M8 360 度激光雷达
  • Arduino Nano
  • DFRobot Black Gladiator
  • SSD1306 OLED 屏幕(128x32)

3 实现效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4 设计原理

1 在 Edge Impulse 中构建平衡良好的数据集

Edge Impulse是一个免费的嵌入式机器学习开发平台,供开发人员(新手或专家)从学习到部署。它具有许多功能和内置神经网络模型,可满足各种要求,例如用于多目标检测的迁移学习。此外,该平台还提供来自连接设备摄像头的实时视频流。因此,本项目使用 Edge Impulse 来识别和监控垃圾。

在构建用于物体检测的神经网络模型之前,需要创建一个平衡良好的数据集来检测多个垃圾类别:

瓶子(玻璃和塑料)
罐(金属)
包装(塑料、纸、纸板等)

通过精心挑选与上述垃圾类别相关的最合适的图像,结合了废物和垃圾的两个不同数据集:

垃圾分类数据集
TACO 垃圾数据集

选择后,每个类别大约有100张图像,总共292张。通常,像这样的小数据集无法在垃圾检测中得到准确的结果。然而,Edge Impulse 在训练模型时采用了迁移学习,所以得到了相当不错的结果,而且准确率很高。

首先,注册Edge Impulse并创建一个新项目(垃圾检测机器人)。
在这里插入图片描述
为了能够使用对象检测模型,请转到仪表板 ➡ 项目信息 ➡ 标签方法并选择Bounding box (object detection) 。
在这里插入图片描述
然后,转到数据获取并选择上传数据(上传现有数据)。
在这里插入图片描述
在这里插入图片描述

成功上传垃圾数据集后,用提到的三个垃圾类别标记每个图像 -瓶子、罐头、包装。在 Edge Impulse 中,标记一个对象,类似在它周围拖动一个框并输入一个标签一样简单。此外,Edge Impulse 在标记对象时在后台运行跟踪算法,因此它会自动为不同图像中的相同对象移动框。

⭐转至数据获取➡标签队列(Object detection labeling)。它显示了数据集中剩余的所有未标记图像。

⭐然后,选择一个未标记的图像,拖动框,单击Save labels ,然后重复此操作,直到整个数据集都被标记。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

完成标记后,可以看到在数据采集下列出了一个平衡良好的数据集,用于垃圾检测。
在这里插入图片描述

2 在 Edge Impulse 中设计脉冲(神经网络模型)

脉冲是边缘脉冲中的自定义神经网络模型。在这个项目中,设计了一个脉冲,它获取原始图像数据,调整图像大小,使用预处理块来处理图像,然后利用学习块对新数据进行分类:

图像预处理块 ➡ 取彩色图像中的数据,可选地使图像灰度化,然后将数据转化为特征数组。
迁移学习学习块 -对象检测(图像)➡ 接收所有图像并学习区分三种(瓶子、罐子、包装)垃圾类别。
预处理块总是为相同的输入返回相同的值(例如,将彩色图像转换为灰度图像),而学习块则从过去的经验中学习。除了内置的预处理块,Edge Impulse 还允许用户创建自定义预处理块(步骤)。

⭐进入创建脉冲,设置图像宽度和图像高度为320,调整大小模式为适合最短轴。然后,添加图像和对象检测(图像)块。最后,单击Save Impulse 。
在这里插入图片描述
配置处理块和功能
⭐ 要配置处理块,请转到Impulse design下的Image ,选择颜色深度为RGB ,然后单击Save parameters 。处理块为模型适当地格式化原始图像数据。
在这里插入图片描述

然后,在特征生成屏幕上,单击生成特征以:

  • 调整图像数据大小,
  • 将处理块应用于图像数据,
  • 并创建完整的垃圾数据集的 3D 可视化。

在这里插入图片描述

3 使用迁移学习训练神经网络模型(脉冲)

它正在努力从头开始构建准确的计算机视觉模型,因为该模型需要各种各样的输入数据才能很好地泛化,并且在 GPU 上训练此类模型可能需要数天时间。然而,Edge Impulse 在训练用于对象检测的神经网络模型时采用了迁移学习。转移学习方法重新训练训练有素的神经网络模型的上层以进行对象检测,从而产生更可靠的模型,这些模型可以在很短的时间内进行训练并使用更小的数据集。

尽管迁移学习使训练对象检测模型变得轻松,但使用机器学习识别和监控垃圾仍然具有挑战性。由于垃圾种类的颜色、形状和材料各不相同,因此我从两个不同的数据集中精心挑选了 292 张最合适的图像,如前面的步骤所述。处理完我的数据集后,我用整个数据集训练模型以区分三种不同的垃圾类别(瓶子、罐头、包装)。

使用数据集训练模型后,Edge Impulse 将精度分数(准确度)评估为60.2% 。

⭐ 进入Impulse design下的Object detection ,选择默认的基础模型,并设置:

训练周期数➡ 40
学习率➡ 0.01

在这里插入图片描述

验证神经网络模型

由于使用整个数据集来训练模型,因此上传了新图像作为测试数据集来验证模型。

在验证模型后,推断它区分瓶子(塑料和玻璃)和罐子(金属)的准确率超过 88%。然而,它很难检测包装(塑料、纸、纸板等),因为包装在很多方面都不同——形状、颜色、材料等。由于收集的种类繁多,包装的准确度在 45% 到 55% 之间。因此,我仍在收集数据以改进我的数据集和包装的准确性。

使用测试数据集(大约 50 张图像),Edge Impulse 评估模型精度为84.11% 。

⭐ 要验证模型,请转到模型测试并选择Classify all 。

在这里插入图片描述

4 将 Raspberry Pi 4 连接到 Edge Impulse

经过设计、训练、验证和验证后,剩下的就是将模型部署到了树莓派 4。由于 Edge Impulse 官方支持树莓派 4,因此使用此开发板部署和运行模型非常简单。

⭐首先,打开终端,运行以下命令安装依赖和模块:
在这里插入图片描述
安装依赖项后,将USB网络摄像头连接到Raspberry Pi 4并运行以下命令:边缘脉冲Linux

然后,登录并使用终端向导选择一个Edge Impulse项目(垃圾检测机器人)。
在这里插入图片描述
要验证树莓派 4 是否成功连接到所选 Edge Impulse 项目,请转到项目页面并单击Devices 。
在这里插入图片描述

5 在 Raspberry Pi 4 上部署和运行模型

⭐ 要在 Raspberry Pi 4 本地部署和运行模型,请打开终端并在下面输入以下命令:

edge-impulse-linux-runner

⭐ 然后,Edge Impulse 会自动编译具有完整硬件加速的模型并将其下载到 Raspberry Pi 4。在这方面,该模型以最小的延迟和功耗运行。
在这里插入图片描述
在 Raspberry Pi 4 上部署模型后,将它和 USB 网络摄像头连接到机器人底盘。就可运行模型,就可以在三个垃圾类别之间进行分类。

瓶子(玻璃和塑料)
罐(金属)
包装(塑料、纸、纸板等)
在这里插入图片描述

由于 Edge Impulse 在模型运行时提供带有来自连接的网络摄像头的分类结果的实时视频流,因此当垃圾检测机器人运行时,不需要创建带有 Motion 或其他模块的网络摄像头服务器来显示分类结果。

⭐ 要显示实时视频流和分类结果,直接在运行模型后转到终端中给定的 URL:
在这里插入图片描述
在这里插入图片描述

6 在 Raspberry Pi 4 上设置 RPLIDAR A1M8 360 度激光雷达

为了让垃圾检测机器人自主移动,使用了 RPLIDAR A1M8-R6 - 360 度激光扫描仪(激光雷达)。该激光雷达可以在 12 米范围内执行 360 度扫描,每秒生成多达 8000 个样本。

没有绘制环境来导航机器人(SLAM),而是使用激光雷达来检测三个不同方向(右、左和前)的障碍物,因为我想让机器人在不受环境(室内或室外)任何限制的情况下运行)。

为了获得 RPLIDAR A1M8 和 Raspberry Pi 生成的 360 度扫描数据,我使用了Adafruit CircuitPython RPLIDAR库。

在使用库收集数据之前,将 RPLIDAR A1M8 连接到计算机 (Windows) 并运行Frame Grabber应用程序以在扫描周围环境的同时检查角度方向和距离测量值。
在这里插入图片描述

7 使用RPLIDAR A1M8检测障碍物并控制机器人底盘

RPLIDAR A1M8 不是采用步进读取方法,而是通过直流电机驱动旋转扫描仪,并根据扫描仪获得的角度生成距离读数。这样,单次旋转不能保证为每个可能的角度(从 0 到 360)产生距离值。只需旋转几次,即可使用此激光雷达进行完整的 360 度扫描。因此,在不调试生成的扫描数据点的情况下,使用 RPLIDAR A1M8 进行避障可能会非常棘手和困难。

在设置好RPLIDAR A1M8并包括所需的模块后,对扫描仪每转产生的不完整的360度扫描数据进行调试和处理,以检测三个不同方向的障碍物。对于每个方向,定义了一个起始角和一个终止角(顺时针):

右 ➡ 开始:60 ,结束:120
左 ➡ 开始:240 ,结束:300
前 ➡ 开始:340结束:20
在每个方向范围内,代码搜索准确的距离测量作为起点和终点:

起点 ➡ 从起始角到起始角 + 15 ,
终点 ➡ 从终点角度到终点角度 - 15 .
这样,代码覆盖每个方向的 30 度范围,以得出准确的距离测量值(起点和终点),不会出现错误或遗漏。然后,如果引出的方向起点和终点小于给定阈值(40 cm),则代码激活机器人底盘(L298N)以避开该方向检测到的障碍物。

在完成代码并将激光雷达(RPLIDAR A1M8)安装到机器人底盘上后,在外壳上测试了垃圾检测机器人的避障系统。

8 使用 Arduino 为机器人构建跌倒检测系统

完成上述步骤后,为垃圾检测机器人添加一个 6 轴加速度计作为跌倒检测系统,以防止碰撞。跌倒检测系统还显示 X、Y 和 Z 轴的加速度测量值。

下载所需的库以从 DFRobot 串行 6 轴加速度计获取数据。
下载控制 SSD1306 OLED 屏幕所需的库。

为了构建机器人的跌倒检测系统,我将 DFRobot 串行 6 轴加速度计、SSD1306 OLED 屏幕(128x32)和蜂鸣器连接到 Arduino Nano。为了提供 Arduino Nano,将它连接到 Raspberry Pi 4:
在这里插入图片描述

5 部分核心代码

/*!
   @file getLightIntensity.ino
   @Set the frequency of data output by the sensor, read the acceleration, angular velocity, and angle of X, Y, and Z axes.
   @n Experimental phenomenon: when the sensor starts, it outputs data at the set frequency and the data will be displayed on serial monitor
   @copyright   Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
   @licence     The MIT License (MIT)
   @author [huyujie](yujie.hu@dfrobot.com)
   @version  V1.0
   @date  2020-12-03
   @https://github.com/DFRobot
*/
#include <DFRobot_WT61PC.h>
#include <SoftwareSerial.h>

//Use software serial port RX:10,TX:11
SoftwareSerial mySerial(10, 11);

DFRobot_WT61PC sensor(&mySerial);

void setup()
{
  //Use Serial as debugging serial port 
  Serial.begin(115200);
  //Use software serial port mySerial as communication seiral port 
  mySerial.begin(9600);
  //Revise the data output frequncy of sensor FREQUENCY_0_1HZ for 0.1Hz, FREQUENCY_0_5HZ for 0.5Hz, FREQUENCY_1HZ for 1Hz, FREQUENCY_2HZ for 2Hz, 
  //                        FREQUENCY_5HZ for 5Hz, FREQUENCY_10HZ for 10Hz, FREQUENCY_20HZ for 20Hz, FREQUENCY_50HZ for 50Hz, 
  //                        FREQUENCY_100HZ for 100Hz, FREQUENCY_125HZ for 125Hz, FREQUENCY_200HZ for 200Hz.
  sensor.modifyFrequency(FREQUENCY_10HZ);
}


void loop()
{
  if (sensor.available()) {
    Serial.print("Acc\t"); Serial.print(sensor.Acc.X); Serial.print("\t"); Serial.print(sensor.Acc.Y); Serial.print("\t"); Serial.println(sensor.Acc.Z); //acceleration information of X,Y,Z
    Serial.print("Gyro\t"); Serial.print(sensor.Gyro.X); Serial.print("\t"); Serial.print(sensor.Gyro.Y); Serial.print("\t"); Serial.println(sensor.Gyro.Z); //angular velocity information of X,Y,Z
    Serial.print("Angle\t"); Serial.print(sensor.Angle.X); Serial.print("\t"); Serial.print(sensor.Angle.Y); Serial.print("\t"); Serial.println(sensor.Angle.Z); //angle information of X, Y, Z 
    Serial.println(" ");
  }
}
void Adafruit_SSD1306::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((x >= 0) && (x < width()) && (y >= 0) && (y < height())) {
    // Pixel is in-bounds. Rotate coordinates if needed.
    switch (getRotation()) {
    case 1:
      ssd1306_swap(x, y);
      x = WIDTH - x - 1;
      break;
    case 2:
      x = WIDTH - x - 1;
      y = HEIGHT - y - 1;
      break;
    case 3:
      ssd1306_swap(x, y);
      y = HEIGHT - y - 1;
      break;
    }
    switch (color) {
    case SSD1306_WHITE:
      buffer[x + (y / 8) * WIDTH] |= (1 << (y & 7));
      break;
    case SSD1306_BLACK:
      buffer[x + (y / 8) * WIDTH] &= ~(1 << (y & 7));
      break;
    case SSD1306_INVERSE:
      buffer[x + (y / 8) * WIDTH] ^= (1 << (y & 7));
      break;
    }
  }
}

/*!
    @brief  Clear contents of display buffer (set all pixels to off).
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void Adafruit_SSD1306::clearDisplay(void) {
  memset(buffer, 0, WIDTH * ((HEIGHT + 7) / 8));
}

/*!
    @brief  Draw a horizontal line. This is also invoked by the Adafruit_GFX
            library in generating many higher-level graphics primitives.
    @param  x
            Leftmost column -- 0 at left to (screen width - 1) at right.
    @param  y
            Row of display -- 0 at top to (screen height -1) at bottom.
    @param  w
            Width of line, in pixels.
    @param  color
            Line color, one of: SSD1306_BLACK, SSD1306_WHITE or SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void Adafruit_SSD1306::drawFastHLine(int16_t x, int16_t y, int16_t w,
                                     uint16_t color) {
  bool bSwap = false;
  switch (rotation) {
  case 1:
    // 90 degree rotation, swap x & y for rotation, then invert x
    bSwap = true;
    ssd1306_swap(x, y);
    x = WIDTH - x - 1;
    break;
  case 2:
    // 180 degree rotation, invert x and y, then shift y around for height.
    x = WIDTH - x - 1;
    y = HEIGHT - y - 1;
    x -= (w - 1);
    break;
  case 3:
    // 270 degree rotation, swap x & y for rotation,
    // then invert y and adjust y for w (not to become h)
    bSwap = true;
    ssd1306_swap(x, y);
    y = HEIGHT - y - 1;
    y -= (w - 1);
    break;
  }

  if (bSwap)
    drawFastVLineInternal(x, y, w, color);
  else
    drawFastHLineInternal(x, y, w, color);
}

/*!
    @brief  Draw a horizontal line with a width and color. Used by public
   methods drawFastHLine,drawFastVLine
        @param x
                   Leftmost column -- 0 at left to (screen width - 1) at right.
        @param y
                   Row of display -- 0 at top to (screen height -1) at bottom.
        @param w
                   Width of line, in pixels.
        @param color
               Line color, one of: SSD1306_BLACK, SSD1306_WHITE or
   SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void Adafruit_SSD1306::drawFastHLineInternal(int16_t x, int16_t y, int16_t w,
                                             uint16_t color) {

  if ((y >= 0) && (y < HEIGHT)) { // Y coord in bounds?
    if (x < 0) {                  // Clip left
      w += x;
      x = 0;
    }
    if ((x + w) > WIDTH) { // Clip right
      w = (WIDTH - x);
    }
    if (w > 0) { // Proceed only if width is positive
      uint8_t *pBuf = &buffer[(y / 8) * WIDTH + x], mask = 1 << (y & 7);
      switch (color) {
      case SSD1306_WHITE:
        while (w--) {
          *pBuf++ |= mask;
        };
        break;
      case SSD1306_BLACK:
        mask = ~mask;
        while (w--) {
          *pBuf++ &= mask;
        };
        break;
      case SSD1306_INVERSE:
        while (w--) {
          *pBuf++ ^= mask;
        };
        break;
      }
    }
  }
}

/*!
    @brief  Draw a vertical line. This is also invoked by the Adafruit_GFX
            library in generating many higher-level graphics primitives.
    @param  x
            Column of display -- 0 at left to (screen width -1) at right.
    @param  y
            Topmost row -- 0 at top to (screen height - 1) at bottom.
    @param  h
            Height of line, in pixels.
    @param  color
            Line color, one of: SSD1306_BLACK, SSD1306_WHITE or SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void Adafruit_SSD1306::drawFastVLine(int16_t x, int16_t y, int16_t h,
                                     uint16_t color) {
  bool bSwap = false;
  switch (rotation) {
  case 1:
    // 90 degree rotation, swap x & y for rotation,
    // then invert x and adjust x for h (now to become w)
    bSwap = true;
    ssd1306_swap(x, y);
    x = WIDTH - x - 1;
    x -= (h - 1);
    break;
  case 2:
    // 180 degree rotation, invert x and y, then shift y around for height.
    x = WIDTH - x - 1;
    y = HEIGHT - y - 1;
    y -= (h - 1);
    break;
  case 3:
    // 270 degree rotation, swap x & y for rotation, then invert y
    bSwap = true;
    ssd1306_swap(x, y);
    y = HEIGHT - y - 1;
    break;
  }

  if (bSwap)
    drawFastHLineInternal(x, y, h, color);
  else
    drawFastVLineInternal(x, y, h, color);
}

/*!
    @brief  Draw a vertical line with a width and color. Used by public method
   drawFastHLine,drawFastVLine
        @param x
                   Leftmost column -- 0 at left to (screen width - 1) at right.
        @param __y
                   Row of display -- 0 at top to (screen height -1) at bottom.
        @param __h height of the line in pixels
        @param color
                   Line color, one of: SSD1306_BLACK, SSD1306_WHITE or
   SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void Adafruit_SSD1306::drawFastVLineInternal(int16_t x, int16_t __y,
                                             int16_t __h, uint16_t color) {

  if ((x >= 0) && (x < WIDTH)) { // X coord in bounds?
    if (__y < 0) {               // Clip top
      __h += __y;
      __y = 0;
    }
    if ((__y + __h) > HEIGHT) { // Clip bottom
      __h = (HEIGHT - __y);
    }
    if (__h > 0) { // Proceed only if height is now positive
      // this display doesn't need ints for coordinates,
      // use local byte registers for faster juggling
      uint8_t y = __y, h = __h;
      uint8_t *pBuf = &buffer[(y / 8) * WIDTH + x];

      // do the first partial byte, if necessary - this requires some masking
      uint8_t mod = (y & 7);
      if (mod) {
        // mask off the high n bits we want to set
        mod = 8 - mod;
        // note - lookup table results in a nearly 10% performance
        // improvement in fill* functions
        // uint8_t mask = ~(0xFF >> mod);
        static const uint8_t PROGMEM premask[8] = {0x00, 0x80, 0xC0, 0xE0,
                                                   0xF0, 0xF8, 0xFC, 0xFE};
        uint8_t mask = pgm_read_byte(&premask[mod]);
        // adjust the mask if we're not going to reach the end of this byte
        if (h < mod)
          mask &= (0XFF >> (mod - h));

        switch (color) {
        case SSD1306_WHITE:
          *pBuf |= mask;
          break;
        case SSD1306_BLACK:
          *pBuf &= ~mask;
          break;
        case SSD1306_INVERSE:
          *pBuf ^= mask;
          break;
        }
        pBuf += WIDTH;
      }

      if (h >= mod) { // More to go?
        h -= mod;
        // Write solid bytes while we can - effectively 8 rows at a time
        if (h >= 8) {
          if (color == SSD1306_INVERSE) {
            // separate copy of the code so we don't impact performance of
            // black/white write version with an extra comparison per loop
            do {
              *pBuf ^= 0xFF; // Invert byte
              pBuf += WIDTH; // Advance pointer 8 rows
              h -= 8;        // Subtract 8 rows from height
            } while (h >= 8);
          } else {
            // store a local value to work with
            uint8_t val = (color != SSD1306_BLACK) ? 255 : 0;
            do {
              *pBuf = val;   // Set byte
              pBuf += WIDTH; // Advance pointer 8 rows
              h -= 8;        // Subtract 8 rows from height
            } while (h >= 8);
          }
        }

        if (h) { // Do the final partial byte, if necessary
          mod = h & 7;
          // this time we want to mask the low bits of the byte,
          // vs the high bits we did above
          // uint8_t mask = (1 << mod) - 1;
          // note - lookup table results in a nearly 10% performance
          // improvement in fill* functions
          static const uint8_t PROGMEM postmask[8] = {0x00, 0x01, 0x03, 0x07,
                                                      0x0F, 0x1F, 0x3F, 0x7F};
          uint8_t mask = pgm_read_byte(&postmask[mod]);
          switch (color) {
          case SSD1306_WHITE:
            *pBuf |= mask;
            break;
          case SSD1306_BLACK:
            *pBuf &= ~mask;
            break;
          case SSD1306_INVERSE:
            *pBuf ^= mask;
            break;
          }
        }
      }
    } // endif positive height
  }   // endif x in bounds
}

/*!
    @brief  Return color of a single pixel in display buffer.
    @param  x
            Column of display -- 0 at left to (screen width - 1) at right.
    @param  y
            Row of display -- 0 at top to (screen height -1) at bottom.
    @return true if pixel is set (usually SSD1306_WHITE, unless display invert
   mode is enabled), false if clear (SSD1306_BLACK).
    @note   Reads from buffer contents; may not reflect current contents of
            screen if display() has not been called.
*/
bool Adafruit_SSD1306::getPixel(int16_t x, int16_t y) {
  if ((x >= 0) && (x < width()) && (y >= 0) && (y < height())) {
    // Pixel is in-bounds. Rotate coordinates if needed.
    switch (getRotation()) {
    case 1:
      ssd1306_swap(x, y);
      x = WIDTH - x - 1;
      break;
    case 2:
      x = WIDTH - x - 1;
      y = HEIGHT - y - 1;
      break;
    case 3:
      ssd1306_swap(x, y);
      y = HEIGHT - y - 1;
      break;
    }
    return (buffer[x + (y / 8) * WIDTH] & (1 << (y & 7)));
  }
  return false; // Pixel out of bounds
}

8 最后

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要实现通过阿里云物联网平台对树莓派进行连接,并且可以通过阿里云物联网平台对树莓派远程控制其GPIO17口高低电平的输出,需要满足以下条件: 1. 树莓派需要连接到互联网,并且可以访问阿里云物联网平台。 2. 需要在阿里云物联网平台上创建一个 IoT 设备,并且为该设备生成一个设备证书。 3. 需要在树莓派上安装阿里云物联网平台提供的 SDK。 4. 树莓派上需要安装 Python 3。 下面是详细的步骤和代码: 1. 在阿里云物联网平台上创建一个 IoT 设备,并为该设备生成一个设备证书。 2. 在树莓派上安装阿里云物联网平台提供的 Python SDK。 ``` pip3 install aliyun-python-sdk-core pip3 install aliyun-python-sdk-iot ``` 3. 在树莓派上创建一个 Python 脚本,用于连接到阿里云物联网平台,获取设备 Shadow,以及更新设备 Shadow。 ```python import time from aliyunsdkcore.client import AcsClient from aliyunsdkcore.request import CommonRequest from aliyunsdkiot.request.v20170420 import PubRequest from aliyunsdkiot.request.v20170420 import RRpcRequest # 阿里云物联网平台的配置 client = AcsClient( "<accessKeyId>", # 替换为你的 AccessKeyId "<accessSecret>", # 替换为你的 AccessSecret "cn-shanghai" ) # 获取设备 Shadow def get_device_shadow(): request = CommonRequest() request.set_domain("iot.cn-shanghai.aliyuncs.com") request.set_version("2018-01-20") request.set_product_key("<productKey>") # 替换为你的 ProductKey request.set_device_name("<deviceName>") # 替换为你的 DeviceName request.set_action_name("GetDeviceShadow") response = client.do_action_with_exception(request) return response # 更新设备 Shadow def update_device_shadow(payload): request = PubRequest.PubRequest() request.set_accept_format('json') request.set_ProductKey('<productKey>') # 替换为你的 ProductKey request.set_MessageContent(payload) request.set_TopicFullName('/sys/<productKey>/<deviceName>/thing/update') request.set_Qos(0) response = client.do_action_with_exception(request) return response # 监听云端的控制指令 def listen_control_command(): request = RRpcRequest.RRpcRequest() request.set_accept_format('json') request.set_ProductKey('<productKey>') # 替换为你的 ProductKey request.set_DeviceName('<deviceName>') # 替换为你的 DeviceName request.set_Topic('/sys/<productKey>/<deviceName>/thing/service/+/+') response = client.do_action_with_exception(request) response = str(response, encoding='utf-8') return response # 控制 GPIO17 的高低电平 def set_gpio17(value): # TODO: 在这里编写控制 GPIO17 的代码 pass # 主程序 while True: # 监听云端的控制指令 command = listen_control_command() # 解析控制指令 # TODO: 在这里编写解析控制指令的代码 # 更新设备 Shadow payload = '{"method":"update","state":{"reported":{"gpio17":' + str(value) + '}}}' update_device_shadow(payload) # 等待一段时间 time.sleep(1) ``` 4. 在控制台上创建一个 Topic Filter,用于监听控制指令。 5. 在控制台上创建一个 Rule,将 Topic Filter 和 Python 脚本关联起来。 6. 在控制台上创建一个 Websocket 连接,用于实时查看设备 Shadow 的变化。 以上就是实现通过阿里云物联网平台对树莓派进行连接,并且可以通过阿里云物联网平台对树莓派远程控制其 GPIO17 口高低电平的输出的详细步骤和代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值