1
0

16 bit length

This commit is contained in:
2026-02-25 16:36:57 +01:00
parent 7ddacd8545
commit e606802541
4 changed files with 189 additions and 72 deletions

View File

@@ -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,10 +63,8 @@ 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)
return self._send_packet(payload) return self._send_packet(payload)

151
tef.py
View File

@@ -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,13 +24,26 @@ 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):
@@ -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
@@ -97,36 +110,30 @@ class TEF6686(BaseTEF668X):
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):
@@ -192,7 +199,7 @@ class TEF6686(BaseTEF668X):
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
@@ -210,11 +217,12 @@ class TEF6686(BaseTEF668X):
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
@@ -222,6 +230,8 @@ class TEF6686(BaseTEF668X):
@_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
@@ -291,6 +301,8 @@ class TEF6686(BaseTEF668X):
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):
@@ -304,3 +316,94 @@ class TEF6686(BaseTEF668X):
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): ...

View File

@@ -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
View File

@@ -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: