/** @brief Identify Cluster Identify Query Response
 *
 *
 *
 * @param timeout   Ver.: always
 */
boolean emberAfIdentifyClusterIdentifyQueryResponseCallback(int16u timeout) {
  // TODO: !!! IMPORTANT !!! add  handling for several responses
  // now the state machine might be broken as we use only one global variable
  // for storing incoming connection information like Short ID and endpoint
  //
  // ignore broadcasts from yourself and from devices that are not
  // in the identifying state
  const EmberAfClusterCommand *const current_cmd = emberAfCurrentCommand();
  if (emberAfGetNodeId() != current_cmd->source && timeout != 0) {
    emberAfDebugPrintln("DEBUG: Got ID Query response");
    emberAfDebugPrintln("DEBUG: Sender 0x%2X", emberAfCurrentCommand()->source);
    // Queue is empty, so we can start commissioning process
    // otherwise we push it in the queue and will process later
    if (GetQueueSize() == 0) {
      // ID Query received -> go to the discover state for getting clusters info
      emberAfDebugPrintln("DEBUG: QUEUE IS EMPTY");
      SetNextState(SC_EZ_DISCOVER);
      SetNextEvent(SC_EZEV_CHECK_CLUSTERS);
      emberEventControlSetActive(StateMachineEvent);
    }
    // Store information about endpoint and short ID of the incoming response
    // for further processing in the Matching (SC_EZ_MATCH) state
    SetInConnBaseInfo(current_cmd->source,
                      current_cmd->apsFrame->sourceEndpoint);
    emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
  }

  return TRUE;
}
bool emberAfEventsClusterPublishEventLogCallback(uint16_t totalNumberOfEvents,
                                                    uint8_t commandIndex,
                                                    uint8_t totalCommands,
                                                    uint8_t logPayloadControl,
                                                    uint8_t* logPayload)
{
  emberAfEventsClusterPrint("RX: PublishEventLog 0x%2x, 0x%x, 0x%x, 0x%x",
                            totalNumberOfEvents,
                            commandIndex,
                            totalCommands,
                            logPayloadControl);

  #if defined(EMBER_AF_PRINT_ENABLE) && defined(EMBER_AF_PRINT_EVENTS_CLUSTER)
  uint16_t logPayloadLen = (emberAfCurrentCommand()->bufLen
                          - (emberAfCurrentCommand()->payloadStartIndex
                             + sizeof(totalNumberOfEvents)
                             + sizeof(commandIndex)
                             + sizeof(totalCommands)
                             + sizeof(logPayloadControl)));
  uint16_t logPayloadIndex = 0;
  uint8_t i;
  if (NULL != logPayload) {
    for (i = 0; i < numberOfEvents(logPayloadControl); i++) {
      uint8_t logId;
      uint16_t eventId;
      uint32_t eventTime;
      uint8_t *eventData;
      uint8_t eventDataLen;
      logId = emberAfGetInt8u(logPayload, logPayloadIndex, logPayloadLen);
      logPayloadIndex++;
      eventId = emberAfGetInt16u(logPayload, logPayloadIndex, logPayloadLen);
      logPayloadIndex += 2;
      eventTime = emberAfGetInt32u(logPayload, logPayloadIndex, logPayloadLen);
      logPayloadIndex += 4;
      eventData = logPayload + logPayloadIndex;
      eventDataLen = emberAfGetInt8u(logPayload, logPayloadIndex, logPayloadLen);
      logPayloadIndex += (1 + eventDataLen);
      emberAfEventsClusterPrint(" [");
      emberAfEventsClusterPrint("0x%x, 0x%2x, 0x%4x, 0x%x", logId, eventId, eventTime, eventDataLen);
      if (eventDataLen > 0) {
        emberAfEventsClusterPrint(", ");
        emberAfEventsClusterPrintString(eventData);
      }
      emberAfEventsClusterPrint("]");
    }
  }
  emberAfEventsClusterPrintln("");
#endif // defined(EMBER_AF_PRINT_ENABLE) && defined(EMBER_AF_PRINT_EVENTS_CLUSTER)

  emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
  return true;
}
bool emberAfScenesClusterEnhancedViewSceneCallback(uint16_t groupId,
                                                      uint8_t sceneId)
{
  return emberAfPluginScenesServerParseViewScene(emberAfCurrentCommand(),
                                                 groupId,
                                                 sceneId);
}
Beispiel #4
0
bool emberAfSimpleMeteringClusterConfigureMirrorCallback(uint32_t issuerEventId,
                                                            uint32_t reportingInterval,
                                                            uint8_t mirrorNotificationReporting,
                                                            uint8_t notificationScheme)
{
  EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
  uint8_t endpoint = emberAfCurrentEndpoint();
  EmberEUI64 sendersEui;
  uint8_t index;

  emberAfSimpleMeteringClusterPrintln("ConfigureMirror on endpoint 0x%x", endpoint);

  if (EMBER_SUCCESS != emberLookupEui64ByNodeId(emberAfCurrentCommand()->source, sendersEui)) {
    emberAfSimpleMeteringClusterPrintln("Error: Meter Mirror plugin cannot determine EUI64 for node ID 0x%2X",
                                        emberAfCurrentCommand()->source);
    status = EMBER_ZCL_STATUS_FAILURE;
    goto kickout;
  }

  index = findMirrorIndex(sendersEui);
  if (index == INVALID_INDEX) {
    emberAfSimpleMeteringClusterPrint("Error: Meter mirror plugin received unknown report from ");
    emberAfPrintBigEndianEui64(sendersEui);
    emberAfSimpleMeteringClusterPrintln("");
    status = EMBER_ZCL_STATUS_NOT_AUTHORIZED;
    goto kickout;
  }
  
  if (mirrorList[index].issuerEventId == 0
      || issuerEventId > mirrorList[index].issuerEventId) {
    if(notificationScheme > 0x02) {
      status = EMBER_ZCL_STATUS_INVALID_FIELD;
      goto kickout;
    }

    mirrorList[index].issuerEventId = issuerEventId;
    mirrorList[index].reportingInterval = reportingInterval;
    mirrorList[index].mirrorNotificationReporting = mirrorNotificationReporting;
    mirrorList[index].notificationScheme = notificationScheme;
  }

kickout:
  emberAfSendImmediateDefaultResponse(status);
  return true;
}
bool emberAfScenesClusterEnhancedAddSceneResponseCallback(uint8_t status,
                                                             uint16_t groupId,
                                                             uint8_t sceneId)
{
  return emberAfPluginScenesClientParseAddSceneResponse(emberAfCurrentCommand(),
                                                        status,
                                                        groupId,
                                                        sceneId);
}
bool emberAfScenesClusterEnhancedAddSceneCallback(uint16_t groupId,
                                                     uint8_t sceneId,
                                                     uint16_t transitionTime,
                                                     uint8_t *sceneName,
                                                     uint8_t *extensionFieldSets)
{
  return emberAfPluginScenesServerParseAddScene(emberAfCurrentCommand(),
                                                groupId,
                                                sceneId,
                                                transitionTime,
                                                sceneName,
                                                extensionFieldSets);
}
bool emberAfScenesClusterEnhancedViewSceneResponseCallback(uint8_t status,
                                                              uint16_t groupId,
                                                              uint8_t sceneId,
                                                              uint16_t transitionTime,
                                                              uint8_t *sceneName,
                                                              uint8_t *extensionFieldSets)
{
  return emberAfPluginScenesClientParseViewSceneResponse(emberAfCurrentCommand(),
                                                         status,
                                                         groupId,
                                                         sceneId,
                                                         transitionTime,
                                                         sceneName,
                                                         extensionFieldSets);
}
/** @brief Is Protocol Supported
 *
 * This function is called by the Tunneling server plugin whenever a Request
 * Tunnel command is received.  The application should return true if the
 * protocol is supported and false otherwise.
 *
 * @param protocolId The identifier of the metering communication protocol for
 * which the tunnel is requested.  Ver.: always
 * @param manufacturerCode The manufacturer code for manufacturer-defined
 * protocols or 0xFFFF in unused.  Ver.: always
 */
