void EnoceanDeviceContainer::handleRadioPacket(Esp3PacketPtr aEsp3PacketPtr, ErrorPtr aError)
{
  if (aError) {
    LOG(LOG_INFO, "Radio packet error: %s\n", aError->description().c_str());
    return;
  }
  // check learning mode
  if (learningMode) {
    // no learn/unlearn actions detected so far
    // - check if we know that device address already. If so, it is a learn-out
    bool learnIn = enoceanDevices.find(aEsp3PacketPtr->radioSender())==enoceanDevices.end();
    // now add/remove the device (if the action is a valid learn/unlearn)
    // detect implicit (RPS) learn in only with sufficient radio strength (or explicit override of that check),
    // explicit ones are always recognized
    if (aEsp3PacketPtr->eepHasTeachInfo(disableProximityCheck ? 0 : MIN_LEARN_DBM, false)) {
      LOG(LOG_NOTICE, "Received EnOcean learn packet while learn mode enabled: %s\n", aEsp3PacketPtr->description().c_str());
      // This is actually a valid learn action
      ErrorPtr learnStatus;
      if (learnIn) {
        // new device learned in, add logical devices for it
        int numNewDevices = EnoceanDevice::createDevicesFromEEP(this, aEsp3PacketPtr->radioSender(), aEsp3PacketPtr->eepProfile(), aEsp3PacketPtr->eepManufacturer());
        if (numNewDevices>0) {
          // successfully learned at least one device
          // - update learn status (device learned)
          getDeviceContainer().reportLearnEvent(true, ErrorPtr());
        }
      }
      else {
        // device learned out, un-pair all logical dS devices it has represented
        // but keep dS level config in case it is reconnected
        unpairDevicesByAddress(aEsp3PacketPtr->radioSender(), false);
        getDeviceContainer().reportLearnEvent(false, ErrorPtr());
      }
      // - only allow one learn action (to prevent learning out device when
      //   button is released or other repetition of radio packet)
      learningMode = false;
    } // learn action
  }
  else {
    // not learning mode, dispatch packet to all devices known for that address
    for (EnoceanDeviceMap::iterator pos = enoceanDevices.lower_bound(aEsp3PacketPtr->radioSender()); pos!=enoceanDevices.upper_bound(aEsp3PacketPtr->radioSender()); ++pos) {
      if (aEsp3PacketPtr->eepHasTeachInfo(MIN_LEARN_DBM, false) && aEsp3PacketPtr->eepRorg()!=rorg_RPS) {
        // learning packet in non-learn mode -> report as non-regular user action, may be attempt to identify a device
        // Note: RPS devices are excluded because for these all telegrams are regular regular user actions. signalDeviceUserAction() will be called
        //   from button
        if (getDeviceContainer().signalDeviceUserAction(*(pos->second), false)) {
          // consumed for device identification purposes, suppress further processing
          break;
        }
      }
      // handle regularily (might be RPS switch which does not have separate learn/action packets
      pos->second->handleRadioPacket(aEsp3PacketPtr);
    }
  }
}
void EnoceanDeviceContainer::handleTestRadioPacket(CompletedCB aCompletedCB, Esp3PacketPtr aEsp3PacketPtr, ErrorPtr aError)
{
  // ignore packets with error
  if (Error::isOK(aError)) {
    if (aEsp3PacketPtr->eepRorg()==rorg_RPS && aEsp3PacketPtr->radioDBm()>MIN_LEARN_DBM && enoceanComm.modemAppVersion()>0) {
      // uninstall handler
      enoceanComm.setRadioPacketHandler(NULL);
      // seen both watchdog response (modem works) and independent RPS telegram (RF is ok)
      LOG(LOG_NOTICE, "- enocean modem info: appVersion=0x%08X, apiVersion=0x%08X, modemAddress=0x%08X\n", enoceanComm.modemAppVersion(), enoceanComm.modemApiVersion(), enoceanComm.modemAddress());
      aCompletedCB(ErrorPtr());
      // done
      return;
    }
  }
  // - still waiting
  LOG(LOG_NOTICE, "- enocean test: still waiting for RPS telegram in learn distance\n");
}