Files
opus-apps/milo/MiloRemote.lua
2018-11-13 21:31:27 -05:00

419 lines
10 KiB
Lua

_G.requireInjector(_ENV)
local Config = require('config')
local Event = require('event')
local Socket = require('socket')
local sync = require('sync').sync
local UI = require('ui')
local Util = require('util')
local colors = _G.colors
local device = _G.device
local os = _G.os
local socket
local SHIELD_SLOT = 2
local config = Config.load('miloRemote', { })
local page = UI.Page {
dummy = UI.Window {
x = 1, ex = -13, y = 1, height = 1,
infoBar = UI.StatusBar {
backgroundColor = colors.lightGray,
},
},
refresh = UI.Button {
y = 1, x = -12,
event = 'refresh',
text = 'Refresh',
},
setupButton = UI.Button {
y = 1, x = -3,
event = 'setup',
text = '\206',
help = 'Configuration',
},
grid = UI.Grid {
y = 2, ey = -2,
columns = {
{ heading = ' Qty', key = 'count' , width = 4, justify = 'right' },
{ heading = 'Name', key = 'displayName' },
},
values = { },
sortColumn = 'displayName',
help = '^(s)tack, ^(a)ll'
},
statusBar = UI.Window {
y = -1,
filter = UI.TextEntry {
x = 1, ex = -9,
limit = 50,
shadowText = 'filter',
backgroundColor = colors.cyan,
backgroundFocusColor = colors.cyan,
accelerators = {
[ 'enter' ] = 'eject',
},
},
amount = UI.TextEntry {
x = -8, ex = -4,
limit = 3,
shadowText = '1',
shadowTextColor = colors.gray,
backgroundColor = colors.black,
backgroundFocusColor = colors.black,
accelerators = {
[ 'enter' ] = 'eject_specified',
},
help = 'Specify an amount to send',
},
display = UI.Button {
x = -3,
event = 'toggle_display',
value = 0,
text = 'A',
help = 'Toggle display mode',
},
},
accelerators = {
r = 'refresh',
[ 'control-r' ] = 'refresh',
[ 'control-e' ] = 'eject',
[ 'control-s' ] = 'eject_stack',
[ 'control-a' ] = 'eject_all',
q = 'quit',
},
setup = UI.SlideOut {
backgroundColor = colors.cyan,
titleBar = UI.TitleBar {
title = 'Remote Setup',
},
form = UI.Form {
x = 2, ex = -2, y = 2, ey = -1,
values = config,
[1] = UI.TextEntry {
formLabel = 'Server', formKey = 'server',
help = 'ID for the server',
shadowText = 'Milo server ID',
limit = 6,
validate = 'numeric',
required = true,
},
[2] = UI.TextEntry {
formLabel = 'User Name', formKey = 'user',
help = 'User name for bound manipulator',
shadowText = 'User name',
limit = 50,
required = true,
},
[3] = UI.TextEntry {
formLabel = 'Return Slot', formKey = 'slot',
help = 'Use a slot for sending to storage',
shadowText = 'Inventory slot #',
limit = 5,
validate = 'numeric',
required = false,
},
[4] = UI.Checkbox {
formLabel = 'Shield Slot', formKey = 'useShield',
help = 'Or, use the shield slot for sending'
},
info = UI.TextArea {
x = 1, ex = -1, y = 7, ey = -3,
textColor = colors.yellow,
marginLeft = 0,
marginRight = 0,
value = [[The Milo turtle must connect to a manipulator with a ]] ..
[[bound introspection module. The neural interface must ]] ..
[[also have an introspection module.]],
},
[5] = UI.Button {
x = 1, y = -2,
text = 'Force scan', event = 'rescan', help = 'Force a scan of all inventories',
},
},
statusBar = UI.StatusBar {
backgroundColor = colors.cyan,
},
},
displayMode = 0,
items = { },
}
local function filterItems(t, filter, displayMode)
if filter or displayMode > 0 then
local r = { }
if filter then
filter = filter:lower()
end
for _,v in pairs(t) do
if not filter or string.find(v.lname, filter, 1, true) then
if not displayMode or
displayMode == 0 or
displayMode == 1 and v.count > 0 or
displayMode == 2 and v.has_recipe then
table.insert(r, v)
end
end
end
return r
end
return t
end
function page:setStatus(status)
self.dummy.infoBar:setStatus(status)
self:sync()
end
function page:sendRequest(data)
local response
if not config.server then
self:setStatus('Invalid configuration')
Event.onTimeout(2, function()
self:setStatus('')
end)
return
end
sync(self, function()
local msg
for _ = 1, 2 do
if not socket or not socket.connected then
self:setStatus('connecting ...')
socket, msg = Socket.connect(config.server, 4242)
if socket then
socket:write(config.user)
local r = socket:read(2)
if r and not r.msg then
self:setStatus('connected ...')
else
msg = r and r.msg or 'Timed out'
socket:close()
socket = nil
break
end
end
end
if socket then
if socket:write(data) then
response = socket:read(2)
if response then
if response.msg then
self:setStatus(response.msg)
response = nil
end
Event.onTimeout(2, function()
self:setStatus('')
end)
return
end
end
socket:close()
end
end
self:setStatus(msg or 'Failed to connect')
Event.onTimeout(2, function()
self:setStatus('')
end)
end)
return response
end
function page.grid:getRowTextColor(row, selected)
if row.is_craftable then
return colors.yellow
end
if row.has_recipe then
return colors.cyan
end
return UI.Grid:getRowTextColor(row, selected)
end
function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
row.count = row.count > 0 and Util.toBytes(row.count) or ''
return row
end
function page:transfer(item, count)
local response = self:sendRequest({ request = 'transfer', item = item, count = count })
if response then
item.count = response.current - response.count
self.grid:draw()
if response.craft > 0 then
self:setStatus(response.craft .. ' crafting ...')
elseif response.craft + response.count < response.requested then
self:setStatus((response.craft + response.count) .. ' available ...')
end
end
end
function page.setup:eventHandler(event)
if event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
end
return UI.SlideOut.eventHandler(self, event)
end
function page:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
elseif event.type == 'setup' then
self.setup.form:setValues(config)
self.setup:show()
elseif event.type == 'form_complete' then
Config.update('miloRemote', config)
self.setup:hide()
self:refresh('list')
self.grid:draw()
elseif event.type == 'form_cancel' then
self.setup:hide()
elseif event.type == 'focus_change' then
self.dummy.infoBar:setStatus(event.focused.help)
elseif event.type == 'eject' or event.type == 'grid_select' then
local item = self.grid:getSelected()
if item then
self:setStatus('requesting 1 ...')
self:transfer(item, 1)
end
elseif event.type == 'eject_stack' then
local item = self.grid:getSelected()
if item then
self:setStatus('requesting stack ...')
self:transfer(item, 'stack')
end
elseif event.type == 'eject_all' then
local item = self.grid:getSelected()
if item then
self:setStatus('requesting all ...')
self:transfer(item, 'all')
end
elseif event.type == 'eject_specified' then
local item = self.grid:getSelected()
local count = tonumber(self.statusBar.amount.value)
if item and count then
self.statusBar.amount:reset()
self:setFocus(self.statusBar.filter)
self:setStatus('requesting ' .. count .. ' ...')
self:transfer(item, count)
else
self:setStatus('nope ...')
end
elseif event.type == 'rescan' then
self:setFocus(self.statusBar.filter)
self:setStatus('rescanning ...')
self:refresh('scan')
self.grid:draw()
elseif event.type == 'refresh' then
self:setFocus(self.statusBar.filter)
self:setStatus('updating ...')
self:refresh('list')
self.grid:draw()
elseif event.type == 'toggle_display' then
local values = {
[0] = 'A',
[1] = 'I',
[2] = 'C',
}
event.button.value = (event.button.value + 1) % 3
self.displayMode = event.button.value
event.button.text = values[event.button.value]
event.button:draw()
self:applyFilter()
self.grid:draw()
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
self.filter = event.text
if #self.filter == 0 then
self.filter = nil
end
self:applyFilter()
self.grid:draw()
else
UI.Page.eventHandler(self, event)
end
return true
end
function page:enable()
self:setFocus(self.statusBar.filter)
UI.Page.enable(self)
if not config.server then
self.setup:show()
end
Event.onTimeout(.1, function()
self:refresh('list')
self.grid:draw()
self:sync()
end)
end
function page:refresh(requestType)
local items = self:sendRequest({ request = requestType })
if items then
self.items = items
self:applyFilter()
end
end
function page:applyFilter()
local t = filterItems(self.items, self.filter, self.displayMode)
self.grid:setValues(t)
end
Event.addRoutine(function()
while true do
os.sleep(1.5)
local neural = device.neuralInterface
local inv = config.useShield and 'getEquipment' or 'getInventory'
if not neural or not neural[inv] then
_G._debug('missing Introspection module')
elseif config.server and (config.useShield or config.slot) then
local method = neural[inv]
local item = method and method().getItemMeta(config.useShield and SHIELD_SLOT or config.slot)
if item then
local slotNo = config.useShield and 'shield' or config.slot
local response = page:sendRequest({
request = 'deposit',
slot = slotNo,
count = item.count,
key = table.concat({ item.name, item.damage, item.nbtHash }, ':')
})
if response then
local ritem = page.items[response.key]
if ritem then
ritem.count = response.current + item.count
end
page.grid:draw()
page:sync()
end
end
end
end
end)
UI:setPage(page)
UI:pullEvents()
if socket then
socket:close()
end