mirror of
https://github.com/radio95-rnt/rds95.git
synced 2026-07-29 08:19:17 +02:00
ipc thing, and some more stuff i guess
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
"debug": "disable",
|
||||
"os": "disable",
|
||||
"jit": "disable",
|
||||
"ffi":"disable",
|
||||
"ffi":"disable"
|
||||
},
|
||||
"workspace.library": ["context.lua"],
|
||||
"diagnostics.disable": [
|
||||
|
||||
+27
-2
@@ -14,7 +14,7 @@ add_executable(rds95 ${SOURCES})
|
||||
find_package(Lua REQUIRED)
|
||||
|
||||
target_include_directories(rds95 PRIVATE ${LUA_INCLUDE_DIR})
|
||||
target_link_libraries(rds95 PRIVATE m pthread pulse pulse-simple inih ${LUA_LIBRARIES})
|
||||
target_link_libraries(rds95 PRIVATE m pthread inih ${LUA_LIBRARIES})
|
||||
|
||||
install(TARGETS rds95 DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
|
||||
|
||||
@@ -23,7 +23,9 @@ install(CODE
|
||||
# Define the paths for the source and destination files
|
||||
set(PREFIX_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/.script.lua\")
|
||||
set(SCRIPTS_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/scripts\")
|
||||
set(MODULES_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/modules\")
|
||||
set(DEST_FILE \"/etc/rds95.lua\")
|
||||
set(DEST_MODULES_DIR \"/etc/rds95\")
|
||||
|
||||
# Initialize content variable
|
||||
set(FINAL_CONTENT \"\")
|
||||
@@ -62,13 +64,36 @@ install(CODE
|
||||
# Write the resulting content to the destination file
|
||||
message(STATUS \"Installing script file to \${DEST_FILE}\")
|
||||
file(WRITE \${DEST_FILE} \"\${FINAL_CONTENT}\")
|
||||
|
||||
if(EXISTS \${MODULES_DIR} AND IS_DIRECTORY \${MODULES_DIR})
|
||||
file(MAKE_DIRECTORY \${DEST_MODULES_DIR})
|
||||
file(GLOB LUA_MODULES \"\${MODULES_DIR}/*.lua\")
|
||||
list(LENGTH LUA_MODULES MODULE_COUNT)
|
||||
|
||||
if(MODULE_COUNT GREATER 0)
|
||||
message(STATUS \"Installing \${MODULE_COUNT} Lua module(s) to \${DEST_MODULES_DIR}.\")
|
||||
foreach(LUA_MODULE \${LUA_MODULES})
|
||||
get_filename_component(MODULE_NAME \${LUA_MODULE} NAME)
|
||||
file(COPY \${LUA_MODULE} DESTINATION \${DEST_MODULES_DIR})
|
||||
message(STATUS \"Installed module: \${MODULE_NAME}\")
|
||||
endforeach()
|
||||
else()
|
||||
message(STATUS \"No Lua modules found in modules directory.\")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS \"Modules directory not found.\")
|
||||
endif()
|
||||
|
||||
# Change ownership to the user who invoked sudo (if applicable)
|
||||
if(DEFINED ENV{SUDO_USER})
|
||||
message(STATUS \"Changing ownership of \${DEST_FILE} to \$ENV{SUDO_USER}\")
|
||||
execute_process(COMMAND chown \$ENV{SUDO_USER}:\$ENV{SUDO_USER} \${DEST_FILE})
|
||||
if(EXISTS \${DEST_MODULES_DIR})
|
||||
message(STATUS \"Changing ownership of \${DEST_MODULES_DIR} to \$ENV{SUDO_USER}\")
|
||||
execute_process(COMMAND chown -R \$ENV{SUDO_USER}:\$ENV{SUDO_USER} \${DEST_MODULES_DIR})
|
||||
endif()
|
||||
else()
|
||||
message(STATUS \"No SUDO_USER detected, skipping chown\")
|
||||
endif()
|
||||
"
|
||||
)
|
||||
)
|
||||
|
||||
+9
-78
@@ -63,7 +63,7 @@ hooks.on_start = {}
|
||||
---@type function[]
|
||||
hooks.on_state = {}
|
||||
|
||||
---This function is called every second (or rather if the second has changed since the last stream 0 group)
|
||||
---This function is called every second
|
||||
---It should be defined by the user in the script.
|
||||
---This is a table of functions. Each will be called
|
||||
---@type function[]
|
||||
@@ -81,12 +81,18 @@ hooks.minute_tick = {}
|
||||
---@type function[]
|
||||
hooks.rt_transmission = {}
|
||||
|
||||
---This function is calld every time a PS transmits start to end
|
||||
---This function is called every time a PS transmits start to end
|
||||
---It should be defined by the user in the script.
|
||||
---This is a table of functions. Each will be called
|
||||
---@type function[]
|
||||
hooks.ps_transmission = {}
|
||||
|
||||
---This function is called before the next group is determined
|
||||
---It should be defined by the user in the script.
|
||||
---This is a table of functions. Each will be called
|
||||
---@type function[]
|
||||
hooks.pre_tx = {}
|
||||
|
||||
---This function is called in order to handle UDP data. The string returned is sent back to the UDP peer as a response
|
||||
---It should be defined by the user in the script.
|
||||
---@param data string
|
||||
@@ -272,79 +278,4 @@ function userdata.get() end
|
||||
---@param offset integer
|
||||
---@param size integer
|
||||
---@return string
|
||||
function userdata.get_offset(offset, size) end
|
||||
|
||||
---@class ext
|
||||
ext = {}
|
||||
---@class RDSext
|
||||
RDS.ext = {}
|
||||
|
||||
-- RT Plus Tags
|
||||
---Sets RT+ tags: type1, start1, len1, type2, start2, len2
|
||||
---@param ertp boolean
|
||||
---@param t1 integer
|
||||
---@param s1 integer
|
||||
---@param l1 integer
|
||||
---@param t2 integer
|
||||
---@param s2 integer
|
||||
---@param l2 integer
|
||||
function RDS.ext.set_rtplus_tags(ertp, t1, s1, l1, t2, s2, l2) end
|
||||
|
||||
---Gets RT+ tags: type1, start1, len1, type2, start2, len2
|
||||
---@param ertp boolean
|
||||
---@return integer type1, integer start1, integer len1, integer type2, integer start2, integer len2
|
||||
function RDS.ext.get_rtplus_tags(ertp) end
|
||||
|
||||
---Toggles RTP or ERTP's toggle switch
|
||||
---@param ertp boolean
|
||||
function RDS.ext.toggle_rtp(ertp) end
|
||||
|
||||
---Sets the metadata of RTP or ERTP
|
||||
---@param ertp boolean
|
||||
---@param running boolean
|
||||
function RDS.ext.set_rtp_meta(ertp, running) end
|
||||
---Gets the metadata of RTP and ERTP
|
||||
---@param ertp boolean
|
||||
---@return boolean running
|
||||
function RDS.ext.get_rtp_meta(ertp) end
|
||||
|
||||
---Sets the AFs included in the ODA
|
||||
---@param afs table
|
||||
function RDS.ext.set_af_oda(afs) end
|
||||
|
||||
---Registers an ODA to be used in the 0x6 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 Send AID once (no data) and destroy
|
||||
---@return integer oda_id
|
||||
function ext.register_oda(group, group_version, aid, data, temp) end
|
||||
|
||||
---Unregisters an ODA, this stops the handler or AID being called/sent
|
||||
---@param oda_id integer
|
||||
function ext.unregister_oda(oda_id) end
|
||||
|
||||
---Sets the data for a existing ODA group
|
||||
---@param oda_id integer
|
||||
---@param data integer
|
||||
function ext.set_oda_id_data(oda_id, data) end
|
||||
|
||||
---The callback function for an ODA handler
|
||||
---@alias ODAHandler fun(): (boolean, integer, integer, integer)
|
||||
|
||||
---Sets a function to handle the ODA data generation.
|
||||
---The handler is called when the group sequence '0xff' 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 B 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 inside block C)
|
||||
---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) end
|
||||
|
||||
---@param ert string
|
||||
function RDS.ext.set_ert(ert) end
|
||||
---@return string
|
||||
function RDS.ext.get_ert() end
|
||||
function userdata.get_offset(offset, size) end
|
||||
@@ -1,7 +1,11 @@
|
||||
_Af_Oda_id = nil
|
||||
_Af_Oda_state = 0
|
||||
_Af_Oda_len = 0
|
||||
_Af_Oda_afs = {}
|
||||
local oda = require("oda")
|
||||
---@class AfModule
|
||||
local af = {}
|
||||
|
||||
local _Af_Oda_id = nil
|
||||
local _Af_Oda_state = 0
|
||||
local _Af_Oda_len = 0
|
||||
local _Af_Oda_afs = {}
|
||||
|
||||
local USERDATA_ODA_OFFSET = 274
|
||||
|
||||
@@ -55,8 +59,8 @@ 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()
|
||||
_Af_Oda_id = oda.register_oda(7, false, 0x6365, 0, false)
|
||||
oda.set_oda_handler(_Af_Oda_id, function()
|
||||
local b, c, d = get_next_af_oda_group()
|
||||
return true, b, c, d
|
||||
end)
|
||||
@@ -106,7 +110,7 @@ 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)
|
||||
function af.set_af_oda(afs)
|
||||
_process_af_list(afs)
|
||||
save_af_to_userdata(afs)
|
||||
end
|
||||
@@ -114,4 +118,6 @@ end
|
||||
table.insert(hooks.on_state, function ()
|
||||
load_af_from_userdata()
|
||||
if _Af_Oda_len ~= 0 then init_af_oda() end
|
||||
end)
|
||||
end)
|
||||
|
||||
return af
|
||||
@@ -1,13 +1,17 @@
|
||||
_Ert_state = 0
|
||||
_Ert_oda_id = nil
|
||||
local oda = require("oda")
|
||||
---@class ErtModule
|
||||
local ert = {}
|
||||
|
||||
local _Ert_state = 0
|
||||
local _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 ()
|
||||
_Ert_oda_id = oda.register_oda(13, false, 0x6552, 1, false)
|
||||
oda.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))
|
||||
@@ -34,22 +38,22 @@ local function init_ert()
|
||||
end
|
||||
end
|
||||
|
||||
function unregister_ert()
|
||||
function ert.unregister_ert()
|
||||
if _Ert_oda_id ~= nil then
|
||||
ext.unregister_oda(_Ert_oda_id)
|
||||
oda.unregister_oda(_Ert_oda_id)
|
||||
_Ert_oda_id = nil
|
||||
end
|
||||
end
|
||||
|
||||
function RDS.ext.set_ert(ert)
|
||||
if #ert == 0 then
|
||||
function ert.set_ert(text)
|
||||
if #text == 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"
|
||||
local data = text .. "\r"
|
||||
data = string.sub(data, 1, 128)
|
||||
|
||||
local padding = (4 - (#data % 4)) % 4
|
||||
@@ -72,7 +76,7 @@ function RDS.ext.set_ert(ert)
|
||||
if _Ert_oda_id == nil then init_ert() end
|
||||
end
|
||||
|
||||
function RDS.ext.get_ert()
|
||||
function ert.get_ert()
|
||||
local segments = string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+128, 1))
|
||||
if segments == 0 then return "" end
|
||||
|
||||
@@ -82,4 +86,6 @@ end
|
||||
|
||||
table.insert(hooks.on_state, function ()
|
||||
if string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+257, 1)) ~= 0 then init_ert() end
|
||||
end)
|
||||
end)
|
||||
|
||||
return ert
|
||||
@@ -1,3 +1,5 @@
|
||||
---@class OdaModule
|
||||
local oda = {}
|
||||
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)
|
||||
@@ -18,7 +20,7 @@ local _RDS_ODA_pointer = 1
|
||||
---@param data integer
|
||||
---@param temp boolean
|
||||
---@return integer oda_id
|
||||
function ext.register_oda(group, group_version, aid, data, temp)
|
||||
function oda.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)
|
||||
@@ -34,7 +36,7 @@ end
|
||||
|
||||
---Unregisters an ODA, this stops the handler or AID being called/sent
|
||||
---@param oda_id integer
|
||||
function ext.unregister_oda(oda_id)
|
||||
function oda.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
|
||||
@@ -46,11 +48,13 @@ 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)
|
||||
function oda.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
|
||||
|
||||
---@alias ODAHandler fun(): (boolean, integer, integer, integer)
|
||||
|
||||
---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.
|
||||
@@ -58,7 +62,7 @@ end
|
||||
---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)
|
||||
function oda.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
|
||||
@@ -124,4 +128,6 @@ require("group").register_group("\x06\xff", group_handler)
|
||||
table.insert(hooks.on_state, function ()
|
||||
_RDS_ODAs = {}
|
||||
_RDS_ODA_pointer = 1
|
||||
end)
|
||||
end)
|
||||
|
||||
return oda
|
||||
@@ -1,15 +1,19 @@
|
||||
_Rtp_oda_id = nil
|
||||
_Ertp_oda_id = nil
|
||||
_Rtp_toggle = false
|
||||
_Ertp_toggle = false
|
||||
local oda = require("oda")
|
||||
---@class RtpModule
|
||||
local rtp = {}
|
||||
|
||||
local _Rtp_oda_id = nil
|
||||
local _Ertp_oda_id = nil
|
||||
local _Rtp_toggle = false
|
||||
local _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 ()
|
||||
_Rtp_oda_id = oda.register_oda(11, false, 0x4BD7, 0, false)
|
||||
oda.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)
|
||||
@@ -31,8 +35,8 @@ 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 ()
|
||||
_Ertp_oda_id = oda.register_oda(12, false, 0x4BD8, 0, false)
|
||||
oda.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)
|
||||
@@ -52,7 +56,7 @@ local function init_ertp()
|
||||
end
|
||||
end
|
||||
|
||||
function RDS.ext.set_rtp_meta(ertp, running)
|
||||
function rtp.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))
|
||||
@@ -61,35 +65,37 @@ function RDS.ext.set_rtp_meta(ertp, running)
|
||||
userdata.set_offset(USERDATA_RTP_OFFSET, 1, string.char(running and 1 or 0))
|
||||
end
|
||||
end
|
||||
function RDS.ext.get_rtp_meta(ertp)
|
||||
function rtp.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)
|
||||
function rtp.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)
|
||||
function rtp.set_rtplus_tags(ertp, t1, s1, l1, t2, s2, l2)
|
||||
rtp.set_rtp_meta(ertp, true)
|
||||
rtp.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)
|
||||
function rtp.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)
|
||||
function rtp.unregister_rtp(ertp)
|
||||
if ertp and _Ertp_oda_id ~= nil then
|
||||
ext.unregister_oda(_Ertp_oda_id)
|
||||
oda.unregister_oda(_Ertp_oda_id)
|
||||
_Ertp_oda_id = nil
|
||||
elseif _Rtp_oda_id ~= nil then
|
||||
ext.unregister_oda(_Rtp_oda_id)
|
||||
oda.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)
|
||||
if rtp.get_rtp_meta(false) then init_rtp() end
|
||||
if rtp.get_rtp_meta(true) then init_ertp() end
|
||||
end)
|
||||
|
||||
return rtp
|
||||
@@ -1,13 +1,15 @@
|
||||
uecp = {}
|
||||
---@class UecpModule
|
||||
local uecp = {}
|
||||
uecp.site_addreses = {}
|
||||
uecp.encoder_addreses = {}
|
||||
uecp.rt_buffer = {}
|
||||
uecp.rt_buffer_index = 1
|
||||
uecp.rt_tx_remaining = 0
|
||||
uecp.freedata_buffer = {}
|
||||
uecp.freedata_buffer_index = 1
|
||||
|
||||
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
|
||||
@@ -23,7 +25,8 @@ hooks.rt_transmission[#hooks.rt_transmission + 1] = function ()
|
||||
if entry.toggle_ab then RDS.toggle_rt_ab() end
|
||||
end
|
||||
|
||||
|
||||
---@param dsn integer
|
||||
---@param write function
|
||||
local function dsn_helper(dsn, write)
|
||||
if dsn == 0 then write()
|
||||
elseif dsn == 254 then
|
||||
@@ -47,7 +50,7 @@ local function dsn_helper(dsn, write)
|
||||
end
|
||||
|
||||
local mec_handlers = {}
|
||||
mec_handlers[1] = function(data)
|
||||
mec_handlers[0x01] = function(data)
|
||||
-- PI
|
||||
local dsn = string.byte(data, 2)
|
||||
local psn = string.byte(data, 3)
|
||||
@@ -59,7 +62,7 @@ mec_handlers[1] = function(data)
|
||||
end)
|
||||
return 5
|
||||
end
|
||||
mec_handlers[2] = function(data)
|
||||
mec_handlers[0x02] = function(data)
|
||||
-- PS
|
||||
local dsn = string.byte(data, 2)
|
||||
local psn = string.byte(data, 3)
|
||||
@@ -84,7 +87,7 @@ mec_handlers[0x21] = function(data)
|
||||
end)
|
||||
return 4 + mel
|
||||
end
|
||||
mec_handlers[4] = function(data)
|
||||
mec_handlers[0x04] = function(data)
|
||||
-- PTYI
|
||||
local dsn = string.byte(data, 2)
|
||||
local psn = string.byte(data, 3)
|
||||
@@ -94,7 +97,7 @@ mec_handlers[4] = function(data)
|
||||
end)
|
||||
return 4
|
||||
end
|
||||
mec_handlers[3] = function(data)
|
||||
mec_handlers[0x03] = function(data)
|
||||
-- TP/TA
|
||||
local dsn = string.byte(data, 2)
|
||||
local psn = string.byte(data, 3)
|
||||
@@ -105,7 +108,7 @@ mec_handlers[3] = function(data)
|
||||
end)
|
||||
return 4
|
||||
end
|
||||
mec_handlers[7] = function(data)
|
||||
mec_handlers[0x07] = function(data)
|
||||
-- PTY
|
||||
local dsn = string.byte(data, 2)
|
||||
local psn = string.byte(data, 3)
|
||||
@@ -218,21 +221,57 @@ 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?
|
||||
-- (yes it does tell me that :) mec 0x28)
|
||||
return 5
|
||||
end
|
||||
|
||||
hooks.pre_tx[#hooks.pre_tx+1] = function()
|
||||
if #uecp.freedata_buffer == 0 then return end
|
||||
|
||||
local entry = uecp.freedata_buffer[uecp.freedata_buffer_index]
|
||||
|
||||
uecp.freedata_buffer_index = uecp.freedata_buffer_index + 1
|
||||
if uecp.freedata_buffer_index > #uecp.freedata_buffer then
|
||||
uecp.freedata_buffer_index = 1
|
||||
end
|
||||
|
||||
RDS.put_custom_group(entry.b, entry.c, entry.d)
|
||||
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 buffer = (bufferdata >> 5) & 3
|
||||
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)
|
||||
local b, c, d = (group << 11) | blockb, (blockc_msb << 8) | blockc_lsb, (blockd_msb << 8) | blockd_lsb
|
||||
if buffer == 0 then
|
||||
RDS.put_custom_group(b, c, d)
|
||||
elseif buffer == 2 then
|
||||
uecp.freedata_buffer[#uecp.freedata_buffer + 1] = { b = b, c = c, d = d }
|
||||
elseif buffer == 3 then
|
||||
uecp.freedata_buffer = {}
|
||||
uecp.freedata_buffer_index = 1
|
||||
end
|
||||
return 7
|
||||
end
|
||||
|
||||
mec_handlers[0x40] = function(data)
|
||||
local group = string.byte(data, 2)
|
||||
local aid_msb = string.byte(data, 3)
|
||||
local aid_lsb = string.byte(data, 4)
|
||||
local buffer = string.byte(data, 5) & 3
|
||||
local msg_msb = string.byte(data, 6) -- 6
|
||||
local msg_lsb = string.byte(data, 7) -- 7
|
||||
local timeout = string.byte(data, 8)
|
||||
-- TODO:
|
||||
return 8
|
||||
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)
|
||||
@@ -385,7 +424,9 @@ function uecp.parse_uecp(packet)
|
||||
break
|
||||
end
|
||||
local advance = handler(string.sub(data, consumed))
|
||||
consumed = consumed + advance
|
||||
Data.set_writing_program(Data.get_output_program())
|
||||
consumed = consumed + advance
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return uecp
|
||||
@@ -33,12 +33,6 @@ Yet another unique feature, the Lua engine allows you to control the encoder, al
|
||||
|
||||
Every RDS2 subcarrier is in full control of Lua (except tunneling - core fills in PTY and the rest)
|
||||
|
||||
### RFT
|
||||
|
||||
For a long time there was no RFT in RDS95, but on 28th December 2025 that changed when i implemented it in lua, just call the load function with the path and id 0, and see the your station's logo in the decoder
|
||||
|
||||
And yes, it is implemented in only Lua and the ODA engine which is also in Lua
|
||||
|
||||
## Disclaimer
|
||||
|
||||
RDS95 is "based" on [Anthony96922](https://github.com/Anthony96922/)'s [MiniRDS](https://github.com/Anthony96922/MiniRDS) licensed under GPLv3
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "ipc_client.h"
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int ipc_connect(IPC_Client *client, const char *socket_path) {
|
||||
client->fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (client->fd < 0) { perror("fm95 ipc: socket"); return -1; }
|
||||
|
||||
struct sockaddr_un addr = { .sun_family = AF_UNIX };
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
|
||||
if (connect(client->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("fm95 ipc: connect");
|
||||
close(client->fd);
|
||||
client->fd = -1;
|
||||
return -1;
|
||||
}
|
||||
memset(client->prev_output, 0, sizeof(client->prev_output));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ipc_close(IPC_Client *client) {
|
||||
if (client->fd >= 0) close(client->fd);
|
||||
client->fd = -1;
|
||||
}
|
||||
|
||||
// Pulls one group's worth of bits (BITS_PER_GROUP), differentially encodes,
|
||||
// packs 8-to-a-byte, sends as opcode 112 with the stream index.
|
||||
int ipc_send_bits(IPC_Client *client, RDSEncoder *enc, uint8_t stream) {
|
||||
if (client->fd < 0) return -1;
|
||||
|
||||
uint8_t raw_bits[BITS_PER_GROUP];
|
||||
get_rds_bits(enc, raw_bits, stream);
|
||||
|
||||
uint8_t packed[(BITS_PER_GROUP + 7) / 8];
|
||||
memset(packed, 0, sizeof(packed));
|
||||
|
||||
uint8_t prev = client->prev_output[stream];
|
||||
for (int i = 0; i < BITS_PER_GROUP; i++) {
|
||||
uint8_t cur = prev ^ raw_bits[i]; // the differential step, moved here
|
||||
prev = cur;
|
||||
if (cur) packed[i / 8] |= (0x80 >> (i % 8));
|
||||
}
|
||||
client->prev_output[stream] = prev;
|
||||
|
||||
uint8_t msg[2 + sizeof(packed)];
|
||||
msg[0] = 112;
|
||||
msg[1] = stream;
|
||||
memcpy(msg + 2, packed, sizeof(packed));
|
||||
|
||||
ssize_t sent = send(client->fd, msg, sizeof(msg), 0);
|
||||
if (sent != (ssize_t)sizeof(msg)) {
|
||||
fprintf(stderr, "fm95 ipc: short/failed send\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t reply;
|
||||
if (recv(client->fd, &reply, 1, 0) <= 0) {
|
||||
fprintf(stderr, "fm95 ipc: no reply / disconnected\n");
|
||||
return -1;
|
||||
}
|
||||
if (reply == 2) fprintf(stderr, "fm95 reported bitring overrun on stream %d\n", stream);
|
||||
else if (reply == 1) fprintf(stderr, "fm95 rejected message (unknown opcode?)\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <stdint.h>
|
||||
#include "rds.h" // for RDSEncoder, get_rds_bits, BITS_PER_GROUP
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
uint8_t prev_output[4]; // per-stream differential state
|
||||
} IPC_Client;
|
||||
|
||||
int ipc_connect(IPC_Client *client, const char *socket_path);
|
||||
void ipc_close(IPC_Client *client);
|
||||
int ipc_send_bits(IPC_Client *client, RDSEncoder *enc, uint8_t stream);
|
||||
+2
-6
@@ -118,18 +118,14 @@ uint8_t init_lua(RDSEncoder* _enc) {
|
||||
lua_newtable(globalL);
|
||||
lua_setfield(globalL, -2, "ps_transmission");
|
||||
lua_newtable(globalL);
|
||||
lua_setfield(globalL, -2, "pre_tx");
|
||||
lua_newtable(globalL);
|
||||
lua_setfield(globalL, -2, "group");
|
||||
lua_pushvalue(globalL, -1);
|
||||
hooks_ref = luaL_ref(globalL, LUA_REGISTRYINDEX);
|
||||
lua_setglobal(globalL, "hooks");
|
||||
|
||||
lua_newtable(globalL);
|
||||
lua_setglobal(globalL, "ext");
|
||||
|
||||
lua_newtable(globalL);
|
||||
|
||||
lua_newtable(globalL);
|
||||
lua_setfield(globalL, -2, "ext");
|
||||
|
||||
lua_pushinteger(globalL, EONs);
|
||||
lua_setfield(globalL, -2, "eon_count");
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "modulator.h"
|
||||
#include "fs.h"
|
||||
|
||||
void init_rds_modulator(RDSModulator* rdsMod, RDSEncoder* enc, uint8_t num_streams) {
|
||||
memset(rdsMod, 0, sizeof(RDSModulator));
|
||||
rdsMod->num_streams = num_streams;
|
||||
|
||||
memset(enc, 0, sizeof(RDSEncoder));
|
||||
rdsMod->enc = enc;
|
||||
|
||||
rdsMod->data = (RDSModulatorModulationData*)calloc(num_streams, sizeof(RDSModulatorModulationData));
|
||||
if (rdsMod->data == NULL) {
|
||||
fprintf(stderr, "Error: Could not allocate memory for RDS modulation data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < num_streams; i++) {
|
||||
rdsMod->data[i].symbol_shift = i * M_PI / 2.0f;
|
||||
rdsMod->data[i].bit_pos = BITS_PER_GROUP;
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup_rds_modulator(RDSModulator* rdsMod) {
|
||||
if (rdsMod->data) {
|
||||
free(rdsMod->data);
|
||||
rdsMod->data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
float get_rds_sample(RDSModulator* rdsMod, uint8_t stream) {
|
||||
if(stream >= rdsMod->num_streams) return 0.0f;
|
||||
|
||||
rdsMod->data[stream].phase += 1187.5 / RDS_SAMPLE_RATE;
|
||||
|
||||
if (rdsMod->data[stream].phase >= 1.0f) {
|
||||
rdsMod->data[stream].phase -= 1.0f;
|
||||
if (rdsMod->data[stream].bit_pos == BITS_PER_GROUP) {
|
||||
get_rds_bits(rdsMod->enc, rdsMod->data[stream].bit_buffer, stream);
|
||||
rdsMod->data[stream].bit_pos = 0;
|
||||
}
|
||||
|
||||
rdsMod->data[stream].cur_bit = rdsMod->data[stream].bit_buffer[rdsMod->data[stream].bit_pos++];
|
||||
rdsMod->data[stream].prev_output = rdsMod->data[stream].cur_output;
|
||||
rdsMod->data[stream].cur_output = rdsMod->data[stream].prev_output ^ rdsMod->data[stream].cur_bit; // Something with differential encoding? Instead of encoding the data, encode if the bit of data changes
|
||||
}
|
||||
|
||||
float sample = sinf(M_2PI * rdsMod->data[stream].phase + rdsMod->data[stream].symbol_shift);
|
||||
if(rdsMod->data[stream].cur_output == 0) sample = -sample; // do bpsk, if you comment this part out, nothing will be decoded
|
||||
|
||||
return sample;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
#include "rds.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t bit_buffer[BITS_PER_GROUP];
|
||||
uint8_t bit_pos : 7;
|
||||
uint8_t prev_output : 1;
|
||||
uint8_t cur_output : 1;
|
||||
uint8_t cur_bit : 1;
|
||||
float symbol_shift;
|
||||
float phase;
|
||||
} RDSModulatorModulationData;
|
||||
|
||||
typedef struct {
|
||||
RDSModulatorModulationData *data;
|
||||
RDSEncoder* enc;
|
||||
uint8_t num_streams;
|
||||
} RDSModulator;
|
||||
|
||||
void init_rds_modulator(RDSModulator* rdsMod, RDSEncoder* enc, uint8_t num_streams);
|
||||
void cleanup_rds_modulator(RDSModulator* rdsMod);
|
||||
float get_rds_sample(RDSModulator* rdsMod, uint8_t stream);
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "rds.h"
|
||||
#include "fs.h"
|
||||
#include "modulator.h"
|
||||
#include "lib.h"
|
||||
#include "lua_rds.h"
|
||||
#include <time.h>
|
||||
@@ -97,7 +96,7 @@ void get_rds_group(RDSEncoder* enc, RDSGroup *group, uint8_t stream) {
|
||||
time(&now);
|
||||
utc = gmtime(&now);
|
||||
|
||||
if(utc->tm_sec != enc->state[enc->program].last_second && stream == 0) {
|
||||
if(utc->tm_sec != enc->state[enc->program].last_second) {
|
||||
enc->state[enc->program].last_second = utc->tm_sec;
|
||||
lua_call_tfunction("tick");
|
||||
}
|
||||
@@ -112,6 +111,8 @@ void get_rds_group(RDSEncoder* enc, RDSGroup *group, uint8_t stream) {
|
||||
}
|
||||
}
|
||||
|
||||
lua_call_tfunction("pre_tx");
|
||||
|
||||
uint8_t good_group = 0;
|
||||
uint8_t grp_sqc_idx = 0;
|
||||
char grp;
|
||||
|
||||
+10
-24
@@ -2,13 +2,11 @@
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
#include <pthread.h>
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
#include "../inih/ini.h"
|
||||
|
||||
#include "rds.h"
|
||||
#include "ipc_client.h"
|
||||
#include "fs.h"
|
||||
#include "modulator.h"
|
||||
#include "udp_server.h"
|
||||
#include "tcp_server.h"
|
||||
#include "lib.h"
|
||||
@@ -49,8 +47,7 @@ static inline void show_help(char *name) {
|
||||
);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
typedef struct {
|
||||
uint16_t udp_port;
|
||||
uint16_t tcp_port;
|
||||
char rds_device_name[48];
|
||||
@@ -201,28 +198,17 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
if(config.asciig == 0) {
|
||||
int pulse_error;
|
||||
IPC_Client client;
|
||||
if (ipc_connect(&client, "/etc/fm95/ctl.socket") < 0) goto exit;
|
||||
|
||||
float *rds_buffer = (float*)malloc(NUM_MPX_FRAMES * config.num_streams * sizeof(float));
|
||||
if (rds_buffer == NULL) {
|
||||
fprintf(stderr, "Error: Could not allocate memory for RDS buffer\n");
|
||||
goto exit;
|
||||
while (!stop_rds) {
|
||||
for (uint8_t s = 0; s < config.num_streams; s++) {
|
||||
ipc_send_bits(&client, &rdsEncoder, s);
|
||||
}
|
||||
msleep((int)(1000.0 * BITS_PER_GROUP / 1187.5));
|
||||
}
|
||||
|
||||
while(!stop_rds) {
|
||||
for (uint16_t i = 0; i < NUM_MPX_FRAMES * config.num_streams; i++) {
|
||||
uint8_t stream = i % config.num_streams;
|
||||
if((rdsEncoder.enabled_streams > stream ? 1 : 0) == 0) rds_buffer[i] = 0.0f;
|
||||
rds_buffer[i] = get_rds_sample(&rdsModulator, stream);
|
||||
}
|
||||
|
||||
if (pa_simple_write(rds_device, rds_buffer, NUM_MPX_FRAMES * config.num_streams * sizeof(float), &pulse_error) != 0) {
|
||||
fprintf(stderr, "Error: could not play audio. (%s : %d)\n", pa_strerror(pulse_error), pulse_error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(rds_buffer);
|
||||
ipc_close(&client);
|
||||
} else {
|
||||
RDSGroup group;
|
||||
char output_buffer[1024];
|
||||
|
||||
Reference in New Issue
Block a user