docs: public void send(CEMI frame, BlockingMode mode) throws KNXTimeoutException,
  		KNXConnectionClosedException
  	{
  		// send state | blocking | nonblocking
  		// -----------------------------------
  		// OK         |send+wait | send+return
  		// WAIT       |wait+s.+w.| throw
  		// ACK_ERROR  |send+wait | send+return
  		// ERROR      |throw     | throw
  	
  		if (state == CLOSED) {
  			logger.warn("send invoked on closed connection - aborted");
  			throw new KNXConnectionClosedException("connection closed");
  		}
  		if (state < 0) {
  			logger.error("send invoked in error state " + state + " - aborted");
  			throw new KNXIllegalStateException("in error state, send aborted");
  		}
  		// arrange into line for blocking mode
  		if (mode != NONBLOCKING)
  			sendWaitQueue.acquire();
  		synchronized (lock) {
  			if (mode == NONBLOCKING && state != OK && state != ACK_ERROR) {
  				logger.warn("nonblocking send invoked while waiting for data response "
  					+ "in state " + state + " - aborted");
  				throw new KNXIllegalStateException("waiting for data response");
  			}
  			try {
  				if (state == CLOSED) {
  					logger.warn("send invoked on closed connection - aborted");
  					throw new KNXConnectionClosedException("connection closed");
  				}
  				updateState = mode == NONBLOCKING;
  				final byte[] buf;
  				if (serviceRequest == KNXnetIPHeader.ROUTING_IND)
  					buf = PacketHelper.toPacket(new RoutingIndication(frame));
  				else
  					buf = PacketHelper.toPacket(new ServiceRequest(serviceRequest,
  						channelID, getSeqNoSend(), frame));
  				final DatagramPacket p =
  					new DatagramPacket(buf, buf.length, dataEP.getAddress(), dataEP
  						.getPort());
  				int attempt = 0;
  				for (; attempt < maxSendAttempts; ++attempt) {
  					logger.trace("sending cEMI frame, " + mode + ", attempt "
  						+ (attempt + 1));
  					socket.send(p);
  					// shortcut for routing, don't switch into 'ack-pending'
  					if (serviceRequest == KNXnetIPHeader.ROUTING_IND)
  						return;
  					internalState = ACK_PENDING;
  					// always forward this state to user
  					state = ACK_PENDING;
  					if (mode == NONBLOCKING)
  						return;
  					waitForStateChange(ACK_PENDING, responseTimeout);
  					if (internalState == CEMI_CON_PENDING || internalState == OK)
  						break;
  				}
  				// close connection on no service ack from server
  				if (attempt == maxSendAttempts) {
  					final KNXAckTimeoutException e =
  						new KNXAckTimeoutException("no acknowledge reply received");
  					close(INTERNAL, "maximum send attempts", LogLevel.ERROR, e);
  					throw e;
  				}
  				// always forward this state to user
  				state = internalState;
  				if (mode == WAIT_FOR_ACK)
  					return;
  				// wait for incoming request
  				waitForStateChange(CEMI_CON_PENDING, CONFIRMATION_TIMEOUT);
  				// throw on no answer
  				if (internalState == CEMI_CON_PENDING) {
  					final KNXTimeoutException e =
  						new KNXTimeoutException("no confirmation reply received");
  					logger.log(LogLevel.INFO, "send response timeout", e);
  					internalState = OK;
  					throw e;
  				}
  			}
  			catch (final IOException e) {
  				close(INTERNAL, "communication failure", LogLevel.ERROR, e);
  				throw new KNXConnectionClosedException("connection closed");
  			}
  			finally {
  				updateState = true;
  				setState(internalState);
  				if (mode != NONBLOCKING)
  					sendWaitQueue.release();
  			}
  		}
  	}