diff -Nru v4l-utils-1.21.0-202107050833/debian/changelog v4l-utils-1.21.0-202107130804/debian/changelog --- v4l-utils-1.21.0-202107050833/debian/changelog 2021-07-05 13:04:39.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/debian/changelog 2021-07-13 14:04:30.000000000 +0000 @@ -1,8 +1,8 @@ -v4l-utils (1.21.0-202107050833-202103201429~ubuntu20.10.1) groovy; urgency=low +v4l-utils (1.21.0-202107130804-202103201429~ubuntu20.10.1) groovy; urgency=low * Auto build. - -- Launchpad Package Builder Mon, 05 Jul 2021 13:04:39 +0000 + -- Launchpad Package Builder Tue, 13 Jul 2021 14:04:30 +0000 v4l-utils (1.21.0-1) experimental; urgency=medium diff -Nru v4l-utils-1.21.0-202107050833/debian/git-build-recipe.manifest v4l-utils-1.21.0-202107130804/debian/git-build-recipe.manifest --- v4l-utils-1.21.0-202107050833/debian/git-build-recipe.manifest 2021-07-05 13:04:39.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/debian/git-build-recipe.manifest 2021-07-13 14:04:30.000000000 +0000 @@ -1,3 +1,3 @@ -# git-build-recipe format 0.4 deb-version {debupstream}-202107050833-202103201429 -lp:~libv4l/libv4l/+git/v4l-utils git-commit:6ffc5248dede6285d76c5ec5680c316f68ff98ca +# git-build-recipe format 0.4 deb-version {debupstream}-202107130804-202103201429 +lp:~libv4l/libv4l/+git/v4l-utils git-commit:a4f2e3a6f306f0bef6664451b44d5a7a18b26803 nest packaging lp:~libv4l/+git/v4l-utils-packaging debian git-commit:930e229c52613c3dd8b51f8b463fc981558c5dc3 diff -Nru v4l-utils-1.21.0-202107050833/.pc/.quilt_patches v4l-utils-1.21.0-202107130804/.pc/.quilt_patches --- v4l-utils-1.21.0-202107050833/.pc/.quilt_patches 2021-07-05 13:04:39.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/.pc/.quilt_patches 2021-07-13 14:04:30.000000000 +0000 @@ -1 +1 @@ -/home/buildd/build-RECIPEBRANCHBUILD-2829581/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches +/home/buildd/build-RECIPEBRANCHBUILD-2834720/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches diff -Nru v4l-utils-1.21.0-202107050833/.pc/.quilt_series v4l-utils-1.21.0-202107130804/.pc/.quilt_series --- v4l-utils-1.21.0-202107050833/.pc/.quilt_series 2021-07-05 13:04:39.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/.pc/.quilt_series 2021-07-13 14:04:30.000000000 +0000 @@ -1 +1 @@ -/home/buildd/build-RECIPEBRANCHBUILD-2829581/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches/series +/home/buildd/build-RECIPEBRANCHBUILD-2834720/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches/series diff -Nru v4l-utils-1.21.0-202107050833/utils/cec-compliance/cec-compliance.cpp v4l-utils-1.21.0-202107130804/utils/cec-compliance/cec-compliance.cpp --- v4l-utils-1.21.0-202107050833/utils/cec-compliance/cec-compliance.cpp 2021-07-05 13:04:35.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-compliance/cec-compliance.cpp 2021-07-13 14:04:20.000000000 +0000 @@ -1236,6 +1236,7 @@ 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 -Nru v4l-utils-1.21.0-202107050833/utils/cec-compliance/cec-compliance.h v4l-utils-1.21.0-202107130804/utils/cec-compliance/cec-compliance.h --- v4l-utils-1.21.0-202107050833/utils/cec-compliance/cec-compliance.h 2021-07-05 13:04:35.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-compliance/cec-compliance.h 2021-07-13 14:04:20.000000000 +0000 @@ -167,6 +167,7 @@ __u16 phys_addr; bool in_standby; __u8 prim_devtype; + time_t current_time; }; struct remote_subtest { @@ -456,6 +457,11 @@ void testRemote(struct node *node, unsigned me, unsigned la, unsigned test_tags, bool interactive); +// cec-test-tuner-record-timer.cpp +extern const vec_remote_subtests tuner_ctl_subtests; +extern const vec_remote_subtests one_touch_rec_subtests; +extern const vec_remote_subtests timer_prog_subtests; + // cec-test-audio.cpp extern const vec_remote_subtests sac_subtests; extern const vec_remote_subtests dal_subtests; diff -Nru v4l-utils-1.21.0-202107050833/utils/cec-compliance/cec-test.cpp v4l-utils-1.21.0-202107130804/utils/cec-compliance/cec-test.cpp --- v4l-utils-1.21.0-202107050833/utils/cec-compliance/cec-test.cpp 2021-07-05 13:04:35.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-compliance/cec-test.cpp 2021-07-13 14:04:20.000000000 +0000 @@ -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; @@ -48,69 +50,6 @@ return OK; } -static int one_touch_rec_on_send(struct node *node, unsigned me, unsigned la, - const struct cec_op_record_src &rec_src, __u8 &rec_status) -{ - struct cec_msg msg; - - cec_msg_init(&msg, me, la); - cec_msg_record_off(&msg, false); - fail_on_test(!transmit_timeout(node, &msg)); - - cec_msg_init(&msg, me, la); - cec_msg_record_on(&msg, true, &rec_src); - /* Allow 10s for reply because the spec says it may take several seconds to accurately respond. */ - fail_on_test(!transmit_timeout(node, &msg, 10000)); - fail_on_test(timed_out_or_abort(&msg)); - cec_ops_record_status(&msg, &rec_status); - - return OK; -} - -static int one_touch_rec_on_send_invalid(struct node *node, unsigned me, unsigned la, - const struct cec_op_record_src &rec_src) -{ - struct cec_msg msg; - - cec_msg_init(&msg, me, la); - cec_msg_record_on(&msg, true, &rec_src); - fail_on_test(!transmit_timeout(node, &msg)); - fail_on_test(!cec_msg_status_is_abort(&msg)); - fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); - - return OK; -} - -/* - * Returns true if the Record Status is an error indicating that the - * request to start recording has failed. - */ -static bool rec_status_is_a_valid_error_status(__u8 rec_status) -{ - switch (rec_status) { - case CEC_OP_RECORD_STATUS_NO_DIG_SERVICE: - case CEC_OP_RECORD_STATUS_NO_ANA_SERVICE: - case CEC_OP_RECORD_STATUS_NO_SERVICE: - case CEC_OP_RECORD_STATUS_INVALID_EXT_PLUG: - case CEC_OP_RECORD_STATUS_INVALID_EXT_PHYS_ADDR: - case CEC_OP_RECORD_STATUS_UNSUP_CA: - case CEC_OP_RECORD_STATUS_NO_CA_ENTITLEMENTS: - case CEC_OP_RECORD_STATUS_CANT_COPY_SRC: - case CEC_OP_RECORD_STATUS_NO_MORE_COPIES: - case CEC_OP_RECORD_STATUS_NO_MEDIA: - case CEC_OP_RECORD_STATUS_PLAYING: - case CEC_OP_RECORD_STATUS_ALREADY_RECORDING: - case CEC_OP_RECORD_STATUS_MEDIA_PROT: - case CEC_OP_RECORD_STATUS_NO_SIGNAL: - case CEC_OP_RECORD_STATUS_MEDIA_PROBLEM: - case CEC_OP_RECORD_STATUS_NO_SPACE: - case CEC_OP_RECORD_STATUS_PARENTAL_LOCK: - case CEC_OP_RECORD_STATUS_OTHER: - return true; - default: - return false; - } -} /* System Information */ @@ -962,765 +901,6 @@ }, }; -/* Tuner Control */ - -static const char *bcast_type2s(__u8 bcast_type) -{ - switch (bcast_type) { - case CEC_OP_ANA_BCAST_TYPE_CABLE: - return "Cable"; - case CEC_OP_ANA_BCAST_TYPE_SATELLITE: - return "Satellite"; - case CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL: - return "Terrestrial"; - default: - return "Future use"; - } -} - -static int log_tuner_service(const struct cec_op_tuner_device_info &info, - const char *prefix = "") -{ - printf("\t\t%s", prefix); - - if (info.is_analog) { - double freq_mhz = (info.analog.ana_freq * 625) / 10000.0; - - printf("Analog Channel %.2f MHz (%s, %s)\n", freq_mhz, - bcast_system2s(info.analog.bcast_system), - bcast_type2s(info.analog.ana_bcast_type)); - - switch (info.analog.bcast_system) { - case CEC_OP_BCAST_SYSTEM_PAL_BG: - case CEC_OP_BCAST_SYSTEM_SECAM_LQ: - case CEC_OP_BCAST_SYSTEM_PAL_M: - case CEC_OP_BCAST_SYSTEM_NTSC_M: - case CEC_OP_BCAST_SYSTEM_PAL_I: - case CEC_OP_BCAST_SYSTEM_SECAM_DK: - case CEC_OP_BCAST_SYSTEM_SECAM_BG: - case CEC_OP_BCAST_SYSTEM_SECAM_L: - case CEC_OP_BCAST_SYSTEM_PAL_DK: - break; - default: - return fail("invalid analog bcast_system %u", info.analog.bcast_system); - } - if (info.analog.ana_bcast_type > CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL) - return fail("invalid analog bcast_type %u\n", info.analog.ana_bcast_type); - fail_on_test(!info.analog.ana_freq); - return 0; - } - - __u8 system = info.digital.dig_bcast_system; - - printf("%s Channel ", dig_bcast_system2s(system)); - if (info.digital.service_id_method) { - __u16 major = info.digital.channel.major; - __u16 minor = info.digital.channel.minor; - - switch (info.digital.channel.channel_number_fmt) { - case CEC_OP_CHANNEL_NUMBER_FMT_2_PART: - printf("%u.%u\n", major, minor); - break; - case CEC_OP_CHANNEL_NUMBER_FMT_1_PART: - printf("%u\n", minor); - break; - default: - printf("%u.%u\n", major, minor); - return fail("invalid service ID method\n"); - } - return 0; - } - - - switch (system) { - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T: { - __u16 tsid = info.digital.arib.transport_id; - __u16 sid = info.digital.arib.service_id; - __u16 onid = info.digital.arib.orig_network_id; - - printf("TSID: %u, SID: %u, ONID: %u\n", tsid, sid, onid); - break; - } - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: { - __u16 tsid = info.digital.atsc.transport_id; - __u16 pn = info.digital.atsc.program_number; - - printf("TSID: %u, Program Number: %u\n", tsid, pn); - break; - } - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T: { - __u16 tsid = info.digital.dvb.transport_id; - __u16 sid = info.digital.dvb.service_id; - __u16 onid = info.digital.dvb.orig_network_id; - - printf("TSID: %u, SID: %u, ONID: %u\n", tsid, sid, onid); - break; - } - default: - break; - } - - switch (system) { - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN: - warn_once("generic digital broadcast systems should not be used"); - break; - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T: - break; - default: - return fail("invalid digital broadcast system %u", system); - } - - if (info.digital.service_id_method > CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) - return fail("invalid service ID method %u\n", info.digital.service_id_method); - - return 0; -} - -static int tuner_ctl_test(struct node *node, unsigned me, unsigned la, bool interactive) -{ - struct cec_msg msg; - struct cec_op_tuner_device_info info = {}; - std::vector info_vec; - bool has_tuner = (1 << la) & (CEC_LOG_ADDR_MASK_TV | CEC_LOG_ADDR_MASK_TUNER); - int ret; - - cec_msg_init(&msg, me, la); - cec_msg_give_tuner_device_status(&msg, true, CEC_OP_STATUS_REQ_ONCE); - fail_on_test(!transmit_timeout(node, &msg)); - fail_on_test(!has_tuner && !timed_out_or_abort(&msg)); - if (!has_tuner) - return OK_NOT_SUPPORTED; - if (timed_out(&msg) || unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (cec_msg_status_is_abort(&msg)) - return OK_REFUSED; - - printf("\t Start Channel Scan\n"); - cec_ops_tuner_device_status(&msg, &info); - info_vec.push_back(info); - ret = log_tuner_service(info); - if (ret) - return ret; - - while (true) { - cec_msg_init(&msg, me, la); - cec_msg_tuner_step_increment(&msg); - fail_on_test(!transmit(node, &msg)); - fail_on_test(cec_msg_status_is_abort(&msg)); - if (cec_msg_status_is_abort(&msg)) { - fail_on_test(abort_reason(&msg) == CEC_OP_ABORT_UNRECOGNIZED_OP); - if (abort_reason(&msg) == CEC_OP_ABORT_REFUSED) { - warn("Tuner step increment does not wrap.\n"); - break; - } - - warn("Tuner at end of service list did not receive feature abort refused.\n"); - break; - } - cec_msg_init(&msg, me, la); - cec_msg_give_tuner_device_status(&msg, true, CEC_OP_STATUS_REQ_ONCE); - fail_on_test(!transmit_timeout(node, &msg)); - fail_on_test(timed_out_or_abort(&msg)); - memset(&info, 0, sizeof(info)); - cec_ops_tuner_device_status(&msg, &info); - if (!memcmp(&info, &info_vec[0], sizeof(info))) - break; - ret = log_tuner_service(info); - if (ret) - return ret; - info_vec.push_back(info); - } - printf("\t Finished Channel Scan\n"); - - printf("\t Start Channel Test\n"); - for (const auto &iter : info_vec) { - cec_msg_init(&msg, me, la); - log_tuner_service(iter, "Select "); - if (iter.is_analog) - cec_msg_select_analogue_service(&msg, iter.analog.ana_bcast_type, - iter.analog.ana_freq, iter.analog.bcast_system); - else - cec_msg_select_digital_service(&msg, &iter.digital); - fail_on_test(!transmit(node, &msg)); - fail_on_test(cec_msg_status_is_abort(&msg)); - cec_msg_init(&msg, me, la); - cec_msg_give_tuner_device_status(&msg, true, CEC_OP_STATUS_REQ_ONCE); - fail_on_test(!transmit_timeout(node, &msg)); - fail_on_test(timed_out_or_abort(&msg)); - memset(&info, 0, sizeof(info)); - cec_ops_tuner_device_status(&msg, &info); - if (memcmp(&info, &iter, sizeof(info))) { - log_tuner_service(info); - log_tuner_service(iter); - } - fail_on_test(memcmp(&info, &iter, sizeof(info))); - } - printf("\t Finished Channel Test\n"); - - cec_msg_init(&msg, me, la); - cec_msg_select_analogue_service(&msg, 3, 16000, 9); - printf("\t\tSelect invalid analog channel\n"); - fail_on_test(!transmit_timeout(node, &msg)); - fail_on_test(!cec_msg_status_is_abort(&msg)); - fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); - cec_msg_init(&msg, me, la); - info.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID; - info.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2; - info.digital.dvb.transport_id = 0; - info.digital.dvb.service_id = 0; - info.digital.dvb.orig_network_id = 0; - cec_msg_select_digital_service(&msg, &info.digital); - printf("\t\tSelect invalid digital channel\n"); - fail_on_test(!transmit_timeout(node, &msg)); - fail_on_test(!cec_msg_status_is_abort(&msg)); - fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); - - return 0; -} - -static const vec_remote_subtests tuner_ctl_subtests{ - { "Tuner Control", CEC_LOG_ADDR_MASK_TUNER | CEC_LOG_ADDR_MASK_TV, tuner_ctl_test }, -}; - -/* One Touch Record */ - -static int one_touch_rec_tv_screen(struct node *node, unsigned me, unsigned la, bool interactive) -{ - struct cec_msg msg; - - cec_msg_init(&msg, me, la); - cec_msg_record_tv_screen(&msg, true); - fail_on_test(!transmit_timeout(node, &msg)); - fail_on_test_v2(node->remote[la].cec_version, - node->remote[la].has_rec_tv && unrecognized_op(&msg)); - fail_on_test_v2(node->remote[la].cec_version, - !node->remote[la].has_rec_tv && !unrecognized_op(&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; - /* Follower should ignore this message if it is not sent by a recording device */ - if (node->prim_devtype != CEC_OP_PRIM_DEVTYPE_RECORD) { - fail_on_test(!timed_out(&msg)); - return OK; - } - fail_on_test(timed_out(&msg)); - - struct cec_op_record_src rec_src = {}; - - cec_ops_record_on(&msg, &rec_src); - - fail_on_test(rec_src.type < CEC_OP_RECORD_SRC_OWN || - rec_src.type > CEC_OP_RECORD_SRC_EXT_PHYS_ADDR); - - if (rec_src.type == CEC_OP_RECORD_SRC_DIGITAL) { - switch (rec_src.digital.dig_bcast_system) { - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2: - case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T: - break; - default: - return fail("Invalid digital service broadcast system operand.\n"); - } - - if (rec_src.digital.service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) - fail_on_test(rec_src.digital.channel.channel_number_fmt < CEC_OP_CHANNEL_NUMBER_FMT_1_PART || - rec_src.digital.channel.channel_number_fmt > CEC_OP_CHANNEL_NUMBER_FMT_2_PART); - } - - if (rec_src.type == CEC_OP_RECORD_SRC_ANALOG) { - fail_on_test(rec_src.analog.ana_bcast_type > CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL); - fail_on_test(rec_src.analog.bcast_system > CEC_OP_BCAST_SYSTEM_PAL_DK && - rec_src.analog.bcast_system != CEC_OP_BCAST_SYSTEM_OTHER); - fail_on_test(rec_src.analog.ana_freq == 0 || rec_src.analog.ana_freq == 0xffff); - } - - if (rec_src.type == CEC_OP_RECORD_SRC_EXT_PLUG) - fail_on_test(rec_src.ext_plug.plug == 0); - - return OK; -} - -static int one_touch_rec_on(struct node *node, unsigned me, unsigned la, bool interactive) -{ - struct cec_msg msg; - struct cec_op_record_src rec_src = {}; - - rec_src.type = CEC_OP_RECORD_SRC_OWN; - cec_msg_init(&msg, me, la); - cec_msg_record_on(&msg, true, &rec_src); - /* Allow 10s for reply because the spec says it may take several seconds to accurately respond. */ - fail_on_test(!transmit_timeout(node, &msg, 10000)); - fail_on_test(timed_out(&msg)); - if (unrecognized_op(&msg)) { - fail_on_test(node->remote[la].prim_type == CEC_OP_PRIM_DEVTYPE_RECORD); - return OK_NOT_SUPPORTED; - } - if (refused(&msg)) - return OK_REFUSED; - if (cec_msg_status_is_abort(&msg)) - return OK_PRESUMED; - - __u8 rec_status; - - cec_ops_record_status(&msg, &rec_status); - if (rec_status != CEC_OP_RECORD_STATUS_CUR_SRC) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - /* In the following tests, these digital services are taken from the cec-follower tuner emulation. */ - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; - rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID; - rec_src.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS; - rec_src.digital.arib.transport_id = 1032; - rec_src.digital.arib.service_id = 30203; - rec_src.digital.arib.orig_network_id = 1; - fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); - if (rec_status != CEC_OP_RECORD_STATUS_DIG_SERVICE) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; - rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; - rec_src.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T; - rec_src.digital.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_2_PART; - rec_src.digital.channel.major = 4; - rec_src.digital.channel.minor = 1; - fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); - if (rec_status != CEC_OP_RECORD_STATUS_DIG_SERVICE) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; - rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID; - rec_src.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T; - rec_src.digital.dvb.transport_id = 1004; - rec_src.digital.dvb.service_id = 1040; - rec_src.digital.dvb.orig_network_id = 8945; - fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); - if (rec_status != CEC_OP_RECORD_STATUS_DIG_SERVICE) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - /* In the following tests, these channels taken from the cec-follower tuner emulation. */ - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_STATUS_ANA_SERVICE; - rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_CABLE; - rec_src.analog.ana_freq = (471250 * 10) / 625; - rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_PAL_BG; - fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); - if (rec_status != CEC_OP_RECORD_STATUS_ANA_SERVICE) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_STATUS_ANA_SERVICE; - rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_SATELLITE; - rec_src.analog.ana_freq = (551250 * 10) / 625; - rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_SECAM_BG; - fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); - if (rec_status != CEC_OP_RECORD_STATUS_ANA_SERVICE) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_STATUS_ANA_SERVICE; - rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL; - rec_src.analog.ana_freq = (185250 * 10) / 625; - rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_PAL_DK; - fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); - if (rec_status != CEC_OP_RECORD_STATUS_ANA_SERVICE) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_EXT_PLUG; - rec_src.ext_plug.plug = 1; - fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); - if (rec_status != CEC_OP_RECORD_STATUS_EXT_INPUT) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_EXT_PHYS_ADDR; - fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); - if (rec_status != CEC_OP_RECORD_STATUS_EXT_INPUT) - fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); - - return OK; -} - -static int one_touch_rec_on_invalid(struct node *node, unsigned me, unsigned la, bool interactive) -{ - struct cec_msg msg; - - cec_msg_init(&msg, me, la); - cec_msg_record_on_own(&msg); - msg.msg[2] = 0; /* Invalid source operand */ - fail_on_test(!transmit_timeout(node, &msg)); - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - fail_on_test(!cec_msg_status_is_abort(&msg)); - fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); - - cec_msg_init(&msg, me, la); - cec_msg_record_on_own(&msg); - msg.msg[2] = 6; /* Invalid source operand */ - fail_on_test(!transmit_timeout(node, &msg)); - fail_on_test(!cec_msg_status_is_abort(&msg)); - fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); - - struct cec_op_record_src rec_src = {}; - - rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; - rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; - rec_src.digital.dig_bcast_system = 0x7f; /* Invalid digital service broadcast system operand */ - rec_src.digital.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART; - rec_src.digital.channel.major = 0; - rec_src.digital.channel.minor = 30203; - fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); - - rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; - rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; - rec_src.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS; - rec_src.digital.channel.channel_number_fmt = 0; /* Invalid channel number format operand */ - rec_src.digital.channel.major = 0; - rec_src.digital.channel.minor = 30609; - fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_ANALOG; - rec_src.analog.ana_bcast_type = 0xff; /* Invalid analog broadcast type */ - rec_src.analog.ana_freq = (519250 * 10) / 625; - rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_PAL_BG; - fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_ANALOG; - rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_SATELLITE; - rec_src.analog.ana_freq = (703250 * 10) / 625; - rec_src.analog.bcast_system = 0xff; /* Invalid analog broadcast system */ - fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_ANALOG; - rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL; - rec_src.analog.ana_freq = 0; /* Invalid frequency */ - rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_NTSC_M; - fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_ANALOG; - rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_CABLE; - rec_src.analog.ana_freq = 0xffff; /* Invalid frequency */ - rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_SECAM_L; - fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); - - memset(&rec_src, 0, sizeof(rec_src)); - rec_src.type = CEC_OP_RECORD_SRC_EXT_PLUG; - rec_src.ext_plug.plug = 0; /* Invalid plug */ - fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); - - return OK; -} - -static int one_touch_rec_off(struct node *node, unsigned me, unsigned la, bool interactive) -{ - struct cec_msg msg; - - cec_msg_init(&msg, me, la); - cec_msg_record_off(&msg, true); - /* Allow 10s for reply because the spec says it may take several seconds to accurately respond. */ - fail_on_test(!transmit_timeout(node, &msg, 10000)); - if (unrecognized_op(&msg)) { - fail_on_test(node->remote[la].prim_type == CEC_OP_PRIM_DEVTYPE_RECORD); - return OK_NOT_SUPPORTED; - } - if (refused(&msg)) - return OK_REFUSED; - if (cec_msg_status_is_abort(&msg)) - return OK_PRESUMED; - if (timed_out(&msg)) - return OK_PRESUMED; - - __u8 rec_status; - - cec_ops_record_status(&msg, &rec_status); - - fail_on_test(rec_status != CEC_OP_RECORD_STATUS_TERMINATED_OK && - rec_status != CEC_OP_RECORD_STATUS_ALREADY_TERM); - - return OK; -} - -static const vec_remote_subtests one_touch_rec_subtests{ - { "Record TV Screen", CEC_LOG_ADDR_MASK_TV, one_touch_rec_tv_screen }, - { - "Record On", - CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, - one_touch_rec_on, - }, - { - "Record On Invalid Operand", - CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, - one_touch_rec_on_invalid, - }, - { "Record Off", CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, one_touch_rec_off }, - -}; - -/* 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); - 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; - } - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - if (cec_msg_status_is_abort(&msg)) - return OK_PRESUMED; - - return 0; -} - -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 = {}; - - digital_service_id.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; - digital_service_id.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART; - 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); - 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; - } - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - if (cec_msg_status_is_abort(&msg)) - return OK_PRESUMED; - - 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); - 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; - } - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - if (cec_msg_status_is_abort(&msg)) - return OK_PRESUMED; - - 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); - 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; - } - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - if (cec_msg_status_is_abort(&msg)) - return OK_PRESUMED; - - 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 = {}; - - digital_service_id.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; - digital_service_id.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART; - 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); - 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; - } - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - if (cec_msg_status_is_abort(&msg)) - return OK_PRESUMED; - - 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); - 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; - } - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - if (cec_msg_status_is_abort(&msg)) - return OK_PRESUMED; - - return 0; -} - -static int timer_prog_set_prog_title(struct node *node, unsigned me, unsigned la, bool interactive) -{ - struct cec_msg msg; - - cec_msg_init(&msg, me, la); - cec_msg_set_timer_program_title(&msg, "Super-Hans II"); - fail_on_test(!transmit_timeout(node, &msg)); - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - - return OK_PRESUMED; -} - -static int timer_prog_timer_status(struct node *node, unsigned me, unsigned la, bool interactive) -{ - struct cec_msg msg; - - 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)); - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - - return OK_PRESUMED; -} - -static int timer_prog_timer_clear_status(struct node *node, unsigned me, unsigned la, bool interactive) -{ - struct cec_msg msg; - - 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)); - if (unrecognized_op(&msg)) - return OK_NOT_SUPPORTED; - if (refused(&msg)) - return OK_REFUSED; - - return OK_PRESUMED; -} - -static const vec_remote_subtests timer_prog_subtests{ - { "Set Analogue Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_set_analog_timer }, - { "Set Digital Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_set_digital_timer }, - { "Set Timer Program Title", CEC_LOG_ADDR_MASK_RECORD, timer_prog_set_prog_title }, - { "Set External Timer", CEC_LOG_ADDR_MASK_RECORD, timer_prog_set_ext_timer }, - { "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 }, -}; - static const char *hec_func_state2s(__u8 hfs) { switch (hfs) { diff -Nru v4l-utils-1.21.0-202107050833/utils/cec-compliance/cec-test-tuner-record-timer.cpp v4l-utils-1.21.0-202107130804/utils/cec-compliance/cec-test-tuner-record-timer.cpp --- v4l-utils-1.21.0-202107050833/utils/cec-compliance/cec-test-tuner-record-timer.cpp 1970-01-01 00:00:00.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-compliance/cec-test-tuner-record-timer.cpp 2021-07-13 14:04:20.000000000 +0000 @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +#include +#include +#include +#include + +#include +#include + +#include "cec-compliance.h" + +enum Months { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }; + +/* Tuner Control */ + +static const char *bcast_type2s(__u8 bcast_type) +{ + switch (bcast_type) { + case CEC_OP_ANA_BCAST_TYPE_CABLE: + return "Cable"; + case CEC_OP_ANA_BCAST_TYPE_SATELLITE: + return "Satellite"; + case CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL: + return "Terrestrial"; + default: + return "Future use"; + } +} + +static int log_tuner_service(const struct cec_op_tuner_device_info &info, + const char *prefix = "") +{ + printf("\t\t%s", prefix); + + if (info.is_analog) { + double freq_mhz = (info.analog.ana_freq * 625) / 10000.0; + + printf("Analog Channel %.2f MHz (%s, %s)\n", freq_mhz, + bcast_system2s(info.analog.bcast_system), + bcast_type2s(info.analog.ana_bcast_type)); + + switch (info.analog.bcast_system) { + case CEC_OP_BCAST_SYSTEM_PAL_BG: + case CEC_OP_BCAST_SYSTEM_SECAM_LQ: + case CEC_OP_BCAST_SYSTEM_PAL_M: + case CEC_OP_BCAST_SYSTEM_NTSC_M: + case CEC_OP_BCAST_SYSTEM_PAL_I: + case CEC_OP_BCAST_SYSTEM_SECAM_DK: + case CEC_OP_BCAST_SYSTEM_SECAM_BG: + case CEC_OP_BCAST_SYSTEM_SECAM_L: + case CEC_OP_BCAST_SYSTEM_PAL_DK: + break; + default: + return fail("invalid analog bcast_system %u", info.analog.bcast_system); + } + if (info.analog.ana_bcast_type > CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL) + return fail("invalid analog bcast_type %u\n", info.analog.ana_bcast_type); + fail_on_test(!info.analog.ana_freq); + return 0; + } + + __u8 system = info.digital.dig_bcast_system; + + printf("%s Channel ", dig_bcast_system2s(system)); + if (info.digital.service_id_method) { + __u16 major = info.digital.channel.major; + __u16 minor = info.digital.channel.minor; + + switch (info.digital.channel.channel_number_fmt) { + case CEC_OP_CHANNEL_NUMBER_FMT_2_PART: + printf("%u.%u\n", major, minor); + break; + case CEC_OP_CHANNEL_NUMBER_FMT_1_PART: + printf("%u\n", minor); + break; + default: + printf("%u.%u\n", major, minor); + return fail("invalid service ID method\n"); + } + return 0; + } + + + switch (system) { + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T: { + __u16 tsid = info.digital.arib.transport_id; + __u16 sid = info.digital.arib.service_id; + __u16 onid = info.digital.arib.orig_network_id; + + printf("TSID: %u, SID: %u, ONID: %u\n", tsid, sid, onid); + break; + } + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: { + __u16 tsid = info.digital.atsc.transport_id; + __u16 pn = info.digital.atsc.program_number; + + printf("TSID: %u, Program Number: %u\n", tsid, pn); + break; + } + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T: { + __u16 tsid = info.digital.dvb.transport_id; + __u16 sid = info.digital.dvb.service_id; + __u16 onid = info.digital.dvb.orig_network_id; + + printf("TSID: %u, SID: %u, ONID: %u\n", tsid, sid, onid); + break; + } + default: + break; + } + + switch (system) { + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN: + warn_once("generic digital broadcast systems should not be used"); + break; + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T: + break; + default: + return fail("invalid digital broadcast system %u", system); + } + + if (info.digital.service_id_method > CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) + return fail("invalid service ID method %u\n", info.digital.service_id_method); + + return 0; +} + +static int tuner_ctl_test(struct node *node, unsigned me, unsigned la, bool interactive) +{ + struct cec_msg msg; + struct cec_op_tuner_device_info info = {}; + std::vector info_vec; + bool has_tuner = (1 << la) & (CEC_LOG_ADDR_MASK_TV | CEC_LOG_ADDR_MASK_TUNER); + int ret; + + cec_msg_init(&msg, me, la); + cec_msg_give_tuner_device_status(&msg, true, CEC_OP_STATUS_REQ_ONCE); + fail_on_test(!transmit_timeout(node, &msg)); + fail_on_test(!has_tuner && !timed_out_or_abort(&msg)); + if (!has_tuner) + return OK_NOT_SUPPORTED; + if (timed_out(&msg) || unrecognized_op(&msg)) + return OK_NOT_SUPPORTED; + if (cec_msg_status_is_abort(&msg)) + return OK_REFUSED; + + printf("\t Start Channel Scan\n"); + cec_ops_tuner_device_status(&msg, &info); + info_vec.push_back(info); + ret = log_tuner_service(info); + if (ret) + return ret; + + while (true) { + cec_msg_init(&msg, me, la); + cec_msg_tuner_step_increment(&msg); + fail_on_test(!transmit(node, &msg)); + fail_on_test(cec_msg_status_is_abort(&msg)); + if (cec_msg_status_is_abort(&msg)) { + fail_on_test(abort_reason(&msg) == CEC_OP_ABORT_UNRECOGNIZED_OP); + if (abort_reason(&msg) == CEC_OP_ABORT_REFUSED) { + warn("Tuner step increment does not wrap.\n"); + break; + } + + warn("Tuner at end of service list did not receive feature abort refused.\n"); + break; + } + cec_msg_init(&msg, me, la); + cec_msg_give_tuner_device_status(&msg, true, CEC_OP_STATUS_REQ_ONCE); + fail_on_test(!transmit_timeout(node, &msg)); + fail_on_test(timed_out_or_abort(&msg)); + memset(&info, 0, sizeof(info)); + cec_ops_tuner_device_status(&msg, &info); + if (!memcmp(&info, &info_vec[0], sizeof(info))) + break; + ret = log_tuner_service(info); + if (ret) + return ret; + info_vec.push_back(info); + } + printf("\t Finished Channel Scan\n"); + + printf("\t Start Channel Test\n"); + for (const auto &iter : info_vec) { + cec_msg_init(&msg, me, la); + log_tuner_service(iter, "Select "); + if (iter.is_analog) + cec_msg_select_analogue_service(&msg, iter.analog.ana_bcast_type, + iter.analog.ana_freq, iter.analog.bcast_system); + else + cec_msg_select_digital_service(&msg, &iter.digital); + fail_on_test(!transmit(node, &msg)); + fail_on_test(cec_msg_status_is_abort(&msg)); + cec_msg_init(&msg, me, la); + cec_msg_give_tuner_device_status(&msg, true, CEC_OP_STATUS_REQ_ONCE); + fail_on_test(!transmit_timeout(node, &msg)); + fail_on_test(timed_out_or_abort(&msg)); + memset(&info, 0, sizeof(info)); + cec_ops_tuner_device_status(&msg, &info); + if (memcmp(&info, &iter, sizeof(info))) { + log_tuner_service(info); + log_tuner_service(iter); + } + fail_on_test(memcmp(&info, &iter, sizeof(info))); + } + printf("\t Finished Channel Test\n"); + + cec_msg_init(&msg, me, la); + cec_msg_select_analogue_service(&msg, 3, 16000, 9); + printf("\t\tSelect invalid analog channel\n"); + fail_on_test(!transmit_timeout(node, &msg)); + fail_on_test(!cec_msg_status_is_abort(&msg)); + fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); + cec_msg_init(&msg, me, la); + info.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID; + info.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2; + info.digital.dvb.transport_id = 0; + info.digital.dvb.service_id = 0; + info.digital.dvb.orig_network_id = 0; + cec_msg_select_digital_service(&msg, &info.digital); + printf("\t\tSelect invalid digital channel\n"); + fail_on_test(!transmit_timeout(node, &msg)); + fail_on_test(!cec_msg_status_is_abort(&msg)); + fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); + + return 0; +} + +const vec_remote_subtests tuner_ctl_subtests{ + { "Tuner Control", CEC_LOG_ADDR_MASK_TUNER | CEC_LOG_ADDR_MASK_TV, tuner_ctl_test }, +}; + +/* One Touch Record */ + +static int one_touch_rec_on_send(struct node *node, unsigned me, unsigned la, + const struct cec_op_record_src &rec_src, __u8 &rec_status) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + cec_msg_record_off(&msg, false); + fail_on_test(!transmit_timeout(node, &msg)); + + cec_msg_init(&msg, me, la); + cec_msg_record_on(&msg, true, &rec_src); + /* Allow 10s for reply because the spec says it may take several seconds to accurately respond. */ + fail_on_test(!transmit_timeout(node, &msg, 10000)); + fail_on_test(timed_out_or_abort(&msg)); + cec_ops_record_status(&msg, &rec_status); + + return OK; +} + +static int one_touch_rec_on_send_invalid(struct node *node, unsigned me, unsigned la, + const struct cec_op_record_src &rec_src) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + cec_msg_record_on(&msg, true, &rec_src); + fail_on_test(!transmit_timeout(node, &msg)); + fail_on_test(!cec_msg_status_is_abort(&msg)); + fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); + + return OK; +} + +/* + * Returns true if the Record Status is an error indicating that the + * request to start recording has failed. + */ +static bool rec_status_is_a_valid_error_status(__u8 rec_status) +{ + switch (rec_status) { + case CEC_OP_RECORD_STATUS_NO_DIG_SERVICE: + case CEC_OP_RECORD_STATUS_NO_ANA_SERVICE: + case CEC_OP_RECORD_STATUS_NO_SERVICE: + case CEC_OP_RECORD_STATUS_INVALID_EXT_PLUG: + case CEC_OP_RECORD_STATUS_INVALID_EXT_PHYS_ADDR: + case CEC_OP_RECORD_STATUS_UNSUP_CA: + case CEC_OP_RECORD_STATUS_NO_CA_ENTITLEMENTS: + case CEC_OP_RECORD_STATUS_CANT_COPY_SRC: + case CEC_OP_RECORD_STATUS_NO_MORE_COPIES: + case CEC_OP_RECORD_STATUS_NO_MEDIA: + case CEC_OP_RECORD_STATUS_PLAYING: + case CEC_OP_RECORD_STATUS_ALREADY_RECORDING: + case CEC_OP_RECORD_STATUS_MEDIA_PROT: + case CEC_OP_RECORD_STATUS_NO_SIGNAL: + case CEC_OP_RECORD_STATUS_MEDIA_PROBLEM: + case CEC_OP_RECORD_STATUS_NO_SPACE: + case CEC_OP_RECORD_STATUS_PARENTAL_LOCK: + case CEC_OP_RECORD_STATUS_OTHER: + return true; + default: + return false; + } +} + +static int one_touch_rec_tv_screen(struct node *node, unsigned me, unsigned la, bool interactive) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + cec_msg_record_tv_screen(&msg, true); + fail_on_test(!transmit_timeout(node, &msg)); + fail_on_test_v2(node->remote[la].cec_version, + node->remote[la].has_rec_tv && unrecognized_op(&msg)); + fail_on_test_v2(node->remote[la].cec_version, + !node->remote[la].has_rec_tv && !unrecognized_op(&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; + /* Follower should ignore this message if it is not sent by a recording device */ + if (node->prim_devtype != CEC_OP_PRIM_DEVTYPE_RECORD) { + fail_on_test(!timed_out(&msg)); + return OK; + } + fail_on_test(timed_out(&msg)); + + struct cec_op_record_src rec_src = {}; + + cec_ops_record_on(&msg, &rec_src); + + fail_on_test(rec_src.type < CEC_OP_RECORD_SRC_OWN || + rec_src.type > CEC_OP_RECORD_SRC_EXT_PHYS_ADDR); + + if (rec_src.type == CEC_OP_RECORD_SRC_DIGITAL) { + switch (rec_src.digital.dig_bcast_system) { + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_GEN: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_CS: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_T: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_CABLE: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_SAT: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_C: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_S2: + case CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T: + break; + default: + return fail("Invalid digital service broadcast system operand.\n"); + } + + if (rec_src.digital.service_id_method == CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL) + fail_on_test(rec_src.digital.channel.channel_number_fmt < CEC_OP_CHANNEL_NUMBER_FMT_1_PART || + rec_src.digital.channel.channel_number_fmt > CEC_OP_CHANNEL_NUMBER_FMT_2_PART); + } + + if (rec_src.type == CEC_OP_RECORD_SRC_ANALOG) { + fail_on_test(rec_src.analog.ana_bcast_type > CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL); + fail_on_test(rec_src.analog.bcast_system > CEC_OP_BCAST_SYSTEM_PAL_DK && + rec_src.analog.bcast_system != CEC_OP_BCAST_SYSTEM_OTHER); + fail_on_test(rec_src.analog.ana_freq == 0 || rec_src.analog.ana_freq == 0xffff); + } + + if (rec_src.type == CEC_OP_RECORD_SRC_EXT_PLUG) + fail_on_test(rec_src.ext_plug.plug == 0); + + return OK; +} + +static int one_touch_rec_on(struct node *node, unsigned me, unsigned la, bool interactive) +{ + struct cec_msg msg; + struct cec_op_record_src rec_src = {}; + + rec_src.type = CEC_OP_RECORD_SRC_OWN; + cec_msg_init(&msg, me, la); + cec_msg_record_on(&msg, true, &rec_src); + /* Allow 10s for reply because the spec says it may take several seconds to accurately respond. */ + fail_on_test(!transmit_timeout(node, &msg, 10000)); + fail_on_test(timed_out(&msg)); + if (unrecognized_op(&msg)) { + fail_on_test(node->remote[la].prim_type == CEC_OP_PRIM_DEVTYPE_RECORD); + return OK_NOT_SUPPORTED; + } + if (refused(&msg)) + return OK_REFUSED; + if (cec_msg_status_is_abort(&msg)) + return OK_PRESUMED; + + __u8 rec_status; + + cec_ops_record_status(&msg, &rec_status); + if (rec_status != CEC_OP_RECORD_STATUS_CUR_SRC) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + /* In the following tests, these digital services are taken from the cec-follower tuner emulation. */ + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; + rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID; + rec_src.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS; + rec_src.digital.arib.transport_id = 1032; + rec_src.digital.arib.service_id = 30203; + rec_src.digital.arib.orig_network_id = 1; + fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); + if (rec_status != CEC_OP_RECORD_STATUS_DIG_SERVICE) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; + rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; + rec_src.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ATSC_T; + rec_src.digital.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_2_PART; + rec_src.digital.channel.major = 4; + rec_src.digital.channel.minor = 1; + fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); + if (rec_status != CEC_OP_RECORD_STATUS_DIG_SERVICE) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; + rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_DIG_ID; + rec_src.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_DVB_T; + rec_src.digital.dvb.transport_id = 1004; + rec_src.digital.dvb.service_id = 1040; + rec_src.digital.dvb.orig_network_id = 8945; + fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); + if (rec_status != CEC_OP_RECORD_STATUS_DIG_SERVICE) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + /* In the following tests, these channels taken from the cec-follower tuner emulation. */ + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_STATUS_ANA_SERVICE; + rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_CABLE; + rec_src.analog.ana_freq = (471250 * 10) / 625; + rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_PAL_BG; + fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); + if (rec_status != CEC_OP_RECORD_STATUS_ANA_SERVICE) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_STATUS_ANA_SERVICE; + rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_SATELLITE; + rec_src.analog.ana_freq = (551250 * 10) / 625; + rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_SECAM_BG; + fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); + if (rec_status != CEC_OP_RECORD_STATUS_ANA_SERVICE) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_STATUS_ANA_SERVICE; + rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL; + rec_src.analog.ana_freq = (185250 * 10) / 625; + rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_PAL_DK; + fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); + if (rec_status != CEC_OP_RECORD_STATUS_ANA_SERVICE) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_EXT_PLUG; + rec_src.ext_plug.plug = 1; + fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); + if (rec_status != CEC_OP_RECORD_STATUS_EXT_INPUT) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_EXT_PHYS_ADDR; + fail_on_test(one_touch_rec_on_send(node, me, la, rec_src, rec_status)); + if (rec_status != CEC_OP_RECORD_STATUS_EXT_INPUT) + fail_on_test(!rec_status_is_a_valid_error_status(rec_status)); + + return OK; +} + +static int one_touch_rec_on_invalid(struct node *node, unsigned me, unsigned la, bool interactive) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + cec_msg_record_on_own(&msg); + msg.msg[2] = 0; /* Invalid source operand */ + fail_on_test(!transmit_timeout(node, &msg)); + if (unrecognized_op(&msg)) + return OK_NOT_SUPPORTED; + fail_on_test(!cec_msg_status_is_abort(&msg)); + fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); + + cec_msg_init(&msg, me, la); + cec_msg_record_on_own(&msg); + msg.msg[2] = 6; /* Invalid source operand */ + fail_on_test(!transmit_timeout(node, &msg)); + fail_on_test(!cec_msg_status_is_abort(&msg)); + fail_on_test(abort_reason(&msg) != CEC_OP_ABORT_INVALID_OP); + + struct cec_op_record_src rec_src = {}; + + rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; + rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; + rec_src.digital.dig_bcast_system = 0x7f; /* Invalid digital service broadcast system operand */ + rec_src.digital.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART; + rec_src.digital.channel.major = 0; + rec_src.digital.channel.minor = 30203; + fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); + + rec_src.type = CEC_OP_RECORD_SRC_DIGITAL; + rec_src.digital.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; + rec_src.digital.dig_bcast_system = CEC_OP_DIG_SERVICE_BCAST_SYSTEM_ARIB_BS; + rec_src.digital.channel.channel_number_fmt = 0; /* Invalid channel number format operand */ + rec_src.digital.channel.major = 0; + rec_src.digital.channel.minor = 30609; + fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_ANALOG; + rec_src.analog.ana_bcast_type = 0xff; /* Invalid analog broadcast type */ + rec_src.analog.ana_freq = (519250 * 10) / 625; + rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_PAL_BG; + fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_ANALOG; + rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_SATELLITE; + rec_src.analog.ana_freq = (703250 * 10) / 625; + rec_src.analog.bcast_system = 0xff; /* Invalid analog broadcast system */ + fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_ANALOG; + rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_TERRESTRIAL; + rec_src.analog.ana_freq = 0; /* Invalid frequency */ + rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_NTSC_M; + fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_ANALOG; + rec_src.analog.ana_bcast_type = CEC_OP_ANA_BCAST_TYPE_CABLE; + rec_src.analog.ana_freq = 0xffff; /* Invalid frequency */ + rec_src.analog.bcast_system = CEC_OP_BCAST_SYSTEM_SECAM_L; + fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); + + memset(&rec_src, 0, sizeof(rec_src)); + rec_src.type = CEC_OP_RECORD_SRC_EXT_PLUG; + rec_src.ext_plug.plug = 0; /* Invalid plug */ + fail_on_test(one_touch_rec_on_send_invalid(node, me, la, rec_src)); + + return OK; +} + +static int one_touch_rec_off(struct node *node, unsigned me, unsigned la, bool interactive) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + cec_msg_record_off(&msg, true); + /* Allow 10s for reply because the spec says it may take several seconds to accurately respond. */ + fail_on_test(!transmit_timeout(node, &msg, 10000)); + if (unrecognized_op(&msg)) { + fail_on_test(node->remote[la].prim_type == CEC_OP_PRIM_DEVTYPE_RECORD); + return OK_NOT_SUPPORTED; + } + if (refused(&msg)) + return OK_REFUSED; + if (cec_msg_status_is_abort(&msg)) + return OK_PRESUMED; + if (timed_out(&msg)) + return OK_PRESUMED; + + __u8 rec_status; + + cec_ops_record_status(&msg, &rec_status); + + fail_on_test(rec_status != CEC_OP_RECORD_STATUS_TERMINATED_OK && + rec_status != CEC_OP_RECORD_STATUS_ALREADY_TERM); + + return OK; +} + +const vec_remote_subtests one_touch_rec_subtests{ + { "Record TV Screen", CEC_LOG_ADDR_MASK_TV, one_touch_rec_tv_screen }, + { + "Record On", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + one_touch_rec_on, + }, + { + "Record On Invalid Operand", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + one_touch_rec_on_invalid, + }, + { "Record Off", CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, one_touch_rec_off }, + +}; + +/* Timer Programming */ + +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 bool timer_has_error(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); + if (prog_error) + return true; + + return false; +} + +static int send_timer_error(struct node *node, unsigned me, unsigned la, __u8 day, __u8 month, + __u8 start_hr, __u8 start_min, __u8 dur_hr, __u8 dur_min, __u8 rec_seq) +{ + struct cec_msg msg; + cec_msg_init(&msg, me, la); + cec_msg_set_analogue_timer(&msg, true, day, month, start_hr, start_min, dur_hr, dur_min, + rec_seq, 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_has_error(msg)); + + return OK; +} + +static bool timer_overlap_warning_is_set(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); + + if (timer_overlap_warning) + return true; + + return false; +} + +static int send_timer_overlap(struct node *node, unsigned me, unsigned la, __u8 day, __u8 month, + __u8 start_hr, __u8 start_min, __u8 dur_hr, __u8 dur_min, __u8 rec_seq) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + cec_msg_set_analogue_timer(&msg, true, day, month, start_hr, start_min, dur_hr, dur_min, + rec_seq, 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_has_error(msg)); + fail_on_test(!timer_overlap_warning_is_set(msg)); + + return OK; +} + +static int clear_timer(struct node *node, unsigned me, unsigned la, __u8 day, __u8 month, + __u8 start_hr, __u8 start_min, __u8 dur_hr, __u8 dur_min, __u8 rec_seq) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + cec_msg_clear_analogue_timer(&msg, true, day, month, start_hr, start_min, dur_hr, dur_min, + rec_seq, 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_has_error(msg)); + fail_on_test(timer_cleared_status_is_valid(msg)); + + return OK; +} + +static int timer_prog_set_analog_timer(struct node *node, unsigned me, unsigned la, bool interactive) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + /* 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); + cec_msg_set_analogue_timer(&msg, true, t->tm_mday, t->tm_mon + 1, 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)); + 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 OK; +} + +static int timer_prog_set_digital_timer(struct node *node, unsigned me, unsigned la, bool interactive) +{ + struct cec_msg msg; + struct cec_op_digital_service_id digital_service_id = {}; + + digital_service_id.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; + digital_service_id.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART; + digital_service_id.channel.minor = 1; + digital_service_id.dig_bcast_system = node->remote[la].dig_bcast_sys; + cec_msg_init(&msg, me, la); + /* 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); + 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); + 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)) + 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) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + /* 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); + cec_msg_set_ext_timer(&msg, true, t->tm_mday, t->tm_mon + 1, 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)); + 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) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + /* 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); + cec_msg_clear_analogue_timer(&msg, true, t->tm_mday, t->tm_mon + 1, 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)); + 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) +{ + struct cec_msg msg; + struct cec_op_digital_service_id digital_service_id = {}; + + digital_service_id.service_id_method = CEC_OP_SERVICE_ID_METHOD_BY_CHANNEL; + digital_service_id.channel.channel_number_fmt = CEC_OP_CHANNEL_NUMBER_FMT_1_PART; + digital_service_id.channel.minor = 1; + digital_service_id.dig_bcast_system = node->remote[la].dig_bcast_sys; + cec_msg_init(&msg, me, la); + /* 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); + cec_msg_clear_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); + 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)) + 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) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + /* 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); + cec_msg_clear_ext_timer(&msg, true, t->tm_mday, t->tm_mon + 1, 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)); + 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_set_prog_title(struct node *node, unsigned me, unsigned la, bool interactive) +{ + struct cec_msg msg; + + cec_msg_init(&msg, me, la); + cec_msg_set_timer_program_title(&msg, "Super-Hans II"); + fail_on_test(!transmit_timeout(node, &msg)); + if (unrecognized_op(&msg)) + return OK_NOT_SUPPORTED; + if (refused(&msg)) + return OK_REFUSED; + + return OK_PRESUMED; +} + +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. */ + fail_on_test(send_timer_error(node, me, la, 31, Nov, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Day error: December 32, at 6:00 am, for 1 hr. */ + fail_on_test(send_timer_error(node, me, la, 32, Dec, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Day error: 0, in January, at 6:00 am, for 1 hr. Day range begins at 1. */ + fail_on_test(send_timer_error(node, me, la, 0, Jan, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Month error: 0, on day 5, at 6:00 am, for 1 hr. CEC month range is 1-12. */ + fail_on_test(send_timer_error(node, me, la, 5, 0, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Month error: 13, on day 5, at 6:00 am, for 1 hr. */ + fail_on_test(send_timer_error(node, me, la, 5, 13, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Start hour error: 24 hr, on August 5, for 1 hr. Start hour range is 0-23. */ + fail_on_test(send_timer_error(node, me, la, 5, Aug, 24, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Start min error: 60 min, on August 5, for 1 hr. Start min range is 0-59. */ + fail_on_test(send_timer_error(node, me, la, 5, Aug, 0, 60, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Recording duration error: 0 hr, 0 min on August 5, at 6:00am. */ + fail_on_test(send_timer_error(node, me, la, 5, Aug, 6, 0, 0, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Duplicate timer error: start 2 hrs from now, for 1 hr. */ + time_t two_hours_ahead = node->current_time + (2 * 60 * 60); + struct tm *t = localtime(&two_hours_ahead); + cec_msg_init(&msg, me, la); + cec_msg_set_analogue_timer(&msg, true, t->tm_mday, t->tm_mon + 1, t->tm_hour, t->tm_min, 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)); + fail_on_test(timer_has_error(msg)); /* The first timer should be set. */ + fail_on_test(send_timer_error(node, me, la, t->tm_mday, t->tm_mon + 1, t->tm_hour, + t->tm_min, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Clear the timer that was set to test duplicate timers. */ + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, t->tm_hour, t->tm_min, 1, 0, + CEC_OP_REC_SEQ_ONCE_ONLY)); + + /* Recording sequence error: 0xff, on August 5, at 6:00 am, for 1 hr. */ + fail_on_test(send_timer_error(node, me, la, 5, Aug, 6, 0, 1, 0, 0xff)); + + /* Error in last day of February, at 6:00 am, for 1 hr. */ + time_t current_time = node->current_time; + t = localtime(¤t_time); + if ((t->tm_mon + 1) > Feb) + t->tm_year++; /* The timer will be for next year. */ + if (!(t->tm_year % 4) && ((t->tm_year % 100) || !(t->tm_year % 400))) + fail_on_test(send_timer_error(node, me, la, 30, Feb, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + else + fail_on_test(send_timer_error(node, me, la, 29, Feb, 6, 0, 1, 0, CEC_OP_REC_SEQ_ONCE_ONLY)); + + return OK; +} + +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); + + /* No overlap: set timer for tomorrow at 8:00 am for 2 hr. */ + cec_msg_init(&msg, me, la); + cec_msg_set_analogue_timer(&msg, true, t->tm_mday, t->tm_mon + 1, 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; + fail_on_test(timed_out_or_abort(&msg)); + fail_on_test(timer_has_error(msg)); + fail_on_test(timer_overlap_warning_is_set(msg)); + + /* 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 + 1, 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_has_error(msg)); + fail_on_test(timer_overlap_warning_is_set(msg)); + + /* 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 + 1, 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_has_error(msg)); + fail_on_test(timer_overlap_warning_is_set(msg)); + + /* Overlap tail end: set timer for tomorrow at 9:00 am for 2 hr, repeats on Sun. */ + fail_on_test(send_timer_overlap(node, me, la, t->tm_mday, t->tm_mon + 1, 9, 0, 2, 0, 0x1)); + + /* Overlap front end: set timer for tomorrow at 7:00 am for 1 hr, 30 min. */ + fail_on_test(send_timer_overlap(node, me, la, t->tm_mday, t->tm_mon + 1, 7, 0, 1, 30, 0x1)); + + /* Overlap same start time: set timer for tomorrow at 8:00 am for 30 min. */ + fail_on_test(send_timer_overlap(node, me, la, t->tm_mday, t->tm_mon + 1, 8, 0, 0, 30, 0x1)); + + /* Overlap same end time: set timer for tomorrow at 9:30 am for 30 min. */ + fail_on_test(send_timer_overlap(node, me, la, t->tm_mday, t->tm_mon + 1, 9, 30, 0, 30, 0x1)); + + /* Overlap all timers: set timer for tomorrow at 6:00 am for 6 hr. */ + fail_on_test(send_timer_overlap(node, me, la, t->tm_mday, t->tm_mon + 1, 6, 0, 6, 0, 0x1)); + + /* Clear all the timers. */ + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, 8, 0, 2, 0, + CEC_OP_REC_SEQ_ONCE_ONLY)); + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, 10, 0, 0, 15, + CEC_OP_REC_SEQ_ONCE_ONLY)); + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, 7, 45, 0, 15, + CEC_OP_REC_SEQ_ONCE_ONLY)); + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, 9, 0, 2, 0, 0x1)); + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, 7, 0, 1, 30, 0x1)); + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, 8, 0, 0, 30, 0x1)); + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, 9, 30, 0, 30, 0x1)); + fail_on_test(clear_timer(node, me, la, t->tm_mday, t->tm_mon + 1, 6, 0, 6, 0, 0x1)); + + return OK; +} + +const vec_remote_subtests timer_prog_subtests{ + { + "Set Analogue Timer", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_prog_set_analog_timer, + }, + { + "Set Digital Timer", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_prog_set_digital_timer, + }, + { + "Set Timer Program Title", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_prog_set_prog_title, + }, + { + "Set External Timer", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_prog_set_ext_timer, + }, + { + "Clear Analogue Timer", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_prog_clear_analog_timer, + }, + { + "Clear Digital Timer", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_prog_clear_digital_timer, + }, + { + "Clear External Timer", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_prog_clear_ext_timer, + }, + { + "Set Timers with Errors", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_errors, + }, + { + "Set Overlapping Timers", + CEC_LOG_ADDR_MASK_RECORD | CEC_LOG_ADDR_MASK_BACKUP, + timer_overlap_warning, + }, +}; diff -Nru v4l-utils-1.21.0-202107050833/utils/cec-compliance/Makefile.am v4l-utils-1.21.0-202107130804/utils/cec-compliance/Makefile.am --- v4l-utils-1.21.0-202107050833/utils/cec-compliance/Makefile.am 2021-07-05 13:04:35.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-compliance/Makefile.am 2021-07-13 14:04:20.000000000 +0000 @@ -1,7 +1,7 @@ bin_PROGRAMS = cec-compliance man_MANS = cec-compliance.1 -cec_compliance_SOURCES = cec-compliance.cpp cec-compliance.h cec-test.cpp cec-test-adapter.cpp cec-test-audio.cpp cec-test-power.cpp cec-test-fuzzing.cpp +cec_compliance_SOURCES = cec-compliance.cpp cec-compliance.h cec-test.cpp cec-test-adapter.cpp cec-test-audio.cpp cec-test-power.cpp cec-test-fuzzing.cpp cec-test-tuner-record-timer.cpp cec_compliance_CPPFLAGS = -I$(top_srcdir)/utils/libcecutil $(GIT_SHA) $(GIT_COMMIT_CNT) $(GIT_COMMIT_DATE) cec_compliance_LDADD = -lrt ../libcecutil/libcecutil.la diff -Nru v4l-utils-1.21.0-202107050833/utils/cec-follower/cec-follower.cpp v4l-utils-1.21.0-202107130804/utils/cec-follower/cec-follower.cpp --- v4l-utils-1.21.0-202107050833/utils/cec-follower/cec-follower.cpp 2021-07-05 13:04:35.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-follower/cec-follower.cpp 2021-07-13 14:04:20.000000000 +0000 @@ -47,6 +47,7 @@ bool show_state; bool show_warnings = true; unsigned warnings; +std::set programmed_timers; static struct option long_options[] = { { "device", required_argument, nullptr, OptSetDevice }, @@ -296,6 +297,62 @@ return retval == -1 ? e : (retval ? -1 : 0); } +void print_timers(struct node *node) +{ + if (show_info) { + printf("Timers Set:\n"); + if (node->state.recording_controlled_by_timer) + printf("Deck is currently recording from the first timer.\n"); + if (node->state.one_touch_record_on && !node->state.recording_controlled_by_timer) + printf("Deck is currently recording independent of timers.\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 +376,8 @@ 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 */ + node.state.recording_controlled_by_timer = false; tuner_dev_info_init(&node.state); node.state.last_aud_rate_rx_ts = 0; } diff -Nru v4l-utils-1.21.0-202107050833/utils/cec-follower/cec-follower.h v4l-utils-1.21.0-202107130804/utils/cec-follower/cec-follower.h --- v4l-utils-1.21.0-202107050833/utils/cec-follower/cec-follower.h 2021-07-05 13:04:35.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-follower/cec-follower.h 2021-07-13 14:04:20.000000000 +0000 @@ -19,12 +19,16 @@ #include #include +#include +#include extern bool show_info; extern bool show_msgs; extern bool show_state; extern bool show_warnings; extern unsigned warnings; +extern std::set programmed_timers; +extern void print_timers(struct node *node); struct state { __u16 active_source_pa; @@ -55,6 +59,8 @@ __u64 deck_skip_start; bool one_touch_record_on; bool record_received_standby; + int media_space_available; + bool recording_controlled_by_timer; time_t toggle_power_status; __u64 last_aud_rate_rx_ts; }; @@ -82,6 +88,44 @@ 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 { @@ -225,7 +269,9 @@ // cec-tuner.cpp void tuner_dev_info_init(struct state *state); -void process_tuner_record_timer_msgs(struct node *node, struct cec_msg &msg, unsigned me, __u8 type); +void process_tuner_msgs(struct node *node, struct cec_msg &msg, unsigned me, __u8 type); +void process_record_msgs(struct node *node, struct cec_msg &msg, unsigned me, __u8 type); +void process_timer_msgs(struct node *node, struct cec_msg &msg, unsigned me, __u8 type); // CEC processing void reply_feature_abort(struct node *node, struct cec_msg *msg, diff -Nru v4l-utils-1.21.0-202107050833/utils/cec-follower/cec-processing.cpp v4l-utils-1.21.0-202107130804/utils/cec-follower/cec-processing.cpp --- v4l-utils-1.21.0-202107050833/utils/cec-follower/cec-processing.cpp 2021-07-05 13:04:35.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-follower/cec-processing.cpp 2021-07-13 14:04:20.000000000 +0000 @@ -673,10 +673,14 @@ case CEC_MSG_SELECT_DIGITAL_SERVICE: case CEC_MSG_TUNER_STEP_DECREMENT: case CEC_MSG_TUNER_STEP_INCREMENT: + process_tuner_msgs(node, msg, me, type); + return; case CEC_MSG_RECORD_TV_SCREEN: case CEC_MSG_RECORD_ON: case CEC_MSG_RECORD_OFF: case CEC_MSG_RECORD_STATUS: + process_record_msgs(node, msg, me, type); + return; case CEC_MSG_SET_ANALOGUE_TIMER: case CEC_MSG_SET_DIGITAL_TIMER: case CEC_MSG_SET_EXT_TIMER: @@ -686,7 +690,7 @@ case CEC_MSG_SET_TIMER_PROGRAM_TITLE: case CEC_MSG_TIMER_CLEARED_STATUS: case CEC_MSG_TIMER_STATUS: - process_tuner_record_timer_msgs(node, msg, me, type); + process_timer_msgs(node, msg, me, type); return; /* Dynamic Auto Lipsync */ @@ -1005,6 +1009,73 @@ } } +static void update_programmed_timers(struct node *node) +{ + std::set::iterator it = programmed_timers.begin(); + /* Use the current minute because timers do not have second precision. */ + time_t current_minute = time(nullptr) / 60; + time_t timer_start_minute = it->start_time / 60; + time_t timer_end_minute = (it->start_time + it->duration) / 60; + + /* Start the timed recording only if the deck is not already recording. */ + if (timer_start_minute == current_minute && !node->state.one_touch_record_on) { + node->state.one_touch_record_on = true; + node->state.recording_controlled_by_timer = true; + print_timers(node); + } + + /* Delete an overlapped timer. Recording will be at best incomplete. */ + if (timer_start_minute < current_minute && + (!node->state.recording_controlled_by_timer || !node->state.one_touch_record_on)) { + programmed_timers.erase(*it); + if (show_info) + printf("Deleted overlapped timer.\n"); + print_timers(node); + } + + if (timer_end_minute != current_minute || !node->state.recording_controlled_by_timer) + return; + + /* Delete finished timers. */ + node->state.one_touch_record_on = false; + node->state.recording_controlled_by_timer = false; + node->state.media_space_available -= it->duration; /* 1 MB per second */ + /* + * TODO: We are only ever decreasing the amount of space available, + * there is no heuristic that reclaims the space. + */ + + if (it->recording_seq) { + struct tm *last_start_time = localtime(&(it->start_time)); + int next_wday = (last_start_time->tm_wday + 1) % 7; + int days_to_move_ahead = 1; + + while ((it->recording_seq & (1 << next_wday)) == 0) { + days_to_move_ahead++; + next_wday = (next_wday + 1) % 7; + } + struct Timer next_timer = {}; + next_timer = *it; + last_start_time->tm_mday += days_to_move_ahead; + last_start_time->tm_isdst = -1; + next_timer.start_time = mktime(last_start_time); + programmed_timers.insert(next_timer); + } + programmed_timers.erase(*it); + if (show_info) + printf("Deleted finished timer.\n"); + print_timers(node); + /* + * If the finished timer was recording, and standby was received during recording, + * enter standby when the recording stops unless recording device is the active source. + */ + if (node->state.record_received_standby) { + if (node->phys_addr != node->state.active_source_pa) + enter_standby(node); + node->state.record_received_standby = false; + } +} + void testProcessing(struct node *node, bool wallclock) { struct cec_log_addrs laddrs; @@ -1164,6 +1235,9 @@ node->state.deck_skip_start = 0; update_deck_state(node, me, CEC_OP_DECK_INFO_PLAY); } + + if (!programmed_timers.empty()) + update_programmed_timers(node); } mode = CEC_MODE_INITIATOR; doioctl(node, CEC_S_MODE, &mode); diff -Nru v4l-utils-1.21.0-202107050833/utils/cec-follower/cec-tuner.cpp v4l-utils-1.21.0-202107130804/utils/cec-follower/cec-tuner.cpp --- v4l-utils-1.21.0-202107050833/utils/cec-follower/cec-tuner.cpp 2021-07-05 13:04:35.000000000 +0000 +++ v4l-utils-1.21.0-202107130804/utils/cec-follower/cec-tuner.cpp 2021-07-13 14:04:20.000000000 +0000 @@ -4,7 +4,6 @@ */ #include -#include #include #include @@ -17,6 +16,8 @@ #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 }; + struct service_info { unsigned tsid; unsigned onid; @@ -541,15 +542,12 @@ return false; } -void process_tuner_record_timer_msgs(struct node *node, struct cec_msg &msg, unsigned me, __u8 type) +void process_tuner_msgs(struct node *node, struct cec_msg &msg, unsigned me, __u8 type) { - __u8 from = cec_msg_initiator(&msg); bool is_bcast = cec_msg_is_broadcast(&msg); - switch (msg.msg[1]) { - - /* Tuner Control */ + switch (msg.msg[1]) { case CEC_MSG_GIVE_TUNER_DEVICE_STATUS: { __u8 status_req; @@ -636,9 +634,23 @@ analog_update_tuner_dev_info(node, node->state.service_idx, &msg); return; } + default: + break; + } + + if (is_bcast) + return; - /* One Touch Record */ + reply_feature_abort(node, &msg); +} + +void process_record_msgs(struct node *node, struct cec_msg &msg, unsigned me, __u8 type) +{ + __u8 from = cec_msg_initiator(&msg); + bool is_bcast = cec_msg_is_broadcast(&msg); + /* One Touch Record */ + switch (msg.msg[1]) { case CEC_MSG_RECORD_TV_SCREEN: { if (!node->has_rec_tv) break; @@ -724,6 +736,15 @@ cec_msg_record_status(&msg, CEC_OP_RECORD_STATUS_TERMINATED_OK); transmit(node, &msg); node->state.one_touch_record_on = false; + + /* Delete any currently active recording timer or it may restart itself in first minute. */ + if (node->state.recording_controlled_by_timer) { + node->state.recording_controlled_by_timer = false; + programmed_timers.erase(programmed_timers.begin()); + if (show_info) + printf("Deleted manually stopped timer.\n"); + print_timers(node); + } /* * If standby was received during recording, enter standby when the * recording is finished unless recording device is the active source. @@ -736,41 +757,298 @@ return; case CEC_MSG_RECORD_STATUS: return; + default: + break; + } + + if (is_bcast) + return; + reply_feature_abort(node, &msg); +} - /* - Timer Programming +static struct Timer get_timer_from_message(const struct cec_msg &msg) +{ + struct Timer timer = {}; - This is only a basic implementation. + __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; - 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 - */ + 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(¤t_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; +} + +void process_timer_msgs(struct node *node, struct cec_msg &msg, unsigned me, __u8 type) +{ + bool is_bcast = cec_msg_is_broadcast(&msg); + + /* Timer Programming */ + switch (msg.msg[1]) { 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.recording_controlled_by_timer && it_previous_year == programmed_timers.begin()) { + timer_cleared_status = CEC_OP_TIMER_CLR_STAT_RECORDING; + node->state.one_touch_record_on = false; + node->state.recording_controlled_by_timer = 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.recording_controlled_by_timer && it_current_year == programmed_timers.begin()) { + timer_cleared_status = CEC_OP_TIMER_CLR_STAT_RECORDING; + node->state.one_touch_record_on = false; + node->state.recording_controlled_by_timer = 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: