Re: [PATCH v3 1/2] cec: expand Timer Programming tests

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Deb,

I have a last set of much smaller comments. It's looking quite nice, but it
can be improved a bit more. It shouldn't take much time to make a v4, I think.

On 11/07/2021 02:37, Deborah Brouwer wrote:
> Check that Timer Status and Time Cleared Status replies have a valid
> operand. Send timers with out-of-range dates and check follower's
> response. Send an out-of-range recording sequence and check that the timer
> is not set. Send a duplicate timer and check that the timer is not set.
> Set overlapping timers and check that the timer overlap warning is set.
> In the follower, keep track of timers that have been received and warn
> if there may be insufficient space for a programmed recording.
> 
> Signed-off-by: Deborah Brouwer <deborahbrouwer3563@xxxxxxxxx>
> ---
>  utils/cec-compliance/cec-compliance.cpp |   1 +
>  utils/cec-compliance/cec-compliance.h   |   1 +
>  utils/cec-compliance/cec-test.cpp       | 512 ++++++++++++++++++++----
>  utils/cec-follower/cec-follower.cpp     |  54 +++
>  utils/cec-follower/cec-follower.h       |  43 ++
>  utils/cec-follower/cec-tuner.cpp        | 229 ++++++++++-
>  6 files changed, 746 insertions(+), 94 deletions(-)
> 
> diff --git a/utils/cec-compliance/cec-compliance.cpp b/utils/cec-compliance/cec-compliance.cpp
> index 25931259..72199762 100644
> --- a/utils/cec-compliance/cec-compliance.cpp
> +++ b/utils/cec-compliance/cec-compliance.cpp
> @@ -1236,6 +1236,7 @@ int main(int argc, char **argv)
>  	node.num_log_addrs = laddrs.num_log_addrs;
>  	memcpy(node.log_addr, laddrs.log_addr, laddrs.num_log_addrs);
>  	node.adap_la_mask = laddrs.log_addr_mask;
> +	node.current_time = time(nullptr);
>  
>  	printf("Find remote devices:\n");
>  	printf("\tPolling: %s\n", ok(poll_remote_devs(&node)));
> diff --git a/utils/cec-compliance/cec-compliance.h b/utils/cec-compliance/cec-compliance.h
> index 41e2d63d..efc828ce 100644
> --- a/utils/cec-compliance/cec-compliance.h
> +++ b/utils/cec-compliance/cec-compliance.h
> @@ -167,6 +167,7 @@ struct node {
>  	__u16 phys_addr;
>  	bool in_standby;
>  	__u8 prim_devtype;
> +	time_t current_time;
>  };
>  
>  struct remote_subtest {
> diff --git a/utils/cec-compliance/cec-test.cpp b/utils/cec-compliance/cec-test.cpp
> index 6c421eed..ff51c9ed 100644
> --- a/utils/cec-compliance/cec-test.cpp
> +++ b/utils/cec-compliance/cec-test.cpp
> @@ -13,6 +13,8 @@
>  
>  #include "cec-compliance.h"
>  
> +enum Months { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };
> +
>  struct remote_test {
>  	const char *name;
>  	const unsigned tags;
> @@ -112,6 +114,72 @@ static bool rec_status_is_a_valid_error_status(__u8 rec_status)
>  	}
>  }
>  
> +static int timer_status_is_valid(const struct cec_msg &msg)
> +{
> +	__u8 timer_overlap_warning;
> +	__u8 media_info;
> +	__u8 prog_info;
> +	__u8 prog_error;
> +	__u8 duration_hr;
> +	__u8 duration_min;
> +
> +	cec_ops_timer_status(&msg, &timer_overlap_warning, &media_info, &prog_info,
> +	                     &prog_error, &duration_hr, &duration_min);
> +	fail_on_test(media_info > CEC_OP_MEDIA_INFO_NO_MEDIA);
> +	if (prog_info)
> +		fail_on_test(prog_info < CEC_OP_PROG_INFO_ENOUGH_SPACE ||
> +		             prog_info > CEC_OP_PROG_INFO_MIGHT_NOT_BE_ENOUGH_SPACE);
> +	else
> +		fail_on_test(prog_error < CEC_OP_PROG_ERROR_NO_FREE_TIMER ||
> +		             (prog_error > CEC_OP_PROG_ERROR_CLOCK_FAILURE &&
> +		              prog_error != CEC_OP_PROG_ERROR_DUPLICATE));
> +
> +	return OK;
> +}
> +
> +static int timer_cleared_status_is_valid(const struct cec_msg &msg)
> +{
> +	__u8 timer_cleared_status;
> +
> +	cec_ops_timer_cleared_status(&msg, &timer_cleared_status);
> +	fail_on_test(timer_cleared_status != CEC_OP_TIMER_CLR_STAT_RECORDING &&
> +	             timer_cleared_status != CEC_OP_TIMER_CLR_STAT_NO_MATCHING &&
> +	             timer_cleared_status != CEC_OP_TIMER_CLR_STAT_NO_INFO &&
> +	             timer_cleared_status != CEC_OP_TIMER_CLR_STAT_CLEARED);
> +
> +	return OK;
> +}
> +
> +static int timer_is_set(const struct cec_msg &msg)

