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
46from .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
814class 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
4956class 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
109161class 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+
138223class 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
149238TYPE_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 )
0 commit comments