亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

Arduino Mega 拍照自动上传 Google Drive:PHPoC WiFi Shield 方案

组件与物料

Arduino Mega 2560 ×1
PHPoC WiFi Shield 2 for Arduino ×1
Seeed Grove 串口摄像头套件 ×1
Seeed Grove 基座扩展板 ×1
按钮开关 ×1

项目简介

初学者可先了解 Arduino 基础。

演示

工作原理

  1. 通过 OAuth 2.0 (针对 IoT 设备) 登录 Google 账号,获取 access_token。登录过程详见 Hackster 上的相关项目。
  2. 按下按钮时,Arduino 从摄像头获取图片,利用 access_token 通过 Google Drive API 上传。上传 API 详见 Google 官方文档。

操作步骤

其他硬件平台

同类项目已在其他硬件平台实现,见链接。

最适合新手的 Arduino 入门套件

如需购买套件,参考 最佳 Arduino 入门套件。

函数参考

代码

ArduinoGoogleDrive (主程序, Arduino)
#include <Phpoc.h>
#include <Arduino_JSON.h>
#include "grove_camera.h"

String GOOGLE_CLIENT_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com";
String GOOGLE_CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxx";

PhpocServer websocket_server(80);

String http_resp_hearder(PhpocClient &client) {
  String hearder = "";
  while (1) {
    if (client.available()) {
      String line = client.readLine();
      if (line == "\r\n") break;
      else hearder += line;
    }
    if (!client.connected()) { client.stop(); break; }
  }
  return hearder;
}

String http_resp_body(PhpocClient &client) {
  String body = "";
  while (1) {
    if (client.available()) {
      char c = client.read();
      body += c;
    }
    if (!client.connected()) { client.stop(); break; }
  }
  return body;
}

String access_token = "";
String refresh_token = "";
unsigned long access_token_expire_at = 0;

void websocket_send(String msg) {
  char wbuf[256];
  msg.toCharArray(wbuf, msg.length() + 1);
  websocket_server.write(wbuf, msg.length());
}

