package toaprotocol;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortInvalidPortException;

import anywheresoftware.b4a.BA;

@BA.ShortName("PelcoP")
@BA.Events(values= {
		"log(msg as string)",
		"portstatus(isopen as boolean)",
		"speechfunctionsettingchange(speechmethod as string, speakernumberlimit as string, automiconoff as string)",
		"functionoperationstatus(status as string)",
		"centralvolume(value as string)",
		"batteryalarm(unitnumber as string)",
		"startspeech(unitnumber as string, channelnumber as string)",
		"endspeech(unitnumber as string, channelnumber as string, failuredetection as string)",
		"speechrequest(unitnumber as string)",
		"speechrequestcancellation(unitnumber as string, failuredetection as string)",
		"speechrequestacceptancestatusquery(acceptedrequest as string, unitnumber1 as string, unitnumber2 as string, unitnumber3 as string, unitnumber4 as string)",
		"startchairmanpriorityspeech(unitnumber as string, channelnumber as string)",
		"endchairmanpriorityspeech(unitnumber as string, channelnumber as string, failuredetection as string)",
		"startvoting(unitnumber as string)",
		"endvoting(unitnumber as string, votingcontent as string)",
		"votingresult(vote1 as string, vote2 as string, vote3 as string)",
		"individualvolume(unitnumber as string, volumevalue as string)",
		"startspeechvolumeadjustment(unitnumber as string, channelnumber as string, volumevalue as string)",
		"speechstatusquery(chairmanpriority as string, onchannel1 as string, onchannel2 as string, onchannel3 as string, onchannel4 as string)",
		"installationcheckresult(chairmanunits as int, delegateunits as int)",
		"errortype2(commandcode as int, reason as string)",
		"errortype3(commandcode as int, unitnumber as string, reason as string)"
})

public class TS910 {
	private BA bax;
	private String event;
	private final Object Me = this;
	private boolean need_log_event = false;
	private boolean need_portstatus_event = false;
	private boolean need_speechfunctionsettingchange_event = false;
	private boolean need_functionoperationstatus_event = false;
	private boolean need_centralvolume_event = false;
	private boolean need_batteryalarm_event = false;
	private boolean need_startspeech_event = false;
	private boolean need_endspeech_event = false;
	private boolean need_speechrequest_event = false;
	private boolean need_speechrequestcancellation_event = false;
	private boolean need_speechrequestacceptancestatusquery_event = false;
	private boolean need_startchairmanpriorityspeech_event = false;
	private boolean need_endchairmanpriorityspeech_event = false;
	private boolean need_startvoting_event = false;
	private boolean need_endvoting_event = false;
	private boolean need_votingresult_event = false;
	private boolean need_individualvolume_event = false;
	private boolean need_startspeechvolumeadjustment_event = false;
	private boolean need_speechstatusquery_event = false;
	private boolean need_installationcheckresult_event = false;
	private boolean need_errortype2_event = false;
	private boolean need_errortype3_event = false;
	
	private SerialPort myport = null;
	private Runnable serialevent = null;
	
	// kalau true, lagi send command dari user
	// kalau false, lagi nungguin event dari TS
	private volatile Boolean manual_action = false;
	
	
	// konstanta
	private final byte ACK = 0x6;
	private final byte ENQ = 0x5;
	private final byte NAK = 0x15;
	private final byte DLE = 0x10;
	private final byte STX = 0x2;
	private final byte ETX = 0x3;
	private final byte EOT = 0x4;
	
	private final int Serial_read_timeout = 10;
	private final int Serial_write_timeout = 10;
	
	private AtomicInteger txpacket = new AtomicInteger();
	private AtomicInteger rxpacket = new AtomicInteger();
	private AtomicInteger txbyte = new AtomicInteger();
	private AtomicInteger rxbyte = new AtomicInteger();
	
	// parameters
	private int last_SpeechMethod = -1;
	private int last_SpeakerNumberLimit = -1;
	private int last_AutoMicOnOFF = -1;
	private int last_FunctionOperationStatus = -1;
	private int last_CentralVolume = -1;
	
	private final Map<String, String> votingcontentmap = new HashMap<String, String>();
	
	private final ExecutorService executor = Executors.newSingleThreadExecutor();
	
	/**
	 * Get Central Volume
	 * <p>values : <br/>
	 *       <b>1-32</b><br/>
	 *       <b>Default</b></p>
	 * @return string value
	 */
	public String getCentralVolume() {
		if (last_CentralVolume>=1 && last_CentralVolume<=0x20) {
			return String.valueOf(last_CentralVolume);
		} else return "Default";
	}
	
	/**
	 * Get Function Operation Status <br/>
	 *    <p>values : <br/>
	 *         <b>Accepting Speech</b> <br/>
	 *         <b>Chairman Priority Speech in Progress</b> <br/>
	 *         <b>Voting in Progress</b> <br/>
	 *         <b>Installation Testing in Progress</b> <br/>
	 *         <b>Else</b> <br/>
	 *         <b>Default</b> </p>
	 * @return string value
	 */
	public String getFunctionOperationStatus() {
		switch(last_FunctionOperationStatus) {
		case 0x41 : 
			return "Accepting Speech";
		case 0x42 :
			return "Chairman Priority Speech in Progress";
		case 0x43 : 
			return "Voting in Progress";
		case 0x44 :
			return "Installation Testing in Progress";
		case 0x52 :
			return "Else";
		default : 
			return "Default";
		}
	}
	
