aboutsummaryrefslogtreecommitdiffstats
path: root/utils/cec-follower/cec-tuner.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/cec-follower/cec-tuner.cpp')
-rw-r--r--utils/cec-follower/cec-tuner.cpp284
1 files changed, 264 insertions, 20 deletions
diff --git a/utils/cec-follower/cec-tuner.cpp b/utils/cec-follower/cec-tuner.cpp
index e1d8b8fc..e3e64a58 100644
--- a/utils/cec-follower/cec-tuner.cpp
+++ b/utils/cec-follower/cec-tuner.cpp
@@ -4,7 +4,6 @@
*/
#include <array>
-#include <ctime>
#include <string>
#include <sys/ioctl.h>
@@ -17,6 +16,141 @@
#define TOT_ANALOG_FREQS analog_freqs_khz[0][0].size()
#define TOT_DIGITAL_CHANS digital_arib_data[0].size() + digital_atsc_data[0].size() + digital_dvb_data[0].size()
+enum Months { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };
+
+static struct Timer get_timer_from_message(const struct cec_msg &msg)
+{
+ struct Timer timer = {};
+
+ __u8 day = 0;
+ __u8 month = 0;
+ __u8 start_hr = 0;
+ __u8 start_min = 0;
+ __u8 duration_hr = 0;
+ __u8 duration_min = 0;
+ __u8 ext_src_spec = 0;
+ __u8 plug = 0;
+ __u16 phys_addr = 0;
+
+ switch (msg.msg[1]) {
+ case CEC_MSG_CLEAR_ANALOGUE_TIMER:
+ case CEC_MSG_SET_ANALOGUE_TIMER:
+ timer.src.type = CEC_OP_RECORD_SRC_ANALOG;
+ cec_ops_set_analogue_timer(&msg, &day, &month, &start_hr, &start_min,
+ &duration_hr, &duration_min, &timer.recording_seq,
+ &timer.src.analog.ana_bcast_type, &timer.src.analog.ana_freq,
+ &timer.src.analog.bcast_system);
+ break;
+ case CEC_MSG_CLEAR_DIGITAL_TIMER:
+ case CEC_MSG_SET_DIGITAL_TIMER: {
+ struct cec_op_digital_service_id digital = {};
+ timer.src.type = CEC_OP_RECORD_SRC_DIGITAL;
+ timer.src.digital = digital;
+ cec_ops_set_digital_timer(&msg, &day, &month, &start_hr, &start_min,
+ &duration_hr, &duration_min, &timer.recording_seq,
+ &timer.src.digital);
+ break;
+ }
+ case CEC_MSG_CLEAR_EXT_TIMER:
+ case CEC_MSG_SET_EXT_TIMER: {
+ cec_ops_set_ext_timer(&msg, &day, &month, &start_hr, &start_min,
+ &duration_hr, &duration_min, &timer.recording_seq, &ext_src_spec,
+ &plug, &phys_addr);
+ if (ext_src_spec == CEC_OP_EXT_SRC_PLUG) {
+ timer.src.type = CEC_OP_RECORD_SRC_EXT_PLUG;
+ timer.src.ext_plug.plug = plug;
+ }
+ if (ext_src_spec == CEC_OP_EXT_SRC_PHYS_ADDR) {
+ timer.src.type = CEC_OP_RECORD_SRC_EXT_PHYS_ADDR;
+ timer.src.ext_phys_addr.phys_addr = phys_addr;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ timer.duration = ((duration_hr * 60) + duration_min) * 60; /* In seconds. */
+
+ /* Use current time in the timer when it is not available from message e.g. year. */
+ time_t current_time = time(nullptr);
+ struct tm *temp = localtime(&current_time);
+ temp->tm_mday = day;
+ temp->tm_mon = month - 1; /* CEC months are 1-12 but struct tm range is 0-11. */
+ temp->tm_hour = start_hr;
+ temp->tm_min = start_min;
+ /*
+ * Timer precision is only to the minute. Set sec to 0 so that differences in seconds
+ * do not affect timer comparisons.
+ */
+ temp->tm_sec = 0;
+ temp->tm_isdst = -1;
+ timer.start_time = mktime(temp);
+
+ return timer;
+}
+
+static bool timer_date_out_of_range(const struct cec_msg &msg, const struct Timer &timer)
+{
+ __u8 day = msg.msg[2];
+ __u8 month = msg.msg[3];
+ /* Hours and minutes are in BCD format */
+ __u8 start_hr = (msg.msg[4] >> 4) * 10 + (msg.msg[4] & 0xf);
+ __u8 start_min = (msg.msg[5] >> 4) * 10 + (msg.msg[5] & 0xf);
+ __u8 duration_hr = (msg.msg[6] >> 4) * 10 + (msg.msg[6] & 0xf);
+ __u8 duration_min = (msg.msg[7] >> 4) * 10 + (msg.msg[7] & 0xf);
+
+ if (start_min > 59 || start_hr > 23 || month > 12 || month == 0 || day > 31 || day == 0 ||
+ duration_min > 59 || (duration_hr == 0 && duration_min == 0))
+ return true;
+
+ switch (month) {
+ case Apr: case Jun: case Sep: case Nov:
+ if (day > 30)
+ return true;
+ break;
+ case Feb: {
+ struct tm *tp = localtime(&timer.start_time);
+
+ if (!(tp->tm_year % 4) && ((tp->tm_year % 100) || !(tp->tm_year % 400))) {
+ if (day > 29)
+ return true;
+ } else {
+ if (day > 28)
+ return true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static bool timer_overlap(const struct Timer &new_timer)
+{
+ if (programmed_timers.size() == 1)
+ return false;
+
+ time_t new_timer_end = new_timer.start_time + new_timer.duration;
+ for (auto &t : programmed_timers) {
+
+ if (new_timer == t)
+ continue; /* Timer doesn't overlap itself. */
+
+ time_t existing_timer_end = t.start_time + t.duration;
+
+ if ((t.start_time < new_timer.start_time && new_timer.start_time < existing_timer_end) ||
+ (t.start_time < new_timer_end && new_timer_end < existing_timer_end) ||
+ (t.start_time == new_timer.start_time || existing_timer_end == new_timer_end) ||
+ (new_timer.start_time < t.start_time && existing_timer_end < new_timer_end))
+ return true;
+ }
+
+ return false;
+}
+
struct service_info {
unsigned tsid;
unsigned onid;
@@ -738,39 +872,149 @@ void process_tuner_record_timer_msgs(struct node *node, struct cec_msg &msg, uns
return;
- /*
- Timer Programming
-
- This is only a basic implementation.
-
- TODO/Ideas:
- - Act like an actual recording device; keep track of recording
- schedule and act correctly when colliding timers are set.
- - Emulate a finite storage space for recordings
- */
+ /* Timer Programming */
case CEC_MSG_SET_ANALOGUE_TIMER:
case CEC_MSG_SET_DIGITAL_TIMER:
- case CEC_MSG_SET_EXT_TIMER:
- if (!cec_has_record(1 << me))
+ case CEC_MSG_SET_EXT_TIMER: {
+ if (type != CEC_LOG_ADDR_TYPE_RECORD)
break;
+
+ __u8 prog_error = 0;
+ __u8 prog_info = 0;
+ __u8 timer_overlap_warning = CEC_OP_TIMER_OVERLAP_WARNING_NO_OVERLAP;
+ __u8 available_space_hr = 0;
+ __u8 available_space_min = 0;
+ struct Timer timer = get_timer_from_message(msg);
+
+ /* If timer starts in the past, increment the year so that timers can be set across year-end. */
+ if (time(nullptr) > timer.start_time) {
+ struct tm *temp = localtime(&timer.start_time);
+ temp->tm_year++;
+ temp->tm_isdst = -1;
+ timer.start_time = mktime(temp);
+ }
+
+ if (timer_date_out_of_range(msg, timer))
+ prog_error = CEC_OP_PROG_ERROR_DATE_OUT_OF_RANGE;
+
+ if (timer.recording_seq > 0x7f)
+ prog_error = CEC_OP_PROG_ERROR_REC_SEQ_ERROR;
+
+ if (programmed_timers.find(timer) != programmed_timers.end())
+ prog_error = CEC_OP_PROG_ERROR_DUPLICATE;
+
+ if (!prog_error) {
+ programmed_timers.insert(timer);
+
+ if (timer_overlap(timer))
+ timer_overlap_warning = CEC_OP_TIMER_OVERLAP_WARNING_OVERLAP;
+
+ if (node->state.media_space_available <= 0 ||
+ timer.duration > node->state.media_space_available) {
+ prog_info = CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE;
+ } else {
+ int space_that_may_be_needed = 0;
+ for (auto &t : programmed_timers) {
+ space_that_may_be_needed += t.duration;
+ if (t == timer) /* Only count the space up to and including the new timer. */
+ break;
+ }
+ if ((node->state.media_space_available - space_that_may_be_needed) >= 0)
+ prog_info = CEC_OP_PROG_INFO_ENOUGH_SPACE;
+ else
+ prog_info = CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE;
+ }
+ print_timers(node);
+ }
+
+ if (prog_info == CEC_OP_PROG_INFO_NOT_ENOUGH_SPACE ||
+ prog_info == CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE ||
+ prog_error == CEC_OP_PROG_ERROR_DUPLICATE) {
+ available_space_hr = node->state.media_space_available / 3600; /* 3600 MB/hour */
+ available_space_min = (node->state.media_space_available % 3600) / 60; /* 60 MB/min */
+ }
cec_msg_set_reply_to(&msg, &msg);
- cec_msg_timer_status(&msg, CEC_OP_TIMER_OVERLAP_WARNING_NO_OVERLAP,
- CEC_OP_MEDIA_INFO_NO_MEDIA,
- CEC_OP_PROG_INFO_ENOUGH_SPACE, 0, 0, 0);
+ cec_msg_timer_status(&msg, timer_overlap_warning, CEC_OP_MEDIA_INFO_UNPROT_MEDIA,
+ prog_info, prog_error, available_space_hr, available_space_min);
transmit(node, &msg);
return;
+ }
case CEC_MSG_CLEAR_ANALOGUE_TIMER:
case CEC_MSG_CLEAR_DIGITAL_TIMER:
- case CEC_MSG_CLEAR_EXT_TIMER:
- if (!cec_has_record(1 << me))
+ case CEC_MSG_CLEAR_EXT_TIMER: {
+ if (type != CEC_LOG_ADDR_TYPE_RECORD)
break;
+
+ __u8 timer_cleared_status = CEC_OP_TIMER_CLR_STAT_NO_MATCHING;
+
+ /* Look for timer in the previous year which have persisted across year-end. */
+ struct Timer timer_in_previous_year = get_timer_from_message(msg);
+ struct tm *temp = localtime(&timer_in_previous_year.start_time);
+ temp->tm_year--;
+ temp->tm_isdst = -1;
+ timer_in_previous_year.start_time = mktime(temp);
+ auto it_previous_year = programmed_timers.find(timer_in_previous_year);
+
+ if (it_previous_year != programmed_timers.end()) {
+ if (node->state.one_touch_record_on && it_previous_year == programmed_timers.begin()) {
+ timer_cleared_status = CEC_OP_TIMER_CLR_STAT_RECORDING;
+ node->state.one_touch_record_on = false;
+ } else {
+ timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
+ }
+ programmed_timers.erase(timer_in_previous_year);
+ print_timers(node);
+ }
+
+ /* Look for timer in the current year. */
+ struct Timer timer_in_current_year = get_timer_from_message(msg);
+ auto it_current_year = programmed_timers.find(timer_in_current_year);
+
+ if (it_current_year != programmed_timers.end()) {
+ if (node->state.one_touch_record_on && it_current_year == programmed_timers.begin()) {
+ timer_cleared_status = CEC_OP_TIMER_CLR_STAT_RECORDING;
+ node->state.one_touch_record_on = false;
+ } else {
+ /* Do not overwrite status if already set. */
+ if (timer_cleared_status == CEC_OP_TIMER_CLR_STAT_NO_MATCHING)
+ timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
+ }
+ programmed_timers.erase(timer_in_current_year);
+ print_timers(node);
+ }
+
+ /* Look for timer in the next year. */
+ struct Timer timer_in_next_year = get_timer_from_message(msg);
+ temp = localtime(&timer_in_next_year.start_time);
+ temp->tm_year++;
+ temp->tm_isdst = -1;
+ timer_in_next_year.start_time = mktime(temp);
+ if (programmed_timers.find(timer_in_next_year) != programmed_timers.end()) {
+ /* Do not overwrite status if already set. */
+ if (timer_cleared_status == CEC_OP_TIMER_CLR_STAT_NO_MATCHING)
+ timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
+ programmed_timers.erase(timer_in_next_year);
+ print_timers(node);
+ }
cec_msg_set_reply_to(&msg, &msg);
- cec_msg_timer_cleared_status(&msg, CEC_OP_TIMER_CLR_STAT_CLEARED);
+ cec_msg_timer_cleared_status(&msg, timer_cleared_status);
transmit(node, &msg);
+ /*
+ * If the cleared timer was recording, and standby was received during recording,
+ * enter standby when the recording stops unless recording device is the active source.
+ */
+ if (timer_cleared_status == CEC_OP_TIMER_CLR_STAT_RECORDING) {
+ if (node->state.record_received_standby) {
+ if (node->phys_addr != node->state.active_source_pa)
+ enter_standby(node);
+ node->state.record_received_standby = false;
+ }
+ }
return;
+ }
case CEC_MSG_SET_TIMER_PROGRAM_TITLE:
- if (!cec_has_record(1 << me))
+ if (type != CEC_LOG_ADDR_TYPE_RECORD)
break;
return;
case CEC_MSG_TIMER_CLEARED_STATUS:

Privacy Policy