This should return a bool.

And actually you test for 'timer_has_error', which is a better name IMHO.

> +{
> +	__u8 timer_overlap_warning;
> +	__u8 media_info;
> +	__u8 prog_info;
> +	__u8 prog_error;
> +	__u8 duration_hr;
> +	__u8 duration_min;
> +
> +	cec_ops_timer_status(&msg, &timer_overlap_warning, &media_info, &prog_info,
> +	                     &prog_error, &duration_hr, &duration_min);
> +
> +	return prog_error;
> +}
> +
> +static int timer_overlap_warning_is_set(const struct cec_msg &msg)

bool

> +{
> +	__u8 timer_overlap_warning;
> +	__u8 media_info;
> +	__u8 prog_info;
> +	__u8 prog_error;
> +	__u8 duration_hr;
> +	__u8 duration_min;
> +
> +	cec_ops_timer_status(&msg, &timer_overlap_warning, &media_info, &prog_info,
> +	                     &prog_error, &duration_hr, &duration_min);
> +
> +	return !timer_overlap_warning;

And here you would drop the !, and the callers of this function have to be updated as well.

> +}
> +
>  /* System Information */
>  
>  int system_info_polling(struct node *node, unsigned me, unsigned la, bool interactive)
> @@ -1499,26 +1567,20 @@ static const vec_remote_subtests one_touch_rec_subtests{
>  
>  /* Timer Programming */
>  
> -/*
> -  TODO: These are very rudimentary tests which should be expanded.
> - */
> -
>  static int timer_prog_set_analog_timer(struct node *node, unsigned me, unsigned la, bool interactive)
>  {
> -	/* TODO: Check the timer status for possible errors, etc. */
> -
>  	struct cec_msg msg;
>  
>  	cec_msg_init(&msg, me, la);
> -	cec_msg_set_analogue_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> -				     CEC_OP_ANA_BCAST_TYPE_CABLE,
> -				     7668, // 479.25 MHz
> -				     node->remote[la].bcast_sys);
> +	/* Set timer to start tomorrow, at current time, for 2 hr, 30 min. */
> +	time_t tomorrow = node->current_time + (24 * 60 * 60);
> +	struct tm t = *(localtime(&tomorrow));
> +	t.tm_mon++; /* Month range in struct tm is 0-11, but month range in CEC is 1-12 */
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, t.tm_hour, t.tm_min, 2, 30,
> +	                           0x7f, CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
>  	fail_on_test(!transmit_timeout(node, &msg, 10000));
> -	if (timed_out(&msg)) {
> -		warn("Timed out waiting for Timer Status. Assuming timer was set.\n");
> -		return OK_PRESUMED;
> -	}
> +	fail_on_test(timed_out(&msg));
>  	if (unrecognized_op(&msg))
>  		return OK_NOT_SUPPORTED;
>  	if (refused(&msg))
> @@ -1526,13 +1588,13 @@ static int timer_prog_set_analog_timer(struct node *node, unsigned me, unsigned
>  	if (cec_msg_status_is_abort(&msg))
>  		return OK_PRESUMED;
>  
> -	return 0;
> +	fail_on_test(timer_status_is_valid(msg));
> +
> +	return OK;
>  }
>  
>  static int timer_prog_set_digital_timer(struct node *node, unsigned me, unsigned la, bool interactive)
>  {
> -	/* TODO: Check the timer status for possible errors, etc. */
> -
>  	struct cec_msg msg;
>  	struct cec_op_digital_service_id digital_service_id = {};
>  
> @@ -1541,77 +1603,76 @@ static int timer_prog_set_digital_timer(struct node *node, unsigned me, unsigned
>  	digital_service_id.channel.minor = 1;
>  	digital_service_id.dig_bcast_system = node->remote[la].dig_bcast_sys;
>  	cec_msg_init(&msg, me, la);
> -	cec_msg_set_digital_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> -				    &digital_service_id);
> +	/* Set timer to start 2 days from now, at current time, for 4 hr, 30 min. */
> +	time_t two_days_ahead = node->current_time + (2 * 24 * 60 * 60);
> +	struct tm t = *(localtime(&two_days_ahead));
> +	t.tm_mon++; /* Month range in struct tm is 0-11, but month range in CEC is 1-12 */
> +	cec_msg_set_digital_timer(&msg, true, t.tm_mday, t.tm_mon, t.tm_hour,
> +	                          t.tm_min, 4, 30, CEC_OP_REC_SEQ_ONCE_ONLY, &digital_service_id);

I would write this slightly differently:

	/* Set timer to start 2 days from current_time for 4 hr, 30 min. */
	time_t two_days_ahead = node->current_time + (2 * 24 * 60 * 60);
	struct tm *t = localtime(&two_days_ahead);
	cec_msg_set_digital_timer(&msg, true, t->tm_mday, t->tm_mon + 1, t->tm_hour, t->tm_min,
				  4, 30, CEC_OP_REC_SEQ_ONCE_ONLY, &digital_service_id);

It's a bit more concise, and having the explicit 't->tm_mon + 1' makes it IMHO clear enough
that CEC starts counting at 1, not 0, for the months, so no need for the additional comment.

The same can be done elsewhere.

>  	fail_on_test(!transmit_timeout(node, &msg, 10000));
> -	if (timed_out(&msg)) {
> -		warn("Timed out waiting for Timer Status. Assuming timer was set.\n");
> -		return OK_PRESUMED;
> -	}
> +	fail_on_test(timed_out(&msg));
>  	if (unrecognized_op(&msg))
>  		return OK_NOT_SUPPORTED;
>  	if (refused(&msg))
>  		return OK_REFUSED;
>  	if (cec_msg_status_is_abort(&msg))
>  		return OK_PRESUMED;
> +	fail_on_test(timer_status_is_valid(msg));
>  
>  	return 0;
>  }
>  
>  static int timer_prog_set_ext_timer(struct node *node, unsigned me, unsigned la, bool interactive)
>  {
> -	/* TODO: Check the timer status. */
> -
>  	struct cec_msg msg;
>  
>  	cec_msg_init(&msg, me, la);
> -	cec_msg_set_ext_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> -			      CEC_OP_EXT_SRC_PHYS_ADDR, 0, node->phys_addr);
> +	/* Set timer to start 3 days from now, at current time, for 6 hr, 30 min. */
> +	time_t three_days_ahead = node->current_time + (3 * 24 * 60 * 60);
> +	struct tm t = *(localtime(&three_days_ahead));
> +	t.tm_mon++; /* Month range in struct tm is 0-11, but month range in CEC is 1-12 */
> +	cec_msg_set_ext_timer(&msg, true, t.tm_mday, t.tm_mon, t.tm_hour, t.tm_min, 6, 30,
> +	                      CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_EXT_SRC_PHYS_ADDR, 0, node->phys_addr);
>  	fail_on_test(!transmit_timeout(node, &msg, 10000));
> -	if (timed_out(&msg)) {
> -		warn("Timed out waiting for Timer Status. Assuming timer was set.\n");
> -		return OK_PRESUMED;
> -	}
> +	fail_on_test(timed_out(&msg));
>  	if (unrecognized_op(&msg))
>  		return OK_NOT_SUPPORTED;
>  	if (refused(&msg))
>  		return OK_REFUSED;
>  	if (cec_msg_status_is_abort(&msg))
>  		return OK_PRESUMED;
> +	fail_on_test(timer_status_is_valid(msg));
>  
>  	return 0;
>  }
>  
>  static int timer_prog_clear_analog_timer(struct node *node, unsigned me, unsigned la, bool interactive)
>  {
> -	/* TODO: Check the timer cleared status. */
> -
>  	struct cec_msg msg;
>  
>  	cec_msg_init(&msg, me, la);
> -	cec_msg_clear_analogue_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> -				     CEC_OP_ANA_BCAST_TYPE_CABLE,
> -				     7668, // 479.25 MHz
> -				     node->remote[la].bcast_sys);
> +	/* Clear timer set to start tomorrow, at current time, for 2 hr, 30 min. */
> +	time_t tomorrow = node->current_time + (24 * 60 * 60);
> +	struct tm t = *(localtime(&tomorrow));
> +	t.tm_mon++; /* Month range in struct tm is 0-11, but month range in CEC is 1-12 */
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, t.tm_hour, t.tm_min, 2, 30,
> +	                             0x7f, CEC_OP_ANA_BCAST_TYPE_CABLE,7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
>  	fail_on_test(!transmit_timeout(node, &msg, 10000));
> -	if (timed_out(&msg)) {
> -		warn("Timed out waiting for Timer Cleared Status. Assuming timer was cleared.\n");
> -		return OK_PRESUMED;
> -	}
> +	fail_on_test(timed_out(&msg));
>  	if (unrecognized_op(&msg))
>  		return OK_NOT_SUPPORTED;
>  	if (refused(&msg))
>  		return OK_REFUSED;
>  	if (cec_msg_status_is_abort(&msg))
>  		return OK_PRESUMED;
> +	fail_on_test(timer_cleared_status_is_valid(msg));
>  
>  	return 0;
>  }
>  
>  static int timer_prog_clear_digital_timer(struct node *node, unsigned me, unsigned la, bool interactive)
>  {
> -	/* TODO: Check the timer cleared status. */
> -
>  	struct cec_msg msg;
>  	struct cec_op_digital_service_id digital_service_id = {};
>  
> @@ -1620,43 +1681,45 @@ static int timer_prog_clear_digital_timer(struct node *node, unsigned me, unsign
>  	digital_service_id.channel.minor = 1;
>  	digital_service_id.dig_bcast_system = node->remote[la].dig_bcast_sys;
>  	cec_msg_init(&msg, me, la);
> -	cec_msg_clear_digital_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> -				    &digital_service_id);
> +	/* Clear timer set to start 2 days from now, at current time, for 4 hr, 30 min. */
> +	time_t two_days_ahead = node->current_time + (2 * 24 * 60 * 60);
> +	struct tm t = *(localtime(&two_days_ahead));
> +	t.tm_mon++; /* Month range in struct tm is 0-11, but month range in CEC is 1-12 */
> +	cec_msg_clear_digital_timer(&msg, true, t.tm_mday, t.tm_mon, t.tm_hour, t.tm_min, 4, 30,
> +	                            CEC_OP_REC_SEQ_ONCE_ONLY, &digital_service_id);
>  	fail_on_test(!transmit_timeout(node, &msg, 10000));
> -	if (timed_out(&msg)) {
> -		warn("Timed out waiting for Timer Cleared Status. Assuming timer was cleared.\n");
> -		return OK_PRESUMED;
> -	}
> +	fail_on_test(timed_out(&msg));
>  	if (unrecognized_op(&msg))
>  		return OK_NOT_SUPPORTED;
>  	if (refused(&msg))
>  		return OK_REFUSED;
>  	if (cec_msg_status_is_abort(&msg))
>  		return OK_PRESUMED;
> +	fail_on_test(timer_cleared_status_is_valid(msg));
>  
>  	return 0;
>  }
>  
>  static int timer_prog_clear_ext_timer(struct node *node, unsigned me, unsigned la, bool interactive)
>  {
> -	/* TODO: Check the timer cleared status. */
> -
>  	struct cec_msg msg;
>  
>  	cec_msg_init(&msg, me, la);
> -	cec_msg_clear_ext_timer(&msg, true, 1, 1, 0, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> -				CEC_OP_EXT_SRC_PHYS_ADDR, 0, node->phys_addr);
> +	/* Clear timer set to start 3 days from now, at current time, for 6 hr, 30 min. */
> +	time_t three_days_ahead = node->current_time + (3 * 24 * 60 * 60);
> +	struct tm t = *(localtime(&three_days_ahead));
> +	t.tm_mon++; /* Month range in struct tm is 0-11, but month range in CEC is 1-12 */
> +	cec_msg_clear_ext_timer(&msg, true, t.tm_mday, t.tm_mon, t.tm_hour, t.tm_min, 6, 30,
> +	                        CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_EXT_SRC_PHYS_ADDR, 0, node->phys_addr);
>  	fail_on_test(!transmit_timeout(node, &msg, 10000));
> -	if (timed_out(&msg)) {
> -		warn("Timed out waiting for Timer Cleared Status. Assuming timer was cleared.\n");
> -		return OK_PRESUMED;
> -	}
> +	fail_on_test(timed_out(&msg));
>  	if (unrecognized_op(&msg))
>  		return OK_NOT_SUPPORTED;
>  	if (refused(&msg))
>  		return OK_REFUSED;
>  	if (cec_msg_status_is_abort(&msg))
>  		return OK_PRESUMED;
> +	fail_on_test(timer_cleared_status_is_valid(msg));
>  
>  	return 0;
>  }
> @@ -1676,37 +1739,338 @@ static int timer_prog_set_prog_title(struct node *node, unsigned me, unsigned la
>  	return OK_PRESUMED;
>  }
>  
> -static int timer_prog_timer_status(struct node *node, unsigned me, unsigned la, bool interactive)
> +static int timer_errors(struct node *node, unsigned me, unsigned la, bool interactive)
>  {
>  	struct cec_msg msg;
>  
> +	/* Day error: November 31, at 6:00 am, for 1 hr. */
>  	cec_msg_init(&msg, me, la);
> -	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);
> -	fail_on_test(!transmit_timeout(node, &msg));
> +	cec_msg_set_analogue_timer(&msg, true, 31, Nov, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
>  	if (unrecognized_op(&msg))
>  		return OK_NOT_SUPPORTED;
> -	if (refused(&msg))
> -		return OK_REFUSED;
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));