void googleDeviceOAuthLogin() {
  PhpocClient client;
  if (client.connectSSL("accounts.google.com", 443)) {
    Serial.println(F("Connected to server"));
    String body = F("client_id=");
    body += GOOGLE_CLIENT_ID;
    body += F("&scope=https://www.googleapis.com/auth/drive.file");
    client.println(F("POST /o/oauth2/device/code HTTP/1.1"));
    client.println(F("Host: accounts.google.com"));
    client.println(F("Connection: close"));
    client.println(F("Accept: */*"));
    client.println(F("Content-Type: application/x-www-form-urlencoded"));
    client.print(F("Content-Length: "));
    client.println(body.length());
    client.println();
    client.print(body);
    String response_hearder = http_resp_hearder(client);
    String response_body = http_resp_body(client);
    JSONVar body_json = JSON.parse(response_body);
    if (JSON.typeof(body_json) == "undefined") {
      Serial.println("Parsing input failed!");
      return;
    }
    String device_code = "", user_code = "", verification_url = "";
    long expires_in = 0;
    int interval = 0;
    bool is_valid = true;
    if (body_json.hasOwnProperty("device_code")) device_code = body_json["device_code"]; else is_valid = false;
    if (body_json.hasOwnProperty("user_code")) user_code = body_json["user_code"]; else is_valid = false;
    if (body_json.hasOwnProperty("expires_in")) expires_in = (long)body_json["expires_in"]; else is_valid = false;
    if (body_json.hasOwnProperty("interval")) interval = (int)body_json["interval"]; else is_valid = false;
    if (body_json.hasOwnProperty("verification_url")) verification_url = body_json["verification_url"]; else is_valid = false;
    if (is_valid) {
      Serial.print(F("Next, visit "));
      Serial.print(verification_url);
      Serial.print(F(" on your desktop or smartphone and enter this code: "));
      Serial.println(user_code);
      String msg = "{\"provider\": \"google\",";
      msg += "\"action\": \"LOGIN\",";
      msg += "\"verification_url\": \"" + verification_url + "\",";
      msg += "\"user_code\": \"" + user_code + "\"}";
      websocket_send(msg);
      int poll_max = expires_in / interval;
      body = F("client_id=");
      body += GOOGLE_CLIENT_ID;
      body += F("&client_secret=");
      body += GOOGLE_CLIENT_SECRET;
      body += F("&code=");
      body += device_code;
      body += F("&grant_type=https://oauth.net/grant_type/device/1.0");
      for (int poll_count = 0; poll_count < poll_max; poll_count++) {
        if (client.connectSSL("www.googleapis.com", 443)) {
          client.println(F("POST /oauth2/v4/token HTTP/1.1"));
          client.println(F("Host: www.googleapis.com"));
          client.println(F("Connection: close"));
          client.println(F("Accept: */*"));
          client.println(F("Content-Type: application/x-www-form-urlencoded"));
          client.print(F("Content-Length: "));
          client.println(body.length());
          client.println();
          client.print(body);
          response_hearder = http_resp_hearder(client);
          response_body = http_resp_body(client);
          body_json = JSON.parse(response_body);
          if (JSON.typeof(body_json) == "undefined") {
            Serial.println("Parsing input failed!");
            return;
          }
          long token_expires_in = 0;
          bool is_authorized = true;
          if (body_json.hasOwnProperty("access_token")) access_token = body_json["access_token"]; else is_authorized = false;
          if (body_json.hasOwnProperty("expires_in")) token_expires_in = (long)body_json["expires_in"]; else is_authorized = false;
          if (body_json.hasOwnProperty("refresh_token")) refresh_token = body_json["refresh_token"]; else is_authorized = false;
          if (is_authorized) {
            access_token_expire_at = millis() + token_expires_in * 1000;
            String msg = "{\"provider\": \"google\",";
            msg += "\"action\": \"SUCCESS\"}";
            websocket_send(msg);
            break;
          }
        }
        delay(interval * 1000);
      }
    } else Serial.println(F("Invalid resonse from Google"));
  } else Serial.println(F("NOT Connected to server"));
}

void cameraToGoogleDrive() {
  if (access_token == "") {
    Serial.println(F("access_token is invalid"));
    return;
  }
  long picture_len = cameraGetPicture();
  if (picture_len) {
    PhpocDateTime datetime;
    PhpocClient client;
    String file_name, metadata, jpeg_boundary, end_boundary;
    datetime.date(F("YmdHis"));
    file_name = datetime.date();
    metadata = F("--foo_bar_baz\r\n");
    metadata += F("Content-Type: application/json; charset=UTF-8\r\n\r\n");
    metadata += "{\"title\": \"ARDUINO_" + file_name + "\"}\r\n\r\n";
    jpeg_boundary = F("--foo_bar_baz\r\n");
    jpeg_boundary += F("Content-Type: image/jpeg\r\n\r\n");
    end_boundary = F("\r\n--foo_bar_baz--");
    unsigned long body_len = metadata.length() + jpeg_boundary.length() + picture_len + end_boundary.length();
    int total = 0;
    if (client.connectSSL("www.googleapis.com", 443)) {
      Serial.println(F("Connected to server"));
      client.println(F("POST /upload/drive/v2/files?uploadType=multipart HTTP/1.1"));
      client.println(F("Host: www.googleapis.com"));
      client.println(F("Connection: close"));
      client.println(F("Accept: */*"));
      client.println(F("Content-Type: multipart/related; boundary=foo_bar_baz"));
      client.print(F("Content-Length: "));
      client.println(body_len);
      client.print(F("Authorization: Bearer "));
      client.println(access_token);
      client.println();
      client.print(metadata);
      client.print(jpeg_boundary);
      int i, packet_num = cameraPacketNum();
      char packet[PIC_PKT_LEN] = {0};
      for (i = 0; i < packet_num; i++) {
        long packet_len = cameraGetPacket(i, packet);
        client.write((const uint8_t *)&packet[4], packet_len - 6);
        total += packet_len - 6;
      }
      cameraGetPacket(i, packet);
      client.print(end_boundary);
      String response_hearder = http_resp_hearder(client);
      String response_body = http_resp_body(client);
      Serial.println(response_body);
    }
  } else {
    Serial.print("picture_len:");
    Serial.println(picture_len);
  }
}