	/**
	 * Get Speech Method <br/>
	 *    <p>values : <br/>
	 *    <b>FIFO</b> <br/>
	 *    <b>LIFO</b> <br/>
	 *    <b>1Fix+LIFO</b> <br/>
	 *    <b>Centralized allowed</b> <br/>
	 *    <b>Centralized Prohibited</b> <br/>
	 *    <b>Default</b></p>
	 * @return string value
	 */
	public String getSpeechMethod() {
		switch(last_SpeechMethod) {
			case 0x41 :
				return "FIFO";
			case 0x42 : 
				return "LIFO";
			case 0x43 :
				return "1 Fix + LIFO";
			case 0x44 : 
				return "Centralized External Control Allowed";
			case 0x45 :
				return "Centralized External Control Prohibited";
			default :
				return "Default";
		}
	}
	
	/**
	 * Get Maximum Speakers Limit
	 *    <p>values : <br/>
	 *    <b>1</b><br/> 
	 *    <b>2</b><br/>  
	 *    <b>3</b><br/> 
	 *    <b>4</b><br/> 
	 *    <b>Default</b> </p>
	 * @return string value
	 */
	public String getSpeakerNumberLimit() {
		switch(last_SpeakerNumberLimit) {
			case 0x31 :
				return "1";
			case 0x32 :
				return "2";
			case 0x33 :
				return "3";
			case 0x34 : 
				return "4";
			default : 
				return "Default";
		}
	}
	
	/**
	 * Get Auto Microphone-Off
	 *    <p>values : <br/> 
	 *    <b>ON</b> <br/> 
	 *    <b>OFF</b> <br/> 
	 *    <b>Default</b></p> 
	 * @return string value
	 */
	public String getAutoMicOnOFF() {
		switch(last_AutoMicOnOFF) {
			case 0x59 :
				return "ON";
			case 0x4E :
				return "OFF";
			default :
				return "Default";
		}
	}
	
	/**
	 * Initialize TS910 Protocol
	 * @param eventname eventname
	 */
	public void Initialize(BA ba, String eventname) {
		this.bax = ba;
		this.event = eventname;
		if (bax!=null) {
			if (event.isEmpty()==false) {
				need_log_event = bax.subExists(event+"_log");
				need_portstatus_event = bax.subExists(event+"_portstatus");
				need_speechfunctionsettingchange_event = bax.subExists(event+"_speechfunctionsettingchange");
				need_functionoperationstatus_event = bax.subExists(event+"_functionoperationstatus");
				need_centralvolume_event = bax.subExists(event+"_centralvolume");
				need_batteryalarm_event = bax.subExists(event+"_batteryalarm");
				need_startspeech_event = bax.subExists(event+"_startspeech");
				need_endspeech_event = bax.subExists(event+"_endspeech");
				need_speechrequest_event = bax.subExists(event+"_speechrequest");
				need_speechrequestcancellation_event = bax.subExists(event+"_speechrequestcancellation");
				need_speechrequestacceptancestatusquery_event = bax.subExists(event+"_speechrequestacceptancestatusquery");
				need_startchairmanpriorityspeech_event = bax.subExists(event+"_startchairmanpriorityspeech");
				need_endchairmanpriorityspeech_event = bax.subExists(event+"_endchairmanpriorityspeech");
				need_startvoting_event = bax.subExists(event+"_startvoting");
				need_endvoting_event = bax.subExists(event+"_endvoting");
				need_votingresult_event = bax.subExists(event+"_votingresult");
				need_individualvolume_event = bax.subExists(event+"_individualvolume");
				need_startspeechvolumeadjustment_event = bax.subExists(event+"_startspeechvolumeadjustment");
				need_speechstatusquery_event = bax.subExists(event+"_speechstatusquery");
				need_installationcheckresult_event = bax.subExists(event+"_installationcheckresult");
				need_errortype2_event = bax.subExists(event+"_errortype2");
				need_errortype3_event = bax.subExists(event+"_errortype3");
			}
		}
	}
	