These tests are all very similar, except for different date/time etc. values.

Perhaps it is a good idea to make a helper function for this?

>  
> -	return OK_PRESUMED;
> +	/* Day error: December 32, at 6:00 am, for 1 hr. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 32, Dec, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Day error: 0, in January, at 6:00 am, for 1 hr. Day range begins at 1. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 0, Jan, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Month error: 0, on day 5, at 6:00 am, for 1 hr. CEC month range is 1-12. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 5, 0, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Month error: 13, on day 5, at 6:00 am, for 1 hr. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 5, 13, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Start hour error: 24 hr, on August 5, for 1 hr. Start hour range is 0-23. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 5, Aug, 24, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Start min error: 60 min, on August 5, for 1 hr. Start min range is 0-59. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 5, Aug, 0, 60, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Recording duration error: 0 hr, 0 min on August 5, at 6:00am. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 5, Aug, 6, 0, 0, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Duplicate timer error: January 24, at 6:00 am, for 1 hr. */

That should be January 25, right? Since below it says 25, not 24.

> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 25, Jan, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);

For this test I would use a date/time in the future (i.e. an offset from current_time).

You are actually creating a timer, so what if someone runs this at Jan 25, 5:59 am?

Better safe than sorry and just schedule this in 2 hours from now.

All the other tests with erroroneous values should fail without adding a new timer,
so hardcoding them is fine, but this duplicate timer test is an exception.

> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg)); /* The first timer should be set. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 25, Jan, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Clear the timer that was set to test duplicate timers. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, 25, Jan, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +	                             CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +
> +	/* Recording sequence error: 0xff, on August 5, at 6:00 am, for 1 hr. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, 5, Aug, 6, 0, 1, 0, 0xff, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	/* Error in last day of February, at 6:00 am, for 1 hr. */
> +	cec_msg_init(&msg, me, la);
> +	time_t now = node->current_time;
> +	struct tm t = *(localtime(&now));
> +	t.tm_mon++; /* Month range in struct tm is 0-11, but month range in CEC is 1-12. */
> +	if (t.tm_mon > Feb)
> +		t.tm_year++; /* The timer will be for next year. */
> +
> +	if (!(t.tm_year % 4) && ((t.tm_year % 100) || !(t.tm_year % 400))) {
> +		cec_msg_set_analogue_timer(&msg, true, 30, Feb, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +		                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +		                           node->remote[la].bcast_sys);
> +	} else {
> +		cec_msg_set_analogue_timer(&msg, true, 29, Feb, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY,
> +		                           CEC_OP_ANA_BCAST_TYPE_CABLE, 7668, // 479.25 MHz
> +		                           node->remote[la].bcast_sys);
> +	}
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out(&msg));
> +	if (cec_msg_status_is_abort(&msg))
> +		fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP);
> +	else
> +		fail_on_test(!timer_is_set(msg));
> +
> +	return OK;
>  }
>  
> -static int timer_prog_timer_clear_status(struct node *node, unsigned me, unsigned la, bool interactive)
> +static int timer_overlap_warning(struct node *node, unsigned me, unsigned la, bool interactive)
>  {
>  	struct cec_msg msg;
>  
> +	time_t tomorrow = node->current_time + (24 * 60 * 60);
> +	struct tm t = *(localtime(&tomorrow));
> +	t.tm_mon++; /* Month range in struct tm is 0-11, but month range in CEC is 1-12 */
> +
> +	/* No overlap: set timer for tomorrow at 8:00 am for 2 hr. */
>  	cec_msg_init(&msg, me, la);
> -	cec_msg_timer_cleared_status(&msg, CEC_OP_TIMER_CLR_STAT_CLEARED);
> -	fail_on_test(!transmit_timeout(node, &msg));
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 8, 0, 2, 0,
> +	                           CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
>  	if (unrecognized_op(&msg))
>  		return OK_NOT_SUPPORTED;
> -	if (refused(&msg))
> -		return OK_REFUSED;
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg));
> +	fail_on_test(!timer_overlap_warning_is_set(msg));
>  
> -	return OK_PRESUMED;
> +	/* No overlap, just adjacent: set timer for tomorrow at 10:00 am for 15 min. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 10, 0, 0, 15,
> +	                           CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg));
> +	fail_on_test(!timer_overlap_warning_is_set(msg));

These are also all very similar. Helper function?

> +
> +	/* No overlap, just adjacent: set timer for tomorrow at 7:45 am for 15 min. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 7, 45, 0, 15,
> +	                           CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg));
> +	fail_on_test(!timer_overlap_warning_is_set(msg));
> +
> +	/* Overlap tail end: set timer for tomorrow at 9:00 am for 2 hr. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 9, 0, 2, 0,
> +	                           CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg));
> +	fail_on_test(timer_overlap_warning_is_set(msg));
> +
> +	/* Overlap front end: set timer for tomorrow at 7:00 am for 1 hr, 30 min. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 7, 0, 1, 30,
> +	                           CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg));
> +	fail_on_test(timer_overlap_warning_is_set(msg));
> +
> +	/* Overlap same start time: set timer for tomorrow at 8:00 am for 30 min. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 8, 0, 0, 30,
> +	                           CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg));
> +	fail_on_test(timer_overlap_warning_is_set(msg));
> +
> +	/* Overlap same end time: set timer for tomorrow at 9:30 am for 30 min. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 9, 30, 0, 30,
> +	                           CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg));
> +	fail_on_test(timer_overlap_warning_is_set(msg));
> +
> +	/* Overlap all timers: set timer for tomorrow at 6:00 am for 6 hr. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_set_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 6, 0, 6, 0,
> +	                           CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                           7668, // 479.25 MHz
> +	                           node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +	fail_on_test(timer_is_set(msg));
> +	fail_on_test(timer_overlap_warning_is_set(msg));
> +
> +	/* Clear all the timers. */
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 8, 0, 2, 0,
> +	                             CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                             7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 10, 0, 0, 15,
> +	                             CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                             7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 7, 45, 0, 15,
> +	                             CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                             7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 9, 0, 2, 0,
> +	                             CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                             7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));

