Skip to content

Commit 48551da

Browse files
committed
Test & fix all peripherals except turtle
1 parent f9656fa commit 48551da

File tree

8 files changed

+711
-38
lines changed

8 files changed

+711
-38
lines changed

computercraft/back.lua

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ ws.send(textutils.serializeJSON{
1515
args={...},
1616
})
1717

18+
function nullify_array(a, size)
19+
local r = {}
20+
for k=1,size do
21+
if a[k] == nil then
22+
r[k] = textutils.json_null
23+
else
24+
r[k] = a[k]
25+
end
26+
end
27+
return r
28+
end
29+
1830
while true do
1931
local event, p1, p2, p3, p4, p5 = os.pullEvent()
2032

@@ -56,7 +68,7 @@ while true do
5668
ws.send(textutils.serializeJSON{
5769
action='event',
5870
event=event,
59-
params={p1, p2, p3, p4, p5},
71+
params=nullify_array({p1, p2, p3, p4, p5}, 5),
6072
})
6173
end
6274

computercraft/rproc.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def any_list(result):
5757
def fact_tuple(*components, tail_nils=0):
5858
def proc(result):
5959
result = any_list(result)
60-
assert len(components) + tail_nils >= len(result) >= len(components)
60+
assert len(components) >= len(result) >= len(components) - tail_nils
6161
while len(result) < len(components):
6262
result.append(None)
6363
assert len(components) == len(result)
@@ -120,3 +120,12 @@ def proc(result):
120120
array_string = fact_array(string)
121121
option_integer = fact_option(integer)
122122
option_string = fact_option(string)
123+
124+
125+
option_string_bool = fact_option(fact_union(
126+
(
127+
lambda v: v is True or v is False,
128+
boolean,
129+
),
130+
pelse=string,
131+
))

computercraft/subapis/disk.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
from typing import Optional, Union
22

33
from .base import BaseSubAPI
4-
from ..rproc import boolean, string, nil, option_integer, option_string, fact_option, fact_union
5-
6-
7-
option_string_bool = fact_option(fact_union(
8-
(
9-
lambda v: v is True or v is False,
10-
boolean,
11-
),
12-
pelse=string,
13-
))
4+
from ..rproc import boolean, nil, option_integer, option_string, option_string_bool
145

156

167
class DiskAPI(BaseSubAPI):

computercraft/subapis/mixins.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,3 @@ async def getPaletteColor(self, colorID: int) -> Tuple[float, float, float]:
5454

5555
async def setPaletteColor(self, colorID: int, r: float, g: float, b: float):
5656
return nil(await self._send('setPaletteColor', colorID, r, g, b))
57-
58-
async def nativePaletteColor(self, colorID: int) -> Tuple[float, float, float]:
59-
return tuple3_number(await self._send('nativePaletteColor', colorID))

computercraft/subapis/os.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@
88
class CCEventQueue:
99
def __init__(self, q):
1010
self._q = q
11-
12-
def __aiter__(self):
13-
return self
14-
15-
async def __anext__(self):
16-
return await self._q.get()
11+
self.filter = self.default_filter
12+
13+
@staticmethod
14+
def default_filter(msg):
15+
return True, msg
16+
17+
async def __aiter__(self):
18+
while True:
19+
msg = await self._q.get()
20+
emit, msg = self.filter(msg)
21+
if emit:
22+
yield msg
1723

1824

1925
class OSAPI(BaseSubAPI):

computercraft/subapis/peripheral.py

Lines changed: 115 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
from typing import Optional, List, Tuple, Any
1+
from contextlib import asynccontextmanager
2+
from dataclasses import dataclass
3+
from typing import Optional, List, Tuple, Any, Union
24

3-
from .base import BaseSubAPI
5+
from .base import BaseSubAPI, LuaNum
46
from .mixins import TermMixin
5-
from ..rproc import boolean, nil, integer, string, option_integer, option_string, tuple2_integer, array_string
7+
from ..errors import LuaException
8+
from ..rproc import (
9+
boolean, nil, integer, string, option_integer, option_string,
10+
tuple2_integer, array_string, option_string_bool, fact_tuple,
11+
)
612