	/**
	 * Open SerialPort to use
	 * will raise event portstatus
	 * @param portname valid serial port name
	 * @return  true if success
	 */
	public boolean OpenPort(String portname) {
		try {
			myport = SerialPort.getCommPort(portname);
			myport.setBaudRate(9600);
			myport.setParity(SerialPort.EVEN_PARITY);
			myport.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, Serial_read_timeout, Serial_write_timeout);
			if (myport.openPort()) {
				raise_portstatus(portname, true);
				
				serialevent = new eventlistener();
				return true;
			} else {
				raise_log("Unable to open Port "+portname);
			}
		} catch(SerialPortInvalidPortException e) {
			raise_log("Unable to open Port "+portname+", exception="+e.getMessage());	
			myport = null;
		}
		return false;
	}
	
	/**
	 * Close SerialPort
	 * will raise event portstatus
	 */
	public void ClosePort() {
		String portname = "";
		if (myport!=null) {
			portname = myport.getDescriptivePortName();
			myport.closePort();
		}
		if (serialevent!=null) {
			serialevent = null;
		}
		
		raise_portstatus(portname, false);
		myport = null;
		
	}
	
	/**
	 * Check if SerialPort is opened
	 * @return true if opened
	 */
	public boolean IsOpened() {
		if (myport!=null) {
			return myport.isOpen();
		}
		return false;
	}
	
	/**
	 * Get SpeechFunctionSetting
	 * will raise event speechfunctionsettingchange
	 * @return true if success
	 */
	public boolean Get_SpeechFunctionSetting() {
		Future<Boolean> result = executor.submit(new requestsender("Get_SpeechFunctionSetting",Make_Command_data((byte)0xF3)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Get_SpeechFunctionSetting exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Set SpeechFunctionSetting
	 * will raise event speechfunctionsettingchange
	 * @param speechmethod value 0 - 5 
	 * <br/><b>0</b> = Default, based on switch at Central Unit
	 * <br/><b>1</b> = FIFO
	 * <br/><b>2</b> = LIFO
	 * <br/><b>3</b> = 1 Fixed + LIFO
	 * <br/><b>4</b> = Centralized external control allowed
	 * <br/><b>5</b> = Centralized external control prohibited
	 * @param speakerlimit value = 0 - 4
	 * <br/><b>0</b> = Default, based on switch at Central Unit
	 * <br/><b>1</b> = 1
	 * <br/><b>2</b> = 2
	 * <br/><b>3</b> = 3
	 * <br/><b>4</b> = 4
	 * @param automicoff value 0 - 2
	 * <br/><b>0</b> = Default, based on switch at Central Unit
	 * <br/><b>1</b> = Yes
	 * <br/><b>2</b> = No
	 * @return true if success
	 */
	public boolean Set_SpeechFunctionSetting(int speechmethod, int speakerlimit, int automicoff) {
		if (speechmethod<0) speechmethod = 0;
		if (speechmethod>5) speechmethod = 5;
		if (speakerlimit<0) speakerlimit = 0;
		if (speakerlimit>4) speakerlimit = 4;
		if (automicoff<0) automicoff = 0;
		if (automicoff>2) automicoff = 2;
		byte[] cmd = Make_Command_data(
				(byte)0xF1, 
				(byte)(speechmethod>0 ? speechmethod+0x40 : 0),
				(byte)(speakerlimit>0 ? speakerlimit+0x30 : 0),
				(byte)(automicoff > 0 ? (automicoff == 1 ? 0x59 : 0x4E) : 0)
				);
		
		Future<Boolean> result = executor.submit(new requestsender("Set_SpeechFunctionSetting",cmd));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Set_SpeechFunctionSetting exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Get FunctionOperationStatus
	 * will raise event functionoperationstatus
	 * @return true if success
	 */
	public boolean Get_FunctionOperationStatus() {
		Future<Boolean> result = executor.submit(new requestsender("Get_FunctionOperationStatus", Make_Command_data((byte)0xF7)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Get_FunctionOperationStatus exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Get Central Volume
	 * will raise event centralvolume
	 * @return true if success
	 */
	public boolean Get_CentralVolume() {
		Future<Boolean> result = executor.submit(new requestsender("Get_CentralVolume", Make_Command_data((byte)0xFB)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Get_CentralVolume exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Set Central Volume
	 * will raise event centralvolume
	 * @param value 0 - 32
	 * <br/><b>0</b> = Default, initial value at 16
	 * <br/><b>1-32</b> = 1 - 32
	 * @return true if success
	 */
	public boolean Set_CentralVolume(int value) {
		if (value<0) value = 0;
		if (value>32) value = 32;
		Future<Boolean> result = executor.submit(new requestsender("Set_CentralVolume", Make_Command_data((byte)0xF9, (byte)value)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Set_CentralVolume exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Start Speech
	 * will raise event startspeech
	 * @param unitnumber 1 - 192
	 * @return true if success
	 */
	public boolean Start_Speech(int unitnumber) {
		if (unitnumber<1) unitnumber = 1;
		if (unitnumber>0xC0) unitnumber = 0xC0;
		Future<Boolean> result = executor.submit(new requestsender("Start_Speech", Make_Command_data((byte)0xA1, (byte)unitnumber)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Start_Speech exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * End Speech
	 * will raise event endspeech
	 * @param unitnumber 1 - 192
	 * @return true if success
	 */
	public boolean End_Speech(int unitnumber) {
		if (unitnumber<1) unitnumber = 1;
		if (unitnumber>0xC0) unitnumber = 0xC0;
		Future<Boolean> result = executor.submit(new requestsender("End_Speech", Make_Command_data((byte)0xA0, (byte)unitnumber)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("End_Speech exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Reject Speech Request from unitnumber
	 * will raise event speechrequestcancellation
	 * @param unitnumber 1 - 192
	 * @return true if success
	 */
	public boolean Reject_SpeechRequest(int unitnumber) {
		if (unitnumber<1) unitnumber = 1;
		if (unitnumber>0xC0) unitnumber = 0xC0;
		Future<Boolean> result = executor.submit(new requestsender("Reject_SpeechRequest", Make_Command_data((byte)0xA4, (byte)unitnumber)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Reject_SpeechRequest exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * End all Speeches
	 * will raise event endspeech
	 * @return true if success
	 */
	public boolean End_All_Speech() {
		Future<Boolean> result = executor.submit(new requestsender("End_All_Speech", Make_Command_data((byte)0xAB)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("End_All_Speech exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Reject all speech requests
	 * will raise event speechrequestcancellation
	 * @return true if success
	 */
	public boolean Reject_All_SpeechRequest() {
		Future<Boolean> result = executor.submit(new requestsender("Reject_All_SpeechRequest", Make_Command_data((byte)0xAC)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Reject_All_SpeechRequest exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Get Speech Status
	 * will raise event speechstatusquery
	 * @return true if success
	 */
	public boolean Get_SpeechStatus() {
		Future<Boolean> result = executor.submit(new requestsender("Get_SpeechStatus", Make_Command_data((byte)0xA3)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Get_SpeechStatus exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Get Speech Request Acceptance
	 * will raise event speechrequestacceptancestatusquery
	 * @return true if success
	 */
	public boolean Get_SpeechRequestAcceptance() {
		Future<Boolean> result = executor.submit(new requestsender("Get_SpeechRequestAcceptance", Make_Command_data((byte)0xA7)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Get_SpeechRequestAcceptance exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Start Voting
	 * will raise multiple event startvoting
	 * @return true if success
	 */
	public boolean Start_Voting() {
		Future<Boolean> result = executor.submit(new requestsender("Start_Voting", Make_Command_data((byte)0xC1)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Start_Voting exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * End Voting
	 * will raise event votingresult
	 * @return true if success
	 */
	public boolean End_Voting() {
		Future<Boolean> result = executor.submit(new requestsender("End_Voting", Make_Command_data((byte)0xC0)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("End_Voting exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Get Final Voting Result
	 * will raise event votingresult
	 * @return true if success
	 */
	public boolean Get_VotingResult() {
		Future<Boolean> result = executor.submit(new requestsender("Get_VotingResult", Make_Command_data((byte)0xC3)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Get_VotingResult exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Get Individual Delegate Vote
	 * will raise multiple event endvoting
	 * @return true if success
	 */
	public boolean Get_VotingResponse() {
		Future<Boolean> result = executor.submit(new requestsender("Get_VotingResponse", Make_Command_data((byte)0xC2)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Get_VotingResponse exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Get Individual Microphone Input Volume
	 * will raise event individualvolume
	 * @param unitnumber 1 - 192
	 * @return true if success
	 */
	public boolean Get_IndividualMicrophoneInputVolume(int unitnumber) {
		if (unitnumber<1) unitnumber = 1;
		if (unitnumber>0xC0) unitnumber = 0xC0;
		Future<Boolean> result = executor.submit(new requestsender("Get_IndividualMicrophoneInputVolume", Make_Command_data((byte)0xF4, (byte)unitnumber)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Get_IndividualMicrophoneInputVolume exception="+e.getMessage());
			return false;
		}
		
	}
	
	/**
	 * SEt Individual Microphone Input Volume
	 * will raise event individualvolume
	 * @param unitnumber 1- 192
	 * @param volume 1 - 32
	 * @return true if success
	 */
	public boolean Set_IndividualMicrophoneInputVolume(int unitnumber, int volume) {
		if (unitnumber<1) unitnumber = 1;
		if (unitnumber>0xC0) unitnumber = 0xC0;
		if (volume<0) volume = 0;
		if (volume>32) volume = 32;
		Future<Boolean> result = executor.submit(new requestsender("Set_IndividualMicrophoneInputVolume", Make_Command_data((byte)0xF2, (byte)unitnumber,(byte)volume)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Set_IndividualMicrophoneInputVolume exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * Start Installation check
	 * @return true if success
	 */
	public boolean Start_Installation_Check() {
		Future<Boolean> result = executor.submit(new requestsender("Start_Installation_Check", Make_Command_data((byte)0xD1)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("Start_Installation_Check exception="+e.getMessage());
			return false;
		}
	}
	
	/**
	 * End Installation Check
	 * 
	 * @return true if success
	 */
	public boolean End_Installation_Check() {
		Future<Boolean> result = executor.submit(new requestsender("End_Installation_Check", Make_Command_data((byte)0xD0)));
		try {
			return result.get();
		} catch (InterruptedException | ExecutionException e) {
			raise_log("End_Installation_Check exception="+e.getMessage());
			return false;
		}
	}
	
	private class requestsender implements Callable<Boolean>{
		private final byte[] cmd;
		private final String description;
		
		public requestsender(String description, byte[] commandtosend) {
			cmd = commandtosend;
			this.description = description;
		}

		@Override
		public Boolean call() throws Exception {
			manual_action = true;
			Thread.sleep(10);
			
			// coba send ENQ
			if (!SendData(ENQ)) {
				raise_log("Unable to Send ENQ at command="+description);
				manual_action = false;
				return false;
			}
			
			// sampe sini , ENQ terkirim.
			// sekarang wait ACK
			if (Expecting_ACK("ENQ")==false) {
				manual_action = false;
				return false;
			}
			
			// sampe sini , terima ACK
			// sekarang kirim data
			if (!SendData(cmd)) {
				raise_log("Unable to Send Command at "+description);
				manual_action = false;
				return false;
			}
			
			// sampe sini , command terkirim
			// sekarang wait ACK
			if (Expecting_ACK(description)==false) {
				manual_action = false;
				return false;
			}
			
			//sampe sini terima ACK
			// sekarang kirim EOT
			if (!SendData(EOT)) {
				raise_log("Unable to Send EOT at command="+description);
				manual_action = false;
				return false;
			}
		
			// sampai sini, EOT terkirim
			// selesai normal
			manual_action = false;
			return true;
		}

		private boolean Expecting_ACK(String after) {
			byte[] bb = new byte[1];
			if (myport.readBytes(bb, bb.length)<bb.length) {
				raise_log("Unable to Read ACK at command="+description);
				return false;
			}
			if (Is_NAK_data(bb)) {
				raise_log("Expecting ACK after "+after+", received NAK");
				return false;
			}
			
			if (Is_ACK_data(bb)) {
				raise_log("ACK received after "+after);
				return true;
			} else {
				raise_log("Expecting ACK after "+after+", received "+String.valueOf(bb[0] & 0xFF));
				return false;
			}
		}
		
		
		
	}
	
	/**
	 * Thread untuk receive data serial
	 * @author rdkartono
	 *
	 */
	private class eventlistener implements Runnable{
		private final List<Byte> bufdata = new ArrayList<>();
		private int idlecounter = 0;
		private final int idlewait = 5;
		public eventlistener() {
			bufdata.clear();
		}
		
		@Override
		public void run() {
			raise_log("SerialPort eventlistener started");
			while(true) {
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					break;
				}
				// kalau port tertutup, selesai
				if (IsOpened()==false) break;
				// kalau ada manual action, pause
				if (manual_action==true) {
					idlecounter = 0;
					continue;
				}
				
				idlecounter++;
				if (myport.bytesAvailable()>0) {
					// ada yang dibaca
					byte[] newdata = new byte[myport.bytesAvailable()];
					myport.readBytes(newdata, newdata.length);
					for(byte xx:newdata) bufdata.add(xx);
					idlecounter = 0;
				} 
				
				if (idlecounter>idlewait) {
					// sudah tungguin sampe 10ms gak ada data baru
					idlecounter=0;
					if (bufdata.size()>0) {
						// ada isinya, berarti satu paket data nih ....
						Process_Incoming_Bytes(bufdata);
						bufdata.clear();
					}
				}
			}
			raise_log("SerialPort eventlistener finished");
		}
		
		/**
		 * Process incoming command
		 * @param bb Byte Array yang sudah stripped out DLE nya 
		 * @return true kalau command valid
		 */
		private boolean Process_Incoming_Command(byte[] bb) {
			int cmdcode = ByteToInt(bb[0]);
			int unitnumber = 0;
			int channelnumber = 0;
			int failuredetection = 0;
			int volume=0;
			int requestcode = 0;
			int reasoncode = 0;
			switch(cmdcode) {
				case 0xF1 :
					// Speech function setting change
				case 0xF3 :
					// Response to Speech function setting status query
					if (bb.length==4) {
						
						int _speechmethod = ByteToInt(bb[1]);
						int _speakernumberlimit = ByteToInt(bb[2]);
						int _automiconoff = ByteToInt(bb[3]);
						boolean haschanges = false;
						if (_speechmethod != last_SpeechMethod) {
							last_SpeechMethod = _speechmethod;
							haschanges = true;
						}
						if (_speakernumberlimit != last_SpeakerNumberLimit) {
							last_SpeakerNumberLimit = _speakernumberlimit;
							haschanges = true;
						}
						if (_automiconoff != last_AutoMicOnOFF) {
							last_AutoMicOnOFF = _automiconoff;
							haschanges = true;
						}
						if (haschanges) {
							raise_speechfunctionsettingchange(getSpeechMethod(), getSpeakerNumberLimit(), getAutoMicOnOFF());
						}
						return true;
					}
					break;
				case 0xF5 :
					// Function operation status
				case 0xF7 :
					// Respnose to function Operation Status query
					if (bb.length==2) {
						
						int _function = ByteToInt(bb[1]);
						if (_function != last_FunctionOperationStatus) {
							last_FunctionOperationStatus = _function;
							raise_functionoperationstatus(getFunctionOperationStatus());
						}
						return true;
					}
					break;
				case 0xF9 :
					// Conference unit microphone input volume adjustment
				case 0xFB :
					// Response to convference unit micrphne input volume adjustment query
					if (bb.length==2) {
						
						int _volume = ByteToInt(bb[1]);
						if (_volume != last_CentralVolume) {
							last_CentralVolume = _volume;
							raise_centralvolume(getCentralVolume());
						}
						return true;
					}
					
					break;
				case 0xFD :
					if (bb.length==2) {
						// Battery alarm
						unitnumber = ByteToInt(bb[1]);
						raise_batteryalarm(getUnitNumber(unitnumber));
						return true;
					}
					
					break;
				case 0xA1 :
					if (bb.length==3) {
						// Start of Speech
						unitnumber = ByteToInt(bb[1]);
						channelnumber = ByteToInt(bb[2]);
						raise_startspeech(getUnitNumber(unitnumber), getChannelNumber(channelnumber));
						return true;
					}
					break;
				case 0xA0 :
					if (bb.length==4) {
						// End of Speech
						unitnumber = ByteToInt(bb[1]);
						channelnumber = ByteToInt(bb[2]);
						failuredetection = ByteToInt(bb[3]);
						raise_endspeech(getUnitNumber(unitnumber), getChannelNumber(channelnumber), getFailureDetection(failuredetection));
						return true;
					}
					
					break;
				case 0xA5 :
					// Speech request
					if (bb.length==2) {
						unitnumber = ByteToInt(bb[1]);
						raise_speechrequest(getUnitNumber(unitnumber));
					}
					break;
				case 0xA7 :
					
					if (bb.length==3) {
						// Speech request cancelation
						unitnumber = ByteToInt(bb[1]);
						failuredetection = ByteToInt(bb[2]);
						raise_speechrequestcancellation(getUnitNumber(unitnumber), getFailureDetection(failuredetection));
						return true;
					} else if (bb.length==6) {
						// Response to speech request acceptance status query
						int accepted = ByteToInt(bb[1]);
						int unitnumber1 = ByteToInt(bb[2]);
						int unitnumber2 = ByteToInt(bb[3]);
						int unitnumber3 = ByteToInt(bb[4]);
						int unitnumber4 = ByteToInt(bb[5]);
						raise_speechrequestacceptancestatusquery(getSpeechRequestAccepted(accepted), getUnitNumber(unitnumber1), getUnitNumber(unitnumber2), getUnitNumber(unitnumber3), getUnitNumber(unitnumber4));
						return true;
					}
					
					break;
				case 0xB1 :
					if (bb.length==3) {
						// start of Chairman priority speech
						unitnumber = ByteToInt(bb[1]);
						channelnumber = ByteToInt(bb[2]);
						raise_startchairmanpriorityspeech(getUnitNumber(unitnumber), getChannelNumber(channelnumber));
						return true;
					}
					break;
				case 0xB0 :
					if (bb.length==4) {
						// End of Chairman priority speech
						unitnumber = ByteToInt(bb[1]);
						channelnumber = ByteToInt(bb[2]);
						failuredetection = ByteToInt(bb[3]);
						raise_endchairmanpriorityspeech(getUnitNumber(unitnumber), getChannelNumber(channelnumber), getFailureDetection(failuredetection));
						return true;
					}
					break;
				case 0xC1 :
					if (bb.length==2) {
						// Start of Voting
						unitnumber = ByteToInt(bb[1]);
						votingcontentmap.put(getUnitNumber(unitnumber), getVotingContent(0));
						raise_startvoting(getUnitNumber(unitnumber));
						return true;
					} else if (bb.length==1) {
						// Start of Installation check test
						raise_log("Start of Installation Check");
						return true;
					}
					
					break;
				case 0xC2 :
					if (bb.length==3) {
						// End of Voting
						unitnumber = ByteToInt(bb[1]);
						int votingcontent = ByteToInt(bb[2]);
						votingcontentmap.put(getUnitNumber(unitnumber), getVotingContent(votingcontent));
						raise_endvoting(getUnitNumber(unitnumber), getVotingContent(votingcontent));
						return true;
					}
					// ada lagi Installation Check Test Response , jumlah byte = 3
					// meragukan !!
					break;
				case 0xC0 :
					if (bb.length==4) {
						// Voting result/response
						int vote1 = ByteToInt(bb[1]);
						int vote2 = ByteToInt(bb[2]);
						int vote3 = ByteToInt(bb[3]);
						raise_votingresult(getVotingResult(vote1), getVotingResult(vote2), getVotingResult(vote3));
						return true;
					} else if (bb.length==1) {
						// End of Installation check test
						raise_log("End Of Installation Check");
						return true;
					}
					
					break;
				case 0xF2 :
					if (bb.length==3) {
						// Conference Unit Individual Microphone Input Volume Adjsutment
						unitnumber = ByteToInt(bb[1]);
						volume = ByteToInt(bb[2]);
						raise_individualvolume(getUnitNumber(unitnumber), getVolumeValue(volume));
						return true;
					}
					break;
				case 0xA2 :
					if (bb.length==4) {
						// Volume Adjustment
						unitnumber = ByteToInt(bb[1]);
						channelnumber = ByteToInt(bb[2]);
						volume = ByteToInt(bb[3]);
						raise_startspeechvolumeadjustment(getUnitNumber(unitnumber), getChannelNumber(channelnumber), getVolumeValue(volume));
						return true;
					}
					break;
				
				case 0xA3 :
					if (bb.length==6) {
						// Response to speech status query
						int chairmanvalue = ByteToInt(bb[1]);
						int onch1 = ByteToInt(bb[2]);
						int onch2 = ByteToInt(bb[3]);
						int onch3 = ByteToInt(bb[4]);
						int onch4 = ByteToInt(bb[5]);
						raise_speechstatusquery(getChairmanPriorityPerformed(chairmanvalue), getUnitNumber(onch1), getUnitNumber(onch2), getUnitNumber(onch3), getUnitNumber(onch4));
						return true;
					}
					break;
				
					
				case 0xC3 :
					if (bb.length==4) {
						// Voting result/response
						int vote1 = ByteToInt(bb[1]);
						int vote2 = ByteToInt(bb[2]);
						int vote3 = ByteToInt(bb[3]);
						raise_votingresult(getVotingResult(vote1), getVotingResult(vote2), getVotingResult(vote3));
						return true;
					} else if (bb.length==3) {
						// Response to isntallation check test result query
						int sumofchairman = ByteToInt(bb[1]);
						int sumofdelegate = ByteToInt(bb[2]);
						raise_installationcheckresult(sumofchairman, sumofdelegate);
						return true;
					}
					
					break;
				case 0xE2 :
					if (bb.length==3) {
						// Error response type 2
						requestcode = ByteToInt(bb[1]);
						reasoncode = ByteToInt(bb[2]);
						raise_errortype2(requestcode, getReasonOfErrors(reasoncode));
						return true;
					}
					break;
				case 0xE3 :
					if (bb.length==4) {
						// Error response type 3
						requestcode = ByteToInt(bb[1]);
						unitnumber = ByteToInt(bb[2]);
						reasoncode = ByteToInt(bb[3]);
						raise_errortype3(requestcode, getUnitNumber(unitnumber), getReasonOfErrors(reasoncode));
						return true;
					}
					break;
				
			}
			return false;
		}
		
		/**
		 * strip out (DLE STX) , (DLE ETX) , (DLE 0x10)
		 * @param bb array of bytes
		 * @return array of bytes
		 */
		private byte[] StripOutDLE(byte[] bb) {
			List<Byte> filtered = new ArrayList<>();
			boolean skipped = false;
			for(byte bx : bb) {
				if (skipped) {
					if (bx==DLE) filtered.add(DLE);		// double DLE, masukin yang kedua aja
					skipped = false;
					continue;
				} else {
					if (bx==DLE) {	
						// kalau ketemu DLE , skip byte
						skipped = true;
						continue;
					} else filtered.add(bx);		// kalau bukan, masukin dalam array filtered
				}
			}
			
			return ListByteToArrayByte(filtered);
		}
		
		/**
		 * Process Incoming List of Byte
		 * @param cmd List of Byte
		 */
		private void Process_Incoming_Bytes(List<Byte> cmd) {
			byte[] bb = ListByteToArrayByte(cmd);
			int length = bb.length;
			if (length<1) return;
			
			if (Is_ENQ_data(bb)) {
				// ada yang mau kirim data
				txbyte.addAndGet(length);
				txpacket.addAndGet(1);
				// jawabin ACK
				SendData(ACK);
				
			} else if (Is_Valid_Command(bb)) {
				// sepaket data
				txbyte.addAndGet(length);
				txpacket.addAndGet(1);
				
				if (Process_Incoming_Command(StripOutDLE(bb))) {
					SendData(ACK);
				} else {
					SendData(NAK);
				}
				
			} else if (Is_EOT_data(bb)) {
				txbyte.addAndGet(length);
				txpacket.addAndGet(1);
				
			}
		}
		
		/**
		 * Check if byte array is valid Command<br/> 
		 * Valid Command always contains [DLE] [STX] [command] [DLE] [ETX] [BCC]
		 * @param bb array of bytes
		 * @return true if valid
		 */
		private boolean Is_Valid_Command(byte... bb) {
			if (bb!=null) {
				int length = bb.length;
				// mesti lebih dari 5 bytes
				// DLE STX [xxx] DLE ETX BCC
				if (length>5) {
					// byte pertama adalah DLE
					if (bb[0]==DLE) {
						// byte kedua adalah STX
						if (bb[1]==STX) {
							if (bb[length-3]==DLE) {
								if (bb[length-2]==ETX) {
									byte bcc = bb[length-1];
									if (CountBCC(bb)==bcc) {
										return true;
									}
								}
							}
						}
					}
				}
			}
			return false;
		}
	}
	
	/**
	 * Convert List<Byte> to array of byte
	 * @param ll List<Byte> to convert
	 * @return array of byte
	 */
	private byte[] ListByteToArrayByte(List<Byte> ll) {
		if (ll != null) {
			int length = ll.size();
			if (length>0) {
				byte[] result = new byte[length];
				for(int ii=0;ii<result.length;ii++) result[ii] = ll.get(ii);
				return result;
			}
		}
		return new byte[0];
	}
	
	/**
	 * Convert byte (signed value) to integer (signed value)
	 * @param bx byte to convert
	 * @return integer value
	 */
	private int ByteToInt(byte bx) {
		return bx & 0xFF;
	}
	
	
	/**
	 * Encapsulate command bytes to Command form<br/>
	 * Valid Command Form is [DLE] [STX] [cmd] [DLE] [ETX] [BCC]
	 * @param cmd command bytes
	 * @return Command form
	 */
	private byte[] Make_Command_data(byte... cmd) {
		byte[] newcommand = new byte[cmd.length+5];
		int length = newcommand.length;
		newcommand[0] = DLE;
		newcommand[1] = STX;
		for(int ii=0;ii<cmd.length;ii++) newcommand[ii+2] = cmd[ii];
		newcommand[length-3] = DLE;
		newcommand[length-2] = ETX;
		newcommand[length-1] = CountBCC(newcommand);
		return newcommand;
	}
	
	/**
	 * Check if array is ENQ Command
	 * @param bb array of byte
	 * @return true if ENQ Command
	 */
	private boolean Is_ENQ_data(byte[] bb) {
		if (bb!=null) {
			if (bb.length==1) {
				return bb[0]==ENQ;
			}
		}
		return false;
	}
	
	/**
	 * Check if array is EOT command
	 * @param bb array of byte
	 * @return true if EOT Command
	 */
	private boolean Is_EOT_data(byte[] bb) {
		if (bb!=null) {
			if (bb.length==1) {
				return bb[0]==EOT;
			}
		}
		return false;
	}
	
	/**
	 * Check if array is ACK command
	 * @param bb array of byte
	 * @return true if ACK Command
	 */
	private boolean Is_ACK_data(byte[] bb) {
		if (bb!=null) {
			if (bb.length==1) {
				return bb[0]==ACK;
			}
		}
		return false;
	}
	
	/**
	 * Check if arrray is NAK Command
	 * @param bb array of byte
	 * @return true if NAK Command
	 */
	private boolean Is_NAK_data(byte[] bb) {
		if (bb!=null) {
			if (bb.length==1) {
				return bb[0]==NAK;
			}
		}
		return false;
	}
	
	
	/**
	 * Count BCC value from Command array
	 * @param bb Command array
	 * @return byte value for BCC
	 */
	private byte CountBCC(byte... bb) {
		if (bb!=null) {
			if (bb.length>5) {
				byte result = 0;
				// skip DLE dan STX di awal
				// skip BCC di paling akhir
				// include DLE ETX di akhir
				//         <-------------------->
				// DLE STX [b1] [b2] [b3] DLE ETX BCC
				for(int ii=2;ii<bb.length-1;ii++) {
					result = (byte)(result ^ bb[ii]);
				}
				return result;
			}
		}
		return 0;
	}
	
	/**
	 * Send Command to serialport
	 * @param data Command to send
	 * @return true if can be send
	 */
	private boolean SendData(byte... data) {
		if (IsOpened()) {
			int written = myport.writeBytes(data, data.length);
			if (written>0) {
				rxbyte.addAndGet(written);
				rxpacket.addAndGet(1);
			}
			
		}
		return false;
	}
	
	/**
	 * Convert unitnumber integer to UnitNumber Description
	 * @param unitnumber unit number integer
	 * @return string value
	 */
	private String getUnitNumber(int unitnumber) {
		if (unitnumber>=1 && unitnumber<=0xC0) {
			return "Unit"+unitnumber;
		} else if (unitnumber>=0xF0 && unitnumber<=0xF3) {
			return "RemoteControl"+(unitnumber-0xF0);
		} else if (unitnumber==0xFA) {
			return "External Equipment";
		} else if (unitnumber==0xFC) {
			return "Central Unit";
		} else return "Not Applicable";
	}
	
	/**
	 * Convert channelnumber integer to ChannelNumber Description
	 * @param channelnumber channel number integer
	 * @return string value
	 */
	private String getChannelNumber(int channelnumber) {
		if (channelnumber>=0x31 && channelnumber<=34)
			return String.valueOf(channelnumber);
		else return "No Channel";
	}
	
	/**
	 * Convert failuredetection code to Failure Detection Description
	 * @param code failure code 
	 * @return string value
	 */
	private String getFailureDetection(int code) {
		switch(code) {
		case 0x41 : return "Out of Operating Range Detection";
		case 0x4D : return "Auto Mic-Off Detection";
		case 0x52 : return "Power-On Detection";
		default : return "Failure Detection Performed";
		}
	}
	
	/**
	 * Convert number of accepted speechs to string value
	 * @param code accepted speech numbers
	 * @return string value
	 */
	private String getSpeechRequestAccepted(int code) {
		if (code>=0x31 && code<=0x34)
			return String.valueOf(code);
		else return "Not Accepted";
	}
	
	/**
	 * Voting Content
	 * @param code valid value "1" to "3"
	 * @return strin value
	 */
	private String getVotingContent(int code) {
		if (code>=0x31 && code<=0x33)
			return String.valueOf(code);
		else return "No Vote";
	}
	
	/**
	 * Voting result to String
	 * @param code valid value 0 ~ 0xC0
	 * @return string value
	 */
	private String getVotingResult(int code) {
		if (code>=0 && code<=0xC0) {
			return String.valueOf(code);
		} else return "Undefined";
	}
	
	/**
	 * Translate reason of error code to string
	 * @param code reason of error
	 * @return string value
	 */
	private String getReasonOfErrors(int code) {
		switch(code) {
		case 0x43 : return "Reception of Undefined Command";
		case 0x4C : return "Invalid Command Data Length";
		case 0x49 : return "Invalid Information Element";
		case 0x46 : return "Function Status Mismatch";
		case 0x55 : return "Unit Status Mismatch";
		case 0x42 : return "No Idle Channel";
		case 0x51 : return "Cannot Accept Speech Request";
		default : return "Unknown";
		}
	}
	
	/**
	 * Convert volume value to String value
	 * @param value 1 - 21
	 * @return string value of -10 dB ~ +10 dB
	 */
	private String getVolumeValue(int value) {
		if (value>=0x1 && value <=0x15) {
			return (value-11)+" dB";
		} else return "0 dB";
	}
	
	private String getChairmanPriorityPerformed(int code) {
		switch(code) {
		case 0x59 : return "Yes";
		case 0x4E : return "No";
		default : return "Unknown";
		}
	}
	
	private void raise_centralvolume(String value) {
		if (need_centralvolume_event) bax.raiseEventFromDifferentThread(Me, null,0, event+"_centralvolume", false, new Object[] {value});
	}
	
	private void raise_log(String msg) {
		if (need_log_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
	}
	private void raise_portstatus(String portname, boolean isopen) {
		if (need_portstatus_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_portstatus", false, new Object[] {portname, isopen});
	}
	private void raise_speechfunctionsettingchange(String speechmethod, String speakernumberlimit, String automiconoff) {
		if (need_speechfunctionsettingchange_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_speechfunctionsettingchange", false, new Object[] {speechmethod,speakernumberlimit,automiconoff});
	}
	
	private void raise_functionoperationstatus(String status) {
		if (need_functionoperationstatus_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_functionoperationstatus", false, new Object[] {status});
	}
	private void raise_batteryalarm(String unitnumber) {
		if (need_batteryalarm_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_batteryalarm", false, new Object[] {unitnumber});
	}
	
	private void raise_startspeech(String unitnumber, String channelnumber) {
		if (need_startspeech_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_startspeech", false, new Object[] {unitnumber, channelnumber});
	}
	private void raise_endspeech(String unitnumber, String channelnumber, String failuredetection) {
		if (need_endspeech_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_endspeech", false, new Object[] {unitnumber, channelnumber, failuredetection});
	}
	private void raise_speechrequest(String unitnumber) {
		if (need_speechrequest_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_speechrequest", false, new Object[] {unitnumber});
	}
	private void raise_speechrequestcancellation(String unitnumber, String failuredetection) {
		if (need_speechrequestcancellation_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_speechrequestcancellation", false, new Object[] {unitnumber, failuredetection});
	}
	private void raise_speechrequestacceptancestatusquery(String acceptedrequest, String unitnumber1, String unitnumber2, String unitnumber3, String unitnumber4) {
		if (need_speechrequestacceptancestatusquery_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_speechrequestacceptancestatusquery", false, new Object[] {acceptedrequest, unitnumber1, unitnumber2, unitnumber3, unitnumber4});
	}
	private void raise_startchairmanpriorityspeech(String unitnumber, String channelnumber) {
		if (need_startchairmanpriorityspeech_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_startchairmanpriorityspeech", false, new Object[] {unitnumber, channelnumber});
	}
	private void raise_endchairmanpriorityspeech(String unitnumber, String channelnumber, String failuredetection) {
		if (need_endchairmanpriorityspeech_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_endchairmanpriorityspeech", false, new Object[] {unitnumber, channelnumber, failuredetection});
	}
	private void raise_startvoting(String unitnumber) {
		if (need_startvoting_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_startvoting", false, new Object[] {unitnumber});
	}
	private void raise_endvoting(String unitnumber, String votingcontent) {
		if (need_endvoting_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_endvoting", false, new Object[] {unitnumber, votingcontent});
	}
	private void raise_votingresult(String vote1, String vote2, String vote3) {
		if (need_votingresult_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_votingresult", false, new Object[] {vote1,vote2,vote3});
	}
	private void raise_individualvolume(String unitnumber, String volume) {
		if (need_individualvolume_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_individualvolume", false, new Object[] {unitnumber, volume});
	}
	private void raise_startspeechvolumeadjustment(String unitnumber, String channelnumber, String volume) {
		if (need_startspeechvolumeadjustment_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_startspeechvolumeadjustment", false, new Object[] {unitnumber, channelnumber, volume});
	}
	private void raise_speechstatusquery(String chairman, String onchannel1, String onchannel2, String onchannel3, String onchannel4) {
		if (need_speechstatusquery_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_speechstatusquery", false, new Object[] {chairman, onchannel1, onchannel2, onchannel3, onchannel4});
	}
	private void raise_installationcheckresult(int chairmanunits, int delegateunits) {
		if (need_installationcheckresult_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_installationcheckresult", false, new Object[] {chairmanunits, delegateunits});
	}
	private void raise_errortype2(int commandcode, String reason) {
		if (need_errortype2_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_errortype2", false, new Object[] {commandcode, reason});
	}
	private void raise_errortype3(int commandcode, String unitnumber, String reason) {
		if (need_errortype3_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_errortype3", false, new Object[] {commandcode, unitnumber, reason});
	}
}
