blob: a894b5d0150ca682210fd0e0a5e415e9bcd08895 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* 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 <AppMain.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/PlatformManager.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/ConcreteAttributePath.h>
#include <app/EventLogging.h>
#include <app/reporting/reporting.h>
#include <app/util/af-types.h>
#include <app/util/attribute-storage.h>
#include <app/util/endpoint-config-api.h>
#include <app/util/util.h>
#include <credentials/DeviceAttestationCredsProvider.h>
#include <credentials/examples/DeviceAttestationCredsExample.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/ZclString.h>
#include <platform/CommissionableDataProvider.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <setup_payload/SetupPayload.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include "CommissionableInit.h"
#include "Device.h"
#include "main.h"
#include <app/server/Server.h>
#include <cassert>
#include <iostream>
#include <string>
#include <vector>
using namespace chip;
using namespace chip::app;
using namespace chip::Credentials;
using namespace chip::Inet;
using namespace chip::Transport;
using namespace chip::DeviceLayer;
using namespace chip::app::Clusters;
namespace {
const int kNodeLabelSize = 32;
// Current ZCL implementation of Struct uses a max-size array of 254 bytes
const int kDescriptorAttributeArraySize = 254;
EndpointId gCurrentEndpointId;
EndpointId gFirstDynamicEndpointId;
// Power source is on the same endpoint as the composed device
Device * gDevices[CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT + 1];
std::vector<Room *> gRooms;
std::vector<Action *> gActions;
const int16_t minMeasuredValue = -27315;
const int16_t maxMeasuredValue = 32766;
const int16_t initialMeasuredValue = 100;
// ENDPOINT DEFINITIONS:
// =================================================================================
//
// Endpoint definitions will be reused across multiple endpoints for every instance of the
// endpoint type.
// There will be no intrinsic storage for the endpoint attributes declared here.
// Instead, all attributes will be treated as EXTERNAL, and therefore all reads
// or writes to the attributes must be handled within the emberAfExternalAttributeWriteCallback
// and emberAfExternalAttributeReadCallback functions declared herein. This fits
// the typical model of a bridge, since a bridge typically maintains its own
// state database representing the devices connected to it.
// Device types for dynamic endpoints: TODO Need a generated file from ZAP to define these!
// (taken from matter-devices.xml)
#define DEVICE_TYPE_BRIDGED_NODE 0x0013
// (taken from lo-devices.xml)
#define DEVICE_TYPE_LO_ON_OFF_LIGHT 0x0100
// (taken from matter-devices.xml)
#define DEVICE_TYPE_POWER_SOURCE 0x0011
// (taken from matter-devices.xml)
#define DEVICE_TYPE_TEMP_SENSOR 0x0302
// Device Version for dynamic endpoints:
#define DEVICE_VERSION_DEFAULT 1
// ---------------------------------------------------------------------------
//
// LIGHT ENDPOINT: contains the following clusters:
// - On/Off
// - Descriptor
// - Bridged Device Basic Information
// Declare On/Off cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(onOffAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(OnOff::Attributes::OnOff::Id, BOOLEAN, 1, 0), /* on/off */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
// Declare Descriptor cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::DeviceTypeList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* device list */
DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ServerList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */
DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ClientList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */
DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::PartsList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* parts list */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
// Declare Bridged Device Basic Information cluster attributes
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::NodeLabel::Id, CHAR_STRING, kNodeLabelSize, 0), /* NodeLabel */
DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::Reachable::Id, BOOLEAN, 1, 0), /* Reachable */
DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::FeatureMap::Id, BITMAP32, 4, 0), /* feature map */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
// Declare Cluster List for Bridged Light endpoint
// TODO: It's not clear whether it would be better to get the command lists from
// the ZAP config on our last fixed endpoint instead.
constexpr CommandId onOffIncomingCommands[] = {
app::Clusters::OnOff::Commands::Off::Id,
app::Clusters::OnOff::Commands::On::Id,
app::Clusters::OnOff::Commands::Toggle::Id,
app::Clusters::OnOff::Commands::OffWithEffect::Id,
app::Clusters::OnOff::Commands::OnWithRecallGlobalScene::Id,
app::Clusters::OnOff::Commands::OnWithTimedOff::Id,
kInvalidCommandId,
};
DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedLightClusters)
DECLARE_DYNAMIC_CLUSTER(OnOff::Id, onOffAttrs, ZAP_CLUSTER_MASK(SERVER), onOffIncomingCommands, nullptr),
DECLARE_DYNAMIC_CLUSTER(Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr,
nullptr) DECLARE_DYNAMIC_CLUSTER_LIST_END;
// Declare Bridged Light endpoint
DECLARE_DYNAMIC_ENDPOINT(bridgedLightEndpoint, bridgedLightClusters);
DataVersion gLight1DataVersions[ArraySize(bridgedLightClusters)];
DataVersion gLight2DataVersions[ArraySize(bridgedLightClusters)];
DeviceOnOff Light1("Light 1", "Office");
DeviceOnOff Light2("Light 2", "Office");
DeviceTempSensor TempSensor1("TempSensor 1", "Office", minMeasuredValue, maxMeasuredValue, initialMeasuredValue);
DeviceTempSensor TempSensor2("TempSensor 2", "Office", minMeasuredValue, maxMeasuredValue, initialMeasuredValue);
DeviceTempSensor ComposedTempSensor1("Composed TempSensor 1", "Bedroom", minMeasuredValue, maxMeasuredValue, initialMeasuredValue);
DeviceTempSensor ComposedTempSensor2("Composed TempSensor 2", "Bedroom", minMeasuredValue, maxMeasuredValue, initialMeasuredValue);
// Declare Bridged endpoints used for Action clusters
DataVersion gActionLight1DataVersions[ArraySize(bridgedLightClusters)];
DataVersion gActionLight2DataVersions[ArraySize(bridgedLightClusters)];
DataVersion gActionLight3DataVersions[ArraySize(bridgedLightClusters)];
DataVersion gActionLight4DataVersions[ArraySize(bridgedLightClusters)];
DeviceOnOff ActionLight1("Action Light 1", "Room 1");
DeviceOnOff ActionLight2("Action Light 2", "Room 1");
DeviceOnOff ActionLight3("Action Light 3", "Room 2");
DeviceOnOff ActionLight4("Action Light 4", "Room 2");
Room room1("Room 1", 0xE001, Actions::EndpointListTypeEnum::kRoom, true);
Room room2("Room 2", 0xE002, Actions::EndpointListTypeEnum::kRoom, true);
Room room3("Zone 3", 0xE003, Actions::EndpointListTypeEnum::kZone, false);
Action action1(0x1001, "Room 1 On", Actions::ActionTypeEnum::kAutomation, 0xE001, 0x1, Actions::ActionStateEnum::kInactive, true);
Action action2(0x1002, "Turn On Room 2", Actions::ActionTypeEnum::kAutomation, 0xE002, 0x01, Actions::ActionStateEnum::kInactive,
true);
Action action3(0x1003, "Turn Off Room 1", Actions::ActionTypeEnum::kAutomation, 0xE003, 0x01, Actions::ActionStateEnum::kInactive,
false);
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(tempSensorAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(TemperatureMeasurement::Attributes::MeasuredValue::Id, INT16S, 2, 0), /* Measured Value */
DECLARE_DYNAMIC_ATTRIBUTE(TemperatureMeasurement::Attributes::MinMeasuredValue::Id, INT16S, 2, 0), /* Min Measured Value */
DECLARE_DYNAMIC_ATTRIBUTE(TemperatureMeasurement::Attributes::MaxMeasuredValue::Id, INT16S, 2, 0), /* Max Measured Value */
DECLARE_DYNAMIC_ATTRIBUTE(TemperatureMeasurement::Attributes::FeatureMap::Id, BITMAP32, 4, 0), /* FeatureMap */
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
// ---------------------------------------------------------------------------
//
// TEMPERATURE SENSOR ENDPOINT: contains the following clusters:
// - Temperature measurement
// - Descriptor
// - Bridged Device Basic Information
DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedTempSensorClusters)
DECLARE_DYNAMIC_CLUSTER(TemperatureMeasurement::Id, tempSensorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
DECLARE_DYNAMIC_CLUSTER(Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
DECLARE_DYNAMIC_CLUSTER_LIST_END;
// Declare Bridged Light endpoint
DECLARE_DYNAMIC_ENDPOINT(bridgedTempSensorEndpoint, bridgedTempSensorClusters);
DataVersion gTempSensor1DataVersions[ArraySize(bridgedTempSensorClusters)];
DataVersion gTempSensor2DataVersions[ArraySize(bridgedTempSensorClusters)];
// ---------------------------------------------------------------------------
//
// COMPOSED DEVICE ENDPOINT: contains the following clusters:
// - Descriptor
// - Bridged Device Basic Information
// - Power source
// Composed Device Configuration
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(powerSourceAttrs)
DECLARE_DYNAMIC_ATTRIBUTE(PowerSource::Attributes::BatChargeLevel::Id, ENUM8, 1, 0),
DECLARE_DYNAMIC_ATTRIBUTE(PowerSource::Attributes::BatReplacementNeeded::Id, BOOLEAN, 1, 0),
DECLARE_DYNAMIC_ATTRIBUTE(PowerSource::Attributes::BatReplaceability::Id, ENUM8, 1, 0),
DECLARE_DYNAMIC_ATTRIBUTE(PowerSource::Attributes::Order::Id, INT8U, 1, 0),
DECLARE_DYNAMIC_ATTRIBUTE(PowerSource::Attributes::Status::Id, ENUM8, 1, 0),
DECLARE_DYNAMIC_ATTRIBUTE(PowerSource::Attributes::Description::Id, CHAR_STRING, 32, 0),
DECLARE_DYNAMIC_ATTRIBUTE(PowerSource::Attributes::EndpointList::Id, ARRAY, 0, 0),
DECLARE_DYNAMIC_ATTRIBUTE(PowerSource::Attributes::FeatureMap::Id, BITMAP32, 4, 0), DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedComposedDeviceClusters)
DECLARE_DYNAMIC_CLUSTER(Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
DECLARE_DYNAMIC_CLUSTER(PowerSource::Id, powerSourceAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
DECLARE_DYNAMIC_CLUSTER_LIST_END;
DECLARE_DYNAMIC_ENDPOINT(bridgedComposedDeviceEndpoint, bridgedComposedDeviceClusters);
DataVersion gComposedDeviceDataVersions[ArraySize(bridgedComposedDeviceClusters)];
DataVersion gComposedTempSensor1DataVersions[ArraySize(bridgedTempSensorClusters)];
DataVersion gComposedTempSensor2DataVersions[ArraySize(bridgedTempSensorClusters)];
} // namespace
// REVISION DEFINITIONS:
// =================================================================================
#define ZCL_DESCRIPTOR_CLUSTER_REVISION (1u)
#define ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION (2u)
#define ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_FEATURE_MAP (0u)
#define ZCL_FIXED_LABEL_CLUSTER_REVISION (1u)
#define ZCL_ON_OFF_CLUSTER_REVISION (4u)
#define ZCL_TEMPERATURE_SENSOR_CLUSTER_REVISION (1u)
#define ZCL_TEMPERATURE_SENSOR_FEATURE_MAP (0u)
#define ZCL_POWER_SOURCE_CLUSTER_REVISION (2u)
// ---------------------------------------------------------------------------
int AddDeviceEndpoint(Device * dev, EmberAfEndpointType * ep, const Span<const EmberAfDeviceType> & deviceTypeList,
const Span<DataVersion> & dataVersionStorage, chip::EndpointId parentEndpointId = chip::kInvalidEndpointId)
{
uint8_t index = 0;
while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
{
if (nullptr == gDevices[index])
{
gDevices[index] = dev;
CHIP_ERROR err;
while (true)
{
// Todo: Update this to schedule the work rather than use this lock
DeviceLayer::StackLock lock;
dev->SetEndpointId(gCurrentEndpointId);
dev->SetParentEndpointId(parentEndpointId);
err =
emberAfSetDynamicEndpoint(index, gCurrentEndpointId, ep, dataVersionStorage, deviceTypeList, parentEndpointId);
if (err == CHIP_NO_ERROR)
{
ChipLogProgress(DeviceLayer, "Added device %s to dynamic endpoint %d (index=%d)", dev->GetName(),
gCurrentEndpointId, index);
return index;
}
if (err != CHIP_ERROR_ENDPOINT_EXISTS)
{
return -1;
}
// Handle wrap condition
if (++gCurrentEndpointId < gFirstDynamicEndpointId)
{
gCurrentEndpointId = gFirstDynamicEndpointId;
}
}
}
index++;
}
ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint: No endpoints available!");
return -1;
}
int RemoveDeviceEndpoint(Device * dev)
{
uint8_t index = 0;
while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
{
if (gDevices[index] == dev)
{
// Todo: Update this to schedule the work rather than use this lock
DeviceLayer::StackLock lock;
// Silence complaints about unused ep when progress logging
// disabled.
[[maybe_unused]] EndpointId ep = emberAfClearDynamicEndpoint(index);
gDevices[index] = nullptr;
ChipLogProgress(DeviceLayer, "Removed device %s from dynamic endpoint %d (index=%d)", dev->GetName(), ep, index);
return index;
}
index++;
}
return -1;
}
std::vector<EndpointListInfo> GetEndpointListInfo(chip::EndpointId parentId)
{
std::vector<EndpointListInfo> infoList;
for (auto room : gRooms)
{
if (room->getIsVisible())
{
EndpointListInfo info(room->getEndpointListId(), room->getName(), room->getType());
int index = 0;
while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
{
if ((gDevices[index] != nullptr) && (gDevices[index]->GetParentEndpointId() == parentId))
{
std::string location;
if (room->getType() == Actions::EndpointListTypeEnum::kZone)
{
location = gDevices[index]->GetZone();
}
else
{
location = gDevices[index]->GetLocation();
}
if (room->getName().compare(location) == 0)
{
info.AddEndpointId(gDevices[index]->GetEndpointId());
}
}
index++;
}
if (info.GetEndpointListSize() > 0)
{
infoList.push_back(info);
}
}
}
return infoList;
}
std::vector<Action *> GetActionListInfo(chip::EndpointId parentId)
{
return gActions;
}
namespace {
void CallReportingCallback(intptr_t closure)
{
auto path = reinterpret_cast<app::ConcreteAttributePath *>(closure);
MatterReportingAttributeChangeCallback(*path);
Platform::Delete(path);
}
void ScheduleReportingCallback(Device * dev, ClusterId cluster, AttributeId attribute)
{
auto * path = Platform::New<app::ConcreteAttributePath>(dev->GetEndpointId(), cluster, attribute);
PlatformMgr().ScheduleWork(CallReportingCallback, reinterpret_cast<intptr_t>(path));
}
} // anonymous namespace
void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask)
{
if (itemChangedMask & Device::kChanged_Reachable)
{
ScheduleReportingCallback(dev, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::Reachable::Id);
}
if (itemChangedMask & Device::kChanged_Name)
{
ScheduleReportingCallback(dev, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::NodeLabel::Id);
}
}
void HandleDeviceOnOffStatusChanged(DeviceOnOff * dev, DeviceOnOff::Changed_t itemChangedMask)
{
if (itemChangedMask & (DeviceOnOff::kChanged_Reachable | DeviceOnOff::kChanged_Name | DeviceOnOff::kChanged_Location))
{
HandleDeviceStatusChanged(static_cast<Device *>(dev), (Device::Changed_t) itemChangedMask);
}
if (itemChangedMask & DeviceOnOff::kChanged_OnOff)
{
ScheduleReportingCallback(dev, OnOff::Id, OnOff::Attributes::OnOff::Id);
}
}
void HandleDevicePowerSourceStatusChanged(DevicePowerSource * dev, DevicePowerSource::Changed_t itemChangedMask)
{
using namespace app::Clusters;
if (itemChangedMask &
(DevicePowerSource::kChanged_Reachable | DevicePowerSource::kChanged_Name | DevicePowerSource::kChanged_Location))
{
HandleDeviceStatusChanged(static_cast<Device *>(dev), (Device::Changed_t) itemChangedMask);
}
if (itemChangedMask & DevicePowerSource::kChanged_BatLevel)
{
MatterReportingAttributeChangeCallback(dev->GetEndpointId(), PowerSource::Id, PowerSource::Attributes::BatChargeLevel::Id);
}
if (itemChangedMask & DevicePowerSource::kChanged_Description)
{
MatterReportingAttributeChangeCallback(dev->GetEndpointId(), PowerSource::Id, PowerSource::Attributes::Description::Id);
}
if (itemChangedMask & DevicePowerSource::kChanged_EndpointList)
{
MatterReportingAttributeChangeCallback(dev->GetEndpointId(), PowerSource::Id, PowerSource::Attributes::EndpointList::Id);
}
}
void HandleDeviceTempSensorStatusChanged(DeviceTempSensor * dev, DeviceTempSensor::Changed_t itemChangedMask)
{
if (itemChangedMask &
(DeviceTempSensor::kChanged_Reachable | DeviceTempSensor::kChanged_Name | DeviceTempSensor::kChanged_Location))
{
HandleDeviceStatusChanged(static_cast<Device *>(dev), (Device::Changed_t) itemChangedMask);
}
if (itemChangedMask & DeviceTempSensor::kChanged_MeasurementValue)
{
ScheduleReportingCallback(dev, TemperatureMeasurement::Id, TemperatureMeasurement::Attributes::MeasuredValue::Id);
}
}
Protocols::InteractionModel::Status HandleReadBridgedDeviceBasicAttribute(Device * dev, chip::AttributeId attributeId,
uint8_t * buffer, uint16_t maxReadLength)
{
using namespace BridgedDeviceBasicInformation::Attributes;
ChipLogProgress(DeviceLayer, "HandleReadBridgedDeviceBasicAttribute: attrId=%d, maxReadLength=%d", attributeId, maxReadLength);
if ((attributeId == Reachable::Id) && (maxReadLength == 1))
{
*buffer = dev->IsReachable() ? 1 : 0;
}
else if ((attributeId == NodeLabel::Id) && (maxReadLength == 32))
{
MutableByteSpan zclNameSpan(buffer, maxReadLength);
MakeZclCharString(zclNameSpan, dev->GetName());
}
else if ((attributeId == ClusterRevision::Id) && (maxReadLength == 2))
{
uint16_t rev = ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION;
memcpy(buffer, &rev, sizeof(rev));
}
else if ((attributeId == FeatureMap::Id) && (maxReadLength == 4))
{
uint32_t featureMap = ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_FEATURE_MAP;
memcpy(buffer, &featureMap, sizeof(featureMap));
}
else
{
return Protocols::InteractionModel::Status::Failure;
}
return Protocols::InteractionModel::Status::Success;
}
Protocols::InteractionModel::Status HandleReadOnOffAttribute(DeviceOnOff * dev, chip::AttributeId attributeId, uint8_t * buffer,
uint16_t maxReadLength)
{
ChipLogProgress(DeviceLayer, "HandleReadOnOffAttribute: attrId=%d, maxReadLength=%d", attributeId, maxReadLength);
if ((attributeId == OnOff::Attributes::OnOff::Id) && (maxReadLength == 1))
{
*buffer = dev->IsOn() ? 1 : 0;
}
else if ((attributeId == OnOff::Attributes::ClusterRevision::Id) && (maxReadLength == 2))
{
uint16_t rev = ZCL_ON_OFF_CLUSTER_REVISION;
memcpy(buffer, &rev, sizeof(rev));
}
else
{
return Protocols::InteractionModel::Status::Failure;
}
return Protocols::InteractionModel::Status::Success;
}
Protocols::InteractionModel::Status HandleWriteOnOffAttribute(DeviceOnOff * dev, chip::AttributeId attributeId, uint8_t * buffer)
{
ChipLogProgress(DeviceLayer, "HandleWriteOnOffAttribute: attrId=%d", attributeId);
if ((attributeId == OnOff::Attributes::OnOff::Id) && (dev->IsReachable()))
{
if (*buffer)
{
dev->SetOnOff(true);
}
else
{
dev->SetOnOff(false);
}
}
else
{
return Protocols::InteractionModel::Status::Failure;
}
return Protocols::InteractionModel::Status::Success;
}
Protocols::InteractionModel::Status HandleReadTempMeasurementAttribute(DeviceTempSensor * dev, chip::AttributeId attributeId,
uint8_t * buffer, uint16_t maxReadLength)
{
using namespace TemperatureMeasurement::Attributes;
if ((attributeId == MeasuredValue::Id) && (maxReadLength == 2))
{
int16_t measuredValue = dev->GetMeasuredValue();
memcpy(buffer, &measuredValue, sizeof(measuredValue));
}
else if ((attributeId == MinMeasuredValue::Id) && (maxReadLength == 2))
{
int16_t minValue = dev->mMin;
memcpy(buffer, &minValue, sizeof(minValue));
}
else if ((attributeId == MaxMeasuredValue::Id) && (maxReadLength == 2))
{
int16_t maxValue = dev->mMax;
memcpy(buffer, &maxValue, sizeof(maxValue));
}
else if ((attributeId == FeatureMap::Id) && (maxReadLength == 4))
{
uint32_t featureMap = ZCL_TEMPERATURE_SENSOR_FEATURE_MAP;
memcpy(buffer, &featureMap, sizeof(featureMap));
}
else if ((attributeId == ClusterRevision::Id) && (maxReadLength == 2))
{
uint16_t clusterRevision = ZCL_TEMPERATURE_SENSOR_CLUSTER_REVISION;
memcpy(buffer, &clusterRevision, sizeof(clusterRevision));
}
else
{
return Protocols::InteractionModel::Status::Failure;
}
return Protocols::InteractionModel::Status::Success;
}
Protocols::InteractionModel::Status emberAfExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId,
const EmberAfAttributeMetadata * attributeMetadata,
uint8_t * buffer, uint16_t maxReadLength)
{
uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint);
Protocols::InteractionModel::Status ret = Protocols::InteractionModel::Status::Failure;
if ((endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) && (gDevices[endpointIndex] != nullptr))
{
Device * dev = gDevices[endpointIndex];
if (clusterId == BridgedDeviceBasicInformation::Id)
{
ret = HandleReadBridgedDeviceBasicAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength);
}
else if (clusterId == OnOff::Id)
{
ret = HandleReadOnOffAttribute(static_cast<DeviceOnOff *>(dev), attributeMetadata->attributeId, buffer, maxReadLength);
}
else if (clusterId == TemperatureMeasurement::Id)
{
ret = HandleReadTempMeasurementAttribute(static_cast<DeviceTempSensor *>(dev), attributeMetadata->attributeId, buffer,
maxReadLength);
}
}
return ret;
}
class BridgedPowerSourceAttrAccess : public AttributeAccessInterface
{
public:
// Register on all endpoints.
BridgedPowerSourceAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), PowerSource::Id) {}
CHIP_ERROR
Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override
{
uint16_t powerSourceDeviceIndex = CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
if ((gDevices[powerSourceDeviceIndex] != nullptr))
{
DevicePowerSource * dev = static_cast<DevicePowerSource *>(gDevices[powerSourceDeviceIndex]);
if (aPath.mEndpointId != dev->GetEndpointId())
{
return CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint);
}
switch (aPath.mAttributeId)
{
case PowerSource::Attributes::BatChargeLevel::Id:
aEncoder.Encode(dev->GetBatChargeLevel());
break;
case PowerSource::Attributes::Order::Id:
aEncoder.Encode(dev->GetOrder());
break;
case PowerSource::Attributes::Status::Id:
aEncoder.Encode(dev->GetStatus());
break;
case PowerSource::Attributes::Description::Id:
aEncoder.Encode(chip::CharSpan(dev->GetDescription().c_str(), dev->GetDescription().size()));
break;
case PowerSource::Attributes::EndpointList::Id: {
std::vector<chip::EndpointId> & list = dev->GetEndpointList();
DataModel::List<EndpointId> dm_list(chip::Span<chip::EndpointId>(list.data(), list.size()));
aEncoder.Encode(dm_list);
break;
}
case PowerSource::Attributes::ClusterRevision::Id:
aEncoder.Encode(ZCL_POWER_SOURCE_CLUSTER_REVISION);
break;
case PowerSource::Attributes::FeatureMap::Id:
aEncoder.Encode(dev->GetFeatureMap());
break;
case PowerSource::Attributes::BatReplacementNeeded::Id:
aEncoder.Encode(false);
break;
case PowerSource::Attributes::BatReplaceability::Id:
aEncoder.Encode(PowerSource::BatReplaceabilityEnum::kNotReplaceable);
break;
default:
return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
}
}
return CHIP_NO_ERROR;
}
};
BridgedPowerSourceAttrAccess gPowerAttrAccess;
Protocols::InteractionModel::Status emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId,
const EmberAfAttributeMetadata * attributeMetadata,
uint8_t * buffer)
{
uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint);
Protocols::InteractionModel::Status ret = Protocols::InteractionModel::Status::Failure;
// ChipLogProgress(DeviceLayer, "emberAfExternalAttributeWriteCallback: ep=%d", endpoint);
if (endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
{
Device * dev = gDevices[endpointIndex];
if ((dev->IsReachable()) && (clusterId == OnOff::Id))
{
ret = HandleWriteOnOffAttribute(static_cast<DeviceOnOff *>(dev), attributeMetadata->attributeId, buffer);
}
}
return ret;
}
void runOnOffRoomAction(Room * room, bool actionOn, EndpointId endpointId, uint16_t actionID, uint32_t invokeID, bool hasInvokeID)
{
if (hasInvokeID)
{
Actions::Events::StateChanged::Type event{ actionID, invokeID, Actions::ActionStateEnum::kActive };
EventNumber eventNumber;
chip::app::LogEvent(event, endpointId, eventNumber);
}
// Check and run the action for ActionLight1 - ActionLight4
if (room->getName().compare(ActionLight1.GetLocation()) == 0)
{
ActionLight1.SetOnOff(actionOn);
}
if (room->getName().compare(ActionLight2.GetLocation()) == 0)
{
ActionLight2.SetOnOff(actionOn);
}
if (room->getName().compare(ActionLight3.GetLocation()) == 0)
{
ActionLight3.SetOnOff(actionOn);
}
if (room->getName().compare(ActionLight4.GetLocation()) == 0)
{
ActionLight4.SetOnOff(actionOn);
}
if (hasInvokeID)
{
Actions::Events::StateChanged::Type event{ actionID, invokeID, Actions::ActionStateEnum::kInactive };
EventNumber eventNumber;
chip::app::LogEvent(event, endpointId, eventNumber);
}
}
bool emberAfActionsClusterInstantActionCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Actions::Commands::InstantAction::DecodableType & commandData)
{
bool hasInvokeID = false;
uint32_t invokeID = 0;
EndpointId endpointID = commandPath.mEndpointId;
auto & actionID = commandData.actionID;
if (commandData.invokeID.HasValue())
{
hasInvokeID = true;
invokeID = commandData.invokeID.Value();
}
if (actionID == action1.getActionId() && action1.getIsVisible())
{
// Turn On Lights in Room 1
runOnOffRoomAction(&room1, true, endpointID, actionID, invokeID, hasInvokeID);
commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success);
return true;
}
if (actionID == action2.getActionId() && action2.getIsVisible())
{
// Turn On Lights in Room 2
runOnOffRoomAction(&room2, true, endpointID, actionID, invokeID, hasInvokeID);
commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success);
return true;
}
if (actionID == action3.getActionId() && action3.getIsVisible())
{
// Turn Off Lights in Room 1
runOnOffRoomAction(&room1, false, endpointID, actionID, invokeID, hasInvokeID);
commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success);
return true;
}
commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::NotFound);
return true;
}
const EmberAfDeviceType gBridgedOnOffDeviceTypes[] = { { DEVICE_TYPE_LO_ON_OFF_LIGHT, DEVICE_VERSION_DEFAULT },
{ DEVICE_TYPE_BRIDGED_NODE, DEVICE_VERSION_DEFAULT } };
const EmberAfDeviceType gBridgedComposedDeviceTypes[] = { { DEVICE_TYPE_BRIDGED_NODE, DEVICE_VERSION_DEFAULT },
{ DEVICE_TYPE_POWER_SOURCE, DEVICE_VERSION_DEFAULT } };
const EmberAfDeviceType gComposedTempSensorDeviceTypes[] = { { DEVICE_TYPE_TEMP_SENSOR, DEVICE_VERSION_DEFAULT } };
const EmberAfDeviceType gBridgedTempSensorDeviceTypes[] = { { DEVICE_TYPE_TEMP_SENSOR, DEVICE_VERSION_DEFAULT },
{ DEVICE_TYPE_BRIDGED_NODE, DEVICE_VERSION_DEFAULT } };
#define POLL_INTERVAL_MS (100)
uint8_t poll_prescale = 0;
bool kbhit()
{
int byteswaiting;
ioctl(0, FIONREAD, &byteswaiting);
return byteswaiting > 0;
}
const int16_t oneDegree = 100;
void * bridge_polling_thread(void * context)
{
bool light1_added = true;
bool light2_added = false;
while (true)
{
if (kbhit())
{
int ch = getchar();
// Commands used for the actions bridge test plan.
if (ch == '2' && light2_added == false)
{
// TC-BR-2 step 2, Add Light2
AddDeviceEndpoint(&Light2, &bridgedLightEndpoint, Span<const EmberAfDeviceType>(gBridgedOnOffDeviceTypes),
Span<DataVersion>(gLight2DataVersions), 1);
light2_added = true;
}
else if (ch == '4' && light1_added == true)
{
// TC-BR-2 step 4, Remove Light 1
RemoveDeviceEndpoint(&Light1);
light1_added = false;
}
if (ch == '5' && light1_added == false)
{
// TC-BR-2 step 5, Add Light 1 back
AddDeviceEndpoint(&Light1, &bridgedLightEndpoint, Span<const EmberAfDeviceType>(gBridgedOnOffDeviceTypes),
Span<DataVersion>(gLight1DataVersions), 1);
light1_added = true;
}
if (ch == 'b')
{
// TC-BR-3 step 1b, rename lights
if (light1_added)
{
Light1.SetName("Light 1b");
}
if (light2_added)
{
Light2.SetName("Light 2b");
}
}
if (ch == 'c')
{
// TC-BR-3 step 2c, change the state of the lights
if (light1_added)
{
Light1.Toggle();
}
if (light2_added)
{
Light2.Toggle();
}
}
if (ch == 't')
{
// TC-BR-4 step 1g, change the state of the temperature sensors
TempSensor1.SetMeasuredValue(static_cast<int16_t>(TempSensor1.GetMeasuredValue() + oneDegree));
TempSensor2.SetMeasuredValue(static_cast<int16_t>(TempSensor2.GetMeasuredValue() + oneDegree));
ComposedTempSensor1.SetMeasuredValue(static_cast<int16_t>(ComposedTempSensor1.GetMeasuredValue() + oneDegree));
ComposedTempSensor2.SetMeasuredValue(static_cast<int16_t>(ComposedTempSensor2.GetMeasuredValue() + oneDegree));
}
// Commands used for the actions cluster test plan.
if (ch == 'r')
{
// TC-ACT-2.2 step 2c, rename "Room 1"
room1.setName("Room 1 renamed");
ActionLight1.SetLocation(room1.getName());
ActionLight2.SetLocation(room1.getName());
}
if (ch == 'f')
{
// TC-ACT-2.2 step 2f, move "Action Light 3" from "Room 2" to "Room 1"
ActionLight3.SetLocation(room1.getName());
}
if (ch == 'i')
{
// TC-ACT-2.2 step 2i, remove "Room 2" (make it not visible in the endpoint list), do not remove the lights
room2.setIsVisible(false);
}
if (ch == 'l')
{
// TC-ACT-2.2 step 2l, add a new "Zone 3" and add "Action Light 2" to the new zone
room3.setIsVisible(true);
ActionLight2.SetZone("Zone 3");
}
if (ch == 'm')
{
// TC-ACT-2.2 step 3c, rename "Turn on Room 1 lights"
action1.setName("Turn On Room 1");
}
if (ch == 'n')
{
// TC-ACT-2.2 step 3f, remove "Turn on Room 2 lights"
action2.setIsVisible(false);
}
if (ch == 'o')
{
// TC-ACT-2.2 step 3i, add "Turn off Room 1 renamed lights"
action3.setIsVisible(true);
}
// Commands used for the Bridged Device Basic Information test plan
if (ch == 'u')
{
// TC-BRBINFO-2.2 step 2 "Set reachable to false"
TempSensor1.SetReachable(false);
}
if (ch == 'v')
{
// TC-BRBINFO-2.2 step 2 "Set reachable to true"
TempSensor1.SetReachable(true);
}
continue;
}
// Sleep to avoid tight loop reading commands
usleep(POLL_INTERVAL_MS * 1000);
}
return nullptr;
}
void ApplicationInit()
{
// Clear out the device database
memset(gDevices, 0, sizeof(gDevices));
// Setup Mock Devices
Light1.SetReachable(true);
Light2.SetReachable(true);
Light1.SetChangeCallback(&HandleDeviceOnOffStatusChanged);
Light2.SetChangeCallback(&HandleDeviceOnOffStatusChanged);
TempSensor1.SetReachable(true);
TempSensor2.SetReachable(true);
TempSensor1.SetChangeCallback(&HandleDeviceTempSensorStatusChanged);
TempSensor2.SetChangeCallback(&HandleDeviceTempSensorStatusChanged);
// Setup devices for action cluster tests
ActionLight1.SetReachable(true);
ActionLight2.SetReachable(true);
ActionLight3.SetReachable(true);
ActionLight4.SetReachable(true);
ActionLight1.SetChangeCallback(&HandleDeviceOnOffStatusChanged);
ActionLight2.SetChangeCallback(&HandleDeviceOnOffStatusChanged);
ActionLight3.SetChangeCallback(&HandleDeviceOnOffStatusChanged);
ActionLight4.SetChangeCallback(&HandleDeviceOnOffStatusChanged);
// Setup composed device with two temperature sensors and a power source
ComposedDevice ComposedDevice("Composed Device", "Bedroom");
DevicePowerSource ComposedPowerSource("Composed Power Source", "Bedroom", PowerSource::Feature::kBattery);
ComposedDevice.SetReachable(true);
ComposedTempSensor1.SetReachable(true);
ComposedTempSensor2.SetReachable(true);
ComposedPowerSource.SetReachable(true);
ComposedPowerSource.SetBatChargeLevel(58);
ComposedTempSensor1.SetChangeCallback(&HandleDeviceTempSensorStatusChanged);
ComposedTempSensor2.SetChangeCallback(&HandleDeviceTempSensorStatusChanged);
ComposedPowerSource.SetChangeCallback(&HandleDevicePowerSourceStatusChanged);
// Set starting endpoint id where dynamic endpoints will be assigned, which
// will be the next consecutive endpoint id after the last fixed endpoint.
gFirstDynamicEndpointId = static_cast<chip::EndpointId>(
static_cast<int>(emberAfEndpointFromIndex(static_cast<uint16_t>(emberAfFixedEndpointCount() - 1))) + 1);
gCurrentEndpointId = gFirstDynamicEndpointId;
// Disable last fixed endpoint, which is used as a placeholder for all of the
// supported clusters so that ZAP will generated the requisite code.
emberAfEndpointEnableDisable(emberAfEndpointFromIndex(static_cast<uint16_t>(emberAfFixedEndpointCount() - 1)), false);
// Add light 1 -> will be mapped to ZCL endpoints 3
AddDeviceEndpoint(&Light1, &bridgedLightEndpoint, Span<const EmberAfDeviceType>(gBridgedOnOffDeviceTypes),
Span<DataVersion>(gLight1DataVersions), 1);
// Add Temperature Sensor devices --> will be mapped to endpoints 4,5
AddDeviceEndpoint(&TempSensor1, &bridgedTempSensorEndpoint, Span<const EmberAfDeviceType>(gBridgedTempSensorDeviceTypes),
Span<DataVersion>(gTempSensor1DataVersions), 1);
AddDeviceEndpoint(&TempSensor2, &bridgedTempSensorEndpoint, Span<const EmberAfDeviceType>(gBridgedTempSensorDeviceTypes),
Span<DataVersion>(gTempSensor2DataVersions), 1);
// Add composed Device with two temperature sensors and a power source
AddDeviceEndpoint(&ComposedDevice, &bridgedComposedDeviceEndpoint, Span<const EmberAfDeviceType>(gBridgedComposedDeviceTypes),
Span<DataVersion>(gComposedDeviceDataVersions), 1);
AddDeviceEndpoint(&ComposedTempSensor1, &bridgedTempSensorEndpoint,
Span<const EmberAfDeviceType>(gComposedTempSensorDeviceTypes),
Span<DataVersion>(gComposedTempSensor1DataVersions), ComposedDevice.GetEndpointId());
AddDeviceEndpoint(&ComposedTempSensor2, &bridgedTempSensorEndpoint,
Span<const EmberAfDeviceType>(gComposedTempSensorDeviceTypes),
Span<DataVersion>(gComposedTempSensor2DataVersions), ComposedDevice.GetEndpointId());
// Add 4 lights for the Action Clusters tests
AddDeviceEndpoint(&ActionLight1, &bridgedLightEndpoint, Span<const EmberAfDeviceType>(gBridgedOnOffDeviceTypes),
Span<DataVersion>(gActionLight1DataVersions), 1);
AddDeviceEndpoint(&ActionLight2, &bridgedLightEndpoint, Span<const EmberAfDeviceType>(gBridgedOnOffDeviceTypes),
Span<DataVersion>(gActionLight2DataVersions), 1);
AddDeviceEndpoint(&ActionLight3, &bridgedLightEndpoint, Span<const EmberAfDeviceType>(gBridgedOnOffDeviceTypes),
Span<DataVersion>(gActionLight3DataVersions), 1);
AddDeviceEndpoint(&ActionLight4, &bridgedLightEndpoint, Span<const EmberAfDeviceType>(gBridgedOnOffDeviceTypes),
Span<DataVersion>(gActionLight4DataVersions), 1);
// Because the power source is on the same endpoint as the composed device, it needs to be explicitly added
gDevices[CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT] = &ComposedPowerSource;
// This provides power for the composed endpoint
std::vector<chip::EndpointId> endpointList;
endpointList.push_back(ComposedDevice.GetEndpointId());
endpointList.push_back(ComposedTempSensor1.GetEndpointId());
endpointList.push_back(ComposedTempSensor2.GetEndpointId());
ComposedPowerSource.SetEndpointList(endpointList);
ComposedPowerSource.SetEndpointId(ComposedDevice.GetEndpointId());
gRooms.push_back(&room1);
gRooms.push_back(&room2);
gRooms.push_back(&room3);
gActions.push_back(&action1);
gActions.push_back(&action2);
gActions.push_back(&action3);
{
pthread_t poll_thread;
int res = pthread_create(&poll_thread, nullptr, bridge_polling_thread, nullptr);
if (res)
{
printf("Error creating polling thread: %d\n", res);
exit(1);
}
}
registerAttributeAccessOverride(&gPowerAttrAccess);
}
void ApplicationShutdown() {}
int main(int argc, char * argv[])
{
if (ChipLinuxAppInit(argc, argv) != 0)
{
return -1;
}
ChipLinuxAppMainLoop();
return 0;
}