Initial commit

This commit is contained in:
neumond 2016-12-03 16:46:42 +03:00
commit 84913f272e
28 changed files with 1556 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
__pycache__/
*.pyc

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Pythonized ComputerCraft API
TODO:
- add host and port cmdline parameters for server
- use current dir for programs

View file

134
computercraft/back.lua Normal file
View file

@ -0,0 +1,134 @@
local genv = getfenv()
local temp = {}
genv.temp = temp
local url = 'http://127.0.0.1:4343/'
local args = {...}
local tasks = {}
-- https://www.lua.org/pil/11.4.html
local Queue = {}
function Queue.new()
return {head = 0, tail = 0}
end
function Queue.push(q, value)
q[q.head] = value
q.head = q.head + 1
end
function Queue.pop(q)
if q.tail >= q.head then return nil end
local value = q[q.tail]
q[q.tail] = nil
q.tail = q.tail + 1
return value
end
function Queue.length(q)
return q.head - q.tail
end
local output = Queue.new()
local function is_special_task(task_id)
return (task_id == '_fetch' or task_id == '_send')
end
local function string_split(text)
local pos = string.find(text, ';')
return string.sub(text, 1, pos - 1), string.sub(text, pos + 1)
end
local function inner_resume(task_id, ...)
local r = {coroutine.resume(tasks[task_id].co, ...)}
if coroutine.status(tasks[task_id].co) == 'dead' then
if not is_special_task(task_id) then
Queue.push(output, {task_id=task_id, result=r})
end
tasks[task_id] = nil
return true
end
if r[1] then tasks[task_id].exp = r[2] end
return false
end
local function make_task(task_id, f, ...)
tasks[task_id] = {co=coroutine.create(f)}
return inner_resume(task_id, ...)
end
local function resume_task(task_id, event, ...)
local exp = tasks[task_id].exp
if exp ~= nil and event ~= exp then return false end
return inner_resume(task_id, event, ...)
end
local function event_queue(task_id, event)
while true do
local estruct = {os.pullEvent(event)}
Queue.push(output, {task_id=task_id, result=estruct})
end
end
local function fetch_fn()
local r = http.post(url..'start/'..os.getComputerID()..'/'..args[1]..'/')
if (r == nil or r.getResponseCode() ~= 200) then
print('Failed to start program '..args[1])
return
end
while true do
r = http.post(url..'gettask/'..os.getComputerID()..'/', answer)
if (r == nil or r.getResponseCode() ~= 200) then
print('Connection broken')
return
end
local cmd = r.readAll()
if cmd == 'END' then
break
elseif cmd ~= 'NOOP' then
local cmd, text = string_split(cmd)
if cmd == 'TASK' then
local task_id, text = string_split(text)
local f = loadstring(text)
setfenv(f, genv)
make_task(task_id, f)
elseif cmd == 'STARTQUEUE' then
local task_id, text = string_split(text)
make_task(task_id, event_queue, task_id, text)
elseif cmd == 'STOPQUEUE' then
tasks[text] = nil
end
end
end
end
local function send_fn()
while true do
local r = Queue.pop(output)
if r == nil then break end
local answer = textutils.serializeJSON(r.result)
http.post(url..'taskresult/'..os.getComputerID()..'/'..r.task_id..'/', answer)
end
end
if make_task('_fetch', fetch_fn) then return end
while true do
local event, p1, p2, p3, p4, p5 = os.pullEvent()
if resume_task('_fetch', event, p1, p2, p3, p4, p5) then break end
-- Use of http API in user code is explicitly disallowed
if event ~= 'http_success' and event ~= 'http_failure' then
local local_task_ids = {}
for task_id in pairs(tasks) do
local_task_ids[task_id] = true
end
for task_id in pairs(local_task_ids) do
if not is_special_task(task_id) then
resume_task(task_id, event, p1, p2, p3, p4, p5)
end
end
end
if tasks['_send'] ~= nil then
resume_task('_send', event, p1, p2, p3, p4, p5)
else
if Queue.length(output) > 0 then make_task('_send', send_fn) end
end
end

10
computercraft/errors.py Normal file
View file

@ -0,0 +1,10 @@
class TurtleException(Exception):
pass
class LuaException(Exception):
pass
class ApiException(Exception):
pass

249
computercraft/server.py Normal file
View file

