Example #1
0
 async def connect(self):
     """ """
     if not self._connected:
         self._lo = asyncio.get_running_loop()
         if not hasattr(self, '_event_thread'):
             self._event_thread = threading.current_thread()
         if not hasattr(self, '_host'):
             found = await util.find_service('rcute-cozmars-????',
                                             '_ws._tcp.local.')
             assert len(
                 found
             ) == 1, "More than one cozmars found." if found else "No cozmars found."
             self._host = found[0].server
         self._ws = await websockets.connect(f'ws://{self._host}/rpc')
         if '-1' == await self._ws.recv():
             raise RuntimeError(
                 'Could not connect to Cozmars, please close other programs that are already connected to Cozmars'
             )
         self._rpc = RPCClient(self._ws)
         self._about = json.loads(await self._get('/about'))
         await self._env.load()
         self._eye_anim_task = asyncio.create_task(
             self._eye_anim.animate(self))
         self._event_task = asyncio.create_task(self._get_event())
         self._connected = True
Example #2
0
async def main():
    async with websockets.connect('ws://localhost:8000') as ws:
        stub = RPCClient(ws)

        # normal rpc
        print(await stub.div(1, 3))

        # cancellation
        s = stub.sleep(3)

        async def cancel_sleep():
            await asyncio.sleep(1)
            s.cancel()

        try:
            asyncio.create_task(cancel_sleep())
            print(await s)
        except asyncio.CancelledError as e:
            print('cancelled')

        # request-streaming
        print(await stub.sum(request_stream=range(1, 3)))

        # response-streaming
        async for i in stub.repeat('bla...', 4):
            print(i)

        # combine request-streaming and response-streaming
        async for i in stub.uppercase(request_stream=['hello', 'rpc']):
            print(i)
Example #3
0
 async def connect(self):
     """Establish a connection with the Rubik's Cube"""
     if not self._connected:
         self._ws = await websockets.connect(f'ws://{self._host}:81')
         if'-1' == await self._ws.recv():
             raise RuntimeError("""Cannot connect to Rubik's Cube, please close other programs that are connecting Rubik's Cube""")
         self._rpc = RPCClient(self._ws)
         about = json.loads(await self._get('/about'))
         self._ip = about['ip']
         self._serial = about['serial']
         self._firmware_version = about['version']
         self._hostname = about['hostname']
         self._mac = about['mac']
         self._event_task = asyncio.create_task(self._get_event())
         self._connected = True
Example #4
0
 async def connect(self):
     """建立与魔方的连接"""
     if not self._connected:
         self._ws = await websockets.connect(f'ws://{self._host}:81')
         if '-1' == await self._ws.recv():
             raise RuntimeError('无法连接魔方, 请先关闭其他正在连接魔方的程序')
         self._rpc = RPCClient(self._ws)
         about = json.loads(await self._get('/about'))
         self._ip = about['ip']
         self._serial = about['serial']
         self._firmware_version = about['version']
         self._hostname = about['hostname']
         self._mac = about['mac']
         self._event_task = asyncio.create_task(self._get_event())
         self._connected = True
Example #5
0
 async def connect(self):
     """Connect Cozmars"""
     if not self._connected:
         self._ws = await websockets.connect(f'ws://{self._host}/rpc')
         if'-1' == await self._ws.recv():
             raise RuntimeError('Could not connect to Cozmars, please close other programs that are already connected to Cozmars')
         self._rpc = RPCClient(self._ws)
         about = json.loads(await self._get('/about'))
         self._ip = about['ip']
         self._serial = about['serial']
         self._firmware_version = about['version']
         self._hostname = about['hostname']
         self._mac = about['mac']
         self._sensor_task = asyncio.create_task(self._get_sensor_data())
         self._eye_anim_task = asyncio.create_task(self._eye_anim.animate(self))
         self._connected = True
Example #6
0
 async def connect(self):
     """连接 Cozmars"""
     if not self._connected:
         self._ws = await websockets.connect(f'ws://{self._host}/rpc')
         if '-1' == await self._ws.recv():
             raise RuntimeError('无法连接 Cozmars, 请先关闭其他已经连接 Cozmars 的程序')
         self._rpc = RPCClient(self._ws)
         about = json.loads(await self._get('/about'))
         self._ip = about['ip']
         self._serial = about['serial']
         self._firmware_version = about['version']
         self._hostname = about['hostname']
         self._mac = about['mac']
         self._sensor_task = asyncio.create_task(self._get_sensor_data())
         self._eye_anim_task = asyncio.create_task(self._eye_anim.animate(self))
         self._connected = True
Example #7
0
 async def connect(self):
     """ """
     if not self._connected:
         self._lo = asyncio.get_event_loop()
         if not hasattr(self, '_event_thread'):
             self._event_thread = threading.current_thread()
         if not hasattr(self, '_host'):
             found = await util.find_service('rcute-cube-????',
                                             '_ws._tcp.local.')
             assert len(
                 found
             ) == 1, "More than one cube found." if found else "No cube found."
             self._host = found[0].server
         self._ws = await websockets.connect(f'ws://{self._host}:81')
         if '-1' == await self._ws.recv():
             raise RuntimeError(
                 """Cannot connect to cube, please close other programs that are connecting cube"""
             )
         self._rpc = RPCClient(self._ws)
         self._about = json.loads(await self._get('/about'))
         self._event_task = asyncio.get_event_loop().create_task(
             self._get_event())
         self._connected = True