713

814
class CCPeripheral(BaseSubAPI):
9-
def __init__(self, cc, call_fn):
15+
def __init__(self, cc, side, call_fn):
1016
super().__init__(cc)
17+
self._side = side
1118
self._send = call_fn
1219

1320

@@ -30,8 +37,8 @@ async def getMountPath(self) -> Optional[str]:
3037
async def hasAudio(self) -> bool:
3138
return boolean(await self._send('hasAudio'))
3239

33-
async def getAudioTitle(self) -> Optional[str]:
34-
return option_string(await self._send('getAudioTitle'))
40+
async def getAudioTitle(self) -> Optional[Union[bool, str]]:
41+
return option_string_bool(await self._send('getAudioTitle'))
3542

3643
async def playAudio(self):
3744
return nil(await self._send('playAudio'))
@@ -47,6 +54,9 @@ async def getDiskID(self) -> Optional[int]:
4754

4855

4956
class CCMonitor(CCPeripheral, TermMixin):
57+
async def getTextScale(self) -> int:
58+
return integer(await self._send('getTextScale'))
59+
5060
async def setTextScale(self, scale: int):
5161
return nil(await self._send('setTextScale', scale))
5262

@@ -64,11 +74,21 @@ async def reboot(self):
6474
async def getID(self) -> int:
6575
return integer(await self._send('getID'))
6676

77+
async def getLabel(self) -> Optional[str]:
78+
return option_string(await self._send('getLabel'))
79+
6780
async def isOn(self) -> bool:
6881
return boolean(await self._send('isOn'))
6982

7083

71-
class CCModem(CCPeripheral):
84+
@dataclass
85+
class ModemMessage:
86+
reply_channel: int
87+
content: Any
88+
distance: LuaNum
89+
90+
91+
class ModemMixin:
7292
async def isOpen(self, channel: int) -> bool:
7393
return boolean(await self._send('isOpen', channel))
7494

@@ -87,13 +107,41 @@ async def transmit(self, channel: int, replyChannel: int, message: Any):
87107
async def isWireless(self) -> bool:
88108
return boolean(await self._send('isWireless'))
89109

90-
# wired only functions below
110+
def _mk_recv_filter(self, channel):
111+
def filter(msg):
112+
if msg[0] != self._side:
113+
return False, None
114+
if msg[1] != channel:
115+
return False, None
116+
return True, ModemMessage(*msg[2:])
117+
return filter
118+
119+
@asynccontextmanager
120+
async def receive(self, channel: int):
121+
if await self.isOpen(channel):
122+
raise Exception('Channel is busy')
123+
await self.open(channel)
124+
try:
125+
async with self._cc.os.captureEvent('modem_message') as q:
126+
q.filter = self._mk_recv_filter(channel)
127+
yield q
128+
finally:
129+
await self.close(channel)
130+
131+
132+
class CCWirelessModem(CCPeripheral, ModemMixin):
133+
pass
134+
135+
136+
class CCWiredModem(CCPeripheral, ModemMixin):
137+
async def getNameLocal(self) -> Optional[str]:
138+
return option_string(await self._send('getNameLocal'))
91139

92140
async def getNamesRemote(self) -> List[str]:
93141
return array_string(await self._send('getNamesRemote'))
94142

95-
async def getTypeRemote(self, peripheralName: str) -> str:
96-
return string(await self._send('getTypeRemote', peripheralName))
143+
async def getTypeRemote(self, peripheralName: str) -> Optional[str]:
144+
return option_string(await self._send('getTypeRemote', peripheralName))
97145

98146
async def isPresentRemote(self, peripheralName: str) -> bool:
99147
return boolean(await self._send('isPresentRemote', peripheralName))
@@ -103,7 +151,11 @@ async def wrapRemote(self, peripheralName: str) -> CCPeripheral:
103151
async def call_fn(method, *args):
104152
return await self._send('callRemote', peripheralName, method, *args)
105153

