/** * * Copyright (c) 2021 Project CHIP Authors * * 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. */ #include "ChannelManager.h" #include #include #include #include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters::Channel; using namespace chip::Uint8; ChannelManager::ChannelManager() { ChannelInfoType abc; abc.affiliateCallSign = MakeOptional(chip::CharSpan::fromCharString("KAAL")); abc.callSign = MakeOptional(chip::CharSpan::fromCharString("KAAL-TV")); abc.name = MakeOptional(chip::CharSpan::fromCharString("ABC")); abc.majorNumber = static_cast(6); abc.minorNumber = static_cast(0); mChannels.push_back(abc); ChannelInfoType pbs; pbs.affiliateCallSign = MakeOptional(chip::CharSpan::fromCharString("KCTS")); pbs.callSign = MakeOptional(chip::CharSpan::fromCharString("KCTS-TV")); pbs.name = MakeOptional(chip::CharSpan::fromCharString("PBS")); pbs.majorNumber = static_cast(9); pbs.minorNumber = static_cast(1); mChannels.push_back(pbs); ChannelInfoType pbsKids; pbsKids.affiliateCallSign = MakeOptional(chip::CharSpan::fromCharString("KCTS")); pbsKids.callSign = MakeOptional(chip::CharSpan::fromCharString("KCTS-TV")); pbsKids.name = MakeOptional(chip::CharSpan::fromCharString("PBS Kids")); pbsKids.majorNumber = static_cast(9); pbsKids.minorNumber = static_cast(2); mChannels.push_back(pbsKids); ChannelInfoType worldChannel; worldChannel.affiliateCallSign = MakeOptional(chip::CharSpan::fromCharString("KCTS")); worldChannel.callSign = MakeOptional(chip::CharSpan::fromCharString("KCTS-TV")); worldChannel.name = MakeOptional(chip::CharSpan::fromCharString("World Channel")); worldChannel.majorNumber = static_cast(9); worldChannel.minorNumber = static_cast(3); mChannels.push_back(worldChannel); mCurrentChannelIndex = 0; mCurrentChannel = mChannels[mCurrentChannelIndex]; ProgramType program1; program1.identifier = chip::CharSpan::fromCharString("progid-abc1"); program1.channel = abc; program1.title = chip::CharSpan::fromCharString("ABC Title1"); program1.subtitle = MakeOptional(chip::CharSpan::fromCharString("My Program Subtitle1")); program1.startTime = 0; program1.endTime = 30 * 60; mPrograms.push_back(program1); ProgramType program_pbs1; program_pbs1.identifier = chip::CharSpan::fromCharString("progid-pbs1"); program_pbs1.channel = pbs; program_pbs1.title = chip::CharSpan::fromCharString("PBS Title1"); program_pbs1.subtitle = MakeOptional(chip::CharSpan::fromCharString("My Program Subtitle1")); program_pbs1.startTime = 0; program_pbs1.endTime = 30 * 60; mPrograms.push_back(program_pbs1); ProgramType program2; program2.identifier = chip::CharSpan::fromCharString("progid-abc2"); program2.channel = abc; program2.title = chip::CharSpan::fromCharString("My Program Title2"); program2.subtitle = MakeOptional(chip::CharSpan::fromCharString("My Program Subtitle2")); program2.startTime = 30 * 60; program2.endTime = 60 * 60; mPrograms.push_back(program2); ProgramType program3; program3.identifier = chip::CharSpan::fromCharString("progid-abc3"); program3.channel = abc; program3.title = chip::CharSpan::fromCharString("My Program Title3"); program3.subtitle = MakeOptional(chip::CharSpan::fromCharString("My Program Subtitle3")); program3.startTime = 0; program3.endTime = 60 * 60; mPrograms.push_back(program3); } CHIP_ERROR ChannelManager::HandleGetChannelList(AttributeValueEncoder & aEncoder) { // TODO: Insert code here return aEncoder.EncodeList([](const auto & encoder) -> CHIP_ERROR { for (auto const & channel : ChannelManager().mChannels) { ReturnErrorOnFailure(encoder.Encode(channel)); } return CHIP_NO_ERROR; }); } CHIP_ERROR ChannelManager::HandleGetLineup(AttributeValueEncoder & aEncoder) { LineupInfoType lineup; lineup.operatorName = chip::CharSpan::fromCharString("Comcast"); lineup.lineupName = MakeOptional(chip::CharSpan::fromCharString("Comcast King County")); lineup.postalCode = MakeOptional(chip::CharSpan::fromCharString("98052")); lineup.lineupInfoType = chip::app::Clusters::Channel::LineupInfoTypeEnum::kMso; return aEncoder.Encode(lineup); } CHIP_ERROR ChannelManager::HandleGetCurrentChannel(AttributeValueEncoder & aEncoder) { return aEncoder.Encode(mCurrentChannel); } bool isChannelMatched(const ChannelInfoType & channel, const CharSpan & match) { char number[32]; sprintf(number, "%d.%d", channel.majorNumber, channel.minorNumber); bool nameMatch = channel.name.HasValue() ? channel.name.Value().data_equal(match) : false; bool affiliateCallSignMatch = channel.affiliateCallSign.HasValue() ? channel.affiliateCallSign.Value().data_equal(match) : false; bool callSignMatch = channel.callSign.HasValue() ? channel.callSign.Value().data_equal(match) : false; bool numberMatch = match.data_equal(chip::CharSpan::fromCharString(number)); return affiliateCallSignMatch || callSignMatch || nameMatch || numberMatch; } void ChannelManager::HandleChangeChannel(CommandResponseHelper & helper, const CharSpan & match) { std::vector matchedChannels; uint16_t index = 0; for (auto const & channel : mChannels) { // verify if CharSpan matches channel name // or callSign or affiliateCallSign or majorNumber.minorNumber if (isChannelMatched(channel, match)) { matchedChannels.push_back(channel); } else if (matchedChannels.size() == 0) { // "index" is only used when we end up with matchedChannels.size() == 1. // In that case, we want it to be the number of non-matching channels we saw before // the matching one. index++; } } ChangeChannelResponseType response; // Error: Found multiple matches if (matchedChannels.size() > 1) { response.status = chip::app::Clusters::Channel::StatusEnum::kMultipleMatches; helper.Success(response); } else if (matchedChannels.size() == 0) { // Error: Found no match response.status = chip::app::Clusters::Channel::StatusEnum::kNoMatches; helper.Success(response); } else { response.status = chip::app::Clusters::Channel::StatusEnum::kSuccess; response.data = chip::MakeOptional(CharSpan::fromCharString("data response")); mCurrentChannel = matchedChannels[0]; mCurrentChannelIndex = index; helper.Success(response); } } bool ChannelManager::HandleChangeChannelByNumber(const uint16_t & majorNumber, const uint16_t & minorNumber) { bool channelChanged = false; uint16_t index = 0; for (auto const & channel : mChannels) { // verify if major & minor matches one of the channel from the list if (channel.minorNumber == minorNumber && channel.majorNumber == majorNumber) { // verify if channel changed by comparing values of current channel with the requested channel if (channel.minorNumber != mCurrentChannel.minorNumber || channel.majorNumber != mCurrentChannel.majorNumber) { channelChanged = true; mCurrentChannelIndex = index; mCurrentChannel = channel; } } index++; } return channelChanged; } bool ChannelManager::HandleSkipChannel(const int16_t & count) { int32_t newChannelIndex = static_cast(count) + static_cast(mCurrentChannelIndex); uint16_t channelsSize = static_cast(mChannels.size()); // handle newChannelIndex out of range. newChannelIndex = newChannelIndex % channelsSize; if (newChannelIndex < 0) { newChannelIndex = newChannelIndex + channelsSize; } mCurrentChannelIndex = static_cast(newChannelIndex); mCurrentChannel = mChannels[mCurrentChannelIndex]; return true; } void ChannelManager::HandleGetProgramGuide( CommandResponseHelper & helper, const chip::Optional & startTime, const chip::Optional & endTime, const chip::Optional> & channelList, const chip::Optional & pageToken, const chip::Optional> & recordingFlag, const chip::Optional> & externalIdList, const chip::Optional & data) { // 1. Decode received parameters // 2. Perform search // 3. Return results // PageTokenType paging; // paging.limit = MakeOptional(static_cast(10)); // paging.after = MakeOptional(chip::CharSpan::fromCharString("after-token")); // paging.before = MakeOptional(chip::CharSpan::fromCharString("before-token")); // ChannelPagingStructType channelPaging; // channelPaging.nextToken = MakeOptional>(paging); std::vector matches; for (auto const & program : mPrograms) { if (startTime.ValueOr(0) > program.startTime) { continue; } if (endTime.HasValue() && endTime.ValueOr(0) < program.endTime) { continue; } if (channelList.HasValue()) { auto iter = channelList.Value().begin(); bool match = false; int listCount = 0; while (iter.Next() && !match) { listCount++; auto & channel = iter.GetValue(); if (channel.minorNumber != program.channel.minorNumber || channel.majorNumber != program.channel.majorNumber) { continue; } // this sample code does not currently check OTT match = true; } if (!match && listCount > 0) { continue; } } // this sample code does not currently filter on external id list matches.push_back(program); } ProgramGuideResponseType response; response.programList = DataModel::List(matches.data(), matches.size()); helper.Success(response); } bool ChannelManager::HandleRecordProgram(const chip::CharSpan & programIdentifier, bool shouldRecordSeries, const DataModel::DecodableList & externalIdList, const chip::ByteSpan & data) { // Start recording std::string idString(programIdentifier.data(), programIdentifier.size()); for (auto & program : mPrograms) { std::string nextIdString(program.identifier.data(), program.identifier.size()); if (nextIdString == idString) { program.recordingFlag = MakeOptional(static_cast(shouldRecordSeries ? 2 : 1)); } } return true; } bool ChannelManager::HandleCancelRecordProgram(const chip::CharSpan & programIdentifier, bool shouldRecordSeries, const DataModel::DecodableList & externalIdList, const chip::ByteSpan & data) { // Cancel recording std::string idString(programIdentifier.data(), programIdentifier.size()); for (auto & program : mPrograms) { std::string nextIdString(program.identifier.data(), program.identifier.size()); if (nextIdString == idString) { program.recordingFlag = MakeOptional(static_cast(0)); } } return true; } uint32_t ChannelManager::GetFeatureMap(chip::EndpointId endpoint) { if (endpoint >= MATTER_DM_CONTENT_LAUNCHER_CLUSTER_SERVER_ENDPOINT_COUNT) { return kEndpointFeatureMap; } uint32_t featureMap = 0; Attributes::FeatureMap::Get(endpoint, &featureMap); return featureMap; } uint16_t ChannelManager::GetClusterRevision(chip::EndpointId endpoint) { if (endpoint >= MATTER_DM_CONTENT_LAUNCHER_CLUSTER_SERVER_ENDPOINT_COUNT) { return kClusterRevision; } uint16_t clusterRevision = 0; bool success = (Attributes::ClusterRevision::Get(endpoint, &clusterRevision) == chip::Protocols::InteractionModel::Status::Success); if (!success) { ChipLogError(Zcl, "ChannelManager::GetClusterRevision error reading cluster revision"); } return clusterRevision; }