@Override
    public void frameReceived(final FrameEvent e) {
      try {
        // with EMI1 frames, there is the possibility we receive some left-over Get-Value responses
        // from BCU switching during link setup, silently discard them
        final byte[] emi1 = e.getFrameBytes();
        if (emi1 != null && BcuSwitcher.isEmi1GetValue(emi1[0] & 0xff)) return;

        final CEMI cemi = onReceive(e);
        if (cemi instanceof CEMIDevMgmt) {
          // XXX check .con correctly (required for setting cEMI link layer mode)
          final int mc = cemi.getMessageCode();
          if (mc == CEMIDevMgmt.MC_PROPWRITE_CON) {
            final CEMIDevMgmt f = (CEMIDevMgmt) cemi;
            if (f.isNegativeResponse())
              logger.error("L-DM negative response, " + f.getErrorMessage());
          }
        }
        // from this point on, we are only dealing with L_Data
        if (!(cemi instanceof CEMILData)) return;
        final CEMILData f = (CEMILData) cemi;
        final int mc = f.getMessageCode();
        if (mc == CEMILData.MC_LDATA_IND) {
          addEvent(l -> l.indication(new FrameEvent(source, f)));
          logger.debug("indication {}", f.toString());
        } else if (mc == CEMILData.MC_LDATA_CON) {
          addEvent(l -> l.confirmation(new FrameEvent(source, f)));
          logger.debug("confirmation of {}", f.getDestination());
        } else
          logger.warn("unspecified frame event - ignored, msg code = 0x" + Integer.toHexString(mc));
      } catch (final KNXFormatException | RuntimeException ex) {
        logger.warn(
            "received unspecified frame {}", DataUnitBuilder.toHex(e.getFrameBytes(), ""), ex);
      }
    }
 // Creates the target EMI format using a cEMI L-Data message
 private byte[] createEmi(final CEMILData f) {
   if (cEMI) {
     final CEMILData adjusted = adjustMsgType(f);
     addMediumInfo(adjusted);
     return adjusted.toByteArray();
   }
   return CEMIFactory.toEmi(f);
 }
 @Override
 public void confirmation(final FrameEvent e) {
   assertNotNull(e);
   assertEquals(lnk, e.getSource());
   final CEMILData f = (CEMILData) e.getFrame();
   con = f;
   assertEquals(CEMILData.MC_LDATA_CON, f.getMessageCode());
   assertTrue(f.isPositiveConfirmation());
   System.out.println("confirmation");
   Debug.printLData(f);
 }
 private CEMILData adjustMsgType(final CEMILData msg) {
   final boolean srcOk = msg.getSource().getRawAddress() != 0;
   // just return if we don't need to adjust source address and don't need LDataEx
   if ((srcOk || medium.getDeviceAddress().getRawAddress() == 0)
       && (medium instanceof TPSettings || msg instanceof CEMILDataEx)) return msg;
   return CEMIFactory.create(srcOk ? null : medium.getDeviceAddress(), null, msg, true);
 }
 @Override
 public void indication(final FrameEvent e) {
   assertNotNull(e);
   assertEquals(lnk, e.getSource());
   final CEMILData f = (CEMILData) e.getFrame();
   ind = f;
   assertEquals(CEMILData.MC_LDATA_IND, ind.getMessageCode());
   System.out.println("indication");
   Debug.printLData(ind);
 }
 @Override
 public void send(final CEMILData msg, final boolean waitForCon)
     throws KNXTimeoutException, KNXLinkClosedException {
   if (closed) throw new KNXLinkClosedException("link closed");
   if (cEMI && !sendCEmiAsByteArray) {
     final CEMILData adjusted = adjustMsgType(msg);
     addMediumInfo(adjusted);
     onSend(adjusted, waitForCon);
     return;
   }
   onSend(msg.getDestination(), createEmi(msg), waitForCon);
 }
  /**
   * {@inheritDoc}<br>
   * If a maximum size is set and the queue already reached maximum size, if {@link #isOverwrite()}
   * evaluates to<br>
   * - <code>true</code>, <code>frame</code> will replace the oldest inserted frame<br>
   * - <code>false</code>, <code>frame</code> is ignored and not queued.
   */
  public synchronized void setFrame(final CEMILData frame) {
    if (!frame.getDestination().equals(getKey()))
      throw new KNXIllegalArgumentException("frame key differs from this key");
    if (!max) ensureCapacity();
    else if (!overwrite && size == timestamps.length) return;
    final CEMILData[] c = (CEMILData[]) value;
    // notify on first time queue fills up
    final boolean notifyListener = max && size == c.length - 1;

    resetTimestamp();
    c[next] = frame;
    timestamps[next] = getTimestamp();
    ++next;
    next %= c.length;
    if (size < c.length) ++size;
    if (notifyListener) fireQueueFilled();
  }