mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 11:03:48 +01:00
preliminary printing support (through ultifi).
This commit is contained in:
parent
9d6992ce95
commit
0f4b938444
@ -16,7 +16,7 @@ local M = {}
|
||||
|
||||
--NOTE: pcall protects from invocation exceptions, which is what we need except
|
||||
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
|
||||
M.DEBUG_PCALLS = true
|
||||
M.DEBUG_PCALLS = false
|
||||
|
||||
--This enables debugging of the REST API from the command-line, specify the path and optionally the request method as follows: 'p=/mod/func rq=POST'
|
||||
M.DEBUG_API = true
|
||||
@ -24,6 +24,7 @@ M.DEBUG_API = true
|
||||
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||
M.API_INCLUDE_ENDPOINT_INFO = false
|
||||
|
||||
M.API_BASE_URL_PATH = 'doodle3d.com' -- includes any base path if necessary (e.g. 'localhost/~user')
|
||||
|
||||
M.network_ap_ssid = {
|
||||
default = 'd3d-ap-%%MAC_ADDR_TAIL%%',
|
||||
|
233
src/rest/api/api_printer.lua
Normal file
233
src/rest/api/api_printer.lua
Normal file
@ -0,0 +1,233 @@
|
||||
local log = require('util.logger')
|
||||
local utils = require('util.utils')
|
||||
local settings = require('util.settings')
|
||||
|
||||
local M = {
|
||||
isApi = true
|
||||
}
|
||||
|
||||
|
||||
local ULTIFI_BASE_PATH = '/tmp/UltiFi'
|
||||
local TEMPERATURE_FILE = 'temp.out'
|
||||
local COMMAND_FILE = 'command.in'
|
||||
local GCODE_TMP_FILE = '/tmp/UltiFi/combined.gc'
|
||||
|
||||
-- returns full path + ultifi path or nil
|
||||
local function printerExists(id)
|
||||
if id == nil then return nil end
|
||||
|
||||
local path = '/dev/ttyACM' .. id
|
||||
local upath = ULTIFI_BASE_PATH .. '/ttyACM' .. id
|
||||
if utils.exists(path) then return path,upath end
|
||||
|
||||
path = '/dev/ttyUSB' .. id
|
||||
upath = ULTIFI_BASE_PATH .. '/ttyUSB' .. id
|
||||
if utils.exists(path) then return path,upath end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- returns printerId,devicePath,ultifiPath or nil if printer does not exist
|
||||
-- when nil is returned, response has already been set as an error
|
||||
local function getPrinterDataOrFail(request, response)
|
||||
local id = tonumber(request:get("id"))
|
||||
|
||||
if id == nil then
|
||||
response:setError("missing id argument")
|
||||
return nil
|
||||
end
|
||||
|
||||
local devpath,ultipath = printerExists(id)
|
||||
|
||||
if not devpath then
|
||||
response:setError("printer does not exist")
|
||||
response:addData('id', id)
|
||||
return nil
|
||||
end
|
||||
|
||||
return id,devpath,ultipath
|
||||
end
|
||||
|
||||
-- assumes printerPath exists, returns true if successful, false if command file already exists and is non-empty (i.e. printer busy),
|
||||
-- nil+err if file could not be opened
|
||||
local function sendGcode(printerPath, gcode)
|
||||
local cmdPath = printerPath .. '/' .. COMMAND_FILE
|
||||
local f,msg = io.open(cmdPath, 'a+') -- 'a+' is important, do not overwrite current contents in any case
|
||||
|
||||
if not f then return nil,msg end
|
||||
if utils.fileSize(f) > 0 then return false end
|
||||
|
||||
log:debug("sending " .. gcode:len() .. " bytes of gcode")
|
||||
f:write(gcode)
|
||||
f:close()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function addToGcodeFile(gcode)
|
||||
if not gcode or type(gcode) ~= 'string' then return nil,"missing gcode data" end
|
||||
|
||||
local gcf,msg = io.open(GCODE_TMP_FILE, 'a+')
|
||||
if not gcf then return nil,msg end
|
||||
|
||||
log:debug("appending " .. gcode:len() .. " bytes of gcode to " .. GCODE_TMP_FILE)
|
||||
gcf:write(gcode)
|
||||
gcf:close()
|
||||
end
|
||||
|
||||
-- assumes printerPath exists, returns true if successful, false if command file already exists and is non-empty (i.e. printer busy),
|
||||
-- nil+err if file could not be opened
|
||||
local function printGcodeFile(printerPath)
|
||||
local cmdPath = printerPath .. '/' .. COMMAND_FILE
|
||||
local cmdf,msg = io.open(cmdPath, 'a+') -- 'a+' is important, do not overwrite current contents in any case
|
||||
|
||||
if not cmdf then return nil,msg end
|
||||
if utils.fileSize(cmdf) > 0 then return false end
|
||||
|
||||
log:debug("starting print of gcode in " .. GCODE_TMP_FILE)
|
||||
cmdf:write('(SENDFILE=' .. GCODE_TMP_FILE)
|
||||
cmdf:close()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function isBusy(printerPath)
|
||||
local cmdPath = printerPath .. '/' .. COMMAND_FILE
|
||||
|
||||
if not utils.exists(cmdPath) then return false end
|
||||
|
||||
local f,msg = io.open(cmdPath, 'r')
|
||||
|
||||
if not f then return nil,msg end
|
||||
local size = utils.fileSize(f)
|
||||
f:close()
|
||||
|
||||
return f > 0
|
||||
end
|
||||
|
||||
|
||||
function M._global(request, response)
|
||||
-- TODO: list all printers (based on /dev/ttyACM* and /dev/ttyUSB*)
|
||||
response:setSuccess()
|
||||
end
|
||||
|
||||
--requires id(int)
|
||||
--accepts with_raw(bool) to include raw printer response
|
||||
function M.temperature(request, response)
|
||||
local withRaw = utils.toboolean(request:get("with_raw"))
|
||||
|
||||
local argId,devpath,ultipath = getPrinterDataOrFail(request, response)
|
||||
if argId == nil then return end
|
||||
|
||||
local f = io.open(ultipath .. '/' .. TEMPERATURE_FILE)
|
||||
|
||||
if not f then
|
||||
response:setError("could not open temperature file")
|
||||
response:addData('id', argId)
|
||||
return
|
||||
end
|
||||
|
||||
local tempText = f:read('*all')
|
||||
f:close()
|
||||
|
||||
local hotend, hotendTarget, bed, bedTarget = tempText:match('T:(.*)%s+/(.*)%s+B:(.*)%s/(.*)%s+@.*')
|
||||
|
||||
response:setSuccess()
|
||||
if withRaw then response:addData('raw', tempText) end
|
||||
response:addData('hotend', hotend)
|
||||
response:addData('bed', bed)
|
||||
response:addData('hotend_target', hotendTarget)
|
||||
response:addData('bed_target', bedTarget)
|
||||
end
|
||||
|
||||
--requires id(int)
|
||||
function M.busy(request, response)
|
||||
local argId,devpath,ultipath = getPrinterDataOrFail(request, response)
|
||||
if argId == nil then return end
|
||||
|
||||
local b,msg = isBusy(ultipath)
|
||||
|
||||
if b == nil then
|
||||
response:setError("could not determine printer state")
|
||||
response:addData('msg', msg)
|
||||
else
|
||||
response:setSuccess()
|
||||
response:addData('busy', b)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M.printing(request, response)
|
||||
response:setError("not implemented")
|
||||
response:addData('api_refer', response:apiURL('printer', 'busy'))
|
||||
end
|
||||
|
||||
--requires id(int)
|
||||
function M.heatup_POST(request, response)
|
||||
local argId,devpath,ultipath = getPrinterDataOrFail(request, response)
|
||||
if argId == nil then return end
|
||||
|
||||
local gcode = settings.get('printer.autoWarmUpCommand')
|
||||
local rv,msg = sendGcode(ultipath, gcode)
|
||||
|
||||
if rv then
|
||||
response:setSuccess()
|
||||
elseif rv == false then
|
||||
response:setFail("printer is busy")
|
||||
else
|
||||
response:setError("could not send gcode")
|
||||
response:addData('msg', msg)
|
||||
end
|
||||
end
|
||||
|
||||
--requires id(int), gcode(string)
|
||||
--accepts: first(bool) (chunks will be concatenated but output file will be cleared first if this argument is true)
|
||||
--accepts: last(bool) (chunks will be concatenated and only when this argument is true will printing be started)
|
||||
function M.print_POST(request, response)
|
||||
local argId,devpath,ultipath = getPrinterDataOrFail(request, response)
|
||||
if argId == nil then return end
|
||||
|
||||
local argGcode = request:get("gcode")
|
||||
local argIsFirst = utils.toboolean(request:get("first"))
|
||||
local argIsLast = utils.toboolean(request:get("last"))
|
||||
|
||||
if argGcode == nil or argGcode == '' then
|
||||
response:setError("missing gcode argument")
|
||||
return
|
||||
end
|
||||
|
||||
if argIsFirst == true then
|
||||
log:debug("clearing all gcode in " .. GCODE_TMP_FILE)
|
||||
response:addData('gcode_clear',true)
|
||||
os.remove(GCODE_TMP_FILE)
|
||||
end
|
||||
|
||||
local rv,msg
|
||||
|
||||
rv,msg = addToGcodeFile(argGcode)
|
||||
if rv == nil then
|
||||
response:setError("could not add gcode")
|
||||
response:addData('msg', msg)
|
||||
return
|
||||
else
|
||||
response:addData('gcode_append',gcode:len())
|
||||
end
|
||||
|
||||
if argIsLast == true then
|
||||
rv,msg = printGcodeFile(ultipath)
|
||||
|
||||
if rv then
|
||||
response:setSuccess()
|
||||
response:addData('gcode_print',true)
|
||||
elseif rv == false then
|
||||
response:setFail("printer is busy")
|
||||
else
|
||||
response:setError("could not send gcode")
|
||||
response:addData('msg', msg)
|
||||
end
|
||||
else
|
||||
response:setSuccess()
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
@ -1,5 +1,6 @@
|
||||
local JSON = require('util/JSON')
|
||||
local s = require('util.settings')
|
||||
local settings = require('util.settings')
|
||||
local defaults = require('conf_defaults')
|
||||
|
||||
local M = {}
|
||||
M.__index = M
|
||||
@ -28,7 +29,7 @@ function M.new(requestObject)
|
||||
local rqId = requestObject:get(REQUEST_ID_ARGUMENT)
|
||||
if rqId ~= nil then self.body[REQUEST_ID_ARGUMENT] = rqId end
|
||||
|
||||
if s.API_INCLUDE_ENDPOINT_INFO == true then
|
||||
if settings.API_INCLUDE_ENDPOINT_INFO == true then
|
||||
self.body['module'] = requestObject:getRequestedApiModule()
|
||||
self.body['function'] = requestObject:getRealApiFunctionName() or ''
|
||||
end
|
||||
@ -60,7 +61,7 @@ function M:setError(msg)
|
||||
self.body.status = 'error'
|
||||
if msg ~= '' then self.body.msg = msg end
|
||||
|
||||
self:addData('more_info', 'http://doodle3d.nl/wiki/wiki/communication-api')
|
||||
self:addData('more_info', 'http://' .. defaults.API_BASE_URL_PATH .. '/wiki/wiki/communication-api')
|
||||
end
|
||||
|
||||
--NOTE: with this method, to add nested data, it is necessary to precreate the table and add it with its root key
|
||||
@ -69,6 +70,12 @@ function M:addData(k, v)
|
||||
self.body.data[k] = v
|
||||
end
|
||||
|
||||
function M:apiURL(mod, func)
|
||||
if not mod then return nil end
|
||||
if func then func = '/' .. func else func = "" end
|
||||
return 'http://' .. defaults.API_BASE_URL_PATH .. '/cgi-bin/d3dapi/' .. mod .. func
|
||||
end
|
||||
|
||||
function M:serializeAsJson()
|
||||
return JSON:encode(self.body)
|
||||
end
|
||||
|
@ -94,6 +94,25 @@ function M:test_create()
|
||||
assert(actualContents == testContents)
|
||||
end
|
||||
|
||||
function M:test_size()
|
||||
local tempFile = '/tmp/132uytjhgfr24e'
|
||||
local text = 'Why is a raven like a writing-desk?'
|
||||
|
||||
local f = io.open(tempFile, 'w')
|
||||
f:write(text)
|
||||
f:close()
|
||||
|
||||
local f = io.open(tempFile, 'r')
|
||||
assert(f:seek() == 0)
|
||||
assert(utils.fileSize(f) == text:len())
|
||||
assert(f:seek() == 0)
|
||||
f:read(4)
|
||||
assert(utils.fileSize(f) == text:len())
|
||||
assert(f:seek() == 4)
|
||||
f:close()
|
||||
os.remove(tempFile)
|
||||
end
|
||||
|
||||
function M:test_symlink()
|
||||
assert(false, 'not implemented')
|
||||
end
|
||||
|
@ -12,6 +12,13 @@ function string:split(div)
|
||||
return arr
|
||||
end
|
||||
|
||||
function M.fileSize(file)
|
||||
local current = file:seek()
|
||||
local size = file:seek('end')
|
||||
file:seek('set', current)
|
||||
return size
|
||||
end
|
||||
|
||||
function M.toboolean(s)
|
||||
if not s then return false end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user