Example #8
0
class AioRobot:
    """Async/await mode of Cozmars robot

    :param serial_or_ip: IP address or serial number of Cozmars to connect
    :type serial_or_ip: str
    """
    def __init__(self, serial_or_ip):
        self._host ='rcute-cozmars-' + serial_or_ip +'.local' if len(serial_or_ip) == 4 else serial_or_ip
        self._mode ='aio'
        self._connected = False
        self._env = env.Env(self)
        self._screen = screen.Screen(self)
        self._camera = camera.Camera(self)
        self._microphone = microphone.Microphone(self)
        self._button = button.Button(self)
        self._sonar = sonar.Sonar(self)
        self._infrared = infrared.Infrared(self)
        self._lift = lift.Lift(self)
        self._head = head.Head(self)
        self._buzzer = buzzer.Buzzer(self)
        self._motor = motor.Motor(self)
        self._eye_anim = eye_animation.EyeAnimation(self)

    def _in_event_loop(self):
        return True

    @util.mode()
    async def animate(self, name, *args, **kwargs):
        """Execute action

        :param name: the name of the action
        :type name: str
        """
        anim = animations[name]
        anim = getattr(anim,'animate', anim)
        await anim(self, *args, **kwargs)

    @property
    def animation_list(self):
        """Action List"""
        return list(animations.keys())

    @property
    def eyes(self):
        """eye"""
        return self._eye_anim

    @property
    def env(self):
        """Environmental Variables"""
        return self._env

    @property
    def button(self):
        """Button"""
        return self._button

    @property
    def infrared(self):
        """Infrared sensor, on the bottom of the robot"""
        return self._infrared

    @property
    def sonar(self):
        """Sonar, namely ultrasonic distance sensor"""
        return self._sonar

    @property
    def motor(self):
        """motor"""
        return self._motor

    @property
    def head(self):
        """head"""
        return self._head

    @property
    def lift(self):
        """Arm"""
        return self._lift

    @property
    def buzzer(self):
        """buzzer"""
        return self._buzzer

    @property
    def screen(self):
        """screen"""
        return self._screen

    @property
    def camera(self):
        """camera"""
        return self._camera

    @property
    def microphone(self):
        """microphone"""
        return self._microphone

    async def connect(self):
        """Connect Cozmars"""
        if not self._connected:
            self._ws = await websockets.connect(f'ws://{self._host}/rpc')
            if'-1' == await self._ws.recv():
                raise RuntimeError('Could not connect to Cozmars, please close other programs that are already connected to Cozmars')
            self._rpc = RPCClient(self._ws)
            about = json.loads(await self._get('/about'))
            self._ip = about['ip']
            self._serial = about['serial']
            self._firmware_version = about['version']
            self._hostname = about['hostname']
            self._mac = about['mac']
            self._sensor_task = asyncio.create_task(self._get_sensor_data())
            self._eye_anim_task = asyncio.create_task(self._eye_anim.animate(self))
            self._connected = True

    @property
    def connected(self):
        """Are robots connected?"""
        return self._connected

    async def _call_callback(self, cb, *args):
        if cb:
            if self._mode =='aio':
                (await cb(*args)) if asyncio.iscoroutinefunction(cb) else cb(*args)
            else:
                self._lo.run_in_executor(None, cb, *args)

    async def _get_sensor_data(self):
        self._sensor_data_rpc = self._rpc.sensor_data()
        async for event, data in self._sensor_data_rpc:
            try:
                if event =='pressed':
                    if not data:
                        self.button._held = self.button._double_pressed = False
                    self.button._pressed = data
                    await self._call_callback(self.button.when_pressed if data else self.button.when_released)
                elif event =='held':
                    self.button._held = data
                    await self._call_callback(self.button.when_held)
                elif event =='double_pressed':
                    self.button._pressed = data
                    self.button._double_pressed = data
                    await self._call_callback(self.button.when_pressed)
                    await self._call_callback(self.button.when_double_pressed)
                elif event =='out_of_range':
                    await self._call_callback(self.sonar.when_out_of_range, data)
                elif event =='in_range':
                    await self._call_callback(self.sonar.when_in_range, data)
                elif event =='lir':
                    self.infrared._state = data, self.infrared._state[1]
                    await self._call_callback(self.infrared.when_state_changed, self.infrared._state)
                elif event =='rir':
                    self.infrared._state = self.infrared._state[0], data
                    await self._call_callback(self.infrared.when_state_changed, self.infrared._state)
            except Exception as e:
                logger.exception(e)

    async def disconnect(self):
        """Disconnect Cozmars"""
        if self._connected:
            self._sensor_task.cancel()
            self._eye_anim_task.cancel()
            self._sensor_data_rpc.cancel()
            await asyncio.gather(self.camera._close(), self.microphone._close(), self.buzzer._close(), return_exceptions=True)
            await self._ws.close()
            self._connected = False

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.disconnect()

    @util.mode(force_sync=False)
    async def forward(self, duration=None):
        """go ahead

        :param duration: duration (seconds)
        :type duration: float
        """
        await self._rpc.speed((1,1), duration)
    
    @util.mode(force_sync=False)
    async def drive(self, speed, duration=1):
        """go ahead

        :param duration: duration (seconds)
        :type duration: float
        """
        await self._rpc.speed(speed, duration)

    @util.mode(force_sync=False)
    async def backward(self, duration=None):
        """Back

        :param duration: duration (seconds)
        :type duration: float
        """
        await self._rpc.speed((-1,-1), duration)

    @util.mode(force_sync=False)
    async def turn_left(self, duration=None):
        """Turn left

        :param duration: duration (seconds)
        :type duration: float
        """
        await self._rpc.speed((-1,1), duration)

    @util.mode(force_sync=False)
    async def turn_right(self, duration=None):
        """Turn right

        :param duration: duration (seconds)
        :type duration: float
        """
        await self._rpc.speed((1,-1), duration)

    @util.mode()
    async def stop(self):
        """stop"""
        await self._rpc.speed((0, 0))

    @property
    def hostname(self):
        """Cozmars URL"""
        return self._hostname

    @property
    def ip(self):
        """Cozmars' IP address"""
        return self._ip

    @property
    def firmware_version(self):
        """Cozmars firmware version

        If it is lower than the latest version on |pypi|, you can log in to the robot's page to update

        """
        return self._firmware_version

    @property
    def mac(self):
        """Cozmars MAC address"""
        return self._mac


    @property
    def serial(self):
        """Cozmars serial number"""
        return self._serial

    @util.mode()
    async def poweroff(self):
        """Close Cozmars"""
        await AioRobot.disconnect(self)
        try:
            await self._get('/poweroff')
        except Exception as e:
            pass

    @util.mode()
    async def reboot(self):
        """Restart Cozmars"""
        await AioRobot.disconnect(self)
        try:
            await self._get('/reboot')
        except Exception as e:
            pass

    async def _get(self, sub_url):
        async with aiohttp.ClientSession() as session:
            async with session.get('http://' + self._host + sub_url) as resp:
                return await resp.text()