bool emberAfPluginTunnelingServerIsProtocolSupportedCallback(uint8_t protocolId,
                                                                uint16_t manufacturerCode)
{
  EmberEUI64 remoteDeviceId;

  emberAfDebugPrintln("CHF: ServerIsProtocolSupported:0x%x 0x%2x", protocolId, manufacturerCode);

  // Since the tunneling cluster server code does not pass the EUI64 or the
  // node ID of the remote end of the tunnel so we need to look them up.
  // Luckily this callback is called in the context of the RequestTunnel
  // command processing and we look into the command for this info.
  emberLookupEui64ByNodeId(emberAfCurrentCommand()->source, remoteDeviceId);

  return (GBCS_TUNNELING_PROTOCOL_ID == protocolId
          && GBCS_TUNNELING_MANUFACTURER_CODE == manufacturerCode
          && emAfPluginCommsHubFunctionTunnelAcceptCallback(remoteDeviceId));
}
bool emberAfPrepaymentClusterGetPrepaySnapshotCallback( uint32_t earliestStartTime, uint32_t latestEndTime,
                                                           uint8_t snapshotOffset, uint32_t snapshotCause ){

  EmberNodeId nodeId;
  uint8_t srcEndpoint, dstEndpoint;

  emberAfPrepaymentClusterPrintln("RX: GetPrepaySnapshot, st=0x%4x, offset=%d, cause=%d", earliestStartTime, snapshotOffset, snapshotCause );
  nodeId = emberAfCurrentCommand()->source;
  srcEndpoint = emberAfGetCommandApsFrame()->destinationEndpoint;
  dstEndpoint = emberAfGetCommandApsFrame()->sourceEndpoint;
  emberAfPrepaymentClusterPrintln("... from 0x%2x, ep=%d", nodeId, dstEndpoint );

  emberAfPluginPrepaymentServerGetSnapshotCallback( nodeId, srcEndpoint, dstEndpoint, 
                                                    earliestStartTime, latestEndTime, snapshotOffset, snapshotCause );
  return true;

}
Beispiel #10
0
bool emberAfReportAttributesCallback(EmberAfClusterId clusterId,
                                        uint8_t *buffer,
                                        uint16_t bufLen)
{
  EmberEUI64 sendersEui;
  uint16_t bufIndex = 0;
  uint8_t endpoint;
  uint8_t index;
  bool attributeReportingComplete = false;

  if (EMBER_SUCCESS
       != emberLookupEui64ByNodeId(emberAfCurrentCommand()->source, sendersEui)) {
    emberAfSimpleMeteringClusterPrintln("Error: Meter Mirror plugin cannot determine EUI64 for node ID 0x%2X",
                                        emberAfCurrentCommand()->source);
    emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
    return true;
  }

  if (emberAfCurrentCommand()->direction
      == ZCL_DIRECTION_CLIENT_TO_SERVER) {
    emberAfSimpleMeteringClusterPrintln("Error:  Meter Mirror Plugin does not accept client to server attributes.\n",
                                        emberAfCurrentCommand()->direction);
    emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
    return true;
  }
  
  index = findMirrorIndex(sendersEui);
  if (index == INVALID_INDEX) {
    emberAfSimpleMeteringClusterPrint("Error: Meter mirror plugin received unknown report from ");
    emberAfPrintBigEndianEui64(sendersEui);
    emberAfSimpleMeteringClusterPrintln("");
    emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_NOT_AUTHORIZED);
    return true;
  }

  if (emberAfCurrentCommand()->mfgSpecific) {
    // Here is where we could handle a MFG specific Report attributes and interpret 
    // it.  This code does not do that, just politely returns an error.
    emberAfSimpleMeteringClusterPrintln("Error: Unknown MFG Code for mirror: 0x%2X",
                                        emberAfCurrentCommand()->mfgCode);
    emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_UNSUP_MANUF_GENERAL_COMMAND);
    return true;
  }

  endpoint = (index + EMBER_AF_PLUGIN_METER_MIRROR_ENDPOINT_START);
  while (bufIndex + ATTRIBUTE_OVERHEAD < bufLen) {
    EmberAfStatus status;
    EmberAfAttributeId attributeId;
    EmberAfAttributeType dataType;
    uint8_t dataSize;

    attributeId = (EmberAfAttributeId)emberAfGetInt16u(buffer,
                                                       bufIndex,
                                                       bufLen);
    bufIndex += 2;
    dataType = (EmberAfAttributeType)emberAfGetInt8u(buffer, bufIndex, bufLen);
    bufIndex++;

    // For strings, the data size is the length of the string (specified by the
    // first byte of data) plus one for the length byte itself.  For everything
    // else, the size is just the size of the data type.
    dataSize = (emberAfIsThisDataTypeAStringType(dataType)
                ? emberAfStringLength(buffer + bufIndex) + 1
                : emberAfGetDataSize(dataType));

    {
#if (BIGENDIAN_CPU)
      uint8_t data[ATTRIBUTE_LARGEST];
      if (isThisDataTypeSentLittleEndianOTA(dataType)) {
        emberReverseMemCopy(data, buffer + bufIndex, dataSize);
      } else {
        MEMMOVE(data, buffer + bufIndex, dataSize);
      }
#else
      uint8_t *data = buffer + bufIndex;
#endif

      if (attributeId == ZCL_ATTRIBUTE_REPORTING_STATUS_ATTRIBUTE_ID) {
        // From the SE 1.2a Specification within the ConfigureMirror command
        // description..
        // 1.  On powering up, the BOMD will send one or more Report Attribute
        //     commands to the Metering client on the mirror endpoint. The last
        //     attribute to be reported to the mirror shall be an Attribute
        //     Reporting Status attribute, as defined in section A.2.
        // 2.  If MirrorReportAttributeResponse is enabled, the server does not
        //     need to request an APS ACK. If the server requests an APS ACK,
        //     the Metering client on the mirror endpoint shall respond first
        //     with an APS ACK and then send the MirrorReportAttributeResponse.
        //
        // If Mirror Notification Reporting is set to false, the
        // MirrorReportAttributeResponse command shall not be enabled; the
        // Metering server may poll the Notification flags by means of a normal
        // ReadAttribute command, as shown in Figure D 29:
        attributeReportingComplete = (data[0] == EMBER_ZCL_ATTRIBUTE_REPORTING_STATUS_ATTRIBUTE_REPORTING_COMPLETE);
        status = EMBER_ZCL_STATUS_SUCCESS;
      } else {
        if (attributeId == ZCL_METERING_DEVICE_TYPE_ATTRIBUTE_ID
            && data[0] < EMBER_ZCL_METERING_DEVICE_TYPE_MIRRORED_ELECTRIC_METERING) {
          data[0] += 127;
        }

        status = emberAfWriteServerAttribute(endpoint,
                                             clusterId,
                                             attributeId,
                                             data,
                                             dataType);
      }
    }

    emberAfSimpleMeteringClusterPrintln("Mirror attribute 0x%2x: 0x%x", attributeId, status);
    bufIndex += dataSize;
  }

  // If reporting is complete then callback to the application so that if it needs
  // to do any post processing on the reported attributes it can do it now.
  if (attributeReportingComplete) {
    emberAfPluginMeterMirrorReportingCompleteCallback(endpoint);
  }

  // Notification flags
  emberAfSimpleMeteringClusterPrintln("Mirror reporting ep: 0x%x, reporting: 0x%x, scheme: 0x%x",
                                      endpoint,
                                      mirrorList[index].mirrorNotificationReporting,
                                      mirrorList[index].notificationScheme);
  if (mirrorList[index].mirrorNotificationReporting  && attributeReportingComplete) {
    if (mirrorList[index].notificationScheme == EMBER_ZCL_NOTIFICATION_SCHEME_PREDEFINED_NOTIFICATION_SCHEME_A
        || mirrorList[index].notificationScheme == EMBER_ZCL_NOTIFICATION_SCHEME_PREDEFINED_NOTIFICATION_SCHEME_B) {
      return sendMirrorReportAttributeResponse(endpoint, index);
    } else {
      // TODO: for custom notification schemes callback to application
      // return emberAfMeterMirrorSendMirrorReportAttributeResponseCallback(...)
    }
  }

  emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
  return true;
}
/** @brief Tunnel Opened
 *
 * This function is called by the Tunneling server plugin whenever a tunnel is
 * opened.  Clients may open tunnels by sending a Request Tunnel command.
 *
 * @param tunnelId The identifier of the tunnel that has been opened.  Ver.:
 * always
 * @param protocolId The identifier of the metering communication protocol for
 * the tunnel.  Ver.: always
 * @param manufacturerCode The manufacturer code for manufacturer-defined
 * protocols or 0xFFFF in unused.  Ver.: always
 * @param flowControlSupport true is flow control support is requested or false
 * if it is not.  Ver.: always
 * @param maximumIncomingTransferSize The maximum incoming transfer size of the
 * client.  Ver.: always
 */
