ipc thing, and some more stuff i guess

This commit is contained in:
2026-07-11 13:37:41 +02:00
parent 564ea1c20e
commit 5bad2b8200
17 changed files with 271 additions and 264 deletions
-127
View File
@@ -1,127 +0,0 @@
local _ODA = { group = 0, group_version = false, aid = 0, data = 0, handler = false, temp = false }
function _ODA.new(group, group_version, aid, data, handler, temp)
local instance = { group = group or 0, group_version = group_version or false, aid = aid or 0, data = data or 0, handler = handler or false, temp = temp or false }
setmetatable(instance, { __index = _ODA })
return instance
end
local _RDS_ODAs = {}
local _RDS_ODA_pointer = 1
---Registers an ODA to be used in the '\x06' of the group sequence. ODAs are stored as state data, thus running reset_rds will clear it
---Groups 14, 15, 2, 0 cannot be registered either version, groups 10, 4, 1 can be only registered as B, any other is free to take
---Group 3A will mean that there will be no group handler for this ODA, meaning it can only be interacted with via the 3A AID group, handler set is not possible with such groups
---@param group integer
---@param group_version boolean
---@param aid integer
---@param data integer
---@param temp boolean
---@return integer oda_id
function ext.register_oda(group, group_version, aid, data, temp)
if group == 14 or group == 15 or group == 2 or group == 0 then error("Group is incorrect", 2) end
if (group == 10 or group == 4 or group == 1) and group_version then error("Group is incorrect", 2) end
local oda = _ODA.new(group, group_version, aid, data, false, temp)
for i = 1, #_RDS_ODAs do
if _RDS_ODAs[i] == false then
_RDS_ODAs[i] = oda
return i
end
end
table.insert(_RDS_ODAs, oda)
return #_RDS_ODAs
end
---Unregisters an ODA, this stops the handler or AID being called/sent
---@param oda_id integer
function ext.unregister_oda(oda_id)
if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end
_RDS_ODAs[oda_id] = false
if _RDS_ODA_pointer == oda_id then _RDS_ODA_pointer = (_RDS_ODA_pointer % #_RDS_ODAs) + 1 end
end
---Sets the id_data for a existing ODA group
---@param oda_id integer
---@param data integer
function ext.set_oda_id_data(oda_id, data)
if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end
_RDS_ODAs[oda_id].data = data
end
---Sets a function to handle the ODA data generation.
---The handler is called when the group sequence '\xff' slot is processed.
---The function must return 3 integers representing RDS Blocks B, C, and D.
---Please note that you do not need to compute the block A to indentify the group and group version, that will be done for you and EVERY SINGLE group has PTY and TP inserted (and also PI if its a B)
---You are asked to set groups B last 5 bits, leave rest 0
---@param oda_id integer The ID returned by register_oda
---@param fun ODAHandler
function ext.set_oda_handler(oda_id, fun)
if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end
if _RDS_ODAs[oda_id].group == 3 then error("3A ODAs cannot have handlers.", 2) end
_RDS_ODAs[oda_id].handler = fun
end
local function get_aid()
local checked = 0
while checked < #_RDS_ODAs do
local oda = _RDS_ODAs[_RDS_ODA_pointer]
if oda ~= false then
local b = 3 << 12 | oda.group << 1 | (oda.group_version and 1 or 0)
local data, aid = oda.data, oda.aid
if oda.temp then _RDS_ODAs[_RDS_ODA_pointer] = false end
_RDS_ODA_pointer = (_RDS_ODA_pointer % #_RDS_ODAs) + 1
return true, b, data, aid
end
_RDS_ODA_pointer = (_RDS_ODA_pointer % #_RDS_ODAs) + 1
checked = checked + 1
end
return false, 0, 0, 0
end
local function get_data()
local checked_count = 0
while checked_count < #_RDS_ODAs do
local oda = _RDS_ODAs[_RDS_ODA_pointer]
if oda ~= false and type(oda.handler) == "function" and not oda.temp then
local ok, generated, b, c, d = pcall(oda.handler)
if ok and generated then
_RDS_ODA_pointer = (_RDS_ODA_pointer % #_RDS_ODAs) + 1
b = b | oda.group << 12
b = b | (oda.group_version and 1 or 0) << 11
return generated, b, c, d
end
end
_RDS_ODA_pointer = (_RDS_ODA_pointer % #_RDS_ODAs) + 1
checked_count = checked_count + 1
end
return false, 0, 0, 0
end
local function group_handler(group_type)
if #_RDS_ODAs == 0 then return false, 0, 0, 0 end
if _RDS_ODA_pointer > #_RDS_ODAs or _RDS_ODA_pointer < 1 then _RDS_ODA_pointer = 1 end
if group_type == "\x06" then return get_aid()
elseif group_type == "\xff" then return get_data()
else return false, 0, 0, 0 end
end
require("group").register_group("\x06\xff", group_handler)
table.insert(hooks.on_state, function ()
_RDS_ODAs = {}
_RDS_ODA_pointer = 1
end)
-117
View File
@@ -1,117 +0,0 @@
_Af_Oda_id = nil
_Af_Oda_state = 0
_Af_Oda_len = 0
_Af_Oda_afs = {}
local USERDATA_ODA_OFFSET = 274
---@param freq number
---@return table
local function encode_af(freq)
local out = {}
if(freq >= 87.6 and freq <= 107.9) then
table.insert(out, math.tointeger((freq * 10) - 875))
elseif freq >= 64.1 and freq <= 88.0 then
table.insert(out, math.tointeger((freq * 10) - 384))
elseif freq >= 153 and freq <= 279 then
table.insert(out, 250) -- LFMF incoming
table.insert(out, math.tointeger((freq - 153) / 9 + 1))
elseif freq >= 531 and freq <= 1602 then
table.insert(out, 250) -- LFMF incoming
table.insert(out, math.tointeger((freq - 531) / 9 + 16))
else error(string.format("Invalid AF frequency: %.1f", freq), 2) end
return out
end
local function get_next_af_oda_group()
local af = {0, 0, 0, 0}
local num_entries = #_Af_Oda_afs
local i = 1
if _Af_Oda_state == 0 then
af[1] = 224 + math.min(_Af_Oda_len, 25)
_Af_Oda_state = 1
i = 2
end
while i <= 4 do
if _Af_Oda_state <= num_entries then
af[i] = _Af_Oda_afs[_Af_Oda_state]
_Af_Oda_state = _Af_Oda_state + 1
else af[i] = 205 end
i = i + 1
end
if _Af_Oda_state > num_entries then _Af_Oda_state = 0 end
local b = (7 << 12)
for j = 1, 4 do
local bit8 = (math.tointeger(af[j]) >> 8) & 0x01
b = b | (bit8 << (j - 1))
end
return b, ((math.tointeger(af[1]) & 0xFF) << 8) | (math.tointeger(af[2]) & 0xFF), ((math.tointeger(af[3]) & 0xFF) << 8) | (math.tointeger(af[4]) & 0xFF)
end
local function init_af_oda()
if _Af_Oda_id == nil then
_Af_Oda_id = ext.register_oda(7, false, 0x6365, 0, false)
ext.set_oda_handler(_Af_Oda_id, function()
local b, c, d = get_next_af_oda_group()
return true, b, c, d
end)
end
end
local function save_af_to_userdata(afs)
local count = #afs
if count > 25 then count = 25 end
local payload = string.pack("B", count)
for i = 1, count do payload = payload .. string.pack("f", afs[i]) end
userdata.set_offset(USERDATA_ODA_OFFSET, #payload, payload)
end
local function _process_af_list(afs)
_Af_Oda_afs = {}
_Af_Oda_len = #afs
for _, f in ipairs(afs) do
local codes = encode_af(f)
for _, code in ipairs(codes) do
table.insert(_Af_Oda_afs, code)
end
end
_Af_Oda_state = 0
init_af_oda()
end
local function load_af_from_userdata()
local header = userdata.get_offset(USERDATA_ODA_OFFSET, 1)
if header == "" or header == nil then return end
local count = string.unpack("B", header)
if count == 0 or count > 25 then return end
local data = userdata.get_offset(USERDATA_ODA_OFFSET + 1, count * 4)
if #data < (count * 4) then return end
local afs = {}
for i = 1, count do
local val = string.unpack("f", data, (i-1)*4 + 1)
table.insert(afs, val)
end
_process_af_list(afs)
end
---Sets the AFs included in the ODA and saves them
---@param afs table List of numbers (e.g., {98.1, 102.5})
function RDS.ext.set_af_oda(afs)
_process_af_list(afs)
save_af_to_userdata(afs)
end
table.insert(hooks.on_state, function ()
load_af_from_userdata()
if _Af_Oda_len ~= 0 then init_af_oda() end
end)
-85
View File
@@ -1,85 +0,0 @@
_Ert_state = 0
_Ert_oda_id = nil
--- Size: 259 bytes
local USERDATA_ERT_OFFSET = 0
local function init_ert()
if _Ert_oda_id == nil then
_Ert_oda_id = ext.register_oda(13, false, 0x6552, 1, false)
ext.set_oda_handler(_Ert_oda_id, function ()
if string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+258, 1)) == 1 then
local new_data = userdata.get_offset(USERDATA_ERT_OFFSET, 128)
local new_segments = string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+128, 1))
userdata.set_offset(USERDATA_ERT_OFFSET+129, 128, new_data)
userdata.set_offset(USERDATA_ERT_OFFSET+257, 1, string.char(new_segments))
userdata.set_offset(USERDATA_ERT_OFFSET+258, 1, string.char(0))
_Ert_state = 0
end
local segments = string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+257, 1))
if segments == 0 then return false, 0, 0, 0 end
if _Ert_state >= segments then _Ert_state = 0 end
local b = _Ert_state & 31
local chunk = userdata.get_offset(USERDATA_ERT_OFFSET + 129 + _Ert_state * 4, 4)
local c = (string.byte(chunk, 1) << 8) | string.byte(chunk, 2)
local d = (string.byte(chunk, 3) << 8) | string.byte(chunk, 4)
_Ert_state = (_Ert_state + 1) % segments
return true, b, c, d
end)
end
end
function unregister_ert()
if _Ert_oda_id ~= nil then
ext.unregister_oda(_Ert_oda_id)
_Ert_oda_id = nil
end
end
function RDS.ext.set_ert(ert)
if #ert == 0 then
userdata.set_offset(USERDATA_ERT_OFFSET, 128, "")
userdata.set_offset(USERDATA_ERT_OFFSET+128, 1, string.char(0))
userdata.set_offset(USERDATA_ERT_OFFSET+258, 1, string.char(1))
return
end
local data = ert .. "\r"
data = string.sub(data, 1, 128)
local padding = (4 - (#data % 4)) % 4
data = data .. string.rep("\0", padding)
userdata.set_offset(USERDATA_ERT_OFFSET, 128, data)
local segments = #data // 4
if segments > 32 then segments = 32 end
userdata.set_offset(USERDATA_ERT_OFFSET+128, 1, string.char(segments))
if string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+257, 1)) == 0 then
init_ert()
userdata.set_offset(USERDATA_ERT_OFFSET+129, 128, data)
userdata.set_offset(USERDATA_ERT_OFFSET+257, 1, string.char(segments))
_Ert_state = 0
else userdata.set_offset(USERDATA_ERT_OFFSET+258, 1, string.char(1)) end
if _Ert_oda_id == nil then init_ert() end
end
function RDS.ext.get_ert()
local segments = string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+128, 1))
if segments == 0 then return "" end
local data = userdata.get_offset(USERDATA_ERT_OFFSET, 128)
return data:match("^(.-)[\r%z]*") or ""
end
table.insert(hooks.on_state, function ()
if string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+257, 1)) ~= 0 then init_ert() end
end)
-95
View File
@@ -1,95 +0,0 @@
_Rtp_oda_id = nil
_Ertp_oda_id = nil
_Rtp_toggle = false
_Ertp_toggle = false
--- Size: 15 bytes
local USERDATA_RTP_OFFSET = 259
local function init_rtp()
if _Rtp_oda_id == nil then
_Rtp_oda_id = ext.register_oda(11, false, 0x4BD7, 0, false)
ext.set_oda_handler(_Rtp_oda_id, function ()
local b = (_Rtp_toggle and 1 or 0) << 4 | string.byte(userdata.get_offset(USERDATA_RTP_OFFSET, 1)) << 3
local data_0 = userdata.get_offset(USERDATA_RTP_OFFSET+1, 3)
local data_1 = userdata.get_offset(USERDATA_RTP_OFFSET+4, 3)
b = b | (string.byte(data_0, 1) & 0xf8) >> 3
local c = (string.byte(data_0, 1) & 0x7) << 13
c = c | (string.byte(data_0, 2) & 0x3f) << 7
c = c | (string.byte(data_0, 3) & 0x3f) << 1
c = c | (string.byte(data_1, 1) & 0xe0) >> 5
local d = (string.byte(data_1, 1) & 0x1f) << 11
d = d | (string.byte(data_1, 2) & 0x3f) << 5
d = d | (string.byte(data_1, 3) & 0x1f)
return true, b, c, d
end)
end
end
local function init_ertp()
if _Ertp_oda_id == nil then
_Ertp_oda_id = ext.register_oda(12, false, 0x4BD8, 0, false)
ext.set_oda_handler(_Ertp_oda_id, function ()
local b = (_Ertp_toggle and 1 or 0) << 4 | string.byte(userdata.get_offset(USERDATA_RTP_OFFSET+7, 1)) << 3
local data_0 = userdata.get_offset(USERDATA_RTP_OFFSET+8, 3)
local data_1 = userdata.get_offset(USERDATA_RTP_OFFSET+11, 3)
b = b | (string.byte(data_0, 1) & 0xf8) >> 3
local c = (string.byte(data_0, 1) & 0x7) << 13
c = c | (string.byte(data_0, 2) & 0x3f) << 7
c = c | (string.byte(data_0, 3) & 0x3f) << 1
c = c | (string.byte(data_1, 1) & 0xe0) >> 5
local d = (string.byte(data_1, 1) & 0x1f) << 11
d = d | (string.byte(data_1, 2) & 0x3f) << 5
d = d | (string.byte(data_1, 3) & 0x1f)
return true, b, c, d
end)
end
end
function RDS.ext.set_rtp_meta(ertp, running)
if ertp then
if running and _Ertp_oda_id == nil then init_ertp() end
userdata.set_offset(USERDATA_RTP_OFFSET+7, 1, string.char(running and 1 or 0))
else
if running and _Rtp_oda_id == nil then init_rtp() end
userdata.set_offset(USERDATA_RTP_OFFSET, 1, string.char(running and 1 or 0))
end
end
function RDS.ext.get_rtp_meta(ertp)
local offset = ertp and (USERDATA_RTP_OFFSET+7) or USERDATA_RTP_OFFSET
return string.byte(userdata.get_offset(offset, 1)) ~= 0
end
function RDS.ext.toggle_rtp(ertp)
if ertp then _Ertp_toggle = not _Ertp_toggle
else _Rtp_toggle = not _Rtp_toggle end
end
function RDS.ext.set_rtplus_tags(ertp, t1, s1, l1, t2, s2, l2)
RDS.ext.set_rtp_meta(ertp, true)
RDS.ext.toggle_rtp(ertp)
userdata.set_offset(ertp and (USERDATA_RTP_OFFSET+8) or (USERDATA_RTP_OFFSET+1), 6, string.char(t1, s1, l1, t2, s2, l2))
end
function RDS.ext.get_rtplus_tags(ertp)
return string.byte(userdata.get_offset(ertp and (USERDATA_RTP_OFFSET+8) or (USERDATA_RTP_OFFSET+1), 6), 1, 6)
end
function unregister_rtp(ertp)
if ertp and _Ertp_oda_id ~= nil then
ext.unregister_oda(_Ertp_oda_id)
_Ertp_oda_id = nil
elseif _Rtp_oda_id ~= nil then
ext.unregister_oda(_Rtp_oda_id)
_Rtp_oda_id = nil
end
end
table.insert(hooks.on_state, function ()
if RDS.ext.get_rtp_meta(false) then init_rtp() end
if RDS.ext.get_rtp_meta(true) then init_ertp() end
end)
-391
View File
@@ -1,391 +0,0 @@
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 = Data.get_writing_program()
for i = 1, Data.max_programs do
local p = i - 1
if p ~= start then
Data.set_writing_program(p)
write()
end
end
elseif dsn == 255 then
for i = 1, Data.max_programs do
Data.set_writing_program(i - 1)
write()
end
else
Data.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.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.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.ta = (data & 1) ~= 0
RDS.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.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.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.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.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.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.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)
Data.set_output_program(dsn)
Data.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
pcall(hooks.parse_ascii, string.sub(data, 5, 4+mel))
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 = Data.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
Data.set_writing_program(Data.get_output_program())
end
end
+17 -12
View File
@@ -1,3 +1,8 @@
local af = require("af")
local ert = require("ert")
local rtp = require("rtp")
local uecp = require("uecp")
---@param data string
function hooks.parse_ascii(data)
if string.sub(data, 1, 4):lower() == "lua=" then
@@ -59,21 +64,21 @@ function hooks.parse_ascii(data)
elseif data == "rdsgen" then return string.format("RDSGEN=%s\r\n", string.format("%d", RDS.get_streams()))
elseif data == "link" then return string.format("LINK=%s\r\n", string.format("%d", (RDS.get_link() and 1 or 0)))
elseif data == "rtp" then
local t1, s1, l1, t2, s2, l2 = RDS.ext.get_rtplus_tags(false)
local t1, s1, l1, t2, s2, l2 = rtp.get_rtplus_tags(false)
return string.format("RTP=%d,%d,%d,%d,%d,%d\r\n", t1, s1, l1, t2, s2, l2)
elseif data == "ertp" then
local t1, s1, l1, t2, s2, l2 = RDS.ext.get_rtplus_tags(true)
local t1, s1, l1, t2, s2, l2 = rtp.get_rtplus_tags(true)
return string.format("ERTP=%d,%d,%d,%d,%d,%d\r\n", t1, s1, l1, t2, s2, l2)
elseif data == "rtprun" then
local running = RDS.ext.get_rtp_meta(false)
local running = rtp.get_rtp_meta(false)
local f1 = 2 or (running and 1 or 0)
return string.format("RTPRUN=%d\r\n", f1)
elseif data == "ertprun" then
local running = RDS.ext.get_rtp_meta(true)
local running = rtp.get_rtp_meta(true)
local f1 = 2 or (running and 1 or 0)
return string.format("ERTPRUN=%d\r\n", f1)
elseif data == "lps" then return string.format("LPS=%s\r\n", RDS.get_lps())
elseif data == "ert" then return string.format("ERT=%s\r\n", RDS.ext.get_ert())
elseif data == "ert" then return string.format("ERT=%s\r\n", ert.get_ert())
else
local eon_cmd, eon_num = data:match("^eon(%d+)([a-z]+)$")
if eon_cmd then
@@ -232,7 +237,7 @@ function hooks.parse_ascii(data)
RDS.set_lps(value)
return "+\r\n"
elseif cmd == "ert" then
RDS.ext.set_ert(value)
ert.set_ert(value)
return "+\r\n"
elseif cmd == "link" then
local link = tonumber(value)
@@ -252,7 +257,7 @@ function hooks.parse_ascii(data)
local t1, s1, l1, t2, s2, l2 = value:match("(%d+),(%d+),(%d+),(%d+),(%d+),(%d+)")
if not l2 then return "-\r\n" end
RDS.ext.set_rtplus_tags(
rtp.set_rtplus_tags(
is_ertp,
---@diagnostic disable-next-line: param-type-mismatch
tonumber(t1), tonumber(s1), tonumber(l1), tonumber(t2), tonumber(s2), tonumber(l2)
@@ -282,8 +287,8 @@ function hooks.parse_ascii(data)
local f2 = tonumber(f2_str) or 0
local running = (f1 & 1) ~= 0
RDS.ext.set_rtp_meta(is_ertp, running)
if f2 ~= 0 then RDS.ext.toggle_rtp(is_ertp) end
rtp.set_rtp_meta(is_ertp, running)
if f2 ~= 0 then rtp.toggle_rtp(is_ertp) end
return "+\r\n"
elseif cmd == "af" then
local af_table = {}
@@ -307,7 +312,7 @@ function hooks.parse_ascii(data)
local af_table = {}
if value == "" or value == "0" then
RDS.ext.set_af_oda({})
af.set_af_oda({})
return "+\r\n"
end
@@ -319,7 +324,7 @@ function hooks.parse_ascii(data)
if #af_table > 25 then return "-\r\n" end
RDS.ext.set_af_oda(af_table)
af.set_af_oda(af_table)
return "+\r\n"
else return "?\r\n" end
end
@@ -328,4 +333,4 @@ end
function hooks.data_handle(data)
if uecp.parse_uecp and string.byte(data, 1) == 0xfe then return uecp.parse_uecp(data)
else return hooks.parse_ascii(data) end
end
end