Example #9
0
class AioCube:
    """Asynchronous (async/await) mode of Cube

    :param serial_or_ip: The IP address or serial number of the cube to be connected. Default to None, in which case the program will automatically detect the cube on connection if there's only one found.
    :type serial_or_ip: str
    """
    def __init__(self, serial_or_ip=None):
        if serial_or_ip:
            self._host = 'rcute-cube-' + serial_or_ip + '.local' if len(
                serial_or_ip) == 4 else serial_or_ip
        self._mode = 'aio'
        self._state = 'moved'
        self._connected = False
        self.last_action = None
        """The last action of the cube"""
        self.when_flipped = None
        """Callback function, called when the cube is flipped (with a parameter to indicate 90 degree flip or 180 degrees), the default is `None` """
        self.when_shaken = None
        """Callback function, called when the cube is shaken, the default is `None` """
        self.when_rotated = None
        """Callback function, called when the cube is rotated horizontally (with a parameter indicating clockwise or counterclockwise), the default is `None` """
        self.when_pushed = None
        """Callback function, called when the cube is translated (with a parameter to indicate the direction of movement), the default is `None` """
        self.when_tilted = None
        """Callback function, called when the cube is tilted (with a parameter to indicate the direction of movement), the default is `None` """
        self.when_tapped = None
        """Callback function, called when the cube is tapped, the default is `None` """
        self.when_fall = None
        """Callback function, called when the cube free fall, the default is `None` """
        self.when_moved = None
        """Callback function, called when the cube is moved (including all the above actions), the default is `None` """
        self.when_static = None
        """Callback function, called when the cube comes to rest, the default is `None` """

    _directions = ['+x', '-x', '+y', '-y', '+z', '-z']

    @staticmethod
    def dir2num(dir):
        """Convert direction to number"""
        return AioCube._directions.index(dir) + 1

    @staticmethod
    def num2dir(num):
        """Convert number to direction"""
        return AioCube._directions[num - 1]

    def _in_event_loop(self):
        return self._event_thread == threading.current_thread()

    async def connect(self):
        """ """
        if not self._connected:
            self._lo = asyncio.get_event_loop()
            if not hasattr(self, '_event_thread'):
                self._event_thread = threading.current_thread()
            if not hasattr(self, '_host'):
                found = await util.find_service('rcute-cube-????',
                                                '_ws._tcp.local.')
                assert len(
                    found
                ) == 1, "More than one cube found." if found else "No cube found."
                self._host = found[0].server
            self._ws = await websockets.connect(f'ws://{self._host}:81')
            if '-1' == await self._ws.recv():
                raise RuntimeError(
                    """Cannot connect to cube, please close other programs that are connecting cube"""
                )
            self._rpc = RPCClient(self._ws)
            self._about = json.loads(await self._get('/about'))
            self._event_task = asyncio.get_event_loop().create_task(
                self._get_event())
            self._connected = True

    async def _call_callback(self, cb, *args):
        if cb:
            if self._mode == 'aio':
                (await cb(*args)) if asyncio.iscoroutinefunction(cb) else cb(
                    *args)
            else:
                self._lo.run_in_executor(None, cb, *args)

    async def _get_event(self):
        self._event_rpc = self._rpc.mpu_event()
        async for event in self._event_rpc:
            try:
                arg = event[1]
                if event[0] in ('static', 'moved'):
                    self._state = event[0]
                else:
                    self.last_action = event[0], arg
                if event[0] in ('static', 'moved', 'fall', 'tapped', 'shaken'):
                    await self._call_callback(getattr(self,
                                                      'when_' + event[0]))
                else:
                    await self._call_callback(
                        getattr(self, 'when_' + event[0]), arg)
            except Exception as e:
                logger.exception(e)

    @property
    def connected(self):
        """ """
        return self._connected

    async def disconnect(self):
        """ """
        if self._connected:
            self._event_rpc.cancel()
            await asyncio.gather(self._event_task, return_exceptions=True)
            await self._ws.close()
            self._connected = False

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.disconnect()

    @property
    def ip(self):
        """cube's IP Address"""
        return self._about['ip']

    @property
    def hostname(self):
        """The URL of the cube"""
        return self._about['hostname']

    @property
    def firmware_version(self):
        """The firmware version of the cube"""
        return self._about['version']

    @property
    def mac(self):
        """cube's MAC Address"""
        return self._about['mac']

    @property
    def serial(self):
        """Serial Number of cube"""
        return self._about['serial']

    async def _get(self, sub_url):
        async with aiohttp.ClientSession() as session:
            async with session.get('http://' + self._host + sub_url) as resp:
                return await resp.text()

    @util.mode(property_type='setter')
    async def led(self, *args):
        """BGR color of the builtin LED"""
        if args:
            await self._rpc.rgb(
                *(util.bgr(args[0]) if args[0] is not None else (0, 0,
                                                                 0))[::-1])
        else:
            return (await self._rpc.rgb())[::-1]

    @util.mode(property_type='getter')
    async def acc(self):
        """Acceleration, when the cube is stationary, the acceleration is equal to gravity (but in fact there is an error)"""
        return await self._rpc.mpu_acc()

    @util.mode(property_type='getter')
    async def static(self):
        """Is it still"""
        #return await self._rpc.mpu_static()
        return self._state == 'static'

    @util.mode(property_type='getter')
    async def top_face(self):
        """Which side is on top, when the cube is stationary, it returns the direction of the top side, and `None` cube is moving """
        return await self.guess_top_face() if self._state == 'static' else None

    @util.mode()
    async def guess_top_face(self):
        acc = await self._rpc.mpu_acc()
        comp, j = abs(acc[0]), 0
        for i in range(1, 3):
            if comp < abs(acc[i]):
                comp, j = abs(acc[i]), i
        return ('-' if acc[j] > 0 else '+') + chr(120 + j)
