mirror of
https://github.com/radio95-rnt/rds95.git
synced 2026-07-29 16:29:17 +02:00
ipc thing, and some more stuff i guess
This commit is contained in:
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user