mirror of
https://github.com/radio95-rnt/rds95.git
synced 2026-07-29 16:29:17 +02:00
392 lines
11 KiB
Lua
392 lines
11 KiB
Lua
uecp = {}
|
|
uecp.site_addreses = {}
|
|
uecp.encoder_addreses = {}
|
|
uecp.rt_buffer = {}
|
|
uecp.rt_buffer_index = 1
|
|
uecp.rt_tx_remaining = 0
|
|
|
|
hooks.rt_transmission[#hooks.rt_transmission + 1] = function ()
|
|
if #uecp.rt_buffer == 0 then return end
|
|
|
|
if #uecp.rt_buffer == 1 then return end
|
|
|
|
if uecp.rt_tx_remaining > 1 then
|
|
uecp.rt_tx_remaining = uecp.rt_tx_remaining - 1
|
|
return
|
|
end
|
|
|
|
uecp.rt_buffer_index = (uecp.rt_buffer_index % #uecp.rt_buffer) + 1
|
|
local entry = uecp.rt_buffer[uecp.rt_buffer_index]
|
|
uecp.rt_tx_remaining = entry.number_tx
|
|
|
|
rds.set_rt_raw(entry.text)
|
|
if entry.toggle_ab then rds.toggle_rt_ab() end
|
|
end
|
|
|
|
|
|
local function dsn_helper(dsn, write)
|
|
if dsn == 0 then write()
|
|
elseif dsn == 254 then
|
|
local start = dp.get_writing_program()
|
|
for i = 1, dp.max_programs do
|
|
local p = i - 1
|
|
if p ~= start then
|
|
dp.set_writing_program(p)
|
|
write()
|
|
end
|
|
end
|
|
elseif dsn == 255 then
|
|
for i = 1, dp.max_programs do
|
|
dp.set_writing_program(i - 1)
|
|
write()
|
|
end
|
|
else
|
|
dp.set_writing_program(dsn - 1)
|
|
write()
|
|
end
|
|
end
|
|
|
|
local mec_handlers = {}
|
|
mec_handlers[1] = function(data)
|
|
-- PI
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local pi_msb = string.byte(data, 4)
|
|
local pi_lsb = string.byte(data, 5)
|
|
|
|
dsn_helper(dsn, function()
|
|
rds.set_pi((pi_msb << 8) | pi_lsb)
|
|
end)
|
|
return 5
|
|
end
|
|
mec_handlers[2] = function(data)
|
|
-- PS
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local ps = string.sub(data, 4, 11) -- Static len
|
|
dsn_helper(dsn, function()
|
|
rds.set_ps(ps)
|
|
end)
|
|
return 11
|
|
end
|
|
mec_handlers[0x21] = function(data)
|
|
-- LPS
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local mel = string.byte(data, 4)
|
|
local lps = string.sub(data, 5, 5+mel)
|
|
|
|
local cut = string.find(lps, "\r", 1, true)
|
|
if cut then lps = string.sub(lps, 1, cut - 1) end
|
|
|
|
dsn_helper(dsn, function()
|
|
rds.set_lps(lps)
|
|
end)
|
|
return 4 + mel
|
|
end
|
|
mec_handlers[4] = function(data)
|
|
-- PTYI
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local data = string.byte(data, 4)
|
|
dsn_helper(dsn, function ()
|
|
rds.set_dpty((data & 8) ~= 0)
|
|
end)
|
|
return 4
|
|
end
|
|
mec_handlers[3] = function(data)
|
|
-- TP/TA
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local data = string.byte(data, 4)
|
|
dsn_helper(dsn, function ()
|
|
rds.set_ta((data & 1) ~= 0)
|
|
rds.set_tp((data & 2) ~= 0)
|
|
end)
|
|
return 4
|
|
end
|
|
mec_handlers[7] = function(data)
|
|
-- PTY
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local data = string.byte(data, 4)
|
|
dsn_helper(dsn, function ()
|
|
rds.set_pty(data)
|
|
end)
|
|
return 4
|
|
end
|
|
mec_handlers[0x3E] = function(data)
|
|
-- PTYN
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local ptyn = string.sub(data, 4, 11) -- Static
|
|
dsn_helper(dsn, function ()
|
|
rds.set_ptyn(ptyn)
|
|
end)
|
|
return 11
|
|
end
|
|
mec_handlers[0x0A] = function(data)
|
|
-- RT
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local mel = string.byte(data, 4)
|
|
|
|
if mel == 0 then
|
|
uecp.rt_buffer = {}
|
|
uecp.rt_buffer_index = 1
|
|
uecp.rt_tx_remaining = 0
|
|
dsn_helper(dsn, function ()
|
|
rds.set_rt_enabled(false)
|
|
end)
|
|
return 4
|
|
end
|
|
|
|
local config = string.byte(data, 5)
|
|
local toggle_ab = (config & 1) ~= 0
|
|
local number_tx = (config >> 1) & 0xF
|
|
local buffer_config = (config >> 5) & 3
|
|
|
|
if mel == 1 and buffer_config == 0 then
|
|
uecp.rt_buffer = {}
|
|
uecp.rt_buffer_index = 1
|
|
uecp.rt_tx_remaining = 0
|
|
dsn_helper(dsn, function ()
|
|
rds.set_rt_enabled(false)
|
|
end)
|
|
return 5
|
|
end
|
|
|
|
local rt_text = string.sub(data, 6, 4 + mel)
|
|
local cr = string.find(rt_text, "\r", 1, true)
|
|
if cr then rt_text = string.sub(rt_text, 1, cr - 1) end
|
|
|
|
if buffer_config == 0 then
|
|
uecp.rt_buffer = {}
|
|
uecp.rt_buffer_index = 1
|
|
uecp.rt_buffer[1] = { text = rt_text, number_tx = number_tx, toggle_ab = toggle_ab }
|
|
uecp.rt_tx_remaining = number_tx
|
|
-- Seed the encoder immediately with the first entry
|
|
dsn_helper(dsn, function ()
|
|
rds.set_rt_raw(rt_text)
|
|
if toggle_ab then rds.toggle_rt_ab() end
|
|
rds.set_rt_enabled(true)
|
|
end)
|
|
elseif buffer_config == 2 then
|
|
uecp.rt_buffer[#uecp.rt_buffer + 1] = { text = rt_text, number_tx = number_tx, toggle_ab = toggle_ab }
|
|
end
|
|
|
|
return 4 + mel
|
|
end
|
|
mec_handlers[0x13] = function (data)
|
|
-- AF
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local mel = string.sub(data, 4)
|
|
|
|
-- TODO: decode - i have zero clue what the hell is the meaning of the docs, it might as well be in chinese (traditional)
|
|
|
|
return 4+mel
|
|
end
|
|
mec_handlers[0x14] = function (data)
|
|
-- EON-AF
|
|
local dsn = string.byte(data, 2)
|
|
local psn = string.byte(data, 3)
|
|
local mel = string.sub(data, 4)
|
|
|
|
-- TODO: decode - haven't read this one
|
|
|
|
return 4+mel
|
|
end
|
|
mec_handlers[0x1A] = function (data)
|
|
-- ECC / SLS
|
|
local dsn = string.byte(data, 2)
|
|
-- No PSN
|
|
local data = string.sub(data, 3)
|
|
local data_lsb = string.sub(data, 4)
|
|
|
|
local full_data = ((data & 15) << 8) | data_lsb
|
|
local variant = (data >> 4) & 3
|
|
if variant == 0 then
|
|
-- TODO: maybe make it more than just ecc? idk why but would be cool
|
|
dsn_helper(dsn, function ()
|
|
rds.set_ecc(full_data)
|
|
end)
|
|
end
|
|
|
|
return 4
|
|
end
|
|
mec_handlers[0x2E] = function (data)
|
|
-- Linkage Information
|
|
-- What? What is the main PSN? Does the server tell me that or am i to fucking guess?
|
|
return 5
|
|
end
|
|
mec_handlers[0x24] = function (data)
|
|
-- Free-format data in type A or B group
|
|
local group = string.byte(data, 2)
|
|
local bufferdata = string.byte(data, 3)
|
|
local buffer = (bufferdata >> 5) & 3 -- No clue
|
|
local blockb = bufferdata & 31
|
|
local blockc_msb = string.byte(data, 4)
|
|
local blockc_lsb = string.byte(data, 5)
|
|
local blockd_msb = string.byte(data, 6)
|
|
local blockd_lsb = string.byte(data, 7)
|
|
rds.put_custom_group((group << 11) | blockb, (blockc_msb << 8) | blockc_lsb, (blockd_msb << 8) | blockd_lsb)
|
|
return 7
|
|
end
|
|
-- Fuck you mean, i have to implement some fucking "spinning wHELLs"? may the lord have mercy
|
|
-- Also no time setting via UECP - this is linux, just install fucking chrony
|
|
mec_handlers[0x19] = function (data)
|
|
-- CT
|
|
local data = string.byte(data, 2)
|
|
dsn_helper(255, function ()
|
|
rds.set_ct(data ~= 0)
|
|
end)
|
|
return 2
|
|
end
|
|
mec_handlers[0x16] = function (data)
|
|
-- Group sequence for stream 0
|
|
local dsn = string.byte(data, 2)
|
|
local mel = string.byte(data, 3)
|
|
local group_sequence = string.sub(data, 4, 3+mel)
|
|
dsn_helper(dsn, function ()
|
|
rds.set_grpseq(group_sequence)
|
|
end)
|
|
return 4 + mel
|
|
end
|
|
mec_handlers[0x23] = function (data)
|
|
-- Site address
|
|
local control_bits = string.byte(data, 2)
|
|
local high = string.byte(data, 3)
|
|
local low = string.byte(data, 4)
|
|
local data = high << 8 | low
|
|
if control_bits == 0 then
|
|
for i, v in ipairs(uecp.site_addreses) do
|
|
if v == data then table.remove(uecp.site_addreses, i) end
|
|
end
|
|
elseif control_bits == 1 then uecp.site_addreses[#uecp.site_addreses+1] = data
|
|
elseif control_bits == 2 then uecp.site_addreses = {} end
|
|
return 4
|
|
end
|
|
mec_handlers[0x27] = function (data)
|
|
-- Encoder address
|
|
local control_bits = string.byte(data, 2)
|
|
local data = string.byte(data, 3)
|
|
if control_bits == 0 then
|
|
for i, v in ipairs(uecp.encoder_addreses) do
|
|
if v == data then table.remove(uecp.encoder_addreses, i) end
|
|
end
|
|
elseif control_bits == 1 then uecp.encoder_addreses[#uecp.encoder_addreses+1] = data
|
|
elseif control_bits == 2 then uecp.encoder_addreses = {} end
|
|
return 3
|
|
end
|
|
mec_handlers[0x1C] = function (data)
|
|
-- DS select
|
|
local dsn = string.byte(data, 2)
|
|
dp.set_output_program(dsn)
|
|
dp.set_writing_program(dsn)
|
|
return 2
|
|
end
|
|
mec_handlers[0x2D] = function (data)
|
|
-- Manufacturer
|
|
local mel = string.byte(data, 2)
|
|
local designation = string.sub(data, 3, 4)
|
|
if string.byte(designation, 1) == 0x39 and string.byte(designation, 2) == 0x35 and mel > 2 then
|
|
local data = string.sub(data, 5, 4+mel)
|
|
pcall(hooks.data_handle, data)
|
|
end
|
|
return 2+mel
|
|
end
|
|
|
|
---@param data string
|
|
local function undo_byte_stuff(data)
|
|
local output = {}
|
|
local i = 1
|
|
while i <= #data do
|
|
local d0 = string.byte(data, i)
|
|
if d0 == 0xFD then
|
|
local d1 = string.byte(data, i + 1)
|
|
if not d1 then error("truncated escape sequence") end
|
|
output[#output + 1] = string.char(0xFD + d1)
|
|
i = i + 2
|
|
else
|
|
output[#output + 1] = string.char(d0)
|
|
i = i + 1
|
|
end
|
|
end
|
|
return table.concat(output)
|
|
end
|
|
|
|
---@param site integer
|
|
---@param encoder integer
|
|
---@return boolean
|
|
local function check_address(site, encoder)
|
|
if site == 0 and encoder == 0 then return true end
|
|
|
|
local site_allowed = false
|
|
if site ~= 0 then
|
|
for i, v in ipairs(uecp.site_addreses) do
|
|
if v == site then
|
|
site_allowed = true
|
|
break
|
|
end
|
|
end
|
|
else site_allowed = true end
|
|
|
|
local encoder_allowed = false
|
|
if encoder ~= 0 then
|
|
for i, v in ipairs(uecp.encoder_addreses) do
|
|
if v == encoder then
|
|
encoder_allowed = true
|
|
break
|
|
end
|
|
end
|
|
else encoder_allowed = true end
|
|
|
|
return site_allowed and encoder_allowed
|
|
end
|
|
|
|
---@param packet string
|
|
function uecp.parse_uecp(packet)
|
|
if not packet or #packet == 0 then return end
|
|
if string.byte(packet, 1) ~= 0xfe or string.byte(packet, #packet) ~= 0xff then return end
|
|
|
|
local unstuffed = undo_byte_stuff(string.sub(packet, 2, #packet - 1))
|
|
|
|
local addr1 = string.byte(unstuffed, 1)
|
|
local addr2 = string.byte(unstuffed, 2)
|
|
local site_addr = (addr1 << 2) | (addr2 >> 6)
|
|
|
|
if not check_address(site_addr, addr2 & 0x3F) then return end
|
|
|
|
local sequence_count = string.byte(unstuffed, 3)
|
|
local mfl = string.byte(unstuffed, 4)
|
|
|
|
local data = string.sub(unstuffed, 5, 4 + mfl)
|
|
|
|
if mfl ~= #data then
|
|
print("data len not right")
|
|
return
|
|
end
|
|
|
|
local crc_calculated = dp.crc16(string.sub(unstuffed, 1, 4 + mfl))
|
|
local crc_hi = string.byte(unstuffed, 4 + mfl + 1)
|
|
local crc_stored = (crc_hi << 8) | string.byte(unstuffed, 4 + mfl + 2)
|
|
|
|
if crc_calculated ~= crc_stored then
|
|
print("bad crc")
|
|
return
|
|
end
|
|
|
|
local consumed = 1
|
|
while consumed <= #data do
|
|
local mec = string.byte(data, consumed)
|
|
local handler = mec_handlers[mec]
|
|
if not handler then
|
|
print(string.format("unknown MEC 0x%02X, cannot continue", mec))
|
|
break
|
|
end
|
|
local advance = handler(string.sub(data, consumed))
|
|
consumed = consumed + advance
|
|
dp.set_writing_program(dp.get_output_program())
|
|
end
|
|
end |