Example #10
0
class AioRobot:
    """Async/await mode of Cozmars robot

    :param serial_or_ip: IP address or serial number of Cozmars to connect. Default to None, in which case the program will automatically detect the robot on connection if there's only one found.
    :type serial_or_ip: str
    """
    def __init__(self, serial_or_ip=None):
        if serial_or_ip:
            self._host = 'rcute-cozmars-' + serial_or_ip + '.local' if len(
                serial_or_ip) == 4 else serial_or_ip
        self._mode = 'aio'
        self._connected = False
        self._env = env.Env(self)
        self._screen = screen.Screen(self)
        self._camera = camera.Camera(self)
        self._microphone = microphone.Microphone(self)
        self._button = button.Button(self)
        self._sonar = sonar.Sonar(self)
        self._infrared = infrared.Infrared(self)
        self._lift = lift.Lift(self)
        self._head = head.Head(self)
        self._buzzer = buzzer.Buzzer(self)
        self._speaker = speaker.Speaker(self)
        self._motor = motor.Motor(self)
        self._eye_anim = eye_animation.EyeAnimation(self)
        self.on_camera_image = None
        """Callback funciton. After :meth:`show_camera_view` is called, :data:`on_camera_image` will be called every time camera feed receives an new image. The callback must take the captured image as input and return processd image."""

    def _in_event_loop(self):
        return self._event_thread == threading.current_thread()

    @util.mode()
    async def animate(self, name, *args, **kwargs):
        """perform animation

        :param name: the name of the animation
        :type name: str
        """
        anim = animations[name]
        anim = getattr(anim, 'animate', anim)
        await anim(self, *args, **kwargs)

    @classmethod
    def get_animation_list(cl):
        """ """
        return list(animations.keys())

    @property
    def eyes(self):
        """eye animation on screen"""
        return self._eye_anim

    @property
    def env(self):
        """Environmental Variables"""
        return self._env

    @property
    def button(self):
        """ """
        return self._button

    @property
    def infrared(self):
        """Infrared sensor at the bottom of the robot"""
        return self._infrared

    @property
    def sonar(self):
        """Sonar, namely ultrasonic distance sensor"""
        return self._sonar

    @property
    def motor(self):
        """ """
        return self._motor

    @property
    def head(self):
        """ """
        return self._head

    @property
    def lift(self):
        """ """
        return self._lift

    @property
    def buzzer(self):
        """only for Cozmars V1"""
        if self.firmware_version.startswith('2'):
            raise AttributeError('Cozmars V2 has no buzzer')
        return self._buzzer

    @property
    def speaker(self):
        """only for Cozmars V2"""
        if self.firmware_version.startswith('1'):
            raise AttributeError('Cozmars V1 has no speaker')
        return self._speaker

    @property
    def screen(self):
        """ """
        return self._screen

    @property
    def camera(self):
        """ """
        return self._camera

    @property
    def microphone(self):
        """ """
        return self._microphone

    async def connect(self):
        """ """
        if not self._connected:
            self._lo = asyncio.get_running_loop()
            if not hasattr(self, '_event_thread'):
                self._event_thread = threading.current_thread()
            if not hasattr(self, '_host'):
                found = await util.find_service('rcute-cozmars-????',
                                                '_ws._tcp.local.')
                assert len(
                    found
                ) == 1, "More than one cozmars found." if found else "No cozmars found."
                self._host = found[0].server
            self._ws = await websockets.connect(f'ws://{self._host}/rpc')
            if '-1' == await self._ws.recv():
                raise RuntimeError(
                    'Could not connect to Cozmars, please close other programs that are already connected to Cozmars'
                )
            self._rpc = RPCClient(self._ws)
            self._about = json.loads(await self._get('/about'))
            await self._env.load()
            self._eye_anim_task = asyncio.create_task(
                self._eye_anim.animate(self))
            self._event_task = asyncio.create_task(self._get_event())
            self._connected = True

    @property
    def connected(self):
        """ """
        return self._connected

    async def _call_callback(self, cb, *args):
        if cb:
            if self._mode == 'aio':
                (await cb(*args)) if asyncio.iscoroutinefunction(cb) else cb(
                    *args)
            else:
                self._lo.run_in_executor(None, cb, *args)

    async def _get_event(self):
        self._event_rpc = self._rpc.sensor_data()
        async for event, data in self._event_rpc:
            try:
                if event == 'pressed':
                    if not data:
                        self.button._long_pressed = self.button._double_pressed = False
                    self.button._pressed = data
                    await self._call_callback(self.button.when_pressed if data
                                              else self.button.when_released)
                elif event == 'long_pressed':
                    self.button._long_pressed = data
                    await self._call_callback(self.button.when_long_pressed)
                elif event == 'double_pressed':
                    self.button._pressed = data
                    self.button._double_pressed = data
                    await self._call_callback(self.button.when_pressed)
                    await self._call_callback(self.button.when_double_pressed)
                elif event == 'out_of_range':
                    await self._call_callback(self.sonar.when_out_of_range,
                                              data)
                elif event == 'in_range':
                    await self._call_callback(self.sonar.when_in_range, data)
                elif event == 'lir':
                    self.infrared._state = data, self.infrared._state[1]
                    await self._call_callback(self.infrared.when_state_changed,
                                              self.infrared._state)
                elif event == 'rir':
                    self.infrared._state = self.infrared._state[0], data
                    await self._call_callback(self.infrared.when_state_changed,
                                              self.infrared._state)
            except Exception as e:
                logger.exception(e)

    async def disconnect(self):
        """ """
        if self._connected:
            self._event_task.cancel()
            self._eye_anim_task.cancel()
            self._event_rpc.cancel()
            await asyncio.gather(self.when_called(None),
                                 self.close_camera_view(),
                                 return_exceptions=True)
            await asyncio.gather(self.camera.close(),
                                 self.microphone.close(),
                                 self.speaker.close(),
                                 return_exceptions=True)
            await self._ws.close()
            self._connected = False

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.disconnect()

    @util.mode(force_sync=False)
    async def forward(self, duration=None):
        """

        :param duration: duration (seconds), default is None for non-stop
        :type duration: float
        """
        await self._rpc.speed((1, 1), duration)

    @util.mode(force_sync=False)
    async def backward(self, duration=None):
        """

        :param duration: duration (seconds), default is None for non-stop
        :type duration: float
        """
        await self._rpc.speed((-1, -1), duration)

    @util.mode(force_sync=False)
    async def turn_left(self, duration=None):
        """

        :param duration: duration (seconds), default is None for non-stop
        :type duration: float
        """
        await self._rpc.speed((-1, 1), duration)

    @util.mode(force_sync=False)
    async def turn_right(self, duration=None):
        """

        :param duration: duration (seconds), default is None for non-stop
        :type duration: float
        """
        await self._rpc.speed((1, -1), duration)

    @util.mode()
    async def stop(self):
        """ """
        await self._rpc.speed((0, 0))

    @util.mode()
    async def say(self, txt, repeat=1, **options):
        """Text to speach for Cozmars V2.

        `rcute-ai <https://rcute-ai.readthedocs.io/>`_ must be installed to support this function.

        :param txt: text to be said
        :type txt: str
        :param repeat: playback times, default is 1
        :type repeat: int, optional
        :param options:
            * voice/lang
            * volume
            * pitch
            * speed
            * word_gap

            See `rcute_ai.tts <https://rcute-ai.readthedocs.io/zh_CN/latest/api/tts.html#rcute_ai.tts.TTS.tts_wav/>`_
        :type options: optional
        """
        if not hasattr(self, '_tts'):
            import rcute_ai as ai
            self._tts = ai.TTS()
        op = self.env.vars.get('say', {}).get(sys.platform, {}).copy()
        op.update(options)
        wav_data = await self._lo.run_in_executor(
            None, functools.partial(self._tts.tts_wav, txt, **op))
        with wave.open(io.BytesIO(wav_data)) as f:
            await self.speaker.play(f.readframes(f.getnframes()),
                                    repeat=repeat,
                                    volume=self.env.vars.get('say_vol'),
                                    sample_rate=f.getframerate(),
                                    dtype='int16')

    @util.mode(property_type='setter')
    async def when_called(self, *args):
        """Callback function, called when the wake word 'R-Cute' or Chinese '阿Q' is detected.

        When set, a thread will be running in background using the robot's microphone. If set to `None`, the background thread will stop.

        `rcute-ai <https://rcute-ai.readthedocs.io/>`_ must be installed to support this function.

        See `rcute_ai.WakeWordDetector <https://rcute-ai.readthedocs.io/zh_CN/latest/api/WakeWordDetector.html>`_"""
        if not args:
            return getattr(self, '_when_called', None)
        cb = self._when_called = args[0]
        if cb:

            def wwd_run():
                if not hasattr(self, '_wwd'):
                    import rcute_ai as ai
                    self._wwd = ai.WakeWordDetection()
                logger.info('Start wake word detection.')
                with self.microphone.get_buffer() as buf:
                    while self._when_called:
                        self._wwd.detect(buf) and cb()
                logger.info('Stop wake word detection.')

            self._wwd_thread = threading.Thread(target=wwd_run, daemon=True)
            self._wwd_thread.start()
        else:
            hasattr(self, '_wwd') and self._wwd.cancel()
            hasattr(self, '_wwd_thread') and self._wwd_thread.is_alive(
            ) and await self._lo.run_in_executor(None, self._wwd_thread.join)

    @util.mode()
    async def listen(self, **kw):
        """Speech recognition.

        :param lang: language, default to `'en'`
        :type lang: str
        :return: recognized string
        :rtype: str

        other paramters are the same as `rcute_ai.STT.stt <https://rcute-ai.readthedocs.io/zh_CN/latest/api/STT.html#rcute_ai.STT.stt>`_ 's keyword arguments.

        `rcute-ai <https://rcute-ai.readthedocs.io/>`_ must be installed to support this function.
        """
        if not hasattr(self, '_stts'):
            self._stts = {}
        lang = kw.pop('lang', 'en')

        def stt_run():
            if not self._stts.get(lang):
                import rcute_ai as ai
                self._stts[lang] = ai.STT(lang=lang)
            with self.microphone.get_buffer() as buf:
                return self._stts[lang].stt(buf, **kw)

        return await self._lo.run_in_executor(None, stt_run)

    @util.mode()
    async def show_camera_view(self):
        """Open camera and run a background thread showing the camera view or processed camera images if :data:`on_camera_image` is set"""
        def cam_run():
            import cv2
            self._stop_cam_view = False
            with self.camera.get_buffer() as buf:
                for im in buf:
                    if self._stop_cam_view:
                        cv2.destroyWindow(self.serial)
                        break
                    self._latest_camera_view = im
                    self.on_camera_image and self.on_camera_image(im)
                    cv2.imshow(self.serial, im)
                    cv2.waitKey(10)

        self._cam_view_thread = threading.Thread(target=cam_run, daemon=True)
        self._cam_view_thread.start()

    @property
    def latest_camera_view(self):
        """latest image from :meth:`show_camera_view` """
        return getattr(self, '_latest_camera_view', None)

    @util.mode()
    async def close_camera_view(self):
        """Stop the background thread created in :meth:`show_camera_view`"""
        self._stop_cam_view = True
        hasattr(self, '_cam_view_thread') and self._cam_view_thread.is_alive(
        ) and await self._lo.run_in_executor(None, self._cam_view_thread.join)

    @property
    def hostname(self):
        """Cozmars URL"""
        return self._about['hostname']

    @property
    def ip(self):
        """Cozmars' IP address"""
        return self._about['ip']

    @property
    def firmware_version(self):
        """Cozmars firmware version"""
        return self._about['version']

    @property
    def mac(self):
        """Cozmars MAC address"""
        return self._about['mac']

    @property
    def serial(self):
        """Cozmars serial number"""
        return self._about['serial']

    @util.mode()
    async def poweroff(self):
        """ """
        await AioRobot.disconnect(self)
        try:
            await self._get('/poweroff')
        except Exception as e:
            pass
        finally:
            self._connected = False

    @util.mode()
    async def reboot(self):
        """ """
        await AioRobot.disconnect(self)
        try:
            await self._get('/reboot')
        except Exception as e:
            pass
        finally:
            self._connected = False

    async def _get(self, sub_url):
        async with aiohttp.ClientSession() as session:
            async with session.get('http://' + self._host + sub_url) as resp:
                return await resp.text()