@ -0,0 +1,249 @@
import asyncio
import json
import string
from aiohttp import web
from traceback import print_exc
from os.path import getmtime, join, dirname, abspath
from os import listdir
import importlib
from subapis.root import RootAPIMixin
from common import LuaException
from subapis.colors import ColorsAPI
from subapis.commands import CommandsAPI
from subapis.disk import DiskAPI
from subapis.fs import FSAPI
from subapis.gps import GpsAPI
from subapis.help import HelpAPI
from subapis.keys import KeysAPI
from subapis.multishell import MultishellAPI
from subapis.os import OSAPI
from subapis.peripheral import PeripheralAPI
from subapis.rednet import RednetAPI
from subapis.redstone import RedstoneAPI
from subapis.settings import SettingsAPI
from subapis.shell import ShellAPI
from subapis.term import TermAPI
from subapis.textutils import TextutilsAPI
from subapis.turtle import TurtleAPI
from subapis.window import WindowAPI
THIS_DIR = dirname(abspath(__file__))
LUA_FILE = join(THIS_DIR, 'back.lua')
exchange = {}
module_map = {}
DIGITS = string.digits + string.ascii_lowercase
def base36(n):
r = ''
while n:
r += DIGITS[n % 36]
n //= 36
return r[::-1]
async def lua_json(request):
body = await request.text()
body = body.replace('\\\n', '\\n')
return json.loads(body)
def program_filenames():
return [f[:-3] for f in listdir(join(THIS_DIR, 'programs')) if f.endswith('.py') and f != '__init__.py']
def m_filename(m):
return join(THIS_DIR, 'programs', '{}.py'.format(m))
async def reload_all_modules(module_map):
prev = set(module_map.keys())
nxt = set(program_filenames())
# unloading old modules
for m in prev - nxt:
del module_map[m]
# loading new modules
for m in nxt - prev:
module_map[m] = importlib.import_module('programs.{}'.format(m))
module_map[m]._mtime_mark = getmtime(m_filename(m))
print('Loaded {}'.format(m))
# reloading modified modules
for m in nxt & prev:
mtime = getmtime(m_filename(m))
if module_map[m]._mtime_mark < mtime:
importlib.reload(module_map[m])
module_map[m]._mtime_mark = mtime
print('Reloaded {}'.format(m))
async def module_reloader():
while True:
fut = asyncio.ensure_future(reload_all_modules(module_map))
await asyncio.wait([fut])
await asyncio.sleep(5)
class CCAPI(RootAPIMixin):
def __init__(self, nid, program):
self._id = nid
self._task_autoid = 1
self._cmd = asyncio.Queue(maxsize=1)
self._result_locks = {}
self._result_values = {}
self._result_queues = {}
self.colors = ColorsAPI
self.commands = CommandsAPI(self)
self.disk = DiskAPI(self)
self.fs = FSAPI(self)
self.gps = GpsAPI(self) # TODO: test
self.help = HelpAPI(self)
self.keys = KeysAPI(self)
self.multishell = MultishellAPI(self)
self.os = OSAPI(self)
self.peripheral = PeripheralAPI(self)
self.rednet = RednetAPI(self)
self.redstone = RedstoneAPI(self)
self.settings = SettingsAPI(self)
self.shell = ShellAPI(self) # TODO: autocomplete functions
self.term = TermAPI(self) # TODO: window redirections
self.textutils = TextutilsAPI(self)
self.turtle = TurtleAPI(self)
self.window = WindowAPI(self) # TODO: unimplemented
async def prog_wrap():
cancel = False
try:
await program(self)
except asyncio.CancelledError:
print('program {} cancelled'.format(self._id))
print_exc()
cancel = True
except Exception as e:
print('program {} crashed: {} {}'.format(self._id, type(e), e))
print_exc()
else:
print('program {} finished'.format(self._id))
finally:
if not cancel:
await self._cmd.put('END')
del exchange[self._id]
self._task = asyncio.ensure_future(prog_wrap())
def _new_task_id(self):
task_id = base36(self._task_autoid)
self._task_autoid += 1
return task_id
async def _send_cmd(self, lua):
task_id = self._new_task_id()
self._result_locks[task_id] = asyncio.Event()
await self._cmd.put('TASK;' + task_id + ';' + lua)
await self._result_locks[task_id].wait()
del self._result_locks[task_id]
result = self._result_values.pop(task_id)
print('{}{}'.format(lua, result))
if not result[0]:
raise LuaException(*result[1:])
return result[1:]
async def _start_queue(self, event):
task_id = self._new_task_id()
self._result_queues[task_id] = asyncio.Queue()
await self._cmd.put('STARTQUEUE;' + task_id + ';' + event)
return self._result_queues[task_id], task_id
async def _stop_queue(self, task_id):
await self._cmd.put('STOPQUEUE;' + task_id)
del self._result_queues[task_id]
async def start(request):
tid = int(request.match_info['turtle'])
if tid in exchange:
# terminate old program
exchange[tid]._task.cancel()
exchange[tid] = CCAPI(tid, module_map[request.match_info['program']].program)
return web.Response(text='')
async def gettask(request):
api = exchange.get(int(request.match_info['turtle']))
if api is None:
return web.Response(text='END')
return web.Response(text=await api._cmd.get())
async def taskresult(request):
api = exchange.get(int(request.match_info['turtle']))
if api is not None:
tid = request.match_info['task_id']
if tid in api._result_locks:
# it's a TASK
api._result_values[tid] = await lua_json(request)
api._result_locks[tid].set()
elif tid in api._result_queues:
# it's a QUEUE
await api._result_queues[tid].put(await lua_json(request))
# otherwise just ignore
return web.Response(text='')
def backdoor(request):
with open(LUA_FILE, 'r') as f:
fcont = f.read()
return web.Response(text=fcont)
logging_config = '''
version: 1
disable_existing_loggers: false
root:
level: ERROR
handlers:
- console
handlers:
console:
level: INFO
class: logging.StreamHandler
formatter: verbose
loggers:
aiohttp.access:
level: INFO
handlers: []
propagate: true
formatters:
verbose:
format: '%(message)s'
'''
def enable_request_logging():
import logging.config
import yaml
logging.config.dictConfig(yaml.load(logging_config))
def main():
# enable_request_logging()
asyncio.ensure_future(module_reloader())
app = web.Application()
app.router.add_get('/', backdoor)
app.router.add_post('/start/{turtle}/{program}/', start)
app.router.add_post('/gettask/{turtle}/', gettask)
app.router.add_post('/taskresult/{turtle}/{task_id}/', taskresult)
web.run_app(app, port=4343)
if __name__ == '__main__':
main()

View file

View file

