Example #1
0
 def _wait_init_reply() -> None:
     while not stop_event.is_set():
         packet = self.read()
         if packet and isinstance(packet, BaseInitReplyPacket):
             logger.trace(f"Got init reply: {packet}")
             break
         elif packet:
             error.set()
             logger.debug(f"Got packet but not init reply: {packet}")
             break
Example #2
0
 def _read(self) -> Optional[Packet]:
     with self.read_lock:
         data = bytes(self.device.read(-1))
         if len(data):
             while 0xff not in data and 0xfe not in data:
                 data += bytes(self.device.read(-1))
             # todo: check if this breaks anything
             data = data.split(b"\xff")[0]
             logger.trace(f"[RECV] {hexdump(data)}")
             packet = Packet.decode(data)
             return packet
         return None
Example #3
0
    def _write(self, data: bytes) -> None:
        with self.write_lock:
            parts = [
                # pad to 8 bytes
                data[i:i + 8].ljust(8, b"\0")
                for i in range(0, len(data), 8)
            ]

            # write and count amount written
            for part in parts:
                logger.trace(f"[SEND] {hexdump(part)}")
                self.device.write(part)
                # todo test how much to delay
                sleep(0.15)
Example #4
0
    def __init__(self, tone_data: Optional[str]) -> None:
        self.tone_data = tone_data

        if not tone_data:
            # null tone data = mute
            self.tone_bytes = bytes([0x01, 0x7f])
            return

        logger.trace(f"RTTTL Input \"{tone_data}\"")
        duration = 4
        octave = 4
        bpm = 120

        tone_data = tone_data.replace(" ", "")
        if not (match := re.match(R"(.*):(([dob]=\d+,?)*):(.*)", tone_data)):
            raise ValueError("Invalid RTTTL Data")
Example #5
0
class Ringtone:
    tone_bytes: bytes
    name: str
    tone_data: str

    NOTE_TO_HEX: Final[Dict[str, int]] = {
        "c4": 0x01,
        "c4#": 0x02,
        "d4": 0x03,
        "d4#": 0x04,
        "e4": 0x05,
        "f4": 0x06,
        "f4#": 0x07,
        "g4": 0x08,
        "g4#": 0x09,
        "a4": 0x0a,
        "a4#": 0x0b,
        "b4": 0x0c,
        "c5": 0x0d,
        "c5#": 0x0e,
        "d5": 0x0f,
        "d5#": 0x10,
        "e5": 0x11,
        "f5": 0x12,
        "f5#": 0x13,
        "g5": 0x14,
        "g5#": 0x15,
        "a5": 0x16,
        "a5#": 0x17,
        # 0x18 - 0x1f missing
        "b5": 0x20,
        "c6": 0x21,
        "c6#": 0x22,
        "d6": 0x23,
        "d6#": 0x24,
        "e6": 0x25,
        "f6": 0x26,
        "f6#": 0x27,
        "g6": 0x28,
        "g6#": 0x29,
        "a6": 0x2a,
        "a6#": 0x2b,
        "b6": 0x2c,
        "c7": 0x2d,
        "c7#": 0x2e,
        "d7": 0x2f,
        "d7#": 0x30,
        "e7": 0x31,
        "f7": 0x32,
        "f7#": 0x33,
        "g7": 0x34,
        "g7#": 0x35,
        "a7": 0x36,
        "a7#": 0x37,
        "b7": 0x38,
    }

    def __init__(self, tone_data: Optional[str]) -> None:
        self.tone_data = tone_data

        if not tone_data:
            # null tone data = mute
            self.tone_bytes = bytes([0x01, 0x7f])
            return

        logger.trace(f"RTTTL Input \"{tone_data}\"")
        duration = 4
        octave = 4
        bpm = 120

        tone_data = tone_data.replace(" ", "")
        if not (match := re.match(R"(.*):(([dob]=\d+,?)*):(.*)", tone_data)):
            raise ValueError("Invalid RTTTL Data")

        name = match.group(1)
        args = match.group(2)
        notes = match.group(4)

        for arg in args.split(","):
            parts = arg.split("=")
            if parts[0] == "d":
                new_duration = int(parts[1])
                if new_duration not in [1, 2, 4, 8, 16, 32]:
                    raise ValueError("Invalid RTTTL Data (Invalid duration)")
                duration = new_duration
            elif parts[0] == "o":
                new_octave = int(parts[1])
                if new_octave not in [4, 5, 6, 7]:
                    raise ValueError("Invalid RTTTL Data (Invalid octave)")
                octave = new_octave
            elif parts[0] == "b":
                bpm = int(parts[1])

        logger.trace(
            f"RTTTL: \"{name}\" (Note Duration: {duration}, Octave: {octave}, BPM: {bpm}) Notes: {notes}"
        )
        self.name = name

        output_bytes = bytearray()
        for match in re.findall(R"(\d?)([a-gA-GpP])(#?)(\d?)(\.?),?", notes):
            note_duration = int(match[0]) if match[0] else duration
            note_ms = int(60000 / bpm * 4 / note_duration / 16)
            if note_ms < 1:
                note_ms = 1
            if note_ms > 255:
                note_ms = 255
            output_bytes.append(note_ms)

            note = match[1].lower()
            sharp = match[2] if match[2] else ""
            note_octave = match[3] if match[3] else octave
            full_note = f"{note}{note_octave}{sharp}"
            if full_note not in Ringtone.NOTE_TO_HEX and note != "p":
                raise ValueError("Invalid RTTTL Data (Invalid note)")
            elif note == "p":
                logger.warning(
                    "RTTTL WARNING: Pauses do not work correctly on the handset"
                )
            output_bytes.append(0x7f if note ==
                                "p" else Ringtone.NOTE_TO_HEX[full_note])

        self.tone_bytes = bytes(output_bytes)
        logger.trace(f"RTTTL Output {hexdump(self.tone_bytes)}")