Example #11
0
class AioRobot:
    """Cozmars 机器人的异步 (async/await) 模式

    :param serial_or_ip: 要连接的 Cozmars 的 IP 地址或序列号
    :type serial_or_ip: str
    """
    def __init__(self, serial_or_ip):
        self._host = 'rcute-cozmars-' + serial_or_ip + '.local' if len(serial_or_ip) == 4 else serial_or_ip
        self._mode = 'aio'
        self._connected = False
        self._env = env.Env(self)
        self._screen = screen.Screen(self)
        self._camera = camera.Camera(self)
        self._microphone = microphone.Microphone(self)
        self._button = button.Button(self)
        self._sonar = sonar.Sonar(self)
        self._infrared = infrared.Infrared(self)
        self._lift = lift.Lift(self)
        self._head = head.Head(self)
        self._buzzer = buzzer.Buzzer(self)
        self._motor = motor.Motor(self)
        self._eye_anim = eye_animation.EyeAnimation(self)

    def _in_event_loop(self):
        return True

    @util.mode()
    async def animate(self, name, *args, **kwargs):
        """执行动作

        :param name: 动作的名称
        :type name: str
        """
        anim = animations[name]
        anim = getattr(anim, 'animate', anim)
        await anim(self, *args, **kwargs)

    @property
    def animation_list(self):
        """动作列表"""
        return list(animations.keys())

    @property
    def eyes(self):
        """眼睛"""
        return self._eye_anim

    @property
    def env(self):
        """环境变量"""
        return self._env

    @property
    def button(self):
        """按钮"""
        return self._button

    @property
    def infrared(self):
        """红外传感器,在机器人底部"""
        return self._infrared

    @property
    def sonar(self):
        """声纳,即超声波距离传感器"""
        return self._sonar

    @property
    def motor(self):
        """马达"""
        return self._motor

    @property
    def head(self):
        """头"""
        return self._head

    @property
    def lift(self):
        """手臂"""
        return self._lift

    @property
    def buzzer(self):
        """蜂鸣器"""
        return self._buzzer

    @property
    def screen(self):
        """屏幕"""
        return self._screen

    @property
    def camera(self):
        """摄像头"""
        return self._camera

    @property
    def microphone(self):
        """麦克风"""
        return self._microphone

    async def connect(self):
        """连接 Cozmars"""
        if not self._connected:
            self._ws = await websockets.connect(f'ws://{self._host}/rpc')
            if '-1' == await self._ws.recv():
                raise RuntimeError('无法连接 Cozmars, 请先关闭其他已经连接 Cozmars 的程序')
            self._rpc = RPCClient(self._ws)
            about = json.loads(await self._get('/about'))
            self._ip = about['ip']
            self._serial = about['serial']
            self._firmware_version = about['version']
            self._hostname = about['hostname']
            self._mac = about['mac']
            self._sensor_task = asyncio.create_task(self._get_sensor_data())
            self._eye_anim_task = asyncio.create_task(self._eye_anim.animate(self))
            self._connected = True

    @property
    def connected(self):
        """是否连接上了机器人"""
        return self._connected

    async def _call_callback(self, cb, *args):
        if cb:
            if self._mode == 'aio':
                (await cb(*args)) if asyncio.iscoroutinefunction(cb) else cb(*args)
            else:
                self._lo.run_in_executor(None, cb, *args)

    async def _get_sensor_data(self):
        self._sensor_data_rpc = self._rpc.sensor_data()
        async for event, data in self._sensor_data_rpc:
            try:
                if event == 'pressed':
                    if not data:
                        self.button._held = self.button._double_pressed = False
                    self.button._pressed = data
                    await self._call_callback(self.button.when_pressed if data else self.button.when_released)
                elif event == 'held':
                    self.button._held = data
                    await self._call_callback(self.button.when_held)
                elif event == 'double_pressed':
                    self.button._pressed = data
                    self.button._double_pressed = data
                    await self._call_callback(self.button.when_pressed)
                    await self._call_callback(self.button.when_double_pressed)
                elif event == 'out_of_range':
                    await self._call_callback(self.sonar.when_out_of_range, data)
                elif event == 'in_range':
                    await self._call_callback(self.sonar.when_in_range, data)
                elif event == 'lir':
                    self.infrared._state = data, self.infrared._state[1]
                    await self._call_callback(self.infrared.when_state_changed, self.infrared._state)
                elif event == 'rir':
                    self.infrared._state = self.infrared._state[0], data
                    await self._call_callback(self.infrared.when_state_changed, self.infrared._state)
            except Exception as e:
                logger.exception(e)

    async def disconnect(self):
        """断开 Cozmars 的连接"""
        if self._connected:
            self._sensor_task.cancel()
            self._eye_anim_task.cancel()
            self._sensor_data_rpc.cancel()
            await asyncio.gather(self.camera._close(), self.microphone._close(), self.buzzer._close(), return_exceptions=True)
            await self._ws.close()
            self._connected = False

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.disconnect()

    @util.mode(force_sync=False)
    async def forward(self, duration=None):
        """前进

        :param duration: 持续时间(秒)
        :type duration: float
        """
        await self._rpc.speed((1,1), duration)

    @util.mode(force_sync=False)
    async def backward(self, duration=None):
        """后退

        :param duration: 持续时间(秒)
        :type duration: float
        """
        await self._rpc.speed((-1,-1), duration)

    @util.mode(force_sync=False)
    async def turn_left(self, duration=None):
        """左传

        :param duration: 持续时间(秒)
        :type duration: float
        """
        await self._rpc.speed((-1,1), duration)

    @util.mode(force_sync=False)
    async def turn_right(self, duration=None):
        """右转

        :param duration: 持续时间(秒)
        :type duration: float
        """
        await self._rpc.speed((1,-1), duration)

    @util.mode()
    async def stop(self):
        """停止"""
        await self._rpc.speed((0, 0))

    @property
    def hostname(self):
        """Cozmars 的网址"""
        return self._hostname

    @property
    def ip(self):
        """Cozmars 的 IP 地址"""
        return self._ip

    @property
    def firmware_version(self):
        """Cozmars 的固件版本

        如果低于 |pypi上的最新版本| ,可以登陆机器人的页面进行更新

        """
        return self._firmware_version

    @property
    def mac(self):
        """Cozmars 的 MAC 地址"""
        return self._mac


    @property
    def serial(self):
        """Cozmars 的序列号"""
        return self._serial

    @util.mode()
    async def poweroff(self):
        """关闭 Cozmars"""
        await AioRobot.disconnect(self)
        try:
            await self._get('/poweroff')
        except Exception as e:
            pass

    @util.mode()
    async def reboot(self):
        """重启 Cozmars"""
        await AioRobot.disconnect(self)
        try:
            await self._get('/reboot')
        except Exception as e:
            pass

    async def _get(self, sub_url):
        async with aiohttp.ClientSession() as session:
            async with session.get('http://' + self._host + sub_url) as resp:
                return await resp.text()
