[Tech Blog] FlashAir: Uploading to Google Drive with LUA



In this example, we’re going to show you how to upload an image to Google Drive using LUA, and Google’s device API. The idea is for it to be run from something with limited input capabilities, such as Toshiba’s “FlashAir” SD card. Unfortunately we’ll still need a device with full input capabilities to set everything up, but once the setup is complete you’re left with a headless system that can upload indefinitely.


1. Setting up the Project in Google

The first step is to create a project in Google’s “Developers Console” Once this is done, enable the Google Drive API and SDK.

Next, go to the “Credentials” section (under “APIs & auth”) in your project and select “Create new client ID”. Choose “Installed application”, then “Other” for installed application type.

2. Authorizing Our Device

Now that that’s setup, we need to authorize our device with Google – which is a two-step process. First we need to send a post request to “accounts.google.com/o/oauth2/device/code”. Set the content type to “application/x-www-form-urlencoded”, with two fields client_id (the Client ID we generated above, under “Client ID for native application”), and scope (which should be set to “https://docs.google.com/feeds”). You can use several tools to accomplish this – but the chrome extension “postman” makes it super easy.


POST /o/oauth2/device/code HTTP/1.1
Host: accounts.google.com
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded

client_id={Your client ID}

NOTE: Google tells you to use “https://www.googleapis.com/auth/drive” for scope, but this will return a “Invalid_scope: Not authorized to request the scope”. Using /feeds/ instead will grant us the Google Drive authorization we need.

Now for part two! The response will contain a user_code, and a verification_url. Navigate to that url (it’s probably https://www.google.com/device) then enter the user_code. Hold on the device code too!

Example Response:

"device_code": {Device code},
    "user_code": {Your user code},
    "verification_url": "https://www.google.com/device",
    "expires_in": 1800,
    "interval": 5

3. Getting a Permanent Refresh Token

Now that everything’s properly authorized by Google, we still need to get a refresh token that our app is going to use. We’re going to use that to get yet another token, the temporary “auth” token, which will actually let us upload to Google Drive. There are a lot of tokens. To get the refresh token, you send a post like the following (line breaks added for readability):

POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded

client_id={Your full client ID}&
client_secret={Your client secret}&
code={Your device code]&

The grant type is actually “http://oauth.net/grant_type/device/1.0” however it gets switched to %’s.

You should receive something that looks like:

"access_token": {Your access token here},
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": {Your refresh token here}

Finally, we can start scripting our headless upload! The main token we really need is the refresh_token. The access_token will work for a short time, but it will expire. With the refresh token we can always get a fresh access code when we need to (which is pretty constantly, as the authorization doesn’t last long).

4. Required Lua Imports

The only two Lua library imports which will be used are ssl.https and JSON which is a stand alone file which can be found at http://regex.info/blog/lua/json

Example code:

local https = require 'ssl.https'
local JSON = require 'JSON'

We will follow these by creating local variables containing all the necessary information.

Example code:

-- Basic account info
local client_id = “{Your full client ID}”
local client_secret = “{Your client secret}”
local refresh_token = “{Your refresh token}”
-- For refresh
local scope = "https://docs.google.com/feeds"
local response_type = "code"
local access_type = "offline"

5. Using the Refresh Token to Re-authenticate

Before we can upload anything, chances are we’re going to need to re-authenticate with the server, so lets put together a getAuth() method first. In our method we will set the message to be sent, find it’s length (as this is a required parameter), then make the https request. The response will arrive as an “Array” but it’s actually all one big string for the first and sole value as Lua can’t natively decode JSON. Conveniently we imported the JSON library earlier, so we can use that to parse it into a table and retrieve our new access token.

Example function:

local function getAuth()

  -- Set our message
  local mes="client_id="..client_id

  local length = string.len(mes)
  print("Sending: ["..mes.."]")
  print "\n"
  b, c, h = fa.request{
    url = "https://accounts.google.com/o/oauth2/token",
    headers = {
        ["Content-Type"] = "application/x-www-form-urlencoded",
        ["Content-Length"] = length,
    method = "POST",

  local tempTable = {}

  tempTable = cjson.decode(b)

  access_token = tempTable["access_token"]

6.The Lua Function that Does the Upload

Now that we have a new access token, we’re ready to upload! We’ll do this with another https post request using ssl.https. We just need to give it an image file, set our authorization code, and we’re done.


local function uploadTest(token)
  local fileSize = lfs.attributes(filePath,"size")
  b, c, h = fa.request{
    url = "https://www.googleapis.com/upload/drive/v2/files",
    headers = {
      ["Content-Type"] = "image/jpeg",
      ["Content-Length"] = fileSize, -- calculate file size
      ["authorization"] = "Bearer "..token,
    method = "POST",
    --NOTE: You probably want to set your own file here,
    --or maybe even pass it as a parameter!


If you’ve been following our examples up until this point, combining them in the order presented and running the two functions using the following code at the bottom of the file will result in an uploaded file up to your Google drive (see accounts.google.com.).

Final code to run the functions:




Comments are closed.