void emberAfPluginTunnelingServerTunnelOpenedCallback(uint16_t tunnelId,
                                                      uint8_t protocolId,
                                                      uint16_t manufacturerCode,
                                                      bool flowControlSupport,
                                                      uint16_t maximumIncomingTransferSize)
{
  EmberEUI64 remoteDeviceId;
  EmberNodeId remoteNodeId;
  uint8_t tunnelIndex;

  emberAfDebugPrintln("CHF: ServerTunnelOpened:0x%x,0x%2x", tunnelId, maximumIncomingTransferSize);

  // Since the tunneling cluster server code does not pass the EUI64 or the
  // node ID of the remote end of the tunnel so we need to look them up.
  // Luckily this callback is called in the context of the RequestTunnel
  // command processing and we look into the command for this info.
  remoteNodeId = emberAfCurrentCommand()->source;
  emberLookupEui64ByNodeId(emberAfCurrentCommand()->source, remoteDeviceId);

  // We only support one tunnel to a given remote device/endpoint so if we
  // already have a tunnel lets work with it.
  tunnelIndex = findTunnelByDeviceId(remoteDeviceId);
  if (tunnelIndex == EM_AF_PLUGIN_COMMS_HUB_FUNCTION_NULL_TUNNEL_INDEX) {
    // Find a slot in the tunnels table for the new tunnel
    tunnelIndex = findUnusedTunnel();
  }

  if (tunnelIndex != EM_AF_PLUGIN_COMMS_HUB_FUNCTION_NULL_TUNNEL_INDEX) {
    MEMCOPY(tunnels[tunnelIndex].remoteDeviceId, remoteDeviceId, EUI64_SIZE);
    tunnels[tunnelIndex].remoteNodeId = remoteNodeId;
    tunnels[tunnelIndex].remoteEndpoint = emberAfCurrentCommand()->apsFrame->sourceEndpoint;
    tunnels[tunnelIndex].type = SERVER_TUNNEL;
    tunnels[tunnelIndex].state = ACTIVE_TUNNEL;
    tunnels[tunnelIndex].tunnelId = tunnelId;
    tunnels[tunnelIndex].timeoutMSec = 0;

    // Per GBCS v0.8 section 10.2.2, Devices supporting the Tunneling Cluster
    // as a Server shall have a MaximumIncomingTransferSize set to 1500 octets,
    // in line with the ZSE default.  All Devices supporting the Tunneling
    // Cluster shall use this value in any RequestTunnelResponse command and
    // any RequestTunnel command.
    //
    // So rather than bring down the tunnel in the case when the maximumIncomingTransferSize
    // is less than 1500 we'll just log a warning message.
    if (maximumIncomingTransferSize < 1500) {
      emberAfPluginCommsHubFunctionPrintln("Warning: tunnel opened but MaximumIncomingTransferSize of client is %d but should be 1500",
                                           maximumIncomingTransferSize);
    }
    return;
  }

  // This is a misconfiguration or a bug in the code calling this API. Either
  // the tunnel client plugin limit is set too low for the number of tunnels
  // required or the code that is calling this function is in error.  Either way,
  // we'll print the error and return false indicating that the tunnel was
  // not created.
  emberAfPluginCommsHubFunctionPrintln("%p%p%p",
                                       "Error: ",
                                       "Tunnel Opened failed: ",
                                       "Too many tunnels");
}
bool emberAfPrepaymentClusterChangePaymentModeCallback(uint32_t providerId,
                                                          uint32_t issuerEventId,
                                                          uint32_t implementationDateTime,
                                                          PaymentControlConfiguration proposedPaymentControlConfiguration,
                                                          uint32_t cutOffValue){

  // The requester can be obtained with emberAfResponseDestination;
  EmberNodeId nodeId;
  uint8_t endpoint;
  uint8_t srcEndpoint, dstEndpoint;
  FriendlyCredit friendlyCredit;
  uint32_t friendlyCreditCalendarId;
  uint32_t emergencyCreditLimit;
  uint32_t emergencyCreditThreshold;
  uint8_t  dataType;
  uint8_t  i;

  emberAfPrepaymentClusterPrintln("RX: ChangePaymentMode, pid=0x%4x, eid=0x%4x, cfg=0x%2x", providerId, issuerEventId, proposedPaymentControlConfiguration);
  endpoint = emberAfCurrentEndpoint();

  if( cutOffValue != CUTOFF_UNCHANGED ){
#ifdef ZCL_USING_PREPAYMENT_CLUSTER_CUT_OFF_VALUE_ATTRIBUTE
    emberAfWriteAttribute( endpoint, ZCL_PREPAYMENT_CLUSTER_ID,
                          ZCL_CUT_OFF_VALUE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
                          (uint8_t *)&cutOffValue, ZCL_INT32S_ATTRIBUTE_TYPE );
#endif
  }

  emberAfPrepaymentSchedulePrepaymentMode( emberAfCurrentEndpoint(), providerId, issuerEventId, implementationDateTime,
                                           proposedPaymentControlConfiguration );

  // Setup the friendly credit & emergency credit limit attributes.
#ifdef EMBER_AF_PLUGIN_CALENDAR_CLIENT
  i = emberAfPluginCalendarClientGetCalendarIndexByType( endpoint, EMBER_ZCL_CALENDAR_TYPE_FRIENDLY_CREDIT_CALENDAR );
  friendlyCredit = ( i < EMBER_AF_PLUGIN_CALENDAR_CLIENT_CALENDARS ) ? 0x01 : 0x00;
  friendlyCreditCalendarId = emberAfPluginCalendarClientGetCalendarId( endpoint, i );
#else
  friendlyCredit = 0x00;
  friendlyCreditCalendarId = EMBER_AF_PLUGIN_CALENDAR_CLIENT_INVALID_CALENDAR_ID;
#endif

#if (!defined ZCL_USING_PREPAYMENT_CLUSTER_EMERGENCY_CREDIT_LIMIT_ALLOWANCE_ATTRIBUTE) || \
    (!defined ZCL_USING_PREPAYMENT_CLUSTER_EMERGENCY_CREDIT_THRESHOLD_ATTRIBUTE)
#error "Prepayment Emergency Credit Limit/Allowance and Threshold attributes required for this plugin!"
#endif
  emberAfReadAttribute( emberAfCurrentEndpoint(), ZCL_PREPAYMENT_CLUSTER_ID,
                        ZCL_EMERGENCY_CREDIT_LIMIT_ALLOWANCE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
                        (uint8_t *)&emergencyCreditLimit, 4, &dataType );
  emberAfReadAttribute( emberAfCurrentEndpoint(), ZCL_PREPAYMENT_CLUSTER_ID,
                        ZCL_EMERGENCY_CREDIT_THRESHOLD_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
                        (uint8_t *)&emergencyCreditThreshold, 4, &dataType );
  nodeId = emberAfCurrentCommand()->source;
  srcEndpoint = emberAfGetCommandApsFrame()->destinationEndpoint;
  dstEndpoint = emberAfGetCommandApsFrame()->sourceEndpoint;
  emberAfSetCommandEndpoints( srcEndpoint, dstEndpoint );
  
  emberAfFillCommandPrepaymentClusterChangePaymentModeResponse( friendlyCredit, friendlyCreditCalendarId, 
                                                                 emergencyCreditLimit, emergencyCreditThreshold );
  emberAfSendCommandUnicast( EMBER_OUTGOING_DIRECT, nodeId );
  return true;
}
bool emberAfScenesClusterCopySceneCallback(uint8_t mode,
                                              uint16_t groupIdFrom,
                                              uint8_t sceneIdFrom,
                                              uint16_t groupIdTo,
                                              uint8_t sceneIdTo)
{
  EmberAfStatus status = EMBER_ZCL_STATUS_INVALID_FIELD;
  bool copyAllScenes = (mode & ZCL_SCENES_CLUSTER_MODE_COPY_ALL_SCENES_MASK);
  uint8_t i;

  emberAfScenesClusterPrintln("RX: CopyScene 0x%x, 0x%2x, 0x%x, 0x%2x, 0x%x",
                              mode,
                              groupIdFrom,
                              sceneIdFrom,
                              groupIdTo,
                              sceneIdTo);

  // If a group id is specified but this endpoint isn't in it, take no action.
  if ((groupIdFrom != ZCL_SCENES_GLOBAL_SCENE_GROUP_ID
       && !emberAfGroupsClusterEndpointInGroupCallback(emberAfCurrentEndpoint(),
                                                       groupIdFrom))
      || (groupIdTo != ZCL_SCENES_GLOBAL_SCENE_GROUP_ID
          && !emberAfGroupsClusterEndpointInGroupCallback(emberAfCurrentEndpoint(),
                                                          groupIdTo))) {
    status = EMBER_ZCL_STATUS_INVALID_FIELD;
    goto kickout;
  }

  for (i = 0; i < EMBER_AF_PLUGIN_SCENES_TABLE_SIZE; i++) {
    EmberAfSceneTableEntry from;
    emberAfPluginScenesServerRetrieveSceneEntry(from, i);
    if (from.endpoint == emberAfCurrentEndpoint()
        && from.groupId == groupIdFrom
        && (copyAllScenes || from.sceneId == sceneIdFrom)) {
      uint8_t j, index = EMBER_AF_SCENE_TABLE_NULL_INDEX;
      for (j = 0; j < EMBER_AF_PLUGIN_SCENES_TABLE_SIZE; j++) {
        EmberAfSceneTableEntry to;
        if (i == j) {
          continue;
        }
        emberAfPluginScenesServerRetrieveSceneEntry(to, j);
        if (to.endpoint == emberAfCurrentEndpoint()
            && to.groupId == groupIdTo
            && to.sceneId == (copyAllScenes ? from.sceneId : sceneIdTo)) {
          index = j;
          break;
        } else if (index == EMBER_AF_SCENE_TABLE_NULL_INDEX
                   && to.endpoint == EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID) {
          index = j;
        }
      }

      // If the target index is still zero, the table is full.
      if (index == EMBER_AF_SCENE_TABLE_NULL_INDEX) {
        status = EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
        goto kickout;
      }

      // Save the "from" entry to the "to" index.  This makes a copy of "from"
      // with the correct group and scene ids and leaves the original in tact.
      from.groupId = groupIdTo;
      if (!copyAllScenes) {
        from.sceneId = sceneIdTo;
      }
      emberAfPluginScenesServerSaveSceneEntry(from, index);

      if (j != index) {
        emberAfPluginScenesServerIncrNumSceneEntriesInUse();
        emberAfScenesSetSceneCountAttribute(emberAfCurrentEndpoint(),
                                            emberAfPluginScenesServerNumSceneEntriesInUse());
     }

      // If we aren't copying all scenes, we can stop here.
      status = EMBER_ZCL_STATUS_SUCCESS;
      if (!copyAllScenes) {
        goto kickout;
      }
    }
  }

kickout:
  // Copy Scene commands are only responded to when they are addressed to a
  // single device.
  if (emberAfCurrentCommand()->type == EMBER_INCOMING_UNICAST
      || emberAfCurrentCommand()->type == EMBER_INCOMING_UNICAST_REPLY) {
    emberAfFillCommandScenesClusterCopySceneResponse(status,
                                                     groupIdFrom,
                                                     sceneIdFrom);
    emberAfSendResponse();
  }
  return true;
}