Example #12
0
class AioCube:
    """Asynchronous (async/await) mode of Cube

    :param serial_or_ip: The IP address or serial number of the Rubik's Cube to be connected
    :type serial_or_ip: str
    """
    def __init__(self, serial_or_ip):
        self._host ='rcute-cube-' + serial_or_ip +'.local' if len(serial_or_ip) == 4 else serial_or_ip
        self._mode ='aio'
        self._state ='moved'
        self._connected = False
        self._dir2color = {'+X':'red','-X':'green','+Y':'blue','-Y':'yellow','+Z':'white', '-Z':'orange'}
        self.last_action = None
        """The last action of the Rubik's Cube"""
        self.when_flipped = None
        """Callback function, called when the Rubik's Cube is flipped (with a direction parameter to indicate 90 degrees flip or 180 degrees), the default is `None` """
        self.when_shaked = None
        """Callback function, called when the Rubik's Cube is shaken, the default is `None` """
        self.when_rotated = None
        """Callback function, called when the cube is rotated horizontally (with a direction parameter indicating clockwise or counterclockwise), the default is `None` """
        self.when_pushed = None
        """Callback function, called when the cube is translated (with a direction parameter to indicate the direction of movement), the default is `None` """
        self.when_tilted = None
        """Callback function, called when the cube is tilted (with a direction parameter to indicate the direction of movement), the default is `None` """
        self.when_tapped = None
        """Callback function, called when tapping the Rubik's Cube, the default is `None` """
        self.when_fall = None
        """Callback function, called when the Rubik's Cube loses weight/free fall, the default is `None` """
        self.when_moved = None
        """Callback function, called when the cube is moved (including the above actions), the default is `None` """
        self.when_static = None
        """Callback function, called when the Rubik's Cube comes to rest, the default is `None` """


    def _in_event_loop(self):
        return True

    async def connect(self):
        """Establish a connection with the Rubik's Cube"""
        if not self._connected:
            self._ws = await websockets.connect(f'ws://{self._host}:81')
            if'-1' == await self._ws.recv():
                raise RuntimeError("""Cannot connect to Rubik's Cube, please close other programs that are connecting Rubik's Cube""")
            self._rpc = RPCClient(self._ws)
            about = json.loads(await self._get('/about'))
            self._ip = about['ip']
            self._serial = about['serial']
            self._firmware_version = about['version']
            self._hostname = about['hostname']
            self._mac = about['mac']
            self._event_task = asyncio.create_task(self._get_event())
            self._connected = True

    async def _call_callback(self, cb, *args):
        if cb:
            if self._mode =='aio':
                (await cb(*args)) if asyncio.iscoroutinefunction(cb) else cb(*args)
            else:
                self._lo.run_in_executor(None, cb, *args)

    async def _get_event(self):
        self._event_rpc = self._rpc.mpu_event()
        async for event in self._event_rpc:
            try:
                arg = self._dir2color.get(event[1], event[1])
                if event[0] in ('static','moved'):
                    self._state = event[0]
                else:
                    self.last_action = event[0], arg
                if event[0] in ('static','moved','fall','tapped','shaked'):
                    await self._call_callback(getattr(self,'when_'+event[0]))
                else:
                    await self._call_callback(getattr(self,'when_'+event[0]), arg)
            except Exception as e:
                logger.exception(e)

    @property
    def connected(self):
        """Whether connected to the Rubik's Cube"""
        return self._connected

    async def disconnect(self):
        """Disconnect from Rubik's Cube"""
        if self._connected:
            self._event_rpc.cancel()
            await asyncio.gather(self._event_task, return_exceptions=True)
            await self._ws.close()
            self._connected = False

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.disconnect()

    @property
    def ip(self):
        """Magic's Cube's IP Address"""
        return self._ip

    @property
    def hostname(self):
        """The Cube's URL"""
        return self._hostname

    @property
    def firmware_version(self):
        """The firmware version of the Rubik's Cube"""
        return self._firmware_version

    @property
    def mac(self):
        """Magic's Cube's MAC Address"""
        return self._mac

    @property
    def serial(self):
        """Serial Number of Rubik's Cube"""
        return self._serial

    async def _get(self, sub_url):
        async with aiohttp.ClientSession() as session:
            async with session.get('http://' + self._host + sub_url) as resp:
                return await resp.text()

    @util.mode(property_type='setter')
    async def color(self, *args):
        """BGR color of LED light"""
        if args:
            await self._rpc.rgb(*(util.bgr(args[0]) if args[0] is not None else (0,0,0))[::-1])
        else:
            return (await self._rpc.rgb())[::-1]

    @util.mode(property_type='getter')
    async def acc(self):
        """Acceleration loss, when the Rubik's Cube is stationary, the acceleration is equal to the acceleration of gravity (but in fact there is an error)"""
        return await self._rpc.mpu_acc()

    @util.mode(property_type='getter')
    async def static(self):
        """Is it still"""
        #return await self._rpc.mpu_static()
        return self._state =='static'

    @util.mode(property_type='getter')  
    async def top_face(self):
        """Which side is up, when the Rubik’s Cube is stationary, it returns to the color of the QR code on the upside, and returns to `None` """
        if self._state !='static':
            return None
        acc = await self._rpc.mpu_acc()
        comp, j = abs(acc[0]), 0
        for i in range(1, 3):
            if comp <abs(acc[i]):
                comp, j = abs(acc[i]), i
        return self._dir2color[('-' if acc[j]>0 else'+') + chr(88+j)]