106-
return TYPE_MAP[await self.getTypeRemote(peripheralName)](self._cc, call_fn)
154+
ptype = await self.getTypeRemote(peripheralName)
155+
if ptype is None:
156+
return None
157+
158+
return TYPE_MAP[ptype](self._cc, None, call_fn)
107159

108160

109161
class CCPrinter(CCPeripheral):
@@ -135,6 +187,39 @@ async def getInkLevel(self) -> int:
135187
return integer(await self._send('getInkLevel'))
136188

137189

190+
class CCSpeaker(CCPeripheral):
191+
async def playNote(self, instrument: str, volume: int = 1, pitch: int = 1) -> bool:
192+
# instrument:
193+
# https://minecraft.gamepedia.com/Note_Block#Instruments
194+
# bass
195+
# basedrum
196+
# bell
197+
# chime
198+
# flute
199+
# guitar
200+
# hat
201+
# snare
202+
# xylophone
203+
# iron_xylophone
204+
# pling
205+
# banjo
206+
# bit
207+
# didgeridoo
208+
# cow_bell
209+
210+
# volume 0..3
211+
# pitch 0..24
212+
return boolean(await self._send('playNote', instrument, volume, pitch))
213+
214+
async def playSound(self, sound: str, volume: int = 1, pitch: int = 1):
215+
# volume 0..3
216+
# pitch 0..2
217+
return boolean(await self._send('playSound', sound, volume, pitch))
218+
219+
220+
run_result = fact_tuple(boolean, option_string, tail_nils=1)
221+
222+
138223
class CCCommandBlock(CCPeripheral):
139224
async def getCommand(self) -> str:
140225
return string(await self._send('getCommand'))
@@ -143,15 +228,19 @@ async def setCommand(self, command: str):
143228
return nil(await self._send('setCommand', command))
144229

145230
async def runCommand(self):
146-
return nil(await self._send('runCommand'))
231+
success, error_msg = run_result(await self._send('runCommand'))
232+
if not success:
233+
raise LuaException(error_msg)
234+
else:
235+
assert error_msg is None
147236

148237

149238
TYPE_MAP = {
150239
'drive': CCDrive,
151240
'monitor': CCMonitor,
152241
'computer': CCComputer,
153-
'modem': CCModem,
154242
'printer': CCPrinter,
243+
'speaker': CCSpeaker,
155244
'command': CCCommandBlock,
156245
}
157246

@@ -168,9 +257,19 @@ async def getType(self, side: str) -> Optional[str]:
168257
async def getNames(self) -> List[str]:
169258
return array_string(await self._send('getNames'))
170259

171-
async def wrap(self, side: str) -> CCPeripheral:
172-
# use instead getMethods and call
260+
# use instead getMethods and call
261+
async def wrap(self, side: str) -> Optional[CCPeripheral]:
173262
async def call_fn(method, *args):
174263
return await self._send('call', side, method, *args)
175264

176-
return TYPE_MAP[await self.getType(side)](self._cc, call_fn)
265+
ptype = await self.getType(side)
266+
if ptype is None:
267+
return None
268+
269+
if ptype == 'modem':
270+
if boolean(await self._send('call', side, 'isWireless')):
271+
return CCWirelessModem(self._cc, side, call_fn)
272+
else:
273+
return CCWiredModem(self._cc, side, call_fn)
274+
else:
275+
return TYPE_MAP[ptype](self._cc, side, call_fn)

computercraft/subapis/term.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .base import BaseSubAPI
44
from .mixins import TermMixin
5-
from ..rproc import nil, tuple2_integer
5+
from ..rproc import nil, tuple2_integer, tuple3_number
66

77

88
class CCWindow(BaseSubAPI, TermMixin):
@@ -29,6 +29,9 @@ async def reposition(self, x: int, y: int, width: int = None, height: int = None
2929
class TermAPI(BaseSubAPI, TermMixin):
3030
_API = 'term'
3131

32+
async def nativePaletteColor(self, colorID: int) -> Tuple[float, float, float]:
33+
return tuple3_number(await self._send('nativePaletteColor', colorID))
34+
3235
# TODO
3336
# term.redirect(target) table previous terminal object
3437
# term.current() table terminal object

0 commit comments

Comments
 (0)