0
1
mirror of https://github.com/radio95-rnt/rds95.git synced 2026-02-26 20:33:53 +01:00

do ert in lua

This commit is contained in:
2025-12-25 21:22:23 +01:00
parent 8fbbb00545
commit 751e107037
10 changed files with 106 additions and 336 deletions

View File

@@ -30,5 +30,6 @@
"jit": "disable",
"ffi": "disable",
"package": "disable"
}
},
"C_Cpp.dimInactiveRegions": false
}

View File

@@ -22,20 +22,49 @@ install(CODE
"
# Define the paths for the source and destination files
set(PREFIX_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/.script_prefix.lua\")
set(SCRIPTS_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/scripts\")
set(SCRIPT_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/src/script.lua\")
set(DEST_FILE \"/etc/rds95.lua\")
# Initialize content variable
set(FINAL_CONTENT \"\")
# Check if the optional prefix file exists
if(EXISTS \${PREFIX_FILE})
message(STATUS \"Prefix file found. Combining with script.lua.\")
message(STATUS \"Prefix file found. Adding .script_prefix.lua.\")
file(READ \${PREFIX_FILE} PREFIX_CONTENT)
file(READ \${SCRIPT_FILE} SCRIPT_CONTENT)
set(FINAL_CONTENT \"\${PREFIX_CONTENT}\n\${SCRIPT_CONTENT}\")
set(FINAL_CONTENT \"\${PREFIX_CONTENT}\")
else()
message(STATUS \"Prefix file not found. Using script.lua directly.\")
file(READ \${SCRIPT_FILE} FINAL_CONTENT)
message(STATUS \"Prefix file not found.\")
endif()
# Check if scripts directory exists and contains .lua files
if(EXISTS \${SCRIPTS_DIR} AND IS_DIRECTORY \${SCRIPTS_DIR})
file(GLOB LUA_SCRIPTS \"\${SCRIPTS_DIR}/*.lua\")
list(LENGTH LUA_SCRIPTS SCRIPT_COUNT)
if(SCRIPT_COUNT GREATER 0)
message(STATUS \"Found \${SCRIPT_COUNT} Lua script(s) in scripts directory.\")
list(SORT LUA_SCRIPTS)
foreach(LUA_SCRIPT \${LUA_SCRIPTS})
get_filename_component(SCRIPT_NAME \${LUA_SCRIPT} NAME)
message(STATUS \"Adding script: \${SCRIPT_NAME}\")
file(READ \${LUA_SCRIPT} SCRIPT_CONTENT)
set(FINAL_CONTENT \"\${FINAL_CONTENT}\n\${SCRIPT_CONTENT}\")
endforeach()
else()
message(STATUS \"No Lua scripts found in scripts directory.\")
endif()
else()
message(STATUS \"Scripts directory not found.\")
endif()
# Add the main script file
message(STATUS \"Adding main script.lua.\")
file(READ \${SCRIPT_FILE} MAIN_SCRIPT_CONTENT)
set(FINAL_CONTENT \"\${FINAL_CONTENT}\n\${MAIN_SCRIPT_CONTENT}\")
# Write the resulting content to the destination file
message(STATUS \"Installing script file to \${DEST_FILE}\")
file(WRITE \${DEST_FILE} \"\${FINAL_CONTENT}\")

View File

@@ -170,8 +170,10 @@ function set_rds_lps(lps) end
---@return string
function get_rds_lps() end
---This is implemented externally
---@param ert string
function set_rds_ert(ert) end
---This is implemented externally
---@return string
function get_rds_ert() end

257
readme.md
View File

@@ -25,263 +25,6 @@ The newer standard which is the IEC one, removes these features:
RDS95 is the only (as far as i can tell) encoder to transmit the 9-bit AF codes
## Commands
### PS
Sets the Program Service: `PS=* RDS *`
### PI
Sets the PI code: `PI=FFFF`
### TP
Sets the TP flag: `TP=1`
### TA
Sets the TA flag and triggers Traffic PS: `TA=0`
*May be overridden by EON*
### CT
Toggles the transmission of CT groups: `CT=1`
### AF
Sets the AF frequencies: `AF=95,89.1`
Clear the AF: `AF=`
### TPS
Sets the Traffic PS: `TPS=Traffic!` (default not set)
*TPS is transmitted instead of PS when TA flag is on*
### RT1
Sets the first radio text: `RT1=Currently Playing: Jessie Ware - Remember Where You Are` or `TEXT=Currently Playing: Jessie Ware - Remember Where You Are`
### RT2
Sets the second radio text: `RT2=Radio Nova - Best Hits around!`
### PTY
Sets the programme type flag: `PTY=11`
PTY values are diffrent for RDS and RDBS, look for them online
### ECC
Sets the extended country code: `ECC=E2`
*Note that the ECC is depended on the first letter of the PI, for example PI:3 and ECC:E2 is poland, but PI:1 would be the czech republic*
### RTP
TODO: RTP
### LPS
Sets the LPS: `LPS=NovaFM❤`
*Note that LPS does UTF-8, while PS, RT don't*
### ERT
Sets the ERT: `ERT=Currently on air we're playing: Lady Gaga - Applause`
*Note that ERT is a 128-character version of RT with UTF-8 support*
### AFO
Sets the AF frequencies for the ODA 9-bit version which enables AF for 64.1-88 MHz: `AFO=69.8,95.0,225` (LowerFM,FM,LF not sure if this even works)
Clear the AFO: `AFO=`
### TEXT
Alias for [RT1](#rt1)
### PTYN
Sets the programme type name: `PTYN=Football`
### DPTY
*Formerly DI*
Sets the DPTY flag: `DPTY=1`
### SLCD
The 1A group where ECC is sent can also be used to send broadcaster data: `SLCD=FFF`
### ERTP
This only will work if ERT is no longer than 64 characters
See [RTP](#rtp)
### LINK
Toggles the linkage bit in 1A groups, enable this if you have EON about a station and that station has EON about you: `LINK=1`
### SITE
Sets up to 2 site addresses: `SITE=44,95`
### G
Sends a custom group to the next group available: `G=F100FFFFFFFF` or `G=00002000AAAAAAAA` for RDS2
### RT1EN
Enables RT 1: `RT1EN=1`
### RT2EN
Enables RT 2: `RT2EN=1`
### RTPER
RT Switching period, in minutes: `RTPER=5`
### LEVEL
Sets the RDS output level: `LEVEL=255`
### PTYNEN
Enables PTYN transmission: `PTYEN=1`
### RTPRUN
Sets the RTP Running bit, to signal if the RTP data is accurate: `RTPRUN=1`
You can also toggle the state: `RTPRUN=1,1`
### GRPSEQ
Sets the group sequence for stream0, available groups:
- 0: 4 PSs
- 1: ECC
- 2: RT
- A: PTYN
- E: EON
- X: UDG1
- Y: UDG2
- R: RT+
- P: eRT+
- S: ERT
- 3: ODA
- F: LPS
- T: Fast tuning info
- U: ODA AF
`GRPSEQ=002222`
### RTTYPE
Sets the RT1/RT2 types of A/B:
- 0: Set to A
- 1: RT1 is A, RT2 is B
- 2: Default, just toggle A/B
### PROGRAM
Switches the current program, so diffrent saves: `PROGRAM=1`
### RDS2MOD
Sets the RDS2 operation mode:
- 0: Default, full tunnelling of stream 0
- 1: Independent tunelling, RDS2 runs a seperate group sequence
### GRPSEQ2
The Group Sequence for the RDS2 independent tunnelling mode
See [GRPSEQ](#grpseq)
### DTTMOUT
Default text timeout, once runs out it sets the RT1 which is saved in memory: `DTTMOUT=60` (1 hour)
### ERTPRUN
See [RTPRUN](#rtprun)
### INIT
Resets program to default settings, no arguments: `INIT`
### VER
If you have output, then it shows the version of the encoder
### RESET
Resets the internal state: `RESET`
### EONxEN
Enables the EON of x: `EON1EN=1`
### EONxPI
Sets the PI of EON x: `EON1PI=30FE`
### EONxPS
Sets the PS of EON x: `EON1PS=AFERA`
### EONxPTY
Sets the PTY of EON x: `EON1PTY=11`
### EONxTA
Enables the TA of EON x: `EON1TA=1`
### EONxTP
Sets the TP of EON x: `EON1TP=1`
### EONxAF
Sets the AF of EON x: `EON1AF=98.6,95.0`
### EONxDT
Sets the broadcaster data of EON x: `EON1DT=F`
### UDG1
Sets the user defined group, max 8 groups: `UDG1=6000FFFFFFFF`
### UDG2
See [UDG1](#udg1)
### 2UDG1
Sets the UDG1 of RDS2, max 8 groups expects 4 blocks: `2UDG1=0000200020202020`
### 2UDG2
See [2UDG1](#2udg1)
### RDSGEN
Sets the rds generator level:
- 0: No streams
- 1: Stream 0 only
- 2: Stream 0 and 1
`RDSGEN=1`
### Don't like the commands?
Implement them yourself then smartass, go on, look into the command.lua file
## Disclaimer
RDS95 is based on [Anthony96922](https://github.com/Anthony96922/)'s [MiniRDS](https://github.com/Anthony96922/MiniRDS)

66
scripts/ert.lua Normal file
View File

@@ -0,0 +1,66 @@
_Ert_state = 0
_Ert_oda_id = nil
local function init_ert()
if _Ert_oda_id == nil then
_Ert_oda_id = register_oda(12, false, 0x6552, 1)
set_oda_handler(_Ert_oda_id, function ()
if string.byte(get_userdata_offset(257, 1)) == 1 then
local new_data = get_userdata_offset(0, 128)
local new_segments = string.byte(get_userdata_offset(128, 1))
set_userdata_offset(129, 128, new_data)
set_userdata_offset(257, 1, string.char(new_segments))
set_userdata_offset(258, 1, string.char(0))
_Ert_state = 0
end
local segments = string.byte(get_userdata_offset(257, 1))
if segments == 0 then segments = 1 end
local b = _Ert_state & 31
local chunk = get_userdata_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 b, c, d
end)
end
end
function set_rds_ert(ert)
if #ert == 0 then
set_userdata_offset(128, 1, string.char(0))
set_userdata_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)
set_userdata_offset(0, 128, data)
local segments = #data // 4
if segments > 32 then segments = 32 end
if string.byte(get_userdata_offset(257, 1)) == 0 then
init_ert()
set_userdata_offset(129, 128, data)
set_userdata_offset(257, 1, string.char(segments))
else set_userdata_offset(258, 1, string.char(1)) end
set_userdata_offset(128, 1, string.char(segments))
end
function get_rds_ert()
local data = get_userdata_offset(0, 128)
if string.byte(get_userdata_offset(128, 1)) == 0 then return "" end
return data:match("^(.-)[\r%z]*") or ""
end
function on_state()
if string.byte(get_userdata_offset(257, 1)) ~= 0 then init_ert() end
end

View File

@@ -311,9 +311,6 @@ STR_SETTER(rt2, set_rds_rt2)
STR_RAW_SETTER(lps, set_rds_lps)
STR_RAW_GETTER(lps, LPS_LENGTH)
STR_RAW_SETTER(ert, set_rds_ert)
STR_RAW_GETTER(ert, ERT_LENGTH)
STR_RAW_SETTER(grp_sqc_rds2, set_rds_grpseq2)
STR_RAW_GETTER(grp_sqc_rds2, 24)
@@ -589,9 +586,6 @@ void init_lua(RDSModulator* rds_mod) {
lua_register(L, "set_rds_lps", lua_set_rds_lps);
lua_register(L, "get_rds_lps", lua_get_rds_lps);
lua_register(L, "set_rds_ert", lua_set_rds_ert);
lua_register(L, "get_rds_ert", lua_get_rds_ert);
lua_register(L, "set_rds_rtplus_tags", lua_set_rds_rtplus_tags);
lua_register(L, "get_rds_rtplus_tags", lua_get_rds_rtplus_tags);
lua_register(L, "toggle_rds_rtp", lua_toggle_rds_rtp);

View File

@@ -14,7 +14,6 @@ void get_rds_fasttuning_group(RDSEncoder* enc, RDSGroup *group);
void get_rds_rt_group(RDSEncoder* enc, RDSGroup *group);
void get_rdsp_rtp_oda_group(RDSGroup *group);
void get_rdsp_ertp_oda_group(RDSGroup *group);
void get_rdsp_ert_oda_group(RDSGroup *group);
void get_rdsp_oda_af_oda_group(RDSGroup *group);
void get_rds_oda_af_group(RDSEncoder* enc, RDSGroup *group);
void get_rdsp_ct_group(RDSGroup *group, time_t now);
@@ -25,7 +24,6 @@ void get_rds_ptyn_group(RDSEncoder* enc, RDSGroup *group);
void get_rds_rtplus_group(RDSEncoder* enc, RDSGroup *group);
void get_rds_ertplus_group(RDSEncoder* enc, RDSGroup *group);
void get_rds_eon_group(RDSEncoder* enc, RDSGroup *group);
void get_rds_ert_group(RDSEncoder* enc, RDSGroup *group);
uint8_t get_rds_custom_groups(RDSEncoder* enc, RDSGroup *group);
uint8_t get_rds_custom_groups2(RDSEncoder* enc, RDSGroup *group);
int get_rdsp_lua_group(RDSGroup *group);
@@ -100,11 +98,6 @@ static void get_rds_sequence_group(RDSEncoder* enc, RDSGroup *group, char* grp,
else get_rdsp_ertp_oda_group(group);
TOGGLE(enc->state[enc->program].ert_oda);
break;
case 'S':
if(enc->state[enc->program].ert_oda == 0) get_rds_ert_group(enc, group);
else get_rdsp_ert_oda_group(group);
TOGGLE(enc->state[enc->program].ert_oda);
break;
case 'F':
get_rds_lps_group(enc, group);
break;
@@ -145,8 +138,7 @@ static uint8_t check_rds_good_group(RDSEncoder* enc, char* grp) {
if(*grp == 'X' && enc->data[enc->program].udg1_len != 0) good_group = 1;
if(*grp == 'Y' && enc->data[enc->program].udg2_len != 0) good_group = 1;
if(*grp == 'R' && enc->rtpData[enc->program][0].enabled) good_group = 1;
if(*grp == 'P' && enc->rtpData[enc->program][1].enabled && (_strnlen(enc->data[enc->program].ert, 65) < 64)) good_group = 1;
if(*grp == 'S' && enc->data[enc->program].ert[0] != '\0') good_group = 1;
if(*grp == 'P' && enc->rtpData[enc->program][1].enabled) good_group = 1;
if(*grp == 'F' && enc->data[enc->program].lps[0] != '\0') good_group = 1;
if(*grp == 'T') good_group = 1;
if(*grp == 'L') good_group = 1;
@@ -364,7 +356,6 @@ void reset_rds_state(RDSEncoder* enc, uint8_t program) {
tempCoder.state[program].ptyn_ab = 1;
set_rds_rt1(&tempCoder, enc->data[program].rt1);
set_rds_rt2(&tempCoder, enc->data[program].rt2);
set_rds_ert(&tempCoder, enc->data[program].ert);
set_rds_ps(&tempCoder, enc->data[program].ps);
set_rds_tps(&tempCoder, enc->data[program].tps);
set_rds_ptyn(&tempCoder, enc->data[program].ptyn);

View File

@@ -1,6 +1,6 @@
#pragma once
#include "common.h"
#define LUA_USER_DATA 512
#define LUA_USER_DATA 768
/* The RDS error-detection code generator polynomial is
* x^10 + x^8 + x^7 + x^5 + x^4 + x^3 + x^0
@@ -14,7 +14,6 @@
#define RDS_SAMPLE_RATE 4750
#define RT_LENGTH 64
#define ERT_LENGTH 128
#define PS_LENGTH 8
#define PTYN_LENGTH 8
#define LPS_LENGTH 32
@@ -30,7 +29,6 @@
// List of ODAs: https://www.nrscstandards.org/committees/dsm/archive/rds-oda-aids.pdf
#define ODA_AID_RTPLUS 0x4bd7
#define ODA_AID_ERT 0x6552
#define ODA_AID_ERTPLUS 0x4BD8
#define ODA_AID_ODAAF 0x6365
@@ -79,10 +77,6 @@ typedef struct {
char default_rt[RT_LENGTH];
char rt2[RT_LENGTH];
uint8_t ert_switching_period;
uint8_t orignal_ert_switching_period;
char ert[ERT_LENGTH];
uint8_t ptyn_enabled : 1;
char ptyn[PTYN_LENGTH];
@@ -144,11 +138,6 @@ typedef struct {
uint8_t rt_segments : 5;
uint8_t rt2_segments : 5;
char ert_text[ERT_LENGTH];
uint8_t ert_state : 6;
uint8_t ert_update : 1;
uint8_t ert_segments : 6;
char ptyn_text[PTYN_LENGTH];
uint8_t ptyn_state : 1;
uint8_t ptyn_update : 1;
@@ -254,7 +243,6 @@ void set_rds_rt2(RDSEncoder* enc, const char *rt2);
void set_rds_ps(RDSEncoder* enc, const char *ps);
void set_rds_tps(RDSEncoder* enc, const char *tps);
void set_rds_lps(RDSEncoder* enc, const char *lps);
void set_rds_ert(RDSEncoder *enc, const char *ert);
void set_rds_rtplus_tags(RDSEncoder *enc, uint8_t *tags);
void set_rds_ertplus_tags(RDSEncoder *enc, uint8_t *tags);
void set_rds_ptyn(RDSEncoder *enc, const char *ptyn);

View File

@@ -148,13 +148,6 @@ void get_rdsp_ertp_oda_group(RDSGroup *group) {
group->d = ODA_AID_ERTPLUS;
}
void get_rdsp_ert_oda_group(RDSGroup *group) {
group->b |= 3 << 12;
group->b |= 12 << 1;
group->c = 1; // UTF-8
group->d = ODA_AID_ERT;
}
void get_rdsp_oda_af_oda_group(RDSGroup *group) {
group->b |= 3 << 12;
group->b |= 7 << 1;
@@ -326,22 +319,6 @@ void get_rds_eon_group(RDSEncoder* enc, RDSGroup *group) {
}
}
void get_rds_ert_group(RDSEncoder* enc, RDSGroup *group) {
if (enc->state[enc->program].ert_state == 0 && enc->state[enc->program].ert_update) {
memcpy(enc->state[enc->program].ert_text, enc->data[enc->program].ert, ERT_LENGTH);
enc->state[enc->program].ert_update = 0;
}
group->b |= 12 << 12 | (enc->state[enc->program].ert_state & 31);
group->c = enc->state[enc->program].ert_text[enc->state[enc->program].ert_state * 4] << 8;
group->c |= enc->state[enc->program].ert_text[enc->state[enc->program].ert_state * 4 + 1];
group->d = enc->state[enc->program].ert_text[enc->state[enc->program].ert_state * 4 + 2] << 8;
group->d |= enc->state[enc->program].ert_text[enc->state[enc->program].ert_state * 4 + 3];
enc->state[enc->program].ert_state++;
if (enc->state[enc->program].ert_state >= enc->state[enc->program].ert_segments) enc->state[enc->program].ert_state = 0;
}
uint8_t get_rds_custom_groups(RDSEncoder* enc, RDSGroup *group) {
if(enc->state[enc->program].custom_group[0] == 1) {
enc->state[enc->program].custom_group[0] = 0;

View File

@@ -87,27 +87,6 @@ void set_rds_lps(RDSEncoder* enc, const char *lps) {
if(enc->state[enc->program].lps_segments > 8) enc->state[enc->program].lps_segments = 8; //make sure
}
void set_rds_ert(RDSEncoder* enc, const char *ert) {
uint8_t i = 0, len = 0;
enc->state[enc->program].ert_update = 1;
memset(enc->data[enc->program].ert, ' ', ERT_LENGTH);
while (*ert != 0 && len < ERT_LENGTH) enc->data[enc->program].ert[len++] = *ert++;
while (len > 0 && enc->data[enc->program].ert[len - 1] == ' ') len--;
if (len < ERT_LENGTH) {
enc->state[enc->program].ert_segments = 0;
enc->data[enc->program].ert[len++] = '\r';
while (i < len) {
i += 4;
enc->state[enc->program].ert_segments++;
}
} else enc->state[enc->program].ert_segments = 32;
if(enc->state[enc->program].ert_segments > 32) enc->state[enc->program].ert_segments = 32; //make sure
}
inline void set_rds_rtplus_tags(RDSEncoder* enc, uint8_t *tags) {
enc->rtpData[enc->program][0].type[0] = tags[0] & 0x3f;
enc->rtpData[enc->program][0].start[0] = tags[1] & 0x3f;