@ -0,0 +1,131 @@
from common import TurtleException, ApiException
from typing import Union
LuaTable = Union[list, dict]
LuaNum = Union[int, float]
def lua_string(v):
return '"{}"'.format(
v.replace('\\', '\\\\')
.replace('\a', '\\a')
.replace('\b', '\\b')
.replace('\f', '\\f')
.replace('\n', '\\n')
.replace('\r', '\\r')
.replace('\t', '\\t')
.replace('\v', '\\v')
.replace('"', '\\"')
.replace("'", "\\'")
.replace('[', '\\[')
.replace(']', '\\]')
)
def lua_list(v):
return '{' + ', '.join(lua_value(x) for x in v) + '}'
def lua_dict(v):
return '{' + ', '.join(
'[{}]={}'.format(lua_value(k), lua_value(v)) for k, v in v.items()
) + '}'
def lua_value(v):
if v is None:
return 'nil'
if v is False:
return 'false'
if v is True:
return 'true'
if isinstance(v, str):
return lua_string(v)
if isinstance(v, (int, float)):
return str(v)
if isinstance(v, list):
return lua_list(v)
if isinstance(v, dict):
return lua_dict(v)
raise ValueError('Can\'t convert a value to lua {}'.format(v))
def lua_args(*params, omit_nulls=True):
ps, can_add = [], True
for p in params:
if omit_nulls and p is None:
can_add = False
continue
if not can_add:
raise ApiException('Optional parameter order error')
ps.append(lua_value(p))
return ', '.join(ps)
def bool_success(v):
if v == [True]:
return
if len(v) == 2 and v[0] is False:
assert isinstance(v[1], str)
raise TurtleException(v[1])
raise ApiException('Bad return value: {}'.format(v))
def single_return(v):
assert len(v) == 1
return v[0]
def make_optional(fn):
def op_fn(v):
if v == []:
return None
return fn(v)
return op_fn
def make_single_value_return(*types):
if len(types) > 1:
types = tuple(types)
else:
types = types[0]
def fn(v):
assert len(v) == 1
assert isinstance(v[0], types)
return v[0]
return fn
bool_return = make_single_value_return(bool)
int_return = make_single_value_return(int)
number_return = make_single_value_return(int, float)
str_return = make_single_value_return(str)
list_return = make_single_value_return(list)
dict_return = make_single_value_return(dict)
@make_optional
def nil_return(v):
assert False
opt_bool_return = make_optional(bool_return)
opt_int_return = make_optional(int_return)
opt_number_return = make_optional(number_return)
opt_str_return = make_optional(str_return)
opt_list_return = make_optional(list_return)
opt_dict_return = make_optional(dict_return)
class BaseSubAPI:
_API = NotImplemented
def __init__(self, cc):
self._cc = cc
async def _send(self, method, *params, omit_nulls=True):
return await self._cc._send_cmd('return {}.{}({})'.format(
self._API, method, lua_args(*params, omit_nulls=omit_nulls)
))

View file

@ -0,0 +1,36 @@
class ColorsAPI:
white = 0x1
orange = 0x2
magenta = 0x4
lightBlue = 0x8
yellow = 0x10
lime = 0x20
pink = 0x40
gray = 0x80
lightGray = 0x100
cyan = 0x200
purple = 0x400
blue = 0x800
brown = 0x1000
green = 0x2000
red = 0x4000
black = 0x8000
chars = {
'0': white,
'1': orange,
'2': magenta,
'3': lightBlue,
'4': yellow,
'5': lime,
'6': pink,
'7': gray,
'8': lightGray,
'9': cyan,
'a': purple,
'b': blue,
'c': brown,
'd': green,
'e': red,
'f': black,
}

View file

@ -0,0 +1,28 @@
from typing import Tuple, List
from .base import BaseSubAPI, int_return, list_return, dict_return
class CommandsAPI(BaseSubAPI):
_API = 'commands'
async def exec(self, command: str):
r = await self._send('exec', command)
assert len(r) == 2
assert isinstance(r[0], bool)
assert isinstance(r[1], list)
return r[0], r[1]
async def execAsync(self, command: str) -> int:
return int_return(await self._send('execAsync', command))
async def list(self) -> List[str]:
return list_return(await self._send('list'))
async def getBlockPosition(self) -> Tuple[int, int, int]:
return tuple(await self._send('getBlockPosition'))
async def getBlockInfo(self, x: int, y: int, z: int) -> dict:
return dict_return(await self._send('getBlockInfo', x, y, z))
async def getBlockInfos(self, x1: int, y1: int, z1: int, x2: int, y2: int, z2: int) -> List[dict]:
return list_return(await self._send('getBlockInfos', x1, y1, z1, x2, y2, z2))

View file

@ -0,0 +1,39 @@
from typing import Optional
from .base import BaseSubAPI, nil_return, bool_return, opt_str_return, opt_int_return
class DiskAPI(BaseSubAPI):
_API = 'disk'
async def isPresent(self, side: str) -> bool:
return bool_return(await self._send('isPresent', side))
async def hasData(self, side: str) -> bool:
return bool_return(await self._send('hasData', side))
async def getMountPath(self, side: str) -> Optional[str]:
return opt_str_return(await self._send('getMountPath', side))
async def setLabel(self, side: str, label: str):
return nil_return(await self._send('setLabel', side, label))
async def getLabel(self, side: str) -> Optional[str]:
return opt_str_return(await self._send('getLabel', side))
async def getID(self, side: str) -> Optional[int]:
return opt_int_return(await self._send('getID', side))
async def hasAudio(self, side: str) -> bool:
return bool_return(await self._send('hasAudio', side))
async def getAudioTitle(self, side: str) -> Optional[str]:
return opt_str_return(await self._send('getAudioTitle', side))
async def playAudio(self, side: str):
return nil_return(await self._send('playAudio', side))
async def stopAudio(self, side: str):
return nil_return(await self._send('stopAudio', side))
async def eject(self, side: str):
return nil_return(await self._send('eject', side))

