From 7dad05a06d63279fc778def262be77b010008c6e Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Tue, 16 Jun 2026 23:40:00 -0400 Subject: [PATCH 01/39] RDKEMW-17673 : Add info coverage gate to CI --- .github/scripts/compare_coverage.py | 309 ++++++++ .github/scripts/compare_coverage_test.py | 907 +++++++++++++++++++++++ .github/workflows/ci.yml | 192 +++++ 3 files changed, 1408 insertions(+) create mode 100644 .github/scripts/compare_coverage.py create mode 100644 .github/scripts/compare_coverage_test.py diff --git a/.github/scripts/compare_coverage.py b/.github/scripts/compare_coverage.py new file mode 100644 index 0000000..a112cfc --- /dev/null +++ b/.github/scripts/compare_coverage.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2026 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" +Coverage comparison script for firebolt-cpp-client. + +Reads unit test (L0) and component test (L1) coverage results, compares overall +line coverage against the stored baseline from the build-metadata branch, and +prints a summary. +""" + +import argparse +import datetime +import json +import os +import sys +from typing import Optional + + +# Minimum threshold +THRESHOLD = 75.0 + +_GREEN = "\033[32m" +_RED = "\033[31m" +_RESET = "\033[0m" + +_SEP_WIDTH = 64 +_OVERALL_WIDTH = 80 +_SEP = "\u2500" * _SEP_WIDTH +_HEADER = "\u2500\u2500 Coverage Gate Report " + "\u2500" * (_SEP_WIDTH - 24) + + +def _colored(token: str, ok: bool) -> str: + return f"{_GREEN if ok else _RED}{token}{_RESET}" + + +def _fmt_timestamp(ts: str) -> str: + """Convert '2026-05-28T12:00:00Z' -> '2026-05-28 12:00 UTC'.""" + try: + dt = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ") + return dt.strftime("%Y-%m-%d %H:%M UTC") + except (ValueError, TypeError): + return ts + + +def _delta_str(current: float, baseline: float) -> str: + delta = current - baseline + sign = "+" if delta >= 0 else "" + return f"{sign}{delta:.2f}%" + + +def _join_names(names: list) -> str: + return names[0] if len(names) == 1 else " and ".join(names) + + +def _suite_analysis(current: Optional[float], baseline: Optional[float]): + """Analyse one test suite. + + Returns (ok, result_str, delta_disp, warn_reason): + ok - True when no advisory issues found. + result_str - Coloured [PASS]/[WARN] token + detail for the table. + delta_disp - String for the Delta column ("N/A" when skipped). + warn_reason - Reason phrase for the summary line; None when ok. + """ + if current is None: + reason = "coverage data missing" + return False, f"{_colored('[WARN]', False)} {reason}", "N/A", reason + + threshold_ok = current >= THRESHOLD + + if baseline is None: + # No baseline stored — threshold check only. + regression_ok = True + detail = "" if threshold_ok else "below threshold" + delta_disp = "N/A" + elif baseline == 0.0: + # A zero baseline is unreliable — skip regression check. + regression_ok = True + base_note = "baseline unreliable (0%) \u00b7 delta skipped" + detail = f"below threshold \u00b7 {base_note}" if not threshold_ok else base_note + delta_disp = "N/A" + else: + regression_ok = current >= baseline + delta_disp = _delta_str(current, baseline) + if threshold_ok and regression_ok: + detail = "" + elif not threshold_ok and not regression_ok: + detail = "below threshold \u00b7 dropped from baseline" + elif not threshold_ok: + detail = "below threshold \u00b7 no baseline regression" + else: + detail = "above threshold but dropped from baseline" + + overall_ok = threshold_ok and regression_ok + token = _colored("[PASS]", True) if overall_ok else _colored("[WARN]", False) + result_str = f"{token} {detail}" if detail else token + warn_reason = detail if not overall_ok else None + return overall_ok, result_str, delta_disp, warn_reason + + +def _build_summary(warn_suites: list) -> str: + """Build a compact summary from WARN suite (name, reason) pairs.""" + if not warn_suites: + return "" + groups: dict = {} + for name, reason in warn_suites: + groups.setdefault(reason, []).append(name) + parts = [f"{_join_names(names)} {reason}" for reason, names in groups.items()] + return ". ".join(parts) + + +# lcov parsing +def parse_lcov_coverage(path: str) -> Optional[float]: + """Return overall line coverage % from an lcov .info file, or None. + + An lcov .info file contains per-source-file records separated by + ``end_of_record``. Each record may include: + LF: — total instrumented lines in that file + LH: — lines executed at least once + + We aggregate across all records to produce a single project-wide %. + Returns None when the file is absent, empty, or contains no line data. + """ + if not path or not os.path.isfile(path): + return None + + total_found = 0 + total_hit = 0 + + try: + with open(path, "r", encoding="utf-8", errors="replace") as fh: + for raw in fh: + line = raw.strip() + if line.startswith("LF:"): + try: + total_found += int(line[3:]) + except ValueError: + pass + elif line.startswith("LH:"): + try: + total_hit += int(line[3:]) + except ValueError: + pass + except OSError as exc: + print(f" WARNING: Could not read {path}: {exc}", file=sys.stderr) + return None + + if total_found == 0: + return None + + return round((total_hit / total_found) * 100.0, 2) + + + +# Baseline loading +def load_baseline(path: str) -> dict: + """Load baseline JSON; return an empty dict on any error.""" + if not path or not os.path.isfile(path): + return {} + try: + with open(path, "r", encoding="utf-8") as fh: + data = json.load(fh) + if isinstance(data, dict): + return data + print( + f" WARNING: Baseline {path} is not a JSON object (got {type(data).__name__}) — ignoring", + file=sys.stderr, + ) + except (OSError, json.JSONDecodeError, ValueError) as exc: + print(f" WARNING: Could not parse baseline {path}: {exc}", file=sys.stderr) + return {} + + + +def main() -> None: + parser = argparse.ArgumentParser( + description=( + "Compare L0/L1 coverage against the develop baseline.\n" + "Exits 1 when coverage fails threshold or regresses from baseline." + ) + ) + parser.add_argument("--baseline", required=True, metavar="PATH", + help="Path to coverage-baseline.json.") + parser.add_argument("--l0", required=False, metavar="PATH", + help="Path to the L0 lcov filtered_coverage.info file.") + parser.add_argument("--l1", required=False, metavar="PATH", + help="Path to the L1 lcov filtered_coverage.info file.") + parser.add_argument("--output-json", required=False, metavar="PATH", + help="Write {L0, L1, commit, timestamp} JSON here for baseline update.") + parser.add_argument("--commit", required=False, default="", + help="Commit SHA to embed in --output-json.") + parser.add_argument("--timestamp", required=False, default="", + help="ISO 8601 timestamp to embed in --output-json.") + args = parser.parse_args() + + baseline = load_baseline(args.baseline) + + def _coerce_pct(value: object) -> Optional[float]: + """Coerce a baseline percentage value to float, or None if invalid.""" + if value is None: + return None + try: + return float(value) + except (TypeError, ValueError): + return None + + baseline_l0: Optional[float] = _coerce_pct(baseline.get("L0")) + baseline_l1: Optional[float] = _coerce_pct(baseline.get("L1")) + + l0_coverage = parse_lcov_coverage(args.l0) if args.l0 else None + l1_coverage = parse_lcov_coverage(args.l1) if args.l1 else None + + # ------------------------------------------------------------------ + # Optional: write extracted numbers for baseline update. + # Skipped (with a warning) when either suite lacks valid coverage data. + # ------------------------------------------------------------------ + if args.output_json: + if l0_coverage is not None and l1_coverage is not None: + payload = { + "L0": l0_coverage, + "L1": l1_coverage, + "commit": args.commit or "", + "timestamp": args.timestamp or "", + } + try: + with open(args.output_json, "w", encoding="utf-8") as fh: + json.dump(payload, fh, indent=2) + fh.write("\n") + except OSError as exc: + print(f" WARNING: Could not write {args.output_json}: {exc}", file=sys.stderr) + else: + print( + f" WARNING: --output-json skipped: coverage data incomplete " + f"(L0={l0_coverage}, L1={l1_coverage})", + file=sys.stderr, + ) + + l0_ok, l0_result, l0_delta, l0_reason = _suite_analysis(l0_coverage, baseline_l0) + l1_ok, l1_result, l1_delta, l1_reason = _suite_analysis(l1_coverage, baseline_l1) + + all_ok = l0_ok and l1_ok + status_token = _colored("[PASS]", True) if all_ok else _colored("[WARN]", False) + + + # Output report + print() + print(_HEADER) + if baseline: + commit = baseline.get("commit", "unknown") + ts = _fmt_timestamp(baseline.get("timestamp", "")) + print(f" Baseline {commit} ({ts})") + else: + print(" Baseline N/A (first-time setup \u2014 regression check skipped)") + print(f" Threshold {THRESHOLD}% | Status {status_token} (informational \u2014 PRs are not blocked)") + print(_SEP) + + # Coverage table + print(f" {'Suite':<7}{'Current':<9}{'Baseline':<10}{'Delta':<10}Result") + for name, current, base, result, delta_disp in [ + ("L0", l0_coverage, baseline_l0, l0_result, l0_delta), + ("L1", l1_coverage, baseline_l1, l1_result, l1_delta), + ]: + cur_str = f"{current:.2f}%" if current is not None else "N/A" + base_str = f"{base:.2f}%" if base is not None else "N/A" + print(f" {name:<7}{cur_str:<9}{base_str:<10}{delta_disp:<10}{result}") + + print(_SEP) + + # Summary + overall bar + warn_suites = [(n, r) for n, r in [("L0", l0_reason), ("L1", l1_reason)] if r] + summary = _build_summary(warn_suites) + if summary: + print(f" {summary}") + + # Notify when one or both suites had no coverage data (artifact absent). + # Gate logic is unchanged — SKIP is treated as passing by design. + skipped = [n for n, cov in [("L0", l0_coverage), ("L1", l1_coverage)] if cov is None] + if skipped: + print(f" NOTE: {_join_names(skipped)} coverage data absent \u2014 artifact missing or unreadable.") + + # " OVERALL: [PASS/WARN] " = 1 + 9 + 6 + 1 = 17 visible chars + # left + " OVERALL: " + token(6) + " " + right == _OVERALL_WIDTH + _mid = len(" OVERALL: ") + 6 + len(" ") # 17 + left = "\u2500" * ((_OVERALL_WIDTH - _mid) // 2) # 31 + right = "\u2500" * (_OVERALL_WIDTH - _mid - len(left)) # 32 + print(f"{left} OVERALL: {status_token} {right}") + print() + + # Informational only — always exit 0 so PRs are never blocked. + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/compare_coverage_test.py b/.github/scripts/compare_coverage_test.py new file mode 100644 index 0000000..41e0362 --- /dev/null +++ b/.github/scripts/compare_coverage_test.py @@ -0,0 +1,907 @@ +#!/usr/bin/env python3 +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2026 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" +Tests for compare_coverage.py + +Covers: + - Unit tests for parse_lcov_coverage(), load_baseline(), _suite_analysis() + - Integration tests (subprocess) simulating all gate scenarios listed in the + Coverage Gate implementation spec. + +Workflow-level scenarios (L0 job fails / L1 job fails / both fail) are handled +by GitHub Actions' implicit success() dependency check on the coverage-gate job +and cannot be tested at the Python script level; they are documented inline. +""" + +import json +import os +import subprocess +import sys +import tempfile +import unittest + +# --------------------------------------------------------------------------- +# Import the module under test +# --------------------------------------------------------------------------- +SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, SCRIPTS_DIR) + +import compare_coverage # noqa: E402 (after sys.path manipulation) + +THRESHOLD = compare_coverage.THRESHOLD # 75.0 + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _make_lcov(lines_found: int, lines_hit: int) -> str: + """Minimal valid lcov .info content with the given LF/LH counts.""" + return ( + "SF:src/fake.cpp\n" + f"LF:{lines_found}\n" + f"LH:{lines_hit}\n" + "end_of_record\n" + ) + + +def _write_lcov(tmp_dir: str, name: str, lines_found: int, lines_hit: int) -> str: + """Write an lcov file and return its absolute path.""" + path = os.path.join(tmp_dir, name) + with open(path, "w") as fh: + fh.write(_make_lcov(lines_found, lines_hit)) + return path + + +def _write_baseline(tmp_dir: str, data: dict, name: str = "baseline.json") -> str: + """Serialise *data* to JSON and return the path.""" + path = os.path.join(tmp_dir, name) + with open(path, "w") as fh: + json.dump(data, fh) + return path + + +def _run_script(*args: str) -> subprocess.CompletedProcess: + """Invoke compare_coverage.py as a subprocess and return the result.""" + cmd = [sys.executable, os.path.join(SCRIPTS_DIR, "compare_coverage.py"), *args] + return subprocess.run(cmd, capture_output=True, text=True) + + +# =========================================================================== +# Unit tests — parse_lcov_coverage() +# =========================================================================== + +class TestParseLcovCoverage(unittest.TestCase): + """Tests for the lcov .info parser.""" + + def setUp(self): + self.tmp = tempfile.mkdtemp() + + def tearDown(self): + import shutil + shutil.rmtree(self.tmp, ignore_errors=True) + + def _write(self, name: str, content: str) -> str: + path = os.path.join(self.tmp, name) + with open(path, "w") as fh: + fh.write(content) + return path + + # --- Missing / empty inputs ------------------------------------------------- + + def test_none_path_returns_none(self): + self.assertIsNone(compare_coverage.parse_lcov_coverage(None)) + + def test_empty_path_returns_none(self): + self.assertIsNone(compare_coverage.parse_lcov_coverage("")) + + def test_nonexistent_file_returns_none(self): + self.assertIsNone(compare_coverage.parse_lcov_coverage("/no/such/file.info")) + + def test_empty_file_returns_none(self): + p = self._write("empty.info", "") + self.assertIsNone(compare_coverage.parse_lcov_coverage(p)) + + def test_no_lf_data_returns_none(self): + p = self._write("no_lf.info", "SF:foo.cpp\nend_of_record\n") + self.assertIsNone(compare_coverage.parse_lcov_coverage(p)) + + def test_lf_zero_returns_none(self): + p = self._write("zero_lf.info", "SF:foo.cpp\nLF:0\nLH:0\nend_of_record\n") + self.assertIsNone(compare_coverage.parse_lcov_coverage(p)) + + # --- Basic coverage values -------------------------------------------------- + + def test_100_percent(self): + p = _write_lcov(self.tmp, "full.info", 100, 100) + self.assertEqual(compare_coverage.parse_lcov_coverage(p), 100.0) + + def test_75_percent_exact(self): + p = _write_lcov(self.tmp, "seventy_five.info", 100, 75) + self.assertEqual(compare_coverage.parse_lcov_coverage(p), 75.0) + + def test_zero_percent(self): + p = _write_lcov(self.tmp, "zero_pct.info", 100, 0) + self.assertEqual(compare_coverage.parse_lcov_coverage(p), 0.0) + + def test_partial_coverage(self): + # 150 / 200 = 75.0 % + p = _write_lcov(self.tmp, "partial.info", 200, 150) + self.assertEqual(compare_coverage.parse_lcov_coverage(p), 75.0) + + # --- Multi-record aggregation ----------------------------------------------- + + def test_aggregates_multiple_records(self): + # 80 + 60 = 140 hit out of 200 → 70.0 % + content = ( + "SF:a.cpp\nLF:100\nLH:80\nend_of_record\n" + "SF:b.cpp\nLF:100\nLH:60\nend_of_record\n" + ) + p = self._write("multi.info", content) + self.assertEqual(compare_coverage.parse_lcov_coverage(p), 70.0) + + # --- Malformed data --------------------------------------------------------- + + def test_malformed_lf_ignored_gracefully(self): + # LF with a non-numeric value; total_found stays 0 → None + p = self._write("bad_lf.info", "SF:a.cpp\nLF:abc\nLH:50\nend_of_record\n") + self.assertIsNone(compare_coverage.parse_lcov_coverage(p)) + + def test_malformed_lh_ignored_gracefully(self): + # LH with garbage value; LF is valid, so LF=100, LH=0 → 0.0 % + p = self._write("bad_lh.info", "SF:a.cpp\nLF:100\nLH:xyz\nend_of_record\n") + self.assertEqual(compare_coverage.parse_lcov_coverage(p), 0.0) + + def test_entirely_non_lcov_content(self): + p = self._write("corrupt.info", "THIS IS NOT A VALID LCOV FILE\n") + self.assertIsNone(compare_coverage.parse_lcov_coverage(p)) + + # --- Rounding --------------------------------------------------------------- + + def test_rounds_to_two_decimal_places(self): + # 1/3 ≈ 33.33 % + p = _write_lcov(self.tmp, "third.info", 3, 1) + self.assertEqual(compare_coverage.parse_lcov_coverage(p), 33.33) + + +# =========================================================================== +# Unit tests — load_baseline() +# =========================================================================== + +class TestLoadBaseline(unittest.TestCase): + """Tests for the JSON baseline loader.""" + + def setUp(self): + self.tmp = tempfile.mkdtemp() + + def tearDown(self): + import shutil + shutil.rmtree(self.tmp, ignore_errors=True) + + def _write_json(self, name: str, content: str) -> str: + path = os.path.join(self.tmp, name) + with open(path, "w") as fh: + fh.write(content) + return path + + # --- Missing / empty inputs ------------------------------------------------- + + def test_none_returns_empty_dict(self): + self.assertEqual(compare_coverage.load_baseline(None), {}) + + def test_empty_path_returns_empty_dict(self): + self.assertEqual(compare_coverage.load_baseline(""), {}) + + def test_nonexistent_file_returns_empty_dict(self): + self.assertEqual(compare_coverage.load_baseline("/no/such/file.json"), {}) + + # --- Valid JSON ------------------------------------------------------------- + + def test_valid_baseline_with_l0_and_l1(self): + p = _write_baseline(self.tmp, {"L0": 80.0, "L1": 85.0}) + self.assertEqual(compare_coverage.load_baseline(p), {"L0": 80.0, "L1": 85.0}) + + def test_valid_empty_json_object(self): + p = _write_baseline(self.tmp, {}) + self.assertEqual(compare_coverage.load_baseline(p), {}) + + def test_valid_baseline_extra_keys_preserved(self): + data = {"L0": 80.0, "L1": 85.0, "commit": "abc123", "timestamp": "2026-01-01"} + p = _write_baseline(self.tmp, data) + self.assertEqual(compare_coverage.load_baseline(p), data) + + # --- Invalid JSON ----------------------------------------------------------- + + def test_malformed_json_returns_empty_dict(self): + p = self._write_json("bad.json", "{not valid json}") + result = compare_coverage.load_baseline(p) + self.assertEqual(result, {}) + + def test_truncated_json_returns_empty_dict(self): + p = self._write_json("truncated.json", '{"L0": 80') + self.assertEqual(compare_coverage.load_baseline(p), {}) + + def test_empty_file_returns_empty_dict(self): + p = self._write_json("empty.json", "") + self.assertEqual(compare_coverage.load_baseline(p), {}) + + # --- Non-dict JSON ---------------------------------------------------------- + + def test_json_array_returns_empty_dict(self): + import io, contextlib + p = self._write_json("list.json", "[1, 2, 3]") + buf = io.StringIO() + with contextlib.redirect_stderr(buf): + result = compare_coverage.load_baseline(p) + self.assertEqual(result, {}) + self.assertIn("WARNING", buf.getvalue()) + + def test_json_string_returns_empty_dict(self): + import io, contextlib + p = self._write_json("str.json", '"just a string"') + buf = io.StringIO() + with contextlib.redirect_stderr(buf): + result = compare_coverage.load_baseline(p) + self.assertEqual(result, {}) + self.assertIn("WARNING", buf.getvalue()) + + def test_json_number_returns_empty_dict(self): + import io, contextlib + p = self._write_json("num.json", "42") + buf = io.StringIO() + with contextlib.redirect_stderr(buf): + result = compare_coverage.load_baseline(p) + self.assertEqual(result, {}) + self.assertIn("WARNING", buf.getvalue()) + + def test_json_null_returns_empty_dict(self): + p = self._write_json("null.json", "null") + # null is parsed as None, which is not a dict — Warning emitted, empty dict returned + self.assertEqual(compare_coverage.load_baseline(p), {}) + + +# =========================================================================== +# Unit tests — _suite_analysis() +# =========================================================================== + +class TestSuiteAnalysis(unittest.TestCase): + """ + Tests for the core gate analysis function. + + Gate passes (ok=True) when BOTH: + 1. current >= THRESHOLD (75.0) + 2. current >= baseline (regression check) + + SKIP when current is None. + Regression check disabled when baseline is None or 0.0. + """ + + # --- Scenario 1: exceeds both threshold AND baseline → PASS ---------------- + + def test_s1_exceeds_threshold_and_baseline(self): + ok, _, _, reason = compare_coverage._suite_analysis(80.0, 77.0) + self.assertTrue(ok) + self.assertIsNone(reason) + + # --- Scenario 2: meets threshold exactly (75%) AND beats baseline → PASS --- + + def test_s2_meets_threshold_exactly_beats_baseline(self): + ok, _, _, reason = compare_coverage._suite_analysis(75.0, 70.0) + self.assertTrue(ok) + self.assertIsNone(reason) + + # --- Scenario 3: meets baseline exactly, exceeds threshold → PASS ---------- + + def test_s3_meets_baseline_exactly_exceeds_threshold(self): + ok, _, _, reason = compare_coverage._suite_analysis(80.0, 80.0) + self.assertTrue(ok) + self.assertIsNone(reason) + + # --- Scenario 4: meets BOTH exactly (75.0 == threshold == baseline) → PASS - + + def test_s4_meets_both_exactly_at_threshold(self): + ok, _, _, reason = compare_coverage._suite_analysis(75.0, 75.0) + self.assertTrue(ok) + self.assertIsNone(reason) + + # --- Scenario 5: exceeds threshold but BELOW baseline → FAIL (regression) -- + + def test_s5_above_threshold_below_baseline(self): + ok, result, _, reason = compare_coverage._suite_analysis(76.0, 80.0) + self.assertFalse(ok) + self.assertIsNotNone(reason) + self.assertIn("dropped from baseline", reason) + + # --- Scenario 6: below threshold but meets/exceeds baseline → FAIL ---------- + + def test_s6_below_threshold_meets_baseline(self): + ok, _, _, reason = compare_coverage._suite_analysis(74.0, 70.0) + self.assertFalse(ok) + self.assertIsNotNone(reason) + self.assertIn("below threshold", reason) + self.assertNotIn("dropped from baseline", reason) # regression check passed + + def test_s6b_below_threshold_equals_baseline(self): + ok, _, _, reason = compare_coverage._suite_analysis(74.0, 74.0) + self.assertFalse(ok) + self.assertIsNotNone(reason) + self.assertIn("below threshold", reason) + self.assertNotIn("dropped from baseline", reason) # regression check passed + + # --- Scenario 7: fails BOTH conditions → FAIL -------------------------------- + + def test_s7_fails_both_conditions(self): + ok, _, _, reason = compare_coverage._suite_analysis(70.0, 80.0) + self.assertFalse(ok) + self.assertIsNotNone(reason) + self.assertIn("below threshold", reason) + self.assertIn("dropped from baseline", reason) + + # --- Scenario 8: no baseline → threshold-only check ------------------------- + + def test_no_baseline_above_threshold_passes(self): + ok, _, delta, reason = compare_coverage._suite_analysis(80.0, None) + self.assertTrue(ok) + self.assertIsNone(reason) + self.assertEqual(delta, "N/A") + + def test_no_baseline_below_threshold_fails(self): + ok, _, _, reason = compare_coverage._suite_analysis(70.0, None) + self.assertFalse(ok) + self.assertIn("below threshold", reason) + + def test_no_baseline_at_threshold_exactly_passes(self): + ok, _, _, reason = compare_coverage._suite_analysis(75.0, None) + self.assertTrue(ok) + self.assertIsNone(reason) + + # --- SKIP case: no current coverage ----------------------------------------- + + def test_skip_when_current_is_none(self): + ok, result, delta, reason = compare_coverage._suite_analysis(None, 80.0) + self.assertFalse(ok, "Missing coverage data should be treated as WARN") + self.assertIn("coverage data missing", result) + self.assertEqual(delta, "N/A") + self.assertIsNotNone(reason) + self.assertIn("coverage data missing", reason) + + def test_skip_when_both_none(self): + ok, result, delta, reason = compare_coverage._suite_analysis(None, None) + self.assertFalse(ok) + self.assertIn("coverage data missing", result) + + # --- Zero baseline: regression check disabled -------------------------------- + + def test_zero_baseline_above_threshold_passes(self): + ok, _, delta, reason = compare_coverage._suite_analysis(80.0, 0.0) + self.assertTrue(ok) + self.assertIsNone(reason) + self.assertEqual(delta, "N/A", "Delta must be N/A for zero baseline") + + def test_zero_baseline_below_threshold_fails(self): + ok, _, _, reason = compare_coverage._suite_analysis(70.0, 0.0) + self.assertFalse(ok) + self.assertIsNotNone(reason) + self.assertIn("below threshold", reason) + + # --- Delta string correctness ------------------------------------------------ + + def test_delta_positive(self): + _, _, delta, _ = compare_coverage._suite_analysis(80.0, 77.0) + self.assertEqual(delta, "+3.00%") + + def test_delta_negative(self): + _, _, delta, _ = compare_coverage._suite_analysis(76.0, 80.0) + self.assertEqual(delta, "-4.00%") + + def test_delta_zero(self): + _, _, delta, _ = compare_coverage._suite_analysis(80.0, 80.0) + self.assertEqual(delta, "+0.00%") + + def test_delta_na_when_no_baseline(self): + _, _, delta, _ = compare_coverage._suite_analysis(80.0, None) + self.assertEqual(delta, "N/A") + + # --- Boundary: one tick below threshold (74.99 is impossible from lcov, + # but 74.0 covers the just-below case) ---------------------------------- + + def test_just_below_threshold_fails(self): + # 74 / 100 = 74.0 % + ok, _, _, reason = compare_coverage._suite_analysis(74.0, 70.0) + self.assertFalse(ok) + + def test_just_at_threshold_passes(self): + ok, _, _, reason = compare_coverage._suite_analysis(75.0, 70.0) + self.assertTrue(ok) + + +# =========================================================================== +# Integration tests — main() via subprocess +# =========================================================================== + +class TestMainIntegration(unittest.TestCase): + """ + End-to-end simulation of every gate scenario. + + Each test invokes the script as a subprocess (exactly as GitHub Actions + would) and asserts on exit code and stdout/stderr content. + """ + + def setUp(self): + self.tmp = tempfile.mkdtemp() + + def tearDown(self): + import shutil + shutil.rmtree(self.tmp, ignore_errors=True) + + # --- Helpers ---------------------------------------------------------------- + + def _lcov(self, name: str, lf: int, lh: int) -> str: + return _write_lcov(self.tmp, name, lf, lh) + + def _baseline(self, data: dict, name: str = "baseline.json") -> str: + return _write_baseline(self.tmp, data, name) + + def _run(self, *args: str) -> subprocess.CompletedProcess: + return _run_script(*args) + + # =========================================================================== + # SCENARIO 1 — Coverage exceeds both threshold AND baseline + # Expected: Gate PASSES (exit 0), baseline updates + # =========================================================================== + + def test_s1_exceeds_threshold_and_baseline(self): + bl = self._baseline({"L0": 77.0, "L1": 78.0}) + l0 = self._lcov("l0.info", 100, 80) # 80 % + l1 = self._lcov("l1.info", 100, 82) # 82 % + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[PASS]", r.stdout) + + # =========================================================================== + # SCENARIO 2 — Coverage meets threshold exactly (75%) and meets baseline + # Expected: Gate PASSES (exit 0) + # =========================================================================== + + def test_s2_meets_threshold_exactly_meets_baseline(self): + bl = self._baseline({"L0": 70.0, "L1": 70.0}) + l0 = self._lcov("l0.info", 100, 75) # 75.0 % + l1 = self._lcov("l1.info", 100, 75) # 75.0 % + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[PASS]", r.stdout) + + # =========================================================================== + # SCENARIO 3 — Meets baseline exactly but exceeds threshold + # Expected: Gate PASSES (exit 0) + # =========================================================================== + + def test_s3_meets_baseline_exactly_exceeds_threshold(self): + bl = self._baseline({"L0": 80.0, "L1": 80.0}) + l0 = self._lcov("l0.info", 100, 80) # 80 % == baseline + l1 = self._lcov("l1.info", 100, 80) # 80 % == baseline + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + # =========================================================================== + # SCENARIO 4 — Meets BOTH exactly (current == threshold == baseline == 75 %) + # Expected: Gate PASSES (exit 0) + # =========================================================================== + + def test_s4_meets_both_exactly(self): + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 75) + l1 = self._lcov("l1.info", 100, 75) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + # =========================================================================== + # SCENARIO 5 — Exceeds threshold but falls BELOW baseline (regression) + # Expected: Gate WARNS (exit 0 — informational only), [WARN] shown + # =========================================================================== + + def test_s5_above_threshold_below_baseline(self): + bl = self._baseline({"L0": 85.0, "L1": 85.0}) + l0 = self._lcov("l0.info", 100, 80) # 80 % < 85 % baseline + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[WARN]", r.stdout) + self.assertIn("dropped from baseline", r.stdout) + + # =========================================================================== + # SCENARIO 6 — Falls BELOW threshold but meets/exceeds baseline + # Expected: Gate WARNS (exit 0 — informational only), [WARN] shown + # =========================================================================== + + def test_s6_below_threshold_meets_baseline(self): + bl = self._baseline({"L0": 70.0, "L1": 70.0}) + l0 = self._lcov("l0.info", 100, 74) # 74 % < 75 % threshold + l1 = self._lcov("l1.info", 100, 74) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[WARN]", r.stdout) + self.assertIn("below threshold", r.stdout) + + # =========================================================================== + # SCENARIO 7 — Fails BOTH conditions (below threshold AND below baseline) + # Expected: Gate WARNS (exit 0 — informational only), [WARN] shown + # =========================================================================== + + def test_s7_fails_both_threshold_and_baseline(self): + bl = self._baseline({"L0": 85.0, "L1": 85.0}) + l0 = self._lcov("l0.info", 100, 70) # 70 % < threshold AND < baseline + l1 = self._lcov("l1.info", 100, 70) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("below threshold", r.stdout) + self.assertIn("dropped from baseline", r.stdout) + + # =========================================================================== + # SCENARIO 8a — Baseline file is MISSING + # Expected: Graceful fallback; threshold-only check; no crash + # =========================================================================== + + def test_s8_baseline_missing_coverage_above_threshold(self): + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run( + "--baseline", "/nonexistent/coverage-baseline.json", + "--l0", l0, "--l1", l1, + ) + # No baseline → regression skipped → threshold pass → exit 0 + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + def test_s8_baseline_missing_coverage_below_threshold(self): + l0 = self._lcov("l0.info", 100, 70) # 70 % < 75 % + l1 = self._lcov("l1.info", 100, 70) + r = self._run( + "--baseline", "/nonexistent/coverage-baseline.json", + "--l0", l0, "--l1", l1, + ) + # Informational only — exit 0 even below threshold; [WARN] shown + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[WARN]", r.stdout) + + # =========================================================================== + # SCENARIO 9 — Baseline file contains invalid / malformed JSON + # Expected: Warning emitted, treated as empty baseline, gate continues + # =========================================================================== + + def test_s9_malformed_json_above_threshold(self): + path = os.path.join(self.tmp, "malformed.json") + with open(path, "w") as fh: + fh.write("{this is not json}") + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", path, "--l0", l0, "--l1", l1) + # Warning must appear in stderr + self.assertIn("WARNING", r.stderr) + # Fallback to empty baseline → threshold-only → pass + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + def test_s9_malformed_json_below_threshold(self): + path = os.path.join(self.tmp, "malformed2.json") + with open(path, "w") as fh: + fh.write("{bad json") + l0 = self._lcov("l0.info", 100, 70) + l1 = self._lcov("l1.info", 100, 70) + r = self._run("--baseline", path, "--l0", l0, "--l1", l1) + self.assertIn("WARNING", r.stderr) + # Informational only — exit 0 even below threshold; [WARN] shown + self.assertEqual(r.returncode, 0) + self.assertIn("[WARN]", r.stdout) + + def test_s9_empty_json_file(self): + path = os.path.join(self.tmp, "empty.json") + with open(path, "w") as fh: + fh.write("") + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", path, "--l0", l0, "--l1", l1) + # Empty file → empty dict baseline → threshold-only → pass + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + def test_s9_non_dict_json(self): + path = os.path.join(self.tmp, "list_json.json") + with open(path, "w") as fh: + json.dump([1, 2, 3], fh) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", path, "--l0", l0, "--l1", l1) + # Non-dict JSON → WARNING emitted, empty dict → threshold-only → pass + self.assertIn("WARNING", r.stderr) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + # =========================================================================== + # SCENARIO 10 — unit_tests job fails + # Coverage Gate does NOT trigger (workflow-level behaviour). + # + # GitHub Actions: coverage_gate has `needs: [unit_tests, component_tests]` + # with no custom `if:`. The implicit success() check means coverage_gate + # is SKIPPED whenever unit_tests fails. This cannot be unit-tested here; + # it is enforced by the workflow graph. + # =========================================================================== + + def test_s10_l0_artifacts_absent_l1_passes(self): + """ + Simulates the artifact-level effect: L0 .info absent (download step + with continue-on-error:true produced no file), L1 coverage present + and passing. Script-level: L0 is WARN (data missing), L1 is PASS. + """ + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l1 = self._lcov("l1.info", 100, 80) # L0 omitted intentionally + r = self._run("--baseline", bl, "--l1", l1) + # L0 WARN (missing) → overall WARN, but exit 0 (informational) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("coverage data missing", r.stdout) + self.assertIn("[WARN]", r.stdout) + + # =========================================================================== + # SCENARIO 11 — L1 job fails (symmetric to scenario 10) + # =========================================================================== + + def test_s11_l1_artifacts_absent_l0_passes(self): + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 80) # L1 omitted intentionally + r = self._run("--baseline", bl, "--l0", l0) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("coverage data missing", r.stdout) + self.assertIn("[WARN]", r.stdout) + + # =========================================================================== + # SCENARIO 12 — Both L0 AND L1 jobs fail + # Coverage Gate does NOT trigger (workflow-level). At script level, both + # .info files are absent → both SKIP → exit 0 (harmless; gate is already + # blocked at the workflow graph layer before the script is ever called). + # =========================================================================== + + def test_s12_both_artifacts_absent(self): + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + # Neither --l0 nor --l1 provided + r = self._run("--baseline", bl) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + # Both rows + summary line show coverage data missing, OVERALL WARN + self.assertGreaterEqual(r.stdout.count("coverage data missing"), 2) + self.assertIn("[WARN]", r.stdout) + self.assertIn("NOTE:", r.stdout) + + # =========================================================================== + # SCENARIO 13 — Coverage Gate step itself throws an unexpected error + # Expected: non-zero exit; error is visible; baseline NOT updated + # (Simulated by passing a completely invalid path for --baseline that + # causes the argument parser or file logic to surface an error.) + # =========================================================================== + + def test_s13_missing_required_baseline_arg(self): + """Invoking the script without --baseline must fail (argparse error).""" + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--l0", l0, "--l1", l1) + # argparse exits with code 2 on missing required argument + self.assertNotEqual(r.returncode, 0) + self.assertTrue(len(r.stderr) > 0, "Error must appear on stderr") + + # =========================================================================== + # Partial-failure cases: one suite fails, other passes + # =========================================================================== + + def test_only_l0_fails_gate_warns(self): + """L0 below threshold, L1 passes → overall [WARN] but exit 0.""" + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 70) # 70 % ✗ + l1 = self._lcov("l1.info", 100, 80) # 80 % ✓ + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + # L1 row still shows PASS + self.assertIn("[PASS]", r.stdout) + self.assertIn("[WARN]", r.stdout) + + def test_only_l1_fails_gate_warns(self): + """L1 below threshold, L0 passes → overall [WARN] but exit 0.""" + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 80) # 80 % ✓ + l1 = self._lcov("l1.info", 100, 70) # 70 % ✗ + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[WARN]", r.stdout) + + def test_only_l0_regresses_gate_warns(self): + """L0 regresses below baseline (still above threshold), L1 passes → [WARN] exit 0.""" + bl = self._baseline({"L0": 85.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 80) # 80 % < 85 % baseline ✗ + l1 = self._lcov("l1.info", 100, 80) # 80 % >= 75 % baseline ✓ + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[WARN]", r.stdout) + + # =========================================================================== + # First-time setup: empty baseline {} → threshold-only + # =========================================================================== + + def test_first_time_setup_empty_baseline_passes(self): + bl = self._baseline({}) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + def test_first_time_setup_empty_baseline_below_threshold_warns(self): + bl = self._baseline({}) + l0 = self._lcov("l0.info", 100, 70) + l1 = self._lcov("l1.info", 100, 70) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + # Informational only — exit 0 even below threshold; [WARN] shown + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[WARN]", r.stdout) + + # =========================================================================== + # Output format validation + # =========================================================================== + + def test_overall_pass_token_in_output(self): + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertIn("OVERALL:", r.stdout) + self.assertIn("[PASS]", r.stdout) + + def test_overall_warn_token_in_output(self): + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 70) + l1 = self._lcov("l1.info", 100, 70) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertIn("OVERALL:", r.stdout) + self.assertIn("[WARN]", r.stdout) + # Informational only — always exit 0 + self.assertEqual(r.returncode, 0) + + # =========================================================================== + # --output-json baseline extraction + # =========================================================================== + + def test_output_json_written_when_both_pass(self): + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 82) + out = os.path.join(self.tmp, "new-baseline.json") + self._run( + "--baseline", bl, + "--l0", l0, "--l1", l1, + "--output-json", out, + "--commit", "abc123", + "--timestamp", "2026-01-01T00:00:00Z", + ) + self.assertTrue(os.path.isfile(out), "output-json must be written") + with open(out) as fh: + data = json.load(fh) + self.assertEqual(data["L0"], 80.0) + self.assertEqual(data["L1"], 82.0) + self.assertEqual(data["commit"], "abc123") + self.assertEqual(data["timestamp"], "2026-01-01T00:00:00Z") + + def test_output_json_written_even_when_gate_fails(self): + """ + --output-json is written as long as coverage data is available, + regardless of gate outcome. The update-baseline step checks + `if [ ! -s new-baseline.json ]` separately. + """ + bl = self._baseline({"L0": 90.0, "L1": 90.0}) + l0 = self._lcov("l0.info", 100, 80) # 80 % < 90 % baseline → WARN + l1 = self._lcov("l1.info", 100, 80) + out = os.path.join(self.tmp, "new-baseline-fail.json") + r = self._run( + "--baseline", bl, + "--l0", l0, "--l1", l1, + "--output-json", out, + ) + # Informational only — always exit 0 regardless of gate outcome + self.assertEqual(r.returncode, 0) + self.assertTrue(os.path.isfile(out), "output-json written even on gate warning") + + def test_output_json_not_written_when_l0_missing(self): + """When L0 .info is absent, --output-json must NOT be written (data incomplete).""" + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l1 = self._lcov("l1.info", 100, 82) + out = os.path.join(self.tmp, "new-baseline-no-l0.json") + r = self._run("--baseline", bl, "--l1", l1, "--output-json", out) + self.assertFalse(os.path.isfile(out), "output-json must NOT be written when L0 absent") + self.assertIn("WARNING", r.stderr) + + def test_output_json_not_written_when_l1_missing(self): + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 80) + out = os.path.join(self.tmp, "new-baseline-no-l1.json") + r = self._run("--baseline", bl, "--l0", l0, "--output-json", out) + self.assertFalse(os.path.isfile(out), "output-json must NOT be written when L1 absent") + self.assertIn("WARNING", r.stderr) + + def test_output_json_not_written_when_both_missing(self): + bl = self._baseline({"L0": 75.0, "L1": 75.0}) + out = os.path.join(self.tmp, "new-baseline-neither.json") + r = self._run("--baseline", bl, "--output-json", out) + self.assertFalse(os.path.isfile(out)) + self.assertIn("WARNING", r.stderr) + + # =========================================================================== + # Baseline coercion: non-float L0/L1 values must not crash the script + # (Fixes comment 2/7 — baseline.get("L0") not validated as float) + # =========================================================================== + + def test_baseline_string_l0_treated_as_missing(self): + """String value for L0 in baseline JSON → coerced to None → threshold-only.""" + bl = self._baseline({"L0": "not-a-number", "L1": 75.0}) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + # Must not crash; L0 baseline treated as absent → threshold-only → pass + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + def test_baseline_null_l1_treated_as_missing(self): + """null value for L1 in baseline JSON → coerced to None → threshold-only.""" + bl = self._baseline({"L0": 80.0, "L1": None}) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + def test_baseline_both_non_float_threshold_only(self): + """Both L0/L1 baseline values invalid → both threshold-only → pass if above 75%.""" + bl = self._baseline({"L0": "bad", "L1": "bad"}) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + def test_baseline_both_non_float_below_threshold_warns(self): + """Both L0/L1 baseline values invalid → threshold-only → [WARN] exit 0 if below 75%.""" + bl = self._baseline({"L0": "bad", "L1": "bad"}) + l0 = self._lcov("l0.info", 100, 70) + l1 = self._lcov("l1.info", 100, 70) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("[WARN]", r.stdout) + + # =========================================================================== + # _fmt_timestamp: null/non-string timestamp must not crash the report + # (Fixes comment 8 — only ValueError was caught, not TypeError) + # =========================================================================== + + def test_null_timestamp_in_baseline_does_not_crash(self): + """null timestamp value in baseline JSON → TypeError handled → report still runs.""" + bl = self._baseline({"L0": 80.0, "L1": 80.0, "commit": "abc", "timestamp": None}) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + # Must not crash; timestamp renders as fallback; gate passes + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + self.assertIn("OVERALL:", r.stdout) + + def test_integer_timestamp_in_baseline_does_not_crash(self): + """Integer timestamp → TypeError in strptime → handled gracefully.""" + bl = self._baseline({"L0": 80.0, "L1": 80.0, "commit": "abc", "timestamp": 12345}) + l0 = self._lcov("l0.info", 100, 80) + l1 = self._lcov("l1.info", 100, 80) + r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c7d124..338cccf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -200,6 +200,7 @@ jobs: --exclude '.*/test/.*\.cpp' \ --decisions \ --medium-threshold 50 --high-threshold 75 \ + --lcov coverage/filtered_coverage.info \ --html-details coverage/index.html \ --cobertura coverage.cobertura.xml \ " @@ -210,6 +211,12 @@ jobs: name: coverage-report path: ${{ github.workspace }}/build/coverage/ + - name: Upload unit test lcov coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-unit + path: ${{ github.workspace }}/build/coverage/filtered_coverage.info + - name: Code Coverage Summary Report uses: irongut/CodeCoverageSummary@v1.3.0 with: @@ -269,6 +276,25 @@ jobs: --app-openrpc /workspace/docs/openrpc/the-spec/firebolt-app-open-rpc.json \ --test-exe /workspace/build/test/ctApp + - name: Generate Coverage Report + run: | + docker run --rm --user "$(id -u):$(id -g)" -v ${{ github.workspace }}:/workspace ${{ needs.build_docker.outputs.image_tag }} \ + bash -c " \ + set -e \ + && cd build \ + && mkdir -p coverage \ + && gcovr -r .. \ + --exclude '.*/test/.*\.h' \ + --exclude '.*/test/.*\.cpp' \ + --lcov coverage/filtered_coverage.info \ + " + + - name: Upload component test lcov coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-component + path: ${{ github.workspace }}/build/coverage/filtered_coverage.info + api_test_app: permissions: contents: read @@ -324,3 +350,169 @@ jobs: --openrpc /workspace/docs/openrpc/the-spec/firebolt-open-rpc.json \ --app-openrpc /workspace/docs/openrpc/the-spec/firebolt-app-open-rpc.json \ --test-exe /workspace/test/api_test_app/build/api-test-app + + coverage_gate: + name: Coverage Gate + needs: [unit_tests, component_tests] + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Fetch baseline from build-metadata branch + # Gracefully handle a missing build-metadata branch (first-time setup) + continue-on-error: true + run: | + set -euo pipefail + if git fetch origin build-metadata 2>/dev/null; then + if git cat-file -e FETCH_HEAD:coverage-baseline.json 2>/dev/null; then + git show FETCH_HEAD:coverage-baseline.json > coverage-baseline.json + echo "Loaded coverage-baseline.json from build-metadata branch" + else + echo "build-metadata branch exists but coverage-baseline.json not found — skipping baseline comparison" + echo "{}" > coverage-baseline.json + fi + else + echo "build-metadata branch not found — absolute threshold check only (first-time setup)" + echo "{}" > coverage-baseline.json + fi + + - name: Download unit test coverage artifact + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: coverage-unit + path: ./unit-coverage + + - name: Download component test coverage artifact + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: coverage-component + path: ./component-coverage + + - name: Compare coverage to baseline + run: | + python3 .github/scripts/compare_coverage.py \ + --baseline coverage-baseline.json \ + --l0 ./unit-coverage/filtered_coverage.info \ + --l1 ./component-coverage/filtered_coverage.info + + update_baseline: + name: Update Coverage Baseline + # Runs on push to develop when both test suites pass. + # Independent of coverage_gate — the gate is informational and must never + # block the baseline from reflecting the actual state of passing tests. + if: >- + github.event_name == 'push' && + github.ref == 'refs/heads/develop' && + needs.unit_tests.result == 'success' && + needs.component_tests.result == 'success' + needs: [unit_tests, component_tests] + runs-on: ubuntu-latest + permissions: + contents: write + actions: read + # Queue concurrent runs; do not cancel in-progress — each merge deserves + # a baseline update and force-push is atomic so queuing is safe. + concurrency: + group: update-baseline-develop + cancel-in-progress: false + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Fetch existing baseline for comparison report + # Best-effort — if the branch or file is absent we compare against nothing. + continue-on-error: true + run: | + set -euo pipefail + if git fetch origin build-metadata 2>/dev/null; then + if git cat-file -e FETCH_HEAD:coverage-baseline.json 2>/dev/null; then + git show FETCH_HEAD:coverage-baseline.json > old-baseline.json + echo "Loaded existing baseline for comparison" + else + echo '{}' > old-baseline.json + fi + else + echo '{}' > old-baseline.json + fi + + - name: Download unit test coverage artifact + uses: actions/download-artifact@v4 + with: + name: coverage-unit + path: ./unit-coverage + + - name: Download component test coverage artifact + uses: actions/download-artifact@v4 + with: + name: coverage-component + path: ./component-coverage + + - name: Extract coverage and write new baseline + id: extract + run: | + set -euo pipefail + BL_ARG="old-baseline.json" + [ -f "$BL_ARG" ] || echo '{}' > "$BL_ARG" + + python3 .github/scripts/compare_coverage.py \ + --baseline "$BL_ARG" \ + --l0 ./unit-coverage/filtered_coverage.info \ + --l1 ./component-coverage/filtered_coverage.info \ + --output-json new-baseline.json \ + --commit "$GITHUB_SHA" \ + --timestamp "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + + if [ ! -s new-baseline.json ]; then + echo "Coverage extraction produced no output — skipping baseline update" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "New baseline to commit:" + cat new-baseline.json + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Commit and push updated baseline to build-metadata + if: steps.extract.outputs.skip == 'false' + run: | + set -euo pipefail + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + + # Check out the build-metadata branch, or create it as an orphan. + if git fetch origin build-metadata 2>/dev/null; then + git checkout -B build-metadata FETCH_HEAD + else + git checkout --orphan build-metadata + git rm -rf . --quiet 2>/dev/null || true + fi + + cp -f new-baseline.json coverage-baseline.json + + git add coverage-baseline.json + + # Only commit when there is an actual change. + if git diff --cached --quiet; then + echo "Coverage baseline unchanged — no commit needed" + else + MSG=$(printf \ + 'chore: update coverage baseline after develop merge [skip ci]\n\nCommit : %s\nRun ID : %s' \ + "$GITHUB_SHA" "$GITHUB_RUN_ID") + git commit -m "$MSG" + git push --force-with-lease origin build-metadata + echo "Pushed updated baseline to build-metadata" + fi From 8f3d2dd2b7d1eb9beab6b8d7d8d4db046d7b502b Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Tue, 23 Jun 2026 16:37:45 -0400 Subject: [PATCH 02/39] RDKEMW-17673 : Address copilot comments --- .github/scripts/compare_coverage.py | 66 +++-- .github/scripts/compare_coverage_test.py | 330 +++++++++++------------ .github/workflows/ci.yml | 11 +- 3 files changed, 203 insertions(+), 204 deletions(-) diff --git a/.github/scripts/compare_coverage.py b/.github/scripts/compare_coverage.py index a112cfc..9fb7699 100644 --- a/.github/scripts/compare_coverage.py +++ b/.github/scripts/compare_coverage.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# If not stated otherwise in this file or this component's LICENSE file the -# following copyright and licenses apply: -# -# Copyright 2026 RDK Management +# Copyright 2026 Comcast Cable Communications Management, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,12 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 """ Coverage comparison script for firebolt-cpp-client. -Reads unit test (L0) and component test (L1) coverage results, compares overall -line coverage against the stored baseline from the build-metadata branch, and -prints a summary. +Reads unit test and component test coverage results, compares overall line +coverage against the stored baseline from the build-metadata branch, and prints +a summary. """ import argparse @@ -190,18 +188,19 @@ def load_baseline(path: str) -> dict: def main() -> None: parser = argparse.ArgumentParser( description=( - "Compare L0/L1 coverage against the develop baseline.\n" - "Exits 1 when coverage fails threshold or regresses from baseline." + "Compare unit test and component test coverage against " + "the develop baseline. Informational only — always exits 0 and does " + "not block PRs." ) ) parser.add_argument("--baseline", required=True, metavar="PATH", help="Path to coverage-baseline.json.") - parser.add_argument("--l0", required=False, metavar="PATH", - help="Path to the L0 lcov filtered_coverage.info file.") - parser.add_argument("--l1", required=False, metavar="PATH", - help="Path to the L1 lcov filtered_coverage.info file.") + parser.add_argument("--unit", required=False, metavar="PATH", + help="Path to the unit test lcov filtered_coverage.info file.") + parser.add_argument("--component", required=False, metavar="PATH", + help="Path to the component test lcov filtered_coverage.info file.") parser.add_argument("--output-json", required=False, metavar="PATH", - help="Write {L0, L1, commit, timestamp} JSON here for baseline update.") + help="Write {Unit, Component, commit, timestamp} JSON here for baseline update.") parser.add_argument("--commit", required=False, default="", help="Commit SHA to embed in --output-json.") parser.add_argument("--timestamp", required=False, default="", @@ -219,22 +218,22 @@ def _coerce_pct(value: object) -> Optional[float]: except (TypeError, ValueError): return None - baseline_l0: Optional[float] = _coerce_pct(baseline.get("L0")) - baseline_l1: Optional[float] = _coerce_pct(baseline.get("L1")) + baseline_unit: Optional[float] = _coerce_pct(baseline.get("Unit")) + baseline_component: Optional[float] = _coerce_pct(baseline.get("Component")) - l0_coverage = parse_lcov_coverage(args.l0) if args.l0 else None - l1_coverage = parse_lcov_coverage(args.l1) if args.l1 else None + unit_coverage = parse_lcov_coverage(args.unit) if args.unit else None + component_coverage = parse_lcov_coverage(args.component) if args.component else None # ------------------------------------------------------------------ # Optional: write extracted numbers for baseline update. # Skipped (with a warning) when either suite lacks valid coverage data. # ------------------------------------------------------------------ if args.output_json: - if l0_coverage is not None and l1_coverage is not None: + if unit_coverage is not None and component_coverage is not None: payload = { - "L0": l0_coverage, - "L1": l1_coverage, - "commit": args.commit or "", + "Unit": unit_coverage, + "Component": component_coverage, + "commit": args.commit or "", "timestamp": args.timestamp or "", } try: @@ -246,17 +245,16 @@ def _coerce_pct(value: object) -> Optional[float]: else: print( f" WARNING: --output-json skipped: coverage data incomplete " - f"(L0={l0_coverage}, L1={l1_coverage})", + f"(Unit={unit_coverage}, Component={component_coverage})", file=sys.stderr, ) - l0_ok, l0_result, l0_delta, l0_reason = _suite_analysis(l0_coverage, baseline_l0) - l1_ok, l1_result, l1_delta, l1_reason = _suite_analysis(l1_coverage, baseline_l1) + unit_ok, unit_result, unit_delta, unit_reason = _suite_analysis(unit_coverage, baseline_unit) + component_ok, component_result, component_delta, component_reason = _suite_analysis(component_coverage, baseline_component) - all_ok = l0_ok and l1_ok + all_ok = unit_ok and component_ok status_token = _colored("[PASS]", True) if all_ok else _colored("[WARN]", False) - # Output report print() print(_HEADER) @@ -270,26 +268,26 @@ def _coerce_pct(value: object) -> Optional[float]: print(_SEP) # Coverage table - print(f" {'Suite':<7}{'Current':<9}{'Baseline':<10}{'Delta':<10}Result") + print(f" {'Suite':<12}{'Current':<9}{'Baseline':<10}{'Delta':<10}Result") for name, current, base, result, delta_disp in [ - ("L0", l0_coverage, baseline_l0, l0_result, l0_delta), - ("L1", l1_coverage, baseline_l1, l1_result, l1_delta), + ("Unit", unit_coverage, baseline_unit, unit_result, unit_delta), + ("Component", component_coverage, baseline_component, component_result, component_delta), ]: cur_str = f"{current:.2f}%" if current is not None else "N/A" base_str = f"{base:.2f}%" if base is not None else "N/A" - print(f" {name:<7}{cur_str:<9}{base_str:<10}{delta_disp:<10}{result}") + print(f" {name:<12}{cur_str:<9}{base_str:<10}{delta_disp:<10}{result}") print(_SEP) # Summary + overall bar - warn_suites = [(n, r) for n, r in [("L0", l0_reason), ("L1", l1_reason)] if r] + warn_suites = [(n, r) for n, r in [("Unit", unit_reason), ("Component", component_reason)] if r] summary = _build_summary(warn_suites) if summary: print(f" {summary}") # Notify when one or both suites had no coverage data (artifact absent). - # Gate logic is unchanged — SKIP is treated as passing by design. - skipped = [n for n, cov in [("L0", l0_coverage), ("L1", l1_coverage)] if cov is None] + # Missing data is reported as [WARN]; the gate remains informational. + skipped = [n for n, cov in [("Unit", unit_coverage), ("Component", component_coverage)] if cov is None] if skipped: print(f" NOTE: {_join_names(skipped)} coverage data absent \u2014 artifact missing or unreadable.") diff --git a/.github/scripts/compare_coverage_test.py b/.github/scripts/compare_coverage_test.py index 41e0362..73e956f 100644 --- a/.github/scripts/compare_coverage_test.py +++ b/.github/scripts/compare_coverage_test.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# If not stated otherwise in this file or this component's LICENSE file the -# following copyright and licenses apply: -# -# Copyright 2026 RDK Management +# Copyright 2026 Comcast Cable Communications Management, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 """ Tests for compare_coverage.py @@ -24,7 +22,7 @@ - Integration tests (subprocess) simulating all gate scenarios listed in the Coverage Gate implementation spec. -Workflow-level scenarios (L0 job fails / L1 job fails / both fail) are handled +Workflow-level scenarios (unit_tests fails / component_tests fails / both fail) are handled by GitHub Actions' implicit success() dependency check on the coverage-gate job and cannot be tested at the Python script level; they are documented inline. """ @@ -213,16 +211,16 @@ def test_nonexistent_file_returns_empty_dict(self): # --- Valid JSON ------------------------------------------------------------- - def test_valid_baseline_with_l0_and_l1(self): - p = _write_baseline(self.tmp, {"L0": 80.0, "L1": 85.0}) - self.assertEqual(compare_coverage.load_baseline(p), {"L0": 80.0, "L1": 85.0}) + def test_valid_baseline_with_unit_and_component(self): + p = _write_baseline(self.tmp, {"Unit": 80.0, "Component": 85.0}) + self.assertEqual(compare_coverage.load_baseline(p), {"Unit": 80.0, "Component": 85.0}) def test_valid_empty_json_object(self): p = _write_baseline(self.tmp, {}) self.assertEqual(compare_coverage.load_baseline(p), {}) def test_valid_baseline_extra_keys_preserved(self): - data = {"L0": 80.0, "L1": 85.0, "commit": "abc123", "timestamp": "2026-01-01"} + data = {"Unit": 80.0, "Component": 85.0, "commit": "abc123", "timestamp": "2026-01-01"} p = _write_baseline(self.tmp, data) self.assertEqual(compare_coverage.load_baseline(p), data) @@ -234,7 +232,7 @@ def test_malformed_json_returns_empty_dict(self): self.assertEqual(result, {}) def test_truncated_json_returns_empty_dict(self): - p = self._write_json("truncated.json", '{"L0": 80') + p = self._write_json("truncated.json", '{"Unit": 80') self.assertEqual(compare_coverage.load_baseline(p), {}) def test_empty_file_returns_empty_dict(self): @@ -467,10 +465,10 @@ def _run(self, *args: str) -> subprocess.CompletedProcess: # =========================================================================== def test_s1_exceeds_threshold_and_baseline(self): - bl = self._baseline({"L0": 77.0, "L1": 78.0}) - l0 = self._lcov("l0.info", 100, 80) # 80 % - l1 = self._lcov("l1.info", 100, 82) # 82 % - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 77.0, "Component": 78.0}) + unit_cov = self._lcov("unit.info", 100, 80) # 80 % + component_cov = self._lcov("component.info", 100, 82) # 82 % + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("[PASS]", r.stdout) @@ -480,10 +478,10 @@ def test_s1_exceeds_threshold_and_baseline(self): # =========================================================================== def test_s2_meets_threshold_exactly_meets_baseline(self): - bl = self._baseline({"L0": 70.0, "L1": 70.0}) - l0 = self._lcov("l0.info", 100, 75) # 75.0 % - l1 = self._lcov("l1.info", 100, 75) # 75.0 % - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 70.0, "Component": 70.0}) + unit_cov = self._lcov("unit.info", 100, 75) # 75.0 % + component_cov = self._lcov("component.info", 100, 75) # 75.0 % + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("[PASS]", r.stdout) @@ -493,10 +491,10 @@ def test_s2_meets_threshold_exactly_meets_baseline(self): # =========================================================================== def test_s3_meets_baseline_exactly_exceeds_threshold(self): - bl = self._baseline({"L0": 80.0, "L1": 80.0}) - l0 = self._lcov("l0.info", 100, 80) # 80 % == baseline - l1 = self._lcov("l1.info", 100, 80) # 80 % == baseline - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 80.0, "Component": 80.0}) + unit_cov = self._lcov("unit.info", 100, 80) # 80 % == baseline + component_cov = self._lcov("component.info", 100, 80) # 80 % == baseline + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) # =========================================================================== @@ -505,10 +503,10 @@ def test_s3_meets_baseline_exactly_exceeds_threshold(self): # =========================================================================== def test_s4_meets_both_exactly(self): - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 75) - l1 = self._lcov("l1.info", 100, 75) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 75) + component_cov = self._lcov("component.info", 100, 75) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) # =========================================================================== @@ -517,10 +515,10 @@ def test_s4_meets_both_exactly(self): # =========================================================================== def test_s5_above_threshold_below_baseline(self): - bl = self._baseline({"L0": 85.0, "L1": 85.0}) - l0 = self._lcov("l0.info", 100, 80) # 80 % < 85 % baseline - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 85.0, "Component": 85.0}) + unit_cov = self._lcov("unit.info", 100, 80) # 80 % < 85 % baseline + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("[WARN]", r.stdout) self.assertIn("dropped from baseline", r.stdout) @@ -531,10 +529,10 @@ def test_s5_above_threshold_below_baseline(self): # =========================================================================== def test_s6_below_threshold_meets_baseline(self): - bl = self._baseline({"L0": 70.0, "L1": 70.0}) - l0 = self._lcov("l0.info", 100, 74) # 74 % < 75 % threshold - l1 = self._lcov("l1.info", 100, 74) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 70.0, "Component": 70.0}) + unit_cov = self._lcov("unit.info", 100, 74) # 74 % < 75 % threshold + component_cov = self._lcov("component.info", 100, 74) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("[WARN]", r.stdout) self.assertIn("below threshold", r.stdout) @@ -545,10 +543,10 @@ def test_s6_below_threshold_meets_baseline(self): # =========================================================================== def test_s7_fails_both_threshold_and_baseline(self): - bl = self._baseline({"L0": 85.0, "L1": 85.0}) - l0 = self._lcov("l0.info", 100, 70) # 70 % < threshold AND < baseline - l1 = self._lcov("l1.info", 100, 70) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 85.0, "Component": 85.0}) + unit_cov = self._lcov("unit.info", 100, 70) # 70 % < threshold AND < baseline + component_cov = self._lcov("component.info", 100, 70) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("below threshold", r.stdout) self.assertIn("dropped from baseline", r.stdout) @@ -559,21 +557,21 @@ def test_s7_fails_both_threshold_and_baseline(self): # =========================================================================== def test_s8_baseline_missing_coverage_above_threshold(self): - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) r = self._run( "--baseline", "/nonexistent/coverage-baseline.json", - "--l0", l0, "--l1", l1, + "--unit", unit_cov, "--component", component_cov, ) # No baseline → regression skipped → threshold pass → exit 0 self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) def test_s8_baseline_missing_coverage_below_threshold(self): - l0 = self._lcov("l0.info", 100, 70) # 70 % < 75 % - l1 = self._lcov("l1.info", 100, 70) + unit_cov = self._lcov("unit.info", 100, 70) # 70 % < 75 % + component_cov = self._lcov("component.info", 100, 70) r = self._run( "--baseline", "/nonexistent/coverage-baseline.json", - "--l0", l0, "--l1", l1, + "--unit", unit_cov, "--component", component_cov, ) # Informational only — exit 0 even below threshold; [WARN] shown self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) @@ -588,9 +586,9 @@ def test_s9_malformed_json_above_threshold(self): path = os.path.join(self.tmp, "malformed.json") with open(path, "w") as fh: fh.write("{this is not json}") - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", path, "--l0", l0, "--l1", l1) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", path, "--unit", unit_cov, "--component", component_cov) # Warning must appear in stderr self.assertIn("WARNING", r.stderr) # Fallback to empty baseline → threshold-only → pass @@ -600,9 +598,9 @@ def test_s9_malformed_json_below_threshold(self): path = os.path.join(self.tmp, "malformed2.json") with open(path, "w") as fh: fh.write("{bad json") - l0 = self._lcov("l0.info", 100, 70) - l1 = self._lcov("l1.info", 100, 70) - r = self._run("--baseline", path, "--l0", l0, "--l1", l1) + unit_cov = self._lcov("unit.info", 100, 70) + component_cov = self._lcov("component.info", 100, 70) + r = self._run("--baseline", path, "--unit", unit_cov, "--component", component_cov) self.assertIn("WARNING", r.stderr) # Informational only — exit 0 even below threshold; [WARN] shown self.assertEqual(r.returncode, 0) @@ -612,9 +610,9 @@ def test_s9_empty_json_file(self): path = os.path.join(self.tmp, "empty.json") with open(path, "w") as fh: fh.write("") - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", path, "--l0", l0, "--l1", l1) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", path, "--unit", unit_cov, "--component", component_cov) # Empty file → empty dict baseline → threshold-only → pass self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) @@ -622,9 +620,9 @@ def test_s9_non_dict_json(self): path = os.path.join(self.tmp, "list_json.json") with open(path, "w") as fh: json.dump([1, 2, 3], fh) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", path, "--l0", l0, "--l1", l1) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", path, "--unit", unit_cov, "--component", component_cov) # Non-dict JSON → WARNING emitted, empty dict → threshold-only → pass self.assertIn("WARNING", r.stderr) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) @@ -639,42 +637,42 @@ def test_s9_non_dict_json(self): # it is enforced by the workflow graph. # =========================================================================== - def test_s10_l0_artifacts_absent_l1_passes(self): + def test_s10_unit_artifacts_absent_component_passes(self): """ - Simulates the artifact-level effect: L0 .info absent (download step - with continue-on-error:true produced no file), L1 coverage present - and passing. Script-level: L0 is WARN (data missing), L1 is PASS. + Simulates the artifact-level effect: unit .info absent (download step + with continue-on-error:true produced no file), component coverage present + and passing. Script-level: Unit is WARN (data missing), Component is PASS. """ - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l1 = self._lcov("l1.info", 100, 80) # L0 omitted intentionally - r = self._run("--baseline", bl, "--l1", l1) - # L0 WARN (missing) → overall WARN, but exit 0 (informational) + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + component_cov = self._lcov("component.info", 100, 80) # Unit omitted intentionally + r = self._run("--baseline", bl, "--component", component_cov) + # Unit WARN (missing) → overall WARN, but exit 0 (informational) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("coverage data missing", r.stdout) self.assertIn("[WARN]", r.stdout) # =========================================================================== - # SCENARIO 11 — L1 job fails (symmetric to scenario 10) + # SCENARIO 11 — component_tests job fails (symmetric to scenario 10) # =========================================================================== - def test_s11_l1_artifacts_absent_l0_passes(self): - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 80) # L1 omitted intentionally - r = self._run("--baseline", bl, "--l0", l0) + def test_s11_component_artifacts_absent_unit_passes(self): + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 80) # Component omitted intentionally + r = self._run("--baseline", bl, "--unit", unit_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("coverage data missing", r.stdout) self.assertIn("[WARN]", r.stdout) # =========================================================================== - # SCENARIO 12 — Both L0 AND L1 jobs fail + # SCENARIO 12 — Both unit_tests AND component_tests jobs fail # Coverage Gate does NOT trigger (workflow-level). At script level, both # .info files are absent → both SKIP → exit 0 (harmless; gate is already # blocked at the workflow graph layer before the script is ever called). # =========================================================================== def test_s12_both_artifacts_absent(self): - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - # Neither --l0 nor --l1 provided + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + # Neither --unit nor --component provided r = self._run("--baseline", bl) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) # Both rows + summary line show coverage data missing, OVERALL WARN @@ -691,9 +689,9 @@ def test_s12_both_artifacts_absent(self): def test_s13_missing_required_baseline_arg(self): """Invoking the script without --baseline must fail (argparse error).""" - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--l0", l0, "--l1", l1) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--unit", unit_cov, "--component", component_cov) # argparse exits with code 2 on missing required argument self.assertNotEqual(r.returncode, 0) self.assertTrue(len(r.stderr) > 0, "Error must appear on stderr") @@ -702,32 +700,32 @@ def test_s13_missing_required_baseline_arg(self): # Partial-failure cases: one suite fails, other passes # =========================================================================== - def test_only_l0_fails_gate_warns(self): - """L0 below threshold, L1 passes → overall [WARN] but exit 0.""" - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 70) # 70 % ✗ - l1 = self._lcov("l1.info", 100, 80) # 80 % ✓ - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + def test_only_unit_fails_gate_warns(self): + """Unit below threshold, Component passes → overall [WARN] but exit 0.""" + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 70) # 70 % ✗ + component_cov = self._lcov("component.info", 100, 80) # 80 % ✓ + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) - # L1 row still shows PASS + # Component row still shows PASS self.assertIn("[PASS]", r.stdout) self.assertIn("[WARN]", r.stdout) - def test_only_l1_fails_gate_warns(self): - """L1 below threshold, L0 passes → overall [WARN] but exit 0.""" - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 80) # 80 % ✓ - l1 = self._lcov("l1.info", 100, 70) # 70 % ✗ - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + def test_only_component_fails_gate_warns(self): + """Component below threshold, Unit passes → overall [WARN] but exit 0.""" + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 80) # 80 % ✓ + component_cov = self._lcov("component.info", 100, 70) # 70 % ✗ + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("[WARN]", r.stdout) - def test_only_l0_regresses_gate_warns(self): - """L0 regresses below baseline (still above threshold), L1 passes → [WARN] exit 0.""" - bl = self._baseline({"L0": 85.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 80) # 80 % < 85 % baseline ✗ - l1 = self._lcov("l1.info", 100, 80) # 80 % >= 75 % baseline ✓ - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + def test_only_unit_regresses_gate_warns(self): + """Unit regresses below baseline (still above threshold), Component passes → [WARN] exit 0.""" + bl = self._baseline({"Unit": 85.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 80) # 80 % < 85 % baseline ✗ + component_cov = self._lcov("component.info", 100, 80) # 80 % >= 75 % baseline ✓ + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("[WARN]", r.stdout) @@ -737,16 +735,16 @@ def test_only_l0_regresses_gate_warns(self): def test_first_time_setup_empty_baseline_passes(self): bl = self._baseline({}) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) def test_first_time_setup_empty_baseline_below_threshold_warns(self): bl = self._baseline({}) - l0 = self._lcov("l0.info", 100, 70) - l1 = self._lcov("l1.info", 100, 70) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + unit_cov = self._lcov("unit.info", 100, 70) + component_cov = self._lcov("component.info", 100, 70) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) # Informational only — exit 0 even below threshold; [WARN] shown self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("[WARN]", r.stdout) @@ -756,18 +754,18 @@ def test_first_time_setup_empty_baseline_below_threshold_warns(self): # =========================================================================== def test_overall_pass_token_in_output(self): - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertIn("OVERALL:", r.stdout) self.assertIn("[PASS]", r.stdout) def test_overall_warn_token_in_output(self): - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 70) - l1 = self._lcov("l1.info", 100, 70) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 70) + component_cov = self._lcov("component.info", 100, 70) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertIn("OVERALL:", r.stdout) self.assertIn("[WARN]", r.stdout) # Informational only — always exit 0 @@ -778,13 +776,13 @@ def test_overall_warn_token_in_output(self): # =========================================================================== def test_output_json_written_when_both_pass(self): - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 82) + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 82) out = os.path.join(self.tmp, "new-baseline.json") self._run( "--baseline", bl, - "--l0", l0, "--l1", l1, + "--unit", unit_cov, "--component", component_cov, "--output-json", out, "--commit", "abc123", "--timestamp", "2026-01-01T00:00:00Z", @@ -792,8 +790,8 @@ def test_output_json_written_when_both_pass(self): self.assertTrue(os.path.isfile(out), "output-json must be written") with open(out) as fh: data = json.load(fh) - self.assertEqual(data["L0"], 80.0) - self.assertEqual(data["L1"], 82.0) + self.assertEqual(data["Unit"], 80.0) + self.assertEqual(data["Component"], 82.0) self.assertEqual(data["commit"], "abc123") self.assertEqual(data["timestamp"], "2026-01-01T00:00:00Z") @@ -803,103 +801,103 @@ def test_output_json_written_even_when_gate_fails(self): regardless of gate outcome. The update-baseline step checks `if [ ! -s new-baseline.json ]` separately. """ - bl = self._baseline({"L0": 90.0, "L1": 90.0}) - l0 = self._lcov("l0.info", 100, 80) # 80 % < 90 % baseline → WARN - l1 = self._lcov("l1.info", 100, 80) + bl = self._baseline({"Unit": 90.0, "Component": 90.0}) + unit_cov = self._lcov("unit.info", 100, 80) # 80 % < 90 % baseline → WARN + component_cov = self._lcov("component.info", 100, 80) out = os.path.join(self.tmp, "new-baseline-fail.json") r = self._run( "--baseline", bl, - "--l0", l0, "--l1", l1, + "--unit", unit_cov, "--component", component_cov, "--output-json", out, ) # Informational only — always exit 0 regardless of gate outcome self.assertEqual(r.returncode, 0) self.assertTrue(os.path.isfile(out), "output-json written even on gate warning") - def test_output_json_not_written_when_l0_missing(self): - """When L0 .info is absent, --output-json must NOT be written (data incomplete).""" - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l1 = self._lcov("l1.info", 100, 82) - out = os.path.join(self.tmp, "new-baseline-no-l0.json") - r = self._run("--baseline", bl, "--l1", l1, "--output-json", out) - self.assertFalse(os.path.isfile(out), "output-json must NOT be written when L0 absent") + def test_output_json_not_written_when_unit_missing(self): + """When unit .info is absent, --output-json must NOT be written (data incomplete).""" + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + component_cov = self._lcov("component.info", 100, 82) + out = os.path.join(self.tmp, "new-baseline-no-unit.json") + r = self._run("--baseline", bl, "--component", component_cov, "--output-json", out) + self.assertFalse(os.path.isfile(out), "output-json must NOT be written when unit absent") self.assertIn("WARNING", r.stderr) - def test_output_json_not_written_when_l1_missing(self): - bl = self._baseline({"L0": 75.0, "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 80) - out = os.path.join(self.tmp, "new-baseline-no-l1.json") - r = self._run("--baseline", bl, "--l0", l0, "--output-json", out) - self.assertFalse(os.path.isfile(out), "output-json must NOT be written when L1 absent") + def test_output_json_not_written_when_component_missing(self): + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 80) + out = os.path.join(self.tmp, "new-baseline-no-component.json") + r = self._run("--baseline", bl, "--unit", unit_cov, "--output-json", out) + self.assertFalse(os.path.isfile(out), "output-json must NOT be written when component absent") self.assertIn("WARNING", r.stderr) def test_output_json_not_written_when_both_missing(self): - bl = self._baseline({"L0": 75.0, "L1": 75.0}) + bl = self._baseline({"Unit": 75.0, "Component": 75.0}) out = os.path.join(self.tmp, "new-baseline-neither.json") r = self._run("--baseline", bl, "--output-json", out) self.assertFalse(os.path.isfile(out)) self.assertIn("WARNING", r.stderr) # =========================================================================== - # Baseline coercion: non-float L0/L1 values must not crash the script - # (Fixes comment 2/7 — baseline.get("L0") not validated as float) + # Baseline coercion: non-float Unit/Component values must not crash the script # =========================================================================== - def test_baseline_string_l0_treated_as_missing(self): - """String value for L0 in baseline JSON → coerced to None → threshold-only.""" - bl = self._baseline({"L0": "not-a-number", "L1": 75.0}) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) - # Must not crash; L0 baseline treated as absent → threshold-only → pass + + def test_baseline_string_unit_treated_as_missing(self): + """String value for Unit in baseline JSON → coerced to None → threshold-only.""" + bl = self._baseline({"Unit": "not-a-number", "Component": 75.0}) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) + # Must not crash; Unit baseline treated as absent → threshold-only → pass self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) - def test_baseline_null_l1_treated_as_missing(self): - """null value for L1 in baseline JSON → coerced to None → threshold-only.""" - bl = self._baseline({"L0": 80.0, "L1": None}) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + def test_baseline_null_component_treated_as_missing(self): + """null value for Component in baseline JSON → coerced to None → threshold-only.""" + bl = self._baseline({"Unit": 80.0, "Component": None}) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) def test_baseline_both_non_float_threshold_only(self): - """Both L0/L1 baseline values invalid → both threshold-only → pass if above 75%.""" - bl = self._baseline({"L0": "bad", "L1": "bad"}) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + """Both Unit/Component baseline values invalid → both threshold-only → pass if above 75%.""" + bl = self._baseline({"Unit": "bad", "Component": "bad"}) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) def test_baseline_both_non_float_below_threshold_warns(self): - """Both L0/L1 baseline values invalid → threshold-only → [WARN] exit 0 if below 75%.""" - bl = self._baseline({"L0": "bad", "L1": "bad"}) - l0 = self._lcov("l0.info", 100, 70) - l1 = self._lcov("l1.info", 100, 70) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + """Both Unit/Component baseline values invalid → threshold-only → [WARN] exit 0 if below 75%.""" + bl = self._baseline({"Unit": "bad", "Component": "bad"}) + unit_cov = self._lcov("unit.info", 100, 70) + component_cov = self._lcov("component.info", 100, 70) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("[WARN]", r.stdout) # =========================================================================== # _fmt_timestamp: null/non-string timestamp must not crash the report - # (Fixes comment 8 — only ValueError was caught, not TypeError) # =========================================================================== + def test_null_timestamp_in_baseline_does_not_crash(self): """null timestamp value in baseline JSON → TypeError handled → report still runs.""" - bl = self._baseline({"L0": 80.0, "L1": 80.0, "commit": "abc", "timestamp": None}) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 80.0, "Component": 80.0, "commit": "abc", "timestamp": None}) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) # Must not crash; timestamp renders as fallback; gate passes self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) self.assertIn("OVERALL:", r.stdout) def test_integer_timestamp_in_baseline_does_not_crash(self): """Integer timestamp → TypeError in strptime → handled gracefully.""" - bl = self._baseline({"L0": 80.0, "L1": 80.0, "commit": "abc", "timestamp": 12345}) - l0 = self._lcov("l0.info", 100, 80) - l1 = self._lcov("l1.info", 100, 80) - r = self._run("--baseline", bl, "--l0", l0, "--l1", l1) + bl = self._baseline({"Unit": 80.0, "Component": 80.0, "commit": "abc", "timestamp": 12345}) + unit_cov = self._lcov("unit.info", 100, 80) + component_cov = self._lcov("component.info", 100, 80) + r = self._run("--baseline", bl, "--unit", unit_cov, "--component", component_cov) self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 338cccf..9fdcc1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -366,6 +366,9 @@ jobs: with: python-version: '3.x' + - name: Run coverage script tests + run: python3 .github/scripts/compare_coverage_test.py + - name: Fetch baseline from build-metadata branch # Gracefully handle a missing build-metadata branch (first-time setup) continue-on-error: true @@ -402,8 +405,8 @@ jobs: run: | python3 .github/scripts/compare_coverage.py \ --baseline coverage-baseline.json \ - --l0 ./unit-coverage/filtered_coverage.info \ - --l1 ./component-coverage/filtered_coverage.info + --unit ./unit-coverage/filtered_coverage.info \ + --component ./component-coverage/filtered_coverage.info update_baseline: name: Update Coverage Baseline @@ -471,8 +474,8 @@ jobs: python3 .github/scripts/compare_coverage.py \ --baseline "$BL_ARG" \ - --l0 ./unit-coverage/filtered_coverage.info \ - --l1 ./component-coverage/filtered_coverage.info \ + --unit ./unit-coverage/filtered_coverage.info \ + --component ./component-coverage/filtered_coverage.info \ --output-json new-baseline.json \ --commit "$GITHUB_SHA" \ --timestamp "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" From 003a0139f55d1dc050b324b74d87f1186b2fc4e6 Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Mon, 29 Jun 2026 10:32:20 -0400 Subject: [PATCH 03/39] Fix api test app in CI --- test/api_test_app/main.cpp | 38 +++++++++++++++++++++++--------------- test/api_test_app/utils.h | 5 ++++- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/test/api_test_app/main.cpp b/test/api_test_app/main.cpp index 6e7f5e7..9c96da4 100644 --- a/test/api_test_app/main.cpp +++ b/test/api_test_app/main.cpp @@ -169,9 +169,30 @@ int main(int argc, char** argv) interfaces.emplace_back(std::make_unique()); interfaces.emplace_back(std::make_unique()); - if (!isatty(fileno(stdin))) + if (appConfig.autoRun) + { + int failures = 0; + for (auto& interface : interfaces) + { + std::cout << "Auto-running interface: " << interface->name() << std::endl; + + for (auto& method : interface->methods()) + { + std::cout << "Auto-running method: " << method << std::endl; + interface->runOption(method); + } + failures += interface->failureCount(); + } + if (failures > 0) + { + std::cout << "FAILED: " << failures << " method(s) returned errors" << std::endl; + Firebolt::IFireboltAccessor::Instance().Disconnect(); + return 1; + } + std::cout << "All methods succeeded" << std::endl; + } + else if (!isatty(fileno(stdin))) { - appConfig.autoRun = true; std::string line; while (std::getline(std::cin, line)) { @@ -198,19 +219,6 @@ int main(int argc, char** argv) } } } - else if (appConfig.autoRun) - { - for (auto& interface : interfaces) - { - std::cout << "Auto-running interface: " << interface->name() << std::endl; - - for (auto& method : interface->methods()) - { - std::cout << "Auto-running method: " << method << std::endl; - interface->runOption(method); - } - } - } else { std::vector interfaceNames; diff --git a/test/api_test_app/utils.h b/test/api_test_app/utils.h index 7a3d72e..ec94590 100644 --- a/test/api_test_app/utils.h +++ b/test/api_test_app/utils.h @@ -47,20 +47,23 @@ class DemoBase std::string name() const { return name_; } virtual void runOption(const std::string& method) = 0; const std::vector& methods() const { return methods_; } + int failureCount() const { return failureCount_; } protected: - template bool succeed(const Firebolt::Result& result) const + template bool succeed(const Firebolt::Result& result) { if (result) { return true; } + ++failureCount_; std::cout << "Error: " << static_cast(result.error()) << std::endl; return false; } std::string name_; std::vector methods_; + int failureCount_ = 0; }; template T chooseEnumFromList(const Firebolt::JSON::EnumType& enumType, const std::string& prompt) From cce99fc94ef2081e9ae3a754cfb8f34990a95ac1 Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Mon, 29 Jun 2026 10:47:31 -0400 Subject: [PATCH 04/39] Fix copilot comment --- test/api_test_app/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/api_test_app/main.cpp b/test/api_test_app/main.cpp index 9c96da4..611cfd7 100644 --- a/test/api_test_app/main.cpp +++ b/test/api_test_app/main.cpp @@ -193,6 +193,7 @@ int main(int argc, char** argv) } else if (!isatty(fileno(stdin))) { + appConfig.autoRun = true; std::string line; while (std::getline(std::cin, line)) { From 1fc00ab697cb7e7c05bc4bf1d0b7666432ef379f Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Fri, 26 Jun 2026 13:17:55 -0400 Subject: [PATCH 05/39] RDKEMW-17667 : Add timezone getter method and onTimeZoneChanged event --- docs/openrpc/openrpc/localization.json | 33 ++++++++ docs/openrpc/the-spec/firebolt-open-rpc.json | 81 +++++++++++++++++++- include/firebolt/localization.h | 16 ++++ src/localization_impl.cpp | 11 +++ src/localization_impl.h | 2 + test/component/localizationTest.cpp | 37 +++++++++ test/unit/localizationTest.cpp | 28 +++++++ 7 files changed, 207 insertions(+), 1 deletion(-) diff --git a/docs/openrpc/openrpc/localization.json b/docs/openrpc/openrpc/localization.json index 632f70f..77ad905 100644 --- a/docs/openrpc/openrpc/localization.json +++ b/docs/openrpc/openrpc/localization.json @@ -118,6 +118,39 @@ } } ] + }, + { + "name": "timeZone", + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:time-zone" + ] + } + ], + "summary": "Get the IANA timezone of the device.", + "params": [], + "result": { + "name": "timeZone", + "summary": "The device timezone.", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Default example", + "params": [], + "result": { + "name": "Default Result", + "value": "America/New_York" + } + } + ] } ], "components": { diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 698d4f1..3cc029f 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -1087,6 +1087,39 @@ } ] }, + { + "name": "Localization.timeZone", + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:time-zone" + ] + } + ], + "summary": "Get the IANA timezone of the device.", + "params": [], + "result": { + "name": "timeZone", + "summary": "The device timezone.", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Default example", + "params": [], + "result": { + "name": "Default Result", + "value": "America/New_York" + } + } + ] + }, { "name": "Metrics.ready", "tags": [ @@ -3396,6 +3429,52 @@ } } }, + { + "name": "Localization.onTimeZoneChanged", + "tags": [ + { + "name": "event", + "x-notifier": "Localization.onTimeZoneChanged", + "x-subscriber-for": "Localization.timeZone" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:time-zone" + ] + } + ], + "summary": "Get the IANA timezone of the device.", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, { "name": "Network.onConnectedChanged", "summary": "Returns whether the device currently has a usable network connection.", @@ -4040,4 +4119,4 @@ } } } -} \ No newline at end of file +} diff --git a/include/firebolt/localization.h b/include/firebolt/localization.h index 3eb9dc3..976bb41 100644 --- a/include/firebolt/localization.h +++ b/include/firebolt/localization.h @@ -81,6 +81,22 @@ class ILocalization virtual Result subscribeOnPresentationLanguageChanged(std::function&& notification) = 0; + /** + * @brief Get the IANA timezone of the device. + * + * @retval The device timezone or error + */ + virtual Result timeZone() const = 0; + + /** + * @brief Subscribe on the change of TimeZoneChanged property + * + * @param[in] notification : The callback function + * + * @retval The subscriptionId or error + */ + virtual Result subscribeOnTimeZoneChanged(std::function&& notification) = 0; + /** * @brief Remove subscriber from subscribers list. This method is generic for * all subscriptions diff --git a/src/localization_impl.cpp b/src/localization_impl.cpp index 454363f..b4c733b 100644 --- a/src/localization_impl.cpp +++ b/src/localization_impl.cpp @@ -43,6 +43,17 @@ Result LocalizationImpl::presentationLanguage() const return helper_.get("Localization.presentationLanguage"); } +Result LocalizationImpl::timeZone() const +{ + return helper_.get("Localization.timeZone"); +} + +Result LocalizationImpl::subscribeOnTimeZoneChanged(std::function&& notification) +{ + return subscriptionManager_.subscribe("Localization.onTimeZoneChanged", + std::move(notification)); +} + Result LocalizationImpl::subscribeOnCountryChanged(std::function&& notification) { return subscriptionManager_.subscribe("Localization.onCountryChanged", diff --git a/src/localization_impl.h b/src/localization_impl.h index 80e7cf5..eb9df69 100644 --- a/src/localization_impl.h +++ b/src/localization_impl.h @@ -36,6 +36,7 @@ class LocalizationImpl : public ILocalization Result country() const override; Result> preferredAudioLanguages() const override; Result presentationLanguage() const override; + Result timeZone() const override; // Events Result subscribeOnCountryChanged(std::function&& notification) override; @@ -43,6 +44,7 @@ class LocalizationImpl : public ILocalization std::function&)>&& notification) override; Result subscribeOnPresentationLanguageChanged(std::function&& notification) override; + Result subscribeOnTimeZoneChanged(std::function&& notification) override; Result unsubscribe(SubscriptionId id) override; void unsubscribeAll() override; diff --git a/test/component/localizationTest.cpp b/test/component/localizationTest.cpp index 3f6a228..8953072 100644 --- a/test/component/localizationTest.cpp +++ b/test/component/localizationTest.cpp @@ -145,3 +145,40 @@ TEST_F(LocalizationCTest, subscribeOnPreferredPresentationLanguageChanged) auto result = Firebolt::IFireboltAccessor::Instance().LocalizationInterface().unsubscribe(id.value_or(0)); ASSERT_TRUE(result) << "error on unsubscribe "; } + +TEST_F(LocalizationCTest, TimeZone) +{ + auto result = Firebolt::IFireboltAccessor::Instance().LocalizationInterface().timeZone(); + ASSERT_TRUE(result) << "error on get"; + + auto expectedValue = jsonEngine.get_value("Localization.timeZone").get(); + EXPECT_EQ(*result, expectedValue); +} + +TEST_F(LocalizationCTest, subscribeOnTimeZoneChanged) +{ + auto id = Firebolt::IFireboltAccessor::Instance().LocalizationInterface().subscribeOnTimeZoneChanged( + [&](const std::string& timeZone) + { + EXPECT_EQ(timeZone, "America/New_York"); + { + std::lock_guard lock(mtx); + eventReceived = true; + } + cv.notify_one(); + }); + + ASSERT_TRUE(id) << "error on subscribe "; + EXPECT_TRUE(id.has_value()) << "error on id"; + + // Trigger the event from the mock server + triggerEvent("Localization.onTimeZoneChanged", R"({"value":"America/New_York"})"); + verifyEventReceived(mtx, cv, eventReceived); + + SetUp(); + triggerEvent("Localization.onTimeZoneChanged", R"({"value":12345})"); + verifyEventNotReceived(mtx, cv, eventReceived); + + auto result = Firebolt::IFireboltAccessor::Instance().LocalizationInterface().unsubscribe(id.value_or(0)); + ASSERT_TRUE(result) << "error on unsubscribe "; +} diff --git a/test/unit/localizationTest.cpp b/test/unit/localizationTest.cpp index fb1eaeb..29a5f69 100644 --- a/test/unit/localizationTest.cpp +++ b/test/unit/localizationTest.cpp @@ -113,3 +113,31 @@ TEST_F(LocalizationUTest, subscribeOnPresentationLanguageChanged) auto result = localizationImpl_.unsubscribe(id.value_or(0)); ASSERT_TRUE(result) << "error on unsubscribe "; } + +TEST_F(LocalizationUTest, TimeZone) +{ + auto expectedValue = jsonEngine.get_value("Localization.timeZone").get(); + mock("Localization.timeZone"); + + auto result = localizationImpl_.timeZone(); + ASSERT_TRUE(result) << "error on get"; + + EXPECT_EQ(*result, expectedValue); +} + +TEST_F(LocalizationUTest, TimeZoneBadResponse) +{ + mock_with_response("Localization.timeZone", 12345); + ASSERT_FALSE(localizationImpl_.timeZone()) << "LocalizationImpl::timeZone() did not return an error"; +} + +TEST_F(LocalizationUTest, subscribeOnTimeZoneChanged) +{ + mockSubscribe("Localization.onTimeZoneChanged"); + + auto id = localizationImpl_.subscribeOnTimeZoneChanged([](auto) {}); + ASSERT_TRUE(id) << "error on subscribe "; + EXPECT_TRUE(id.has_value()) << "error on id"; + auto result = localizationImpl_.unsubscribe(id.value_or(0)); + ASSERT_TRUE(result) << "error on unsubscribe "; +} From 05e22bb3d80bac6de570fb3749365632adbea19b Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Mon, 29 Jun 2026 12:05:34 -0400 Subject: [PATCH 06/39] RDKEMW-17667: Fix clang-format issue and onTimeZoneChanged to app OpenRPC spec --- .../the-spec/firebolt-app-open-rpc.json | 37 +++++++++++++++++++ src/localization_impl.cpp | 5 ++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/openrpc/the-spec/firebolt-app-open-rpc.json b/docs/openrpc/the-spec/firebolt-app-open-rpc.json index b2eff49..d026dec 100644 --- a/docs/openrpc/the-spec/firebolt-app-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-app-open-rpc.json @@ -774,6 +774,43 @@ } ] }, + { + "name": "Localization.onTimeZoneChanged", + "tags": [ + { + "name": "notifier", + "x-notifier-for": "Localization.timeZone", + "x-event": "Localization.onTimeZoneChanged" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:time-zone" + ] + } + ], + "summary": "Get the IANA timezone of the device.", + "params": [ + { + "name": "timeZone", + "summary": "The device timezone.", + "schema": { + "type": "string" + } + } + ], + "examples": [ + { + "name": "Default example", + "params": [ + { + "name": "Default Result", + "value": "America/New_York" + } + ] + } + ] + }, { "name": "Network.onConnectedChanged", "summary": "Returns whether the device currently has a usable network connection.", diff --git a/src/localization_impl.cpp b/src/localization_impl.cpp index b4c733b..ff45210 100644 --- a/src/localization_impl.cpp +++ b/src/localization_impl.cpp @@ -48,10 +48,11 @@ Result LocalizationImpl::timeZone() const return helper_.get("Localization.timeZone"); } -Result LocalizationImpl::subscribeOnTimeZoneChanged(std::function&& notification) +Result +LocalizationImpl::subscribeOnTimeZoneChanged(std::function&& notification) { return subscriptionManager_.subscribe("Localization.onTimeZoneChanged", - std::move(notification)); + std::move(notification)); } Result LocalizationImpl::subscribeOnCountryChanged(std::function&& notification) From a93d9d3f55269dec29d9ac97cead116010adbd70 Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Mon, 29 Jun 2026 12:37:05 -0400 Subject: [PATCH 07/39] RDKEMW-17667: Fix comments --- include/firebolt/localization.h | 16 ++++++++-------- src/localization_impl.cpp | 3 +-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/include/firebolt/localization.h b/include/firebolt/localization.h index 976bb41..5131a3e 100644 --- a/include/firebolt/localization.h +++ b/include/firebolt/localization.h @@ -52,6 +52,13 @@ class ILocalization */ virtual Result presentationLanguage() const = 0; + /** + * @brief Get the IANA timezone of the device. + * + * @retval The device timezone or error + */ + virtual Result timeZone() const = 0; + /** * @brief Subscribe on the change of CountryChanged property * @@ -82,14 +89,7 @@ class ILocalization subscribeOnPresentationLanguageChanged(std::function&& notification) = 0; /** - * @brief Get the IANA timezone of the device. - * - * @retval The device timezone or error - */ - virtual Result timeZone() const = 0; - - /** - * @brief Subscribe on the change of TimeZoneChanged property + * @brief Subscribe on the change of timeZone property * * @param[in] notification : The callback function * diff --git a/src/localization_impl.cpp b/src/localization_impl.cpp index ff45210..378401f 100644 --- a/src/localization_impl.cpp +++ b/src/localization_impl.cpp @@ -48,8 +48,7 @@ Result LocalizationImpl::timeZone() const return helper_.get("Localization.timeZone"); } -Result -LocalizationImpl::subscribeOnTimeZoneChanged(std::function&& notification) +Result LocalizationImpl::subscribeOnTimeZoneChanged(std::function&& notification) { return subscriptionManager_.subscribe("Localization.onTimeZoneChanged", std::move(notification)); From 7793784cbb23b2e16a03cec7332519ff20dbc76c Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Mon, 29 Jun 2026 13:51:18 -0400 Subject: [PATCH 08/39] RDKEMW-17667 : Add timeZone method to api test app --- test/api_test_app/apis/localizationDemo.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/api_test_app/apis/localizationDemo.cpp b/test/api_test_app/apis/localizationDemo.cpp index 19bd772..4fd9ee7 100644 --- a/test/api_test_app/apis/localizationDemo.cpp +++ b/test/api_test_app/apis/localizationDemo.cpp @@ -30,6 +30,7 @@ LocalizationDemo::LocalizationDemo() methods_.push_back("Localization.country"); methods_.push_back("Localization.preferredAudioLanguages"); methods_.push_back("Localization.presentationLanguage"); + methods_.push_back("Localization.timeZone"); } void LocalizationDemo::runOption(const std::string& method) @@ -63,4 +64,12 @@ void LocalizationDemo::runOption(const std::string& method) std::cout << "Presentation Language: " << *r << std::endl; } } + else if (method == "Localization.timeZone") + { + auto r = Firebolt::IFireboltAccessor::Instance().LocalizationInterface().timeZone(); + if (succeed(r)) + { + std::cout << "TimeZone: " << *r << std::endl; + } + } } From 0d84595ad2a8d107953f05dbb8f95b9d129b46ae Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Mon, 6 Jul 2026 10:14:39 -0400 Subject: [PATCH 09/39] RDKEMW-14887 : Update Stats.memoryUsage api to return value in bytes (#82) * RDKEMW-14887 : Update Stats.memoryUsage api to return value in bytes * RDKEMW-14887 : Fix spacing issues * address copilot comment * clang format fix --------- Co-authored-by: rajanika Co-authored-by: rradha446 --- docs/openrpc/openrpc/stats.json | 38 ++++++++++--------- .../the-spec/firebolt-app-open-rpc.json | 28 ++++++++------ .../the-spec/firebolt-open-rpc--legacy.json | 38 ++++++++++--------- docs/openrpc/the-spec/firebolt-open-rpc.json | 38 ++++++++++--------- include/firebolt/stats.h | 16 ++++---- src/json_types/stats.h | 19 +++++----- test/api_test_app/apis/statsDemo.cpp | 4 +- test/component/statsTest.cpp | 8 ++-- test/unit/statsTest.cpp | 8 ++-- 9 files changed, 106 insertions(+), 91 deletions(-) diff --git a/docs/openrpc/openrpc/stats.json b/docs/openrpc/openrpc/stats.json index 2b41f70..6b7a917 100644 --- a/docs/openrpc/openrpc/stats.json +++ b/docs/openrpc/openrpc/stats.json @@ -8,7 +8,7 @@ "methods": [ { "name": "memoryUsage", - "summary": "Returns information about container memory usage, in units of 1024 bytes.", + "summary": "Returns information about container memory usage in bytes.", "tags": [ { "name": "capabilities", @@ -32,10 +32,10 @@ "name": "value", "description": "The memory usage information", "value": { - "userMemoryUsedKiB": 123456, - "userMemoryLimitKiB": 789012, - "gpuMemoryUsedKiB": 345678, - "gpuMemoryLimitKiB": 901234 + "userMemoryUsed": 126418944, + "userMemoryLimit": 807948288, + "gpuMemoryUsed": 353974272, + "gpuMemoryLimit": 922863616 } } } @@ -49,28 +49,32 @@ "type": "object", "description": "Describes current and maximum memory usage of the container.", "properties": { - "userMemoryUsedKiB": { + "userMemoryUsed": { "type": "integer", - "description": "User memory currently used in 1024 bytes." + "description": "User memory currently used, in bytes.", + "minimum": 0 }, - "userMemoryLimitKiB": { + "userMemoryLimit": { "type": "integer", - "description": "Maximum user memory available in 1024 bytes." + "description": "Maximum user memory available, in bytes.", + "minimum": 0 }, - "gpuMemoryUsedKiB": { + "gpuMemoryUsed": { "type": "integer", - "description": "GPU memory currently used in 1024 bytes." + "description": "GPU memory currently used, in bytes.", + "minimum": 0 }, - "gpuMemoryLimitKiB": { + "gpuMemoryLimit": { "type": "integer", - "description": "Maximum GPU memory available in 1024 bytes." + "description": "Maximum GPU memory available, in bytes.", + "minimum": 0 } }, "required": [ - "userMemoryUsedKiB", - "userMemoryLimitKiB", - "gpuMemoryUsedKiB", - "gpuMemoryLimitKiB" + "userMemoryUsed", + "userMemoryLimit", + "gpuMemoryUsed", + "gpuMemoryLimit" ] } } diff --git a/docs/openrpc/the-spec/firebolt-app-open-rpc.json b/docs/openrpc/the-spec/firebolt-app-open-rpc.json index d026dec..fd9cd08 100644 --- a/docs/openrpc/the-spec/firebolt-app-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-app-open-rpc.json @@ -1046,28 +1046,32 @@ "type": "object", "description": "Describes current and maximum memory usage of the container.", "properties": { - "userMemoryUsedKiB": { + "userMemoryUsed": { "type": "integer", - "description": "User memory currently used in 1024 bytes." + "description": "User memory currently used, in bytes.", + "minimum": 0 }, - "userMemoryLimitKiB": { + "userMemoryLimit": { "type": "integer", - "description": "Maximum user memory available in 1024 bytes." + "description": "Maximum user memory available, in bytes.", + "minimum": 0 }, - "gpuMemoryUsedKiB": { + "gpuMemoryUsed": { "type": "integer", - "description": "GPU memory currently used in 1024 bytes." + "description": "GPU memory currently used, in bytes.", + "minimum": 0 }, - "gpuMemoryLimitKiB": { + "gpuMemoryLimit": { "type": "integer", - "description": "Maximum GPU memory available in 1024 bytes." + "description": "Maximum GPU memory available, in bytes.", + "minimum": 0 } }, "required": [ - "userMemoryUsedKiB", - "userMemoryLimitKiB", - "gpuMemoryUsedKiB", - "gpuMemoryLimitKiB" + "userMemoryUsed", + "userMemoryLimit", + "gpuMemoryUsed", + "gpuMemoryLimit" ] }, "TTSEnabled": { diff --git a/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json b/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json index ea3b2d0..6383dff 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json @@ -2707,7 +2707,7 @@ }, { "name": "Stats.memoryUsage", - "summary": "Returns information about container memory usage, in units of 1024 bytes.", + "summary": "Returns information about container memory usage in bytes.", "tags": [ { "name": "capabilities", @@ -2731,10 +2731,10 @@ "name": "value", "description": "The memory usage information", "value": { - "userMemoryUsedKiB": 123456, - "userMemoryLimitKiB": 789012, - "gpuMemoryUsedKiB": 345678, - "gpuMemoryLimitKiB": 901234 + "userMemoryUsed": 126418944, + "userMemoryLimit": 807948288, + "gpuMemoryUsed": 353974272, + "gpuMemoryLimit": 922863616 } } } @@ -3530,28 +3530,32 @@ "type": "object", "description": "Describes current and maximum memory usage of the container.", "properties": { - "userMemoryUsedKiB": { + "userMemoryUsed": { "type": "integer", - "description": "User memory currently used in 1024 bytes." + "description": "User memory currently used, in bytes.", + "minimum": 0 }, - "userMemoryLimitKiB": { + "userMemoryLimit": { "type": "integer", - "description": "Maximum user memory available in 1024 bytes." + "description": "Maximum user memory available, in bytes.", + "minimum": 0 }, - "gpuMemoryUsedKiB": { + "gpuMemoryUsed": { "type": "integer", - "description": "GPU memory currently used in 1024 bytes." + "description": "GPU memory currently used, in bytes.", + "minimum": 0 }, - "gpuMemoryLimitKiB": { + "gpuMemoryLimit": { "type": "integer", - "description": "Maximum GPU memory available in 1024 bytes." + "description": "Maximum GPU memory available, in bytes.", + "minimum": 0 } }, "required": [ - "userMemoryUsedKiB", - "userMemoryLimitKiB", - "gpuMemoryUsedKiB", - "gpuMemoryLimitKiB" + "userMemoryUsed", + "userMemoryLimit", + "gpuMemoryUsed", + "gpuMemoryLimit" ] }, "TTSEnabled": { diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 3cc029f..527bb17 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -2261,7 +2261,7 @@ }, { "name": "Stats.memoryUsage", - "summary": "Returns information about container memory usage, in units of 1024 bytes.", + "summary": "Returns information about container memory usage in bytes.", "tags": [ { "name": "capabilities", @@ -2285,10 +2285,10 @@ "name": "value", "description": "The memory usage information", "value": { - "userMemoryUsedKiB": 123456, - "userMemoryLimitKiB": 789012, - "gpuMemoryUsedKiB": 345678, - "gpuMemoryLimitKiB": 901234 + "userMemoryUsed": 126418944, + "userMemoryLimit": 807948288, + "gpuMemoryUsed": 353974272, + "gpuMemoryLimit": 922863616 } } } @@ -3728,28 +3728,32 @@ "type": "object", "description": "Describes current and maximum memory usage of the container.", "properties": { - "userMemoryUsedKiB": { + "userMemoryUsed": { "type": "integer", - "description": "User memory currently used in 1024 bytes." + "description": "User memory currently used, in bytes.", + "minimum": 0 }, - "userMemoryLimitKiB": { + "userMemoryLimit": { "type": "integer", - "description": "Maximum user memory available in 1024 bytes." + "description": "Maximum user memory available, in bytes.", + "minimum": 0 }, - "gpuMemoryUsedKiB": { + "gpuMemoryUsed": { "type": "integer", - "description": "GPU memory currently used in 1024 bytes." + "description": "GPU memory currently used, in bytes.", + "minimum": 0 }, - "gpuMemoryLimitKiB": { + "gpuMemoryLimit": { "type": "integer", - "description": "Maximum GPU memory available in 1024 bytes." + "description": "Maximum GPU memory available, in bytes.", + "minimum": 0 } }, "required": [ - "userMemoryUsedKiB", - "userMemoryLimitKiB", - "gpuMemoryUsedKiB", - "gpuMemoryLimitKiB" + "userMemoryUsed", + "userMemoryLimit", + "gpuMemoryUsed", + "gpuMemoryLimit" ] }, "TTSEnabled": { diff --git a/include/firebolt/stats.h b/include/firebolt/stats.h index 17efcd4..ed4e1e6 100644 --- a/include/firebolt/stats.h +++ b/include/firebolt/stats.h @@ -24,10 +24,10 @@ namespace Firebolt::Stats { struct MemoryInfo { - uint32_t userMemoryUsed; - uint32_t userMemoryLimit; - uint32_t gpuMemoryUsed; - uint32_t gpuMemoryLimit; + uint64_t userMemoryUsed; + uint64_t userMemoryLimit; + uint64_t gpuMemoryUsed; + uint64_t gpuMemoryLimit; }; class IStats @@ -36,10 +36,10 @@ class IStats virtual ~IStats() = default; /** - @brief Returns information about container memory usage, in units of 1024 bytes - * - * @retval MemoryInfo struct or error - */ + * @brief Returns information about container memory usage in bytes. + * + * @retval MemoryInfo struct or error + */ virtual Result memoryUsage() const = 0; }; diff --git a/src/json_types/stats.h b/src/json_types/stats.h index 45576d7..e8ad013 100644 --- a/src/json_types/stats.h +++ b/src/json_types/stats.h @@ -29,15 +29,14 @@ class MemoryInfo : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Stats::Memor public: void fromJson(const nlohmann::json& json) override { - if (!checkRequiredFields(json, - {"userMemoryUsedKiB", "userMemoryLimitKiB", "gpuMemoryUsedKiB", "gpuMemoryLimitKiB"})) + if (!checkRequiredFields(json, {"userMemoryUsed", "userMemoryLimit", "gpuMemoryUsed", "gpuMemoryLimit"})) { throw std::invalid_argument("Missing required fields in JSON"); } - userMemoryUsed = json["userMemoryUsedKiB"].get(); - userMemoryLimit = json["userMemoryLimitKiB"].get(); - gpuMemoryUsed = json["gpuMemoryUsedKiB"].get(); - gpuMemoryLimit = json["gpuMemoryLimitKiB"].get(); + userMemoryUsed = json["userMemoryUsed"].get(); + userMemoryLimit = json["userMemoryLimit"].get(); + gpuMemoryUsed = json["gpuMemoryUsed"].get(); + gpuMemoryLimit = json["gpuMemoryLimit"].get(); } ::Firebolt::Stats::MemoryInfo value() const override { @@ -45,9 +44,9 @@ class MemoryInfo : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Stats::Memor } private: - uint32_t userMemoryUsed; - uint32_t userMemoryLimit; - uint32_t gpuMemoryUsed; - uint32_t gpuMemoryLimit; + uint64_t userMemoryUsed; + uint64_t userMemoryLimit; + uint64_t gpuMemoryUsed; + uint64_t gpuMemoryLimit; }; } // namespace Firebolt::Stats::JsonData diff --git a/test/api_test_app/apis/statsDemo.cpp b/test/api_test_app/apis/statsDemo.cpp index cc2524f..6fe5047 100644 --- a/test/api_test_app/apis/statsDemo.cpp +++ b/test/api_test_app/apis/statsDemo.cpp @@ -38,8 +38,8 @@ void StatsDemo::runOption(const std::string& method) auto r = Firebolt::IFireboltAccessor::Instance().StatsInterface().memoryUsage(); if (succeed(r)) { - std::cout << "User Memory Used: " << r->userMemoryUsed << " / " << r->userMemoryLimit << std::endl; - std::cout << "GPU Memory Used: " << r->gpuMemoryUsed << " / " << r->gpuMemoryLimit << std::endl; + std::cout << "User Memory Used (bytes): " << r->userMemoryUsed << " / " << r->userMemoryLimit << std::endl; + std::cout << "GPU Memory Used (bytes): " << r->gpuMemoryUsed << " / " << r->gpuMemoryLimit << std::endl; } } } diff --git a/test/component/statsTest.cpp b/test/component/statsTest.cpp index 01d8a29..3f9676e 100644 --- a/test/component/statsTest.cpp +++ b/test/component/statsTest.cpp @@ -33,8 +33,8 @@ TEST_F(StatsCTest, MemoryUsage) ASSERT_TRUE(result) << "StatsImpl::memoryUsage() returned an error"; - EXPECT_EQ(result->gpuMemoryLimit, expectedValue.at("gpuMemoryLimitKiB").get()); - EXPECT_EQ(result->gpuMemoryUsed, expectedValue.at("gpuMemoryUsedKiB").get()); - EXPECT_EQ(result->userMemoryLimit, expectedValue.at("userMemoryLimitKiB").get()); - EXPECT_EQ(result->userMemoryUsed, expectedValue.at("userMemoryUsedKiB").get()); + EXPECT_EQ(result->gpuMemoryLimit, expectedValue.at("gpuMemoryLimit").get()); + EXPECT_EQ(result->gpuMemoryUsed, expectedValue.at("gpuMemoryUsed").get()); + EXPECT_EQ(result->userMemoryLimit, expectedValue.at("userMemoryLimit").get()); + EXPECT_EQ(result->userMemoryUsed, expectedValue.at("userMemoryUsed").get()); } diff --git a/test/unit/statsTest.cpp b/test/unit/statsTest.cpp index fb614e7..355695f 100644 --- a/test/unit/statsTest.cpp +++ b/test/unit/statsTest.cpp @@ -35,10 +35,10 @@ TEST_F(StatsUTest, MemoryUsage) ASSERT_TRUE(result) << "StatsImpl::memoryUsage() returned an error"; - EXPECT_EQ(result->userMemoryUsed, expectedValue.at("userMemoryUsedKiB").get()); - EXPECT_EQ(result->userMemoryLimit, expectedValue.at("userMemoryLimitKiB").get()); - EXPECT_EQ(result->gpuMemoryUsed, expectedValue.at("gpuMemoryUsedKiB").get()); - EXPECT_EQ(result->gpuMemoryLimit, expectedValue.at("gpuMemoryLimitKiB").get()); + EXPECT_EQ(result->userMemoryUsed, expectedValue.at("userMemoryUsed").get()); + EXPECT_EQ(result->userMemoryLimit, expectedValue.at("userMemoryLimit").get()); + EXPECT_EQ(result->gpuMemoryUsed, expectedValue.at("gpuMemoryUsed").get()); + EXPECT_EQ(result->gpuMemoryLimit, expectedValue.at("gpuMemoryLimit").get()); } TEST_F(StatsUTest, MemoryUsageBadResponse) From 07c67e32a30a64cf98979016cb11ace4c7656613 Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Tue, 7 Jul 2026 14:05:00 -0400 Subject: [PATCH 10/39] =?UTF-8?q?RDKEMW-17486=20:=20Add=20dolbyAtmosExperi?= =?UTF-8?q?enceAvailable=20getter=20and=20event=20sub=E2=80=A6=20(#86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RDKEMW-17486 : Add dolbyAtmosExperienceAvailable getter and event subscription * RDKEMW-17486 : Fix clang-format issues * RDKEMW-17486 : Address copilot comments --- .../the-spec/firebolt-app-open-rpc.json | 37 ++++++++ .../the-spec/firebolt-open-rpc--legacy.json | 91 +++++++++++++++++++ docs/openrpc/the-spec/firebolt-open-rpc.json | 79 ++++++++++++++++ include/firebolt/device.h | 15 +++ src/device_impl.cpp | 11 +++ src/device_impl.h | 4 + test/api_test_app/apis/deviceDemo.cpp | 9 ++ test/component/deviceTest.cpp | 31 +++++++ test/unit/deviceTest.cpp | 31 +++++++ 9 files changed, 308 insertions(+) diff --git a/docs/openrpc/the-spec/firebolt-app-open-rpc.json b/docs/openrpc/the-spec/firebolt-app-open-rpc.json index fd9cd08..7c090c1 100644 --- a/docs/openrpc/the-spec/firebolt-app-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-app-open-rpc.json @@ -811,6 +811,43 @@ } ] }, + { + "name": "Device.onDolbyAtmosExperienceAvailableChanged", + "summary": "Returns whether Dolby Atmos experience is available on the device", + "tags": [ + { + "name": "notifier", + "x-notifier-for": "Device.dolbyAtmosExperienceAvailable", + "x-event": "Device.onDolbyAtmosExperienceAvailableChanged" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "params": [ + { + "name": "dolbyAtmosExperienceAvailable", + "summary": "Whether Dolby Atmos experience is available on the device", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Getting Dolby Atmos experience availability", + "params": [ + { + "name": "dolbyAtmosExperienceAvailable", + "value": true + } + ] + } + ] + }, { "name": "Network.onConnectedChanged", "summary": "Returns whether the device currently has a usable network connection.", diff --git a/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json b/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json index 6383dff..1bec372 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json @@ -752,6 +752,97 @@ } ] }, + { + "name": "Device.dolbyAtmosExperienceAvailable", + "summary": "Returns whether Dolby Atmos experience is available on the device", + "params": [], + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "dolbyAtmosExperienceAvailable", + "summary": "Whether Dolby Atmos experience is available on the device", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Getting Dolby Atmos experience availability", + "params": [], + "result": { + "name": "Default Result", + "value": true + } + } + ] + }, + { + "name": "Device.onDolbyAtmosExperienceAvailableChanged", + "summary": "Returns whether Dolby Atmos experience is available on the device", + "params": [ + { + "name": "listen", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "subscriber", + "x-subscriber-for": "Device.dolbyAtmosExperienceAvailable" + }, + { + "name": "event", + "x-alternative": "dolbyAtmosExperienceAvailable" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "dolbyAtmosExperienceAvailable", + "summary": "Whether Dolby Atmos experience is available on the device", + "schema": { + "anyOf": [ + { + "$ref": "#/x-schemas/Types/ListenResponse" + }, + { + "type": "boolean" + } + ] + } + }, + "examples": [ + { + "name": "Getting Dolby Atmos experience availability", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "Default Result", + "value": true + } + } + ] + }, { "name": "Discovery.watched", "summary": "Notify the platform that content was partially or completely watched", diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 527bb17..da2da05 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -527,6 +527,39 @@ } ] }, + { + "name": "Device.dolbyAtmosExperienceAvailable", + "summary": "Returns whether Dolby Atmos experience is available on the device", + "params": [], + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "dolbyAtmosExperienceAvailable", + "summary": "Whether Dolby Atmos experience is available on the device", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Getting Dolby Atmos experience availability", + "params": [], + "result": { + "name": "Default Result", + "value": true + } + } + ] + }, { "name": "Discovery.watched", "summary": "Notify the platform that content was partially or completely watched", @@ -3278,6 +3311,52 @@ } } }, + { + "name": "Device.onDolbyAtmosExperienceAvailableChanged", + "summary": "Returns whether Dolby Atmos experience is available on the device", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "event", + "x-notifier": "Device.onDolbyAtmosExperienceAvailableChanged", + "x-subscriber-for": "Device.dolbyAtmosExperienceAvailable" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "examples": [ + { + "name": "Getting Dolby Atmos experience availability", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, { "name": "Localization.onCountryChanged", "tags": [ diff --git a/include/firebolt/device.h b/include/firebolt/device.h index 29c3b0a..37ba5d1 100644 --- a/include/firebolt/device.h +++ b/include/firebolt/device.h @@ -116,6 +116,21 @@ class IDevice * @brief Remove all active subscriptions from subscribers list. */ virtual void unsubscribeAll() = 0; + + /** + * @brief Returns whether Dolby Atmos experience is available on the device + * + * @retval True if Dolby Atmos experience is available, or error + */ + virtual Result dolbyAtmosExperienceAvailable() const = 0; + + /** + * @brief Subscribe to Dolby Atmos experience availability changes + * + * @retval SubscriptionId or error + */ + virtual Result + subscribeOnDolbyAtmosExperienceAvailableChanged(std::function&& notification) = 0; }; } // namespace Firebolt::Device diff --git a/src/device_impl.cpp b/src/device_impl.cpp index 58447ff..f383a27 100644 --- a/src/device_impl.cpp +++ b/src/device_impl.cpp @@ -71,4 +71,15 @@ void DeviceImpl::unsubscribeAll() { subscriptionManager_.unsubscribeAll(); } + +Result DeviceImpl::dolbyAtmosExperienceAvailable() const +{ + return helper_.get("Device.dolbyAtmosExperienceAvailable"); +} + +Result DeviceImpl::subscribeOnDolbyAtmosExperienceAvailableChanged(std::function&& notification) +{ + return subscriptionManager_.subscribe("Device.onDolbyAtmosExperienceAvailableChanged", + std::move(notification)); +} } // namespace Firebolt::Device diff --git a/src/device_impl.h b/src/device_impl.h index ca9a29f..c706427 100644 --- a/src/device_impl.h +++ b/src/device_impl.h @@ -44,6 +44,10 @@ class DeviceImpl : public IDevice Result unsubscribe(SubscriptionId id) override; void unsubscribeAll() override; + Result dolbyAtmosExperienceAvailable() const override; + Result + subscribeOnDolbyAtmosExperienceAvailableChanged(std::function&& notification) override; + private: Firebolt::Helpers::IHelper& helper_; Firebolt::Helpers::SubscriptionManager subscriptionManager_; diff --git a/test/api_test_app/apis/deviceDemo.cpp b/test/api_test_app/apis/deviceDemo.cpp index 94c9cb2..fd63323 100644 --- a/test/api_test_app/apis/deviceDemo.cpp +++ b/test/api_test_app/apis/deviceDemo.cpp @@ -33,6 +33,7 @@ DeviceDemo::DeviceDemo() { methods_.push_back("Device.chipsetId"); methods_.push_back("Device.deviceClass"); + methods_.push_back("Device.dolbyAtmosExperienceAvailable"); methods_.push_back("Device.hdr"); methods_.push_back("Device.timeInActiveState"); methods_.push_back("Device.uid"); @@ -93,4 +94,12 @@ void DeviceDemo::runOption(const std::string& method) std::cout << "Device Uptime (seconds): " << *r << std::endl; } } + else if (method == "Device.dolbyAtmosExperienceAvailable") + { + auto r = Firebolt::IFireboltAccessor::Instance().DeviceInterface().dolbyAtmosExperienceAvailable(); + if (succeed(r)) + { + std::cout << std::boolalpha << "Dolby Atmos Experience Available: " << *r << std::endl; + } + } } diff --git a/test/component/deviceTest.cpp b/test/component/deviceTest.cpp index 289a16a..62f5858 100644 --- a/test/component/deviceTest.cpp +++ b/test/component/deviceTest.cpp @@ -121,3 +121,34 @@ TEST_F(DeviceCTest, SubscribeOnHdrChanged) auto result = Firebolt::IFireboltAccessor::Instance().DeviceInterface().unsubscribe(id.value()); verifyUnsubscribeResult(result); } + +TEST_F(DeviceCTest, DolbyAtmosExperienceAvailable) +{ + auto expectedValue = jsonEngine.get_value("Device.dolbyAtmosExperienceAvailable"); + auto result = Firebolt::IFireboltAccessor::Instance().DeviceInterface().dolbyAtmosExperienceAvailable(); + ASSERT_TRUE(result) << "DeviceImpl::dolbyAtmosExperienceAvailable() returned an error"; + EXPECT_EQ(*result, expectedValue.get()); +} + +TEST_F(DeviceCTest, SubscribeOnDolbyAtmosExperienceAvailableChanged) +{ + auto id = Firebolt::IFireboltAccessor::Instance().DeviceInterface().subscribeOnDolbyAtmosExperienceAvailableChanged( + [&](const bool& value) + { + std::cout << "[Subscription] Device Dolby Atmos experience availability changed" << std::endl; + EXPECT_EQ(value, true); + { + std::lock_guard lock(mtx); + eventReceived = true; + } + cv.notify_one(); + }); + + verifyEventSubscription(id); + + triggerEvent("Device.onDolbyAtmosExperienceAvailableChanged", R"({ "value": true })"); + verifyEventReceived(mtx, cv, eventReceived); + + auto result = Firebolt::IFireboltAccessor::Instance().DeviceInterface().unsubscribe(id.value()); + verifyUnsubscribeResult(result); +} diff --git a/test/unit/deviceTest.cpp b/test/unit/deviceTest.cpp index c708b9a..6ecd042 100644 --- a/test/unit/deviceTest.cpp +++ b/test/unit/deviceTest.cpp @@ -139,3 +139,34 @@ TEST_F(DeviceUTest, SubscribeOnHdrChanged) deviceImpl_.unsubscribe(*result); } + +TEST_F(DeviceUTest, DolbyAtmosExperienceAvailable) +{ + mock("Device.dolbyAtmosExperienceAvailable"); + auto expectedValue = jsonEngine.get_value("Device.dolbyAtmosExperienceAvailable"); + + auto result = deviceImpl_.dolbyAtmosExperienceAvailable(); + ASSERT_TRUE(result) << "DeviceImpl::dolbyAtmosExperienceAvailable() returned an error"; + + EXPECT_EQ(*result, expectedValue.get()); +} + +TEST_F(DeviceUTest, DolbyAtmosExperienceAvailableBadResponse) +{ + mock_with_response("Device.dolbyAtmosExperienceAvailable", "invalid_response"); + ASSERT_FALSE(deviceImpl_.dolbyAtmosExperienceAvailable()) + << "DeviceImpl::dolbyAtmosExperienceAvailable() did not return an error"; +} + +TEST_F(DeviceUTest, SubscribeOnDolbyAtmosExperienceAvailableChanged) +{ + nlohmann::json expectedValue = 1; + mockSubscribe("Device.onDolbyAtmosExperienceAvailableChanged"); + + auto result = deviceImpl_.subscribeOnDolbyAtmosExperienceAvailableChanged([&](const bool& /*value*/) {}); + + ASSERT_TRUE(result) << "DeviceImpl::subscribeOnDolbyAtmosExperienceAvailableChanged() returned an error"; + EXPECT_EQ(*result, expectedValue); + + deviceImpl_.unsubscribe(*result); +} From b4b261e2f3b9a9c7b299b0c00157cf4fb6eb566f Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Wed, 8 Jul 2026 16:06:09 -0400 Subject: [PATCH 11/39] Feature/rdkemw 20911 develop (#89) * RDKEMW-20911 : Return full JSON document from Actions.intent/onIntent * RDKEMW-20911 : Address copilot comments * RDKEMW-20911: Update changelog for v0.6.3 --- CHANGELOG.md | 5 +++++ docs/openrpc/the-spec/firebolt-open-rpc.json | 22 ++++++++++++++------ src/actions_impl.cpp | 4 ++-- src/json_types/actions.h | 14 +++++++++++++ test/component/actionsGeneratedTest.cpp | 10 ++++++--- test/unit/actionsTest.cpp | 6 ++++-- 6 files changed, 48 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cc774a..ece73a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [0.6.3](https://github.com/rdkcentral/firebolt-cpp-client/compare/0.6.2...v0.6.3) + +### Fixed +- `Actions.intent` and `Actions.onIntent` now correctly handle a JSON object payload (`{"intent":"...","intentId":N}`) sent by the Firebolt backend. Previously the client failed to parse the response because it expected a plain string. + ## [0.6.2](https://github.com/rdkcentral/firebolt-cpp-client/compare/v0.6.1...v0.6.2) ### Fixed diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index da2da05..321cbe3 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -66,9 +66,14 @@ "params": [], "result": { "name": "intent", - "summary": "The current intent.", + "summary": "The current intent as a JSON document.", "schema": { - "type": "string" + "type": "object", + "required": ["intent", "intentId"], + "properties": { + "intent": { "type": "string" }, + "intentId": { "type": "integer" } + } } }, "examples": [ @@ -76,7 +81,7 @@ "name": "Get the current intent", "result": { "name": "Default Result", - "value": "launch" + "value": { "intent": "launch", "intentId": 1 } } } ] @@ -107,9 +112,14 @@ ], "result": { "name": "intent", - "summary": "The current intent.", + "summary": "The current intent as a JSON document.", "schema": { - "type": "string" + "type": "object", + "required": ["intent", "intentId"], + "properties": { + "intent": { "type": "string" }, + "intentId": { "type": "integer" } + } } }, "examples": [ @@ -123,7 +133,7 @@ ], "result": { "name": "Default Result", - "value": "launch" + "value": { "intent": "launch", "intentId": 1 } } } ] diff --git a/src/actions_impl.cpp b/src/actions_impl.cpp index b699bca..e65fc4e 100644 --- a/src/actions_impl.cpp +++ b/src/actions_impl.cpp @@ -34,12 +34,12 @@ ActionsImpl::ActionsImpl(Firebolt::Helpers::IHelper& helper) Result ActionsImpl::intent() const { - return helper_.get("Actions.intent"); + return helper_.get("Actions.intent"); } Result ActionsImpl::subscribeOnIntent(std::function&& notification) { - return subscriptionManager_.subscribe("Actions.onIntent", std::move(notification)); + return subscriptionManager_.subscribe("Actions.onIntent", std::move(notification)); } Result ActionsImpl::unsubscribe(SubscriptionId id) diff --git a/src/json_types/actions.h b/src/json_types/actions.h index 60c76ce..732775c 100644 --- a/src/json_types/actions.h +++ b/src/json_types/actions.h @@ -34,6 +34,20 @@ namespace Firebolt::Actions namespace JsonData { +// Serialises any JSON value (object, string, …) to its compact JSON text +// representation. Used for Actions.intent / Actions.onIntent whose wire format +// is the object {"intent":"...","intentId":N} but whose public C++ API surface +// exposes the whole document as a std::string, per the Firebolt 9 spec. +class JsonString : public Firebolt::JSON::NL_Json_Basic +{ +public: + void fromJson(const nlohmann::json& json) override { value_ = json.dump(); } + std::string value() const override { return value_; } + +private: + std::string value_; +}; + } // namespace JsonData } // namespace Firebolt::Actions diff --git a/test/component/actionsGeneratedTest.cpp b/test/component/actionsGeneratedTest.cpp index 38a5356..a8105c0 100644 --- a/test/component/actionsGeneratedTest.cpp +++ b/test/component/actionsGeneratedTest.cpp @@ -36,7 +36,9 @@ TEST_F(ActionsGeneratedCTest, Intent) { auto result = Firebolt::IFireboltAccessor::Instance().ActionsInterface().intent(); ASSERT_TRUE(result) << toError(result); - EXPECT_EQ(*result, "launch"); + auto parsed = nlohmann::json::parse(*result); + EXPECT_EQ(parsed.at("intent").get(), "launch"); + EXPECT_EQ(parsed.at("intentId").get(), 1); } TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) @@ -44,7 +46,9 @@ TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) auto id = Firebolt::IFireboltAccessor::Instance().ActionsInterface().subscribeOnIntent( [&](const std::string& intent) { - EXPECT_EQ(intent, "launch"); + auto parsed = nlohmann::json::parse(intent); + EXPECT_EQ(parsed.at("intent").get(), "launch"); + EXPECT_EQ(parsed.at("intentId").get(), 1); { std::lock_guard lock(mtx); eventReceived = true; @@ -55,7 +59,7 @@ TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) ASSERT_TRUE(id) << toError(id); verifyEventSubscription(id); - triggerEvent("Actions.onIntent", R"("launch")"); + triggerEvent("Actions.onIntent", R"({"intent":"launch","intentId":1})"); verifyEventReceived(mtx, cv, eventReceived); auto result = Firebolt::IFireboltAccessor::Instance().ActionsInterface().unsubscribe(id.value()); diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp index 1b87480..feec5ed 100644 --- a/test/unit/actionsTest.cpp +++ b/test/unit/actionsTest.cpp @@ -28,11 +28,13 @@ class ActionsUTest : public ::testing::Test, protected MockBase TEST_F(ActionsUTest, Start) { - mock_with_response("Actions.intent", "launch"); + mock_with_response("Actions.intent", nlohmann::json({{"intent", "launch"}, {"intentId", 1}})); auto result = actionsImpl_.intent(); ASSERT_TRUE(result) << "ActionsImpl::intent() returned an error"; - EXPECT_EQ(*result, "launch"); + auto parsed = nlohmann::json::parse(*result); + EXPECT_EQ(parsed.at("intent").get(), "launch"); + EXPECT_EQ(parsed.at("intentId").get(), 1); } TEST_F(ActionsUTest, SubscribeOnIntent) From 0c3f3093b551c2bc009fde6b8afcbcb151ccfefb Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Fri, 10 Jul 2026 11:24:46 -0400 Subject: [PATCH 12/39] RDKEMW-20349 : Change discovery.watchedV2 return type to void (#80) * RDKEMW-17483 Change discovery.watchedV2 return type to void * RDKEMW-20349 : Address copilot comments * RDKEMW-20349 : Route discovery.watchedV2 to discovery.watched wire method * RDKEMW-20349: Document Discovery.watched as backward-compatible legacy method --- docs/openrpc/openrpc/discovery.json | 9 ++++----- docs/openrpc/the-spec/firebolt-open-rpc.json | 9 ++++----- include/firebolt/discovery.h | 11 +++++++---- src/discovery_impl.cpp | 4 ++-- src/discovery_impl.h | 2 +- test/api_test_app/apis/discoveryDemo.cpp | 2 +- test/component/discoveryTest.cpp | 2 -- test/unit/discoveryTest.cpp | 8 +++----- 8 files changed, 22 insertions(+), 25 deletions(-) diff --git a/docs/openrpc/openrpc/discovery.json b/docs/openrpc/openrpc/discovery.json index ac06280..bff6a97 100644 --- a/docs/openrpc/openrpc/discovery.json +++ b/docs/openrpc/openrpc/discovery.json @@ -126,7 +126,7 @@ }, { "name": "watchedV2", - "summary": "Notify the platform that content was partially or completely watched, returns whether the notification was accepted", + "summary": "Notify the platform that content was partially or completely watched", "tags": [ { "name": "polymorphic-reducer" @@ -180,9 +180,8 @@ ], "result": { "name": "result", - "summary": "Whether the platform accepted the watched notification", "schema": { - "type": "boolean" + "type": "null" } }, "examples": [ @@ -208,7 +207,7 @@ ], "result": { "name": "result", - "value": true + "value": null } }, { @@ -237,7 +236,7 @@ ], "result": { "name": "result", - "value": true + "value": null } } ] diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 321cbe3..2292d55 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -690,7 +690,7 @@ }, { "name": "Discovery.watchedV2", - "summary": "Notify the platform that content was partially or completely watched, returns whether the notification was accepted", + "summary": "Notify the platform that content was partially or completely watched", "tags": [ { "name": "polymorphic-reducer" @@ -744,9 +744,8 @@ ], "result": { "name": "result", - "summary": "Whether the platform accepted the watched notification", "schema": { - "type": "boolean" + "type": "null" } }, "examples": [ @@ -772,7 +771,7 @@ ], "result": { "name": "result", - "value": true + "value": null } }, { @@ -801,7 +800,7 @@ ], "result": { "name": "result", - "value": true + "value": null } } ] diff --git a/include/firebolt/discovery.h b/include/firebolt/discovery.h index a3e9982..f17bedc 100644 --- a/include/firebolt/discovery.h +++ b/include/firebolt/discovery.h @@ -40,14 +40,17 @@ class IDiscovery * to which content may be directed * * @retval Whether the platform successfully recorded the watched notification, or an error + * + * @note This method is retained for backward compatibility with the original Discovery spec. + * Prefer watchedV2() for new integrations, which returns Result and omits the + * redundant boolean payload. */ virtual Result watched(const std::string& entityId, std::optional progress, std::optional completed, std::optional watchedOn, std::optional agePolicy) const = 0; /** - * @brief Notify the platform that content was partially or completely watched, returns whether the notification - * was accepted + * @brief Notify the platform that content was partially or completely watched * * @param[in] entityId : The entity Id of the watched content * @param[in] progress : How much of the content has been watched (percentage as (0-0.999) for VOD, number of @@ -57,9 +60,9 @@ class IDiscovery * @param[in] agePolicy : The age policy associated with the watch event. The age policy describes the age groups * to which content may be directed * - * @retval Whether the platform accepted the watched notification, or an error + * @retval An ok Result on success, or an error; no value is returned */ - virtual Result watchedV2(const std::string& entityId, std::optional progress, + virtual Result watchedV2(const std::string& entityId, std::optional progress, std::optional completed, std::optional watchedOn, std::optional agePolicy) const = 0; }; diff --git a/src/discovery_impl.cpp b/src/discovery_impl.cpp index 67b0f5b..242702d 100644 --- a/src/discovery_impl.cpp +++ b/src/discovery_impl.cpp @@ -53,7 +53,7 @@ Result DiscoveryImpl::watched(const std::string& entityId, std::optional("Discovery.watched", parameters); } -Result DiscoveryImpl::watchedV2(const std::string& entityId, std::optional progress, +Result DiscoveryImpl::watchedV2(const std::string& entityId, std::optional progress, std::optional completed, std::optional watchedOn, std::optional agePolicy) const { @@ -76,6 +76,6 @@ Result DiscoveryImpl::watchedV2(const std::string& entityId, std::optional parameters["agePolicy"] = Firebolt::JSON::toString(Firebolt::JsonData::AgePolicyEnum, *agePolicy); } - return helper_.get("Discovery.watchedV2", parameters); + return helper_.invoke("Discovery.watched", parameters); } } // namespace Firebolt::Discovery diff --git a/src/discovery_impl.h b/src/discovery_impl.h index 7710e40..285186e 100644 --- a/src/discovery_impl.h +++ b/src/discovery_impl.h @@ -37,7 +37,7 @@ class DiscoveryImpl : public IDiscovery std::optional watchedOn, std::optional agePolicy) const override; - Result watchedV2(const std::string& entityId, std::optional progress, std::optional completed, + Result watchedV2(const std::string& entityId, std::optional progress, std::optional completed, std::optional watchedOn, std::optional agePolicy) const override; diff --git a/test/api_test_app/apis/discoveryDemo.cpp b/test/api_test_app/apis/discoveryDemo.cpp index 932785c..8c8d0ea 100644 --- a/test/api_test_app/apis/discoveryDemo.cpp +++ b/test/api_test_app/apis/discoveryDemo.cpp @@ -87,7 +87,7 @@ void DiscoveryDemo::runOption(const std::string& method) watchedOn, agePolicyOpt); if (succeed(r)) { - std::cout << "Discovery.watchedV2: " << (*r ? "true" : "false") << std::endl; + std::cout << "Discovery.watchedV2: Success" << std::endl; } } } diff --git a/test/component/discoveryTest.cpp b/test/component/discoveryTest.cpp index bdab3a5..8caf34f 100644 --- a/test/component/discoveryTest.cpp +++ b/test/component/discoveryTest.cpp @@ -40,10 +40,8 @@ TEST_F(DiscoveryCTest, Watched) TEST_F(DiscoveryCTest, WatchedV2) { - auto expectedValue = jsonEngine.get_value("Discovery.watchedV2"); auto result = Firebolt::IFireboltAccessor::Instance().DiscoveryInterface().watchedV2("entity123", 0.75f, true, "2024-10-01T12:00:00Z", Firebolt::AgePolicy::ADULT); ASSERT_TRUE(result) << "Failed to call watchedV2"; - EXPECT_EQ(*result, expectedValue.get()); } diff --git a/test/unit/discoveryTest.cpp b/test/unit/discoveryTest.cpp index edba6d9..09dd9da 100644 --- a/test/unit/discoveryTest.cpp +++ b/test/unit/discoveryTest.cpp @@ -78,7 +78,7 @@ TEST_F(DiscoveryUTest, watched_payload) TEST_F(DiscoveryUTest, watchedV2) { - mock("Discovery.watchedV2"); + mockInvoke("Discovery.watched"); std::string entityId = "content123"; std::optional progress = 0.75f; std::optional completed = true; @@ -86,7 +86,6 @@ TEST_F(DiscoveryUTest, watchedV2) std::optional agePolicy = Firebolt::AgePolicy::ADULT; auto result = discoveryImpl_.watchedV2(entityId, progress, completed, watchedOn, agePolicy); ASSERT_TRUE(result) << "Error on watchedV2"; - EXPECT_TRUE(*result); } TEST_F(DiscoveryUTest, watchedV2_payload) @@ -97,13 +96,13 @@ TEST_F(DiscoveryUTest, watchedV2_payload) expected["completed"] = true; expected["watchedOn"] = "2024-06-01T12:00:00Z"; expected["agePolicy"] = "app:adult"; - EXPECT_CALL(mockHelper, getJson("Discovery.watchedV2", _)) + EXPECT_CALL(mockHelper, invoke("Discovery.watched", _)) .WillOnce(Invoke( [&](const std::string& /* methodName */, const nlohmann::json& parameters) { EXPECT_EQ(parameters, expected) << "Parameters do not match expected payload: " << expected.dump() << " but got: " << parameters.dump(); - return Firebolt::Result{nlohmann::json(true)}; + return Firebolt::Result{Firebolt::Error::None}; })); std::string entityId = "content123"; std::optional progress = 0.75f; @@ -112,5 +111,4 @@ TEST_F(DiscoveryUTest, watchedV2_payload) std::optional agePolicy = Firebolt::AgePolicy::ADULT; auto result = discoveryImpl_.watchedV2(entityId, progress, completed, watchedOn, agePolicy); ASSERT_TRUE(result) << "Error on watchedV2"; - EXPECT_TRUE(*result); } From c689a6d562f3b5865cc63df9014c959b14e658a5 Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Tue, 21 Jul 2026 12:44:32 -0400 Subject: [PATCH 13/39] RDKEMW-19315 : Add coding guidelines for firebolt-cpp-client (#91) * RDKEMW-19315 : Add coding guidelines for firebolt-cpp-client * RDKEMW-19315 : Correct factual errors and gaps in coding guidelines * RDKEMW-19315 : Address copilot comments * RDKEMW-19315 : Address copilot comments * RDKEMW-19315 : Address copilot comments * RDKEMW-19315 : Address copilot comments --- .github/copilot-instructions.md | 84 --- .../coding-guidelines.instructions.md | 621 ++++++++++++++++++ 2 files changed, 621 insertions(+), 84 deletions(-) delete mode 100644 .github/copilot-instructions.md create mode 100644 .github/instructions/coding-guidelines.instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index e80616f..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,84 +0,0 @@ -# firebolt-cpp-client Copilot Instructions - -Scope: This file applies to the firebolt-cpp-client repository. - -## Primary goals - -- Preserve API contract correctness across interface, implementation, tests, and OpenRPC fixtures. -- Keep generated surfaces and bespoke conventions aligned. -- Prefer minimal, targeted changes. - -## High-signal workflow - -1. For API-facing changes, update all of the following in one pass: - - `include/firebolt/*.h` - - `src/*_impl.h` and `src/*_impl.cpp` - - `test/unit/*Test.cpp` and `test/component/*Test.cpp` - - `docs/openrpc/the-spec/firebolt-open-rpc.json` when component tests depend on fixture shape. -2. Run component tests after edits. -3. If behavior is generator-owned, patch generator code in sibling repo and regenerate module artifacts. - -## Test commands - -- Local one-shot (current preferred): - - `./run-component-tests-local.sh` - - `./run-component-tests-local.sh --skip-image-build` -- Legacy wrappers may still exist in conversation history; prefer the local script in this repo. -- Unit-only: - - `./run-unit-tests.sh` - -## Actions module rules (important) - -- `Actions.intent` is getter-only: - - takes no parameters - - returns `Result` -- `Actions.onIntent` callback payload is a string value. -- Component event trigger for `Actions.onIntent` should use a string JSON payload (for example `"launch"`), not an object. - -## Generated-code conventions that must be preserved - -- `*Impl` classes should delete copy constructor and copy assignment: - - `ClassName(const ClassName&) = delete;` - - `ClassName& operator=(const ClassName&) = delete;` -- Unless explicitly justified as safe, `*Impl` classes should also delete move operations: - - `ClassName(ClassName&&) = delete;` - - `ClassName& operator=(ClassName&&) = delete;` -- Keep include hygiene strict: - - include `` when using `std::move` - - remove unused includes such as `` when not used -- Keep test names in consistent CamelCase for filtering. - -## Component test expectations - -- Red schema validation lines in logs can be expected for negative-path tests. -- Negative tests must still verify runtime behavior (callbacks not delivered for invalid payloads), not just compile-time surface checks. - -## OpenRPC fixture expectations - -- Module descriptions must match actual API behavior. -- Getter-style methods should carry property tags consistent with the rest of the file (for example `property:readonly` where applicable). -- Keep notifier/subscriber metadata aligned (`x-notifier`, `x-subscriber-for`). - -## Regeneration notes (sibling repo) - -When a change is generator-owned, use `firebolt-sdk-gen` and apply module-scoped output back into this repo. - -Typical flow: - -- From `../firebolt-sdk-gen`: - - `./sync-plan-checklist.sh --profile core --module actions --apply --no-accessor-touchpoints --target-root ../firebolt-cpp-client` - -This keeps migration incremental and avoids unrelated accessor touchpoint churn. - -## CI parity reminders - -- CI uses Dockerized build/test flow and mock-firebolt integration. -- Keep changes compatible with: - - `.github/workflows/ci.yml` - - `.github/scripts/run-component-tests.sh` - -## PR hygiene - -- If a review asks for include-file fixes, prefer precise header/source edits and re-run component tests. -- Do not relax negative tests just to suppress red validation logs. -- Keep commit messages scoped and explicit (example: `fix(actions): address include review comments`). diff --git a/.github/instructions/coding-guidelines.instructions.md b/.github/instructions/coding-guidelines.instructions.md new file mode 100644 index 0000000..51d451e --- /dev/null +++ b/.github/instructions/coding-guidelines.instructions.md @@ -0,0 +1,621 @@ +--- +applyTo: "**/*.h,**/*.cpp,**/CMakeLists.txt" +--- + +# firebolt-cpp-client — Coding Guidelines + +**Scope:** This document governs code generation and modification for the `firebolt-cpp-client` repository. +It is intended for use by both AI agents (Copilot, openspec) and human developers. + +**How to read this document:** +- **Current practice** — observed consistently in the codebase; enforce as-is. +- **Recommended going forward** — not yet consistent but must be adopted for new/modified code. +- **Anti-pattern** — seen in the repo OR likely to be introduced by AI generation; includes why it is wrong in this specific codebase. + +Markers: +- `[ASSUMPTION]` — inferred from code patterns where no explicit policy exists. + +--- + +## 1. Architecture and Module Boundaries + +### 1.1 Three-Layer Architecture + +The codebase has exactly three conceptual layers. Do not collapse or skip layers. + +| Layer | Location | Purpose | +|---|---|---| +| Public API | `include/firebolt/.h` | Pure virtual interfaces; the only surface consumers see | +| Implementation | `src/_impl.h` + `src/_impl.cpp` | Concrete logic; hidden from consumers | +| JSON deserialization | `src/json_types/.h` | Wire format ↔ native type adapters; never exposed publicly | + +**Current practice (confirmed across all existing modules):** +- Every module has exactly one interface (`I`), one impl (`Impl`), and zero or more JSON type adapters. Four modules (discovery, localization, network, presentation) have no `json_types/` file; they use primitive-type helpers (`Firebolt::JSON::String`, `Boolean`, `Unsigned`) directly. +- The singleton entry point is `Firebolt::IFireboltAccessor::Instance()`, implemented in `src/firebolt.cpp` as a Meyers singleton over `FireboltAccessorImpl`. + +**Anti-patterns:** +- Do not add business logic to `src/json_types/*.h` files. They must only do: field validation, field extraction, enum mapping. +- Do not add `#include .h>` inside another module's public header unless there is a direct type dependency. Cross-module dependencies at the public layer risk coupling the consumer's include graph. `include/firebolt/metrics.h` correctly includes `"firebolt/common_types.h"` because it uses `AgePolicy`. +- Do not add new public `include/firebolt/` headers for internal types. Internal types live in `src/`. + +### 1.2 Module Registration + +Every new module must be wired into `src/firebolt.cpp`: +1. Member variable of type `Module::ModuleImpl` in `FireboltAccessorImpl`. +2. Initialised via `Firebolt::Helpers::GetHelperInstance()` in the constructor initialiser list. +3. Accessor method returning `Module::IModule&` override. +4. `unsubscribeAll()` call in the private `unsubscribeAll()` helper — **only if the module supports subscriptions** (i.e., the interface exposes any `subscribeOn*` methods). Note: `Device` currently exposes `subscribeOnHdrChanged(...)` but is absent from `FireboltAccessorImpl::unsubscribeAll()` — see Known Issue below. + +Reference: `src/firebolt.cpp` (`FireboltAccessorImpl` ctor initializer list and `unsubscribeAll()`). + +**Known issue:** `FireboltAccessorImpl::unsubscribeAll()` in `src/firebolt.cpp` does not call `device_.unsubscribeAll()` even though `DeviceImpl` supports subscriptions (`subscribeOnHdrChanged`, `subscribeOnDolbyAtmosExperienceAvailableChanged`). Device subscriptions are never cleaned up on Disconnect. Track this as a separate defect. + +### 1.3 API-Facing Change Discipline + +When making an API-facing change — adding or modifying an interface method, changing a return type, or adding a subscription — update all four artifacts in a single commit: +1. `include/firebolt/.h` +2. `src/_impl.h` and `src/_impl.cpp` +3. `test/unit/Test.cpp` and `test/component/Test.cpp` +4. `docs/openrpc/the-spec/firebolt-open-rpc.json` — when component tests depend on fixture shape. + +Never leave the three layers out of sync after a commit. A compile-passing diff that omits a test update or fixture update is incomplete. + +After the change, run `./run-component-tests-local.sh` to validate all layers together before pushing. + +--- + +## 2. Naming Conventions + +### 2.1 Namespaces + +**Current practice (confirmed in every file):** +- Top-level namespace: `Firebolt` +- Module namespace: `Firebolt::` where `` matches the directory/header name with initial capital (e.g., `Firebolt::Device`, `Firebolt::Lifecycle`, `Firebolt::TextToSpeech`). +- JSON adapter namespace: `Firebolt::::JsonData` for module-local types (e.g., `Firebolt::Device::JsonData`, `Firebolt::Accessibility::JsonData`). +- Cross-module shared JSON types: `Firebolt::JsonData` (e.g., `AgePolicyEnum` in `src/json_types/common.h`). +- Every `.h` and `.cpp` file closes with a namespace-end comment: `} // namespace Firebolt::`. + +**Anti-patterns:** +- Do not use `using namespace Firebolt::Helpers;` (or `using namespace Firebolt::;`) in `.cpp` files. Prefer fully-qualified names or targeted `using Firebolt::Helpers::ClassName;` declarations instead. (Applies retroactively to `stats_impl.cpp` and `lifecycle_impl.cpp` — both currently have `using namespace Firebolt::Helpers;` at file scope — flagged for cleanup.) + +### 2.2 Interfaces and Implementations + +**Current practice:** +- Interface: `I` declared in `include/firebolt/.h` (e.g., `IDevice`, `ILifecycle`, `IActions`). +- Implementation: `Impl` in `src/_impl.h` (e.g., `DeviceImpl`, `LifecycleImpl`). +- Neither the interface nor the impl class is named simply `` — that name is reserved for the namespace. + +### 2.3 Method Names + +**Current practice (confirmed across all modules):** +- Getter methods: lowerCamelCase, no `get` prefix (e.g., `chipsetId()`, `audioDescription()`, `connected()`). +- Subscription methods: `subscribeOn()` returning `Result` (e.g., `subscribeOnHdrChanged`, `subscribeOnCountryChanged`, `subscribeOnIntent`). +- Unsubscription: `unsubscribe(SubscriptionId id)` (universal, module-scoped) and `unsubscribeAll()`. +- Invoke-style (fire-and-forget): verb phrases matching the RPC method (e.g., `ready()`, `signIn()`, `close()`). + +**Anti-patterns:** +- Do not name subscription methods `subscribe()` without the `On` prefix — it violates the established naming scheme. +- Do not use `Get`, `Set`, `Is` prefixes on getter methods. + +### 2.4 RPC Method and Event Name Strings + +**Current practice:** +- Getter/invoke RPC name: `"."` in camelCase (e.g., `"Device.chipsetId"`, `"Metrics.startContent"`). +- Event name: `".on"` (e.g., `"Device.onHdrChanged"`, `"Actions.onIntent"`, `"Lifecycle2.onStateChanged"`). +- `Lifecycle2` is the correct wire name for lifecycle RPCs in this version — do not use `Lifecycle`. +- TextToSpeech event names use lowercase suffixes matching the wire protocol (e.g., `"TextToSpeech.onWillspeak"`, `"TextToSpeech.onSpeechstart"`). These must match the OpenRPC fixture exactly. + +**Anti-patterns:** +- Do not guess RPC or event string names. Always derive them from `docs/openrpc/the-spec/firebolt-open-rpc.json`. + +### 2.5 Enum Names + +**Current practice:** +- C++ enum class names: `SCREAMING_SNAKE_CASE` (e.g., `INITIALIZING`, `ACTIVE`, `KILL_RELOAD`). +- `EnumType` instance variable names: `Enum` (e.g., `LifecycleStateEnum`, `CloseReasonEnum`, `DeviceClassEnum`, `AgePolicyEnum`). +- Wire values in `EnumType` map: lowercase strings matching the OpenRPC fixture (e.g., `{"initializing", ...}`, `{"killReload", ...}`). + +### 2.6 JSON Adapter Classes + +**Current practice:** +- Struct adapters: `class : public Firebolt::JSON::NL_Json_Basic<::>` (e.g., `HDRFormat`, `ClosedCaptionsSettings`, `StateChange`). +- Enum adapter instantiation: `inline const Firebolt::JSON::EnumType Enum({{...}})` at namespace scope. + +### 2.7 Test Class Names + +**Current practice (partial inconsistency — see note):** +- Unit test class: `UTest` inheriting `::testing::Test` and `MockBase` (e.g., `DeviceUTest`, `AccessibilityUTest`). +- Component test class: `CTest` inheriting `::testing::Test` (e.g., `DeviceCTest`, `LifecycleCTest`). +- `ActionsGeneratedUTest` in `test/unit/actionsGeneratedTest.cpp` uses a non-standard name because it is auto-generated. This is acceptable only for auto-generated test files. + +**Recommended going forward:** New manually-authored test files must follow the `UTest` / `CTest` naming. + +--- + +## 3. Header Guards and Include Style + +### 3.1 Header Guards + +**Current practice:** +- Bespoke headers (`include/firebolt/` and most `src/`): use `#pragma once`. +- Auto-generated **interface and impl** headers (e.g., `include/firebolt/actions.h`, `src/actions_impl.h`): use `#ifndef FIREBOLT__H` / `#define FIREBOLT__H` / `#endif` guards — confirmed in `include/firebolt/actions.h` and `src/actions_impl.h`. +- Auto-generated **json_types** headers (e.g., `src/json_types/actions.h`): use `#pragma once` — consistent with all bespoke json_types headers. +- Do not mix both guards in the same file. + +**Recommended going forward:** All new bespoke headers use `#pragma once` exclusively. New auto-generated interface/impl headers follow the `#ifndef`/`#define`/`#endif` pattern; new auto-generated json_types headers follow `#pragma once`. + +### 3.2 Include Style + +**Current practice (confirmed across all existing modules):** +- Includes of public firebolt headers: angle brackets with full path (`#include `, `#include `). +- Includes of implementation-local headers: double quotes without path prefix (`#include "device_impl.h"`, `#include "json_types/device.h"`). +- Includes of the module's own public header from within `_impl.h`: double quotes with full path (`#include "firebolt/device.h"`). + +**Anti-pattern:** Do not `#include ` in `include/firebolt/*.h` public headers. Helpers are an internal abstraction not part of the public API. Consumers must never see `IHelper`. + +### 3.3 Include Hygiene + +**Current practice:** +- Include `` when using `std::move`. +- Remove unused includes such as `` when not used. + +Confirmed observation: `include/firebolt/actions.h` (auto-generated) includes `` at line 30 because it uses `std::move` in the `subscribeOnIntentChanged` default method body. + +--- + +## 4. Class Structure and Copy/Move Semantics + +### 4.1 `*Impl` Class Declaration Order + +**Recommended going forward:** +``` +class Impl : public I +{ +public: + explicit Impl(Firebolt::Helpers::IHelper& helper); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + Impl(Impl&&) = delete; + Impl& operator=(Impl&&) = delete; + ~Impl() override = default; // or override with body when custom cleanup needed + + // method overrides + +private: + Firebolt::Helpers::IHelper& helper_; + Firebolt::Helpers::SubscriptionManager subscriptionManager_; // only if module supports subscriptions +}; +``` + +- All `*Impl` constructors take `Firebolt::Helpers::IHelper&` as their only parameter and must be marked `explicit`. Three impl classes currently lack `explicit` — flagged for cleanup: + - `src/stats_impl.h` — `StatsImpl(Firebolt::Helpers::IHelper&)` + - `src/lifecycle_impl.h` — `LifecycleImpl(Firebolt::Helpers::IHelper&)` + - `src/localization_impl.h` — `LocalizationImpl(Firebolt::Helpers::IHelper&)` +- Method return types and parameter types in `*_impl.h` must exactly match those declared in the corresponding public interface header. Always use `uint32_t` (from ``), never the non-standard POSIX type `u_int32_t`. A type mismatch between interface and override produces an invalid override and fails to compile on non-POSIX targets. + + **Cleanup to fix:** `src/device_impl.h` — `timeInActiveState()` is declared as `u_int32_t`; must be changed to `uint32_t` to match `include/firebolt/device.h`. + +### 4.2 Deleted Copy Operations + +**Current practice:** Copy constructor and copy assignment operator are explicitly deleted in **all** 13 `*Impl` classes and in `FireboltAccessorImpl` in `src/firebolt.cpp`. This is mandatory. + +### 4.3 Move Operations + +All `*Impl` classes and `FireboltAccessorImpl` must explicitly delete the move constructor and move assignment operator, placed immediately after the deleted copy operations: + +```cpp +ClassName(ClassName&&) = delete; +ClassName& operator=(ClassName&&) = delete; +``` + +**Rationale:** These classes hold a non-reassignable reference member (`helper_`) and, where applicable, a `SubscriptionManager`. A compiler-generated move would leave the source object with a dangling reference or corrupted subscription state. Explicit deletion makes the intent clear and prevents accidental moves at call sites. + +**Cleanup to fix:** No `*Impl` class currently deletes move operations. The following files must be updated: +- `src/accessibility_impl.h` +- `src/actions_impl.h` +- `src/advertising_impl.h` +- `src/device_impl.h` +- `src/discovery_impl.h` +- `src/display_impl.h` +- `src/lifecycle_impl.h` +- `src/localization_impl.h` +- `src/metrics_impl.h` +- `src/network_impl.h` +- `src/presentation_impl.h` +- `src/stats_impl.h` +- `src/texttospeech_impl.h` +- `src/firebolt.cpp` (`FireboltAccessorImpl`) + +### 4.4 Destructor + +Always declare `~Impl() override = default;`. Do not define a destructor with an empty body `{}` — an empty body is not custom cleanup and must be written as `= default`. Define a body only when it performs actual cleanup work (e.g., releasing a resource not managed by RAII). + +**Cleanup to fix:** +- `src/stats_impl.h` + `src/stats_impl.cpp` — destructor is declared non-inline with an empty body; replace the declaration with `~StatsImpl() override = default;` in the header and remove the definition from the `.cpp` file. +- `src/lifecycle_impl.h` + `src/lifecycle_impl.cpp` — same issue; apply the same fix. + +### 4.5 Friend Declarations + +Do not use `friend` declarations to grant test classes access to implementation internals. Design the public interface to be testable. If internal state genuinely must be observed in a test, use a `protected` member with a test-only subclass. Friend declarations break encapsulation without providing a durable or type-safe test seam, and no other class in this codebase uses this pattern. + +**Cleanup to fix:** `src/lifecycle_impl.h` — `friend class ::LifecycleTest;` is the only `friend` declaration in the codebase and must be removed. + +--- + +## 5. Error Handling + +### 5.1 `Result` at the API Boundary + +**Current practice (without exception across all existing modules):** +- Every method in `I` returns `Result` or `Result`. +- `Result` is used for methods that send a command and carry no return value (e.g., `Metrics.ready()`, `Lifecycle.close()`). +- Callers check the result with boolean conversion (`if (result)`) or dereference after assertion (`*result`). + +**Anti-pattern:** Do not throw exceptions from public interface methods. Do not return bare `T` where failure is possible. Do not use `std::optional` as a substitute for `Result` — `optional` cannot carry an error code. + +### 5.2 Error Propagation in Implementations + +**Current practice:** Implementations propagate errors by returning the result of `helper_.get<>()`, `helper_.invoke()`, or `subscriptionManager_.subscribe<>()` directly. No intermediate `try-catch` is present in any `*_impl.cpp` file. + +**Anti-pattern:** Do not add `try-catch` blocks in `*_impl.cpp` for errors the helper/transport already handles. Do not swallow errors silently. + +### 5.3 Error Handling in JSON Adapters + +**Current practice (confirmed in `src/json_types/`):** +- `fromJson()` throws `std::invalid_argument("Missing required fields in JSON")` when required fields are absent. This is caught by the framework and converted to `Result` with `Error::InvalidParams`. +- `EnumType::at()` throws when an unknown wire value is encountered — also caught by the framework. +- Do not use `Result` inside `fromJson()`. Throw only. + +--- + +## 6. JSON Deserialization Layer (`src/json_types/`) + +### 6.1 JSON Type File Rules + +**Current practice:** +- Each `src/json_types/.h` includes its corresponding `include/firebolt/.h` and ``. +- No `.cpp` file exists under `src/json_types/` — all JSON adapter logic is header-only. +- JSON adapter classes are defined in the `Firebolt::::JsonData` namespace. + +### 6.2 Struct Adapters + +**Current practice:** +```cpp +class : public Firebolt::JSON::NL_Json_Basic<::> +{ +public: + void fromJson(const nlohmann::json& json) override + { + if (!checkRequiredFields(json, {"field1", "field2"})) + { + throw std::invalid_argument("Missing required fields in JSON"); + } + field1_ = json["field1"].get(); + field2_ = json["field2"].get(); + } + :: value() const override + { + return ::{field1_, field2_}; + } +private: + CppType field1_; + CppType field2_; +}; +``` + +Reference: `src/json_types/accessibility.h` (`ClosedCaptionsSettings`, `VoiceGuidanceSettings`), `src/json_types/device.h` (`HDRFormat`), `src/json_types/lifecycle.h` (`StateChange`). + +### 6.3 Enum Adapters + +**Current practice:** +```cpp +inline const Firebolt::JSON::EnumType<::> Enum({ + {"wire-string", ::::ENUMERATOR}, + ... +}); +``` +Wire strings are lowercase or camelCase matching the OpenRPC fixture exactly. + +Reference: `src/json_types/lifecycle.h` (`LifecycleStateEnum`, `CloseReasonEnum`), `src/json_types/device.h` (`DeviceClassEnum`), `src/json_types/common.h` (`AgePolicyEnum`). + +### 6.4 Unit Conversion in JSON Adapters + +**Current practice (specific to `Stats` module):** +The wire payload uses `*KiB` field names (e.g., `userMemoryUsedKiB`). The JSON adapter in `src/json_types/stats.h` reads the raw KiB values and the public API returns those values in KiB units. Tests in `test/unit/statsTest.cpp` and `test/component/statsTest.cpp` validate against the fixture's raw KiB values. + +**Anti-pattern:** Do not add unit conversion (e.g., ×1024 for KiB→bytes) inside `fromJson()` without a corresponding change to the public API type, tests, and OpenRPC fixture annotation. + +--- + +## 7. Helper Abstraction Usage + +### 7.1 `IHelper` Injection + +**Current practice:** All `*Impl` constructors accept `Firebolt::Helpers::IHelper&` by reference and store it in `helper_`. This allows the unit test `MockHelper` to be injected without a virtual wrapper on the Impl class itself. + +**Anti-pattern:** Do not accept `IHelper*` (pointer) — the codebase consistently uses references. Do not store a copy of the helper. + +### 7.2 `helper_.get(methodName)` — Getter Methods + +**Current practice:** +- No-parameter getters: `helper_.get("Module.method")` +- Primitive types: use `Firebolt::JSON::String`, `Firebolt::JSON::Boolean`, `Firebolt::JSON::Unsigned`, etc. +- Struct types: use the module's `JsonData` class (e.g., `JsonData::HDRFormat`) +- Array types: use `Firebolt::JSON::NL_Json_Array` (e.g., `Localization.preferredAudioLanguages`) + +Reference: `src/device_impl.cpp`, `src/localization_impl.cpp`. + +### 7.3 `helper_.invoke(methodName, params)` — Fire-and-Forget Methods + +**Current practice:** Used for methods that return `Result`. Parameters are constructed as `nlohmann::json` before the call. Optional parameters are conditionally added. + +Reference: `src/metrics_impl.cpp` (all methods), `src/lifecycle_impl.cpp` (`close()`). + +### 7.4 `subscriptionManager_.subscribe(eventName, notification)` — Subscriptions + +**Current practice:** +- `JsonType` is the JSON adapter class, not the native type. +- `notification` is moved via `std::move()`. +- The `subscriptionManager_` is only present if the module exposes subscription methods. + +Reference: `src/accessibility_impl.cpp`, `src/actions_impl.cpp`, `src/lifecycle_impl.cpp`. + +--- + +## 8. Threading and Async Patterns + +### 8.1 Implementation Files + +**Current practice:** No threading primitives (`std::thread`, `std::mutex`, `std::condition_variable`, `std::atomic`) appear in any `*_impl.cpp` file. All async behaviour is delegated to the transport layer via `IHelper`. + +**Anti-pattern:** Do not introduce thread management in `*Impl` classes. The transport manages its own threading. + +### 8.2 Component Tests + +**Current practice (confirmed in all event-bearing component tests):** +```cpp +class ModuleCTest : public ::testing::Test +{ +protected: + void SetUp() override { eventReceived = false; } + std::condition_variable cv; + std::mutex mtx; + bool eventReceived; +}; +``` +Event delivery uses `cv.wait_for(lock, EventWaitTime, [&] { return eventReceived; })` via `verifyEventReceived()` and `verifyEventNotReceived()` from `test/utils.h`. `EventWaitTime` is `std::chrono::seconds(2)` (defined in `test/utils.cpp`). + +**Current practice — triggering events:** +- String payload: `triggerEvent("Module.onEvent", R"("string_value")")` — note outer double-quotes in JSON +- Object payload: `triggerEvent("Module.onEvent", R"({"field": value})")` +- For `Actions.onIntent`, the payload is a JSON-encoded object: `triggerEvent("Actions.onIntent", R"({"intent":"launch","intentId":1})")`. The callback receives this string and must parse it with `nlohmann::json::parse()`. See §12.4 for the full contract. + +Reference: `test/component/actionsGeneratedTest.cpp`, `test/component/deviceTest.cpp`, `test/component/networkTest.cpp`. + +--- + +## 9. Logging + +**Current practice:** `FIREBOLT_LOG_NOTICE("Client", "Version: %s", Version::String)` appears only in `src/firebolt.cpp` at connection time. No logging macros appear in individual module `*_impl.cpp` files. + +**[ASSUMPTION]** The logging macro originates from the `FireboltTransport` dependency, not from this repo. Individual module implementations intentionally do not log. + +**Anti-pattern:** Do not add `std::cout`, `printf`, or `FIREBOLT_LOG_*` calls to `*_impl.cpp` files. Diagnostic output in component tests uses `std::cout` only — this is test-scoped and acceptable. + +--- + +## 10. Testing Patterns + +### 10.1 Unit Tests + +**Current practice:** +- Location: `test/unit/Test.cpp` +- Uses `MockHelper` (GMock) via `MockBase` from `test/unit/mock_helper.h`. +- Test fixture: `class UTest : public ::testing::Test, protected MockBase`. +- Impl is instantiated directly: `Firebolt::::Impl impl_{mockHelper};` +- OpenRPC fixture is read via `JsonEngine` from `MockBase`. + +**Test case rules (confirmed across all unit test files):** +- Happy path getter: call `mock("Module.method")`, then call impl method, then `ASSERT_TRUE(result)` + value check. +- Negative path (bad wire data): call `mock_with_response("Module.method", )`, then `ASSERT_FALSE(result)`. +- Subscribe test: call `mockSubscribe("Module.onEvent")`, subscribe, assert `ASSERT_TRUE(result)`, then call `unsubscribe` and assert success. +- Enum validation: `validate_enum("EnumName", Firebolt::::JsonData::Enum)` checks the fixture's schema against the code's enum map. + +**Anti-pattern:** Do not test `*Impl` via `IFireboltAccessor::Instance()` in unit tests. Unit tests must isolate the impl with a mock helper, not the full singleton. + +**Exception for auto-generated unit tests:** `test/unit/actionsGeneratedTest.cpp` directly instantiates `::testing::NiceMock` without inheriting `MockBase`, and uses `EXPECT_CALL(mockHelper, getJson(...))` rather than the `mock()` / `mock_with_response()` convenience wrappers. This pattern is generator-owned. Do not replicate it in bespoke test files. + +### 10.2 Component Tests + +**Current practice:** +- Location: `test/component/Test.cpp` +- Uses `Firebolt::IFireboltAccessor::Instance()` directly (live transport connection). +- Test fixture: `class CTest : public ::testing::Test` (no `MockBase`). +- Expected values derived from `jsonEngine.get_value("Module.method")` against the OpenRPC fixture. + +**Event delivery tests:** +1. Subscribe with callback that sets `eventReceived = true` and calls `cv.notify_one()`. +2. Call `triggerEvent(...)`. +3. Call `verifyEventReceived(mtx, cv, eventReceived)`. +4. Unsubscribe with `verifyUnsubscribeResult(result)`. + +**Negative event tests (invalid payload):** +1. Subscribe. +2. Call `triggerEvent(...)` with invalid JSON payload. +3. Call `verifyEventNotReceived(mtx, cv, eventReceived)` — callback must NOT fire. +4. Unsubscribe. + +Reference: `test/component/lifecycleTest.cpp` (`subscribeOnState_JSON_RPC_compliant`). + +**Component test log expectations:** Red schema validation lines in the component test log are expected and normal for negative-path tests — they indicate the transport rejected the invalid payload as intended. Do not treat them as test failures and do not suppress them by weakening the test. + +Negative tests must verify runtime behaviour — specifically that callbacks are not delivered when the payload is invalid. A test that merely asserts the code compiles with an invalid type is insufficient. Always pair with `verifyEventNotReceived`. Do not relax or remove a negative test because it produces red schema validation lines. + +### 10.3 Pairing Rule + +**Current practice (confirmed across all existing modules):** Every module has both a unit test file and a component test file. When adding a new module or method: +- Add unit tests in `test/unit/Test.cpp` +- Add component tests in `test/component/Test.cpp` +- Both test files must cover all public API methods +- Each getter/property method must have at minimum: one happy-path test and one bad-response negative test + +### 10.4 Expected Values from OpenRPC + +**Current practice:** Both unit and component tests derive expected values from `jsonEngine.get_value("Module.method")` (the first example in the OpenRPC fixture). Do not hardcode values that duplicate the fixture unless the value requires a type conversion (e.g., enum comparison using `static_cast`). + +Exception: `test/component/actionsGeneratedTest.cpp` hardcodes `"launch"` (the `intent` field) and `1` (the `intentId` field) from the fixture's `Actions.intent` / `Actions.onIntent` example result — permissible only for auto-generated files. + +--- + +## 11. OpenRPC Fixture Alignment + +### 11.1 Fixture Examples and Enum Alignment + +**Current practice:** +- Fixture location: `docs/openrpc/the-spec/firebolt-open-rpc.json` +- Both unit and component test binaries read this file at runtime (path injected via `UT_OPEN_RPC_FILE` define in `test/CMakeLists.txt`). +- When adding or changing a method, the fixture must be updated to include the method, its parameters schema, and at least one example. +- Enum values in code must match `components.schemas..enum` in the fixture — validated by `validate_enum()`. + +**Rule:** When a component test validates against `jsonEngine.get_value("Module.method")`, the fixture's example value must produce the same result as what the live mock-firebolt instance returns. Keep these in sync. + +### 11.2 Fixture Metadata Rules + +**Module descriptions:** The `description` field for each module and method in the fixture must accurately describe the module's actual API behaviour. Do not copy-paste descriptions from other modules. + +**Property tags:** Getter-style methods must carry a `property:readonly` tag where other getter methods in the same file use this tag. Before adding a new getter method to the fixture, verify the tagging pattern used by the surrounding methods. + +**Notifier/subscriber metadata:** Subscription event entries must keep `x-notifier` and `x-subscriber-for` fields aligned with the corresponding getter or property. Adding a subscription event without updating both fields is a fixture defect. + +--- + +## 12. Auto-Generated vs Bespoke Code + +### 12.1 Auto-Generated File Recognition + +Files with the following banner are owned by the `firebolt-sdk-gen` generator tool — do not modify them directly: +``` +// ============================================================================ +// AUTO-GENERATED by fb-gen — DO NOT EDIT +// ============================================================================ +``` + +Confirmed auto-generated files in the repo: +- `include/firebolt/actions.h` +- `src/actions_impl.h` +- `src/actions_impl.cpp` +- `src/json_types/actions.h` +- `test/unit/actionsGeneratedTest.cpp` +- `test/component/actionsGeneratedTest.cpp` + +### 12.2 Modifying Auto-Generated Output + +When a change is generator-owned, use `firebolt-sdk-gen` from the sibling repo: +```bash +./sync-plan-checklist.sh --profile core --module --apply --no-accessor-touchpoints --target-root ../firebolt-cpp-client +``` +Do not hand-edit auto-generated files. If the generated output has a defect, fix the generator. + +### 12.3 Keeping Bespoke and Generated Files Aligned + +When a new bespoke module is added, ensure it follows the same structure as generated modules (`actions`) so the two styles remain similar enough that the generator could own the bespoke code in the future. + +### 12.4 Actions Module API Contract + +`Actions.intent` is a getter-only method: it takes no parameters and returns `Result`. Do not add parameters to it and do not change its return type. + +The `std::string` returned by `Actions.intent()` is a JSON-serialized object, not a plain scalar. Callers must parse it (confirmed in `test/component/actionsGeneratedTest.cpp`): +```cpp +auto result = accessor.ActionsInterface().intent(); +ASSERT_TRUE(result); +auto parsed = nlohmann::json::parse(*result); +EXPECT_EQ(parsed.at("intent").get(), "launch"); +EXPECT_EQ(parsed.at("intentId").get(), 1); +``` +Do not treat the return value as a plain scalar string. + +The `Actions.onIntent` callback also delivers a JSON-serialized object string. The component event trigger must use a JSON-encoded object payload (confirmed at `test/component/actionsGeneratedTest.cpp:62`): +```cpp +triggerEvent("Actions.onIntent", R"({"intent":"launch","intentId":1})") +``` +The callback receives the full JSON string and must parse it with `nlohmann::json::parse(intent)`. Do not use a plain string payload such as `R"("launch")"`. + +The OpenRPC fixture confirms: both `Actions.intent` and `Actions.onIntent` example results are `{"intent": "launch", "intentId": 1}` (verified in `docs/openrpc/the-spec/firebolt-open-rpc.json`). + +--- + +## 13. CMake and Build + +**Current practice:** +- C++ standard: C++17, required (`CXX_STANDARD 17`, `CXX_STANDARD_REQUIRED YES`). +- Warning flags: `-Wall -Wextra -Wpedantic` are unconditionally applied in `CMakeLists.txt`. +- New `*_impl.cpp` files are picked up automatically via `file(GLOB SOURCES CONFIGURE_DEPENDS *.cpp json_types/*.cpp)` in `src/CMakeLists.txt`. +- New test files are picked up automatically via `file(GLOB UNIT_TESTS CONFIGURE_DEPENDS unit/*Test.cpp)` and `file(GLOB COMPONENT_TESTS CONFIGURE_DEPENDS component/*Test.cpp)`. +- Export macro: `FIREBOLTCLIENT_EXPORT` from the generated `firebolt/client_export.h`. Apply to public symbols in `include/firebolt/firebolt.h`. + +**Anti-pattern:** Do not manually list sources in `src/CMakeLists.txt` — the glob handles this. Do not introduce new `CMakeLists.txt` files inside nested subdirectories under `src/` or `test/` (e.g., `src/json_types/`, `test/unit/`, `test/component/`). The top-level `src/CMakeLists.txt` and `test/CMakeLists.txt` already exist and must not be replaced. + +**Formatting enforced by CI:** `clang-format` with the project's `.clang-format` (LLVM-based, column limit 120, 4-space indent, Allman braces, C++17). Running `git ls-files -- '*.cpp' '*.h' | xargs clang-format --dry-run --Werror` is a required CI check. Do not bypass it. + +**CI compatibility:** All build and source changes must remain compatible with the CI workflow. Do not modify build configuration in a way that passes locally but diverges from the Docker-based environment defined in `.github/workflows/ci.yml` and `.github/scripts/run-component-tests.sh`. + +--- + +## 14. Anti-Patterns Catalogue + +The following patterns are explicitly wrong for this codebase. Each entry notes where the risk originates. + +| # | Anti-Pattern | Why It Is Wrong Here | +|---|---|---| +| AP-1 | Returning `std::optional` instead of `Result` from interface methods | Cannot carry an error code; breaks the uniform error contract used across all modules | +| AP-2 | Throwing exceptions from `*_impl.cpp` method bodies | Consumers expect `Result`; exceptions escape the module boundary unexpectedly | +| AP-3 | Adding `unique_ptr` or `shared_ptr` for module ownership in `FireboltAccessorImpl` | All modules are owned by value in `FireboltAccessorImpl`; smart pointers add indirection with no benefit here | +| AP-4 | Storing `IHelper` by pointer | Consistent reference storage; pointer would allow null and is not the established contract | +| AP-5 | Making `*Impl` classes copyable or movable | They hold a non-owning reference (`helper_`) and a `SubscriptionManager`; copying/moving would silently break subscription ownership tracking | +| AP-6 | Calling `IFireboltAccessor::Instance()` in unit tests | Unit tests must isolate the impl with `MockHelper`; the singleton instantiates real transport | +| AP-7 | Hardcoding JSON field names as magic strings in `*_impl.cpp` | Field names must live in `src/json_types/` only; impl code must not parse JSON directly | +| AP-8 | Adding `nlohmann::json` includes to `include/firebolt/*.h` | Public headers must not expose the JSON library as a transitive dependency | +| AP-9 | Adding logging to `*_impl.cpp` | Logging is intentionally absent in module implementations; all diagnostics go through the transport layer | +| AP-10 | Writing a new module that omits `subscribeOnStateChanged`-style subscription when the OpenRPC spec has `on*` events | Subscriptions are load-bearing API surface; omitting them silently breaks consumer event handling | +| AP-11 | Assuming `Actions.onIntent` uses a plain string trigger payload | The actual payload is a JSON-encoded object: `triggerEvent("Actions.onIntent", R"({"intent":"launch","intentId":1})")` (confirmed at `test/component/actionsGeneratedTest.cpp:62`). Always check each module's component test file and the OpenRPC fixture for the correct payload shape before writing event trigger calls | +| AP-12 | Using `#include ` or other heavyweight headers without a direct use | Unnecessary includes increase compile time and leak transitive dependencies into consumers' include graphs. `-Wall -Wextra -Wpedantic` do not warn on unused includes; keep includes minimal as a discipline, not for warning suppression. `` is the canonical example of a heavyweight header with no use in this codebase | +| AP-13 | Defining a new public header without `#pragma once` | Bespoke headers require `#pragma once`; `#ifndef` guards are reserved for auto-generated output | +| AP-14 | Modifying auto-generated files by hand | Files with `// AUTO-GENERATED by fb-gen — DO NOT EDIT` must be regenerated via the generator tool | + +--- + +## 15. Explicit Assumptions + +The following items are inferred from code patterns where no explicit policy documentation existed. They are treated as policy until contradicted. + +| ID | Assumption | +|---|---| +| A-1 | `FIREBOLT_LOG_*` macros come from `FireboltTransport`. Individual module impls intentionally omit logging — inferred from the absence of any logging in 12 of 13 impl files. | +| A-2 | The `using namespace Firebolt::Helpers;` pattern in `stats_impl.cpp` and `lifecycle_impl.cpp` is incidental rather than policy — inferred from its absence in the other 11 impl files. | +| A-3 | `StatsImpl`'s, `LifecycleImpl`'s, and `LocalizationImpl`'s non-`explicit` constructors are legacy remnants — confirmed at `src/stats_impl.h:30`, `src/lifecycle_impl.h:37`, `src/localization_impl.h:29`. `StatsImpl`'s non-`= default` destructor is also a remnant. All other impls use `explicit` and `= default`. | +| A-4 | Wire names for TextToSpeech events (`onWillspeak`, `onSpeechstart`, etc.) are lowercase-concatenated because the Firebolt protocol lowercases them — inferred from the pattern in `src/texttospeech_impl.cpp` and the absence of a different naming convention for other modules' events. | + +--- + +## 16. Relationship to Existing Policy Files + +This document is the single authoritative source for coding conventions and workflow rules in the `firebolt-cpp-client` repository. It supersedes `.github/copilot-instructions.md`, which has been absorbed into this document and deleted. + +`CONTRIBUTING.md` governs contribution process. This document governs code shape and workflow. + +--- + +## 17. Test Execution Commands + +**Component tests (current preferred, local):** +```bash +./run-component-tests-local.sh +./run-component-tests-local.sh --skip-image-build # reuse existing Docker image +``` + +**Unit tests only:** +```bash +./run-unit-tests.sh +``` + +Always run component tests after any API-facing change. Component tests run in Docker against mock-firebolt and are the authoritative validation gate. From e1bb21ca4851e27844fba579df0a7056ed4979c3 Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Mon, 3 Aug 2026 12:02:21 -0400 Subject: [PATCH 14/39] Sync develop with v0.6.3 changes (#95) * Support/v0.6.3 (#90) * RDKEMW-20911 : Return full JSON document from Actions.intent/onIntent * RDKEMW-20911 : Address copilot comments * RDKEMW-20911: Update changelog for v0.6.3 * RDKEMW-20911 : Fix intent type as object and not string * RDKEMW-21724 : Update Actions module per Firebolt 9 spec * RDKEMW-21724 : Address copilot comments * RDKEMW-21724 : Address copilot comments * RDKEMW-21724 : Update OpenRPC intent schema to reflect required sub-fields * RDKEMW-21724 : Make context/source optional and use typed IntentData for start * RDKEMW-21724 : Fix OpenRPC schema * RDKEMW-21724 : Fix actionsDemo * RDKEMW-21724 : Address copilot comments --------- Co-authored-by: Brendan O'Bra --- docs/openrpc/the-spec/firebolt-open-rpc.json | 341 ++++++++----------- include/firebolt/actions.h | 28 +- src/actions_impl.cpp | 23 +- src/actions_impl.h | 6 +- src/json_types/actions.h | 33 +- test/api_test_app/apis/actionsDemo.cpp | 108 ++++++ test/api_test_app/apis/actionsDemo.h | 30 ++ test/api_test_app/apis/lifecycleDemo.cpp | 6 +- test/api_test_app/apis/lifecycleDemo.h | 1 + test/api_test_app/main.cpp | 2 + test/component/actionsGeneratedTest.cpp | 27 +- test/unit/actionsTest.cpp | 31 +- 12 files changed, 408 insertions(+), 228 deletions(-) create mode 100644 test/api_test_app/apis/actionsDemo.cpp create mode 100644 test/api_test_app/apis/actionsDemo.h diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 2292d55..98fdd71 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -69,10 +69,28 @@ "summary": "The current intent as a JSON document.", "schema": { "type": "object", - "required": ["intent", "intentId"], + "required": [ + "intent", + "intentId" + ], "properties": { - "intent": { "type": "string" }, - "intentId": { "type": "integer" } + "intent": { + "type": "object", + "required": ["action"], + "properties": { + "action": { "type": "string" }, + "context": { + "type": "object", + "properties": { + "source": { "type": "string" } + } + } + } + }, + "intentId": { + "type": "integer", + "minimum": 0 + } } } }, @@ -81,7 +99,15 @@ "name": "Get the current intent", "result": { "name": "Default Result", - "value": { "intent": "launch", "intentId": 1 } + "value": { + "intent": { + "action": "pre-load", + "context": { + "source": "system" + } + }, + "intentId": 0 + } } } ] @@ -115,10 +141,28 @@ "summary": "The current intent as a JSON document.", "schema": { "type": "object", - "required": ["intent", "intentId"], + "required": [ + "intent", + "intentId" + ], "properties": { - "intent": { "type": "string" }, - "intentId": { "type": "integer" } + "intent": { + "type": "object", + "required": ["action"], + "properties": { + "action": { "type": "string" }, + "context": { + "type": "object", + "properties": { + "source": { "type": "string" } + } + } + } + }, + "intentId": { + "type": "integer", + "minimum": 0 + } } } }, @@ -133,7 +177,81 @@ ], "result": { "name": "Default Result", - "value": { "intent": "launch", "intentId": 1 } + "value": { + "intent": { + "action": "pre-load", + "context": { + "source": "system" + } + }, + "intentId": 0 + } + } + } + ] + }, + { + "name": "Actions.start", + "summary": "Sends an intent to the platform.", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:actions:intent" + ] + } + ], + "params": [ + { + "name": "intent", + "summary": "The intent to send, as a JSON document.", + "required": true, + "schema": { + "type": "object", + "required": ["action"], + "properties": { + "action": { "type": "string" }, + "context": { + "type": "object", + "properties": { + "source": { "type": "string" } + } + } + } + } + }, + { + "name": "handlerAppId", + "summary": "Optional ID of the application that should handle the intent.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Start an intent", + "params": [ + { + "name": "intent", + "value": { + "action": "pre-load", + "context": { + "source": "system" + } + } + } + ], + "result": { + "name": "Default Result", + "value": null } } ] @@ -537,39 +655,6 @@ } ] }, - { - "name": "Device.dolbyAtmosExperienceAvailable", - "summary": "Returns whether Dolby Atmos experience is available on the device", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "dolbyAtmosExperienceAvailable", - "summary": "Whether Dolby Atmos experience is available on the device", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Getting Dolby Atmos experience availability", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, { "name": "Discovery.watched", "summary": "Notify the platform that content was partially or completely watched", @@ -690,7 +775,7 @@ }, { "name": "Discovery.watchedV2", - "summary": "Notify the platform that content was partially or completely watched", + "summary": "Notify the platform that content was partially or completely watched, returns whether the notification was accepted", "tags": [ { "name": "polymorphic-reducer" @@ -744,8 +829,9 @@ ], "result": { "name": "result", + "summary": "Whether the platform accepted the watched notification", "schema": { - "type": "null" + "type": "boolean" } }, "examples": [ @@ -771,7 +857,7 @@ ], "result": { "name": "result", - "value": null + "value": true } }, { @@ -800,7 +886,7 @@ ], "result": { "name": "result", - "value": null + "value": true } } ] @@ -1129,39 +1215,6 @@ } ] }, - { - "name": "Localization.timeZone", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:time-zone" - ] - } - ], - "summary": "Get the IANA timezone of the device.", - "params": [], - "result": { - "name": "timeZone", - "summary": "The device timezone.", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": "America/New_York" - } - } - ] - }, { "name": "Metrics.ready", "tags": [ @@ -2303,7 +2356,7 @@ }, { "name": "Stats.memoryUsage", - "summary": "Returns information about container memory usage in bytes.", + "summary": "Returns information about container memory usage, in units of 1024 bytes.", "tags": [ { "name": "capabilities", @@ -2327,10 +2380,10 @@ "name": "value", "description": "The memory usage information", "value": { - "userMemoryUsed": 126418944, - "userMemoryLimit": 807948288, - "gpuMemoryUsed": 353974272, - "gpuMemoryLimit": 922863616 + "userMemoryUsedKiB": 123456, + "userMemoryLimitKiB": 789012, + "gpuMemoryUsedKiB": 345678, + "gpuMemoryLimitKiB": 901234 } } } @@ -3320,52 +3373,6 @@ } } }, - { - "name": "Device.onDolbyAtmosExperienceAvailableChanged", - "summary": "Returns whether Dolby Atmos experience is available on the device", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Device.onDolbyAtmosExperienceAvailableChanged", - "x-subscriber-for": "Device.dolbyAtmosExperienceAvailable" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "examples": [ - { - "name": "Getting Dolby Atmos experience availability", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, { "name": "Localization.onCountryChanged", "tags": [ @@ -3517,52 +3524,6 @@ } } }, - { - "name": "Localization.onTimeZoneChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onTimeZoneChanged", - "x-subscriber-for": "Localization.timeZone" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:time-zone" - ] - } - ], - "summary": "Get the IANA timezone of the device.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, { "name": "Network.onConnectedChanged", "summary": "Returns whether the device currently has a usable network connection.", @@ -3816,32 +3777,28 @@ "type": "object", "description": "Describes current and maximum memory usage of the container.", "properties": { - "userMemoryUsed": { + "userMemoryUsedKiB": { "type": "integer", - "description": "User memory currently used, in bytes.", - "minimum": 0 + "description": "User memory currently used in 1024 bytes." }, - "userMemoryLimit": { + "userMemoryLimitKiB": { "type": "integer", - "description": "Maximum user memory available, in bytes.", - "minimum": 0 + "description": "Maximum user memory available in 1024 bytes." }, - "gpuMemoryUsed": { + "gpuMemoryUsedKiB": { "type": "integer", - "description": "GPU memory currently used, in bytes.", - "minimum": 0 + "description": "GPU memory currently used in 1024 bytes." }, - "gpuMemoryLimit": { + "gpuMemoryLimitKiB": { "type": "integer", - "description": "Maximum GPU memory available, in bytes.", - "minimum": 0 + "description": "Maximum GPU memory available in 1024 bytes." } }, "required": [ - "userMemoryUsed", - "userMemoryLimit", - "gpuMemoryUsed", - "gpuMemoryLimit" + "userMemoryUsedKiB", + "userMemoryLimitKiB", + "gpuMemoryUsedKiB", + "gpuMemoryLimitKiB" ] }, "TTSEnabled": { @@ -4211,4 +4168,4 @@ } } } -} +} \ No newline at end of file diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index 660abd6..dd93d7e 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -26,22 +26,37 @@ #include #include #include -#include #include -#include namespace Firebolt::Actions { +struct IntentContext +{ + std::optional source; +}; + +struct IntentData +{ + std::string action; + std::optional context; +}; + +struct Intent +{ + IntentData intent; + uint32_t intentId{0}; +}; + class IActions { public: virtual ~IActions() = default; - virtual Result intent() const = 0; + virtual Result intent() const = 0; - virtual Result subscribeOnIntent(std::function&& notification) = 0; - virtual Result subscribeOnIntentChanged(std::function&& notification) + virtual Result subscribeOnIntent(std::function&& notification) = 0; + virtual Result subscribeOnIntentChanged(std::function&& notification) { return subscribeOnIntent(std::move(notification)); } @@ -49,6 +64,9 @@ class IActions virtual Result unsubscribe(SubscriptionId id) = 0; virtual void unsubscribeAll() = 0; + virtual Result start(const IntentData& intent, + std::optional handlerAppId = std::nullopt) const = 0; + }; // class IActions } // namespace Firebolt::Actions diff --git a/src/actions_impl.cpp b/src/actions_impl.cpp index e65fc4e..887fb55 100644 --- a/src/actions_impl.cpp +++ b/src/actions_impl.cpp @@ -32,14 +32,29 @@ ActionsImpl::ActionsImpl(Firebolt::Helpers::IHelper& helper) { } -Result ActionsImpl::intent() const +Result ActionsImpl::intent() const { - return helper_.get("Actions.intent"); + return helper_.get("Actions.intent"); } -Result ActionsImpl::subscribeOnIntent(std::function&& notification) +Result ActionsImpl::subscribeOnIntent(std::function&& notification) { - return subscriptionManager_.subscribe("Actions.onIntent", std::move(notification)); + return subscriptionManager_.subscribe("Actions.onIntent", std::move(notification)); +} + +Result ActionsImpl::start(const IntentData& intent, std::optional handlerAppId) const +{ + nlohmann::json params; + params["intent"]["action"] = intent.action; + if (intent.context && intent.context->source) + { + params["intent"]["context"]["source"] = *intent.context->source; + } + if (handlerAppId) + { + params["handlerAppId"] = *handlerAppId; + } + return helper_.invoke("Actions.start", params); } Result ActionsImpl::unsubscribe(SubscriptionId id) diff --git a/src/actions_impl.h b/src/actions_impl.h index 4c8d6ba..523f82d 100644 --- a/src/actions_impl.h +++ b/src/actions_impl.h @@ -36,9 +36,11 @@ class ActionsImpl : public IActions ActionsImpl& operator=(const ActionsImpl&) = delete; ~ActionsImpl() override = default; - Result intent() const override; + Result intent() const override; - Result subscribeOnIntent(std::function&& notification) override; + Result subscribeOnIntent(std::function&& notification) override; + + Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const override; Result unsubscribe(SubscriptionId id) override; void unsubscribeAll() override; diff --git a/src/json_types/actions.h b/src/json_types/actions.h index 732775c..4335be8 100644 --- a/src/json_types/actions.h +++ b/src/json_types/actions.h @@ -26,7 +26,7 @@ #include "firebolt/actions.h" #include #include -#include +#include namespace Firebolt::Actions { @@ -34,18 +34,33 @@ namespace Firebolt::Actions namespace JsonData { -// Serialises any JSON value (object, string, …) to its compact JSON text -// representation. Used for Actions.intent / Actions.onIntent whose wire format -// is the object {"intent":"...","intentId":N} but whose public C++ API surface -// exposes the whole document as a std::string, per the Firebolt 9 spec. -class JsonString : public Firebolt::JSON::NL_Json_Basic +// Deserialises the wire object {"intent":{"action":"...","context":{"source":"..."}},"intentId":N} +// into Firebolt::Actions::Intent. nlohmann stays hidden in this impl-layer header. +class JsonValue : public Firebolt::JSON::NL_Json_Basic { public: - void fromJson(const nlohmann::json& json) override { value_ = json.dump(); } - std::string value() const override { return value_; } + void fromJson(const nlohmann::json& json) override + { + value_ = {}; + if (!checkRequiredFields(json, {"intent", "intentId"}) || !json["intent"].is_object() || + !checkRequiredFields(json["intent"], {"action"})) + { + throw std::invalid_argument("Missing required fields in JSON"); + } + value_.intent.action = json["intent"]["action"].get(); + if (json["intent"].contains("context") && json["intent"]["context"].is_object()) + { + IntentContext ctx; + if (json["intent"]["context"].contains("source")) + ctx.source = json["intent"]["context"]["source"].get(); + value_.intent.context = ctx; + } + value_.intentId = json["intentId"].get(); + } + Intent value() const override { return value_; } private: - std::string value_; + Intent value_; }; } // namespace JsonData diff --git a/test/api_test_app/apis/actionsDemo.cpp b/test/api_test_app/apis/actionsDemo.cpp new file mode 100644 index 0000000..bb1fd26 --- /dev/null +++ b/test/api_test_app/apis/actionsDemo.cpp @@ -0,0 +1,108 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "actionsDemo.h" +#include +#include +#include +#include + +using namespace Firebolt; +using namespace Firebolt::Actions; + +ActionsDemo::ActionsDemo() + : DemoBase("Actions") +{ + methods_.push_back("Actions.intent"); + methods_.push_back("Actions.start"); + methods_.push_back("Actions.onIntent"); + methods_.push_back("Actions.unsubscribe"); + methods_.push_back("Actions.unsubscribeAll"); +} + +void ActionsDemo::runOption(const std::string& method) +{ + std::cout << "Running Actions method: " << method << std::endl; + + if (method == "Actions.intent") + { + auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().intent(); + if (succeed(r)) + { + std::cout << "Current Intent - action: " << r->intent.action + << ", source: " << (r->intent.context && r->intent.context->source ? *r->intent.context->source : "(none)") + << ", intentId: " << r->intentId << std::endl; + } + } + else if (method == "Actions.start") + { + std::string actionStr = paramFromConsole("action", "pre-load"); + std::string sourceStr = paramFromConsole("context.source (leave empty to skip)", "system"); + std::string handlerAppIdStr = paramFromConsole("handlerAppId (leave empty to skip)", ""); + std::optional handlerAppId; + if (!handlerAppIdStr.empty()) + handlerAppId = handlerAppIdStr; + Firebolt::Actions::IntentData intentData{actionStr}; + if (!sourceStr.empty()) + intentData.context = Firebolt::Actions::IntentContext{sourceStr}; + auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().start(intentData, handlerAppId); + if (succeed(r)) + { + std::cout << "Actions.start: Success" << std::endl; + } + } + else if (method == "Actions.onIntent") + { + auto callback = [&](const Intent& payload) + { + std::cout << "Intent received - action: " << payload.intent.action + << ", source: " + << (payload.intent.context && payload.intent.context->source + ? *payload.intent.context->source + : "(none)") + << ", intentId: " << payload.intentId << std::endl; + }; + auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().subscribeOnIntent(std::move(callback)); + if (succeed(r)) + { + std::cout << "Subscribed to Actions.onIntent with Subscription ID: " << *r << std::endl; + } + } + else if (method == "Actions.unsubscribe") + { + std::string idStr = paramFromConsole("subscription ID", "0"); + SubscriptionId id = 0; + try + { + id = static_cast(std::stoul(idStr)); + } + catch (const std::exception&) + { + } + auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().unsubscribe(id); + if (succeed(r)) + { + std::cout << "Unsubscribed from Actions subscription " << id << std::endl; + } + } + else if (method == "Actions.unsubscribeAll") + { + Firebolt::IFireboltAccessor::Instance().ActionsInterface().unsubscribeAll(); + std::cout << "Unsubscribed from all Actions subscriptions" << std::endl; + } +} diff --git a/test/api_test_app/apis/actionsDemo.h b/test/api_test_app/apis/actionsDemo.h new file mode 100644 index 0000000..ae564cc --- /dev/null +++ b/test/api_test_app/apis/actionsDemo.h @@ -0,0 +1,30 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "utils.h" +#include + +class ActionsDemo : public DemoBase +{ +public: + ActionsDemo(); + ~ActionsDemo() = default; + void runOption(const std::string& method) override; +}; diff --git a/test/api_test_app/apis/lifecycleDemo.cpp b/test/api_test_app/apis/lifecycleDemo.cpp index a550527..f05c133 100644 --- a/test/api_test_app/apis/lifecycleDemo.cpp +++ b/test/api_test_app/apis/lifecycleDemo.cpp @@ -73,15 +73,17 @@ void LifecycleDemo::runOption(const std::string& method) Firebolt::IFireboltAccessor::Instance().LifecycleInterface().subscribeOnStateChanged(std::move(callback)); if (succeed(r)) { + lastSubscriptionId_ = *r; std::cout << "Subscribed to Lifecycle state changes with Subscription ID: " << *r << std::endl; } } else if (method == "Lifecycle2.unsubscribe") { - SubscriptionId id = 0; + SubscriptionId id = lastSubscriptionId_; try { - id = static_cast(std::stoul(paramFromConsole("Subscription ID to unsubscribe", "0"))); + id = static_cast( + std::stoul(paramFromConsole("Subscription ID to unsubscribe", std::to_string(lastSubscriptionId_)))); } catch (const std::exception&) { diff --git a/test/api_test_app/apis/lifecycleDemo.h b/test/api_test_app/apis/lifecycleDemo.h index 9ccf509..d423741 100644 --- a/test/api_test_app/apis/lifecycleDemo.h +++ b/test/api_test_app/apis/lifecycleDemo.h @@ -31,4 +31,5 @@ class LifecycleDemo : public DemoBase private: Firebolt::Lifecycle::LifecycleState currentState_; + Firebolt::SubscriptionId lastSubscriptionId_{0}; }; diff --git a/test/api_test_app/main.cpp b/test/api_test_app/main.cpp index 611cfd7..a6c2f05 100644 --- a/test/api_test_app/main.cpp +++ b/test/api_test_app/main.cpp @@ -17,6 +17,7 @@ */ #include "accessibilityDemo.h" +#include "actionsDemo.h" #include "advertisingDemo.h" #include "deviceDemo.h" #include "discoveryDemo.h" @@ -157,6 +158,7 @@ int main(int argc, char** argv) std::vector> interfaces; interfaces.emplace_back(std::make_unique()); + interfaces.emplace_back(std::make_unique()); interfaces.emplace_back(std::make_unique()); interfaces.emplace_back(std::make_unique()); interfaces.emplace_back(std::make_unique()); diff --git a/test/component/actionsGeneratedTest.cpp b/test/component/actionsGeneratedTest.cpp index a8105c0..a3ce10a 100644 --- a/test/component/actionsGeneratedTest.cpp +++ b/test/component/actionsGeneratedTest.cpp @@ -36,19 +36,23 @@ TEST_F(ActionsGeneratedCTest, Intent) { auto result = Firebolt::IFireboltAccessor::Instance().ActionsInterface().intent(); ASSERT_TRUE(result) << toError(result); - auto parsed = nlohmann::json::parse(*result); - EXPECT_EQ(parsed.at("intent").get(), "launch"); - EXPECT_EQ(parsed.at("intentId").get(), 1); + EXPECT_EQ(result->intent.action, "pre-load"); + ASSERT_TRUE(result->intent.context); + ASSERT_TRUE(result->intent.context->source); + EXPECT_EQ(*result->intent.context->source, "system"); + EXPECT_EQ(result->intentId, 0u); } TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) { auto id = Firebolt::IFireboltAccessor::Instance().ActionsInterface().subscribeOnIntent( - [&](const std::string& intent) + [&](const Firebolt::Actions::Intent& payload) { - auto parsed = nlohmann::json::parse(intent); - EXPECT_EQ(parsed.at("intent").get(), "launch"); - EXPECT_EQ(parsed.at("intentId").get(), 1); + EXPECT_EQ(payload.intent.action, "pre-load"); + ASSERT_TRUE(payload.intent.context); + ASSERT_TRUE(payload.intent.context->source); + EXPECT_EQ(*payload.intent.context->source, "system"); + EXPECT_EQ(payload.intentId, 0u); { std::lock_guard lock(mtx); eventReceived = true; @@ -59,9 +63,16 @@ TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) ASSERT_TRUE(id) << toError(id); verifyEventSubscription(id); - triggerEvent("Actions.onIntent", R"({"intent":"launch","intentId":1})"); + triggerEvent("Actions.onIntent", R"({"intent":{"action":"pre-load","context":{"source":"system"}},"intentId":0})"); verifyEventReceived(mtx, cv, eventReceived); auto result = Firebolt::IFireboltAccessor::Instance().ActionsInterface().unsubscribe(id.value()); verifyUnsubscribeResult(result); } + +TEST_F(ActionsGeneratedCTest, Start) +{ + auto result = Firebolt::IFireboltAccessor::Instance().ActionsInterface().start( + Firebolt::Actions::IntentData{"pre-load", Firebolt::Actions::IntentContext{{"system"}}}); + ASSERT_TRUE(result) << toError(result); +} diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp index feec5ed..236cf91 100644 --- a/test/unit/actionsTest.cpp +++ b/test/unit/actionsTest.cpp @@ -20,21 +20,27 @@ #include "json_engine.h" #include "mock_helper.h" +using ::testing::Invoke; + class ActionsUTest : public ::testing::Test, protected MockBase { protected: Firebolt::Actions::ActionsImpl actionsImpl_{mockHelper}; }; -TEST_F(ActionsUTest, Start) +TEST_F(ActionsUTest, Intent) { - mock_with_response("Actions.intent", nlohmann::json({{"intent", "launch"}, {"intentId", 1}})); + mock_with_response("Actions.intent", + nlohmann::json({{"intent", {{"action", "pre-load"}, {"context", {{"source", "system"}}}}}, + {"intentId", 0u}})); auto result = actionsImpl_.intent(); ASSERT_TRUE(result) << "ActionsImpl::intent() returned an error"; - auto parsed = nlohmann::json::parse(*result); - EXPECT_EQ(parsed.at("intent").get(), "launch"); - EXPECT_EQ(parsed.at("intentId").get(), 1); + EXPECT_EQ(result->intent.action, "pre-load"); + ASSERT_TRUE(result->intent.context); + ASSERT_TRUE(result->intent.context->source); + EXPECT_EQ(*result->intent.context->source, "system"); + EXPECT_EQ(result->intentId, 0u); } TEST_F(ActionsUTest, SubscribeOnIntent) @@ -42,7 +48,7 @@ TEST_F(ActionsUTest, SubscribeOnIntent) nlohmann::json expectedValue = 1; mockSubscribe("Actions.onIntent"); - auto result = actionsImpl_.subscribeOnIntent([&](const std::string& /*value*/) {}); + auto result = actionsImpl_.subscribeOnIntent([&](const Firebolt::Actions::Intent& /*value*/) {}); ASSERT_TRUE(result) << "ActionsImpl::subscribeOnIntent() returned an error"; EXPECT_EQ(*result, expectedValue); @@ -50,3 +56,16 @@ TEST_F(ActionsUTest, SubscribeOnIntent) auto unsubResult = actionsImpl_.unsubscribe(*result); ASSERT_TRUE(unsubResult) << "ActionsImpl::unsubscribe() returned an error"; } + +TEST_F(ActionsUTest, Start) +{ + nlohmann::json expectedParams; + expectedParams["intent"] = {{"action", "pre-load"}, {"context", {{"source", "system"}}}}; + EXPECT_CALL(mockHelper, invoke("Actions.start", expectedParams)) + .WillOnce(Invoke([&](const std::string& /*methodName*/, const nlohmann::json& /*parameters*/) + { return Firebolt::Result{Firebolt::Error::None}; })); + + auto result = actionsImpl_.start( + Firebolt::Actions::IntentData{"pre-load", Firebolt::Actions::IntentContext{{"system"}}}); + ASSERT_TRUE(result) << "ActionsImpl::start() returned an error"; +} From 839895314125950ce9bdbec01378e23bfbfc124f Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 5 Aug 2026 17:26:34 -0700 Subject: [PATCH 15/39] RDKEMW-14869: VideoOutput implementation + fix broken unit tests --- .clang-tidy | 13 + docs/openrpc/the-spec/firebolt-open-rpc.json | 8464 +++++++++--------- include/firebolt/actions.h | 3 +- include/firebolt/firebolt.h | 3 + include/firebolt/videooutput.h | 188 + lint.sh | 31 +- src/firebolt.cpp | 7 +- src/json_types/videooutput.h | 195 + src/videooutput_impl.cpp | 115 + src/videooutput_impl.h | 73 + test/api_test_app/apis/actionsDemo.cpp | 12 +- test/component/videooutputGeneratedTest.cpp | 36 + test/unit/actionsTest.cpp | 4 +- test/unit/videooutputGeneratedTest.cpp | 61 + 14 files changed, 5012 insertions(+), 4193 deletions(-) create mode 100644 .clang-tidy create mode 100644 include/firebolt/videooutput.h create mode 100644 src/json_types/videooutput.h create mode 100644 src/videooutput_impl.cpp create mode 100644 src/videooutput_impl.h create mode 100644 test/component/videooutputGeneratedTest.cpp create mode 100644 test/unit/videooutputGeneratedTest.cpp diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..f4eefa9 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,13 @@ +Checks: > + -*, + bugprone-*, + modernize-*, + readability-*, + performance-*, + -modernize-use-trailing-return-type, + -readability-magic-numbers, + -readability-identifier-length, + -bugprone-easily-swappable-parameters + +HeaderFilterRegex: '(include|src)/.*\.h$' +WarningsAsErrors: '' diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 98fdd71..d716ac2 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -1,4171 +1,4295 @@ { - "openrpc": "1.2.4", - "info": { - "title": "Firebolt JSON-RPC API", - "version": "", - "x-module-descriptions": { - "Accessibility": "The `Accessibility` module provides access to the user/device settings for closed captioning and voice guidance.\n\nApps **SHOULD** attempt o respect these settings, rather than manage and persist seprate settings, which would be different per-app.", - "Actions": "Methods for getting and observing app intents.", - "Advertising": "A module for platform provided advertising settings and functionality.", - "Device": "A module for querying about the device and it's capabilities.", - "Discovery": "Your App likely wants to integrate with the Platform's discovery capabilities. For example to add a \"Watch Next\" tile that links to your app from the platform's home screen.\n\nGetting access to this information requires to connect to lower level APIs made available by the platform. Since implementations differ between operators and platforms, the Firebolt SDK offers a Discovery module, that exposes a generic, agnostic interface to the developer.\n\nUnder the hood, an underlaying transport layer will then take care of calling the right APIs for the actual platform implementation that your App is running on.\n\nThe Discovery plugin is used to _send_ information to the Platform.\n\n### Localization\nApps should provide all user-facing strings in the device's language, as specified by the Firebolt `Localization.language` property.\n\nApps should provide prices in the same currency presented in the app. If multiple currencies are supported in the app, the app should provide prices in the user's current default currency.", - "Display": "A module for querying about the display", - "Lifecycle2": "Methods and events for responding to Lifecycle changes in your app.", - "Localization": "Methods for accessing location and language preferences.", - "Metrics": "Methods for sending metrics", - "Network": "Methods for accessing network information.", - "Presentation": "Methods for accessing Presentation preferences.", - "Stats": "Provides methods to retrieve application-level system information.", - "TextToSpeech": "A module for controlling and accessing Text To Speech over Firebolt." - } - }, - "methods": [ - { - "name": "rpc.discover", - "summary": "The OpenRPC schema for this JSON-RPC API", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:rpc:discover" - ] - } - ], - "result": { - "name": "OpenRPC Schema", - "schema": { - "type": "object" - } - }, - "examples": [ - { - "name": "Default", - "params": [], - "result": { - "name": "schema", - "value": {} - } - } - ] - }, - { - "name": "Actions.intent", - "summary": "Returns the current intent.", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "params": [], - "result": { - "name": "intent", - "summary": "The current intent as a JSON document.", - "schema": { - "type": "object", - "required": [ - "intent", - "intentId" - ], - "properties": { - "intent": { - "type": "object", - "required": ["action"], - "properties": { - "action": { "type": "string" }, - "context": { - "type": "object", - "properties": { - "source": { "type": "string" } - } - } - } - }, - "intentId": { - "type": "integer", - "minimum": 0 - } - } - } - }, - "examples": [ - { - "name": "Get the current intent", - "result": { - "name": "Default Result", - "value": { - "intent": { - "action": "pre-load", - "context": { - "source": "system" - } - }, - "intentId": 0 - } - } - } - ] - }, - { - "name": "Actions.onIntent", - "tags": [ - { - "name": "event", - "x-notifier": "Actions.onIntent", - "x-subscriber-for": "Actions.intent" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "summary": "Notifies when the current intent changes.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "result": { - "name": "intent", - "summary": "The current intent as a JSON document.", - "schema": { - "type": "object", - "required": [ - "intent", - "intentId" - ], - "properties": { - "intent": { - "type": "object", - "required": ["action"], - "properties": { - "action": { "type": "string" }, - "context": { - "type": "object", - "properties": { - "source": { "type": "string" } - } - } - } - }, - "intentId": { - "type": "integer", - "minimum": 0 - } - } - } - }, - "examples": [ - { - "name": "Listen for intent changes", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "Default Result", - "value": { - "intent": { - "action": "pre-load", - "context": { - "source": "system" - } - }, - "intentId": 0 - } - } - } - ] - }, - { - "name": "Actions.start", - "summary": "Sends an intent to the platform.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "params": [ - { - "name": "intent", - "summary": "The intent to send, as a JSON document.", - "required": true, - "schema": { - "type": "object", - "required": ["action"], - "properties": { - "action": { "type": "string" }, - "context": { - "type": "object", - "properties": { - "source": { "type": "string" } - } - } - } - } - }, - { - "name": "handlerAppId", - "summary": "Optional ID of the application that should handle the intent.", - "required": false, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Start an intent", - "params": [ - { - "name": "intent", - "value": { - "action": "pre-load", - "context": { - "source": "system" - } - } - } - ], - "result": { - "name": "Default Result", - "value": null - } - } - ] - }, - { - "name": "Accessibility.audioDescription", - "summary": "Returns the audio description setting of the device", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:audio-descriptions" - ] - } - ], - "result": { - "name": "setting", - "summary": "the audio description setting", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Getting the audio description setting", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Accessibility.closedCaptionsSettings", - "summary": "Returns captions settings: enabled, and a list of zero or more languages in order of decreasing preference", - "params": [], - "tags": [ - { - "name": "property:readonly", - "x-notifier-params-flattening": "true" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:closed-captions" - ] - } - ], - "result": { - "name": "closedCaptionsSettings", - "summary": "the closed captions settings", - "schema": { - "$ref": "#/x-schemas/Accessibility/ClosedCaptionsSettings" - } - }, - "examples": [ - { - "name": "Getting the closed captions settings", - "params": [], - "result": { - "name": "settings", - "value": { - "enabled": true, - "preferredLanguages": [ - "eng", - "spa" - ] - } - } - } - ] - }, - { - "name": "Accessibility.highContrastUI", - "summary": "Returns the high contrast UI device setting", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:high-contrast-ui" - ] - } - ], - "result": { - "name": "highContrastUI", - "summary": "Whether high-contrast UI mode is enabled", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "High-contrast UI mode is enabled", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Accessibility.voiceGuidanceSettings", - "summary": "Returns voice guidance settings: enabled, rate, and verbosity", - "params": [], - "tags": [ - { - "name": "property:readonly", - "x-notifier-params-flattening": "true" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:voice-guidance" - ] - } - ], - "result": { - "name": "settings", - "summary": "the voice guidance settings", - "schema": { - "$ref": "#/x-schemas/Accessibility/VoiceGuidanceSettings" - } - }, - "examples": [ - { - "name": "Getting the voice guidance settings", - "params": [], - "result": { - "name": "Default Result", - "value": { - "enabled": true, - "rate": 0.8, - "navigationHints": true - } - } - } - ] - }, - { - "name": "Advertising.advertisingId", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:advertising:identifier" - ] - } - ], - "summary": "Returns the IFA.", - "params": [], - "result": { - "name": "advertisingId", - "summary": "The advertising ID", - "schema": { - "$ref": "#/components/schemas/AdvertisingIdResult" - } - }, - "examples": [ - { - "name": "Getting the advertising ID", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "sspid", - "lmt": "0" - } - } - }, - { - "name": "Getting the advertising ID with scope browse", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "sspid", - "lmt": "1" - } - } - }, - { - "name": "Getting the advertising ID with scope content", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "idfa", - "lmt": "0" - } - } - } - ] - }, - { - "name": "Device.uid", - "summary": "Returns a persistent unique UUID for the current app and device. The UUID is reset when the app or device is reset", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:uid" - ] - } - ], - "result": { - "name": "uniqueId", - "summary": "A unique UUID for this app-device pair.", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the unique UUID", - "params": [], - "result": { - "name": "Default Result", - "value": "ee6723b8-7ab3-462c-8d93-dbf61227998e" - } - } - ] - }, - { - "name": "Device.deviceClass", - "summary": "Returns the class of the device", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:device-class" - ] - } - ], - "result": { - "name": "deviceClass", - "summary": "The device class", - "schema": { - "$ref": "#/components/schemas/DeviceClass" - } - }, - "examples": [ - { - "name": "Getting the device class", - "params": [], - "result": { - "name": "Default Result", - "value": "ott" - } - } - ] - }, - { - "name": "Device.uptime", - "summary": "Returns the number of seconds since most recent device boot, including any time spent during deep sleep", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "uptime", - "summary": "The device uptime", - "schema": { - "type": "number" - } - }, - "examples": [ - { - "name": "Getting the device uptime", - "params": [], - "result": { - "name": "Default Result", - "value": 123456 - } - } - ] - }, - { - "name": "Device.timeInActiveState", - "summary": "Returns the number of seconds since the device transitioned to the ON power state", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "timeInActiveState", - "summary": "The device time in active state", - "schema": { - "type": "number" - } - }, - "examples": [ - { - "name": "Getting the number of seconds since the device transitioned to the ON power state", - "params": [], - "result": { - "name": "Default Result", - "value": 654321 - } - } - ] - }, - { - "name": "Device.chipsetId", - "summary": "Returns chipset ID as a printable string, e.g. BCM72180", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "chipsetId", - "summary": "The device chipset ID", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the device chipset ID", - "params": [], - "result": { - "name": "Default Result", - "value": "BCM72180" - } - } - ] - }, - { - "name": "Device.hdr", - "summary": "Returns the HDR standards that are supported by the attached TV or the integral display", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "negotiatedHdrFormats", - "summary": "the negotiated HDR formats", - "schema": { - "$ref": "#/components/schemas/HDRFormatMap" - } - }, - "examples": [ - { - "name": "Getting the negotiated HDR formats", - "params": [], - "result": { - "name": "Default Result", - "value": { - "hdr10": true, - "hdr10Plus": true, - "dolbyVision": true, - "hlg": true - } - } - } - ] - }, - { - "name": "Discovery.watched", - "summary": "Notify the platform that content was partially or completely watched", - "tags": [ - { - "name": "polymorphic-reducer" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:discovery:watched" - ] - } - ], - "params": [ - { - "name": "entityId", - "required": true, - "schema": { - "type": "string" - }, - "summary": "The entity Id of the watched content." - }, - { - "name": "progress", - "summary": "How much of the content has been watched (percentage as (0-0.999) for VOD, number of seconds for live)", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "completed", - "summary": "Whether or not this viewing is considered \"complete,\" per the app's definition thereof", - "schema": { - "type": "boolean" - } - }, - { - "name": "watchedOn", - "summary": "Date/Time the content was watched, ISO 8601 Date/Time", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "agePolicy", - "description": "The age policy associated with the watch event. The age policy describes the age groups to which content may be directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "success", - "summary": "Whether the call was successful or not", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Notify the platform of watched content", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - } - ], - "result": { - "name": "success", - "value": true - } - }, - { - "name": "Notify the platform that child-directed content was watched", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "success", - "value": true - } - } - ] - }, - { - "name": "Discovery.watchedV2", - "summary": "Notify the platform that content was partially or completely watched, returns whether the notification was accepted", - "tags": [ - { - "name": "polymorphic-reducer" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:discovery:watched" - ] - } - ], - "params": [ - { - "name": "entityId", - "required": true, - "schema": { - "type": "string" - }, - "summary": "The entity Id of the watched content." - }, - { - "name": "progress", - "summary": "How much of the content has been watched (percentage as (0-0.999) for VOD, number of seconds for live)", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "completed", - "summary": "Whether or not this viewing is considered \"complete,\" per the app's definition thereof", - "schema": { - "type": "boolean" - } - }, - { - "name": "watchedOn", - "summary": "Date/Time the content was watched, ISO 8601 Date/Time", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "agePolicy", - "description": "The age policy associated with the watch event. The age policy describes the age groups to which content may be directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "summary": "Whether the platform accepted the watched notification", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Notify the platform of watched content (v2)", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - } - ], - "result": { - "name": "result", - "value": true - } - }, - { - "name": "Notify the platform that child-directed content was watched (v2)", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "result", - "value": true - } - } - ] - }, - { - "name": "Display.edid", - "summary": "Returns the EDID (and extensions) of the connected or integral display, as a Base64 encoded string", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "Base64 EDID", - "summary": "The EDID (and extensions) of the connected or integral display, as a Base64 encoded string", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the display EDID", - "params": [], - "result": { - "name": "Default Result", - "value": "ZWU2NzIzYjgtN2FiMy00NjJjLThkOTMtZGJmNjEyMjc5OThl" - } - } - ] - }, - { - "name": "Display.size", - "summary": "Returns the physical dimensions of the connected or integral display, in centimeters. Returns 0, 0 on a OTT/STB device when a display is not connected over HDMI", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "size", - "summary": "The display size in centimeters", - "schema": { - "type": "object", - "properties": { - "width": { - "type": "integer", - "description": "The width of the display in centimeters" - }, - "height": { - "type": "integer", - "description": "The height of the display in centimeters" - } - } - } - }, - "examples": [ - { - "name": "Getting the display size", - "params": [], - "result": { - "name": "Default Result", - "value": { - "width": 48, - "height": 27 - } - } - } - ] - }, - { - "name": "Display.maxResolution", - "summary": "Returns the physical/native resolution of the connected or integral display, in pixels. Returns 0, 0 on a OTT/STB device when a display is not connected over HDMI", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "maxResolution", - "summary": "The display resolution", - "schema": { - "type": "object", - "properties": { - "width": { - "type": "integer", - "description": "The width of the display in pixels" - }, - "height": { - "type": "integer", - "description": "The height of the display in pixels" - } - } - } - }, - "examples": [ - { - "name": "Getting the display size", - "params": [], - "result": { - "name": "Default Result", - "value": { - "width": 1920, - "height": 1080 - } - } - } - ] - }, - { - "name": "Lifecycle2.close", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Request the platform to deactivate the app, and possibly take further action.", - "params": [ - { - "name": "type", - "summary": "The type of the close app is requesting", - "required": true, - "schema": { - "$ref": "#/components/schemas/CloseType" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Close the app when the user presses back on the app home screen", - "params": [ - { - "name": "type", - "value": "unload" - } - ], - "result": { - "name": "Default Result", - "value": null - } - }, - { - "name": "Close the app when the user selects an exit menu item", - "params": [ - { - "name": "type", - "value": "deactivate" - } - ], - "result": { - "name": "Default Result", - "value": null - } - } - ] - }, - { - "name": "Lifecycle2.state", - "summary": "Get the current lifecycle state of the app.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "params": [], - "result": { - "name": "state", - "summary": "The current lifecycle state of the app.", - "schema": { - "$ref": "#/components/schemas/LifecycleState" - } - }, - "examples": [ - { - "name": "Default Example", - "params": [], - "result": { - "name": "Default Result", - "value": "active" - } - } - ] - }, - { - "name": "Localization.country", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:country-code" - ] - } - ], - "summary": "Returns the ISO 3166-1 alpha-2 code for the country device is located in.", - "params": [], - "result": { - "name": "code", - "summary": "The device country code.", - "schema": { - "$ref": "#/x-schemas/Localization/CountryCode" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": "US" - } - }, - { - "name": "Another example", - "params": [], - "result": { - "name": "Default Result", - "value": "GB" - } - } - ] - }, - { - "name": "Localization.preferredAudioLanguages", - "summary": "Returns a list of ISO 639-2/B codes for the preferred audio languages on this device.", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:preferred-audio-languages" - ] - } - ], - "result": { - "name": "languages", - "summary": "The preferred audio languages.", - "schema": { - "type": "array", - "items": { - "$ref": "#/x-schemas/Localization/ISO639_2Language" - } - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": [ - "spa", - "eng" - ] - } - } - ] - }, - { - "name": "Localization.presentationLanguage", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:locale" - ] - } - ], - "summary": "Get the *full* BCP 47 code, including script, region, variant, etc., for the preferred locale", - "params": [], - "result": { - "name": "locale", - "summary": "The device locale.", - "schema": { - "$ref": "#/x-schemas/Localization/Locale" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": "en-US" - } - } - ] - }, - { - "name": "Metrics.ready", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your app is minimally usable. This method is called automatically by `Lifecycle.ready()`", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ready metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.signIn", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Log a sign in event, called by Discovery.signIn().", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send signIn metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.signOut", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Log a sign out event, called by Discovery.signOut().", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send signOut metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.startContent", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has started content.", - "params": [ - { - "name": "entityId", - "summary": "Optional entity ID of the content.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send startContent metric", - "params": [], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send startContent metric w/ entity", - "params": [ - { - "name": "entityId", - "value": "abc" - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send startContent metric and notify the platform that the content is child-directed", - "params": [ - { - "name": "entityId", - "value": "abc" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.stopContent", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has stopped content.", - "params": [ - { - "name": "entityId", - "summary": "Optional entity ID of the content.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send stopContent metric", - "params": [], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send stopContent metric w/ entity", - "params": [ - { - "name": "entityId", - "value": "abc" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.page", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has navigated to a page or view.", - "params": [ - { - "name": "pageId", - "summary": "Page ID of the content.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send page metric", - "params": [ - { - "name": "pageId", - "value": "xyz" - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send page metric w/ pageId", - "params": [ - { - "name": "pageId", - "value": "home" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.error", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform of an error that has occurred in your app.", - "params": [ - { - "name": "type", - "summary": "The type of error", - "schema": { - "$ref": "#/components/schemas/ErrorType" - }, - "required": true - }, - { - "name": "code", - "summary": "an app-specific error code", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "description", - "summary": "A short description of the error", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "visible", - "summary": "Whether or not this error was visible to the user.", - "schema": { - "type": "boolean" - }, - "required": true - }, - { - "name": "parameters", - "summary": "Optional additional parameters to be logged with the error", - "schema": { - "$ref": "#/x-schemas/Types/FlatMap" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send error metric", - "params": [ - { - "name": "type", - "value": "media" - }, - { - "name": "code", - "value": "MEDIA-STALLED" - }, - { - "name": "description", - "value": "playback stalled" - }, - { - "name": "visible", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaLoadStart", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when setting the URL of a media asset to play, in order to infer load time.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send loadstart metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPlay", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback should start due to autoplay, user-initiated play, or unpausing.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send play metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPlaying", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback actually starts due to autoplay, user-initiated play, unpausing, or recovering from a buffering interruption.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send playing metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPause", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback will pause due to an intentional pause operation.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send pause metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaWaiting", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback will halt due to a network, buffer, or other unintentional constraint.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send waiting metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaSeeking", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when a seek is initiated during media playback.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "target", - "summary": "Target destination of the seek, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "schema": { - "$ref": "#/components/schemas/MediaPosition" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send seeking metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "target", - "value": 0.5 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaSeeked", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when a seek is completed during media playback.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "position", - "summary": "Resulting position of the seek operation, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "schema": { - "$ref": "#/components/schemas/MediaPosition" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send seeked metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "position", - "value": 0.51 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaRateChanged", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when the playback rate of media is changed.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "rate", - "summary": "The new playback rate.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ratechange metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "rate", - "value": 2 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaRenditionChanged", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when the playback rendition (e.g. bitrate, dimensions, profile, etc) is changed.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "bitrate", - "summary": "The new bitrate in kbps.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "width", - "summary": "The new resolution width.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "height", - "summary": "The new resolution height.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "profile", - "summary": "A description of the new profile, e.g. 'HDR' etc.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send renditionchange metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "bitrate", - "value": 5000 - }, - { - "name": "width", - "value": 1920 - }, - { - "name": "height", - "value": 1080 - }, - { - "name": "profile", - "value": "HDR+" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaEnded", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when playback has stopped because the end of the media was reached.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ended metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.event", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:distributor" - ] - } - ], - "summary": "Inform the platform of 1st party distributor metrics. 'data' parameter is a JSON document", - "params": [ - { - "name": "schema", - "summary": "The schema URI of the metric type", - "schema": { - "type": "string", - "format": "uri" - }, - "required": true - }, - { - "name": "data", - "summary": "A JSON payload", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send foo event", - "params": [ - { - "name": "schema", - "value": "http://meta.rdkcentral.com/some/schema" - }, - { - "name": "data", - "value": "foo" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.appInfo", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform about an app's build info.", - "params": [ - { - "name": "build", - "summary": "The build / version of this app.", - "schema": { - "type": "string" - }, - "required": true - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send appInfo metric", - "params": [ - { - "name": "build", - "value": "1.2.2" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Network.connected", - "summary": "Returns whether the device currently has a usable network connection.", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:network:connected" - ] - } - ], - "params": [], - "result": { - "name": "success", - "summary": "Whether the device currently has a usable network connection.", - "schema": { - "$ref": "#/components/schemas/Connected" - } - }, - "examples": [ - { - "name": "Connected example", - "params": [], - "result": { - "name": "success", - "value": true - } - } - ] - }, - { - "name": "Presentation.focused", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Whether the app is in focus, i.e. receiving key presses. Provided for those apps/runtimes that cannot use Wayland", - "params": [], - "result": { - "name": "focused", - "summary": "Whether the app is in focus.", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Stats.memoryUsage", - "summary": "Returns information about container memory usage, in units of 1024 bytes.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "params": [], - "result": { - "name": "result", - "schema": { - "$ref": "#/components/schemas/MemoryUsage" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "value", - "description": "The memory usage information", - "value": { - "userMemoryUsedKiB": 123456, - "userMemoryLimitKiB": 789012, - "gpuMemoryUsedKiB": 345678, - "gpuMemoryLimitKiB": 901234 - } - } - } - ] - }, - { - "name": "TextToSpeech.speak", - "summary": "Speak the utterance immediately. Any ongoing speech is interrupted.", - "description": "Text argument is either plain text or a well-formed SSML document TTS_status, not success attribute, to be used by caller to indicate success of call 0 OK, 1 Fail, 2 not enabled, 3 invalid configuration Raises onSpeechinterrupted if speaking is interrupted", - "params": [ - { - "name": "text", - "summary": "String to be converted to Audio for speech", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "speakResult", - "summary": "Result for Speak", - "schema": { - "$ref": "#/components/schemas/SpeechResponse" - } - }, - "examples": [ - { - "name": "Getting the result of speak", - "params": [ - { - "name": "text", - "value": "I am a text waiting for speech." - } - ], - "result": { - "name": "result", - "value": { - "speechid": 1, - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.pause", - "summary": "Pauses the speech for given speech id", - "description": "Pauses the utterance. Raises onSpeechpause if ongoing speech is paused. Does nothing if utterance is already paused", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "pauseResult", - "summary": "Result for Pause", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Pause a given speech id", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.resume", - "summary": "Resumes the speech for given speech id", - "description": "Continue the paused utterance. Raises onSpeechresume if paused speech is resumed. Does nothing if the utterance is not paused", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "resumeResult", - "summary": "Result for Resume", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Resume a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.cancel", - "summary": "Cancels the speech for given speech id", - "description": "Stop speaking if utterance is currently being spoken. Raises onSpeechinterrupted if speaking was interrupted.", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "cancelResult", - "summary": "Result for cancel", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Cancel a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.getspeechstate", - "summary": "Returns the state of the utterance.", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "speechStateResult", - "summary": "Result for speech state", - "schema": { - "$ref": "#/components/schemas/SpeechStateResponse" - } - }, - "examples": [ - { - "name": "State for a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "speechstate", - "value": { - "speechstate": 1, - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.listvoices", - "summary": "Returns the list of available voices as human-readable strings, e.g. 'ava', 'amelie', 'angelica'", - "params": [ - { - "name": "language", - "summary": "Language - string - BCP 47", - "schema": { - "$ref": "#/x-schemas/Localization/Locale" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "listvoices", - "summary": "The list of voices supported for the language", - "schema": { - "$ref": "#/components/schemas/ListVoicesResponse" - } - }, - "examples": [ - { - "name": "Getting the list of voices", - "params": [ - { - "name": "language", - "value": "en-US" - } - ], - "result": { - "name": "voiceList", - "value": { - "TTS_Status": 0, - "voices": [ - "carol", - "tom" - ] - } - } - } - ] - }, - { - "name": "Lifecycle2.onStateChanged", - "tags": [ - { - "name": "event", - "x-contextual-parameters": 0, - "x-notifier": "Lifecycle2.onStateChanged" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Notification of lifecycle state change, raised after the platform has transitioned the app/runtime to the new lifecycle state", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "App is active after being initialized", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Single transition to paused state", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onWillspeak", - "summary": "Text to speech conversion is about to start.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onWillspeak" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechstart", - "summary": "Utterance is about to be spoken.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechstart" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechpause", - "summary": "Ongoing speech was paused.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechpause" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechresume", - "summary": "Paused speech was resumed.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechresume" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechcomplete", - "summary": "Speech completed successfully.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechcomplete" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechinterrupted", - "summary": "Speech was stopped, due to another call to speak or cancel.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechinterrupted" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onNetworkerror", - "summary": "Utterance failed due to network error.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onNetworkerror" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onPlaybackerror", - "summary": "Utterance failed during playback.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onPlaybackerror" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onAudioDescriptionChanged", - "summary": "Returns the audio description setting of the device", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Accessibility.onAudioDescriptionChanged", - "x-subscriber-for": "Accessibility.audioDescription" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:audio-descriptions" - ] - } - ], - "examples": [ - { - "name": "Getting the audio description setting", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onClosedCaptionsSettingsChanged", - "summary": "Returns captions settings: enabled, and a list of zero or more languages in order of decreasing preference", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier-params-flattening": "true", - "x-notifier": "Accessibility.onClosedCaptionsSettingsChanged", - "x-subscriber-for": "Accessibility.closedCaptionsSettings" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:closed-captions" - ] - } - ], - "examples": [ - { - "name": "Getting the closed captions settings", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onHighContrastUIChanged", - "summary": "Returns the high contrast UI device setting", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Accessibility.onHighContrastUIChanged", - "x-subscriber-for": "Accessibility.highContrastUI" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:high-contrast-ui" - ] - } - ], - "examples": [ - { - "name": "High-contrast UI mode is enabled", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onVoiceGuidanceSettingsChanged", - "summary": "Returns voice guidance settings: enabled, rate, and verbosity", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier-params-flattening": "true", - "x-notifier": "Accessibility.onVoiceGuidanceSettingsChanged", - "x-subscriber-for": "Accessibility.voiceGuidanceSettings" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:voice-guidance" - ] - } - ], - "examples": [ - { - "name": "Getting the voice guidance settings", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Device.onHdrChanged", - "summary": "Returns the HDR standards that are supported by the attached TV or the integral display", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Device.onHdrChanged", - "x-subscriber-for": "Device.hdr" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "examples": [ - { - "name": "Getting the negotiated HDR formats", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onCountryChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onCountryChanged", - "x-subscriber-for": "Localization.country" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:country-code" - ] - } - ], - "summary": "Returns the ISO 3166-1 alpha-2 code for the country device is located in.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Another example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onPreferredAudioLanguagesChanged", - "summary": "Returns a list of ISO 639-2/B codes for the preferred audio languages on this device.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onPreferredAudioLanguagesChanged", - "x-subscriber-for": "Localization.preferredAudioLanguages" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:preferred-audio-languages" - ] - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onPresentationLanguageChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onPresentationLanguageChanged", - "x-subscriber-for": "Localization.presentationLanguage" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:locale" - ] - } - ], - "summary": "Get the *full* BCP 47 code, including script, region, variant, etc., for the preferred locale", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Network.onConnectedChanged", - "summary": "Returns whether the device currently has a usable network connection.", - "tags": [ - { - "name": "event", - "x-notifier": "Network.onConnectedChanged", - "x-subscriber-for": "Network.connected" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:network:connected" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Connected example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Presentation.onFocusedChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Presentation.onFocusedChanged", - "x-subscriber-for": "Presentation.focused" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Whether the app is in focus, i.e. receiving key presses. Provided for those apps/runtimes that cannot use Wayland", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - } - ], - "components": { - "schemas": { - "AdvertisingIdResult": { - "title": "AdvertisingIdResult", - "type": "object", - "properties": { - "ifa": { - "type": "string", - "description": "UUID conforming to IAB standard" - }, - "ifa_type": { - "type": "string", - "description": "Source of the IFA as defined by IAB" - }, - "lmt": { - "type": "string", - "enum": [ - "0", - "1" - ], - "description": "Boolean that if set to 1, user has requested ad tracking and measurement is disabled" - } - }, - "required": [ - "ifa", - "ifa_type", - "lmt" - ] - }, - "HDRFormatMap": { - "title": "HDRFormatMap", - "type": "object", - "properties": { - "hdr10": { - "type": "boolean" - }, - "hdr10Plus": { - "type": "boolean" - }, - "dolbyVision": { - "type": "boolean" - }, - "hlg": { - "type": "boolean" - } - }, - "required": [ - "hdr10", - "hdr10Plus", - "dolbyVision", - "hlg" - ], - "description": "The type of HDR format" - }, - "DeviceClass": { - "title": "DeviceClass", - "type": "string", - "enum": [ - "ott", - "stb", - "tv" - ], - "description": "The type of device" - }, - "CloseType": { - "title": "CloseType", - "description": "The application close type", - "type": "string", - "enum": [ - "deactivate", - "unload", - "killReload", - "killReactivate" - ] - }, - "LifecycleState": { - "title": "LifecycleState", - "description": "The application Lifecycle state", - "type": "string", - "enum": [ - "initializing", - "active", - "paused", - "suspended", - "hibernated", - "terminating" - ] - }, - "StateChange": { - "title": "StateChange", - "type": "object", - "properties": { - "newState": { - "$ref": "#/components/schemas/LifecycleState" - }, - "oldState": { - "$ref": "#/components/schemas/LifecycleState" - } - } - }, - "MediaPosition": { - "title": "MediaPosition", - "description": "Represents a position inside playback content, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "oneOf": [ - { - "const": 0 - }, - { - "type": "number", - "exclusiveMinimum": 0, - "exclusiveMaximum": 1 - }, - { - "type": "integer", - "minimum": 1, - "maximum": 86400 - } - ] - }, - "ErrorType": { - "title": "ErrorType", - "type": "string", - "enum": [ - "network", - "media", - "restriction", - "entitlement", - "other" - ] - }, - "EventObjectPrimitives": { - "title": "EventObjectPrimitives", - "anyOf": [ - { - "type": "string", - "maxLength": 256 - }, - { - "type": "number" - }, - { - "type": "integer" - }, - { - "type": "boolean" - }, - { - "type": "null" - } - ] - }, - "Connected": { - "type": "boolean", - "description": "Indicates whether the device currently has a usable network connection." - }, - "MemoryUsage": { - "title": "MemoryUsage", - "type": "object", - "description": "Describes current and maximum memory usage of the container.", - "properties": { - "userMemoryUsedKiB": { - "type": "integer", - "description": "User memory currently used in 1024 bytes." - }, - "userMemoryLimitKiB": { - "type": "integer", - "description": "Maximum user memory available in 1024 bytes." - }, - "gpuMemoryUsedKiB": { - "type": "integer", - "description": "GPU memory currently used in 1024 bytes." - }, - "gpuMemoryLimitKiB": { - "type": "integer", - "description": "Maximum GPU memory available in 1024 bytes." - } - }, - "required": [ - "userMemoryUsedKiB", - "userMemoryLimitKiB", - "gpuMemoryUsedKiB", - "gpuMemoryLimitKiB" - ] - }, - "TTSEnabled": { - "title": "TTSEnabled", - "type": "object", - "required": [ - "TTS_Status", - "isenabled" - ], - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "isenabled": { - "type": "boolean" - } - } - }, - "ListVoicesResponse": { - "title": "ListVoicesResponse", - "type": "object", - "required": [ - "TTS_Status", - "voices" - ], - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "voices": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "TTSConfiguration": { - "title": "TTSConfiguration", - "type": "object", - "required": [ - "success" - ], - "properties": { - "success": { - "type": "boolean" - }, - "ttsendpoint": { - "type": "string", - "description": "URL for Text to Speech API" - }, - "ttsendpointsecured": { - "type": "string", - "description": "Secure URL for Text to Speech API" - }, - "language": { - "type": "string", - "description": "Language used by Text to speech" - }, - "voice": { - "type": "string", - "description": "Voice used by Text to speech" - }, - "volume": { - "type": "integer", - "description": "Volume for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "primvolduckpercent": { - "type": "integer", - "description": "Prime Volume duck percent for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "rate": { - "type": "integer", - "description": "Speech rate for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "speechrate": { - "description": "Rate for speech", - "$ref": "#/components/schemas/SpeechRate" - }, - "fallbacktext": { - "description": "Fallback text for TTS", - "$ref": "#/components/schemas/FallbackText" - } - }, - "examples": [ - {} - ] - }, - "SpeechRate": { - "title": "SpeechRate", - "type": "string", - "enum": [ - "slow", - "medium", - "fast", - "faster", - "fastest" - ] - }, - "FallbackText": { - "title": "FallbackText", - "type": "object", - "properties": { - "scenario": { - "type": "string", - "description": "Scenario for fallback Text" - }, - "value": { - "type": "string", - "description": "Value for fallback Text" - } - } - }, - "SpeechResponse": { - "title": "SpeechResponse", - "type": "object", - "properties": { - "speechid": { - "$ref": "#/components/schemas/SpeechId" - }, - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "speechid", - "TTS_Status", - "success" - ] - }, - "SpeechId": { - "type": "integer" - }, - "SpeechIdEvent": { - "type": "object", - "properties": { - "speechid": { - "$ref": "#/components/schemas/SpeechId" - } - }, - "required": [ - "speechid" - ] - }, - "TTSStatus": { - "title": "TTSStatus", - "type": "integer", - "minimum": 0, - "maximum": 3 - }, - "SpeechState": { - "title": "SpeechState", - "type": "integer", - "enum": [ - 0, - 1, - 2, - 3 - ], - "description": "0 = SPEECH_PENDING, 1 = SPEECH_IN_PROGRESS, 2 = SPEECH_PAUSED, 3 = SPEECH_NOT_FOUND" - }, - "SpeechStateResponse": { - "title": "SpeechStateResponse", - "type": "object", - "properties": { - "speechstate": { - "$ref": "#/components/schemas/SpeechState" - }, - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "speechstate", - "TTS_Status", - "success" - ] - }, - "TTSStatusResponse": { - "title": "TTSStatusResponse", - "type": "object", - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "TTS_Status", - "success" - ] - }, - "TTSState": { - "title": "TTSState", - "type": "object", - "properties": { - "state": { - "type": "boolean" - } - }, - "required": [ - "state" - ] - }, - "TTSVoice": { - "title": "TTSVoice", - "type": "object", - "properties": { - "voice": { - "type": "string" - } - }, - "required": [ - "voice" - ] - } - } - }, - "x-schemas": { - "Accessibility": { - "uri": "https://meta.comcast.com/firebolt/accessibility", - "ClosedCaptionsSettings": { - "title": "ClosedCaptionsSettings", - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether or not closed-captions should be enabled by default" - }, - "preferredLanguages": { - "type": "array", - "items": { - "$ref": "#/x-schemas/Localization/ISO639_2Language" - } - } - }, - "examples": [ - { - "enabled": true, - "styles": { - "fontFamily": "monospaced_serif", - "fontSize": 1, - "fontColor": "#ffffff", - "fontEdge": "none", - "fontEdgeColor": "#7F7F7F", - "fontOpacity": 100, - "backgroundColor": "#000000", - "backgroundOpacity": 100, - "textAlign": "center", - "textAlignVertical": "middle", - "windowColor": "white", - "windowOpacity": 50 - }, - "preferredLanguages": [ - "eng", - "spa" - ] - } - ] - }, - "VoiceGuidanceSettings": { - "title": "VoiceGuidanceSettings", - "type": "object", - "required": [ - "enabled", - "navigationHints", - "rate" - ], - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether or not voice guidance should be enabled by default" - }, - "rate": { - "$ref": "#/x-schemas/Accessibility/SpeechRate", - "description": "The rate at which voice guidance speech will be read back to the user" - }, - "navigationHints": { - "type": "boolean", - "description": "Whether or not voice guidance should include additional navigation hints" - } - }, - "examples": [ - { - "enabled": true, - "navigationHints": true, - "rate": 0.8 - } - ] - }, - "SpeechRate": { - "title": "SpeechRate", - "type": "number", - "minimum": 0.1, - "maximum": 10 - } - }, - "Localization": { - "uri": "https://meta.comcast.com/firebolt/localization", - "ISO639_2Language": { - "type": "string", - "pattern": "^[a-z]{3}$" - }, - "CountryCode": { - "type": "string", - "pattern": "^[A-Z]{2}$" - }, - "Locale": { - "type": "string", - "pattern": "^[a-zA-Z]+([a-zA-Z0-9\\-]*)$" - } - }, - "Policies": { - "uri": "https://meta.comcast.com/firebolt/policies", - "AgePolicy": { - "title": "AgePolicy", - "description": "The policy that describes various age groups to which content is directed. See distributor documentation for further details.", - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [ - "app:adult", - "app:child", - "app:teen" - ] - } - ] - } - }, - "Types": { - "uri": "https://meta.comcast.com/firebolt/types", - "FlatMap": { - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - } - } - } - } -} \ No newline at end of file + "openrpc": "1.2.4", + "info": { + "title": "Firebolt JSON-RPC API", + "version": "", + "x-module-descriptions": { + "Accessibility": "The `Accessibility` module provides access to the user/device settings for closed captioning and voice guidance.\n\nApps **SHOULD** attempt o respect these settings, rather than manage and persist seprate settings, which would be different per-app.", + "Actions": "Methods for getting and observing app intents.", + "Advertising": "A module for platform provided advertising settings and functionality.", + "Device": "A module for querying about the device and it's capabilities.", + "Discovery": "Your App likely wants to integrate with the Platform's discovery capabilities. For example to add a \"Watch Next\" tile that links to your app from the platform's home screen.\n\nGetting access to this information requires to connect to lower level APIs made available by the platform. Since implementations differ between operators and platforms, the Firebolt SDK offers a Discovery module, that exposes a generic, agnostic interface to the developer.\n\nUnder the hood, an underlaying transport layer will then take care of calling the right APIs for the actual platform implementation that your App is running on.\n\nThe Discovery plugin is used to _send_ information to the Platform.\n\n### Localization\nApps should provide all user-facing strings in the device's language, as specified by the Firebolt `Localization.language` property.\n\nApps should provide prices in the same currency presented in the app. If multiple currencies are supported in the app, the app should provide prices in the user's current default currency.", + "Display": "A module for querying about the display", + "Lifecycle2": "Methods and events for responding to Lifecycle changes in your app.", + "Localization": "Methods for accessing location and language preferences.", + "Metrics": "Methods for sending metrics", + "Network": "Methods for accessing network information.", + "Presentation": "Methods for accessing Presentation preferences.", + "Stats": "Provides methods to retrieve application-level system information.", + "TextToSpeech": "A module for controlling and accessing Text To Speech over Firebolt." + } + }, + "methods": [ + { + "name": "rpc.discover", + "summary": "The OpenRPC schema for this JSON-RPC API", + "params": [], + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:rpc:discover" + ] + } + ], + "result": { + "name": "OpenRPC Schema", + "schema": { + "type": "object" + } + }, + "examples": [ + { + "name": "Default", + "params": [], + "result": { + "name": "schema", + "value": {} + } + } + ] + }, + { + "name": "Actions.intent", + "summary": "Returns the current intent.", + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:actions:intent" + ] + } + ], + "params": [], + "result": { + "name": "intent", + "summary": "The current intent as a JSON document.", + "schema": { + "type": "object", + "required": [ + "intent", + "intentId" + ], + "properties": { + "intent": { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string" + }, + "context": { + "type": "object", + "properties": { + "source": { + "type": "string" + } + } + } + } + }, + "intentId": { + "type": "integer", + "minimum": 0 + } + } + } + }, + "examples": [ + { + "name": "Get the current intent", + "result": { + "name": "Default Result", + "value": { + "intent": { + "action": "pre-load", + "context": { + "source": "system" + } + }, + "intentId": 0 + } + } + } + ] + }, + { + "name": "Actions.onIntent", + "tags": [ + { + "name": "event", + "x-notifier": "Actions.onIntent", + "x-subscriber-for": "Actions.intent" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:actions:intent" + ] + } + ], + "summary": "Notifies when the current intent changes.", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "result": { + "name": "intent", + "summary": "The current intent as a JSON document.", + "schema": { + "type": "object", + "required": [ + "intent", + "intentId" + ], + "properties": { + "intent": { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string" + }, + "context": { + "type": "object", + "properties": { + "source": { + "type": "string" + } + } + } + } + }, + "intentId": { + "type": "integer", + "minimum": 0 + } + } + } + }, + "examples": [ + { + "name": "Listen for intent changes", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "Default Result", + "value": { + "intent": { + "action": "pre-load", + "context": { + "source": "system" + } + }, + "intentId": 0 + } + } + } + ] + }, + { + "name": "Actions.start", + "summary": "Sends an intent to the platform.", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:actions:intent" + ] + } + ], + "params": [ + { + "name": "intent", + "summary": "The intent to send, as a JSON document.", + "required": true, + "schema": { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string" + }, + "context": { + "type": "object", + "properties": { + "source": { + "type": "string" + } + } + } + } + } + }, + { + "name": "handlerAppId", + "summary": "Optional ID of the application that should handle the intent.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Start an intent", + "params": [ + { + "name": "intent", + "value": { + "action": "pre-load", + "context": { + "source": "system" + } + } + } + ], + "result": { + "name": "Default Result", + "value": null + } + } + ] + }, + { + "name": "Accessibility.audioDescription", + "summary": "Returns the audio description setting of the device", + "params": [], + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:accessibility:audio-descriptions" + ] + } + ], + "result": { + "name": "setting", + "summary": "the audio description setting", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Getting the audio description setting", + "params": [], + "result": { + "name": "Default Result", + "value": true + } + } + ] + }, + { + "name": "Accessibility.closedCaptionsSettings", + "summary": "Returns captions settings: enabled, and a list of zero or more languages in order of decreasing preference", + "params": [], + "tags": [ + { + "name": "property:readonly", + "x-notifier-params-flattening": "true" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:accessibility:closed-captions" + ] + } + ], + "result": { + "name": "closedCaptionsSettings", + "summary": "the closed captions settings", + "schema": { + "$ref": "#/x-schemas/Accessibility/ClosedCaptionsSettings" + } + }, + "examples": [ + { + "name": "Getting the closed captions settings", + "params": [], + "result": { + "name": "settings", + "value": { + "enabled": true, + "preferredLanguages": [ + "eng", + "spa" + ] + } + } + } + ] + }, + { + "name": "Accessibility.highContrastUI", + "summary": "Returns the high contrast UI device setting", + "params": [], + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:accessibility:high-contrast-ui" + ] + } + ], + "result": { + "name": "highContrastUI", + "summary": "Whether high-contrast UI mode is enabled", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "High-contrast UI mode is enabled", + "params": [], + "result": { + "name": "Default Result", + "value": true + } + } + ] + }, + { + "name": "Accessibility.voiceGuidanceSettings", + "summary": "Returns voice guidance settings: enabled, rate, and verbosity", + "params": [], + "tags": [ + { + "name": "property:readonly", + "x-notifier-params-flattening": "true" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:accessibility:voice-guidance" + ] + } + ], + "result": { + "name": "settings", + "summary": "the voice guidance settings", + "schema": { + "$ref": "#/x-schemas/Accessibility/VoiceGuidanceSettings" + } + }, + "examples": [ + { + "name": "Getting the voice guidance settings", + "params": [], + "result": { + "name": "Default Result", + "value": { + "enabled": true, + "rate": 0.8, + "navigationHints": true + } + } + } + ] + }, + { + "name": "Advertising.advertisingId", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:advertising:identifier" + ] + } + ], + "summary": "Returns the IFA.", + "params": [], + "result": { + "name": "advertisingId", + "summary": "The advertising ID", + "schema": { + "$ref": "#/components/schemas/AdvertisingIdResult" + } + }, + "examples": [ + { + "name": "Getting the advertising ID", + "params": [], + "result": { + "name": "Default Result", + "value": { + "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", + "ifa_type": "sspid", + "lmt": "0" + } + } + }, + { + "name": "Getting the advertising ID with scope browse", + "params": [], + "result": { + "name": "Default Result", + "value": { + "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", + "ifa_type": "sspid", + "lmt": "1" + } + } + }, + { + "name": "Getting the advertising ID with scope content", + "params": [], + "result": { + "name": "Default Result", + "value": { + "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", + "ifa_type": "idfa", + "lmt": "0" + } + } + } + ] + }, + { + "name": "Device.uid", + "summary": "Returns a persistent unique UUID for the current app and device. The UUID is reset when the app or device is reset", + "params": [], + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:uid" + ] + } + ], + "result": { + "name": "uniqueId", + "summary": "A unique UUID for this app-device pair.", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Getting the unique UUID", + "params": [], + "result": { + "name": "Default Result", + "value": "ee6723b8-7ab3-462c-8d93-dbf61227998e" + } + } + ] + }, + { + "name": "Device.deviceClass", + "summary": "Returns the class of the device", + "params": [], + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:device-class" + ] + } + ], + "result": { + "name": "deviceClass", + "summary": "The device class", + "schema": { + "$ref": "#/components/schemas/DeviceClass" + } + }, + "examples": [ + { + "name": "Getting the device class", + "params": [], + "result": { + "name": "Default Result", + "value": "ott" + } + } + ] + }, + { + "name": "Device.uptime", + "summary": "Returns the number of seconds since most recent device boot, including any time spent during deep sleep", + "params": [], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "uptime", + "summary": "The device uptime", + "schema": { + "type": "number" + } + }, + "examples": [ + { + "name": "Getting the device uptime", + "params": [], + "result": { + "name": "Default Result", + "value": 123456 + } + } + ] + }, + { + "name": "Device.timeInActiveState", + "summary": "Returns the number of seconds since the device transitioned to the ON power state", + "params": [], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "timeInActiveState", + "summary": "The device time in active state", + "schema": { + "type": "number" + } + }, + "examples": [ + { + "name": "Getting the number of seconds since the device transitioned to the ON power state", + "params": [], + "result": { + "name": "Default Result", + "value": 654321 + } + } + ] + }, + { + "name": "Device.chipsetId", + "summary": "Returns chipset ID as a printable string, e.g. BCM72180", + "params": [], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "chipsetId", + "summary": "The device chipset ID", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Getting the device chipset ID", + "params": [], + "result": { + "name": "Default Result", + "value": "BCM72180" + } + } + ] + }, + { + "name": "Device.hdr", + "summary": "Returns the HDR standards that are supported by the attached TV or the integral display", + "params": [], + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "negotiatedHdrFormats", + "summary": "the negotiated HDR formats", + "schema": { + "$ref": "#/components/schemas/HDRFormatMap" + } + }, + "examples": [ + { + "name": "Getting the negotiated HDR formats", + "params": [], + "result": { + "name": "Default Result", + "value": { + "hdr10": true, + "hdr10Plus": true, + "dolbyVision": true, + "hlg": true + } + } + } + ] + }, + { + "name": "Device.dolbyAtmosExperienceAvailable", + "params": [], + "result": { + "name": "result", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Default", + "params": [], + "result": { + "name": "value", + "value": true + } + } + ] + }, + { + "name": "Discovery.watched", + "summary": "Notify the platform that content was partially or completely watched", + "tags": [ + { + "name": "polymorphic-reducer" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:discovery:watched" + ] + } + ], + "params": [ + { + "name": "entityId", + "required": true, + "schema": { + "type": "string" + }, + "summary": "The entity Id of the watched content." + }, + { + "name": "progress", + "summary": "How much of the content has been watched (percentage as (0-0.999) for VOD, number of seconds for live)", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "completed", + "summary": "Whether or not this viewing is considered \"complete,\" per the app's definition thereof", + "schema": { + "type": "boolean" + } + }, + { + "name": "watchedOn", + "summary": "Date/Time the content was watched, ISO 8601 Date/Time", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "agePolicy", + "description": "The age policy associated with the watch event. The age policy describes the age groups to which content may be directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "success", + "summary": "Whether the call was successful or not", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Notify the platform of watched content", + "params": [ + { + "name": "entityId", + "value": "partner.com/entity/123" + }, + { + "name": "progress", + "value": 0.95 + }, + { + "name": "completed", + "value": true + }, + { + "name": "watchedOn", + "value": "2021-04-23T18:25:43.511Z" + } + ], + "result": { + "name": "success", + "value": true + } + }, + { + "name": "Notify the platform that child-directed content was watched", + "params": [ + { + "name": "entityId", + "value": "partner.com/entity/123" + }, + { + "name": "progress", + "value": 0.95 + }, + { + "name": "completed", + "value": true + }, + { + "name": "watchedOn", + "value": "2021-04-23T18:25:43.511Z" + }, + { + "name": "agePolicy", + "value": "app:child" + } + ], + "result": { + "name": "success", + "value": true + } + } + ] + }, + { + "name": "Discovery.watchedV2", + "summary": "Notify the platform that content was partially or completely watched, returns whether the notification was accepted", + "tags": [ + { + "name": "polymorphic-reducer" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:discovery:watched" + ] + } + ], + "params": [ + { + "name": "entityId", + "required": true, + "schema": { + "type": "string" + }, + "summary": "The entity Id of the watched content." + }, + { + "name": "progress", + "summary": "How much of the content has been watched (percentage as (0-0.999) for VOD, number of seconds for live)", + "schema": { + "type": "number", + "minimum": 0 + } + }, + { + "name": "completed", + "summary": "Whether or not this viewing is considered \"complete,\" per the app's definition thereof", + "schema": { + "type": "boolean" + } + }, + { + "name": "watchedOn", + "summary": "Date/Time the content was watched, ISO 8601 Date/Time", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "agePolicy", + "description": "The age policy associated with the watch event. The age policy describes the age groups to which content may be directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "summary": "Whether the platform accepted the watched notification", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Notify the platform of watched content (v2)", + "params": [ + { + "name": "entityId", + "value": "partner.com/entity/123" + }, + { + "name": "progress", + "value": 0.95 + }, + { + "name": "completed", + "value": true + }, + { + "name": "watchedOn", + "value": "2021-04-23T18:25:43.511Z" + } + ], + "result": { + "name": "result", + "value": true + } + }, + { + "name": "Notify the platform that child-directed content was watched (v2)", + "params": [ + { + "name": "entityId", + "value": "partner.com/entity/123" + }, + { + "name": "progress", + "value": 0.95 + }, + { + "name": "completed", + "value": true + }, + { + "name": "watchedOn", + "value": "2021-04-23T18:25:43.511Z" + }, + { + "name": "agePolicy", + "value": "app:child" + } + ], + "result": { + "name": "result", + "value": true + } + } + ] + }, + { + "name": "Display.edid", + "summary": "Returns the EDID (and extensions) of the connected or integral display, as a Base64 encoded string", + "params": [], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:display:info" + ] + } + ], + "result": { + "name": "Base64 EDID", + "summary": "The EDID (and extensions) of the connected or integral display, as a Base64 encoded string", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Getting the display EDID", + "params": [], + "result": { + "name": "Default Result", + "value": "ZWU2NzIzYjgtN2FiMy00NjJjLThkOTMtZGJmNjEyMjc5OThl" + } + } + ] + }, + { + "name": "Display.size", + "summary": "Returns the physical dimensions of the connected or integral display, in centimeters. Returns 0, 0 on a OTT/STB device when a display is not connected over HDMI", + "params": [], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:display:info" + ] + } + ], + "result": { + "name": "size", + "summary": "The display size in centimeters", + "schema": { + "type": "object", + "properties": { + "width": { + "type": "integer", + "description": "The width of the display in centimeters" + }, + "height": { + "type": "integer", + "description": "The height of the display in centimeters" + } + } + } + }, + "examples": [ + { + "name": "Getting the display size", + "params": [], + "result": { + "name": "Default Result", + "value": { + "width": 48, + "height": 27 + } + } + } + ] + }, + { + "name": "Display.maxResolution", + "summary": "Returns the physical/native resolution of the connected or integral display, in pixels. Returns 0, 0 on a OTT/STB device when a display is not connected over HDMI", + "params": [], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:display:info" + ] + } + ], + "result": { + "name": "maxResolution", + "summary": "The display resolution", + "schema": { + "type": "object", + "properties": { + "width": { + "type": "integer", + "description": "The width of the display in pixels" + }, + "height": { + "type": "integer", + "description": "The height of the display in pixels" + } + } + } + }, + "examples": [ + { + "name": "Getting the display size", + "params": [], + "result": { + "name": "Default Result", + "value": { + "width": 1920, + "height": 1080 + } + } + } + ] + }, + { + "name": "Lifecycle2.close", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:cpp-client-only" + ] + } + ], + "summary": "Request the platform to deactivate the app, and possibly take further action.", + "params": [ + { + "name": "type", + "summary": "The type of the close app is requesting", + "required": true, + "schema": { + "$ref": "#/components/schemas/CloseType" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Close the app when the user presses back on the app home screen", + "params": [ + { + "name": "type", + "value": "unload" + } + ], + "result": { + "name": "Default Result", + "value": null + } + }, + { + "name": "Close the app when the user selects an exit menu item", + "params": [ + { + "name": "type", + "value": "deactivate" + } + ], + "result": { + "name": "Default Result", + "value": null + } + } + ] + }, + { + "name": "Lifecycle2.state", + "summary": "Get the current lifecycle state of the app.", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:cpp-client-only" + ] + } + ], + "params": [], + "result": { + "name": "state", + "summary": "The current lifecycle state of the app.", + "schema": { + "$ref": "#/components/schemas/LifecycleState" + } + }, + "examples": [ + { + "name": "Default Example", + "params": [], + "result": { + "name": "Default Result", + "value": "active" + } + } + ] + }, + { + "name": "Localization.country", + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:country-code" + ] + } + ], + "summary": "Returns the ISO 3166-1 alpha-2 code for the country device is located in.", + "params": [], + "result": { + "name": "code", + "summary": "The device country code.", + "schema": { + "$ref": "#/x-schemas/Localization/CountryCode" + } + }, + "examples": [ + { + "name": "Default example", + "params": [], + "result": { + "name": "Default Result", + "value": "US" + } + }, + { + "name": "Another example", + "params": [], + "result": { + "name": "Default Result", + "value": "GB" + } + } + ] + }, + { + "name": "Localization.preferredAudioLanguages", + "summary": "Returns a list of ISO 639-2/B codes for the preferred audio languages on this device.", + "params": [], + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:preferred-audio-languages" + ] + } + ], + "result": { + "name": "languages", + "summary": "The preferred audio languages.", + "schema": { + "type": "array", + "items": { + "$ref": "#/x-schemas/Localization/ISO639_2Language" + } + } + }, + "examples": [ + { + "name": "Default example", + "params": [], + "result": { + "name": "Default Result", + "value": [ + "spa", + "eng" + ] + } + } + ] + }, + { + "name": "Localization.presentationLanguage", + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:locale" + ] + } + ], + "summary": "Get the *full* BCP 47 code, including script, region, variant, etc., for the preferred locale", + "params": [], + "result": { + "name": "locale", + "summary": "The device locale.", + "schema": { + "$ref": "#/x-schemas/Localization/Locale" + } + }, + "examples": [ + { + "name": "Default example", + "params": [], + "result": { + "name": "Default Result", + "value": "en-US" + } + } + ] + }, + { + "name": "Localization.timeZone", + "params": [], + "result": { + "name": "result", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Default", + "params": [], + "result": { + "name": "value", + "value": "America/New_York" + } + } + ] + }, + { + "name": "Metrics.ready", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:general" + ] + } + ], + "summary": "Inform the platform that your app is minimally usable. This method is called automatically by `Lifecycle.ready()`", + "params": [], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send ready metric", + "params": [], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.signIn", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:general" + ] + } + ], + "summary": "Log a sign in event, called by Discovery.signIn().", + "params": [], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send signIn metric", + "params": [], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.signOut", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:general" + ] + } + ], + "summary": "Log a sign out event, called by Discovery.signOut().", + "params": [], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send signOut metric", + "params": [], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.startContent", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:general" + ] + } + ], + "summary": "Inform the platform that your user has started content.", + "params": [ + { + "name": "entityId", + "summary": "Optional entity ID of the content.", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send startContent metric", + "params": [], + "result": { + "name": "result", + "value": null + } + }, + { + "name": "Send startContent metric w/ entity", + "params": [ + { + "name": "entityId", + "value": "abc" + } + ], + "result": { + "name": "result", + "value": null + } + }, + { + "name": "Send startContent metric and notify the platform that the content is child-directed", + "params": [ + { + "name": "entityId", + "value": "abc" + }, + { + "name": "agePolicy", + "value": "app:child" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.stopContent", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:general" + ] + } + ], + "summary": "Inform the platform that your user has stopped content.", + "params": [ + { + "name": "entityId", + "summary": "Optional entity ID of the content.", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send stopContent metric", + "params": [], + "result": { + "name": "result", + "value": null + } + }, + { + "name": "Send stopContent metric w/ entity", + "params": [ + { + "name": "entityId", + "value": "abc" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.page", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:general" + ] + } + ], + "summary": "Inform the platform that your user has navigated to a page or view.", + "params": [ + { + "name": "pageId", + "summary": "Page ID of the content.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send page metric", + "params": [ + { + "name": "pageId", + "value": "xyz" + } + ], + "result": { + "name": "result", + "value": null + } + }, + { + "name": "Send page metric w/ pageId", + "params": [ + { + "name": "pageId", + "value": "home" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.error", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:general" + ] + } + ], + "summary": "Inform the platform of an error that has occurred in your app.", + "params": [ + { + "name": "type", + "summary": "The type of error", + "schema": { + "$ref": "#/components/schemas/ErrorType" + }, + "required": true + }, + { + "name": "code", + "summary": "an app-specific error code", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "description", + "summary": "A short description of the error", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "visible", + "summary": "Whether or not this error was visible to the user.", + "schema": { + "type": "boolean" + }, + "required": true + }, + { + "name": "parameters", + "summary": "Optional additional parameters to be logged with the error", + "schema": { + "$ref": "#/x-schemas/Types/FlatMap" + }, + "required": false + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send error metric", + "params": [ + { + "name": "type", + "value": "media" + }, + { + "name": "code", + "value": "MEDIA-STALLED" + }, + { + "name": "description", + "value": "playback stalled" + }, + { + "name": "visible", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaLoadStart", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when setting the URL of a media asset to play, in order to infer load time.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send loadstart metric.", + "params": [ + { + "name": "entityId", + "value": "345" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaPlay", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when media playback should start due to autoplay, user-initiated play, or unpausing.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send play metric.", + "params": [ + { + "name": "entityId", + "value": "345" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaPlaying", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when media playback actually starts due to autoplay, user-initiated play, unpausing, or recovering from a buffering interruption.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send playing metric.", + "params": [ + { + "name": "entityId", + "value": "345" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaPause", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when media playback will pause due to an intentional pause operation.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send pause metric.", + "params": [ + { + "name": "entityId", + "value": "345" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaWaiting", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when media playback will halt due to a network, buffer, or other unintentional constraint.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send waiting metric.", + "params": [ + { + "name": "entityId", + "value": "345" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaSeeking", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when a seek is initiated during media playback.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "target", + "summary": "Target destination of the seek, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", + "schema": { + "$ref": "#/components/schemas/MediaPosition" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send seeking metric.", + "params": [ + { + "name": "entityId", + "value": "345" + }, + { + "name": "target", + "value": 0.5 + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaSeeked", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when a seek is completed during media playback.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "position", + "summary": "Resulting position of the seek operation, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", + "schema": { + "$ref": "#/components/schemas/MediaPosition" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send seeked metric.", + "params": [ + { + "name": "entityId", + "value": "345" + }, + { + "name": "position", + "value": 0.51 + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaRateChanged", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when the playback rate of media is changed.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "rate", + "summary": "The new playback rate.", + "schema": { + "type": "number" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send ratechange metric.", + "params": [ + { + "name": "entityId", + "value": "345" + }, + { + "name": "rate", + "value": 2 + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaRenditionChanged", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when the playback rendition (e.g. bitrate, dimensions, profile, etc) is changed.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "bitrate", + "summary": "The new bitrate in kbps.", + "schema": { + "type": "number" + }, + "required": true + }, + { + "name": "width", + "summary": "The new resolution width.", + "schema": { + "type": "number" + }, + "required": true + }, + { + "name": "height", + "summary": "The new resolution height.", + "schema": { + "type": "number" + }, + "required": true + }, + { + "name": "profile", + "summary": "A description of the new profile, e.g. 'HDR' etc.", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send renditionchange metric.", + "params": [ + { + "name": "entityId", + "value": "345" + }, + { + "name": "bitrate", + "value": 5000 + }, + { + "name": "width", + "value": 1920 + }, + { + "name": "height", + "value": 1080 + }, + { + "name": "profile", + "value": "HDR+" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.mediaEnded", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:media" + ] + } + ], + "summary": "Called when playback has stopped because the end of the media was reached.", + "params": [ + { + "name": "entityId", + "summary": "The entityId of the media.", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send ended metric.", + "params": [ + { + "name": "entityId", + "value": "345" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.event", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:distributor" + ] + } + ], + "summary": "Inform the platform of 1st party distributor metrics. 'data' parameter is a JSON document", + "params": [ + { + "name": "schema", + "summary": "The schema URI of the metric type", + "schema": { + "type": "string", + "format": "uri" + }, + "required": true + }, + { + "name": "data", + "summary": "A JSON payload", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "agePolicy", + "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", + "schema": { + "$ref": "#/x-schemas/Policies/AgePolicy" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send foo event", + "params": [ + { + "name": "schema", + "value": "http://meta.rdkcentral.com/some/schema" + }, + { + "name": "data", + "value": "foo" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Metrics.appInfo", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:metrics:general" + ] + } + ], + "summary": "Inform the platform about an app's build info.", + "params": [ + { + "name": "build", + "summary": "The build / version of this app.", + "schema": { + "type": "string" + }, + "required": true + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send appInfo metric", + "params": [ + { + "name": "build", + "value": "1.2.2" + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "Network.connected", + "summary": "Returns whether the device currently has a usable network connection.", + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:network:connected" + ] + } + ], + "params": [], + "result": { + "name": "success", + "summary": "Whether the device currently has a usable network connection.", + "schema": { + "$ref": "#/components/schemas/Connected" + } + }, + "examples": [ + { + "name": "Connected example", + "params": [], + "result": { + "name": "success", + "value": true + } + } + ] + }, + { + "name": "Presentation.focused", + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:cpp-client-only" + ] + } + ], + "summary": "Whether the app is in focus, i.e. receiving key presses. Provided for those apps/runtimes that cannot use Wayland", + "params": [], + "result": { + "name": "focused", + "summary": "Whether the app is in focus.", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Default example", + "params": [], + "result": { + "name": "Default Result", + "value": true + } + } + ] + }, + { + "name": "Stats.memoryUsage", + "summary": "Returns information about container memory usage, in units of 1024 bytes.", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:cpp-client-only" + ] + } + ], + "params": [], + "result": { + "name": "result", + "schema": { + "$ref": "#/components/schemas/MemoryUsage" + } + }, + "examples": [ + { + "name": "Default example", + "params": [], + "result": { + "name": "value", + "description": "The memory usage information", + "value": { + "userMemoryUsed": 123456, + "userMemoryLimit": 789012, + "gpuMemoryUsed": 345678, + "gpuMemoryLimit": 901234 + } + } + } + ] + }, + { + "name": "TextToSpeech.speak", + "summary": "Speak the utterance immediately. Any ongoing speech is interrupted.", + "description": "Text argument is either plain text or a well-formed SSML document TTS_status, not success attribute, to be used by caller to indicate success of call 0 OK, 1 Fail, 2 not enabled, 3 invalid configuration Raises onSpeechinterrupted if speaking is interrupted", + "params": [ + { + "name": "text", + "summary": "String to be converted to Audio for speech", + "schema": { + "type": "string" + }, + "required": true + } + ], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "result": { + "name": "speakResult", + "summary": "Result for Speak", + "schema": { + "$ref": "#/components/schemas/SpeechResponse" + } + }, + "examples": [ + { + "name": "Getting the result of speak", + "params": [ + { + "name": "text", + "value": "I am a text waiting for speech." + } + ], + "result": { + "name": "result", + "value": { + "speechid": 1, + "TTS_Status": 0, + "success": true + } + } + } + ] + }, + { + "name": "TextToSpeech.pause", + "summary": "Pauses the speech for given speech id", + "description": "Pauses the utterance. Raises onSpeechpause if ongoing speech is paused. Does nothing if utterance is already paused", + "params": [ + { + "name": "speechid", + "summary": "Identifier for the speech call", + "schema": { + "$ref": "#/components/schemas/SpeechId" + }, + "required": true + } + ], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "result": { + "name": "pauseResult", + "summary": "Result for Pause", + "schema": { + "$ref": "#/components/schemas/TTSStatusResponse" + } + }, + "examples": [ + { + "name": "Pause a given speech id", + "params": [ + { + "name": "speechid", + "value": 1 + } + ], + "result": { + "name": "TTS_Status", + "value": { + "TTS_Status": 0, + "success": true + } + } + } + ] + }, + { + "name": "TextToSpeech.resume", + "summary": "Resumes the speech for given speech id", + "description": "Continue the paused utterance. Raises onSpeechresume if paused speech is resumed. Does nothing if the utterance is not paused", + "params": [ + { + "name": "speechid", + "summary": "Identifier for the speech call", + "schema": { + "$ref": "#/components/schemas/SpeechId" + }, + "required": true + } + ], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "result": { + "name": "resumeResult", + "summary": "Result for Resume", + "schema": { + "$ref": "#/components/schemas/TTSStatusResponse" + } + }, + "examples": [ + { + "name": "Resume a given speech id.", + "params": [ + { + "name": "speechid", + "value": 1 + } + ], + "result": { + "name": "TTS_Status", + "value": { + "TTS_Status": 0, + "success": true + } + } + } + ] + }, + { + "name": "TextToSpeech.cancel", + "summary": "Cancels the speech for given speech id", + "description": "Stop speaking if utterance is currently being spoken. Raises onSpeechinterrupted if speaking was interrupted.", + "params": [ + { + "name": "speechid", + "summary": "Identifier for the speech call", + "schema": { + "$ref": "#/components/schemas/SpeechId" + }, + "required": true + } + ], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "result": { + "name": "cancelResult", + "summary": "Result for cancel", + "schema": { + "$ref": "#/components/schemas/TTSStatusResponse" + } + }, + "examples": [ + { + "name": "Cancel a given speech id.", + "params": [ + { + "name": "speechid", + "value": 1 + } + ], + "result": { + "name": "TTS_Status", + "value": { + "TTS_Status": 0, + "success": true + } + } + } + ] + }, + { + "name": "TextToSpeech.getspeechstate", + "summary": "Returns the state of the utterance.", + "params": [ + { + "name": "speechid", + "summary": "Identifier for the speech call", + "schema": { + "$ref": "#/components/schemas/SpeechId" + }, + "required": true + } + ], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "result": { + "name": "speechStateResult", + "summary": "Result for speech state", + "schema": { + "$ref": "#/components/schemas/SpeechStateResponse" + } + }, + "examples": [ + { + "name": "State for a given speech id.", + "params": [ + { + "name": "speechid", + "value": 1 + } + ], + "result": { + "name": "speechstate", + "value": { + "speechstate": 1, + "TTS_Status": 0, + "success": true + } + } + } + ] + }, + { + "name": "TextToSpeech.listvoices", + "summary": "Returns the list of available voices as human-readable strings, e.g. 'ava', 'amelie', 'angelica'", + "params": [ + { + "name": "language", + "summary": "Language - string - BCP 47", + "schema": { + "$ref": "#/x-schemas/Localization/Locale" + }, + "required": true + } + ], + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "result": { + "name": "listvoices", + "summary": "The list of voices supported for the language", + "schema": { + "$ref": "#/components/schemas/ListVoicesResponse" + } + }, + "examples": [ + { + "name": "Getting the list of voices", + "params": [ + { + "name": "language", + "value": "en-US" + } + ], + "result": { + "name": "voiceList", + "value": { + "TTS_Status": 0, + "voices": [ + "carol", + "tom" + ] + } + } + } + ] + }, + { + "name": "Lifecycle2.onStateChanged", + "tags": [ + { + "name": "event", + "x-contextual-parameters": 0, + "x-notifier": "Lifecycle2.onStateChanged" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:cpp-client-only" + ] + } + ], + "summary": "Notification of lifecycle state change, raised after the platform has transitioned the app/runtime to the new lifecycle state", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "App is active after being initialized", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + }, + { + "name": "Single transition to paused state", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "TextToSpeech.onWillspeak", + "summary": "Text to speech conversion is about to start.", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "event", + "x-notifier": "TextToSpeech.onWillspeak" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "TextToSpeech.onSpeechstart", + "summary": "Utterance is about to be spoken.", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "event", + "x-notifier": "TextToSpeech.onSpeechstart" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "TextToSpeech.onSpeechpause", + "summary": "Ongoing speech was paused.", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "event", + "x-notifier": "TextToSpeech.onSpeechpause" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "TextToSpeech.onSpeechresume", + "summary": "Paused speech was resumed.", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "event", + "x-notifier": "TextToSpeech.onSpeechresume" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "TextToSpeech.onSpeechcomplete", + "summary": "Speech completed successfully.", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "event", + "x-notifier": "TextToSpeech.onSpeechcomplete" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "TextToSpeech.onSpeechinterrupted", + "summary": "Speech was stopped, due to another call to speak or cancel.", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "event", + "x-notifier": "TextToSpeech.onSpeechinterrupted" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "TextToSpeech.onNetworkerror", + "summary": "Utterance failed due to network error.", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "event", + "x-notifier": "TextToSpeech.onNetworkerror" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "TextToSpeech.onPlaybackerror", + "summary": "Utterance failed during playback.", + "tags": [ + { + "name": "rpc-only" + }, + { + "name": "event", + "x-notifier": "TextToSpeech.onPlaybackerror" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:text-to-speech:general" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Accessibility.onAudioDescriptionChanged", + "summary": "Returns the audio description setting of the device", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "event", + "x-notifier": "Accessibility.onAudioDescriptionChanged", + "x-subscriber-for": "Accessibility.audioDescription" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:accessibility:audio-descriptions" + ] + } + ], + "examples": [ + { + "name": "Getting the audio description setting", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Accessibility.onClosedCaptionsSettingsChanged", + "summary": "Returns captions settings: enabled, and a list of zero or more languages in order of decreasing preference", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "event", + "x-notifier-params-flattening": "true", + "x-notifier": "Accessibility.onClosedCaptionsSettingsChanged", + "x-subscriber-for": "Accessibility.closedCaptionsSettings" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:accessibility:closed-captions" + ] + } + ], + "examples": [ + { + "name": "Getting the closed captions settings", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Accessibility.onHighContrastUIChanged", + "summary": "Returns the high contrast UI device setting", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "event", + "x-notifier": "Accessibility.onHighContrastUIChanged", + "x-subscriber-for": "Accessibility.highContrastUI" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:accessibility:high-contrast-ui" + ] + } + ], + "examples": [ + { + "name": "High-contrast UI mode is enabled", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Accessibility.onVoiceGuidanceSettingsChanged", + "summary": "Returns voice guidance settings: enabled, rate, and verbosity", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "event", + "x-notifier-params-flattening": "true", + "x-notifier": "Accessibility.onVoiceGuidanceSettingsChanged", + "x-subscriber-for": "Accessibility.voiceGuidanceSettings" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:accessibility:voice-guidance" + ] + } + ], + "examples": [ + { + "name": "Getting the voice guidance settings", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Device.onHdrChanged", + "summary": "Returns the HDR standards that are supported by the attached TV or the integral display", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "event", + "x-notifier": "Device.onHdrChanged", + "x-subscriber-for": "Device.hdr" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "examples": [ + { + "name": "Getting the negotiated HDR formats", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Device.onDolbyAtmosExperienceAvailableChanged", + "params": [ + { + "name": "listen", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Default", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "value", + "value": true + } + } + ] + }, + { + "name": "Localization.onCountryChanged", + "tags": [ + { + "name": "event", + "x-notifier": "Localization.onCountryChanged", + "x-subscriber-for": "Localization.country" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:country-code" + ] + } + ], + "summary": "Returns the ISO 3166-1 alpha-2 code for the country device is located in.", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + }, + { + "name": "Another example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Localization.onPreferredAudioLanguagesChanged", + "summary": "Returns a list of ISO 639-2/B codes for the preferred audio languages on this device.", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "event", + "x-notifier": "Localization.onPreferredAudioLanguagesChanged", + "x-subscriber-for": "Localization.preferredAudioLanguages" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:preferred-audio-languages" + ] + } + ], + "examples": [ + { + "name": "Default example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Localization.onPresentationLanguageChanged", + "tags": [ + { + "name": "event", + "x-notifier": "Localization.onPresentationLanguageChanged", + "x-subscriber-for": "Localization.presentationLanguage" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:locale" + ] + } + ], + "summary": "Get the *full* BCP 47 code, including script, region, variant, etc., for the preferred locale", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Localization.onTimeZoneChanged", + "params": [ + { + "name": "listen", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Default", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "value", + "value": "America/New_York" + } + } + ] + }, + { + "name": "Network.onConnectedChanged", + "summary": "Returns whether the device currently has a usable network connection.", + "tags": [ + { + "name": "event", + "x-notifier": "Network.onConnectedChanged", + "x-subscriber-for": "Network.connected" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:network:connected" + ] + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Connected example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, + { + "name": "Presentation.onFocusedChanged", + "tags": [ + { + "name": "event", + "x-notifier": "Presentation.onFocusedChanged", + "x-subscriber-for": "Presentation.focused" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:cpp-client-only" + ] + } + ], + "summary": "Whether the app is in focus, i.e. receiving key presses. Provided for those apps/runtimes that cannot use Wayland", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + } + ], + "components": { + "schemas": { + "AdvertisingIdResult": { + "title": "AdvertisingIdResult", + "type": "object", + "properties": { + "ifa": { + "type": "string", + "description": "UUID conforming to IAB standard" + }, + "ifa_type": { + "type": "string", + "description": "Source of the IFA as defined by IAB" + }, + "lmt": { + "type": "string", + "enum": [ + "0", + "1" + ], + "description": "Boolean that if set to 1, user has requested ad tracking and measurement is disabled" + } + }, + "required": [ + "ifa", + "ifa_type", + "lmt" + ] + }, + "HDRFormatMap": { + "title": "HDRFormatMap", + "type": "object", + "properties": { + "hdr10": { + "type": "boolean" + }, + "hdr10Plus": { + "type": "boolean" + }, + "dolbyVision": { + "type": "boolean" + }, + "hlg": { + "type": "boolean" + } + }, + "required": [ + "hdr10", + "hdr10Plus", + "dolbyVision", + "hlg" + ], + "description": "The type of HDR format" + }, + "DeviceClass": { + "title": "DeviceClass", + "type": "string", + "enum": [ + "ott", + "stb", + "tv" + ], + "description": "The type of device" + }, + "CloseType": { + "title": "CloseType", + "description": "The application close type", + "type": "string", + "enum": [ + "deactivate", + "unload", + "killReload", + "killReactivate" + ] + }, + "LifecycleState": { + "title": "LifecycleState", + "description": "The application Lifecycle state", + "type": "string", + "enum": [ + "initializing", + "active", + "paused", + "suspended", + "hibernated", + "terminating" + ] + }, + "StateChange": { + "title": "StateChange", + "type": "object", + "properties": { + "newState": { + "$ref": "#/components/schemas/LifecycleState" + }, + "oldState": { + "$ref": "#/components/schemas/LifecycleState" + } + } + }, + "MediaPosition": { + "title": "MediaPosition", + "description": "Represents a position inside playback content, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", + "oneOf": [ + { + "const": 0 + }, + { + "type": "number", + "exclusiveMinimum": 0, + "exclusiveMaximum": 1 + }, + { + "type": "integer", + "minimum": 1, + "maximum": 86400 + } + ] + }, + "ErrorType": { + "title": "ErrorType", + "type": "string", + "enum": [ + "network", + "media", + "restriction", + "entitlement", + "other" + ] + }, + "EventObjectPrimitives": { + "title": "EventObjectPrimitives", + "anyOf": [ + { + "type": "string", + "maxLength": 256 + }, + { + "type": "number" + }, + { + "type": "integer" + }, + { + "type": "boolean" + }, + { + "type": "null" + } + ] + }, + "Connected": { + "type": "boolean", + "description": "Indicates whether the device currently has a usable network connection." + }, + "MemoryUsage": { + "title": "MemoryUsage", + "type": "object", + "description": "Describes current and maximum memory usage of the container.", + "properties": { + "userMemoryUsedKiB": { + "type": "integer", + "description": "User memory currently used in 1024 bytes." + }, + "userMemoryLimitKiB": { + "type": "integer", + "description": "Maximum user memory available in 1024 bytes." + }, + "gpuMemoryUsedKiB": { + "type": "integer", + "description": "GPU memory currently used in 1024 bytes." + }, + "gpuMemoryLimitKiB": { + "type": "integer", + "description": "Maximum GPU memory available in 1024 bytes." + } + }, + "required": [ + "userMemoryUsedKiB", + "userMemoryLimitKiB", + "gpuMemoryUsedKiB", + "gpuMemoryLimitKiB" + ] + }, + "TTSEnabled": { + "title": "TTSEnabled", + "type": "object", + "required": [ + "TTS_Status", + "isenabled" + ], + "properties": { + "TTS_Status": { + "$ref": "#/components/schemas/TTSStatus" + }, + "isenabled": { + "type": "boolean" + } + } + }, + "ListVoicesResponse": { + "title": "ListVoicesResponse", + "type": "object", + "required": [ + "TTS_Status", + "voices" + ], + "properties": { + "TTS_Status": { + "$ref": "#/components/schemas/TTSStatus" + }, + "voices": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "TTSConfiguration": { + "title": "TTSConfiguration", + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean" + }, + "ttsendpoint": { + "type": "string", + "description": "URL for Text to Speech API" + }, + "ttsendpointsecured": { + "type": "string", + "description": "Secure URL for Text to Speech API" + }, + "language": { + "type": "string", + "description": "Language used by Text to speech" + }, + "voice": { + "type": "string", + "description": "Voice used by Text to speech" + }, + "volume": { + "type": "integer", + "description": "Volume for Text to speech", + "minimum": 0, + "maximum": 100 + }, + "primvolduckpercent": { + "type": "integer", + "description": "Prime Volume duck percent for Text to speech", + "minimum": 0, + "maximum": 100 + }, + "rate": { + "type": "integer", + "description": "Speech rate for Text to speech", + "minimum": 0, + "maximum": 100 + }, + "speechrate": { + "description": "Rate for speech", + "$ref": "#/components/schemas/SpeechRate" + }, + "fallbacktext": { + "description": "Fallback text for TTS", + "$ref": "#/components/schemas/FallbackText" + } + }, + "examples": [ + {} + ] + }, + "SpeechRate": { + "title": "SpeechRate", + "type": "string", + "enum": [ + "slow", + "medium", + "fast", + "faster", + "fastest" + ] + }, + "FallbackText": { + "title": "FallbackText", + "type": "object", + "properties": { + "scenario": { + "type": "string", + "description": "Scenario for fallback Text" + }, + "value": { + "type": "string", + "description": "Value for fallback Text" + } + } + }, + "SpeechResponse": { + "title": "SpeechResponse", + "type": "object", + "properties": { + "speechid": { + "$ref": "#/components/schemas/SpeechId" + }, + "TTS_Status": { + "$ref": "#/components/schemas/TTSStatus" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "speechid", + "TTS_Status", + "success" + ] + }, + "SpeechId": { + "type": "integer" + }, + "SpeechIdEvent": { + "type": "object", + "properties": { + "speechid": { + "$ref": "#/components/schemas/SpeechId" + } + }, + "required": [ + "speechid" + ] + }, + "TTSStatus": { + "title": "TTSStatus", + "type": "integer", + "minimum": 0, + "maximum": 3 + }, + "SpeechState": { + "title": "SpeechState", + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "description": "0 = SPEECH_PENDING, 1 = SPEECH_IN_PROGRESS, 2 = SPEECH_PAUSED, 3 = SPEECH_NOT_FOUND" + }, + "SpeechStateResponse": { + "title": "SpeechStateResponse", + "type": "object", + "properties": { + "speechstate": { + "$ref": "#/components/schemas/SpeechState" + }, + "TTS_Status": { + "$ref": "#/components/schemas/TTSStatus" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "speechstate", + "TTS_Status", + "success" + ] + }, + "TTSStatusResponse": { + "title": "TTSStatusResponse", + "type": "object", + "properties": { + "TTS_Status": { + "$ref": "#/components/schemas/TTSStatus" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "TTS_Status", + "success" + ] + }, + "TTSState": { + "title": "TTSState", + "type": "object", + "properties": { + "state": { + "type": "boolean" + } + }, + "required": [ + "state" + ] + }, + "TTSVoice": { + "title": "TTSVoice", + "type": "object", + "properties": { + "voice": { + "type": "string" + } + }, + "required": [ + "voice" + ] + } + } + }, + "x-schemas": { + "Accessibility": { + "uri": "https://meta.comcast.com/firebolt/accessibility", + "ClosedCaptionsSettings": { + "title": "ClosedCaptionsSettings", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether or not closed-captions should be enabled by default" + }, + "preferredLanguages": { + "type": "array", + "items": { + "$ref": "#/x-schemas/Localization/ISO639_2Language" + } + } + }, + "examples": [ + { + "enabled": true, + "styles": { + "fontFamily": "monospaced_serif", + "fontSize": 1, + "fontColor": "#ffffff", + "fontEdge": "none", + "fontEdgeColor": "#7F7F7F", + "fontOpacity": 100, + "backgroundColor": "#000000", + "backgroundOpacity": 100, + "textAlign": "center", + "textAlignVertical": "middle", + "windowColor": "white", + "windowOpacity": 50 + }, + "preferredLanguages": [ + "eng", + "spa" + ] + } + ] + }, + "VoiceGuidanceSettings": { + "title": "VoiceGuidanceSettings", + "type": "object", + "required": [ + "enabled", + "navigationHints", + "rate" + ], + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether or not voice guidance should be enabled by default" + }, + "rate": { + "$ref": "#/x-schemas/Accessibility/SpeechRate", + "description": "The rate at which voice guidance speech will be read back to the user" + }, + "navigationHints": { + "type": "boolean", + "description": "Whether or not voice guidance should include additional navigation hints" + } + }, + "examples": [ + { + "enabled": true, + "navigationHints": true, + "rate": 0.8 + } + ] + }, + "SpeechRate": { + "title": "SpeechRate", + "type": "number", + "minimum": 0.1, + "maximum": 10 + } + }, + "Localization": { + "uri": "https://meta.comcast.com/firebolt/localization", + "ISO639_2Language": { + "type": "string", + "pattern": "^[a-z]{3}$" + }, + "CountryCode": { + "type": "string", + "pattern": "^[A-Z]{2}$" + }, + "Locale": { + "type": "string", + "pattern": "^[a-zA-Z]+([a-zA-Z0-9\\-]*)$" + } + }, + "Policies": { + "uri": "https://meta.comcast.com/firebolt/policies", + "AgePolicy": { + "title": "AgePolicy", + "description": "The policy that describes various age groups to which content is directed. See distributor documentation for further details.", + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "app:adult", + "app:child", + "app:teen" + ] + } + ] + } + }, + "Types": { + "uri": "https://meta.comcast.com/firebolt/types", + "FlatMap": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + } + } + } +} diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index dd93d7e..7d28222 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -64,8 +64,7 @@ class IActions virtual Result unsubscribe(SubscriptionId id) = 0; virtual void unsubscribeAll() = 0; - virtual Result start(const IntentData& intent, - std::optional handlerAppId = std::nullopt) const = 0; + virtual Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const = 0; }; // class IActions diff --git a/include/firebolt/firebolt.h b/include/firebolt/firebolt.h index d889ec0..fbcc122 100644 --- a/include/firebolt/firebolt.h +++ b/include/firebolt/firebolt.h @@ -32,6 +32,7 @@ #include "firebolt/presentation.h" #include "firebolt/stats.h" #include "firebolt/texttospeech.h" +#include "firebolt/videooutput.h" #include #include #include @@ -169,5 +170,7 @@ class FIREBOLTCLIENT_EXPORT IFireboltAccessor * @return Reference to Actions interface */ virtual Actions::IActions& ActionsInterface() = 0; + + virtual Videooutput::IVideooutput& VideooutputInterface() = 0; }; } // namespace Firebolt diff --git a/include/firebolt/videooutput.h b/include/firebolt/videooutput.h new file mode 100644 index 0000000..11a6701 --- /dev/null +++ b/include/firebolt/videooutput.h @@ -0,0 +1,188 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +// +// ============================================================================ +// AUTO-GENERATED by firebolt-sdk-gen v0.1.0 — DO NOT EDIT +// ============================================================================ +#ifndef FIREBOLT_VIDEOOUTPUT_H +#define FIREBOLT_VIDEOOUTPUT_H + +#include +#include +#include +#include +#include + +namespace Firebolt::Videooutput +{ +enum class CecStateValue +{ + Active, + Inactive, + Unsupported, +}; + +enum class ColorDepthValue +{ + D0, + D10, + D12, + D8, +}; + +enum class ColorFormatValue +{ + None, + Rgb444, + Ycbcr420, + Ycbcr422, + Ycbcr444, +}; + +enum class DynamicRangeValue +{ + DolbyVision, + Hdr10, + Hdr10plus, + Hlg, + None, + Sdr, +}; + +enum class HdcpState +{ + Direct, + Hdcp14, + Hdcp22, + None, +}; + +enum class OutputColorimetry +{ + Bt2020rgb, + Bt2020ycc, + Bt709, + None, + Oprgb, +}; + +enum class QuantizationRangeValue +{ + Full, + Limited, + None, +}; + +enum class RefreshRateValue +{ + R0, + R23976, + R24, + R25, + R2997, + R30, + R50, + R5994, + R60, +}; + +struct VideoOutputResolution +{ + uint32_t height; + uint32_t width; +}; + +class IVideooutput +{ +public: + virtual ~IVideooutput() = default; + + virtual Result cecState() const = 0; + virtual Result subscribeOnCecStateChanged(std::function&& notification) = 0; + + virtual Result colorDepth() const = 0; + + virtual Result colorFormat() const = 0; + + virtual Result colorimetry() const = 0; + + virtual Result dynamicRange() const = 0; + + virtual Result hdcp() const = 0; + virtual Result subscribeOnHdcpChanged(std::function&& notification) = 0; + + virtual Result quantizationRange() const = 0; + + virtual Result refreshRate() const = 0; + virtual Result + subscribeOnRefreshRateChanged(std::function&& notification) = 0; + + virtual Result resolution() const = 0; + virtual Result + subscribeOnResolutionChanged(std::function&& notification) = 0; + + virtual Result unsubscribe(SubscriptionId id) = 0; + virtual void unsubscribeAll() = 0; + +}; // class IVideooutput + +#ifndef FIREBOLT_NO_METHOD_AVAILABILITY + +/// Enum of available methods in this module for runtime capability queries. +enum class MethodId +{ + resolution = 0, + hdcp = 1, + cecState = 2, + refreshRate = 3, + colorDepth = 4, + colorFormat = 5, + colorimetry = 6, + dynamicRange = 7, + quantizationRange = 8, +}; + +/// Check if a method is available in this SDK build. +/// Returns true if the method is compiled in, false otherwise. +/// All methods in this build are currently available (true). +inline bool isMethodAvailable(MethodId method) noexcept +{ + static constexpr bool kMethodAvailable[] = { + true, true, true, true, true, true, true, true, true, + }; + const int idx = static_cast(method); + if (idx < 0 || idx >= static_cast(sizeof(kMethodAvailable) / sizeof(kMethodAvailable[0]))) + { + return false; + } + return kMethodAvailable[idx]; +} + +/// Static array containing all available methods in this module. +/// Use for convenient enumeration: for (auto m : allMethods) { ... } +static constexpr std::array allMethods = { + MethodId::resolution, MethodId::hdcp, MethodId::cecState, + MethodId::refreshRate, MethodId::colorDepth, MethodId::colorFormat, + MethodId::colorimetry, MethodId::dynamicRange, MethodId::quantizationRange, +}; + +#endif // FIREBOLT_NO_METHOD_AVAILABILITY + +} // namespace Firebolt::Videooutput + +#endif // FIREBOLT_VIDEOOUTPUT_H diff --git a/lint.sh b/lint.sh index 552384d..3bfc41a 100755 --- a/lint.sh +++ b/lint.sh @@ -267,19 +267,28 @@ if [[ "$RUN_CLANG_TIDY" == true ]]; then clang_tidy_failed=0 total_files=${#source_files[@]} - index=0 - for f in "${source_files[@]}"; do - index=$((index + 1)) - echo "[lint][clang-tidy] ${index}/${total_files}: $f" - clang_tidy_cmd=(clang-tidy -p "$BUILD_DIR") - if [[ "$APPLY_FIXES" == true ]]; then - clang_tidy_cmd+=("-fix") - fi - clang_tidy_cmd+=("$f") - if ! "${clang_tidy_cmd[@]}"; then + NPROC=$(nproc 2>/dev/null || echo 4) + + if [[ "$APPLY_FIXES" == false ]] && command -v run-clang-tidy >/dev/null 2>&1; then + echo "[lint][clang-tidy] Running ${total_files} files in parallel (${NPROC} jobs)" + if ! run-clang-tidy -p "$BUILD_DIR" -j "$NPROC" "${source_files[@]}"; then clang_tidy_failed=1 fi - done + else + index=0 + for f in "${source_files[@]}"; do + index=$((index + 1)) + echo "[lint][clang-tidy] ${index}/${total_files}: $f" + clang_tidy_cmd=(clang-tidy -p "$BUILD_DIR") + if [[ "$APPLY_FIXES" == true ]]; then + clang_tidy_cmd+=("-fix") + fi + clang_tidy_cmd+=("$f") + if ! "${clang_tidy_cmd[@]}"; then + clang_tidy_failed=1 + fi + done + fi if [[ $clang_tidy_failed -ne 0 ]]; then echo "clang-tidy reported issues." >&2 diff --git a/src/firebolt.cpp b/src/firebolt.cpp index 0afad53..cde9fad 100644 --- a/src/firebolt.cpp +++ b/src/firebolt.cpp @@ -31,6 +31,7 @@ #include "presentation_impl.h" #include "stats_impl.h" #include "texttospeech_impl.h" +#include "videooutput_impl.h" #include namespace Firebolt @@ -51,7 +52,8 @@ class FireboltAccessorImpl : public IFireboltAccessor network_(Firebolt::Helpers::GetHelperInstance()), presentation_(Firebolt::Helpers::GetHelperInstance()), stats_(Firebolt::Helpers::GetHelperInstance()), - textToSpeech_(Firebolt::Helpers::GetHelperInstance()) + textToSpeech_(Firebolt::Helpers::GetHelperInstance()), + videooutput_(Firebolt::Helpers::GetHelperInstance()) { } @@ -86,6 +88,7 @@ class FireboltAccessorImpl : public IFireboltAccessor Stats::IStats& StatsInterface() override { return stats_; } TextToSpeech::ITextToSpeech& TextToSpeechInterface() override { return textToSpeech_; } Actions::IActions& ActionsInterface() override { return actions_; } + Videooutput::IVideooutput& VideooutputInterface() override { return videooutput_; } private: void unsubscribeAll() @@ -97,6 +100,7 @@ class FireboltAccessorImpl : public IFireboltAccessor network_.unsubscribeAll(); presentation_.unsubscribeAll(); textToSpeech_.unsubscribeAll(); + videooutput_.unsubscribeAll(); } private: @@ -113,6 +117,7 @@ class FireboltAccessorImpl : public IFireboltAccessor Presentation::PresentationImpl presentation_; Stats::StatsImpl stats_; TextToSpeech::TextToSpeechImpl textToSpeech_; + Videooutput::VideooutputImpl videooutput_; }; /* static */ IFireboltAccessor& IFireboltAccessor::Instance() diff --git a/src/json_types/videooutput.h b/src/json_types/videooutput.h new file mode 100644 index 0000000..188d319 --- /dev/null +++ b/src/json_types/videooutput.h @@ -0,0 +1,195 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +// +// ============================================================================ +// AUTO-GENERATED by firebolt-sdk-gen v0.1.0 — DO NOT EDIT +// ============================================================================ +#ifndef FIREBOLT_VIDEOOUTPUT_JSON_H +#define FIREBOLT_VIDEOOUTPUT_JSON_H + +#include "firebolt/videooutput.h" +#include +#include +#include + +namespace Firebolt::Videooutput +{ + +NLOHMANN_JSON_SERIALIZE_ENUM(CecStateValue, { + {CecStateValue::Active, "ACTIVE"}, + {CecStateValue::Inactive, "INACTIVE"}, + {CecStateValue::Unsupported, "UNSUPPORTED"}, + }) + +NLOHMANN_JSON_SERIALIZE_ENUM(ColorDepthValue, { + {ColorDepthValue::D0, "D0"}, + {ColorDepthValue::D10, "D10"}, + {ColorDepthValue::D12, "D12"}, + {ColorDepthValue::D8, "D8"}, + }) + +NLOHMANN_JSON_SERIALIZE_ENUM(ColorFormatValue, { + {ColorFormatValue::None, "NONE"}, + {ColorFormatValue::Rgb444, "RGB444"}, + {ColorFormatValue::Ycbcr420, "YCBCR420"}, + {ColorFormatValue::Ycbcr422, "YCBCR422"}, + {ColorFormatValue::Ycbcr444, "YCBCR444"}, + }) + +NLOHMANN_JSON_SERIALIZE_ENUM(DynamicRangeValue, { + {DynamicRangeValue::DolbyVision, "DOLBY_VISION"}, + {DynamicRangeValue::Hdr10, "HDR10"}, + {DynamicRangeValue::Hdr10plus, "HDR10PLUS"}, + {DynamicRangeValue::Hlg, "HLG"}, + {DynamicRangeValue::None, "NONE"}, + {DynamicRangeValue::Sdr, "SDR"}, + }) + +NLOHMANN_JSON_SERIALIZE_ENUM(HdcpState, { + {HdcpState::Direct, "DIRECT"}, + {HdcpState::Hdcp14, "HDCP14"}, + {HdcpState::Hdcp22, "HDCP22"}, + {HdcpState::None, "NONE"}, + }) + +NLOHMANN_JSON_SERIALIZE_ENUM(OutputColorimetry, { + {OutputColorimetry::Bt2020rgb, "BT2020RGB"}, + {OutputColorimetry::Bt2020ycc, "BT2020YCC"}, + {OutputColorimetry::Bt709, "BT709"}, + {OutputColorimetry::None, "NONE"}, + {OutputColorimetry::Oprgb, "OPRGB"}, + }) + +NLOHMANN_JSON_SERIALIZE_ENUM(QuantizationRangeValue, { + {QuantizationRangeValue::Full, "FULL"}, + {QuantizationRangeValue::Limited, "LIMITED"}, + {QuantizationRangeValue::None, "NONE"}, + }) + +NLOHMANN_JSON_SERIALIZE_ENUM(RefreshRateValue, { + {RefreshRateValue::R0, "R0"}, + {RefreshRateValue::R23976, "R23_976"}, + {RefreshRateValue::R24, "R24"}, + {RefreshRateValue::R25, "R25"}, + {RefreshRateValue::R2997, "R29_97"}, + {RefreshRateValue::R30, "R30"}, + {RefreshRateValue::R50, "R50"}, + {RefreshRateValue::R5994, "R59_94"}, + {RefreshRateValue::R60, "R60"}, + }) + +namespace JsonData +{ + +inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::CecStateValue> CecStateValueEnum({ + {"ACTIVE", ::Firebolt::Videooutput::CecStateValue::Active}, + {"INACTIVE", ::Firebolt::Videooutput::CecStateValue::Inactive}, + {"UNSUPPORTED", ::Firebolt::Videooutput::CecStateValue::Unsupported}, +}); + +inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::ColorDepthValue> ColorDepthValueEnum({ + {"D0", ::Firebolt::Videooutput::ColorDepthValue::D0}, + {"D10", ::Firebolt::Videooutput::ColorDepthValue::D10}, + {"D12", ::Firebolt::Videooutput::ColorDepthValue::D12}, + {"D8", ::Firebolt::Videooutput::ColorDepthValue::D8}, +}); + +inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::ColorFormatValue> ColorFormatValueEnum({ + {"NONE", ::Firebolt::Videooutput::ColorFormatValue::None}, + {"RGB444", ::Firebolt::Videooutput::ColorFormatValue::Rgb444}, + {"YCBCR420", ::Firebolt::Videooutput::ColorFormatValue::Ycbcr420}, + {"YCBCR422", ::Firebolt::Videooutput::ColorFormatValue::Ycbcr422}, + {"YCBCR444", ::Firebolt::Videooutput::ColorFormatValue::Ycbcr444}, +}); + +inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::DynamicRangeValue> DynamicRangeValueEnum({ + {"DOLBY_VISION", ::Firebolt::Videooutput::DynamicRangeValue::DolbyVision}, + {"HDR10", ::Firebolt::Videooutput::DynamicRangeValue::Hdr10}, + {"HDR10PLUS", ::Firebolt::Videooutput::DynamicRangeValue::Hdr10plus}, + {"HLG", ::Firebolt::Videooutput::DynamicRangeValue::Hlg}, + {"NONE", ::Firebolt::Videooutput::DynamicRangeValue::None}, + {"SDR", ::Firebolt::Videooutput::DynamicRangeValue::Sdr}, +}); + +inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::HdcpState> HdcpStateEnum({ + {"DIRECT", ::Firebolt::Videooutput::HdcpState::Direct}, + {"HDCP14", ::Firebolt::Videooutput::HdcpState::Hdcp14}, + {"HDCP22", ::Firebolt::Videooutput::HdcpState::Hdcp22}, + {"NONE", ::Firebolt::Videooutput::HdcpState::None}, +}); + +inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::OutputColorimetry> OutputColorimetryEnum({ + {"BT2020RGB", ::Firebolt::Videooutput::OutputColorimetry::Bt2020rgb}, + {"BT2020YCC", ::Firebolt::Videooutput::OutputColorimetry::Bt2020ycc}, + {"BT709", ::Firebolt::Videooutput::OutputColorimetry::Bt709}, + {"NONE", ::Firebolt::Videooutput::OutputColorimetry::None}, + {"OPRGB", ::Firebolt::Videooutput::OutputColorimetry::Oprgb}, +}); + +inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::QuantizationRangeValue> QuantizationRangeValueEnum({ + {"FULL", ::Firebolt::Videooutput::QuantizationRangeValue::Full}, + {"LIMITED", ::Firebolt::Videooutput::QuantizationRangeValue::Limited}, + {"NONE", ::Firebolt::Videooutput::QuantizationRangeValue::None}, +}); + +inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::RefreshRateValue> RefreshRateValueEnum({ + {"R0", ::Firebolt::Videooutput::RefreshRateValue::R0}, + {"R23_976", ::Firebolt::Videooutput::RefreshRateValue::R23976}, + {"R24", ::Firebolt::Videooutput::RefreshRateValue::R24}, + {"R25", ::Firebolt::Videooutput::RefreshRateValue::R25}, + {"R29_97", ::Firebolt::Videooutput::RefreshRateValue::R2997}, + {"R30", ::Firebolt::Videooutput::RefreshRateValue::R30}, + {"R50", ::Firebolt::Videooutput::RefreshRateValue::R50}, + {"R59_94", ::Firebolt::Videooutput::RefreshRateValue::R5994}, + {"R60", ::Firebolt::Videooutput::RefreshRateValue::R60}, +}); + +class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Videooutput::VideoOutputResolution> +{ +public: + void fromJson(const nlohmann::json& json) override + { + if (!checkRequiredFields(json, {"height", "width"})) + { + throw std::invalid_argument("Missing required fields in JSON"); + } + height_ = json["height"].get(); + width_ = json["width"].get(); + } + ::Firebolt::Videooutput::VideoOutputResolution value() const override + { + return ::Firebolt::Videooutput::VideoOutputResolution{height_, width_}; + } + +private: + uint32_t height_{}; + uint32_t width_{}; +}; + +} // namespace JsonData + +inline void to_json(nlohmann::json& j, const VideoOutputResolution& v) +{ + j = nlohmann::json::object(); + j["height"] = v.height; + j["width"] = v.width; +} + +} // namespace Firebolt::Videooutput + +#endif // FIREBOLT_VIDEOOUTPUT_JSON_H diff --git a/src/videooutput_impl.cpp b/src/videooutput_impl.cpp new file mode 100644 index 0000000..42786ba --- /dev/null +++ b/src/videooutput_impl.cpp @@ -0,0 +1,115 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +// +// ============================================================================ +// AUTO-GENERATED by firebolt-sdk-gen v0.1.0 — DO NOT EDIT +// ============================================================================ +#include "videooutput_impl.h" +#include "json_types/videooutput.h" +#include +#include +#include + +namespace Firebolt::Videooutput +{ +VideooutputImpl::VideooutputImpl(Firebolt::Helpers::IHelper& helper) + : helper_(helper), + subscriptionManager_(helper, this) +{ +} + +Result VideooutputImpl::resolution() const +{ + return helper_.get("Videooutput.resolution"); +} +Result +VideooutputImpl::subscribeOnResolutionChanged(std::function&& notification) +{ + return subscriptionManager_.subscribe("Videooutput.onResolutionChanged", + std::move(notification)); +} + +Result VideooutputImpl::hdcp() const +{ + return helper_.get, HdcpState>("Videooutput.hdcp"); +} +Result VideooutputImpl::subscribeOnHdcpChanged(std::function&& notification) +{ + return subscriptionManager_.subscribe>("Videooutput.onHdcpChanged", + std::move(notification)); +} + +Result VideooutputImpl::cecState() const +{ + return helper_.get, CecStateValue>("Videooutput.cecState"); +} +Result VideooutputImpl::subscribeOnCecStateChanged(std::function&& notification) +{ + return subscriptionManager_.subscribe>("Videooutput.onCecStateChanged", + std::move(notification)); +} + +Result VideooutputImpl::refreshRate() const +{ + return helper_.get, RefreshRateValue>("Videooutput.refreshRate"); +} +Result +VideooutputImpl::subscribeOnRefreshRateChanged(std::function&& notification) +{ + return subscriptionManager_ + .subscribe>("Videooutput.onRefreshRateChanged", + std::move(notification)); +} + +Result VideooutputImpl::colorDepth() const +{ + return helper_.get, ColorDepthValue>("Videooutput.colorDepth"); +} + +Result VideooutputImpl::colorFormat() const +{ + return helper_.get, ColorFormatValue>("Videooutput.colorFormat"); +} + +Result VideooutputImpl::colorimetry() const +{ + return helper_.get, OutputColorimetry>("Videooutput.colorimetry"); +} + +Result VideooutputImpl::dynamicRange() const +{ + return helper_.get, DynamicRangeValue>("Videooutput.dynamicRange"); +} + +Result VideooutputImpl::quantizationRange() const +{ + return helper_.get, QuantizationRangeValue>( + "Videooutput.quantizationRange"); +} + +Result VideooutputImpl::unsubscribe(SubscriptionId id) +{ + return subscriptionManager_.unsubscribe(id); +} + +void VideooutputImpl::unsubscribeAll() +{ + subscriptionManager_.unsubscribeAll(); +} + +} // namespace Firebolt::Videooutput diff --git a/src/videooutput_impl.h b/src/videooutput_impl.h new file mode 100644 index 0000000..308897f --- /dev/null +++ b/src/videooutput_impl.h @@ -0,0 +1,73 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +// +// ============================================================================ +// AUTO-GENERATED by firebolt-sdk-gen v0.1.0 — DO NOT EDIT +// ============================================================================ +#ifndef FIREBOLT_VIDEOOUTPUT_IMPL_H +#define FIREBOLT_VIDEOOUTPUT_IMPL_H + +#include "firebolt/videooutput.h" +#include + +namespace Firebolt::Videooutput +{ + +class VideooutputImpl : public IVideooutput +{ +public: + explicit VideooutputImpl(Firebolt::Helpers::IHelper& helper); + VideooutputImpl(const VideooutputImpl&) = delete; + VideooutputImpl& operator=(const VideooutputImpl&) = delete; + ~VideooutputImpl() override = default; + + Result resolution() const override; + Result + subscribeOnResolutionChanged(std::function&& notification) override; + + Result hdcp() const override; + Result subscribeOnHdcpChanged(std::function&& notification) override; + + Result cecState() const override; + Result subscribeOnCecStateChanged(std::function&& notification) override; + + Result refreshRate() const override; + Result + subscribeOnRefreshRateChanged(std::function&& notification) override; + + Result colorDepth() const override; + + Result colorFormat() const override; + + Result colorimetry() const override; + + Result dynamicRange() const override; + + Result quantizationRange() const override; + + Result unsubscribe(SubscriptionId id) override; + void unsubscribeAll() override; + +private: + Firebolt::Helpers::IHelper& helper_; + Firebolt::Helpers::SubscriptionManager subscriptionManager_; +}; + +} // namespace Firebolt::Videooutput + +#endif // FIREBOLT_VIDEOOUTPUT_IMPL_H diff --git a/test/api_test_app/apis/actionsDemo.cpp b/test/api_test_app/apis/actionsDemo.cpp index bb1fd26..31b982f 100644 --- a/test/api_test_app/apis/actionsDemo.cpp +++ b/test/api_test_app/apis/actionsDemo.cpp @@ -44,8 +44,8 @@ void ActionsDemo::runOption(const std::string& method) auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().intent(); if (succeed(r)) { - std::cout << "Current Intent - action: " << r->intent.action - << ", source: " << (r->intent.context && r->intent.context->source ? *r->intent.context->source : "(none)") + std::cout << "Current Intent - action: " << r->intent.action << ", source: " + << (r->intent.context && r->intent.context->source ? *r->intent.context->source : "(none)") << ", intentId: " << r->intentId << std::endl; } } @@ -70,11 +70,9 @@ void ActionsDemo::runOption(const std::string& method) { auto callback = [&](const Intent& payload) { - std::cout << "Intent received - action: " << payload.intent.action - << ", source: " - << (payload.intent.context && payload.intent.context->source - ? *payload.intent.context->source - : "(none)") + std::cout << "Intent received - action: " << payload.intent.action << ", source: " + << (payload.intent.context && payload.intent.context->source ? *payload.intent.context->source + : "(none)") << ", intentId: " << payload.intentId << std::endl; }; auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().subscribeOnIntent(std::move(callback)); diff --git a/test/component/videooutputGeneratedTest.cpp b/test/component/videooutputGeneratedTest.cpp new file mode 100644 index 0000000..fef7d12 --- /dev/null +++ b/test/component/videooutputGeneratedTest.cpp @@ -0,0 +1,36 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "firebolt/videooutput.h" +#include + +TEST(VideooutputGeneratedCTest, InterfaceSurfaceHasresolution) +{ + using Interface = Firebolt::Videooutput::IVideooutput; + auto ptr = &Interface::resolution; + (void)ptr; + SUCCEED(); +} + +TEST(VideooutputGeneratedCTest, InterfaceSurfaceHascolorDepth) +{ + using Interface = Firebolt::Videooutput::IVideooutput; + auto ptr = &Interface::colorDepth; + (void)ptr; + SUCCEED(); +} diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp index 236cf91..12a9089 100644 --- a/test/unit/actionsTest.cpp +++ b/test/unit/actionsTest.cpp @@ -65,7 +65,7 @@ TEST_F(ActionsUTest, Start) .WillOnce(Invoke([&](const std::string& /*methodName*/, const nlohmann::json& /*parameters*/) { return Firebolt::Result{Firebolt::Error::None}; })); - auto result = actionsImpl_.start( - Firebolt::Actions::IntentData{"pre-load", Firebolt::Actions::IntentContext{{"system"}}}); + auto result = + actionsImpl_.start(Firebolt::Actions::IntentData{"pre-load", Firebolt::Actions::IntentContext{{"system"}}}); ASSERT_TRUE(result) << "ActionsImpl::start() returned an error"; } diff --git a/test/unit/videooutputGeneratedTest.cpp b/test/unit/videooutputGeneratedTest.cpp new file mode 100644 index 0000000..f11463e --- /dev/null +++ b/test/unit/videooutputGeneratedTest.cpp @@ -0,0 +1,61 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mock_helper.h" +#include "videooutput_impl.h" +#include + +class VideooutputGeneratedUTest : public ::testing::Test +{ +protected: + ::testing::NiceMock mockHelper; + Firebolt::Videooutput::VideooutputImpl impl{mockHelper}; +}; + +TEST_F(VideooutputGeneratedUTest, Constructs) +{ + SUCCEED(); +} + +TEST_F(VideooutputGeneratedUTest, UnsubscribeForwardsToHelper) +{ + EXPECT_CALL(mockHelper, unsubscribe(7)).WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::None})); + + auto result = impl.unsubscribe(7); + ASSERT_TRUE(result) << "unsubscribe should return success when helper succeeds"; +} + +TEST_F(VideooutputGeneratedUTest, ForwardsresolutionTransportErrors) +{ + EXPECT_CALL(mockHelper, getJson("Videooutput.resolution", ::testing::_)) + .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& /*params*/) + { return Firebolt::Result{Firebolt::Error::General}; })); + + auto result = impl.resolution(); + EXPECT_FALSE(result) << "Expected error propagation when helper getJson fails"; +} + +TEST_F(VideooutputGeneratedUTest, ForwardscolorDepthTransportErrors) +{ + EXPECT_CALL(mockHelper, getJson("Videooutput.colorDepth", ::testing::_)) + .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& /*params*/) + { return Firebolt::Result{Firebolt::Error::General}; })); + + auto result = impl.colorDepth(); + EXPECT_FALSE(result) << "Expected error propagation when helper getJson fails"; +} From 1b42981141ea15e3f4133db05f5ac38d7c1c29b0 Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Thu, 6 Aug 2026 09:48:20 -0400 Subject: [PATCH 16/39] Restore develop firebolt-open-rpc.json (#96) * Restore develop firebolt-open-rpc.json * Fix firebolt-open-rpc.json * Add fmt.sh and clean up lint.sh clang-format tooling --- docs/openrpc/the-spec/firebolt-open-rpc.json | 243 ++++++++++++++++--- fmt.sh | 44 ++++ include/firebolt/actions.h | 3 +- lint.sh | 103 +------- test/api_test_app/apis/actionsDemo.cpp | 12 +- test/unit/actionsTest.cpp | 4 +- 6 files changed, 268 insertions(+), 141 deletions(-) create mode 100755 fmt.sh diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 98fdd71..743e3fe 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -76,13 +76,19 @@ "properties": { "intent": { "type": "object", - "required": ["action"], + "required": [ + "action" + ], "properties": { - "action": { "type": "string" }, + "action": { + "type": "string" + }, "context": { "type": "object", "properties": { - "source": { "type": "string" } + "source": { + "type": "string" + } } } } @@ -148,13 +154,19 @@ "properties": { "intent": { "type": "object", - "required": ["action"], + "required": [ + "action" + ], "properties": { - "action": { "type": "string" }, + "action": { + "type": "string" + }, "context": { "type": "object", "properties": { - "source": { "type": "string" } + "source": { + "type": "string" + } } } } @@ -208,13 +220,19 @@ "required": true, "schema": { "type": "object", - "required": ["action"], + "required": [ + "action" + ], "properties": { - "action": { "type": "string" }, + "action": { + "type": "string" + }, "context": { "type": "object", "properties": { - "source": { "type": "string" } + "source": { + "type": "string" + } } } } @@ -655,6 +673,39 @@ } ] }, + { + "name": "Device.dolbyAtmosExperienceAvailable", + "summary": "Returns whether Dolby Atmos experience is available on the device", + "params": [], + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "dolbyAtmosExperienceAvailable", + "summary": "Whether Dolby Atmos experience is available on the device", + "schema": { + "type": "boolean" + } + }, + "examples": [ + { + "name": "Getting Dolby Atmos experience availability", + "params": [], + "result": { + "name": "Default Result", + "value": true + } + } + ] + }, { "name": "Discovery.watched", "summary": "Notify the platform that content was partially or completely watched", @@ -775,7 +826,7 @@ }, { "name": "Discovery.watchedV2", - "summary": "Notify the platform that content was partially or completely watched, returns whether the notification was accepted", + "summary": "Notify the platform that content was partially or completely watched", "tags": [ { "name": "polymorphic-reducer" @@ -829,9 +880,8 @@ ], "result": { "name": "result", - "summary": "Whether the platform accepted the watched notification", "schema": { - "type": "boolean" + "type": "null" } }, "examples": [ @@ -857,7 +907,7 @@ ], "result": { "name": "result", - "value": true + "value": null } }, { @@ -886,7 +936,7 @@ ], "result": { "name": "result", - "value": true + "value": null } } ] @@ -1215,6 +1265,39 @@ } ] }, + { + "name": "Localization.timeZone", + "tags": [ + { + "name": "property:readonly" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:time-zone" + ] + } + ], + "summary": "Get the IANA timezone of the device.", + "params": [], + "result": { + "name": "timeZone", + "summary": "The device timezone.", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Default example", + "params": [], + "result": { + "name": "Default Result", + "value": "America/New_York" + } + } + ] + }, { "name": "Metrics.ready", "tags": [ @@ -2356,7 +2439,7 @@ }, { "name": "Stats.memoryUsage", - "summary": "Returns information about container memory usage, in units of 1024 bytes.", + "summary": "Returns information about container memory usage in bytes.", "tags": [ { "name": "capabilities", @@ -2380,10 +2463,10 @@ "name": "value", "description": "The memory usage information", "value": { - "userMemoryUsedKiB": 123456, - "userMemoryLimitKiB": 789012, - "gpuMemoryUsedKiB": 345678, - "gpuMemoryLimitKiB": 901234 + "userMemoryUsed": 126418944, + "userMemoryLimit": 807948288, + "gpuMemoryUsed": 353974272, + "gpuMemoryLimit": 922863616 } } } @@ -3373,6 +3456,52 @@ } } }, + { + "name": "Device.onDolbyAtmosExperienceAvailableChanged", + "summary": "Returns whether Dolby Atmos experience is available on the device", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "tags": [ + { + "name": "event", + "x-notifier": "Device.onDolbyAtmosExperienceAvailableChanged", + "x-subscriber-for": "Device.dolbyAtmosExperienceAvailable" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "examples": [ + { + "name": "Getting Dolby Atmos experience availability", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, { "name": "Localization.onCountryChanged", "tags": [ @@ -3524,6 +3653,52 @@ } } }, + { + "name": "Localization.onTimeZoneChanged", + "tags": [ + { + "name": "event", + "x-notifier": "Localization.onTimeZoneChanged", + "x-subscriber-for": "Localization.timeZone" + }, + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:localization:time-zone" + ] + } + ], + "summary": "Get the IANA timezone of the device.", + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + } + }, { "name": "Network.onConnectedChanged", "summary": "Returns whether the device currently has a usable network connection.", @@ -3777,28 +3952,32 @@ "type": "object", "description": "Describes current and maximum memory usage of the container.", "properties": { - "userMemoryUsedKiB": { + "userMemoryUsed": { "type": "integer", - "description": "User memory currently used in 1024 bytes." + "description": "User memory currently used, in bytes.", + "minimum": 0 }, - "userMemoryLimitKiB": { + "userMemoryLimit": { "type": "integer", - "description": "Maximum user memory available in 1024 bytes." + "description": "Maximum user memory available, in bytes.", + "minimum": 0 }, - "gpuMemoryUsedKiB": { + "gpuMemoryUsed": { "type": "integer", - "description": "GPU memory currently used in 1024 bytes." + "description": "GPU memory currently used, in bytes.", + "minimum": 0 }, - "gpuMemoryLimitKiB": { + "gpuMemoryLimit": { "type": "integer", - "description": "Maximum GPU memory available in 1024 bytes." + "description": "Maximum GPU memory available, in bytes.", + "minimum": 0 } }, "required": [ - "userMemoryUsedKiB", - "userMemoryLimitKiB", - "gpuMemoryUsedKiB", - "gpuMemoryLimitKiB" + "userMemoryUsed", + "userMemoryLimit", + "gpuMemoryUsed", + "gpuMemoryLimit" ] }, "TTSEnabled": { @@ -4168,4 +4347,4 @@ } } } -} \ No newline at end of file +} diff --git a/fmt.sh b/fmt.sh new file mode 100755 index 0000000..b3ec468 --- /dev/null +++ b/fmt.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Check (default) or fix clang-format. Like `cargo fmt [--check]`. +# ./fmt.sh — check only (exit 1 if violations) +# ./fmt.sh --fix — reformat in place +# +# Uses Docker by default (matches CI exactly). If Docker is unavailable or +# SKIP_DOCKER=1 is set, falls back to the locally installed clang-format. +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Minimal image — only clang-format, matches the Ubuntu version used in CI +IMAGE="firebolt-cpp-client-fmt:local" + +use_docker=true +if [[ "${SKIP_DOCKER:-0}" == "1" ]] || ! command -v docker &>/dev/null; then + use_docker=false +fi + +if [[ "$use_docker" == true ]]; then + if ! docker image inspect "$IMAGE" &>/dev/null; then + echo "Building clang-format Docker image (one-time, ~30s)..." + docker build -t "$IMAGE" - <<'DOCKERFILE' +FROM ubuntu:24.04 +RUN apt-get update && apt-get install -y --no-install-recommends clang-format git && rm -rf /var/lib/apt/lists/* +WORKDIR /workspace +DOCKERFILE + fi + RUN="docker run --rm --user $(id -u):$(id -g) -v $SCRIPT_DIR:/workspace $IMAGE bash -c" +else + if ! command -v clang-format &>/dev/null; then + echo "clang-format not found. Install it or run without SKIP_DOCKER=1." >&2 + exit 1 + fi + echo "[fmt] Using local clang-format ($(clang-format --version))" + RUN="bash -c" +fi + +if [[ "${1:-}" == "--fix" ]]; then + $RUN "set -e && git ls-files -- '*.cpp' '*.h' | xargs clang-format -i" + echo "Done. Files reformatted." +else + $RUN "set -e && git ls-files -- '*.cpp' '*.h' | xargs clang-format --dry-run --Werror" + echo "Formatting OK." +fi diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index dd93d7e..7d28222 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -64,8 +64,7 @@ class IActions virtual Result unsubscribe(SubscriptionId id) = 0; virtual void unsubscribeAll() = 0; - virtual Result start(const IntentData& intent, - std::optional handlerAppId = std::nullopt) const = 0; + virtual Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const = 0; }; // class IActions diff --git a/lint.sh b/lint.sh index 552384d..a7dc561 100755 --- a/lint.sh +++ b/lint.sh @@ -24,30 +24,23 @@ NO_BUILD=false CLEAN=false RUN_CLANG_TIDY=true RUN_CPPCHECK=true -RUN_CLANG_FORMAT=true APPLY_FIXES=false -FORMAT_FIX=false CLANG_TIDY_PATHS=(src include test/unit test/component) -CLANG_FORMAT_PATHS=(src include test) usage() { cat < Build directory containing compile_commands.json (default: build-dev) --tidy-path

Add path for clang-tidy scan (repeatable) - --format-path

Add path for clang-format scan (repeatable) --fix Apply clang-tidy fix-its (clang-tidy only) - --format-fix Apply clang-format fixes in-place - --format-only Run clang-format only - --no-format Skip clang-format checks --tidy-only Run clang-tidy only --cppcheck-only Run cppcheck only --help Show this help @@ -56,10 +49,7 @@ Examples: ./lint.sh ./lint.sh --tidy-only ./lint.sh --tidy-only --fix - ./lint.sh --format-only - ./lint.sh --format-fix ./lint.sh --tidy-path test/api_test_app - ./lint.sh --format-path include/firebolt ./lint.sh --no-build --build-dir build-dev EOF } @@ -90,37 +80,14 @@ while [[ $# -gt 0 ]]; do CLANG_TIDY_PATHS+=("${2:-}") shift ;; - --format-path) - if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then - echo "Missing value for --format-path" >&2 - usage - exit 1 - fi - CLANG_FORMAT_PATHS+=("${2:-}") - shift - ;; --fix) APPLY_FIXES=true ;; - --format-fix) - FORMAT_FIX=true - RUN_CLANG_FORMAT=true - ;; - --format-only) - RUN_CLANG_FORMAT=true - RUN_CLANG_TIDY=false - RUN_CPPCHECK=false - ;; - --no-format) - RUN_CLANG_FORMAT=false - ;; --tidy-only) - RUN_CLANG_FORMAT=false RUN_CLANG_TIDY=true RUN_CPPCHECK=false ;; --cppcheck-only) - RUN_CLANG_FORMAT=false RUN_CLANG_TIDY=false RUN_CPPCHECK=true ;; @@ -144,8 +111,8 @@ if [[ "$RUN_CLANG_TIDY" == true && "$NO_BUILD" == false && "$BUILD_DIR" != "buil exit 1 fi -if [[ "$RUN_CLANG_FORMAT" == false && "$RUN_CLANG_TIDY" == false && "$RUN_CPPCHECK" == false ]]; then - echo "Nothing to run: clang-format, clang-tidy, and cppcheck are all disabled." >&2 +if [[ "$RUN_CLANG_TIDY" == false && "$RUN_CPPCHECK" == false ]]; then + echo "Nothing to run: clang-tidy and cppcheck are both disabled." >&2 exit 1 fi @@ -154,11 +121,6 @@ if [[ "$APPLY_FIXES" == true && "$RUN_CLANG_TIDY" == false ]]; then exit 1 fi -if [[ "$FORMAT_FIX" == true && "$RUN_CLANG_FORMAT" == false ]]; then - echo "--format-fix requires clang-format to be enabled (remove --no-format)." >&2 - exit 1 -fi - if [[ "$RUN_CLANG_TIDY" == true ]] && ! command -v clang-tidy >/dev/null 2>&1; then echo "clang-tidy not found. Install it (e.g. apt install clang-tidy)." >&2 exit 1 @@ -169,11 +131,6 @@ if [[ "$RUN_CPPCHECK" == true ]] && ! command -v cppcheck >/dev/null 2>&1; then exit 1 fi -if [[ "$RUN_CLANG_FORMAT" == true ]] && ! command -v clang-format >/dev/null 2>&1; then - echo "clang-format not found. Install it (e.g. apt install clang-format)." >&2 - exit 1 -fi - if [[ "$CLEAN" == true ]]; then rm -rf "$BUILD_DIR" fi @@ -187,56 +144,6 @@ if [[ "$RUN_CLANG_TIDY" == true && ! -f "$BUILD_DIR/compile_commands.json" ]]; t exit 1 fi -if [[ "$RUN_CLANG_FORMAT" == true ]]; then - if [[ "$FORMAT_FIX" == true ]]; then - echo "[lint] Running clang-format with fixes enabled" - else - echo "[lint] Running clang-format check" - fi - - format_paths=() - for p in "${CLANG_FORMAT_PATHS[@]}"; do - if [[ -e "$p" ]]; then - format_paths+=("$p") - fi - done - - if [[ ${#format_paths[@]} -eq 0 ]]; then - echo "No valid clang-format paths found." >&2 - exit 1 - fi - - mapfile -t format_files < <( - find "${format_paths[@]}" -type f \( -name "*.h" -o -name "*.hh" -o -name "*.hpp" -o -name "*.hxx" -o -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.cxx" \) | sort - ) - - if [[ ${#format_files[@]} -eq 0 ]]; then - echo "No C/C++ files found for clang-format." >&2 - exit 1 - fi - - clang_format_failed=0 - total_format_files=${#format_files[@]} - format_index=0 - for f in "${format_files[@]}"; do - format_index=$((format_index + 1)) - echo "[lint][clang-format] ${format_index}/${total_format_files}: $f" - if [[ "$FORMAT_FIX" == true ]]; then - clang-format -i "$f" - else - if ! clang-format --dry-run --Werror "$f"; then - clang_format_failed=1 - fi - fi - done - - if [[ "$FORMAT_FIX" == false && $clang_format_failed -ne 0 ]]; then - echo "clang-format reported issues." >&2 - echo "Run ./lint.sh --format-fix to apply formatting automatically." >&2 - exit 1 - fi -fi - if [[ "$RUN_CLANG_TIDY" == true ]]; then if [[ "$APPLY_FIXES" == true ]]; then echo "[lint] Running clang-tidy with fixes enabled" diff --git a/test/api_test_app/apis/actionsDemo.cpp b/test/api_test_app/apis/actionsDemo.cpp index bb1fd26..31b982f 100644 --- a/test/api_test_app/apis/actionsDemo.cpp +++ b/test/api_test_app/apis/actionsDemo.cpp @@ -44,8 +44,8 @@ void ActionsDemo::runOption(const std::string& method) auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().intent(); if (succeed(r)) { - std::cout << "Current Intent - action: " << r->intent.action - << ", source: " << (r->intent.context && r->intent.context->source ? *r->intent.context->source : "(none)") + std::cout << "Current Intent - action: " << r->intent.action << ", source: " + << (r->intent.context && r->intent.context->source ? *r->intent.context->source : "(none)") << ", intentId: " << r->intentId << std::endl; } } @@ -70,11 +70,9 @@ void ActionsDemo::runOption(const std::string& method) { auto callback = [&](const Intent& payload) { - std::cout << "Intent received - action: " << payload.intent.action - << ", source: " - << (payload.intent.context && payload.intent.context->source - ? *payload.intent.context->source - : "(none)") + std::cout << "Intent received - action: " << payload.intent.action << ", source: " + << (payload.intent.context && payload.intent.context->source ? *payload.intent.context->source + : "(none)") << ", intentId: " << payload.intentId << std::endl; }; auto r = Firebolt::IFireboltAccessor::Instance().ActionsInterface().subscribeOnIntent(std::move(callback)); diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp index 236cf91..12a9089 100644 --- a/test/unit/actionsTest.cpp +++ b/test/unit/actionsTest.cpp @@ -65,7 +65,7 @@ TEST_F(ActionsUTest, Start) .WillOnce(Invoke([&](const std::string& /*methodName*/, const nlohmann::json& /*parameters*/) { return Firebolt::Result{Firebolt::Error::None}; })); - auto result = actionsImpl_.start( - Firebolt::Actions::IntentData{"pre-load", Firebolt::Actions::IntentContext{{"system"}}}); + auto result = + actionsImpl_.start(Firebolt::Actions::IntentData{"pre-load", Firebolt::Actions::IntentContext{{"system"}}}); ASSERT_TRUE(result) << "ActionsImpl::start() returned an error"; } From 43cd062552f6a4fa14b35d140aa0481da6709758 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Thu, 6 Aug 2026 09:23:29 -0700 Subject: [PATCH 17/39] RDKEMW-14869: correcting namespace casing for VideoOutput --- include/firebolt/accessibility.h | 8 +- include/firebolt/actions.h | 4 +- include/firebolt/advertising.h | 2 +- include/firebolt/device.h | 14 +-- include/firebolt/discovery.h | 4 +- include/firebolt/display.h | 6 +- include/firebolt/firebolt.h | 2 +- include/firebolt/lifecycle.h | 4 +- include/firebolt/localization.h | 8 +- include/firebolt/metrics.h | 46 ++++----- include/firebolt/network.h | 2 +- include/firebolt/presentation.h | 2 +- include/firebolt/stats.h | 2 +- include/firebolt/texttospeech.h | 12 +-- include/firebolt/videooutput.h | 28 +++--- src/accessibility_impl.h | 12 +-- src/actions_impl.h | 4 +- src/advertising_impl.h | 2 +- src/device_impl.h | 14 +-- src/discovery_impl.h | 4 +- src/display_impl.h | 6 +- src/firebolt.cpp | 8 +- src/json_types/accessibility.h | 4 +- src/json_types/actions.h | 14 +-- src/json_types/advertising.h | 2 +- src/json_types/device.h | 4 +- src/json_types/display.h | 2 +- src/json_types/lifecycle.h | 4 +- src/json_types/stats.h | 2 +- src/json_types/texttospeech.h | 10 +- src/json_types/videooutput.h | 104 ++++++++++---------- src/lifecycle_impl.cpp | 2 +- src/lifecycle_impl.h | 10 +- src/localization_impl.h | 8 +- src/metrics_impl.h | 46 ++++----- src/network_impl.h | 2 +- src/presentation_impl.h | 6 +- src/stats_impl.cpp | 2 +- src/stats_impl.h | 2 +- src/texttospeech_impl.h | 12 +-- src/videooutput_impl.cpp | 65 ++++++------ src/videooutput_impl.h | 35 +++---- test/component/accessibilityTest.cpp | 8 +- test/component/actionsGeneratedTest.cpp | 4 +- test/component/deviceTest.cpp | 8 +- test/component/discoveryTest.cpp | 4 +- test/component/lifecycleTest.cpp | 10 +- test/component/metricsTest.cpp | 2 +- test/component/networkTest.cpp | 2 +- test/component/presentationTest.cpp | 2 +- test/component/videooutputGeneratedTest.cpp | 4 +- test/unit/actionsTest.cpp | 4 +- test/unit/discoveryTest.cpp | 12 +-- test/unit/metricsTest.cpp | 2 +- test/unit/videooutputGeneratedTest.cpp | 6 +- 55 files changed, 299 insertions(+), 299 deletions(-) diff --git a/include/firebolt/accessibility.h b/include/firebolt/accessibility.h index 810151e..8417591 100644 --- a/include/firebolt/accessibility.h +++ b/include/firebolt/accessibility.h @@ -49,7 +49,7 @@ class IAccessibility * * @retval The audio description setting state or error */ - virtual Result audioDescription() const = 0; + [[nodiscard]] virtual Result audioDescription() const = 0; /** * @brief Subscribe to audio description setting changes @@ -63,7 +63,7 @@ class IAccessibility * * @retval ClosedCaptionsSettings or error */ - virtual Result closedCaptionsSettings() const = 0; + [[nodiscard]] virtual Result closedCaptionsSettings() const = 0; virtual Result subscribeOnClosedCaptionsSettingsChanged(std::function&& notification) = 0; @@ -73,7 +73,7 @@ class IAccessibility * * @retval The high contrast UI setting or error */ - virtual Result highContrastUI() const = 0; + [[nodiscard]] virtual Result highContrastUI() const = 0; virtual Result subscribeOnHighContrastUIChanged(std::function&& notification) = 0; @@ -81,7 +81,7 @@ class IAccessibility * @brief Returns voice guidance settings: enabled, rate, and verbosity * @retval VoiceGuidanceSettings or error */ - virtual Result voiceGuidanceSettings() const = 0; + [[nodiscard]] virtual Result voiceGuidanceSettings() const = 0; virtual Result subscribeOnVoiceGuidanceSettingsChanged(std::function&& notification) = 0; diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index 7d28222..68bb1f3 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -53,7 +53,7 @@ class IActions public: virtual ~IActions() = default; - virtual Result intent() const = 0; + [[nodiscard]] virtual Result intent() const = 0; virtual Result subscribeOnIntent(std::function&& notification) = 0; virtual Result subscribeOnIntentChanged(std::function&& notification) @@ -64,7 +64,7 @@ class IActions virtual Result unsubscribe(SubscriptionId id) = 0; virtual void unsubscribeAll() = 0; - virtual Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const = 0; + [[nodiscard]] virtual Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const = 0; }; // class IActions diff --git a/include/firebolt/advertising.h b/include/firebolt/advertising.h index 1a986a9..4a4ab54 100644 --- a/include/firebolt/advertising.h +++ b/include/firebolt/advertising.h @@ -56,6 +56,6 @@ class IAdvertising * @return Ifa struct or error * */ - virtual Result advertisingId() const = 0; + [[nodiscard]] virtual Result advertisingId() const = 0; }; } // namespace Firebolt::Advertising diff --git a/include/firebolt/device.h b/include/firebolt/device.h index 37ba5d1..458d951 100644 --- a/include/firebolt/device.h +++ b/include/firebolt/device.h @@ -58,42 +58,42 @@ class IDevice * * @retval The chipset id string or error */ - virtual Result chipsetId() const = 0; + [[nodiscard]] virtual Result chipsetId() const = 0; /** * @brief Get the class of the device * * @retval The class property or error */ - virtual Result deviceClass() const = 0; + [[nodiscard]] virtual Result deviceClass() const = 0; /** * @brief Returns the HDR standards that are supported by the attached TV or the integral display * * @retval The HDR format capabilities or error */ - virtual Result hdr() const = 0; + [[nodiscard]] virtual Result hdr() const = 0; /** * @brief Returns number of seconds since most recent device boot, including any time spent during deep sleep * * @retval The uptime in seconds or error */ - virtual Result timeInActiveState() const = 0; + [[nodiscard]] virtual Result timeInActiveState() const = 0; /** * @brief Returns a persistent unique UUID for the current app and device. The UUID is reset when the app or device is reset * * @retval The uid string or error */ - virtual Result uid() const = 0; + [[nodiscard]] virtual Result uid() const = 0; /** * @brief Returns number of seconds since most recent device boot, including any time spent during deep sleep * * @retval The uptime in seconds or error */ - virtual Result uptime() const = 0; + [[nodiscard]] virtual Result uptime() const = 0; /** * @brief Subscribe to HDR format changes @@ -122,7 +122,7 @@ class IDevice * * @retval True if Dolby Atmos experience is available, or error */ - virtual Result dolbyAtmosExperienceAvailable() const = 0; + [[nodiscard]] virtual Result dolbyAtmosExperienceAvailable() const = 0; /** * @brief Subscribe to Dolby Atmos experience availability changes diff --git a/include/firebolt/discovery.h b/include/firebolt/discovery.h index f17bedc..334ef39 100644 --- a/include/firebolt/discovery.h +++ b/include/firebolt/discovery.h @@ -45,7 +45,7 @@ class IDiscovery * Prefer watchedV2() for new integrations, which returns Result and omits the * redundant boolean payload. */ - virtual Result watched(const std::string& entityId, std::optional progress, + [[nodiscard]] virtual Result watched(const std::string& entityId, std::optional progress, std::optional completed, std::optional watchedOn, std::optional agePolicy) const = 0; @@ -62,7 +62,7 @@ class IDiscovery * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result watchedV2(const std::string& entityId, std::optional progress, + [[nodiscard]] virtual Result watchedV2(const std::string& entityId, std::optional progress, std::optional completed, std::optional watchedOn, std::optional agePolicy) const = 0; }; diff --git a/include/firebolt/display.h b/include/firebolt/display.h index ac723fe..ac4719d 100644 --- a/include/firebolt/display.h +++ b/include/firebolt/display.h @@ -41,7 +41,7 @@ class IDisplay * * @retval The class property or error */ - virtual Result edid() const = 0; + [[nodiscard]] virtual Result edid() const = 0; /** * @brief Returns the physical/native resolution of the connected or integral display, in pixels @@ -56,7 +56,7 @@ class IDisplay * * @retval The display resolution (width and height in pixels) or error */ - virtual Result maxResolution() const = 0; + [[nodiscard]] virtual Result maxResolution() const = 0; /** * @brief Returns the physical dimensions of the connected or integral display, in centimeters @@ -71,7 +71,7 @@ class IDisplay * * @retval The class property or error */ - virtual Result size() const = 0; + [[nodiscard]] virtual Result size() const = 0; }; } // namespace Firebolt::Display diff --git a/include/firebolt/firebolt.h b/include/firebolt/firebolt.h index fbcc122..1d1568d 100644 --- a/include/firebolt/firebolt.h +++ b/include/firebolt/firebolt.h @@ -171,6 +171,6 @@ class FIREBOLTCLIENT_EXPORT IFireboltAccessor */ virtual Actions::IActions& ActionsInterface() = 0; - virtual Videooutput::IVideooutput& VideooutputInterface() = 0; + virtual VideoOutput::IVideoOutput& VideoOutputInterface() = 0; }; } // namespace Firebolt diff --git a/include/firebolt/lifecycle.h b/include/firebolt/lifecycle.h index 94ed93e..3581cda 100644 --- a/include/firebolt/lifecycle.h +++ b/include/firebolt/lifecycle.h @@ -68,14 +68,14 @@ class ILifecycle * * @param[in] type The type of the close app is requesting */ - virtual Result close(const CloseType& type) const = 0; + [[nodiscard]] virtual Result close(const CloseType& type) const = 0; /** * @brief Get the current lifecycle state of the app * * @retval The current lifecycle state or error */ - virtual Result state() const = 0; + [[nodiscard]] virtual Result state() const = 0; /** * @brief Subscribe to lifecycle state changes diff --git a/include/firebolt/localization.h b/include/firebolt/localization.h index 5131a3e..6e33374 100644 --- a/include/firebolt/localization.h +++ b/include/firebolt/localization.h @@ -35,7 +35,7 @@ class ILocalization * * @retval The device country code or error */ - virtual Result country() const = 0; + [[nodiscard]] virtual Result country() const = 0; /** * @brief A list of zero or more languages in order of decreasing preference. Typically two languages are present. @@ -43,21 +43,21 @@ class ILocalization * * @retval The preferred audio languages or error */ - virtual Result> preferredAudioLanguages() const = 0; + [[nodiscard]] virtual Result> preferredAudioLanguages() const = 0; /** * @brief The presentation language of the device, in BCP 47, e.g. en-US * * @retval The preferred audio languages or error */ - virtual Result presentationLanguage() const = 0; + [[nodiscard]] virtual Result presentationLanguage() const = 0; /** * @brief Get the IANA timezone of the device. * * @retval The device timezone or error */ - virtual Result timeZone() const = 0; + [[nodiscard]] virtual Result timeZone() const = 0; /** * @brief Subscribe on the change of CountryChanged property diff --git a/include/firebolt/metrics.h b/include/firebolt/metrics.h index 6927674..b15d998 100644 --- a/include/firebolt/metrics.h +++ b/include/firebolt/metrics.h @@ -45,21 +45,21 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result ready() const = 0; + [[nodiscard]] virtual Result ready() const = 0; /** * @brief Logs a sign in event * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result signIn() const = 0; + [[nodiscard]] virtual Result signIn() const = 0; /** * @brief Logs a sign out event * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result signOut() const = 0; + [[nodiscard]] virtual Result signOut() const = 0; /** * @brief Informs the platform that your user has started content @@ -70,8 +70,8 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result startContent(const std::optional& entityId, - const std::optional agePolicy) const = 0; + [[nodiscard]] virtual Result startContent(const std::optional& entityId, + std::optional agePolicy) const = 0; /** * @brief Informs the platform that your user has stopped content @@ -82,8 +82,8 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result stopContent(const std::optional& entityId, - const std::optional agePolicy) const = 0; + [[nodiscard]] virtual Result stopContent(const std::optional& entityId, + std::optional agePolicy) const = 0; /** * @brief Informs the platform that your user has navigated to a page or view @@ -94,7 +94,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result page(const std::string& pageId, const std::optional& agePolicy) const = 0; + [[nodiscard]] virtual Result page(const std::string& pageId, const std::optional& agePolicy) const = 0; /** * @brief Informs the platform of an error that has occurred in your app @@ -109,8 +109,8 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result error(const ErrorType type, const std::string& code, const std::string& description, - const bool visible, const std::optional>& parameters, + [[nodiscard]] virtual Result error(ErrorType type, const std::string& code, const std::string& description, + bool visible, const std::optional>& parameters, const std::optional& agePolicy) const = 0; /** @@ -122,7 +122,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaLoadStart(const std::string& entityId, + [[nodiscard]] virtual Result mediaLoadStart(const std::string& entityId, const std::optional& agePolicy) const = 0; /** @@ -135,7 +135,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaPlaying(const std::string& entityId, + [[nodiscard]] virtual Result mediaPlaying(const std::string& entityId, const std::optional& agePolicy) const = 0; /** @@ -147,7 +147,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaPlay(const std::string& entityId, + [[nodiscard]] virtual Result mediaPlay(const std::string& entityId, const std::optional& agePolicy) const = 0; /** @@ -159,7 +159,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaPause(const std::string& entityId, + [[nodiscard]] virtual Result mediaPause(const std::string& entityId, const std::optional& agePolicy) const = 0; /** @@ -171,7 +171,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaWaiting(const std::string& entityId, + [[nodiscard]] virtual Result mediaWaiting(const std::string& entityId, const std::optional& agePolicy) const = 0; /** @@ -185,7 +185,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaSeeking(const std::string& entityId, const double target, + [[nodiscard]] virtual Result mediaSeeking(const std::string& entityId, double target, const std::optional& agePolicy) const = 0; /** @@ -200,7 +200,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaSeeked(const std::string& entityId, const double position, + [[nodiscard]] virtual Result mediaSeeked(const std::string& entityId, double position, const std::optional& agePolicy) const = 0; /** @@ -213,7 +213,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaRateChanged(const std::string& entityId, const double rate, + [[nodiscard]] virtual Result mediaRateChanged(const std::string& entityId, double rate, const std::optional& agePolicy) const = 0; /** @@ -229,8 +229,8 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaRenditionChanged(const std::string& entityId, const unsigned bitrate, const unsigned width, - const unsigned height, const std::optional& profile, + [[nodiscard]] virtual Result mediaRenditionChanged(const std::string& entityId, unsigned bitrate, unsigned width, + unsigned height, const std::optional& profile, const std::optional& agePolicy) const = 0; /** @@ -242,7 +242,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result mediaEnded(const std::string& entityId, + [[nodiscard]] virtual Result mediaEnded(const std::string& entityId, const std::optional& agePolicy) const = 0; /** @@ -255,7 +255,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result event(const std::string& schema, const std::string& data, + [[nodiscard]] virtual Result event(const std::string& schema, const std::string& data, const std::optional& agePolicy) const = 0; /** @@ -265,7 +265,7 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - virtual Result appInfo(const std::string& build) const = 0; + [[nodiscard]] virtual Result appInfo(const std::string& build) const = 0; }; } // namespace Firebolt::Metrics diff --git a/include/firebolt/network.h b/include/firebolt/network.h index 0a0bc33..8732cf7 100644 --- a/include/firebolt/network.h +++ b/include/firebolt/network.h @@ -34,7 +34,7 @@ class INetwork * * @retval The connection state or error */ - virtual Result connected() const = 0; + [[nodiscard]] virtual Result connected() const = 0; /** * @brief Subscribe to connection changes diff --git a/include/firebolt/presentation.h b/include/firebolt/presentation.h index e7bcf85..687e3a4 100644 --- a/include/firebolt/presentation.h +++ b/include/firebolt/presentation.h @@ -33,7 +33,7 @@ class IPresentation * * @retval The focus state or error */ - virtual Result focused() const = 0; + [[nodiscard]] virtual Result focused() const = 0; /** * @brief Subscribe to focus change notifications diff --git a/include/firebolt/stats.h b/include/firebolt/stats.h index ed4e1e6..8e5dd03 100644 --- a/include/firebolt/stats.h +++ b/include/firebolt/stats.h @@ -40,7 +40,7 @@ class IStats * * @retval MemoryInfo struct or error */ - virtual Result memoryUsage() const = 0; + [[nodiscard]] virtual Result memoryUsage() const = 0; }; } // namespace Firebolt::Stats diff --git a/include/firebolt/texttospeech.h b/include/firebolt/texttospeech.h index 6650498..eaf41e2 100644 --- a/include/firebolt/texttospeech.h +++ b/include/firebolt/texttospeech.h @@ -91,7 +91,7 @@ class ITextToSpeech * * @retval The list of voices supported for the language */ - virtual Result listVoices(const std::string& language) const = 0; + [[nodiscard]] virtual Result listVoices(const std::string& language) const = 0; /** * @brief Speak the uttered text using the TTS engine @@ -100,7 +100,7 @@ class ITextToSpeech * * @retval Result for Speak */ - virtual Result speak(const std::string& text) const = 0; + [[nodiscard]] virtual Result speak(const std::string& text) const = 0; /** * @brief Pauses the speech for given speech id @@ -109,7 +109,7 @@ class ITextToSpeech * * @retval Result for Pause */ - virtual Result pause(SpeechId speechId) const = 0; + [[nodiscard]] virtual Result pause(SpeechId speechId) const = 0; /** * @brief Resumes the speech for given speech id @@ -118,7 +118,7 @@ class ITextToSpeech * * @retval Result for Resume */ - virtual Result resume(SpeechId speechId) const = 0; + [[nodiscard]] virtual Result resume(SpeechId speechId) const = 0; /** * @brief Cancels the speech for given speech id @@ -127,7 +127,7 @@ class ITextToSpeech * * @retval Result for cancel */ - virtual Result cancel(SpeechId speechId) const = 0; + [[nodiscard]] virtual Result cancel(SpeechId speechId) const = 0; /** * @brief Returns the current state of the speech request. @@ -136,7 +136,7 @@ class ITextToSpeech * * @retval Result for speech state */ - virtual Result getSpeechState(SpeechId speechId) const = 0; + [[nodiscard]] virtual Result getSpeechState(SpeechId speechId) const = 0; /** * @brief Triggered when the text to speech conversion is about to start. It diff --git a/include/firebolt/videooutput.h b/include/firebolt/videooutput.h index 11a6701..4833e74 100644 --- a/include/firebolt/videooutput.h +++ b/include/firebolt/videooutput.h @@ -28,7 +28,7 @@ #include #include -namespace Firebolt::Videooutput +namespace Firebolt::VideoOutput { enum class CecStateValue { @@ -107,39 +107,39 @@ struct VideoOutputResolution uint32_t width; }; -class IVideooutput +class IVideoOutput { public: - virtual ~IVideooutput() = default; + virtual ~IVideoOutput() = default; - virtual Result cecState() const = 0; + [[nodiscard]] virtual Result cecState() const = 0; virtual Result subscribeOnCecStateChanged(std::function&& notification) = 0; - virtual Result colorDepth() const = 0; + [[nodiscard]] virtual Result colorDepth() const = 0; - virtual Result colorFormat() const = 0; + [[nodiscard]] virtual Result colorFormat() const = 0; - virtual Result colorimetry() const = 0; + [[nodiscard]] virtual Result colorimetry() const = 0; - virtual Result dynamicRange() const = 0; + [[nodiscard]] virtual Result dynamicRange() const = 0; - virtual Result hdcp() const = 0; + [[nodiscard]] virtual Result hdcp() const = 0; virtual Result subscribeOnHdcpChanged(std::function&& notification) = 0; - virtual Result quantizationRange() const = 0; + [[nodiscard]] virtual Result quantizationRange() const = 0; - virtual Result refreshRate() const = 0; + [[nodiscard]] virtual Result refreshRate() const = 0; virtual Result subscribeOnRefreshRateChanged(std::function&& notification) = 0; - virtual Result resolution() const = 0; + [[nodiscard]] virtual Result resolution() const = 0; virtual Result subscribeOnResolutionChanged(std::function&& notification) = 0; virtual Result unsubscribe(SubscriptionId id) = 0; virtual void unsubscribeAll() = 0; -}; // class IVideooutput +}; // class IVideoOutput #ifndef FIREBOLT_NO_METHOD_AVAILABILITY @@ -183,6 +183,6 @@ static constexpr std::array allMethods = { #endif // FIREBOLT_NO_METHOD_AVAILABILITY -} // namespace Firebolt::Videooutput +} // namespace Firebolt::VideoOutput #endif // FIREBOLT_VIDEOOUTPUT_H diff --git a/src/accessibility_impl.h b/src/accessibility_impl.h index 6a327fb..3ff383c 100644 --- a/src/accessibility_impl.h +++ b/src/accessibility_impl.h @@ -33,22 +33,22 @@ class AccessibilityImpl : public IAccessibility ~AccessibilityImpl() override = default; - Result audioDescription() const override; + [[nodiscard]] Result audioDescription() const override; Result subscribeOnAudioDescriptionChanged(std::function&& notification) override; - Result closedCaptionsSettings() const override; + [[nodiscard]] Result closedCaptionsSettings() const override; Result subscribeOnClosedCaptionsSettingsChanged(std::function&& notification) override; - Result highContrastUI() const override; + [[nodiscard]] Result highContrastUI() const override; Result subscribeOnHighContrastUIChanged(std::function&& notification) override; - Result voiceGuidanceSettings() const override; + [[nodiscard]] Result voiceGuidanceSettings() const override; Result subscribeOnVoiceGuidanceSettingsChanged(std::function&& notification) override; - virtual Result unsubscribe(SubscriptionId id) override; - virtual void unsubscribeAll() override; + Result unsubscribe(SubscriptionId id) override; + void unsubscribeAll() override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/src/actions_impl.h b/src/actions_impl.h index 523f82d..edfe2c0 100644 --- a/src/actions_impl.h +++ b/src/actions_impl.h @@ -36,11 +36,11 @@ class ActionsImpl : public IActions ActionsImpl& operator=(const ActionsImpl&) = delete; ~ActionsImpl() override = default; - Result intent() const override; + [[nodiscard]] Result intent() const override; Result subscribeOnIntent(std::function&& notification) override; - Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const override; + [[nodiscard]] Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const override; Result unsubscribe(SubscriptionId id) override; void unsubscribeAll() override; diff --git a/src/advertising_impl.h b/src/advertising_impl.h index efad60f..cd0a5ff 100644 --- a/src/advertising_impl.h +++ b/src/advertising_impl.h @@ -32,7 +32,7 @@ class AdvertisingImpl : public IAdvertising ~AdvertisingImpl() override = default; - Result advertisingId() const override; + [[nodiscard]] Result advertisingId() const override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/src/device_impl.h b/src/device_impl.h index c706427..1c2dce2 100644 --- a/src/device_impl.h +++ b/src/device_impl.h @@ -32,19 +32,19 @@ class DeviceImpl : public IDevice ~DeviceImpl() override = default; - Result chipsetId() const override; - Result deviceClass() const override; - Result hdr() const override; - Result timeInActiveState() const override; - Result uid() const override; - Result uptime() const override; + [[nodiscard]] Result chipsetId() const override; + [[nodiscard]] Result deviceClass() const override; + [[nodiscard]] Result hdr() const override; + [[nodiscard]] Result timeInActiveState() const override; + [[nodiscard]] Result uid() const override; + [[nodiscard]] Result uptime() const override; Result subscribeOnHdrChanged(std::function&& notification) override; Result unsubscribe(SubscriptionId id) override; void unsubscribeAll() override; - Result dolbyAtmosExperienceAvailable() const override; + [[nodiscard]] Result dolbyAtmosExperienceAvailable() const override; Result subscribeOnDolbyAtmosExperienceAvailableChanged(std::function&& notification) override; diff --git a/src/discovery_impl.h b/src/discovery_impl.h index 285186e..7eafde1 100644 --- a/src/discovery_impl.h +++ b/src/discovery_impl.h @@ -33,11 +33,11 @@ class DiscoveryImpl : public IDiscovery ~DiscoveryImpl() override = default; - Result watched(const std::string& entityId, std::optional progress, std::optional completed, + [[nodiscard]] Result watched(const std::string& entityId, std::optional progress, std::optional completed, std::optional watchedOn, std::optional agePolicy) const override; - Result watchedV2(const std::string& entityId, std::optional progress, std::optional completed, + [[nodiscard]] Result watchedV2(const std::string& entityId, std::optional progress, std::optional completed, std::optional watchedOn, std::optional agePolicy) const override; diff --git a/src/display_impl.h b/src/display_impl.h index bbe5bd7..0b99e72 100644 --- a/src/display_impl.h +++ b/src/display_impl.h @@ -32,9 +32,9 @@ class DisplayImpl : public IDisplay ~DisplayImpl() override = default; - Result edid() const override; - Result maxResolution() const override; - Result size() const override; + [[nodiscard]] Result edid() const override; + [[nodiscard]] Result maxResolution() const override; + [[nodiscard]] Result size() const override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/src/firebolt.cpp b/src/firebolt.cpp index cde9fad..cdbb797 100644 --- a/src/firebolt.cpp +++ b/src/firebolt.cpp @@ -60,7 +60,7 @@ class FireboltAccessorImpl : public IFireboltAccessor FireboltAccessorImpl(const FireboltAccessorImpl&) = delete; FireboltAccessorImpl& operator=(const FireboltAccessorImpl&) = delete; - ~FireboltAccessorImpl() { unsubscribeAll(); } + ~FireboltAccessorImpl() override { unsubscribeAll(); } Firebolt::Error Connect(const Firebolt::Config& config, OnConnectionChanged listener) override { @@ -88,7 +88,7 @@ class FireboltAccessorImpl : public IFireboltAccessor Stats::IStats& StatsInterface() override { return stats_; } TextToSpeech::ITextToSpeech& TextToSpeechInterface() override { return textToSpeech_; } Actions::IActions& ActionsInterface() override { return actions_; } - Videooutput::IVideooutput& VideooutputInterface() override { return videooutput_; } + VideoOutput::IVideoOutput& VideoOutputInterface() override { return videooutput_; } private: void unsubscribeAll() @@ -103,7 +103,7 @@ class FireboltAccessorImpl : public IFireboltAccessor videooutput_.unsubscribeAll(); } -private: + Accessibility::AccessibilityImpl accessibility_; Advertising::AdvertisingImpl advertising_; Actions::ActionsImpl actions_; @@ -117,7 +117,7 @@ class FireboltAccessorImpl : public IFireboltAccessor Presentation::PresentationImpl presentation_; Stats::StatsImpl stats_; TextToSpeech::TextToSpeechImpl textToSpeech_; - Videooutput::VideooutputImpl videooutput_; + VideoOutput::VideoOutputImpl videooutput_; }; /* static */ IFireboltAccessor& IFireboltAccessor::Instance() diff --git a/src/json_types/accessibility.h b/src/json_types/accessibility.h index e071f04..4037b10 100644 --- a/src/json_types/accessibility.h +++ b/src/json_types/accessibility.h @@ -36,7 +36,7 @@ class ClosedCaptionsSettings : public Firebolt::JSON::NL_Json_Basic<::Firebolt:: enabled_ = json["enabled"].get(); preferredLanguages_ = json["preferredLanguages"].get>(); } - ::Firebolt::Accessibility::ClosedCaptionsSettings value() const override + [[nodiscard]] ::Firebolt::Accessibility::ClosedCaptionsSettings value() const override { return ::Firebolt::Accessibility::ClosedCaptionsSettings{enabled_, preferredLanguages_}; } @@ -59,7 +59,7 @@ class VoiceGuidanceSettings : public Firebolt::JSON::NL_Json_Basic<::Firebolt::A rate_ = json["rate"].get(); navigationHints_ = json["navigationHints"].get(); } - ::Firebolt::Accessibility::VoiceGuidanceSettings value() const override + [[nodiscard]] ::Firebolt::Accessibility::VoiceGuidanceSettings value() const override { return ::Firebolt::Accessibility::VoiceGuidanceSettings{enabled_, rate_, navigationHints_}; } diff --git a/src/json_types/actions.h b/src/json_types/actions.h index 4335be8..1995282 100644 --- a/src/json_types/actions.h +++ b/src/json_types/actions.h @@ -28,10 +28,9 @@ #include #include -namespace Firebolt::Actions -{ -namespace JsonData + +namespace Firebolt::Actions::JsonData { // Deserialises the wire object {"intent":{"action":"...","context":{"source":"..."}},"intentId":N} @@ -51,20 +50,21 @@ class JsonValue : public Firebolt::JSON::NL_Json_Basic if (json["intent"].contains("context") && json["intent"]["context"].is_object()) { IntentContext ctx; - if (json["intent"]["context"].contains("source")) + if (json["intent"]["context"].contains("source")) { ctx.source = json["intent"]["context"]["source"].get(); +} value_.intent.context = ctx; } value_.intentId = json["intentId"].get(); } - Intent value() const override { return value_; } + [[nodiscard]] Intent value() const override { return value_; } private: Intent value_; }; -} // namespace JsonData +} // namespace Firebolt::Actions::JsonData + -} // namespace Firebolt::Actions #endif // FIREBOLT_ACTIONS_JSON_H diff --git a/src/json_types/advertising.h b/src/json_types/advertising.h index 38b2ecc..decd700 100644 --- a/src/json_types/advertising.h +++ b/src/json_types/advertising.h @@ -38,7 +38,7 @@ class IfaJson : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Advertising::If ifa_type = json["ifa_type"].get(); lmt = json["lmt"].get(); } - ::Firebolt::Advertising::Ifa value() const override { return ::Firebolt::Advertising::Ifa{ifa, ifa_type, lmt}; } + [[nodiscard]] ::Firebolt::Advertising::Ifa value() const override { return ::Firebolt::Advertising::Ifa{ifa, ifa_type, lmt}; } private: std::string ifa; diff --git a/src/json_types/device.h b/src/json_types/device.h index 82c6993..9e78aea 100644 --- a/src/json_types/device.h +++ b/src/json_types/device.h @@ -36,7 +36,7 @@ class DeviceClassJson : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Device: { public: void fromJson(const nlohmann::json& json) override { deviceClass_ = DeviceClassEnum.at(json); } - ::Firebolt::Device::DeviceClass value() const override { return deviceClass_; } + [[nodiscard]] ::Firebolt::Device::DeviceClass value() const override { return deviceClass_; } private: ::Firebolt::Device::DeviceClass deviceClass_; @@ -56,7 +56,7 @@ class HDRFormat : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Device::HDRFo hdrFormat_.dolbyVision = json["dolbyVision"].get(); hdrFormat_.hlg = json["hlg"].get(); } - ::Firebolt::Device::HDRFormat value() const override { return hdrFormat_; } + [[nodiscard]] ::Firebolt::Device::HDRFormat value() const override { return hdrFormat_; } private: ::Firebolt::Device::HDRFormat hdrFormat_; diff --git a/src/json_types/display.h b/src/json_types/display.h index 7e14b18..90f850a 100644 --- a/src/json_types/display.h +++ b/src/json_types/display.h @@ -38,7 +38,7 @@ class DisplaySizeJson : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Display width_ = json["width"].get(); height_ = json["height"].get(); } - ::Firebolt::Display::DisplaySize value() const override { return Firebolt::Display::DisplaySize{width_, height_}; } + [[nodiscard]] ::Firebolt::Display::DisplaySize value() const override { return Firebolt::Display::DisplaySize{width_, height_}; } private: uint32_t width_; diff --git a/src/json_types/lifecycle.h b/src/json_types/lifecycle.h index acc43dd..0b130be 100644 --- a/src/json_types/lifecycle.h +++ b/src/json_types/lifecycle.h @@ -44,7 +44,7 @@ class LifecycleState : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Lifecycl { public: void fromJson(const nlohmann::json& json) override { state_ = LifecycleStateEnum.at(json.get()); } - ::Firebolt::Lifecycle::LifecycleState value() const override { return state_; } + [[nodiscard]] ::Firebolt::Lifecycle::LifecycleState value() const override { return state_; } private: ::Firebolt::Lifecycle::LifecycleState state_; @@ -62,7 +62,7 @@ class StateChange : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Lifecycle:: oldState_ = LifecycleStateEnum.at(json["oldState"]); newState_ = LifecycleStateEnum.at(json["newState"]); } - ::Firebolt::Lifecycle::StateChange value() const override + [[nodiscard]] ::Firebolt::Lifecycle::StateChange value() const override { return ::Firebolt::Lifecycle::StateChange{oldState_, newState_}; } diff --git a/src/json_types/stats.h b/src/json_types/stats.h index e8ad013..89610a9 100644 --- a/src/json_types/stats.h +++ b/src/json_types/stats.h @@ -38,7 +38,7 @@ class MemoryInfo : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Stats::Memor gpuMemoryUsed = json["gpuMemoryUsed"].get(); gpuMemoryLimit = json["gpuMemoryLimit"].get(); } - ::Firebolt::Stats::MemoryInfo value() const override + [[nodiscard]] ::Firebolt::Stats::MemoryInfo value() const override { return ::Firebolt::Stats::MemoryInfo{userMemoryUsed, userMemoryLimit, gpuMemoryUsed, gpuMemoryLimit}; } diff --git a/src/json_types/texttospeech.h b/src/json_types/texttospeech.h index b0f2686..481bb0a 100644 --- a/src/json_types/texttospeech.h +++ b/src/json_types/texttospeech.h @@ -47,7 +47,7 @@ class ListVoicesResponse : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Text voices_.push_back(voice.get()); } } - ::Firebolt::TextToSpeech::ListVoicesResponse value() const override + [[nodiscard]] ::Firebolt::TextToSpeech::ListVoicesResponse value() const override { return ::Firebolt::TextToSpeech::ListVoicesResponse{ttsStatus_, voices_}; } @@ -68,7 +68,7 @@ class SpeechIdEvent : public Firebolt::JSON::NL_Json_Basic<::Firebolt::TextToSpe } speechId_ = json["speechid"].get(); } - ::Firebolt::TextToSpeech::SpeechIdEvent value() const override + [[nodiscard]] ::Firebolt::TextToSpeech::SpeechIdEvent value() const override { return ::Firebolt::TextToSpeech::SpeechIdEvent{speechId_}; } @@ -90,7 +90,7 @@ class SpeechResponse : public Firebolt::JSON::NL_Json_Basic<::Firebolt::TextToSp ttsStatus_ = json["TTS_Status"].get(); success_ = json["success"].get(); } - ::Firebolt::TextToSpeech::SpeechResponse value() const override + [[nodiscard]] ::Firebolt::TextToSpeech::SpeechResponse value() const override { return ::Firebolt::TextToSpeech::SpeechResponse{speechId_, ttsStatus_, success_}; } @@ -114,7 +114,7 @@ class SpeechStateResponse : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Tex ttsStatus_ = json["TTS_Status"].get(); success_ = json["success"].get(); } - ::Firebolt::TextToSpeech::SpeechStateResponse value() const override + [[nodiscard]] ::Firebolt::TextToSpeech::SpeechStateResponse value() const override { return ::Firebolt::TextToSpeech::SpeechStateResponse{speechState_, ttsStatus_, success_}; } @@ -137,7 +137,7 @@ class TTSStatusResponse : public Firebolt::JSON::NL_Json_Basic<::Firebolt::TextT ttsStatus_ = json["TTS_Status"].get(); success_ = json["success"].get(); } - ::Firebolt::TextToSpeech::TTSStatusResponse value() const override + [[nodiscard]] ::Firebolt::TextToSpeech::TTSStatusResponse value() const override { return ::Firebolt::TextToSpeech::TTSStatusResponse{ttsStatus_, success_}; } diff --git a/src/json_types/videooutput.h b/src/json_types/videooutput.h index 188d319..9faa931 100644 --- a/src/json_types/videooutput.h +++ b/src/json_types/videooutput.h @@ -27,7 +27,7 @@ #include #include -namespace Firebolt::Videooutput +namespace Firebolt::VideoOutput { NLOHMANN_JSON_SERIALIZE_ENUM(CecStateValue, { @@ -96,70 +96,70 @@ NLOHMANN_JSON_SERIALIZE_ENUM(RefreshRateValue, { namespace JsonData { -inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::CecStateValue> CecStateValueEnum({ - {"ACTIVE", ::Firebolt::Videooutput::CecStateValue::Active}, - {"INACTIVE", ::Firebolt::Videooutput::CecStateValue::Inactive}, - {"UNSUPPORTED", ::Firebolt::Videooutput::CecStateValue::Unsupported}, +inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::CecStateValue> CecStateValueEnum({ + {"ACTIVE", ::Firebolt::VideoOutput::CecStateValue::Active}, + {"INACTIVE", ::Firebolt::VideoOutput::CecStateValue::Inactive}, + {"UNSUPPORTED", ::Firebolt::VideoOutput::CecStateValue::Unsupported}, }); -inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::ColorDepthValue> ColorDepthValueEnum({ - {"D0", ::Firebolt::Videooutput::ColorDepthValue::D0}, - {"D10", ::Firebolt::Videooutput::ColorDepthValue::D10}, - {"D12", ::Firebolt::Videooutput::ColorDepthValue::D12}, - {"D8", ::Firebolt::Videooutput::ColorDepthValue::D8}, +inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::ColorDepthValue> ColorDepthValueEnum({ + {"D0", ::Firebolt::VideoOutput::ColorDepthValue::D0}, + {"D10", ::Firebolt::VideoOutput::ColorDepthValue::D10}, + {"D12", ::Firebolt::VideoOutput::ColorDepthValue::D12}, + {"D8", ::Firebolt::VideoOutput::ColorDepthValue::D8}, }); -inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::ColorFormatValue> ColorFormatValueEnum({ - {"NONE", ::Firebolt::Videooutput::ColorFormatValue::None}, - {"RGB444", ::Firebolt::Videooutput::ColorFormatValue::Rgb444}, - {"YCBCR420", ::Firebolt::Videooutput::ColorFormatValue::Ycbcr420}, - {"YCBCR422", ::Firebolt::Videooutput::ColorFormatValue::Ycbcr422}, - {"YCBCR444", ::Firebolt::Videooutput::ColorFormatValue::Ycbcr444}, +inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::ColorFormatValue> ColorFormatValueEnum({ + {"NONE", ::Firebolt::VideoOutput::ColorFormatValue::None}, + {"RGB444", ::Firebolt::VideoOutput::ColorFormatValue::Rgb444}, + {"YCBCR420", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr420}, + {"YCBCR422", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr422}, + {"YCBCR444", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr444}, }); -inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::DynamicRangeValue> DynamicRangeValueEnum({ - {"DOLBY_VISION", ::Firebolt::Videooutput::DynamicRangeValue::DolbyVision}, - {"HDR10", ::Firebolt::Videooutput::DynamicRangeValue::Hdr10}, - {"HDR10PLUS", ::Firebolt::Videooutput::DynamicRangeValue::Hdr10plus}, - {"HLG", ::Firebolt::Videooutput::DynamicRangeValue::Hlg}, - {"NONE", ::Firebolt::Videooutput::DynamicRangeValue::None}, - {"SDR", ::Firebolt::Videooutput::DynamicRangeValue::Sdr}, +inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::DynamicRangeValue> DynamicRangeValueEnum({ + {"DOLBY_VISION", ::Firebolt::VideoOutput::DynamicRangeValue::DolbyVision}, + {"HDR10", ::Firebolt::VideoOutput::DynamicRangeValue::Hdr10}, + {"HDR10PLUS", ::Firebolt::VideoOutput::DynamicRangeValue::Hdr10plus}, + {"HLG", ::Firebolt::VideoOutput::DynamicRangeValue::Hlg}, + {"NONE", ::Firebolt::VideoOutput::DynamicRangeValue::None}, + {"SDR", ::Firebolt::VideoOutput::DynamicRangeValue::Sdr}, }); -inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::HdcpState> HdcpStateEnum({ - {"DIRECT", ::Firebolt::Videooutput::HdcpState::Direct}, - {"HDCP14", ::Firebolt::Videooutput::HdcpState::Hdcp14}, - {"HDCP22", ::Firebolt::Videooutput::HdcpState::Hdcp22}, - {"NONE", ::Firebolt::Videooutput::HdcpState::None}, +inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::HdcpState> HdcpStateEnum({ + {"DIRECT", ::Firebolt::VideoOutput::HdcpState::Direct}, + {"HDCP14", ::Firebolt::VideoOutput::HdcpState::Hdcp14}, + {"HDCP22", ::Firebolt::VideoOutput::HdcpState::Hdcp22}, + {"NONE", ::Firebolt::VideoOutput::HdcpState::None}, }); -inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::OutputColorimetry> OutputColorimetryEnum({ - {"BT2020RGB", ::Firebolt::Videooutput::OutputColorimetry::Bt2020rgb}, - {"BT2020YCC", ::Firebolt::Videooutput::OutputColorimetry::Bt2020ycc}, - {"BT709", ::Firebolt::Videooutput::OutputColorimetry::Bt709}, - {"NONE", ::Firebolt::Videooutput::OutputColorimetry::None}, - {"OPRGB", ::Firebolt::Videooutput::OutputColorimetry::Oprgb}, +inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::OutputColorimetry> OutputColorimetryEnum({ + {"BT2020RGB", ::Firebolt::VideoOutput::OutputColorimetry::Bt2020rgb}, + {"BT2020YCC", ::Firebolt::VideoOutput::OutputColorimetry::Bt2020ycc}, + {"BT709", ::Firebolt::VideoOutput::OutputColorimetry::Bt709}, + {"NONE", ::Firebolt::VideoOutput::OutputColorimetry::None}, + {"OPRGB", ::Firebolt::VideoOutput::OutputColorimetry::Oprgb}, }); -inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::QuantizationRangeValue> QuantizationRangeValueEnum({ - {"FULL", ::Firebolt::Videooutput::QuantizationRangeValue::Full}, - {"LIMITED", ::Firebolt::Videooutput::QuantizationRangeValue::Limited}, - {"NONE", ::Firebolt::Videooutput::QuantizationRangeValue::None}, +inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::QuantizationRangeValue> QuantizationRangeValueEnum({ + {"FULL", ::Firebolt::VideoOutput::QuantizationRangeValue::Full}, + {"LIMITED", ::Firebolt::VideoOutput::QuantizationRangeValue::Limited}, + {"NONE", ::Firebolt::VideoOutput::QuantizationRangeValue::None}, }); -inline const Firebolt::JSON::EnumType<::Firebolt::Videooutput::RefreshRateValue> RefreshRateValueEnum({ - {"R0", ::Firebolt::Videooutput::RefreshRateValue::R0}, - {"R23_976", ::Firebolt::Videooutput::RefreshRateValue::R23976}, - {"R24", ::Firebolt::Videooutput::RefreshRateValue::R24}, - {"R25", ::Firebolt::Videooutput::RefreshRateValue::R25}, - {"R29_97", ::Firebolt::Videooutput::RefreshRateValue::R2997}, - {"R30", ::Firebolt::Videooutput::RefreshRateValue::R30}, - {"R50", ::Firebolt::Videooutput::RefreshRateValue::R50}, - {"R59_94", ::Firebolt::Videooutput::RefreshRateValue::R5994}, - {"R60", ::Firebolt::Videooutput::RefreshRateValue::R60}, +inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::RefreshRateValue> RefreshRateValueEnum({ + {"R0", ::Firebolt::VideoOutput::RefreshRateValue::R0}, + {"R23_976", ::Firebolt::VideoOutput::RefreshRateValue::R23976}, + {"R24", ::Firebolt::VideoOutput::RefreshRateValue::R24}, + {"R25", ::Firebolt::VideoOutput::RefreshRateValue::R25}, + {"R29_97", ::Firebolt::VideoOutput::RefreshRateValue::R2997}, + {"R30", ::Firebolt::VideoOutput::RefreshRateValue::R30}, + {"R50", ::Firebolt::VideoOutput::RefreshRateValue::R50}, + {"R59_94", ::Firebolt::VideoOutput::RefreshRateValue::R5994}, + {"R60", ::Firebolt::VideoOutput::RefreshRateValue::R60}, }); -class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Videooutput::VideoOutputResolution> +class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::VideoOutput::VideoOutputResolution> { public: void fromJson(const nlohmann::json& json) override @@ -171,9 +171,9 @@ class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::V height_ = json["height"].get(); width_ = json["width"].get(); } - ::Firebolt::Videooutput::VideoOutputResolution value() const override + [[nodiscard]] ::Firebolt::VideoOutput::VideoOutputResolution value() const override { - return ::Firebolt::Videooutput::VideoOutputResolution{height_, width_}; + return ::Firebolt::VideoOutput::VideoOutputResolution{height_, width_}; } private: @@ -190,6 +190,6 @@ inline void to_json(nlohmann::json& j, const VideoOutputResolution& v) j["width"] = v.width; } -} // namespace Firebolt::Videooutput +} // namespace Firebolt::VideoOutput #endif // FIREBOLT_VIDEOOUTPUT_JSON_H diff --git a/src/lifecycle_impl.cpp b/src/lifecycle_impl.cpp index 0380b56..5817d38 100644 --- a/src/lifecycle_impl.cpp +++ b/src/lifecycle_impl.cpp @@ -32,7 +32,7 @@ LifecycleImpl::LifecycleImpl(Firebolt::Helpers::IHelper& helper) { } -LifecycleImpl::~LifecycleImpl() {} +LifecycleImpl::~LifecycleImpl() = default; Result LifecycleImpl::close(const CloseType& reason) const { diff --git a/src/lifecycle_impl.h b/src/lifecycle_impl.h index 43a7796..8ae612f 100644 --- a/src/lifecycle_impl.h +++ b/src/lifecycle_impl.h @@ -39,17 +39,17 @@ class LifecycleImpl : public ILifecycle LifecycleImpl& operator=(const LifecycleImpl&) = delete; ~LifecycleImpl() override; - virtual Result close(const CloseType& type) const override; - virtual Result state() const override; + [[nodiscard]] Result close(const CloseType& type) const override; + [[nodiscard]] Result state() const override; Result subscribeOnStateChanged(std::function&)>&& notification) override; - virtual Result unsubscribe(SubscriptionId id) override; - virtual void unsubscribeAll() override; + Result unsubscribe(SubscriptionId id) override; + void unsubscribeAll() override; private: -private: + Firebolt::Helpers::IHelper& helper_; Firebolt::Helpers::SubscriptionManager subscriptionManager_; diff --git a/src/localization_impl.h b/src/localization_impl.h index eb9df69..3c05e14 100644 --- a/src/localization_impl.h +++ b/src/localization_impl.h @@ -33,10 +33,10 @@ class LocalizationImpl : public ILocalization ~LocalizationImpl() override = default; // Methods - Result country() const override; - Result> preferredAudioLanguages() const override; - Result presentationLanguage() const override; - Result timeZone() const override; + [[nodiscard]] Result country() const override; + [[nodiscard]] Result> preferredAudioLanguages() const override; + [[nodiscard]] Result presentationLanguage() const override; + [[nodiscard]] Result timeZone() const override; // Events Result subscribeOnCountryChanged(std::function&& notification) override; diff --git a/src/metrics_impl.h b/src/metrics_impl.h index ace303b..41904e5 100644 --- a/src/metrics_impl.h +++ b/src/metrics_impl.h @@ -32,41 +32,41 @@ class MetricsImpl : public IMetrics ~MetricsImpl() override = default; - Result ready() const override; - Result signIn() const override; - Result signOut() const override; - Result startContent(const std::optional& entityId, - const std::optional agePolicy) const override; - Result stopContent(const std::optional& entityId, - const std::optional agePolicy) const override; - Result page(const std::string& pageId, const std::optional& agePolicy) const override; - Result error(const ErrorType type, const std::string& code, const std::string& description, - const bool visible, const std::optional>& parameters, + [[nodiscard]] Result ready() const override; + [[nodiscard]] Result signIn() const override; + [[nodiscard]] Result signOut() const override; + [[nodiscard]] Result startContent(const std::optional& entityId, + std::optional agePolicy) const override; + [[nodiscard]] Result stopContent(const std::optional& entityId, + std::optional agePolicy) const override; + [[nodiscard]] Result page(const std::string& pageId, const std::optional& agePolicy) const override; + [[nodiscard]] Result error(ErrorType type, const std::string& code, const std::string& description, + bool visible, const std::optional>& parameters, const std::optional& agePolicy) const override; - Result mediaLoadStart(const std::string& entityId, + [[nodiscard]] Result mediaLoadStart(const std::string& entityId, const std::optional& agePolicy) const override; - Result mediaPlay(const std::string& entityId, + [[nodiscard]] Result mediaPlay(const std::string& entityId, const std::optional& agePolicy) const override; - Result mediaPlaying(const std::string& entityId, + [[nodiscard]] Result mediaPlaying(const std::string& entityId, const std::optional& agePolicy) const override; - Result mediaPause(const std::string& entityId, + [[nodiscard]] Result mediaPause(const std::string& entityId, const std::optional& agePolicy) const override; - Result mediaWaiting(const std::string& entityId, + [[nodiscard]] Result mediaWaiting(const std::string& entityId, const std::optional& agePolicy) const override; - Result mediaSeeking(const std::string& entityId, const double target, + [[nodiscard]] Result mediaSeeking(const std::string& entityId, double target, const std::optional& agePolicy) const override; - Result mediaSeeked(const std::string& entityId, const double position, + [[nodiscard]] Result mediaSeeked(const std::string& entityId, double position, const std::optional& agePolicy) const override; - Result mediaRateChanged(const std::string& entityId, const double rate, + [[nodiscard]] Result mediaRateChanged(const std::string& entityId, double rate, const std::optional& agePolicy) const override; - Result mediaRenditionChanged(const std::string& entityId, const unsigned bitrate, const unsigned width, - const unsigned height, const std::optional& profile, + [[nodiscard]] Result mediaRenditionChanged(const std::string& entityId, unsigned bitrate, unsigned width, + unsigned height, const std::optional& profile, const std::optional& agePolicy) const override; - Result mediaEnded(const std::string& entityId, + [[nodiscard]] Result mediaEnded(const std::string& entityId, const std::optional& agePolicy) const override; - Result event(const std::string& schema, const std::string& data, + [[nodiscard]] Result event(const std::string& schema, const std::string& data, const std::optional& agePolicy) const override; - Result appInfo(const std::string& build) const override; + [[nodiscard]] Result appInfo(const std::string& build) const override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/src/network_impl.h b/src/network_impl.h index 178d22d..50ce47b 100644 --- a/src/network_impl.h +++ b/src/network_impl.h @@ -32,7 +32,7 @@ class NetworkImpl : public INetwork ~NetworkImpl() override = default; - Result connected() const override; + [[nodiscard]] Result connected() const override; Result subscribeOnConnectedChanged(std::function&& notification) override; diff --git a/src/presentation_impl.h b/src/presentation_impl.h index b37485e..26637d4 100644 --- a/src/presentation_impl.h +++ b/src/presentation_impl.h @@ -34,11 +34,11 @@ class PresentationImpl : public IPresentation ~PresentationImpl() override = default; - Result focused() const override; + [[nodiscard]] Result focused() const override; Result subscribeOnFocusedChanged(std::function&& notification) override; - virtual Result unsubscribe(SubscriptionId id) override; - virtual void unsubscribeAll() override; + Result unsubscribe(SubscriptionId id) override; + void unsubscribeAll() override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/src/stats_impl.cpp b/src/stats_impl.cpp index a61e413..fa16a80 100644 --- a/src/stats_impl.cpp +++ b/src/stats_impl.cpp @@ -31,7 +31,7 @@ StatsImpl::StatsImpl(Firebolt::Helpers::IHelper& helper) { } -StatsImpl::~StatsImpl() {} +StatsImpl::~StatsImpl() = default; Result StatsImpl::memoryUsage() const { diff --git a/src/stats_impl.h b/src/stats_impl.h index fa693f6..f662375 100644 --- a/src/stats_impl.h +++ b/src/stats_impl.h @@ -32,7 +32,7 @@ class StatsImpl : public IStats StatsImpl& operator=(const StatsImpl&) = delete; ~StatsImpl() override; - virtual Result memoryUsage() const override; + [[nodiscard]] Result memoryUsage() const override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/src/texttospeech_impl.h b/src/texttospeech_impl.h index a4aef02..6dc646d 100644 --- a/src/texttospeech_impl.h +++ b/src/texttospeech_impl.h @@ -32,12 +32,12 @@ class TextToSpeechImpl : public ITextToSpeech ~TextToSpeechImpl() override = default; - Result listVoices(const std::string& language) const override; - Result speak(const std::string& text) const override; - Result pause(SpeechId speechId) const override; - Result resume(SpeechId speechId) const override; - Result cancel(SpeechId speechId) const override; - Result getSpeechState(SpeechId speechId) const override; + [[nodiscard]] Result listVoices(const std::string& language) const override; + [[nodiscard]] Result speak(const std::string& text) const override; + [[nodiscard]] Result pause(SpeechId speechId) const override; + [[nodiscard]] Result resume(SpeechId speechId) const override; + [[nodiscard]] Result cancel(SpeechId speechId) const override; + [[nodiscard]] Result getSpeechState(SpeechId speechId) const override; Result subscribeOnWillSpeak(std::function&& notification) override; Result subscribeOnSpeechStart(std::function&& notification) override; diff --git a/src/videooutput_impl.cpp b/src/videooutput_impl.cpp index 42786ba..5fe38ad 100644 --- a/src/videooutput_impl.cpp +++ b/src/videooutput_impl.cpp @@ -21,95 +21,94 @@ // ============================================================================ #include "videooutput_impl.h" #include "json_types/videooutput.h" -#include -#include #include +#include -namespace Firebolt::Videooutput +namespace Firebolt::VideoOutput { -VideooutputImpl::VideooutputImpl(Firebolt::Helpers::IHelper& helper) +VideoOutputImpl::VideoOutputImpl(Firebolt::Helpers::IHelper& helper) : helper_(helper), subscriptionManager_(helper, this) { } -Result VideooutputImpl::resolution() const +Result VideoOutputImpl::resolution() const { - return helper_.get("Videooutput.resolution"); + return helper_.get("VideoOutput.resolution"); } Result -VideooutputImpl::subscribeOnResolutionChanged(std::function&& notification) +VideoOutputImpl::subscribeOnResolutionChanged(std::function&& notification) { - return subscriptionManager_.subscribe("Videooutput.onResolutionChanged", + return subscriptionManager_.subscribe("VideoOutput.onResolutionChanged", std::move(notification)); } -Result VideooutputImpl::hdcp() const +Result VideoOutputImpl::hdcp() const { - return helper_.get, HdcpState>("Videooutput.hdcp"); + return helper_.get, HdcpState>("VideoOutput.hdcp"); } -Result VideooutputImpl::subscribeOnHdcpChanged(std::function&& notification) +Result VideoOutputImpl::subscribeOnHdcpChanged(std::function&& notification) { - return subscriptionManager_.subscribe>("Videooutput.onHdcpChanged", + return subscriptionManager_.subscribe>("VideoOutput.onHdcpChanged", std::move(notification)); } -Result VideooutputImpl::cecState() const +Result VideoOutputImpl::cecState() const { - return helper_.get, CecStateValue>("Videooutput.cecState"); + return helper_.get, CecStateValue>("VideoOutput.cecState"); } -Result VideooutputImpl::subscribeOnCecStateChanged(std::function&& notification) +Result VideoOutputImpl::subscribeOnCecStateChanged(std::function&& notification) { - return subscriptionManager_.subscribe>("Videooutput.onCecStateChanged", + return subscriptionManager_.subscribe>("VideoOutput.onCecStateChanged", std::move(notification)); } -Result VideooutputImpl::refreshRate() const +Result VideoOutputImpl::refreshRate() const { - return helper_.get, RefreshRateValue>("Videooutput.refreshRate"); + return helper_.get, RefreshRateValue>("VideoOutput.refreshRate"); } Result -VideooutputImpl::subscribeOnRefreshRateChanged(std::function&& notification) +VideoOutputImpl::subscribeOnRefreshRateChanged(std::function&& notification) { return subscriptionManager_ - .subscribe>("Videooutput.onRefreshRateChanged", + .subscribe>("VideoOutput.onRefreshRateChanged", std::move(notification)); } -Result VideooutputImpl::colorDepth() const +Result VideoOutputImpl::colorDepth() const { - return helper_.get, ColorDepthValue>("Videooutput.colorDepth"); + return helper_.get, ColorDepthValue>("VideoOutput.colorDepth"); } -Result VideooutputImpl::colorFormat() const +Result VideoOutputImpl::colorFormat() const { - return helper_.get, ColorFormatValue>("Videooutput.colorFormat"); + return helper_.get, ColorFormatValue>("VideoOutput.colorFormat"); } -Result VideooutputImpl::colorimetry() const +Result VideoOutputImpl::colorimetry() const { - return helper_.get, OutputColorimetry>("Videooutput.colorimetry"); + return helper_.get, OutputColorimetry>("VideoOutput.colorimetry"); } -Result VideooutputImpl::dynamicRange() const +Result VideoOutputImpl::dynamicRange() const { - return helper_.get, DynamicRangeValue>("Videooutput.dynamicRange"); + return helper_.get, DynamicRangeValue>("VideoOutput.dynamicRange"); } -Result VideooutputImpl::quantizationRange() const +Result VideoOutputImpl::quantizationRange() const { return helper_.get, QuantizationRangeValue>( - "Videooutput.quantizationRange"); + "VideoOutput.quantizationRange"); } -Result VideooutputImpl::unsubscribe(SubscriptionId id) +Result VideoOutputImpl::unsubscribe(SubscriptionId id) { return subscriptionManager_.unsubscribe(id); } -void VideooutputImpl::unsubscribeAll() +void VideoOutputImpl::unsubscribeAll() { subscriptionManager_.unsubscribeAll(); } -} // namespace Firebolt::Videooutput +} // namespace Firebolt::VideoOutput diff --git a/src/videooutput_impl.h b/src/videooutput_impl.h index 308897f..61a3dd0 100644 --- a/src/videooutput_impl.h +++ b/src/videooutput_impl.h @@ -25,40 +25,41 @@ #include "firebolt/videooutput.h" #include -namespace Firebolt::Videooutput +namespace Firebolt::VideoOutput { -class VideooutputImpl : public IVideooutput +class VideoOutputImpl : public IVideoOutput { public: - explicit VideooutputImpl(Firebolt::Helpers::IHelper& helper); - VideooutputImpl(const VideooutputImpl&) = delete; - VideooutputImpl& operator=(const VideooutputImpl&) = delete; - ~VideooutputImpl() override = default; - - Result resolution() const override; + explicit VideoOutputImpl(Firebolt::Helpers::IHelper& helper); + VideoOutputImpl(const VideoOutputImpl&) = delete; + VideoOutputImpl& operator=(const VideoOutputImpl&) = delete; + ~VideoOutputImpl() override = default; + VideoOutputImpl(VideoOutputImpl&&) = delete; + VideoOutputImpl& operator=(VideoOutputImpl&&) = delete; + [[nodiscard]] Result resolution() const override; Result subscribeOnResolutionChanged(std::function&& notification) override; - Result hdcp() const override; + [[nodiscard]] Result hdcp() const override; Result subscribeOnHdcpChanged(std::function&& notification) override; - Result cecState() const override; + [[nodiscard]] Result cecState() const override; Result subscribeOnCecStateChanged(std::function&& notification) override; - Result refreshRate() const override; + [[nodiscard]] Result refreshRate() const override; Result subscribeOnRefreshRateChanged(std::function&& notification) override; - Result colorDepth() const override; + [[nodiscard]] Result colorDepth() const override; - Result colorFormat() const override; + [[nodiscard]] Result colorFormat() const override; - Result colorimetry() const override; + [[nodiscard]] Result colorimetry() const override; - Result dynamicRange() const override; + [[nodiscard]] Result dynamicRange() const override; - Result quantizationRange() const override; + [[nodiscard]] Result quantizationRange() const override; Result unsubscribe(SubscriptionId id) override; void unsubscribeAll() override; @@ -68,6 +69,6 @@ class VideooutputImpl : public IVideooutput Firebolt::Helpers::SubscriptionManager subscriptionManager_; }; -} // namespace Firebolt::Videooutput +} // namespace Firebolt::VideoOutput #endif // FIREBOLT_VIDEOOUTPUT_IMPL_H diff --git a/test/component/accessibilityTest.cpp b/test/component/accessibilityTest.cpp index 856aca7..cf7d8db 100644 --- a/test/component/accessibilityTest.cpp +++ b/test/component/accessibilityTest.cpp @@ -50,7 +50,7 @@ TEST_F(AccessibilityCTest, SubscribeOnAudioDescriptionChanged) [&](const bool& enabled) { - std::cout << "[Subscription] Accessibility audio description changed" << std::endl; + std::cout << "[Subscription] Accessibility audio description changed" << '\n'; EXPECT_EQ(enabled, true); { @@ -83,7 +83,7 @@ TEST_F(AccessibilityCTest, SubscribeOnClosedCaptionsSettingsChanged) auto id = Firebolt::IFireboltAccessor::Instance().AccessibilityInterface().subscribeOnClosedCaptionsSettingsChanged( [&](const Firebolt::Accessibility::ClosedCaptionsSettings& settings) { - std::cout << "[Subscription] Accessibility closed captions settings changed" << std::endl; + std::cout << "[Subscription] Accessibility closed captions settings changed" << '\n'; EXPECT_EQ(settings.enabled, true); EXPECT_EQ(settings.preferredLanguages.size(), 2); @@ -126,7 +126,7 @@ TEST_F(AccessibilityCTest, SubscribeOnHighContrastUIChanged) auto id = Firebolt::IFireboltAccessor::Instance().AccessibilityInterface().subscribeOnHighContrastUIChanged( [&](const bool& enabled) { - std::cout << "[Subscription] Accessibility high contrast UI changed" << std::endl; + std::cout << "[Subscription] Accessibility high contrast UI changed" << '\n'; EXPECT_EQ(enabled, true); { @@ -161,7 +161,7 @@ TEST_F(AccessibilityCTest, SubscribeOnVoiceGuidanceSettingsChanged) auto id = Firebolt::IFireboltAccessor::Instance().AccessibilityInterface().subscribeOnVoiceGuidanceSettingsChanged( [&](const Firebolt::Accessibility::VoiceGuidanceSettings& settings) { - std::cout << "[Subscription] Accessibility voice guidance settings changed" << std::endl; + std::cout << "[Subscription] Accessibility voice guidance settings changed" << '\n'; EXPECT_EQ(settings.enabled, true); EXPECT_EQ(settings.rate, 0.8); diff --git a/test/component/actionsGeneratedTest.cpp b/test/component/actionsGeneratedTest.cpp index a3ce10a..ceb55a7 100644 --- a/test/component/actionsGeneratedTest.cpp +++ b/test/component/actionsGeneratedTest.cpp @@ -40,7 +40,7 @@ TEST_F(ActionsGeneratedCTest, Intent) ASSERT_TRUE(result->intent.context); ASSERT_TRUE(result->intent.context->source); EXPECT_EQ(*result->intent.context->source, "system"); - EXPECT_EQ(result->intentId, 0u); + EXPECT_EQ(result->intentId, 0U); } TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) @@ -52,7 +52,7 @@ TEST_F(ActionsGeneratedCTest, SubscribeOnIntent) ASSERT_TRUE(payload.intent.context); ASSERT_TRUE(payload.intent.context->source); EXPECT_EQ(*payload.intent.context->source, "system"); - EXPECT_EQ(payload.intentId, 0u); + EXPECT_EQ(payload.intentId, 0U); { std::lock_guard lock(mtx); eventReceived = true; diff --git a/test/component/deviceTest.cpp b/test/component/deviceTest.cpp index 62f5858..5536070 100644 --- a/test/component/deviceTest.cpp +++ b/test/component/deviceTest.cpp @@ -69,7 +69,7 @@ TEST_F(DeviceCTest, TimeInActiveState) ASSERT_TRUE(result) << "DeviceImpl::timeInActiveState() returned an error"; if (expectedValue.empty()) { - std::cout << "[ !!! ] Expected is empty, received: " << *result << std::endl; + std::cout << "[ !!! ] Expected is empty, received: " << *result << '\n'; return; } EXPECT_EQ(*result, expectedValue); @@ -90,7 +90,7 @@ TEST_F(DeviceCTest, Uptime) ASSERT_TRUE(result) << "DeviceImpl::uptime() returned an error"; if (expectedValue.empty()) { - std::cout << "[ !!! ] Expected is empty, received: " << *result << std::endl; + std::cout << "[ !!! ] Expected is empty, received: " << *result << '\n'; return; } EXPECT_EQ(*result, expectedValue); @@ -101,7 +101,7 @@ TEST_F(DeviceCTest, SubscribeOnHdrChanged) auto id = Firebolt::IFireboltAccessor::Instance().DeviceInterface().subscribeOnHdrChanged( [&](const Firebolt::Device::HDRFormat& value) { - std::cout << "[Subscription] Device HDR changed" << std::endl; + std::cout << "[Subscription] Device HDR changed" << '\n'; EXPECT_EQ(value.hdr10, true); EXPECT_EQ(value.hdr10Plus, true); EXPECT_EQ(value.dolbyVision, true); @@ -135,7 +135,7 @@ TEST_F(DeviceCTest, SubscribeOnDolbyAtmosExperienceAvailableChanged) auto id = Firebolt::IFireboltAccessor::Instance().DeviceInterface().subscribeOnDolbyAtmosExperienceAvailableChanged( [&](const bool& value) { - std::cout << "[Subscription] Device Dolby Atmos experience availability changed" << std::endl; + std::cout << "[Subscription] Device Dolby Atmos experience availability changed" << '\n'; EXPECT_EQ(value, true); { std::lock_guard lock(mtx); diff --git a/test/component/discoveryTest.cpp b/test/component/discoveryTest.cpp index 8caf34f..bb04cca 100644 --- a/test/component/discoveryTest.cpp +++ b/test/component/discoveryTest.cpp @@ -30,7 +30,7 @@ class DiscoveryCTest : public ::testing::Test TEST_F(DiscoveryCTest, Watched) { auto expectedValue = jsonEngine.get_value("Discovery.watched"); - auto result = Firebolt::IFireboltAccessor::Instance().DiscoveryInterface().watched("entity123", 0.75f, true, + auto result = Firebolt::IFireboltAccessor::Instance().DiscoveryInterface().watched("entity123", 0.75F, true, "2024-10-01T12:00:00Z", Firebolt::AgePolicy::ADULT); ASSERT_TRUE(result) << "Failed to call watched"; @@ -40,7 +40,7 @@ TEST_F(DiscoveryCTest, Watched) TEST_F(DiscoveryCTest, WatchedV2) { - auto result = Firebolt::IFireboltAccessor::Instance().DiscoveryInterface().watchedV2("entity123", 0.75f, true, + auto result = Firebolt::IFireboltAccessor::Instance().DiscoveryInterface().watchedV2("entity123", 0.75F, true, "2024-10-01T12:00:00Z", Firebolt::AgePolicy::ADULT); ASSERT_TRUE(result) << "Failed to call watchedV2"; diff --git a/test/component/lifecycleTest.cpp b/test/component/lifecycleTest.cpp index d6504c9..4047461 100644 --- a/test/component/lifecycleTest.cpp +++ b/test/component/lifecycleTest.cpp @@ -59,9 +59,9 @@ TEST_F(LifecycleCTest, subscribeOnState_JSON_RPC_compliant) auto id = Firebolt::IFireboltAccessor::Instance().LifecycleInterface().subscribeOnStateChanged( [&](const std::vector& changes) { - EXPECT_TRUE(changes.size() > 0); + EXPECT_TRUE(!changes.empty()); std::cout << "[Subscription] Lifecycle state changed: " << static_cast(changes[0].newState) - << ", old state: " << static_cast(changes[0].oldState) << std::endl; + << ", old state: " << static_cast(changes[0].oldState) << '\n'; EXPECT_EQ(changes[0].newState, Firebolt::Lifecycle::LifecycleState::PAUSED); EXPECT_EQ(changes[0].oldState, Firebolt::Lifecycle::LifecycleState::INITIALIZING); @@ -91,9 +91,9 @@ TEST_F(LifecycleCTest, subscribeOnState_noValue) auto id = Firebolt::IFireboltAccessor::Instance().LifecycleInterface().subscribeOnStateChanged( [&](const std::vector& changes) { - EXPECT_TRUE(changes.size() > 0); + EXPECT_TRUE(!changes.empty()); std::cout << "[Subscription] Lifecycle state changed: " << static_cast(changes[0].newState) - << ", old state: " << static_cast(changes[0].oldState) << std::endl; + << ", old state: " << static_cast(changes[0].oldState) << '\n'; EXPECT_EQ(changes[0].newState, Firebolt::Lifecycle::LifecycleState::PAUSED); EXPECT_EQ(changes[0].oldState, Firebolt::Lifecycle::LifecycleState::INITIALIZING); @@ -109,7 +109,7 @@ TEST_F(LifecycleCTest, subscribeOnState_noValue) Firebolt::Config config; if (config.legacyRPCv1) { - std::cout << "Commented out as it cannot be tested in CI/CI due to unknown 'id' value" << std::endl; + std::cout << "Commented out as it cannot be tested in CI/CI due to unknown 'id' value" << '\n'; /* nlohmann::json p; p["id"] = 30; diff --git a/test/component/metricsTest.cpp b/test/component/metricsTest.cpp index 9e1d6fc..6c1e9b7 100644 --- a/test/component/metricsTest.cpp +++ b/test/component/metricsTest.cpp @@ -200,7 +200,7 @@ TEST_F(MetricsCTest, MediaEnded) TEST_F(MetricsCTest, Event) { auto result = Firebolt::IFireboltAccessor::Instance().MetricsInterface().event("https://com.example.event", - "{\"key\":\"value\"}", + R"({"key":"value"})", Firebolt::AgePolicy::ADULT); ASSERT_TRUE(result) << "MetricsImpl::event() returned an error"; } diff --git a/test/component/networkTest.cpp b/test/component/networkTest.cpp index aedf44c..ae74fb2 100644 --- a/test/component/networkTest.cpp +++ b/test/component/networkTest.cpp @@ -47,7 +47,7 @@ TEST_F(NetworkCTest, SubscribeOnConnectedChanged) auto id = Firebolt::IFireboltAccessor::Instance().NetworkInterface().subscribeOnConnectedChanged( [&](const bool& value) { - std::cout << "[Subscription] Network connected changed" << std::endl; + std::cout << "[Subscription] Network connected changed" << '\n'; EXPECT_EQ(value, true); { std::lock_guard lock(mtx); diff --git a/test/component/presentationTest.cpp b/test/component/presentationTest.cpp index a4bc8f5..d909b61 100644 --- a/test/component/presentationTest.cpp +++ b/test/component/presentationTest.cpp @@ -73,7 +73,7 @@ TEST_F(PresentationCTest, unsubscribeInCallback) Firebolt::IFireboltAccessor::Instance().PresentationInterface().subscribeOnFocusedChanged( [&](const bool& /* focus */) { - std::cout << "In the callback, unsubscribing from the event" << subscriptionId << std::endl; + std::cout << "In the callback, unsubscribing from the event" << subscriptionId << '\n'; auto result = Firebolt::IFireboltAccessor::Instance().PresentationInterface().unsubscribe(subscriptionId); verifyUnsubscribeResult(result); { diff --git a/test/component/videooutputGeneratedTest.cpp b/test/component/videooutputGeneratedTest.cpp index fef7d12..b2f9958 100644 --- a/test/component/videooutputGeneratedTest.cpp +++ b/test/component/videooutputGeneratedTest.cpp @@ -21,7 +21,7 @@ TEST(VideooutputGeneratedCTest, InterfaceSurfaceHasresolution) { - using Interface = Firebolt::Videooutput::IVideooutput; + using Interface = Firebolt::VideoOutput::IVideoOutput; auto ptr = &Interface::resolution; (void)ptr; SUCCEED(); @@ -29,7 +29,7 @@ TEST(VideooutputGeneratedCTest, InterfaceSurfaceHasresolution) TEST(VideooutputGeneratedCTest, InterfaceSurfaceHascolorDepth) { - using Interface = Firebolt::Videooutput::IVideooutput; + using Interface = Firebolt::VideoOutput::IVideoOutput; auto ptr = &Interface::colorDepth; (void)ptr; SUCCEED(); diff --git a/test/unit/actionsTest.cpp b/test/unit/actionsTest.cpp index 12a9089..98936f7 100644 --- a/test/unit/actionsTest.cpp +++ b/test/unit/actionsTest.cpp @@ -32,7 +32,7 @@ TEST_F(ActionsUTest, Intent) { mock_with_response("Actions.intent", nlohmann::json({{"intent", {{"action", "pre-load"}, {"context", {{"source", "system"}}}}}, - {"intentId", 0u}})); + {"intentId", 0U}})); auto result = actionsImpl_.intent(); ASSERT_TRUE(result) << "ActionsImpl::intent() returned an error"; @@ -40,7 +40,7 @@ TEST_F(ActionsUTest, Intent) ASSERT_TRUE(result->intent.context); ASSERT_TRUE(result->intent.context->source); EXPECT_EQ(*result->intent.context->source, "system"); - EXPECT_EQ(result->intentId, 0u); + EXPECT_EQ(result->intentId, 0U); } TEST_F(ActionsUTest, SubscribeOnIntent) diff --git a/test/unit/discoveryTest.cpp b/test/unit/discoveryTest.cpp index 09dd9da..aee6363 100644 --- a/test/unit/discoveryTest.cpp +++ b/test/unit/discoveryTest.cpp @@ -38,7 +38,7 @@ TEST_F(DiscoveryUTest, watched) { mock("Discovery.watched"); std::string entityId = "content123"; - std::optional progress = 0.75f; + std::optional progress = 0.75F; std::optional completed = true; std::optional watchedOn = "2024-06-01T12:00:00Z"; std::optional agePolicy = Firebolt::AgePolicy::ADULT; @@ -53,7 +53,7 @@ TEST_F(DiscoveryUTest, watched_payload) { nlohmann::json expected; expected["entityId"] = "content123"; - expected["progress"] = 0.75f; + expected["progress"] = 0.75F; expected["completed"] = true; expected["watchedOn"] = "2024-06-01T12:00:00Z"; expected["agePolicy"] = "app:adult"; @@ -67,7 +67,7 @@ TEST_F(DiscoveryUTest, watched_payload) return Firebolt::Result{nlohmann::json(res)}; })); std::string entityId = "content123"; - std::optional progress = 0.75f; + std::optional progress = 0.75F; std::optional completed = true; std::optional watchedOn = "2024-06-01T12:00:00Z"; std::optional agePolicy = Firebolt::AgePolicy::ADULT; @@ -80,7 +80,7 @@ TEST_F(DiscoveryUTest, watchedV2) { mockInvoke("Discovery.watched"); std::string entityId = "content123"; - std::optional progress = 0.75f; + std::optional progress = 0.75F; std::optional completed = true; std::optional watchedOn = "2024-06-01T12:00:00Z"; std::optional agePolicy = Firebolt::AgePolicy::ADULT; @@ -92,7 +92,7 @@ TEST_F(DiscoveryUTest, watchedV2_payload) { nlohmann::json expected; expected["entityId"] = "content123"; - expected["progress"] = 0.75f; + expected["progress"] = 0.75F; expected["completed"] = true; expected["watchedOn"] = "2024-06-01T12:00:00Z"; expected["agePolicy"] = "app:adult"; @@ -105,7 +105,7 @@ TEST_F(DiscoveryUTest, watchedV2_payload) return Firebolt::Result{Firebolt::Error::None}; })); std::string entityId = "content123"; - std::optional progress = 0.75f; + std::optional progress = 0.75F; std::optional completed = true; std::optional watchedOn = "2024-06-01T12:00:00Z"; std::optional agePolicy = Firebolt::AgePolicy::ADULT; diff --git a/test/unit/metricsTest.cpp b/test/unit/metricsTest.cpp index a7dbcab..627ec7e 100644 --- a/test/unit/metricsTest.cpp +++ b/test/unit/metricsTest.cpp @@ -173,7 +173,7 @@ TEST_F(MetricsUTest, MediaEnded) TEST_F(MetricsUTest, Event) { mockInvoke("Metrics.event"); - auto result = metricsImpl_.event("https://com.example.schema", "{\"key\":\"value\"}", Firebolt::AgePolicy::ADULT); + auto result = metricsImpl_.event("https://com.example.schema", R"({"key":"value"})", Firebolt::AgePolicy::ADULT); EXPECT_TRUE(result); } diff --git a/test/unit/videooutputGeneratedTest.cpp b/test/unit/videooutputGeneratedTest.cpp index f11463e..958291e 100644 --- a/test/unit/videooutputGeneratedTest.cpp +++ b/test/unit/videooutputGeneratedTest.cpp @@ -24,7 +24,7 @@ class VideooutputGeneratedUTest : public ::testing::Test { protected: ::testing::NiceMock mockHelper; - Firebolt::Videooutput::VideooutputImpl impl{mockHelper}; + Firebolt::VideoOutput::VideoOutputImpl impl{mockHelper}; }; TEST_F(VideooutputGeneratedUTest, Constructs) @@ -42,7 +42,7 @@ TEST_F(VideooutputGeneratedUTest, UnsubscribeForwardsToHelper) TEST_F(VideooutputGeneratedUTest, ForwardsresolutionTransportErrors) { - EXPECT_CALL(mockHelper, getJson("Videooutput.resolution", ::testing::_)) + EXPECT_CALL(mockHelper, getJson("VideoOutput.resolution", ::testing::_)) .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& /*params*/) { return Firebolt::Result{Firebolt::Error::General}; })); @@ -52,7 +52,7 @@ TEST_F(VideooutputGeneratedUTest, ForwardsresolutionTransportErrors) TEST_F(VideooutputGeneratedUTest, ForwardscolorDepthTransportErrors) { - EXPECT_CALL(mockHelper, getJson("Videooutput.colorDepth", ::testing::_)) + EXPECT_CALL(mockHelper, getJson("VideoOutput.colorDepth", ::testing::_)) .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& /*params*/) { return Firebolt::Result{Firebolt::Error::General}; })); From 6acda2d60ba499d5ab81c6f96c35d3d60da04bab Mon Sep 17 00:00:00 2001 From: bobra200 Date: Thu, 6 Aug 2026 10:21:58 -0700 Subject: [PATCH 18/39] RDKEMW-14869: fixing marshallers --- src/json_types/videooutput.h | 156 +++++++++++++++++------------------ 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/src/json_types/videooutput.h b/src/json_types/videooutput.h index 9faa931..538a1d9 100644 --- a/src/json_types/videooutput.h +++ b/src/json_types/videooutput.h @@ -31,132 +31,132 @@ namespace Firebolt::VideoOutput { NLOHMANN_JSON_SERIALIZE_ENUM(CecStateValue, { - {CecStateValue::Active, "ACTIVE"}, - {CecStateValue::Inactive, "INACTIVE"}, - {CecStateValue::Unsupported, "UNSUPPORTED"}, + {CecStateValue::Active, "active"}, + {CecStateValue::Inactive, "inactive"}, + {CecStateValue::Unsupported, "unsupported"}, }) NLOHMANN_JSON_SERIALIZE_ENUM(ColorDepthValue, { - {ColorDepthValue::D0, "D0"}, - {ColorDepthValue::D10, "D10"}, - {ColorDepthValue::D12, "D12"}, - {ColorDepthValue::D8, "D8"}, + {ColorDepthValue::D0, "0"}, + {ColorDepthValue::D10, "10"}, + {ColorDepthValue::D12, "12"}, + {ColorDepthValue::D8, "8"}, }) NLOHMANN_JSON_SERIALIZE_ENUM(ColorFormatValue, { - {ColorFormatValue::None, "NONE"}, - {ColorFormatValue::Rgb444, "RGB444"}, - {ColorFormatValue::Ycbcr420, "YCBCR420"}, - {ColorFormatValue::Ycbcr422, "YCBCR422"}, - {ColorFormatValue::Ycbcr444, "YCBCR444"}, + {ColorFormatValue::None, "none"}, + {ColorFormatValue::Rgb444, "rgb444"}, + {ColorFormatValue::Ycbcr420, "ycbcr420"}, + {ColorFormatValue::Ycbcr422, "ycbcr422"}, + {ColorFormatValue::Ycbcr444, "ycbcr444"}, }) NLOHMANN_JSON_SERIALIZE_ENUM(DynamicRangeValue, { - {DynamicRangeValue::DolbyVision, "DOLBY_VISION"}, - {DynamicRangeValue::Hdr10, "HDR10"}, - {DynamicRangeValue::Hdr10plus, "HDR10PLUS"}, - {DynamicRangeValue::Hlg, "HLG"}, - {DynamicRangeValue::None, "NONE"}, - {DynamicRangeValue::Sdr, "SDR"}, + {DynamicRangeValue::DolbyVision, "dolby_vision"}, + {DynamicRangeValue::Hdr10, "hdr10"}, + {DynamicRangeValue::Hdr10plus, "hdr10plus"}, + {DynamicRangeValue::Hlg, "hlg"}, + {DynamicRangeValue::None, "none"}, + {DynamicRangeValue::Sdr, "sdr"}, }) NLOHMANN_JSON_SERIALIZE_ENUM(HdcpState, { - {HdcpState::Direct, "DIRECT"}, - {HdcpState::Hdcp14, "HDCP14"}, - {HdcpState::Hdcp22, "HDCP22"}, - {HdcpState::None, "NONE"}, + {HdcpState::Direct, "direct"}, + {HdcpState::Hdcp14, "hdcp14"}, + {HdcpState::Hdcp22, "hdcp22"}, + {HdcpState::None, "none"}, }) NLOHMANN_JSON_SERIALIZE_ENUM(OutputColorimetry, { - {OutputColorimetry::Bt2020rgb, "BT2020RGB"}, - {OutputColorimetry::Bt2020ycc, "BT2020YCC"}, - {OutputColorimetry::Bt709, "BT709"}, - {OutputColorimetry::None, "NONE"}, - {OutputColorimetry::Oprgb, "OPRGB"}, + {OutputColorimetry::Bt2020rgb, "bt2020rgb"}, + {OutputColorimetry::Bt2020ycc, "bt2020ycc"}, + {OutputColorimetry::Bt709, "bt709"}, + {OutputColorimetry::None, "none"}, + {OutputColorimetry::Oprgb, "oprgb"}, }) NLOHMANN_JSON_SERIALIZE_ENUM(QuantizationRangeValue, { - {QuantizationRangeValue::Full, "FULL"}, - {QuantizationRangeValue::Limited, "LIMITED"}, - {QuantizationRangeValue::None, "NONE"}, + {QuantizationRangeValue::Full, "full"}, + {QuantizationRangeValue::Limited, "limited"}, + {QuantizationRangeValue::None, "none"}, }) NLOHMANN_JSON_SERIALIZE_ENUM(RefreshRateValue, { - {RefreshRateValue::R0, "R0"}, - {RefreshRateValue::R23976, "R23_976"}, - {RefreshRateValue::R24, "R24"}, - {RefreshRateValue::R25, "R25"}, - {RefreshRateValue::R2997, "R29_97"}, - {RefreshRateValue::R30, "R30"}, - {RefreshRateValue::R50, "R50"}, - {RefreshRateValue::R5994, "R59_94"}, - {RefreshRateValue::R60, "R60"}, + {RefreshRateValue::R0, "0"}, + {RefreshRateValue::R23976, "23.976"}, + {RefreshRateValue::R24, "24"}, + {RefreshRateValue::R25, "25"}, + {RefreshRateValue::R2997, "29.97"}, + {RefreshRateValue::R30, "30"}, + {RefreshRateValue::R50, "50"}, + {RefreshRateValue::R5994, "59.94"}, + {RefreshRateValue::R60, "60"}, }) namespace JsonData { inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::CecStateValue> CecStateValueEnum({ - {"ACTIVE", ::Firebolt::VideoOutput::CecStateValue::Active}, - {"INACTIVE", ::Firebolt::VideoOutput::CecStateValue::Inactive}, - {"UNSUPPORTED", ::Firebolt::VideoOutput::CecStateValue::Unsupported}, + {"active", ::Firebolt::VideoOutput::CecStateValue::Active}, + {"inactive", ::Firebolt::VideoOutput::CecStateValue::Inactive}, + {"unsupported", ::Firebolt::VideoOutput::CecStateValue::Unsupported}, }); inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::ColorDepthValue> ColorDepthValueEnum({ - {"D0", ::Firebolt::VideoOutput::ColorDepthValue::D0}, - {"D10", ::Firebolt::VideoOutput::ColorDepthValue::D10}, - {"D12", ::Firebolt::VideoOutput::ColorDepthValue::D12}, - {"D8", ::Firebolt::VideoOutput::ColorDepthValue::D8}, + {"0", ::Firebolt::VideoOutput::ColorDepthValue::D0}, + {"10", ::Firebolt::VideoOutput::ColorDepthValue::D10}, + {"12", ::Firebolt::VideoOutput::ColorDepthValue::D12}, + {"8", ::Firebolt::VideoOutput::ColorDepthValue::D8}, }); inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::ColorFormatValue> ColorFormatValueEnum({ - {"NONE", ::Firebolt::VideoOutput::ColorFormatValue::None}, - {"RGB444", ::Firebolt::VideoOutput::ColorFormatValue::Rgb444}, - {"YCBCR420", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr420}, - {"YCBCR422", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr422}, - {"YCBCR444", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr444}, + {"none", ::Firebolt::VideoOutput::ColorFormatValue::None}, + {"rgbb444", ::Firebolt::VideoOutput::ColorFormatValue::Rgb444}, + {"ycbcr420", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr420}, + {"ycbcr422", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr422}, + {"ycbcr444", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr444}, }); inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::DynamicRangeValue> DynamicRangeValueEnum({ - {"DOLBY_VISION", ::Firebolt::VideoOutput::DynamicRangeValue::DolbyVision}, - {"HDR10", ::Firebolt::VideoOutput::DynamicRangeValue::Hdr10}, - {"HDR10PLUS", ::Firebolt::VideoOutput::DynamicRangeValue::Hdr10plus}, - {"HLG", ::Firebolt::VideoOutput::DynamicRangeValue::Hlg}, - {"NONE", ::Firebolt::VideoOutput::DynamicRangeValue::None}, - {"SDR", ::Firebolt::VideoOutput::DynamicRangeValue::Sdr}, + {"dolbyVision", ::Firebolt::VideoOutput::DynamicRangeValue::DolbyVision}, + {"hdr10", ::Firebolt::VideoOutput::DynamicRangeValue::Hdr10}, + {"hdr10plus", ::Firebolt::VideoOutput::DynamicRangeValue::Hdr10plus}, + {"hlg", ::Firebolt::VideoOutput::DynamicRangeValue::Hlg}, + {"none", ::Firebolt::VideoOutput::DynamicRangeValue::None}, + {"sdr", ::Firebolt::VideoOutput::DynamicRangeValue::Sdr}, }); inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::HdcpState> HdcpStateEnum({ - {"DIRECT", ::Firebolt::VideoOutput::HdcpState::Direct}, - {"HDCP14", ::Firebolt::VideoOutput::HdcpState::Hdcp14}, - {"HDCP22", ::Firebolt::VideoOutput::HdcpState::Hdcp22}, - {"NONE", ::Firebolt::VideoOutput::HdcpState::None}, + {"direct", ::Firebolt::VideoOutput::HdcpState::Direct}, + {"hdcp1.4", ::Firebolt::VideoOutput::HdcpState::Hdcp14}, + {"hdcp2.2", ::Firebolt::VideoOutput::HdcpState::Hdcp22}, + {"none", ::Firebolt::VideoOutput::HdcpState::None}, }); inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::OutputColorimetry> OutputColorimetryEnum({ - {"BT2020RGB", ::Firebolt::VideoOutput::OutputColorimetry::Bt2020rgb}, - {"BT2020YCC", ::Firebolt::VideoOutput::OutputColorimetry::Bt2020ycc}, - {"BT709", ::Firebolt::VideoOutput::OutputColorimetry::Bt709}, - {"NONE", ::Firebolt::VideoOutput::OutputColorimetry::None}, - {"OPRGB", ::Firebolt::VideoOutput::OutputColorimetry::Oprgb}, + {"bt2020rgb", ::Firebolt::VideoOutput::OutputColorimetry::Bt2020rgb}, + {"bt2020ycc", ::Firebolt::VideoOutput::OutputColorimetry::Bt2020ycc}, + {"bt709", ::Firebolt::VideoOutput::OutputColorimetry::Bt709}, + {"none", ::Firebolt::VideoOutput::OutputColorimetry::None}, + {"oprgb", ::Firebolt::VideoOutput::OutputColorimetry::Oprgb}, }); inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::QuantizationRangeValue> QuantizationRangeValueEnum({ - {"FULL", ::Firebolt::VideoOutput::QuantizationRangeValue::Full}, - {"LIMITED", ::Firebolt::VideoOutput::QuantizationRangeValue::Limited}, - {"NONE", ::Firebolt::VideoOutput::QuantizationRangeValue::None}, + {"full", ::Firebolt::VideoOutput::QuantizationRangeValue::Full}, + {"limited", ::Firebolt::VideoOutput::QuantizationRangeValue::Limited}, + {"none", ::Firebolt::VideoOutput::QuantizationRangeValue::None}, }); inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::RefreshRateValue> RefreshRateValueEnum({ - {"R0", ::Firebolt::VideoOutput::RefreshRateValue::R0}, - {"R23_976", ::Firebolt::VideoOutput::RefreshRateValue::R23976}, - {"R24", ::Firebolt::VideoOutput::RefreshRateValue::R24}, - {"R25", ::Firebolt::VideoOutput::RefreshRateValue::R25}, - {"R29_97", ::Firebolt::VideoOutput::RefreshRateValue::R2997}, - {"R30", ::Firebolt::VideoOutput::RefreshRateValue::R30}, - {"R50", ::Firebolt::VideoOutput::RefreshRateValue::R50}, - {"R59_94", ::Firebolt::VideoOutput::RefreshRateValue::R5994}, - {"R60", ::Firebolt::VideoOutput::RefreshRateValue::R60}, + {"0", ::Firebolt::VideoOutput::RefreshRateValue::R0}, + {"23.976", ::Firebolt::VideoOutput::RefreshRateValue::R23976}, + {"24", ::Firebolt::VideoOutput::RefreshRateValue::R24}, + {"25", ::Firebolt::VideoOutput::RefreshRateValue::R25}, + {"29.97", ::Firebolt::VideoOutput::RefreshRateValue::R2997}, + {"30", ::Firebolt::VideoOutput::RefreshRateValue::R30}, + {"50", ::Firebolt::VideoOutput::RefreshRateValue::R50}, + {"59.94", ::Firebolt::VideoOutput::RefreshRateValue::R5994}, + {"60", ::Firebolt::VideoOutput::RefreshRateValue::R60}, }); class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::VideoOutput::VideoOutputResolution> From fb712c786c8289bc8f1bbd365adad3cb1c780a69 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Fri, 7 Aug 2026 07:35:02 -0700 Subject: [PATCH 19/39] RDKEMW-14869: chore, lint fix --- include/firebolt/actions.h | 3 ++- include/firebolt/discovery.h | 8 ++++---- include/firebolt/metrics.h | 39 +++++++++++++++++++----------------- src/actions_impl.h | 3 ++- src/discovery_impl.h | 12 +++++------ src/firebolt.cpp | 1 - src/json_types/actions.h | 9 +++------ src/json_types/advertising.h | 5 ++++- src/json_types/display.h | 5 ++++- src/lifecycle_impl.h | 1 - src/metrics_impl.h | 35 ++++++++++++++++---------------- src/videooutput_impl.cpp | 2 +- 12 files changed, 65 insertions(+), 58 deletions(-) diff --git a/include/firebolt/actions.h b/include/firebolt/actions.h index 68bb1f3..543320c 100644 --- a/include/firebolt/actions.h +++ b/include/firebolt/actions.h @@ -64,7 +64,8 @@ class IActions virtual Result unsubscribe(SubscriptionId id) = 0; virtual void unsubscribeAll() = 0; - [[nodiscard]] virtual Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const = 0; + [[nodiscard]] virtual Result start(const IntentData& intent, + std::optional handlerAppId = std::nullopt) const = 0; }; // class IActions diff --git a/include/firebolt/discovery.h b/include/firebolt/discovery.h index 334ef39..aced324 100644 --- a/include/firebolt/discovery.h +++ b/include/firebolt/discovery.h @@ -46,8 +46,8 @@ class IDiscovery * redundant boolean payload. */ [[nodiscard]] virtual Result watched(const std::string& entityId, std::optional progress, - std::optional completed, std::optional watchedOn, - std::optional agePolicy) const = 0; + std::optional completed, std::optional watchedOn, + std::optional agePolicy) const = 0; /** * @brief Notify the platform that content was partially or completely watched @@ -63,7 +63,7 @@ class IDiscovery * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result watchedV2(const std::string& entityId, std::optional progress, - std::optional completed, std::optional watchedOn, - std::optional agePolicy) const = 0; + std::optional completed, std::optional watchedOn, + std::optional agePolicy) const = 0; }; } // namespace Firebolt::Discovery diff --git a/include/firebolt/metrics.h b/include/firebolt/metrics.h index b15d998..23b8673 100644 --- a/include/firebolt/metrics.h +++ b/include/firebolt/metrics.h @@ -71,7 +71,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result startContent(const std::optional& entityId, - std::optional agePolicy) const = 0; + std::optional agePolicy) const = 0; /** * @brief Informs the platform that your user has stopped content @@ -83,7 +83,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result stopContent(const std::optional& entityId, - std::optional agePolicy) const = 0; + std::optional agePolicy) const = 0; /** * @brief Informs the platform that your user has navigated to a page or view @@ -94,7 +94,8 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - [[nodiscard]] virtual Result page(const std::string& pageId, const std::optional& agePolicy) const = 0; + [[nodiscard]] virtual Result page(const std::string& pageId, + const std::optional& agePolicy) const = 0; /** * @brief Informs the platform of an error that has occurred in your app @@ -110,8 +111,9 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result error(ErrorType type, const std::string& code, const std::string& description, - bool visible, const std::optional>& parameters, - const std::optional& agePolicy) const = 0; + bool visible, + const std::optional>& parameters, + const std::optional& agePolicy) const = 0; /** * @brief Called when setting the URL of a media asset to play, in order to infer load time @@ -123,7 +125,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaLoadStart(const std::string& entityId, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called when media playback actually starts due to autoplay, user-initiated play, unpausing, or recovering @@ -136,7 +138,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaPlaying(const std::string& entityId, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called when media playback should start due to autoplay, user-initiated play, or unpausing @@ -148,7 +150,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaPlay(const std::string& entityId, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called when media playback will pause due to an intentional pause operation @@ -160,7 +162,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaPause(const std::string& entityId, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called when media playback will halt due to a network, buffer, or other unintentional constraint @@ -172,7 +174,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaWaiting(const std::string& entityId, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called when a seek is initiated during media playback @@ -186,7 +188,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaSeeking(const std::string& entityId, double target, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called when a seek is completed during media playback @@ -201,7 +203,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaSeeked(const std::string& entityId, double position, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called when the playback rate of media is changed @@ -214,7 +216,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaRateChanged(const std::string& entityId, double rate, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called when the rendition of media is changed, such as bitrate, dimensions, or profile @@ -229,9 +231,10 @@ class IMetrics * * @retval An ok Result on success, or an error; no value is returned */ - [[nodiscard]] virtual Result mediaRenditionChanged(const std::string& entityId, unsigned bitrate, unsigned width, - unsigned height, const std::optional& profile, - const std::optional& agePolicy) const = 0; + [[nodiscard]] virtual Result + mediaRenditionChanged(const std::string& entityId, unsigned bitrate, unsigned width, unsigned height, + const std::optional& profile, + const std::optional& agePolicy) const = 0; /** * @brief Called when playback has stopped because the end of the media was reached @@ -243,7 +246,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result mediaEnded(const std::string& entityId, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Called to inform the platform of 1st party distributor metrics @@ -256,7 +259,7 @@ class IMetrics * @retval An ok Result on success, or an error; no value is returned */ [[nodiscard]] virtual Result event(const std::string& schema, const std::string& data, - const std::optional& agePolicy) const = 0; + const std::optional& agePolicy) const = 0; /** * @brief Inform the platform about an app's build info diff --git a/src/actions_impl.h b/src/actions_impl.h index edfe2c0..a61d03b 100644 --- a/src/actions_impl.h +++ b/src/actions_impl.h @@ -40,7 +40,8 @@ class ActionsImpl : public IActions Result subscribeOnIntent(std::function&& notification) override; - [[nodiscard]] Result start(const IntentData& intent, std::optional handlerAppId = std::nullopt) const override; + [[nodiscard]] Result start(const IntentData& intent, + std::optional handlerAppId = std::nullopt) const override; Result unsubscribe(SubscriptionId id) override; void unsubscribeAll() override; diff --git a/src/discovery_impl.h b/src/discovery_impl.h index 7eafde1..6f864ec 100644 --- a/src/discovery_impl.h +++ b/src/discovery_impl.h @@ -33,13 +33,13 @@ class DiscoveryImpl : public IDiscovery ~DiscoveryImpl() override = default; - [[nodiscard]] Result watched(const std::string& entityId, std::optional progress, std::optional completed, - std::optional watchedOn, - std::optional agePolicy) const override; + [[nodiscard]] Result watched(const std::string& entityId, std::optional progress, + std::optional completed, std::optional watchedOn, + std::optional agePolicy) const override; - [[nodiscard]] Result watchedV2(const std::string& entityId, std::optional progress, std::optional completed, - std::optional watchedOn, - std::optional agePolicy) const override; + [[nodiscard]] Result watchedV2(const std::string& entityId, std::optional progress, + std::optional completed, std::optional watchedOn, + std::optional agePolicy) const override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/src/firebolt.cpp b/src/firebolt.cpp index cdbb797..140d8ef 100644 --- a/src/firebolt.cpp +++ b/src/firebolt.cpp @@ -103,7 +103,6 @@ class FireboltAccessorImpl : public IFireboltAccessor videooutput_.unsubscribeAll(); } - Accessibility::AccessibilityImpl accessibility_; Advertising::AdvertisingImpl advertising_; Actions::ActionsImpl actions_; diff --git a/src/json_types/actions.h b/src/json_types/actions.h index 1995282..0310384 100644 --- a/src/json_types/actions.h +++ b/src/json_types/actions.h @@ -28,8 +28,6 @@ #include #include - - namespace Firebolt::Actions::JsonData { @@ -50,9 +48,10 @@ class JsonValue : public Firebolt::JSON::NL_Json_Basic if (json["intent"].contains("context") && json["intent"]["context"].is_object()) { IntentContext ctx; - if (json["intent"]["context"].contains("source")) { + if (json["intent"]["context"].contains("source")) + { ctx.source = json["intent"]["context"]["source"].get(); -} + } value_.intent.context = ctx; } value_.intentId = json["intentId"].get(); @@ -65,6 +64,4 @@ class JsonValue : public Firebolt::JSON::NL_Json_Basic } // namespace Firebolt::Actions::JsonData - - #endif // FIREBOLT_ACTIONS_JSON_H diff --git a/src/json_types/advertising.h b/src/json_types/advertising.h index decd700..3036557 100644 --- a/src/json_types/advertising.h +++ b/src/json_types/advertising.h @@ -38,7 +38,10 @@ class IfaJson : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Advertising::If ifa_type = json["ifa_type"].get(); lmt = json["lmt"].get(); } - [[nodiscard]] ::Firebolt::Advertising::Ifa value() const override { return ::Firebolt::Advertising::Ifa{ifa, ifa_type, lmt}; } + [[nodiscard]] ::Firebolt::Advertising::Ifa value() const override + { + return ::Firebolt::Advertising::Ifa{ifa, ifa_type, lmt}; + } private: std::string ifa; diff --git a/src/json_types/display.h b/src/json_types/display.h index 90f850a..40877c2 100644 --- a/src/json_types/display.h +++ b/src/json_types/display.h @@ -38,7 +38,10 @@ class DisplaySizeJson : public Firebolt::JSON::NL_Json_Basic<::Firebolt::Display width_ = json["width"].get(); height_ = json["height"].get(); } - [[nodiscard]] ::Firebolt::Display::DisplaySize value() const override { return Firebolt::Display::DisplaySize{width_, height_}; } + [[nodiscard]] ::Firebolt::Display::DisplaySize value() const override + { + return Firebolt::Display::DisplaySize{width_, height_}; + } private: uint32_t width_; diff --git a/src/lifecycle_impl.h b/src/lifecycle_impl.h index 8ae612f..6135381 100644 --- a/src/lifecycle_impl.h +++ b/src/lifecycle_impl.h @@ -49,7 +49,6 @@ class LifecycleImpl : public ILifecycle void unsubscribeAll() override; private: - Firebolt::Helpers::IHelper& helper_; Firebolt::Helpers::SubscriptionManager subscriptionManager_; diff --git a/src/metrics_impl.h b/src/metrics_impl.h index 41904e5..1bccfa6 100644 --- a/src/metrics_impl.h +++ b/src/metrics_impl.h @@ -36,36 +36,37 @@ class MetricsImpl : public IMetrics [[nodiscard]] Result signIn() const override; [[nodiscard]] Result signOut() const override; [[nodiscard]] Result startContent(const std::optional& entityId, - std::optional agePolicy) const override; + std::optional agePolicy) const override; [[nodiscard]] Result stopContent(const std::optional& entityId, - std::optional agePolicy) const override; - [[nodiscard]] Result page(const std::string& pageId, const std::optional& agePolicy) const override; + std::optional agePolicy) const override; + [[nodiscard]] Result page(const std::string& pageId, + const std::optional& agePolicy) const override; [[nodiscard]] Result error(ErrorType type, const std::string& code, const std::string& description, - bool visible, const std::optional>& parameters, - const std::optional& agePolicy) const override; + bool visible, const std::optional>& parameters, + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaLoadStart(const std::string& entityId, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaPlay(const std::string& entityId, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaPlaying(const std::string& entityId, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaPause(const std::string& entityId, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaWaiting(const std::string& entityId, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaSeeking(const std::string& entityId, double target, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaSeeked(const std::string& entityId, double position, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaRateChanged(const std::string& entityId, double rate, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaRenditionChanged(const std::string& entityId, unsigned bitrate, unsigned width, - unsigned height, const std::optional& profile, - const std::optional& agePolicy) const override; + unsigned height, const std::optional& profile, + const std::optional& agePolicy) const override; [[nodiscard]] Result mediaEnded(const std::string& entityId, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result event(const std::string& schema, const std::string& data, - const std::optional& agePolicy) const override; + const std::optional& agePolicy) const override; [[nodiscard]] Result appInfo(const std::string& build) const override; private: diff --git a/src/videooutput_impl.cpp b/src/videooutput_impl.cpp index 5fe38ad..00059a2 100644 --- a/src/videooutput_impl.cpp +++ b/src/videooutput_impl.cpp @@ -21,8 +21,8 @@ // ============================================================================ #include "videooutput_impl.h" #include "json_types/videooutput.h" -#include #include +#include namespace Firebolt::VideoOutput { From c90a90c59db8b9e24de8625506dd47556d8b821d Mon Sep 17 00:00:00 2001 From: bobra200 Date: Fri, 7 Aug 2026 09:51:01 -0700 Subject: [PATCH 20/39] RDKEMW-14869: responding to PR feedback --- src/json_types/videooutput.h | 9 ++++----- src/videooutput_impl.cpp | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/json_types/videooutput.h b/src/json_types/videooutput.h index 538a1d9..7698e95 100644 --- a/src/json_types/videooutput.h +++ b/src/json_types/videooutput.h @@ -25,7 +25,6 @@ #include "firebolt/videooutput.h" #include #include -#include namespace Firebolt::VideoOutput { @@ -52,7 +51,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(ColorFormatValue, { }) NLOHMANN_JSON_SERIALIZE_ENUM(DynamicRangeValue, { - {DynamicRangeValue::DolbyVision, "dolby_vision"}, + {DynamicRangeValue::DolbyVision, "dolbyVision"}, {DynamicRangeValue::Hdr10, "hdr10"}, {DynamicRangeValue::Hdr10plus, "hdr10plus"}, {DynamicRangeValue::Hlg, "hlg"}, @@ -62,8 +61,8 @@ NLOHMANN_JSON_SERIALIZE_ENUM(DynamicRangeValue, { NLOHMANN_JSON_SERIALIZE_ENUM(HdcpState, { {HdcpState::Direct, "direct"}, - {HdcpState::Hdcp14, "hdcp14"}, - {HdcpState::Hdcp22, "hdcp22"}, + {HdcpState::Hdcp14, "hdcp1.4"}, + {HdcpState::Hdcp22, "hdcp2.2"}, {HdcpState::None, "none"}, }) @@ -111,7 +110,7 @@ inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::ColorDepthValue> inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::ColorFormatValue> ColorFormatValueEnum({ {"none", ::Firebolt::VideoOutput::ColorFormatValue::None}, - {"rgbb444", ::Firebolt::VideoOutput::ColorFormatValue::Rgb444}, + {"rgb444", ::Firebolt::VideoOutput::ColorFormatValue::Rgb444}, {"ycbcr420", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr420}, {"ycbcr422", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr422}, {"ycbcr444", ::Firebolt::VideoOutput::ColorFormatValue::Ycbcr444}, diff --git a/src/videooutput_impl.cpp b/src/videooutput_impl.cpp index 00059a2..7cfd629 100644 --- a/src/videooutput_impl.cpp +++ b/src/videooutput_impl.cpp @@ -22,7 +22,7 @@ #include "videooutput_impl.h" #include "json_types/videooutput.h" #include -#include + namespace Firebolt::VideoOutput { From 0ef9a096bff91de824258fd0c8df39573939a8c2 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Fri, 7 Aug 2026 10:07:15 -0700 Subject: [PATCH 21/39] RDKEKMW-14869: clang antics --- src/videooutput_impl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/videooutput_impl.cpp b/src/videooutput_impl.cpp index 7cfd629..73242b3 100644 --- a/src/videooutput_impl.cpp +++ b/src/videooutput_impl.cpp @@ -23,7 +23,6 @@ #include "json_types/videooutput.h" #include - namespace Firebolt::VideoOutput { VideoOutputImpl::VideoOutputImpl(Firebolt::Helpers::IHelper& helper) From 0cacd1d76655a608fc1ecf7302bd283f2d89a6cc Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 10 Aug 2026 10:33:57 -0700 Subject: [PATCH 22/39] RDKEMW-14869: small refactor to videoutput based PR feedback --- src/json_types/videooutput.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/json_types/videooutput.h b/src/json_types/videooutput.h index 7698e95..89cfbd1 100644 --- a/src/json_types/videooutput.h +++ b/src/json_types/videooutput.h @@ -26,7 +26,7 @@ #include #include -namespace Firebolt::VideoOutput +namespace Firebolt::VideoOutput::JsonData { NLOHMANN_JSON_SERIALIZE_ENUM(CecStateValue, { @@ -92,8 +92,6 @@ NLOHMANN_JSON_SERIALIZE_ENUM(RefreshRateValue, { {RefreshRateValue::R60, "60"}, }) -namespace JsonData -{ inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::CecStateValue> CecStateValueEnum({ {"active", ::Firebolt::VideoOutput::CecStateValue::Active}, @@ -182,13 +180,13 @@ class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::V } // namespace JsonData -inline void to_json(nlohmann::json& j, const VideoOutputResolution& v) +inline void to_json(nlohmann::json& j, const Firebolt::VideoOutput::VideoOutputResolution& v) { j = nlohmann::json::object(); j["height"] = v.height; j["width"] = v.width; } -} // namespace Firebolt::VideoOutput + // namespace Firebolt::VideoOutput #endif // FIREBOLT_VIDEOOUTPUT_JSON_H From 987f822d98f68f151e42d1a7c8ee0e222bf21ad5 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 10 Aug 2026 11:26:12 -0700 Subject: [PATCH 23/39] RDKEMW-14869: pr response --- .../the-spec/firebolt-open-rpc.json.orig | 8646 ----------------- src/videooutput_impl.h | 7 +- 2 files changed, 2 insertions(+), 8651 deletions(-) delete mode 100644 docs/openrpc/the-spec/firebolt-open-rpc.json.orig diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json.orig b/docs/openrpc/the-spec/firebolt-open-rpc.json.orig deleted file mode 100644 index 7ab45c5..0000000 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json.orig +++ /dev/null @@ -1,8646 +0,0 @@ -{ -<<<<<<< HEAD - "openrpc": "1.2.4", - "info": { - "title": "Firebolt JSON-RPC API", - "version": "", - "x-module-descriptions": { - "Accessibility": "The `Accessibility` module provides access to the user/device settings for closed captioning and voice guidance.\n\nApps **SHOULD** attempt o respect these settings, rather than manage and persist seprate settings, which would be different per-app.", - "Actions": "Methods for getting and observing app intents.", - "Advertising": "A module for platform provided advertising settings and functionality.", - "Device": "A module for querying about the device and it's capabilities.", - "Discovery": "Your App likely wants to integrate with the Platform's discovery capabilities. For example to add a \"Watch Next\" tile that links to your app from the platform's home screen.\n\nGetting access to this information requires to connect to lower level APIs made available by the platform. Since implementations differ between operators and platforms, the Firebolt SDK offers a Discovery module, that exposes a generic, agnostic interface to the developer.\n\nUnder the hood, an underlaying transport layer will then take care of calling the right APIs for the actual platform implementation that your App is running on.\n\nThe Discovery plugin is used to _send_ information to the Platform.\n\n### Localization\nApps should provide all user-facing strings in the device's language, as specified by the Firebolt `Localization.language` property.\n\nApps should provide prices in the same currency presented in the app. If multiple currencies are supported in the app, the app should provide prices in the user's current default currency.", - "Display": "A module for querying about the display", - "Lifecycle2": "Methods and events for responding to Lifecycle changes in your app.", - "Localization": "Methods for accessing location and language preferences.", - "Metrics": "Methods for sending metrics", - "Network": "Methods for accessing network information.", - "Presentation": "Methods for accessing Presentation preferences.", - "Stats": "Provides methods to retrieve application-level system information.", - "TextToSpeech": "A module for controlling and accessing Text To Speech over Firebolt." - } - }, - "methods": [ - { - "name": "rpc.discover", - "summary": "The OpenRPC schema for this JSON-RPC API", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:rpc:discover" - ] - } - ], - "result": { - "name": "OpenRPC Schema", - "schema": { - "type": "object" - } - }, - "examples": [ - { - "name": "Default", - "params": [], - "result": { - "name": "schema", - "value": {} - } - } - ] - }, - { - "name": "Actions.intent", - "summary": "Returns the current intent.", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "params": [], - "result": { - "name": "intent", - "summary": "The current intent as a JSON document.", - "schema": { - "type": "object", - "required": [ - "intent", - "intentId" - ], - "properties": { - "intent": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string" - }, - "context": { - "type": "object", - "properties": { - "source": { - "type": "string" - } - } - } - } - }, - "intentId": { - "type": "integer", - "minimum": 0 - } - } - } - }, - "examples": [ - { - "name": "Get the current intent", - "result": { - "name": "Default Result", - "value": { - "intent": { - "action": "pre-load", - "context": { - "source": "system" - } - }, - "intentId": 0 - } - } - } - ] - }, - { - "name": "Actions.onIntent", - "tags": [ - { - "name": "event", - "x-notifier": "Actions.onIntent", - "x-subscriber-for": "Actions.intent" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "summary": "Notifies when the current intent changes.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "result": { - "name": "intent", - "summary": "The current intent as a JSON document.", - "schema": { - "type": "object", - "required": [ - "intent", - "intentId" - ], - "properties": { - "intent": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string" - }, - "context": { - "type": "object", - "properties": { - "source": { - "type": "string" - } - } - } - } - }, - "intentId": { - "type": "integer", - "minimum": 0 - } - } - } - }, - "examples": [ - { - "name": "Listen for intent changes", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "Default Result", - "value": { - "intent": { - "action": "pre-load", - "context": { - "source": "system" - } - }, - "intentId": 0 - } - } - } - ] - }, - { - "name": "Actions.start", - "summary": "Sends an intent to the platform.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "params": [ - { - "name": "intent", - "summary": "The intent to send, as a JSON document.", - "required": true, - "schema": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string" - }, - "context": { - "type": "object", - "properties": { - "source": { - "type": "string" - } - } - } - } - } - }, - { - "name": "handlerAppId", - "summary": "Optional ID of the application that should handle the intent.", - "required": false, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Start an intent", - "params": [ - { - "name": "intent", - "value": { - "action": "pre-load", - "context": { - "source": "system" - } - } - } - ], - "result": { - "name": "Default Result", - "value": null - } - } - ] - }, - { - "name": "Accessibility.audioDescription", - "summary": "Returns the audio description setting of the device", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:audio-descriptions" - ] - } - ], - "result": { - "name": "setting", - "summary": "the audio description setting", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Getting the audio description setting", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Accessibility.closedCaptionsSettings", - "summary": "Returns captions settings: enabled, and a list of zero or more languages in order of decreasing preference", - "params": [], - "tags": [ - { - "name": "property:readonly", - "x-notifier-params-flattening": "true" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:closed-captions" - ] - } - ], - "result": { - "name": "closedCaptionsSettings", - "summary": "the closed captions settings", - "schema": { - "$ref": "#/x-schemas/Accessibility/ClosedCaptionsSettings" - } - }, - "examples": [ - { - "name": "Getting the closed captions settings", - "params": [], - "result": { - "name": "settings", - "value": { - "enabled": true, - "preferredLanguages": [ - "eng", - "spa" - ] - } - } - } - ] - }, - { - "name": "Accessibility.highContrastUI", - "summary": "Returns the high contrast UI device setting", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:high-contrast-ui" - ] - } - ], - "result": { - "name": "highContrastUI", - "summary": "Whether high-contrast UI mode is enabled", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "High-contrast UI mode is enabled", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Accessibility.voiceGuidanceSettings", - "summary": "Returns voice guidance settings: enabled, rate, and verbosity", - "params": [], - "tags": [ - { - "name": "property:readonly", - "x-notifier-params-flattening": "true" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:voice-guidance" - ] - } - ], - "result": { - "name": "settings", - "summary": "the voice guidance settings", - "schema": { - "$ref": "#/x-schemas/Accessibility/VoiceGuidanceSettings" - } - }, - "examples": [ - { - "name": "Getting the voice guidance settings", - "params": [], - "result": { - "name": "Default Result", - "value": { - "enabled": true, - "rate": 0.8, - "navigationHints": true - } - } - } - ] - }, - { - "name": "Advertising.advertisingId", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:advertising:identifier" - ] - } - ], - "summary": "Returns the IFA.", - "params": [], - "result": { - "name": "advertisingId", - "summary": "The advertising ID", - "schema": { - "$ref": "#/components/schemas/AdvertisingIdResult" - } - }, - "examples": [ - { - "name": "Getting the advertising ID", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "sspid", - "lmt": "0" - } - } - }, - { - "name": "Getting the advertising ID with scope browse", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "sspid", - "lmt": "1" - } - } - }, - { - "name": "Getting the advertising ID with scope content", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "idfa", - "lmt": "0" - } - } - } - ] - }, - { - "name": "Device.uid", - "summary": "Returns a persistent unique UUID for the current app and device. The UUID is reset when the app or device is reset", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:uid" - ] - } - ], - "result": { - "name": "uniqueId", - "summary": "A unique UUID for this app-device pair.", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the unique UUID", - "params": [], - "result": { - "name": "Default Result", - "value": "ee6723b8-7ab3-462c-8d93-dbf61227998e" - } - } - ] - }, - { - "name": "Device.deviceClass", - "summary": "Returns the class of the device", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:device-class" - ] - } - ], - "result": { - "name": "deviceClass", - "summary": "The device class", - "schema": { - "$ref": "#/components/schemas/DeviceClass" - } - }, - "examples": [ - { - "name": "Getting the device class", - "params": [], - "result": { - "name": "Default Result", - "value": "ott" - } - } - ] - }, - { - "name": "Device.uptime", - "summary": "Returns the number of seconds since most recent device boot, including any time spent during deep sleep", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "uptime", - "summary": "The device uptime", - "schema": { - "type": "number" - } - }, - "examples": [ - { - "name": "Getting the device uptime", - "params": [], - "result": { - "name": "Default Result", - "value": 123456 - } - } - ] - }, - { - "name": "Device.timeInActiveState", - "summary": "Returns the number of seconds since the device transitioned to the ON power state", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "timeInActiveState", - "summary": "The device time in active state", - "schema": { - "type": "number" - } - }, - "examples": [ - { - "name": "Getting the number of seconds since the device transitioned to the ON power state", - "params": [], - "result": { - "name": "Default Result", - "value": 654321 - } - } - ] - }, - { - "name": "Device.chipsetId", - "summary": "Returns chipset ID as a printable string, e.g. BCM72180", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "chipsetId", - "summary": "The device chipset ID", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the device chipset ID", - "params": [], - "result": { - "name": "Default Result", - "value": "BCM72180" - } - } - ] - }, - { - "name": "Device.hdr", - "summary": "Returns the HDR standards that are supported by the attached TV or the integral display", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "negotiatedHdrFormats", - "summary": "the negotiated HDR formats", - "schema": { - "$ref": "#/components/schemas/HDRFormatMap" - } - }, - "examples": [ - { - "name": "Getting the negotiated HDR formats", - "params": [], - "result": { - "name": "Default Result", - "value": { - "hdr10": true, - "hdr10Plus": true, - "dolbyVision": true, - "hlg": true - } - } - } - ] - }, - { - "name": "Device.dolbyAtmosExperienceAvailable", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Default", - "params": [], - "result": { - "name": "value", - "value": true - } - } - ] - }, - { - "name": "Discovery.watched", - "summary": "Notify the platform that content was partially or completely watched", - "tags": [ - { - "name": "polymorphic-reducer" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:discovery:watched" - ] - } - ], - "params": [ - { - "name": "entityId", - "required": true, - "schema": { - "type": "string" - }, - "summary": "The entity Id of the watched content." - }, - { - "name": "progress", - "summary": "How much of the content has been watched (percentage as (0-0.999) for VOD, number of seconds for live)", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "completed", - "summary": "Whether or not this viewing is considered \"complete,\" per the app's definition thereof", - "schema": { - "type": "boolean" - } - }, - { - "name": "watchedOn", - "summary": "Date/Time the content was watched, ISO 8601 Date/Time", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "agePolicy", - "description": "The age policy associated with the watch event. The age policy describes the age groups to which content may be directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "success", - "summary": "Whether the call was successful or not", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Notify the platform of watched content", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - } - ], - "result": { - "name": "success", - "value": true - } - }, - { - "name": "Notify the platform that child-directed content was watched", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "success", - "value": true - } - } - ] - }, - { - "name": "Discovery.watchedV2", - "summary": "Notify the platform that content was partially or completely watched, returns whether the notification was accepted", - "tags": [ - { - "name": "polymorphic-reducer" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:discovery:watched" - ] - } - ], - "params": [ - { - "name": "entityId", - "required": true, - "schema": { - "type": "string" - }, - "summary": "The entity Id of the watched content." - }, - { - "name": "progress", - "summary": "How much of the content has been watched (percentage as (0-0.999) for VOD, number of seconds for live)", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "completed", - "summary": "Whether or not this viewing is considered \"complete,\" per the app's definition thereof", - "schema": { - "type": "boolean" - } - }, - { - "name": "watchedOn", - "summary": "Date/Time the content was watched, ISO 8601 Date/Time", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "agePolicy", - "description": "The age policy associated with the watch event. The age policy describes the age groups to which content may be directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "summary": "Whether the platform accepted the watched notification", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Notify the platform of watched content (v2)", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - } - ], - "result": { - "name": "result", - "value": true - } - }, - { - "name": "Notify the platform that child-directed content was watched (v2)", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "result", - "value": true - } - } - ] - }, - { - "name": "Display.edid", - "summary": "Returns the EDID (and extensions) of the connected or integral display, as a Base64 encoded string", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "Base64 EDID", - "summary": "The EDID (and extensions) of the connected or integral display, as a Base64 encoded string", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the display EDID", - "params": [], - "result": { - "name": "Default Result", - "value": "ZWU2NzIzYjgtN2FiMy00NjJjLThkOTMtZGJmNjEyMjc5OThl" - } - } - ] - }, - { - "name": "Display.size", - "summary": "Returns the physical dimensions of the connected or integral display, in centimeters. Returns 0, 0 on a OTT/STB device when a display is not connected over HDMI", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "size", - "summary": "The display size in centimeters", - "schema": { - "type": "object", - "properties": { - "width": { - "type": "integer", - "description": "The width of the display in centimeters" - }, - "height": { - "type": "integer", - "description": "The height of the display in centimeters" - } - } - } - }, - "examples": [ - { - "name": "Getting the display size", - "params": [], - "result": { - "name": "Default Result", - "value": { - "width": 48, - "height": 27 - } - } - } - ] - }, - { - "name": "Display.maxResolution", - "summary": "Returns the physical/native resolution of the connected or integral display, in pixels. Returns 0, 0 on a OTT/STB device when a display is not connected over HDMI", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "maxResolution", - "summary": "The display resolution", - "schema": { - "type": "object", - "properties": { - "width": { - "type": "integer", - "description": "The width of the display in pixels" - }, - "height": { - "type": "integer", - "description": "The height of the display in pixels" - } - } - } - }, - "examples": [ - { - "name": "Getting the display size", - "params": [], - "result": { - "name": "Default Result", - "value": { - "width": 1920, - "height": 1080 - } - } - } - ] - }, - { - "name": "Lifecycle2.close", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Request the platform to deactivate the app, and possibly take further action.", - "params": [ - { - "name": "type", - "summary": "The type of the close app is requesting", - "required": true, - "schema": { - "$ref": "#/components/schemas/CloseType" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Close the app when the user presses back on the app home screen", - "params": [ - { - "name": "type", - "value": "unload" - } - ], - "result": { - "name": "Default Result", - "value": null - } - }, - { - "name": "Close the app when the user selects an exit menu item", - "params": [ - { - "name": "type", - "value": "deactivate" - } - ], - "result": { - "name": "Default Result", - "value": null - } - } - ] - }, - { - "name": "Lifecycle2.state", - "summary": "Get the current lifecycle state of the app.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "params": [], - "result": { - "name": "state", - "summary": "The current lifecycle state of the app.", - "schema": { - "$ref": "#/components/schemas/LifecycleState" - } - }, - "examples": [ - { - "name": "Default Example", - "params": [], - "result": { - "name": "Default Result", - "value": "active" - } - } - ] - }, - { - "name": "Localization.country", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:country-code" - ] - } - ], - "summary": "Returns the ISO 3166-1 alpha-2 code for the country device is located in.", - "params": [], - "result": { - "name": "code", - "summary": "The device country code.", - "schema": { - "$ref": "#/x-schemas/Localization/CountryCode" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": "US" - } - }, - { - "name": "Another example", - "params": [], - "result": { - "name": "Default Result", - "value": "GB" - } - } - ] - }, - { - "name": "Localization.preferredAudioLanguages", - "summary": "Returns a list of ISO 639-2/B codes for the preferred audio languages on this device.", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:preferred-audio-languages" - ] - } - ], - "result": { - "name": "languages", - "summary": "The preferred audio languages.", - "schema": { - "type": "array", - "items": { - "$ref": "#/x-schemas/Localization/ISO639_2Language" - } - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": [ - "spa", - "eng" - ] - } - } - ] - }, - { - "name": "Localization.presentationLanguage", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:locale" - ] - } - ], - "summary": "Get the *full* BCP 47 code, including script, region, variant, etc., for the preferred locale", - "params": [], - "result": { - "name": "locale", - "summary": "The device locale.", - "schema": { - "$ref": "#/x-schemas/Localization/Locale" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": "en-US" - } - } - ] - }, - { - "name": "Localization.timeZone", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Default", - "params": [], - "result": { - "name": "value", - "value": "America/New_York" - } - } - ] - }, - { - "name": "Metrics.ready", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your app is minimally usable. This method is called automatically by `Lifecycle.ready()`", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ready metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.signIn", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Log a sign in event, called by Discovery.signIn().", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send signIn metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.signOut", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Log a sign out event, called by Discovery.signOut().", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send signOut metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.startContent", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has started content.", - "params": [ - { - "name": "entityId", - "summary": "Optional entity ID of the content.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send startContent metric", - "params": [], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send startContent metric w/ entity", - "params": [ - { - "name": "entityId", - "value": "abc" - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send startContent metric and notify the platform that the content is child-directed", - "params": [ - { - "name": "entityId", - "value": "abc" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.stopContent", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has stopped content.", - "params": [ - { - "name": "entityId", - "summary": "Optional entity ID of the content.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send stopContent metric", - "params": [], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send stopContent metric w/ entity", - "params": [ - { - "name": "entityId", - "value": "abc" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.page", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has navigated to a page or view.", - "params": [ - { - "name": "pageId", - "summary": "Page ID of the content.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send page metric", - "params": [ - { - "name": "pageId", - "value": "xyz" - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send page metric w/ pageId", - "params": [ - { - "name": "pageId", - "value": "home" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.error", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform of an error that has occurred in your app.", - "params": [ - { - "name": "type", - "summary": "The type of error", - "schema": { - "$ref": "#/components/schemas/ErrorType" - }, - "required": true - }, - { - "name": "code", - "summary": "an app-specific error code", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "description", - "summary": "A short description of the error", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "visible", - "summary": "Whether or not this error was visible to the user.", - "schema": { - "type": "boolean" - }, - "required": true - }, - { - "name": "parameters", - "summary": "Optional additional parameters to be logged with the error", - "schema": { - "$ref": "#/x-schemas/Types/FlatMap" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send error metric", - "params": [ - { - "name": "type", - "value": "media" - }, - { - "name": "code", - "value": "MEDIA-STALLED" - }, - { - "name": "description", - "value": "playback stalled" - }, - { - "name": "visible", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaLoadStart", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when setting the URL of a media asset to play, in order to infer load time.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send loadstart metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPlay", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback should start due to autoplay, user-initiated play, or unpausing.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send play metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPlaying", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback actually starts due to autoplay, user-initiated play, unpausing, or recovering from a buffering interruption.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send playing metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPause", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback will pause due to an intentional pause operation.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send pause metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaWaiting", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback will halt due to a network, buffer, or other unintentional constraint.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send waiting metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaSeeking", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when a seek is initiated during media playback.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "target", - "summary": "Target destination of the seek, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "schema": { - "$ref": "#/components/schemas/MediaPosition" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send seeking metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "target", - "value": 0.5 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaSeeked", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when a seek is completed during media playback.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "position", - "summary": "Resulting position of the seek operation, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "schema": { - "$ref": "#/components/schemas/MediaPosition" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send seeked metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "position", - "value": 0.51 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaRateChanged", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when the playback rate of media is changed.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "rate", - "summary": "The new playback rate.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ratechange metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "rate", - "value": 2 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaRenditionChanged", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when the playback rendition (e.g. bitrate, dimensions, profile, etc) is changed.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "bitrate", - "summary": "The new bitrate in kbps.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "width", - "summary": "The new resolution width.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "height", - "summary": "The new resolution height.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "profile", - "summary": "A description of the new profile, e.g. 'HDR' etc.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send renditionchange metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "bitrate", - "value": 5000 - }, - { - "name": "width", - "value": 1920 - }, - { - "name": "height", - "value": 1080 - }, - { - "name": "profile", - "value": "HDR+" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaEnded", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when playback has stopped because the end of the media was reached.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ended metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.event", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:distributor" - ] - } - ], - "summary": "Inform the platform of 1st party distributor metrics. 'data' parameter is a JSON document", - "params": [ - { - "name": "schema", - "summary": "The schema URI of the metric type", - "schema": { - "type": "string", - "format": "uri" - }, - "required": true - }, - { - "name": "data", - "summary": "A JSON payload", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send foo event", - "params": [ - { - "name": "schema", - "value": "http://meta.rdkcentral.com/some/schema" - }, - { - "name": "data", - "value": "foo" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.appInfo", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform about an app's build info.", - "params": [ - { - "name": "build", - "summary": "The build / version of this app.", - "schema": { - "type": "string" - }, - "required": true - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send appInfo metric", - "params": [ - { - "name": "build", - "value": "1.2.2" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Network.connected", - "summary": "Returns whether the device currently has a usable network connection.", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:network:connected" - ] - } - ], - "params": [], - "result": { - "name": "success", - "summary": "Whether the device currently has a usable network connection.", - "schema": { - "$ref": "#/components/schemas/Connected" - } - }, - "examples": [ - { - "name": "Connected example", - "params": [], - "result": { - "name": "success", - "value": true - } - } - ] - }, - { - "name": "Presentation.focused", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Whether the app is in focus, i.e. receiving key presses. Provided for those apps/runtimes that cannot use Wayland", - "params": [], - "result": { - "name": "focused", - "summary": "Whether the app is in focus.", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Stats.memoryUsage", - "summary": "Returns information about container memory usage, in units of 1024 bytes.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "params": [], - "result": { - "name": "result", - "schema": { - "$ref": "#/components/schemas/MemoryUsage" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "value", - "description": "The memory usage information", - "value": { - "userMemoryUsed": 123456, - "userMemoryLimit": 789012, - "gpuMemoryUsed": 345678, - "gpuMemoryLimit": 901234 - } - } - } - ] - }, - { - "name": "TextToSpeech.speak", - "summary": "Speak the utterance immediately. Any ongoing speech is interrupted.", - "description": "Text argument is either plain text or a well-formed SSML document TTS_status, not success attribute, to be used by caller to indicate success of call 0 OK, 1 Fail, 2 not enabled, 3 invalid configuration Raises onSpeechinterrupted if speaking is interrupted", - "params": [ - { - "name": "text", - "summary": "String to be converted to Audio for speech", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "speakResult", - "summary": "Result for Speak", - "schema": { - "$ref": "#/components/schemas/SpeechResponse" - } - }, - "examples": [ - { - "name": "Getting the result of speak", - "params": [ - { - "name": "text", - "value": "I am a text waiting for speech." - } - ], - "result": { - "name": "result", - "value": { - "speechid": 1, - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.pause", - "summary": "Pauses the speech for given speech id", - "description": "Pauses the utterance. Raises onSpeechpause if ongoing speech is paused. Does nothing if utterance is already paused", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "pauseResult", - "summary": "Result for Pause", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Pause a given speech id", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.resume", - "summary": "Resumes the speech for given speech id", - "description": "Continue the paused utterance. Raises onSpeechresume if paused speech is resumed. Does nothing if the utterance is not paused", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "resumeResult", - "summary": "Result for Resume", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Resume a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.cancel", - "summary": "Cancels the speech for given speech id", - "description": "Stop speaking if utterance is currently being spoken. Raises onSpeechinterrupted if speaking was interrupted.", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "cancelResult", - "summary": "Result for cancel", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Cancel a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.getspeechstate", - "summary": "Returns the state of the utterance.", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "speechStateResult", - "summary": "Result for speech state", - "schema": { - "$ref": "#/components/schemas/SpeechStateResponse" - } - }, - "examples": [ - { - "name": "State for a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "speechstate", - "value": { - "speechstate": 1, - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.listvoices", - "summary": "Returns the list of available voices as human-readable strings, e.g. 'ava', 'amelie', 'angelica'", - "params": [ - { - "name": "language", - "summary": "Language - string - BCP 47", - "schema": { - "$ref": "#/x-schemas/Localization/Locale" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "listvoices", - "summary": "The list of voices supported for the language", - "schema": { - "$ref": "#/components/schemas/ListVoicesResponse" - } - }, - "examples": [ - { - "name": "Getting the list of voices", - "params": [ - { - "name": "language", - "value": "en-US" - } - ], - "result": { - "name": "voiceList", - "value": { - "TTS_Status": 0, - "voices": [ - "carol", - "tom" - ] - } - } - } - ] - }, - { - "name": "Lifecycle2.onStateChanged", - "tags": [ - { - "name": "event", - "x-contextual-parameters": 0, - "x-notifier": "Lifecycle2.onStateChanged" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Notification of lifecycle state change, raised after the platform has transitioned the app/runtime to the new lifecycle state", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "App is active after being initialized", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Single transition to paused state", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onWillspeak", - "summary": "Text to speech conversion is about to start.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onWillspeak" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechstart", - "summary": "Utterance is about to be spoken.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechstart" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechpause", - "summary": "Ongoing speech was paused.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechpause" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechresume", - "summary": "Paused speech was resumed.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechresume" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechcomplete", - "summary": "Speech completed successfully.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechcomplete" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechinterrupted", - "summary": "Speech was stopped, due to another call to speak or cancel.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechinterrupted" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onNetworkerror", - "summary": "Utterance failed due to network error.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onNetworkerror" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onPlaybackerror", - "summary": "Utterance failed during playback.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onPlaybackerror" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onAudioDescriptionChanged", - "summary": "Returns the audio description setting of the device", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Accessibility.onAudioDescriptionChanged", - "x-subscriber-for": "Accessibility.audioDescription" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:audio-descriptions" - ] - } - ], - "examples": [ - { - "name": "Getting the audio description setting", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onClosedCaptionsSettingsChanged", - "summary": "Returns captions settings: enabled, and a list of zero or more languages in order of decreasing preference", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier-params-flattening": "true", - "x-notifier": "Accessibility.onClosedCaptionsSettingsChanged", - "x-subscriber-for": "Accessibility.closedCaptionsSettings" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:closed-captions" - ] - } - ], - "examples": [ - { - "name": "Getting the closed captions settings", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onHighContrastUIChanged", - "summary": "Returns the high contrast UI device setting", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Accessibility.onHighContrastUIChanged", - "x-subscriber-for": "Accessibility.highContrastUI" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:high-contrast-ui" - ] - } - ], - "examples": [ - { - "name": "High-contrast UI mode is enabled", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onVoiceGuidanceSettingsChanged", - "summary": "Returns voice guidance settings: enabled, rate, and verbosity", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier-params-flattening": "true", - "x-notifier": "Accessibility.onVoiceGuidanceSettingsChanged", - "x-subscriber-for": "Accessibility.voiceGuidanceSettings" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:voice-guidance" - ] - } - ], - "examples": [ - { - "name": "Getting the voice guidance settings", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Device.onHdrChanged", - "summary": "Returns the HDR standards that are supported by the attached TV or the integral display", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Device.onHdrChanged", - "x-subscriber-for": "Device.hdr" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "examples": [ - { - "name": "Getting the negotiated HDR formats", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Device.onDolbyAtmosExperienceAvailableChanged", - "params": [ - { - "name": "listen", - "required": true, - "schema": { - "type": "boolean" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Default", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "value", - "value": true - } - } - ] - }, - { - "name": "Localization.onCountryChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onCountryChanged", - "x-subscriber-for": "Localization.country" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:country-code" - ] - } - ], - "summary": "Returns the ISO 3166-1 alpha-2 code for the country device is located in.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Another example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onPreferredAudioLanguagesChanged", - "summary": "Returns a list of ISO 639-2/B codes for the preferred audio languages on this device.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onPreferredAudioLanguagesChanged", - "x-subscriber-for": "Localization.preferredAudioLanguages" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:preferred-audio-languages" - ] - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onPresentationLanguageChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onPresentationLanguageChanged", - "x-subscriber-for": "Localization.presentationLanguage" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:locale" - ] - } - ], - "summary": "Get the *full* BCP 47 code, including script, region, variant, etc., for the preferred locale", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onTimeZoneChanged", - "params": [ - { - "name": "listen", - "required": true, - "schema": { - "type": "boolean" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Default", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "value", - "value": "America/New_York" - } - } - ] - }, - { - "name": "Network.onConnectedChanged", - "summary": "Returns whether the device currently has a usable network connection.", - "tags": [ - { - "name": "event", - "x-notifier": "Network.onConnectedChanged", - "x-subscriber-for": "Network.connected" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:network:connected" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Connected example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Presentation.onFocusedChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Presentation.onFocusedChanged", - "x-subscriber-for": "Presentation.focused" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Whether the app is in focus, i.e. receiving key presses. Provided for those apps/runtimes that cannot use Wayland", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - } - ], - "components": { - "schemas": { - "AdvertisingIdResult": { - "title": "AdvertisingIdResult", - "type": "object", - "properties": { - "ifa": { - "type": "string", - "description": "UUID conforming to IAB standard" - }, - "ifa_type": { - "type": "string", - "description": "Source of the IFA as defined by IAB" - }, - "lmt": { - "type": "string", - "enum": [ - "0", - "1" - ], - "description": "Boolean that if set to 1, user has requested ad tracking and measurement is disabled" - } - }, - "required": [ - "ifa", - "ifa_type", - "lmt" - ] - }, - "HDRFormatMap": { - "title": "HDRFormatMap", - "type": "object", - "properties": { - "hdr10": { - "type": "boolean" - }, - "hdr10Plus": { - "type": "boolean" - }, - "dolbyVision": { - "type": "boolean" - }, - "hlg": { - "type": "boolean" - } - }, - "required": [ - "hdr10", - "hdr10Plus", - "dolbyVision", - "hlg" - ], - "description": "The type of HDR format" - }, - "DeviceClass": { - "title": "DeviceClass", - "type": "string", - "enum": [ - "ott", - "stb", - "tv" - ], - "description": "The type of device" - }, - "CloseType": { - "title": "CloseType", - "description": "The application close type", - "type": "string", - "enum": [ - "deactivate", - "unload", - "killReload", - "killReactivate" - ] - }, - "LifecycleState": { - "title": "LifecycleState", - "description": "The application Lifecycle state", - "type": "string", - "enum": [ - "initializing", - "active", - "paused", - "suspended", - "hibernated", - "terminating" - ] - }, - "StateChange": { - "title": "StateChange", - "type": "object", - "properties": { - "newState": { - "$ref": "#/components/schemas/LifecycleState" - }, - "oldState": { - "$ref": "#/components/schemas/LifecycleState" - } - } - }, - "MediaPosition": { - "title": "MediaPosition", - "description": "Represents a position inside playback content, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "oneOf": [ - { - "const": 0 - }, - { - "type": "number", - "exclusiveMinimum": 0, - "exclusiveMaximum": 1 - }, - { - "type": "integer", - "minimum": 1, - "maximum": 86400 - } - ] - }, - "ErrorType": { - "title": "ErrorType", - "type": "string", - "enum": [ - "network", - "media", - "restriction", - "entitlement", - "other" - ] - }, - "EventObjectPrimitives": { - "title": "EventObjectPrimitives", - "anyOf": [ - { - "type": "string", - "maxLength": 256 - }, - { - "type": "number" - }, - { - "type": "integer" - }, - { - "type": "boolean" - }, - { - "type": "null" - } - ] - }, - "Connected": { - "type": "boolean", - "description": "Indicates whether the device currently has a usable network connection." - }, - "MemoryUsage": { - "title": "MemoryUsage", - "type": "object", - "description": "Describes current and maximum memory usage of the container.", - "properties": { - "userMemoryUsedKiB": { - "type": "integer", - "description": "User memory currently used in 1024 bytes." - }, - "userMemoryLimitKiB": { - "type": "integer", - "description": "Maximum user memory available in 1024 bytes." - }, - "gpuMemoryUsedKiB": { - "type": "integer", - "description": "GPU memory currently used in 1024 bytes." - }, - "gpuMemoryLimitKiB": { - "type": "integer", - "description": "Maximum GPU memory available in 1024 bytes." - } - }, - "required": [ - "userMemoryUsedKiB", - "userMemoryLimitKiB", - "gpuMemoryUsedKiB", - "gpuMemoryLimitKiB" - ] - }, - "TTSEnabled": { - "title": "TTSEnabled", - "type": "object", - "required": [ - "TTS_Status", - "isenabled" - ], - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "isenabled": { - "type": "boolean" - } - } - }, - "ListVoicesResponse": { - "title": "ListVoicesResponse", - "type": "object", - "required": [ - "TTS_Status", - "voices" - ], - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "voices": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "TTSConfiguration": { - "title": "TTSConfiguration", - "type": "object", - "required": [ - "success" - ], - "properties": { - "success": { - "type": "boolean" - }, - "ttsendpoint": { - "type": "string", - "description": "URL for Text to Speech API" - }, - "ttsendpointsecured": { - "type": "string", - "description": "Secure URL for Text to Speech API" - }, - "language": { - "type": "string", - "description": "Language used by Text to speech" - }, - "voice": { - "type": "string", - "description": "Voice used by Text to speech" - }, - "volume": { - "type": "integer", - "description": "Volume for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "primvolduckpercent": { - "type": "integer", - "description": "Prime Volume duck percent for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "rate": { - "type": "integer", - "description": "Speech rate for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "speechrate": { - "description": "Rate for speech", - "$ref": "#/components/schemas/SpeechRate" - }, - "fallbacktext": { - "description": "Fallback text for TTS", - "$ref": "#/components/schemas/FallbackText" - } - }, - "examples": [ - {} - ] - }, - "SpeechRate": { - "title": "SpeechRate", - "type": "string", - "enum": [ - "slow", - "medium", - "fast", - "faster", - "fastest" - ] - }, - "FallbackText": { - "title": "FallbackText", - "type": "object", - "properties": { - "scenario": { - "type": "string", - "description": "Scenario for fallback Text" - }, - "value": { - "type": "string", - "description": "Value for fallback Text" - } - } - }, - "SpeechResponse": { - "title": "SpeechResponse", - "type": "object", - "properties": { - "speechid": { - "$ref": "#/components/schemas/SpeechId" - }, - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "speechid", - "TTS_Status", - "success" - ] - }, - "SpeechId": { - "type": "integer" - }, - "SpeechIdEvent": { - "type": "object", - "properties": { - "speechid": { - "$ref": "#/components/schemas/SpeechId" - } - }, - "required": [ - "speechid" - ] - }, - "TTSStatus": { - "title": "TTSStatus", - "type": "integer", - "minimum": 0, - "maximum": 3 - }, - "SpeechState": { - "title": "SpeechState", - "type": "integer", - "enum": [ - 0, - 1, - 2, - 3 - ], - "description": "0 = SPEECH_PENDING, 1 = SPEECH_IN_PROGRESS, 2 = SPEECH_PAUSED, 3 = SPEECH_NOT_FOUND" - }, - "SpeechStateResponse": { - "title": "SpeechStateResponse", - "type": "object", - "properties": { - "speechstate": { - "$ref": "#/components/schemas/SpeechState" - }, - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "speechstate", - "TTS_Status", - "success" - ] - }, - "TTSStatusResponse": { - "title": "TTSStatusResponse", - "type": "object", - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "TTS_Status", - "success" - ] - }, - "TTSState": { - "title": "TTSState", - "type": "object", - "properties": { - "state": { - "type": "boolean" - } - }, - "required": [ - "state" - ] - }, - "TTSVoice": { - "title": "TTSVoice", - "type": "object", - "properties": { - "voice": { - "type": "string" - } - }, - "required": [ - "voice" - ] - } - } - }, - "x-schemas": { - "Accessibility": { - "uri": "https://meta.comcast.com/firebolt/accessibility", - "ClosedCaptionsSettings": { - "title": "ClosedCaptionsSettings", - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether or not closed-captions should be enabled by default" - }, - "preferredLanguages": { - "type": "array", - "items": { - "$ref": "#/x-schemas/Localization/ISO639_2Language" - } - } - }, - "examples": [ - { - "enabled": true, - "styles": { - "fontFamily": "monospaced_serif", - "fontSize": 1, - "fontColor": "#ffffff", - "fontEdge": "none", - "fontEdgeColor": "#7F7F7F", - "fontOpacity": 100, - "backgroundColor": "#000000", - "backgroundOpacity": 100, - "textAlign": "center", - "textAlignVertical": "middle", - "windowColor": "white", - "windowOpacity": 50 - }, - "preferredLanguages": [ - "eng", - "spa" - ] - } - ] - }, - "VoiceGuidanceSettings": { - "title": "VoiceGuidanceSettings", - "type": "object", - "required": [ - "enabled", - "navigationHints", - "rate" - ], - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether or not voice guidance should be enabled by default" - }, - "rate": { - "$ref": "#/x-schemas/Accessibility/SpeechRate", - "description": "The rate at which voice guidance speech will be read back to the user" - }, - "navigationHints": { - "type": "boolean", - "description": "Whether or not voice guidance should include additional navigation hints" - } - }, - "examples": [ - { - "enabled": true, - "navigationHints": true, - "rate": 0.8 - } - ] - }, - "SpeechRate": { - "title": "SpeechRate", - "type": "number", - "minimum": 0.1, - "maximum": 10 - } - }, - "Localization": { - "uri": "https://meta.comcast.com/firebolt/localization", - "ISO639_2Language": { - "type": "string", - "pattern": "^[a-z]{3}$" - }, - "CountryCode": { - "type": "string", - "pattern": "^[A-Z]{2}$" - }, - "Locale": { - "type": "string", - "pattern": "^[a-zA-Z]+([a-zA-Z0-9\\-]*)$" - } - }, - "Policies": { - "uri": "https://meta.comcast.com/firebolt/policies", - "AgePolicy": { - "title": "AgePolicy", - "description": "The policy that describes various age groups to which content is directed. See distributor documentation for further details.", - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [ - "app:adult", - "app:child", - "app:teen" - ] - } - ] - } - }, - "Types": { - "uri": "https://meta.comcast.com/firebolt/types", - "FlatMap": { - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - } - } - } - } -======= - "openrpc": "1.2.4", - "info": { - "title": "Firebolt JSON-RPC API", - "version": "", - "x-module-descriptions": { - "Accessibility": "The `Accessibility` module provides access to the user/device settings for closed captioning and voice guidance.\n\nApps **SHOULD** attempt o respect these settings, rather than manage and persist seprate settings, which would be different per-app.", - "Actions": "Methods for getting and observing app intents.", - "Advertising": "A module for platform provided advertising settings and functionality.", - "Device": "A module for querying about the device and it's capabilities.", - "Discovery": "Your App likely wants to integrate with the Platform's discovery capabilities. For example to add a \"Watch Next\" tile that links to your app from the platform's home screen.\n\nGetting access to this information requires to connect to lower level APIs made available by the platform. Since implementations differ between operators and platforms, the Firebolt SDK offers a Discovery module, that exposes a generic, agnostic interface to the developer.\n\nUnder the hood, an underlaying transport layer will then take care of calling the right APIs for the actual platform implementation that your App is running on.\n\nThe Discovery plugin is used to _send_ information to the Platform.\n\n### Localization\nApps should provide all user-facing strings in the device's language, as specified by the Firebolt `Localization.language` property.\n\nApps should provide prices in the same currency presented in the app. If multiple currencies are supported in the app, the app should provide prices in the user's current default currency.", - "Display": "A module for querying about the display", - "Lifecycle2": "Methods and events for responding to Lifecycle changes in your app.", - "Localization": "Methods for accessing location and language preferences.", - "Metrics": "Methods for sending metrics", - "Network": "Methods for accessing network information.", - "Presentation": "Methods for accessing Presentation preferences.", - "Stats": "Provides methods to retrieve application-level system information.", - "TextToSpeech": "A module for controlling and accessing Text To Speech over Firebolt." - } - }, - "methods": [ - { - "name": "rpc.discover", - "summary": "The OpenRPC schema for this JSON-RPC API", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:rpc:discover" - ] - } - ], - "result": { - "name": "OpenRPC Schema", - "schema": { - "type": "object" - } - }, - "examples": [ - { - "name": "Default", - "params": [], - "result": { - "name": "schema", - "value": {} - } - } - ] - }, - { - "name": "Actions.intent", - "summary": "Returns the current intent.", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "params": [], - "result": { - "name": "intent", - "summary": "The current intent as a JSON document.", - "schema": { - "type": "object", - "required": [ - "intent", - "intentId" - ], - "properties": { - "intent": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string" - }, - "context": { - "type": "object", - "properties": { - "source": { - "type": "string" - } - } - } - } - }, - "intentId": { - "type": "integer", - "minimum": 0 - } - } - } - }, - "examples": [ - { - "name": "Get the current intent", - "result": { - "name": "Default Result", - "value": { - "intent": { - "action": "pre-load", - "context": { - "source": "system" - } - }, - "intentId": 0 - } - } - } - ] - }, - { - "name": "Actions.onIntent", - "tags": [ - { - "name": "event", - "x-notifier": "Actions.onIntent", - "x-subscriber-for": "Actions.intent" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "summary": "Notifies when the current intent changes.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "result": { - "name": "intent", - "summary": "The current intent as a JSON document.", - "schema": { - "type": "object", - "required": [ - "intent", - "intentId" - ], - "properties": { - "intent": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string" - }, - "context": { - "type": "object", - "properties": { - "source": { - "type": "string" - } - } - } - } - }, - "intentId": { - "type": "integer", - "minimum": 0 - } - } - } - }, - "examples": [ - { - "name": "Listen for intent changes", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "Default Result", - "value": { - "intent": { - "action": "pre-load", - "context": { - "source": "system" - } - }, - "intentId": 0 - } - } - } - ] - }, - { - "name": "Actions.start", - "summary": "Sends an intent to the platform.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:actions:intent" - ] - } - ], - "params": [ - { - "name": "intent", - "summary": "The intent to send, as a JSON document.", - "required": true, - "schema": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string" - }, - "context": { - "type": "object", - "properties": { - "source": { - "type": "string" - } - } - } - } - } - }, - { - "name": "handlerAppId", - "summary": "Optional ID of the application that should handle the intent.", - "required": false, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Start an intent", - "params": [ - { - "name": "intent", - "value": { - "action": "pre-load", - "context": { - "source": "system" - } - } - } - ], - "result": { - "name": "Default Result", - "value": null - } - } - ] - }, - { - "name": "Accessibility.audioDescription", - "summary": "Returns the audio description setting of the device", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:audio-descriptions" - ] - } - ], - "result": { - "name": "setting", - "summary": "the audio description setting", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Getting the audio description setting", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Accessibility.closedCaptionsSettings", - "summary": "Returns captions settings: enabled, and a list of zero or more languages in order of decreasing preference", - "params": [], - "tags": [ - { - "name": "property:readonly", - "x-notifier-params-flattening": "true" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:closed-captions" - ] - } - ], - "result": { - "name": "closedCaptionsSettings", - "summary": "the closed captions settings", - "schema": { - "$ref": "#/x-schemas/Accessibility/ClosedCaptionsSettings" - } - }, - "examples": [ - { - "name": "Getting the closed captions settings", - "params": [], - "result": { - "name": "settings", - "value": { - "enabled": true, - "preferredLanguages": [ - "eng", - "spa" - ] - } - } - } - ] - }, - { - "name": "Accessibility.highContrastUI", - "summary": "Returns the high contrast UI device setting", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:high-contrast-ui" - ] - } - ], - "result": { - "name": "highContrastUI", - "summary": "Whether high-contrast UI mode is enabled", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "High-contrast UI mode is enabled", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Accessibility.voiceGuidanceSettings", - "summary": "Returns voice guidance settings: enabled, rate, and verbosity", - "params": [], - "tags": [ - { - "name": "property:readonly", - "x-notifier-params-flattening": "true" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:voice-guidance" - ] - } - ], - "result": { - "name": "settings", - "summary": "the voice guidance settings", - "schema": { - "$ref": "#/x-schemas/Accessibility/VoiceGuidanceSettings" - } - }, - "examples": [ - { - "name": "Getting the voice guidance settings", - "params": [], - "result": { - "name": "Default Result", - "value": { - "enabled": true, - "rate": 0.8, - "navigationHints": true - } - } - } - ] - }, - { - "name": "Advertising.advertisingId", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:advertising:identifier" - ] - } - ], - "summary": "Returns the IFA.", - "params": [], - "result": { - "name": "advertisingId", - "summary": "The advertising ID", - "schema": { - "$ref": "#/components/schemas/AdvertisingIdResult" - } - }, - "examples": [ - { - "name": "Getting the advertising ID", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "sspid", - "lmt": "0" - } - } - }, - { - "name": "Getting the advertising ID with scope browse", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "sspid", - "lmt": "1" - } - } - }, - { - "name": "Getting the advertising ID with scope content", - "params": [], - "result": { - "name": "Default Result", - "value": { - "ifa": "bd87dd10-8d1d-4b93-b1a6-a8e5d410e400", - "ifa_type": "idfa", - "lmt": "0" - } - } - } - ] - }, - { - "name": "Device.uid", - "summary": "Returns a persistent unique UUID for the current app and device. The UUID is reset when the app or device is reset", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:uid" - ] - } - ], - "result": { - "name": "uniqueId", - "summary": "A unique UUID for this app-device pair.", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the unique UUID", - "params": [], - "result": { - "name": "Default Result", - "value": "ee6723b8-7ab3-462c-8d93-dbf61227998e" - } - } - ] - }, - { - "name": "Device.deviceClass", - "summary": "Returns the class of the device", - "params": [], - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:device-class" - ] - } - ], - "result": { - "name": "deviceClass", - "summary": "The device class", - "schema": { - "$ref": "#/components/schemas/DeviceClass" - } - }, - "examples": [ - { - "name": "Getting the device class", - "params": [], - "result": { - "name": "Default Result", - "value": "ott" - } - } - ] - }, - { - "name": "Device.uptime", - "summary": "Returns the number of seconds since most recent device boot, including any time spent during deep sleep", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "uptime", - "summary": "The device uptime", - "schema": { - "type": "number" - } - }, - "examples": [ - { - "name": "Getting the device uptime", - "params": [], - "result": { - "name": "Default Result", - "value": 123456 - } - } - ] - }, - { - "name": "Device.timeInActiveState", - "summary": "Returns the number of seconds since the device transitioned to the ON power state", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "timeInActiveState", - "summary": "The device time in active state", - "schema": { - "type": "number" - } - }, - "examples": [ - { - "name": "Getting the number of seconds since the device transitioned to the ON power state", - "params": [], - "result": { - "name": "Default Result", - "value": 654321 - } - } - ] - }, - { - "name": "Device.chipsetId", - "summary": "Returns chipset ID as a printable string, e.g. BCM72180", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "chipsetId", - "summary": "The device chipset ID", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the device chipset ID", - "params": [], - "result": { - "name": "Default Result", - "value": "BCM72180" - } - } - ] - }, - { - "name": "Device.hdr", - "summary": "Returns the HDR standards that are supported by the attached TV or the integral display", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "negotiatedHdrFormats", - "summary": "the negotiated HDR formats", - "schema": { - "$ref": "#/components/schemas/HDRFormatMap" - } - }, - "examples": [ - { - "name": "Getting the negotiated HDR formats", - "params": [], - "result": { - "name": "Default Result", - "value": { - "hdr10": true, - "hdr10Plus": true, - "dolbyVision": true, - "hlg": true - } - } - } - ] - }, - { - "name": "Device.dolbyAtmosExperienceAvailable", - "summary": "Returns whether Dolby Atmos experience is available on the device", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "result": { - "name": "dolbyAtmosExperienceAvailable", - "summary": "Whether Dolby Atmos experience is available on the device", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Getting Dolby Atmos experience availability", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Discovery.watched", - "summary": "Notify the platform that content was partially or completely watched", - "tags": [ - { - "name": "polymorphic-reducer" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:discovery:watched" - ] - } - ], - "params": [ - { - "name": "entityId", - "required": true, - "schema": { - "type": "string" - }, - "summary": "The entity Id of the watched content." - }, - { - "name": "progress", - "summary": "How much of the content has been watched (percentage as (0-0.999) for VOD, number of seconds for live)", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "completed", - "summary": "Whether or not this viewing is considered \"complete,\" per the app's definition thereof", - "schema": { - "type": "boolean" - } - }, - { - "name": "watchedOn", - "summary": "Date/Time the content was watched, ISO 8601 Date/Time", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "agePolicy", - "description": "The age policy associated with the watch event. The age policy describes the age groups to which content may be directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "success", - "summary": "Whether the call was successful or not", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Notify the platform of watched content", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - } - ], - "result": { - "name": "success", - "value": true - } - }, - { - "name": "Notify the platform that child-directed content was watched", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "success", - "value": true - } - } - ] - }, - { - "name": "Discovery.watchedV2", - "summary": "Notify the platform that content was partially or completely watched", - "tags": [ - { - "name": "polymorphic-reducer" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:discovery:watched" - ] - } - ], - "params": [ - { - "name": "entityId", - "required": true, - "schema": { - "type": "string" - }, - "summary": "The entity Id of the watched content." - }, - { - "name": "progress", - "summary": "How much of the content has been watched (percentage as (0-0.999) for VOD, number of seconds for live)", - "schema": { - "type": "number", - "minimum": 0 - } - }, - { - "name": "completed", - "summary": "Whether or not this viewing is considered \"complete,\" per the app's definition thereof", - "schema": { - "type": "boolean" - } - }, - { - "name": "watchedOn", - "summary": "Date/Time the content was watched, ISO 8601 Date/Time", - "schema": { - "type": "string", - "format": "date-time" - } - }, - { - "name": "agePolicy", - "description": "The age policy associated with the watch event. The age policy describes the age groups to which content may be directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Notify the platform of watched content (v2)", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Notify the platform that child-directed content was watched (v2)", - "params": [ - { - "name": "entityId", - "value": "partner.com/entity/123" - }, - { - "name": "progress", - "value": 0.95 - }, - { - "name": "completed", - "value": true - }, - { - "name": "watchedOn", - "value": "2021-04-23T18:25:43.511Z" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Display.edid", - "summary": "Returns the EDID (and extensions) of the connected or integral display, as a Base64 encoded string", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "Base64 EDID", - "summary": "The EDID (and extensions) of the connected or integral display, as a Base64 encoded string", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Getting the display EDID", - "params": [], - "result": { - "name": "Default Result", - "value": "ZWU2NzIzYjgtN2FiMy00NjJjLThkOTMtZGJmNjEyMjc5OThl" - } - } - ] - }, - { - "name": "Display.size", - "summary": "Returns the physical dimensions of the connected or integral display, in centimeters. Returns 0, 0 on a OTT/STB device when a display is not connected over HDMI", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "size", - "summary": "The display size in centimeters", - "schema": { - "type": "object", - "properties": { - "width": { - "type": "integer", - "description": "The width of the display in centimeters" - }, - "height": { - "type": "integer", - "description": "The height of the display in centimeters" - } - } - } - }, - "examples": [ - { - "name": "Getting the display size", - "params": [], - "result": { - "name": "Default Result", - "value": { - "width": 48, - "height": 27 - } - } - } - ] - }, - { - "name": "Display.maxResolution", - "summary": "Returns the physical/native resolution of the connected or integral display, in pixels. Returns 0, 0 on a OTT/STB device when a display is not connected over HDMI", - "params": [], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:display:info" - ] - } - ], - "result": { - "name": "maxResolution", - "summary": "The display resolution", - "schema": { - "type": "object", - "properties": { - "width": { - "type": "integer", - "description": "The width of the display in pixels" - }, - "height": { - "type": "integer", - "description": "The height of the display in pixels" - } - } - } - }, - "examples": [ - { - "name": "Getting the display size", - "params": [], - "result": { - "name": "Default Result", - "value": { - "width": 1920, - "height": 1080 - } - } - } - ] - }, - { - "name": "Lifecycle2.close", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Request the platform to deactivate the app, and possibly take further action.", - "params": [ - { - "name": "type", - "summary": "The type of the close app is requesting", - "required": true, - "schema": { - "$ref": "#/components/schemas/CloseType" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Close the app when the user presses back on the app home screen", - "params": [ - { - "name": "type", - "value": "unload" - } - ], - "result": { - "name": "Default Result", - "value": null - } - }, - { - "name": "Close the app when the user selects an exit menu item", - "params": [ - { - "name": "type", - "value": "deactivate" - } - ], - "result": { - "name": "Default Result", - "value": null - } - } - ] - }, - { - "name": "Lifecycle2.state", - "summary": "Get the current lifecycle state of the app.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "params": [], - "result": { - "name": "state", - "summary": "The current lifecycle state of the app.", - "schema": { - "$ref": "#/components/schemas/LifecycleState" - } - }, - "examples": [ - { - "name": "Default Example", - "params": [], - "result": { - "name": "Default Result", - "value": "active" - } - } - ] - }, - { - "name": "Localization.country", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:country-code" - ] - } - ], - "summary": "Returns the ISO 3166-1 alpha-2 code for the country device is located in.", - "params": [], - "result": { - "name": "code", - "summary": "The device country code.", - "schema": { - "$ref": "#/x-schemas/Localization/CountryCode" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": "US" - } - }, - { - "name": "Another example", - "params": [], - "result": { - "name": "Default Result", - "value": "GB" - } - } - ] - }, - { - "name": "Localization.preferredAudioLanguages", - "summary": "Returns a list of ISO 639-2/B codes for the preferred audio languages on this device.", - "params": [], - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:preferred-audio-languages" - ] - } - ], - "result": { - "name": "languages", - "summary": "The preferred audio languages.", - "schema": { - "type": "array", - "items": { - "$ref": "#/x-schemas/Localization/ISO639_2Language" - } - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": [ - "spa", - "eng" - ] - } - } - ] - }, - { - "name": "Localization.presentationLanguage", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:locale" - ] - } - ], - "summary": "Get the *full* BCP 47 code, including script, region, variant, etc., for the preferred locale", - "params": [], - "result": { - "name": "locale", - "summary": "The device locale.", - "schema": { - "$ref": "#/x-schemas/Localization/Locale" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": "en-US" - } - } - ] - }, - { - "name": "Localization.timeZone", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:time-zone" - ] - } - ], - "summary": "Get the IANA timezone of the device.", - "params": [], - "result": { - "name": "timeZone", - "summary": "The device timezone.", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": "America/New_York" - } - } - ] - }, - { - "name": "Metrics.ready", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your app is minimally usable. This method is called automatically by `Lifecycle.ready()`", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ready metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.signIn", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Log a sign in event, called by Discovery.signIn().", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send signIn metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.signOut", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Log a sign out event, called by Discovery.signOut().", - "params": [], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send signOut metric", - "params": [], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.startContent", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has started content.", - "params": [ - { - "name": "entityId", - "summary": "Optional entity ID of the content.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send startContent metric", - "params": [], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send startContent metric w/ entity", - "params": [ - { - "name": "entityId", - "value": "abc" - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send startContent metric and notify the platform that the content is child-directed", - "params": [ - { - "name": "entityId", - "value": "abc" - }, - { - "name": "agePolicy", - "value": "app:child" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.stopContent", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has stopped content.", - "params": [ - { - "name": "entityId", - "summary": "Optional entity ID of the content.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send stopContent metric", - "params": [], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send stopContent metric w/ entity", - "params": [ - { - "name": "entityId", - "value": "abc" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.page", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform that your user has navigated to a page or view.", - "params": [ - { - "name": "pageId", - "summary": "Page ID of the content.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send page metric", - "params": [ - { - "name": "pageId", - "value": "xyz" - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Send page metric w/ pageId", - "params": [ - { - "name": "pageId", - "value": "home" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.error", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform of an error that has occurred in your app.", - "params": [ - { - "name": "type", - "summary": "The type of error", - "schema": { - "$ref": "#/components/schemas/ErrorType" - }, - "required": true - }, - { - "name": "code", - "summary": "an app-specific error code", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "description", - "summary": "A short description of the error", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "visible", - "summary": "Whether or not this error was visible to the user.", - "schema": { - "type": "boolean" - }, - "required": true - }, - { - "name": "parameters", - "summary": "Optional additional parameters to be logged with the error", - "schema": { - "$ref": "#/x-schemas/Types/FlatMap" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send error metric", - "params": [ - { - "name": "type", - "value": "media" - }, - { - "name": "code", - "value": "MEDIA-STALLED" - }, - { - "name": "description", - "value": "playback stalled" - }, - { - "name": "visible", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaLoadStart", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when setting the URL of a media asset to play, in order to infer load time.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send loadstart metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPlay", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback should start due to autoplay, user-initiated play, or unpausing.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send play metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPlaying", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback actually starts due to autoplay, user-initiated play, unpausing, or recovering from a buffering interruption.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send playing metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaPause", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback will pause due to an intentional pause operation.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send pause metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaWaiting", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when media playback will halt due to a network, buffer, or other unintentional constraint.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send waiting metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaSeeking", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when a seek is initiated during media playback.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "target", - "summary": "Target destination of the seek, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "schema": { - "$ref": "#/components/schemas/MediaPosition" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send seeking metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "target", - "value": 0.5 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaSeeked", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when a seek is completed during media playback.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "position", - "summary": "Resulting position of the seek operation, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "schema": { - "$ref": "#/components/schemas/MediaPosition" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send seeked metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "position", - "value": 0.51 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaRateChanged", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when the playback rate of media is changed.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "rate", - "summary": "The new playback rate.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ratechange metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "rate", - "value": 2 - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaRenditionChanged", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when the playback rendition (e.g. bitrate, dimensions, profile, etc) is changed.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "bitrate", - "summary": "The new bitrate in kbps.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "width", - "summary": "The new resolution width.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "height", - "summary": "The new resolution height.", - "schema": { - "type": "number" - }, - "required": true - }, - { - "name": "profile", - "summary": "A description of the new profile, e.g. 'HDR' etc.", - "schema": { - "type": "string" - }, - "required": false - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send renditionchange metric.", - "params": [ - { - "name": "entityId", - "value": "345" - }, - { - "name": "bitrate", - "value": 5000 - }, - { - "name": "width", - "value": 1920 - }, - { - "name": "height", - "value": 1080 - }, - { - "name": "profile", - "value": "HDR+" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.mediaEnded", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:media" - ] - } - ], - "summary": "Called when playback has stopped because the end of the media was reached.", - "params": [ - { - "name": "entityId", - "summary": "The entityId of the media.", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send ended metric.", - "params": [ - { - "name": "entityId", - "value": "345" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.event", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:distributor" - ] - } - ], - "summary": "Inform the platform of 1st party distributor metrics. 'data' parameter is a JSON document", - "params": [ - { - "name": "schema", - "summary": "The schema URI of the metric type", - "schema": { - "type": "string", - "format": "uri" - }, - "required": true - }, - { - "name": "data", - "summary": "A JSON payload", - "schema": { - "type": "string" - }, - "required": true - }, - { - "name": "agePolicy", - "summary": "The age policy to associate with the metrics event. The age policy describes the age group to which content is directed.", - "schema": { - "$ref": "#/x-schemas/Policies/AgePolicy" - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send foo event", - "params": [ - { - "name": "schema", - "value": "http://meta.rdkcentral.com/some/schema" - }, - { - "name": "data", - "value": "foo" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Metrics.appInfo", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:metrics:general" - ] - } - ], - "summary": "Inform the platform about an app's build info.", - "params": [ - { - "name": "build", - "summary": "The build / version of this app.", - "schema": { - "type": "string" - }, - "required": true - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send appInfo metric", - "params": [ - { - "name": "build", - "value": "1.2.2" - } - ], - "result": { - "name": "result", - "value": null - } - } - ] - }, - { - "name": "Network.connected", - "summary": "Returns whether the device currently has a usable network connection.", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:network:connected" - ] - } - ], - "params": [], - "result": { - "name": "success", - "summary": "Whether the device currently has a usable network connection.", - "schema": { - "$ref": "#/components/schemas/Connected" - } - }, - "examples": [ - { - "name": "Connected example", - "params": [], - "result": { - "name": "success", - "value": true - } - } - ] - }, - { - "name": "Presentation.focused", - "tags": [ - { - "name": "property:readonly" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Whether the app is in focus, i.e. receiving key presses. Provided for those apps/runtimes that cannot use Wayland", - "params": [], - "result": { - "name": "focused", - "summary": "Whether the app is in focus.", - "schema": { - "type": "boolean" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "Default Result", - "value": true - } - } - ] - }, - { - "name": "Stats.memoryUsage", - "summary": "Returns information about container memory usage in bytes.", - "tags": [ - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "params": [], - "result": { - "name": "result", - "schema": { - "$ref": "#/components/schemas/MemoryUsage" - } - }, - "examples": [ - { - "name": "Default example", - "params": [], - "result": { - "name": "value", - "description": "The memory usage information", - "value": { - "userMemoryUsed": 126418944, - "userMemoryLimit": 807948288, - "gpuMemoryUsed": 353974272, - "gpuMemoryLimit": 922863616 - } - } - } - ] - }, - { - "name": "TextToSpeech.speak", - "summary": "Speak the utterance immediately. Any ongoing speech is interrupted.", - "description": "Text argument is either plain text or a well-formed SSML document TTS_status, not success attribute, to be used by caller to indicate success of call 0 OK, 1 Fail, 2 not enabled, 3 invalid configuration Raises onSpeechinterrupted if speaking is interrupted", - "params": [ - { - "name": "text", - "summary": "String to be converted to Audio for speech", - "schema": { - "type": "string" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "speakResult", - "summary": "Result for Speak", - "schema": { - "$ref": "#/components/schemas/SpeechResponse" - } - }, - "examples": [ - { - "name": "Getting the result of speak", - "params": [ - { - "name": "text", - "value": "I am a text waiting for speech." - } - ], - "result": { - "name": "result", - "value": { - "speechid": 1, - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.pause", - "summary": "Pauses the speech for given speech id", - "description": "Pauses the utterance. Raises onSpeechpause if ongoing speech is paused. Does nothing if utterance is already paused", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "pauseResult", - "summary": "Result for Pause", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Pause a given speech id", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.resume", - "summary": "Resumes the speech for given speech id", - "description": "Continue the paused utterance. Raises onSpeechresume if paused speech is resumed. Does nothing if the utterance is not paused", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "resumeResult", - "summary": "Result for Resume", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Resume a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.cancel", - "summary": "Cancels the speech for given speech id", - "description": "Stop speaking if utterance is currently being spoken. Raises onSpeechinterrupted if speaking was interrupted.", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "cancelResult", - "summary": "Result for cancel", - "schema": { - "$ref": "#/components/schemas/TTSStatusResponse" - } - }, - "examples": [ - { - "name": "Cancel a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "TTS_Status", - "value": { - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.getspeechstate", - "summary": "Returns the state of the utterance.", - "params": [ - { - "name": "speechid", - "summary": "Identifier for the speech call", - "schema": { - "$ref": "#/components/schemas/SpeechId" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "speechStateResult", - "summary": "Result for speech state", - "schema": { - "$ref": "#/components/schemas/SpeechStateResponse" - } - }, - "examples": [ - { - "name": "State for a given speech id.", - "params": [ - { - "name": "speechid", - "value": 1 - } - ], - "result": { - "name": "speechstate", - "value": { - "speechstate": 1, - "TTS_Status": 0, - "success": true - } - } - } - ] - }, - { - "name": "TextToSpeech.listvoices", - "summary": "Returns the list of available voices as human-readable strings, e.g. 'ava', 'amelie', 'angelica'", - "params": [ - { - "name": "language", - "summary": "Language - string - BCP 47", - "schema": { - "$ref": "#/x-schemas/Localization/Locale" - }, - "required": true - } - ], - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "result": { - "name": "listvoices", - "summary": "The list of voices supported for the language", - "schema": { - "$ref": "#/components/schemas/ListVoicesResponse" - } - }, - "examples": [ - { - "name": "Getting the list of voices", - "params": [ - { - "name": "language", - "value": "en-US" - } - ], - "result": { - "name": "voiceList", - "value": { - "TTS_Status": 0, - "voices": [ - "carol", - "tom" - ] - } - } - } - ] - }, - { - "name": "Lifecycle2.onStateChanged", - "tags": [ - { - "name": "event", - "x-contextual-parameters": 0, - "x-notifier": "Lifecycle2.onStateChanged" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Notification of lifecycle state change, raised after the platform has transitioned the app/runtime to the new lifecycle state", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "App is active after being initialized", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Single transition to paused state", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onWillspeak", - "summary": "Text to speech conversion is about to start.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onWillspeak" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechstart", - "summary": "Utterance is about to be spoken.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechstart" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechpause", - "summary": "Ongoing speech was paused.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechpause" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechresume", - "summary": "Paused speech was resumed.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechresume" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechcomplete", - "summary": "Speech completed successfully.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechcomplete" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onSpeechinterrupted", - "summary": "Speech was stopped, due to another call to speak or cancel.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onSpeechinterrupted" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onNetworkerror", - "summary": "Utterance failed due to network error.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onNetworkerror" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "TextToSpeech.onPlaybackerror", - "summary": "Utterance failed during playback.", - "tags": [ - { - "name": "rpc-only" - }, - { - "name": "event", - "x-notifier": "TextToSpeech.onPlaybackerror" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:text-to-speech:general" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default Example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onAudioDescriptionChanged", - "summary": "Returns the audio description setting of the device", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Accessibility.onAudioDescriptionChanged", - "x-subscriber-for": "Accessibility.audioDescription" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:audio-descriptions" - ] - } - ], - "examples": [ - { - "name": "Getting the audio description setting", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onClosedCaptionsSettingsChanged", - "summary": "Returns captions settings: enabled, and a list of zero or more languages in order of decreasing preference", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier-params-flattening": "true", - "x-notifier": "Accessibility.onClosedCaptionsSettingsChanged", - "x-subscriber-for": "Accessibility.closedCaptionsSettings" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:closed-captions" - ] - } - ], - "examples": [ - { - "name": "Getting the closed captions settings", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onHighContrastUIChanged", - "summary": "Returns the high contrast UI device setting", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Accessibility.onHighContrastUIChanged", - "x-subscriber-for": "Accessibility.highContrastUI" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:high-contrast-ui" - ] - } - ], - "examples": [ - { - "name": "High-contrast UI mode is enabled", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Accessibility.onVoiceGuidanceSettingsChanged", - "summary": "Returns voice guidance settings: enabled, rate, and verbosity", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier-params-flattening": "true", - "x-notifier": "Accessibility.onVoiceGuidanceSettingsChanged", - "x-subscriber-for": "Accessibility.voiceGuidanceSettings" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:accessibility:voice-guidance" - ] - } - ], - "examples": [ - { - "name": "Getting the voice guidance settings", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Device.onHdrChanged", - "summary": "Returns the HDR standards that are supported by the attached TV or the integral display", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Device.onHdrChanged", - "x-subscriber-for": "Device.hdr" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "examples": [ - { - "name": "Getting the negotiated HDR formats", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Device.onDolbyAtmosExperienceAvailableChanged", - "summary": "Returns whether Dolby Atmos experience is available on the device", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Device.onDolbyAtmosExperienceAvailableChanged", - "x-subscriber-for": "Device.dolbyAtmosExperienceAvailable" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:device:info" - ] - } - ], - "examples": [ - { - "name": "Getting Dolby Atmos experience availability", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onCountryChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onCountryChanged", - "x-subscriber-for": "Localization.country" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:country-code" - ] - } - ], - "summary": "Returns the ISO 3166-1 alpha-2 code for the country device is located in.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - }, - { - "name": "Another example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onPreferredAudioLanguagesChanged", - "summary": "Returns a list of ISO 639-2/B codes for the preferred audio languages on this device.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onPreferredAudioLanguagesChanged", - "x-subscriber-for": "Localization.preferredAudioLanguages" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:preferred-audio-languages" - ] - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onPresentationLanguageChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onPresentationLanguageChanged", - "x-subscriber-for": "Localization.presentationLanguage" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:locale" - ] - } - ], - "summary": "Get the *full* BCP 47 code, including script, region, variant, etc., for the preferred locale", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Localization.onTimeZoneChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Localization.onTimeZoneChanged", - "x-subscriber-for": "Localization.timeZone" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:localization:time-zone" - ] - } - ], - "summary": "Get the IANA timezone of the device.", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Network.onConnectedChanged", - "summary": "Returns whether the device currently has a usable network connection.", - "tags": [ - { - "name": "event", - "x-notifier": "Network.onConnectedChanged", - "x-subscriber-for": "Network.connected" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:network:connected" - ] - } - ], - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Connected example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - }, - { - "name": "Presentation.onFocusedChanged", - "tags": [ - { - "name": "event", - "x-notifier": "Presentation.onFocusedChanged", - "x-subscriber-for": "Presentation.focused" - }, - { - "name": "capabilities", - "x-uses": [ - "xrn:firebolt:capability:cpp-client-only" - ] - } - ], - "summary": "Whether the app is in focus, i.e. receiving key presses. Provided for those apps/runtimes that cannot use Wayland", - "params": [ - { - "name": "listen", - "schema": { - "type": "boolean" - } - } - ], - "examples": [ - { - "name": "Default example", - "params": [ - { - "name": "listen", - "value": true - } - ], - "result": { - "name": "result", - "value": null - } - } - ], - "result": { - "name": "result", - "schema": { - "type": "null" - } - } - } - ], - "components": { - "schemas": { - "AdvertisingIdResult": { - "title": "AdvertisingIdResult", - "type": "object", - "properties": { - "ifa": { - "type": "string", - "description": "UUID conforming to IAB standard" - }, - "ifa_type": { - "type": "string", - "description": "Source of the IFA as defined by IAB" - }, - "lmt": { - "type": "string", - "enum": [ - "0", - "1" - ], - "description": "Boolean that if set to 1, user has requested ad tracking and measurement is disabled" - } - }, - "required": [ - "ifa", - "ifa_type", - "lmt" - ] - }, - "HDRFormatMap": { - "title": "HDRFormatMap", - "type": "object", - "properties": { - "hdr10": { - "type": "boolean" - }, - "hdr10Plus": { - "type": "boolean" - }, - "dolbyVision": { - "type": "boolean" - }, - "hlg": { - "type": "boolean" - } - }, - "required": [ - "hdr10", - "hdr10Plus", - "dolbyVision", - "hlg" - ], - "description": "The type of HDR format" - }, - "DeviceClass": { - "title": "DeviceClass", - "type": "string", - "enum": [ - "ott", - "stb", - "tv" - ], - "description": "The type of device" - }, - "CloseType": { - "title": "CloseType", - "description": "The application close type", - "type": "string", - "enum": [ - "deactivate", - "unload", - "killReload", - "killReactivate" - ] - }, - "LifecycleState": { - "title": "LifecycleState", - "description": "The application Lifecycle state", - "type": "string", - "enum": [ - "initializing", - "active", - "paused", - "suspended", - "hibernated", - "terminating" - ] - }, - "StateChange": { - "title": "StateChange", - "type": "object", - "properties": { - "newState": { - "$ref": "#/components/schemas/LifecycleState" - }, - "oldState": { - "$ref": "#/components/schemas/LifecycleState" - } - } - }, - "MediaPosition": { - "title": "MediaPosition", - "description": "Represents a position inside playback content, as a decimal percentage (0-0.999) for content with a known duration, or an integer number of seconds (0-86400) for content with an unknown duration.", - "oneOf": [ - { - "const": 0 - }, - { - "type": "number", - "exclusiveMinimum": 0, - "exclusiveMaximum": 1 - }, - { - "type": "integer", - "minimum": 1, - "maximum": 86400 - } - ] - }, - "ErrorType": { - "title": "ErrorType", - "type": "string", - "enum": [ - "network", - "media", - "restriction", - "entitlement", - "other" - ] - }, - "EventObjectPrimitives": { - "title": "EventObjectPrimitives", - "anyOf": [ - { - "type": "string", - "maxLength": 256 - }, - { - "type": "number" - }, - { - "type": "integer" - }, - { - "type": "boolean" - }, - { - "type": "null" - } - ] - }, - "Connected": { - "type": "boolean", - "description": "Indicates whether the device currently has a usable network connection." - }, - "MemoryUsage": { - "title": "MemoryUsage", - "type": "object", - "description": "Describes current and maximum memory usage of the container.", - "properties": { - "userMemoryUsed": { - "type": "integer", - "description": "User memory currently used, in bytes.", - "minimum": 0 - }, - "userMemoryLimit": { - "type": "integer", - "description": "Maximum user memory available, in bytes.", - "minimum": 0 - }, - "gpuMemoryUsed": { - "type": "integer", - "description": "GPU memory currently used, in bytes.", - "minimum": 0 - }, - "gpuMemoryLimit": { - "type": "integer", - "description": "Maximum GPU memory available, in bytes.", - "minimum": 0 - } - }, - "required": [ - "userMemoryUsed", - "userMemoryLimit", - "gpuMemoryUsed", - "gpuMemoryLimit" - ] - }, - "TTSEnabled": { - "title": "TTSEnabled", - "type": "object", - "required": [ - "TTS_Status", - "isenabled" - ], - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "isenabled": { - "type": "boolean" - } - } - }, - "ListVoicesResponse": { - "title": "ListVoicesResponse", - "type": "object", - "required": [ - "TTS_Status", - "voices" - ], - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "voices": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "TTSConfiguration": { - "title": "TTSConfiguration", - "type": "object", - "required": [ - "success" - ], - "properties": { - "success": { - "type": "boolean" - }, - "ttsendpoint": { - "type": "string", - "description": "URL for Text to Speech API" - }, - "ttsendpointsecured": { - "type": "string", - "description": "Secure URL for Text to Speech API" - }, - "language": { - "type": "string", - "description": "Language used by Text to speech" - }, - "voice": { - "type": "string", - "description": "Voice used by Text to speech" - }, - "volume": { - "type": "integer", - "description": "Volume for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "primvolduckpercent": { - "type": "integer", - "description": "Prime Volume duck percent for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "rate": { - "type": "integer", - "description": "Speech rate for Text to speech", - "minimum": 0, - "maximum": 100 - }, - "speechrate": { - "description": "Rate for speech", - "$ref": "#/components/schemas/SpeechRate" - }, - "fallbacktext": { - "description": "Fallback text for TTS", - "$ref": "#/components/schemas/FallbackText" - } - }, - "examples": [ - {} - ] - }, - "SpeechRate": { - "title": "SpeechRate", - "type": "string", - "enum": [ - "slow", - "medium", - "fast", - "faster", - "fastest" - ] - }, - "FallbackText": { - "title": "FallbackText", - "type": "object", - "properties": { - "scenario": { - "type": "string", - "description": "Scenario for fallback Text" - }, - "value": { - "type": "string", - "description": "Value for fallback Text" - } - } - }, - "SpeechResponse": { - "title": "SpeechResponse", - "type": "object", - "properties": { - "speechid": { - "$ref": "#/components/schemas/SpeechId" - }, - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "speechid", - "TTS_Status", - "success" - ] - }, - "SpeechId": { - "type": "integer" - }, - "SpeechIdEvent": { - "type": "object", - "properties": { - "speechid": { - "$ref": "#/components/schemas/SpeechId" - } - }, - "required": [ - "speechid" - ] - }, - "TTSStatus": { - "title": "TTSStatus", - "type": "integer", - "minimum": 0, - "maximum": 3 - }, - "SpeechState": { - "title": "SpeechState", - "type": "integer", - "enum": [ - 0, - 1, - 2, - 3 - ], - "description": "0 = SPEECH_PENDING, 1 = SPEECH_IN_PROGRESS, 2 = SPEECH_PAUSED, 3 = SPEECH_NOT_FOUND" - }, - "SpeechStateResponse": { - "title": "SpeechStateResponse", - "type": "object", - "properties": { - "speechstate": { - "$ref": "#/components/schemas/SpeechState" - }, - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "speechstate", - "TTS_Status", - "success" - ] - }, - "TTSStatusResponse": { - "title": "TTSStatusResponse", - "type": "object", - "properties": { - "TTS_Status": { - "$ref": "#/components/schemas/TTSStatus" - }, - "success": { - "type": "boolean" - } - }, - "required": [ - "TTS_Status", - "success" - ] - }, - "TTSState": { - "title": "TTSState", - "type": "object", - "properties": { - "state": { - "type": "boolean" - } - }, - "required": [ - "state" - ] - }, - "TTSVoice": { - "title": "TTSVoice", - "type": "object", - "properties": { - "voice": { - "type": "string" - } - }, - "required": [ - "voice" - ] - } - } - }, - "x-schemas": { - "Accessibility": { - "uri": "https://meta.comcast.com/firebolt/accessibility", - "ClosedCaptionsSettings": { - "title": "ClosedCaptionsSettings", - "type": "object", - "required": [ - "enabled" - ], - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether or not closed-captions should be enabled by default" - }, - "preferredLanguages": { - "type": "array", - "items": { - "$ref": "#/x-schemas/Localization/ISO639_2Language" - } - } - }, - "examples": [ - { - "enabled": true, - "styles": { - "fontFamily": "monospaced_serif", - "fontSize": 1, - "fontColor": "#ffffff", - "fontEdge": "none", - "fontEdgeColor": "#7F7F7F", - "fontOpacity": 100, - "backgroundColor": "#000000", - "backgroundOpacity": 100, - "textAlign": "center", - "textAlignVertical": "middle", - "windowColor": "white", - "windowOpacity": 50 - }, - "preferredLanguages": [ - "eng", - "spa" - ] - } - ] - }, - "VoiceGuidanceSettings": { - "title": "VoiceGuidanceSettings", - "type": "object", - "required": [ - "enabled", - "navigationHints", - "rate" - ], - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether or not voice guidance should be enabled by default" - }, - "rate": { - "$ref": "#/x-schemas/Accessibility/SpeechRate", - "description": "The rate at which voice guidance speech will be read back to the user" - }, - "navigationHints": { - "type": "boolean", - "description": "Whether or not voice guidance should include additional navigation hints" - } - }, - "examples": [ - { - "enabled": true, - "navigationHints": true, - "rate": 0.8 - } - ] - }, - "SpeechRate": { - "title": "SpeechRate", - "type": "number", - "minimum": 0.1, - "maximum": 10 - } - }, - "Localization": { - "uri": "https://meta.comcast.com/firebolt/localization", - "ISO639_2Language": { - "type": "string", - "pattern": "^[a-z]{3}$" - }, - "CountryCode": { - "type": "string", - "pattern": "^[A-Z]{2}$" - }, - "Locale": { - "type": "string", - "pattern": "^[a-zA-Z]+([a-zA-Z0-9\\-]*)$" - } - }, - "Policies": { - "uri": "https://meta.comcast.com/firebolt/policies", - "AgePolicy": { - "title": "AgePolicy", - "description": "The policy that describes various age groups to which content is directed. See distributor documentation for further details.", - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [ - "app:adult", - "app:child", - "app:teen" - ] - } - ] - } - }, - "Types": { - "uri": "https://meta.comcast.com/firebolt/types", - "FlatMap": { - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - } - } - } - } ->>>>>>> 1b42981141ea15e3f4133db05f5ac38d7c1c29b0 -} diff --git a/src/videooutput_impl.h b/src/videooutput_impl.h index 61a3dd0..8d2b4ec 100644 --- a/src/videooutput_impl.h +++ b/src/videooutput_impl.h @@ -15,10 +15,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -// -// ============================================================================ -// AUTO-GENERATED by firebolt-sdk-gen v0.1.0 — DO NOT EDIT -// ============================================================================ +#pragma once #ifndef FIREBOLT_VIDEOOUTPUT_IMPL_H #define FIREBOLT_VIDEOOUTPUT_IMPL_H @@ -34,9 +31,9 @@ class VideoOutputImpl : public IVideoOutput explicit VideoOutputImpl(Firebolt::Helpers::IHelper& helper); VideoOutputImpl(const VideoOutputImpl&) = delete; VideoOutputImpl& operator=(const VideoOutputImpl&) = delete; - ~VideoOutputImpl() override = default; VideoOutputImpl(VideoOutputImpl&&) = delete; VideoOutputImpl& operator=(VideoOutputImpl&&) = delete; + ~VideoOutputImpl() override = default; [[nodiscard]] Result resolution() const override; Result subscribeOnResolutionChanged(std::function&& notification) override; From 7b48980b1a114784a800c81c9a5cf468c2c5f3be Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 10 Aug 2026 13:42:52 -0700 Subject: [PATCH 24/39] RDKEMW-14869: chore lint --- src/json_types/videooutput.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/json_types/videooutput.h b/src/json_types/videooutput.h index 89cfbd1..aac0c26 100644 --- a/src/json_types/videooutput.h +++ b/src/json_types/videooutput.h @@ -92,7 +92,6 @@ NLOHMANN_JSON_SERIALIZE_ENUM(RefreshRateValue, { {RefreshRateValue::R60, "60"}, }) - inline const Firebolt::JSON::EnumType<::Firebolt::VideoOutput::CecStateValue> CecStateValueEnum({ {"active", ::Firebolt::VideoOutput::CecStateValue::Active}, {"inactive", ::Firebolt::VideoOutput::CecStateValue::Inactive}, @@ -177,7 +176,6 @@ class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::V uint32_t height_{}; uint32_t width_{}; }; - } // namespace JsonData inline void to_json(nlohmann::json& j, const Firebolt::VideoOutput::VideoOutputResolution& v) @@ -185,8 +183,6 @@ inline void to_json(nlohmann::json& j, const Firebolt::VideoOutput::VideoOutputR j = nlohmann::json::object(); j["height"] = v.height; j["width"] = v.width; -} - - // namespace Firebolt::VideoOutput +} // namespace Firebolt::VideoOutput #endif // FIREBOLT_VIDEOOUTPUT_JSON_H From 002e518744d21b80e4c46ac5752fc23261c176c6 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 10 Aug 2026 14:36:19 -0700 Subject: [PATCH 25/39] RDKEMW-14869: more unit tests --- test/unit/videooutputGeneratedTest.cpp | 408 ++++++++++++++++++++++++- 1 file changed, 398 insertions(+), 10 deletions(-) diff --git a/test/unit/videooutputGeneratedTest.cpp b/test/unit/videooutputGeneratedTest.cpp index 958291e..57711db 100644 --- a/test/unit/videooutputGeneratedTest.cpp +++ b/test/unit/videooutputGeneratedTest.cpp @@ -18,11 +18,39 @@ #include "mock_helper.h" #include "videooutput_impl.h" +#include #include class VideooutputGeneratedUTest : public ::testing::Test { protected: + static bool areGetterParamsEmpty(const nlohmann::json& params) + { + return params.is_null() || (params.is_object() && params.empty()); + } + + void expectGetterResponse(const std::string& methodName, const nlohmann::json& response) + { + EXPECT_CALL(mockHelper, getJson(methodName, ::testing::_)) + .WillOnce(::testing::Invoke([methodName, response](const std::string& /*method*/, const nlohmann::json& params) + { + EXPECT_TRUE(areGetterParamsEmpty(params)) + << methodName << " getter should not send request params"; + return Firebolt::Result{response}; + })); + } + + void expectGetterTransportError(const std::string& methodName, Firebolt::Error error = Firebolt::Error::General) + { + EXPECT_CALL(mockHelper, getJson(methodName, ::testing::_)) + .WillOnce(::testing::Invoke([methodName, error](const std::string& /*method*/, const nlohmann::json& params) + { + EXPECT_TRUE(areGetterParamsEmpty(params)) + << methodName << " getter should not send request params"; + return Firebolt::Result{error}; + })); + } + ::testing::NiceMock mockHelper; Firebolt::VideoOutput::VideoOutputImpl impl{mockHelper}; }; @@ -40,22 +68,382 @@ TEST_F(VideooutputGeneratedUTest, UnsubscribeForwardsToHelper) ASSERT_TRUE(result) << "unsubscribe should return success when helper succeeds"; } -TEST_F(VideooutputGeneratedUTest, ForwardsresolutionTransportErrors) +TEST_F(VideooutputGeneratedUTest, UnsubscribeForwardsHelperErrors) { - EXPECT_CALL(mockHelper, getJson("VideoOutput.resolution", ::testing::_)) - .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& /*params*/) - { return Firebolt::Result{Firebolt::Error::General}; })); + EXPECT_CALL(mockHelper, unsubscribe(42)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = impl.unsubscribe(42); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, UnsubscribeAllForwardsToHelper) +{ + EXPECT_CALL(mockHelper, unsubscribeAll(&impl)).Times(2); + + impl.unsubscribeAll(); +} + +TEST_F(VideooutputGeneratedUTest, ResolutionReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.resolution", nlohmann::json{{"height", 1080}, {"width", 1920}}); + + auto result = impl.resolution(); + ASSERT_TRUE(result); + EXPECT_EQ(result->height, 1080U); + EXPECT_EQ(result->width, 1920U); +} + +TEST_F(VideooutputGeneratedUTest, ResolutionForwardsTransportErrors) +{ + expectGetterTransportError("VideoOutput.resolution"); auto result = impl.resolution(); - EXPECT_FALSE(result) << "Expected error propagation when helper getJson fails"; + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, ResolutionReturnsInvalidParamsWhenPayloadIsMalformed) +{ + expectGetterResponse("VideoOutput.resolution", nlohmann::json{{"width", 1920}}); + + auto result = impl.resolution(); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::InvalidParams); +} + +TEST_F(VideooutputGeneratedUTest, HdcpReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.hdcp", nlohmann::json(static_cast(Firebolt::VideoOutput::HdcpState::Hdcp22))); + + auto result = impl.hdcp(); + ASSERT_TRUE(result); + EXPECT_EQ(*result, Firebolt::VideoOutput::HdcpState::Hdcp22); +} + +TEST_F(VideooutputGeneratedUTest, HdcpForwardsTransportErrors) +{ + expectGetterTransportError("VideoOutput.hdcp"); + + auto result = impl.hdcp(); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, CecStateReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.cecState", + nlohmann::json(static_cast(Firebolt::VideoOutput::CecStateValue::Inactive))); + + auto result = impl.cecState(); + ASSERT_TRUE(result); + EXPECT_EQ(*result, Firebolt::VideoOutput::CecStateValue::Inactive); } -TEST_F(VideooutputGeneratedUTest, ForwardscolorDepthTransportErrors) +TEST_F(VideooutputGeneratedUTest, CecStateForwardsTransportErrors) { - EXPECT_CALL(mockHelper, getJson("VideoOutput.colorDepth", ::testing::_)) - .WillOnce(::testing::Invoke([](const std::string& /*method*/, const nlohmann::json& /*params*/) - { return Firebolt::Result{Firebolt::Error::General}; })); + expectGetterTransportError("VideoOutput.cecState"); + + auto result = impl.cecState(); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, RefreshRateReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.refreshRate", + nlohmann::json(static_cast(Firebolt::VideoOutput::RefreshRateValue::R5994))); + + auto result = impl.refreshRate(); + ASSERT_TRUE(result); + EXPECT_EQ(*result, Firebolt::VideoOutput::RefreshRateValue::R5994); +} + +TEST_F(VideooutputGeneratedUTest, RefreshRateForwardsTransportErrors) +{ + expectGetterTransportError("VideoOutput.refreshRate"); + + auto result = impl.refreshRate(); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, ColorDepthReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.colorDepth", + nlohmann::json(static_cast(Firebolt::VideoOutput::ColorDepthValue::D12))); + + auto result = impl.colorDepth(); + ASSERT_TRUE(result); + EXPECT_EQ(*result, Firebolt::VideoOutput::ColorDepthValue::D12); +} + +TEST_F(VideooutputGeneratedUTest, ColorDepthForwardsTransportErrors) +{ + expectGetterTransportError("VideoOutput.colorDepth"); auto result = impl.colorDepth(); - EXPECT_FALSE(result) << "Expected error propagation when helper getJson fails"; + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, ColorFormatReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.colorFormat", + nlohmann::json(static_cast(Firebolt::VideoOutput::ColorFormatValue::Ycbcr444))); + + auto result = impl.colorFormat(); + ASSERT_TRUE(result); + EXPECT_EQ(*result, Firebolt::VideoOutput::ColorFormatValue::Ycbcr444); +} + +TEST_F(VideooutputGeneratedUTest, ColorFormatForwardsTransportErrors) +{ + expectGetterTransportError("VideoOutput.colorFormat"); + + auto result = impl.colorFormat(); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, ColorimetryReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.colorimetry", + nlohmann::json(static_cast(Firebolt::VideoOutput::OutputColorimetry::Bt2020rgb))); + + auto result = impl.colorimetry(); + ASSERT_TRUE(result); + EXPECT_EQ(*result, Firebolt::VideoOutput::OutputColorimetry::Bt2020rgb); +} + +TEST_F(VideooutputGeneratedUTest, ColorimetryForwardsTransportErrors) +{ + expectGetterTransportError("VideoOutput.colorimetry"); + + auto result = impl.colorimetry(); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, DynamicRangeReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.dynamicRange", + nlohmann::json(static_cast(Firebolt::VideoOutput::DynamicRangeValue::Hdr10plus))); + + auto result = impl.dynamicRange(); + ASSERT_TRUE(result); + EXPECT_EQ(*result, Firebolt::VideoOutput::DynamicRangeValue::Hdr10plus); +} + +TEST_F(VideooutputGeneratedUTest, DynamicRangeForwardsTransportErrors) +{ + expectGetterTransportError("VideoOutput.dynamicRange"); + + auto result = impl.dynamicRange(); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, QuantizationRangeReturnsParsedValue) +{ + expectGetterResponse("VideoOutput.quantizationRange", + nlohmann::json(static_cast(Firebolt::VideoOutput::QuantizationRangeValue::Limited))); + + auto result = impl.quantizationRange(); + ASSERT_TRUE(result); + EXPECT_EQ(*result, Firebolt::VideoOutput::QuantizationRangeValue::Limited); +} + +TEST_F(VideooutputGeneratedUTest, QuantizationRangeForwardsTransportErrors) +{ + expectGetterTransportError("VideoOutput.quantizationRange"); + + auto result = impl.quantizationRange(); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnResolutionChangedForwardsAndDispatchesParsedPayload) +{ + bool notified = false; + Firebolt::VideoOutput::VideoOutputResolution received{}; + + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onResolutionChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, + std::move(notification)}; + callback(&data, nlohmann::json{{"height", 2160}, {"width", 3840}}); + return Firebolt::Result{99}; + })); + + auto result = impl.subscribeOnResolutionChanged( + [&](const Firebolt::VideoOutput::VideoOutputResolution& value) + { + notified = true; + received = value; + }); + + ASSERT_TRUE(result); + EXPECT_EQ(*result, 99U); + EXPECT_TRUE(notified); + EXPECT_EQ(received.height, 2160U); + EXPECT_EQ(received.width, 3840U); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnResolutionChangedSwallowsMalformedEventPayload) +{ + bool notified = false; + + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onResolutionChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, + std::move(notification)}; + callback(&data, nlohmann::json{{"width", 3840}}); + return Firebolt::Result{5}; + })); + + auto result = impl.subscribeOnResolutionChanged( + [&](const Firebolt::VideoOutput::VideoOutputResolution& /*value*/) { notified = true; }); + + ASSERT_TRUE(result); + EXPECT_EQ(*result, 5U); + EXPECT_FALSE(notified); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnResolutionChangedForwardsSubscribeErrors) +{ + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onResolutionChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = impl.subscribeOnResolutionChanged( + [](const Firebolt::VideoOutput::VideoOutputResolution& /*value*/) {}); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnHdcpChangedForwardsAndDispatchesParsedPayload) +{ + bool notified = false; + Firebolt::VideoOutput::HdcpState received = Firebolt::VideoOutput::HdcpState::None; + + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onHdcpChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, + std::move(notification)}; + callback(&data, + nlohmann::json(static_cast(Firebolt::VideoOutput::HdcpState::Direct))); + return Firebolt::Result{11}; + })); + + auto result = impl.subscribeOnHdcpChanged( + [&](const Firebolt::VideoOutput::HdcpState& value) + { + notified = true; + received = value; + }); + + ASSERT_TRUE(result); + EXPECT_EQ(*result, 11U); + EXPECT_TRUE(notified); + EXPECT_EQ(received, Firebolt::VideoOutput::HdcpState::Direct); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnHdcpChangedForwardsSubscribeErrors) +{ + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onHdcpChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = impl.subscribeOnHdcpChanged([](const Firebolt::VideoOutput::HdcpState& /*value*/) {}); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnCecStateChangedForwardsAndDispatchesParsedPayload) +{ + bool notified = false; + Firebolt::VideoOutput::CecStateValue received = Firebolt::VideoOutput::CecStateValue::Unsupported; + + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onCecStateChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, + std::move(notification)}; + callback(&data, + nlohmann::json(static_cast(Firebolt::VideoOutput::CecStateValue::Active))); + return Firebolt::Result{12}; + })); + + auto result = impl.subscribeOnCecStateChanged( + [&](const Firebolt::VideoOutput::CecStateValue& value) + { + notified = true; + received = value; + }); + + ASSERT_TRUE(result); + EXPECT_EQ(*result, 12U); + EXPECT_TRUE(notified); + EXPECT_EQ(received, Firebolt::VideoOutput::CecStateValue::Active); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnCecStateChangedForwardsSubscribeErrors) +{ + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onCecStateChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = impl.subscribeOnCecStateChanged([](const Firebolt::VideoOutput::CecStateValue& /*value*/) {}); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnRefreshRateChangedForwardsAndDispatchesParsedPayload) +{ + bool notified = false; + Firebolt::VideoOutput::RefreshRateValue received = Firebolt::VideoOutput::RefreshRateValue::R0; + + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onRefreshRateChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, + std::move(notification)}; + callback(&data, + nlohmann::json(static_cast(Firebolt::VideoOutput::RefreshRateValue::R24))); + return Firebolt::Result{13}; + })); + + auto result = impl.subscribeOnRefreshRateChanged( + [&](const Firebolt::VideoOutput::RefreshRateValue& value) + { + notified = true; + received = value; + }); + + ASSERT_TRUE(result); + EXPECT_EQ(*result, 13U); + EXPECT_TRUE(notified); + EXPECT_EQ(received, Firebolt::VideoOutput::RefreshRateValue::R24); +} + +TEST_F(VideooutputGeneratedUTest, SubscribeOnRefreshRateChangedForwardsSubscribeErrors) +{ + EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onRefreshRateChanged", ::testing::_, ::testing::_)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = impl.subscribeOnRefreshRateChanged( + [](const Firebolt::VideoOutput::RefreshRateValue& /*value*/) {}); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); } From 5701c8dda1c36c51dcecccd8a601b9f84f1eb290 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Tue, 11 Aug 2026 10:10:09 -0700 Subject: [PATCH 26/39] RDKEMW-14869: chore - align lint.sh w/ci --- README.md | 11 +- lint.sh | 220 ++++++++----------------- src/json_types/videooutput.h | 2 +- test/unit/videooutputGeneratedTest.cpp | 122 +++++++------- 4 files changed, 137 insertions(+), 218 deletions(-) diff --git a/README.md b/README.md index 085c0dc..2a388b7 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,17 @@ Before running it, export `DEVICE_SSH_USER`, `DEVICE_SSH_HOST`, and `DEVICE_SSH_ ## Lint -Use `lint.sh` to run local static analysis for C/C++ sources. +Use `lint.sh` to run the same clang-format lint that CI enforces. +It checks tracked `*.cpp` and `*.h` files using: + +- `git ls-files -- '*.cpp' '*.h' | xargs clang-format --dry-run --Werror` Examples: - `./lint.sh` -- `./lint.sh --tidy-only` -- `./lint.sh --tidy-only --fix` -- `./lint.sh --cppcheck-only` +- `./lint.sh --fix` +- `./lint.sh --local` +- `SKIP_DOCKER=1 ./lint.sh` ## Coverity diff --git a/lint.sh b/lint.sh index 114d887..17a1ced 100755 --- a/lint.sh +++ b/lint.sh @@ -19,78 +19,61 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BUILD_DIR="build-dev" -NO_BUILD=false -CLEAN=false -RUN_CLANG_TIDY=true -RUN_CPPCHECK=true -APPLY_FIXES=false -CLANG_TIDY_PATHS=(src include test/unit test/component) +MODE="check" +USE_DOCKER="auto" +IMAGE="${LINT_DOCKER_IMAGE:-firebolt-cpp-client-fmt:local}" usage() { cat < Build directory containing compile_commands.json (default: build-dev) - --tidy-path

Add path for clang-tidy scan (repeatable) - --fix Apply clang-tidy fix-its (clang-tidy only) - --tidy-only Run clang-tidy only - --cppcheck-only Run cppcheck only - --help Show this help + --fix Reformat files in place (same file set as CI) + --check Explicitly run check mode (default) + --docker Force Docker execution + --local Force local clang-format execution + --image Docker image name (default: firebolt-cpp-client-fmt:local) + --help Show this help + +Environment: + SKIP_DOCKER=1 Same as --local Examples: ./lint.sh - ./lint.sh --tidy-only - ./lint.sh --tidy-only --fix - ./lint.sh --tidy-path test/api_test_app - ./lint.sh --no-build --build-dir build-dev + ./lint.sh --fix + ./lint.sh --docker + SKIP_DOCKER=1 ./lint.sh EOF } while [[ $# -gt 0 ]]; do case "$1" in - --clean) - CLEAN=true + --fix) + MODE="fix" ;; - --no-build) - NO_BUILD=true + --check) + MODE="check" ;; - --build-dir) - if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then - echo "Missing value for --build-dir" >&2 - usage - exit 1 - fi - BUILD_DIR="${2:-}" - shift + --docker) + USE_DOCKER="true" + ;; + --local) + USE_DOCKER="false" ;; - --tidy-path) + --image) if [[ $# -lt 2 || -z "${2:-}" || "$2" == --* ]]; then - echo "Missing value for --tidy-path" >&2 + echo "Missing value for --image" >&2 usage exit 1 fi - CLANG_TIDY_PATHS+=("${2:-}") + IMAGE="$2" shift ;; - --fix) - APPLY_FIXES=true - ;; - --tidy-only) - RUN_CLANG_TIDY=true - RUN_CPPCHECK=false - ;; - --cppcheck-only) - RUN_CLANG_TIDY=false - RUN_CPPCHECK=true - ;; --help|-h) usage exit 0 @@ -104,116 +87,55 @@ while [[ $# -gt 0 ]]; do shift done -cd "$ROOT_DIR" - -if [[ "$RUN_CLANG_TIDY" == true && "$NO_BUILD" == false && "$BUILD_DIR" != "build-dev" ]]; then - echo "--build-dir is only supported with --no-build (build step always uses build-dev)." >&2 - exit 1 -fi - -if [[ "$RUN_CLANG_TIDY" == false && "$RUN_CPPCHECK" == false ]]; then - echo "Nothing to run: clang-tidy and cppcheck are both disabled." >&2 - exit 1 -fi - -if [[ "$APPLY_FIXES" == true && "$RUN_CLANG_TIDY" == false ]]; then - echo "--fix requires clang-tidy to be enabled (remove --cppcheck-only)." >&2 - exit 1 -fi - -if [[ "$RUN_CLANG_TIDY" == true ]] && ! command -v clang-tidy >/dev/null 2>&1; then - echo "clang-tidy not found. Install it (e.g. apt install clang-tidy)." >&2 - exit 1 -fi - -if [[ "$RUN_CPPCHECK" == true ]] && ! command -v cppcheck >/dev/null 2>&1; then - echo "cppcheck not found. Install it (e.g. apt install cppcheck)." >&2 - exit 1 -fi - -if [[ "$CLEAN" == true ]]; then - rm -rf "$BUILD_DIR" -fi - -if [[ "$NO_BUILD" == false && "$RUN_CLANG_TIDY" == true ]]; then - ./build.sh +tests -fi - -if [[ "$RUN_CLANG_TIDY" == true && ! -f "$BUILD_DIR/compile_commands.json" ]]; then - echo "Missing $BUILD_DIR/compile_commands.json. Run ./build.sh +tests first." >&2 - exit 1 -fi - -if [[ "$RUN_CLANG_TIDY" == true ]]; then - if [[ "$APPLY_FIXES" == true ]]; then - echo "[lint] Running clang-tidy with fixes enabled" +if [[ "${SKIP_DOCKER:-0}" == "1" ]]; then + USE_DOCKER="false" +elif [[ "$USE_DOCKER" == "auto" ]]; then + if command -v docker >/dev/null 2>&1; then + USE_DOCKER="true" else - echo "[lint] Running clang-tidy" - fi - - existing_paths=() - for p in "${CLANG_TIDY_PATHS[@]}"; do - if [[ -e "$p" ]]; then - existing_paths+=("$p") - fi - done - - if [[ ${#existing_paths[@]} -eq 0 ]]; then - echo "No valid clang-tidy paths found." >&2 - exit 1 + USE_DOCKER="false" fi +fi - mapfile -t source_files < <( - find "${existing_paths[@]}" -type f \( -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.cxx" \) | sort - ) +cd "$ROOT_DIR" - if [[ ${#source_files[@]} -eq 0 ]]; then - echo "No C/C++ source files found for clang-tidy." >&2 - exit 1 +if [[ "$USE_DOCKER" == "true" ]]; then + if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then + echo "[lint] Building Docker image '$IMAGE' with clang-format (one-time)" + docker build -t "$IMAGE" - <<'DOCKERFILE' +FROM ubuntu:24.04 +RUN apt-get update \ + && apt-get install -y --no-install-recommends clang-format git \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /workspace +DOCKERFILE fi - clang_tidy_failed=0 - total_files=${#source_files[@]} - NPROC=$(nproc 2>/dev/null || echo 4) - - if [[ "$APPLY_FIXES" == false ]] && command -v run-clang-tidy >/dev/null 2>&1; then - echo "[lint][clang-tidy] Running ${total_files} files in parallel (${NPROC} jobs)" - if ! run-clang-tidy -p "$BUILD_DIR" -j "$NPROC" "${source_files[@]}"; then - clang_tidy_failed=1 - fi + if [[ "$MODE" == "fix" ]]; then + echo "[lint] Running CI-equivalent clang-format file set in FIX mode via Docker" + docker run --rm --user "$(id -u):$(id -g)" -v "$ROOT_DIR:/workspace" "$IMAGE" \ + bash -lc "set -e && git ls-files -- '*.cpp' '*.h' | xargs clang-format -i" + echo "[lint] Formatting fixes applied" else - index=0 - for f in "${source_files[@]}"; do - index=$((index + 1)) - echo "[lint][clang-tidy] ${index}/${total_files}: $f" - clang_tidy_cmd=(clang-tidy -p "$BUILD_DIR") - if [[ "$APPLY_FIXES" == true ]]; then - clang_tidy_cmd+=("-fix") - fi - clang_tidy_cmd+=("$f") - if ! "${clang_tidy_cmd[@]}"; then - clang_tidy_failed=1 - fi - done + echo "[lint] Running CI-equivalent clang-format check via Docker" + docker run --rm --user "$(id -u):$(id -g)" -v "$ROOT_DIR:/workspace" "$IMAGE" \ + bash -lc "set -e && git ls-files -- '*.cpp' '*.h' | xargs clang-format --dry-run --Werror" + echo "[lint] Formatting OK" fi - - if [[ $clang_tidy_failed -ne 0 ]]; then - echo "clang-tidy reported issues." >&2 +else + if ! command -v clang-format >/dev/null 2>&1; then + echo "clang-format not found. Install it or run without SKIP_DOCKER=1." >&2 exit 1 fi -fi -if [[ "$RUN_CPPCHECK" == true ]]; then - echo "[lint] Running cppcheck" - cppcheck \ - --enable=warning,style,performance,portability \ - --std=c++17 \ - --language=c++ \ - --inline-suppr \ - --error-exitcode=1 \ - -I include \ - -I src \ - src include test + echo "[lint] Using local clang-format: $(clang-format --version)" + if [[ "$MODE" == "fix" ]]; then + echo "[lint] Running CI-equivalent clang-format file set in FIX mode" + git ls-files -- '*.cpp' '*.h' | xargs clang-format -i + echo "[lint] Formatting fixes applied" + else + echo "[lint] Running CI-equivalent clang-format check" + git ls-files -- '*.cpp' '*.h' | xargs clang-format --dry-run --Werror + echo "[lint] Formatting OK" + fi fi - -echo "[lint] Completed successfully" diff --git a/src/json_types/videooutput.h b/src/json_types/videooutput.h index aac0c26..ba041fa 100644 --- a/src/json_types/videooutput.h +++ b/src/json_types/videooutput.h @@ -176,7 +176,7 @@ class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::V uint32_t height_{}; uint32_t width_{}; }; -} // namespace JsonData +} // namespace Firebolt::VideoOutput::JsonData inline void to_json(nlohmann::json& j, const Firebolt::VideoOutput::VideoOutputResolution& v) { diff --git a/test/unit/videooutputGeneratedTest.cpp b/test/unit/videooutputGeneratedTest.cpp index 57711db..736ac6c 100644 --- a/test/unit/videooutputGeneratedTest.cpp +++ b/test/unit/videooutputGeneratedTest.cpp @@ -32,23 +32,23 @@ class VideooutputGeneratedUTest : public ::testing::Test void expectGetterResponse(const std::string& methodName, const nlohmann::json& response) { EXPECT_CALL(mockHelper, getJson(methodName, ::testing::_)) - .WillOnce(::testing::Invoke([methodName, response](const std::string& /*method*/, const nlohmann::json& params) - { - EXPECT_TRUE(areGetterParamsEmpty(params)) - << methodName << " getter should not send request params"; - return Firebolt::Result{response}; - })); + .WillOnce(::testing::Invoke( + [methodName, response](const std::string& /*method*/, const nlohmann::json& params) + { + EXPECT_TRUE(areGetterParamsEmpty(params)) << methodName << " getter should not send request params"; + return Firebolt::Result{response}; + })); } void expectGetterTransportError(const std::string& methodName, Firebolt::Error error = Firebolt::Error::General) { EXPECT_CALL(mockHelper, getJson(methodName, ::testing::_)) - .WillOnce(::testing::Invoke([methodName, error](const std::string& /*method*/, const nlohmann::json& params) - { - EXPECT_TRUE(areGetterParamsEmpty(params)) - << methodName << " getter should not send request params"; - return Firebolt::Result{error}; - })); + .WillOnce(::testing::Invoke( + [methodName, error](const std::string& /*method*/, const nlohmann::json& params) + { + EXPECT_TRUE(areGetterParamsEmpty(params)) << methodName << " getter should not send request params"; + return Firebolt::Result{error}; + })); } ::testing::NiceMock mockHelper; @@ -70,8 +70,7 @@ TEST_F(VideooutputGeneratedUTest, UnsubscribeForwardsToHelper) TEST_F(VideooutputGeneratedUTest, UnsubscribeForwardsHelperErrors) { - EXPECT_CALL(mockHelper, unsubscribe(42)) - .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + EXPECT_CALL(mockHelper, unsubscribe(42)).WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); auto result = impl.unsubscribe(42); ASSERT_FALSE(result); @@ -270,14 +269,14 @@ TEST_F(VideooutputGeneratedUTest, SubscribeOnResolutionChangedForwardsAndDispatc Firebolt::VideoOutput::VideoOutputResolution received{}; EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onResolutionChanged", ::testing::_, ::testing::_)) - .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, - void (*callback)(void*, const nlohmann::json&)) - { - Firebolt::Helpers::SubscriptionData data{owner, eventName, - std::move(notification)}; - callback(&data, nlohmann::json{{"height", 2160}, {"width", 3840}}); - return Firebolt::Result{99}; - })); + .WillOnce(::testing::Invoke( + [&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, std::move(notification)}; + callback(&data, nlohmann::json{{"height", 2160}, {"width", 3840}}); + return Firebolt::Result{99}; + })); auto result = impl.subscribeOnResolutionChanged( [&](const Firebolt::VideoOutput::VideoOutputResolution& value) @@ -298,17 +297,17 @@ TEST_F(VideooutputGeneratedUTest, SubscribeOnResolutionChangedSwallowsMalformedE bool notified = false; EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onResolutionChanged", ::testing::_, ::testing::_)) - .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, - void (*callback)(void*, const nlohmann::json&)) - { - Firebolt::Helpers::SubscriptionData data{owner, eventName, - std::move(notification)}; - callback(&data, nlohmann::json{{"width", 3840}}); - return Firebolt::Result{5}; - })); - - auto result = impl.subscribeOnResolutionChanged( - [&](const Firebolt::VideoOutput::VideoOutputResolution& /*value*/) { notified = true; }); + .WillOnce(::testing::Invoke( + [&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, std::move(notification)}; + callback(&data, nlohmann::json{{"width", 3840}}); + return Firebolt::Result{5}; + })); + + auto result = impl.subscribeOnResolutionChanged([&](const Firebolt::VideoOutput::VideoOutputResolution& /*value*/) + { notified = true; }); ASSERT_TRUE(result); EXPECT_EQ(*result, 5U); @@ -320,8 +319,7 @@ TEST_F(VideooutputGeneratedUTest, SubscribeOnResolutionChangedForwardsSubscribeE EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onResolutionChanged", ::testing::_, ::testing::_)) .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); - auto result = impl.subscribeOnResolutionChanged( - [](const Firebolt::VideoOutput::VideoOutputResolution& /*value*/) {}); + auto result = impl.subscribeOnResolutionChanged([](const Firebolt::VideoOutput::VideoOutputResolution& /*value*/) {}); ASSERT_FALSE(result); EXPECT_EQ(result.error(), Firebolt::Error::General); @@ -333,15 +331,14 @@ TEST_F(VideooutputGeneratedUTest, SubscribeOnHdcpChangedForwardsAndDispatchesPar Firebolt::VideoOutput::HdcpState received = Firebolt::VideoOutput::HdcpState::None; EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onHdcpChanged", ::testing::_, ::testing::_)) - .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, - void (*callback)(void*, const nlohmann::json&)) - { - Firebolt::Helpers::SubscriptionData data{owner, eventName, - std::move(notification)}; - callback(&data, - nlohmann::json(static_cast(Firebolt::VideoOutput::HdcpState::Direct))); - return Firebolt::Result{11}; - })); + .WillOnce(::testing::Invoke( + [&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, std::move(notification)}; + callback(&data, nlohmann::json(static_cast(Firebolt::VideoOutput::HdcpState::Direct))); + return Firebolt::Result{11}; + })); auto result = impl.subscribeOnHdcpChanged( [&](const Firebolt::VideoOutput::HdcpState& value) @@ -373,15 +370,14 @@ TEST_F(VideooutputGeneratedUTest, SubscribeOnCecStateChangedForwardsAndDispatche Firebolt::VideoOutput::CecStateValue received = Firebolt::VideoOutput::CecStateValue::Unsupported; EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onCecStateChanged", ::testing::_, ::testing::_)) - .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, - void (*callback)(void*, const nlohmann::json&)) - { - Firebolt::Helpers::SubscriptionData data{owner, eventName, - std::move(notification)}; - callback(&data, - nlohmann::json(static_cast(Firebolt::VideoOutput::CecStateValue::Active))); - return Firebolt::Result{12}; - })); + .WillOnce(::testing::Invoke( + [&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, std::move(notification)}; + callback(&data, nlohmann::json(static_cast(Firebolt::VideoOutput::CecStateValue::Active))); + return Firebolt::Result{12}; + })); auto result = impl.subscribeOnCecStateChanged( [&](const Firebolt::VideoOutput::CecStateValue& value) @@ -413,15 +409,14 @@ TEST_F(VideooutputGeneratedUTest, SubscribeOnRefreshRateChangedForwardsAndDispat Firebolt::VideoOutput::RefreshRateValue received = Firebolt::VideoOutput::RefreshRateValue::R0; EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onRefreshRateChanged", ::testing::_, ::testing::_)) - .WillOnce(::testing::Invoke([&](void* owner, const std::string& eventName, std::any&& notification, - void (*callback)(void*, const nlohmann::json&)) - { - Firebolt::Helpers::SubscriptionData data{owner, eventName, - std::move(notification)}; - callback(&data, - nlohmann::json(static_cast(Firebolt::VideoOutput::RefreshRateValue::R24))); - return Firebolt::Result{13}; - })); + .WillOnce(::testing::Invoke( + [&](void* owner, const std::string& eventName, std::any&& notification, + void (*callback)(void*, const nlohmann::json&)) + { + Firebolt::Helpers::SubscriptionData data{owner, eventName, std::move(notification)}; + callback(&data, nlohmann::json(static_cast(Firebolt::VideoOutput::RefreshRateValue::R24))); + return Firebolt::Result{13}; + })); auto result = impl.subscribeOnRefreshRateChanged( [&](const Firebolt::VideoOutput::RefreshRateValue& value) @@ -441,8 +436,7 @@ TEST_F(VideooutputGeneratedUTest, SubscribeOnRefreshRateChangedForwardsSubscribe EXPECT_CALL(mockHelper, subscribe(&impl, "VideoOutput.onRefreshRateChanged", ::testing::_, ::testing::_)) .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); - auto result = impl.subscribeOnRefreshRateChanged( - [](const Firebolt::VideoOutput::RefreshRateValue& /*value*/) {}); + auto result = impl.subscribeOnRefreshRateChanged([](const Firebolt::VideoOutput::RefreshRateValue& /*value*/) {}); ASSERT_FALSE(result); EXPECT_EQ(result.error(), Firebolt::Error::General); From 1f0094a89f84583b9f566986325fe76951ac5cb5 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Tue, 11 Aug 2026 10:20:24 -0700 Subject: [PATCH 27/39] RDKEMW-14869: fix namespace --- src/json_types/videooutput.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json_types/videooutput.h b/src/json_types/videooutput.h index ba041fa..9cc1398 100644 --- a/src/json_types/videooutput.h +++ b/src/json_types/videooutput.h @@ -176,13 +176,13 @@ class VideoOutputResolution : public Firebolt::JSON::NL_Json_Basic<::Firebolt::V uint32_t height_{}; uint32_t width_{}; }; -} // namespace Firebolt::VideoOutput::JsonData inline void to_json(nlohmann::json& j, const Firebolt::VideoOutput::VideoOutputResolution& v) { j = nlohmann::json::object(); j["height"] = v.height; j["width"] = v.width; -} // namespace Firebolt::VideoOutput +} +} // namespace Firebolt::VideoOutput::JsonData #endif // FIREBOLT_VIDEOOUTPUT_JSON_H From c6ba9c3e29446eee074f113a9dc1efc26dbdaf7e Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 12 Aug 2026 15:55:18 -0700 Subject: [PATCH 28/39] RDKEMW-21812: texttospeech.speak fb9 changes --- include/firebolt/texttospeech.h | 13 +++- src/texttospeech_impl.cpp | 30 +++++++- src/texttospeech_impl.h | 6 +- test/unit/textToSpeechTest.cpp | 131 ++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 4 deletions(-) diff --git a/include/firebolt/texttospeech.h b/include/firebolt/texttospeech.h index eaf41e2..ddb8b57 100644 --- a/include/firebolt/texttospeech.h +++ b/include/firebolt/texttospeech.h @@ -97,11 +97,20 @@ class ITextToSpeech * @brief Speak the uttered text using the TTS engine * * @param[in] text : String to be converted to Audio for speech + * @param[in] callSign : Optional call sign for the app making the request + * @param[in] language : Optional language for the speech request + * @param[in] voice : Optional voice for the speech request + * @param[in] volume : Optional volume for the speech request + * @param[in] rate : Optional rate for the speech request + * @param[in] pitch : Optional pitch for the speech request * * @retval Result for Speak */ - [[nodiscard]] virtual Result speak(const std::string& text) const = 0; - + [[nodiscard]] virtual Result + speak(const std::string& text, std::optional callSign = std::nullopt, + std::optional language = std::nullopt, std::optional voice = std::nullopt, + std::optional volume = std::nullopt, std::optional rate = std::nullopt, + std::optional pitch = std::nullopt) const = 0; /** * @brief Pauses the speech for given speech id * diff --git a/src/texttospeech_impl.cpp b/src/texttospeech_impl.cpp index d02d17d..82cfc7b 100644 --- a/src/texttospeech_impl.cpp +++ b/src/texttospeech_impl.cpp @@ -34,10 +34,38 @@ Result TextToSpeechImpl::listVoices(const std::string& langu return helper_.get("TextToSpeech.listvoices", params); } -Result TextToSpeechImpl::speak(const std::string& text) const +Result TextToSpeechImpl::speak(const std::string& text, std::optional callSign, + std::optional language, std::optional voice, + std::optional volume, std::optional rate, + std::optional pitch) const { nlohmann::json params; params["text"] = text; + if (callSign) + { + params["callSign"] = *callSign; + } // or "callsign" if API expects lower-case + if (language) + { + params["language"] = *language; + } + if (voice) + { + params["voice"] = *voice; + } + if (volume) + { + params["volume"] = *volume; + } + if (rate) + { + params["rate"] = *rate; + } + if (pitch) + { + params["pitch"] = *pitch; + } + return helper_.get("TextToSpeech.speak", params); } diff --git a/src/texttospeech_impl.h b/src/texttospeech_impl.h index 6dc646d..7d8f3d8 100644 --- a/src/texttospeech_impl.h +++ b/src/texttospeech_impl.h @@ -33,7 +33,11 @@ class TextToSpeechImpl : public ITextToSpeech ~TextToSpeechImpl() override = default; [[nodiscard]] Result listVoices(const std::string& language) const override; - [[nodiscard]] Result speak(const std::string& text) const override; + [[nodiscard]] Result + speak(const std::string& text, std::optional callSign = std::nullopt, + std::optional language = std::nullopt, std::optional voice = std::nullopt, + std::optional volume = std::nullopt, std::optional rate = std::nullopt, + std::optional pitch = std::nullopt) const override; [[nodiscard]] Result pause(SpeechId speechId) const override; [[nodiscard]] Result resume(SpeechId speechId) const override; [[nodiscard]] Result cancel(SpeechId speechId) const override; diff --git a/test/unit/textToSpeechTest.cpp b/test/unit/textToSpeechTest.cpp index 18c0633..1802636 100644 --- a/test/unit/textToSpeechTest.cpp +++ b/test/unit/textToSpeechTest.cpp @@ -62,6 +62,137 @@ TEST_F(TextToSpeechUTest, speak) EXPECT_EQ(speak->success, expectedValue["success"].get()); } +TEST_F(TextToSpeechUTest, speak_payloadDefaultsToTextOnly) +{ + EXPECT_CALL(mockHelper, getJson("TextToSpeech.speak", _)) + .WillOnce(Invoke( + [&](const std::string& /*methodName*/, const nlohmann::json& parameters) + { + nlohmann::json expected = {{"text", "I am a text waiting for speech."}}; + EXPECT_EQ(parameters, expected) << "Parameters do not match expected payload: " << expected.dump() + << " but got: " << parameters.dump(); + return Firebolt::Result{jsonEngine.get_value("TextToSpeech.speak")}; + })); + + auto result = ttsImpl.speak("I am a text waiting for speech."); + ASSERT_TRUE(result); +} + +TEST_F(TextToSpeechUTest, speak_payloadIncludesAllOptionalFieldsWhenProvided) +{ + EXPECT_CALL(mockHelper, getJson("TextToSpeech.speak", _)) + .WillOnce(Invoke( + [&](const std::string& /*methodName*/, const nlohmann::json& parameters) + { + nlohmann::json expected; + expected["text"] = "I am a text waiting for speech."; + expected["callSign"] = "AppA"; + expected["language"] = "en-US"; + expected["voice"] = "female-1"; + expected["volume"] = "80"; + expected["rate"] = "normal"; + expected["pitch"] = "medium"; + EXPECT_EQ(parameters, expected) << "Parameters do not match expected payload: " << expected.dump() + << " but got: " << parameters.dump(); + return Firebolt::Result{jsonEngine.get_value("TextToSpeech.speak")}; + })); + + auto result = ttsImpl.speak("I am a text waiting for speech.", std::string("AppA"), std::string("en-US"), + std::string("female-1"), std::string("80"), std::string("normal"), std::string("medium")); + ASSERT_TRUE(result); +} + +TEST_F(TextToSpeechUTest, speak_payloadIncludesOnlyProvidedOptionalFields) +{ + EXPECT_CALL(mockHelper, getJson("TextToSpeech.speak", _)) + .WillOnce(Invoke( + [&](const std::string& /*methodName*/, const nlohmann::json& parameters) + { + nlohmann::json expected; + expected["text"] = "I am a text waiting for speech."; + expected["language"] = "en-US"; + expected["rate"] = "normal"; + EXPECT_EQ(parameters, expected) << "Parameters do not match expected payload: " << expected.dump() + << " but got: " << parameters.dump(); + return Firebolt::Result{jsonEngine.get_value("TextToSpeech.speak")}; + })); + + auto result = ttsImpl.speak("I am a text waiting for speech.", std::nullopt, std::string("en-US"), std::nullopt, + std::nullopt, std::string("normal"), std::nullopt); + ASSERT_TRUE(result); +} + +TEST_F(TextToSpeechUTest, speak_payloadOmitsUnsetOptionalKeys) +{ + EXPECT_CALL(mockHelper, getJson("TextToSpeech.speak", _)) + .WillOnce(Invoke( + [&](const std::string& /*methodName*/, const nlohmann::json& parameters) + { + EXPECT_EQ(parameters.size(), 1U); + EXPECT_TRUE(parameters.contains("text")); + EXPECT_FALSE(parameters.contains("callSign")); + EXPECT_FALSE(parameters.contains("language")); + EXPECT_FALSE(parameters.contains("voice")); + EXPECT_FALSE(parameters.contains("volume")); + EXPECT_FALSE(parameters.contains("rate")); + EXPECT_FALSE(parameters.contains("pitch")); + return Firebolt::Result{jsonEngine.get_value("TextToSpeech.speak")}; + })); + + auto result = ttsImpl.speak("I am a text waiting for speech."); + ASSERT_TRUE(result); +} + +TEST_F(TextToSpeechUTest, speak_payloadPreservesEmptyStringOptionalValues) +{ + EXPECT_CALL(mockHelper, getJson("TextToSpeech.speak", _)) + .WillOnce(Invoke( + [&](const std::string& /*methodName*/, const nlohmann::json& parameters) + { + EXPECT_TRUE(parameters.contains("text")); + EXPECT_TRUE(parameters.contains("callSign")); + EXPECT_TRUE(parameters.contains("rate")); + EXPECT_EQ(parameters["callSign"], ""); + EXPECT_EQ(parameters["rate"], ""); + EXPECT_FALSE(parameters.contains("language")); + EXPECT_FALSE(parameters.contains("voice")); + EXPECT_FALSE(parameters.contains("volume")); + EXPECT_FALSE(parameters.contains("pitch")); + return Firebolt::Result{jsonEngine.get_value("TextToSpeech.speak")}; + })); + + auto result = ttsImpl.speak("I am a text waiting for speech.", std::string(""), std::nullopt, std::nullopt, + std::nullopt, std::string(""), std::nullopt); + ASSERT_TRUE(result); +} + +TEST_F(TextToSpeechUTest, speak_payloadUsesCallSignKeyNotLegacyCallsign) +{ + EXPECT_CALL(mockHelper, getJson("TextToSpeech.speak", _)) + .WillOnce(Invoke( + [&](const std::string& /*methodName*/, const nlohmann::json& parameters) + { + EXPECT_TRUE(parameters.contains("callSign")); + EXPECT_FALSE(parameters.contains("callsign")); + EXPECT_EQ(parameters["callSign"], "AppA"); + return Firebolt::Result{jsonEngine.get_value("TextToSpeech.speak")}; + })); + + auto result = ttsImpl.speak("I am a text waiting for speech.", std::string("AppA")); + ASSERT_TRUE(result); +} + +TEST_F(TextToSpeechUTest, speak_propagatesHelperError) +{ + EXPECT_CALL(mockHelper, getJson("TextToSpeech.speak", _)) + .WillOnce(Invoke([&](const std::string& /*methodName*/, const nlohmann::json& /*parameters*/) + { return Firebolt::Result{Firebolt::Error::General}; })); + + auto result = ttsImpl.speak("I am a text waiting for speech.", std::string("AppA")); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + TEST_F(TextToSpeechUTest, pause) { mock("TextToSpeech.pause"); From 47b8b4406f33fd883ee68a75dd46cd410de7a349 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 12 Aug 2026 15:57:42 -0700 Subject: [PATCH 29/39] RDKEMW-21812: texttospeech.speak fb9 schema updates --- docs/openrpc/openrpc/text_to_speech.json | 66 +++++++++++++++++++ .../the-spec/firebolt-open-rpc--legacy.json | 66 +++++++++++++++++++ docs/openrpc/the-spec/firebolt-open-rpc.json | 66 +++++++++++++++++++ 3 files changed, 198 insertions(+) diff --git a/docs/openrpc/openrpc/text_to_speech.json b/docs/openrpc/openrpc/text_to_speech.json index 57575b4..3b499f1 100644 --- a/docs/openrpc/openrpc/text_to_speech.json +++ b/docs/openrpc/openrpc/text_to_speech.json @@ -18,6 +18,48 @@ "type": "string" }, "required": true + }, + { + "name": "callSign", + "summary": "Optional call sign for the app making the request", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "summary": "Optional language override as a BCP 47 locale tag", + "schema": { + "type": "string" + } + }, + { + "name": "voice", + "summary": "Optional voice identifier", + "schema": { + "type": "string" + } + }, + { + "name": "volume", + "summary": "Optional volume override", + "schema": { + "type": "string" + } + }, + { + "name": "rate", + "summary": "Optional speech rate override", + "schema": { + "type": "string" + } + }, + { + "name": "pitch", + "summary": "Optional pitch override", + "schema": { + "type": "string" + } } ], "tags": [ @@ -45,6 +87,30 @@ { "name": "text", "value": "I am a text waiting for speech." + }, + { + "name": "callSign", + "value": "AppA" + }, + { + "name": "language", + "value": "en-US" + }, + { + "name": "voice", + "value": "female-1" + }, + { + "name": "volume", + "value": "80" + }, + { + "name": "rate", + "value": "normal" + }, + { + "name": "pitch", + "value": "medium" } ], "result": { diff --git a/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json b/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json index 1bec372..a7cf042 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc--legacy.json @@ -2843,6 +2843,48 @@ "type": "string" }, "required": true + }, + { + "name": "callSign", + "summary": "Optional call sign for the app making the request", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "summary": "Optional language override as a BCP 47 locale tag", + "schema": { + "type": "string" + } + }, + { + "name": "voice", + "summary": "Optional voice identifier", + "schema": { + "type": "string" + } + }, + { + "name": "volume", + "summary": "Optional volume override", + "schema": { + "type": "string" + } + }, + { + "name": "rate", + "summary": "Optional speech rate override", + "schema": { + "type": "string" + } + }, + { + "name": "pitch", + "summary": "Optional pitch override", + "schema": { + "type": "string" + } } ], "tags": [ @@ -2870,6 +2912,30 @@ { "name": "text", "value": "I am a text waiting for speech." + }, + { + "name": "callSign", + "value": "AppA" + }, + { + "name": "language", + "value": "en-US" + }, + { + "name": "voice", + "value": "female-1" + }, + { + "name": "volume", + "value": "80" + }, + { + "name": "rate", + "value": "normal" + }, + { + "name": "pitch", + "value": "medium" } ], "result": { diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 743e3fe..0f76a96 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -2484,6 +2484,48 @@ "type": "string" }, "required": true + }, + { + "name": "callSign", + "summary": "Optional call sign for the app making the request", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "summary": "Optional language override as a BCP 47 locale tag", + "schema": { + "type": "string" + } + }, + { + "name": "voice", + "summary": "Optional voice identifier", + "schema": { + "type": "string" + } + }, + { + "name": "volume", + "summary": "Optional volume override", + "schema": { + "type": "string" + } + }, + { + "name": "rate", + "summary": "Optional speech rate override", + "schema": { + "type": "string" + } + }, + { + "name": "pitch", + "summary": "Optional pitch override", + "schema": { + "type": "string" + } } ], "tags": [ @@ -2511,6 +2553,30 @@ { "name": "text", "value": "I am a text waiting for speech." + }, + { + "name": "callSign", + "value": "AppA" + }, + { + "name": "language", + "value": "en-US" + }, + { + "name": "voice", + "value": "female-1" + }, + { + "name": "volume", + "value": "80" + }, + { + "name": "rate", + "value": "normal" + }, + { + "name": "pitch", + "value": "medium" } ], "result": { From 42daba1ad4b0ba14ef928bc3477295720220c34f Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 12 Aug 2026 16:25:04 -0700 Subject: [PATCH 30/39] RDKEMW-21812: copilot, nitpick --- src/texttospeech_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/texttospeech_impl.cpp b/src/texttospeech_impl.cpp index 82cfc7b..7463b5a 100644 --- a/src/texttospeech_impl.cpp +++ b/src/texttospeech_impl.cpp @@ -44,7 +44,7 @@ Result TextToSpeechImpl::speak(const std::string& text, std::opt if (callSign) { params["callSign"] = *callSign; - } // or "callsign" if API expects lower-case + } if (language) { params["language"] = *language; From 3a6cdae3dd95653139ec61037152b4608dc281ed Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 17 Aug 2026 10:24:52 -0700 Subject: [PATCH 31/39] RDKEMW-21812: SpeechSynthesisImpl::speak --- include/firebolt/firebolt.h | 8 ++++ include/firebolt/speechsynthesis.h | 37 +++++++++++++++++ src/firebolt.cpp | 4 ++ src/speechsynthesis_impl.cpp | 65 ++++++++++++++++++++++++++++++ src/speechsynthesis_impl.h | 44 ++++++++++++++++++++ test/unit/speechSynthesisTest.cpp | 31 ++++++++++++++ 6 files changed, 189 insertions(+) create mode 100644 include/firebolt/speechsynthesis.h create mode 100644 src/speechsynthesis_impl.cpp create mode 100644 src/speechsynthesis_impl.h create mode 100644 test/unit/speechSynthesisTest.cpp diff --git a/include/firebolt/firebolt.h b/include/firebolt/firebolt.h index 1d1568d..acd4399 100644 --- a/include/firebolt/firebolt.h +++ b/include/firebolt/firebolt.h @@ -30,6 +30,7 @@ #include "firebolt/metrics.h" #include "firebolt/network.h" #include "firebolt/presentation.h" +#include "firebolt/speechsynthesis.h" #include "firebolt/stats.h" #include "firebolt/texttospeech.h" #include "firebolt/videooutput.h" @@ -164,6 +165,13 @@ class FIREBOLTCLIENT_EXPORT IFireboltAccessor */ virtual TextToSpeech::ITextToSpeech& TextToSpeechInterface() = 0; + /** + * @brief Returns instance of SpeechSynthesis interface + * + * @return Reference to SpeechSynthesis interface + */ + virtual SpeechSynthesis::ISpeechSynthesis& SpeechSynthesisInterface() = 0; + /** * @brief Returns instance of Actions interface * diff --git a/include/firebolt/speechsynthesis.h b/include/firebolt/speechsynthesis.h new file mode 100644 index 0000000..fa107a2 --- /dev/null +++ b/include/firebolt/speechsynthesis.h @@ -0,0 +1,37 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include + +#include +#include +#include +namespace Firebolt::SpeechSynthesis +{ +class ISpeechSynthesis +{ +public: + virtual ~ISpeechSynthesis() = default; + + [[nodiscard]] virtual Result speak(const std::string& text, std::optional callSign = std::nullopt, + std::optional language = std::nullopt, std::optional voice = std::nullopt, + std::optional volume = std::nullopt, std::optional rate = std::nullopt, + std::optional pitch = std::nullopt) const = 0; +}; +} // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/firebolt.cpp b/src/firebolt.cpp index 140d8ef..8748de6 100644 --- a/src/firebolt.cpp +++ b/src/firebolt.cpp @@ -29,6 +29,7 @@ #include "metrics_impl.h" #include "network_impl.h" #include "presentation_impl.h" +#include "speechsynthesis_impl.h" #include "stats_impl.h" #include "texttospeech_impl.h" #include "videooutput_impl.h" @@ -51,6 +52,7 @@ class FireboltAccessorImpl : public IFireboltAccessor metrics_(Firebolt::Helpers::GetHelperInstance()), network_(Firebolt::Helpers::GetHelperInstance()), presentation_(Firebolt::Helpers::GetHelperInstance()), + speechSynthesis_(Firebolt::Helpers::GetHelperInstance()), stats_(Firebolt::Helpers::GetHelperInstance()), textToSpeech_(Firebolt::Helpers::GetHelperInstance()), videooutput_(Firebolt::Helpers::GetHelperInstance()) @@ -85,6 +87,7 @@ class FireboltAccessorImpl : public IFireboltAccessor Metrics::IMetrics& MetricsInterface() override { return metrics_; } Network::INetwork& NetworkInterface() override { return network_; } Presentation::IPresentation& PresentationInterface() override { return presentation_; } + SpeechSynthesis::ISpeechSynthesis& SpeechSynthesisInterface() override { return speechSynthesis_; } Stats::IStats& StatsInterface() override { return stats_; } TextToSpeech::ITextToSpeech& TextToSpeechInterface() override { return textToSpeech_; } Actions::IActions& ActionsInterface() override { return actions_; } @@ -114,6 +117,7 @@ class FireboltAccessorImpl : public IFireboltAccessor Metrics::MetricsImpl metrics_; Network::NetworkImpl network_; Presentation::PresentationImpl presentation_; + SpeechSynthesis::SpeechSynthesisImpl speechSynthesis_; Stats::StatsImpl stats_; TextToSpeech::TextToSpeechImpl textToSpeech_; VideoOutput::VideoOutputImpl videooutput_; diff --git a/src/speechsynthesis_impl.cpp b/src/speechsynthesis_impl.cpp new file mode 100644 index 0000000..af77dcb --- /dev/null +++ b/src/speechsynthesis_impl.cpp @@ -0,0 +1,65 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "speechsynthesis_impl.h" +#include +#include + +namespace Firebolt::SpeechSynthesis +{ +SpeechSynthesisImpl::SpeechSynthesisImpl(Firebolt::Helpers::IHelper& helper) + : helper_(helper) +{ +} + +Result SpeechSynthesisImpl::speak(const std::string& text, std::optional callSign, + std::optional language, std::optional voice, + std::optional volume, std::optional rate, + std::optional pitch) const +{ + nlohmann::json params; + params["text"] = text; + + if (callSign) + { + params["callSign"] = *callSign; + } + if (language) + { + params["language"] = *language; + } + if (voice) + { + params["voice"] = *voice; + } + if (volume) + { + params["volume"] = *volume; + } + if (rate) + { + params["rate"] = *rate; + } + if (pitch) + { + params["pitch"] = *pitch; + } + + return helper_.get("SpeechSynthesis.speak", params); +} +} // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.h b/src/speechsynthesis_impl.h new file mode 100644 index 0000000..7a30abb --- /dev/null +++ b/src/speechsynthesis_impl.h @@ -0,0 +1,44 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +namespace Firebolt::SpeechSynthesis +{ +class SpeechSynthesisImpl : public ISpeechSynthesis +{ +public: + explicit SpeechSynthesisImpl(Firebolt::Helpers::IHelper& helper); + SpeechSynthesisImpl(const SpeechSynthesisImpl&) = delete; + SpeechSynthesisImpl& operator=(const SpeechSynthesisImpl&) = delete; + + ~SpeechSynthesisImpl() override = default; + + + [[nodiscard]] Result speak(const std::string& text, std::optional callSign = std::nullopt, + std::optional language = std::nullopt, std::optional voice = std::nullopt, + std::optional volume = std::nullopt, std::optional rate = std::nullopt, + std::optional pitch = std::nullopt) const override; + +private: + Firebolt::Helpers::IHelper& helper_; +}; +} // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/test/unit/speechSynthesisTest.cpp b/test/unit/speechSynthesisTest.cpp new file mode 100644 index 0000000..30c31cc --- /dev/null +++ b/test/unit/speechSynthesisTest.cpp @@ -0,0 +1,31 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mock_helper.h" +#include "speechsynthesis_impl.h" + +class SpeechSynthesisUTest : public ::testing::Test, protected MockBase +{ +protected: + Firebolt::SpeechSynthesis::SpeechSynthesisImpl speechSynthesisImpl_{mockHelper}; +}; + +TEST_F(SpeechSynthesisUTest, Constructs) +{ + SUCCEED(); +} \ No newline at end of file From cd4529a5fea4b4ac8a999d209b80f3a5bdd0e10e Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 17 Aug 2026 10:42:18 -0700 Subject: [PATCH 32/39] RDKEMW-21812: SpeechSynthesisImpl::speak unit tests --- include/firebolt/speechsynthesis.h | 3 ++- src/speechsynthesis_impl.cpp | 8 +++--- src/speechsynthesis_impl.h | 11 ++++---- test/unit/speechSynthesisTest.cpp | 42 ++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/include/firebolt/speechsynthesis.h b/include/firebolt/speechsynthesis.h index fa107a2..2777e7f 100644 --- a/include/firebolt/speechsynthesis.h +++ b/include/firebolt/speechsynthesis.h @@ -29,7 +29,8 @@ class ISpeechSynthesis public: virtual ~ISpeechSynthesis() = default; - [[nodiscard]] virtual Result speak(const std::string& text, std::optional callSign = std::nullopt, + [[nodiscard]] virtual Result + speak(const std::string& text, std::optional callSign = std::nullopt, std::optional language = std::nullopt, std::optional voice = std::nullopt, std::optional volume = std::nullopt, std::optional rate = std::nullopt, std::optional pitch = std::nullopt) const = 0; diff --git a/src/speechsynthesis_impl.cpp b/src/speechsynthesis_impl.cpp index af77dcb..88b8437 100644 --- a/src/speechsynthesis_impl.cpp +++ b/src/speechsynthesis_impl.cpp @@ -17,8 +17,8 @@ */ #include "speechsynthesis_impl.h" -#include #include +#include namespace Firebolt::SpeechSynthesis { @@ -28,9 +28,9 @@ SpeechSynthesisImpl::SpeechSynthesisImpl(Firebolt::Helpers::IHelper& helper) } Result SpeechSynthesisImpl::speak(const std::string& text, std::optional callSign, - std::optional language, std::optional voice, - std::optional volume, std::optional rate, - std::optional pitch) const + std::optional language, std::optional voice, + std::optional volume, std::optional rate, + std::optional pitch) const { nlohmann::json params; params["text"] = text; diff --git a/src/speechsynthesis_impl.h b/src/speechsynthesis_impl.h index 7a30abb..f2ff343 100644 --- a/src/speechsynthesis_impl.h +++ b/src/speechsynthesis_impl.h @@ -18,8 +18,8 @@ #pragma once -#include #include +#include namespace Firebolt::SpeechSynthesis { @@ -32,11 +32,12 @@ class SpeechSynthesisImpl : public ISpeechSynthesis ~SpeechSynthesisImpl() override = default; - [[nodiscard]] Result speak(const std::string& text, std::optional callSign = std::nullopt, - std::optional language = std::nullopt, std::optional voice = std::nullopt, - std::optional volume = std::nullopt, std::optional rate = std::nullopt, - std::optional pitch = std::nullopt) const override; + std::optional language = std::nullopt, + std::optional voice = std::nullopt, + std::optional volume = std::nullopt, + std::optional rate = std::nullopt, + std::optional pitch = std::nullopt) const override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/test/unit/speechSynthesisTest.cpp b/test/unit/speechSynthesisTest.cpp index 30c31cc..ba5c184 100644 --- a/test/unit/speechSynthesisTest.cpp +++ b/test/unit/speechSynthesisTest.cpp @@ -28,4 +28,46 @@ class SpeechSynthesisUTest : public ::testing::Test, protected MockBase TEST_F(SpeechSynthesisUTest, Constructs) { SUCCEED(); +} + +TEST_F(SpeechSynthesisUTest, speak) +{ + EXPECT_CALL(mockHelper, getJson("SpeechSynthesis.speak", _)) + .WillOnce(Invoke([&](const std::string& /*methodName*/, const nlohmann::json& /*parameters*/) + { return Firebolt::Result{42}; })); + + auto result = speechSynthesisImpl_.speak("Hello from speech synthesis"); + + ASSERT_TRUE(result); + EXPECT_EQ(*result, 42U); +} + +TEST_F(SpeechSynthesisUTest, speak_payloadIncludesOnlyProvidedOptionalFields) +{ + EXPECT_CALL(mockHelper, getJson("SpeechSynthesis.speak", _)) + .WillOnce(Invoke( + [&](const std::string& /*methodName*/, const nlohmann::json& parameters) + { + nlohmann::json expected; + expected["text"] = "payload"; + expected["language"] = "en-US"; + expected["pitch"] = "medium"; + EXPECT_EQ(parameters, expected); + return Firebolt::Result{7}; + })); + + auto result = speechSynthesisImpl_.speak("payload", std::nullopt, std::string("en-US"), std::nullopt, std::nullopt, + std::nullopt, std::string("medium")); + + ASSERT_TRUE(result); + EXPECT_EQ(*result, 7U); +} + +TEST_F(SpeechSynthesisUTest, speak_invalidResponse) +{ + mock_with_response("SpeechSynthesis.speak", "not-a-number"); + + auto result = speechSynthesisImpl_.speak("Hello from speech synthesis"); + + ASSERT_FALSE(result); } \ No newline at end of file From 55cc929ac0ebfcdb34bfcf60a8e62eba27f9ec93 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 17 Aug 2026 13:13:07 -0700 Subject: [PATCH 33/39] RDKEMW-21812: SpeechSynthesisImpl::voices --- docs/openrpc/openrpc/speech_synthesis.json | 9 ++++ include/firebolt/speechsynthesis.h | 9 ++++ src/json_types/speechsynthesis.h | 55 ++++++++++++++++++++++ src/speechsynthesis_impl.cpp | 5 ++ src/speechsynthesis_impl.h | 1 + test/unit/speechSynthesisTest.cpp | 48 +++++++++++++++++++ 6 files changed, 127 insertions(+) create mode 100644 docs/openrpc/openrpc/speech_synthesis.json create mode 100644 src/json_types/speechsynthesis.h diff --git a/docs/openrpc/openrpc/speech_synthesis.json b/docs/openrpc/openrpc/speech_synthesis.json new file mode 100644 index 0000000..f221912 --- /dev/null +++ b/docs/openrpc/openrpc/speech_synthesis.json @@ -0,0 +1,9 @@ +{ + "openrpc": "1.2.4", + "info": { + "title": "SpeechSynthesis", + "description": "Scaffold for the SpeechSynthesis Firebolt module.", + "version": "0.0.0" + }, + "methods": [] +} \ No newline at end of file diff --git a/include/firebolt/speechsynthesis.h b/include/firebolt/speechsynthesis.h index 2777e7f..4d98859 100644 --- a/include/firebolt/speechsynthesis.h +++ b/include/firebolt/speechsynthesis.h @@ -24,6 +24,12 @@ #include namespace Firebolt::SpeechSynthesis { +struct Voice +{ + std::string name; + std::string lang; + bool _default; +}; class ISpeechSynthesis { public: @@ -34,5 +40,8 @@ class ISpeechSynthesis std::optional language = std::nullopt, std::optional voice = std::nullopt, std::optional volume = std::nullopt, std::optional rate = std::nullopt, std::optional pitch = std::nullopt) const = 0; + [[nodiscard]] virtual Result> voices() const = 0; + + //[[nodiscard]] virtual Result subscribeOnVoicesChanged(std::function&& notification) = 0; }; } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/json_types/speechsynthesis.h b/src/json_types/speechsynthesis.h new file mode 100644 index 0000000..83ce0eb --- /dev/null +++ b/src/json_types/speechsynthesis.h @@ -0,0 +1,55 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "firebolt/speechsynthesis.h" +#include + +namespace Firebolt::SpeechSynthesis::JsonData +{ +class VoicesResponse : public Firebolt::JSON::NL_Json_Basic<::std::pmr::vector<::Firebolt::SpeechSynthesis::Voice>> +{ +public: + void fromJson(const nlohmann::json& json) override + { + if (!json.is_array()) + { + throw std::invalid_argument("Expected an array for voices response"); + } + + voices_.clear(); + for (const auto& item : json) + { + if (!checkRequiredFields(item, {"name", "lang", "default"})) + { + throw std::invalid_argument("Missing required fields in JSON"); + } + + voices_.push_back(::Firebolt::SpeechSynthesis::Voice{item["name"].get(), + item["lang"].get(), + item["default"].get()}); + } + } + + [[nodiscard]] ::std::pmr::vector<::Firebolt::SpeechSynthesis::Voice> value() const override { return voices_; } + +private: + ::std::pmr::vector<::Firebolt::SpeechSynthesis::Voice> voices_; +}; +} // namespace Firebolt::SpeechSynthesis::JsonData \ No newline at end of file diff --git a/src/speechsynthesis_impl.cpp b/src/speechsynthesis_impl.cpp index 88b8437..f29c2f3 100644 --- a/src/speechsynthesis_impl.cpp +++ b/src/speechsynthesis_impl.cpp @@ -17,6 +17,7 @@ */ #include "speechsynthesis_impl.h" +#include "json_types/speechsynthesis.h" #include #include @@ -62,4 +63,8 @@ Result SpeechSynthesisImpl::speak(const std::string& text, std::option return helper_.get("SpeechSynthesis.speak", params); } +Result> SpeechSynthesisImpl::voices() const +{ + return helper_.get>("SpeechSynthesis.voices"); +} } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.h b/src/speechsynthesis_impl.h index f2ff343..273f7ab 100644 --- a/src/speechsynthesis_impl.h +++ b/src/speechsynthesis_impl.h @@ -38,6 +38,7 @@ class SpeechSynthesisImpl : public ISpeechSynthesis std::optional volume = std::nullopt, std::optional rate = std::nullopt, std::optional pitch = std::nullopt) const override; + [[nodiscard]] Result> voices() const override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/test/unit/speechSynthesisTest.cpp b/test/unit/speechSynthesisTest.cpp index ba5c184..2e60294 100644 --- a/test/unit/speechSynthesisTest.cpp +++ b/test/unit/speechSynthesisTest.cpp @@ -69,5 +69,53 @@ TEST_F(SpeechSynthesisUTest, speak_invalidResponse) auto result = speechSynthesisImpl_.speak("Hello from speech synthesis"); + ASSERT_FALSE(result); +} + +TEST_F(SpeechSynthesisUTest, voices) +{ + nlohmann::json response = nlohmann::json::array(); + response.push_back({{"name", "Salli"}, {"lang", "en-US"}, {"default", true}}); + response.push_back({{"name", "Arthur"}, {"lang", "en-GB"}, {"default", false}}); + mock_with_response("SpeechSynthesis.voices", response); + + auto result = speechSynthesisImpl_.voices(); + + ASSERT_TRUE(result); + ASSERT_EQ(result->size(), 2U); + EXPECT_EQ((*result)[0].name, "Salli"); + EXPECT_EQ((*result)[0].lang, "en-US"); + EXPECT_TRUE((*result)[0]._default); + EXPECT_EQ((*result)[1].name, "Arthur"); + EXPECT_EQ((*result)[1].lang, "en-GB"); + EXPECT_FALSE((*result)[1]._default); +} + +TEST_F(SpeechSynthesisUTest, voices_payloadHasNoParameters) +{ + EXPECT_CALL(mockHelper, getJson("SpeechSynthesis.voices", _)) + .WillOnce(Invoke( + [&](const std::string& /*methodName*/, const nlohmann::json& parameters) + { + EXPECT_TRUE(parameters.is_object()); + EXPECT_TRUE(parameters.empty()); + nlohmann::json response = nlohmann::json::array(); + response.push_back({{"name", "Salli"}, {"lang", "en-US"}, {"default", true}}); + return Firebolt::Result{response}; + })); + + auto result = speechSynthesisImpl_.voices(); + + ASSERT_TRUE(result); + ASSERT_EQ(result->size(), 1U); +} + +TEST_F(SpeechSynthesisUTest, voices_invalidResponse) +{ + nlohmann::json badResponse = {{"name", "not-an-array"}}; + mock_with_response("SpeechSynthesis.voices", badResponse); + + auto result = speechSynthesisImpl_.voices(); + ASSERT_FALSE(result); } \ No newline at end of file From 675a81b01e686fcaaa8c889ff369f9694c058926 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 17 Aug 2026 13:45:35 -0700 Subject: [PATCH 34/39] RDKEMW-21812: SpeechSynthesisImpl::subscribeVoiceChanged --- include/firebolt/speechsynthesis.h | 4 ++-- src/speechsynthesis_impl.cpp | 11 ++++++++++- src/speechsynthesis_impl.h | 4 ++++ test/unit/speechSynthesisTest.cpp | 22 ++++++++++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/include/firebolt/speechsynthesis.h b/include/firebolt/speechsynthesis.h index 4d98859..8398ae3 100644 --- a/include/firebolt/speechsynthesis.h +++ b/include/firebolt/speechsynthesis.h @@ -41,7 +41,7 @@ class ISpeechSynthesis std::optional volume = std::nullopt, std::optional rate = std::nullopt, std::optional pitch = std::nullopt) const = 0; [[nodiscard]] virtual Result> voices() const = 0; - - //[[nodiscard]] virtual Result subscribeOnVoicesChanged(std::function&& notification) = 0; + [[nodiscard]] virtual Result + subscribeOnVoicesChanged(std::function&)>&& notification) = 0; }; } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.cpp b/src/speechsynthesis_impl.cpp index f29c2f3..3943a35 100644 --- a/src/speechsynthesis_impl.cpp +++ b/src/speechsynthesis_impl.cpp @@ -24,7 +24,8 @@ namespace Firebolt::SpeechSynthesis { SpeechSynthesisImpl::SpeechSynthesisImpl(Firebolt::Helpers::IHelper& helper) - : helper_(helper) + : helper_(helper), + subscriptionManager_(helper, this) { } @@ -67,4 +68,12 @@ Result> SpeechSynthesisImpl::voices() const { return helper_.get>("SpeechSynthesis.voices"); } + +Result +SpeechSynthesisImpl::subscribeOnVoicesChanged(std::function&)>&& notification) +{ + return subscriptionManager_.subscribe("SpeechSynthesis.onVoicesChanged", + std::move(notification)); +} + } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.h b/src/speechsynthesis_impl.h index 273f7ab..e3668cf 100644 --- a/src/speechsynthesis_impl.h +++ b/src/speechsynthesis_impl.h @@ -40,7 +40,11 @@ class SpeechSynthesisImpl : public ISpeechSynthesis std::optional pitch = std::nullopt) const override; [[nodiscard]] Result> voices() const override; + [[nodiscard]] Result + subscribeOnVoicesChanged(std::function&)>&& notification) override; + private: Firebolt::Helpers::IHelper& helper_; + Firebolt::Helpers::SubscriptionManager subscriptionManager_; }; } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/test/unit/speechSynthesisTest.cpp b/test/unit/speechSynthesisTest.cpp index 2e60294..f10d21f 100644 --- a/test/unit/speechSynthesisTest.cpp +++ b/test/unit/speechSynthesisTest.cpp @@ -118,4 +118,26 @@ TEST_F(SpeechSynthesisUTest, voices_invalidResponse) auto result = speechSynthesisImpl_.voices(); ASSERT_FALSE(result); +} + +TEST_F(SpeechSynthesisUTest, subscribeOnVoicesChanged) +{ + EXPECT_CALL(mockHelper, subscribe(_, "SpeechSynthesis.onVoicesChanged", _, _)) + .WillOnce(::testing::Return(Firebolt::Result{1})); + + auto result = speechSynthesisImpl_.subscribeOnVoicesChanged( + [](const std::pmr::vector& /*voices*/) {}); + ASSERT_TRUE(result) << "error on subscribe "; + EXPECT_TRUE(result.has_value()) << "error on id"; +} + +TEST_F(SpeechSynthesisUTest, subscribeOnVoicesChanged_subscribeError) +{ + EXPECT_CALL(mockHelper, subscribe(_, "SpeechSynthesis.onVoicesChanged", _, _)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = speechSynthesisImpl_.subscribeOnVoicesChanged( + [](const std::pmr::vector& /*voices*/) {}); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); } \ No newline at end of file From bac42d0e65ff2d0fae005f5296ba429c8329a39e Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 17 Aug 2026 13:55:39 -0700 Subject: [PATCH 35/39] RDKEMW-21812: SpeechSynthesisImpl::cancel --- include/firebolt/speechsynthesis.h | 3 +++ src/speechsynthesis_impl.cpp | 7 ++++++- src/speechsynthesis_impl.h | 2 ++ test/unit/speechSynthesisTest.cpp | 22 ++++++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/firebolt/speechsynthesis.h b/include/firebolt/speechsynthesis.h index 8398ae3..a95f3fa 100644 --- a/include/firebolt/speechsynthesis.h +++ b/include/firebolt/speechsynthesis.h @@ -30,6 +30,7 @@ struct Voice std::string lang; bool _default; }; +using UtteranceId = std::uint64_t; class ISpeechSynthesis { public: @@ -43,5 +44,7 @@ class ISpeechSynthesis [[nodiscard]] virtual Result> voices() const = 0; [[nodiscard]] virtual Result subscribeOnVoicesChanged(std::function&)>&& notification) = 0; + + [[nodiscard]] virtual Result cancel(UtteranceId id) const = 0; }; } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.cpp b/src/speechsynthesis_impl.cpp index 3943a35..7ee209a 100644 --- a/src/speechsynthesis_impl.cpp +++ b/src/speechsynthesis_impl.cpp @@ -75,5 +75,10 @@ SpeechSynthesisImpl::subscribeOnVoicesChanged(std::function("SpeechSynthesis.onVoicesChanged", std::move(notification)); } - +Result SpeechSynthesisImpl::cancel(UtteranceId id) const +{ + nlohmann::json params; + params["id"] = id; + return helper_.invoke("SpeechSynthesis.cancel", params); +} } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.h b/src/speechsynthesis_impl.h index e3668cf..0516a7b 100644 --- a/src/speechsynthesis_impl.h +++ b/src/speechsynthesis_impl.h @@ -43,6 +43,8 @@ class SpeechSynthesisImpl : public ISpeechSynthesis [[nodiscard]] Result subscribeOnVoicesChanged(std::function&)>&& notification) override; + [[nodiscard]] Result cancel(UtteranceId id) const override; + private: Firebolt::Helpers::IHelper& helper_; Firebolt::Helpers::SubscriptionManager subscriptionManager_; diff --git a/test/unit/speechSynthesisTest.cpp b/test/unit/speechSynthesisTest.cpp index f10d21f..b1be8d5 100644 --- a/test/unit/speechSynthesisTest.cpp +++ b/test/unit/speechSynthesisTest.cpp @@ -140,4 +140,26 @@ TEST_F(SpeechSynthesisUTest, subscribeOnVoicesChanged_subscribeError) [](const std::pmr::vector& /*voices*/) {}); ASSERT_FALSE(result); EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(SpeechSynthesisUTest, cancel) +{ + nlohmann::json expectedParams; + expectedParams["id"] = static_cast(123); + + EXPECT_CALL(mockHelper, invoke("SpeechSynthesis.cancel", expectedParams)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::None})); + + auto result = speechSynthesisImpl_.cancel(static_cast(123)); + ASSERT_TRUE(result); +} + +TEST_F(SpeechSynthesisUTest, cancel_invokeError) +{ + EXPECT_CALL(mockHelper, invoke("SpeechSynthesis.cancel", _)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = speechSynthesisImpl_.cancel(static_cast(123)); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); } \ No newline at end of file From 6d7ae94546ed22d43147c50288c668f1683a72ee Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 17 Aug 2026 14:03:01 -0700 Subject: [PATCH 36/39] RDKEMW-21812: SpeechSynthesisImpl::resume, pause --- include/firebolt/speechsynthesis.h | 2 ++ src/speechsynthesis_impl.cpp | 12 ++++++++ src/speechsynthesis_impl.h | 2 ++ test/unit/speechSynthesisTest.cpp | 45 ++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/include/firebolt/speechsynthesis.h b/include/firebolt/speechsynthesis.h index a95f3fa..9d5e9cc 100644 --- a/include/firebolt/speechsynthesis.h +++ b/include/firebolt/speechsynthesis.h @@ -46,5 +46,7 @@ class ISpeechSynthesis subscribeOnVoicesChanged(std::function&)>&& notification) = 0; [[nodiscard]] virtual Result cancel(UtteranceId id) const = 0; + [[nodiscard]] virtual Result pause(UtteranceId id) const = 0; + [[nodiscard]] virtual Result resume(UtteranceId id) const = 0; }; } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.cpp b/src/speechsynthesis_impl.cpp index 7ee209a..a73d763 100644 --- a/src/speechsynthesis_impl.cpp +++ b/src/speechsynthesis_impl.cpp @@ -81,4 +81,16 @@ Result SpeechSynthesisImpl::cancel(UtteranceId id) const params["id"] = id; return helper_.invoke("SpeechSynthesis.cancel", params); } +Result SpeechSynthesisImpl::pause(UtteranceId id) const +{ + nlohmann::json params; + params["id"] = id; + return helper_.invoke("SpeechSynthesis.pause", params); +} +Result SpeechSynthesisImpl::resume(UtteranceId id) const +{ + nlohmann::json params; + params["id"] = id; + return helper_.invoke("SpeechSynthesis.resume", params); +} } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.h b/src/speechsynthesis_impl.h index 0516a7b..c09ac25 100644 --- a/src/speechsynthesis_impl.h +++ b/src/speechsynthesis_impl.h @@ -44,6 +44,8 @@ class SpeechSynthesisImpl : public ISpeechSynthesis subscribeOnVoicesChanged(std::function&)>&& notification) override; [[nodiscard]] Result cancel(UtteranceId id) const override; + [[nodiscard]] Result pause(UtteranceId id) const override; + [[nodiscard]] Result resume(UtteranceId id) const override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/test/unit/speechSynthesisTest.cpp b/test/unit/speechSynthesisTest.cpp index b1be8d5..707210b 100644 --- a/test/unit/speechSynthesisTest.cpp +++ b/test/unit/speechSynthesisTest.cpp @@ -162,4 +162,49 @@ TEST_F(SpeechSynthesisUTest, cancel_invokeError) auto result = speechSynthesisImpl_.cancel(static_cast(123)); ASSERT_FALSE(result); EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(SpeechSynthesisUTest, pause) +{ + nlohmann::json expectedParams; + expectedParams["id"] = static_cast(123); + + EXPECT_CALL(mockHelper, invoke("SpeechSynthesis.pause", expectedParams)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::None})); + + auto result = speechSynthesisImpl_.pause(static_cast(123)); + ASSERT_TRUE(result); +} + +TEST_F(SpeechSynthesisUTest, pause_invokeError) +{ + EXPECT_CALL(mockHelper, invoke("SpeechSynthesis.pause", _)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = speechSynthesisImpl_.pause(static_cast(123)); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(SpeechSynthesisUTest, resume) +{ + nlohmann::json expectedParams; + expectedParams["id"] = static_cast(123); + + EXPECT_CALL(mockHelper, invoke("SpeechSynthesis.resume", expectedParams)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::None})); + + auto result = speechSynthesisImpl_.resume(static_cast(123)); + ASSERT_TRUE(result); +} +// Removed duplicate pause test + +TEST_F(SpeechSynthesisUTest, resume_invokeError) +{ + EXPECT_CALL(mockHelper, invoke("SpeechSynthesis.resume", _)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = speechSynthesisImpl_.resume(static_cast(123)); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); } \ No newline at end of file From 174c34ac2ba8b9edbfd6b5670d928b6e00682254 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Mon, 17 Aug 2026 14:18:01 -0700 Subject: [PATCH 37/39] RDKEMW-21812: SpeechSynthesisImpl::onUtteranceEvent --- include/firebolt/speechsynthesis.h | 23 ++++++++++++++++ src/json_types/speechsynthesis.h | 42 +++++++++++++++++++++++++++++ src/speechsynthesis_impl.cpp | 6 +++++ src/speechsynthesis_impl.h | 2 ++ test/unit/speechSynthesisTest.cpp | 43 ++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+) diff --git a/include/firebolt/speechsynthesis.h b/include/firebolt/speechsynthesis.h index 9d5e9cc..a809351 100644 --- a/include/firebolt/speechsynthesis.h +++ b/include/firebolt/speechsynthesis.h @@ -30,7 +30,28 @@ struct Voice std::string lang; bool _default; }; + +enum class UtteranceEventEnum +{ + synthesisStarting, + playbackStarting, + paused, + resumed, + completed, + interrupted, + networkFailed, + synthesisFailed, + playbackFailed +}; + using UtteranceId = std::uint64_t; + +struct UtteranceEvent +{ + UtteranceId id; + UtteranceEventEnum event; +}; + class ISpeechSynthesis { public: @@ -48,5 +69,7 @@ class ISpeechSynthesis [[nodiscard]] virtual Result cancel(UtteranceId id) const = 0; [[nodiscard]] virtual Result pause(UtteranceId id) const = 0; [[nodiscard]] virtual Result resume(UtteranceId id) const = 0; + [[nodiscard]] virtual Result + subscribeOnUtteranceEvent(std::function&& notification) = 0; }; } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/json_types/speechsynthesis.h b/src/json_types/speechsynthesis.h index 83ce0eb..00f4993 100644 --- a/src/json_types/speechsynthesis.h +++ b/src/json_types/speechsynthesis.h @@ -23,6 +23,18 @@ namespace Firebolt::SpeechSynthesis::JsonData { +inline const Firebolt::JSON::EnumType<::Firebolt::SpeechSynthesis::UtteranceEventEnum> UtteranceEventEnum({ + {"synthesisStarting", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::synthesisStarting}, + {"playbackStarting", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::playbackStarting}, + {"paused", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::paused}, + {"resumed", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::resumed}, + {"completed", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::completed}, + {"interrupted", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::interrupted}, + {"networkFailed", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::networkFailed}, + {"synthesisFailed", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::synthesisFailed}, + {"playbackFailed", ::Firebolt::SpeechSynthesis::UtteranceEventEnum::playbackFailed}, +}); + class VoicesResponse : public Firebolt::JSON::NL_Json_Basic<::std::pmr::vector<::Firebolt::SpeechSynthesis::Voice>> { public: @@ -52,4 +64,34 @@ class VoicesResponse : public Firebolt::JSON::NL_Json_Basic<::std::pmr::vector<: private: ::std::pmr::vector<::Firebolt::SpeechSynthesis::Voice> voices_; }; + +class UtteranceEventResponse : public Firebolt::JSON::NL_Json_Basic<::Firebolt::SpeechSynthesis::UtteranceEvent> +{ +public: + void fromJson(const nlohmann::json& json) override + { + if (!checkRequiredFields(json, {"id", "event"})) + { + throw std::invalid_argument("Missing required fields in JSON"); + } + + const auto eventName = json["event"].get(); + const auto eventIt = UtteranceEventEnum.find(eventName); + if (eventIt == UtteranceEventEnum.end()) + { + throw std::invalid_argument("Unknown utterance event"); + } + event_ = eventIt->second; + id_ = json["id"].get<::Firebolt::SpeechSynthesis::UtteranceId>(); + } + + [[nodiscard]] ::Firebolt::SpeechSynthesis::UtteranceEvent value() const override + { + return ::Firebolt::SpeechSynthesis::UtteranceEvent{id_, event_}; + } + +private: + ::Firebolt::SpeechSynthesis::UtteranceId id_; + ::Firebolt::SpeechSynthesis::UtteranceEventEnum event_; +}; } // namespace Firebolt::SpeechSynthesis::JsonData \ No newline at end of file diff --git a/src/speechsynthesis_impl.cpp b/src/speechsynthesis_impl.cpp index a73d763..f924936 100644 --- a/src/speechsynthesis_impl.cpp +++ b/src/speechsynthesis_impl.cpp @@ -93,4 +93,10 @@ Result SpeechSynthesisImpl::resume(UtteranceId id) const params["id"] = id; return helper_.invoke("SpeechSynthesis.resume", params); } +Result +SpeechSynthesisImpl::subscribeOnUtteranceEvent(std::function&& notification) +{ + return subscriptionManager_.subscribe("SpeechSynthesis.onUtteranceEvent", + std::move(notification)); +} } // namespace Firebolt::SpeechSynthesis \ No newline at end of file diff --git a/src/speechsynthesis_impl.h b/src/speechsynthesis_impl.h index c09ac25..147d9f7 100644 --- a/src/speechsynthesis_impl.h +++ b/src/speechsynthesis_impl.h @@ -46,6 +46,8 @@ class SpeechSynthesisImpl : public ISpeechSynthesis [[nodiscard]] Result cancel(UtteranceId id) const override; [[nodiscard]] Result pause(UtteranceId id) const override; [[nodiscard]] Result resume(UtteranceId id) const override; + [[nodiscard]] Result + subscribeOnUtteranceEvent(std::function&& notification) override; private: Firebolt::Helpers::IHelper& helper_; diff --git a/test/unit/speechSynthesisTest.cpp b/test/unit/speechSynthesisTest.cpp index 707210b..b33d650 100644 --- a/test/unit/speechSynthesisTest.cpp +++ b/test/unit/speechSynthesisTest.cpp @@ -16,6 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "json_types/speechsynthesis.h" #include "mock_helper.h" #include "speechsynthesis_impl.h" @@ -207,4 +208,46 @@ TEST_F(SpeechSynthesisUTest, resume_invokeError) auto result = speechSynthesisImpl_.resume(static_cast(123)); ASSERT_FALSE(result); EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(SpeechSynthesisUTest, subscribeOnUtteranceEvent) +{ + EXPECT_CALL(mockHelper, subscribe(_, "SpeechSynthesis.onUtteranceEvent", _, _)) + .WillOnce(::testing::Return(Firebolt::Result{1})); + + auto result = speechSynthesisImpl_.subscribeOnUtteranceEvent( + [](const Firebolt::SpeechSynthesis::UtteranceEvent& /*event*/) {}); + ASSERT_TRUE(result) << "error on subscribe "; + EXPECT_TRUE(result.has_value()) << "error on id"; +} + +TEST_F(SpeechSynthesisUTest, subscribeOnUtteranceEvent_subscribeError) +{ + EXPECT_CALL(mockHelper, subscribe(_, "SpeechSynthesis.onUtteranceEvent", _, _)) + .WillOnce(::testing::Return(Firebolt::Result{Firebolt::Error::General})); + + auto result = speechSynthesisImpl_.subscribeOnUtteranceEvent( + [](const Firebolt::SpeechSynthesis::UtteranceEvent& /*event*/) {}); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), Firebolt::Error::General); +} + +TEST_F(SpeechSynthesisUTest, utteranceEventResponse_parsesValidPayload) +{ + Firebolt::SpeechSynthesis::JsonData::UtteranceEventResponse response; + nlohmann::json payload = {{"id", static_cast(77)}, {"event", "resumed"}}; + + response.fromJson(payload); + auto value = response.value(); + + EXPECT_EQ(value.id, static_cast(77)); + EXPECT_EQ(value.event, Firebolt::SpeechSynthesis::UtteranceEventEnum::resumed); +} + +TEST_F(SpeechSynthesisUTest, utteranceEventResponse_rejectsUnknownEvent) +{ + Firebolt::SpeechSynthesis::JsonData::UtteranceEventResponse response; + nlohmann::json payload = {{"id", static_cast(77)}, {"event", "not-valid"}}; + + EXPECT_THROW(response.fromJson(payload), std::invalid_argument); } \ No newline at end of file From bf9256d8221c0d2bcc4f6eeee7b4ff873f7e2788 Mon Sep 17 00:00:00 2001 From: swethasukumarr Date: Wed, 19 Aug 2026 13:50:50 -0400 Subject: [PATCH 38/39] Add device apis for osName, osVersion and firmware (#99) * Add device apis for osName, osVersion and firmware * Fix payload for Device.setOsName and Device.setOsVersion, fix format issues * Fix unit tests --- docs/openrpc/the-spec/firebolt-open-rpc.json | 176 +++++++++++++++++++ include/firebolt/device.h | 35 ++++ src/device_impl.cpp | 25 +++ src/device_impl.h | 5 + test/api_test_app/apis/deviceDemo.cpp | 40 +++++ test/component/deviceTest.cpp | 35 ++++ test/unit/deviceTest.cpp | 50 ++++++ 7 files changed, 366 insertions(+) diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 743e3fe..1178fae 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -536,6 +536,182 @@ } ] }, + { + "name": "Device.osName", + "summary": "Returns the operating system name as defined by the operator", + "params": [], + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "osName", + "summary": "The operating system name", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Getting the operating system name", + "params": [], + "result": { + "name": "Default Result", + "value": "Linux" + } + } + ] + }, + { + "name": "Device.setOsName", + "summary": "Sets the operating system name as defined by the operator", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "params": [ + { + "name": "osName", + "summary": "The operating system name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Setting the operating system name", + "params": [ + { + "name": "osName", + "value": "Linux" + } + ], + "result": { + "name": "Default Result", + "value": null + } + } + ] + }, + { + "name": "Device.osVersion", + "summary": "Returns the operating system version as defined by the operator", + "params": [], + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "osVersion", + "summary": "The operating system version", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Getting the operating system version", + "params": [], + "result": { + "name": "Default Result", + "value": "5.15.0" + } + } + ] + }, + { + "name": "Device.setOsVersion", + "summary": "Sets the operating system version as defined by the operator", + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "params": [ + { + "name": "osVersion", + "summary": "The operating system version", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Setting the operating system version", + "params": [ + { + "name": "osVersion", + "value": "5.15.0" + } + ], + "result": { + "name": "Default Result", + "value": null + } + } + ] + }, + { + "name": "Device.firmware", + "summary": "Returns a string that identifies the firmware image of the device", + "params": [], + "tags": [ + { + "name": "capabilities", + "x-uses": [ + "xrn:firebolt:capability:device:info" + ] + } + ], + "result": { + "name": "firmware", + "summary": "The firmware image string", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Getting the firmware image", + "params": [], + "result": { + "name": "Default Result", + "value": "1.0.0-20240101" + } + } + ] + }, { "name": "Device.uptime", "summary": "Returns the number of seconds since most recent device boot, including any time spent during deep sleep", diff --git a/include/firebolt/device.h b/include/firebolt/device.h index 458d951..a5d9481 100644 --- a/include/firebolt/device.h +++ b/include/firebolt/device.h @@ -67,6 +67,41 @@ class IDevice */ [[nodiscard]] virtual Result deviceClass() const = 0; + /** + * @brief Get the operating system name + * + * @retval The OS name string or error + */ + [[nodiscard]] virtual Result osName() const = 0; + + /** + * @brief Set the operating system name + * + * @retval The status + */ + [[nodiscard]] virtual Result setOsName(const std::string& osName) = 0; + + /** + * @brief Get the operating system version + * + * @retval The OS version string or error + */ + [[nodiscard]] virtual Result osVersion() const = 0; + + /** + * @brief Set the operating system version + * + * @retval The status + */ + [[nodiscard]] virtual Result setOsVersion(const std::string& osVersion) = 0; + + /** + * @brief Get a string that identifies the firmware image of the device + * + * @retval The firmware image string or error + */ + [[nodiscard]] virtual Result firmware() const = 0; + /** * @brief Returns the HDR standards that are supported by the attached TV or the integral display * diff --git a/src/device_impl.cpp b/src/device_impl.cpp index f383a27..9d899c7 100644 --- a/src/device_impl.cpp +++ b/src/device_impl.cpp @@ -37,6 +37,31 @@ Result DeviceImpl::deviceClass() const return Result(helper_.get("Device.deviceClass")); } +Result DeviceImpl::osName() const +{ + return helper_.get("Device.osName"); +} + +Result DeviceImpl::setOsName(const std::string& osName) +{ + return helper_.invoke("Device.setOsName", nlohmann::json(osName)); +} + +Result DeviceImpl::osVersion() const +{ + return helper_.get("Device.osVersion"); +} + +Result DeviceImpl::setOsVersion(const std::string& osVersion) +{ + return helper_.invoke("Device.setOsVersion", nlohmann::json(osVersion)); +} + +Result DeviceImpl::firmware() const +{ + return helper_.get("Device.firmware"); +} + Result DeviceImpl::hdr() const { return Result(helper_.get("Device.hdr")); diff --git a/src/device_impl.h b/src/device_impl.h index 1c2dce2..297eede 100644 --- a/src/device_impl.h +++ b/src/device_impl.h @@ -34,6 +34,11 @@ class DeviceImpl : public IDevice [[nodiscard]] Result chipsetId() const override; [[nodiscard]] Result deviceClass() const override; + [[nodiscard]] Result osName() const override; + [[nodiscard]] Result setOsName(const std::string& osName) override; + [[nodiscard]] Result osVersion() const override; + [[nodiscard]] Result setOsVersion(const std::string& osVersion) override; + [[nodiscard]] Result firmware() const override; [[nodiscard]] Result hdr() const override; [[nodiscard]] Result timeInActiveState() const override; [[nodiscard]] Result uid() const override; diff --git a/test/api_test_app/apis/deviceDemo.cpp b/test/api_test_app/apis/deviceDemo.cpp index fd63323..ef1a99f 100644 --- a/test/api_test_app/apis/deviceDemo.cpp +++ b/test/api_test_app/apis/deviceDemo.cpp @@ -61,6 +61,46 @@ void DeviceDemo::runOption(const std::string& method) << std::endl; } } + else if (method == "Device.osName") + { + auto r = Firebolt::IFireboltAccessor::Instance().DeviceInterface().osName(); + if (succeed(r)) + { + std::cout << "Device OS Name: " << *r << std::endl; + } + } + else if (method == "Device.setOsName") + { + auto r = Firebolt::IFireboltAccessor::Instance().DeviceInterface().setOsName("Linux"); + if (succeed(r)) + { + std::cout << "Device OS Name set successfully" << std::endl; + } + } + else if (method == "Device.osVersion") + { + auto r = Firebolt::IFireboltAccessor::Instance().DeviceInterface().osVersion(); + if (succeed(r)) + { + std::cout << "Device OS Version: " << *r << std::endl; + } + } + else if (method == "Device.setOsVersion") + { + auto r = Firebolt::IFireboltAccessor::Instance().DeviceInterface().setOsVersion("5.15.0"); + if (succeed(r)) + { + std::cout << "Device OS Version set successfully" << std::endl; + } + } + else if (method == "Device.firmware") + { + auto r = Firebolt::IFireboltAccessor::Instance().DeviceInterface().firmware(); + if (succeed(r)) + { + std::cout << "Device Firmware: " << *r << std::endl; + } + } else if (method == "Device.hdr") { auto r = Firebolt::IFireboltAccessor::Instance().DeviceInterface().hdr(); diff --git a/test/component/deviceTest.cpp b/test/component/deviceTest.cpp index 5536070..00163ac 100644 --- a/test/component/deviceTest.cpp +++ b/test/component/deviceTest.cpp @@ -50,6 +50,41 @@ TEST_F(DeviceCTest, DeviceClass) ASSERT_TRUE(result) << "DeviceImpl::deviceClass() returned an error"; EXPECT_EQ(static_cast(*result), static_cast(Firebolt::Device::JsonData::DeviceClassEnum.at(expectedValue))); } +TEST_F(DeviceCTest, OsName) +{ + auto expectedValue = jsonEngine.get_value("Device.osName"); + auto result = Firebolt::IFireboltAccessor::Instance().DeviceInterface().osName(); + ASSERT_TRUE(result) << "DeviceImpl::osName() returned an error"; + EXPECT_EQ(*result, expectedValue); +} + +TEST_F(DeviceCTest, SetOsName) +{ + auto result = Firebolt::IFireboltAccessor::Instance().DeviceInterface().setOsName("Linux"); + ASSERT_TRUE(result) << "DeviceImpl::setOsName() returned an error"; +} + +TEST_F(DeviceCTest, OsVersion) +{ + auto expectedValue = jsonEngine.get_value("Device.osVersion"); + auto result = Firebolt::IFireboltAccessor::Instance().DeviceInterface().osVersion(); + ASSERT_TRUE(result) << "DeviceImpl::osVersion() returned an error"; + EXPECT_EQ(*result, expectedValue); +} + +TEST_F(DeviceCTest, SetOsVersion) +{ + auto result = Firebolt::IFireboltAccessor::Instance().DeviceInterface().setOsVersion("5.15.0"); + ASSERT_TRUE(result) << "DeviceImpl::setOsVersion() returned an error"; +} + +TEST_F(DeviceCTest, Firmware) +{ + auto expectedValue = jsonEngine.get_value("Device.firmware"); + auto result = Firebolt::IFireboltAccessor::Instance().DeviceInterface().firmware(); + ASSERT_TRUE(result) << "DeviceImpl::firmware() returned an error"; + EXPECT_EQ(*result, expectedValue); +} TEST_F(DeviceCTest, Hdr) { diff --git a/test/unit/deviceTest.cpp b/test/unit/deviceTest.cpp index 6ecd042..a90a1bc 100644 --- a/test/unit/deviceTest.cpp +++ b/test/unit/deviceTest.cpp @@ -56,6 +56,56 @@ TEST_F(DeviceUTest, DeviceClass) EXPECT_EQ(static_cast(*result), static_cast(Firebolt::Device::JsonData::DeviceClassEnum.at(expectedValue))); } +TEST_F(DeviceUTest, OsName) +{ + mock_with_response("Device.osName", "Linux"); + + auto result = deviceImpl_.osName(); + ASSERT_TRUE(result) << "DeviceImpl::osName() returned an error"; + EXPECT_EQ(*result, "Linux"); +} + +TEST_F(DeviceUTest, SetOsName) +{ + EXPECT_CALL(mockHelper, invoke("Device.setOsName", nlohmann::json("Linux"))) + .WillOnce(Invoke([](const std::string&, const nlohmann::json&) + { return Firebolt::Result{Firebolt::Error::None}; })); + + auto result = deviceImpl_.setOsName("Linux"); + + ASSERT_TRUE(result); +} + +TEST_F(DeviceUTest, OsVersion) +{ + + mock_with_response("Device.osVersion", "5.15.0"); + + auto result = deviceImpl_.osVersion(); + ASSERT_TRUE(result) << "DeviceImpl::osVersion() returned an error"; + EXPECT_EQ(*result, "5.15.0"); +} + +TEST_F(DeviceUTest, SetOsVersion) +{ + EXPECT_CALL(mockHelper, invoke("Device.setOsVersion", nlohmann::json("5.15.0"))) + .WillOnce(Invoke([](const std::string&, const nlohmann::json&) + { return Firebolt::Result{Firebolt::Error::None}; })); + + auto result = deviceImpl_.setOsVersion("5.15.0"); + + ASSERT_TRUE(result); +} + +TEST_F(DeviceUTest, Firmware) +{ + mock_with_response("Device.firmware", "1.0.0-20240101"); + + auto result = deviceImpl_.firmware(); + ASSERT_TRUE(result) << "DeviceImpl::firmware() returned an error"; + EXPECT_EQ(*result, "1.0.0-20240101"); +} + TEST_F(DeviceUTest, DeviceClassBadResponse) { mock_with_response("Device.deviceClass", "abc"); From d4d3076443602b5c7da0b33ad29e9fc09f443895 Mon Sep 17 00:00:00 2001 From: bobra200 Date: Wed, 19 Aug 2026 10:59:37 -0700 Subject: [PATCH 39/39] RDKEMW-21812: Adding component tests --- docs/openrpc/the-spec/firebolt-open-rpc.json | 368 +++++++++++++++++++ test/component/speechSynthesisTest.cpp | 101 +++++ test/component/textToSpeechTest.cpp | 32 ++ 3 files changed, 501 insertions(+) create mode 100644 test/component/speechSynthesisTest.cpp diff --git a/docs/openrpc/the-spec/firebolt-open-rpc.json b/docs/openrpc/the-spec/firebolt-open-rpc.json index 0f76a96..2592c69 100644 --- a/docs/openrpc/the-spec/firebolt-open-rpc.json +++ b/docs/openrpc/the-spec/firebolt-open-rpc.json @@ -15,6 +15,7 @@ "Metrics": "Methods for sending metrics", "Network": "Methods for accessing network information.", "Presentation": "Methods for accessing Presentation preferences.", + "SpeechSynthesis": "A module for controlling speech synthesis over Firebolt.", "Stats": "Provides methods to retrieve application-level system information.", "TextToSpeech": "A module for controlling and accessing Text To Speech over Firebolt." } @@ -2847,6 +2848,373 @@ } ] }, + { + "name": "SpeechSynthesis.speak", + "summary": "Speak an utterance.", + "params": [ + { + "name": "text", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "callSign", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "schema": { + "type": "string" + } + }, + { + "name": "voice", + "schema": { + "type": "string" + } + }, + { + "name": "volume", + "schema": { + "type": "string" + } + }, + { + "name": "rate", + "schema": { + "type": "string" + } + }, + { + "name": "pitch", + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "utteranceId", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "examples": [ + { + "name": "Speak text", + "params": [ + { + "name": "text", + "value": "I am a text waiting for speech." + }, + { + "name": "callSign", + "value": "AppA" + }, + { + "name": "language", + "value": "en-US" + }, + { + "name": "voice", + "value": "female-1" + }, + { + "name": "volume", + "value": "80" + }, + { + "name": "rate", + "value": "normal" + }, + { + "name": "pitch", + "value": "medium" + } + ], + "result": { + "name": "result", + "value": 1 + } + } + ] + }, + { + "name": "SpeechSynthesis.voices", + "summary": "Get available synthesis voices.", + "params": [], + "result": { + "name": "voices", + "schema": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "lang", + "default" + ], + "properties": { + "name": { + "type": "string" + }, + "lang": { + "type": "string" + }, + "default": { + "type": "boolean" + } + } + } + } + }, + "examples": [ + { + "name": "Get voices", + "params": [], + "result": { + "name": "result", + "value": [ + { + "name": "Salli", + "lang": "en-US", + "default": true + } + ] + } + } + ] + }, + { + "name": "SpeechSynthesis.cancel", + "summary": "Cancel an utterance.", + "params": [ + { + "name": "id", + "schema": { + "type": "integer", + "minimum": 0 + }, + "required": true + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Cancel utterance", + "params": [ + { + "name": "id", + "value": 1 + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "SpeechSynthesis.pause", + "summary": "Pause an utterance.", + "params": [ + { + "name": "id", + "schema": { + "type": "integer", + "minimum": 0 + }, + "required": true + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Pause utterance", + "params": [ + { + "name": "id", + "value": 1 + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "SpeechSynthesis.resume", + "summary": "Resume an utterance.", + "params": [ + { + "name": "id", + "schema": { + "type": "integer", + "minimum": 0 + }, + "required": true + } + ], + "result": { + "name": "result", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Resume utterance", + "params": [ + { + "name": "id", + "value": 1 + } + ], + "result": { + "name": "result", + "value": null + } + } + ] + }, + { + "name": "SpeechSynthesis.onVoicesChanged", + "summary": "Notification when available voices change.", + "tags": [ + { + "name": "event", + "x-notifier": "SpeechSynthesis.onVoicesChanged" + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "lang", + "default" + ], + "properties": { + "name": { + "type": "string" + }, + "lang": { + "type": "string" + }, + "default": { + "type": "boolean" + } + } + } + } + } + }, + { + "name": "SpeechSynthesis.onUtteranceEvent", + "summary": "Notification for utterance lifecycle events.", + "tags": [ + { + "name": "event", + "x-notifier": "SpeechSynthesis.onUtteranceEvent" + } + ], + "params": [ + { + "name": "listen", + "schema": { + "type": "boolean" + } + } + ], + "examples": [ + { + "name": "Default Example", + "params": [ + { + "name": "listen", + "value": true + } + ], + "result": { + "name": "result", + "value": null + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "object", + "required": [ + "id", + "event" + ], + "properties": { + "id": { + "type": "integer", + "minimum": 0 + }, + "event": { + "type": "string", + "enum": [ + "synthesisStarting", + "playbackStarting", + "paused", + "resumed", + "completed", + "interrupted", + "networkFailed", + "synthesisFailed", + "playbackFailed" + ] + } + } + } + } + }, { "name": "Lifecycle2.onStateChanged", "tags": [ diff --git a/test/component/speechSynthesisTest.cpp b/test/component/speechSynthesisTest.cpp new file mode 100644 index 0000000..2e98600 --- /dev/null +++ b/test/component/speechSynthesisTest.cpp @@ -0,0 +1,101 @@ +/** + * Copyright 2026 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "firebolt/firebolt.h" +#include "utils.h" +#include + +class SpeechSynthesisCTest : public ::testing::Test +{ +protected: + std::condition_variable cv_; + std::mutex mtx_; + bool eventReceived_ = false; +}; + +TEST_F(SpeechSynthesisCTest, speak) +{ + auto result = Firebolt::IFireboltAccessor::Instance().SpeechSynthesisInterface().speak("component test"); + ASSERT_TRUE(result) << "SpeechSynthesis.speak unavailable"; + EXPECT_GT(*result, 0U); +} + +TEST_F(SpeechSynthesisCTest, voices) +{ + auto result = Firebolt::IFireboltAccessor::Instance().SpeechSynthesisInterface().voices(); + ASSERT_TRUE(result) << "SpeechSynthesis.voices unavailable"; + EXPECT_GE(result->size(), 1U); + EXPECT_FALSE((*result)[0].name.empty()); + EXPECT_FALSE((*result)[0].lang.empty()); +} + +TEST_F(SpeechSynthesisCTest, cancelPauseResume) +{ + constexpr Firebolt::SpeechSynthesis::UtteranceId id = 1; + + auto cancelResult = Firebolt::IFireboltAccessor::Instance().SpeechSynthesisInterface().cancel(id); + ASSERT_TRUE(cancelResult) << "SpeechSynthesis.cancel unavailable"; + + auto pauseResult = Firebolt::IFireboltAccessor::Instance().SpeechSynthesisInterface().pause(id); + ASSERT_TRUE(pauseResult) << "pause failed after successful cancel call"; + + auto resumeResult = Firebolt::IFireboltAccessor::Instance().SpeechSynthesisInterface().resume(id); + ASSERT_TRUE(resumeResult) << "resume failed after successful cancel call"; +} + +TEST_F(SpeechSynthesisCTest, subscribeOnVoicesChanged) +{ + auto id = Firebolt::IFireboltAccessor::Instance().SpeechSynthesisInterface().subscribeOnVoicesChanged( + [&](const std::pmr::vector& voices) + { + ASSERT_EQ(voices.size(), 1U); + EXPECT_EQ(voices[0].name, "Salli"); + EXPECT_EQ(voices[0].lang, "en-US"); + EXPECT_TRUE(voices[0]._default); + { + std::lock_guard lock(mtx_); + eventReceived_ = true; + } + cv_.notify_one(); + }); + + verifyEventSubscription(id); + + triggerEvent("SpeechSynthesis.onVoicesChanged", R"([{"name":"Salli","lang":"en-US","default":true}])"); + verifyEventReceived(mtx_, cv_, eventReceived_); +} + +TEST_F(SpeechSynthesisCTest, subscribeOnUtteranceEvent) +{ + auto id = Firebolt::IFireboltAccessor::Instance().SpeechSynthesisInterface().subscribeOnUtteranceEvent( + [&](const Firebolt::SpeechSynthesis::UtteranceEvent& event) + { + EXPECT_EQ(event.id, static_cast(7)); + EXPECT_EQ(event.event, Firebolt::SpeechSynthesis::UtteranceEventEnum::resumed); + { + std::lock_guard lock(mtx_); + eventReceived_ = true; + } + cv_.notify_one(); + }); + + verifyEventSubscription(id); + + triggerEvent("SpeechSynthesis.onUtteranceEvent", R"({"id":7,"event":"resumed"})"); + verifyEventReceived(mtx_, cv_, eventReceived_); +} diff --git a/test/component/textToSpeechTest.cpp b/test/component/textToSpeechTest.cpp index 1627d7e..4be5478 100644 --- a/test/component/textToSpeechTest.cpp +++ b/test/component/textToSpeechTest.cpp @@ -20,6 +20,7 @@ #include "firebolt/firebolt.h" #include "json_engine.h" #include "utils.h" +#include class TextToSpeechCTest : public ::testing::Test { @@ -49,6 +50,37 @@ TEST_F(TextToSpeechCTest, speak) EXPECT_EQ(speakResult->success, expectedValue["success"].get()); } +TEST_F(TextToSpeechCTest, speak_withAllOptionalArguments) +{ + auto speakResult = + Firebolt::IFireboltAccessor::Instance().TextToSpeechInterface().speak("I am a text waiting for speech.", + std::string("AppA"), std::string("en-US"), + std::string("female-1"), + std::string("80"), std::string("normal"), + std::string("medium")); + ASSERT_TRUE(speakResult) << "Error on speak with all optional arguments"; + + auto expectedValue = jsonEngine.get_value("TextToSpeech.speak"); + EXPECT_EQ(speakResult->speechId, expectedValue["speechid"].get()); + EXPECT_EQ(speakResult->ttsStatus, expectedValue["TTS_Status"].get()); + EXPECT_EQ(speakResult->success, expectedValue["success"].get()); +} + +TEST_F(TextToSpeechCTest, speak_withSelectedOptionalArguments) +{ + auto speakResult = + Firebolt::IFireboltAccessor::Instance().TextToSpeechInterface().speak("I am a text waiting for speech.", + std::nullopt, std::string("en-US"), + std::nullopt, std::nullopt, + std::string("normal"), std::nullopt); + ASSERT_TRUE(speakResult) << "Error on speak with selected optional arguments"; + + auto expectedValue = jsonEngine.get_value("TextToSpeech.speak"); + EXPECT_EQ(speakResult->speechId, expectedValue["speechid"].get()); + EXPECT_EQ(speakResult->ttsStatus, expectedValue["TTS_Status"].get()); + EXPECT_EQ(speakResult->success, expectedValue["success"].get()); +} + TEST_F(TextToSpeechCTest, pause) { int32_t speechId = 1;