Skip to content

Instantly share code, notes, and snippets.

@flostellbrink
Last active September 9, 2021 15:29
Show Gist options
  • Save flostellbrink/61fbadd9cad26843d5697a3c851c0a67 to your computer and use it in GitHub Desktop.
Save flostellbrink/61fbadd9cad26843d5697a3c851c0a67 to your computer and use it in GitHub Desktop.
FlashAir Media Uploader

FlashAir Media Uploader

Script that iterates over all photos and movies saved on an SD card and uploads them to an HTTP endpoint. Uploaded files are renamed. A tiny node server receives the files.

{
"name": "file_receiver",
"version": "0.0.0",
"description": "receive files uploaded by flashair",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "flo",
"license": "ISC",
"dependencies": {
"formidable": "^1.1.1",
"winston": "^2.3.1"
}
}
/* File Receiver
* Handles uploads from flashair.
* Install dependencies with npm using the package.json file: https://docs.npmjs.com/cli/install
* Start with node: https://nodejs.org/en/
*/
var util = require('util')
var spawn = require('child_process').spawn;
var http = require('http');
var formidable = require('formidable');
var fs = require('fs');
var winston = require('winston');
var uploadPath = '/media/main/Bilder/FlashAir/';
// Create log file
winston.add(winston.transports.File, { filename: '/home/pi/upload/winston.json' });
// Redirect all errors into log file
function errorHandler(err) {
if(err) {
winston.error(err);
throw err;
}
}
winston.info('Starting file receiver');
http.createServer(function (req, res) {
// Handle upload requests
if (req.url == '/upload') {
winston.info('Upload request');
var form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) {
// console.log(util.inspect(files, false, null));
if(!files.file) {
winston.error('No file in request');
res.writeHead(400);
res.end('No file in request');
return;
}
var readStream = fs.createReadStream(files.file.path);
var writeStream = fs.createWriteStream(uploadPath + files.file.name);
winston.info('File uploaded to: ' + files.file.path)
readStream.on('error', errorHandler);
writeStream.on('error', errorHandler);
readStream.on('close', function () {
winston.info('File moved to destination with name: ' + files.file.name)
res.end('Done');
fs.unlink(files.file.path, function(err) {
errorHandler(err);
if(err) return;
winston.info('Removed temporary file');
});
/* I run a backup and sort script at this point.
* Feel free to uncomment this and insert your own.
winston.info('Running backup and sorting');
spawn('bash', [ '/home/pi/backup/backup.sh' ]);
*/
});
readStream.pipe(writeStream);
});
} else {
// For other requests show a basic html upload form
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<form action="upload" method="post" enctype="multipart/form-data">');
res.write('<input type="file" name="file"><br>');
res.write('<input type="submit">');
res.write('</form>');
return res.end();
}
}).listen(3333);
-- FlashAir Lua script
-- Copy to 'Lua' folder on your SD card
-- Set as RUN_SCRIPT/SD_EVENT in your SD_WLAN/CONFIG file:
-- LUA_RUN_SCRIPT=/Lua/upload.lua
-- LUA_SD_EVENT=/Lua/upload.lua
local httpendpoint = 'http://192.168.1.13:3333/upload'
local folder = '/DCIM'
-- Recursively iterate through directories
function walk_directory(folder, extension)
for file in lfs.dir(folder) do
-- Not sure if this is necessary, but it keeps 'LUA : not enough memory' out of the log
collectgarbage()
-- Find file path and type
local path = folder .. "/" .. file
local attr = lfs.attributes(path)
if attr.mode == "file" then
handle_file(file, path, extension)
elseif attr.mode == "directory" then
walk_directory(path, extension)
end
end
end
-- Check file for upload, upload and rename it
function handle_file(file, path, extension)
-- Find files not yet marked as "_SAVED"
if string.sub(path, -4, -1) ~= extension then return end
if string.sub(path, -10, -5) == "_SAVED" then return end
-- Upload file to HTTP server
local boundary = '--61141483716826'
local contenttype = 'multipart/form-data; boundary=' .. boundary
-- <!--WLANSDFILE--> will be replaced with file content
local body = '--'.. boundary .. '\r\n'
..'Content-Disposition: form-data; name=\"file\"; filename=\"'..file ..'\"\r\n'
..'Content-Type: text/plain\r\n'
..'\r\n'
..'<!--WLANSDFILE-->\r\n'
..'--' .. boundary .. '--\r\n'
-- Exclude size of placeholder
local size = lfs.attributes(path, 'size') + string.len(body) - 17
-- Execute request (returns response body, response code, response header)
local b, c, h = fa.request {
url = httpendpoint,
method = 'POST',
headers = {['Content-Length'] = tostring(size),
['Content-Type'] = contenttype},
file = path,
body = body
}
b, h = nil
collectgarbage()
if (c ~= 200) then return end
-- Mark file as "_SAVED"
fa.rename(path, string.sub(path, 0, -5) .. '_SAVED' .. string.sub(path, -4, -1))
end
-- Keep checking WLAN status until it is up or 30 seconds passed
for i = 1, 300 do
-- Allow card to connect with WLAN
sleep(100)
-- Check if WLAN is up
if fa.WlanLink() == 1 then
-- Upload pictures first
walk_directory(folder, '.JPG')
-- Upload movies later, they are much larger and might fail
walk_directory(folder, '.MOV')
break
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment