some changes
This commit is contained in:
+395
@@ -0,0 +1,395 @@
|
|||||||
|
from driver.tef import TEF6686
|
||||||
|
from driver.protocol import I2CPCClient # cpcc? so close (cccp?), hammer and sicle?
|
||||||
|
import os
|
||||||
|
from multiprocessing import Queue, Process, freeze_support
|
||||||
|
from queue import Empty
|
||||||
|
import libtimer
|
||||||
|
import asyncio
|
||||||
|
import websockets
|
||||||
|
from websockets import Headers, Response
|
||||||
|
import json
|
||||||
|
from cliserver_rdsDecoder import decodeRds
|
||||||
|
|
||||||
|
NAME = "CLI Server"
|
||||||
|
DESCRIPTION = ""
|
||||||
|
|
||||||
|
INITIAL_FREQ = 95.0
|
||||||
|
|
||||||
|
LATITUDE = 0
|
||||||
|
LONGITUDE = 0
|
||||||
|
|
||||||
|
PRESET0 = 87.5
|
||||||
|
PRESET1 = 87.5
|
||||||
|
PRESET2 = 87.5
|
||||||
|
PRESET3 = 87.5
|
||||||
|
|
||||||
|
# Mint - 1
|
||||||
|
# Cappucino - 2
|
||||||
|
# Nature - 3
|
||||||
|
# Ocean - 4
|
||||||
|
# Terminal - 5
|
||||||
|
# Nightlife - 6
|
||||||
|
# Blurple - 7
|
||||||
|
# Construction - 8
|
||||||
|
# Amoled - 9
|
||||||
|
# Dark Red - 10
|
||||||
|
# Ocean Blue - 11
|
||||||
|
DEFAULT_THEME = 1
|
||||||
|
|
||||||
|
BG_IMAGE = None
|
||||||
|
|
||||||
|
RBDS = False
|
||||||
|
RDS_TIMEOUT = 0
|
||||||
|
|
||||||
|
# "af":[99800,102600,90300],
|
||||||
|
# "txInfo":{"tx":"Radio Plus","pol":"V","erp":0.2,"city":"Pila","itu":"POL","dist":"3","azi":"238","id":6400902,"pi":"321C","reg":false,"otherMatches":[],"score":1},
|
||||||
|
|
||||||
|
def serialpart(inq: Queue, outq: Queue, rds: Queue):
|
||||||
|
p = I2CPCClient(os.getenv("PORT", "COM17"), int(os.getenv("BAUD", 115200)))
|
||||||
|
tef = TEF6686(p)
|
||||||
|
tef.init(clock=12_000_000)
|
||||||
|
tef.FM_Set_RDS(1)
|
||||||
|
tef.AUDIO_Set_Mute(False)
|
||||||
|
tef.FM_Set_ChannelEqualizer()
|
||||||
|
tef.FM_Set_MphSuppression()
|
||||||
|
tef.FM_Set_Stereo_Max(False)
|
||||||
|
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, int(INITIAL_FREQ * 100))
|
||||||
|
|
||||||
|
signal_timer = libtimer.Timer()
|
||||||
|
rds_timer = libtimer.Timer()
|
||||||
|
data_timer = libtimer.Timer()
|
||||||
|
|
||||||
|
rds_dropout_time = libtimer.Timer()
|
||||||
|
|
||||||
|
fm = True
|
||||||
|
|
||||||
|
wsdata = {
|
||||||
|
"pi": "?",
|
||||||
|
"freq": f"{INITIAL_FREQ:.3f}",
|
||||||
|
"sig": 11.25, # dBf
|
||||||
|
"sigRaw": "", # Raw signal from xrd proto
|
||||||
|
"sigTop": -1000,
|
||||||
|
"bw": "",
|
||||||
|
"st": False,
|
||||||
|
"stForced": False, # its forced for mono, lol
|
||||||
|
"rds": False,
|
||||||
|
"ps": "".ljust(8),
|
||||||
|
"tp": 0,
|
||||||
|
"ta": 0,
|
||||||
|
"ms": -1,
|
||||||
|
"pty": 0,
|
||||||
|
"ecc": None,
|
||||||
|
"af": [],
|
||||||
|
"rt0": "".ljust(64),
|
||||||
|
"rt1": "".ljust(64),
|
||||||
|
"rt_flag": 0,
|
||||||
|
"ims": 0,
|
||||||
|
"eq": 0,
|
||||||
|
"agc": "0", # static
|
||||||
|
"ant": "1", # static
|
||||||
|
"txInfo": {},
|
||||||
|
"country_name": "",
|
||||||
|
"country_iso": "UN",
|
||||||
|
"users": 0,
|
||||||
|
"ps_errors": "0,0,0,0,0,0,0,0",
|
||||||
|
"rt1_errors":"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
|
||||||
|
"rt0_errors":"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||||
|
}
|
||||||
|
|
||||||
|
def resetRds():
|
||||||
|
wsdata["rds"] = False
|
||||||
|
wsdata["pi"] = "?"
|
||||||
|
wsdata["ps"] = "".ljust(8)
|
||||||
|
wsdata["tp"] = 0
|
||||||
|
wsdata["ta"] = 0
|
||||||
|
wsdata["ms"] = -1
|
||||||
|
wsdata["pty"] = 0
|
||||||
|
wsdata["ecc"] = None
|
||||||
|
wsdata["af"] = []
|
||||||
|
wsdata["rt0"] = "".ljust(64)
|
||||||
|
wsdata["rt1"] = "".ljust(64)
|
||||||
|
wsdata["rt_flag"] = 0
|
||||||
|
wsdata["country_name"] = ""
|
||||||
|
wsdata["country_iso"] = "UN"
|
||||||
|
wsdata["ps_errors"] = "0,0,0,0,0,0,0,0"
|
||||||
|
wsdata["rt0_errors"] = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||||
|
wsdata["rt1_errors"] = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||||
|
rds.put("G:\r\nRESET-------\r\n\r\n")
|
||||||
|
|
||||||
|
while not p.ser.closed:
|
||||||
|
try:
|
||||||
|
msg = str(inq.get(False))
|
||||||
|
if msg == "!x":
|
||||||
|
tef.close()
|
||||||
|
break
|
||||||
|
elif msg == ".": raise Empty
|
||||||
|
elif msg == "+":
|
||||||
|
wsdata["users"] += 1
|
||||||
|
raise Empty
|
||||||
|
elif msg == "-":
|
||||||
|
wsdata["users"] -= 1
|
||||||
|
raise Empty
|
||||||
|
out = ""
|
||||||
|
if msg.startswith("M"):
|
||||||
|
mod = int(msg.removeprefix("M").strip(), 2)
|
||||||
|
if mod: fm = False
|
||||||
|
else: fm = True
|
||||||
|
out += f"M{mod}\n"
|
||||||
|
elif msg.startswith("T"):
|
||||||
|
freq = int(msg.removeprefix("T").strip()) // 10
|
||||||
|
if fm:
|
||||||
|
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, freq)
|
||||||
|
wsdata["freq"] = f"{(freq / 100):.3f}"
|
||||||
|
else:
|
||||||
|
tef.AM_Tune_To(TEF6686.TuneTo_Mode.Preset, freq)
|
||||||
|
wsdata["freq"] = f"{(freq / 1000):.3f}"
|
||||||
|
out += f"T{freq*10}\n"
|
||||||
|
wsdata["sigTop"] = -1000
|
||||||
|
resetRds()
|
||||||
|
elif msg.startswith("G"):
|
||||||
|
eqims = int(msg.removeprefix("G").strip(), 2)
|
||||||
|
tef.FM_Set_ChannelEqualizer((eqims & 1) == 1)
|
||||||
|
tef.FM_Set_MphSuppression((eqims & 2) == 2)
|
||||||
|
out += f"G{bin(eqims).removeprefix('0b').zfill(2)}\n"
|
||||||
|
wsdata["ims"] = (eqims & 1)
|
||||||
|
wsdata["eq"] = (eqims & 2) >> 1
|
||||||
|
elif msg.startswith("B"):
|
||||||
|
wsdata["stForced"] = bool(int(msg.removeprefix("B").strip(), 2))
|
||||||
|
tef.FM_Set_Stereo_Min(2 if wsdata["stForced"] else 0)
|
||||||
|
out += f"B{int(wsdata["stForced"])}\n"
|
||||||
|
elif msg.startswith("D"):
|
||||||
|
deemp = int(msg.removeprefix("D").strip())
|
||||||
|
dtime = 500 if deemp == 0 else (750 if deemp == 1 else 0)
|
||||||
|
tef.FM_Set_Deemphasis(dtime)
|
||||||
|
out += f"D{deemp}\n"
|
||||||
|
elif msg.startswith("W"):
|
||||||
|
bw = int(msg.removeprefix("W").strip())
|
||||||
|
auto = (bw == 0)
|
||||||
|
tef.FM_Set_Bandwidth(auto, 2360 if auto else (bw // 100))
|
||||||
|
out += f"W{bw}\n"
|
||||||
|
if out.strip(): outq.put(out)
|
||||||
|
except Empty:
|
||||||
|
if (RDS_TIMEOUT and RDS_TIMEOUT != 0) and rds_dropout_time.get_time() > RDS_TIMEOUT: resetRds()
|
||||||
|
|
||||||
|
if signal_timer.get_time() > 0.1:
|
||||||
|
signal_timer.reset()
|
||||||
|
res = None
|
||||||
|
if fm: res = tef.FM_Get_Quality_Data()
|
||||||
|
else: res = tef.AM_Get_Quality_Data()
|
||||||
|
|
||||||
|
if res:
|
||||||
|
if fm:
|
||||||
|
stereo_pilot, _ = d if (d := tef.FM_Get_Signal_Status()) else (None, None)
|
||||||
|
else: stereo_pilot = False
|
||||||
|
_, level, usn, wam, _, bandwidth, *_ = res
|
||||||
|
|
||||||
|
level /= 10
|
||||||
|
bandwidth /= 10
|
||||||
|
|
||||||
|
data = "S"
|
||||||
|
if wsdata["stForced"]: data += "M"
|
||||||
|
elif stereo_pilot: data += "s"
|
||||||
|
else: data += "m"
|
||||||
|
|
||||||
|
wsdata["st"] = stereo_pilot
|
||||||
|
|
||||||
|
level += 11.25 # to dBf from dBμV
|
||||||
|
|
||||||
|
data += f"{level},{wam//10},{usn//10},{bandwidth}"
|
||||||
|
wsdata["bw"] = str(bandwidth)
|
||||||
|
wsdata["sigRaw"] = data
|
||||||
|
wsdata["sig"] = level
|
||||||
|
wsdata["sigTop"] = wsdata["sigTop"] if level < wsdata["sigTop"] else level
|
||||||
|
if rds_timer.get_time() > 0.086 and fm:
|
||||||
|
rds_timer.reset()
|
||||||
|
|
||||||
|
res = tef.FM_Get_RDS_Data__decoder()
|
||||||
|
if res is None: continue
|
||||||
|
status, A, B, C, D, dec_error = res
|
||||||
|
dec_error >>= 8
|
||||||
|
|
||||||
|
if (status & (1 << 9) == 0) or (status & (1 << 15) == 0): continue
|
||||||
|
|
||||||
|
gdata = "G:\r\n"
|
||||||
|
a_error = dec_error >> 6
|
||||||
|
b_error = (dec_error >> 4) & 0b11
|
||||||
|
c_error = (dec_error >> 2) & 0b11
|
||||||
|
d_error = dec_error & 0b11
|
||||||
|
|
||||||
|
wsdata["rds"] = True
|
||||||
|
pi_error = decodeRds(wsdata, status, A, B, C, D, dec_error)
|
||||||
|
|
||||||
|
def threshold(group: str, error: int): return group if (error < 3) else "----"
|
||||||
|
|
||||||
|
if (status & (1 << 13) == 0):
|
||||||
|
if (status & (1 << 12) == 0):
|
||||||
|
# Type A, PI in A
|
||||||
|
gdata += threshold(f"{A:04X}", a_error)
|
||||||
|
gdata += threshold(f"{B:04X}", b_error)
|
||||||
|
gdata += threshold(f"{C:04X}", c_error)
|
||||||
|
elif (status >> 12) & 1:
|
||||||
|
# Type B, PI in A and C
|
||||||
|
pi = wsdata["pi"]
|
||||||
|
gdata += threshold(pi, pi_error)
|
||||||
|
gdata += threshold(f"{B:04X}", b_error)
|
||||||
|
gdata += threshold(pi, pi_error)
|
||||||
|
gdata += threshold(f"{D:04X}", d_error)
|
||||||
|
gdata += "\r\n\r\n"
|
||||||
|
|
||||||
|
rds.put(gdata)
|
||||||
|
rds_dropout_time.reset()
|
||||||
|
|
||||||
|
if data_timer.get_time() > 0.1:
|
||||||
|
data_timer.reset()
|
||||||
|
outq.put(json.dumps(wsdata))
|
||||||
|
|
||||||
|
inq = Queue()
|
||||||
|
outq = Queue()
|
||||||
|
rds = Queue()
|
||||||
|
|
||||||
|
wsc: list[websockets.ServerConnection] = []
|
||||||
|
rds_wsc: list[websockets.ServerConnection] = []
|
||||||
|
|
||||||
|
async def worker():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
msg = await asyncio.to_thread(outq.get, False)
|
||||||
|
if msg == "!x": break
|
||||||
|
for websocket in wsc[:]:
|
||||||
|
try: await websocket.send(msg)
|
||||||
|
except websockets.exceptions.ConnectionClosed: pass
|
||||||
|
except Empty: await asyncio.sleep(0.015)
|
||||||
|
async def rds_worker():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
msg = await asyncio.to_thread(rds.get, False)
|
||||||
|
if msg == "!x": break
|
||||||
|
for websocket in rds_wsc[:]:
|
||||||
|
try: await websocket.send(msg)
|
||||||
|
except websockets.exceptions.ConnectionClosed: pass
|
||||||
|
except Empty: await asyncio.sleep(0.015)
|
||||||
|
|
||||||
|
async def handle_text(websocket: websockets.ServerConnection):
|
||||||
|
wsc.append(websocket)
|
||||||
|
await asyncio.to_thread(inq.put, "+")
|
||||||
|
try:
|
||||||
|
async for message in websocket:
|
||||||
|
if message not in ("+", "-"):
|
||||||
|
print(message)
|
||||||
|
await asyncio.to_thread(inq.put, message)
|
||||||
|
await websocket.recv() # For loop exits normally if closed normally. This should raise an ConnectionClosed if the loop exited, because the connection was closed
|
||||||
|
except websockets.exceptions.ConnectionClosed:
|
||||||
|
wsc.remove(websocket)
|
||||||
|
await asyncio.to_thread(inq.put, "-")
|
||||||
|
print("Client disconnected")
|
||||||
|
|
||||||
|
async def handle_rds(websocket: websockets.ServerConnection):
|
||||||
|
rds_wsc.append(websocket)
|
||||||
|
await websocket.wait_closed()
|
||||||
|
rds_wsc.remove(websocket)
|
||||||
|
|
||||||
|
async def handler(websocket: websockets.ServerConnection):
|
||||||
|
if not websocket.request:
|
||||||
|
await websocket.close(1011)
|
||||||
|
return
|
||||||
|
print(f"Client connected: {websocket.remote_address} path: {websocket.request.path}")
|
||||||
|
if websocket.request.path == "/text": await handle_text(websocket)
|
||||||
|
elif websocket.request.path == "/rds": await handle_rds(websocket)
|
||||||
|
else: await websocket.close(1013)
|
||||||
|
|
||||||
|
async def handle_http(websocket: websockets.ServerConnection, reqeust: websockets.Request) -> Response | None:
|
||||||
|
tosend = None
|
||||||
|
mimetype = "text/text"
|
||||||
|
match reqeust.path:
|
||||||
|
case "/static_data":
|
||||||
|
json_data = {
|
||||||
|
"qthLatitude": f"{LATITUDE:.6f}",
|
||||||
|
"qthLongitude": f"{LONGITUDE:.6f}",
|
||||||
|
"presets": [
|
||||||
|
f"{PRESET0:.1f}",
|
||||||
|
f"{PRESET1:.1f}",
|
||||||
|
f"{PRESET2:.1f}",
|
||||||
|
f"{PRESET3:.1f}"
|
||||||
|
],
|
||||||
|
"defaultTheme": f"theme{DEFAULT_THEME}",
|
||||||
|
"bgImage": BG_IMAGE,
|
||||||
|
"rdsMode": RBDS,
|
||||||
|
"rdsTimeout": f"{RDS_TIMEOUT}",
|
||||||
|
"tunerName": NAME,
|
||||||
|
"tunerDesc": DESCRIPTION,
|
||||||
|
"ant": {
|
||||||
|
"enabled": False,
|
||||||
|
"ant1": {
|
||||||
|
"enabled": False,
|
||||||
|
"name": "Ant H"
|
||||||
|
},
|
||||||
|
"ant2": {
|
||||||
|
"enabled": False,
|
||||||
|
"name": "Ant V"
|
||||||
|
},
|
||||||
|
"ant3": {
|
||||||
|
"enabled": False,
|
||||||
|
"name": "Ant C"
|
||||||
|
},
|
||||||
|
"ant4": {
|
||||||
|
"enabled": False,
|
||||||
|
"name": "Ant D"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tosend = json.dumps(json_data).encode()
|
||||||
|
mimetype = "application/json"
|
||||||
|
case "/ping":
|
||||||
|
tosend = b"pong"
|
||||||
|
if tosend is not None:
|
||||||
|
return Response(200, "OK",
|
||||||
|
Headers([("Content-Type", mimetype), ("Content-Length", f"{len(tosend)}")]),
|
||||||
|
tosend)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def process_requests(websocket: websockets.ServerConnection, request: websockets.Request):
|
||||||
|
print(request)
|
||||||
|
if request.path not in ("/text", "/rds"):
|
||||||
|
handle = await handle_http(websocket, request)
|
||||||
|
if not handle:
|
||||||
|
data = b"This path does not have an handler"
|
||||||
|
return Response(
|
||||||
|
404,
|
||||||
|
"Not Found",
|
||||||
|
Headers([("Content-Type", "text/text"), ("Content-Length", f"{len(data)}")]),
|
||||||
|
data
|
||||||
|
)
|
||||||
|
else: return handle
|
||||||
|
if not "upgrade" in request.headers.get("Connection", "").lower():
|
||||||
|
return Response(
|
||||||
|
426,
|
||||||
|
"Upgrade Required",
|
||||||
|
Headers([("Connection", "Upgrade"), ("Upgrade", "websocket")]),
|
||||||
|
b"WebSocket upgrade required\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
wtask = asyncio.create_task(worker())
|
||||||
|
wtask2 = asyncio.create_task(rds_worker())
|
||||||
|
async with websockets.serve(handler, "localhost", 8765, process_request=process_requests, server_header="FM-DX-Cliserver"):
|
||||||
|
print("Server running on ws://localhost:8765")
|
||||||
|
await wtask
|
||||||
|
await wtask2
|
||||||
|
|
||||||
|
def start_process():
|
||||||
|
proc = Process(target=serialpart, args=(inq,outq,rds))
|
||||||
|
proc.start()
|
||||||
|
return proc
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# I fucking hate windows, i wish they'd hang gates in a city center like they did mussolini
|
||||||
|
freeze_support()
|
||||||
|
proc = start_process()
|
||||||
|
try: asyncio.run(main())
|
||||||
|
except SystemExit:
|
||||||
|
inq.put("!x")
|
||||||
|
outq.put("!x")
|
||||||
|
rds.put("!x")
|
||||||
|
proc.terminate()
|
||||||
|
proc.join()
|
||||||
|
raise
|
||||||
@@ -0,0 +1,616 @@
|
|||||||
|
countries = [
|
||||||
|
"Albania",
|
||||||
|
"Estonia",
|
||||||
|
"Algeria",
|
||||||
|
"Ethiopia",
|
||||||
|
"Andorra",
|
||||||
|
"Angola",
|
||||||
|
"Finland",
|
||||||
|
"Armenia",
|
||||||
|
"France",
|
||||||
|
"Ascension Island",
|
||||||
|
"Gabon",
|
||||||
|
"Austria",
|
||||||
|
"Gambia",
|
||||||
|
"Azerbaijan",
|
||||||
|
"Georgia",
|
||||||
|
"Germany",
|
||||||
|
"Bahrein",
|
||||||
|
"Ghana",
|
||||||
|
"Belarus",
|
||||||
|
"Gibraltar",
|
||||||
|
"Belgium",
|
||||||
|
"Greece",
|
||||||
|
"Benin",
|
||||||
|
"Guinea",
|
||||||
|
"Bosnia Herzegovina",
|
||||||
|
"Guinea-Bissau",
|
||||||
|
"Botswana",
|
||||||
|
"Hungary",
|
||||||
|
"Bulgaria",
|
||||||
|
"Iceland",
|
||||||
|
"Burkina Faso",
|
||||||
|
"Iraq",
|
||||||
|
"Burundi",
|
||||||
|
"Ireland",
|
||||||
|
"Cabinda",
|
||||||
|
"Israel",
|
||||||
|
"Cameroon",
|
||||||
|
"Italy",
|
||||||
|
"Jordan",
|
||||||
|
"Cape Verde",
|
||||||
|
"Kazakhstan",
|
||||||
|
"Central African Republic",
|
||||||
|
"Kenya",
|
||||||
|
"Chad",
|
||||||
|
"Kosovo",
|
||||||
|
"Comoros",
|
||||||
|
"Kuwait",
|
||||||
|
"DR Congo",
|
||||||
|
"Kyrgyzstan",
|
||||||
|
"Republic of Congo",
|
||||||
|
"Latvia",
|
||||||
|
"Cote d'Ivoire",
|
||||||
|
"Lebanon",
|
||||||
|
"Croatia",
|
||||||
|
"Lesotho",
|
||||||
|
"Cyprus",
|
||||||
|
"Liberia",
|
||||||
|
"Czechia",
|
||||||
|
"Libya",
|
||||||
|
"Denmark",
|
||||||
|
"Liechtenstein",
|
||||||
|
"Djiboutia",
|
||||||
|
"Lithuania",
|
||||||
|
"Egypt",
|
||||||
|
"Luxembourg",
|
||||||
|
"Equatorial Guinea",
|
||||||
|
"Macedonia",
|
||||||
|
"Eritrea",
|
||||||
|
"Madagascar",
|
||||||
|
"Seychelles",
|
||||||
|
"Malawi",
|
||||||
|
"Sierra Leone",
|
||||||
|
"Mali",
|
||||||
|
"Slovakia",
|
||||||
|
"Malta",
|
||||||
|
"Slovenia",
|
||||||
|
"Mauritania",
|
||||||
|
"Somalia",
|
||||||
|
"Mauritius",
|
||||||
|
"South Africa",
|
||||||
|
"Moldova",
|
||||||
|
"South Sudan",
|
||||||
|
"Monaco",
|
||||||
|
"Spain",
|
||||||
|
"Mongolia",
|
||||||
|
"Sudan",
|
||||||
|
"Montenegro",
|
||||||
|
"Swaziland",
|
||||||
|
"Morocco",
|
||||||
|
"Sweden",
|
||||||
|
"Mozambique",
|
||||||
|
"Switzerland",
|
||||||
|
"Namibia",
|
||||||
|
"Syria",
|
||||||
|
"Netherlands",
|
||||||
|
"Tajikistan",
|
||||||
|
"Niger",
|
||||||
|
"Tanzania",
|
||||||
|
"Nigeria",
|
||||||
|
"Togo",
|
||||||
|
"Norway",
|
||||||
|
"Tunisia",
|
||||||
|
"Oman",
|
||||||
|
"Turkey",
|
||||||
|
"Palestine",
|
||||||
|
"Turkmenistan",
|
||||||
|
"Poland",
|
||||||
|
"Uganda",
|
||||||
|
"Portugal",
|
||||||
|
"Ukraine",
|
||||||
|
"Qatar",
|
||||||
|
"United Arab Emirates",
|
||||||
|
"Romania",
|
||||||
|
"United Kingdom",
|
||||||
|
"Russia",
|
||||||
|
"Uzbekistan",
|
||||||
|
"Rwanda",
|
||||||
|
"Vatican",
|
||||||
|
"San Marino",
|
||||||
|
"Western Sahara",
|
||||||
|
"Sao Tome and Principe",
|
||||||
|
"Yemen",
|
||||||
|
"Saudi Arabia",
|
||||||
|
"Zambia",
|
||||||
|
"Senegal",
|
||||||
|
"Zimbabwe",
|
||||||
|
"Serbia",
|
||||||
|
"Anguilla",
|
||||||
|
"Guyana",
|
||||||
|
"Antigua and Barbuda",
|
||||||
|
"Haiti",
|
||||||
|
"Argentina",
|
||||||
|
"Honduras",
|
||||||
|
"Aruba",
|
||||||
|
"Jamaica",
|
||||||
|
"Bahamas",
|
||||||
|
"Martinique",
|
||||||
|
"Barbados",
|
||||||
|
"Mexico",
|
||||||
|
"Belize",
|
||||||
|
"Montserrat",
|
||||||
|
"Brazil/Bermuda",
|
||||||
|
"Brazil/AN",
|
||||||
|
"Bolivia",
|
||||||
|
"Nicaragua",
|
||||||
|
"Brazil",
|
||||||
|
"Panama",
|
||||||
|
"Canada",
|
||||||
|
"Paraguay",
|
||||||
|
"Cayman Islands",
|
||||||
|
"Peru",
|
||||||
|
"Chile",
|
||||||
|
"USA/VI/PR",
|
||||||
|
"Colombia",
|
||||||
|
"St. Kitts",
|
||||||
|
"Costa Rica",
|
||||||
|
"St. Lucia",
|
||||||
|
"Cuba",
|
||||||
|
"St. Pierre and Miquelon",
|
||||||
|
"Dominica",
|
||||||
|
"St. Vincent",
|
||||||
|
"Dominican Republic",
|
||||||
|
"Suriname",
|
||||||
|
"El Salvador",
|
||||||
|
"Trinidad and Tobago",
|
||||||
|
"Turks and Caicos islands",
|
||||||
|
"Falkland Islands",
|
||||||
|
"Greenland",
|
||||||
|
"Uruguay",
|
||||||
|
"Grenada",
|
||||||
|
"Venezuela",
|
||||||
|
"Guadeloupe",
|
||||||
|
"Virgin Islands",
|
||||||
|
"Guatemala",
|
||||||
|
"Afghanistan",
|
||||||
|
"South Korea",
|
||||||
|
"Laos",
|
||||||
|
"Australia Capital Territory",
|
||||||
|
"Macao",
|
||||||
|
"Australia New South Wales",
|
||||||
|
"Malaysia",
|
||||||
|
"Australia Victoria",
|
||||||
|
"Maldives",
|
||||||
|
"Australia Queensland",
|
||||||
|
"Marshall Islands",
|
||||||
|
"Australia South Australia",
|
||||||
|
"Micronesia",
|
||||||
|
"Australia Western Australia",
|
||||||
|
"Myanmar",
|
||||||
|
"Australia Tasmania",
|
||||||
|
"Nauru",
|
||||||
|
"Australia Northern Territory",
|
||||||
|
"Nepal",
|
||||||
|
"Bangladesh",
|
||||||
|
"New Zealand",
|
||||||
|
"Bhutan",
|
||||||
|
"Pakistan",
|
||||||
|
"Brunei Darussalam",
|
||||||
|
"Papua New Guinea",
|
||||||
|
"Cambodia",
|
||||||
|
"Philippines",
|
||||||
|
"China",
|
||||||
|
"Samoa",
|
||||||
|
"Singapore",
|
||||||
|
"Solomon Islands",
|
||||||
|
"Fiji",
|
||||||
|
"Sri Lanka",
|
||||||
|
"Hong Kong",
|
||||||
|
"Taiwan",
|
||||||
|
"India",
|
||||||
|
"Thailand",
|
||||||
|
"Indonesia",
|
||||||
|
"Tonga",
|
||||||
|
"Iran",
|
||||||
|
"Vanuatu",
|
||||||
|
"Japan",
|
||||||
|
"Vietnam",
|
||||||
|
"Kiribati",
|
||||||
|
"North Korea",
|
||||||
|
"Brazil/Equator"
|
||||||
|
]
|
||||||
|
|
||||||
|
iso = [
|
||||||
|
"AL",
|
||||||
|
"EE",
|
||||||
|
"DZ",
|
||||||
|
"ET",
|
||||||
|
"AD",
|
||||||
|
"AO",
|
||||||
|
"FI",
|
||||||
|
"AM",
|
||||||
|
"FR",
|
||||||
|
"SH",
|
||||||
|
"GA",
|
||||||
|
"AT",
|
||||||
|
"GM",
|
||||||
|
"AZ",
|
||||||
|
"GE",
|
||||||
|
"DE",
|
||||||
|
"BH",
|
||||||
|
"GH",
|
||||||
|
"BY",
|
||||||
|
"GI",
|
||||||
|
"BE",
|
||||||
|
"GR",
|
||||||
|
"BJ",
|
||||||
|
"GN",
|
||||||
|
"BA",
|
||||||
|
"GW",
|
||||||
|
"BW",
|
||||||
|
"HU",
|
||||||
|
"BG",
|
||||||
|
"IS",
|
||||||
|
"BF",
|
||||||
|
"IQ",
|
||||||
|
"BI",
|
||||||
|
"IE",
|
||||||
|
"--",
|
||||||
|
"IL",
|
||||||
|
"CM",
|
||||||
|
"IT",
|
||||||
|
"JO",
|
||||||
|
"CV",
|
||||||
|
"KZ",
|
||||||
|
"CF",
|
||||||
|
"KE",
|
||||||
|
"TD",
|
||||||
|
"XK",
|
||||||
|
"KM",
|
||||||
|
"KW",
|
||||||
|
"CD",
|
||||||
|
"KG",
|
||||||
|
"CG",
|
||||||
|
"LV",
|
||||||
|
"CI",
|
||||||
|
"LB",
|
||||||
|
"HR",
|
||||||
|
"LS",
|
||||||
|
"CY",
|
||||||
|
"LR",
|
||||||
|
"CZ",
|
||||||
|
"LY",
|
||||||
|
"DK",
|
||||||
|
"LI",
|
||||||
|
"DJ",
|
||||||
|
"LT",
|
||||||
|
"EG",
|
||||||
|
"LU",
|
||||||
|
"GQ",
|
||||||
|
"MK",
|
||||||
|
"ER",
|
||||||
|
"MG",
|
||||||
|
"SC",
|
||||||
|
"MW",
|
||||||
|
"SL",
|
||||||
|
"ML",
|
||||||
|
"SK",
|
||||||
|
"MT",
|
||||||
|
"SI",
|
||||||
|
"MR",
|
||||||
|
"SO",
|
||||||
|
"MU",
|
||||||
|
"ZA",
|
||||||
|
"MD",
|
||||||
|
"SS",
|
||||||
|
"MC",
|
||||||
|
"ES",
|
||||||
|
"MN",
|
||||||
|
"SD",
|
||||||
|
"ME",
|
||||||
|
"SZ",
|
||||||
|
"MA",
|
||||||
|
"SE",
|
||||||
|
"MZ",
|
||||||
|
"CH",
|
||||||
|
"NA",
|
||||||
|
"SY",
|
||||||
|
"NL",
|
||||||
|
"TJ",
|
||||||
|
"NE",
|
||||||
|
"TZ",
|
||||||
|
"NG",
|
||||||
|
"TG",
|
||||||
|
"NO",
|
||||||
|
"TN",
|
||||||
|
"OM",
|
||||||
|
"TR",
|
||||||
|
"PS",
|
||||||
|
"TM",
|
||||||
|
"PL",
|
||||||
|
"UG",
|
||||||
|
"PT",
|
||||||
|
"UA",
|
||||||
|
"QA",
|
||||||
|
"AE",
|
||||||
|
"RO",
|
||||||
|
"GB",
|
||||||
|
"RU",
|
||||||
|
"UZ",
|
||||||
|
"RW",
|
||||||
|
"VA",
|
||||||
|
"SM",
|
||||||
|
"EH",
|
||||||
|
"ST",
|
||||||
|
"YE",
|
||||||
|
"SA",
|
||||||
|
"ZM",
|
||||||
|
"SN",
|
||||||
|
"ZW",
|
||||||
|
"RS",
|
||||||
|
"AI",
|
||||||
|
"GY",
|
||||||
|
"AG",
|
||||||
|
"HT",
|
||||||
|
"AR",
|
||||||
|
"HN",
|
||||||
|
"AW",
|
||||||
|
"JM",
|
||||||
|
"BS",
|
||||||
|
"MQ",
|
||||||
|
"BB",
|
||||||
|
"MX",
|
||||||
|
"BZ",
|
||||||
|
"MS",
|
||||||
|
"--",
|
||||||
|
"--",
|
||||||
|
"BO",
|
||||||
|
"NI",
|
||||||
|
"BR",
|
||||||
|
"PA",
|
||||||
|
"CA",
|
||||||
|
"PY",
|
||||||
|
"KY",
|
||||||
|
"PE",
|
||||||
|
"CL",
|
||||||
|
"--",
|
||||||
|
"CO",
|
||||||
|
"KN",
|
||||||
|
"CR",
|
||||||
|
"LC",
|
||||||
|
"CU",
|
||||||
|
"PM",
|
||||||
|
"DM",
|
||||||
|
"VC",
|
||||||
|
"DO",
|
||||||
|
"SR",
|
||||||
|
"SN",
|
||||||
|
"TT",
|
||||||
|
"TB",
|
||||||
|
"FK",
|
||||||
|
"GL",
|
||||||
|
"UY",
|
||||||
|
"GD",
|
||||||
|
"VE",
|
||||||
|
"GP",
|
||||||
|
"VG",
|
||||||
|
"GT",
|
||||||
|
"AF",
|
||||||
|
"KR",
|
||||||
|
"LA",
|
||||||
|
"AU",
|
||||||
|
"MO",
|
||||||
|
"AU",
|
||||||
|
"MY",
|
||||||
|
"AU",
|
||||||
|
"MV",
|
||||||
|
"AU",
|
||||||
|
"MH",
|
||||||
|
"AU",
|
||||||
|
"FM",
|
||||||
|
"AU",
|
||||||
|
"MM",
|
||||||
|
"AU",
|
||||||
|
"NR",
|
||||||
|
"AU",
|
||||||
|
"NP",
|
||||||
|
"BD",
|
||||||
|
"NZ",
|
||||||
|
"BT",
|
||||||
|
"PK",
|
||||||
|
"BN",
|
||||||
|
"PG",
|
||||||
|
"KH",
|
||||||
|
"PH",
|
||||||
|
"CN",
|
||||||
|
"WS",
|
||||||
|
"SG",
|
||||||
|
"SB",
|
||||||
|
"FJ",
|
||||||
|
"LK",
|
||||||
|
"HK",
|
||||||
|
"TW",
|
||||||
|
"IN",
|
||||||
|
"TH",
|
||||||
|
"ID",
|
||||||
|
"TO",
|
||||||
|
"IR",
|
||||||
|
"VU",
|
||||||
|
"JP",
|
||||||
|
"VN",
|
||||||
|
"KI",
|
||||||
|
"KP",
|
||||||
|
"--"
|
||||||
|
]
|
||||||
|
|
||||||
|
rdsEccA0A6Lut = [
|
||||||
|
# A0
|
||||||
|
[
|
||||||
|
"USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR",
|
||||||
|
"USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR",
|
||||||
|
"USA/VI/PR", None, "USA/VI/PR", "USA/VI/PR", None
|
||||||
|
],
|
||||||
|
# A1
|
||||||
|
[
|
||||||
|
None, None, None, None, None,
|
||||||
|
None, None, None, None, None,
|
||||||
|
"Canada", "Canada", "Canada", "Canada", "Greenland"
|
||||||
|
],
|
||||||
|
# A2
|
||||||
|
[
|
||||||
|
"Anguilla", "Antigua and Barbuda", "Brazil/Equator", "Falkland Islands", "Barbados",
|
||||||
|
"Belize", "Cayman Islands", "Costa Rica", "Cuba", "Argentina",
|
||||||
|
"Brazil", "Brazil/Bermuda", "Brazil/AN", "Guadeloupe", "Bahamas"
|
||||||
|
],
|
||||||
|
# A3
|
||||||
|
[
|
||||||
|
"Bolivia", "Colombia", "Jamaica", "Martinique", None,
|
||||||
|
"Paraguay", "Nicaragua", None, "Panama", "Dominica",
|
||||||
|
"Dominican Republic", "Chile", "Grenada", "Turks and Caicos islands", "Guyana"
|
||||||
|
],
|
||||||
|
# A4
|
||||||
|
[
|
||||||
|
"Guatemala", "Honduras", "Aruba", None, "Montserrat",
|
||||||
|
"Trinidad and Tobago", "Peru", "Suriname", "Uruguay", "St. Kitts",
|
||||||
|
"St. Lucia", "El Salvador", "Haiti", "Venezuela", "Virgin Islands"
|
||||||
|
],
|
||||||
|
# A5
|
||||||
|
[
|
||||||
|
None, None, None, None, None,
|
||||||
|
None, None, None, None, None,
|
||||||
|
"Mexico", "St. Vincent", "Mexico", "Mexico", "Mexico"
|
||||||
|
],
|
||||||
|
# A6
|
||||||
|
[
|
||||||
|
None, None, None, None, None,
|
||||||
|
None, None, None, None, None,
|
||||||
|
None, None, None, None, "St. Pierre and Miquelon"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
rdsEccD0D4Lut = [
|
||||||
|
# D0
|
||||||
|
[
|
||||||
|
"Cameroon", "Central African Republic", "Djiboutia", "Madagascar", "Mali",
|
||||||
|
"Angola", "Equatorial Guinea", "Gabon", "Guinea", "South Africa",
|
||||||
|
"Burkina Faso", "Republic of Congo", "Togo", "Benin", "Malawi"
|
||||||
|
],
|
||||||
|
# D1
|
||||||
|
[
|
||||||
|
"Namibia", "Liberia", "Ghana", "Mauritania", "Sao Tome and Principe",
|
||||||
|
"Cape Verde", "Senegal", "Gambia", "Burundi", "Ascension Island",
|
||||||
|
"Botswana", "Comoros", "Tanzania", "Ethiopia", "Nigeria"
|
||||||
|
],
|
||||||
|
# D2
|
||||||
|
[
|
||||||
|
"Sierra Leone", "Zimbabwe", "Mozambique", "Uganda", "Swaziland",
|
||||||
|
"Kenya", "Somalia", "Niger", "Chad", "Guinea-Bissau",
|
||||||
|
"DR Congo", "Cote d'Ivoire", None, "Zambia", "Eritrea"
|
||||||
|
],
|
||||||
|
# D3
|
||||||
|
[
|
||||||
|
None, None, "Western Sahara", "Cabinda", "Rwanda",
|
||||||
|
"Lesotho", None, "Seychelles", None, "Mauritius",
|
||||||
|
None, "Sudan", None, None, None
|
||||||
|
],
|
||||||
|
# D4
|
||||||
|
[
|
||||||
|
None, None, None, None, None,
|
||||||
|
None, None, None, None, "South Sudan",
|
||||||
|
None, None, None, None, None
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
rdsEccE0E5Lut = [
|
||||||
|
# E0
|
||||||
|
[
|
||||||
|
"Germany", "Algeria", "Andorra", "Israel", "Italy",
|
||||||
|
"Belgium", "Russia", "Palestine", "Albania", "Austria",
|
||||||
|
"Hungary", "Malta", "Germany", None, "Egypt"
|
||||||
|
],
|
||||||
|
# E1
|
||||||
|
[
|
||||||
|
"Greece", "Cyprus", "San Marino", "Switzerland", "Jordan",
|
||||||
|
"Finland", "Luxembourg", "Bulgaria", "Denmark", "Gibraltar",
|
||||||
|
"Iraq", "United Kingdom", "Libya", "Romania", "France"
|
||||||
|
],
|
||||||
|
# E2
|
||||||
|
[
|
||||||
|
"Morocco", "Czechia", "Poland", "Vatican", "Slovakia",
|
||||||
|
"Syria", "Tunisia", None, "Liechtenstein", "Iceland",
|
||||||
|
"Monaco", "Lithuania", "Serbia", "Spain", "Norway"
|
||||||
|
],
|
||||||
|
# E3
|
||||||
|
[
|
||||||
|
"Montenegro", "Ireland", "Turkey", None, "Tajikistan",
|
||||||
|
None, None, "Netherlands", "Latvia", "Lebanon",
|
||||||
|
"Azerbaijan", "Croatia", "Kazakhstan", "Sweden", "Belarus"
|
||||||
|
],
|
||||||
|
# E4
|
||||||
|
[
|
||||||
|
"Moldova", "Estonia", "Macedonia", None, None,
|
||||||
|
"Ukraine", "Kosovo", "Portugal", "Slovenia", "Armenia",
|
||||||
|
"Uzbekistan", "Georgia", None, "Turkmenistan", "Bosnia Herzegovina"
|
||||||
|
],
|
||||||
|
# E5
|
||||||
|
[
|
||||||
|
None, None, "Kyrgyzstan", None, None,
|
||||||
|
None, None, None, None, None,
|
||||||
|
None, None, None, None, None
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
rdsEccF0F4Lut = [
|
||||||
|
# F0
|
||||||
|
[
|
||||||
|
"Australia Capital Territory", "Australia New South Wales", "Australia Victoria", "Australia Queensland", "Australia South Australia",
|
||||||
|
"Australia Western Australia", "Australia Tasmania", "Australia Northern Territory", "Saudi Arabia", "Afghanistan",
|
||||||
|
"Myanmar", "China", "North Korea", "Bahrein", "Malaysia"
|
||||||
|
],
|
||||||
|
# F1
|
||||||
|
[
|
||||||
|
"Kiribati", "Bhutan", "Bangladesh", "Pakistan", "Fiji",
|
||||||
|
"Oman", "Nauru", "Iran", "New Zealand", "Solomon Islands",
|
||||||
|
"Brunei Darussalam", "Sri Lanka", "Taiwan", "South Korea", "Hong Kong"
|
||||||
|
],
|
||||||
|
# F2
|
||||||
|
[
|
||||||
|
"Kuwait", "Qatar", "Cambodia", "Samoa", "India",
|
||||||
|
"Macao", "Vietnam", "Philippines", "Japan", "Singapore",
|
||||||
|
"Maldives", "Indonesia", "United Arab Emirates", "Nepal", "Vanuatu"
|
||||||
|
],
|
||||||
|
# F3
|
||||||
|
[
|
||||||
|
"Laos", "Thailand", "Tonga", None, None,
|
||||||
|
None, None, "China", "Papua New Guinea", None,
|
||||||
|
"Yemen", None, None, "Micronesia", "Mongolia"
|
||||||
|
],
|
||||||
|
# F4
|
||||||
|
[
|
||||||
|
None, None, None, None, None,
|
||||||
|
None, None, None, "China", None,
|
||||||
|
"Marshall Islands", None, None, None, None
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
def rds_ecc_lookup(pi, ecc):
|
||||||
|
PI_UNKNOWN = -1
|
||||||
|
pi_country = (pi >> 12) & 0xF
|
||||||
|
if pi == PI_UNKNOWN or pi_country == 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
pi_id = pi_country - 1
|
||||||
|
ecc_ranges = [
|
||||||
|
{"min": 0xA0, "max": 0xA6, "lut": rdsEccA0A6Lut},
|
||||||
|
{"min": 0xD0, "max": 0xD4, "lut": rdsEccD0D4Lut},
|
||||||
|
{"min": 0xE0, "max": 0xE5, "lut": rdsEccE0E5Lut},
|
||||||
|
{"min": 0xF0, "max": 0xF4, "lut": rdsEccF0F4Lut},
|
||||||
|
]
|
||||||
|
|
||||||
|
for range_ in ecc_ranges:
|
||||||
|
if range_["min"] <= ecc <= range_["max"]:
|
||||||
|
ecc_id = ecc - range_["min"]
|
||||||
|
return range_["lut"][ecc_id][pi_id]
|
||||||
|
|
||||||
|
return ""
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import struct
|
||||||
|
import librds.charset
|
||||||
|
from librds.comfort import SubstituteCharacterAtPosition
|
||||||
|
from cliserver_Countries import rds_ecc_lookup, iso, countries
|
||||||
|
|
||||||
|
def decodeRds(wsdata: dict, status: int, A: int, B: int, C: int, D: int, dec_error: int):
|
||||||
|
a_error = dec_error >> 6
|
||||||
|
b_error = (dec_error >> 4) & 0b11
|
||||||
|
c_error = (dec_error >> 2) & 0b11
|
||||||
|
d_error = dec_error & 0b11
|
||||||
|
|
||||||
|
pi = A
|
||||||
|
pi_error = a_error
|
||||||
|
if (status >> 12) & 1 and c_error < a_error:
|
||||||
|
pi = C
|
||||||
|
pi_error = c_error
|
||||||
|
|
||||||
|
if pi_error < 3: wsdata["pi"] = f"{pi:04X}" + ("?" * pi_error)
|
||||||
|
|
||||||
|
if b_error > 3: return pi_error
|
||||||
|
|
||||||
|
group = B >> 11
|
||||||
|
print(group)
|
||||||
|
wsdata["tp"] = (B >> 10) & 1
|
||||||
|
wsdata["pty"] = (B >> 5) & 0b11111
|
||||||
|
|
||||||
|
match group:
|
||||||
|
case 0 | 1:
|
||||||
|
# 0A or 0B
|
||||||
|
ps_errors: list[str] = wsdata["ps_errors"].split(",")
|
||||||
|
|
||||||
|
wsdata["ta"] = (B >> 4) & 1
|
||||||
|
wsdata["ms"] = (B >> 3) & 1
|
||||||
|
|
||||||
|
if d_error > 2: return pi_error
|
||||||
|
segment = B & 0b11
|
||||||
|
|
||||||
|
old_chars = wsdata["ps"][2*segment:(2*segment)+2]
|
||||||
|
chars = "".join([librds.charset.RDSCharsetDecode.translate(i) for i in struct.pack(">H", D)])
|
||||||
|
|
||||||
|
err = int(d_error * (10/3))
|
||||||
|
if (old_chars == chars and int(ps_errors[2*segment]) > err) or old_chars != chars:
|
||||||
|
for offset, char in enumerate(chars):
|
||||||
|
wsdata["ps"] = SubstituteCharacterAtPosition(wsdata["ps"], char, (2 * segment) + offset)
|
||||||
|
ps_errors[2*segment] = str(err)
|
||||||
|
ps_errors[2*segment+1] = str(err)
|
||||||
|
wsdata["ps_errors"] = ",".join(ps_errors)
|
||||||
|
# TODO: AF
|
||||||
|
case 2:
|
||||||
|
# 1A
|
||||||
|
if c_error > 2: return pi_error
|
||||||
|
|
||||||
|
variant_code = (C >> 12) & 0b111
|
||||||
|
match variant_code:
|
||||||
|
case 0:
|
||||||
|
# ECC
|
||||||
|
wsdata["ecc"] = f"{(C & 0xff):02X}"
|
||||||
|
wsdata["country_name"] = rds_ecc_lookup(pi, C & 0xff)
|
||||||
|
wsdata["country_iso"] = "UN" if wsdata["country_name"] == 0 else (
|
||||||
|
iso[countries.index(wsdata["country_name"])]
|
||||||
|
)
|
||||||
|
case 4 | 5:
|
||||||
|
# 2A or 2B
|
||||||
|
segment = B & 0b1111
|
||||||
|
segment_pointer = (4 if group == 4 else 2) * segment
|
||||||
|
d_offset = 2 if group == 4 else 0
|
||||||
|
ab = bool((B >> 4) & 1)
|
||||||
|
|
||||||
|
if wsdata["rt_flag"] != int(ab):
|
||||||
|
wsdata["rt1" if ab else "rt0"] = "".ljust(64)
|
||||||
|
wsdata["rt1_errors" if ab else "rt0_errors"] = ",".join((["0"]*64))
|
||||||
|
wsdata["rt_flag"] = int(ab)
|
||||||
|
|
||||||
|
chars_c = "".join([librds.charset.RDSCharsetDecode.translate(i) for i in struct.pack(">H", C)]).replace("\r", "")
|
||||||
|
chars_d = "".join([librds.charset.RDSCharsetDecode.translate(i) for i in struct.pack(">H", D)]).replace("\r", "")
|
||||||
|
|
||||||
|
rt_errors: list[str] = wsdata["rt1_errors" if ab else "rt0_errors"].split(",")
|
||||||
|
old_rt: str = wsdata["rt1" if ab else "rt0"]
|
||||||
|
|
||||||
|
old_c = old_rt[segment_pointer:segment_pointer+2]
|
||||||
|
old_c_error = int(rt_errors[segment_pointer])
|
||||||
|
_c_error = int(c_error * (10/3))
|
||||||
|
old_d = old_rt[segment_pointer+d_offset:segment_pointer+d_offset+2]
|
||||||
|
old_d_error = int(rt_errors[segment_pointer+d_offset])
|
||||||
|
_d_error = int(d_error * (10/3))
|
||||||
|
|
||||||
|
if group == 4 and _c_error < 3 and (chars_c != old_c or _c_error < old_c_error):
|
||||||
|
for offset, char in enumerate(chars_c):
|
||||||
|
old_rt = SubstituteCharacterAtPosition(old_rt, char, segment_pointer + offset)
|
||||||
|
rt_errors[segment_pointer] = str(_c_error)
|
||||||
|
rt_errors[segment_pointer+1] = str(_c_error)
|
||||||
|
if _d_error < 3 and (chars_d != old_d or _d_error < old_d_error):
|
||||||
|
for offset, char in enumerate(chars_d):
|
||||||
|
old_rt = SubstituteCharacterAtPosition(old_rt, char, segment_pointer + offset + d_offset)
|
||||||
|
rt_errors[segment_pointer+d_offset] = str(_d_error)
|
||||||
|
rt_errors[segment_pointer+d_offset+1] = str(_d_error)
|
||||||
|
wsdata["rt1_errors" if ab else "rt0_errors"] = ",".join(rt_errors)
|
||||||
|
wsdata["rt1" if ab else "rt0"] = old_rt
|
||||||
|
|
||||||
|
return pi_error
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import struct
|
import struct
|
||||||
from protocol import I2CPCClient, time
|
from driver.protocol import I2CPCClient, time
|
||||||
from patches import *
|
from driver.patches import *
|
||||||
|
|
||||||
from typing import TypeVar, ParamSpec
|
from typing import TypeVar, ParamSpec
|
||||||
|
|
||||||
@@ -27,12 +27,12 @@ class I2CPCClient:
|
|||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
while not ((d := self.ser.read_all()) and b"\xff" in d):
|
while not ((d := self.ser.read_all()) and b"\xff" in d):
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
if ((time.monotonic()) - start) > 2: return False
|
if ((time.monotonic()) - start) > 3: return False
|
||||||
return True
|
return True
|
||||||
if not wake(None):
|
if not wake(None):
|
||||||
for baud in wake_bauds:
|
for baud in wake_bauds:
|
||||||
if wake(baud):
|
if wake(baud):
|
||||||
warn(f"Initial baud was changed to {baud}, due to the device being in it")
|
if baud != baudrate: warn(f"Initial baud was changed to {baud}, due to the device being in it")
|
||||||
break
|
break
|
||||||
else: raise ConnectionError("Could not wake")
|
else: raise ConnectionError("Could not wake")
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from base_tef import BaseTEF668X
|
from driver.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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from protocol import I2CPCClient
|
from driver.protocol import I2CPCClient
|
||||||
|
|
||||||
def sync_rtc(p: I2CPCClient):
|
def sync_rtc(p: I2CPCClient):
|
||||||
TIMEOFFSET_ADDR = 2275
|
TIMEOFFSET_ADDR = 2275
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
from tef import TEF6686
|
from driver.tef import TEF6686
|
||||||
from protocol import I2CPCClient
|
from driver.protocol import I2CPCClient
|
||||||
import time
|
import time
|
||||||
|
|
||||||
p = I2CPCClient("COM17")
|
p = I2CPCClient("COM17")
|
||||||
|
print(p.version())
|
||||||
with TEF6686(p) as tef:
|
with TEF6686(p) as tef:
|
||||||
tef.init(clock=12000000)
|
tef.init()
|
||||||
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, 9500)
|
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)
|
||||||
tef.FM_Set_ChannelEqualizer(True)
|
tef.FM_Set_ChannelEqualizer(True)
|
||||||
|
tef.FM_Set_Stereo_Max(False)
|
||||||
# tef.FM_Set_Specials(1)
|
# tef.FM_Set_Specials(1)
|
||||||
tef.FM_Set_Bandwidth(True)
|
tef.FM_Set_Bandwidth(True)
|
||||||
time.sleep(0.032)
|
time.sleep(0.032)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import secrets
|
|||||||
import string
|
import string
|
||||||
import libtimer
|
import libtimer
|
||||||
import time
|
import time
|
||||||
from tef import TEF6686
|
from driver.tef import TEF6686
|
||||||
from protocol import I2CPCClient
|
from driver.protocol import I2CPCClient
|
||||||
import os
|
import os
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
@@ -193,22 +193,20 @@ def send_rds_data(tef: TEF6686, conn: socket.socket, state: dict):
|
|||||||
status, A, B, C, D, dec_error = res
|
status, A, B, C, D, dec_error = res
|
||||||
dec_error >>= 8
|
dec_error >>= 8
|
||||||
|
|
||||||
if (status & (1 << 9) == 0) or (status & (1 << 15) == 0): return
|
if (status & (1 << 9) == 0) or (status & (1 << 15) == 0) or (status & (1 << 13) == (1 << 13)): return
|
||||||
|
|
||||||
data = b""
|
data = b""
|
||||||
a_error = dec_error >> 6
|
a_error = dec_error >> 6
|
||||||
c_error = (dec_error >> 2) & 0b11
|
c_error = (dec_error >> 2) & 0b11
|
||||||
if (status & (1 << 13) == 0):
|
if (status & (1 << 12) == 0):
|
||||||
if (status & (1 << 12) == 0):
|
# Type A, PI in A
|
||||||
# Type A, PI in A
|
data = f"R{A:04X}{B:04X}{C:04X}".encode()
|
||||||
data = f"R{A:04X}{B:04X}{C:04X}".encode()
|
elif (status >> 12) & 1:
|
||||||
elif (status >> 12) & 1:
|
# Type B, PI in A and C
|
||||||
# Type B, PI in A and C
|
pi = A
|
||||||
pi = A
|
if c_error < a_error: pi = C
|
||||||
if c_error < a_error: pi = C
|
data = f"R{pi:04X}{B:04X}{pi:04X}".encode()
|
||||||
data = f"R{pi:04X}{B:04X}{pi:04X}".encode()
|
data += f"{D:04X}{dec_error:02X}\n".encode()
|
||||||
data += f"{D:04X}{dec_error:02X}\n".encode()
|
|
||||||
else: data = f"P{A:04X}{"?" * a_error}\n".encode() # No group data, only PI
|
|
||||||
|
|
||||||
if not data.strip(): return
|
if not data.strip(): return
|
||||||
conn.sendall(data)
|
conn.sendall(data)
|
||||||
|
|||||||
Reference in New Issue
Block a user