118
computercraft/subapis/fs.py Normal file
View file

@ -0,0 +1,118 @@
from typing import Optional, Union, List
from .base import (
BaseSubAPI, lua_string,
nil_return, opt_str_return, str_return, bool_return, int_return, list_return, opt_int_return,
)
from uuid import uuid4
class CCFile(BaseSubAPI):
def __init__(self, cc, path, mode):
super().__init__(cc)
self._path = path
self._mode = mode
async def __aenter__(self):
self._id = str(uuid4())
self._API = 'temp[{}]'.format(lua_string(self._id))
await self._cc._send_cmd('{} = fs.open({}, {})'.format(self._API, *map(lua_string, [
self._path, self._mode
])))
return self
async def __aexit__(self, exc_type, exc, tb):
await self._cc._send_cmd('{}.close(); {} = nil'.format(self._API, self._API))
async def read(self) -> Optional[int]:
return opt_int_return(await self._send('read'))
async def readLine(self) -> Optional[str]:
return opt_str_return(await self._send('readLine'))
async def readAll(self) -> str:
return str_return(await self._send('readAll'))
async def write(self, data: Union[str, int]):
return nil_return(await self._send('write', data))
async def writeLine(self, data: str):
return nil_return(await self._send('writeLine', data))
async def flush(self):
return nil_return(await self._send('flush'))
def __aiter__(self):
return self
async def __anext__(self):
line = await self.readLine()
if line is None:
raise StopAsyncIteration
return line
class FSAPI(BaseSubAPI):
_API = 'fs'
async def list(self, path: str) -> List[str]:
return list_return(await self._send('list', path))
async def exists(self, path: str) -> bool:
return bool_return(await self._send('exists', path))
async def isDir(self, path: str) -> bool:
return bool_return(await self._send('isDir', path))
async def isReadOnly(self, path: str) -> bool:
return bool_return(await self._send('isReadOnly', path))
async def getName(self, path: str) -> str:
return str_return(await self._send('getName', path))
async def getDrive(self, path: str) -> Optional[str]:
return opt_str_return(await self._send('getDrive', path))
async def getSize(self, path: str) -> int:
return int_return(await self._send('getSize', path))
async def getFreeSpace(self, path: str) -> int:
return int_return(await self._send('getFreeSpace', path))
async def makeDir(self, path: str):
return nil_return(await self._send('makeDir', path))
async def move(self, fromPath: str, toPath: str):
return nil_return(await self._send('move', fromPath, toPath))
async def copy(self, fromPath: str, toPath: str):
return nil_return(await self._send('copy', fromPath, toPath))
async def delete(self, path: str):
return nil_return(await self._send('delete', path))
async def combine(self, basePath: str, localPath: str) -> str:
return str_return(await self._send('combine', basePath, localPath))
def open(self, path: str, mode: str) -> CCFile:
'''
Usage:
async with api.fs.open('filename', 'w') as f:
await f.writeLine('textline')
async with api.fs.open('filename', 'r') as f:
async for line in f:
...
'''
return CCFile(self._cc, path, mode)
async def find(self, wildcard: str) -> List[str]:
return list_return(await self._send('find', wildcard))
async def getDir(self, path: str) -> str:
return str_return(await self._send('getDir', path))
async def complete(
self, partialName: str, path: str, includeFiles: bool=None, includeSlashes: bool=None
) -> List[str]:
return list_return(await self._send('complete', partialName, path, includeFiles, includeSlashes))

View file

@ -0,0 +1,12 @@
from typing import Tuple, Optional
from .base import BaseSubAPI, LuaNum
class GpsAPI(BaseSubAPI):
_API = 'gps'
async def locate(self, timeout: LuaNum=None, debug: bool=None) -> Optional[Tuple[int, int, int]]:
r = await self._send('locate', timeout, debug)
if r == [None]:
return None
return tuple(r)

View file

@ -0,0 +1,21 @@
from typing import Optional, List
from .base import BaseSubAPI, nil_return, opt_str_return, str_return, list_return
class HelpAPI(BaseSubAPI):
_API = 'help'
async def path(self) -> str:
return str_return(await self._send('path'))
async def setPath(self, path: str):
return nil_return(await self._send('setPath', path))
async def lookup(self, topic: str) -> Optional[str]:
return opt_str_return(await self._send('lookup', topic))
async def topics(self) -> List[str]:
return list_return(await self._send('topics'))
async def completeTopic(self, topicPrefix: str) -> List[str]:
return list_return(await self._send('completeTopic', topicPrefix))

View file

@ -0,0 +1,8 @@
from .base import BaseSubAPI, str_return
class KeysAPI(BaseSubAPI):
_API = 'keys'
async def getName(self, code: int) -> str:
return str_return(await self._send('getName', code))

View file

@ -0,0 +1,46 @@
from typing import Tuple
from .base import bool_return, nil_return, int_return
class TermMixin:
async def write(self, text: str):
return nil_return(await self._send('write', text))
async def blit(self, text: str, textColors: str, backgroundColors: str):
return nil_return(await self._send('blit', text, textColors, backgroundColors))
async def clear(self):
return nil_return(await self._send('clear'))
async def clearLine(self):
return nil_return(await self._send('clearLine'))
async def getCursorPos(self) -> Tuple[int, int]:
return tuple(await self._send('getCursorPos'))
async def setCursorPos(self, x: int, y: int):
return nil_return(await self._send('setCursorPos', x, y))
async def setCursorBlink(self, value: bool):
return nil_return(await self._send('setCursorBlink', value))
async def isColor(self) -> bool:
return bool_return(await self._send('isColor'))
async def getSize(self) -> Tuple[int, int]:
return tuple(await self._send('getSize'))
async def scroll(self, n: int):
return nil_return(await self._send('scroll', n))
async def setTextColor(self, color: int):
return nil_return(await self._send('setTextColor', color))
async def getTextColor(self) -> int:
return int_return(await self._send('getTextColor'))
async def setBackgroundColor(self, color: int):
return nil_return(await self._send('setBackgroundColor', color))
async def getBackgroundColor(self) -> int:
return int_return(await self._send('getBackgroundColor'))

View file

@ -0,0 +1,27 @@
from typing import List
from .base import BaseSubAPI, str_return, int_return, nil_return, bool_return
class MultishellAPI(BaseSubAPI):
_API = 'multishell'
async def getCurrent(self) -> int:
return int_return(await self._send('getCurrent'))
async def getCount(self) -> int:
return int_return(await self._send('getCount'))
async def launch(self, environment: dict, programPath: str, *args: List[str]) -> int:
return int_return(await self._send('launch', environment, programPath, *args))
async def setFocus(self, tabID: int):
return bool_return(await self._send('setFocus', tabID))
async def setTitle(self, tabID: int, title: str):
return nil_return(await self._send('setTitle', tabID, title))
async def getTitle(self, tabID: int) -> str:
return str_return(await self._send('getTitle', tabID))
async def getFocus(self) -> int:
return int_return(await self._send('getFocus'))

107
computercraft/subapis/os.py Normal file
View file

@ -0,0 +1,107 @@
from typing import Optional, List
from .base import (
BaseSubAPI, LuaTable, LuaNum,
nil_return, str_return, opt_str_return, number_return, int_return, bool_return
)
class CCEventQueue(BaseSubAPI):
def __init__(self, cc, targetEvent):
super().__init__(cc)
self._targetEvent = targetEvent
async def __aenter__(self):
self._q, self._tid = await self._cc._start_queue(self._targetEvent)
return self
async def __aexit__(self, exc_type, exc, tb):
await self._cc._stop_queue(self._tid)
def __aiter__(self):
return self
async def __anext__(self):
return await self._q.get()
class OSAPI(BaseSubAPI):
_API = 'os'
async def version(self) -> str:
return str_return(await self._send('version'))
async def getComputerID(self) -> int:
return int_return(await self._send('getComputerID'))
async def getComputerLabel(self) -> Optional[str]:
return opt_str_return(await self._send('getComputerLabel'))
async def setComputerLabel(self, label: Optional[str]):
return nil_return(await self._send('setComputerLabel', label, omit_nulls=False))
async def run(self, environment: LuaTable, programPath: str, *args: List[str]):
return bool_return(await self._send('run', environment, programPath, *args))
async def loadAPI(self, path: str):
return bool_return(await self._send('loadAPI', path))
async def unloadAPI(self, name: str):
return nil_return(await self._send('unloadAPI', name))
def registerEventQueue(self, targetEvent: str) -> CCEventQueue:
'''
Use this function instead loop over pullEvent/pullEventRaw.
This makes a queue capable of effective event translation, excluding roundtrip delay.
E.g.:
1. You start short timer using os.startTimer().
2. Then you call os.pullEvent() in loop to catch timer event.
There exist some dead intervals of time, while pullEvent is going to be transferred to python side.
Lua side can receive and discard timer event since there's no consumer for it.
registerEventQueue gives you reliable way of receiving events without losses.
Register queue before firing a timer, start a timer, listen for messages in queue.
# it's significant here: start queue before starting a timer
async with api.os.registerEventQueue('timer') as timer_queue:
myTimer = await api.os.startTimer(3)
async for e in timer_queue:
if e[1] == myTimer:
await api.print('Timer reached')
break
'''
return CCEventQueue(self._cc, targetEvent)
async def queueEvent(self, event: str, *params):
return nil_return(await self._send('queueEvent', event, *params))
async def clock(self) -> LuaNum:
return number_return(await self._send('clock'))
async def startTimer(self, timeout: LuaNum) -> int:
return int_return(await self._send('startTimer', timeout))
async def cancelTimer(self, timerID: int):
return nil_return(await self._send('cancelTimer', timerID))
async def time(self) -> LuaNum:
return number_return(await self._send('time'))
async def sleep(self, seconds: LuaNum):
return nil_return(await self._send('sleep', seconds))
async def day(self) -> int:
return int_return(await self._send('day'))
async def setAlarm(self, time: LuaNum) -> int:
return int_return(await self._send('setAlarm', time))
async def cancelAlarm(self, alarmID: int):
return nil_return(await self._send('cancelAlarm', alarmID))
async def shutdown(self):
return nil_return(await self._send('shutdown'))
async def reboot(self):
return nil_return(await self._send('reboot'))

View file

@ -0,0 +1,177 @@
from typing import Optional, List, Tuple, Any
from .base import (
BaseSubAPI, bool_return, opt_str_return, list_return,
nil_return, opt_int_return, int_return, str_return, bool_success,
)
from .mixins import TermMixin
class CCPeripheral(BaseSubAPI):
def __init__(self, cc, call_fn):
super().__init__(cc)
self._send = call_fn
class CCDrive(CCPeripheral):
async def isDiskPresent(self) -> bool:
return bool_return(await self._send('isDiskPresent'))
async def getDiskLabel(self) -> Optional[str]:
return opt_str_return(await self._send('getDiskLabel'))
async def setDiskLabel(self, label: str):
return nil_return(await self._send('setDiskLabel', label))
async def hasData(self) -> bool:
return bool_return(await self._send('hasData'))
async def getMountPath(self) -> Optional[str]:
return opt_str_return(await self._send('getMountPath'))
async def hasAudio(self) -> bool:
return bool_return(await self._send('hasAudio'))
async def getAudioTitle(self) -> Optional[str]:
return opt_str_return(await self._send('getAudioTitle'))
async def playAudio(self):
return nil_return(await self._send('playAudio'))
async def stopAudio(self):
return nil_return(await self._send('stopAudio'))
async def ejectDisk(self):
return nil_return(await self._send('ejectDisk'))
async def getDiskID(self) -> Optional[int]:
return opt_int_return(await self._send('getDiskID'))
class CCMonitor(CCPeripheral, TermMixin):
async def setTextScale(self, scale: int):
return nil_return(await self._send('setTextScale', scale))
class CCComputer(CCPeripheral):
async def turnOn(self):
return nil_return(await self._send('turnOn'))
async def shutdown(self):
return nil_return(await self._send('shutdown'))
async def reboot(self):
return nil_return(await self._send('reboot'))
async def getID(self) -> int:
return int_return(await self._send('getID'))
async def isOn(self) -> bool:
return bool_return(await self._send('isOn'))
class CCModem(CCPeripheral):
async def isOpen(self, channel: int) -> bool:
return bool_return(await self._send('isOpen', channel))
async def open(self, channel: int):
return nil_return(await self._send('open', channel))
async def close(self, channel: int):
return nil_return(await self._send('close', channel))
async def closeAll(self):
return nil_return(await self._send('closeAll'))
async def transmit(self, channel: int, replyChannel: int, message: Any):
return nil_return(await self._send('transmit', channel, replyChannel, message))
async def isWireless(self) -> bool:
return bool_return(await self._send('isWireless'))
# wired only functions below
async def getNamesRemote(self) -> List[str]:
return list_return(await self._send('getNamesRemote'))
async def getTypeRemote(self, peripheralName: str) -> str:
return str_return(await self._send('getTypeRemote', peripheralName))
async def isPresentRemote(self, peripheralName: str) -> bool:
return bool_return(await self._send('isPresentRemote', peripheralName))
async def wrapRemote(self, peripheralName: str) -> CCPeripheral:
# use instead getMethodsRemote and callRemote
async def call_fn(method, *args):
return await self._send('callRemote', peripheralName, method, *args)
return TYPE_MAP[await self.getTypeRemote(peripheralName)](self._cc, call_fn)
class CCPrinter(CCPeripheral):
async def newPage(self) -> bool:
return bool_return(await self._send('newPage'))
async def endPage(self) -> bool:
return bool_return(await self._send('endPage'))
async def write(self, text: str):
return nil_return(await self._send('write', text))
async def setCursorPos(self, x: int, y: int):
return nil_return(await self._send('setCursorPos', x, y))
async def getCursorPos(self) -> Tuple[int, int]:
return tuple(await self._send('getCursorPos'))
async def getPageSize(self) -> Tuple[int, int]:
return tuple(await self._send('getPageSize'))
async def setPageTitle(self, title: str):
return nil_return(await self._send('setPageTitle', title))
async def getPaperLevel(self) -> int:
return int_return(await self._send('getPaperLevel'))
async def getInkLevel(self) -> int:
return int_return(await self._send('getInkLevel'))
class CCCommandBlock(CCPeripheral):
async def getCommand(self) -> str:
return str_return(await self._send('getCommand'))
async def setCommand(self, command: str):
return nil_return(await self._send('setCommand', command))
async def runCommand(self):
return bool_success(await self._send('runCommand'))
TYPE_MAP = {
'drive': CCDrive,
'monitor': CCMonitor,
'computer': CCComputer,
'modem': CCModem,
'printer': CCPrinter,
'command': CCCommandBlock,
}
class PeripheralAPI(BaseSubAPI):
_API = 'peripheral'
async def isPresent(self, side: str) -> bool:
return bool_return(await self._send('isPresent', side))
async def getType(self, side: str) -> Optional[str]:
return opt_str_return(await self._send('getType', side))
async def getNames(self) -> List[str]:
return list_return(await self._send('getNames'))
async def wrap(self, side: str) -> CCPeripheral:
# use instead getMethods and call
async def call_fn(method, *args):
return await self._send('call', side, method, *args)
return TYPE_MAP[await self.getType(side)](self._cc, call_fn)

View file

@ -0,0 +1,33 @@
from typing import Any, List
from .base import BaseSubAPI, LuaNum, list_return, nil_return, int_return, bool_return
class RednetAPI(BaseSubAPI):
_API = 'rednet'
async def open(self, side: str):
return nil_return(await self._send('open', side))
async def close(self, side: str):
return nil_return(await self._send('close', side))
async def send(self, receiverID: int, message: Any, protocol: str=None):
return nil_return(await self._send('send', receiverID, message, protocol))
async def broadcast(self, message: Any, protocol: str=None):
return nil_return(await self._send('broadcast', message, protocol))
async def receive(self, protocolFilter: str=None, timeout: LuaNum=None) -> int:
return int_return(await self._send('receive', protocolFilter, timeout))
async def isOpen(self, side: str) -> bool:
return bool_return(await self._send('isOpen', side))
async def host(self, protocol: str, hostname: str):
return nil_return(await self._send('host', protocol, hostname))
async def unhost(self, protocol: str, hostname: str):
return nil_return(await self._send('unhost', protocol, hostname))
async def lookup(self, protocol: str, hostname: str) -> List[int]:
return list_return(await self._send('lookup', protocol, hostname))

View file

