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
|
||||
from protocol import I2CPCClient, time
|
||||
from patches import *
|
||||
from driver.protocol import I2CPCClient, time
|
||||
from driver.patches import *
|
||||
|
||||
from typing import TypeVar, ParamSpec
|
||||
|
||||
@@ -27,12 +27,12 @@ class I2CPCClient:
|
||||
start = time.monotonic()
|
||||
while not ((d := self.ser.read_all()) and b"\xff" in d):
|
||||
time.sleep(0.01)
|
||||
if ((time.monotonic()) - start) > 2: return False
|
||||
if ((time.monotonic()) - start) > 3: 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")
|
||||
if baud != baudrate: warn(f"Initial baud was changed to {baud}, due to the device being in it")
|
||||
break
|
||||
else: raise ConnectionError("Could not wake")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from base_tef import BaseTEF668X
|
||||
from driver.base_tef import BaseTEF668X
|
||||
from typing import overload, ParamSpec, TypeVar, Concatenate, Callable
|
||||
from functools import wraps
|
||||
import struct
|
||||
@@ -1,5 +1,5 @@
|
||||
import datetime
|
||||
from protocol import I2CPCClient
|
||||
from driver.protocol import I2CPCClient
|
||||
|
||||
def sync_rtc(p: I2CPCClient):
|
||||
TIMEOFFSET_ADDR = 2275
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
from tef import TEF6686
|
||||
from protocol import I2CPCClient
|
||||
from driver.tef import TEF6686
|
||||
from driver.protocol import I2CPCClient
|
||||
import time
|
||||
|
||||
p = I2CPCClient("COM17")
|
||||
print(p.version())
|
||||
with TEF6686(p) as tef:
|
||||
tef.init(clock=12000000)
|
||||
tef.init()
|
||||
tef.FM_Tune_To(TEF6686.TuneTo_Mode.Preset, 9500)
|
||||
tef.AUDIO_Set_Mute(False)
|
||||
tef.AUDIO_Set_Volume(70)
|
||||
tef.FM_Set_MphSuppression(True)
|
||||
tef.FM_Set_ChannelEqualizer(True)
|
||||
tef.FM_Set_Stereo_Max(False)
|
||||
# tef.FM_Set_Specials(1)
|
||||
tef.FM_Set_Bandwidth(True)
|
||||
time.sleep(0.032)
|
||||
|
||||
@@ -8,8 +8,8 @@ import secrets
|
||||
import string
|
||||
import libtimer
|
||||
import time
|
||||
from tef import TEF6686
|
||||
from protocol import I2CPCClient
|
||||
from driver.tef import TEF6686
|
||||
from driver.protocol import I2CPCClient
|
||||
import os
|
||||
from functools import wraps
|
||||
from typing import Callable
|
||||
@@ -193,12 +193,11 @@ def send_rds_data(tef: TEF6686, conn: socket.socket, state: dict):
|
||||
status, A, B, C, D, dec_error = res
|
||||
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""
|
||||
a_error = dec_error >> 6
|
||||
c_error = (dec_error >> 2) & 0b11
|
||||
if (status & (1 << 13) == 0):
|
||||
if (status & (1 << 12) == 0):
|
||||
# Type A, PI in A
|
||||
data = f"R{A:04X}{B:04X}{C:04X}".encode()
|
||||
@@ -208,7 +207,6 @@ def send_rds_data(tef: TEF6686, conn: socket.socket, state: dict):
|
||||
if c_error < a_error: pi = C
|
||||
data = f"R{pi:04X}{B:04X}{pi:04X}".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
|
||||
conn.sendall(data)
|
||||
|
||||
Reference in New Issue
Block a user