blob: 7784f79bc5ba1ef845a830269c6826438741b907 [file] [log] [blame]
/*
*
* 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 <cstdarg>
#include <memory>
#include <type_traits>
#include <app/BufferedReadCallback.h>
#include <app/DeviceProxy.h>
#include <app/ReadClient.h>
#include <app/WriteClient.h>
#include <lib/support/CodeUtils.h>
#include <cstdio>
#include <lib/support/logging/CHIPLogging.h>
using namespace chip;
using namespace chip::app;
using PyObject = void;
namespace chip {
namespace python {
struct __attribute__((packed)) AttributePath
{
chip::EndpointId endpointId;
chip::ClusterId clusterId;
chip::AttributeId attributeId;
};
struct __attribute__((packed)) EventPath
{
chip::EndpointId endpointId;
chip::ClusterId clusterId;
chip::EventId eventId;
};
using OnReadAttributeDataCallback = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId,
chip::AttributeId attributeId,
std::underlying_type_t<Protocols::InteractionModel::Status> imstatus, uint8_t * data,
uint32_t dataLen);
using OnReadEventDataCallback = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId,
chip::EventId eventId, chip::EventNumber eventNumber, uint8_t priority, uint64_t timestamp,
uint8_t timestampType, uint8_t * data, uint32_t dataLen);
using OnSubscriptionEstablishedCallback = void (*)(PyObject * appContext, uint64_t subscriptionId);
using OnReadErrorCallback = void (*)(PyObject * appContext, uint32_t chiperror);
using OnReadDoneCallback = void (*)(PyObject * appContext);
using OnReportBeginCallback = void (*)(PyObject * appContext);
using OnReportEndCallback = void (*)(PyObject * appContext);
OnReadAttributeDataCallback gOnReadAttributeDataCallback = nullptr;
OnReadEventDataCallback gOnReadEventDataCallback = nullptr;
OnSubscriptionEstablishedCallback gOnSubscriptionEstablishedCallback = nullptr;
OnReadErrorCallback gOnReadErrorCallback = nullptr;
OnReadDoneCallback gOnReadDoneCallback = nullptr;
OnReportBeginCallback gOnReportBeginCallback = nullptr;
OnReportBeginCallback gOnReportEndCallback = nullptr;
class ReadClientCallback : public ReadClient::Callback
{
public:
ReadClientCallback(PyObject * appContext) : mBufferedReadCallback(*this), mAppContext(appContext) {}
app::BufferedReadCallback * GetBufferedReadCallback() { return &mBufferedReadCallback; }
void OnAttributeData(const ReadClient * apReadClient, const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
const StatusIB & aStatus) override
{
//
// We shouldn't be getting list item operations in the provided path since that should be handled by the buffered read
// callback. If we do, that's a bug.
//
VerifyOrDie(!aPath.IsListItemOperation());
size_t bufferLen = (apData == nullptr ? 0 : apData->GetRemainingLength() + apData->GetLengthRead());
std::unique_ptr<uint8_t[]> buffer = std::unique_ptr<uint8_t[]>(apData == nullptr ? nullptr : new uint8_t[bufferLen]);
uint32_t size = 0;
// When the apData is nullptr, means we did not receive a valid attribute data from server, status will be some error
// status.
if (apData != nullptr)
{
// The TLVReader's read head is not pointing to the first element in the container instead of the container itself, use
// a TLVWriter to get a TLV with a normalized TLV buffer (Wrapped with a anonymous tag, no extra "end of container" tag
// at the end.)
TLV::TLVWriter writer;
writer.Init(buffer.get(), bufferLen);
CHIP_ERROR err = writer.CopyElement(TLV::AnonymousTag(), *apData);
if (err != CHIP_NO_ERROR)
{
app::StatusIB status;
status.mStatus = Protocols::InteractionModel::Status::Failure;
this->OnError(apReadClient, err);
return;
}
size = writer.GetLengthWritten();
}
gOnReadAttributeDataCallback(mAppContext, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId,
to_underlying(aStatus.mStatus), buffer.get(), size);
}
void OnSubscriptionEstablished(const ReadClient * apReadClient) override
{
gOnSubscriptionEstablishedCallback(mAppContext, apReadClient->GetSubscriptionId().ValueOr(0));
}
void OnEventData(const ReadClient * apReadClient, const EventHeader & aEventHeader, TLV::TLVReader * apData,
const StatusIB * apStatus) override
{
uint8_t buffer[CHIP_CONFIG_DEFAULT_UDP_MTU_SIZE];
uint32_t size = 0;
CHIP_ERROR err = CHIP_NO_ERROR;
// When the apData is nullptr, means we did not receive a valid event data from server, status will be some error
// status.
if (apData != nullptr)
{
// The TLVReader's read head is not pointing to the first element in the container instead of the container itself, use
// a TLVWriter to get a TLV with a normalized TLV buffer (Wrapped with a anonymous tag, no extra "end of container" tag
// at the end.)
TLV::TLVWriter writer;
writer.Init(buffer);
err = writer.CopyElement(TLV::AnonymousTag(), *apData);
if (err != CHIP_NO_ERROR)
{
this->OnError(apReadClient, err);
return;
}
size = writer.GetLengthWritten();
}
else
{
err = CHIP_ERROR_INCORRECT_STATE;
this->OnError(apReadClient, err);
}
gOnReadEventDataCallback(mAppContext, aEventHeader.mPath.mEndpointId, aEventHeader.mPath.mClusterId,
aEventHeader.mPath.mEventId, aEventHeader.mEventNumber, to_underlying(aEventHeader.mPriorityLevel),
aEventHeader.mTimestamp.mValue, to_underlying(aEventHeader.mTimestamp.mType), buffer, size);
}
void OnError(const ReadClient * apReadClient, CHIP_ERROR aError) override
{
gOnReadErrorCallback(mAppContext, aError.AsInteger());
}
void OnReportBegin(const ReadClient * apReadClient) override { gOnReportBeginCallback(mAppContext); }
void OnReportEnd(const ReadClient * apReadClient) override { gOnReportEndCallback(mAppContext); }
void OnDone(ReadClient * apReadClient) override
{
gOnReadDoneCallback(mAppContext);
delete apReadClient;
delete this;
};
private:
BufferedReadCallback mBufferedReadCallback;
PyObject * mAppContext;
};
extern "C" {
struct __attribute__((packed)) PyReadAttributeParams
{
uint32_t minInterval; // MinInterval in subscription request
uint32_t maxInterval; // MaxInterval in subscription request
bool isSubscription;
bool isFabricFiltered;
};
// Encodes n attribute write requests, follows 3 * n arguments, in the (AttributeWritePath*=void *, uint8_t*, size_t) order.
chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device,
uint16_t timedWriteTimeoutMs, size_t n, ...);
chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, ReadClient ** pReadClient,
ReadClientCallback ** pCallback, DeviceProxy * device,
uint8_t * readParamsBuf, size_t n, ...);
}
using OnWriteResponseCallback = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId,
chip::AttributeId attributeId,
std::underlying_type_t<Protocols::InteractionModel::Status> imstatus);
using OnWriteErrorCallback = void (*)(PyObject * appContext, uint32_t chiperror);
using OnWriteDoneCallback = void (*)(PyObject * appContext);
OnWriteResponseCallback gOnWriteResponseCallback = nullptr;
OnWriteErrorCallback gOnWriteErrorCallback = nullptr;
OnWriteDoneCallback gOnWriteDoneCallback = nullptr;
class WriteClientCallback : public WriteClient::Callback
{
public:
WriteClientCallback(PyObject * appContext) : mAppContext(appContext) {}
void OnResponse(const WriteClient * apWriteClient, const ConcreteAttributePath & aPath, app::StatusIB aStatus) override
{
gOnWriteResponseCallback(mAppContext, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId,
to_underlying(aStatus.mStatus));
}
void OnError(const WriteClient * apWriteClient, CHIP_ERROR aProtocolError) override
{
gOnWriteErrorCallback(mAppContext, aProtocolError.AsInteger());
}
void OnDone(WriteClient * apWriteClient) override
{
gOnWriteDoneCallback(mAppContext);
delete apWriteClient;
delete this;
};
private:
PyObject * mAppContext = nullptr;
};
} // namespace python
} // namespace chip
using namespace chip::python;
extern "C" {
void pychip_WriteClient_InitCallbacks(OnWriteResponseCallback onWriteResponseCallback, OnWriteErrorCallback onWriteErrorCallback,
OnWriteDoneCallback onWriteDoneCallback)
{
gOnWriteResponseCallback = onWriteResponseCallback;
gOnWriteErrorCallback = onWriteErrorCallback;
gOnWriteDoneCallback = onWriteDoneCallback;
}
void pychip_ReadClient_InitCallbacks(OnReadAttributeDataCallback onReadAttributeDataCallback,
OnReadEventDataCallback onReadEventDataCallback,
OnSubscriptionEstablishedCallback onSubscriptionEstablishedCallback,
OnReadErrorCallback onReadErrorCallback, OnReadDoneCallback onReadDoneCallback,
OnReportBeginCallback onReportBeginCallback, OnReportEndCallback onReportEndCallback)
{
gOnReadAttributeDataCallback = onReadAttributeDataCallback;
gOnReadEventDataCallback = onReadEventDataCallback;
gOnSubscriptionEstablishedCallback = onSubscriptionEstablishedCallback;
gOnReadErrorCallback = onReadErrorCallback;
gOnReadDoneCallback = onReadDoneCallback;
gOnReportBeginCallback = onReportBeginCallback;
gOnReportEndCallback = onReportEndCallback;
}
chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device,
uint16_t timedWriteTimeoutMs, size_t n, ...)
{
CHIP_ERROR err = CHIP_NO_ERROR;
std::unique_ptr<WriteClientCallback> callback = std::make_unique<WriteClientCallback>(appContext);
std::unique_ptr<WriteClient> client = std::make_unique<WriteClient>(
app::InteractionModelEngine::GetInstance()->GetExchangeManager(), callback.get(),
timedWriteTimeoutMs != 0 ? Optional<uint16_t>(timedWriteTimeoutMs) : Optional<uint16_t>::Missing());
va_list args;
va_start(args, n);
VerifyOrExit(device != nullptr && device->GetSecureSession().HasValue(), err = CHIP_ERROR_INCORRECT_STATE);
{
for (size_t i = 0; i < n; i++)
{
void * path = va_arg(args, void *);
void * tlv = va_arg(args, void *);
int length = va_arg(args, int);
python::AttributePath pathObj;
memcpy(&pathObj, path, sizeof(python::AttributePath));
uint8_t * tlvBuffer = reinterpret_cast<uint8_t *>(tlv);
TLV::TLVWriter * writer;
TLV::TLVReader reader;
SuccessOrExit(err = client->PrepareAttribute(
chip::app::AttributePathParams(pathObj.endpointId, pathObj.clusterId, pathObj.attributeId)));
VerifyOrExit((writer = client->GetAttributeDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
reader.Init(tlvBuffer, static_cast<uint32_t>(length));
reader.Next();
SuccessOrExit(
err = writer->CopyElement(chip::TLV::ContextTag(to_underlying(chip::app::AttributeDataIB::Tag::kData)), reader));
SuccessOrExit(err = client->FinishAttribute());
}
}
SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value()));
client.release();
callback.release();
exit:
va_end(args);
return err.AsInteger();
}
void pychip_ReadClient_Abort(ReadClient * apReadClient, ReadClientCallback * apCallback)
{
VerifyOrDie(apReadClient != nullptr);
VerifyOrDie(apCallback != nullptr);
delete apReadClient;
delete apCallback;
}
chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, ReadClient ** pReadClient,
ReadClientCallback ** pCallback, DeviceProxy * device,
uint8_t * readParamsBuf, size_t n, ...)
{
CHIP_ERROR err = CHIP_NO_ERROR;
PyReadAttributeParams pyParams = {};
// The readParamsBuf might be not aligned, using a memcpy to avoid some unexpected behaviors.
memcpy(&pyParams, readParamsBuf, sizeof(pyParams));
std::unique_ptr<ReadClientCallback> callback = std::make_unique<ReadClientCallback>(appContext);
va_list args;
va_start(args, n);
std::unique_ptr<AttributePathParams[]> readPaths(new AttributePathParams[n]);
std::unique_ptr<ReadClient> readClient;
{
for (size_t i = 0; i < n; i++)
{
void * path = va_arg(args, void *);
python::AttributePath pathObj;
memcpy(&pathObj, path, sizeof(python::AttributePath));
readPaths[i] = AttributePathParams(pathObj.endpointId, pathObj.clusterId, pathObj.attributeId);
}
}
Optional<SessionHandle> session = device->GetSecureSession();
VerifyOrExit(session.HasValue(), err = CHIP_ERROR_NOT_CONNECTED);
readClient = std::make_unique<ReadClient>(
InteractionModelEngine::GetInstance(), device->GetExchangeManager(), *callback->GetBufferedReadCallback(),
pyParams.isSubscription ? ReadClient::InteractionType::Subscribe : ReadClient::InteractionType::Read);
{
ReadPrepareParams params(session.Value());
params.mpAttributePathParamsList = readPaths.get();
params.mAttributePathParamsListSize = n;
if (pyParams.isSubscription)
{
params.mMinIntervalFloorSeconds = pyParams.minInterval;
params.mMaxIntervalCeilingSeconds = pyParams.maxInterval;
}
params.mIsFabricFiltered = pyParams.isFabricFiltered;
err = readClient->SendRequest(params);
SuccessOrExit(err);
}
*pReadClient = readClient.get();
*pCallback = callback.get();
callback.release();
readClient.release();
exit:
va_end(args);
return err.AsInteger();
}
chip::ChipError::StorageType pychip_ReadClient_ReadEvents(void * appContext, DeviceProxy * device, uint8_t * readParamsBuf,
size_t n, ...)
{
CHIP_ERROR err = CHIP_NO_ERROR;
PyReadAttributeParams pyParams = {};
memcpy(&pyParams, readParamsBuf, sizeof(pyParams));
std::unique_ptr<ReadClientCallback> callback = std::make_unique<ReadClientCallback>(appContext);
va_list args;
va_start(args, n);
std::unique_ptr<EventPathParams[]> readPaths(new EventPathParams[n]);
std::unique_ptr<ReadClient> readClient;
{
for (size_t i = 0; i < n; i++)
{
void * path = va_arg(args, void *);
python::EventPath pathObj;
memcpy(&pathObj, path, sizeof(python::EventPath));
readPaths[i] = EventPathParams(pathObj.endpointId, pathObj.clusterId, pathObj.eventId);
}
}
Optional<SessionHandle> session = device->GetSecureSession();
VerifyOrExit(session.HasValue(), err = CHIP_ERROR_NOT_CONNECTED);
readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(), device->GetExchangeManager(), *callback.get(),
pyParams.isSubscription ? ReadClient::InteractionType::Subscribe
: ReadClient::InteractionType::Read);
{
ReadPrepareParams params(session.Value());
params.mpEventPathParamsList = readPaths.get();
params.mEventPathParamsListSize = n;
if (pyParams.isSubscription)
{
params.mMinIntervalFloorSeconds = pyParams.minInterval;
params.mMaxIntervalCeilingSeconds = pyParams.maxInterval;
}
err = readClient->SendRequest(params);
SuccessOrExit(err);
}
callback.release();
readClient.release();
exit:
va_end(args);
return err.AsInteger();
}
}