You've already forked TEF6686_Driver
16 bit length
This commit is contained in:
51
protocol.py
51
protocol.py
@@ -1,6 +1,7 @@
|
|||||||
import serial
|
import serial
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
def crc8(data: bytes) -> int:
|
def crc8(data: bytes) -> int:
|
||||||
crc = 0x00
|
crc = 0x00
|
||||||
@@ -12,39 +13,49 @@ def crc8(data: bytes) -> int:
|
|||||||
return crc
|
return crc
|
||||||
|
|
||||||
class I2CPCClient:
|
class I2CPCClient:
|
||||||
def __init__(self, port, baudrate=115200, timeout=1):
|
def __init__(self, port, baudrate=115200, timeout=1, wake_bauds: list | None = None):
|
||||||
|
if wake_bauds is None: wake_bauds = [921_600, 115_200, 9600, 19200, 576_000]
|
||||||
self.ser = serial.Serial(port=port, baudrate=baudrate, timeout=timeout)
|
self.ser = serial.Serial(port=port, baudrate=baudrate, timeout=timeout)
|
||||||
|
|
||||||
self.ser.write(b"~/")
|
def wake(baud: int | None):
|
||||||
self.ser.write(b"\x00"*128)
|
if baud: self.ser.baudrate = baud
|
||||||
self.ser.flush()
|
self.ser.write(b"~/")
|
||||||
self.ser.write(b"~/")
|
self.ser.write(b"\x00"*128)
|
||||||
self.ser.flush()
|
self.ser.flush()
|
||||||
|
self.ser.write(b"~/")
|
||||||
start = time.monotonic()
|
self.ser.flush()
|
||||||
while not ((d := self.ser.read_all()) and b"\xff" in d):
|
start = time.monotonic()
|
||||||
time.sleep(0.01)
|
while not ((d := self.ser.read_all()) and b"\xff" in d):
|
||||||
if ((time.monotonic()) - start) > 5: raise ConnectionError("Could not wake")
|
time.sleep(0.01)
|
||||||
|
if ((time.monotonic()) - start) > 2: return False
|
||||||
|
return True
|
||||||
|
if not wake(None):
|
||||||
|
for baud in wake_bauds:
|
||||||
|
if wake(baud):
|
||||||
|
warn(f"Initial baud was changed to {baud}, due to the device being in it")
|
||||||
|
break
|
||||||
|
else: raise ConnectionError("Could not wake")
|
||||||
|
|
||||||
def _send_packet(self, payload: bytes, crc: bool = True):
|
def _send_packet(self, payload: bytes, crc: bool = True):
|
||||||
if len(payload) > 127: raise ValueError("Payload too large")
|
if len(payload) > 0x7fff: raise ValueError("Payload too large")
|
||||||
|
|
||||||
length_byte = len(payload) | (0x80 if crc else 0x00)
|
length_data = len(payload) | (0x8000 if crc else 0)
|
||||||
data = bytes([length_byte]) + payload
|
data = struct.pack(">H", length_data) + payload
|
||||||
if crc: data += bytes([crc8(data)])
|
if crc: data += bytes([crc8(data)])
|
||||||
self.ser.write(data)
|
self.ser.write(data)
|
||||||
|
|
||||||
# Read response length
|
# Read response length
|
||||||
resp_len_raw = self.ser.read(1)
|
resp_len_raw = self.ser.read(2)
|
||||||
if not resp_len_raw: raise TimeoutError("No response")
|
if not resp_len_raw: raise TimeoutError("No response")
|
||||||
|
|
||||||
resp_len = resp_len_raw[0]
|
resp_len, *_ = struct.unpack(">H", resp_len_raw)
|
||||||
resp_has_crc = bool(resp_len & 0x80)
|
resp_has_crc = bool(resp_len & 0x8000)
|
||||||
resp_len &= 0x7F
|
resp_len &= 0x7FFF
|
||||||
|
|
||||||
total_to_read = resp_len + (1 if resp_has_crc else 0)
|
total_to_read = resp_len + (1 if resp_has_crc else 0)
|
||||||
|
|
||||||
response = self.ser.read(total_to_read)
|
response = self.ser.read(total_to_read)
|
||||||
|
if response[0] == 0xff and len(response) > 1: raise Exception(f"Error from device: {response[1]}")
|
||||||
if len(response) != total_to_read: raise TimeoutError("Incomplete response")
|
if len(response) != total_to_read: raise TimeoutError("Incomplete response")
|
||||||
|
|
||||||
if resp_has_crc:
|
if resp_has_crc:
|
||||||
@@ -52,9 +63,7 @@ class I2CPCClient:
|
|||||||
body = resp_len_raw + response[:-1]
|
body = resp_len_raw + response[:-1]
|
||||||
if crc8(body) != received_crc: raise ValueError("CRC mismatch")
|
if crc8(body) != received_crc: raise ValueError("CRC mismatch")
|
||||||
response = response[:-1]
|
response = response[:-1]
|
||||||
if response[0] == 0xff and resp_len == 1: raise Exception(f"Error from device: {response[1]}")
|
|
||||||
return response[1:]
|
return response[1:]
|
||||||
|
|
||||||
|
|
||||||
def set_clock(self, clock_hz: int):
|
def set_clock(self, clock_hz: int):
|
||||||
payload = bytes([0]) + struct.pack(">I", clock_hz)
|
payload = bytes([0]) + struct.pack(">I", clock_hz)
|
||||||
@@ -67,7 +76,7 @@ class I2CPCClient:
|
|||||||
def write_read_i2c(self, addr: int, write_data: bytes, read_len: int):
|
def write_read_i2c(self, addr: int, write_data: bytes, read_len: int):
|
||||||
payload = bytes([2, addr, len(write_data)]) + write_data + bytes([read_len])
|
payload = bytes([2, addr, len(write_data)]) + write_data + bytes([read_len])
|
||||||
return self._send_packet(payload, False)
|
return self._send_packet(payload, False)
|
||||||
|
|
||||||
def write_eeprom(self, addr: int, data: bytes):
|
def write_eeprom(self, addr: int, data: bytes):
|
||||||
payload = bytes([7]) + struct.pack(">H", addr) + data
|
payload = bytes([7]) + struct.pack(">H", addr) + data
|
||||||
return self._send_packet(payload, False)
|
return self._send_packet(payload, False)
|
||||||
|
|||||||
179
tef.py
179
tef.py
@@ -3,6 +3,7 @@ from base_tef import BaseTEF668X
|
|||||||
from typing import overload, ParamSpec, TypeVar, Concatenate, Callable
|
from typing import overload, ParamSpec, TypeVar, Concatenate, Callable
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import struct
|
import struct
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
@@ -23,18 +24,31 @@ def _command_wrapper(func: Callable[Concatenate[TEF6686, P], tuple[bytes, int |
|
|||||||
return data
|
return data
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
def pack(*values: int, signed: bool = False): return struct.pack(">" + (("h" if signed else "H")*len(values)), *values)
|
class SignedInt(int): ...
|
||||||
|
def pack(*values: int | SignedInt):
|
||||||
|
struct_string = ">"
|
||||||
|
for value in values:
|
||||||
|
if isinstance(value, SignedInt): struct_string += "h"
|
||||||
|
else: struct_string += "H"
|
||||||
|
return struct.pack(struct_string, *values)
|
||||||
|
|
||||||
class TEF6686(BaseTEF668X):
|
class TEF6686(BaseTEF668X):
|
||||||
|
class TuneTo_Mode(Enum):
|
||||||
|
Preset = 1
|
||||||
|
Search = 2
|
||||||
|
FM_AF_Update = 3
|
||||||
|
FM_Jump = 4
|
||||||
|
FM_Check = 5
|
||||||
|
End = 7
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Tune_To(self, mode: int, frequency: int | None): return b"\x20\x01\x01" + (pack(mode, frequency) if frequency is not None else pack(mode)), None, None
|
def FM_Tune_To(self, mode: TuneTo_Mode, frequency: int | None): return b"\x20\x01\x01" + (pack(mode.value, frequency) if frequency is not None else pack(mode.value)), None, None
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AM_Tune_To(self, mode: int, frequency: int | None): return b"\x21\x01\x01" + (pack(mode, frequency) if frequency is not None else pack(mode)), None, None
|
def AM_Tune_To(self, mode: TuneTo_Mode, frequency: int | None): return b"\x21\x01\x01" + (pack(mode.value, frequency) if frequency is not None else pack(mode.value)), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_Tune_Options(self, afu_bw_mode: bool = False, afu_bandwidth: int = 2360, afu_mute_time: int = 1000, afu_sample_time: int = 2000):
|
def FM_Set_Tune_Options(self, afu_bw_mode: bool = False, afu_bandwidth: int = 2360, afu_mute_time: int = 1000, afu_sample_time: int = 2000):
|
||||||
return b"\x20\x02\x01" + pack(afu_bw_mode, afu_bandwidth, afu_mute_time, afu_sample_time), None, None
|
return b"\x20\x02\x01" + pack(afu_bw_mode, afu_bandwidth, afu_mute_time, afu_sample_time), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
@overload
|
@overload
|
||||||
def FM_Set_Bandwidth(self, mode: bool = True, bandwidth: int = 2360, control_sensitivity: int = 1000, low_level_sensitivity: int = 1000, min_bandwidth: int = 560, nominal_bandwidth: int = 2360, control_attack: int = 300):
|
def FM_Set_Bandwidth(self, mode: bool = True, bandwidth: int = 2360, control_sensitivity: int = 1000, low_level_sensitivity: int = 1000, min_bandwidth: int = 560, nominal_bandwidth: int = 2360, control_attack: int = 300):
|
||||||
@@ -55,8 +69,7 @@ class TEF6686(BaseTEF668X):
|
|||||||
def AM_Set_Antenna(self, attenuation: int = 0): return b"\x21\x0c\x01" + pack(attenuation), None, None
|
def AM_Set_Antenna(self, attenuation: int = 0): return b"\x21\x0c\x01" + pack(attenuation), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AM_Set_CoChannelDet(self, mode: bool = True, restart: int = 2, sensitivity: int = 1000, count: int = 3):
|
def AM_Set_CoChannelDet(self, mode: bool = True, restart: int = 2, sensitivity: int = 1000, count: int = 3): return b"\x21\x0e\x01" + pack(mode, restart, sensitivity, count), None, None
|
||||||
return b"\x21\x0e\x01" + pack(mode, restart, sensitivity, count), None, None
|
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_MphSuppression(self, mode: bool = False): return b"\x20\x14\x01" + pack(mode), None, None
|
def FM_Set_MphSuppression(self, mode: bool = False): return b"\x20\x14\x01" + pack(mode), None, None
|
||||||
@@ -66,7 +79,7 @@ class TEF6686(BaseTEF668X):
|
|||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
@overload
|
@overload
|
||||||
def FM_Set_NoiseBlanker(self, mode: bool = True, sensitivity: int = 1000, modulation: int = 900, offset: int = 1, attack: int = 140, decay: int = 2800):
|
def FM_Set_NoiseBlanker(self, mode: bool = True, sensitivity: int = 1000, modulation: int = 900, offset: int = 1, attack: int = 140, decay: int = 2800):
|
||||||
return b"\x20\x17\x01" + pack(mode, sensitivity, 0, modulation, offset, attack, decay), None, None
|
return b"\x20\x17\x01" + pack(mode, sensitivity, 0, modulation, offset, attack, decay), None, None
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_NoiseBlanker(self, mode: bool = True, sensitivity: int = 1000): return b"\x20\x17\x01" + pack(mode, sensitivity), None, None
|
def FM_Set_NoiseBlanker(self, mode: bool = True, sensitivity: int = 1000): return b"\x20\x17\x01" + pack(mode, sensitivity), None, None
|
||||||
@@ -90,44 +103,38 @@ class TEF6686(BaseTEF668X):
|
|||||||
def FM_Set_Deemphasis(self, timeconstant: int = 500): return b"\x20\x1f\x01" + pack(timeconstant), None, None
|
def FM_Set_Deemphasis(self, timeconstant: int = 500): return b"\x20\x1f\x01" + pack(timeconstant), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_LevelStep(self, step1: int = -20, step2: int = -30, step3: int = -40, step4: int = -50, step5: int = -60, step6: int = -60, step7: int = -60):
|
def FM_Set_LevelStep(self, step1: int = -20, step2: int = -30, step3: int = -40, step4: int = -50, step5: int = -60, step6: int = -60, step7: int = -60):
|
||||||
return b"\x20\x26\x01" + pack(step1, step2, step3, step4, step5, step6, step7), None, None
|
return b"\x20\x26\x01" + pack(step1, step2, step3, step4, step5, step6, step7), None, None
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AM_Set_LevelStep(self, step1: int = -10, step2: int = -20, step3: int = -30, step4: int = -40, step5: int = -50, step6: int = -60, step7: int = -60):
|
def AM_Set_LevelStep(self, step1: int = -10, step2: int = -20, step3: int = -30, step4: int = -40, step5: int = -50, step6: int = -60, step7: int = -60):
|
||||||
return b"\x21\x26\x01" + pack(step1, step2, step3, step4, step5, step6, step7), None, None
|
return b"\x21\x26\x01" + pack(step1, step2, step3, step4, step5, step6, step7), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_LevelOffset(self, offset: int = 0): return b"\x20\x27\x01" + pack(offset, signed=True), None, None
|
def FM_Set_LevelOffset(self, offset: int = 0): return b"\x20\x27\x01" + pack(SignedInt(offset)), None, None
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AM_Set_LevelOffset(self, offset: int = 0): return b"\x21\x27\x01" + pack(offset, signed=True), None, None
|
def AM_Set_LevelOffset(self, offset: int = 0): return b"\x21\x27\x01" + pack(SignedInt(offset)), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_Softmute_Time(self, slow_attack: int = 120, slow_decay: int = 500, fast_attack: int = 20, fast_decay: int = 20):
|
def FM_Set_Softmute_Time(self, slow_attack: int = 120, slow_decay: int = 500, fast_attack: int = 20, fast_decay: int = 20):
|
||||||
return b"\x20\x28\x01" + slow_attack.to_bytes(2, "big") + slow_decay.to_bytes(2, "big") + fast_attack.to_bytes(2, "big") + fast_decay.to_bytes(2, "big"), None, None
|
return b"\x20\x28\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AM_Set_Softmute_Time(self, slow_attack: int = 120, slow_decay: int = 500, fast_attack: int = 120, fast_decay: int = 500):
|
def AM_Set_Softmute_Time(self, slow_attack: int = 120, slow_decay: int = 500, fast_attack: int = 120, fast_decay: int = 500):
|
||||||
return b"\x21\x28\x01" + slow_attack.to_bytes(2, "big") + slow_decay.to_bytes(2, "big") + fast_attack.to_bytes(2, "big") + fast_decay.to_bytes(2, "big"), None, None
|
return b"\x21\x28\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AM_Set_Softmute_Mod(self, mode: bool = False, start: int = 210, slope: int = 120, shift: int = 260): return b"\x21\x29\x01" + pack(mode, start, slope, shift), None, None
|
def AM_Set_Softmute_Mod(self, mode: bool = False, start: int = 210, slope: int = 120, shift: int = 260): return b"\x21\x29\x01" + pack(mode, start, slope, shift), None, None
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_Softmute_Level(self, mode: int = 0, start: int = 150, slope: int = 220):
|
def FM_Set_Softmute_Level(self, mode: int = 0, start: int = 150, slope: int = 220): return b"\x20\x2A\x01" + pack(mode, start, slope), None, None
|
||||||
return b"\x20\x2A\x01" + mode.to_bytes(2, "big") + start.to_bytes(2, "big") + slope.to_bytes(2, "big"), None, None
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AM_Set_Softmute_Level(self, mode: int = 0, start: int = 280, slope: int = 250):
|
def AM_Set_Softmute_Level(self, mode: int = 0, start: int = 280, slope: int = 250): return b"\x21\x2A\x01" + pack(mode, start, slope), None, None
|
||||||
return b"\x21\x2A\x01" + mode.to_bytes(2, "big") + start.to_bytes(2, "big") + slope.to_bytes(2, "big"), None, None
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_Softmute_Noise(self, mode: int = 0, start: int = 500, slope: int = 1000):
|
def FM_Set_Softmute_Noise(self, mode: int = 0, start: int = 500, slope: int = 1000): return b"\x20\x2b\x01" + pack(mode, start, slope), None, None
|
||||||
return b"\x20\x2b\x01" + mode.to_bytes(2, "big") + start.to_bytes(2, "big") + slope.to_bytes(2, "big"), None, None
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_Softmute_Mph(self, mode: int = 0, start: int = 500, slope: int = 1000):
|
def FM_Set_Softmute_Mph(self, mode: int = 0, start: int = 500, slope: int = 1000): return b"\x20\x2c\x01" + pack(mode, start, slope), None, None
|
||||||
return b"\x20\x2c\x01" + mode.to_bytes(2, "big") + start.to_bytes(2, "big") + slope.to_bytes(2, "big"), None, None
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_Softmute_Max(self, mode: bool = True, start: int = 200):
|
def FM_Set_Softmute_Max(self, mode: bool = True, start: int = 200): return b"\x20\x2c\x01" + pack(mode, start), None, None
|
||||||
return b"\x20\x2c\x01" + mode.to_bytes(2, "big") + start.to_bytes(2, "big"), None, None
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AM_Set_Softmute_Max(self, mode: bool = True, start: int = 250):
|
def AM_Set_Softmute_Max(self, mode: bool = True, start: int = 250): return b"\x21\x2c\x01" + pack(mode, start), None, None
|
||||||
return b"\x21\x2c\x01" + mode.to_bytes(2, "big") + start.to_bytes(2, "big"), None, None
|
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_Highcut_Time(self, slow_attack: int = 500, slow_decay: int = 2000, fast_attack: int = 20, fast_decay: int = 20):
|
def FM_Set_Highcut_Time(self, slow_attack: int = 500, slow_decay: int = 2000, fast_attack: int = 20, fast_decay: int = 20):
|
||||||
return b"\x20\x32\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
|
return b"\x20\x32\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
|
||||||
@@ -190,10 +197,10 @@ class TEF6686(BaseTEF668X):
|
|||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Set_Bandwidth_Options(self, modulation: int = 950): return b"\x20\x56\x01" + pack(modulation), None, None
|
def FM_Set_Bandwidth_Options(self, modulation: int = 950): return b"\x20\x56\x01" + pack(modulation), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AUDIO_Set_Volume(self, volume: int = 0): return b"\x30\x0a\x01" + pack(volume, signed=True), None, None
|
def AUDIO_Set_Volume(self, volume: int = 0): return b"\x30\x0a\x01" + pack(SignedInt(volume)), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AUDIO_Set_Mute(self, mute: bool = True): return b"\x30\x0b\x01" + pack(mute), None, None
|
def AUDIO_Set_Mute(self, mute: bool = True): return b"\x30\x0b\x01" + pack(mute), None, None
|
||||||
|
|
||||||
@@ -205,23 +212,26 @@ class TEF6686(BaseTEF668X):
|
|||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AUDIO_Set_Ana_Out(self, signal: int, mode: bool = True): return b"\x30\x15\x01" + pack(signal, mode), None, None
|
def AUDIO_Set_Ana_Out(self, signal: int, mode: bool = True): return b"\x30\x15\x01" + pack(signal, mode), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AUDIO_Set_Dig_IO(self, signal: int, mode: int = 0, format: int = 32, operation: int = 0, sample_rate: int = 4410): return b"\x30\x16\x01" + pack(signal, mode, format, operation, sample_rate), None, None
|
def AUDIO_Set_Dig_IO(self, signal: int, mode: int = 0, format: int = 32, operation: int = 0, sample_rate: int = 4410): return b"\x30\x16\x01" + pack(signal, mode, format, operation, sample_rate), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AUDIO_Set_Input_Scaler(self, source: int, gain: int = 0): return b"\x30\x17\x01" + pack(source) + pack(gain, signed=True), None, None
|
def AUDIO_Set_Input_Scaler(self, source: int, gain: int = 0): return b"\x30\x17\x01" + pack(source, SignedInt(gain)), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def AUDIO_Set_WaveGen(self, mode: int = 0, offset: int = 0, amplitude1: int = -200, frequency1: int = 400, amplitude2: int = -200, frequency2: int = 1000):
|
def AUDIO_Set_WaveGen(self, mode: int = 0, offset: int = 0, amplitude1: int = -200, frequency1: int = 400, amplitude2: int = -200, frequency2: int = 1000):
|
||||||
return b"\x30\x18\x01" + pack(mode) + offset.to_bytes(2, "big") + pack(amplitude1, signed=True) + pack(frequency1) + pack(amplitude2, signed=True) + pack(frequency2), None, None
|
# Not sure if offset is signed or not
|
||||||
|
return b"\x30\x18\x01" + pack(mode, offset, SignedInt(amplitude1), frequency1, SignedInt(amplitude2), frequency2), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def APPL_Set_OperationMode(self, mode: bool = True): return b"\x40\x01\x01" + pack(mode), None, None
|
def APPL_Set_OperationMode(self, mode: bool = True): return b"\x40\x01\x01" + pack(mode), None, None
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def APPL_Set_GPIO(self, pin: int, module: int, feature: int): return b"\x40\x03\x01" + pack(pin, module, feature), None, None
|
def APPL_Set_GPIO(self, pin: int, module: int, feature: int): return b"\x40\x03\x01" + pack(pin, module, feature), None, None
|
||||||
|
|
||||||
|
# Activate and refrence clock are in the base
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_quality_data(data) -> None | tuple[int, int, int, int, int, int, int]:
|
def _get_quality_data(data) -> None | tuple[int, int, int, int, int, int, int]:
|
||||||
if data[0] != 0: return None
|
if data[0] != 0: return None
|
||||||
@@ -272,7 +282,7 @@ class TEF6686(BaseTEF668X):
|
|||||||
if data[0] != 0: return None
|
if data[0] != 0: return None
|
||||||
return struct.unpack(">HH", data[1:])
|
return struct.unpack(">HH", data[1:])
|
||||||
return b"\x20\x84\x01", 2*2, proc
|
return b"\x20\x84\x01", 2*2, proc
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Get_Signal_Status(self):
|
def FM_Get_Signal_Status(self):
|
||||||
"""
|
"""
|
||||||
@@ -283,14 +293,16 @@ class TEF6686(BaseTEF668X):
|
|||||||
value, *_ = struct.unpack(">H", data[1:])
|
value, *_ = struct.unpack(">H", data[1:])
|
||||||
return (value & (1 << 15)) != 0, (value & (1 << 14)) != 0
|
return (value & (1 << 15)) != 0, (value & (1 << 14)) != 0
|
||||||
return b"\x20\x85\x01", 2, proc
|
return b"\x20\x85\x01", 2, proc
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def FM_Get_Processing_Status(self):
|
def FM_Get_Processing_Status(self):
|
||||||
def proc(data):
|
def proc(data):
|
||||||
if data[0] != 0: return None
|
if data[0] != 0: return None
|
||||||
return struct.unpack(">HHHH", data[1:])
|
return struct.unpack(">HHHH", data[1:])
|
||||||
return b"\x20\x86\x01", 2*4, proc
|
return b"\x20\x86\x01", 2*4, proc
|
||||||
|
|
||||||
|
# Get_Operation_Status is defined in the base
|
||||||
|
|
||||||
@_command_wrapper
|
@_command_wrapper
|
||||||
def APPL_Get_GPIO_Status(self):
|
def APPL_Get_GPIO_Status(self):
|
||||||
def proc(data):
|
def proc(data):
|
||||||
@@ -303,4 +315,95 @@ class TEF6686(BaseTEF668X):
|
|||||||
def proc(data):
|
def proc(data):
|
||||||
if data[0] != 0: return None
|
if data[0] != 0: return None
|
||||||
return struct.unpack(">HHH", data[1:])
|
return struct.unpack(">HHH", data[1:])
|
||||||
return b"\x40\x82\x01", 2*3, proc
|
return b"\x40\x82\x01", 2*3, proc
|
||||||
|
|
||||||
|
@_command_wrapper
|
||||||
|
def APPL_Get_LastWrite(self):
|
||||||
|
def proc(data):
|
||||||
|
if data[0] != 0: return None
|
||||||
|
return struct.unpack(">HHHHHHH", data[1:])
|
||||||
|
return b"\x40\x83\x01", 2*7, proc
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def _7_command_wrapper(func: Callable[Concatenate[TEF6687, P],tuple[bytes, int | None, None],],) -> Callable[Concatenate[TEF6687, P], bytes]: ...
|
||||||
|
@overload
|
||||||
|
def _7_command_wrapper(func: Callable[Concatenate[TEF6687, P],tuple[bytes, int | None, Callable[[bytes], T]],],) -> Callable[Concatenate[TEF6687, P], T]: ...
|
||||||
|
|
||||||
|
def _7_command_wrapper(func: Callable[Concatenate[TEF6687, P], tuple[bytes, int | None, Callable[[bytes], T] | None], ]) -> Callable[Concatenate[TEF6687, P], bytes | T]:
|
||||||
|
@wraps(func)
|
||||||
|
def inner(self: TEF6687, *args: P.args, **kwargs: P.kwargs) -> bytes | T:
|
||||||
|
data, read_bytes, out_parser = func(self, *args, **kwargs)
|
||||||
|
|
||||||
|
if read_bytes: data = self.p.write_read_i2c(self.address, data, read_bytes)
|
||||||
|
else: data = self.p.write_i2c(self.address, data)
|
||||||
|
if out_parser: return out_parser(data)
|
||||||
|
return data
|
||||||
|
return inner
|
||||||
|
|
||||||
|
class TEF6687(TEF6686):
|
||||||
|
@_7_command_wrapper
|
||||||
|
def FM_Set_StereoImprovement(self, mode: bool = False):
|
||||||
|
return b"\x20\x20\x01" + pack(mode), None, None
|
||||||
|
|
||||||
|
@_7_command_wrapper
|
||||||
|
def FM_Set_StBandBlend_Time(self, attack: int = 50, decay: int = 50):
|
||||||
|
return b"\x20\x5a\x01" + pack(attack, decay), None, None
|
||||||
|
@_7_command_wrapper
|
||||||
|
def FM_Set_StBandBlend_Gain(self, band1: int = 1000, band2: int = 1000, band3: int = 1000, band4: int = 1000):
|
||||||
|
return b"\x20\x5b\x01" + pack(band1, band2, band3, band4), None, None
|
||||||
|
@_7_command_wrapper
|
||||||
|
def FM_Set_StBandBlend_Bias(self, band1: int = -75, band2: int = -35, band3: int = -25, band4: int = -25):
|
||||||
|
return b"\x20\x5c\x01" + pack(SignedInt(band1), SignedInt(band2), SignedInt(band3), SignedInt(band4)), None, None
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def _8_command_wrapper(func: Callable[Concatenate[TEF6688, P],tuple[bytes, int | None, None],],) -> Callable[Concatenate[TEF6688, P], bytes]: ...
|
||||||
|
@overload
|
||||||
|
def _8_command_wrapper(func: Callable[Concatenate[TEF6688, P],tuple[bytes, int | None, Callable[[bytes], T]],],) -> Callable[Concatenate[TEF6688, P], T]: ...
|
||||||
|
|
||||||
|
def _8_command_wrapper(func: Callable[Concatenate[TEF6688, P], tuple[bytes, int | None, Callable[[bytes], T] | None], ]) -> Callable[Concatenate[TEF6688, P], bytes | T]:
|
||||||
|
@wraps(func)
|
||||||
|
def inner(self: TEF6688, *args: P.args, **kwargs: P.kwargs) -> bytes | T:
|
||||||
|
data, read_bytes, out_parser = func(self, *args, **kwargs)
|
||||||
|
|
||||||
|
if read_bytes: data = self.p.write_read_i2c(self.address, data, read_bytes)
|
||||||
|
else: data = self.p.write_i2c(self.address, data)
|
||||||
|
if out_parser: return out_parser(data)
|
||||||
|
return data
|
||||||
|
return inner
|
||||||
|
|
||||||
|
class TEF6688(TEF6686):
|
||||||
|
@_8_command_wrapper
|
||||||
|
def FM_Set_DigitalRadio(self, mode: bool = False):
|
||||||
|
return b"\x20\x1e\x01" + pack(mode), None, None
|
||||||
|
@_8_command_wrapper
|
||||||
|
def AM_Set_DigitalRadio(self, mode: bool = False):
|
||||||
|
return b"\x21\x1e\x01" + pack(mode), None, None
|
||||||
|
|
||||||
|
@_8_command_wrapper
|
||||||
|
def FM_Set_DR_Blend(self, mode: int = 0, in_time: int = 50, out_time: int = 50, gain: int = 0):
|
||||||
|
return b"\x20\x53\x01" + pack(mode, in_time, out_time, SignedInt(gain)), None, None
|
||||||
|
@_8_command_wrapper
|
||||||
|
def AM_Set_DR_Blend(self, mode: int = 0, in_time: int = 50, out_time: int = 50, gain: int = 0):
|
||||||
|
return b"\x21\x53\x01" + pack(mode, in_time, out_time, SignedInt(gain)), None, None
|
||||||
|
|
||||||
|
@_8_command_wrapper
|
||||||
|
def FM_Set_DR_Options(self, samplerate: int = 0, mode: bool = False, format: int = 4112):
|
||||||
|
"""Mode = false, means open drain data pin mode"""
|
||||||
|
actual_mode = (34 << 8) | (2 if mode else 4)
|
||||||
|
return b"\x20\x54\x01" + pack(samplerate, actual_mode, format), None, None
|
||||||
|
|
||||||
|
@_8_command_wrapper
|
||||||
|
def FM_Get_Interface_Status(self):
|
||||||
|
def parse(data):
|
||||||
|
if data[0] != 0: return None
|
||||||
|
return struct.unpack(">H", data[1:])
|
||||||
|
return b"\x20\x87\x01", 2, parse
|
||||||
|
@_8_command_wrapper
|
||||||
|
def AM_Get_Interface_Status(self):
|
||||||
|
def parse(data):
|
||||||
|
if data[0] != 0: return None
|
||||||
|
return struct.unpack(">H", data[1:])
|
||||||
|
return b"\x21\x87\x01", 2, parse
|
||||||
|
|
||||||
|
# This is a combination of all of the features of those all
|
||||||
|
class TEF6689(TEF6687, TEF6688): ...
|
||||||
7
test.py
7
test.py
@@ -5,7 +5,7 @@ import time
|
|||||||
p = I2CPCClient("COM17")
|
p = I2CPCClient("COM17")
|
||||||
with TEF6686(p) as tef:
|
with TEF6686(p) as tef:
|
||||||
tef.init(clock=12000000)
|
tef.init(clock=12000000)
|
||||||
tef.AM_Tune_To(1, 225)
|
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, 9500)
|
||||||
tef.AUDIO_Set_Mute(False)
|
tef.AUDIO_Set_Mute(False)
|
||||||
tef.AUDIO_Set_Volume(70)
|
tef.AUDIO_Set_Volume(70)
|
||||||
tef.FM_Set_MphSuppression(True)
|
tef.FM_Set_MphSuppression(True)
|
||||||
@@ -14,7 +14,8 @@ with TEF6686(p) as tef:
|
|||||||
tef.FM_Set_Bandwidth(True)
|
tef.FM_Set_Bandwidth(True)
|
||||||
time.sleep(0.032)
|
time.sleep(0.032)
|
||||||
while True:
|
while True:
|
||||||
status, level, usn, wam, offset, bandwidth, modulation = tef.FM_Get_Quality_Data()
|
res = tef.FM_Get_Quality_Data()
|
||||||
if not status or not level or not usn or not wam or not offset or not bandwidth or not modulation: continue
|
if not res: continue
|
||||||
|
status, level, usn, wam, offset, bandwidth, modulation = res
|
||||||
print(f"{level / 10} dbμV | {usn / 10}% USN | {wam / 10}% WAM | {offset / 10} kHz offset | {bandwidth / 10} kHz bandwidth | {modulation / 10}% modulation")
|
print(f"{level / 10} dbμV | {usn / 10}% USN | {wam / 10}% WAM | {offset / 10} kHz offset | {bandwidth / 10} kHz bandwidth | {modulation / 10}% modulation")
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
24
xrd.py
24
xrd.py
@@ -15,7 +15,7 @@ from functools import wraps
|
|||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
INITIAL_FREQ = 9500
|
INITIAL_FREQ = 9500
|
||||||
INITIAL_EQ = False
|
INITIAL_EQ = True
|
||||||
INITIAL_IMS = True
|
INITIAL_IMS = True
|
||||||
|
|
||||||
HOST = os.getenv("HOST") or '0.0.0.0'
|
HOST = os.getenv("HOST") or '0.0.0.0'
|
||||||
@@ -43,7 +43,7 @@ def init_tef():
|
|||||||
tef.init(clock=CLOCK)
|
tef.init(clock=CLOCK)
|
||||||
tef.AUDIO_Set_Mute(False)
|
tef.AUDIO_Set_Mute(False)
|
||||||
tef.AUDIO_Set_Volume(30)
|
tef.AUDIO_Set_Volume(30)
|
||||||
tef.FM_Tune_To(1, INITIAL_FREQ)
|
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, INITIAL_FREQ)
|
||||||
tef.FM_Set_RDS(1)
|
tef.FM_Set_RDS(1)
|
||||||
tef.FM_Set_ChannelEqualizer(INITIAL_EQ)
|
tef.FM_Set_ChannelEqualizer(INITIAL_EQ)
|
||||||
tef.FM_Set_MphSuppression(INITIAL_IMS)
|
tef.FM_Set_MphSuppression(INITIAL_IMS)
|
||||||
@@ -55,11 +55,15 @@ def authenticate(conn: socket.socket):
|
|||||||
salt = "".join(secrets.choice(string.ascii_lowercase) for _ in range(SALT_LENGTH))
|
salt = "".join(secrets.choice(string.ascii_lowercase) for _ in range(SALT_LENGTH))
|
||||||
conn.sendall(salt.encode() + b"\n")
|
conn.sendall(salt.encode() + b"\n")
|
||||||
expected_hash = hashlib.sha1((salt + PASSWORD).encode()).hexdigest().encode()
|
expected_hash = hashlib.sha1((salt + PASSWORD).encode()).hexdigest().encode()
|
||||||
|
start = time.monotonic()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
data = conn.recv(1024)
|
try:
|
||||||
if not data: return False
|
data = conn.recv(1024)
|
||||||
if data.strip() == expected_hash.strip(): return True
|
if not data: return False
|
||||||
|
if data.strip() == expected_hash.strip(): return True
|
||||||
|
except BlockingIOError:
|
||||||
|
if (time.monotonic() - start) > 3: conn.close() # Close connection if they can't fucking bruteforce the password in 3 seconds
|
||||||
|
|
||||||
def process_command(tef: TEF6686, data: bytes, state: dict, conn: socket.socket):
|
def process_command(tef: TEF6686, data: bytes, state: dict, conn: socket.socket):
|
||||||
out = b""
|
out = b""
|
||||||
@@ -67,7 +71,7 @@ def process_command(tef: TEF6686, data: bytes, state: dict, conn: socket.socket)
|
|||||||
if cmd.startswith(b"T"):
|
if cmd.startswith(b"T"):
|
||||||
freq = int(cmd.decode().removeprefix("T").strip()) // 10
|
freq = int(cmd.decode().removeprefix("T").strip()) // 10
|
||||||
if freq < 6500 or freq > 10800: continue
|
if freq < 6500 or freq > 10800: continue
|
||||||
tef.FM_Tune_To(1, freq)
|
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, freq)
|
||||||
if not freq_allowed(freq): tef.AUDIO_Set_Mute(True)
|
if not freq_allowed(freq): tef.AUDIO_Set_Mute(True)
|
||||||
else: tef.AUDIO_Set_Mute(False)
|
else: tef.AUDIO_Set_Mute(False)
|
||||||
state['last_tune'] = freq
|
state['last_tune'] = freq
|
||||||
@@ -92,7 +96,7 @@ def process_command(tef: TEF6686, data: bytes, state: dict, conn: socket.socket)
|
|||||||
elif cmd.startswith(b"x"):
|
elif cmd.startswith(b"x"):
|
||||||
out += b"OK\n"
|
out += b"OK\n"
|
||||||
tef.APPL_Set_OperationMode(False) # Enable
|
tef.APPL_Set_OperationMode(False) # Enable
|
||||||
tef.FM_Tune_To(1, state["last_tune"])
|
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, state["last_tune"])
|
||||||
if not freq_allowed(state["last_tune"]): tef.AUDIO_Set_Mute(True)
|
if not freq_allowed(state["last_tune"]): tef.AUDIO_Set_Mute(True)
|
||||||
else: tef.AUDIO_Set_Mute(False)
|
else: tef.AUDIO_Set_Mute(False)
|
||||||
elif cmd.startswith(b"X"):
|
elif cmd.startswith(b"X"):
|
||||||
@@ -123,7 +127,7 @@ def process_command(tef: TEF6686, data: bytes, state: dict, conn: socket.socket)
|
|||||||
if not start: conn.sendall(b", ") # Prevent trailing comma, because the FM-DX-Webserver spectrum plugin treats us as actual firmware and throws as harder api, without it we're treated as a module
|
if not start: conn.sendall(b", ") # Prevent trailing comma, because the FM-DX-Webserver spectrum plugin treats us as actual firmware and throws as harder api, without it we're treated as a module
|
||||||
start = False
|
start = False
|
||||||
|
|
||||||
tef.FM_Tune_To(2, freq) # Auto mutes, less commands sent
|
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Search, freq) # Auto mutes, less commands sent
|
||||||
time.sleep(0.0067) # sick seven
|
time.sleep(0.0067) # sick seven
|
||||||
if not freq_allowed(freq):
|
if not freq_allowed(freq):
|
||||||
conn.sendall(f"{freq * 10} = 11.25".encode())
|
conn.sendall(f"{freq * 10} = 11.25".encode())
|
||||||
@@ -133,7 +137,7 @@ def process_command(tef: TEF6686, data: bytes, state: dict, conn: socket.socket)
|
|||||||
conn.sendall(str(freq * 10).encode() + b" = " + str((level / 10) + 11.25).encode())
|
conn.sendall(str(freq * 10).encode() + b" = " + str((level / 10) + 11.25).encode())
|
||||||
conn.sendall(b"\n")
|
conn.sendall(b"\n")
|
||||||
|
|
||||||
tef.FM_Tune_To(1, state["last_tune"])
|
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, state["last_tune"])
|
||||||
if not freq_allowed(state["last_tune"]): tef.AUDIO_Set_Mute(True)
|
if not freq_allowed(state["last_tune"]): tef.AUDIO_Set_Mute(True)
|
||||||
else: tef.AUDIO_Set_Mute(False)
|
else: tef.AUDIO_Set_Mute(False)
|
||||||
tef.FM_Set_Bandwidth((state["bw"] == 0), 2360 if (state["bw"] == 0) else (state["bw"] // 100))
|
tef.FM_Set_Bandwidth((state["bw"] == 0), 2360 if (state["bw"] == 0) else (state["bw"] // 100))
|
||||||
@@ -248,6 +252,7 @@ def run_server():
|
|||||||
with conn:
|
with conn:
|
||||||
reset_periodic()
|
reset_periodic()
|
||||||
print(f"Connected by {addr}")
|
print(f"Connected by {addr}")
|
||||||
|
conn.setblocking(False)
|
||||||
if not authenticate(conn):
|
if not authenticate(conn):
|
||||||
print("Authentication failed.")
|
print("Authentication failed.")
|
||||||
continue
|
continue
|
||||||
@@ -259,7 +264,6 @@ def run_server():
|
|||||||
conn.sendall(f"D{state['deemp']}\n".encode())
|
conn.sendall(f"D{state['deemp']}\n".encode())
|
||||||
conn.sendall(f"W{state['bw']}\n".encode())
|
conn.sendall(f"W{state['bw']}\n".encode())
|
||||||
conn.sendall(f"M0\n".encode())
|
conn.sendall(f"M0\n".encode())
|
||||||
conn.setblocking(False)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user