@ -0,0 +1,39 @@
from typing import List
from .base import BaseSubAPI, list_return, bool_return, nil_return, int_return
class RedstoneAPI(BaseSubAPI):
_API = 'redstone'
async def getSides(self) -> List[str]:
return list_return(await self._send('getSides'))
async def getInput(self, side: str) -> bool:
return bool_return(await self._send('getInput', side))
async def setOutput(self, side: str, value: bool):
return nil_return(await self._send('setOutput', side, value))
async def getOutput(self, side: str) -> bool:
return bool_return(await self._send('getOutput', side))
async def getAnalogInput(self, side: str) -> int:
return int_return(await self._send('getAnalogInput', side))
async def setAnalogOutput(self, side: str, strength: int):
return nil_return(await self._send('setAnalogOutput', side, strength))
async def getAnalogOutput(self, side: str) -> int:
return int_return(await self._send('getAnalogOutput', side))
async def getBundledInput(self, side: str) -> int:
return int_return(await self._send('getBundledInput', side))
async def setBundledOutput(self, side: str, colors: int):
return nil_return(await self._send('setBundledOutput', side, colors))
async def getBundledOutput(self, side: str) -> int:
return int_return(await self._send('getBundledOutput', side))
async def testBundledInput(self, side: str, color: int) -> bool:
return bool_return(await self._send('testBundledInput', side, color))

View file

@ -0,0 +1,21 @@
from subapis.base import nil_return, bool_return, lua_args, str_return
class RootAPIMixin:
async def print(self, *args):
return nil_return(await self._send_cmd('print({})'.format(lua_args(*args))))
async def read_line(self) -> str:
return str_return(await self._send_cmd('return io.read()'))
async def has_commands_api(self) -> bool:
return bool_return(await self._send_cmd('return commands ~= nil'))
async def has_multishell_api(self) -> bool:
return bool_return(await self._send_cmd('return multishell ~= nil'))
async def has_turtle_api(self) -> bool:
return bool_return(await self._send_cmd('return turtle ~= nil'))
async def is_pocket(self) -> bool:
return bool_return(await self._send_cmd('return pocket ~= nil'))

View file

@ -0,0 +1,27 @@
from typing import Any, List
from .base import BaseSubAPI, list_return, nil_return, single_return, bool_return
class SettingsAPI(BaseSubAPI):
_API = 'settings'
async def set(self, name: str, value):
return nil_return(await self._send('set', name, value))
async def get(self, name: str, value=None) -> Any:
return single_return(await self._send('get', name, value))
async def unset(self, name: str):
return nil_return(await self._send('unset', name))
async def clear(self):
return nil_return(await self._send('clear'))
async def getNames(self) -> List[str]:
return list_return(await self._send('getNames'))
async def load(self, path: str) -> bool:
return bool_return(await self._send('load', path))
async def save(self, path: str) -> bool:
return bool_return(await self._send('save', path))

View file

@ -0,0 +1,61 @@
from typing import List, Dict
from .base import BaseSubAPI, nil_return, str_return, bool_return, int_return, dict_return, list_return
class ShellAPI(BaseSubAPI):
_API = 'shell'
async def exit(self):
return nil_return(await self._send('exit'))
async def dir(self) -> str:
return str_return(await self._send('dir'))
async def setDir(self, path: str):
return nil_return(await self._send('setDir', path))
async def path(self) -> str:
return str_return(await self._send('path'))
async def setPath(self, path: str):
return nil_return(await self._send('setPath', path))
async def resolve(self, localPath: str) -> str:
return str_return(await self._send('resolve', localPath))
async def resolveProgram(self, name: str) -> str:
return str_return(await self._send('resolveProgram', name))
async def aliases(self) -> Dict[str, str]:
return dict_return(await self._send('aliases'))
async def setAlias(self, alias: str, program: str):
return nil_return(await self._send('setAlias', alias, program))
async def clearAlias(self, alias: str):
return nil_return(await self._send('clearAlias', alias))
async def programs(self, showHidden: bool=None) -> List[str]:
return list_return(await self._send('programs', showHidden))
async def getRunningProgram(self) -> str:
return str_return(await self._send('getRunningProgram'))
async def run(self, command: str, *args: List[str]):
return bool_return(await self._send('run', command, *args))
async def openTab(self, command: str, *args: List[str]) -> int:
return int_return(await self._send('openTab', command, *args))
async def switchTab(self, tabID: int):
return nil_return(await self._send('switchTab', tabID))
async def complete(self, prefix: str) -> List[str]:
return list_return(await self._send('complete', prefix))
async def completeProgram(self, prefix: str) -> List[str]:
return list_return(await self._send('completeProgram', prefix))
# TODO: autocomplete functions
# async def setCompletionFunction(self, path: str)
# async def getCompletionInfo(self) -> LuaTable

View file

@ -0,0 +1,35 @@
from typing import Tuple
from .base import BaseSubAPI, list_return, nil_return
from .mixins import TermMixin
class CCWindow(BaseSubAPI, TermMixin):
# TODO
def __init__(self, cc):
super().__init__(cc)
async def setVisible(self, visibility: bool):
return nil_return(await self._send('setVisible', visibility))
async def redraw(self):
return nil_return(await self._send('redraw'))
async def restoreCursor(self):
return nil_return(await self._send('restoreCursor'))
async def getPosition(self) -> Tuple[int, int]:
return tuple(await self._send('getPosition'))
async def reposition(self, x: int, y: int, width: int=None, height: int=None):
return nil_return(await self._send('reposition', x, y, width, height))
class TermAPI(BaseSubAPI, TermMixin):
_API = 'term'
# TODO
# async def redirect
# term.redirect(target) table previous terminal object
# term.current() table terminal object
# term.native() table terminal object

View file