Example #13
0
class AioCube:
    """魔方的异步 (async/await) 模式

    :param serial_or_ip: 要连接的 魔方的 IP 地址或序列号
    :type serial_or_ip: str
    """
    def __init__(self, serial_or_ip):
        self._host = 'rcute-cube-' + serial_or_ip + '.local' if len(
            serial_or_ip) == 4 else serial_or_ip
        self._mode = 'aio'
        self._state = 'moved'
        self._connected = False
        self._dir2color = {
            '+X': 'red',
            '-X': 'green',
            '+Y': 'blue',
            '-Y': 'yellow',
            '+Z': 'white',
            '-Z': 'orange'
        }
        self.last_action = None
        """魔方的上一个动作"""
        self.when_flipped = None
        """回调函数,当魔方被翻转时调用(带一个方向参数表示90度翻转或180度),默认为 `None` """
        self.when_shaked = None
        """回调函数,当魔方被甩动时调用,默认为 `None` """
        self.when_rotated = None
        """回调函数,当魔方被水平旋转时调用(带一个方向参数表示顺时针或逆时针),默认为 `None` """
        self.when_pushed = None
        """回调函数,当魔方被平移时调用(带一个方向参数表示移动方向),默认为 `None` """
        self.when_tilted = None
        """回调函数,当魔方被倾斜时调用(带一个方向参数表示移动方向),默认为 `None` """
        self.when_tapped = None
        """回调函数,轻敲魔方时被调用,默认为 `None` """
        self.when_fall = None
        """回调函数,当魔方失重/自由落体时调用,默认为 `None` """
        self.when_moved = None
        """回调函数,当魔方被移动时调用(包括以上动作),默认为 `None` """
        self.when_static = None
        """回调函数,当魔方恢复静止时调用,默认为 `None` """

    def _in_event_loop(self):
        return True

    async def connect(self):
        """建立与魔方的连接"""
        if not self._connected:
            self._ws = await websockets.connect(f'ws://{self._host}:81')
            if '-1' == await self._ws.recv():
                raise RuntimeError('无法连接魔方, 请先关闭其他正在连接魔方的程序')
            self._rpc = RPCClient(self._ws)
            about = json.loads(await self._get('/about'))
            self._ip = about['ip']
            self._serial = about['serial']
            self._firmware_version = about['version']
            self._hostname = about['hostname']
            self._mac = about['mac']
            self._event_task = asyncio.create_task(self._get_event())
            self._connected = True

    async def _call_callback(self, cb, *args):
        if cb:
            if self._mode == 'aio':
                (await cb(*args)) if asyncio.iscoroutinefunction(cb) else cb(
                    *args)
            else:
                self._lo.run_in_executor(None, cb, *args)

    async def _get_event(self):
        self._event_rpc = self._rpc.mpu_event()
        async for event in self._event_rpc:
            try:
                arg = self._dir2color.get(event[1], event[1])
                if event[0] in ('static', 'moved'):
                    self._state = event[0]
                else:
                    self.last_action = event[0], arg
                if event[0] in ('static', 'moved', 'fall', 'tapped', 'shaked'):
                    await self._call_callback(getattr(self,
                                                      'when_' + event[0]))
                else:
                    await self._call_callback(
                        getattr(self, 'when_' + event[0]), arg)
            except Exception as e:
                logger.exception(e)

    @property
    def connected(self):
        """是否连接上了魔方"""
        return self._connected

    async def disconnect(self):
        """断开与魔方的连接"""
        if self._connected:
            self._event_rpc.cancel()
            await asyncio.gather(self._event_task, return_exceptions=True)
            await self._ws.close()
            self._connected = False

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc, tb):
        await self.disconnect()

    @property
    def ip(self):
        """魔方的 IP 地址"""
        return self._ip

    @property
    def hostname(self):
        """魔方的网址"""
        return self._hostname

    @property
    def firmware_version(self):
        """魔方的固件版本"""
        return self._firmware_version

    @property
    def mac(self):
        """魔方的 MAC 地址"""
        return self._mac

    @property
    def serial(self):
        """魔方的序列号"""
        return self._serial

    async def _get(self, sub_url):
        async with aiohttp.ClientSession() as session:
            async with session.get('http://' + self._host + sub_url) as resp:
                return await resp.text()

    @util.mode(property_type='setter')
    async def color(self, *args):
        """LED 灯的 BGR 颜色"""
        if args:
            await self._rpc.rgb(
                *(util.bgr(args[0]) if args[0] is not None else (0, 0,
                                                                 0))[::-1])
        else:
            return (await self._rpc.rgb())[::-1]

    @util.mode(property_type='getter')
    async def acc(self):
        """加速度失量,当魔方静止时,加速度等于重力加速度(但实际上是有误差的)"""
        return await self._rpc.mpu_acc()

    @util.mode(property_type='getter')
    async def static(self):
        """是否静止"""
        #return await self._rpc.mpu_static()
        return self._state == 'static'

    @util.mode(property_type='getter')
    async def top_face(self):
        """哪一面朝上,当魔方静止时返回朝上一面的二维码的颜色,非静止时返回 `None` """
        if self._state != 'static':
            return None
        acc = await self._rpc.mpu_acc()
        comp, j = abs(acc[0]), 0
        for i in range(1, 3):
            if comp < abs(acc[i]):
                comp, j = abs(acc[i]), i
        return self._dir2color[('-' if acc[j] > 0 else '+') + chr(88 + j)]