int buttonState;
int lastButtonState = LOW;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

bool isButtonPressed(int pin) {
  int reading = digitalRead(pin);
  if (reading != lastButtonState) lastDebounceTime = millis();
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == HIGH) return true;
    }
  }
  lastButtonState = reading;
  return false;
}

void setup() {
  Serial.begin(115200);
  while (!Serial);
  Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
  websocket_server.beginWebSocket("login");
  Serial.print("WebSocket server address : ");
  Serial.println(Phpoc.localIP());
  pinMode(2, INPUT);
  cameraInit(CT_JPEG, PR_160x120, JR_640x480);
}

void loop() {
  PhpocClient client = websocket_server.available();
  if (client) {
    String ws_str = client.readLine();
    if (ws_str == "google\r\n") {
      googleDeviceOAuthLogin();
    }
  }
  if (isButtonPressed(2)) {
    if (access_token != "" && access_token_expire_at > millis())
      cameraToGoogleDrive();
    else
      Serial.println("access_token is invalid, please login again");
  }
}
login.php (PHP) - 上传到 PHPoC Shield 提供 Web 登录界面
<html>
<head>
<title>PHPoC / <?echo system("uname -i")?></title>
<meta content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=0.5, width=device-width, user-scalable=yes" name="viewport">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto" type="text/css">
<style>
body { text-align:center; }
.center { margin: auto; position: absolute; -webkit-backface-visibility: hidden; left:0; right:0; text-align: center; top: 20%; }
.hearder { width: 100%; max-width:400px; color: #008B8B; padding: 5px; border-bottom: solid; margin-bottom: 5px; font-size: 200%; display: inline-block; }
.wc_text, .loader { display: inline-block; width: 100%; max-width:300px; line-height: 150%; }
.code { font-family: "Courier New", Courier, monospace; font-size: 150%; font-weight: bold; color: #A52A2A; }
.success {font-weight: bold; color: #A52A2A;}
.lds-roller { display: inline-block; position: relative; width: 64px; height: 64px; }
.lds-roller div { animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; transform-origin: 32px 32px; }
.lds-roller div:after { content: " "; display: block; position: absolute; width: 6px; height: 6px; border-radius: 50%; background: #A52A2A; margin: -3px 0 0 -3px; }
.lds-roller div:nth-child(1) {animation-delay: -0.036s;} .lds-roller div:nth-child(1):after {top: 50px;left: 50px;}
.lds-roller div:nth-child(2) {animation-delay: -0.072s;} .lds-roller div:nth-child(2):after {top: 54px;left: 45px;}
.lds-roller div:nth-child(3) {animation-delay: -0.108s;} .lds-roller div:nth-child(3):after {top: 57px;left: 39px;}
.lds-roller div:nth-child(4) {animation-delay: -0.144s;} .lds-roller div:nth-child(4):after {top: 58px;left: 32px;}
.lds-roller div:nth-child(5) {animation-delay: -0.18s;} .lds-roller div:nth-child(5):after {top: 57px;left: 25px;}
.lds-roller div:nth-child(6) {animation-delay: -0.216s;} .lds-roller div:nth-child(6):after {top: 54px;left: 19px;}
.lds-roller div:nth-child(7) {animation-delay: -0.252s;} .lds-roller div:nth-child(7):after {top: 50px;left: 14px;}
.lds-roller div:nth-child(8) {animation-delay: -0.288s;} .lds-roller div:nth-child(8):after {top: 45px;left: 10px;}
@keyframes lds-roller {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}}
</style>
<script>
var ws;
function init() {
  ws = new WebSocket("ws:///login", "text.phpoc");
  ws.onopen = ws_onopen;
  ws.onclose = ws_onclose;
  ws.onmessage = ws_onmessage;
}
function ws_onopen() { if(ws && (ws.readyState == 1)) ws.send('google\r\n'); }
function ws_onclose() { alert('CANNOT connect to device. Please reload webpage'); ws.onopen = null; ws.onclose = null; ws.onmessage = null; ws = null; }
function ws_onmessage(e_msg) {
  e_msg = e_msg || window.event;
  var obj = JSON.parse(e_msg.data);
  var wc_text = document.getElementById('wc_text');
  if(obj.action == 'LOGIN') {
    wc_text.innerHTML = 'Next, visit <a href="' + obj.verification_url + '" target="_blank">' + obj.verification_url + '</a> and enter this code:<br>';
    wc_text.innerHTML += '<span class="code">' + obj.user_code + '</span>';
  } else if(obj.action == 'SUCCESS') {
    document.getElementById('loader').style.display = 'none';
    wc_text.innerHTML = '<span class="success">Success!</span><br>';
    wc_text.innerHTML += 'You are now logged in from Arduino via PHPoC Shield';
  }
}
window.onload = init;
</script>
</head>
<body>
<div class="center">
<div class="hearder">
<div style="font-size: 150%">
<span style="color:#4285F4">G</span>
<span style="color:#EA4335">o</span>
<span style="color:#FBBC05">o</span>
<span style="color:#4285F4">g</span>
<span style="color:#34A853">l</span>
<span style="color:#EA4335">e</span>
</div>
Login for Arduino
</div>
<br><br>
<div class="wc_text" id="wc_text"></div><br><br>
<div class="loader" id="loader">
<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>
</div>
</div>
</body>
</html>
grove_camera.h (C/C++) - Grove 摄像头驱动库
#define PIC_PKT_LEN 512
#define CAM_ADDR 0
#define CT_GRAYSCALE_2 0x01
#define CT_GRAYSCALE_4 0x02
#define CT_GRAYSCALE_8 0x03
#define CT_COLOR_12 0x05
#define CT_COLOR_16 0x06
#define CT_JPEG 0x07
#define PR_80x60 0x01
#define PR_160x120 0x03
#define JR_80x64 0x01
#define JR_160x128 0x03
#define JR_320x240 0x05
#define JR_640x480 0x07

const byte camera_address = (CAM_ADDR << 5);
unsigned int camera_packet_num;
unsigned int camera_last_packet_len;

void cameraClearRxBuf() { while (Serial.available()) Serial.read(); }
void cameraSendCmd(char cmd[], int cmd_len) { for (char i = 0; i < cmd_len; i++) Serial.print(cmd[i]); }
unsigned int cameraPacketNum() { return camera_packet_num; }

void cameraInit(int color_type, int preview_resolution, int jpeg_resolution) {
  char cmd[] = {0xaa, 0x0d | camera_address, 0x00, 0x00, 0x00, 0x00};
  unsigned char resp[6];
  Serial.setTimeout(500);
  while (1) {
    cameraSendCmd(cmd, 6);
    if (Serial.readBytes((char *)resp, 6) != 6) continue;
    if (resp[0] == 0xaa && resp[1] == (0x0e | camera_address) && resp[2] == 0x0d && resp[4] == 0 && resp[5] == 0) {
      if (Serial.readBytes((char *)resp, 6) != 6) continue;
      if (resp[0] == 0xaa && resp[1] == (0x0d | camera_address) && resp[2] == 0 && resp[3] == 0 && resp[4] == 0 && resp[5] == 0) break;
    }
  }
  cmd[1] = 0x0e | camera_address;
  cmd[2] = 0x0d;
  cameraSendCmd(cmd, 6);
  char cmd2[] = {0xaa, 0x01 | camera_address, 0x00, color_type, preview_resolution, jpeg_resolution};
  Serial.setTimeout(100);
  while (1) {
    cameraClearRxBuf();
    cameraSendCmd(cmd2, 6);
    if (Serial.readBytes((char *)resp, 6) != 6) continue;
    if (resp[0] == 0xaa && resp[1] == (0x0e | camera_address) && resp[2] == 0x01 && resp[4] == 0 && resp[5] == 0) break;
  }
  char cmd3[] = {0xaa, 0x06 | camera_address, 0x08, PIC_PKT_LEN & 0xff, (PIC_PKT_LEN >> 8) & 0xff, 0};
  while (1) {
    cameraClearRxBuf();
    cameraSendCmd(cmd3, 6);
    if (Serial.readBytes((char *)resp, 6) != 6) continue;
    if (resp[0] == 0xaa && resp[1] == (0x0e | camera_address) && resp[2] == 0x06 && resp[4] == 0 && resp[5] == 0) break;
  }
}

long cameraGetPicture() {
  char cmd[] = {0xaa, 0x04 | camera_address, 0x01, 0x00, 0x00, 0x00};
  unsigned char resp[6];
  unsigned long picTotalLen = 0;
  while (1) {
    cameraClearRxBuf();
    cameraSendCmd(cmd, 6);
    if (Serial.readBytes((char *)resp, 6) != 6) continue;
    if (resp[0] == 0xaa && resp[1] == (0x0e | camera_address) && resp[2] == 0x04 && resp[4] == 0 && resp[5] == 0) {
      Serial.setTimeout(1000);
      if (Serial.readBytes((char *)resp, 6) != 6) continue;
      if (resp[0] == 0xaa && resp[1] == (0x0a | camera_address) && resp[2] == 0x01) {
        picTotalLen = (resp[3]) | (resp[4] << 8) | (resp[5] << 16);
        break;
      }
    }
  }
  camera_packet_num = (picTotalLen) / (PIC_PKT_LEN - 6);
  camera_last_packet_len = PIC_PKT_LEN;
  if ((picTotalLen % (PIC_PKT_LEN - 6)) != 0) {
    camera_packet_num += 1;
    camera_last_packet_len = picTotalLen % (PIC_PKT_LEN - 6) + 6;
  }
  return picTotalLen;
}

long cameraGetPacket(unsigned int i, char* buf) {
  char cmd[] = {0xaa, 0x0e | camera_address, 0x00, 0x00, 0x00, 0x00};
  Serial.setTimeout(100);
  if (i < camera_packet_num) {
    cmd[4] = i & 0xff;
    cmd[5] = (i >> 8) & 0xff;
    cameraClearRxBuf();
    cameraSendCmd(cmd, 6);
    int pkt_len = (i < (camera_packet_num - 1)) ? PIC_PKT_LEN : camera_last_packet_len;
    uint16_t cnt = Serial.readBytes((char *)buf, pkt_len);
    return cnt;
  } else {
    cmd[4] = 0xf0;
    cmd[5] = 0xf0;
    cameraSendCmd(cmd, 6);
  }
  return 0;
}

原理图

Arduino Mega 拍照自动上传 Google Drive:PHPoC WiFi Shield 方案

制造工艺

  1. DIY 蓝牙控制 Arduino 间谍小车
  2. FlickMote:基于手势的智能家居红外遥控器
  3. DIY Arduino TV-B-Gone:万能关机遥控器
  4. 自制大尺寸LED主时钟带闹钟功能 — 12米外清晰可见
  5. Find Me:基于 Arduino 与蓝牙的智能寻物器制作指南
  6. Arduino 项目优化供电方案:从按键开关到电池监测与数据记录
  7. Arduino 井字棋游戏:MAX7219 点阵屏 + 纸板外壳 DIY
  8. 从零构建智能 Arduino 四足机器人:完整指南
  9. 语音控制家电:Google Assistant + 蓝牙 + Arduino UNO
  10. DIY Arduino 手柄方向盘 - 游戏竞速模拟器自制指南
  11. 基于 Arduino 与 Google Sheets 的实时 RFID 考勤系统
  12. 谷歌投资ADT 4.5亿美元加速IoT安防普及