@ -0,0 +1,35 @@
from typing import List, Union
from .base import BaseSubAPI, LuaNum, str_return, nil_return, int_return, list_return
class TextutilsAPI(BaseSubAPI):
_API = 'textutils'
async def slowWrite(self, text: str, rate: LuaNum):
return nil_return(await self._send('slowWrite', text, rate))
async def slowPrint(self, text: str, rate: LuaNum):
return nil_return(await self._send('slowPrint', text, rate))
async def formatTime(self, time: LuaNum, twentyFourHour: bool) -> str:
return str_return(await self._send('formatTime', time, twentyFourHour))
async def tabulate(self, *rows_and_colors: Union[list, int]):
return nil_return(await self._send('tabulate', *rows_and_colors))
async def pagedTabulate(self, *rows_and_colors: Union[list, int]):
return nil_return(await self._send('pagedTabulate', *rows_and_colors))
async def pagedPrint(self, text: str, freeLines: int=None) -> int:
return int_return(await self._send('pagedPrint', text, freeLines))
async def complete(self, partialName: str, environment: dict=None) -> List[str]:
return list_return(await self._send('complete', partialName, environment))
# Questionable to implement
# serialize
# unserialize
# Will not implement, use pythonic equivalents
# serializeJSON
# urlEncode

View file

@ -0,0 +1,144 @@
from typing import Optional
from .base import BaseSubAPI, bool_success, number_return, LuaNum, int_return, bool_return, opt_dict_return
from common import TurtleException
class TurtleAPI(BaseSubAPI):
_API = 'turtle'
async def craft(self, quantity: int):
return bool_success(await self._send('craft', quantity))
async def forward(self):
return bool_success(await self._send('forward'))
async def back(self):
return bool_success(await self._send('back'))
async def up(self):
return bool_success(await self._send('up'))
async def down(self):
return bool_success(await self._send('down'))
async def turnLeft(self):
return bool_success(await self._send('turnLeft'))
async def turnRight(self):
return bool_success(await self._send('turnRight'))
async def select(self, slotNum: int):
return bool_success(await self._send('select', slotNum))
async def getSelectedSlot(self) -> int:
return int_return(await self._send('getSelectedSlot'))
async def getItemCount(self, slotNum: int=None) -> int:
return int_return(await self._send('getItemCount', slotNum))
async def getItemSpace(self, slotNum: int=None) -> int:
return int_return(await self._send('getItemSpace', slotNum))
async def getItemDetail(self, slotNum: int=None) -> dict:
return opt_dict_return(await self._send('getItemDetail', slotNum))
async def equipLeft(self):
return bool_success(await self._send('equipLeft'))
async def equipRight(self):
return bool_success(await self._send('equipRight'))
async def attack(self):
return bool_success(await self._send('attack'))
async def attackUp(self):
return bool_success(await self._send('attackUp'))
async def attackDown(self):
return bool_success(await self._send('attackDown'))
async def dig(self):
return bool_success(await self._send('dig'))
async def digUp(self):
return bool_success(await self._send('digUp'))
async def digDown(self):
return bool_success(await self._send('digDown'))
async def place(self, signText: str=None):
return bool_success(await self._send('place', signText))
async def placeUp(self):
return bool_success(await self._send('placeUp'))
async def placeDown(self):
return bool_success(await self._send('placeDown'))
async def detect(self) -> bool:
return bool_return(await self._send('detect'))
async def detectUp(self) -> bool:
return bool_return(await self._send('detectUp'))
async def detectDown(self) -> bool:
return bool_return(await self._send('detectDown'))
async def _inspect_result(self, v):
r, data = v
if not r and data == 'No block to inspect':
return None
if not r:
raise TurtleException(data)
return data
async def inspect(self) -> Optional[dict]:
return self._inspect_result(await self._send('inspect'))
async def inspectUp(self) -> Optional[dict]:
return self._inspect_result(await self._send('inspectUp'))
async def inspectDown(self) -> Optional[dict]:
return self._inspect_result(await self._send('inspectDown'))
async def compare(self) -> bool:
return bool_return(await self._send('compare'))
async def compareUp(self) -> bool:
return bool_return(await self._send('compareUp'))
async def compareDown(self) -> bool:
return bool_return(await self._send('compareDown'))
async def compareTo(self, slot: int) -> bool:
return bool_return(await self._send('compareTo', slot))
async def drop(self, count: LuaNum=None):
return bool_success(await self._send('drop', count))
async def dropUp(self, count: LuaNum=None):
return bool_success(await self._send('dropUp', count))
async def dropDown(self, count: LuaNum=None):
return bool_success(await self._send('dropDown', count))
async def suck(self, amount: LuaNum=None):
return bool_success(await self._send('suck', amount))
async def suckUp(self, amount: LuaNum=None):
return bool_success(await self._send('suckUp', amount))
async def suckDown(self, amount: LuaNum=None):
return bool_success(await self._send('suckDown', amount))
async def refuel(self, quantity: LuaNum=None):
return bool_success(await self._send('refuel', quantity))
async def getFuelLevel(self) -> LuaNum:
return number_return(await self._send('getFuelLevel'))
async def getFuelLimit(self) -> LuaNum:
return number_return(await self._send('getFuelLimit'))
async def transferTo(self, slot: int, quantity: int=None):
return bool_success(await self._send('transferTo', slot, quantity))

View file

@ -0,0 +1,11 @@
from .base import BaseSubAPI, LuaTable
class WindowAPI(BaseSubAPI):
_API = 'window'
async def create(
self, parentTerm: LuaTable, x: int, y: int, width: int, height: int, visible: bool=None
) -> LuaTable:
# TODO
return await self._send('create', parentTerm, x, y, width, height, visible)