package waterlevelsensor;

import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusNumberException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusProtocolException;
import com.intelligt.modbus.jlibmodbus.master.ModbusMaster;
import com.intelligt.modbus.jlibmodbus.master.ModbusMasterFactory;
import com.intelligt.modbus.jlibmodbus.serial.SerialParameters;
import com.intelligt.modbus.jlibmodbus.serial.SerialPort;
import com.intelligt.modbus.jlibmodbus.serial.SerialPortException;
import com.intelligt.modbus.jlibmodbus.serial.SerialUtils;
import com.intelligt.modbus.jlibmodbus.utils.FrameEvent;
import com.intelligt.modbus.jlibmodbus.utils.FrameEventListener;

import anywheresoftware.b4a.BA;

@BA.ShortName("Holykell_HPT604")
@BA.Events(values= {
		"log(msg as string)",
		"txrxcounter(tx as int, rx as int)",
		"sentbytes(bb() as byte)",
		"receivedbytes(bb() as byte)",
		
})

/**
 * Holykell HPT604 submersible water level sensor, RS-485 Modbus RTU communication
 * @author rdkartono
 *
 */
public class Holykell_HPT604_Modbus_RTU {

	private BA ba;
	private Object caller;
	private String event;
	private final Object Me = this;
	private boolean need_log_event = false;
	private boolean need_txrxcounter_event =false;
	private boolean need_sentbytes_event = false;
	private boolean need_receivedbytes_event = false;
	
	
	private boolean inited = false;
	private SerialParameters sp;
	private ModbusMaster modbus;
	private int sentcounter = 0;
	private int receivedcounter = 0;
	
	private int devid = 1;				// deviceid
	private double fullscale = 5.0;   // in meters
	
	/**
	 * Initialize HPT604 class
	 * @param callerobject : caller object
	 * @param eventname : eventname
	 */
	public void Initialize(BA bax, Object callerobject, String eventname) {
		ba = bax;
		caller = callerobject;
		event = eventname;
		if (ba instanceof BA) {
			if (caller != null) {
				if (event instanceof String) {
					if (!event.isEmpty()) {
						need_log_event = ba.subExists(event+"_log");
						need_txrxcounter_event = ba.subExists(event+"_txrxcounter");
						need_sentbytes_event = ba.subExists(event+"_sentbytes");
						need_receivedbytes_event = ba.subExists(event+"_receivedbytes");
						
					}
				}
			}
		}
		
		SerialUtils.setSerialPortFactoryJSerialComm();  // use jSerialComm for communication
		
		 Runtime.getRuntime().addShutdownHook(new Thread() {
	            @Override
	            public void run() {
	                Close();
	            }
	        });
		 
		 inited = true;
	}
	
	/**
	 * Check if already initialized
	 * @return true if inited
	 */
	public boolean Initialized() {
		return inited;
	}
	
	/**
	 * Get / Set sensor full scale in meters
	 * @return full scale
	 */
	public double getFullScaleMeter() {
		return fullscale;
	}
	
	/**
	 * Get / Set sensor full scale in meters
	 * @param value value in meters
	 */
	public void setFullScaleMeter(double value) {
		if (value < 0) value = 5.0;
		fullscale = value;
	}
	
	/**
	 * Get / Set assigned Device ID
	 * @return device ID
	 */
	public int getDeviceID() {
		return devid;
	}
	
	/**
	 * Get / Set assigned Device ID
	 * @param value device ID
	 */
	public void setDeviceID(int value) {
		if (value<0) value = 1;
		if (value>247) value = 247;
		devid = value;
	}
	
	/**
	 * Open Modbus Connection to HPT604
	 * Parameter will be set 9600 bps, 8 bit, 1 stop, parity NONE
	 * @param serialname : serial port name
	 * @return true if opened
	 */
	public boolean OpenSerial(String serialname) {
		if ((sp instanceof SerialParameters)==false) {
			sp = new SerialParameters();
		}
		
		sp.setDevice(serialname);
		sp.setBaudRate(SerialPort.BaudRate.BAUD_RATE_9600);
		
		try {
			modbus = ModbusMasterFactory.createModbusMasterRTU(sp);
			modbus.addListener(modbus_event);
			modbus.connect();
			sentcounter = 0;
			receivedcounter = 0;
			return modbus.isConnected();
		} catch (SerialPortException e) {
			raise_log("Unable to open Serial "+serialname+", Msg : "+e.getMessage());
		} catch (ModbusIOException e) {
			raise_log("Unable to modbus.connect, Msg : "+e.getMessage());
		}
		return false;
	}
	
	/**
	 * Close Modbus Connection
	 */
	public void Close() {
		if (modbus instanceof ModbusMaster) {
			try {
				modbus.removeListeners();
				modbus.disconnect();
			} catch (ModbusIOException e) {
				raise_log("Exception on Modbus.disconnect, Msg : "+e.getMessage());
			}
		}
		modbus = null;
	}
	
	/**
	 * Check if Modbus RTU connected to HPT604
	 * @return true if connected
	 */
	public boolean IsConnected() {
		if (modbus instanceof ModbusMaster) {
			return modbus.isConnected();
		}
		return false;
	}
	
	/**
	 * Read Sensor Serial Number
	 * @return -1 if failed to read
	 */
	public int Read_Sensor_SerialNumber() {
		if (IsConnected()) {
			try {
				int[] result = modbus.readInputRegisters(devid, 3, 1);		// function = 4, register address = 3, register length = 1
				if (result.length>0) {
					return result[0];
				}
			} catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) {
				raise_log("Unable to Read Sensor SerialNumber, Msg : "+e.getMessage());
			}
			
		}
		return -1;
	}
	
	/**
	 * Read WaterLevel Measurement
	 * Check returned data, on valid boolean , to verify
	 * @return HPT604Data object
	 */
	public HPT604Data Read_WaterLevel() {
		if (IsConnected()) {
			try {
				int[] result = modbus.readHoldingRegisters(devid, 0, 1);		// function = 3, register address = 0, register length =  1
				if (result.length>0) {
					return new HPT604Data(result[0],fullscale);
				}
			} catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) {
				raise_log("Unable to Read WaterLevel, Msg : "+e.getMessage());
			}
			
		}
		return new HPT604Data();		// just return random data
	}
	
	private void raise_log(String msg) {
		if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg});
	}
	
	private void raise_txrxcounter() {
		if (need_txrxcounter_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_txrxcounter", false, new Object[] {sentcounter,receivedcounter});
	}
	
	private void raise_sentbytes(byte[] bb) {
		if (need_sentbytes_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_sentbytes", false, new Object[] {bb});
	}
	
	private void raise_receivedbytes(byte[] bb) {
		if (need_receivedbytes_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_receivedbytes", false, new Object[] {bb});
	}
	
	
	
	private FrameEventListener modbus_event = new FrameEventListener() {

		@Override
		public void frameSentEvent(FrameEvent event) {
			if (sentcounter<Integer.MAX_VALUE) sentcounter+=1; else sentcounter=0;
			raise_txrxcounter();
			raise_sentbytes(event.getBytes());
		}

		@Override
		public void frameReceivedEvent(FrameEvent event) {
			if (receivedcounter<Integer.MAX_VALUE) receivedcounter+=1; else receivedcounter = 0;
			raise_txrxcounter();
			raise_receivedbytes(event.getBytes());
		}
		
	};
}