All very similar, helper function?

> +
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 7, 0, 1, 30,
> +	                             CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                             7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 8, 0, 0, 30,
> +	                             CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                             7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 9, 30, 0, 30,
> +	                             CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                             7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +
> +	cec_msg_init(&msg, me, la);
> +	cec_msg_clear_analogue_timer(&msg, true, t.tm_mday, t.tm_mon, 6, 0, 6, 0,
> +	                             CEC_OP_REC_SEQ_ONCE_ONLY, CEC_OP_ANA_BCAST_TYPE_CABLE,
> +	                             7668, // 479.25 MHz
> +	                             node->remote[la].bcast_sys);
> +	fail_on_test(!transmit_timeout(node, &msg, 10000));
> +	fail_on_test(timed_out_or_abort(&msg));
> +
> +	return OK;
>  }
>  
>  static const vec_remote_subtests timer_prog_subtests{
> @@ -1717,8 +2081,8 @@ static const vec_remote_subtests timer_prog_subtests{
>  	{ "Clear Analogue Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_clear_analog_timer },
>  	{ "Clear Digital Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_clear_digital_timer },
>  	{ "Clear External Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_clear_ext_timer },
> -	{ "Timer Status", CEC_LOG_ADDR_MASK_RECORD, timer_prog_timer_status },
> -	{ "Timer Cleared Status", CEC_LOG_ADDR_MASK_RECORD, timer_prog_timer_clear_status },
> +	{ "Set Timers with Errors", CEC_LOG_ADDR_MASK_RECORD, timer_errors },
> +	{ "Set Overlapping Timers", CEC_LOG_ADDR_MASK_RECORD, timer_overlap_warning },
>  };
>  
>  static const char *hec_func_state2s(__u8 hfs)
> diff --git a/utils/cec-follower/cec-follower.cpp b/utils/cec-follower/cec-follower.cpp
> index 2816fb85..b273b988 100644
> --- a/utils/cec-follower/cec-follower.cpp
> +++ b/utils/cec-follower/cec-follower.cpp
> @@ -47,6 +47,7 @@ bool show_msgs;
>  bool show_state;
>  bool show_warnings = true;
>  unsigned warnings;
> +std::set<struct Timer> programmed_timers;
>  
>  static struct option long_options[] = {
>  	{ "device", required_argument, nullptr, OptSetDevice },
> @@ -296,6 +297,58 @@ int cec_named_ioctl(int fd, const char *name,
>  	return retval == -1 ? e : (retval ? -1 : 0);
>  }
>  
> +void print_timers(struct node *node)
> +{
> +	if (show_info) {
> +		printf("Timers Set:\n");
> +		for (auto &t : programmed_timers) {
> +			std::string start = ctime(&t.start_time);
> +			time_t end_time = t.start_time + t.duration;
> +			std::string end = ctime(&end_time);
> +			/* Remove the seconds because timer is precise only to the minute. */
> +			start.erase(16, 3);
> +			end.erase(16, 3);
> +			/* Remove the new line characters. */
> +			start.erase(start.end() - 1);
> +			end.erase(end.end() - 1);
> +			/* Remove the start year if it is the same as the end year. */
> +			if ((start.compare(start.size() - 4, 5, end, end.size() - 4, 5) == 0))
> +				start.erase(start.size() - 5, 5);
> +			printf("\t%s - %s, ", start.c_str(), end.c_str());
> +			/* Find and print the source. */
> +			std::string source;
> +			switch (t.src.type) {
> +			case CEC_OP_RECORD_SRC_OWN:
> +				source = "own";
> +				break;
> +			case CEC_OP_RECORD_SRC_DIGITAL:
> +				source = "digital";
> +				break;
> +			case CEC_OP_RECORD_SRC_ANALOG:
> +				source = "analog";
> +				break;
> +			case CEC_OP_RECORD_SRC_EXT_PLUG:
> +				source = "ext plug";
> +				break;
> +			case CEC_OP_RECORD_SRC_EXT_PHYS_ADDR:
> +				source = "ext phy addr";
> +				break;
> +			default:
> +				break;
> +			}
> +			printf("source: %s, ", source.c_str());
> +			if (t.recording_seq)
> +				printf("rec-seq: 0x%x, ", t.recording_seq);
> +			printf("needs: %ld %s\n", t.duration, "MB."); /* 1MB per second. */
> +		}
> +		printf("Total media space available for recording: ");
> +		if (node->state.media_space_available >= 0)
> +			printf("%d MB.\n\n", node->state.media_space_available);
> +		else
> +			printf("0 MB.\n\n");
> +	}
> +}
> +
>  void state_init(struct node &node)
>  {
>  	if (options[OptStandby])
> @@ -319,6 +372,7 @@ void state_init(struct node &node)
>  	node.state.deck_skip_start = 0;
>  	node.state.one_touch_record_on = false;
>  	node.state.record_received_standby = false;
> +	node.state.media_space_available = 36000; /* In MB; space for 10 hours @ 1MB/sec */
>  	tuner_dev_info_init(&node.state);
>  	node.state.last_aud_rate_rx_ts = 0;
>  }
> diff --git a/utils/cec-follower/cec-follower.h b/utils/cec-follower/cec-follower.h
> index 833dec5e..69c96aa7 100644
> --- a/utils/cec-follower/cec-follower.h
> +++ b/utils/cec-follower/cec-follower.h
> @@ -19,12 +19,16 @@
>  
>  #include <cec-info.h>
>  #include <cec-log.h>
> +#include <set>
> +#include <ctime>
>  
>  extern bool show_info;
>  extern bool show_msgs;
>  extern bool show_state;
>  extern bool show_warnings;
>  extern unsigned warnings;
> +extern std::set<struct Timer> programmed_timers;
> +extern void print_timers(struct node *node);
>  
>  struct state {
>  	__u16 active_source_pa;
> @@ -55,6 +59,7 @@ struct state {
>  	__u64 deck_skip_start;
>  	bool one_touch_record_on;
>  	bool record_received_standby;
> +	int media_space_available;
>  	time_t toggle_power_status;
>  	__u64 last_aud_rate_rx_ts;
>  };
> @@ -82,6 +87,44 @@ struct node {
>  	unsigned short ignore_opcode[256];
>  };
>  
> +struct Timer {
> +	time_t start_time;
> +	time_t duration; /* In seconds. */
> +	__u8 recording_seq;
> +	struct cec_op_record_src src;
> +
> +	Timer()
> +	{
> +		start_time = 0;
> +		duration = 0;
> +		recording_seq = 0;
> +		src = {};
> +	}
> +
> +	Timer(const Timer& timer)
> +	{
> +		start_time = timer.start_time;
> +		duration = timer.duration;
> +		recording_seq = timer.recording_seq;
> +		src = timer.src;
> +	}
> +
> +	bool operator<(const Timer &r) const
> +	{
> +		return start_time < r.start_time ||
> +		       (start_time == r.start_time && duration < r.duration) ||
> +		       (start_time == r.start_time && duration == r.duration && src.type < r.src.type) ||
> +		       (start_time == r.start_time && duration == r.duration && src.type == r.src.type &&
> +		       recording_seq < r.recording_seq);
> +	}
> +
> +	bool operator==(const Timer &right) const
> +	{
> +		return start_time == right.start_time && duration == right.duration &&
> +		       src.type == right.src.type && recording_seq == right.recording_seq;
> +	}
> +};
> +
>  struct la_info {
>  	__u64 ts;
>  	struct {
> diff --git a/utils/cec-follower/cec-tuner.cpp b/utils/cec-follower/cec-tuner.cpp
> index e1d8b8fc..544cb662 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,147 @@
>  #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);
> +
> +	/* If timer starts in past, increment the year so that timers can be set across year-end. */
> +	if (current_time > timer.start_time) {
> +		temp.tm_year++;
> +		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 +878,88 @@ 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_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 {
> +				unsigned 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;
> +		struct Timer timer = get_timer_from_message(msg);
> +
> +		if (programmed_timers.find(timer) != programmed_timers.end()) {
> +			timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
> +			programmed_timers.erase(timer);
> +			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);
>  		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:
> 

Regards,

	Hans




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux