some changes

This commit is contained in:
2026-03-03 18:41:17 +01:00
parent e606802541
commit 00394f0c65
12 changed files with 1134 additions and 23 deletions
+395
View File
@@ -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
+616
View File
@@ -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 ""
+100
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
import datetime
from protocol import I2CPCClient
from driver.protocol import I2CPCClient
def sync_rtc(p: I2CPCClient):
TIMEOFFSET_ADDR = 2275
+5 -3
View File
@@ -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)
+3 -5
View File
@@ -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)