commit 16e007b8fa92132429967d48ffbeb1b3e2607d26 Author: rdkartono Date: Wed Dec 4 09:32:07 2024 +0700 first commit diff --git a/bin/SBC/Amlogic/T982.class b/bin/SBC/Amlogic/T982.class new file mode 100644 index 0000000..d4faa30 Binary files /dev/null and b/bin/SBC/Amlogic/T982.class differ diff --git a/bin/SBC/AndroidNTP.class b/bin/SBC/AndroidNTP.class new file mode 100644 index 0000000..5a512db Binary files /dev/null and b/bin/SBC/AndroidNTP.class differ diff --git a/bin/SBC/BasicSBCInfo$1.class b/bin/SBC/BasicSBCInfo$1.class new file mode 100644 index 0000000..8132cad Binary files /dev/null and b/bin/SBC/BasicSBCInfo$1.class differ diff --git a/bin/SBC/BasicSBCInfo$2.class b/bin/SBC/BasicSBCInfo$2.class new file mode 100644 index 0000000..105a23b Binary files /dev/null and b/bin/SBC/BasicSBCInfo$2.class differ diff --git a/bin/SBC/BasicSBCInfo$3.class b/bin/SBC/BasicSBCInfo$3.class new file mode 100644 index 0000000..87c70de Binary files /dev/null and b/bin/SBC/BasicSBCInfo$3.class differ diff --git a/bin/SBC/BasicSBCInfo$4.class b/bin/SBC/BasicSBCInfo$4.class new file mode 100644 index 0000000..56c4031 Binary files /dev/null and b/bin/SBC/BasicSBCInfo$4.class differ diff --git a/bin/SBC/BasicSBCInfo$5.class b/bin/SBC/BasicSBCInfo$5.class new file mode 100644 index 0000000..d540f90 Binary files /dev/null and b/bin/SBC/BasicSBCInfo$5.class differ diff --git a/bin/SBC/BasicSBCInfo$CheckIPReachable.class b/bin/SBC/BasicSBCInfo$CheckIPReachable.class new file mode 100644 index 0000000..c0bcf06 Binary files /dev/null and b/bin/SBC/BasicSBCInfo$CheckIPReachable.class differ diff --git a/bin/SBC/BasicSBCInfo$CheckIPValid.class b/bin/SBC/BasicSBCInfo$CheckIPValid.class new file mode 100644 index 0000000..d7934d6 Binary files /dev/null and b/bin/SBC/BasicSBCInfo$CheckIPValid.class differ diff --git a/bin/SBC/BasicSBCInfo$cpumonitorrunnable.class b/bin/SBC/BasicSBCInfo$cpumonitorrunnable.class new file mode 100644 index 0000000..a2ac235 Binary files /dev/null and b/bin/SBC/BasicSBCInfo$cpumonitorrunnable.class differ diff --git a/bin/SBC/BasicSBCInfo$networkmeterrunnable.class b/bin/SBC/BasicSBCInfo$networkmeterrunnable.class new file mode 100644 index 0000000..9ac7694 Binary files /dev/null and b/bin/SBC/BasicSBCInfo$networkmeterrunnable.class differ diff --git a/bin/SBC/BasicSBCInfo.class b/bin/SBC/BasicSBCInfo.class new file mode 100644 index 0000000..e6a9b6a Binary files /dev/null and b/bin/SBC/BasicSBCInfo.class differ diff --git a/bin/SBC/CPU_Info.class b/bin/SBC/CPU_Info.class new file mode 100644 index 0000000..1611e39 Binary files /dev/null and b/bin/SBC/CPU_Info.class differ diff --git a/bin/SBC/GeneralAndroid$1.class b/bin/SBC/GeneralAndroid$1.class new file mode 100644 index 0000000..ba024c3 Binary files /dev/null and b/bin/SBC/GeneralAndroid$1.class differ diff --git a/bin/SBC/GeneralAndroid$2.class b/bin/SBC/GeneralAndroid$2.class new file mode 100644 index 0000000..e8a65b1 Binary files /dev/null and b/bin/SBC/GeneralAndroid$2.class differ diff --git a/bin/SBC/GeneralAndroid.class b/bin/SBC/GeneralAndroid.class new file mode 100644 index 0000000..1fad0e8 Binary files /dev/null and b/bin/SBC/GeneralAndroid.class differ diff --git a/bin/SBC/GetProp_Info.class b/bin/SBC/GetProp_Info.class new file mode 100644 index 0000000..ba170a0 Binary files /dev/null and b/bin/SBC/GetProp_Info.class differ diff --git a/bin/SBC/Hwmon_Info.class b/bin/SBC/Hwmon_Info.class new file mode 100644 index 0000000..3654308 Binary files /dev/null and b/bin/SBC/Hwmon_Info.class differ diff --git a/bin/SBC/Ifconfig_Info.class b/bin/SBC/Ifconfig_Info.class new file mode 100644 index 0000000..a816222 Binary files /dev/null and b/bin/SBC/Ifconfig_Info.class differ diff --git a/bin/SBC/NetworkInterface_info.class b/bin/SBC/NetworkInterface_info.class new file mode 100644 index 0000000..6b1c7b3 Binary files /dev/null and b/bin/SBC/NetworkInterface_info.class differ diff --git a/bin/SBC/NetworkMeterInfo.class b/bin/SBC/NetworkMeterInfo.class new file mode 100644 index 0000000..f20d370 Binary files /dev/null and b/bin/SBC/NetworkMeterInfo.class differ diff --git a/bin/SBC/ProcStat_Info.class b/bin/SBC/ProcStat_Info.class new file mode 100644 index 0000000..fb0f71c Binary files /dev/null and b/bin/SBC/ProcStat_Info.class differ diff --git a/bin/SBC/RAM_Info.class b/bin/SBC/RAM_Info.class new file mode 100644 index 0000000..0a22f38 Binary files /dev/null and b/bin/SBC/RAM_Info.class differ diff --git a/bin/SBC/RaspberryPi/Pi4.class b/bin/SBC/RaspberryPi/Pi4.class new file mode 100644 index 0000000..510a7c2 Binary files /dev/null and b/bin/SBC/RaspberryPi/Pi4.class differ diff --git a/bin/SBC/Size_Info.class b/bin/SBC/Size_Info.class new file mode 100644 index 0000000..5d7c013 Binary files /dev/null and b/bin/SBC/Size_Info.class differ diff --git a/bin/SBC/Storage_Info.class b/bin/SBC/Storage_Info.class new file mode 100644 index 0000000..7080325 Binary files /dev/null and b/bin/SBC/Storage_Info.class differ diff --git a/bin/SBC/WifiInfo.class b/bin/SBC/WifiInfo.class new file mode 100644 index 0000000..f354286 Binary files /dev/null and b/bin/SBC/WifiInfo.class differ diff --git a/bin/SBC/cpudata.class b/bin/SBC/cpudata.class new file mode 100644 index 0000000..dac36fd Binary files /dev/null and b/bin/SBC/cpudata.class differ diff --git a/bin/androgpio/Closer.class b/bin/androgpio/Closer.class new file mode 100644 index 0000000..2c097ea Binary files /dev/null and b/bin/androgpio/Closer.class differ diff --git a/bin/androgpio/DigitalInput/DigitalInput.class b/bin/androgpio/DigitalInput/DigitalInput.class new file mode 100644 index 0000000..1d8f7c3 Binary files /dev/null and b/bin/androgpio/DigitalInput/DigitalInput.class differ diff --git a/bin/androgpio/DigitalInput/DigitalInputEvent.class b/bin/androgpio/DigitalInput/DigitalInputEvent.class new file mode 100644 index 0000000..f1533a9 Binary files /dev/null and b/bin/androgpio/DigitalInput/DigitalInputEvent.class differ diff --git a/bin/androgpio/DigitalOutput/DigitalOutput.class b/bin/androgpio/DigitalOutput/DigitalOutput.class new file mode 100644 index 0000000..6a09e39 Binary files /dev/null and b/bin/androgpio/DigitalOutput/DigitalOutput.class differ diff --git a/bin/androgpio/DigitalOutput/DigitalOutputEvent.class b/bin/androgpio/DigitalOutput/DigitalOutputEvent.class new file mode 100644 index 0000000..bac0ba0 Binary files /dev/null and b/bin/androgpio/DigitalOutput/DigitalOutputEvent.class differ diff --git a/bin/androgpio/DigitalOutput/GtcAndroidOutput.class b/bin/androgpio/DigitalOutput/GtcAndroidOutput.class new file mode 100644 index 0000000..97862f8 Binary files /dev/null and b/bin/androgpio/DigitalOutput/GtcAndroidOutput.class differ diff --git a/bin/androgpio/DigitalOutput/GtcAndroidOutputEvent.class b/bin/androgpio/DigitalOutput/GtcAndroidOutputEvent.class new file mode 100644 index 0000000..c04d4ec Binary files /dev/null and b/bin/androgpio/DigitalOutput/GtcAndroidOutputEvent.class differ diff --git a/bin/androgpio/DigitalOutput/OutputDevice.class b/bin/androgpio/DigitalOutput/OutputDevice.class new file mode 100644 index 0000000..cec4cd9 Binary files /dev/null and b/bin/androgpio/DigitalOutput/OutputDevice.class differ diff --git a/bin/androgpio/I2C/I2CException.class b/bin/androgpio/I2C/I2CException.class new file mode 100644 index 0000000..99d4157 Binary files /dev/null and b/bin/androgpio/I2C/I2CException.class differ diff --git a/bin/androgpio/I2C/I2C_BUS.class b/bin/androgpio/I2C/I2C_BUS.class new file mode 100644 index 0000000..1bac995 Binary files /dev/null and b/bin/androgpio/I2C/I2C_BUS.class differ diff --git a/bin/androgpio/I2C/I2C_BUS_Event.class b/bin/androgpio/I2C/I2C_BUS_Event.class new file mode 100644 index 0000000..8f86e1c Binary files /dev/null and b/bin/androgpio/I2C/I2C_BUS_Event.class differ diff --git a/bin/androgpio/I2C/I2C_Device.class b/bin/androgpio/I2C/I2C_Device.class new file mode 100644 index 0000000..fc61a1f Binary files /dev/null and b/bin/androgpio/I2C/I2C_Device.class differ diff --git a/bin/androgpio/I2C/I2C_Device_Event.class b/bin/androgpio/I2C/I2C_Device_Event.class new file mode 100644 index 0000000..fc8c7b5 Binary files /dev/null and b/bin/androgpio/I2C/I2C_Device_Event.class differ diff --git a/bin/androgpio/I2C/libc$Linux_C_lib$timeval.class b/bin/androgpio/I2C/libc$Linux_C_lib$timeval.class new file mode 100644 index 0000000..bbb4fe2 Binary files /dev/null and b/bin/androgpio/I2C/libc$Linux_C_lib$timeval.class differ diff --git a/bin/androgpio/I2C/libc$Linux_C_lib.class b/bin/androgpio/I2C/libc$Linux_C_lib.class new file mode 100644 index 0000000..dbbd50c Binary files /dev/null and b/bin/androgpio/I2C/libc$Linux_C_lib.class differ diff --git a/bin/androgpio/I2C/libc$Linux_C_lib_DirectMapping.class b/bin/androgpio/I2C/libc$Linux_C_lib_DirectMapping.class new file mode 100644 index 0000000..839278c Binary files /dev/null and b/bin/androgpio/I2C/libc$Linux_C_lib_DirectMapping.class differ diff --git a/bin/androgpio/I2C/libc.class b/bin/androgpio/I2C/libc.class new file mode 100644 index 0000000..cb8f663 Binary files /dev/null and b/bin/androgpio/I2C/libc.class differ diff --git a/bin/androgpio/ModbusWrapper/jModbusCoil.class b/bin/androgpio/ModbusWrapper/jModbusCoil.class new file mode 100644 index 0000000..f3b60fd Binary files /dev/null and b/bin/androgpio/ModbusWrapper/jModbusCoil.class differ diff --git a/bin/androgpio/ModbusWrapper/jModbusHoldingRegister.class b/bin/androgpio/ModbusWrapper/jModbusHoldingRegister.class new file mode 100644 index 0000000..2ad9784 Binary files /dev/null and b/bin/androgpio/ModbusWrapper/jModbusHoldingRegister.class differ diff --git a/bin/androgpio/ModbusWrapper/jModbusMaster$1.class b/bin/androgpio/ModbusWrapper/jModbusMaster$1.class new file mode 100644 index 0000000..0eb08a5 Binary files /dev/null and b/bin/androgpio/ModbusWrapper/jModbusMaster$1.class differ diff --git a/bin/androgpio/ModbusWrapper/jModbusMaster$2.class b/bin/androgpio/ModbusWrapper/jModbusMaster$2.class new file mode 100644 index 0000000..5474f7e Binary files /dev/null and b/bin/androgpio/ModbusWrapper/jModbusMaster$2.class differ diff --git a/bin/androgpio/ModbusWrapper/jModbusMaster.class b/bin/androgpio/ModbusWrapper/jModbusMaster.class new file mode 100644 index 0000000..43f2caa Binary files /dev/null and b/bin/androgpio/ModbusWrapper/jModbusMaster.class differ diff --git a/bin/androgpio/ModbusWrapper/jModbusSlave$1.class b/bin/androgpio/ModbusWrapper/jModbusSlave$1.class new file mode 100644 index 0000000..d0083e5 Binary files /dev/null and b/bin/androgpio/ModbusWrapper/jModbusSlave$1.class differ diff --git a/bin/androgpio/ModbusWrapper/jModbusSlave$2.class b/bin/androgpio/ModbusWrapper/jModbusSlave$2.class new file mode 100644 index 0000000..f3349a9 Binary files /dev/null and b/bin/androgpio/ModbusWrapper/jModbusSlave$2.class differ diff --git a/bin/androgpio/ModbusWrapper/jModbusSlave.class b/bin/androgpio/ModbusWrapper/jModbusSlave.class new file mode 100644 index 0000000..adea5ad Binary files /dev/null and b/bin/androgpio/ModbusWrapper/jModbusSlave.class differ diff --git a/bin/androgpio/ScreenRotater$1.class b/bin/androgpio/ScreenRotater$1.class new file mode 100644 index 0000000..52e9542 Binary files /dev/null and b/bin/androgpio/ScreenRotater$1.class differ diff --git a/bin/androgpio/ScreenRotater.class b/bin/androgpio/ScreenRotater.class new file mode 100644 index 0000000..c9bfdf3 Binary files /dev/null and b/bin/androgpio/ScreenRotater.class differ diff --git a/bin/androgpio/SerialPort/SerialComm$1.class b/bin/androgpio/SerialPort/SerialComm$1.class new file mode 100644 index 0000000..60fded9 Binary files /dev/null and b/bin/androgpio/SerialPort/SerialComm$1.class differ diff --git a/bin/androgpio/SerialPort/SerialComm$2.class b/bin/androgpio/SerialPort/SerialComm$2.class new file mode 100644 index 0000000..941cdf2 Binary files /dev/null and b/bin/androgpio/SerialPort/SerialComm$2.class differ diff --git a/bin/androgpio/SerialPort/SerialComm.class b/bin/androgpio/SerialPort/SerialComm.class new file mode 100644 index 0000000..ecd4591 Binary files /dev/null and b/bin/androgpio/SerialPort/SerialComm.class differ diff --git a/bin/androgpio/SerialPort/jSerialPort_Event.class b/bin/androgpio/SerialPort/jSerialPort_Event.class new file mode 100644 index 0000000..c41ee82 Binary files /dev/null and b/bin/androgpio/SerialPort/jSerialPort_Event.class differ diff --git a/bin/androgpio/androgpio.class b/bin/androgpio/androgpio.class new file mode 100644 index 0000000..1c8508e Binary files /dev/null and b/bin/androgpio/androgpio.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$1.class b/bin/androgpio/customsocket/AndroFTPClient$1.class new file mode 100644 index 0000000..88f5945 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$1.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$10.class b/bin/androgpio/customsocket/AndroFTPClient$10.class new file mode 100644 index 0000000..d950992 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$10.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$11.class b/bin/androgpio/customsocket/AndroFTPClient$11.class new file mode 100644 index 0000000..a5ddee2 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$11.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$2.class b/bin/androgpio/customsocket/AndroFTPClient$2.class new file mode 100644 index 0000000..4a327d4 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$2.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$3.class b/bin/androgpio/customsocket/AndroFTPClient$3.class new file mode 100644 index 0000000..18cccd1 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$3.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$4.class b/bin/androgpio/customsocket/AndroFTPClient$4.class new file mode 100644 index 0000000..4158e97 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$4.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$5.class b/bin/androgpio/customsocket/AndroFTPClient$5.class new file mode 100644 index 0000000..8880ddc Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$5.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$6$1.class b/bin/androgpio/customsocket/AndroFTPClient$6$1.class new file mode 100644 index 0000000..12ef61f Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$6$1.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$6.class b/bin/androgpio/customsocket/AndroFTPClient$6.class new file mode 100644 index 0000000..4b5426d Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$6.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$7$1.class b/bin/androgpio/customsocket/AndroFTPClient$7$1.class new file mode 100644 index 0000000..d4c46eb Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$7$1.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$7.class b/bin/androgpio/customsocket/AndroFTPClient$7.class new file mode 100644 index 0000000..13d89b6 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$7.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$8.class b/bin/androgpio/customsocket/AndroFTPClient$8.class new file mode 100644 index 0000000..d195a01 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$8.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$9$1.class b/bin/androgpio/customsocket/AndroFTPClient$9$1.class new file mode 100644 index 0000000..2e45094 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$9$1.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient$9.class b/bin/androgpio/customsocket/AndroFTPClient$9.class new file mode 100644 index 0000000..2ef79ae Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient$9.class differ diff --git a/bin/androgpio/customsocket/AndroFTPClient.class b/bin/androgpio/customsocket/AndroFTPClient.class new file mode 100644 index 0000000..1529edf Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPClient.class differ diff --git a/bin/androgpio/customsocket/AndroFTPEntry.class b/bin/androgpio/customsocket/AndroFTPEntry.class new file mode 100644 index 0000000..780c5b1 Binary files /dev/null and b/bin/androgpio/customsocket/AndroFTPEntry.class differ diff --git a/bin/androgpio/customsocket/JsonArray.class b/bin/androgpio/customsocket/JsonArray.class new file mode 100644 index 0000000..f01fd5d Binary files /dev/null and b/bin/androgpio/customsocket/JsonArray.class differ diff --git a/bin/androgpio/customsocket/JsonObject.class b/bin/androgpio/customsocket/JsonObject.class new file mode 100644 index 0000000..62f6fe0 Binary files /dev/null and b/bin/androgpio/customsocket/JsonObject.class differ diff --git a/bin/androgpio/customsocket/NTPClient$1.class b/bin/androgpio/customsocket/NTPClient$1.class new file mode 100644 index 0000000..7e9b22a Binary files /dev/null and b/bin/androgpio/customsocket/NTPClient$1.class differ diff --git a/bin/androgpio/customsocket/NTPClient$2.class b/bin/androgpio/customsocket/NTPClient$2.class new file mode 100644 index 0000000..664fef8 Binary files /dev/null and b/bin/androgpio/customsocket/NTPClient$2.class differ diff --git a/bin/androgpio/customsocket/NTPClient.class b/bin/androgpio/customsocket/NTPClient.class new file mode 100644 index 0000000..0bdf576 Binary files /dev/null and b/bin/androgpio/customsocket/NTPClient.class differ diff --git a/bin/androgpio/customsocket/NTPInfo.class b/bin/androgpio/customsocket/NTPInfo.class new file mode 100644 index 0000000..4658625 Binary files /dev/null and b/bin/androgpio/customsocket/NTPInfo.class differ diff --git a/bin/androgpio/customsocket/SocketIoObject.class b/bin/androgpio/customsocket/SocketIoObject.class new file mode 100644 index 0000000..afcf933 Binary files /dev/null and b/bin/androgpio/customsocket/SocketIoObject.class differ diff --git a/bin/androgpio/customsocket/TCPSocket$1.class b/bin/androgpio/customsocket/TCPSocket$1.class new file mode 100644 index 0000000..7d63ca4 Binary files /dev/null and b/bin/androgpio/customsocket/TCPSocket$1.class differ diff --git a/bin/androgpio/customsocket/TCPSocket$tcpsocketrun$1.class b/bin/androgpio/customsocket/TCPSocket$tcpsocketrun$1.class new file mode 100644 index 0000000..7eb89df Binary files /dev/null and b/bin/androgpio/customsocket/TCPSocket$tcpsocketrun$1.class differ diff --git a/bin/androgpio/customsocket/TCPSocket$tcpsocketrun.class b/bin/androgpio/customsocket/TCPSocket$tcpsocketrun.class new file mode 100644 index 0000000..21339ff Binary files /dev/null and b/bin/androgpio/customsocket/TCPSocket$tcpsocketrun.class differ diff --git a/bin/androgpio/customsocket/TCPSocket.class b/bin/androgpio/customsocket/TCPSocket.class new file mode 100644 index 0000000..19ec0b6 Binary files /dev/null and b/bin/androgpio/customsocket/TCPSocket.class differ diff --git a/bin/androgpio/customsocket/TCPSocketServer$1.class b/bin/androgpio/customsocket/TCPSocketServer$1.class new file mode 100644 index 0000000..59b3a81 Binary files /dev/null and b/bin/androgpio/customsocket/TCPSocketServer$1.class differ diff --git a/bin/androgpio/customsocket/TCPSocketServer$tcpsocketserverrun.class b/bin/androgpio/customsocket/TCPSocketServer$tcpsocketserverrun.class new file mode 100644 index 0000000..19af147 Binary files /dev/null and b/bin/androgpio/customsocket/TCPSocketServer$tcpsocketserverrun.class differ diff --git a/bin/androgpio/customsocket/TCPSocketServer.class b/bin/androgpio/customsocket/TCPSocketServer.class new file mode 100644 index 0000000..5618a23 Binary files /dev/null and b/bin/androgpio/customsocket/TCPSocketServer.class differ diff --git a/bin/androgpio/customsocket/TcpSocketJavaEvent.class b/bin/androgpio/customsocket/TcpSocketJavaEvent.class new file mode 100644 index 0000000..bd006aa Binary files /dev/null and b/bin/androgpio/customsocket/TcpSocketJavaEvent.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1$1.class b/bin/androgpio/customsocket/jSocketIOClientV1$1.class new file mode 100644 index 0000000..743c2c3 Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1$1.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1$2.class b/bin/androgpio/customsocket/jSocketIOClientV1$2.class new file mode 100644 index 0000000..b95517f Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1$2.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1$3.class b/bin/androgpio/customsocket/jSocketIOClientV1$3.class new file mode 100644 index 0000000..9fff8bb Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1$3.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1$4.class b/bin/androgpio/customsocket/jSocketIOClientV1$4.class new file mode 100644 index 0000000..aeea408 Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1$4.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1$5.class b/bin/androgpio/customsocket/jSocketIOClientV1$5.class new file mode 100644 index 0000000..d2b85ec Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1$5.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1$6.class b/bin/androgpio/customsocket/jSocketIOClientV1$6.class new file mode 100644 index 0000000..25fda7a Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1$6.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1$7.class b/bin/androgpio/customsocket/jSocketIOClientV1$7.class new file mode 100644 index 0000000..0f1bb7a Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1$7.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1$8.class b/bin/androgpio/customsocket/jSocketIOClientV1$8.class new file mode 100644 index 0000000..af8b5c5 Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1$8.class differ diff --git a/bin/androgpio/customsocket/jSocketIOClientV1.class b/bin/androgpio/customsocket/jSocketIOClientV1.class new file mode 100644 index 0000000..5e223a6 Binary files /dev/null and b/bin/androgpio/customsocket/jSocketIOClientV1.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket$udprun.class b/bin/androgpio/customsocket/jUDPSocket$udprun.class new file mode 100644 index 0000000..0078407 Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket$udprun.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket.class b/bin/androgpio/customsocket/jUDPSocket.class new file mode 100644 index 0000000..465d7ab Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket2$1.class b/bin/androgpio/customsocket/jUDPSocket2$1.class new file mode 100644 index 0000000..f51e652 Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket2$1.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket2$2.class b/bin/androgpio/customsocket/jUDPSocket2$2.class new file mode 100644 index 0000000..ee06e6b Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket2$2.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket2$3.class b/bin/androgpio/customsocket/jUDPSocket2$3.class new file mode 100644 index 0000000..afe9bc0 Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket2$3.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket2$4.class b/bin/androgpio/customsocket/jUDPSocket2$4.class new file mode 100644 index 0000000..44d22bb Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket2$4.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket2$5.class b/bin/androgpio/customsocket/jUDPSocket2$5.class new file mode 100644 index 0000000..37c1984 Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket2$5.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket2$udpreceiver.class b/bin/androgpio/customsocket/jUDPSocket2$udpreceiver.class new file mode 100644 index 0000000..76e3550 Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket2$udpreceiver.class differ diff --git a/bin/androgpio/customsocket/jUDPSocket2.class b/bin/androgpio/customsocket/jUDPSocket2.class new file mode 100644 index 0000000..3f4c99f Binary files /dev/null and b/bin/androgpio/customsocket/jUDPSocket2.class differ diff --git a/bin/androgpio/gps/AdzanCalculator.class b/bin/androgpio/gps/AdzanCalculator.class new file mode 100644 index 0000000..cfff355 Binary files /dev/null and b/bin/androgpio/gps/AdzanCalculator.class differ diff --git a/bin/androgpio/gps/DataKota.class b/bin/androgpio/gps/DataKota.class new file mode 100644 index 0000000..effc4fb Binary files /dev/null and b/bin/androgpio/gps/DataKota.class differ diff --git a/bin/androgpio/gps/GpsGPGGA.class b/bin/androgpio/gps/GpsGPGGA.class new file mode 100644 index 0000000..528cc9b Binary files /dev/null and b/bin/androgpio/gps/GpsGPGGA.class differ diff --git a/bin/androgpio/gps/GpsGPGLL.class b/bin/androgpio/gps/GpsGPGLL.class new file mode 100644 index 0000000..185aaf5 Binary files /dev/null and b/bin/androgpio/gps/GpsGPGLL.class differ diff --git a/bin/androgpio/gps/GpsGPGSA.class b/bin/androgpio/gps/GpsGPGSA.class new file mode 100644 index 0000000..f7a77ca Binary files /dev/null and b/bin/androgpio/gps/GpsGPGSA.class differ diff --git a/bin/androgpio/gps/GpsGPRMC.class b/bin/androgpio/gps/GpsGPRMC.class new file mode 100644 index 0000000..408fc94 Binary files /dev/null and b/bin/androgpio/gps/GpsGPRMC.class differ diff --git a/bin/androgpio/gps/GpsGPVTG.class b/bin/androgpio/gps/GpsGPVTG.class new file mode 100644 index 0000000..5cdab49 Binary files /dev/null and b/bin/androgpio/gps/GpsGPVTG.class differ diff --git a/bin/androgpio/gps/GpsReceiver$1.class b/bin/androgpio/gps/GpsReceiver$1.class new file mode 100644 index 0000000..7a44855 Binary files /dev/null and b/bin/androgpio/gps/GpsReceiver$1.class differ diff --git a/bin/androgpio/gps/GpsReceiver$2.class b/bin/androgpio/gps/GpsReceiver$2.class new file mode 100644 index 0000000..03d11ac Binary files /dev/null and b/bin/androgpio/gps/GpsReceiver$2.class differ diff --git a/bin/androgpio/gps/GpsReceiver.class b/bin/androgpio/gps/GpsReceiver.class new file mode 100644 index 0000000..be63e99 Binary files /dev/null and b/bin/androgpio/gps/GpsReceiver.class differ diff --git a/bin/androgpio/gps/KotaIndonesia.class b/bin/androgpio/gps/KotaIndonesia.class new file mode 100644 index 0000000..6ee16a2 Binary files /dev/null and b/bin/androgpio/gps/KotaIndonesia.class differ diff --git a/bin/androgpio/gps/PrayTime.class b/bin/androgpio/gps/PrayTime.class new file mode 100644 index 0000000..96a1eb1 Binary files /dev/null and b/bin/androgpio/gps/PrayTime.class differ diff --git a/bin/androgpio/gps/kotaindonesiaminified.json b/bin/androgpio/gps/kotaindonesiaminified.json new file mode 100644 index 0000000..6d0e26c --- /dev/null +++ b/bin/androgpio/gps/kotaindonesiaminified.json @@ -0,0 +1,545 @@ +[ +{"nid":1,"parent_nid":0,"name":"Provinsi Aceh","serial":11,"type":1,"latitude":4.695135,"longitude":96.7493993,"status":1}, +{"nid":2,"parent_nid":0,"name":"Provinsi Sumatera Utara","serial":12,"type":1,"latitude":2.1153547,"longitude":99.5450974,"status":1}, +{"nid":3,"parent_nid":0,"name":"Provinsi Sumatera Barat","serial":13,"type":1,"latitude":-0.7399397,"longitude":100.8000051,"status":1}, +{"nid":4,"parent_nid":0,"name":"Provinsi Riau","serial":14,"type":1,"latitude":0.2933469,"longitude":101.7068294,"status":1}, +{"nid":5,"parent_nid":0,"name":"Provinsi Jambi","serial":15,"type":1,"latitude":-1.4851831,"longitude":102.4380581,"status":1}, +{"nid":6,"parent_nid":0,"name":"Provinsi Sumatera Selatan","serial":16,"type":1,"latitude":-3.3194374,"longitude":103.914399,"status":1}, +{"nid":7,"parent_nid":0,"name":"Provinsi Bengkulu","serial":17,"type":1,"latitude":-3.5778471,"longitude":102.3463875,"status":1}, +{"nid":8,"parent_nid":0,"name":"Provinsi Lampung","serial":18,"type":1,"latitude":-4.5585849,"longitude":105.4068079,"status":1}, +{"nid":9,"parent_nid":0,"name":"Provinsi Kepulauan Bangka Belitung","serial":19,"type":1,"latitude":-2.7410513,"longitude":106.4405872,"status":1}, +{"nid":10,"parent_nid":0,"name":"Provinsi Kepulauan Riau","serial":21,"type":1,"latitude":3.9456514,"longitude":108.1428669,"status":1}, +{"nid":11,"parent_nid":0,"name":"Provinsi DKI Jakarta","serial":31,"type":1,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":12,"parent_nid":0,"name":"Provinsi Jawa Barat","serial":32,"type":1,"latitude":-7.090911,"longitude":107.668887,"status":1}, +{"nid":13,"parent_nid":0,"name":"Provinsi Jawa Tengah","serial":33,"type":1,"latitude":-7.150975,"longitude":110.1402594,"status":1}, +{"nid":14,"parent_nid":0,"name":"Provinsi DI Yogyakarta","serial":34,"type":1,"latitude":-7.8753849,"longitude":110.4262088,"status":1}, +{"nid":15,"parent_nid":0,"name":"Provinsi Jawa Timur","serial":35,"type":1,"latitude":-7.5360639,"longitude":112.2384017,"status":1}, +{"nid":16,"parent_nid":0,"name":"Provinsi Banten","serial":36,"type":1,"latitude":-6.4058172,"longitude":106.0640179,"status":1}, +{"nid":17,"parent_nid":0,"name":"Provinsi Bali","serial":51,"type":1,"latitude":-8.4095178,"longitude":115.188916,"status":1}, +{"nid":18,"parent_nid":0,"name":"Provinsi Nusa Tenggara Barat","serial":52,"type":1,"latitude":-8.6529334,"longitude":117.3616476,"status":1}, +{"nid":19,"parent_nid":0,"name":"Provinsi Nusa Tenggara Timur","serial":53,"type":1,"latitude":-8.6573819,"longitude":121.0793705,"status":1}, +{"nid":20,"parent_nid":0,"name":"Provinsi Kalimantan Barat","serial":61,"type":1,"latitude":-0.2787808,"longitude":111.4752851,"status":1}, +{"nid":21,"parent_nid":0,"name":"Provinsi Kalimantan Tengah","serial":62,"type":1,"latitude":-1.6814878,"longitude":113.3823545,"status":1}, +{"nid":22,"parent_nid":0,"name":"Provinsi Kalimantan Selatan","serial":63,"type":1,"latitude":-3.0926415,"longitude":115.2837585,"status":1}, +{"nid":23,"parent_nid":0,"name":"Provinsi Kalimantan Timur","serial":64,"type":1,"latitude":1.6406296,"longitude":116.419389,"status":1}, +{"nid":24,"parent_nid":0,"name":"Provinsi Sulawesi Utara","serial":71,"type":1,"latitude":0.6246932,"longitude":123.9750018,"status":1}, +{"nid":25,"parent_nid":0,"name":"Provinsi Sulawesi Tengah","serial":72,"type":1,"latitude":-1.4300254,"longitude":121.4456179,"status":1}, +{"nid":26,"parent_nid":0,"name":"Provinsi Sulawesi Selatan","serial":73,"type":1,"latitude":-3.6687994,"longitude":119.9740534,"status":1}, +{"nid":27,"parent_nid":0,"name":"Provinsi Sulawesi Tenggara","serial":74,"type":1,"latitude":-4.14491,"longitude":122.174605,"status":1}, +{"nid":28,"parent_nid":0,"name":"Provinsi Gorontalo","serial":75,"type":1,"latitude":0.6999372,"longitude":122.4467238,"status":1}, +{"nid":29,"parent_nid":0,"name":"Provinsi Sulawesi Barat","serial":76,"type":1,"latitude":-2.8441371,"longitude":119.2320784,"status":1}, +{"nid":30,"parent_nid":0,"name":"Provinsi Maluku","serial":81,"type":1,"latitude":-3.2384616,"longitude":130.1452734,"status":1}, +{"nid":31,"parent_nid":0,"name":"Provinsi Maluku Utara","serial":82,"type":1,"latitude":1.5709993,"longitude":127.8087693,"status":1}, +{"nid":32,"parent_nid":0,"name":"Provinsi Papua Barat","serial":91,"type":1,"latitude":-1.3361154,"longitude":133.1747162,"status":1}, +{"nid":33,"parent_nid":0,"name":"Provinsi Papua","serial":94,"type":1,"latitude":-4.269928,"longitude":138.0803529,"status":1}, +{"nid":562,"parent_nid":74,"name":"Kota Tangerang Selatan","serial":0,"type":2,"latitude":-6.2888889,"longitude":106.7180556,"status":1}, +{"nid":821,"parent_nid":15,"name":"Kabupaten Banyuwangi","serial":1501,"type":2,"latitude":-8.2186111,"longitude":114.3669444,"status":1}, +{"nid":823,"parent_nid":15,"name":"Kabupaten Madiun","serial":1502,"type":2,"latitude":-7.627753,"longitude":111.505483,"status":1}, +{"nid":824,"parent_nid":15,"name":"Kabupaten Ponorogo","serial":1503,"type":2,"latitude":-7.867827,"longitude":111.466003,"status":1}, +{"nid":825,"parent_nid":15,"name":"Kabupaten Magetan","serial":1504,"type":2,"latitude":-7.6493413,"longitude":111.3381593,"status":1}, +{"nid":826,"parent_nid":15,"name":"Kabupaten Pacitan","serial":1505,"type":2,"latitude":-8.204614,"longitude":111.08769,"status":1}, +{"nid":827,"parent_nid":15,"name":"Kabupaten Ngawi","serial":1506,"type":2,"latitude":-7.38993,"longitude":111.46193,"status":1}, +{"nid":828,"parent_nid":15,"name":"Kabupaten Bangkalan","serial":1507,"type":2,"latitude":-7.0306912,"longitude":112.7450068,"status":1}, +{"nid":829,"parent_nid":15,"name":"Kabupaten Kediri","serial":1508,"type":2,"latitude":-7.809356,"longitude":112.032356,"status":1}, +{"nid":830,"parent_nid":15,"name":"Kabupaten Bondowoso","serial":1509,"type":2,"latitude":-7.917704,"longitude":113.813483,"status":1}, +{"nid":831,"parent_nid":15,"name":"Kabupaten Blitar","serial":1510,"type":2,"latitude":-8.1014419,"longitude":112.162762,"status":1}, +{"nid":832,"parent_nid":15,"name":"Kabupaten Trenggalek","serial":1511,"type":2,"latitude":-8.05,"longitude":111.7166667,"status":1}, +{"nid":833,"parent_nid":15,"name":"Kabupaten Tulungagung","serial":1512,"type":2,"latitude":-8.0666667,"longitude":111.9,"status":1}, +{"nid":834,"parent_nid":15,"name":"Kabupaten Nganjuk","serial":1513,"type":2,"latitude":-7.602932,"longitude":111.901808,"status":1}, +{"nid":835,"parent_nid":15,"name":"Kabupaten Situbondo","serial":1514,"type":2,"latitude":-7.702534,"longitude":113.955605,"status":1}, +{"nid":836,"parent_nid":15,"name":"Kabupaten Malang","serial":1515,"type":2,"latitude":-8.0495643,"longitude":112.6884549,"status":1}, +{"nid":837,"parent_nid":15,"name":"Kabupaten Jember","serial":1516,"type":2,"latitude":-8.172357,"longitude":113.700302,"status":1}, +{"nid":838,"parent_nid":15,"name":"Kabupaten Sumenep","serial":1517,"type":2,"latitude":-6.9253999,"longitude":113.9060624,"status":1}, +{"nid":839,"parent_nid":15,"name":"Kabupaten Pasuruan","serial":1518,"type":2,"latitude":-6.8623098,"longitude":108.8001936,"status":1}, +{"nid":840,"parent_nid":15,"name":"Kabupaten Pamekasan","serial":1519,"type":2,"latitude":-7.1666667,"longitude":113.4666667,"status":1}, +{"nid":841,"parent_nid":15,"name":"Kabupaten Probolinggo","serial":1520,"type":2,"latitude":-7.753965,"longitude":113.210675,"status":1}, +{"nid":842,"parent_nid":15,"name":"Kabupaten Lumajang","serial":1521,"type":2,"latitude":-8.137022,"longitude":113.226601,"status":1}, +{"nid":843,"parent_nid":15,"name":"Kabupaten Bojonegoro","serial":1522,"type":2,"latitude":0.882681,"longitude":124.4669566,"status":1}, +{"nid":844,"parent_nid":15,"name":"Kabupaten Tuban","serial":1523,"type":2,"latitude":-8.7493146,"longitude":115.1711298,"status":1}, +{"nid":845,"parent_nid":15,"name":"Kabupaten Lamongan","serial":1524,"type":2,"latitude":-7.406153,"longitude":109.3946794,"status":1}, +{"nid":846,"parent_nid":15,"name":"Kabupaten Sidoarjo","serial":1525,"type":2,"latitude":-7.4530278,"longitude":112.7173389,"status":1}, +{"nid":847,"parent_nid":15,"name":"Kabupaten Sampang","serial":1526,"type":2,"latitude":-7.5782556,"longitude":109.2058436,"status":1}, +{"nid":848,"parent_nid":15,"name":"Kabupaten Mojokerto","serial":1527,"type":2,"latitude":-7.488075,"longitude":112.427027,"status":1}, +{"nid":849,"parent_nid":15,"name":"Kabupaten Gresik","serial":1528,"type":2,"latitude":-7.15665,"longitude":112.6555,"status":1}, +{"nid":850,"parent_nid":15,"name":"Kabupaten Jombang","serial":1529,"type":2,"latitude":-7.5468395,"longitude":112.2264794,"status":1}, +{"nid":851,"parent_nid":15,"name":"Kota Mojokerto","serial":1530,"type":2,"latitude":-7.4722222,"longitude":112.4336111,"status":1}, +{"nid":852,"parent_nid":15,"name":"Kota Surabaya","serial":1531,"type":2,"latitude":-7.289166,"longitude":112.734398,"status":1}, +{"nid":853,"parent_nid":15,"name":"Kota Madiun","serial":1532,"type":2,"latitude":-7.629714,"longitude":111.513702,"status":1}, +{"nid":854,"parent_nid":15,"name":"Kota Blitar","serial":1533,"type":2,"latitude":-8.1,"longitude":112.15,"status":1}, +{"nid":855,"parent_nid":15,"name":"Kota Malang","serial":1534,"type":2,"latitude":-7.981894,"longitude":112.626503,"status":1}, +{"nid":856,"parent_nid":15,"name":"Kota Batu","serial":1535,"type":2,"latitude":-7.8671,"longitude":112.5239,"status":1}, +{"nid":857,"parent_nid":15,"name":"Kota Pasuruan","serial":1536,"type":2,"latitude":-7.644872,"longitude":112.903297,"status":1}, +{"nid":858,"parent_nid":15,"name":"Kota Kediri","serial":1537,"type":2,"latitude":-7.816895,"longitude":112.011398,"status":1}, +{"nid":859,"parent_nid":15,"name":"Kota Probolinggo","serial":1538,"type":2,"latitude":-7.756928,"longitude":113.211502,"status":1}, +{"nid":925,"parent_nid":5,"name":"Kabupaten Batanghari","serial":501,"type":2,"latitude":-1.7083922,"longitude":103.0817903,"status":1}, +{"nid":926,"parent_nid":5,"name":"Kabupaten Bungo","serial":502,"type":2,"latitude":-1.6401338,"longitude":101.8891721,"status":1}, +{"nid":927,"parent_nid":5,"name":"Kabupaten Kerinci","serial":503,"type":2,"latitude":-1.8720467,"longitude":101.4339148,"status":1}, +{"nid":928,"parent_nid":5,"name":"Kabupaten Merangin","serial":504,"type":2,"latitude":-2.1752789,"longitude":101.9804613,"status":1}, +{"nid":929,"parent_nid":5,"name":"Kabupaten Muaro Jambi","serial":505,"type":2,"latitude":-1.596672,"longitude":103.615799,"status":1}, +{"nid":930,"parent_nid":5,"name":"Kabupaten Sarolangun","serial":506,"type":2,"latitude":-2.2654937,"longitude":102.6905326,"status":1}, +{"nid":931,"parent_nid":5,"name":"Kabupaten Tanjung Jabung Barat","serial":507,"type":2,"latitude":-1.2332122,"longitude":103.7984428,"status":1}, +{"nid":932,"parent_nid":5,"name":"Kabupaten Tanjung Jabung Timur","serial":508,"type":2,"latitude":-1.3291599,"longitude":103.89973,"status":1}, +{"nid":933,"parent_nid":5,"name":"Kabupaten Tebo","serial":509,"type":2,"latitude":-1.2592999,"longitude":102.3463875,"status":1}, +{"nid":934,"parent_nid":5,"name":"Kota Jambi","serial":510,"type":2,"latitude":-1.596672,"longitude":103.615799,"status":1}, +{"nid":935,"parent_nid":5,"name":"Kota Sungai Penuh","serial":511,"type":2,"latitude":-2.06314,"longitude":101.387199,"status":1}, +{"nid":1326,"parent_nid":1,"name":"Kabupaten Simeulue","serial":1109,"type":2,"latitude":2.583333,"longitude":96.083333,"status":1}, +{"nid":1327,"parent_nid":1,"name":"Kabupaten Aceh Singkil","serial":1102,"type":2,"latitude":2.3589459,"longitude":97.87216,"status":1}, +{"nid":1328,"parent_nid":1,"name":"Kabupaten Aceh Selatan","serial":1101,"type":2,"latitude":3.3115056,"longitude":97.3516558,"status":1}, +{"nid":1329,"parent_nid":1,"name":"Kabupaten Aceh Tenggara","serial":1102,"type":2,"latitude":3.3088666,"longitude":97.6982272,"status":1}, +{"nid":1330,"parent_nid":1,"name":"Kabupaten Aceh Timur","serial":1103,"type":2,"latitude":5.255443,"longitude":95.9885456,"status":1}, +{"nid":1331,"parent_nid":1,"name":"Kabupaten Aceh Tengah","serial":1104,"type":2,"latitude":4.4482641,"longitude":96.8350999,"status":1}, +{"nid":1332,"parent_nid":1,"name":"Kabupaten Aceh Barat","serial":1105,"type":2,"latitude":4.4542745,"longitude":96.1526985,"status":1}, +{"nid":1333,"parent_nid":1,"name":"Kabupaten Aceh Besar","serial":1106,"type":2,"latitude":5.4529168,"longitude":95.4777811,"status":1}, +{"nid":1334,"parent_nid":1,"name":"Kabupaten Pidie","serial":1107,"type":2,"latitude":5.0742659,"longitude":95.940971,"status":1}, +{"nid":1335,"parent_nid":1,"name":"Kabupaten Bireuen","serial":1111,"type":2,"latitude":5.18254,"longitude":96.89005,"status":1}, +{"nid":1336,"parent_nid":1,"name":"Kabupaten Aceh Utara","serial":1108,"type":2,"latitude":4.9786331,"longitude":97.2221421,"status":1}, +{"nid":1337,"parent_nid":1,"name":"Kabupaten Aceh Barat Daya","serial":1112,"type":2,"latitude":3.0512643,"longitude":97.3368031,"status":1}, +{"nid":1338,"parent_nid":1,"name":"Kabupaten Gayo Lues","serial":1113,"type":2,"latitude":3.955165,"longitude":97.3516558,"status":1}, +{"nid":1339,"parent_nid":1,"name":"Kabupaten Aceh Tamiang","serial":1116,"type":2,"latitude":4.2328871,"longitude":98.0028892,"status":1}, +{"nid":1340,"parent_nid":1,"name":"Kabupaten Nagan Raya","serial":1115,"type":2,"latitude":4.1248406,"longitude":96.4929797,"status":1}, +{"nid":1341,"parent_nid":1,"name":"Kabupaten Aceh Jaya","serial":1114,"type":2,"latitude":4.7873684,"longitude":95.6457951,"status":1}, +{"nid":1342,"parent_nid":1,"name":"Kabupaten Bener Meriah","serial":1117,"type":2,"latitude":4.7748348,"longitude":97.0068393,"status":1}, +{"nid":1343,"parent_nid":1,"name":"Kabupaten Pidie Jaya","serial":1118,"type":2,"latitude":5.1548063,"longitude":96.195132,"status":1}, +{"nid":1344,"parent_nid":1,"name":"Kota Banda Aceh","serial":1171,"type":2,"latitude":5.55,"longitude":95.3166667,"status":1}, +{"nid":1345,"parent_nid":1,"name":"Kota Sabang","serial":1172,"type":2,"latitude":5.8946929,"longitude":95.3192982,"status":1}, +{"nid":1346,"parent_nid":1,"name":"Kota Langsa","serial":1174,"type":2,"latitude":4.48,"longitude":97.9633333,"status":1}, +{"nid":1347,"parent_nid":1,"name":"Kota Lhokseumawe","serial":1173,"type":2,"latitude":5.1880556,"longitude":97.1402778,"status":1}, +{"nid":1348,"parent_nid":1,"name":"Kota Subulussalam","serial":1175,"type":2,"latitude":2.6449927,"longitude":98.0165205,"status":1}, +{"nid":1349,"parent_nid":2,"name":"Kabupaten Nias","serial":1204,"type":2,"latitude":-8.1712591,"longitude":113.7111274,"status":1}, +{"nid":1350,"parent_nid":2,"name":"Kabupaten Mandailing Natal","serial":1213,"type":2,"latitude":0.7432372,"longitude":99.3673084,"status":1}, +{"nid":1351,"parent_nid":2,"name":"Kabupaten Tapanuli Selatan","serial":1203,"type":2,"latitude":1.5774933,"longitude":99.2785583,"status":1}, +{"nid":1352,"parent_nid":2,"name":"Kabupaten Tapanuli Tengah","serial":1201,"type":2,"latitude":1.8493299,"longitude":98.704075,"status":1}, +{"nid":1353,"parent_nid":2,"name":"Kabupaten Tapanuli Utara","serial":1202,"type":2,"latitude":2.0405246,"longitude":99.1013498,"status":1}, +{"nid":1354,"parent_nid":2,"name":"Kabupaten Toba Samosir","serial":1212,"type":2,"latitude":2.3502398,"longitude":99.2785583,"status":1}, +{"nid":1355,"parent_nid":2,"name":"Kabupaten Labuhanbatu","serial":1210,"type":2,"latitude":2.3439863,"longitude":100.1703257,"status":1}, +{"nid":1356,"parent_nid":2,"name":"Kabupaten Asahan","serial":1209,"type":2,"latitude":2.8174722,"longitude":99.634135,"status":1}, +{"nid":1357,"parent_nid":2,"name":"Kabupaten Simalungun","serial":1208,"type":2,"latitude":2.9781612,"longitude":99.2785583,"status":1}, +{"nid":1358,"parent_nid":2,"name":"Kabupaten Dairi","serial":1211,"type":2,"latitude":2.8675801,"longitude":98.265058,"status":1}, +{"nid":1359,"parent_nid":2,"name":"Kabupaten Karo","serial":1206,"type":2,"latitude":3.1052909,"longitude":98.265058,"status":1}, +{"nid":1360,"parent_nid":2,"name":"Kabupaten Deli Serdang","serial":1207,"type":2,"latitude":3.4201802,"longitude":98.704075,"status":1}, +{"nid":1361,"parent_nid":2,"name":"Kabupaten Langkat","serial":1205,"type":2,"latitude":3.8653916,"longitude":98.3088441,"status":1}, +{"nid":1362,"parent_nid":2,"name":"Kabupaten Nias Selatan","serial":1214,"type":2,"latitude":0.7086091,"longitude":97.8286368,"status":1}, +{"nid":1363,"parent_nid":2,"name":"Kabupaten Humbang Hasundutan","serial":1216,"type":2,"latitude":2.1988508,"longitude":98.5721016,"status":1}, +{"nid":1364,"parent_nid":2,"name":"Kabupaten Pakpak Bharat","serial":1215,"type":2,"latitude":2.545786,"longitude":98.299838,"status":1}, +{"nid":1365,"parent_nid":2,"name":"Kabupaten Samosir","serial":1217,"type":2,"latitude":2.5833333,"longitude":98.8166667,"status":1}, +{"nid":1366,"parent_nid":2,"name":"Kabupaten Serdang Bedagai","serial":1218,"type":2,"latitude":3.3371694,"longitude":99.0571089,"status":1}, +{"nid":1367,"parent_nid":2,"name":"Kabupaten Batu Bara","serial":1219,"type":2,"latitude":3.1740979,"longitude":99.5006143,"status":1}, +{"nid":1368,"parent_nid":2,"name":"Kabupaten Padang Lawas Utara","serial":1220,"type":2,"latitude":1.5758644,"longitude":99.634135,"status":1}, +{"nid":1369,"parent_nid":2,"name":"Kabupaten Padang Lawas","serial":1221,"type":2,"latitude":1.1186977,"longitude":99.8124935,"status":1}, +{"nid":1370,"parent_nid":2,"name":"Kota Sibolga","serial":1273,"type":2,"latitude":1.7403745,"longitude":98.7827988,"status":1}, +{"nid":1371,"parent_nid":2,"name":"Kota Tanjung Balai","serial":1274,"type":2,"latitude":2.965122,"longitude":99.800331,"status":1}, +{"nid":1372,"parent_nid":2,"name":"Kota Pematang Siantar","serial":1272,"type":2,"latitude":2.96,"longitude":99.06,"status":1}, +{"nid":1373,"parent_nid":2,"name":"Kota Tebing Tinggi","serial":1276,"type":2,"latitude":3.3856205,"longitude":99.2009815,"status":1}, +{"nid":1374,"parent_nid":2,"name":"Kota Medan","serial":1271,"type":2,"latitude":3.585242,"longitude":98.6755979,"status":1}, +{"nid":1375,"parent_nid":2,"name":"Kota Binjai","serial":1275,"type":2,"latitude":3.594462,"longitude":98.482246,"status":1}, +{"nid":1376,"parent_nid":2,"name":"Kota Padangsidimpuan","serial":1277,"type":2,"latitude":1.380424,"longitude":99.273972,"status":1}, +{"nid":1377,"parent_nid":3,"name":"Kabupaten Kepulauan Mentawai","serial":1309,"type":2,"latitude":-1.426001,"longitude":98.9245343,"status":1}, +{"nid":1378,"parent_nid":3,"name":"Kabupaten Pesisir Selatan","serial":1301,"type":2,"latitude":-1.7223147,"longitude":100.8903099,"status":1}, +{"nid":1379,"parent_nid":3,"name":"Kabupaten Solok","serial":1302,"type":2,"latitude":-0.803027,"longitude":100.644402,"status":1}, +{"nid":1380,"parent_nid":3,"name":"Kabupaten Sijunjung","serial":1303,"type":2,"latitude":-0.6881586,"longitude":100.997658,"status":1}, +{"nid":1381,"parent_nid":3,"name":"Kabupaten Tanah Datar","serial":1304,"type":2,"latitude":-0.4797043,"longitude":100.5746224,"status":1}, +{"nid":1382,"parent_nid":3,"name":"Kabupaten Padang Pariaman","serial":1305,"type":2,"latitude":-0.5546757,"longitude":100.2151578,"status":1}, +{"nid":1383,"parent_nid":3,"name":"Kabupaten Agam","serial":1306,"type":2,"latitude":-0.2209392,"longitude":100.1703257,"status":1}, +{"nid":1384,"parent_nid":3,"name":"Kabupaten Lima Puluh Kota","serial":1307,"type":2,"latitude":3.168216,"longitude":99.4187929,"status":1}, +{"nid":1385,"parent_nid":3,"name":"Kabupaten Pasaman","serial":1308,"type":2,"latitude":0.1288752,"longitude":99.7901781,"status":1}, +{"nid":1386,"parent_nid":3,"name":"Kabupaten Solok Selatan","serial":1311,"type":2,"latitude":-1.4157329,"longitude":101.2523792,"status":1}, +{"nid":1387,"parent_nid":3,"name":"Kabupaten Dharmas Raya","serial":1310,"type":2,"latitude":-1.1120568,"longitude":101.6157773,"status":1}, +{"nid":1388,"parent_nid":3,"name":"Kabupaten Pasaman Barat","serial":1312,"type":2,"latitude":0.2213005,"longitude":99.634135,"status":1}, +{"nid":1389,"parent_nid":3,"name":"Kota Padang","serial":1371,"type":2,"latitude":-0.95,"longitude":100.3530556,"status":1}, +{"nid":1390,"parent_nid":3,"name":"Kota Solok","serial":1372,"type":2,"latitude":-0.803027,"longitude":100.644402,"status":1}, +{"nid":1391,"parent_nid":3,"name":"Kota Sawah Lunto","serial":1373,"type":2,"latitude":-0.6810286,"longitude":100.7763604,"status":1}, +{"nid":1392,"parent_nid":3,"name":"Kota Padang Panjang","serial":1374,"type":2,"latitude":-0.470679,"longitude":100.4059456,"status":1}, +{"nid":1393,"parent_nid":3,"name":"Kota Bukittinggi","serial":1375,"type":2,"latitude":-0.3055556,"longitude":100.3691667,"status":1}, +{"nid":1394,"parent_nid":3,"name":"Kota Payakumbuh","serial":1376,"type":2,"latitude":-0.22887,"longitude":100.632301,"status":1}, +{"nid":1395,"parent_nid":3,"name":"Kota Pariaman","serial":1377,"type":2,"latitude":-0.6264389,"longitude":100.1179574,"status":1}, +{"nid":1396,"parent_nid":4,"name":"Kabupaten Kuantan Singingi","serial":1409,"type":2,"latitude":-0.4411596,"longitude":101.5248055,"status":1}, +{"nid":1397,"parent_nid":4,"name":"Kabupaten Indragiri Hulu","serial":1402,"type":2,"latitude":-0.7361181,"longitude":102.2547919,"status":1}, +{"nid":1398,"parent_nid":4,"name":"Kabupaten Indragiri Hilir","serial":1404,"type":2,"latitude":-0.1456733,"longitude":102.989615,"status":1}, +{"nid":1399,"parent_nid":4,"name":"Kabupaten Pelalawan","serial":1405,"type":2,"latitude":0.441415,"longitude":102.088699,"status":1}, +{"nid":1400,"parent_nid":4,"name":"Kabupaten S I A K","serial":1408,"type":2,"latitude":-0.789275,"longitude":113.921327,"status":1}, +{"nid":1401,"parent_nid":4,"name":"Kabupaten Kampar","serial":1401,"type":2,"latitude":0.146671,"longitude":101.1617356,"status":1}, +{"nid":1402,"parent_nid":4,"name":"Kabupaten Rokan Hulu","serial":1406,"type":2,"latitude":1.0410934,"longitude":100.439656,"status":1}, +{"nid":1403,"parent_nid":4,"name":"Kabupaten Bengkalis","serial":1403,"type":2,"latitude":1.4897222,"longitude":102.0797222,"status":1}, +{"nid":1404,"parent_nid":4,"name":"Kabupaten Rokan Hilir","serial":1407,"type":2,"latitude":1.6463978,"longitude":100.8000051,"status":1}, +{"nid":1405,"parent_nid":4,"name":"Kota Pekanbaru","serial":1471,"type":2,"latitude":0.5333333,"longitude":101.45,"status":1}, +{"nid":1406,"parent_nid":4,"name":"Kota Dumai","serial":1472,"type":2,"latitude":1.665742,"longitude":101.447601,"status":1}, +{"nid":1407,"parent_nid":5,"name":"Kabupaten Kerinci","serial":1501,"type":2,"latitude":-1.697,"longitude":101.264,"status":1}, +{"nid":1408,"parent_nid":5,"name":"Kabupaten Merangin","serial":1502,"type":2,"latitude":-2.1752789,"longitude":101.9804613,"status":1}, +{"nid":1409,"parent_nid":5,"name":"Kabupaten Sarolangun","serial":1503,"type":2,"latitude":-2.2654937,"longitude":102.6905326,"status":1}, +{"nid":1410,"parent_nid":5,"name":"Kabupaten Batang Hari","serial":1504,"type":2,"latitude":-1.7083922,"longitude":103.0817903,"status":1}, +{"nid":1411,"parent_nid":5,"name":"Kabupaten Muaro Jambi","serial":1505,"type":2,"latitude":-1.596672,"longitude":103.615799,"status":1}, +{"nid":1412,"parent_nid":5,"name":"Kabupaten Tanjung Jabung Timur","serial":1507,"type":2,"latitude":-1.3291599,"longitude":103.89973,"status":1}, +{"nid":1413,"parent_nid":5,"name":"Kabupaten Tanjung Jabung Barat","serial":1506,"type":2,"latitude":-1.2332122,"longitude":103.7984428,"status":1}, +{"nid":1414,"parent_nid":5,"name":"Kabupaten Tebo","serial":1509,"type":2,"latitude":-1.2592999,"longitude":102.3463875,"status":1}, +{"nid":1415,"parent_nid":5,"name":"Kabupaten Bungo","serial":1508,"type":2,"latitude":-1.6401338,"longitude":101.8891721,"status":1}, +{"nid":1416,"parent_nid":5,"name":"Kota Jambi","serial":1571,"type":2,"latitude":-1.596672,"longitude":103.615799,"status":1}, +{"nid":1417,"parent_nid":6,"name":"Kabupaten Ogan Komering Ulu","serial":1601,"type":2,"latitude":-4.0283486,"longitude":104.0072348,"status":1}, +{"nid":1418,"parent_nid":6,"name":"Kabupaten Ogan Komering Ilir","serial":1602,"type":2,"latitude":-3.4559744,"longitude":105.2194808,"status":1}, +{"nid":1419,"parent_nid":6,"name":"Kabupaten Muara Enim","serial":1603,"type":2,"latitude":-3.651581,"longitude":103.770798,"status":1}, +{"nid":1420,"parent_nid":6,"name":"Kabupaten Lahat","serial":1604,"type":2,"latitude":-3.7863889,"longitude":103.5427778,"status":1}, +{"nid":1421,"parent_nid":6,"name":"Kabupaten Musi Rawas","serial":1605,"type":2,"latitude":-2.8625305,"longitude":102.989615,"status":1}, +{"nid":1422,"parent_nid":6,"name":"Kabupaten Musi Banyuasin","serial":1606,"type":2,"latitude":-2.5442029,"longitude":103.7289167,"status":1}, +{"nid":1423,"parent_nid":6,"name":"Kabupaten Banyu Asin","serial":1607,"type":2,"latitude":-2.6095639,"longitude":104.7520939,"status":1}, +{"nid":1424,"parent_nid":6,"name":"Kabupaten Ogan Komering Ulu Selatan","serial":1609,"type":2,"latitude":-4.6681951,"longitude":104.0072348,"status":1}, +{"nid":1425,"parent_nid":6,"name":"Kabupaten Ogan Komering Ulu Timur","serial":1608,"type":2,"latitude":-3.8567934,"longitude":104.7520939,"status":1}, +{"nid":1426,"parent_nid":6,"name":"Kabupaten Ogan Ilir","serial":1610,"type":2,"latitude":-3.426544,"longitude":104.6121475,"status":1}, +{"nid":1427,"parent_nid":6,"name":"Kabupaten Empat Lawang","serial":1611,"type":2,"latitude":-3.7286029,"longitude":102.8975098,"status":1}, +{"nid":1428,"parent_nid":6,"name":"Kota Palembang","serial":1671,"type":2,"latitude":-2.9911083,"longitude":104.7567333,"status":1}, +{"nid":1429,"parent_nid":6,"name":"Kota Prabumulih","serial":1674,"type":2,"latitude":-3.440956,"longitude":104.235397,"status":1}, +{"nid":1430,"parent_nid":6,"name":"Kota Pagar Alam","serial":1672,"type":2,"latitude":-4.03767,"longitude":103.265297,"status":1}, +{"nid":1431,"parent_nid":6,"name":"Kota Lubuklinggau","serial":1673,"type":2,"latitude":-3.2966667,"longitude":102.8616667,"status":1}, +{"nid":1432,"parent_nid":7,"name":"Kabupaten Bengkulu Selatan","serial":1701,"type":2,"latitude":-4.3248409,"longitude":103.035694,"status":1}, +{"nid":1433,"parent_nid":7,"name":"Kabupaten Rejang Lebong","serial":1702,"type":2,"latitude":-3.4548154,"longitude":102.6675575,"status":1}, +{"nid":1434,"parent_nid":7,"name":"Kabupaten Bengkulu Utara","serial":1703,"type":2,"latitude":-3.4219555,"longitude":102.1632718,"status":1}, +{"nid":1435,"parent_nid":7,"name":"Kabupaten Kaur","serial":1704,"type":2,"latitude":-4.6792278,"longitude":103.4511768,"status":1}, +{"nid":1436,"parent_nid":7,"name":"Kabupaten Seluma","serial":1705,"type":2,"latitude":-4.0622929,"longitude":102.5642261,"status":1}, +{"nid":1437,"parent_nid":7,"name":"Kabupaten Mukomuko","serial":1706,"type":2,"latitude":-2.5760003,"longitude":101.1169805,"status":1}, +{"nid":1438,"parent_nid":7,"name":"Kabupaten Lebong","serial":1707,"type":2,"latitude":-2.992617,"longitude":104.382202,"status":1}, +{"nid":1439,"parent_nid":7,"name":"Kabupaten Kepahiang","serial":1708,"type":2,"latitude":-3.651431,"longitude":102.578201,"status":1}, +{"nid":1440,"parent_nid":7,"name":"Kota Bengkulu","serial":1771,"type":2,"latitude":-3.7955556,"longitude":102.2591667,"status":1}, +{"nid":1441,"parent_nid":8,"name":"Kabupaten Lampung Barat","serial":1804,"type":2,"latitude":-5.1490396,"longitude":104.1930918,"status":1}, +{"nid":1442,"parent_nid":8,"name":"Kabupaten Tanggamus","serial":1802,"type":2,"latitude":-5.3027489,"longitude":104.5655273,"status":1}, +{"nid":1443,"parent_nid":8,"name":"Kabupaten Lampung Selatan","serial":1801,"type":2,"latitude":-5.5622614,"longitude":105.5474373,"status":1}, +{"nid":1444,"parent_nid":8,"name":"Kabupaten Lampung Timur","serial":1807,"type":2,"latitude":-5.1134995,"longitude":105.6881788,"status":1}, +{"nid":1445,"parent_nid":8,"name":"Kabupaten Lampung Tengah","serial":1802,"type":2,"latitude":-4.8008086,"longitude":105.3131185,"status":1}, +{"nid":1446,"parent_nid":8,"name":"Kabupaten Lampung Utara","serial":1803,"type":2,"latitude":-4.8133905,"longitude":104.7520939,"status":1}, +{"nid":1447,"parent_nid":8,"name":"Kabupaten Way Kanan","serial":1808,"type":2,"latitude":-4.4963689,"longitude":104.5655273,"status":1}, +{"nid":1448,"parent_nid":8,"name":"Kabupaten Tulangbawang","serial":1812,"type":2,"latitude":-4.3176576,"longitude":105.5005483,"status":1}, +{"nid":1449,"parent_nid":8,"name":"Kabupaten Pesawaran","serial":1809,"type":2,"latitude":-5.493245,"longitude":105.0791228,"status":1}, +{"nid":1450,"parent_nid":8,"name":"Kota Bandar Lampung","serial":1871,"type":2,"latitude":-5.45,"longitude":105.2666667,"status":1}, +{"nid":1451,"parent_nid":8,"name":"Kota Metro","serial":1872,"type":2,"latitude":-5.1166667,"longitude":105.3,"status":1}, +{"nid":1452,"parent_nid":9,"name":"Kabupaten Bangka","serial":1901,"type":2,"latitude":-2.2884782,"longitude":106.0640179,"status":1}, +{"nid":1453,"parent_nid":9,"name":"Kabupaten Belitung","serial":1902,"type":2,"latitude":-2.8708938,"longitude":107.9531836,"status":1}, +{"nid":1454,"parent_nid":9,"name":"Kabupaten Bangka Barat","serial":1905,"type":2,"latitude":-2.2884782,"longitude":106.0640179,"status":1}, +{"nid":1455,"parent_nid":9,"name":"Kabupaten Bangka Tengah","serial":1904,"type":2,"latitude":-2.2884782,"longitude":106.0640179,"status":1}, +{"nid":1456,"parent_nid":9,"name":"Kabupaten Bangka Selatan","serial":1903,"type":2,"latitude":-2.2884782,"longitude":106.0640179,"status":1}, +{"nid":1457,"parent_nid":9,"name":"Kabupaten Belitung Timur","serial":1906,"type":2,"latitude":-2.8708938,"longitude":107.9531836,"status":1}, +{"nid":1458,"parent_nid":9,"name":"Kota Pangkal Pinang","serial":1971,"type":2,"latitude":-2.129323,"longitude":106.109596,"status":1}, +{"nid":1459,"parent_nid":10,"name":"Kabupaten Karimun","serial":2102,"type":2,"latitude":1.05,"longitude":103.3666667,"status":1}, +{"nid":1460,"parent_nid":10,"name":"Kabupaten Bintan","serial":2101,"type":2,"latitude":1.0619173,"longitude":104.5189214,"status":1}, +{"nid":1461,"parent_nid":10,"name":"Kabupaten Natuna","serial":2103,"type":2,"latitude":3.9329945,"longitude":108.1812242,"status":1}, +{"nid":1462,"parent_nid":10,"name":"Kabupaten Lingga","serial":2104,"type":2,"latitude":-0.1627686,"longitude":104.6354631,"status":1}, +{"nid":1463,"parent_nid":10,"name":"Kota Batam","serial":2171,"type":2,"latitude":1.0456264,"longitude":104.0304535,"status":1}, +{"nid":1464,"parent_nid":10,"name":"Kota Tanjung Pinang","serial":2172,"type":2,"latitude":0.9179205,"longitude":104.446464,"status":1}, +{"nid":1465,"parent_nid":11,"name":"Kabupaten Kepulauan Seribu","serial":3101,"type":2,"latitude":-5.7985266,"longitude":106.5071982,"status":1}, +{"nid":1466,"parent_nid":11,"name":"Kota Jakarta Selatan","serial":3174,"type":2,"latitude":-6.332973,"longitude":106.807915,"status":1}, +{"nid":1467,"parent_nid":11,"name":"Kota Jakarta Timur","serial":3175,"type":2,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":1468,"parent_nid":11,"name":"Kota Jakarta Pusat","serial":3171,"type":2,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":1469,"parent_nid":11,"name":"Kota Jakarta Barat","serial":3173,"type":2,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":1470,"parent_nid":11,"name":"Kota Jakarta Utara","serial":3172,"type":2,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":1471,"parent_nid":12,"name":"Kabupaten Bogor","serial":3201,"type":2,"latitude":-6.6,"longitude":106.8,"status":1}, +{"nid":1472,"parent_nid":12,"name":"Kabupaten Sukabumi","serial":3202,"type":2,"latitude":-6.92405,"longitude":106.922203,"status":1}, +{"nid":1473,"parent_nid":12,"name":"Kabupaten Cianjur","serial":3203,"type":2,"latitude":-6.8172531,"longitude":107.1307289,"status":1}, +{"nid":1474,"parent_nid":12,"name":"Kabupaten Bandung","serial":3204,"type":2,"latitude":-6.9147444,"longitude":107.6098111,"status":1}, +{"nid":1475,"parent_nid":12,"name":"Kabupaten Garut","serial":3205,"type":2,"latitude":-7.227906,"longitude":107.908699,"status":1}, +{"nid":1476,"parent_nid":12,"name":"Kabupaten Tasikmalaya","serial":3206,"type":2,"latitude":-7.327954,"longitude":108.214104,"status":1}, +{"nid":1477,"parent_nid":12,"name":"Kabupaten Ciamis","serial":3207,"type":2,"latitude":-7.3333333,"longitude":108.35,"status":1}, +{"nid":1478,"parent_nid":12,"name":"Kabupaten Kuningan","serial":3208,"type":2,"latitude":-6.9833333,"longitude":108.4833333,"status":1}, +{"nid":1479,"parent_nid":12,"name":"Kabupaten Cirebon","serial":3209,"type":2,"latitude":-6.715534,"longitude":108.564003,"status":1}, +{"nid":1480,"parent_nid":12,"name":"Kabupaten Majalengka","serial":3210,"type":2,"latitude":-6.8531026,"longitude":108.2258897,"status":1}, +{"nid":1481,"parent_nid":12,"name":"Kabupaten Sumedang","serial":3211,"type":2,"latitude":0.6095949,"longitude":110.0330554,"status":1}, +{"nid":1482,"parent_nid":12,"name":"Kabupaten Indramayu","serial":3212,"type":2,"latitude":-6.336315,"longitude":108.325104,"status":1}, +{"nid":1483,"parent_nid":12,"name":"Kabupaten Subang","serial":3213,"type":2,"latitude":-6.569361,"longitude":107.752403,"status":1}, +{"nid":1484,"parent_nid":12,"name":"Kabupaten Purwakarta","serial":3214,"type":2,"latitude":-6.5386806,"longitude":107.4499404,"status":1}, +{"nid":1485,"parent_nid":12,"name":"Kabupaten Karawang","serial":3215,"type":2,"latitude":-6.3227303,"longitude":107.3375791,"status":1}, +{"nid":1486,"parent_nid":12,"name":"Kabupaten Bekasi","serial":3216,"type":2,"latitude":-6.2333333,"longitude":107,"status":1}, +{"nid":1487,"parent_nid":12,"name":"Kabupaten Bandung Barat","serial":3217,"type":2,"latitude":-6.8937121,"longitude":107.4321959,"status":1}, +{"nid":1488,"parent_nid":12,"name":"Kota Bogor","serial":3271,"type":2,"latitude":-6.6,"longitude":106.8,"status":1}, +{"nid":1489,"parent_nid":12,"name":"Kota Sukabumi","serial":3272,"type":2,"latitude":-6.92405,"longitude":106.922203,"status":1}, +{"nid":1490,"parent_nid":12,"name":"Kota Bandung","serial":3273,"type":2,"latitude":-6.9147444,"longitude":107.6098111,"status":1}, +{"nid":1491,"parent_nid":12,"name":"Kota Cirebon","serial":3274,"type":2,"latitude":-6.715534,"longitude":108.564003,"status":1}, +{"nid":1492,"parent_nid":12,"name":"Kota Bekasi","serial":3275,"type":2,"latitude":-6.2333333,"longitude":107,"status":1}, +{"nid":1493,"parent_nid":12,"name":"Kota Depok","serial":3276,"type":2,"latitude":-6.39,"longitude":106.83,"status":1}, +{"nid":1494,"parent_nid":12,"name":"Kota Cimahi","serial":3277,"type":2,"latitude":-6.880239,"longitude":107.5355,"status":1}, +{"nid":1495,"parent_nid":12,"name":"Kota Tasikmalaya","serial":3278,"type":2,"latitude":-7.327954,"longitude":108.214104,"status":1}, +{"nid":1496,"parent_nid":12,"name":"Kota Banjar","serial":3279,"type":2,"latitude":-7.3666667,"longitude":108.5333333,"status":1}, +{"nid":1497,"parent_nid":13,"name":"Kabupaten Cilacap","serial":3301,"type":2,"latitude":-7.733333,"longitude":109,"status":1}, +{"nid":1498,"parent_nid":13,"name":"Kabupaten Banyumas","serial":3302,"type":2,"latitude":-7.4832133,"longitude":109.140438,"status":1}, +{"nid":1499,"parent_nid":13,"name":"Kabupaten Purbalingga","serial":3303,"type":2,"latitude":-7.390747,"longitude":109.3638,"status":1}, +{"nid":1500,"parent_nid":13,"name":"Kabupaten Banjarnegara","serial":3304,"type":2,"latitude":-7.402706,"longitude":109.681396,"status":1}, +{"nid":1501,"parent_nid":13,"name":"Kabupaten Kebumen","serial":3305,"type":2,"latitude":-7.678682,"longitude":109.656502,"status":1}, +{"nid":1502,"parent_nid":13,"name":"Kabupaten Purworejo","serial":3306,"type":2,"latitude":-7.709731,"longitude":110.008003,"status":1}, +{"nid":1503,"parent_nid":13,"name":"Kabupaten Wonosobo","serial":3307,"type":2,"latitude":-7.362109,"longitude":109.899399,"status":1}, +{"nid":1504,"parent_nid":13,"name":"Kabupaten Magelang","serial":3308,"type":2,"latitude":-7.481253,"longitude":110.213799,"status":1}, +{"nid":1505,"parent_nid":13,"name":"Kabupaten Boyolali","serial":3309,"type":2,"latitude":-7.523819,"longitude":110.595901,"status":1}, +{"nid":1506,"parent_nid":13,"name":"Kabupaten Klaten","serial":3310,"type":2,"latitude":-7.711687,"longitude":110.595497,"status":1}, +{"nid":1507,"parent_nid":13,"name":"Kabupaten Sukoharjo","serial":3311,"type":2,"latitude":-7.6808818,"longitude":110.8195292,"status":1}, +{"nid":1508,"parent_nid":13,"name":"Kabupaten Wonogiri","serial":3312,"type":2,"latitude":-7.817782,"longitude":110.920601,"status":1}, +{"nid":1509,"parent_nid":13,"name":"Kabupaten Karanganyar","serial":3313,"type":2,"latitude":-7.5961111,"longitude":110.9508333,"status":1}, +{"nid":1510,"parent_nid":13,"name":"Kabupaten Sragen","serial":3314,"type":2,"latitude":-7.430229,"longitude":111.021301,"status":1}, +{"nid":1511,"parent_nid":13,"name":"Kabupaten Grobogan","serial":3315,"type":2,"latitude":-7.0217194,"longitude":110.9625854,"status":1}, +{"nid":1512,"parent_nid":13,"name":"Kabupaten Blora","serial":3316,"type":2,"latitude":-6.95,"longitude":111.4166667,"status":1}, +{"nid":1513,"parent_nid":13,"name":"Kabupaten Rembang","serial":3317,"type":2,"latitude":-6.71124,"longitude":111.345299,"status":1}, +{"nid":1514,"parent_nid":13,"name":"Kabupaten Pati","serial":3318,"type":2,"latitude":-6.751338,"longitude":111.038002,"status":1}, +{"nid":1515,"parent_nid":13,"name":"Kabupaten Kudus","serial":3319,"type":2,"latitude":-6.804087,"longitude":110.838203,"status":1}, +{"nid":1516,"parent_nid":13,"name":"Kabupaten Jepara","serial":3320,"type":2,"latitude":-6.5596059,"longitude":110.6717,"status":1}, +{"nid":1517,"parent_nid":13,"name":"Kabupaten Demak","serial":3321,"type":2,"latitude":-6.888115,"longitude":110.639297,"status":1}, +{"nid":1518,"parent_nid":13,"name":"Kabupaten Semarang","serial":3322,"type":2,"latitude":-6.9666667,"longitude":110.4166667,"status":1}, +{"nid":1519,"parent_nid":13,"name":"Kabupaten Temanggung","serial":3323,"type":2,"latitude":-7.316669,"longitude":110.174797,"status":1}, +{"nid":1520,"parent_nid":13,"name":"Kabupaten Kendal","serial":3324,"type":2,"latitude":-6.919686,"longitude":110.205597,"status":1}, +{"nid":1521,"parent_nid":13,"name":"Kabupaten Batang","serial":3325,"type":2,"latitude":-6.8941111,"longitude":109.7234519,"status":1}, +{"nid":1522,"parent_nid":13,"name":"Kabupaten Pekalongan","serial":3326,"type":2,"latitude":-6.882887,"longitude":109.669998,"status":1}, +{"nid":1523,"parent_nid":13,"name":"Kabupaten Pemalang","serial":3327,"type":2,"latitude":-6.884234,"longitude":109.377998,"status":1}, +{"nid":1524,"parent_nid":13,"name":"Kabupaten Tegal","serial":3328,"type":2,"latitude":-6.8666667,"longitude":109.1333333,"status":1}, +{"nid":1525,"parent_nid":13,"name":"Kabupaten Brebes","serial":3329,"type":2,"latitude":-6.8833333,"longitude":109.05,"status":1}, +{"nid":1526,"parent_nid":13,"name":"Kota Magelang","serial":3371,"type":2,"latitude":-7.481253,"longitude":110.213799,"status":1}, +{"nid":1527,"parent_nid":13,"name":"Kota Surakarta","serial":3372,"type":2,"latitude":-7.5666667,"longitude":110.8166667,"status":1}, +{"nid":1528,"parent_nid":13,"name":"Kota Salatiga","serial":3373,"type":2,"latitude":-7.302328,"longitude":110.4729,"status":1}, +{"nid":1529,"parent_nid":13,"name":"Kota Semarang","serial":3374,"type":2,"latitude":-6.9666667,"longitude":110.4166667,"status":1}, +{"nid":1530,"parent_nid":13,"name":"Kota Pekalongan","serial":3375,"type":2,"latitude":-6.882887,"longitude":109.669998,"status":1}, +{"nid":1531,"parent_nid":13,"name":"Kota Tegal","serial":3376,"type":2,"latitude":-6.8666667,"longitude":109.1333333,"status":1}, +{"nid":1532,"parent_nid":14,"name":"Kabupaten Kulon Progo","serial":3401,"type":2,"latitude":-7.8266798,"longitude":110.1640846,"status":1}, +{"nid":1533,"parent_nid":14,"name":"Kabupaten Bantul","serial":3402,"type":2,"latitude":-7.8846111,"longitude":110.3341111,"status":1}, +{"nid":1534,"parent_nid":14,"name":"Kabupaten Gunung Kidul","serial":3403,"type":2,"latitude":-8.0305091,"longitude":110.6168921,"status":1}, +{"nid":1536,"parent_nid":14,"name":"Kota Yogyakarta","serial":3471,"type":2,"latitude":-7.797224,"longitude":110.368797,"status":1}, +{"nid":1537,"parent_nid":16,"name":"Kabupaten Pandeglang","serial":3601,"type":2,"latitude":-6.314835,"longitude":106.103897,"status":1}, +{"nid":1538,"parent_nid":16,"name":"Kabupaten Lebak","serial":3602,"type":2,"latitude":-6.5643956,"longitude":106.2522143,"status":1}, +{"nid":1539,"parent_nid":16,"name":"Kabupaten Tangerang","serial":3603,"type":2,"latitude":-6.1783056,"longitude":106.6318889,"status":1}, +{"nid":1540,"parent_nid":16,"name":"Kabupaten Serang","serial":3604,"type":2,"latitude":-6.12009,"longitude":106.150299,"status":1}, +{"nid":1541,"parent_nid":16,"name":"Kota Tangerang","serial":3671,"type":2,"latitude":-6.1783056,"longitude":106.6318889,"status":1}, +{"nid":1542,"parent_nid":16,"name":"Kota Cilegon","serial":3672,"type":2,"latitude":-6.0169825,"longitude":106.040506,"status":1}, +{"nid":1543,"parent_nid":16,"name":"Kota Serang","serial":3673,"type":2,"latitude":-6.12009,"longitude":106.150299,"status":1}, +{"nid":1544,"parent_nid":17,"name":"Kabupaten Jembrana","serial":5101,"type":2,"latitude":-8.361852,"longitude":114.6418,"status":1}, +{"nid":1545,"parent_nid":17,"name":"Kabupaten Tabanan","serial":5102,"type":2,"latitude":-8.544516,"longitude":115.119797,"status":1}, +{"nid":1546,"parent_nid":17,"name":"Kabupaten Badung","serial":5103,"type":2,"latitude":-8.5819296,"longitude":115.1770586,"status":1}, +{"nid":1547,"parent_nid":17,"name":"Kabupaten Gianyar","serial":5104,"type":2,"latitude":-8.544185,"longitude":115.3255,"status":1}, +{"nid":1548,"parent_nid":17,"name":"Kabupaten Klungkung","serial":5105,"type":2,"latitude":-8.5389222,"longitude":115.4045111,"status":1}, +{"nid":1549,"parent_nid":17,"name":"Kabupaten Bangli","serial":5106,"type":2,"latitude":-8.454303,"longitude":115.354897,"status":1}, +{"nid":1550,"parent_nid":17,"name":"Kabupaten Karang Asem","serial":5107,"type":2,"latitude":-6.3996057,"longitude":108.0503042,"status":1}, +{"nid":1551,"parent_nid":17,"name":"Kabupaten Buleleng","serial":5108,"type":2,"latitude":-8.113831,"longitude":115.126999,"status":1}, +{"nid":1552,"parent_nid":17,"name":"Kota Denpasar","serial":5171,"type":2,"latitude":-8.65629,"longitude":115.222099,"status":1}, +{"nid":1553,"parent_nid":18,"name":"Kabupaten Lombok Barat","serial":5201,"type":2,"latitude":-8.6464599,"longitude":116.1123078,"status":1}, +{"nid":1554,"parent_nid":18,"name":"Kabupaten Lombok Tengah","serial":5202,"type":2,"latitude":-8.694623,"longitude":116.2777073,"status":1}, +{"nid":1555,"parent_nid":18,"name":"Kabupaten Lombok Timur","serial":5203,"type":2,"latitude":-8.5134471,"longitude":116.5609857,"status":1}, +{"nid":1556,"parent_nid":18,"name":"Kabupaten Sumbawa","serial":5204,"type":2,"latitude":-8.6529334,"longitude":117.3616476,"status":1}, +{"nid":1557,"parent_nid":18,"name":"Kabupaten Dompu","serial":5205,"type":2,"latitude":-8.4966318,"longitude":118.4747173,"status":1}, +{"nid":1558,"parent_nid":18,"name":"Kabupaten Bima","serial":5206,"type":2,"latitude":-8.460566,"longitude":118.727402,"status":1}, +{"nid":1559,"parent_nid":18,"name":"Kabupaten Sumbawa Barat","serial":5207,"type":2,"latitude":-8.9292907,"longitude":116.8910342,"status":1}, +{"nid":1560,"parent_nid":18,"name":"Kota Mataram","serial":5271,"type":2,"latitude":-8.5833333,"longitude":116.1166667,"status":1}, +{"nid":1561,"parent_nid":18,"name":"Kota Bima","serial":5272,"type":2,"latitude":-8.460566,"longitude":118.727402,"status":1}, +{"nid":1562,"parent_nid":19,"name":"Kabupaten Sumba Barat","serial":5312,"type":2,"latitude":-9.6548326,"longitude":119.3947135,"status":1}, +{"nid":1563,"parent_nid":19,"name":"Kabupaten Sumba Timur","serial":5311,"type":2,"latitude":-9.9802103,"longitude":120.3435506,"status":1}, +{"nid":1564,"parent_nid":19,"name":"Kabupaten Kupang","serial":5301,"type":2,"latitude":-10.1833333,"longitude":123.5833333,"status":1}, +{"nid":1565,"parent_nid":19,"name":"Kabupaten Timor Tengah Selatan","serial":5302,"type":2,"latitude":-9.7762816,"longitude":124.4198243,"status":1}, +{"nid":1566,"parent_nid":19,"name":"Kabupaten Timor Tengah Utara","serial":5303,"type":2,"latitude":-9.4522647,"longitude":124.597132,"status":1}, +{"nid":1567,"parent_nid":19,"name":"Kabupaten Belu","serial":5304,"type":2,"latitude":-9.4125796,"longitude":124.9506625,"status":1}, +{"nid":1568,"parent_nid":19,"name":"Kabupaten Alor","serial":5305,"type":2,"latitude":-8.2754027,"longitude":124.7298765,"status":1}, +{"nid":1569,"parent_nid":19,"name":"Kabupaten Lembata","serial":5313,"type":2,"latitude":-8.4719075,"longitude":123.4831906,"status":1}, +{"nid":1570,"parent_nid":19,"name":"Kabupaten Flores Timur","serial":5306,"type":2,"latitude":-8.3130942,"longitude":122.9663018,"status":1}, +{"nid":1571,"parent_nid":19,"name":"Kabupaten Sikka","serial":5307,"type":2,"latitude":-8.6766175,"longitude":122.1291843,"status":1}, +{"nid":1572,"parent_nid":19,"name":"Kabupaten Ende","serial":5308,"type":2,"latitude":-8.854053,"longitude":121.654198,"status":1}, +{"nid":1573,"parent_nid":19,"name":"Kabupaten Ngada","serial":5309,"type":2,"latitude":-8.7430424,"longitude":120.9876321,"status":1}, +{"nid":1574,"parent_nid":19,"name":"Kabupaten Manggarai","serial":5310,"type":2,"latitude":-8.6796987,"longitude":120.3896651,"status":1}, +{"nid":1575,"parent_nid":19,"name":"Kabupaten Rote Ndao","serial":5314,"type":2,"latitude":-10.7386421,"longitude":123.1239049,"status":1}, +{"nid":1576,"parent_nid":19,"name":"Kabupaten Manggarai Barat","serial":5315,"type":2,"latitude":-8.6688149,"longitude":120.0665236,"status":1}, +{"nid":1577,"parent_nid":19,"name":"Kabupaten Sumba Tengah","serial":5317,"type":2,"latitude":-9.4879226,"longitude":119.6962677,"status":1}, +{"nid":1578,"parent_nid":19,"name":"Kabupaten Sumba Barat Daya","serial":5318,"type":2,"latitude":-9.539139,"longitude":119.1390642,"status":1}, +{"nid":1579,"parent_nid":19,"name":"Kabupaten Nagekeo","serial":5316,"type":2,"latitude":-8.6753545,"longitude":121.3084088,"status":1}, +{"nid":1580,"parent_nid":19,"name":"Kabupaten Manggarai Timur","serial":5319,"type":2,"latitude":-8.6206712,"longitude":120.6199895,"status":1}, +{"nid":1581,"parent_nid":19,"name":"Kota Kupang","serial":5371,"type":2,"latitude":-10.1833333,"longitude":123.5833333,"status":1}, +{"nid":1582,"parent_nid":20,"name":"Kabupaten Sambas","serial":6101,"type":2,"latitude":1.361328,"longitude":109.309998,"status":1}, +{"nid":1583,"parent_nid":20,"name":"Kabupaten Bengkayang","serial":6107,"type":2,"latitude":0.8209729,"longitude":109.477699,"status":1}, +{"nid":1584,"parent_nid":20,"name":"Kabupaten Landak","serial":6108,"type":2,"latitude":0.4237287,"longitude":109.7591675,"status":1}, +{"nid":1585,"parent_nid":20,"name":"Kabupaten Pontianak","serial":6102,"type":2,"latitude":-0.022523,"longitude":109.330307,"status":1}, +{"nid":1586,"parent_nid":20,"name":"Kabupaten Sanggau","serial":6103,"type":2,"latitude":0.119275,"longitude":110.597298,"status":1}, +{"nid":1587,"parent_nid":20,"name":"Kabupaten Ketapang","serial":6104,"type":2,"latitude":-1.859098,"longitude":109.971901,"status":1}, +{"nid":1588,"parent_nid":20,"name":"Kabupaten Sintang","serial":6105,"type":2,"latitude":0.080238,"longitude":111.495499,"status":1}, +{"nid":1589,"parent_nid":20,"name":"Kabupaten Kapuas Hulu","serial":6106,"type":2,"latitude":-0.7931004,"longitude":113.9060624,"status":1}, +{"nid":1590,"parent_nid":20,"name":"Kabupaten Sekadau","serial":6109,"type":2,"latitude":0.015637,"longitude":110.888603,"status":1}, +{"nid":1591,"parent_nid":20,"name":"Kabupaten Melawi","serial":6110,"type":2,"latitude":-0.7000681,"longitude":111.6660725,"status":1}, +{"nid":1592,"parent_nid":20,"name":"Kabupaten Kayong Utara","serial":6111,"type":2,"latitude":-0.9225877,"longitude":110.0449662,"status":1}, +{"nid":1593,"parent_nid":20,"name":"Kabupaten Kubu Raya","serial":6112,"type":2,"latitude":-0.3533938,"longitude":109.4735066,"status":1}, +{"nid":1594,"parent_nid":20,"name":"Kota Pontianak","serial":6171,"type":2,"latitude":-0.022523,"longitude":109.330307,"status":1}, +{"nid":1595,"parent_nid":20,"name":"Kota Singkawang","serial":6172,"type":2,"latitude":0.908795,"longitude":108.984596,"status":1}, +{"nid":1596,"parent_nid":21,"name":"Kabupaten Kotawaringin Barat","serial":6201,"type":2,"latitude":-6.1961131,"longitude":106.8630174,"status":1}, +{"nid":1597,"parent_nid":21,"name":"Kabupaten Kotawaringin Timur","serial":6202,"type":2,"latitude":-6.1952992,"longitude":106.8630737,"status":1}, +{"nid":1598,"parent_nid":21,"name":"Kabupaten Kapuas","serial":6203,"type":2,"latitude":-0.0459972,"longitude":110.1313251,"status":1}, +{"nid":1599,"parent_nid":21,"name":"Kabupaten Barito Selatan","serial":6204,"type":2,"latitude":-1.875943,"longitude":114.8092691,"status":1}, +{"nid":1600,"parent_nid":21,"name":"Kabupaten Barito Utara","serial":6205,"type":2,"latitude":-0.9587136,"longitude":115.094045,"status":1}, +{"nid":1601,"parent_nid":21,"name":"Kabupaten Sukamara","serial":6208,"type":2,"latitude":-2.6267517,"longitude":111.2368084,"status":1}, +{"nid":1602,"parent_nid":21,"name":"Kabupaten Lamandau","serial":6209,"type":2,"latitude":-1.9269166,"longitude":111.1891151,"status":1}, +{"nid":1603,"parent_nid":21,"name":"Kabupaten Seruyan","serial":6207,"type":2,"latitude":-3.0123467,"longitude":112.4291464,"status":1}, +{"nid":1604,"parent_nid":21,"name":"Kabupaten Katingan","serial":6206,"type":2,"latitude":-0.9758379,"longitude":112.8105512,"status":1}, +{"nid":1605,"parent_nid":21,"name":"Kabupaten Pulang Pisau","serial":6211,"type":2,"latitude":-2.6849607,"longitude":113.9536466,"status":1}, +{"nid":1606,"parent_nid":21,"name":"Kabupaten Gunung Mas","serial":6210,"type":2,"latitude":-6.7052778,"longitude":106.9913889,"status":1}, +{"nid":1607,"parent_nid":21,"name":"Kabupaten Barito Timur","serial":6213,"type":2,"latitude":-2.0123999,"longitude":115.188916,"status":1}, +{"nid":1608,"parent_nid":21,"name":"Kabupaten Murung Raya","serial":6212,"type":2,"latitude":-0.1362171,"longitude":114.3341432,"status":1}, +{"nid":1609,"parent_nid":21,"name":"Kota Palangka Raya","serial":6271,"type":2,"latitude":-2.21,"longitude":113.92,"status":1}, +{"nid":1610,"parent_nid":22,"name":"Kabupaten Tanah Laut","serial":6301,"type":2,"latitude":-3.7694047,"longitude":114.8092691,"status":1}, +{"nid":1611,"parent_nid":22,"name":"Kabupaten Kota Baru","serial":6302,"type":2,"latitude":-6.332973,"longitude":106.807915,"status":1}, +{"nid":1612,"parent_nid":22,"name":"Kabupaten Banjar","serial":6303,"type":2,"latitude":-7.3666667,"longitude":108.5333333,"status":1}, +{"nid":1613,"parent_nid":22,"name":"Kabupaten Barito Kuala","serial":6304,"type":2,"latitude":-3.0714738,"longitude":114.6667939,"status":1}, +{"nid":1614,"parent_nid":22,"name":"Kabupaten Tapin","serial":6305,"type":2,"latitude":-2.9160746,"longitude":115.0465991,"status":1}, +{"nid":1615,"parent_nid":22,"name":"Kabupaten Hulu Sungai Selatan","serial":6306,"type":2,"latitude":-2.7662681,"longitude":115.2363408,"status":1}, +{"nid":1616,"parent_nid":22,"name":"Kabupaten Hulu Sungai Tengah","serial":6307,"type":2,"latitude":-2.6153162,"longitude":115.5207358,"status":1}, +{"nid":1617,"parent_nid":22,"name":"Kabupaten Hulu Sungai Utara","serial":6308,"type":2,"latitude":-2.4421225,"longitude":115.188916,"status":1}, +{"nid":1618,"parent_nid":22,"name":"Kabupaten Tabalong","serial":6309,"type":2,"latitude":-1.864302,"longitude":115.5681084,"status":1}, +{"nid":1619,"parent_nid":22,"name":"Kabupaten Tanah Bumbu","serial":6310,"type":2,"latitude":-3.4512244,"longitude":115.5681084,"status":1}, +{"nid":1620,"parent_nid":22,"name":"Kabupaten Balangan","serial":6311,"type":2,"latitude":-2.3260425,"longitude":115.6154732,"status":1}, +{"nid":1621,"parent_nid":22,"name":"Kota Banjarmasin","serial":6371,"type":2,"latitude":-3.328499,"longitude":114.589203,"status":1}, +{"nid":1622,"parent_nid":22,"name":"Kota Banjar Baru","serial":6372,"type":2,"latitude":-3.4666667,"longitude":114.75,"status":1}, +{"nid":1623,"parent_nid":23,"name":"Kabupaten Paser","serial":6401,"type":2,"latitude":-1.7175266,"longitude":115.9467997,"status":1}, +{"nid":1624,"parent_nid":23,"name":"Kabupaten Kutai Barat","serial":6407,"type":2,"latitude":0.1353881,"longitude":115.094045,"status":1}, +{"nid":1625,"parent_nid":23,"name":"Kabupaten Kutai Kartanegara","serial":6402,"type":2,"latitude":-0.1336655,"longitude":116.6081653,"status":1}, +{"nid":1626,"parent_nid":23,"name":"Kabupaten Kutai Timur","serial":6408,"type":2,"latitude":0.9433774,"longitude":116.9852422,"status":1}, +{"nid":1627,"parent_nid":23,"name":"Kabupaten Berau","serial":6403,"type":2,"latitude":2.0450883,"longitude":117.3616476,"status":1}, +{"nid":1628,"parent_nid":23,"name":"Kabupaten Malinau","serial":6406,"type":2,"latitude":3.584221,"longitude":116.647797,"status":1}, +{"nid":1629,"parent_nid":23,"name":"Kabupaten Bulungan","serial":6404,"type":2,"latitude":2.9042476,"longitude":116.9852422,"status":1}, +{"nid":1630,"parent_nid":23,"name":"Kabupaten Nunukan","serial":6405,"type":2,"latitude":4.0609227,"longitude":117.666952,"status":1}, +{"nid":1631,"parent_nid":23,"name":"Kabupaten Penajam Paser Utara","serial":6409,"type":2,"latitude":-1.2917094,"longitude":116.5137964,"status":1}, +{"nid":1632,"parent_nid":23,"name":"Kabupaten Tana Tidung","serial":6410,"type":2,"latitude":3.551869,"longitude":117.0794082,"status":1}, +{"nid":1633,"parent_nid":23,"name":"Kota Balikpapan","serial":6471,"type":2,"latitude":-1.2635389,"longitude":116.8278833,"status":1}, +{"nid":1634,"parent_nid":23,"name":"Kota Samarinda","serial":6472,"type":2,"latitude":-0.502183,"longitude":117.153801,"status":1}, +{"nid":1635,"parent_nid":23,"name":"Kota Tarakan","serial":6473,"type":2,"latitude":3.3,"longitude":117.6333333,"status":1}, +{"nid":1636,"parent_nid":23,"name":"Kota Bontang","serial":6474,"type":2,"latitude":0.1333333,"longitude":117.5,"status":1}, +{"nid":1637,"parent_nid":24,"name":"Kabupaten Bolaang Mongondow","serial":7101,"type":2,"latitude":0.6870994,"longitude":124.0641419,"status":1}, +{"nid":1638,"parent_nid":24,"name":"Kabupaten Minahasa","serial":7102,"type":2,"latitude":1,"longitude":124.5833333,"status":1}, +{"nid":1639,"parent_nid":24,"name":"Kabupaten Kepulauan Sangihe","serial":7103,"type":2,"latitude":3.5303212,"longitude":125.5438967,"status":1}, +{"nid":1640,"parent_nid":24,"name":"Kabupaten Kepulauan Talaud","serial":7104,"type":2,"latitude":4.092,"longitude":126.768,"status":1}, +{"nid":1641,"parent_nid":24,"name":"Kabupaten Minahasa Selatan","serial":7105,"type":2,"latitude":1.0946773,"longitude":124.4641848,"status":1}, +{"nid":1642,"parent_nid":24,"name":"Kabupaten Minahasa Utara","serial":7106,"type":2,"latitude":1.5327973,"longitude":124.994751,"status":1}, +{"nid":1643,"parent_nid":24,"name":"Kabupaten Bolaang Mongondow Utara","serial":7108,"type":2,"latitude":0.818691,"longitude":123.5280072,"status":1}, +{"nid":1644,"parent_nid":24,"name":"Kabupaten Siau Tagulandang Biaro","serial":7109,"type":2,"latitude":2.345964,"longitude":125.4124355,"status":1}, +{"nid":1645,"parent_nid":24,"name":"Kabupaten Minahasa Tenggara","serial":7107,"type":2,"latitude":1.0278551,"longitude":124.7298765,"status":1}, +{"nid":1646,"parent_nid":24,"name":"Kota Manado","serial":7171,"type":2,"latitude":1.4917014,"longitude":124.842843,"status":1}, +{"nid":1647,"parent_nid":24,"name":"Kota Bitung","serial":7172,"type":2,"latitude":1.4553529,"longitude":125.204697,"status":1}, +{"nid":1648,"parent_nid":24,"name":"Kota Tomohon","serial":7173,"type":2,"latitude":1.3234131,"longitude":124.8384504,"status":1}, +{"nid":1649,"parent_nid":24,"name":"Kota Kotamobagu","serial":7174,"type":2,"latitude":0.7333333,"longitude":124.3166667,"status":1}, +{"nid":1650,"parent_nid":25,"name":"Kabupaten Banggai Kepulauan","serial":7207,"type":2,"latitude":-1.6408137,"longitude":123.5504076,"status":1}, +{"nid":1651,"parent_nid":25,"name":"Kabupaten Banggai","serial":7201,"type":2,"latitude":-1.6408137,"longitude":123.5504076,"status":1}, +{"nid":1652,"parent_nid":25,"name":"Kabupaten Morowali","serial":7206,"type":2,"latitude":-2.3003072,"longitude":121.5370003,"status":1}, +{"nid":1653,"parent_nid":25,"name":"Kabupaten Poso","serial":7202,"type":2,"latitude":-1.391922,"longitude":120.766998,"status":1}, +{"nid":1654,"parent_nid":25,"name":"Kabupaten Donggala","serial":7203,"type":2,"latitude":-0.4233155,"longitude":119.8352303,"status":1}, +{"nid":1655,"parent_nid":25,"name":"Kabupaten Toli-Toli","serial":7204,"type":2,"latitude":0.8768231,"longitude":120.7579834,"status":1}, +{"nid":1656,"parent_nid":25,"name":"Kabupaten Buol","serial":7205,"type":2,"latitude":0.9695452,"longitude":121.3541631,"status":1}, +{"nid":1657,"parent_nid":25,"name":"Kabupaten Parigi Moutong","serial":7208,"type":2,"latitude":0.5817607,"longitude":120.8039474,"status":1}, +{"nid":1658,"parent_nid":25,"name":"Kabupaten Tojo Una-Una","serial":7209,"type":2,"latitude":-1.098757,"longitude":121.5370003,"status":1}, +{"nid":1659,"parent_nid":25,"name":"Kota Palu","serial":7271,"type":2,"latitude":-0.898583,"longitude":119.850601,"status":1}, +{"nid":1660,"parent_nid":26,"name":"Kabupaten Selayar","serial":7301,"type":2,"latitude":-6,"longitude":120.5,"status":1}, +{"nid":1661,"parent_nid":26,"name":"Kabupaten Bulukumba","serial":7302,"type":2,"latitude":-5.4329368,"longitude":120.2051096,"status":1}, +{"nid":1662,"parent_nid":26,"name":"Kabupaten Bantaeng","serial":7303,"type":2,"latitude":-5.5169316,"longitude":120.0202964,"status":1}, +{"nid":1663,"parent_nid":26,"name":"Kabupaten Jeneponto","serial":7304,"type":2,"latitude":-5.554579,"longitude":119.6730939,"status":1}, +{"nid":1664,"parent_nid":26,"name":"Kabupaten Takalar","serial":7305,"type":2,"latitude":-5.4162493,"longitude":119.4875668,"status":1}, +{"nid":1665,"parent_nid":26,"name":"Kabupaten Gowa","serial":7306,"type":2,"latitude":-5.3102888,"longitude":119.742604,"status":1}, +{"nid":1666,"parent_nid":26,"name":"Kabupaten Sinjai","serial":7307,"type":2,"latitude":-5.2171961,"longitude":120.112735,"status":1}, +{"nid":1667,"parent_nid":26,"name":"Kabupaten Maros","serial":7309,"type":2,"latitude":-4.94695,"longitude":119.578903,"status":1}, +{"nid":1668,"parent_nid":26,"name":"Kabupaten Pangkajene Dan Kepulauan","serial":7310,"type":2,"latitude":-4.805035,"longitude":119.5571677,"status":1}, +{"nid":1669,"parent_nid":26,"name":"Kabupaten Barru","serial":7311,"type":2,"latitude":-4.4172651,"longitude":119.6730939,"status":1}, +{"nid":1670,"parent_nid":26,"name":"Kabupaten Bone","serial":7308,"type":2,"latitude":-2.083333,"longitude":120.216667,"status":1}, +{"nid":1671,"parent_nid":26,"name":"Kabupaten Soppeng","serial":7312,"type":2,"latitude":-4.3518541,"longitude":119.9277947,"status":1}, +{"nid":1672,"parent_nid":26,"name":"Kabupaten Wajo","serial":7313,"type":2,"latitude":-4.022229,"longitude":120.0665236,"status":1}, +{"nid":1673,"parent_nid":26,"name":"Kabupaten Sidenreng Rappang","serial":7314,"type":2,"latitude":-3.7738981,"longitude":120.0202964,"status":1}, +{"nid":1674,"parent_nid":26,"name":"Kabupaten Pinrang","serial":7315,"type":2,"latitude":-3.793071,"longitude":119.6408,"status":1}, +{"nid":1675,"parent_nid":26,"name":"Kabupaten Enrekang","serial":7316,"type":2,"latitude":-3.563128,"longitude":119.7612,"status":1}, +{"nid":1676,"parent_nid":26,"name":"Kabupaten Luwu","serial":7317,"type":2,"latitude":-3.3052214,"longitude":120.2512728,"status":1}, +{"nid":1677,"parent_nid":26,"name":"Kabupaten Tana Toraja","serial":7318,"type":2,"latitude":-3.0753003,"longitude":119.742604,"status":1}, +{"nid":1678,"parent_nid":26,"name":"Kabupaten Luwu Utara","serial":7322,"type":2,"latitude":-2.2690446,"longitude":119.9740534,"status":1}, +{"nid":1679,"parent_nid":26,"name":"Kabupaten Luwu Timur","serial":7324,"type":2,"latitude":-2.5825518,"longitude":121.1710389,"status":1}, +{"nid":1680,"parent_nid":26,"name":"Kota Makassar","serial":7371,"type":2,"latitude":-5.1333333,"longitude":119.4166667,"status":1}, +{"nid":1681,"parent_nid":26,"name":"Kota Pare-Pare","serial":7372,"type":2,"latitude":-4.0166667,"longitude":119.6236111,"status":1}, +{"nid":1682,"parent_nid":26,"name":"Kota Palopo","serial":7373,"type":2,"latitude":-3,"longitude":120.2,"status":1}, +{"nid":1683,"parent_nid":27,"name":"Kabupaten Buton","serial":7404,"type":2,"latitude":-5.3096355,"longitude":122.9888319,"status":1}, +{"nid":1684,"parent_nid":27,"name":"Kabupaten Muna","serial":7403,"type":2,"latitude":-4.901629,"longitude":122.6277455,"status":1}, +{"nid":1685,"parent_nid":27,"name":"Kabupaten Konawe","serial":7402,"type":2,"latitude":-3.9380432,"longitude":122.0837445,"status":1}, +{"nid":1686,"parent_nid":27,"name":"Kabupaten Kolaka","serial":7401,"type":2,"latitude":-4.049665,"longitude":121.593803,"status":1}, +{"nid":1687,"parent_nid":27,"name":"Kabupaten Konawe Selatan","serial":7405,"type":2,"latitude":-4.2027915,"longitude":122.4467238,"status":1}, +{"nid":1688,"parent_nid":27,"name":"Kabupaten Bombana","serial":7406,"type":2,"latitude":-4.6543462,"longitude":121.9017954,"status":1}, +{"nid":1689,"parent_nid":27,"name":"Kabupaten Wakatobi","serial":7407,"type":2,"latitude":-5.3264442,"longitude":123.5951925,"status":1}, +{"nid":1690,"parent_nid":27,"name":"Kabupaten Kolaka Utara","serial":7408,"type":2,"latitude":-3.1347227,"longitude":121.1710389,"status":1}, +{"nid":1691,"parent_nid":27,"name":"Kabupaten Buton Utara","serial":7410,"type":2,"latitude":-4.7023424,"longitude":123.0338767,"status":1}, +{"nid":1692,"parent_nid":27,"name":"Kabupaten Konawe Utara","serial":7409,"type":2,"latitude":-3.3803291,"longitude":122.0837445,"status":1}, +{"nid":1693,"parent_nid":27,"name":"Kota Kendari","serial":7471,"type":2,"latitude":-3.972201,"longitude":122.5149028,"status":1}, +{"nid":1694,"parent_nid":27,"name":"Kota Bau-Bau","serial":7472,"type":2,"latitude":-5.46667,"longitude":122.633,"status":1}, +{"nid":1695,"parent_nid":28,"name":"Kabupaten Boalemo","serial":7502,"type":2,"latitude":0.7013419,"longitude":122.2653887,"status":1}, +{"nid":1696,"parent_nid":28,"name":"Kabupaten Gorontalo","serial":7501,"type":2,"latitude":0.5333333,"longitude":123.0666667,"status":1}, +{"nid":1697,"parent_nid":28,"name":"Kabupaten Pohuwato","serial":7504,"type":2,"latitude":0.7055278,"longitude":121.7195459,"status":1}, +{"nid":1698,"parent_nid":28,"name":"Kabupaten Bone Bolango","serial":7503,"type":2,"latitude":0.5657885,"longitude":123.3486147,"status":1}, +{"nid":1699,"parent_nid":28,"name":"Kabupaten Gorontalo Utara","serial":7505,"type":2,"latitude":0.9252647,"longitude":122.4920088,"status":1}, +{"nid":1700,"parent_nid":28,"name":"Kota Gorontalo","serial":7571,"type":2,"latitude":0.5333333,"longitude":123.0666667,"status":1}, +{"nid":1701,"parent_nid":29,"name":"Kabupaten Majene","serial":7605,"type":2,"latitude":-3.0297251,"longitude":118.9062794,"status":1}, +{"nid":1702,"parent_nid":29,"name":"Kabupaten Polewali Mandar","serial":7604,"type":2,"latitude":-3.3419323,"longitude":119.1390642,"status":1}, +{"nid":1703,"parent_nid":29,"name":"Kabupaten Mamasa","serial":7603,"type":2,"latitude":-2.960135,"longitude":119.368202,"status":1}, +{"nid":1704,"parent_nid":29,"name":"Kabupaten Mamuju","serial":7602,"type":2,"latitude":-2.7293364,"longitude":118.9295737,"status":1}, +{"nid":1705,"parent_nid":29,"name":"Kabupaten Mamuju Utara","serial":7601,"type":2,"latitude":-1.5264542,"longitude":119.5107708,"status":1}, +{"nid":1706,"parent_nid":30,"name":"Kabupaten Maluku Tenggara Barat","serial":8103,"type":2,"latitude":-7.5322642,"longitude":131.3611121,"status":1}, +{"nid":1707,"parent_nid":30,"name":"Kabupaten Maluku Tenggara","serial":8102,"type":2,"latitude":-5.7512455,"longitude":132.7271587,"status":1}, +{"nid":1708,"parent_nid":30,"name":"Kabupaten Maluku Tengah","serial":8101,"type":2,"latitude":-3.0166501,"longitude":129.4864411,"status":1}, +{"nid":1709,"parent_nid":30,"name":"Kabupaten Buru Selatan","serial":8109,"type":2,"latitude":-3.3927754,"longitude":126.7819505,"status":1}, +{"nid":1710,"parent_nid":30,"name":"Kabupaten Kepulauan Aru","serial":8107,"type":2,"latitude":-6.1946502,"longitude":134.5501935,"status":1}, +{"nid":1711,"parent_nid":30,"name":"Kabupaten Seram Bagian Barat","serial":8106,"type":2,"latitude":-3.1271575,"longitude":128.4008357,"status":1}, +{"nid":1712,"parent_nid":30,"name":"Kabupaten Seram Bagian Timur","serial":8105,"type":2,"latitude":-3.4150761,"longitude":130.390488,"status":1}, +{"nid":1713,"parent_nid":30,"name":"Kota Ambon","serial":8171,"type":2,"latitude":-3.65607,"longitude":128.166419,"status":1}, +{"nid":1714,"parent_nid":30,"name":"KotaTual","serial":8172,"type":2,"latitude":-5.640851,"longitude":132.7475093,"status":1}, +{"nid":1715,"parent_nid":31,"name":"Kabupaten Halmahera Barat","serial":8201,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1716,"parent_nid":31,"name":"Kabupaten Halmahera Tengah","serial":8202,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1717,"parent_nid":31,"name":"Kabupaten Kepulauan Sula","serial":8205,"type":2,"latitude":-1.8666667,"longitude":125.3666667,"status":1}, +{"nid":1718,"parent_nid":31,"name":"Kabupaten Halmahera Selatan","serial":8204,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1719,"parent_nid":31,"name":"Kabupaten Halmahera Utara","serial":8203,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1720,"parent_nid":31,"name":"Kabupaten Halmahera Timur","serial":8206,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1721,"parent_nid":31,"name":"Kota Ternate","serial":8271,"type":2,"latitude":0.7833333,"longitude":127.3666667,"status":1}, +{"nid":1722,"parent_nid":31,"name":"Kota Tidore Kepulauan","serial":8272,"type":2,"latitude":0.6833333,"longitude":127.4,"status":1}, +{"nid":1723,"parent_nid":32,"name":"Kabupaten Fakfak","serial":9203,"type":2,"latitude":-2.885237,"longitude":132.2658282,"status":1}, +{"nid":1724,"parent_nid":32,"name":"Kabupaten Kaimana","serial":9208,"type":2,"latitude":-3.660925,"longitude":133.774506,"status":1}, +{"nid":1725,"parent_nid":32,"name":"Kabupaten Teluk Wondama","serial":9207,"type":2,"latitude":-2.8551699,"longitude":134.3236557,"status":1}, +{"nid":1726,"parent_nid":32,"name":"Kabupaten Teluk Bintuni","serial":9206,"type":2,"latitude":-1.9056848,"longitude":133.329466,"status":1}, +{"nid":1727,"parent_nid":32,"name":"Kabupaten Manokwari","serial":9202,"type":2,"latitude":-0.8614531,"longitude":134.0620421,"status":1}, +{"nid":1728,"parent_nid":32,"name":"Kabupaten Sorong Selatan","serial":9204,"type":2,"latitude":-0.8666667,"longitude":131.25,"status":1}, +{"nid":1729,"parent_nid":32,"name":"Kota Sorong","serial":9271,"type":2,"latitude":-0.8666667,"longitude":131.25,"status":1}, +{"nid":1730,"parent_nid":32,"name":"Kabupaten Raja Ampat","serial":9205,"type":2,"latitude":-1.0915151,"longitude":130.8778586,"status":1}, +{"nid":1731,"parent_nid":32,"name":"Kabupaten Sorong","serial":9201,"type":2,"latitude":-0.8666667,"longitude":131.25,"status":1}, +{"nid":1732,"parent_nid":33,"name":"Kabupaten Merauke","serial":9101,"type":2,"latitude":-8.4960406,"longitude":140.3945527,"status":1}, +{"nid":1733,"parent_nid":33,"name":"Kabupaten Jayawijaya","serial":9102,"type":2,"latitude":-4.0004481,"longitude":138.7995122,"status":1}, +{"nid":1734,"parent_nid":33,"name":"Kabupaten Jayapura","serial":9103,"type":2,"latitude":-2.533,"longitude":140.717,"status":1}, +{"nid":1735,"parent_nid":33,"name":"Kabupaten Nabire","serial":9104,"type":2,"latitude":-3.5095462,"longitude":135.7520985,"status":1}, +{"nid":1736,"parent_nid":33,"name":"Kabupaten Kepulauan Yapen","serial":9105,"type":2,"latitude":-1.7469359,"longitude":136.1709012,"status":1}, +{"nid":1737,"parent_nid":33,"name":"Kabupaten Biak Numfor","serial":9106,"type":2,"latitude":-1.0381022,"longitude":135.9800848,"status":1}, +{"nid":1738,"parent_nid":33,"name":"Kabupaten Paniai","serial":9108,"type":2,"latitude":-3.7876441,"longitude":136.3624686,"status":1}, +{"nid":1739,"parent_nid":33,"name":"Kabupaten Puncak Jaya","serial":9107,"type":2,"latitude":-4.0836111,"longitude":137.1847222,"status":1}, +{"nid":1740,"parent_nid":33,"name":"Kabupaten Mimika","serial":9109,"type":2,"latitude":-4.4553223,"longitude":137.1362125,"status":1}, +{"nid":1741,"parent_nid":33,"name":"Kabupaten Boven Digoel","serial":9116,"type":2,"latitude":-5.7400018,"longitude":140.3481835,"status":1}, +{"nid":1742,"parent_nid":33,"name":"Kabupaten Mappi","serial":9117,"type":2,"latitude":-7.102232,"longitude":139.396393,"status":1}, +{"nid":1743,"parent_nid":33,"name":"Kabupaten Asmat","serial":9118,"type":2,"latitude":-5.0573958,"longitude":138.3988186,"status":1}, +{"nid":1744,"parent_nid":33,"name":"Kabupaten Yahukimo","serial":9113,"type":2,"latitude":-4.4939717,"longitude":139.5279996,"status":1}, +{"nid":1745,"parent_nid":33,"name":"Kabupaten Pegunungan Bintang","serial":9112,"type":2,"latitude":-4.5589872,"longitude":140.5135589,"status":1}, +{"nid":1746,"parent_nid":33,"name":"Kabupaten Tolikara","serial":9114,"type":2,"latitude":-3.481132,"longitude":138.4787258,"status":1}, +{"nid":1747,"parent_nid":33,"name":"Kabupaten Sarmi","serial":9110,"type":2,"latitude":-1.868727,"longitude":138.743607,"status":1}, +{"nid":1748,"parent_nid":33,"name":"Kabupaten Keerom","serial":9111,"type":2,"latitude":-3.3449536,"longitude":140.7624493,"status":1}, +{"nid":1749,"parent_nid":33,"name":"Kabupaten Waropen","serial":9115,"type":2,"latitude":-2.8435717,"longitude":136.670534,"status":1}, +{"nid":1750,"parent_nid":33,"name":"Kabupaten Supiori","serial":9119,"type":2,"latitude":-0.7295099,"longitude":135.6385125,"status":1}, +{"nid":1751,"parent_nid":33,"name":"Kabupaten Mamberamo Raya","serial":9120,"type":2,"latitude":-2.5331255,"longitude":137.7637565,"status":1}, +{"nid":1752,"parent_nid":33,"name":"Kota Jayapura","serial":9171,"type":2,"latitude":-2.533,"longitude":140.717,"status":1}, +{"nid":1753,"parent_nid":2,"name":"Kabupaten Labuhanbatu Utara","serial":1223,"type":2,"latitude":2.3465638,"longitude":99.8124935,"status":1}, +{"nid":1754,"parent_nid":2,"name":"Kabupaten Labuhanbatu Selatan","serial":1222,"type":2,"latitude":1.8799353,"longitude":100.1703257,"status":1}, +{"nid":1756,"parent_nid":2,"name":"Kabupaten Nias Utara","serial":1224,"type":2,"latitude":1.1255279,"longitude":97.5247243,"status":1}, +{"nid":1757,"parent_nid":2,"name":"Kabupaten Nias Barat","serial":1225,"type":2,"latitude":1.1255279,"longitude":97.5247243,"status":1}, +{"nid":1758,"parent_nid":2,"name":"Kota Gunungsitoli","serial":1278,"type":2,"latitude":1.281964,"longitude":97.61594,"status":1}, +{"nid":1759,"parent_nid":4,"name":"Kabupaten Kepulauan Meranti","serial":1410,"type":2,"latitude":0.9208765,"longitude":102.6675575,"status":1}, +{"nid":1760,"parent_nid":5,"name":"Kota Sungai Penuh","serial":1572,"type":2,"latitude":-2.06314,"longitude":101.387199,"status":1}, +{"nid":1761,"parent_nid":7,"name":"Kabupaten Bengkulu Tengah","serial":1709,"type":2,"latitude":-3.7955556,"longitude":102.2591667,"status":1}, +{"nid":1762,"parent_nid":8,"name":"Kabupaten Tulangbawang Barat","serial":1806,"type":2,"latitude":-4.5256967,"longitude":105.0791228,"status":1}, +{"nid":1763,"parent_nid":8,"name":"Kabupaten Pringsewu","serial":1810,"type":2,"latitude":-5.3539884,"longitude":104.9622498,"status":1}, +{"nid":1764,"parent_nid":8,"name":"Kabupaten Mesuji","serial":1811,"type":2,"latitude":-4.0044783,"longitude":105.3131185,"status":1}, +{"nid":1765,"parent_nid":10,"name":"Kabupaten Lingga","serial":2104,"type":2,"latitude":-0.1627686,"longitude":104.6354631,"status":1}, +{"nid":1766,"parent_nid":10,"name":"Kabupaten Anambas","serial":2105,"type":2,"latitude":3.1055459,"longitude":105.6537231,"status":1}, +{"nid":1767,"parent_nid":14,"name":"Kabupaten Sleman","serial":3404,"type":2,"latitude":-7.716165,"longitude":110.335403,"status":1}, +{"nid":1768,"parent_nid":16,"name":"Kota Tangerang Selatan","serial":3674,"type":2,"latitude":-6.2888889,"longitude":106.7180556,"status":1}, +{"nid":1769,"parent_nid":18,"name":"Kabupaten Lombok Utara","serial":5208,"type":2,"latitude":-8.3739076,"longitude":116.2777073,"status":1}, +{"nid":1770,"parent_nid":19,"name":"Kabupaten Sabu Raijua","serial":5302,"type":2,"latitude":-10.5541116,"longitude":121.8334868,"status":1}, +{"nid":1771,"parent_nid":24,"name":"Kabupaten Bolang Mongondow Timur","serial":7110,"type":2,"latitude":0.7152651,"longitude":124.4641848,"status":1}, +{"nid":1772,"parent_nid":24,"name":"Kabupaten Bolang Mongondow Selatan","serial":7111,"type":2,"latitude":0.4053215,"longitude":123.8411288,"status":1}, +{"nid":1773,"parent_nid":25,"name":"Kabupaten Sigi","serial":7210,"type":2,"latitude":-1.3834127,"longitude":120.0665236,"status":1}, +{"nid":1774,"parent_nid":26,"name":"Kabupaten Toraja Utara","serial":7326,"type":2,"latitude":-2.8621942,"longitude":119.8352303,"status":1}, +{"nid":1775,"parent_nid":30,"name":"Kabupaten Maluku Barat Daya","serial":8108,"type":2,"latitude":-7.7851588,"longitude":126.3498097,"status":1}, +{"nid":1776,"parent_nid":30,"name":"Kabupaten Buru","serial":8104,"type":2,"latitude":-3.3927754,"longitude":126.7819505,"status":1}, +{"nid":1778,"parent_nid":31,"name":"Kabupaten Pulau Morota","serial":8207,"type":2,"latitude":2.3656672,"longitude":128.4008357,"status":1}, +{"nid":1789,"parent_nid":32,"name":"Kabupaten Tambrauw","serial":9209,"type":2,"latitude":-0.781856,"longitude":132.3938375,"status":1}, +{"nid":1790,"parent_nid":32,"name":"Kabupaten Maybat","serial":9210,"type":2,"latitude":3.1472,"longitude":101.6997,"status":1}, +{"nid":1791,"parent_nid":33,"name":"Kabupaten Memberamo Tengah","serial":9121,"type":2,"latitude":-2.3745692,"longitude":138.3190276,"status":1}, +{"nid":1792,"parent_nid":33,"name":"Kabupaten Yalimo","serial":9122,"type":2,"latitude":-3.7852847,"longitude":139.4466005,"status":1}, +{"nid":1793,"parent_nid":33,"name":"Kabupaten Lanny Jaya","serial":9123,"type":2,"latitude":-3.971033,"longitude":138.3190276,"status":1}, +{"nid":1794,"parent_nid":33,"name":"Kabupaten Nduga","serial":9124,"type":2,"latitude":-4.4069496,"longitude":138.2393528,"status":1}, +{"nid":1795,"parent_nid":33,"name":"Kabupaten Puncak","serial":9125,"type":2,"latitude":-6.7125476,"longitude":106.9542425,"status":1}, +{"nid":1796,"parent_nid":33,"name":"Kabupaten Dogiyai","serial":9126,"type":2,"latitude":-4.0193872,"longitude":135.9610446,"status":1}, +{"nid":1797,"parent_nid":33,"name":"Kabupaten Intan Jaya","serial":9127,"type":2,"latitude":-3.5076422,"longitude":136.7478493,"status":1}, +{"nid":1798,"parent_nid":33,"name":"Kabupaten Deiyai","serial":9128,"type":2,"latitude":-4.0974893,"longitude":136.4393054,"status":1} +] \ No newline at end of file diff --git a/bin/androgpio/mycodes.class b/bin/androgpio/mycodes.class new file mode 100644 index 0000000..7a25654 Binary files /dev/null and b/bin/androgpio/mycodes.class differ diff --git a/bin/androgpio/networkrelated/IfConfigResult.class b/bin/androgpio/networkrelated/IfConfigResult.class new file mode 100644 index 0000000..27e3426 Binary files /dev/null and b/bin/androgpio/networkrelated/IfConfigResult.class differ diff --git a/bin/androgpio/networkrelated/ethernetmanager$1.class b/bin/androgpio/networkrelated/ethernetmanager$1.class new file mode 100644 index 0000000..a81ec18 Binary files /dev/null and b/bin/androgpio/networkrelated/ethernetmanager$1.class differ diff --git a/bin/androgpio/networkrelated/ethernetmanager$2.class b/bin/androgpio/networkrelated/ethernetmanager$2.class new file mode 100644 index 0000000..3d74d1e Binary files /dev/null and b/bin/androgpio/networkrelated/ethernetmanager$2.class differ diff --git a/bin/androgpio/networkrelated/ethernetmanager.class b/bin/androgpio/networkrelated/ethernetmanager.class new file mode 100644 index 0000000..356314c Binary files /dev/null and b/bin/androgpio/networkrelated/ethernetmanager.class differ diff --git a/bin/androgpio/openweather/OpenWeather.class b/bin/androgpio/openweather/OpenWeather.class new file mode 100644 index 0000000..e19b9fc Binary files /dev/null and b/bin/androgpio/openweather/OpenWeather.class differ diff --git a/bin/androgpio/openweather/OpenWeatherData.class b/bin/androgpio/openweather/OpenWeatherData.class new file mode 100644 index 0000000..1ef74e1 Binary files /dev/null and b/bin/androgpio/openweather/OpenWeatherData.class differ diff --git a/bin/androgpio/openweather/OpenWeatherEvent.class b/bin/androgpio/openweather/OpenWeatherEvent.class new file mode 100644 index 0000000..ffc056c Binary files /dev/null and b/bin/androgpio/openweather/OpenWeatherEvent.class differ diff --git a/bin/androgpio/openweather/jsondata/clouds.class b/bin/androgpio/openweather/jsondata/clouds.class new file mode 100644 index 0000000..7ee77b1 Binary files /dev/null and b/bin/androgpio/openweather/jsondata/clouds.class differ diff --git a/bin/androgpio/openweather/jsondata/coord.class b/bin/androgpio/openweather/jsondata/coord.class new file mode 100644 index 0000000..205c768 Binary files /dev/null and b/bin/androgpio/openweather/jsondata/coord.class differ diff --git a/bin/androgpio/openweather/jsondata/main.class b/bin/androgpio/openweather/jsondata/main.class new file mode 100644 index 0000000..e701595 Binary files /dev/null and b/bin/androgpio/openweather/jsondata/main.class differ diff --git a/bin/androgpio/openweather/jsondata/rain.class b/bin/androgpio/openweather/jsondata/rain.class new file mode 100644 index 0000000..735171b Binary files /dev/null and b/bin/androgpio/openweather/jsondata/rain.class differ diff --git a/bin/androgpio/openweather/jsondata/snow.class b/bin/androgpio/openweather/jsondata/snow.class new file mode 100644 index 0000000..1e429bf Binary files /dev/null and b/bin/androgpio/openweather/jsondata/snow.class differ diff --git a/bin/androgpio/openweather/jsondata/sys.class b/bin/androgpio/openweather/jsondata/sys.class new file mode 100644 index 0000000..6abf2a4 Binary files /dev/null and b/bin/androgpio/openweather/jsondata/sys.class differ diff --git a/bin/androgpio/openweather/jsondata/weather.class b/bin/androgpio/openweather/jsondata/weather.class new file mode 100644 index 0000000..d99c0d0 Binary files /dev/null and b/bin/androgpio/openweather/jsondata/weather.class differ diff --git a/bin/androgpio/openweather/jsondata/wind.class b/bin/androgpio/openweather/jsondata/wind.class new file mode 100644 index 0000000..da8e27b Binary files /dev/null and b/bin/androgpio/openweather/jsondata/wind.class differ diff --git a/bin/devices/Button$1.class b/bin/devices/Button$1.class new file mode 100644 index 0000000..f8e14be Binary files /dev/null and b/bin/devices/Button$1.class differ diff --git a/bin/devices/Button$2.class b/bin/devices/Button$2.class new file mode 100644 index 0000000..71ee89b Binary files /dev/null and b/bin/devices/Button$2.class differ diff --git a/bin/devices/Button.class b/bin/devices/Button.class new file mode 100644 index 0000000..284892c Binary files /dev/null and b/bin/devices/Button.class differ diff --git a/bin/devices/Buzzer$1.class b/bin/devices/Buzzer$1.class new file mode 100644 index 0000000..0c39e78 Binary files /dev/null and b/bin/devices/Buzzer$1.class differ diff --git a/bin/devices/Buzzer$2.class b/bin/devices/Buzzer$2.class new file mode 100644 index 0000000..822fb84 Binary files /dev/null and b/bin/devices/Buzzer$2.class differ diff --git a/bin/devices/Buzzer$3.class b/bin/devices/Buzzer$3.class new file mode 100644 index 0000000..51ee8b9 Binary files /dev/null and b/bin/devices/Buzzer$3.class differ diff --git a/bin/devices/Buzzer$4.class b/bin/devices/Buzzer$4.class new file mode 100644 index 0000000..1c9e6fa Binary files /dev/null and b/bin/devices/Buzzer$4.class differ diff --git a/bin/devices/Buzzer.class b/bin/devices/Buzzer.class new file mode 100644 index 0000000..04760f2 Binary files /dev/null and b/bin/devices/Buzzer.class differ diff --git a/bin/devices/PCF8574$1.class b/bin/devices/PCF8574$1.class new file mode 100644 index 0000000..f5c3020 Binary files /dev/null and b/bin/devices/PCF8574$1.class differ diff --git a/bin/devices/PCF8574.class b/bin/devices/PCF8574.class new file mode 100644 index 0000000..76a23cd Binary files /dev/null and b/bin/devices/PCF8574.class differ diff --git a/bin/devices/PublicIPChecker$1.class b/bin/devices/PublicIPChecker$1.class new file mode 100644 index 0000000..e5c4da9 Binary files /dev/null and b/bin/devices/PublicIPChecker$1.class differ diff --git a/bin/devices/PublicIPChecker.class b/bin/devices/PublicIPChecker.class new file mode 100644 index 0000000..7707d2b Binary files /dev/null and b/bin/devices/PublicIPChecker.class differ diff --git a/bin/devices/Relay$1.class b/bin/devices/Relay$1.class new file mode 100644 index 0000000..10417e8 Binary files /dev/null and b/bin/devices/Relay$1.class differ diff --git a/bin/devices/Relay.class b/bin/devices/Relay.class new file mode 100644 index 0000000..737029a Binary files /dev/null and b/bin/devices/Relay.class differ diff --git a/lib/arm64-v8a/libjnidispatch.so b/lib/arm64-v8a/libjnidispatch.so new file mode 100644 index 0000000..874e8c3 Binary files /dev/null and b/lib/arm64-v8a/libjnidispatch.so differ diff --git a/lib/armeabi-v7a/libjnidispatch.so b/lib/armeabi-v7a/libjnidispatch.so new file mode 100644 index 0000000..a7202b6 Binary files /dev/null and b/lib/armeabi-v7a/libjnidispatch.so differ diff --git a/lib/armeabi/libjnidispatch.so b/lib/armeabi/libjnidispatch.so new file mode 100644 index 0000000..24ec174 Binary files /dev/null and b/lib/armeabi/libjnidispatch.so differ diff --git a/lib/x86/libjnidispatch.so b/lib/x86/libjnidispatch.so new file mode 100644 index 0000000..1ca3403 Binary files /dev/null and b/lib/x86/libjnidispatch.so differ diff --git a/lib/x86_64/libjnidispatch.so b/lib/x86_64/libjnidispatch.so new file mode 100644 index 0000000..2162836 Binary files /dev/null and b/lib/x86_64/libjnidispatch.so differ diff --git a/libs/classes.jar b/libs/classes.jar new file mode 100644 index 0000000..ed040a9 Binary files /dev/null and b/libs/classes.jar differ diff --git a/src/SBC/Amlogic/T982.java b/src/SBC/Amlogic/T982.java new file mode 100644 index 0000000..e899efd --- /dev/null +++ b/src/SBC/Amlogic/T982.java @@ -0,0 +1,27 @@ +package SBC.Amlogic; + +import SBC.BasicSBCInfo; +import anywheresoftware.b4a.BA; + +@BA.ShortName("IFPD_T982") +public class T982 extends BasicSBCInfo { + + public final String eth_name = "eth0"; + + public T982() { + super(); + } + + + @Override + @BA.Hide + public double Get_CPU_CoreVolt() { + // No way to get CPU Core Volt + return 0; + } + + public void Initialize(BA bax, Object callerobject, String event) { + setup_events(bax, callerobject, event, this); + + } +} diff --git a/src/SBC/AndroidNTP.java b/src/SBC/AndroidNTP.java new file mode 100644 index 0000000..bd00390 --- /dev/null +++ b/src/SBC/AndroidNTP.java @@ -0,0 +1,48 @@ +package SBC; + +import java.util.TimeZone; + +import android.app.AlarmManager; +import android.content.Context; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.List; + +//@BA.ShortName("AndroidNTP") +//@BA.Permissions(values={"android.permission.SET_TIME", "android.permission.SET_TIME_ZONE"}) + +@SuppressWarnings("unused") +public class AndroidNTP { + + private BA ba; + private String eventName; + private Object Me; + + public void Initialize(BA ba, String EventName) { + this.ba = ba; + this.eventName = EventName.toLowerCase(BA.cul); + Me = this; + } + + @BA.Hide + public void SetDateTime(String NTPServer) { + android.provider.Settings.Global.putString(ba.context.getContentResolver(), "ntp_server", NTPServer); + android.provider.Settings.Global.putInt(ba.context.getContentResolver(), "auto_time", 0); + android.provider.Settings.Global.putInt(ba.context.getContentResolver(), "auto_time_zone", 0); + } + + public List GetTimeZones() { + List result = new List(); + result.Initialize(); + String[] tzid = java.util.TimeZone.getAvailableIDs(); + for (String tz : tzid) { + result.Add(TimeZone.getTimeZone(tz).getDisplayName()); + } + return result; + } + + public void XX() { + AlarmManager am = (AlarmManager) ba.context.getSystemService(Context.ALARM_SERVICE); + + + } +} diff --git a/src/SBC/BasicSBCInfo.java b/src/SBC/BasicSBCInfo.java new file mode 100644 index 0000000..dd39838 --- /dev/null +++ b/src/SBC/BasicSBCInfo.java @@ -0,0 +1,1918 @@ +package SBC; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.sun.jna.Platform; + +import androgpio.mycodes; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.media.AudioManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.PowerManager; +import android.os.SystemClock; +import android.view.PixelCopy; +import android.view.PixelCopy.OnPixelCopyFinishedListener; +import android.view.SurfaceView; +import android.view.View; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.keywords.DateTime; +import anywheresoftware.b4a.keywords.Regex; +import anywheresoftware.b4a.objects.collections.List; +import anywheresoftware.b4a.objects.collections.Map; + + +@BA.Events(values= { + "log(msg as string)", + "isvalidip(toip as string, isvalid as boolean)", + "isreachableip(toip as string, isreachable as boolean)", + "networkstatistic(value as NetworkMeterInfo)", + "cpuusage(value as Map)", + "screenshotfile(filename as string, success as boolean)", + "screenshotbase64(base64 as string, success as boolean)" +}) + +@BA.Permissions(values = { + "android.permission.REBOOT", + "android.permission.DEVICE_POWER" +}) + +public abstract class BasicSBCInfo { + private final String sysclassnet = "/sys/class/net/"; + private final String sysclasshwmon = "/sys/class/hwmon/"; + private boolean need_log_event = false; + private boolean need_isvalidip_event = false; + private boolean need_isreachableip_event = false; + private boolean need_networkstatistic_event = false; + private boolean need_cpuusage_event = false; + private Object caller; + protected String eventname; + protected BA ba; + protected Object Me; + private String rundir = ""; + protected BasicSBCInfo(){ + + File ff = new File(""); + rundir = ff.getAbsolutePath(); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + Stop_CPU_Usage_Monitoring(); + Stop_Network_Meter(); + } + }); + + + } + + public abstract double Get_CPU_CoreVolt(); + + + protected void setup_events(final BA bax, final Object callerobject, final String event, final Object Sender) { + ba = bax; + caller = callerobject; + eventname = event; + Me = Sender; + if (ba instanceof BA) { + if (caller != null) { + if (eventname instanceof String) { + if (!eventname.isEmpty()) { + if (Me != null) { + need_isvalidip_event = ba.subExists(eventname + "_isvalidip"); + need_log_event = ba.subExists(eventname + "_log"); + need_isreachableip_event = ba.subExists(eventname + "_isreachableip"); + need_networkstatistic_event = ba.subExists(eventname+"_networkstatistic"); + need_cpuusage_event = ba.subExists(eventname+"_cpuusage"); + } + } + } + } + } + } + + + + + /** + * Read All Lines from a path + * @param path path to read + * @return null if failed + */ + private String[] ReadLines(String path){ + if (valid_string(path)) { + try { + File xx = new File(path); + if (xx.exists() && xx.isFile() && xx.canRead()) { + BufferedReader reader = new BufferedReader(new FileReader(path)); + String[] result = reader.lines().toArray(String[]::new); + reader.close(); + return result; + } else BA.Log("ReadLines failed, path is invalid or not readable"); + } catch(NullPointerException e) { + BA.Log("ReadLines failed on path "+path+", Msg : "+e.getMessage()); + } catch(SecurityException e) { + BA.Log("ReadLines failed on path "+path+", Msg : "+e.getMessage()); + } catch (FileNotFoundException e) { + BA.Log("ReadLines failed on path "+path+", Msg : "+e.getMessage()); + } catch (IOException e) { + BA.Log("ReadLines failed on path "+path+", Msg : "+e.getMessage()); + } + + } else BA.Log("ReadLines failed, path is invalid"); + return null; + } + + private String[] Shell(String cmd) { + if (valid_string(cmd)) { + try { + + Process p = Runtime.getRuntime().exec(cmd); + BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); + String[] result = br.lines().toArray(String[]::new); + br.close(); + return result; + } catch (IOException e) { + BA.LogError("Failed executing shell, exception = "+e.getMessage()); + } + + } + return null; + } + + /** + * Take Screenshot of a SurfaceView and convert to Base64 + * on finish, will raise event screenshotbase64(base64 as string, success as boolean) + * @param view SurfaceView + * @param compressionformat "PNG" / "JPEG" / "WEBP", default to "PNG" if invalid + * @param quality 0 - 100, default to 100 + * @return Object to use in Wait For + */ + public Object SurfaceView_Screenshot_toBase64(BA bax, SurfaceView view, final String compressionformat, final int quality) { + Object sender = new Object(); + final String event = eventname + "_screenshotbase64"; + final boolean need_event = bax.subExists(event); + + if (view != null) { + + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); + + HandlerThread handlerThread = new HandlerThread("View_Screenshot_toBase64"); + handlerThread.start(); + + PixelCopy.request(view, bitmap, new OnPixelCopyFinishedListener() { + + @SuppressWarnings("deprecation") + @Override + public void onPixelCopyFinished(int copyResult) { + if (copyResult == PixelCopy.SUCCESS) { + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + switch(compressionformat) { + case "JPEG": + case "JPG" : + bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos); + break; + case "WEBP": + bitmap.compress(Bitmap.CompressFormat.WEBP, quality, baos); + break; + default: + bitmap.compress(Bitmap.CompressFormat.PNG, quality, baos); + break; + } + + byte[] b = baos.toByteArray(); + baos.close(); + + String base64 = android.util.Base64.encodeToString(b, android.util.Base64.DEFAULT); + if (need_event) + bax.raiseEventFromDifferentThread(sender, null, 0, event, + false, new Object[] { base64, true }); + handlerThread.quitSafely(); + return; + } catch (IOException e) { + raise_log("View_Screenshot_toBase64 failed, Msg : " + e.getMessage()); + } + + } else { + String msg = ""; + switch (copyResult) { + case PixelCopy.ERROR_DESTINATION_INVALID: + msg = "ERROR_DESTINATION_INVALID"; + break; + case PixelCopy.ERROR_SOURCE_INVALID: + msg = "ERROR_SOURCE_INVALID"; + break; + case PixelCopy.ERROR_SOURCE_NO_DATA: + msg = "ERROR_SOURCE_NO_DATA"; + break; + case PixelCopy.ERROR_TIMEOUT: + msg = "ERROR_TIMEOUT"; + break; + case PixelCopy.ERROR_UNKNOWN: + msg = "ERROR_UNKNOWN"; + break; + } + raise_log("View_Screenshot_toBase64 failed, Msg : " + msg); + } + if (need_event) + ba.raiseEventFromDifferentThread(sender, null, 0, event, false, + new Object[] { "", false }); + handlerThread.quitSafely(); + } + + }, new Handler(handlerThread.getLooper())); + + } else { + raise_log("View_Screenshot_toBase64 failed, View is null"); + if (need_event) + bax.raiseEventFromDifferentThread(sender, null, 0, event, false, + new Object[] { "", false }); + } + + + + return sender; + + } + + /** + * Take Screenshot of a SurfaceView and save to file + * on finish, will raise event screenshotfile(filename as string, success as boolean) + * @param view SurfaceView or Textureview to take screenshot + * @param filename destination filename + * @param compressionformat "PNG" / "JPEG" / "WEBP", default to "PNG" if invalid + * @param quality 0 - 100, default to 100 + */ + public void SurfaceView_Screenshot_toFile(SurfaceView view, String filename, final String compressionformat, final int quality) { + if (ba!=null) { + final boolean need_event = ba.subExists(eventname + "_screenshotfile"); + if (view!=null) { + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); + HandlerThread handlerThread = new HandlerThread("View_Screenshot_toFile"); + handlerThread.start(); + + PixelCopy.request(view, bitmap, new OnPixelCopyFinishedListener() { + + @SuppressWarnings("deprecation") + @Override + public void onPixelCopyFinished(int copyResult) { + if (copyResult==PixelCopy.SUCCESS) { + + try { + FileOutputStream fos = new FileOutputStream(filename); + switch (compressionformat) { + case "JPEG": + case "JPG": + bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos); + break; + case "WEBP": + bitmap.compress(Bitmap.CompressFormat.WEBP, quality, fos); + break; + default: + bitmap.compress(Bitmap.CompressFormat.PNG, quality, fos); + break; + } + + fos.flush(); + fos.close(); + handlerThread.quitSafely(); + if (need_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname + "_screenshotfile", false, new Object[] { filename, true }); + return; + } catch (FileNotFoundException e) { + raise_log("View_Screenshot_toFile failed, Msg : "+e.getMessage()); + } catch (IOException e) { + raise_log("View_Screenshot_toFile failed, Msg : "+e.getMessage()); + } + + + } else { + String msg = ""; + switch(copyResult) { + case PixelCopy.ERROR_DESTINATION_INVALID: + msg = "ERROR_DESTINATION_INVALID"; + break; + case PixelCopy.ERROR_SOURCE_INVALID: + msg = "ERROR_SOURCE_INVALID"; + break; + case PixelCopy.ERROR_SOURCE_NO_DATA: + msg = "ERROR_SOURCE_NO_DATA"; + break; + case PixelCopy.ERROR_TIMEOUT: + msg = "ERROR_TIMEOUT"; + break; + case PixelCopy.ERROR_UNKNOWN: + msg = "ERROR_UNKNOWN"; + break; + } + raise_log("View_Screenshot_toFile failed, Msg : "+msg); + } + handlerThread.quitSafely(); + if (need_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname + "_screenshotfile", false, new Object[] { filename, false }); + } + + }, new Handler(handlerThread.getLooper())); + + } else raise_log("View_Screenshot_toFile failed, View is null"); + if (need_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname + "_screenshotfile", false, new Object[] { filename, false }); + } + } + + /** + * Take screenshot from Activity and return base64 string + * will raise event screenshotbase64(base64 as string, success as boolean) + * @return Object to use in Wait For + */ + public Object Activity_Screenshot_toBase64(BA bax, String compressionformat, int quality) { + View activityview = bax.activity.getWindow().getDecorView(); + Rect frame = new Rect(); + activityview.getWindowVisibleDisplayFrame(frame); + return View_Screenshot_toBase64(bax, activityview, compressionformat, quality); + } + + /** + * Take screenshot from Activity and save to file + * @param filename : destination filename + * @param compressionformat "PNG" / "JPEG" / "WEBP", default to "PNG" if invalid + * @param quality 0 - 100, default to 100 + * @return Object to use in Wait For + */ + public Object Activity_Screenshot_toFile(BA bax, String filename, String compressionformat, int quality) { + View activityview = bax.activity.getWindow().getDecorView(); + Rect frame = new Rect(); + activityview.getWindowVisibleDisplayFrame(frame); + return View_Screenshot_toFile(bax, activityview, filename, compressionformat, quality); + } + + /** + * Take screenshot from normal Android View and return base64 string + * will raise event screenshotbase64(base64 as string, success as boolean) + * @param view View to take screenshot + * @return Object to use in Wait For + */ + public Object View_Screenshot_toBase64(BA bax, View view, final String compressionformat, final int quality) { + Object sender = new Object(); + final String event = eventname + "_screenshotbase64"; + final boolean need_event = bax.subExists(event); + BA.submitRunnable(new Runnable() { + + @SuppressWarnings("deprecation") + @Override + public void run() { + if (view!=null) { + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + switch(compressionformat) { + case "JPEG": + case "JPG": + bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos); + break; + case "WEBP": + bitmap.compress(Bitmap.CompressFormat.WEBP, quality, baos); + break; + default: + bitmap.compress(Bitmap.CompressFormat.PNG, quality, baos); + break; + } + byte[] b = baos.toByteArray(); + String base64 = android.util.Base64.encodeToString(b, android.util.Base64.DEFAULT); + if (need_event) bax.raiseEventFromDifferentThread(sender, null, 0, event, false, new Object[] { base64, true }); + baos.close(); + } catch(IOException e) { + raise_log("View_Screenshot_toBase64 failed, Msg : "+e.getMessage()); + if (need_event) bax.raiseEventFromDifferentThread(sender, null, 0, event, false, new Object[] { "", false }); + + } + + + } else { + raise_log("View_Screenshot_toBase64 failed, View is null"); + if (need_event) bax.raiseEventFromDifferentThread(sender, null, 0, event, false, new Object[] { "", false }); + } + + } + + }, null, 0); + + return sender; + } + + + /** + * Take screenshot and save to file + * on finish, will raise event screenshotfile(filename as string, success as boolean) + * Only works on normal view, not on SurfaceView or GLSurfaceView or VideoView + * @param filename : destination filename in PNG extension + */ + public Object View_Screenshot_toFile(BA bax, View view, String filename, String compressionformat, int quality) { + Object sender = new Object(); + final String event = eventname + "_screenshotfile"; + final boolean need_event = ba.subExists(event); + BA.submitRunnable(new Runnable() { + @SuppressWarnings("deprecation") + @Override + public void run() { + if (view!=null) { + //raise_log("Screenshot_toFile, width="+view.getWidth()+", height="+view.getHeight()); + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + + try { + FileOutputStream fos = new FileOutputStream(filename); + switch (compressionformat) { + case "JPEG": + case "JPG": + bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos); + break; + case "WEBP": + bitmap.compress(Bitmap.CompressFormat.WEBP, quality, fos); + break; + default: + bitmap.compress(Bitmap.CompressFormat.PNG, quality, fos); + break; + } + + fos.flush(); + fos.close(); + if (need_event) ba.raiseEventFromDifferentThread(sender, null, 0, eventname+"_screenshotfile", false, new Object[] {filename, true}); + return; + } catch (FileNotFoundException e) { + raise_log("Screenshot_toFile failed, Msg : "+e.getMessage()); + } catch (IOException e) { + raise_log("Screenshot_toFile failed, Msg : "+e.getMessage()); + } + + } else raise_log("Screenshot_toFile failed, View is null"); + + if (need_event) ba.raiseEventFromDifferentThread(sender, null, 0, eventname+"_screenshotfile", false, new Object[] {filename, false}); + + }; + }, null, 0); + return sender; + + + } + + /** + * Get CPU Info + * @return CPU Info object + */ + public CPU_Info Get_CPUINFO() { + CPU_Info result = new CPU_Info(ReadLines("/proc/cpuinfo")); + return result; + } + + + /** + * Same as Get_MemoryInfo, but with dedicated RAM_Info class + * @return RAM_Info class, or null if invalid + */ + public RAM_Info Get_RAMInfo() { + RAM_Info result = new RAM_Info(ReadLines("/proc/meminfo")); + return result; + + } + + /** + * Get NetworkInterface Info from specific interface name + * @param interfacename Interface name + * @return null if not available + */ + public NetworkInterface_info Get_NetworkInterface(String interfacename) { + NetworkInterface ni; + try { + ni = NetworkInterface.getByName(interfacename); + if (ni!=null) { + return new NetworkInterface_info(ba,ni); + } + } catch (SocketException e) { + } + return null; + } + + + /** + * Same as Get_StorageInfo, but with dedicated Storage_Info class + * @param folderpath : path to check + * @return Storage_Info class, or null if failed + */ + public Storage_Info Get_StorageInformation(String folderpath) { + Map vv = Get_StorageInfo(folderpath); + if (vv instanceof Map) { + if (vv.IsInitialized()) { + if (vv.getSize()>0) { + Storage_Info result = new Storage_Info(vv); + return result; + } + } + } + return null; + } + + /** + * Get Storage Info + * Map will contain keys : + * key = "Path" , requested path + * key = "Total" , value (Double) in bytes + * key = "Free", , value (Double) in bytes + * key = "FreePercentage" , value (Double) in Percentage + * + * Use function Convert_FileSize_toString for easy reading string + * @param folderpath : path to check + * @return Map result + */ + private Map Get_StorageInfo(String folderpath) { + Map data = new Map(); + data.Initialize(); + + File dir = new File(folderpath); + if (dir.exists()) { + if (dir.isDirectory()) { + double total = dir.getTotalSpace(); + double free = dir.getUsableSpace(); + double percentage = (free / total) * 100; + data.Put("Path", folderpath); + data.Put("Total", total); + data.Put("Free", free); + data.Put("FreePercentage", mycodes.pembulatan_1desimal(percentage)); + + } + } + return data; + } + + + private final double file_KB = 1024; + private final double file_MB = file_KB * 1024; + private final double file_GB = file_MB * 1024; + private final double file_TB = file_GB * 1024; + /** + * Convert a filesize to easy to read string + * @param value : size in bytes + * @return string of filesize + */ + public String Convert_FileSize_toString(double value) { + if (value < file_KB) { + return mycodes.pembulatan_1desimal(value)+" B"; + } else if (value < file_MB) { + return mycodes.pembulatan_1desimal(value/file_KB)+" KB"; + } else if (value < file_GB) { + return mycodes.pembulatan_1desimal(value / file_MB)+" MB"; + } else if (value < file_TB) { + return mycodes.pembulatan_1desimal(value / file_GB)+" GB"; + } else return mycodes.pembulatan_1desimal(value / file_TB)+" TB"; + } + + /** + * Get All registered Network Adapters, whether up, down, or virtual + * @return null if not available or failed + */ + public String[] Get_NetworkAdapters() { + File dir1 = new File("/sys/class/net"); + if (dir1!=null) { + if (dir1.isDirectory()) { + String[] dir1_content = dir1.list(); + if (dir1_content!=null) { + if (dir1_content.length>0) { + return dir1_content; + } + } + } + } + return null; + } + + + + /** + * Get MAC Address + * @param networkdevice : network device name eth0 / wlan0 + * @return MAC (String) if success, or empty string if failed + */ + public String Get_MAC(String networkdevice) { + final String targetfile = sysclassnet+networkdevice+"/address"; + String[] result = ReadLines(targetfile); + if (result!=null && result.length>0) { + return result[0]; + } + return ""; + } + + /** + * Get Network Status if Up or Down + * @param networkdevice : network device name eth0 / wlan0 + * @return true if UP, or false if DOWN + */ + public boolean NetworkIsActive(String networkdevice) { + final String targetfile = sysclassnet+networkdevice+"/operstate"; + if (new File(targetfile).exists()) { + String[] result = ReadLines(targetfile); + if (result!=null && result.length>0) { + return "up".equalsIgnoreCase(result[0]); + } + } else { + try { + NetworkInterface inf = NetworkInterface.getByName(networkdevice); + if (inf!=null) return inf.isUp(); + } catch (SocketException e) { + if (BA.debugMode) BA.Log("NetworkIsActive device="+networkdevice+" exception, Msg : "+e.getMessage()); + } + + } + + return false; + } + + /** + * Get Volume Level + * Types are : + * 0 = System + * 1 = Music + * 2 = Alarm + * 3 = Notification + * 4 = Ring + * 5 = Voice Call + * 6 = DTMF + * 7 = Accessibility + * @param type 0 - 7, default to 0 + * @return volume level, -1 if not available + */ + public int GetVolume(int type) { + AudioManager am = (AudioManager) ba.context.getSystemService(Context.AUDIO_SERVICE); + switch(type) { + case 1 : + return am.getStreamVolume(AudioManager.STREAM_MUSIC); + case 2 : + return am.getStreamVolume(AudioManager.STREAM_ALARM); + case 3 : + return am.getStreamVolume(AudioManager.STREAM_NOTIFICATION); + case 4 : + return am.getStreamVolume(AudioManager.STREAM_RING); + case 5: + return am.getStreamVolume(AudioManager.STREAM_VOICE_CALL); + case 6: + return am.getStreamVolume(AudioManager.STREAM_DTMF); + case 7: + return am.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY); + default: + return am.getStreamVolume(AudioManager.STREAM_SYSTEM); + } + } + + /** + * Set Volume Level + * Types are : + * 0 = System + * 1 = Music + * 2 = Alarm + * 3 = Notification + * 4 = Ring + * 5 = Voice Call + * 6 = DTMF + * 7 = Accessibility + * @param type 0 - 7, default to 0 + * @param level volume level + */ + public void SetVolume(int type, int level) { + AudioManager am = (AudioManager) ba.context.getSystemService(Context.AUDIO_SERVICE); + switch(type) { + case 1: + am.adjustStreamVolume(AudioManager.STREAM_MUSIC, level, 0); + break; + case 2: + am.adjustStreamVolume(AudioManager.STREAM_ALARM, level, 0); + break; + case 3: + am.adjustStreamVolume(AudioManager.STREAM_NOTIFICATION, level, 0); + break; + case 4: + am.adjustStreamVolume(AudioManager.STREAM_RING, level, 0); + break; + case 5: + am.adjustStreamVolume(AudioManager.STREAM_VOICE_CALL, level, 0); + break; + case 6: + am.adjustStreamVolume(AudioManager.STREAM_DTMF, level, 0); + break; + case 7: + am.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY, level, 0); + break; + default: + am.adjustStreamVolume(AudioManager.STREAM_SYSTEM, level, 0); + break; + } + } + + /** + * Set Muted + * Types are : + * 0 = System + * 1 = Music + * 2 = Alarm + * 3 = Notification + * 4 = Ring + * 5 = Voice Call + * 6 = DTMF + * 7 = Accessibility + * @param type 0 - 7, default to 0 + * @param mute true to mute, false to unmute + */ + public void SetMuted(int type, boolean mute) { + AudioManager am = (AudioManager) ba.context.getSystemService(Context.AUDIO_SERVICE); + switch(type) { + case 1: + am.setStreamVolume(AudioManager.STREAM_MUSIC, mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, 0); + break; + case 2: + am.setStreamVolume(AudioManager.STREAM_ALARM, mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, + 0); + break; + case 3: + am.setStreamVolume(AudioManager.STREAM_NOTIFICATION, + mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, 0); + break; + case 4: + am.setStreamVolume(AudioManager.STREAM_RING, mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, + 0); + break; + case 5: + am.setStreamVolume(AudioManager.STREAM_VOICE_CALL, + mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, 0); + break; + case 6: + am.setStreamVolume(AudioManager.STREAM_DTMF, mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, + 0); + break; + case 7: + am.setStreamVolume(AudioManager.STREAM_ACCESSIBILITY, + mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, 0); + default : + am.setStreamVolume(AudioManager.STREAM_SYSTEM, mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, 0); + break; + } + + } + + /** + * Get Mute status + * Types are : + * 0 = System + * 1 = Music + * 2 = Alarm + * 3 = Notification + * 4 = Ring + * 5 = Voice Call + * 6 = DTMF + * 7 = Accessibility + * @param type 0 - 7, default to 0 + * @return true if muted, false if not + */ + public boolean GetMute(int type) { + AudioManager am = (AudioManager) ba.context.getSystemService(Context.AUDIO_SERVICE); + switch(type) { + case 1: + return am.isStreamMute(AudioManager.STREAM_MUSIC); + case 2: + return am.isStreamMute(AudioManager.STREAM_ALARM); + case 3: + return am.isStreamMute(AudioManager.STREAM_NOTIFICATION); + case 4: + return am.isStreamMute(AudioManager.STREAM_RING); + case 5: + return am.isStreamMute(AudioManager.STREAM_VOICE_CALL); + case 6: + return am.isStreamMute(AudioManager.STREAM_DTMF); + case 7: + return am.isStreamMute(AudioManager.STREAM_ACCESSIBILITY); + default : + return am.isStreamMute(AudioManager.STREAM_SYSTEM); + } + } + + /** + * Get Maximum allowed Volume Level + * Types are : + * 0 = System + * 1 = Music + * 2 = Alarm + * 3 = Notification + * 4 = Ring + * 5 = Voice Call + * 6 = DTMF + * 7 = Accessibility + * @param type 0 - 7, default to 0 + * @return maximum volume level, -1 if not available + */ + public int GetMaximumVolume(int type) { + AudioManager am = (AudioManager) ba.context.getSystemService(Context.AUDIO_SERVICE); + switch (type) { + case 1: + return am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + case 2: + return am.getStreamMaxVolume(AudioManager.STREAM_ALARM); + case 3: + return am.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION); + case 4: + return am.getStreamMaxVolume(AudioManager.STREAM_RING); + case 5: + return am.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL); + case 6: + return am.getStreamMaxVolume(AudioManager.STREAM_DTMF); + case 7: + return am.getStreamMaxVolume(AudioManager.STREAM_ACCESSIBILITY); + default: + return am.getStreamMaxVolume(AudioManager.STREAM_SYSTEM); + } + } + + /** + * Get Minimum allowed Volume Level + * Types are : + * 0 = System + * 1 = Music + * 2 = Alarm + * 3 = Notification + * 4 = Ring + * 5 = Voice Call + * 6 = DTMF + * 7 = Accessibility + * @param type 0 - 7, default to 0 + * @return minimum volume level, -1 if not available + */ + public int GetMinimumVolume(int type) { + AudioManager am = (AudioManager) ba.context.getSystemService(Context.AUDIO_SERVICE); + switch (type) { + case 1: + return am.getStreamMinVolume(AudioManager.STREAM_MUSIC); + case 2: + return am.getStreamMinVolume(AudioManager.STREAM_ALARM); + case 3: + return am.getStreamMinVolume(AudioManager.STREAM_NOTIFICATION); + case 4: + return am.getStreamMinVolume(AudioManager.STREAM_RING); + case 5: + return am.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL); + case 6: + return am.getStreamMinVolume(AudioManager.STREAM_DTMF); + case 7: + return am.getStreamMinVolume(AudioManager.STREAM_ACCESSIBILITY); + default: + return am.getStreamMinVolume(AudioManager.STREAM_SYSTEM); + } + } + + /** + * Check if Screen ON or OFF + * @return true if ON, false if OFF + */ + public boolean IsScreenON() { + PowerManager pm = (PowerManager) ba.context.getSystemService(Context.POWER_SERVICE); + return pm.isInteractive(); + } + + /** + * Set Screen ON (wakeup) or OFF (sleep) + * Require permission android.permission.DEVICE_POWER to be enabled in Manifest + * @param isON true to wakeup, false to sleep + * @return true if success + */ + public boolean SetScreenON(boolean isON) { + PowerManager pm = (PowerManager) ba.context.getSystemService(Context.POWER_SERVICE); + if (isON) { + + try { + pm.getClass().getMethod("wakeUp", new Class[]{long.class}).invoke(pm, SystemClock.uptimeMillis()); + return true; + } catch (IllegalAccessException e) { + raise_log("SetScreenON isON=true exception, Msg : "+e.getMessage()); + } catch (InvocationTargetException e) { + raise_log("SetScreenON isON=true exception, Msg : "+e.getMessage()); + } catch (NoSuchMethodException e) { + raise_log("SetScreenON isON=true exception, Msg : "+e.getMessage()); + } + } else { + try { + pm.getClass().getMethod("goToSleep", new Class[]{long.class}).invoke(pm, SystemClock.uptimeMillis()); + return true; + } catch (IllegalAccessException e) { + raise_log("SetScreenON isON=false exception, Msg : "+e.getMessage()); + } catch (InvocationTargetException e) { + raise_log("SetScreenON isON=false exception, Msg : "+e.getMessage()); + } catch (NoSuchMethodException e) { + raise_log("SetScreenON isON=false exception, Msg : "+e.getMessage()); + } + } + return false; + } + + /** + * Reboot the device + * Require permission android.permission.REBOOT to be enabled in Manifest + * @return true if can be rebooted + */ + public boolean Reboot() { + PowerManager pm = (PowerManager) ba.context.getSystemService(Context.POWER_SERVICE); + if (pm.isRebootingUserspaceSupported()) { + pm.reboot("Rebooting"); + return true; + } + return false; + } + + /** + * Check if network device is a WLan Device + * @param networkdevice + * @return true if wireless device + */ + public boolean IsWirelessDevice(String networkdevice) { + if (networkdevice!=null && networkdevice.length()>0) { + File wireless = new File(sysclassnet+networkdevice+"/wireless"); + return wireless.exists() && wireless.isDirectory(); + } + return false; + } + + /** + * Get Wireless SSID + * @param networkdevice + * @return null if failed or not wireless device or not connected + */ + public String Get_WirelessSSID(String networkdevice) { + if (IsWirelessDevice(networkdevice)) { + final String regex = ".*SSID:.(\\S+).*"; + final Pattern pattern = Pattern.compile(regex); + String[] result = Shell("iw dev "+networkdevice+" link"); + if (result!=null && result.length>0) { + for(String rr : result) { + Matcher m = pattern.matcher(rr); + if (m.find()) { + return m.group(1); + } + } + } + } + return null; + } + + /** + * Get Wireless Signal Strength + * @param networkdevice + * @return null if not available, or failed, or not wireless device + */ + public String Get_WirelessSignal(String networkdevice) { + if (IsWirelessDevice(networkdevice)) { + final String regex = ".*signal:.(\\S+).(\\S+).*"; + final Pattern pattern = Pattern.compile(regex); + String[] result = Shell("iw dev " + networkdevice + " link"); + if (result != null && result.length > 0) { + for (String rr : result) { + Matcher m = pattern.matcher(rr); + if (m.find()) { + return m.group(1)+" "+m.group(2); + } + } + } + } + return null; + } + + /** + * Get Wireless Frequency + * @param networkdevice + * @return 0 if failed or not available + */ + public int Get_WirelessFrequency(String networkdevice) { + if (IsWirelessDevice(networkdevice)) { + final String regex = ".*freq:.(\\d+).*"; + final Pattern pattern = Pattern.compile(regex); + String[] result = Shell("iw dev "+networkdevice+" link"); + if (result!=null && result.length>0) { + for(String rr : result) { + Matcher m = pattern.matcher(rr); + if (m.find()) { + try { + return Integer.parseInt(m.group(1)); + + } catch(NumberFormatException e) { + + } + + + } + } + } + } + return 0; + } + + /** + * Get Network Speed + * @param networkdevice + * @return -1 if not available + */ + public int Get_NetworkSpeed(String networkdevice) { + if (networkdevice!=null && networkdevice.length()>0) { + if (IsWirelessDevice(networkdevice)) { + // wireless device, pake metode iw + final String regex = ".*tx bitrate:.(\\S+).MBit/s"; + final Pattern pattern = Pattern.compile(regex); + String[] result = Shell("iw dev "+networkdevice+" link"); + if (result!=null && result.length>0) { + for(String rr : result) { + Matcher m = pattern.matcher(rr); + if (m.find()) { + try { + double value = Double.parseDouble(m.group(1)); + return (int) value; + } catch (NumberFormatException e) { + + } + } + } + } + } else { + // device lain, coba pakai metode speed + final String targetfile = sysclassnet+networkdevice+"/speed"; + String[] result = ReadLines(targetfile); + if (result!=null && result.length>0) { + try { + int value = Integer.parseInt(result[0]); + return value; + } catch(NumberFormatException e) { + + } + } + } + } + return -1; + } + + /** + * Get result from ifconfig command + * @param networkdevice network to check + * @return null if failed, also check for Ifconfig_Info.isvalid() to verify + */ + public Ifconfig_Info Get_IfConfig(String networkdevice) { + String[] result = Shell("ifconfig "+networkdevice); + if (result!=null && result.length>0) { + return new Ifconfig_Info(result); + } + return null; + } + + public GetProp_Info Get_Properties() { + String[] result = Shell("getprop"); + if (result!=null && result.length>0) { + return new GetProp_Info(result); + } + return null; + } + + /** + * Get IP Address in String + * Revision 12072020: Sometimes an interface may contain more than 1 IP addres + * so this function will return List instead of one String + * @param networkdevice : network device name eth0 / wlan0 + * @return List contain IP Address (String) if success, or empty List if failed + */ + public List Get_IPAddress(String networkdevice) { + List result = new List(); + result.Initialize(); + try { + NetworkInterface inf = NetworkInterface.getByName(networkdevice); + + if (inf instanceof NetworkInterface) { + if (inf.isUp()) { + Enumeration inetAddress = inf.getInetAddresses(); + InetAddress currentAddress; + + while (inetAddress.hasMoreElements()) { + currentAddress = inetAddress.nextElement(); + if (currentAddress instanceof Inet4Address && !currentAddress.isLoopbackAddress()) { + String ip = currentAddress.getHostAddress(); + if (!ip.isEmpty()) + result.Add(ip); + } + } + } + } + + } catch (SocketException e) { + raise_log("Get_IPAddress device="+networkdevice+" exception, Msg : "+e.getMessage()); + } + + return result; + } + + /** + * Get IP Bytes + * @param networkdevice : network device name eth0 / wlan0 + * @return IP Bytes (4 integers) if success, or null if failed + */ + public int[] Get_IP_Bytes(String networkdevice) { + try { + NetworkInterface inf = NetworkInterface.getByName(networkdevice); + if (inf instanceof NetworkInterface) { + if (inf.isUp()) { + Enumeration inetAddress = inf.getInetAddresses(); + InetAddress currentAddress; + while (inetAddress.hasMoreElements()) { + currentAddress = inetAddress.nextElement(); + if (currentAddress instanceof Inet4Address && !currentAddress.isLoopbackAddress()) { + byte[] result = currentAddress.getAddress(); + if (result == null) + return null; + if (result.length != 4) + return null; + int[] intresult = new int[4]; + for (int ii = 0; ii < 4; ii++) { + intresult[ii] = Bit.And(result[ii], 0xFF); + } + return intresult; + } + } + } + } + return null; + } catch (SocketException e) { + raise_log("Get_IP_Bytes device="+networkdevice+" exception, Msg : "+e.getMessage()); + return null; + } + } + + private boolean cpu_monitoring = false; + private long _last_boottime = 0; + + /** + * Get Boot Time + * @return Date and Time in String + */ + public String getBootTime() { + long tick = _last_boottime * DateTime.TicksPerSecond; + return DateTime.Date(tick)+" "+DateTime.Time(tick); + } + + /** + * Get Processor State + * will try to read state from /proc/stat + * @return null if invalid + */ + public ProcStat_Info Get_Proc_Stat() { + if (Platform.isAndroid()) { + String[] result = ReadLines("/proc/stat"); + if (result!=null && result.length>0) { + return new ProcStat_Info(result); + } + } + return null; + } + + + + /** + * Start CPU Usage Monitoring + * will raise event cpuusage + * @param interval value in milliseconds + * @return true if monitoring thread can be started + */ + public boolean Start_CPU_Usage_Monitoring(int interval) { + if (Platform.isAndroid()) { + Thread tx = new Thread(new cpumonitorrunnable(interval)); + tx.start(); + return true; + } + return false; + } + + private class cpumonitorrunnable implements Runnable { + private ProcStat_Info prev; + private int _interval = 1000; + public cpumonitorrunnable(int interval) { + if (interval>0) _interval = interval; + + } + + @Override + public void run() { + raise_log("Start CPU Usage Monitoring"); + cpu_monitoring = true; + while(cpu_monitoring) { + // Sleep 1 detik + try { + Thread.sleep(_interval); + } catch (InterruptedException e) { + break; + } + + String[] values = ReadLines("/proc/stat"); + if (values != null && values.length>0) { + ProcStat_Info xx = new ProcStat_Info(values); + _last_boottime = xx.getBoottime(); + if (prev==null) + prev = xx; + else { + Map result = new Map(); + result.Initialize(); + + result.Put("Total", xx.getTotalCpuData().getCPU_Usage(prev.getTotalCpuData())); + cpudata[] prevdata = prev.getCpuData(); + cpudata[] nowdata = xx.getCpuData(); + if (prevdata.length==nowdata.length) { + for (int ii = 0; ii < prevdata.length; ii++) { + result.Put(prevdata[ii].name, nowdata[ii].getCPU_Usage(prevdata[ii])); + } + } + raise_cpuusage(result); + + prev = xx; + + } + } else cpu_monitoring=false; // proc stat gak bisa dibaca + + } + raise_log("Stop CPU Usage Monitoring"); + } + } + + + + public void Stop_CPU_Usage_Monitoring() { + cpu_monitoring = false; + } + + + + private boolean valid_string(String x) { + if (x != null) { + if (x.length()>0) { + return true; + } + } + return false; + } + + + + /** + * Check if a value is valid IP address + * will raise event isvalidip(toip as string, isvalid as boolean) + * @param value : target host ip or name + */ + public void IP_isValid(String value) { + if (value.isEmpty()) { + raise_isvalidip(value, false); + return; + } + Thread tx = new Thread(new CheckIPValid(value)); + tx.start(); + } + + private class CheckIPValid implements Runnable{ + final String targetip; + + public CheckIPValid(String value){ + targetip = value.trim(); + + } + + + + @Override + public void run() { + try { + InetAddress result = InetAddress.getByName(targetip); + if (result instanceof InetAddress) { + raise_isvalidip(targetip, true); // ikut default + } else { + raise_isvalidip(targetip, false); + } + + } catch(UnknownHostException | SecurityException e) { + raise_log("CheckIPValid "+targetip+" Exception, Msg : "+e.getMessage()); + raise_isvalidip(targetip, false); + } + } + + }; + + private class CheckIPReachable implements Runnable{ + final String targetip; + final String networkdev; + + // revisi 31052021 + // timeout berubah dari 2000 ke 5000 + final int pingtimeout = 5000; + public CheckIPReachable(String value, String networkdevice){ + targetip = value; + networkdev = networkdevice; + } + + + @Override + public void run() { + + try { + NetworkInterface nef = NetworkInterface.getByName(networkdev); + if (nef instanceof NetworkInterface) { + InetAddress ip = InetAddress.getByName(targetip); + if (ip instanceof InetAddress) { + boolean result = ip.isReachable(nef, 0, pingtimeout); + raise_isreachableip(targetip, result); // ikut default + return; + } + } + + + } catch (SocketException e1) { // dari NetworkInterface.getByName + raise_log("CheckIPReachable IP="+targetip+" Device="+networkdev+" Exception, Msg : "+e1.getMessage()); + + } catch (UnknownHostException | SecurityException e) { // dari InetAddress.getByName + raise_log("CheckIPReachable IP="+targetip+" Device="+networkdev+" Exception, Msg : "+e.getMessage()); + + + } catch (IOException e) { // dari InetAddress.IsReachable + raise_log("CheckIPReachable IP="+targetip+" Device="+networkdev+" Exception, Msg : "+e.getMessage()); + } + + raise_isreachableip(targetip, false); // ikut default + } + } + + + /** + * Check if IP address is reachable / Ping-able + * will raise event isreachableip(toip as string, isreachable as boolean) + * @param value : Ip address + * @param networkdevice : network device name eth0 /wlan0 + */ + public void IP_isReachable(String value, String networkdevice) { + if (value.isEmpty()) { + raise_isreachableip(value, false); + return; + } + + if (networkdevice.isEmpty()) { + raise_isreachableip(value, false); + return; + } + + Thread tx = new Thread(new CheckIPReachable(value, networkdevice)); + tx.start(); + + } + + + + /** + * Get Single Board Computer Model + * @return empty string if not available + */ + public String read_SBC_model() { + if (!Platform.isAndroid()) return ""; + + File ff = new File("/sys/firmware/devicetree/base/model"); + String result = ""; + if (ff.exists()) { + if (ff.canRead()) { + try { + BufferedReader reader = new BufferedReader(new FileReader(ff.getAbsolutePath())); + result = reader.readLine(); + if (result!=null) { + result = result.trim(); + } else result = ""; + reader.close(); + + } catch (IOException e) { + raise_log("Failed to read from "+ff.getAbsolutePath()+", Message="+e.getMessage()+", Caused="+e.getCause()); + } + + } + } + return result; + } + + /** + * Get HW Monitor Infos + * @return null if failed + */ + public Hwmon_Info[] Get_Hwmon_Info() { + File dir1 = new File(sysclasshwmon); + if (dir1 != null && dir1.isDirectory()) { + String[] hwmons = dir1.list(); + if (hwmons!=null && hwmons.length>0) { + Set result = new HashSet(); + + for(String xx: hwmons) { + File _hwmondir = new File(sysclasshwmon+xx); + Hwmon_Info hwmon = new Hwmon_Info(_hwmondir); + if (hwmon.IsValid()) result.add(hwmon); + } + + return result.toArray(new Hwmon_Info[result.size()]); + } + } + return null; + } + + + //TODO remove kalau berhasil + @BA.Hide + /** + * Get CPU Temperature + * On multiple cores , Map will contains several keys + * Map key = name of thermal zone, start from 0 (thermal_zone0) + * Map value = CPU temperature in double + * @return Map containing values if success, or empty map if failed + */ + public Map Get_CPU_Temp() { + Map result = new Map(); + result.Initialize(); + final String childstring = "thermal_zone"; + final String parentpath = "/sys/devices/virtual/thermal"; + final String tempstring = "temp"; + + File parent = new File(parentpath); + if (parent.exists()) { + String parentcontent[] = parent.list(); + if (parentcontent!=null) { + if (parentcontent.length>0) { + for(String xx: parentcontent) { + if (xx.contains(childstring)) { + // ketemu nya thermal_zoneX + + File parentchild = new File(parentpath, xx); + if (parentchild.isDirectory()) { + File tempfile = new File(parentchild.getAbsolutePath(), tempstring); + if (tempfile.exists()) { + if (tempfile.canRead()) { + try { + BufferedReader reader = new BufferedReader(new FileReader(tempfile.getAbsolutePath())); + String readresult = reader.readLine(); + if (readresult!=null) { + readresult = readresult.trim(); + } else readresult = ""; + reader.close(); + + double doubleresult = Double.valueOf(readresult) / 1000.0; + // pembulatan 1 desimal + int pembulatan = (int) (doubleresult * 10); + doubleresult = pembulatan / 10.0; + + result.Put(xx, doubleresult); + } catch(NumberFormatException e) { + // readresult bukan angka + result.Put(xx, -1); + raise_log("NumberFormatException "+e.getMessage()); + } + catch ( IOException e) { + raise_log("Failed to access "+tempfile.getAbsolutePath()+", Msg:"+e.getMessage()); + result.Put(xx, -1); + + } + } + } else { + + result.Put(xx, -1); + raise_log(tempfile.getAbsolutePath()+" is not exist"); + } + } + } + } + } + } + } + + return result; + } + + protected boolean network_metering = false; + private final String rx_bytes_file = "rx_bytes"; + private final String tx_bytes_file = "tx_bytes"; + private final String rx_packets_file = "rx_packets"; + private final String tx_packets_file = "tx_packets"; + private final String rx_errors_file = "rx_errors"; + private final String tx_errors_file = "tx_errors"; + private final String rx_dropped_file = "rx_dropped"; + private final String tx_dropped_file = "tx_dropped"; + + /** + * Start Network Metering + * Will raise networkstatistic(ifname as string, rx_pps as long, tx_pps as long, rx_bps as long, tx_bps as long) + * @param ifname : valid interface name + * @return true if started + */ + public boolean Start_Network_Meter(String ifname) { + if (Platform.isAndroid()) { + if (NetworkIsActive(ifname)) { + String networkpath = sysclassnet+ifname+"/statistics"; + File netdir = new File(networkpath); + if (netdir.exists()) { + if (netdir.isDirectory()) { + if (FileExistAndReadable(networkpath, rx_bytes_file)) { + if (FileExistAndReadable(networkpath, tx_bytes_file)) { + if (FileExistAndReadable(networkpath, rx_packets_file)) { + if (FileExistAndReadable(networkpath, tx_packets_file)) { + if (FileExistAndReadable(networkpath, rx_dropped_file)) { + if (FileExistAndReadable(networkpath, tx_dropped_file)) { + if (FileExistAndReadable(networkpath, rx_errors_file)) { + if (FileExistAndReadable(networkpath, tx_errors_file)) { + Thread tx = new Thread(new networkmeterrunnable(ifname,networkpath)); + tx.start(); + return true; + } else raise_log("Unable to access "+tx_errors_file); + } else raise_log("Unable to access "+rx_errors_file); + } else raise_log("Unable to access "+tx_dropped_file); + } else raise_log("Unable to access "+rx_dropped_file); + } else raise_log("Unable to access "+tx_packets_file); + } else raise_log("Unable to access "+rx_packets_file); + } else raise_log("Unable to access "+tx_bytes_file); + } else raise_log("Unable to access "+rx_bytes_file); + } else raise_log(networkpath+" is not directory"); + } else raise_log(networkpath+" is not exist"); + + } else raise_log(ifname+" is down"); + } else raise_log("System is not Linux"); + return false; + } + + public void Stop_Network_Meter() { + network_metering = false; + + } + + /** + * Update manually Linux Date and Time + * @param datenya : must in format yyyy-MM-dd + * year only valid from 2000 - 2999 + * @param timenya : must in format hh:mm:ss + * @return true if success + */ + public boolean Set_Linux_DateTime(String datenya, String timenya) { + if (!Regex.IsMatch("^(2[0-9]{3})-([0]?[1-9]|[1][0-2])-([0]?[1-9]|[1|2][0-9]|[3][0-1])$", datenya)) { + raise_log("Date Format must be yyyy-MM-dd"); + return false; + } + + if (!Regex.IsMatch("^([0]?[0-9]|[1][0-9]|[2][0-3]):([0]?[0-9]|[1-5][0-9]):([0]?[0-9]|[1-5][0-9])$", timenya)) { + raise_log("Time Format must be hh:mm:ss"); + return false; + } + try { + Process changetime = Runtime.getRuntime().exec("sudo date -s '"+datenya+" "+timenya+"'"); + if (changetime!=null) { + if (changetime.exitValue()==0) { + return true; + } + } + } catch (Exception e) { + raise_log("SetDateTime Exception, Msg : "+e.getMessage()); + + } + return false; + } + + /** + * Manually set Linux Date and Time + * @param DayOfMonth : start from 1 + * Month = 1, 3, 5, 7, 8 10, 12 --> Day Of Month up to 31 + * Month = 4, 6, 9, 11 --> Day of Month up to 30 + * Month = 2, normal 28, kabisat 29 + * @param Month : 1 - 12 + * @param Year : 2000 - 2999 + * @param Hour : 0 - 23 + * @param Minute : 0 - 59 + * @param Second : 0 - 59 + * @return true if success + */ + public boolean Set_Linux_Date_and_Time(byte DayOfMonth, byte Month, int Year, byte Hour, byte Minute, byte Second) { + if (Hour<0) return false; + if (Hour>23) return false; + if (Minute<0) return false; + if (Minute>59) return false; + if (Second<0) return false; + if (Second>59) return false; + + if (Year < 2000) return false; + if (Year > 2999) return false; + if (Month<1) return false; + if (Month>12) return false; + + if (DayOfMonth<1) return false; + switch(Month) { + case 2 : + // february + if ((Year % 4)==0) { + // kabisat + if (DayOfMonth>29) return false; + } else { + if (DayOfMonth>28) return false; + } + + break; + // bulan -bulan yang berakhir di 31 + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12 : + if (DayOfMonth>31) return false; + break; + // sisanya, berarti bulan yang berakhir di 30 + default: + if (DayOfMonth>30) return false; + break; + } + + // sampe sini dah valid + // bentuk datevalue dengan format yyyy-MM-dd + String datevalue = Create_YYYY_MM_DD(DayOfMonth, Month, Year); + // bentuk timevalue dengan format hh:mm:ss + String timevalue = Create_HH_MM_SS(Hour, Minute, Second); + return Set_Linux_DateTime(datevalue, timevalue); + + } + + private String Create_YYYY_MM_DD(byte DD, byte MM, int YYYY) { + StringBuilder str = new StringBuilder(); + if (DD<10) str.append("0"); + str.append(DD); + str.append("-"); + if (MM<10) str.append("0"); + str.append(MM); + str.append("-"); + str.append(YYYY); + return str.toString(); + } + + private String Create_HH_MM_SS(byte HH, byte MM, byte SS) { + StringBuilder str = new StringBuilder(); + if (HH<10) str.append("0"); + str.append(HH); + str.append(":"); + if (MM<10) str.append("0"); + str.append(MM); + str.append(":"); + if (SS <10) str.append("0"); + str.append(SS); + return str.toString(); + } + + protected boolean FileExistAndReadable(String path, String filename) { + File ff = new File(path, filename); + if (ff.exists()) { + if (ff.canRead()) { + return true; + } + } + return false; + } + + private class networkmeterrunnable implements Runnable{ + + private final String ifname; + private final String rx_bytes_path; + private final String tx_bytes_path; + private final String rx_packets_path; + private final String tx_packets_path; + private final String rx_dropped_path; + private final String rx_errors_path; + private final String tx_dropped_path; + private final String tx_errors_path; + private NetworkMeterInfo previous = null; + networkmeterrunnable(String ifname, String pathname){ + this.ifname = ifname; + this.rx_bytes_path = new File(pathname,rx_bytes_file).getAbsolutePath(); + this.tx_bytes_path = new File(pathname,tx_bytes_file).getAbsolutePath(); + this.rx_packets_path = new File(pathname,rx_packets_file).getAbsolutePath(); + this.tx_packets_path = new File(pathname,tx_packets_file).getAbsolutePath(); + this.rx_dropped_path = new File(pathname,rx_dropped_file).getAbsolutePath(); + this.tx_dropped_path = new File(pathname,tx_dropped_file).getAbsolutePath(); + this.rx_errors_path = new File(pathname,rx_errors_file).getAbsolutePath(); + this.tx_errors_path = new File(pathname,tx_errors_file).getAbsolutePath(); + } + @Override + public void run() { + raise_log("Network Meter started"); + network_metering = true; + while(network_metering) { + NetworkMeterInfo current = new NetworkMeterInfo(ifname, DateTime.getNow(), + read_value(rx_bytes_path), read_value(tx_bytes_path), + read_value(rx_packets_path), read_value(tx_packets_path), + read_value(rx_dropped_path), read_value(tx_dropped_path), + read_value(rx_errors_path), read_value(tx_errors_path) + ); + + if (current.IsValidValue()) { + current.calculate(previous); + previous = current; + raise_networkstatistic(current); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + + + } + raise_log("Network Meter stopped"); + + } + + private long read_value(String path) { + + try(BufferedReader reader = new BufferedReader(new FileReader(path))) { + String line = reader.readLine(); + if (line!=null && line.length()>0) { + line = line.trim(); + long result = Long.valueOf(line); + return result; + } + } catch (IOException | NumberFormatException e) { + } + + return 0; + } + + }; + + protected void raise_log(String msg) { + if (need_log_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, eventname + "_log", false, new Object[] { msg }); + } + } + + protected void raise_isreachableip(String toip, boolean isreachable) { + if (need_isreachableip_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, eventname + "_isreachableip", false, + new Object[] { toip, isreachable }); + } + } + + + + protected void raise_isvalidip(String toip, boolean isvalid) { + if (need_isvalidip_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, eventname + "_isvalidip", false, + new Object[] { toip, isvalid }); + } + } + + + + protected void raise_networkstatistic(NetworkMeterInfo value) { + if (need_networkstatistic_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_networkstatistic", false, new Object[] {value}); + } + + } + + protected void raise_cpuusage(Map value) { + if (need_cpuusage_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_cpuusage", false, new Object[] {value}); + } + } + + + /** + * Untuk write hasil 'Sub Application_Error (Error As Exception, StackTrace As String) As Boolean' ke file + * hasil nya file di run directory, dengan format Crash-ddmmyyyy-hhmmss.log + * @param error : exception dari Application Error + * @param stacktrace : stacktrace dari Application Error + * @return true kalau success create file + */ + public boolean Create_Crash_Log(Exception error, String stacktrace) { + if (error instanceof Exception) { + final String errormessage = error.getMessage(); + if (errormessage instanceof String) { + if (!errormessage.isEmpty()) { + if (stacktrace instanceof String) { + if (!stacktrace.isEmpty()) { + + + File crashfile = new File(rundir, create_crashlog_name()); + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(crashfile.getAbsolutePath())); + writer.write(error.getMessage()); + writer.newLine(); + writer.write(stacktrace); + writer.close(); + return true; + } catch (IOException e) { + BA.Log("Unable to Create Crash Log, Msg : "+e.getMessage()); + } + } + } + } + } + } + return false; + + } + + + /** + * Get Current Running Directory + * @return current running directory + */ + public String getRunDirectory() { + return rundir; + } + + private String create_crashlog_name() { + // simpan format awal + String dateformat = DateTime.getDateFormat(); + String timeformat = DateTime.getTimeFormat(); + + // ganti format Datetime + DateTime.setDateFormat("ddMMyyyy"); + DateTime.setTimeFormat("HHmmss"); + + StringBuilder result = new StringBuilder(); + long tt = DateTime.getNow(); + result.append("Crash-"); + result.append(DateTime.Date(tt)); + result.append("-"); + result.append(DateTime.Time(tt)); + result.append(".log"); + + // balikin format Datetime ke format awal + DateTime.setDateFormat(dateformat); + DateTime.setTimeFormat(timeformat); + return result.toString(); + } + + public void WriteLog(String appname, String msg) { + File logfile = new File(rundir, create_applog_name(appname)); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(logfile.getAbsolutePath(),true)); + writer.write(create_log_time()); + writer.write(msg); + writer.newLine(); + writer.close(); + } catch (IOException e) { + BA.Log("Unable to Create Crash Log, Msg : "+e.getMessage()); + } + } + + private String create_log_time() { + String timeformat = DateTime.getTimeFormat(); + DateTime.setTimeFormat("HH:mm:ss"); + String result = DateTime.Time(DateTime.getNow())+"\t"; + DateTime.setTimeFormat(timeformat); + return result; + } + + private String create_applog_name(String appname) { + // simpan format awal + String dateformat = DateTime.getDateFormat(); + + // ganti format Datetime + DateTime.setDateFormat("ddMMyyyy"); + + StringBuilder result = new StringBuilder(); + long tt = DateTime.getNow(); + result.append(appname.trim()); + result.append("-"); + result.append(DateTime.Date(tt)); + result.append(".log"); + + // balikin format Datetime ke format awal + DateTime.setDateFormat(dateformat); + return result.toString(); + } + +} diff --git a/src/SBC/CPU_Info.java b/src/SBC/CPU_Info.java new file mode 100644 index 0000000..c9d4c55 --- /dev/null +++ b/src/SBC/CPU_Info.java @@ -0,0 +1,95 @@ +package SBC; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("CPU_Info") +public class CPU_Info { + private String _serial=null; + private String _hardware=null; + private String _processor=null; + private int _proc_count = -1; + private final String serialpattern_string = "^Serial\\s+:\\s(\\S+)$"; + + // revisi 07022024 + //private final String hardwarepattern_string1 = "^Hardware\\s+:\\s(\\S+)$"; + private final String hardwarepattern_string = "^Hardware\\s+:\\s(\\S+( \\S+)*)$"; + + // baru, ada di huawei + private final String processorpattern_string = "^Processor\\s+:\\s(\\S+( \\S+)*)$"; + + private final String processorcountpattern_string = "^processor\\s+:\\s(\\d)$"; + + + public CPU_Info(String[] cpuinfovalues) { + + if (cpuinfovalues!=null) { + if (cpuinfovalues.length>0) { + + Stream.of(cpuinfovalues).forEach(ss ->{ + //BA.Log("CPU Info value : "+ss); + // CPU Serial Number + if (ss.matches(serialpattern_string)) { + Matcher m = Pattern.compile(serialpattern_string).matcher(ss); + if (m.find()) _serial = m.group(1); + } + // Hardware + if (ss.matches(hardwarepattern_string)) { + Matcher m = Pattern.compile(hardwarepattern_string).matcher(ss); + if (m.find()) _hardware = m.group(1); + } + + // Processor + if (ss.matches(processorpattern_string)) { + Matcher m = Pattern.compile(processorpattern_string).matcher(ss); + if (m.find()) _processor = m.group(1); + } + + // Processor Count + if (ss.matches(processorcountpattern_string)) { + Matcher m = Pattern.compile(processorcountpattern_string).matcher(ss); + if (m.find()) { + try { + int vv = Integer.valueOf(m.group(1)); + if (vv > _proc_count) _proc_count = vv; + } catch(NumberFormatException e) { + + } + + } + } + }); + } + } + } + + public String getSerial() { + return _serial; + } + + public String getHardware() { + return _hardware; + } + + public String getProcessorName() { + return _processor; + } + + public int getCPUCores() { + return _proc_count+1; + } + + public boolean isValid() { + if (_serial!=null && _serial.length()>0) { + if (_hardware!=null && _hardware.length()>0) { + if (_proc_count>-1) { + return true; + } + } + } + return false; + } +} diff --git a/src/SBC/GeneralAndroid.java b/src/SBC/GeneralAndroid.java new file mode 100644 index 0000000..3b6fd05 --- /dev/null +++ b/src/SBC/GeneralAndroid.java @@ -0,0 +1,728 @@ +package SBC; + +import java.io.IOException; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import androgpio.mycodes; +import androgpio.customsocket.JsonObject; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.Build; +import android.provider.Settings; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.DateTime; + +@BA.ShortName("GeneralAndroid") +@BA.Permissions(values= { + "android.permission.INTERNET", + "android.permission.ACCESS_NETWORK_STATE", + "android.permission.ACCESS_WIFI_STATE", + "android.permission.READ_PRIVILEGED_PHONE_STATE" + }) +@BA.Events(values= { + "wifinetworkstatus(available as boolean, blocked as boolean, network as NetworkInterface_info)", + "cellularnetworkstatus(available as boolean, blocked as boolean, network as NetworkInterface_info)", + "ethernetnetworkstatus(available as boolean, blocked as boolean, network as NetworkInterface_info)", + "bluetoothnetworkstatus(available as boolean, blocked as boolean, network as NetworkInterface_info)", + "vpnnetworkstatus(available as boolean, blocked as boolean, network as NetworkInterface_info)", + "usbnetworkstatus(available as boolean, blocked as boolean, network as NetworkInterface_info)" + }) + +public class GeneralAndroid extends BasicSBCInfo { + + ConnectivityManager _cm = null; + + NetworkCallback ncb_wifi = null; + NetworkCallback ncb_cellular = null; + NetworkCallback ncb_ethernet = null; + NetworkCallback ncb_bluetooth = null; + NetworkCallback ncb_vpn = null; + NetworkCallback ncb_usb = null; + + public GeneralAndroid() { + super(); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // do something + raise_log("GeneralAndroid ShutdownHook called"); + if (_cm != null) { + if (ncb_wifi != null) { + _cm.unregisterNetworkCallback(ncb_wifi); + ncb_wifi = null; + } + if (ncb_cellular != null) { + _cm.unregisterNetworkCallback(ncb_cellular); + ncb_cellular = null; + } + if (ncb_ethernet != null) { + _cm.unregisterNetworkCallback(ncb_ethernet); + ncb_ethernet = null; + } + if (ncb_bluetooth != null) { + _cm.unregisterNetworkCallback(ncb_bluetooth); + ncb_bluetooth = null; + } + if (ncb_vpn != null) { + _cm.unregisterNetworkCallback(ncb_vpn); + ncb_vpn = null; + } + if (ncb_usb != null) { + _cm.unregisterNetworkCallback(ncb_usb); + ncb_usb = null; + } + + + } + } + }); + } + + /** + * Initialize General Android Device + * @param callerobject Callback object + * @param event Event name + */ + public void Initialize(BA bax, Object callerobject, String event) { + setup_events(bax, callerobject, event, this); + if (bax!=null) { + if (bax.context != null) { + _cm = (ConnectivityManager) bax.context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + } + } + + + @BA.Hide + @Override + public double Get_CPU_CoreVolt() { + // Belum bisa diimplementasikan + return 0; + } + + @SuppressWarnings("deprecation") + /** + * Get Android Serial Number + * On old android, will return Build.Serial + * On Android 8.0 and above, will return Build.getSerial() + * if failed will return Settings.Secure.ANDROID_ID + * @return Serialnumber in string + */ + public String Get_SerialNumber() { + String value = Build.UNKNOWN; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + value = Build.getSerial(); + + if (BA.debugMode) raise_log("Get_SerialNumber using Build.getSerial()"); + } catch (SecurityException e) { + //raise_log("Get_SerialNumber Build.getSerial exception = " + e.getMessage()); + } + + } else { + value = Build.SERIAL; + if (BA.debugMode) raise_log("Get_SerialNumber using Build.SERIAL"); + } + + if (value!=null && value.length()>0) { + if (!value.equals("unknown")) return value; + } + if (BA.debugMode) raise_log("Get_SerialNumber using Settings.Secure.ANDROID_ID"); + return Settings.Secure.getString(ba.context.getContentResolver(), Settings.Secure.ANDROID_ID); + } + + + /** + * Get All registered Network Adapters, whether up, down, or virtual + * @return null if not available or failed + */ + public String[] Get_NetworkAdapters() { + try { + Enumeration values = NetworkInterface.getNetworkInterfaces(); + if (values!=null) { + List result = new ArrayList<>(); + while(values.hasMoreElements()) { + result.add(values.nextElement().getName()); + } + return result.toArray(new String[0]); + } + + } catch (SocketException e) { + raise_log("Get_NetworkAdapters exception = "+e.getMessage()); + } + return new String[0]; + } + + + /** + * Get MAC Address + * @param networkdevice : network device name eth0 / wlan0 + * @return MAC (String) if success, or empty string if failed + */ + public String Get_MAC(String networkdevice) { + try { + NetworkInterface xx = NetworkInterface.getByName(networkdevice); + if (xx!=null) { + byte[] _mac = xx.getHardwareAddress(); + if (mycodes.valid_bytearray(_mac)) { + StringBuilder str = new StringBuilder(); + for(byte vv: _mac) { + if (str.length()>0) str.append(":"); + str.append(mycodes.Byte_toHex(vv).toUpperCase()); + } + return str.toString(); + } + } + } catch (SocketException | NullPointerException e) { + raise_log("Get_MAC exception = "+e.getMessage()); + } + return ""; + } + + /** + * Get Network Status if Up or Down + * @param networkdevice : network device name eth0 / wlan0 + * @return true if UP, or false if DOWN + */ + public boolean NetworkIsActive(String networkdevice) { + try { + NetworkInterface xx = NetworkInterface.getByName(networkdevice); + if (xx!=null) { + + return xx.isUp(); + } + } catch (SocketException e) { + raise_log("NetworkIsActive exception = "+e.getMessage()); + } + return false; + } + + private Map _map_network = new HashMap<>(); + + /** + * Register Network Status + * will raise event_wifinetworstatus, event_cellularnetworkstatus, event_ethernetnetworkstatus, event_bluetoothnetworkstatus, event_vpnnetworkstatus, event_usbnetworkstatus + * @param networktype : WIFI, CELLULAR, ETHERNET, BLUETOOTH, VPN, USB + */ + public void StartMonitorNetworkStatus(String networktype) { + if (_cm != null) { + final String nettype = networktype.toUpperCase().trim(); + final String _event = eventname+ "_"+nettype.toLowerCase()+"networkstatus"; + final boolean need_event = ba!=null ? ba.subExists(_event) : false; + + if (BA.debugMode) { + if (need_event) { + raise_log("StartMonitorNetworkStatus, networktype="+networktype+" will raise " + _event); + } else { + raise_log("StartMonitorNetworkStatus, networktype="+networktype+" no event to raise"); + } + } + + NetworkRequest.Builder b = new NetworkRequest.Builder(); + + NetworkCallback ncb = new NetworkCallback() { + @Override + public void onAvailable(Network network) { + //raise_log(nettype + " NetworkCallback onAvailable, handle = "+network.getNetworkHandle()); + NetworkInterface_info nii = _map_network.get(network.getNetworkHandle()); + if (nii != null) { + nii.setAvailable(true); + } else { + nii = new NetworkInterface_info(ba, network); + nii.setAvailable(true); + _map_network.put(network.getNetworkHandle(), nii); + } + + if (need_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, _event, false, new Object[] {nii.getAvailable(), nii.getBlocked(), nii}); + } + } + + @Override + public void onLost(Network network) { + //raise_log(nettype + " NetworkCallback onLost, handle = "+network.getNetworkHandle()); + NetworkInterface_info nii = _map_network.get(network.getNetworkHandle()); + if (nii != null) { + nii.setAvailable(false); + } else { + nii = new NetworkInterface_info(ba, network); + nii.setAvailable(false); + _map_network.put(network.getNetworkHandle(), nii); + } + + if (need_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, _event, false, new Object[] {nii.getAvailable(), nii.getBlocked(), nii}); + } + } + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties lp) { + //raise_log(nettype + " NetworkCallback onLinkPropertiesChanged, handle = "+network.getNetworkHandle()); + NetworkInterface_info nii = _map_network.get(network.getNetworkHandle()); + if (nii != null) { + nii.setLinkProperties(lp); + } else { + nii = new NetworkInterface_info(ba, network); + nii.setLinkProperties(lp); + _map_network.put(network.getNetworkHandle(), nii); + } + + if (need_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, _event, false, new Object[] {nii.getAvailable(), nii.getBlocked(), nii}); + } + } + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { + //raise_log(nettype + " NetworkCallback onCapabilitiesChanged, handle = "+network.getNetworkHandle()); + NetworkInterface_info nii = _map_network.get(network.getNetworkHandle()); + if (nii != null) { + nii.setNetworkCapabilities(nc); + } else { + nii = new NetworkInterface_info(ba, network); + nii.setNetworkCapabilities(nc); + _map_network.put(network.getNetworkHandle(), nii); + } + if (need_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, _event, false, + new Object[] { nii.getAvailable(), nii.getBlocked(), nii }); + } + } + + @Override + public void onBlockedStatusChanged(Network network, boolean blocked) { + //raise_log(nettype + " NetworkCallback onBlockedStatusChanged, handle = "+network.getNetworkHandle()); + NetworkInterface_info nii = _map_network.get(network.getNetworkHandle()); + if (nii != null) { + nii.setBlocked(blocked); + } else { + nii = new NetworkInterface_info(ba, network); + nii.setBlocked(blocked); + _map_network.put(network.getNetworkHandle(), nii); + } + if (need_event) { + ba.raiseEventFromDifferentThread(Me, null, 0, _event, false, + new Object[] { nii.getAvailable(), nii.getBlocked(), nii }); + } + } + }; + + switch(nettype) { + case "WIFI": + b.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + ncb_wifi = ncb; + break; + case "CELLULAR": + b.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + ncb_cellular = ncb; + break; + case "ETHERNET": + b.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET); + ncb_ethernet = ncb; + break; + case "BLUETOOTH": + b.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH); + ncb_bluetooth = ncb; + break; + case "VPN": + b.addTransportType(NetworkCapabilities.TRANSPORT_VPN); + ncb_vpn = ncb; + break; + case "USB": + b.addTransportType(NetworkCapabilities.TRANSPORT_USB); + ncb_usb = ncb; + break; + } + + NetworkRequest nr = b.build(); + _cm.registerNetworkCallback(nr, ncb); + + } + } + + /** + * Unregister Network Status + * @param networktype : WIFI, CELLULAR, ETHERNET, BLUETOOTH, VPN, USB + */ + public void StopMonitorNetworkStatus(String networktype) { + if (_cm != null) { + final String nettype = networktype.toUpperCase().trim(); + switch(nettype) { + case "WIFI": + if (ncb_wifi != null) { + _cm.unregisterNetworkCallback(ncb_wifi); + ncb_wifi = null; + } + break; + case "CELLULAR": + if (ncb_cellular != null) { + _cm.unregisterNetworkCallback(ncb_cellular); + ncb_cellular = null; + } + break; + case "ETHERNET": + if (ncb_ethernet != null) { + _cm.unregisterNetworkCallback(ncb_ethernet); + ncb_ethernet = null; + } + break; + case "BLUETOOTH": + if (ncb_bluetooth != null) { + _cm.unregisterNetworkCallback(ncb_bluetooth); + ncb_bluetooth = null; + } + break; + case "VPN": + if (ncb_vpn != null) { + _cm.unregisterNetworkCallback(ncb_vpn); + ncb_vpn = null; + } + break; + case "USB": + if (ncb_usb != null) { + _cm.unregisterNetworkCallback(ncb_usb); + ncb_usb = null; + } + break; + + } + } + } + + /** + * Get Current Network Type + * possible return value : WIFI, CELLULAR, ETHERNET, BLUETOOTH, VPN, USB, UNKNOWN + * @return null if failed + */ + public String ActiveNetwork_Type() { + if (_cm != null) { + NetworkCapabilities capabilities = _cm.getNetworkCapabilities(_cm.getActiveNetwork()); + if (capabilities != null) { + + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return "WIFI"; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return "CELLULAR"; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return "ETHERNET"; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) { + return "BLUETOOTH"; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { + return "VPN"; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_USB)) { + return "USB"; + } else { + return "UNKNOWN"; + } + } + } + return null; + } + + /** + * Check if Current Network is connected to Internet + * @return true if connected, or false if not connected + */ + public boolean ActiveNetwork_IsInternetConnected() { + if (_cm!=null) { + NetworkCapabilities capabilities = _cm.getNetworkCapabilities(_cm.getActiveNetwork()); + if (capabilities != null) { + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + } + return false; + } + + /** + * Check if current network can ping to target + * @param target : target IP Address or Hostname + * @return true if can ping, or false if not + */ + public boolean ActiveNetwork_CanPing(String target) { + try { + Process pingprocess = Runtime.getRuntime().exec("ping -c 1 " + target); + int exitvalue = pingprocess.waitFor(); + return exitvalue == 0; + } catch (IOException e) { + if (BA.debugMode) raise_log("ActiveNetwork_CanPing exception = "+e.getMessage()); + } catch (InterruptedException e) { + if (BA.debugMode) raise_log("ActiveNetwork_CanPing exception = "+e.getMessage()); + } + return false; + } + + + /** + * Check if Network is a specific type + * @param n Network object + * @param type Type constant : TRANSPORT_WIFI, TRANSPORT_CELLULAR, TRANSPORT_ETHERNET, TRANSPORT_BLUETOOTH, TRANSPORT_VPN, TRANSPORT_USB + * @return true if it is, or false if it's not + */ + private boolean NetworkIsType(Network n, int type) { + if (n != null) { + if (_cm != null) { + NetworkCapabilities nc = _cm.getNetworkCapabilities(n); + if (nc != null) { + return nc.hasTransport(type); + } + } + } + return false; + } + + /** + * Check if Android have Wifi Network + * @return true if have, or false if not + */ + public boolean Have_Wifi_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + return networks.stream().anyMatch(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_WIFI)); + } + return false; + } + + /** + * Check if Android have Ethernet Network + * @return true if have, or false if not + */ + public boolean Have_Ethernet_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + return networks.stream().anyMatch(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_ETHERNET)); + } + return false; + } + + /** + * Check if Android have Cellular Network + * @return true if have, or false if not + */ + public boolean Have_Cellular_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + return networks.stream().anyMatch(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_CELLULAR)); + } + return false; + } + + /** + * Check if Android have Bluetooth Network + * + * @return true if have, or false if not + */ + public boolean Have_Bluetooth_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + return networks.stream().anyMatch(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_BLUETOOTH)); + } + return false; + } + + /** + * Check if Android have VPN Network + * + * @return true if have, or false if not + */ + public boolean Have_VPN_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + return networks.stream().anyMatch(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_VPN)); + } + return false; + } + + /** + * Check if Android have USB Network + * + * @return true if have, or false if not + */ + public boolean Have_USB_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + return networks.stream().anyMatch(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_USB)); + } + return false; + } + + + /** + * Get Network Interface with Wifi Type (first find, the rest not checked) + * @return null if not available + */ + public NetworkInterface_info Get_Wifi_Network() { + if (_cm!=null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + Network n = networks.stream().filter(e->NetworkIsType(e, NetworkCapabilities.TRANSPORT_WIFI )).findFirst().orElse(null); + if (n!=null) { + return new NetworkInterface_info(ba, n); + } + } + return null; + } + + /** + * Get Network Interface with Ethernet Type (firdt find, the rest not checked) + * @return null if not available + */ + public NetworkInterface_info Get_Ethernet_Network() { + if (_cm!=null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + Network n = networks.stream().filter(e->NetworkIsType(e, NetworkCapabilities.TRANSPORT_ETHERNET)).findFirst().orElse(null); + if (n!=null) { + return new NetworkInterface_info(ba, n); + } + } + + return null; + } + + /** + * Get Network Interface with Cellular Type (first find, the rest not checked) + * @return null if not available + */ + public NetworkInterface_info Get_Celluar_Network() { + if (_cm!=null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + Network n = networks.stream().filter(e->NetworkIsType(e, NetworkCapabilities.TRANSPORT_CELLULAR)).findFirst().orElse(null); + if (n!=null) { + return new NetworkInterface_info(ba, n); + } + } + return null; + } + + /** + * Get Network Interface with Bluetooth Type (first find, the rest not checked) + * @return null if not available + */ + public NetworkInterface_info Get_Bluetooth_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + Network n = networks.stream().filter(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_BLUETOOTH)) + .findFirst().orElse(null); + if (n != null) { + return new NetworkInterface_info(ba, n); + } + } + return null; + } + + /** + * Get Network Interface with VPN Type (first find, the rest not checked) + * @return null if not available + */ + public NetworkInterface_info Get_VPN_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + Network n = networks.stream().filter(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_VPN)).findFirst() + .orElse(null); + if (n != null) { + return new NetworkInterface_info(ba, n); + } + } + return null; + } + + /** + * Get Network Interface with USB Type (first find, the rest not checked) + * + * @return null if not available + */ + public NetworkInterface_info Get_USB_Network() { + if (_cm != null) { + @SuppressWarnings("deprecation") + List networks = Arrays.asList(_cm.getAllNetworks()); + Network n = networks.stream().filter(e -> NetworkIsType(e, NetworkCapabilities.TRANSPORT_USB)).findFirst() + .orElse(null); + if (n != null) { + return new NetworkInterface_info(ba, n); + } + } + return null; + } + + /** + * Return File Information in JsonObject + * Object structure : + * { + * FileName : short filename + * FileSize : size of file + * Duration : put 0 + * CreationTime : creation time + * AccessTime : access time + * ModifiedTime : modified time + * } + * @param filename filename to check + * @return null if failed + */ + public JsonObject GetFileInformation(String filename) { + + if (mycodes.valid_string(filename)) { + java.io.File f = new java.io.File(filename); + if (f.exists()) { + if (f.isFile()) { + try { + BasicFileAttributes attr = Files.readAttributes(f.toPath(), BasicFileAttributes.class); + JsonObject jo = new JsonObject(); + jo.InitializeEmpty(); + jo.PutString("FileName", f.getName()); + jo.PutLong("FileSize", f.length()); + jo.PutInt("Duration", 0); + jo.PutString("CreationTime", MakeDateTimeFromLong(attr.creationTime().to(TimeUnit.MILLISECONDS))); + jo.PutString("AccessTime", MakeDateTimeFromLong(attr.lastAccessTime().to(TimeUnit.MILLISECONDS))); + jo.PutString("ModifiedTime", MakeDateTimeFromLong(attr.lastModifiedTime().to(TimeUnit.MILLISECONDS))); + return jo; + } catch (IOException e) { + raise_log("GetFileInformation exception = "+e.getMessage()); + } + + + } + } + } + return null; + } + + private final String[] monthname = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; + + private String MakeDateTimeFromLong(long value) { + if (value>0) { + StringBuilder str = new StringBuilder(); + str.append(DateTime.GetDayOfMonth(value)).append(" "); + str.append(monthname[DateTime.GetMonth(value)-1]).append(" "); + str.append(DateTime.GetYear(value)).append(" "); + str.append(DateTime.GetHour(value)).append(":"); + str.append(DateTime.GetMinute(value)).append(":"); + str.append(DateTime.GetSecond(value)); + return str.toString(); + } + return ""; + } +} diff --git a/src/SBC/GetProp_Info.java b/src/SBC/GetProp_Info.java new file mode 100644 index 0000000..c5266ec --- /dev/null +++ b/src/SBC/GetProp_Info.java @@ -0,0 +1,130 @@ +package SBC; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("GetProp_Info") +public class GetProp_Info { + private final String regex = "\\[(\\S+)\\]: \\[(\\S+)\\]"; + private final Pattern p = Pattern.compile(regex); + private Map mm; + public GetProp_Info(String[] values) { + mm = new HashMap<>(); + if (values!=null && values.length>0) { + Stream.of(values).forEach(vv ->{ + Matcher xx = p.matcher(vv); + if (xx.find()) { + mm.put(xx.group(1), xx.group(2)); + } + }); + } + } + + public boolean HaveProperties() { + if (mm!=null) { + if (mm.size()>0) { + return true; + } + } + return false; + } + + public String[] GetKeys() { + if (mm!=null) { + if (mm.size()>0) { + return mm.keySet().toArray(new String[0]); + } + } + return null; + } + + public String getBuildType() { + return GetValue("ro.product.build.type",null); + } + + public String getBuildFingerprint() { + return GetValue("ro.product.build.fingerprint",GetValue("ro.build.fingerprint",null)); + } + + public String getBuildID() { + return GetValue("ro.build.id",null); + } + + public String getBuildDate() { + return GetValue("ro.build.date",null); + } + + public String getBuildProduct() { + return GetValue("ro.build.product",null); + } + + public String getAndroidVersion() { + return GetValue("ro.build.version.release",null); + } + + public String getAndroidSDKVersion() { + return GetValue("ro.build.version.sdk",null); + } + + public String getSecurityPatchDate() { + return GetValue("ro.build.version.security_patch",null); + } + + public String getBoardVersion() { + + return GetValue("ro.board.version",GetValue("ro.product.hardwareversion",null)); + } + + public String getSoftwareVersion() { + return GetValue("ro.sys.info.software_version",null); + } + + public String getBoardPlatform() { + return GetValue("ro.board.platform",null); + } + + public String getBoardName() { + return GetValue("ro.board.name", GetValue("ro.board.boardname",null)); + } + + public String getProductModel() { + return GetValue("ro.product.model",null); + } + + public String getProductName() { + return GetValue("ro.product.name", null); + } + + public String getNetHostName() { + return GetValue("net.hostname", null); + } + + public String getHardware() { + return GetValue("ro.hardware",null); + } + + public String getBoardBrand() { + return GetValue("ro.product.brand",null); + } + + public String GetValue(String key, String ifnotfound) { + if (key!=null && key.length()>0) { + String lcasekey = key.toLowerCase(); + if (HaveProperties()) { + String kk = mm.keySet().stream() + .filter(e -> e.toLowerCase().indexOf(lcasekey)!=-1) + .findFirst() + .orElse(null); + if (kk!=null && kk.length()>0) { + return mm.get(kk); + } + } + } + return ifnotfound; + } +} diff --git a/src/SBC/Hwmon_Info.java b/src/SBC/Hwmon_Info.java new file mode 100644 index 0000000..102dfd5 --- /dev/null +++ b/src/SBC/Hwmon_Info.java @@ -0,0 +1,90 @@ +package SBC; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.regex.Pattern; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("Hwmon_Info") +public class Hwmon_Info { + private String name; + private double temperature; + private double critical; + private String regex_temp_input = "temp(\\d+)_input"; + private String regex_temp_critical = "temp(\\d+)_crit"; + Hwmon_Info(File hwmondir){ + if (hwmondir!=null) { + if (hwmondir.isDirectory()) { + File[] files = hwmondir.listFiles(); + if (files!=null) { + for (File file : files) { + if (file.getName().contains("name")) { + name = readfile(file); + } + if (Pattern.matches(regex_temp_input, file.getName())) { + String _value = readfile(file); + temperature = Integer.parseUnsignedInt(_value)/1000.0; + } + if (Pattern.matches(regex_temp_critical, file.getName())) { + String _value = readfile(file); + critical = Integer.parseUnsignedInt(_value)/1000.0; + } + } + } + } + } + } + + private String readfile(File file) { + if (file!=null) { + if (file.isFile() && file.canRead()) { + try(BufferedReader reader = new BufferedReader(new FileReader(file))){ + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine())!= null) { + sb.append(line); + } + return sb.toString(); + } catch (FileNotFoundException e) { + + e.printStackTrace(); + } catch (IOException e) { + + e.printStackTrace(); + } + + + } + } + return null; + } + + Hwmon_Info(String name, String temperature, String critical){ + this.name = name; + this.temperature = Integer.parseUnsignedInt(temperature)/1000.0; + this.critical = Integer.parseUnsignedInt(critical)/1000.0; + } + + public String getName() { + return name; + } + + public double getTemperature() { + return temperature; + } + + public double getCritical() { + return critical; + } + + public boolean IsValid() { + if (name==null || temperature==0 || critical==0) { + return false; + } + return true; + } +} diff --git a/src/SBC/Ifconfig_Info.java b/src/SBC/Ifconfig_Info.java new file mode 100644 index 0000000..957643b --- /dev/null +++ b/src/SBC/Ifconfig_Info.java @@ -0,0 +1,201 @@ +package SBC; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("Ifconfig_Info") +public class Ifconfig_Info { + //final String regex = "^(\\S+)\\s+Link encap:(\\S+)\\s+HWaddr\\s+(\\S+).*\\s+inet addr:(\\S+)\\s+Bcast:(\\S+)\\s+Mask:(\\S+)\\n\\s+inet6 addr: (\\S+).*\\n.*MTU:(\\d+).*\\n.*RX packets:(\\d+) errors:(\\d+) dropped:(\\d+) overruns:(\\d+) frame:(\\d+)\\n.*TX packets:(\\d+) errors:(\\d+) dropped:(\\d+) overruns:(\\d+) carrier:(\\d+)\\n.*collisions:(\\d+) txqueuelen:(\\d+)\\n.*RX bytes:(\\d+).*TX bytes:(\\d+)\\n.*$"; + private final String regex_devicename = "(\\S+).*Link encap.*"; + private final String regex_devicetype = ".*Link encap:(\\S+).*"; + private final String regex_macaddress = ".*HWaddr (\\S+).*"; + private final String regex_driver = ".*Driver (\\S+).*"; + private final String regex_ipv4 = ".*inet addr:(\\S+).*"; + private final String regex_broadcastv4 = ".*Bcast:(\\S+).*"; + private final String regex_subnetv4 = ".*Mask:(\\S+).*"; + private final String regex_ipv6 = ".*inet6 addr: (\\S+).*"; + private final String regex_scope = ".*Scope: (\\S+).*"; + private final String regex_mtu = ".*MTU:(\\d+).*"; + private final String regex_rx_status = ".*RX packets:(\\d+).errors:(\\d+).dropped:(\\d+).overruns:(\\d+).frame:(\\d+).*"; + private final String regex_tx_status = ".*TX packets:(\\d+).errors:(\\d+).dropped:(\\d+).overruns:(\\d+).carrier:(\\d+).*"; + private final String regex_collision = ".*collisions:(\\d+).*"; + private final String regex_txqueuelen = ".*txqueuelen:(\\d+).*"; + private final String regex_rx_tx_bytes = ".*RX bytes:(\\d+).TX bytes:(\\d+).*"; + //private final String regex_interrupt = ".*Interrupt:(\\d+).*"; + private String _devicename; + private String _devicetype; + private String _macaddress; + private String _ipv4; + private String _broadcastv4; + private String _subnetv4; + private String _ipv6; + private int _mtu; + private int _rx_packet; + private int _rx_error; + private int _rx_dropped; + private int _rx_overrun; + private int _rx_frame; + private int _tx_packet; + private int _tx_error; + private int _tx_dropped; + private int _tx_overrun; + private int _tx_carrier; + private int _collision; + private int _tx_queue_len; + private long _rx_bytes; + private long _tx_bytes; + private String _driver; + private String _scope; + //private int _interrupt; + + public String getDeviceName() {return _devicename;} + public String getDeviceType() {return _devicetype;} + public String getMACAddress() {return _macaddress;} + public String getIPAddressV4() {return _ipv4;} + public String getBroadcastAddressV4() {return _broadcastv4;} + public String getSubnetMaskV4() {return _subnetv4;} + public String getIPAddressV6() {return _ipv6;} + public int getMTU() {return _mtu;} + public int getRX_Packet() {return _rx_packet;} + public int getRX_Error() {return _rx_error;} + public int getRX_Dropped() {return _rx_dropped;} + public int getRX_Overrun() {return _rx_overrun;} + public int getRX_Frame() {return _rx_frame;} + public int getTX_Packet() {return _tx_packet;} + public int getTX_Error() {return _tx_error;} + public int getTX_Dropped() {return _tx_dropped;} + public int getTX_Overrun() {return _tx_overrun;} + public int getTX_Carrier() {return _tx_carrier;} + public int getCollision() {return _collision;} + public int getTX_Queue_Length() {return _tx_queue_len;} + public long getRX_bytes() {return _rx_bytes;} + public long getTX_bytes() {return _tx_bytes;} + public String getDriver() {return _driver;} + public String getScope() {return _scope;} + //public int getInterrupt() {return _interrupt;} + + public boolean isValid() { + return valid_string(_devicename) & valid_string(_macaddress) & valid_string(_ipv4); + } + + public Ifconfig_Info(String[] values) { + if (values!=null && values.length>0) { + for(String xx : values) { + if (valid_string(xx)) { + if (Pattern.matches(regex_devicename, xx)) { + _devicename = getvalue(xx, regex_devicename,1, null); + } + if (Pattern.matches(regex_devicetype, xx)) { + _devicetype = getvalue(xx,regex_devicetype,1,null); + } + if (Pattern.matches(regex_macaddress, xx)) { + _macaddress = getvalue(xx,regex_macaddress,1,null); + } + if (Pattern.matches(regex_ipv4, xx)) { + _ipv4 = getvalue(xx,regex_ipv4,1,null); + } + if (Pattern.matches(regex_broadcastv4, xx)) { + _broadcastv4 = getvalue(xx,regex_broadcastv4,1,null); + } + if (Pattern.matches(regex_subnetv4, xx)) { + _subnetv4 = getvalue(xx,regex_subnetv4,1,null); + } + if (Pattern.matches(regex_ipv6, xx)) { + _ipv6 = getvalue(xx,regex_ipv6,1,null); + } + if (Pattern.matches(regex_mtu, xx)) { + _mtu = getvalue(xx,regex_mtu,1,0); + } + if (Pattern.matches(regex_rx_status, xx)) { + _rx_packet = getvalue(xx,regex_rx_status,1,0); + _rx_error = getvalue(xx,regex_rx_status,2,0); + _rx_dropped = getvalue(xx,regex_rx_status,3,0); + _rx_overrun = getvalue(xx,regex_rx_status,4,0); + _rx_frame = getvalue(xx,regex_rx_status,5,0); + } + if (Pattern.matches(regex_tx_status, xx)) { + _tx_packet = getvalue(xx,regex_tx_status,1,0); + _tx_error = getvalue(xx,regex_tx_status,2,0); + _tx_dropped = getvalue(xx,regex_tx_status,3,0); + _tx_overrun = getvalue(xx,regex_tx_status,4,0); + _tx_carrier = getvalue(xx,regex_tx_status,5,0); + } + if (Pattern.matches(regex_collision, xx)) { + _collision = getvalue(xx,regex_collision,1,0); + } + if (Pattern.matches(regex_driver, xx)) { + _driver = getvalue(xx,regex_driver,1,null); + } + if (Pattern.matches(regex_scope, xx)) { + _scope = getvalue(xx,regex_scope,1,null); + } + if (Pattern.matches(regex_txqueuelen, xx)) { + _tx_queue_len = getvalue(xx,regex_txqueuelen,1,0); + } + if (Pattern.matches(regex_rx_tx_bytes, xx)) { + _rx_bytes = getvalue(xx,regex_rx_tx_bytes,1,0l); + _tx_bytes = getvalue(xx,regex_rx_tx_bytes,2,0l); + } +// if (Pattern.matches(regex_interrupt, xx)) { +// _interrupt = getvalue(xx,regex_interrupt,1,0); +// } + } + } + } + } + + + + private boolean valid_string(String x) { + if (x!=null) { + if (x.length()>0) { + return true; + } + } + return false; + } + + private String getvalue(final String source, final String regex, int index, String ifnotexists) { + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(source); + if (m.find()) { + if (index<=m.groupCount()) { + return m.group(index); + } + } + return ifnotexists; + } + + private int getvalue(final String source, final String regex, int index, int ifnotexists) { + String result = getvalue(source,regex,index,null); + if (result!=null && result.length()>0) { + try { + int resultint = Integer.parseInt(result); + return resultint; + } catch(NumberFormatException e) { + + } + } + return ifnotexists; + } + + private long getvalue(final String source, final String regex, int index, long ifnotexists) { + String result = getvalue(source,regex,index,null); + if (result!=null && result.length()>0) { + try { + long resultlong = Long.parseLong(result); + return resultlong; + } catch(NumberFormatException e) { + + } + } + return ifnotexists; + } + + +} + + + + diff --git a/src/SBC/NetworkInterface_info.java b/src/SBC/NetworkInterface_info.java new file mode 100644 index 0000000..e9aa7b4 --- /dev/null +++ b/src/SBC/NetworkInterface_info.java @@ -0,0 +1,522 @@ +package SBC; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import androgpio.mycodes; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.IpPrefix; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.RouteInfo; +import anywheresoftware.b4a.*; + +@BA.ShortName("NetworkInterface_info") +public class NetworkInterface_info { + private NetworkInterface _networkinterface = null; + private Network _network = null; + private NetworkCapabilities _networkcapabilities = null; + private LinkProperties _linkproperties = null; + private final BA _ba; + private ConnectivityManager _cm = null; + + private boolean _blocked = false; + private boolean _available = false; + + /** + * Create uninitialized NetworkInterface_Info. + */ + public NetworkInterface_info() { + _ba = null; + } + + @BA.Hide + /** + * Create NetworkInterface_Info from NetworkInterface. + * @param net NetworkInterface to wrap. + */ + public NetworkInterface_info(BA ba, NetworkInterface net) { + _networkinterface = net; + _ba = ba; + if (_ba != null) { + if (_ba.context != null) { + _cm = (ConnectivityManager) _ba.context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + } + } + + @BA.Hide + /** + * Create NetworkInterface_Info from Network. + * @param net Network to wrap. + */ + public NetworkInterface_info(BA ba, Network net) { + _ba = ba; + if (_ba != null) { + if (_ba.context != null) { + _cm = (ConnectivityManager) _ba.context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (_cm!=null) { + if (net != null) { + LinkProperties prop = _cm.getLinkProperties(net); + if (prop != null) { + try { + _networkinterface = NetworkInterface.getByName(prop.getInterfaceName()); + _available = true; + } catch (SocketException e) { + + } + } + } + + } + } + } + } + + public boolean getAvailable() { + return _available; + } + + @BA.Hide + public void setAvailable(boolean value) { + _available = value; + } + + public boolean getBlocked() { + return _blocked; + } + + @BA.Hide + public void setBlocked(boolean value) { + _blocked = value; + } + + @BA.Hide + public void setNetwork(Network net) { + _network = net; + } + + @BA.Hide + public Network getNetwork() { + return _network; + } + + @BA.Hide + public void setNetworkCapabilities(NetworkCapabilities nc) { + _networkcapabilities = nc; + } + + @BA.Hide + public NetworkCapabilities getNetworkCapabilities() { + return _networkcapabilities; + } + + @BA.Hide + public void setLinkProperties(LinkProperties lp) { + _linkproperties = lp; + } + + @BA.Hide + public LinkProperties getLinkProperties() { + return _linkproperties; + } + + private NetworkInterface LinkPropertiesToNetworkInterface() { + if (_linkproperties != null) { + try { + return NetworkInterface.getByName(_linkproperties.getInterfaceName()); + } catch (SocketException e) { + + } + } + return null; + } + + /** + * Check if the network interface is up and running. + * @return true if the network interface is up, false if not available or not up. + */ + public boolean isUp() { + + if (_networkinterface ==null) _networkinterface = LinkPropertiesToNetworkInterface(); + + if (_networkinterface != null) { + try { + return _networkinterface.isUp(); + } catch (SocketException e) { + } + } + return false; + } + + /** + * Get NetworkInterface name. + * @return null if not available. + */ + public String GetName() { + if (_networkinterface == null) _networkinterface = LinkPropertiesToNetworkInterface(); + + if (_networkinterface != null) { + return _networkinterface.getName(); + } + return null; + } + + /** + * Get MAC Address of the network interface. + * @return null if not available. + */ + public String GetMACAddress() { + if (_networkinterface == null) _networkinterface = LinkPropertiesToNetworkInterface(); + if (_networkinterface != null) { + try { + byte[] mac = _networkinterface.getHardwareAddress(); + if (mac != null) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mac.length; i++) { + sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? ":" : "")); + } + return sb.toString(); + } + } catch (SocketException e) { + } + } + return null; + } + + /** + * Check if the network interface supports multicast. + * @return true if supports multicast, false if not available or not supported. + */ + public boolean SupportsMulticast() { + if (_networkinterface == null) _networkinterface = LinkPropertiesToNetworkInterface(); + if (_networkinterface != null) { + try { + return _networkinterface.supportsMulticast(); + } catch (SocketException e) { + } + } + return false; + } + + /** + * Get IP Address of the network interface. + * @param mode 0 = IPv4, 1 = IPv6, other = Both + * @return comma separated string of IP Addresses. + */ + public String GetInetAddresses_String(int mode) { + String[] addresses = GetInetAddresses(mode); + return String.join(",", addresses); + } + + /** + * Get Gateway IPV4 Address. + * @return "0.0.0.0" if not available + */ + public String GetGateway() { + if (_linkproperties!=null) { + for (RouteInfo route : _linkproperties.getRoutes()) { + if (route.isDefaultRoute()) { + InetAddress gateway = route.getGateway(); + if (gateway.getAddress().length == 4) { + return gateway.getHostAddress(); + } + } + } + } else BA.Log("GetGateway failed, linkproperties is null"); + return "0.0.0.0"; + } + + /** + * Get Netmask of the network interface. + * @return "0.0.0.0" if failed + */ + public String GetNetmask() { + if (_linkproperties!=null) { + for (RouteInfo route : _linkproperties.getRoutes()) { + if (route.isDefaultRoute()) { + IpPrefix prefix = route.getDestination(); + return prefix.getPrefixLength()+""; + } + } + } else BA.Log("GetNetmask failed, linkproperties is null"); + return "0.0.0.0"; + } + + /** + * Get IP Address of the network interface. + * @param mode 0 = IPv4, 1 = IPv6, other = Both + * @return array of IP Addresses in string. + */ + public String[] GetInetAddresses(int mode) { + if (_networkinterface == null) _networkinterface = LinkPropertiesToNetworkInterface(); + if (_networkinterface!=null) { + Enumeration inetAddresses = _networkinterface.getInetAddresses(); + if (inetAddresses != null) { + List result = new ArrayList(); + while (inetAddresses.hasMoreElements()) { + InetAddress addr = inetAddresses.nextElement(); + if (addr.isLoopbackAddress()) { + continue; + } + + switch(mode) { + case 0: + if (addr.getAddress().length == 4) { + result.add(addr.getHostAddress()); + } + + break; + case 1: + if (addr.getAddress().length == 16) { + result.add(addr.getHostAddress()); + } + + break; + default: + result.add(addr.getHostAddress()); + break; + } + + + } + return result.toArray(new String[0]); + } + } + return new String[0]; + } + + /** + * Get the maximum transmission unit (MTU) of the network interface. + * @return 0 if not available. + */ + public int GetMTU() { + if (_networkinterface == null) _networkinterface = LinkPropertiesToNetworkInterface(); + if (_networkinterface != null) { + try { + return _networkinterface.getMTU(); + } catch (SocketException e) { + } + } + return 0; + } + + /** + * Get Connectivity Type of the network interface. + * Possible values are WIFI, CELLULAR, ETHERNET, USB, BLUETOOTH, VPN, UNKNOWN + * @return null if failed + */ + public String GetConnectivityType() { + if (_networkcapabilities != null) { + if (_networkcapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return "WIFI"; + } else if (_networkcapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return "CELLULAR"; + } else if (_networkcapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return "ETHERNET"; + } else if (_networkcapabilities.hasTransport(NetworkCapabilities.TRANSPORT_USB)) { + return "USB"; + } else if (_networkcapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) { + return "BLUETOOTH"; + } else if (_networkcapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { + return "VPN"; + } else { + return "UNKNOWN"; + } + } + return null; + } + + /** + * Check if the network interface is configured for internet and validated. + * @return false if not confirmed + */ + public boolean HaveInternetCapability() { + if (_networkcapabilities != null) { + return _networkcapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + && _networkcapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + return false; + } + + + + /** + * Check if the network interface is internet connected + * Wait time to check is 3 seconds. + * @param host specific host to use for checking internet connection. + * On null or empty, will default to www.google.com + * @param timeout timeout in milliseconds. On 0 or negative, will default to 3000. + * @return true if the network interface is internet connected. + */ + public boolean CanConnectToHost(String host, int timeout) { + if (isUp()) { + if (host == null || host.isEmpty()) { + host = "www.google.com"; + } + + if (timeout <= 0) { + timeout = 3000; + } + + if (_network!=null) { + try{ + URLConnection urlconnect = _network.openConnection(new URL("https://" + host)); + urlconnect.setConnectTimeout(timeout); + urlconnect.connect(); + return true; + } catch(SocketTimeoutException e) { + if (BA.debugMode) BA.Log("CanConnectToHost timeout: " + e.getMessage()); + } catch (IOException e) { + if (BA.debugMode) BA.Log("CanConnectToHost exception: " + e.getMessage()); + } + } + } + return false; + } + + /** + * Get DHCP Server IPV4 Address. + * IPV6 is not supported. + * @return "0.0.0.0" if not available. + */ + public String GetDHCPServer() { + if (_linkproperties != null) { + InetAddress inet = _linkproperties.getDhcpServerAddress(); + if (inet != null) { + return inet.getHostAddress(); + } + } + return "0.0.0.0"; + } + + /** + * Get DNS Server IPV4 Address. + * @return array of DNS Server IP Addresses in string. + */ + public String[] GetDNSServer() { + if (_linkproperties != null) { + List dns = _linkproperties.getDnsServers(); + if (dns != null) { + List result = new ArrayList(); + for (InetAddress addr : dns) { + result.add(addr.getHostAddress()); + } + return result.toArray(new String[0]); + } + } + return new String[0]; + } + + /** + * Get Downstream Bandwidth of the network interface. + * + * @return upstream in Kbps, or 0 if not available. + */ + public int GetUpstreamBandwidth() { + if (_networkcapabilities != null) { + return _networkcapabilities.getLinkUpstreamBandwidthKbps(); + } + return 0; + } + + /** + * Get Downstream Bandwidth of the network interface. + * + * @return downstream in Kbps, or 0 if not available. + */ + public int GetDownstreamBandwidth() { + if (_networkcapabilities != null) { + return _networkcapabilities.getLinkDownstreamBandwidthKbps(); + } + return 0; + } + + @BA.Hide + /** + * Set DNS Server IPV4 Address. + * + * @param dns1 : DNS Server 1 in IPV4 Address + * @param dns2 : DNS Server 2 in IPV4 Address + * @return true if success, false if failed. + */ + public boolean SetDNS(String dns1, String dns2) { + //TODO masih belum jelas + if (_linkproperties != null) { + List dns = new ArrayList(); + if (mycodes.valid_string(dns1)) { + try { + dns.add(InetAddress.getByName(dns1)); + } catch(UnknownHostException e) { + if (BA.debugMode) BA.Log("SetDNS exception on dns1: " + e.getMessage()); + } + } else if (BA.debugMode) BA.Log("SetDNS invalid dns1"); + + if (mycodes.valid_string(dns2)) { + try { + dns.add(InetAddress.getByName(dns2)); + } catch (UnknownHostException e) { + if (BA.debugMode) BA.Log("SetDNS exception on dns2: " + e.getMessage()); + } + } else if (BA.debugMode) BA.Log("SetDNS invalid dns2"); + + _linkproperties.setDnsServers(dns); + return true; + } + return false; + } + + @BA.Hide + /** + * Set DHCP Server IPV4 Address. + * @param dhcp + * @return true if success, false if failed. + */ + public boolean SetDHCPServer(String dhcp) { + //TODO masih belum jelas + if (_linkproperties != null) { + try { + _linkproperties.setDhcpServerAddress((Inet4Address) Inet4Address.getByName(dhcp)); + return true; + } catch (UnknownHostException e) { + if (BA.debugMode) BA.Log("SetDHCPServer exception: " + e.getMessage()); + } + } + return false; + } + + @BA.Hide + public boolean SetStaticIP(String ip) { + //TODO masih belum jelas + if (_linkproperties != null) { + + } + return false; + } + + + @BA.Hide + private List toInetAddressList(String[] addresses) { + List result = new ArrayList(); + for (String addr : addresses) { + try { + result.add(InetAddress.getByName(addr)); + } catch (IOException e) { + + } + } + return result; + } +} diff --git a/src/SBC/NetworkMeterInfo.java b/src/SBC/NetworkMeterInfo.java new file mode 100644 index 0000000..4457fce --- /dev/null +++ b/src/SBC/NetworkMeterInfo.java @@ -0,0 +1,330 @@ +package SBC; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.DateTime; +import anywheresoftware.b4a.objects.collections.Map; + +@BA.ShortName("NetworkMeterInfo") +public class NetworkMeterInfo { + public final String InterfaceName; + public final long ticktime; + public final long Total_RX_Bytes; + public final long Total_TX_Bytes; + public final long Total_RX_Packets; + public final long Total_TX_Packets; + public final long Total_RX_Errors; + public final long Total_TX_Errors; + public final long Total_RX_Drop; + public final long Total_TX_Drop; + + + private boolean isvalid = false; + + private long rx_pps = 0; + private long tx_pps = 0; + private long rx_bps = 0; + private long tx_bps = 0; + public NetworkMeterInfo() { + InterfaceName=""; + ticktime = DateTime.getNow(); + Total_RX_Bytes = 0; + Total_TX_Bytes = 0; + Total_RX_Packets =0; + Total_TX_Packets = 0; + Total_RX_Errors = 0; + Total_TX_Errors = 0; + Total_RX_Drop = 0; + Total_TX_Drop = 0; + isvalid = false; + } + public NetworkMeterInfo(String ifname, long tick, long rx_bytes, long tx_bytes, long rx_packets, long tx_packets, long rx_errors, long tx_errors, long rx_drop, long tx_drop) { + InterfaceName = ifname; + ticktime = tick; + Total_RX_Bytes = rx_bytes; + Total_TX_Bytes = tx_bytes; + Total_RX_Packets = rx_packets; + Total_TX_Packets = tx_packets; + Total_RX_Errors = rx_errors; + Total_TX_Errors = tx_errors; + Total_RX_Drop = rx_drop; + Total_TX_Drop = tx_drop; + if (!InterfaceName.isEmpty()) { + if (ticktime>0) { + if (Total_TX_Bytes>0) { + if (Total_RX_Bytes>0) { + if (Total_TX_Packets>0) { + if (Total_RX_Packets>0) { + isvalid = true; + } + } + } + } + } + } + } + + /** + * Check if contain valid value + * @return true if valid + */ + public boolean IsValidValue() { + return isvalid; + } + + /** + * RX Bytes per second + * @return value in long + */ + public long RX_BytesPerSecond() { + return rx_bps; + } + + /** + * RX Packets per second + * @return value in long + */ + public long RX_PacketsPerSecond() { + return rx_pps; + } + + /** + * TX Bytes per second + * @return value in long + */ + public long TX_BytesPerSecond() { + return tx_bps; + } + + /** + * TX Packets per second + * @return value in long + */ + public long TX_PacketsPerSecond() { + return tx_pps; + } + + public long TX_Errors() {return Total_TX_Errors;} + + public long RX_Errors() {return Total_RX_Errors;} + + public long RX_Drop() {return Total_RX_Drop;} + + public long TX_Drop() {return Total_TX_Drop;} + + protected void calculate(NetworkMeterInfo prev) { + if (prev instanceof NetworkMeterInfo) { + if (prev.isvalid) { + rx_pps = Total_RX_Packets - prev.Total_RX_Packets; + tx_pps = Total_TX_Packets - prev.Total_TX_Packets; + rx_bps = Total_RX_Bytes - prev.Total_RX_Bytes; + tx_bps = Total_TX_Bytes - prev.Total_TX_Bytes; + + if (rx_pps<1) rx_pps = 0; // tidak boleh negatif + if (tx_pps<1) tx_pps = 0; + if (rx_bps<1) rx_bps = 0; + if (tx_bps<1) tx_bps = 0; + } + } + } + + private final long kp = 1000; + private final long mp = kp * 1000; + private final long gp = mp * 1000; + private final long tp = gp * 1000; + private final long kb = 1024; + private final long mb = kb * 1024; + private final long gb = mb * 1024; + private final long tb = gb * 1024; + + /** + * Current Total received packets, in readable string + * @return value in string + */ + public String Current_RX_PacketTotal() { + if (this.Total_RX_Packets < kp) { + return String.valueOf(Total_RX_Packets); + } else if (Total_RX_Packets < mp) { + return pembulatan_1_desimal(Total_RX_Packets, kp)+" K"; + } else if (Total_RX_Packets _cpumap = new HashMap<>(); + private long _boottime = 0; + private int _processes = 0; + private int _procsrunning = 0; + private int _procsblocked = 0; + + /** + * Get the time which the System booted, in seconds since January 1, 1970. + * If want to get DateTime tick (long), multiply by DateTime.TicksPerSecond. + * @return seconds since January 1, 1970. + */ + public long getBoottime() { + return _boottime; + } + + /** + * Get the Boottime in String format. + * @return Date and Time in String format. + */ + public String getBootTimeString() { + long tick = _boottime * DateTime.TicksPerSecond; + return DateTime.Date(tick)+" "+DateTime.Time(tick); + } + + /** + * Get the number of created processes and threads. + * @return of created processes and threads. + */ + public int getProcesses() { + return _processes; + } + + /** + * Get the number of processes currently running in CPU + * @return running processes in CPU. + */ + public int getProcessRunning() { + return _procsrunning; + } + + /** + * Get the number of processes currently blocked, waiting for I/O to complete. + * @return blocked processes + */ + public int getProcessBlocked() { + return _procsblocked; + } + + public cpudata getTotalCpuData() { + return _cpudata; + } + + public cpudata[] getCpuData() { + return _cpumap.values().toArray(new cpudata[_cpumap.size()]); + } + + public ProcStat_Info(String[] values) { + if (values != null && values.length > 0) { + for(String vv : values) { + if (Pattern.matches(regex_cpu, vv)) { + Matcher m = Pattern.compile(regex_cpu).matcher(vv); + if (m.find()) { + int _user = getvalue(m.group(1), 0); + int _nice = getvalue(m.group(2), 0); + int _system = getvalue(m.group(3), 0); + int _idle = getvalue(m.group(4), 0); + int _iowait = getvalue(m.group(5), 0); + int _irq = getvalue(m.group(6), 0); + int _softirq = getvalue(m.group(7), 0); + int _steal = getvalue(m.group(8), 0); + int _guest = getvalue(m.group(9), 0); + int _guestnice = getvalue(m.group(10), 0); + _cpudata = new cpudata("Total",_user,_nice,_system,_idle,_iowait,_irq,_softirq,_steal,_guest,_guestnice); + + } + } + if (Pattern.matches(regex_cpuN, vv)) { + + Matcher m = Pattern.compile(regex_cpuN).matcher(vv); + if (m.find()) { + String _name = m.group(1); + int _user = getvalue(m.group(2), 0); + int _nice = getvalue(m.group(3), 0); + int _system = getvalue(m.group(4), 0); + int _idle = getvalue(m.group(5), 0); + int _iowait = getvalue(m.group(6), 0); + int _irq = getvalue(m.group(7), 0); + int _softirq = getvalue(m.group(8), 0); + int _steal = getvalue(m.group(9), 0); + int _guest = getvalue(m.group(10), 0); + int _guestnice = getvalue(m.group(11), 0); + cpudata n = new cpudata(_name,_user,_nice,_system,_idle,_iowait,_irq, _softirq,_steal,_guest,_guestnice); + _cpumap.put(_name, n); + + } + } + if (Pattern.matches(regex_boottime, vv)) { + + Matcher m = Pattern.compile(regex_boottime).matcher(vv); + if (m.find()) { + _boottime = Long.parseLong(m.group(1)); + + } + } + if (Pattern.matches(regex_processes, vv)) { + + Matcher m = Pattern.compile(regex_processes).matcher(vv); + if (m.find()) { + _processes = Integer.parseInt(m.group(1)); + + } + } + if (Pattern.matches(regex_procsrunning, vv)) { + + Matcher m = Pattern.compile(regex_procsrunning).matcher(vv); + if (m.find()) { + _procsrunning = Integer.parseInt(m.group(1)); + + } + } + if (Pattern.matches(regex_procsblocked, vv)) { + + Matcher m = Pattern.compile(regex_procsblocked).matcher(vv); + if (m.find()) { + _procsblocked = Integer.parseInt(m.group(1)); + + } + } + + } + } + } + + private int getvalue(String value, int defaultValue) { + try { + return Integer.parseInt(value); + } catch (Exception e) { + return defaultValue; + } + } + +} diff --git a/src/SBC/RAM_Info.java b/src/SBC/RAM_Info.java new file mode 100644 index 0000000..6eeadb6 --- /dev/null +++ b/src/SBC/RAM_Info.java @@ -0,0 +1,195 @@ +package SBC; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.Map; + +@BA.ShortName("RAM_Info") +public class RAM_Info { + + private double _jvmtotalmemory; + private double _jvmfreememory; + private double _jvmmaxmemory; + private double _jvmfreepercentage; + private final String memtotal_pattern = "^MemTotal:\\s+(\\d+)\\skB$"; + private double _memtotal; + private final String memfree_pattern = "^MemFree:\\s+(\\d+)\\skB$"; + private double _memfree; + private final String memavailable_pattern = "^MemAvailable:\\s+(\\d+)\\skB$"; + private double _memavailable; + private double _memfreepercentage; + private final String swaptotal_pattern = "^SwapTotal:\\s+(\\d+)\\skB$"; + private double _swaptotal; + private final String swapfree_pattern = "^SwapFree:\\s+(\\d+)\\skB$"; + private double _swapfree; + private double _swapfreepercentage; // tambahan + + private final int _KB = 1000; + + + public double JVMTotalMemory_KB() { + return _jvmtotalmemory; + } + + public double JVMFreeMemory_KB() { + return _jvmfreememory; + } + + public double JVMMaxMemory_KB() { + return _jvmmaxmemory; + } + + public double JVMFreePercentage() { + return _jvmfreepercentage; + } + + public double MemTotal_KB() { + return _memtotal; + } + + public double MemTotal() { + return _memtotal * _KB; + } + + public double MemFree_KB() { + return _memfree; + } + + public double MemFree() { + return _memfree * _KB; + } + + public double MemUsed_KB() { + return _memtotal - _memfree; + } + + public double MemUsed() { + return MemUsed_KB() * _KB; + } + + public double MemAvailable_KB() { + return _memavailable; + } + + public double MemAvailable() { + return _memavailable * _KB; + } + + public double MemFreePercentage() { + return _memfreepercentage; + } + + public double SwapTotal_KB() { + return _swaptotal; + } + + public double SwapFree_KB() { + return _swapfree; + } + + public double SwapFreePercentage() { + return _swapfreepercentage; + } + + public RAM_Info(String[] values) { + Runtime jvm = java.lang.Runtime.getRuntime(); + _jvmtotalmemory = pembulatan_1desimal(jvm.totalMemory() / 1024.0); + _jvmfreememory = pembulatan_1desimal(jvm.freeMemory() / 1024.0); + _jvmmaxmemory = pembulatan_1desimal(jvm.maxMemory() / 1024.0); + _jvmfreepercentage = pembulatan_1desimal((_jvmfreememory/_jvmtotalmemory)*100); + Stream.of(values).forEach(xx ->{ + if (xx.matches(memavailable_pattern)) { + _memavailable = getvalue(xx, memavailable_pattern); + } + if (xx.matches(memfree_pattern)) { + _memfree = getvalue(xx, memfree_pattern); + } + if (xx.matches(memtotal_pattern)) { + _memtotal = getvalue(xx, memtotal_pattern); + } + if (xx.matches(swapfree_pattern)) { + _swapfree = getvalue(xx, swapfree_pattern); + } + if (xx.matches(swaptotal_pattern)) { + _swaptotal = getvalue(xx, swaptotal_pattern); + } + }); + } + + private double getvalue(String value, String pattern) { + Matcher m = Pattern.compile(pattern).matcher(value); + if (m.find()) + return Double.valueOf(m.group(1)); + else + return 0; + } + + + + private double pembulatan_1desimal(double value) { + int temp = (int)(value * 10); + return (temp / 10.0); + } + + private final double memory_MB = 1024; + private final double memory_GB = memory_MB * 1024; + private final double memory_TB = memory_GB * 1024; + /** + * Return an easy to read from values inside this class + * @param value : Member of this class, example MemTotal_KB, or MemAvailable_KB + * @return String value + */ + public String EasyRead_Value(double value) { + if (value < memory_MB) { + return pembulatan_1desimal(value)+ " KB"; + } else if (value < memory_GB) { + return pembulatan_1desimal(value / memory_MB)+" MB"; + } else if (value < memory_TB) { + return pembulatan_1desimal(value / memory_GB)+" GB"; + } else return pembulatan_1desimal(value / memory_TB)+" TB"; + } + + /** + * Return an easy to read from values inside this class + * @param value value to read + * @return Size_Info object + */ + public Size_Info EasyRead_SizeInfo(double value) { + Size_Info xx = new Size_Info(); + xx.SetInKB(value); + return xx; + //TODO hapus kalau sudah benar +// if (value < memory_MB) { +// return new Size_Info(pembulatan_1desimal(value), "KB"); +// } else if (value < memory_GB) { +// return new Size_Info(pembulatan_1desimal(value / memory_MB), "MB"); +// } else if (value < memory_TB) { +// return new Size_Info(pembulatan_1desimal(value / memory_GB), "GB"); +// } else +// return new Size_Info(pembulatan_1desimal(value / memory_TB), "TB"); + } + + /** + * Create values in JSON Map + * @return Map value + */ + public Map MakeJSONMap() { + Map result = new Map(); + result.Initialize(); + result.Put("JVMTotalMemory", EasyRead_Value(this._jvmtotalmemory)); + result.Put("JVMFreeMemory", EasyRead_Value(this._jvmfreememory)); + result.Put("JVMMaxMemory", EasyRead_Value(this._jvmmaxmemory)); + result.Put("JVMFreePercentage", this._jvmfreepercentage); + result.Put("MemTotal", EasyRead_Value(this._memtotal)); + result.Put("MemFree", EasyRead_Value(this._memfree)); + result.Put("MemFreePercentage", this._memfreepercentage); + result.Put("MemAvailable", EasyRead_Value(this._memavailable)); + result.Put("SwapTotal", EasyRead_Value(this._swaptotal)); + result.Put("SwapFree", EasyRead_Value(this._swapfree)); + result.Put("SwapFreePercentage", this._swapfreepercentage); + return result; + } +} diff --git a/src/SBC/RaspberryPi/Pi4.java b/src/SBC/RaspberryPi/Pi4.java new file mode 100644 index 0000000..30cc8db --- /dev/null +++ b/src/SBC/RaspberryPi/Pi4.java @@ -0,0 +1,369 @@ +package SBC.RaspberryPi; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import SBC.BasicSBCInfo; +import SBC.WifiInfo; +import anywheresoftware.b4a.BA; + + +@BA.ShortName("RaspberryPi4") +public class Pi4 extends BasicSBCInfo { + + public Pi4() { + super(); + } + + + /** + * Equal Pi4J GPIO 8 + * atau GPIO02 di referensi Linux + * Dipakai I2C 1 (SDA) + */ + public final int pin03 = 2; + + /** + * Equal Pi4J GPIO 9 + * atau GPIO03 di referensi Linux + * Dipakai I2C 1 (SCL) + */ + public final int pin05 = 3; + + /** + * Equal Pi4J GPIO 7 + * atau GPIO04 di referensi Linux + * Dipakai GPCLK0 + */ + public final int pin07 = 4; + + /** + * Equal Pi4J GPIO 0 + * atau GPIO17 di referensi Linux + */ + public final int pin11 = 17; + + /** + * Equal Pi4J GPIO 2 + * atau GPIO27 di referensi Linux + * Biasa dipakai sebagai Relay 1 di PCB Leon + */ + public final int pin13 = 27; + + /** + * PCB Leon, Relay 1, di pin 13 + */ + public final int PCB_Relay1 = pin13; + + /** + * Equal Pi4J GPIO 3 + * atau GPIO22 di referensi Linux + * Biasa dipakai sebagai Relay 2 di PCB Leon + */ + public final int pin15 = 22; + + /** + * PCB Leon, Relay 2, di pin 15 + */ + public final int PCB_Relay2 = pin15; + + + /** + * Equal Pi4J GPIO 12 + * atau GPIO10 di referensi Linux + * Dipakai SPI 0 (MOSI) + * Biasa dipakai sebagai Relay 3 di PCB Leon + */ + public final int pin19 = 10; + + /** + * PCB Leon, relay 3 , di pin 19 + */ + public final int PCB_Relay3 = pin19; + + /** + * Equal Pi4J GPIO 13 + * atau GPIO09 di referensi Linux + * Dipakai SPI 0 (MISO) + * Biasa dipakai sebagai Relay 4 di PCB Leon + */ + public final int pin21 = 9; + + /** + * PCB Leon, relay 4 , di pin 21 + */ + public final int PCB_Relay4 = pin21; + + /** + * Equal Pi4J GPIO 14 + * atau GPIO11 di referensi Linux + * Dipakai SPI 0 (CLK) + * Biasa dipakai sebagai Action LED di PCB Leon + */ + public final int pin23 = 11; + + /** + * PCB Leon, Action LED, di pin 23 + */ + public final int PCB_ActionLED = pin23; + + /** + * Equal Pi4J GPIO 30 + * atau GPIO00 di referensi Linux + * Dipakai I2C 0 (SDA) + */ + public final int pin27 = 0; + + /** + * Equal Pi4J GPIO 21 + * atau GPIO05 di referensi Linux + * Biasa dipakai sebagai Network LED di PCB Leon + */ + public final int pin29 = 5; + + /** + * PCB Leon, Network LED, di pin 29 + */ + public final int PCB_NetworkLED = pin29; + + /** + * Equal Pi4J GPIO 22 + * atau GPIO6 di referensi Linux + */ + public final int pin31 = 6; + + /** + * Equal Pi4J GPIO 23 + * atau GPIO13 di referensi Linux + * Dipakai PWM 1 + */ + public final int pin33 = 13; + + /** + * Equal Pi4J GPIO 24 + * atau GPIO19 di referensi Linux + */ + public final int pin35 = 19; + + /** + * Equal Pi4J GPIO 25 + * atau GPIO26 di referensi Linux + */ + public final int pin37 = 26; + + /** + * Equal Pi4J GPIO 15 + * atau GPIO14 di referensi Linux + * Dipakai UART 0 (TXD) + */ + public final int pin08 = 14; + + /** + * Equal Pi4J GPIO 16 + * atau GPIO15 di referensi Linux + * Dipakai UART 0 (RXD) + */ + public final int pin10 = 15; + + /** + * Equal Pi4J GPIO 1 + * atau GPIO18 di referensi Linux + * Dipakai PWM 0 + * Biasa dipakai sebagai Button 1 di PCB Leon + */ + public final int pin12 = 18; + + /** + * PCB Leon, Button 1 , di pin 12; + */ + public final int PCB_Button1 = pin12; + + /** + * Equal Pi4J GPIO 4 + * atau GPIO23 di referensi Linux + * Biasa dipakai sebagai Button 2 di PCB Leon + */ + public final int pin16 = 23; + + /** + * PCB Leon, Button 2, di pin 16 + */ + public final int PCB_Button2 = pin16; + + /** + * Equal Pi4J GPIO 5 + * atau GPIO24 di referensi Linux + * Biasa dipakai sebagai Button 3 di PCB Leon + */ + public final int pin18 = 24; + + /** + * PCB Leon, Button 3, di pin 18 + */ + public final int PCB_Button3 = pin18; + + /** + * Equal Pi4J GPIO 6 + * atau GPIO25 di referensi Linux + * Biasa dipakai sebagai Button 4 di PCB Leon + */ + public final int pin22 = 25; + + /** + * PCB Leon, Button 4, di pin 22 + */ + public final int PCB_Button4 = pin22; + + /** + * Equal Pi4J GPIO 10 + * atau GPIO8 di referensi Linux + * dipakai SPI 0 (Chip Enable 0) + * Biasa dipakai sebagai Button 5 di PCB Leon + */ + public final int pin24 = 8; + + /** + * PCB Leon, Button 5 , di pin 24 + */ + public final int PCB_Button5 = pin24; + + /** + * Equal Pi4J GPIO 11 + * atau GPIO7 di referensi Linux + * Dipakai SPI 0 (Chip Enable 1) + */ + public final int pin26 = 7; + + /** + * Equal Pi4J GPIO 26 + * atau GPIO12 di referensi Linux + * Dipakai PWM 0 + */ + public final int pin32 = 12; + + /** + * Equal Pi4J GPIO 27 + * atau GPIO16 di referensi Linux + */ + public final int pin36 = 16; + + /** + * Equal Pi4J GPIO 28 + * atau GPIO20 di referensi Linux + */ + public final int pin38 = 20; + + /** + * Equal Pi4J GPIO 29 + * atau GPIO21 di referensi Linux + */ + public final int pin40 = 21; + + public final String wlan_name = "wlan0"; + public final String eth_name = "eth0"; + + /** + * Nama untuk serial Bluetooth adalah ttyAMA0 + */ + public final String serial_Bluetooth = "/dev/ttyAMA0"; + + /** + * Nama untuk serial port UART adalah ttyS0 + */ + public final String serial_UART = "/dev/ttyS0"; + + /** + * I2C Port 1 + */ + public final String i2c_port1 = "/dev/i2c-1"; + + /** + * I2C Port 20, somehow bisa detect ini ?? + */ + public final String i2c_port20 = "/dev/i2c-20"; + + /** + * I2C Port 21, somehow bisa detect ini ?? + */ + public final String i2c_port21 = "/dev/i2c-21"; + + + /** + * Belum bisa, return 0 + */ + @Override + public double Get_CPU_CoreVolt() { + // TODO Auto-generated method stub + return 0; + } + + /** + * Initialize Pi4 for Android + * @param {Object} callerobject callerobject + * @param {String} event eventname + */ + public void Initialize(BA bax, Object callerobject, String event) { + setup_events(bax, callerobject, event, this); + + } + + /** + * Try to get Wifi Information + * @param wlanname wifi device name + * @return null if failed + */ + public WifiInfo Get_Wifi_Info(String wlanname) { + WifiInfo result = null; + if (wlanname!=null && wlanname.length()>0) { + try { + final String cmd = "iw dev "+wlanname+" info"; + final String interfacename = "Interface "+wlanname; + final String addr = "addr "; + final String ssid = "ssid "; + final String channel = "channel "; + final String txpower = "txpower "; + + Process prc = Runtime.getRuntime().exec(cmd); + if (prc!=null) { + InputStream ins = prc.getInputStream(); + if (ins!=null) { + BufferedReader bufread = new BufferedReader(new InputStreamReader(ins)); + String line; + do { + line = bufread.readLine(); + if (line!=null && line.length()>0) { + line = line.trim(); + if (line.startsWith(interfacename)) { + // correct interface, create result + result = new WifiInfo(); + } + if (line.startsWith(addr)) { + if (result!=null) result.MAC = line.substring(addr.length()); + } + if (line.startsWith(ssid)) { + if (result!=null) result.SSID = line.substring(ssid.length()); + } + if (line.startsWith("channel")) { + if (result!=null) result.Channel = line.substring(channel.length()); + } + if (line.startsWith("txpower")) { + if (result!=null) result.TxPower = line.substring(txpower.length()); + } + + } + } while(line!=null); + ins.close(); + return result; + } + + } + } catch (IOException e) { + raise_log("Get_Wifi_Info failed, exception = "+e.getMessage()); + } + + } else raise_log("Get_Wifi_Info failed, wlanname is invalid"); + return result; + } +} diff --git a/src/SBC/Size_Info.java b/src/SBC/Size_Info.java new file mode 100644 index 0000000..027ea45 --- /dev/null +++ b/src/SBC/Size_Info.java @@ -0,0 +1,195 @@ +package SBC; +import anywheresoftware.b4a.BA; + +@BA.ShortName("Size_Info") +public class Size_Info { + private long originalValue; + private double _value; + private String _unit; + private final long _KB = 1024; + private final long _MB = 1024 * _KB; + private final long _GB = 1024 * _MB; + private final long _TB = 1024 * _GB; + + /** + * Initialize Size_Info with originalvalue = 0 + */ + public Size_Info() { + originalValue = 0; + Set(originalValue); + } + + /** + * Initialize Size_Info with originalvalue + * @param valueinbytes originalvalue in bytes + */ + public Size_Info(long valueinbytes) { + originalValue = valueinbytes; + Set(valueinbytes); + } + + public Size_Info(double valueinbytes) { + originalValue = (long) valueinbytes; + Set(originalValue); + } + +// @BA.Hide +// public Size_Info(double value, String unit) { +// _value = value; +// _unit = unit; +// } + + /** + * Set originalvalue of Size_Info + * @param value originalvalue in bytes + */ + public void Set(long value) { + if (value < _KB) { + _value = 1.0* value; + _unit = "B"; + } else if (value < _MB) { + _value = 1.0* value / _KB; + _unit = "KB"; + } else if (value < _GB) { + _value = 1.0* value / _MB; + _unit = "MB"; + } else if (value < _TB) { + _value = 1.0* value / _GB; + _unit = "GB"; + } else { + _value = 1.0* value / _TB; + _unit = "TB"; + } + } + + /** + * Set originalvalue in KB + * value will be multiplied by 1024 + * @param value in KB + */ + public void SetInKB(double value) { + originalValue = (long) (value * _KB); + Set(originalValue); + } + + /** + * Set originalvalue in MB value will be multiplied by 1024*1024 + * + * @param value in MB + */ + public void SetInMB(double value) { + originalValue = (long) (value * _MB); + Set(originalValue); + } + + /** + * Set originalvalue in GB value will be multiplied by 1024*1024*1024 + * + * @param value in GB + */ + public void SetInGB(double value) { + originalValue = (long) (value * _GB); + Set(originalValue); + } + + /** + * Set originalvalue in TB value will be multiplied by 1024*1024*1024*1024 + * + * @param value in TB + */ + public void SetInTB(double value) { + originalValue = (long) (value * _TB); + Set(originalValue); + } + + /** + * Get originalvalue in bytes + * @return originalvalue in bytes + */ + public long GetOriginalValue() { + return originalValue; + } + + /** + * Get originalvalue in KB + * @param decimals number of decimals, if decimals < 1, return as is (long decimals) + * @return value in double + */ + public double GetOriginalValueInKB(int decimals) { + double value = 1.0 * originalValue / _KB; + if (decimals < 1) { + return value; + } else { + int vf = (int)(value * Math.pow(10, decimals)); + return 1.0 * vf / Math.pow(10, decimals); + } + } + + /** + * Get originalvalue in MB + * + * @param decimals number of decimals, if decimals < 1, return as is (long + * decimals) + * @return value in double + */ + public double GetOriginalValueInMB(int decimals) { + double value = 1.0 * originalValue / _MB; + if (decimals < 1) { + return value; + } else { + int vf = (int) (value * Math.pow(10, decimals)); + return 1.0 * vf / Math.pow(10, decimals); + } + } + + /** + * Get originalvalue in GB + * + * @param decimals number of decimals, if decimals < 1, return as is (long + * decimals) + * @return value in double + */ + public double GetOriginalValueInGB(int decimals) { + double value = 1.0 * originalValue / _GB; + if (decimals < 1) { + return value; + } else { + int vf = (int) (value * Math.pow(10, decimals)); + return 1.0 * vf / Math.pow(10, decimals); + } + } + + /** + * Get originalvalue in TB + * + * @param decimals number of decimals, if decimals < 1, return as is (long + * decimals) + * @return value in double + */ + public double GetOriginalValueInTB(int decimals) { + double value = 1.0 * originalValue / _TB; + if (decimals < 1) { + return value; + } else { + int vf = (int) (value * Math.pow(10, decimals)); + return 1.0 * vf / Math.pow(10, decimals); + } + } + + /** + * Return Easy-to-Read Value of originalvalue + * @return value in double + */ + public double Value() { + return _value; + } + + /** + * Return Easy-to-Read Unit of originalvalue + * + * @return unit in string + */ + public String Unit() { + return _unit; + } +} diff --git a/src/SBC/Storage_Info.java b/src/SBC/Storage_Info.java new file mode 100644 index 0000000..c61d057 --- /dev/null +++ b/src/SBC/Storage_Info.java @@ -0,0 +1,116 @@ +package SBC; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.Map; + +@BA.ShortName("Storage_Info") +public class Storage_Info { + public final String Path; + public final double Total; + public final double Free; + public final double Used; + public final double FreePercentage; + public final boolean isvalid; + + public Storage_Info() { + Path=""; + Total=0; + Free=0; + FreePercentage=0; + Used=0; + isvalid = false; + } + + public Storage_Info(Map value) { + Path = (String) value.GetDefault("Path", ""); + Total = (double) value.GetDefault("Total", 0); + Free = (double) value.GetDefault("Free", 0); + Used = Total - Free; + FreePercentage = (double) value.GetDefault("FreePercentage", 0); // sudah pembulatan 1 desimal + if (!Path.isEmpty()) { + if (Total>0) { + isvalid = true; + } else isvalid = false; + } else isvalid = false; + } + + public double Total_KB() { + return Total / file_KB; + } + + public double Free_KB() { + return Free / file_KB; + } + + public double Used_KB() { + return Used / file_KB; + } + + private final double file_KB = 1024; + private final double file_MB = file_KB * 1024; + private final double file_GB = file_MB * 1024; + private final double file_TB = file_GB * 1024; + + private double pembulatan_1desimal(double value) { + int temp = (int)(value * 10); + return (temp / 10.0); + } + + /** + * Return an easy to read from values inside this class + * @param value : Member of this class, either Total or Free + * @return String value + */ + public String EasyRead_Value(double value) { + if (value < file_KB) { + return pembulatan_1desimal(value)+ " B"; + } else if (value < file_MB) { + return pembulatan_1desimal(value / file_KB)+" KB"; + } else if (value < file_GB) { + return pembulatan_1desimal(value / file_MB)+" MB"; + } else if (value < file_TB) { + return pembulatan_1desimal(value / file_GB)+" GB"; + } else { + return pembulatan_1desimal(value / file_TB)+" TB"; + } + } + + /** + * Return an easy to read from values inside this class + * @param value value to read + * @return Size_Info object + */ + public Size_Info EasyRead_SizeInfo(double value) { + return new Size_Info(value); + //TODO hapus kalau sudah benar +// if (value < file_KB) { +// return new Size_Info(pembulatan_1desimal(value), "B"); +// } else if (value < file_MB) { +// return new Size_Info(pembulatan_1desimal(value / file_KB), "KB"); +// } else if (value < file_GB) { +// return new Size_Info(pembulatan_1desimal(value / file_MB), "MB"); +// } else if (value < file_TB) { +// return new Size_Info(pembulatan_1desimal(value / file_GB), "GB"); +// } else { +// return new Size_Info(pembulatan_1desimal(value / file_TB), "TB"); +// } + } + + /** + * Create values in JSON Map + * @return Map value + */ + public Map MakeJSONMap() { + Map result = new Map(); + result.Initialize(); + + result.Put("Path", this.Path); + result.Put("Total", EasyRead_Value(this.Total)); + result.Put("Free", EasyRead_Value(this.Free)); + result.Put("FreePercentage", this.FreePercentage); + + return result; + } + + +} diff --git a/src/SBC/WifiInfo.java b/src/SBC/WifiInfo.java new file mode 100644 index 0000000..c1c07a5 --- /dev/null +++ b/src/SBC/WifiInfo.java @@ -0,0 +1,11 @@ +package SBC; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("Wifi_Info") +public class WifiInfo { + public String MAC; + public String SSID; + public String Channel; + public String TxPower; +} diff --git a/src/SBC/cpudata.java b/src/SBC/cpudata.java new file mode 100644 index 0000000..59e8ada --- /dev/null +++ b/src/SBC/cpudata.java @@ -0,0 +1,153 @@ +package SBC; + +import anywheresoftware.b4a.BA; + +/** + * Untuk olah data dari /proc/stat + * Mencari CPU usage + * https://stackoverflow.com/questions/23367857/accurate-calculation-of-cpu-usage-given-in-percentage-in-linux + * @author rdkartono + * + */ +@BA.ShortName("cpudata") +public class cpudata { + public String name; + public long user; + public long nice; + public long system; + public long idle; + public long iowait; + public long irq; + public long softirq; + public long steal; + public long guest; + public long guest_nice; + private boolean correct_data; + + public cpudata() { + this.name = ""; + this.user = 0; + this.nice = 0; + this.system = 0; + this.idle = 0; + this.iowait = 0; + this.irq = 0; + this.softirq = 0; + this.steal = 0; + this.guest = 0; + this.guest_nice = 0; + this.correct_data = false; + } + + public cpudata(String name, long user, long nice, long system, long idle, long iowait, long irq, long softirq, long steal, long guest, long guest_nice) { + this.name = name; + this.user = user; + this.nice = nice; + this.system = system; + this.idle = idle; + this.iowait = iowait; + this.irq = irq; + this.softirq = softirq; + this.steal = steal; + this.guest = guest; + this.guest_nice = guest_nice; + this.correct_data = false; + if (this.name!=null && this.name.length()>0) { + if (getTotalData()>0) { + correct_data = true; + } + } + } + + public boolean isCorrectData() { + return correct_data; + } + + + + /** + * IdleData = idle + iowait + * @return IdleData (Long) + */ + public long getIdleData() { + return idle+iowait; + } + + /** + * SystemData = system + irq + softirq + steal + * @return 0 if invalid + */ + public long getSystemData() { + return system+irq+softirq+steal; + } + + /** + * VirtualData = guest + guest_nice + * @return 0 if invalid + */ + public long getVirtualData() { + return guest+guest_nice; + } + + /** + * BusyData = user + nice + system + irq + softirq + * @return BusyData (Long) + */ + public long getBusyData() { + return user+nice+system+irq+softirq; + } + + /** + * TotalData = IdleData + BusyData + * @return TotalData (Long) + */ + public long getTotalData() { + return getIdleData()+getBusyData(); + } + + /** + * Get Percentage of Idle Time + * @return 0 - 100 + */ + public double getIdlePercentage() { + return (getIdleData() * 100.0) / getTotalData(); + } + + /** + * Get Busy Percentage + * @return 0 - 100 + */ + public double getBusyPercentage() { + return (getBusyData() * 100.0) / getTotalData(); + } + + /** + * Get CPU Usage, in percentage + * @param prev : previous cpudata. if null, then will calculate total average cpu_usage since boot + * @return CPU Usage (double) + */ + public double getCPU_Usage(cpudata prev) { + long prev_total = 0; + long prev_idle = 0; + if (prev != null) { + if (prev.correct_data) { + prev_total = prev.getTotalData(); + prev_idle =prev.getIdleData(); + } + } + + long delta_total = getTotalData() - prev_total; + long delta_idle = getIdleData() - prev_idle; + + double result = delta_total - delta_idle; + result = (result / delta_total) * 100; + return Math.round(result * 10.0) / 10.0; // pembulatan 1 desimal + } + + @Override + public String toString() { + return "name=" + name + ", user=" + user + ", nice=" + nice + ", system=" + system + ", idle=" + idle + + ", iowait=" + iowait + ", irq=" + irq + ", softirq=" + softirq + ", steal=" + steal + ", guest=" + + guest + ", guest_nice=" + guest_nice; + } +} diff --git a/src/androgpio/Closer.java b/src/androgpio/Closer.java new file mode 100644 index 0000000..8db9a54 --- /dev/null +++ b/src/androgpio/Closer.java @@ -0,0 +1,33 @@ +package androgpio; + +import java.io.Closeable; +import java.net.DatagramSocket; +import java.net.Socket; + +import anywheresoftware.b4a.BA; + +public class Closer { + // closeAll() + public static void closeSilently(Object... xs) { + // Note: on Android API levels prior to 19 Socket does not implement Closeable + for (Object x : xs) { + if (x != null) { + try { + BA.Log("closing: "+x); + if (x instanceof Closeable) { + ((Closeable)x).close(); + } else if (x instanceof Socket) { + ((Socket)x).close(); + } else if (x instanceof DatagramSocket) { + ((DatagramSocket)x).close(); + } else { + BA.Log("cannot close: "+x); + throw new RuntimeException("cannot close "+x); + } + } catch (Throwable e) { + BA.Log(e.getMessage()); + } + } + } + } +} diff --git a/src/androgpio/DigitalInput/DigitalInput.java b/src/androgpio/DigitalInput/DigitalInput.java new file mode 100644 index 0000000..58f66ec --- /dev/null +++ b/src/androgpio/DigitalInput/DigitalInput.java @@ -0,0 +1,192 @@ +package androgpio.DigitalInput; + + +import java.io.File; +import java.io.IOException; + +import com.sun.jna.Platform; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + + +@BA.ShortName("DigitalInput") +@BA.Events(values={"error(pinnumber as int, Msg as String)"}) + +public class DigitalInput { + private BA bax; + private String event=""; + private int pinnya=0; + private Object myobject; + + private boolean need_error_event = false; + + private int last_value = -1; + private boolean currentIsON = false; + private boolean initialized = false; + private DigitalInputEvent javaevent; + + /** + * Buat di JAVA coding + */ + public DigitalInput() { + // do nothing here, buat syarat + + } + + @BA.Hide + /** + * Buat di JAVA coding + * @param pin_number : pin number + */ + public DigitalInput(int pin_number) { + + pinnya = pin_number; + if (Setup_Pin()) initialized = true; + } + + /** + * Initialize Digital Input + * @param caller : caller object + * @param eventname : event name + * @param pinnumber : pin number + */ + public void Initialize(BA xx, Object caller, String eventname,int pinnumber){ + bax = xx; + event=eventname; + pinnya = pinnumber; + myobject = caller; + + if (bax!=null) { + if (myobject!=null) { + if (!event.isEmpty()) { + need_error_event = bax.subExists(event+"_error"); + } + } + } + + + + initialized = true; + } + + /** + * Open Pin for operation + * @return true if success + */ + public boolean Open_Pin() { + if (Platform.isAndroid()) { + if (Setup_Pin()) { + // percuma, gak bisa release +// Runtime.getRuntime().addShutdownHook(new Thread() { +// @Override +// public void run() { +// Release(); +// } +// }); + return true; + } + } else raise_Error("System is not Android based"); + return false; + } + + + private boolean Setup_Pin() { + File ff = new File(mycodes.gpio_direction_path(pinnya)); + try { + if (!ff.exists()) { + // belum ada, export dulu + boolean exportresult = mycodes.GPIO_Export(pinnya); + if (!exportresult) { + // GPIO Export gagal + return false; + } + } + // sampe sini , harusnya direction path ada + return mycodes.GPIO_SetDirection(pinnya, false); + } catch(SecurityException|IOException e) { + BA.Log("Setup_Pin on DigitalInput pin "+pinnya+" failed, Msg : "+e.getMessage()); + } + return false; + } + + + + + @BA.Hide + public void SetJavaEvent(DigitalInputEvent xx) { + javaevent = xx; + } + + public boolean getIsInitialized(){ + return initialized; + } + + + /** + * Read State of GPIO + * @return 0 = Low, 1 = high, -1 = failed reading + */ + public int ReadState(){ + try { + int value = mycodes.GPIO_GetValue(pinnya); + if (value==0) { + currentIsON = false; + } else if (value==1) { + currentIsON = true; + } + + if (last_value != value) { + last_value = value; + if (javaevent!=null) { + if (last_value == 0) { + javaevent.newinstate(pinnya, false); + } else { + javaevent.newinstate(pinnya, true); + } + + } + } + + return value; + } catch (IOException|SecurityException e) { + raise_Error("DigitalInput GPIO="+pinnya+" ReadState error, Msg:"+e.getMessage()+", Caused:"+e.getCause()); + return -1; + } + } + + + /* + * Get last ReadState value + * return true if High 1 + * return false if Low 0 + */ + public boolean getIsON(){ + return currentIsON; + } + + @BA.Hide + /* + * Release control of Digital Output (not controlling anymore) + * return true if success operation + */ + public boolean Release(){ + try { + return mycodes.GPIO_UnExport(pinnya); + } catch (IOException|SecurityException e) { + raise_Error("DigitalInput GPIO="+pinnya+" Release error, Msg:"+e.getMessage()+", Caused:"+e.getCause()); + return false; + } + } + + private void raise_Error(String msg) { + if (need_error_event) { + bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_error", false,new Object[]{pinnya,msg}); + } + if (javaevent!=null) { + javaevent.error(pinnya, msg); + } + + } + +} diff --git a/src/androgpio/DigitalInput/DigitalInputEvent.java b/src/androgpio/DigitalInput/DigitalInputEvent.java new file mode 100644 index 0000000..4563da6 --- /dev/null +++ b/src/androgpio/DigitalInput/DigitalInputEvent.java @@ -0,0 +1,6 @@ +package androgpio.DigitalInput; + +public interface DigitalInputEvent { + void error(int pinnumber, String Msg ); + void newinstate(int pinnumber , boolean isOn); +} diff --git a/src/androgpio/DigitalOutput/DigitalOutput.java b/src/androgpio/DigitalOutput/DigitalOutput.java new file mode 100644 index 0000000..c6a83f8 --- /dev/null +++ b/src/androgpio/DigitalOutput/DigitalOutput.java @@ -0,0 +1,169 @@ +package androgpio.DigitalOutput; + + +import java.io.File; +import java.io.IOException; + +import com.sun.jna.Platform; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + + +@BA.ShortName("DigitalOutput") +@BA.Events(values={"error(pinnumber as int, Msg as String)"}) + +public class DigitalOutput { + private BA bax; + private String event=""; + private int pinnya=0; + private Object myobject; + private boolean need_error_event = false; + + + private boolean initialized = false; + + private DigitalOutputEvent javaevent; + + /** + * Buat di JAVA coding + */ + public DigitalOutput(){ + + } + + @BA.Hide + /** + * Buat di JAVA coding + * @param pin_number : pin number + * @param initialstate : true --> initial state = high, false --> initial state = low + */ + public DigitalOutput(int pin_number, boolean initialstate){ + pinnya = pin_number; + if (Setup_Pin(initialstate)) initialized = true; + } + + /** + * Initialize Digital Output + * @param caller : caller object + * @param eventname : event name + * @param pinnumber : pin number + */ + public void Initialize(BA xx, Object caller, String eventname,int pinnumber){ + bax = xx; + event=eventname; + pinnya = pinnumber; + myobject = caller; + + if (bax!=null) { + if(myobject!=null) { + if (!event.isEmpty()) { + need_error_event = bax.subExists(event+"_error"); + } + } + } + + initialized = true; + } + + /** + * Open Pin for Operation + * @param initialstate : true --> initial state = high, false --> initial state = low + * @return false kalau gagal + */ + public boolean Open_Pin(boolean initialstate) { + if (Platform.isAndroid()) { + if (Setup_Pin(initialstate)) { + + // Percuma, gak bisa release +// Runtime.getRuntime().addShutdownHook(new Thread() { +// @Override +// public void run() { +// Release(); +// } +// }); + return true; + } + } else raise_Error("System is not Android based"); + return false; + } + + @BA.Hide + public void SetJavaEvent(DigitalOutputEvent xx) { + javaevent = xx; + } + + public boolean getIsInitialized(){ + return initialized; + } + + private boolean Setup_Pin(boolean initialstate) { + File ff = new File(mycodes.gpio_direction_path(pinnya)); + try { + if (!ff.exists()) { + // belum ada, export dulu + boolean exportresult = mycodes.GPIO_Export(pinnya); + if (!exportresult) { + // GPIO Export gagal + return false; + } + } + // sampe sini , harusnya direction path ada + return mycodes.GPIO_SetDirection(pinnya, true); + } catch(SecurityException|IOException e) { + BA.Log("Setup_Pin on DigitalOutput pin "+pinnya+" failed, Msg : "+e.getMessage()); + } + return false; + } + + /* + * Set Digital Output to High + * return true if success operation + */ + public boolean SetHigh(){ + try { + return mycodes.GPIO_SetValue(pinnya, true); + } catch (IOException|SecurityException e) { + raise_Error("DigitalOutput GPIO="+pinnya+" SetHigh error, Msg:"+e.getMessage()+", Caused:"+e.getCause()); + return false; + } + } + + /* + * Set Digital Output to Low + * return true if success operation + */ + public boolean SetLow(){ + try { + return mycodes.GPIO_SetValue(pinnya, false); + } catch (IOException|SecurityException e) { + raise_Error("DigitalOutput GPIO="+pinnya+" SetLow error, Msg:"+e.getMessage()+", Caused:"+e.getCause()); + return false; + } + } + + @BA.Hide + /* + * Release control of Digital Output (not controlling anymore) + * return true if success operation + */ + public boolean Release(){ + try { + return mycodes.GPIO_UnExport(pinnya); + } catch (IOException|SecurityException e) { + raise_Error("DigitalOutput GPIO="+pinnya+" Release error, Msg:"+e.getMessage()+", Caused:"+e.getCause()); + return false; + } + } + + private void raise_Error(String msg) { + if (need_error_event) { + bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_error", false,new Object[]{pinnya,msg}); + } + if (javaevent!=null) { + javaevent.error(pinnya, msg); + } + + } + +} diff --git a/src/androgpio/DigitalOutput/DigitalOutputEvent.java b/src/androgpio/DigitalOutput/DigitalOutputEvent.java new file mode 100644 index 0000000..cd8eef8 --- /dev/null +++ b/src/androgpio/DigitalOutput/DigitalOutputEvent.java @@ -0,0 +1,6 @@ +package androgpio.DigitalOutput; + +public interface DigitalOutputEvent { + void error(int pinnumber, String Msg ); + void newoutstate(int pinnumber, boolean isOn); +} diff --git a/src/androgpio/DigitalOutput/GtcAndroidOutput.java b/src/androgpio/DigitalOutput/GtcAndroidOutput.java new file mode 100644 index 0000000..dd67dfc --- /dev/null +++ b/src/androgpio/DigitalOutput/GtcAndroidOutput.java @@ -0,0 +1,313 @@ +package androgpio.DigitalOutput; + + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + +@BA.ShortName("GtcAndroidOutput") + +@BA.Events(values = { + "log(msg as String)", +}) + +public class GtcAndroidOutput { + private BA ba; + private String eventName; + private boolean need_log_event = false; + private final String path = "/sys/class/leds"; + private final String brightness = "brightness"; + + + private OutputDevice relay1; + private OutputDevice relay2; + private OutputDevice relay3; + private OutputDevice relay4; + private OutputDevice networkled; + private OutputDevice pilotled; + private OutputDevice actionled; + + private GtcAndroidOutputEvent javaEvent=null; + + @BA.Hide + public GtcAndroidOutput(GtcAndroidOutputEvent event) { + javaEvent = event; + + relay1 = new OutputDevice(path, "relay1",brightness); + relay2 = new OutputDevice(path, "relay2",brightness); + relay3 = new OutputDevice(path, "relay3",brightness); + relay4 = new OutputDevice(path, "relay4",brightness); + networkled = new OutputDevice(path, "networkled",brightness); + pilotled = new OutputDevice(path, "pilotled",brightness); + actionled = new OutputDevice(path, "actionled",brightness); + + } + + /** + * Initializes the GtcAndroidOutput object for B4X + * GtcAndroidOutput is different from Relay and DigitalOutput in Android + * @param eventname Eventname for the object + */ + public void Initialize(BA ba, String eventname) { + this.ba = ba; + this.eventName = eventname.toLowerCase(BA.cul); + if (this.ba!=null) { + if (mycodes.valid_string(eventName)) { + need_log_event = ba.subExists(eventName + "_log"); + } + } + } + + /** + * Check if relay1 exists + * @return true if exists and usable + */ + public boolean hasRelay1() { + return relay1.Exists(); + } + + /** + * Check if relay1 can be read + * @return true if readable + */ + public boolean canreadRelay1() { + return relay1.canRead(); + } + + /** + * Check if relay1 can be written + * + * @return true if writable + */ + public boolean canwriteRelay1() { + return relay1.canWrite(); + } + + /** + * Turn Relay1 on or off + * @param On true to turn on, false to turn off + * @return true if successful + */ + public boolean Relay1(boolean On) { + return relay1.ChangeState(On); + } + + /** + * Check if relay2 exists + * + * @return true if exists and usable + */ + public boolean hasRelay2() { + return relay2.Exists(); + } + + /** + * Check if relay2 can be read + * @return true if readable + */ + public boolean canreadRelay2() { + return relay2.canRead(); + } + + /** + * Check if relay2 can be written + * + * @return true if writable + */ + public boolean canwriteRelay2() { + return relay2.canWrite(); + } + + /** + * Turn Relay2 on or off + * @param On true to turn on, false to turn off + * @return true if successful + */ + public boolean Relay2(boolean On) { + return relay2.ChangeState(On); + } + + /** + * Check if relay3 exists + * + * @return true if exists and usable + */ + public boolean hasRelay3() { + return relay3.Exists(); + } + + /** + * Check if relay3 can be read + * @return true if readable + */ + public boolean canreadRelay3() { + return relay3.canRead(); + } + + /** + * Check if relay3 can be written + * + * @return true if writable + */ + public boolean canwriteRelay3() { + return relay3.canWrite(); + } + + /** + * Turn Relay3 on or off + * @param On true to turn on, false to turn off + * @return true if successful + */ + public boolean Relay3(boolean On) { + return relay3.ChangeState(On); + } + + /** + * Check if relay4 exists + * + * @return true if exists and usable + */ + public boolean hasRelay4() { + return relay4.Exists(); + } + + /** + * Check if relay4 can be read + * @return true if readable + */ + public boolean canreadRelay4() { + return relay4.canRead(); + } + + /** + * Check if relay1 can be written + * + * @return true if writable + */ + public boolean canwriteRelay4() { + return relay4.canWrite(); + } + + /** + * Turn Relay4 on or off + * @param On true to turn on, false to turn off + * @return true if successful + */ + public boolean Relay4(boolean On) { + return relay4.ChangeState(On); + } + + /** + * Check if networkled exists + * + * @return true if exists and usable + */ + public boolean hasNetworkLed() { + return networkled.Exists(); + } + + /** + * Check if networkled can be read + * + * @return true if readable + */ + public boolean canreadNetworkLed() { + return networkled.canRead(); + } + + /** + * Check if networkled can be written + * + * @return true if writable + */ + public boolean canwriteNetworkLed() { + return networkled.canWrite(); + } + + /** + * Turn NetworkLed on or off + * @param On true to turn on, false to turn off + * @return true if successful + */ + public boolean NetworkLed(boolean On) { + return networkled.ChangeState(On); + } + + /** + * Check if pilotled exists + * + * @return true if exists and usable + */ + public boolean hasPilotLed() { + return pilotled.Exists(); + } + + /** + * Check if pilotled can be read + * + * @return true if readable + */ + public boolean canreadPilotLed() { + return pilotled.canRead(); + } + + /** + * Check if pilotled can be written + * + * @return true if writable + */ + public boolean canwritePilotLed() { + return pilotled.canWrite(); + } + + /** + * Turn PilotLed on or off + * @param On true to turn on, false to turn off + * @return true if successful + */ + public boolean PilotLed(boolean On) { + return pilotled.ChangeState(On); + } + + /** + * Check if actionled exists + * + * @return true if exists and usable + */ + public boolean hasActionLed() { + return actionled.Exists(); + } + + /** + * Check if actionled can be read + * + * @return true if readable + */ + public boolean canreadActionLed() { + return actionled.canRead(); + } + + /** + * Check if actionled can be written + * + * @return true if writable + */ + public boolean canwriteActionLed() { + return actionled.canWrite(); + } + + /** + * Turn ActionLed on or off + * + * @param On true to turn on, false to turn off + * @return true if successful + */ + public boolean ActionLed(boolean On) { + return actionled.ChangeState(On); + } + + @SuppressWarnings("unused") + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(GtcAndroidOutput.this, null, 0, eventName+"_log", false, new Object[] {msg}); + if (javaEvent!=null) javaEvent.Log(msg); + } +} diff --git a/src/androgpio/DigitalOutput/GtcAndroidOutputEvent.java b/src/androgpio/DigitalOutput/GtcAndroidOutputEvent.java new file mode 100644 index 0000000..9484bd9 --- /dev/null +++ b/src/androgpio/DigitalOutput/GtcAndroidOutputEvent.java @@ -0,0 +1,12 @@ +package androgpio.DigitalOutput; + +public interface GtcAndroidOutputEvent { + void Log(String msg); + void Relay1State(boolean On); + void Relay2State(boolean On); + void Relay3State(boolean On); + void Relay4State(boolean On); + void NetworkLedState(boolean On); + void PilotLedState(boolean On); + void ActionLedState(boolean On); +} diff --git a/src/androgpio/DigitalOutput/OutputDevice.java b/src/androgpio/DigitalOutput/OutputDevice.java new file mode 100644 index 0000000..6647471 --- /dev/null +++ b/src/androgpio/DigitalOutput/OutputDevice.java @@ -0,0 +1,96 @@ +package androgpio.DigitalOutput; + +import java.io.File; + +import androgpio.mycodes; + +public class OutputDevice { + final String name; + final String path; + final File f; + public OutputDevice(String path, String name, String suffix) { + this.name = name; + this.path = mycodes.path_combine(path, name, suffix); + this.f = new File(this.path); + } + + /** + * Get the name of the device + * + * @return device name + */ + public String getName() { + return name; + } + + /** + * Get the full path of the device + * @return full path + */ + public String getPath() { + return path; + } + + /** + * Check if the file exists + * + * @return + */ + public boolean Exists() { + return mycodes.path_exists("", path); + } + + /** + * Check if the file is readable + * + * @return + */ + public boolean canRead() { + return mycodes.path_readable("", path); + } + + /** + * Check if the file is writable + * @return + */ + public boolean canWrite() { + return mycodes.path_writable("", path); + } + + /** + * Change State of the device + * + * @param state true = on, false = off + * @return true if successful + */ + public boolean ChangeState(boolean state) { + if (Exists() && canWrite()) { + return mycodes.FileWrite(f, state ? "1" : "0"); + } + return false; + } + + /** + * Read State of the device + * + * @return 0 = off, 1 = on, -1 = error + */ + public int ReadState() { + if (Exists() && canRead()) { + String s = mycodes.FileRead(f); + if (s != null) { + try { + int result = Integer.parseInt(s); + if (result == 0) + return 0; + else if (result == 1) + return 1; + } catch (NumberFormatException e) { + + } + + } + } + return -1; + } +} diff --git a/src/androgpio/I2C/I2CException.java b/src/androgpio/I2C/I2CException.java new file mode 100644 index 0000000..0406f30 --- /dev/null +++ b/src/androgpio/I2C/I2CException.java @@ -0,0 +1,49 @@ +package androgpio.I2C; + +import anywheresoftware.b4a.keywords.Bit; + +public class I2CException extends Exception{ + /** + * + */ + private static final long serialVersionUID = -8227969179418888752L; + public final byte bytes[]; + public I2CException(String msg) { + super(msg); + bytes = null; + } + public I2CException(String msg, final byte[] bytes) { + super(msg); + this.bytes = bytes; + } + public I2CException(String msg, byte command) { + super(msg); + this.bytes = new byte[] {command}; + } + + public I2CException(String function, String msg, byte command) { + super("Function="+function+", Message="+msg); + this.bytes = new byte[] {command}; + } + + public I2CException(String function, String msg, byte[] bytes) { + super("Function="+function+", Message="+msg); + this.bytes = bytes; + } + + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + str.append(this.getMessage()); + if (bytes!=null) { + if (bytes.length>0) { + str.append(" [ "); + for(byte xx:bytes) { + str.append(Bit.ToHexString(Bit.And(xx, 0xFF))).append(" "); + } + str.append("]"); + } + } + return str.toString(); + } +} diff --git a/src/androgpio/I2C/I2C_BUS.java b/src/androgpio/I2C/I2C_BUS.java new file mode 100644 index 0000000..7167d2f --- /dev/null +++ b/src/androgpio/I2C/I2C_BUS.java @@ -0,0 +1,84 @@ +package androgpio.I2C; +import anywheresoftware.b4a.BA; + +/** + * Source : https://groups.google.com/forum/#!topic/jna-users/9X0bu4tv7Fk + * @author rdkartono + * + */ +public class I2C_BUS { + private final int O_RDWR = 0x00000002; + + private boolean opened = false; + private String i2c_name = ""; + private int i2c_handle = -1; + private I2C_BUS_Event javaevent; + + /** + * Open I2C Bus using I2C Name + * @param i2c_name : i2c name, example : /dev/i2c-1 + */ + public I2C_BUS(String i2c_name) { + if (!i2c_name.isEmpty())this.i2c_name = i2c_name; + + } + + @BA.Hide + public void SetJavaEvent(I2C_BUS_Event ev) { + javaevent = ev; + } + + /** + * Open Bus + * @return true if opened + */ + public boolean Open_Bus() { + opened = false; + if (!i2c_name.isEmpty()) { + i2c_handle = libc.libC.open(i2c_name, O_RDWR); + if (i2c_handle<0) { + raise_log("Open_Bus failed"); + } + else { + raise_log("Open_Bus success"); + opened = true; + } + } + return opened; + + } + + /** + * Close Bus + */ + public void Close_Bus() { + if (i2c_handle >= 0) { + libc.libC.close(i2c_handle); + i2c_handle = -1; + } + opened = false; + raise_log("Bus Closed"); + } + + /** + * check if I2C bus is opened + * @return true if opened + */ + public boolean IsOpened() { + return opened; + } + + /** + * Get I2C Open handle + * @return -1 if closed + */ + public int I2C_Handle() { + return i2c_handle; + } + + private void raise_log(String msg) { + if (javaevent!=null) { + javaevent.Log(msg); + } + } +} diff --git a/src/androgpio/I2C/I2C_BUS_Event.java b/src/androgpio/I2C/I2C_BUS_Event.java new file mode 100644 index 0000000..5596d2a --- /dev/null +++ b/src/androgpio/I2C/I2C_BUS_Event.java @@ -0,0 +1,5 @@ +package androgpio.I2C; + +public interface I2C_BUS_Event { + void Log(String msg); +} diff --git a/src/androgpio/I2C/I2C_Device.java b/src/androgpio/I2C/I2C_Device.java new file mode 100644 index 0000000..59da88a --- /dev/null +++ b/src/androgpio/I2C/I2C_Device.java @@ -0,0 +1,271 @@ +package androgpio.I2C; +import anywheresoftware.b4a.BA; + +public class I2C_Device { + + private boolean opened = false; + + private int bus_handle = -1; + private int dev_handle = -1; + private int dev_address = -1; + private I2C_Device_Event javaevent; + + /** + * Create a I2C Device (Slave) + * @param bushandle : bus handle , get it from I2C_BUS object + * @param address : slave address to control + */ + public I2C_Device(int bushandle, int address) { + opened = false; + if (bushandle < 0) return; + if (address<0) return; + this.bus_handle = bushandle; + this.dev_address = address; + } + + @BA.Hide + public void SetJavaEvent(I2C_Device_Event ev) { + javaevent = ev; + } + + /** + * Open this Device, in I2C_SLAVE mode + * @return true if opened + */ + public boolean OpenDevice_SLAVE() { + opened = false; + if (bus_handle>0) { + if (dev_address>0) { + dev_handle = libc.libC.ioctl(bus_handle, libc.I2C_SLAVE, dev_address); + if (dev_handle<0) { + // gagal + raise_log("OpenDevice_SLAVE failed, dev_handle="+dev_handle+", dev_address="+dev_address); + } else { + raise_log("OpenDevice_SLAVE success"); + opened = true; + } + } else raise_log("OpenDevice_SLAVE failed, dev_address="+dev_address); + } else raise_log("OpenDevice_SLAVE failed, bus_handle="+bus_handle); + return opened; + } + + /** + * Open this Device, in I2C_SLAVE_FORCE mode. + * @return true if opened + */ + public boolean OpenDevice_SLAVEFORCE() { + opened = false; + if (bus_handle<0) return false; + if (dev_address<0) return false; + dev_handle = libc.libC.ioctl(bus_handle, libc.I2C_SLAVE_FORCE, dev_address); + + if (dev_handle<0) { + // gagal + raise_log("OpenDevice_SLAVEFORCE failed"); + } + else { + libc.libC.ioctl(bus_handle, libc.I2C_RETRIES, 3); + libc.libC.ioctl(bus_handle, libc.I2C_TIMEOUT, 3); + raise_log("OpenDevice_SLAVEFORCE success"); + opened = true; + } + return opened; + } + + /** + * Open this Device, in I2C_RDWR mode + * @return true if opened + */ + public boolean OpenDevice_RDWR() { + opened = false; + if (bus_handle<0) return false; + if (dev_address<0) return false; + dev_handle = libc.libC.ioctl(bus_handle, libc.I2C_RDWR, dev_address); + if (dev_handle<0) { + raise_log("OpenDevice_RDWR failed"); + } + else { + raise_log("OpenDevice_RDWR success"); + opened = true; + } + return opened; + } + + /** + * Open this Device, in I2C_TENBIT + * @return true if opened + */ + public boolean OpenDevice_SMBUS() { + opened = false; + if (bus_handle<0) return false; + if (dev_address<0) return false; + dev_handle = libc.libC.ioctl(bus_handle, libc.I2C_SMBUS, dev_address); + if (dev_handle<0) { + raise_log("OpenDevice_SMBUS failed"); + } + else { + raise_log("OpenDevice_SMBUS success"); + opened = true; + } + return opened; + } + + + /** + * Close this Device + */ + public void CloseDevice() { + opened = false; + bus_handle = -1; + dev_address = -1; + if (dev_handle>=0) { + int closeresult = libc.libC.close(dev_handle); + dev_handle = -1; + if (closeresult==0) + raise_log("Device Closed"); + else + raise_log("Failure in I2C_Device CloseDevice"); + } + + } + + /** + * Check if slave is opened + * @return true if opened + */ + public boolean IsOpened() { + return opened; + } + + /** + * Get associated Bus handle + * @return -1 if closed + */ + public int BusHandle() { + return this.bus_handle; + } + + /** + * Get associated Slave address + * @return -1 if closed + */ + public int SlaveAddress() { + return this.dev_address; + } + + /** + * Get associated Device Open Handle + * @return -1 if closed + */ + public int DeviceHandle() { + return this.dev_handle; + } + + /** + * Write bytes + * @param bb : data bytes + * @return 0 if write fail + */ + public int WriteBytes(byte[] bb) { + if (bus_handle<0) { + raise_log("WriteBytes failed, Bus Handle closed"); + return 0; + } + if (dev_handle<0) { + raise_log("WriteBytes failed, Device Handle closed"); + return 0; + } + if (dev_address<0) { + raise_log("WriteBytes failed, Device Address invalid"); + return 0; + } + + return libc.libC.write(bus_handle, bb, bb.length); + } + + public int WriteBytes(int register_address, byte bb) { + byte[] cmd = new byte[2]; + cmd[0] = (byte) register_address; + cmd[1] = bb; + return WriteBytes(cmd); + } + + public int WriteBytes(int register_address, byte[] bb ) { + if (bb!=null) { + if (bb.length>0) { + byte[] cmd = new byte[bb.length+1]; + cmd[0] = (byte) register_address; + for(int ii=0;ii=0) { + if (bb.length > (offset+size)) { + byte[] newbyte = new byte[size+1]; + newbyte[0] = (byte) register_address; + for(int ii=0;ii=0) { + if (bb.length > (offset + size)) { + + byte[] newbyte = new byte[size]; + for(int ii=0;ii getFieldOrder() { + return Arrays.asList(// + "tv_sec",// + "tv_usec"// + ); + } + @BA.Hide public void autoWrite(){} + @BA.Hide public void autoRead(){} + + public timeval(long second, long usecond) { + tv_sec = new NativeLong(second); + tv_usec = new NativeLong(usecond); + } + } + } + +} + + + + + diff --git a/src/androgpio/ModbusWrapper/jModbusCoil.java b/src/androgpio/ModbusWrapper/jModbusCoil.java new file mode 100644 index 0000000..a366f0e --- /dev/null +++ b/src/androgpio/ModbusWrapper/jModbusCoil.java @@ -0,0 +1,22 @@ +package androgpio.ModbusWrapper; + +import com.intelligt.modbus.jlibmodbus.data.ModbusCoils; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("ModbusCoils") +/*** + * ModbusCoil dataholder + * by default, create 1024 coils + * @author rdkartono + * + */ +public class jModbusCoil extends ModbusCoils { + public jModbusCoil() { + super(1024); + } + public jModbusCoil(int size) { + super(size); + } + +} diff --git a/src/androgpio/ModbusWrapper/jModbusHoldingRegister.java b/src/androgpio/ModbusWrapper/jModbusHoldingRegister.java new file mode 100644 index 0000000..4fd517c --- /dev/null +++ b/src/androgpio/ModbusWrapper/jModbusHoldingRegister.java @@ -0,0 +1,22 @@ +package androgpio.ModbusWrapper; + +import com.intelligt.modbus.jlibmodbus.data.ModbusHoldingRegisters; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("ModbusHoldingRegisters") +/** + * ModbusHoldingRegister dataholder + * by default create 1024 registers + * @author rdkartono + * + */ +public class jModbusHoldingRegister extends ModbusHoldingRegisters { + public jModbusHoldingRegister(){ + super(1024); + } + + public jModbusHoldingRegister(int size){ + super(size); + } +} diff --git a/src/androgpio/ModbusWrapper/jModbusMaster.java b/src/androgpio/ModbusWrapper/jModbusMaster.java new file mode 100644 index 0000000..bdf0fe8 --- /dev/null +++ b/src/androgpio/ModbusWrapper/jModbusMaster.java @@ -0,0 +1,447 @@ +package androgpio.ModbusWrapper; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import com.fazecast.jSerialComm.SerialPort; +import com.intelligt.modbus.jlibmodbus.Modbus; +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.SerialUtils; +import com.intelligt.modbus.jlibmodbus.serial.SerialPort.BaudRate; +import com.intelligt.modbus.jlibmodbus.serial.SerialPort.Parity; +import com.intelligt.modbus.jlibmodbus.serial.SerialPortException; +import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters; +import com.intelligt.modbus.jlibmodbus.utils.FrameEvent; +import com.intelligt.modbus.jlibmodbus.utils.FrameEventListener; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.objects.collections.List; + +@BA.ShortName("jModbusMaster") +@BA.Events(values= { + "log(msg as string)", + "datasent(bb() as byte)", + "datareceived(bb() as byte)" +}) +@BA.DependsOn(values = { "jSerialComm-2.9.2","jlibmodbus-1.2.9.7" }) + +/** + * Create Modbus Master + * in Modbus, Master is actually a client that request data from server (Slave) + * @author rdkartono + * + */ +public class jModbusMaster { + private Object caller; + private String event; + private BA ba; + private final Object Me = this; + + private boolean need_log_event = false; + private boolean need_datasent_event = false; + private boolean need_datareceived_event = false; + + private ModbusMaster modbus; + + /** + * Initialize ModbusSerialSlaveRTU + * @param callerobject : caller object + * @param eventname : event name + */ + public void Initialize(BA ba, Object callerobject, String eventname) { + this.ba = ba; + event = eventname.trim(); + caller = callerobject; + + if (this.ba!=null) { + if (caller!=null) { + if (!event.isEmpty()) { + need_log_event = this.ba.subExists(event+"_log"); + need_datasent_event = this.ba.subExists(event+"_datasent"); + need_datareceived_event = this.ba.subExists(event+"_datareceived"); + } + } + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + Close(); + } + }); + } + + /** + * Get available Serial Port Names + * @return List of port names. If exists, List size > 0 + */ + public List GetSerialPortNames() { + List result = new List(); + result.Initialize(); + + + SerialPort[] sp = SerialPort.getCommPorts(); + if (sp!=null) { + if (sp.length>0) { + for(SerialPort xx : sp) { + result.Add(xx.getSystemPortName()); + } + } + } + return result; + } + + /** + * Open Modbus Master in Serial ASCII format + * @param serialname : serial port name + * @param baudrate : baud rate, typically 4800, 9600, 144400, 19200, 38400, 57600, 115200 + * @param databit : data bit length , typically 8 + * @param stopbit : typically 1 + * @param parity : 0 = None, 1 = Odd, 2 = Even, 3 = Mark, 4 = Space + * @return true if success + */ + public boolean OpenSerial_ASCII(String serialname, int baudrate, int databit, int stopbit, int parity) { + SerialParameters sp = new SerialParameters(); + + sp.setDataBits(databit); + sp.setDevice(serialname); + sp.setStopBits(stopbit); + sp.setBaudRate(BaudRate.getBaudRate(baudrate)); + sp.setParity(Parity.getParity(parity)); + + SerialUtils.setSerialPortFactoryJSerialComm(); + + try { + modbus = ModbusMasterFactory.createModbusMasterASCII(sp); + modbus.addListener(listener); + modbus.connect(); + return true; + } catch (SerialPortException e) { + raise_log("OpenSerial_ASCII failed, Msg : "+e.getMessage()); + return false; + } catch (ModbusIOException e) { + raise_log("Modbus Connect failed, Msg : "+e.getMessage()); + return false; + } + + } + + /** + * Open Modbus Master in Serial RTU format + * @param serialname : serial port name + * @param baudrate : baud rate, typically 4800, 9600, 144400, 19200, 38400, 57600, 115200 + * @param databit : data bit length , typically 8 + * @param stopbit : typically 1 + * @param parity : 0 = None, 1 = Odd, 2 = Even, 3 = Mark, 4 = Space + * @return true if success + */ + public boolean OpenSerial_RTU(String serialname, int baudrate, int databit, int stopbit, int parity) { + SerialParameters sp = new SerialParameters(); + + sp.setDataBits(databit); + sp.setDevice(serialname); + sp.setStopBits(stopbit); + sp.setBaudRate(BaudRate.getBaudRate(baudrate)); + sp.setParity(Parity.getParity(parity)); + + SerialUtils.setSerialPortFactoryJSerialComm(); + + try { + modbus = ModbusMasterFactory.createModbusMasterRTU(sp); + modbus.addListener(listener); + modbus.connect(); + return true; + } catch (SerialPortException e) { + raise_log("OpenSerial_RTU failed, Msg : "+e.getMessage()); + return false; + } catch (ModbusIOException e) { + raise_log("Modbus Connect failed, Msg : "+e.getMessage()); + return false; + } + + } + + /** + * Open Modbus Master in TCP + * @param hostaddress : target slave IP address + * @param port : tcp port. if negative, will use default 502 + * @param enable_keepalive : set true to enable keep alive connection + * @return true if success + */ + public boolean OpenTCP(String hostaddress, int port, boolean enable_keepalive) { + InetAddress host; + try { + host = InetAddress.getByName(hostaddress); + } catch (UnknownHostException e) { + raise_log("OpenTCP failed, Invalid hostaddress="+hostaddress+", Msg : "+e.getMessage()); + return false; + } + + if (port<1) port = Modbus.TCP_PORT; + TcpParameters tp = new TcpParameters(); + tp.setPort(port); + tp.setHost(host); + tp.setKeepAlive(enable_keepalive); + + modbus = ModbusMasterFactory.createModbusMasterTCP(tp); + modbus.addListener(listener); + try { + modbus.connect(); + return true; + } catch (ModbusIOException e) { + raise_log("Modbus Connect failed, Msg : "+e.getMessage()); + return false; + } + } + + /** + * Close Modbus Master connection + * @return true if success + */ + public boolean Close() { + if (modbus instanceof ModbusMaster) { + modbus.removeListeners(); + try { + modbus.disconnect(); + } catch (ModbusIOException e) { + raise_log("Modbus Disconnect failed, Msg : "+e.getMessage()); + return false; + } + } + return true; + } + + /*** + * Write Single Coil (command = 5) + * @param slave_address : slave address + * @param coil_address : coil address + * @param is_on : true = 1, false = 0 + * @return true if success + */ + public boolean WriteSingleCoil(int slave_address, int coil_address, boolean is_on) { + if (modbus instanceof ModbusMaster) { + if (modbus.isConnected()) { + try { + modbus.writeSingleCoil(slave_address, coil_address, is_on); + return true; + } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) { + raise_log("WriteSingleCoil failed, Msg : "+e.getMessage()); + + } + } + } + return false; + } + + /** + * Write Single Register (command = 6) + * @param slave_address : slave address + * @param register_address : register address + * @param value : register value , 16 bit + * @return true if success + */ + public boolean WriteSingleRegister(int slave_address, int register_address, int value) { + if (modbus instanceof ModbusMaster) { + if (modbus.isConnected()) { + try { + modbus.writeSingleRegister(slave_address, register_address, value); + return true; + } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) { + raise_log("WriteSingleRegister failed, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Write multiple coils (command = 15) + * @param slave_address : slave address + * @param start_address : start address of coils + * @param values : array of boolean for coils, true = 1, false = 0 + * @return true if success + */ + public boolean WriteMultipleCoils(int slave_address, int start_address, boolean[] values) { + if (modbus instanceof ModbusMaster) { + if (modbus.isConnected()) { + try { + modbus.writeMultipleCoils(slave_address, start_address, values); + return true; + } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) { + raise_log("WriteMultipleCoils failed, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Write Multiple Register (command = 16) + * @param slave_address : slave address + * @param start_address : start address of register + * @param values : array of int (16 bit) for registers + * @return true if success + */ + public boolean WriteMultipleRegisters(int slave_address, int start_address, int[] values) { + if (modbus instanceof ModbusMaster) { + if (modbus.isConnected()) { + try { + modbus.writeMultipleRegisters(slave_address, start_address, values); + return true; + } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) { + raise_log("WriteMultipleRegisters failed, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Read Single coil (command = 1) + * @param slave_address : slave address + * @param coil_address : coil address + * @return -1 = failed, 0 = false, 1 = true + */ + public int ReadSingleCoil(int slave_address, int coil_address) { + if (modbus instanceof ModbusMaster) { + if (modbus.isConnected()) { + try { + boolean[] result = modbus.readCoils(slave_address, coil_address, 1); + if (result!=null) { + if (result.length>0) { + if (result[0]) return 1; else return 0; + } + } + } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) { + raise_log("ReadSingleCoil failed, Msg : "+e.getMessage()); + } + } + } + return -1; + } + + /** + * Read single Register (16 bit) (command = 3) + * @param slave_address : slave address + * @param register_address : register address + * @return -1 = failed, other = register value + */ + public int ReadSingleRegister(int slave_address, int register_address) { + if (modbus instanceof ModbusMaster) { + if (modbus.isConnected()) { + try { + int[] result = modbus.readHoldingRegisters(slave_address, register_address, 1); + if (result!=null) { + if (result.length>0) { + return Bit.And(result[0], 0xFFFF); + } + } + } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) { + raise_log("ReadSingleRegister failed, Msg : "+e.getMessage()); + } + } + } + return -1; + } + + /** + * Read multiple coils + * @param slave_address : slave address + * @param start_address : coil start address + * @param quantity : how many coils want to be read + * @return array of boolean, or null if failed. + */ + public boolean[] ReadMultipleCoils(int slave_address, int start_address, int quantity) { + if (modbus instanceof ModbusMaster) { + if (modbus.isConnected()) { + try { + boolean[] result = modbus.readCoils(slave_address, start_address, quantity); + if (result!=null) { + if (result.length>0) { + return result; + } + } + } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) { + raise_log("ReadMultipleCoils failed, Msg : "+e.getMessage()); + } + + } + } + return null; + } + + /** + * Read multiple registers + * @param slave_address : slave address + * @param start_address : register start address + * @param quantity : how many registers want to be read + * @return array of int, or null if failed + */ + public int[] ReadMultipleRegisters(int slave_address, int start_address, int quantity) { + if (modbus instanceof ModbusMaster) { + if (modbus.isConnected()) { + try { + int[] result = modbus.readHoldingRegisters(slave_address, start_address, quantity); + if (result!=null) { + if (result.length>0) { + return result; + } + } + } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) { + raise_log("ReadMultipleRegisters failed, Msg : "+e.getMessage()); + } + } + } + return null; + } + + + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_datasent(byte[] value) { + if (need_datasent_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_datasent", false, new Object[] {value}); + } + + private void raise_datareceived(byte[] value) { + if (need_datareceived_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_datareceived", false, new Object[] {value}); + } + + private String printbytes(byte[] bb) { + if (bb!=null) { + if (bb.length>0) { + StringBuilder str = new StringBuilder(); + for(byte xx: bb) { + str.append(Bit.ToHexString(Bit.And(xx, 0xFF))).append(" "); + } + return str.toString(); + } + } + return ""; + } + + FrameEventListener listener = new FrameEventListener() { + + @Override + public void frameSentEvent(FrameEvent event) { + + raise_log("Data Sent : "+printbytes(event.getBytes())); + raise_datasent(event.getBytes()); + } + + @Override + public void frameReceivedEvent(FrameEvent event) { + raise_log("Data Received : "+printbytes(event.getBytes())); + raise_datareceived(event.getBytes()); + } + + + }; +} diff --git a/src/androgpio/ModbusWrapper/jModbusSlave.java b/src/androgpio/ModbusWrapper/jModbusSlave.java new file mode 100644 index 0000000..c236656 --- /dev/null +++ b/src/androgpio/ModbusWrapper/jModbusSlave.java @@ -0,0 +1,345 @@ +package androgpio.ModbusWrapper; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import com.fazecast.jSerialComm.SerialPort; +import com.intelligt.modbus.jlibmodbus.Modbus; +import com.intelligt.modbus.jlibmodbus.data.DataHolder; +import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException; +import com.intelligt.modbus.jlibmodbus.serial.SerialParameters; +import com.intelligt.modbus.jlibmodbus.serial.SerialPort.BaudRate; +import com.intelligt.modbus.jlibmodbus.serial.SerialPort.Parity; +import com.intelligt.modbus.jlibmodbus.serial.SerialPortException; +import com.intelligt.modbus.jlibmodbus.serial.SerialUtils; +import com.intelligt.modbus.jlibmodbus.slave.ModbusSlave; +import com.intelligt.modbus.jlibmodbus.slave.ModbusSlaveFactory; +import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters; +import com.intelligt.modbus.jlibmodbus.utils.FrameEvent; +import com.intelligt.modbus.jlibmodbus.utils.FrameEventListener; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.objects.collections.List; + +@BA.ShortName("jModbusSlave") +@BA.Events(values= { + "log(msg as string)", + "datasent(bb() as byte)", + "datareceived(bb() as byte)" +}) +@BA.DependsOn(values = { "jSerialComm-2.9.2","jlibmodbus-1.2.9.7" }) + +/** + * Create Modbus Slave + * in Modbus, Slave is actually a server that hold data. + * Master is a client, that request data from server. + * @author rdkartono + * + */ +public class jModbusSlave { + + + private Object caller; + private String event; + private BA ba; + private final Object Me = this; + + private boolean need_log_event = false; + private boolean need_datasent_event = false; + private boolean need_datareceived_event = false; + + + private ModbusSlave modbus = null; + private DataHolder dh = null; + private jModbusCoil coil = null; + private jModbusHoldingRegister reg = null; + + /** + * Initialize ModbusSerialSlaveRTU + * @param callerobject : caller object + * @param eventname : event name + */ + public void Initialize(BA ba, Object callerobject, String eventname) { + this.ba = ba; + event = eventname.trim(); + caller = callerobject; + dh = new DataHolder(); + if (this.ba!=null) { + if (caller!=null) { + if (!event.isEmpty()) { + need_log_event = this.ba.subExists(event+"_log"); + need_datasent_event = this.ba.subExists(event+"_datasent"); + need_datareceived_event = this.ba.subExists(event+"_datareceived"); + } + } + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + Close(); + } + }); + } + + /** + * Get available Serial Port Names + * @return List of port names. If exists, List size > 0 + */ + public List GetSerialPortNames() { + List result = new List(); + result.Initialize(); + + + SerialPort[] sp = SerialPort.getCommPorts(); + if (sp!=null) { + if (sp.length>0) { + for(SerialPort xx : sp) { + result.Add(xx.getSystemPortName()); + } + } + } + return result; + } + + /** + * Open Modbus Slave in Serial ASCII format + * @param serialname : serial port name + * @param baudrate : baud rate, typically 4800, 9600, 144400, 19200, 38400, 57600, 115200 + * @param databit : data bit length , typically 8 + * @param stopbit : typically 1 + * @param parity : 0 = None, 1 = Odd, 2 = Even, 3 = Mark, 4 = Space + * @return true if success + */ + public boolean OpenSerial_ASCII(String serialname, int baudrate, int databit, int stopbit, int parity) { + SerialParameters sp = new SerialParameters(); + + sp.setDataBits(databit); + sp.setDevice(serialname); + sp.setStopBits(stopbit); + sp.setBaudRate(BaudRate.getBaudRate(baudrate)); + sp.setParity(Parity.getParity(parity)); + + SerialUtils.setSerialPortFactoryJSerialComm(); + + try { + modbus = ModbusSlaveFactory.createModbusSlaveASCII(sp); + modbus.addListener(listener); + if (dh==null) dh = new DataHolder(); + modbus.setDataHolder(dh); + modbus.listen(); + return true; + } catch(SerialPortException e) { + raise_log("OpenSerial_ASCII failed, Msg : "+e.getMessage()); + return false; + } catch (ModbusIOException e) { + raise_log("Modbus Listen failed, Msg : "+e.getMessage()); + return false; + } + } + + /** + * Open Modbus Slave in Serial RTU format + * @param serialname : serial port name + * @param baudrate : baud rate, typically 4800, 9600, 144400, 19200, 38400, 57600, 115200 + * @param databit : data bit length , typically 8 + * @param stopbit : typically 1 + * @param parity : 0 = None, 1 = Odd, 2 = Even, 3 = Mark, 4 = Space + * @return true if success + */ + public boolean OpenSerial_RTU(String serialname, int baudrate, int databit, int stopbit, int parity) { + + SerialParameters sp = new SerialParameters(); + + sp.setDataBits(databit); + sp.setDevice(serialname); + sp.setStopBits(stopbit); + sp.setBaudRate(BaudRate.getBaudRate(baudrate)); + sp.setParity(Parity.getParity(parity)); + + SerialUtils.setSerialPortFactoryJSerialComm(); + + try { + + modbus = ModbusSlaveFactory.createModbusSlaveRTU(sp); + + modbus.addListener(listener); + if (dh==null) dh = new DataHolder(); + modbus.setDataHolder(dh); + modbus.listen(); + return true; + } catch (SerialPortException e) { + raise_log("OpenSerial_RTU failed, Msg : "+e.getMessage()); + return false; + } catch (ModbusIOException e) { + raise_log("Modbus Listen failed, Msg : "+e.getMessage()); + return false; + } + } + + /** + * Open Modbus Slave in TCP + * @param hostaddress : network adapter IP address, or empty string for default ip address + * @param port : port number, if negative number, will use default TCP port 502 + * @param enable_keepalive : set true to enable keep alive connection + * @return true if success + */ + public boolean OpenTCP(String hostaddress, int port, boolean enable_keepalive) { + InetAddress host; + try { + if (hostaddress.isEmpty()) { + host = InetAddress.getLocalHost(); + } else { + host = InetAddress.getByName(hostaddress); + } + } catch (UnknownHostException | SecurityException e) { + raise_log("Unable to get InetAddress for host="+hostaddress+", Msg : "+e.getMessage()); + return false; + } + + if (port<1) port = Modbus.TCP_PORT; + + TcpParameters tp = new TcpParameters(); + tp.setPort(port); + tp.setHost(host); + tp.setKeepAlive(enable_keepalive); + + modbus = ModbusSlaveFactory.createModbusSlaveTCP(tp); + if (dh==null) dh = new DataHolder(); + + modbus.setDataHolder(dh); + modbus.addListener(listener); + try { + modbus.listen(); + } catch (ModbusIOException e) { + raise_log("Unable to start Modbus Listen, Msg : "+e.getMessage()); + return false; + } + return true; + } + + /** + * Get / Set Slave ID. + * On getting, when returning -1, means modbus not yet opened + */ + public void setID(int value) { + if (modbus!=null) { + modbus.setServerAddress(value); + } + } + + public int getID() { + if (modbus!=null) { + return modbus.getServerAddress(); + } else return -1; + } + + /** + * Initialize coils + * coil is binary (true/false) value, 1 bit + * @param size : how many coils want to initialize. If < 1, default to 1024 + */ + public void Initialize_Coils(int size) { + if (size>0) + coil = new jModbusCoil(size); + else + coil = new jModbusCoil(); + + if (dh==null) dh = new DataHolder(); + dh.setCoils(coil); + } + + /** + * Get Coils that already been initialized + * @return null if failed + */ + public jModbusCoil getCoils() { + return coil; + } + + /** + * Get Registers that already been initialized + * @return null if failed + */ + public jModbusHoldingRegister getRegisters() { + return reg; + } + + /** + * Initialize Register + * Register is 16 bit value, can read and write + * @param size : how many registers want to initialize. If < 1, default to 1024 + */ + public void Initialize_Registers(int size) { + if (size>0) + reg = new jModbusHoldingRegister(size); + else + reg = new jModbusHoldingRegister(); + + if (dh==null) dh = new DataHolder(); + dh.setHoldingRegisters(reg); + } + + /** + * Close Modbus Serial Slave RTU + * @return true if success + */ + public boolean Close() { + if (modbus!=null) { + try { + modbus.removeListeners(); + modbus.shutdown(); + } catch (ModbusIOException e) { + raise_log("Modbus Close failed, Msg : "+e.getMessage()); + return false; + } + } + return true; + } + + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_datasent(byte[] value) { + if (need_datasent_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_datasent", false, new Object[] {value}); + } + + private void raise_datareceived(byte[] value) { + if (need_datareceived_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_datareceived", false, new Object[] {value}); + } + + private String printbytes(byte[] bb) { + if (bb!=null) { + if (bb.length>0) { + StringBuilder str = new StringBuilder(); + for(byte xx: bb) { + str.append(Bit.ToHexString(Bit.And(xx, 0xFF))).append(" "); + } + return str.toString(); + } + } + return ""; + } + + FrameEventListener listener = new FrameEventListener() { + + @Override + public void frameSentEvent(FrameEvent event) { + + raise_log("Data Sent : "+printbytes(event.getBytes())); + raise_datasent(event.getBytes()); + } + + @Override + public void frameReceivedEvent(FrameEvent event) { + raise_log("Data Received : "+printbytes(event.getBytes())); + raise_datareceived(event.getBytes()); + } + + + }; + + +} diff --git a/src/androgpio/ScreenRotater.java b/src/androgpio/ScreenRotater.java new file mode 100644 index 0000000..6979b4c --- /dev/null +++ b/src/androgpio/ScreenRotater.java @@ -0,0 +1,145 @@ +package androgpio; + +import android.content.ContentResolver; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.IOnActivityResult; + +@BA.ShortName("ScreenRotater") +@BA.Permissions(values= { + "android.permission.WRITE_SETTINGS" +}) +@BA.Events(values= { + "log(msg as string)", + "waitpermission(result as int)" +}) +public class ScreenRotater { + private BA ba; + private String event; + private Object Me; + private boolean need_log_event = false; + private boolean need_waitpermission_event = false; + + + /** + * Initialize Screen Rotater + * @param eventname Eventname + */ + + public void Initialize(BA ba, String eventname) { + this.ba = ba; + this.event = eventname; + Me = this; + check_events(); + + } + + @SuppressWarnings("static-access") + private String getPackageName() { + return ba.packageName; + } + + @SuppressWarnings("static-access") + private ContentResolver getContentResolver() { + return ba.applicationContext.getContentResolver(); + } + + public boolean PermissionAllowed() { + + return Settings.System.canWrite(ba.context); + } + + public void RequestPermission() { + Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); + + intent.setData(Uri.parse("package:" + getPackageName())); + ba.startActivityForResult(new IOnActivityResult() { + + @Override + public void ResultArrived(int resultCode, Intent intent) { + raise_log("Result code = "+resultCode); + raise_waitpermission(resultCode); + } + + }, intent); + + } + + /** + * Get Rotation value + * @return -1 if not defined, 0 / 90 / 180 / 270 if defined + */ + public int GetRotation() { + int value = android.provider.Settings.System.getInt(getContentResolver(), android.provider.Settings.System.USER_ROTATION, -1); + switch(value) { + case 0 : + raise_log("GetRotation user_rotation=0"); + return 0; + case 1 : + raise_log("GetRotation user_rotation=1"); + return 90; + case 2 : + raise_log("GetRotation user_rotation=2"); + return 180; + case 3 : + raise_log("GetRotation user_rotation=3"); + return 270; + default : + raise_log("GetRotation user_rotation undefined"); + return -1; + } + } + + /** + * Set Screen Rotation + * @param value 0 / 90 / 180 / 270 + * @return true if valid + */ + public boolean SetRotation(int value) { + // disable accelerator rotation + android.provider.Settings.System.putInt(getContentResolver(), android.provider.Settings.System.ACCELEROMETER_ROTATION, 0); + + switch(value) { + case 0 : + android.provider.Settings.System.putInt(getContentResolver(), android.provider.Settings.System.USER_ROTATION, 0); + raise_log("SetRotation user_rotation=0"); + return true; + case 90 : + android.provider.Settings.System.putInt(getContentResolver(), android.provider.Settings.System.USER_ROTATION, 1); + raise_log("SetRotation user_rotation=1"); + return true; + case 180 : + android.provider.Settings.System.putInt(getContentResolver(), android.provider.Settings.System.USER_ROTATION, 2); + raise_log("SetRotation user_rotation=2"); + return true; + case 270 : + android.provider.Settings.System.putInt(getContentResolver(), android.provider.Settings.System.USER_ROTATION, 3); + raise_log("SetRotation user_rotation=3"); + return true; + default : + raise_log("rotate failed, invalid value="+value); + return false; + } + } + + private void check_events() { + if (ba!=null) { + if (event!=null) { + if (event.length()>0) { + need_log_event = ba.subExists(event+"_log"); + need_waitpermission_event =ba.subExists(event+"_waitpermission"); + } + } + } + } + + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_waitpermission(int result) { + if (need_waitpermission_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_waitpermission", false, new Object[] {result}); + } +} diff --git a/src/androgpio/SerialPort/SerialComm.java b/src/androgpio/SerialPort/SerialComm.java new file mode 100644 index 0000000..0f0ebf2 --- /dev/null +++ b/src/androgpio/SerialPort/SerialComm.java @@ -0,0 +1,505 @@ +package androgpio.SerialPort; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.List; + +import com.fazecast.jSerialComm.*; + + +@BA.ShortName("SerialComm") +@BA.Events(values= { + "log(msg as string)", + "newdata(bb() as byte)" + +}) +@BA.DependsOn(values = { "jSerialComm-2.9.2" }) + +public class SerialComm { + + private BA ba; + private Object myobject; + private String event; + private boolean need_log_event = false; + private boolean need_newdata_event = false; + + + private jSerialPort_Event javaevent; + private boolean isReady = false; + private String version=""; + private long writtencount = 0; + private long readcount = 0; + + private SerialPort myport; + + private String myportname; + + + /** + * Initialize jSerialPort (B4J / B4A) + * @param caller : caller object + * @param eventname : event name + */ + public void Initialize(BA bax, Object caller, String eventname) { + isReady = false; + ba = bax; + event = eventname; + myobject = caller; + need_log_event = ba.subExists(event+"_log"); + need_newdata_event = ba.subExists(event+"_newdata"); + + version = SerialPort.getVersion(); + if (version!="") isReady = true; + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + ClosePort(); + } + }); + } + + /** + * Check Version String of SerialPort Library + * @return version string + */ + public String GetVersion() { + return version; + } + + /** + * Get all available port names in this system + * @return List of Serial Port Names + */ + public List GetPortNames(){ + List result = new List(); + result.Initialize(); + if (isReady) { + for(SerialPort xx:SerialPort.getCommPorts()) { + if (xx != null) { + + String portname = xx.getSystemPortName(); + if (portname!="") { + result.Add(portname); + } + } + } + } else { + raise_log("GetPortNames failed, SerialPort Library not ready"); + } + return result; + } + + /** + * Check if SerialComm is Ready to work + * @return true if ready + */ + public boolean PortIsReady() { + if (myport!=null) { + if (myport instanceof SerialPort) { + return myport.isOpen(); + } + } + return false; + + } + + /** + * Open Serial Port, with default value 1000 bytes RX buffer, and 1000 bytes TX Buffer + * @param portname : portname (a member of GetPortNames) + * @return true if port opened + */ + public boolean OpenPort(String portname) { + myportname=""; + writtencount = 0; + readcount = 0; + if (isReady) { + myport = SerialPort.getCommPort(portname); + if (myport instanceof SerialPort) { + if (myport.openPort()) { + + if (myport.addDataListener(new SerialPortDataListener() { + + @Override + public int getListeningEvents() { + return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; + } + + @Override + public void serialEvent(SerialPortEvent event) { + if (event instanceof SerialPortEvent) { + if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) { + int available = myport.bytesAvailable(); + if (available>0) { + byte[] bb = new byte[available]; + int readbytes = myport.readBytes(bb, available); + + raise_newdata(bb); + readcount+= readbytes; + } + } + } + } + + })) { + + } else { + raise_log("Failed to add DataListener to SerialComm"); + } + + + myportname = portname; + raise_log("SerialPort name="+myportname+" is opened"); + return true; + } + } + + } + raise_log("Failed to open SerialPort name="+portname); + return false; + } + + @BA.Hide + /** + * Read serial port + * @param count : maximum bytes wanted to read + * @return array of bytes, or null if failed + */ + public byte[] Read(int count) { + if (isReady) { + if (myport instanceof SerialPort) { + if (myport.isOpen()) { + int available = myport.bytesAvailable(); + if (available>0) { + byte[] bb = new byte[count]; + myport.readBytes(bb, bb.length); + + readcount+= bb.length; + return bb; + } + } + } + } + return null; + } + + /** + * Close Serial Port + */ + public void ClosePort() { + if (isReady) { + if (myport!=null) { + if (myport instanceof SerialPort) { + try { + myport.removeDataListener(); + if (myport.isOpen()) myport.closePort(); + } catch(Exception e) { + raise_log("ClosePort exception, Msg : "+e.getMessage()); + } + } + + myport = null; + raise_log("SerialPort name="+myportname+" is closed"); + } + } + isReady = false; + myportname = ""; + + } + + /** + * Get current used Port Name + * @return port name, or empty string if closed + */ + public String CurrentPortName() { + return myportname; + } + + /** + * Get Baud Rate + * @return -1 if failed + */ + public int GetBaudRate() { + if (isReady) { + if (myport instanceof SerialPort) { + return myport.getBaudRate(); + } + } + return -1; + } + + /** + * Set Baud Rate + * @param value : baud rate value + * @return true if success + */ + public boolean SetBaudRate(int value) { + if (isReady) { + if (myport instanceof SerialPort) { + myport.setBaudRate(value); + return true; + } + } + return false; + } + + /** + * Get Data bits + * @return -1 if failed + */ + public int GetDataBits() { + if (isReady) { + if (myport instanceof SerialPort) { + return myport.getNumDataBits(); + } + } + + return -1; + } + + /** + * Set Data bits + * @param value : data bits value + * @return true if success + */ + public boolean SetDataBits(int value) { + if (isReady) { + if (myport instanceof SerialPort) { + myport.setNumDataBits(value); + return true; + } + } + return false; + } + + /** + * Get / Set Stop bit + * @return -1 = failed, 1 = 1 stop bit, 2 = 2 stop bit, 3 = 1.5 stop bit + */ + public int GetStopBits() { + if (isReady) { + if (myport instanceof SerialPort) { + int value = myport.getNumStopBits(); + switch(value) { + case SerialPort.ONE_STOP_BIT : + return 1; + case SerialPort.ONE_POINT_FIVE_STOP_BITS : + return 3; + case SerialPort.TWO_STOP_BITS : + return 2; + } + } + } + return -1; + } + + /** + * Set Stop bit + * @param stopbitcode : 1 = 1 stop bit, 2 = 2 stop bit, 3 = 1.5 stop bit, other = 1 stop bit + * @return true if success + */ + public boolean SetStopBits(int stopbitcode) { + if (isReady) { + if (myport instanceof SerialPort) { + switch(stopbitcode) { + case 3 : + myport.setNumStopBits(SerialPort.ONE_POINT_FIVE_STOP_BITS); + break; + case 2 : + myport.setNumStopBits(SerialPort.TWO_STOP_BITS); + break; + default : + myport.setNumStopBits(SerialPort.ONE_STOP_BIT); + break; + + } + return true; + } + } + return false; + } + + /** + * Get Parity Code + * @return -1 if failed, 0 = None, 1 = Odd, 2 = Even, 3 = Mark, 4 = Space + */ + public int GetParityBits() { + if (isReady) { + if (myport instanceof SerialPort) { + int value = myport.getParity(); + switch(value) { + case SerialPort.ODD_PARITY : + return 1; + case SerialPort.EVEN_PARITY : + return 2; + case SerialPort.MARK_PARITY : + return 3; + case SerialPort.SPACE_PARITY : + return 4; + default : + return 0; + } + } + } + return -1; + } + + /** + * Set Parity + * @param paritycode : 1 = odd, 2 = even, 3 = mark, 4 = space, default (0) = None + * @return true if success + */ + public boolean SetParityBits(int paritycode) { + if (isReady) { + if (myport instanceof SerialPort) { + switch(paritycode) { + case 1 : + myport.setParity(SerialPort.ODD_PARITY); + break; + case 2 : + myport.setParity(SerialPort.EVEN_PARITY); + break; + case 3 : + myport.setParity(SerialPort.MARK_PARITY); + break; + case 4 : + myport.setParity(SerialPort.SPACE_PARITY); + break; + default : + myport.setParity(SerialPort.NO_PARITY); + break; + } + return true; + } + } + return false; + } + + /** + * Get available bytes to read in internal buffer + * @return -1 if failed + */ + public int getAvailableBytesToRead() { + if (isReady) { + if (myport instanceof SerialPort) { + return myport.bytesAvailable(); + } + } + return -1; + } + + /** + * Get number of bytes stil waiting to send in internal buffer + * @return -1 if failed + */ + public int getWaitingBytesToSend() { + if (isReady) { + if (myport instanceof SerialPort) { + return myport.bytesAwaitingWrite(); + } + } + return -1; + } + + /** + * Get Clear-To-Send bit value + * @return true if CTS set + */ + public boolean getCTS() { + if (isReady) { + if (myport instanceof SerialPort) { + return myport.getCTS(); + } + } + return false; + } + + /** + * Set Request-To-Send bit + * @param value : if true, RTS is ON + */ + public void setRTS(boolean value) { + if (isReady) { + if (myport instanceof SerialPort) { + + if (value) + myport.setRTS(); + else + myport.clearRTS(); + } + } + } + + /** + * Write bytes to Serial + * @param bb : bytes to write + * @return number of bytes written to internal buffer, or -1 if failed + */ + public int Write(byte[] bb) { + if (isReady) { + if (myport instanceof SerialPort) { + int writeresult = myport.writeBytes(bb, bb.length); + writtencount+=writeresult; + return writeresult; + } + } + return -1; + } + + /** + * Set Read and Write blocking / timeout. Positive value means block until specific timeout + * @param readblocking_ms : 0 = no read block, positive = milliseconds to block waiting for read + * @param writeblocking_ms : 0 = no write block, positive = milliseconds to block until writing complete + */ + public void SetBlockingMode(int readblocking_ms, int writeblocking_ms) { + if (isReady) { + if (myport instanceof SerialPort) { + int blockvalue = 0; // default is NON_BLOCKING + if (readblocking_ms>0) blockvalue |= SerialPort.TIMEOUT_READ_BLOCKING; + if (writeblocking_ms>0) blockvalue |= SerialPort.TIMEOUT_WRITE_BLOCKING; + + myport.setComPortTimeouts(blockvalue, readblocking_ms, writeblocking_ms); + } + } + } + + /** + * Get how many bytes written since opening + * @return bytes written + */ + public long getBytesWritten() { + return writtencount; + } + + /** + * Get how many bytes read since opening + * @return bytes read + */ + public long getBytesRead() { + return readcount; + } + + @BA.Hide + public void SetJavaEvent(jSerialPort_Event event) { + javaevent = event; + } + + private void raise_log(String msg) { + if (need_log_event) { + ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {msg}); + } + if (javaevent!=null) { + javaevent.Log(msg); + } + } + + private void raise_newdata(byte[] bb) { + if (need_newdata_event) { + ba.raiseEventFromDifferentThread(myobject, null, 0, event+"_newdata", false, new Object[] {bb}); + } + if (javaevent!=null) { + javaevent.newdata(bb); + } + } + + +} diff --git a/src/androgpio/SerialPort/jSerialPort_Event.java b/src/androgpio/SerialPort/jSerialPort_Event.java new file mode 100644 index 0000000..f7ff80c --- /dev/null +++ b/src/androgpio/SerialPort/jSerialPort_Event.java @@ -0,0 +1,6 @@ +package androgpio.SerialPort; + +public interface jSerialPort_Event { + void Log(String msg); + void newdata(byte[] bb); +} diff --git a/src/androgpio/androgpio.java b/src/androgpio/androgpio.java new file mode 100644 index 0000000..c80374d --- /dev/null +++ b/src/androgpio/androgpio.java @@ -0,0 +1,113 @@ +package androgpio; + +import java.io.File; + +import com.sun.jna.Platform; + +import android.content.pm.ApplicationInfo; +import anywheresoftware.b4a.BA; + +@BA.Author("Rudy Darmawan") +@BA.Version(0.31f) +@BA.ShortName("androgpio") +@BA.DependsOn(values = { "jna-min","jna-platform"}) +public class androgpio { + BA ba; + String eventname; + String rundirectory; + ApplicationInfo pi; + public void Initialize(BA ba, String eventname) { + this.ba = ba; + this.eventname = eventname; + File xxx = new File(""); + rundirectory = xxx.getAbsolutePath(); + pi = ba.context.getApplicationInfo(); + + } + + /** + * Get UserID assigned by kernel + * @return user ID number, or XX if androgpio not initialized before + */ + public String getUserID() { + return (pi != null ? String.valueOf(pi.uid) : "XX"); + } + + /** + * Get Native JNI Library Directory + * @return "N/A" kalau gak ada + */ + public String getNativeLibraryDir() { + return (pi != null ? pi.nativeLibraryDir : "N/A"); + } + + + /** + * Get Shared Library Files + * @return array string + */ + public String[] getSharedLibraryFiles() { + return (pi != null ? pi.sharedLibraryFiles : new String[] {}); + } + + /** + * Get Data Directory + * @return empty string if not initialized + */ + public String getDataDir() { + return (pi != null ? pi.dataDir : ""); + } + + /** + * Get Device's Protected Data Dir + * @return empty string if not initialized + */ + public String getProtectedDataDir() { + return (pi!=null ? pi.deviceProtectedDataDir : ""); + } + + public boolean isAndroid() { + return Platform.isAndroid(); + } + + public boolean is64bit() { + return Platform.is64Bit(); + } + + public String PlatformArchitecture() { + return System.getProperty("os.arch"); + } + + /** + * Will return Linux instead of Android + * @return OS Name in string + */ + public String OSName() { + return System.getProperty("os.name"); + } + + /** + * Will return Kernel Version + * @return Kernel Version in String + */ + public String KernelVersion() { + return System.getProperty("os.version"); + } + + public String PathSeparator() { + return System.getProperty("path.separator"); + } + + public String JavaLibraryPath() { + return System.getProperty("java.library.path"); + } + + public String JavaVMName() { + return System.getProperty("java.vm.name"); + } + + public static void main(String[] args) { + System.out.println("androgpio"); + } + +} diff --git a/src/androgpio/customsocket/AndroFTPClient.java b/src/androgpio/customsocket/AndroFTPClient.java new file mode 100644 index 0000000..72e3326 --- /dev/null +++ b/src/androgpio/customsocket/AndroFTPClient.java @@ -0,0 +1,1036 @@ +package androgpio.customsocket; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; + +import org.apache.commons.net.ProtocolCommandEvent; +import org.apache.commons.net.ProtocolCommandListener; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPClientConfig; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPSClient; +import org.apache.commons.net.io.CopyStreamEvent; +import org.apache.commons.net.io.CopyStreamListener; +import org.apache.commons.net.util.TrustManagerUtils; + + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + +// Source : https://medium.com/bliblidotcom-techblog/java-ftp-integration-using-apache-commons-net-5efb3d300829 +// Source : https://commons.apache.org/proper/commons-net/apidocs/org/apache/commons/net/ftp/FTPClient.html + +@BA.ShortName("AndroFTPClient") +@BA.Events(values= { + "log(msg as string)", + "configurecomplete(success as boolean)", + "listcomplete(path as string, success as boolean, files() as AndroFTPEntry, folders() as AndroFTPEntry)", + "makedirectorycomplete(path as string, success as boolean)", + "deletefilecomplete(path as string, success as boolean)", + "removedirectorycomplete(path as string, success as boolean)", + "uploadcomplete(localfile as string, ftppath as string, success as boolean)", + "downloadcomplete(ftppath as string, localfile as string, success as boolean)", + "downloadprogress(ftppath as string, localfile as string, totalbytestransfered as long, bytestransfered as long, size as long, speedKbps as double)", + "uploadprogress(localfile as string, ftppath as string, totalbytestransfered as long, bytestransfered as long, size as long)", + "androftpresult(path as string, success as boolean, entry as AndroFTPEntry)", + "renamefilecomplete(oldname as string, newname as string, success as boolean)", + "appendfilecomplete(localpath as string, ftppath as string, success as boolean)", + "appendfileprogress(localpath as string, ftppath as string, totalbytestransfered as long, bytestransfered as long, size as long)" + +}) + +@BA.DependsOn(values = { "commons-net-3.10.0" }) +@BA.Permissions(values = { "android.permission.INTERNET" }) +public class AndroFTPClient { + + private BA ba; + private String eventname; + private Object Me; + private boolean need_log_event = false; + private boolean need_listcomplete_event = false; + + private boolean need_makedirectorycomplete_event = false; + private boolean need_deletefilecomplete_event = false; + private boolean need_removedirectorycomplete_event = false; + private boolean need_uploadcomplete_event = false; + private boolean need_downloadcomplete_event = false; + + private boolean serverconfirmed = false; + private String mServer = null; + private int mPort = 21; + private String mUser = null; + private String mPassword = null; + private boolean mPassiveMode = false; + + private boolean mSecured = false; + private FTPClientConfig mConfig = null; + + + /** + * Initialize AndroFTPClient + * @param eventname event name + */ + public void Initialize(BA ba, String eventname) { + this.ba = ba; + this.eventname = eventname; + Me = this; + mConfig = new FTPClientConfig(FTPClientConfig.SYST_L8); + //mConfig.setUnparseableEntries(true); + + if (ba!=null) { + if (mycodes.valid_string(eventname)) { + need_log_event = ba.subExists(eventname + "_log"); + need_listcomplete_event = ba.subExists(eventname + "_listcomplete"); + need_makedirectorycomplete_event = ba.subExists(eventname + "_makedirectorycomplete"); + need_deletefilecomplete_event = ba.subExists(eventname + "_deletefilecomplete"); + need_uploadcomplete_event = ba.subExists(eventname + "_uploadcomplete"); + need_downloadcomplete_event = ba.subExists(eventname + "_downloadcomplete"); + } + } + } + + /** + * Check if Server Address and Port is confirmed and connectable + * @return true if confirmed + */ + public boolean ServerConfirmed() { + return serverconfirmed; + } + + // Helper to get either FTPClient or FTPSClient based on boolean secured +// private T getftpclient(boolean secured, T iftrue, T iffalse) { +// return secured ? iftrue : iffalse; +// } + + + + + + // Helper to create new Object either FTPClient or FTPSClient based on boolean secured + // FTPSClient is extending from FTPClient + private FTPClient CreateFTPClient(boolean secured) { + if (secured) { + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + FTPSClient cl = new FTPSClient(); + cl.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager()); + try { + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(null, null); + KeyManager km = kmf.getKeyManagers()[0]; + cl.setKeyManager(km); + } catch (NoSuchAlgorithmException e) { + raise_log("CreateFTPClient [secured=true] failed, Exception=" + e.getMessage()); + } catch (UnrecoverableKeyException e) { + raise_log("CreateFTPClient [secured=true] failed, Exception=" + e.getMessage()); + } catch (KeyStoreException e) { + raise_log("CreateFTPClient [secured=true] failed, Exception=" + e.getMessage()); + } + + cl.setBufferSize(8*1024); + return cl; + } else { + + FTPClient cl = new FTPClient(); + cl.setBufferSize(8*1024); + return cl; + } + + } + + + /** + * Configure FTP Connection + * Will raise event configurecomplete(success as boolean) + * @param server server address + * @param port server port + * @param user username + * @param password password + * @param passive true for passive mode, false for active mode + * @param binary true for binary mode, false for ascii mode + * @param secured true for secured connection (FTPS), false for unsecured (FTP) + * @return Object to use in Wait For + */ + public Object Configure_Connection(BA bax,String server, int port, String user, String password, boolean passive, boolean binary, boolean secured) { + serverconfirmed = false; + mServer = null; + mPort = 0; + mUser = null; + mPassword = null; + mPassiveMode = false; + mSecured = false; + Object sender = new Object(); + BA.runAsync(bax, sender, eventname+"_configurecomplete", new Object[] {false}, new Callable() { + + @Override + public Object[] call() throws Exception { + FTPClient ftp = CreateFTPClient(secured); + ftp.configure(mConfig); + + //if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + try { + + ftp.setConnectTimeout(5000); + ftp.connect(server, port); + if (ftp.login(user, password)){ + + ftp.logout(); + serverconfirmed = true; + mServer = server; + mPort = port; + mUser = user; + mPassword = password; + mPassiveMode = passive; + + mSecured = secured; + } else raise_log("Unable to login to server"); + ftp.disconnect(); + } catch (IOException e) { + raise_log("Unable to FTP Connect to Server " + server + ":" + port + ", Exception=" + e.getMessage()); + } + return new Object[] {serverconfirmed}; + } + + }); + return sender; + } + + /** + * Check if using FTPS (secured) or FTP (unsecured) + * @return true if secured + */ + public boolean getSecured() { + return mSecured; + } + + /** + * Get FTP Server Address + * @return null if not assigned + */ + public String getServer() { + return mServer; + } + + /** + * Get FTP Server Port + * + * @return 0 if not assigned + */ + public int getPort() { + return mPort; + } + + + /** + * Get FTP Username + * + * @return null if not assigned + */ + public String getUser() { + return mUser; + } + + + /** + * Get FTP Password + * + * @return null if not assigned + */ + public String getPassword() { + return mPassword; + } + + /** + * Get Passive Mode + * + * @return true if passive mode + */ + public boolean getPassiveMode() { + return mPassiveMode; + } + + + + + /** + * Make Directory in FTP Server + * Will raise event makedirectorycomplete(path as string, success as boolean) + * @param path path to create + * @return Object to use in Wait For + */ + public Object MakeDirectory(BA bax, final String path) { + Object sender = new Object(); + if (serverconfirmed) { + BA.runAsync(bax, sender, eventname+"_makedirectorycomplete", new Object[] {path, false}, new Callable(){ + + @Override + public Object[] call() throws Exception{ + boolean result = false; + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + + //if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + try { + ftp.connect(mServer, mPort); + if (ftp.login(mUser, mPassword)) { + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + result = ftp.makeDirectory(path); + ftp.logout(); + } + ftp.disconnect(); + } catch (IOException e) { + raise_log("MakeDirectory failed, Exception=" + e.getMessage()); + } + return new Object[] {path, result}; + } + + }); + + } else { + raise_log("MakeDirectory failed, Server not confirmed"); + raise_makedirectorycomplete(sender,path, false); + } + return sender; + + } + + /** + * Delete File in FTP Server + * Will raise event deletefilecomplete(path as string, success as boolean) + * + * @param path path to delete + * @return Object to use in Wait For + */ + public Object DeleteFile(BA bax,final String path) { + Object sender = new Object(); + if (serverconfirmed) { + BA.runAsync(bax, sender, eventname+"_deletefilecomplete", new Object[] {path, false}, new Callable() { + + @Override + public Object[] call() throws Exception { + boolean result = false; + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + + // if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + try { + ftp.connect(mServer, mPort); + if (ftp.login(mUser, mPassword)) { + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + result = ftp.deleteFile(path); + + ftp.logout(); + } + ftp.disconnect(); + } catch (IOException e) { + raise_log("DeleteFile failed, Exception=" + e.getMessage()); + } + return new Object[] {path, result}; + } + + }); + + } else { + raise_log("DeleteFile failed, Server not confirmed"); + raise_deletefilecomplete(sender, path, false); + } + return sender; + } + + /** + * Remove Directory in FTP Server + * Will raise event removedirectorycomplete(path as string, success as boolean) + * @param path path to remove + * @return Object to use in Wait For + */ + public Object RemoveDirectory(BA bax,final String path) { + Object sender = new Object(); + if (serverconfirmed) { + BA.runAsync(bax, sender, eventname+"_removedirectorycomplete", new Object[] {path, false}, new Callable() { + + @Override + public Object[] call() throws Exception { + boolean result = false; + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + + // if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + try { + ftp.connect(mServer, mPort); + if (ftp.login(mUser, mPassword)) { + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + result = ftp.removeDirectory(path); + + ftp.logout(); + } + ftp.disconnect(); + } catch (IOException e) { + raise_log("RemoveDirectory failed, Exception=" + e.getMessage()); + } + return new Object[] {path, result}; + } + + }); + + } else { + raise_log("RemoveDirectory failed, Server not confirmed"); + raise_removedirectorycomplete(sender, path, false); + } + return sender; + } + + /** + * Append File to FTP Server + * Will raise event appendfilecomplete(localpath as string, ftppath as string, success as boolean) + * @param localpath local file as source to append + * @param ftppath target ftp path to append + * @return Object to use in Wait For + */ + public Object AppendFile(BA bax, final String localpath, final String ftppath) { + Object sender = new Object(); + if (serverconfirmed) { + BA.runAsync(bax, sender, eventname+"_appendfilecomplete", new Object[] {localpath, ftppath, false}, new Callable(){ + + @Override + public Object[] call() throws Exception { + boolean result = false; + final boolean need_progress = bax.subExists(eventname+"_appendfileprogress"); + try { + File fsource = new File(localpath); + if (fsource.isFile()) { + if (fsource.length()>0) { + // sampai sini file local ada dan size > 0 + //BufferedInputStream fis = new BufferedInputStream(new FileInputStream(fsource)); + FileInputStream fis = new FileInputStream(fsource); + // konfigurasi FTP Client + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + ftp.connect(mServer, mPort); + + if (mPassiveMode) { + ftp.enterLocalPassiveMode(); + ftp.setIpAddressFromPasvResponse(true); + } + if (ftp.login(mUser, mPassword)) { + + //Harus set ini setelah login. + //Source : https://stackoverflow.com/questions/70395475/apache-commons-ftpclient-not-retrieving-all-bytes-from-source-file + ftp.setFileType(FTPClient.BINARY_FILE_TYPE); + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + // check apakah ftppath exist? + FTPFile ftarget = null; + FTPFile[] list = ftp.listFiles(ftppath); + + if (list!=null && list.length>0) ftarget = list[0]; + + if (ftarget != null) { // exists + if (ftarget.isFile()) { // berupa file + if (need_progress) ftp.setCopyStreamListener(new CopyStreamListener() { + @Override + public void bytesTransferred(CopyStreamEvent event) { + raise_log("Append Progress A : Source [" + + event.getSource().toString() + "] " + + event.getTotalBytesTransferred() + " / " + + event.getBytesTransferred() + " / " + + event.getStreamSize()); + raise_appendprogress(bax, sender, localpath, ftppath, + event.getTotalBytesTransferred(), + event.getBytesTransferred(), event.getStreamSize()); + } + + @Override + public void bytesTransferred(long totalBytesTransferred, + int bytesTransferred, long streamSize) { + raise_log("Append Progress B : " + totalBytesTransferred + " / " + + bytesTransferred + " / " + streamSize); + raise_appendprogress(bax, sender, localpath, ftppath, + totalBytesTransferred, bytesTransferred, streamSize); + } + }); + + // kasih keep alive biar koneksi terjaga + ftp.setKeepAlive(true); + // keep alive tiap 10 detik + ftp.setControlKeepAliveTimeout(Duration.ofSeconds(10)); + + result = ftp.appendFile(ftppath, fis); + + } else raise_log("AppendFile failed, FTP path [" + ftppath + "] is not File"); + } else raise_log("AppendFile failed, FTP path [" + ftppath + "] not exists"); + ftp.logout(); + } else raise_log("AppendFile [" + ftppath + "] failed, Unable to login to server"); + // disconnect FTP + ftp.disconnect(); + // close Input File + fis.close(); + } else raise_log("AppendFile failed, Local file [" + localpath + "] size is 0"); + } else raise_log("AppendFile failed, Local file [" + localpath + "] not exists"); + + + } catch (IOException e) { + raise_log("AppendFile [" + ftppath + "] to [" + localpath + "] failed, Exception=" + + e.getMessage()); + } + return new Object[] {localpath, ftppath, result}; + } + + }); + } else { + raise_log("AppendFile failed, Server not confirmed"); + bax.raiseEventFromDifferentThread(sender, null, 0, eventname+"_appendfilecomplete", false, new Object[] {localpath, ftppath, false}); + } + return sender; + } + + /** + * Download File from FTP Server + * will raise event downloadcomplete(ftppath as string, localfile as string, success as boolean) + * will raise event downloadprogress(ftppath as string, localfile as string, totalbytestransfered as long, bytestransfered as long, size as long, speedKbps as double) + * @param ftppath path to download + * @param localfile filename to save + * @return Object to use in Wait For + */ + public Object DownloadFile(BA bax,final String ftppath, final String localfile) { + Object sender = new Object(); + if (serverconfirmed) { + BA.runAsync(bax, sender, eventname+"_downloadcomplete", new Object[] {ftppath, localfile, false}, new Callable() { + + @Override + public Object[] call() throws Exception { + final boolean need_progress = bax.subExists(eventname+"_downloadprogress"); + boolean result = false; + final long sourcesize; // untuk progress, sebab event bytesTransferred streamSize return -1 + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + + if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + + try { + + ftp.connect(mServer, mPort); + + if (mPassiveMode) { + ftp.enterLocalPassiveMode(); + ftp.setIpAddressFromPasvResponse(true); + } + + if (ftp.login(mUser, mPassword)) { + //Harus set ini setelah login. + //Source : https://stackoverflow.com/questions/70395475/apache-commons-ftpclient-not-retrieving-all-bytes-from-source-file + ftp.setFileType(FTPClient.BINARY_FILE_TYPE); + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + // check apakah ftppath exist? + FTPFile fsource = null; + FTPFile[] list = ftp.listFiles(ftppath); + if (list!=null && list.length>0) fsource = list[0]; + + if (fsource!=null) { // exists + if (fsource.isFile()) { // berupa file + sourcesize = fsource.getSize(); + if (sourcesize>0) { // ada ukuran nya + // buat file di local storage + File ftarget = new File(localfile); + //BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(ftarget)); + FileOutputStream fos = new FileOutputStream(ftarget); + + // untuk download progress + if (need_progress) { + final AtomicLong transfered = new AtomicLong(0); + final AtomicLong tick = new AtomicLong(System.currentTimeMillis()); + final AtomicReference KBps = new AtomicReference(0.0); + + // FTP transfer status + ftp.setCopyStreamListener(new CopyStreamListener() { + + @Override + public void bytesTransferred(CopyStreamEvent event) { + // event ini gak trigger + //raise_log("Download Progress A : Source ["+event.getSource().toString()+"] "+event.getTotalBytesTransferred()+" / "+event.getBytesTransferred()+" / "+event.getStreamSize()); + //raise_downloadprogress(bax, sender, ftppath, localfile, event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); + } + + @Override + public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, + long streamSize) { + //bytetransfered tergantung buffer size, default di 1024 + //raise_log("Download Progress B : "+totalBytesTransferred+" / "+bytesTransferred+" / "+targetsize); + + transfered.set(transfered.get()+bytesTransferred); + long millis = System.currentTimeMillis(); + if ((millis-tick.get())>=1000) { + // update speed + long delta = millis-tick.get(); + tick.set(millis); + // dapat berapa bytes per second + double speed = transfered.get()/(delta/1000.0); + // reset + transfered.set(0); + KBps.set(speed/1024.0); + + + } + raise_downloadprogress(bax, sender, ftppath, localfile, totalBytesTransferred, bytesTransferred, sourcesize, KBps.get()); + } + }); + } + // kasih keep alive biar koneksi terjaga + ftp.setKeepAlive(true); + // keep alive tiap 10 detik + ftp.setControlKeepAliveTimeout(Duration.ofSeconds(10)); + + // perintah download, true = success, false = failed + result = ftp.retrieveFile(ftppath, fos); + // tutup file + fos.flush(); + fos.close(); + + long newsize = ftarget.length(); + raise_log("DownloadFile from ["+ftppath+"] to ["+localfile+"] "+(result ? "success":"failed")); + raise_log("FTP Size : "+sourcesize+" / Local Size : "+newsize); + } else raise_log("DownloadFile failed, FTP path ["+ftppath+"] size is 0"); + } else raise_log("DownloadFile failed, FTP path ["+ftppath+"] is not File"); + } else raise_log("DownloadFile failed, FTP path ["+ftppath+"] not exists"); + ftp.logout(); + } else raise_log("DownloadFile ["+ftppath+"] failed, Unable to login to server"); + ftp.disconnect(); + } catch (IOException e) { + raise_log("DownloadFile ["+ftppath+"] to ["+localfile+"] failed, Exception=" + e.getMessage()); + } + return new Object[] {ftppath, localfile, result}; + } + + }); + + } else { + raise_log("DownloadFile failed, Server not confirmed"); + raise_downloadcomplete(sender, ftppath, localfile, false); + } + return sender; + } + + /** + * Rename File in FTP Server + * Will raise event renamefilecomplete(oldname as string, newname as string, success as boolean) + * @param oldname old file name + * @param newname new file name + * @return Object to use in Wait For + */ + public Object RenameFile(BA bax, final String oldname, final String newname) { + Object sender = new Object(); + if (serverconfirmed) { + BA.runAsync(bax, sender, eventname+"_renamefilecomplete", new Object[] {oldname, newname, false}, new Callable() { + + @Override + public Object[] call() throws Exception { + boolean result = false; + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + + //if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + + try { + + ftp.connect(mServer, mPort); + + if (mPassiveMode) { + ftp.enterLocalPassiveMode(); + ftp.setIpAddressFromPasvResponse(true); + } + + if (ftp.login(mUser, mPassword)) { + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + // perintah rename, true = success, false = failed + result = ftp.rename(oldname, newname); + ftp.logout(); + } else raise_log("RenameFile ["+oldname+"] to ["+newname+"] failed, Unable to login to server"); + ftp.disconnect(); + } catch (IOException e) { + raise_log("RenameFile ["+oldname+"] to ["+newname+"] failed, Exception=" + e.getMessage()); + } + return new Object[] {oldname, newname, result}; + } + + }); + } else { + raise_log("RenameFile failed, Server not confirmed"); + bax.raiseEventFromDifferentThread(sender, null, 0, eventname+"_renamefilecomplete", false, new Object[] {oldname, newname, false}); + } + return sender; + } + + /** + * Upload File to FTP Server + * will raise event uploadcomplete(localfile as string, ftppath as string, success as boolean) + * will raise event uploadprogress(localfile as string, ftppath as string, totalbytestransfered as long, bytestransfered as long, size as long) + * @param localpath local file to upload + * @param ftppath target FTP path + * @return Object to use in Wait For + */ + public Object UploadFile(BA bax,final String localpath, final String ftppath) { + Object sender = new Object(); + if (serverconfirmed) { + + BA.runAsync(bax, sender, eventname + "_uploadcomplete", new Object[] { localpath, ftppath, false }, + new Callable() { + + @Override + public Object[] call() throws Exception { + boolean result = false; + final long targetsize; // untuk progress, sebab event bytesTransferred streamSize return -1 + final boolean need_progress = bax.subExists(eventname + "_uploadprogress"); + File fsource = new File(localpath); + if (fsource.isFile()) { + targetsize = fsource.length(); + if (targetsize>0) { + // buat FTP Client + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + + // untuk logging + if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + + try { + + ftp.connect(mServer, mPort); + + if (mPassiveMode) { + ftp.enterLocalPassiveMode(); + ftp.setIpAddressFromPasvResponse(true); + } + + if (ftp.login(mUser, mPassword)) { + //Harus set ini setelah login. + //Source : https://stackoverflow.com/questions/70395475/apache-commons-ftpclient-not-retrieving-all-bytes-from-source-file + ftp.setFileType(FTPClient.BINARY_FILE_TYPE); + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + // untuk progress + if (need_progress) ftp.setCopyStreamListener(new CopyStreamListener() { + @Override + public void bytesTransferred(CopyStreamEvent event) { + // event ini gak trigger + //raise_log("Upload Progress A : Source ["+event.getSource().toString()+"] "+event.getTotalBytesTransferred()+" / "+event.getBytesTransferred()+" / "+event.getStreamSize()); + //raise_uploadprogress(bax, sender, localpath, ftppath, event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); + } + + @Override + public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, + long streamSize) { + //bytetransfered tergantung buffer size, default di 1024 + raise_log("Upload Progress B : "+totalBytesTransferred+" / "+bytesTransferred+" / "+targetsize); + raise_uploadprogress(bax, sender, localpath, ftppath, totalBytesTransferred, bytesTransferred, targetsize); + } + }); + // kasih keep alive biar koneksi terjaga + ftp.setKeepAlive(true); + // keep alive durasi 10 detik + ftp.setControlKeepAliveTimeout(Duration.ofSeconds(10)); + + //BufferedInputStream fis = new BufferedInputStream(new FileInputStream(fsource)); + FileInputStream fis = new FileInputStream(fsource); + result = ftp.storeFile(ftppath, fis); + fis.close(); + ftp.logout(); + raise_log("UploadFile ["+localpath+"] to ["+ftppath+"] "+(result?"success":"failed")); + } else raise_log("UploadFile ["+localpath+"] failed, Unable to login to server"); + ftp.disconnect(); + } catch (IOException e) { + raise_log("UploadFile ["+localpath+"] to ["+ftppath+"] failed, Exception=" + e.getMessage()); + } + } else raise_log("UploadFile failed, local file ["+localpath+"] size is 0"); + } else raise_log("UploadFile failed, local file ["+localpath+"] not exists"); + + return new Object[] { localpath, ftppath, result }; + } + + }); + + } else { + raise_log("UploadFile failed, Server not confirmed"); + raise_uploadcomplete(sender, localpath, ftppath, false); + } + return sender; + } + + /** + * Get AndroFTPEntry from FTP Server + * Can be used to check if a path is exists, if it is a file or a folder, or to get file size + * Will raise event androftpresult(path as string, success as boolean, entry as AndroFTPEntry) + * If not exists or failed to get, success will be false and entry will be null + * @param path path to get + * @return Object to use in Wait For + */ + public Object GetAndroFTPEntry(BA bax, final String path) { + Object sender = new Object(); + if (serverconfirmed) { + BA.runAsync(bax, sender, eventname+"_androftpresult", new Object[] {path, false, null}, new Callable() { + + @Override + public Object[] call() throws Exception { + AndroFTPEntry result = null; + boolean success = false; + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + + // if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + try { + ftp.connect(mServer, mPort); + if (ftp.login(mUser, mPassword)) { + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + FTPFile[] list = ftp.listFiles(path); + if (list!=null && list.length>0) { + result = new AndroFTPEntry(list[0]); + success = true; + } + + ftp.logout(); + } + ftp.disconnect(); + } catch (IOException e) { + raise_log("GetAndroFTPEntry failed, Exception=" + e.getMessage()); + } + return new Object[] {path, success, result}; + } + + }); + } else { + raise_log("GetAndroFTPEntry failed, Server not confirmed"); + bax.raiseEventFromDifferentThread(sender, null, 0, eventname+"_androftpresult", false, new Object[] {path, false, null}); + } + return sender; + } + + + + + /** + * List Files and Folders in FTP Server + * Will raise event listcomplete(path as string, success as boolean, files() as AndroFTPEntry, folders() as AndroFTPEntry) + * @param path path to list + * @return Object to use in Wait For + */ + public Object ListFiles(BA bax,final String path) { + Object sender = new Object(); + + if (serverconfirmed) { + BA.runAsync(bax, sender, eventname+"_listcomplete", new Object[] {path, false,null, null}, new Callable() { + + + + @Override + public Object[] call() throws Exception { + boolean result = false; + AndroFTPEntry[] files = null; + AndroFTPEntry[] folders = null; + // new FTPClient + FTPClient ftp = CreateFTPClient(mSecured); + ftp.configure(mConfig); + + //if (BA.debugMode) ftp.addProtocolCommandListener(pcl); + try { + + ftp.connect(mServer, mPort); + // harus setelah connect, kalau sebelum connect, akan direset menjadi ASCII_FILE_TYPE + + // harus setelah connect, kalau sebelum connect, akan direset menjadi LOCAL_ACTIVE_MODE + if (mPassiveMode) { + ftp.enterLocalPassiveMode(); + ftp.setIpAddressFromPasvResponse(true); + } + + if (ftp.login(mUser, mPassword)) { + // setelah login, secured connection butuh set PBSZ dan PROT + // Source : https://stackoverflow.com/questions/21453716/android-ftp-file-tranfer-over-explicit-tls/21460934#21460934 + if (mSecured) { + ((FTPSClient)ftp).execPBSZ(0); + ((FTPSClient)ftp).execPROT("P"); + } + //if (BA.debugMode) raise_log("ListFiles FTP Login Success, charset="+ftp.getCharsetName()+", control encoding="+ftp.getControlEncoding()); + + if (mycodes.valid_string(path)) { + if (ftp.changeWorkingDirectory(path)) { + //if (BA.debugMode) raise_log("ListFiles ChangeWorkingDirectory to ["+path+"] Success"); + } else { + //if (BA.debugMode) raise_log("ListFiles ChangeWorkingDirectory to ["+path+"] Failed"); + } + } + + //if (BA.debugMode) raise_log("PWD : "+ftp.printWorkingDirectory()); + + + FTPFile[] _files = ftp.listFiles(); + //if (BA.debugMode) raise_log("ListFiles ["+path+"] result: "+_files.length+" items"); + + + //if (BA.debugMode) Stream.of(_files).forEach(ff-> BA.Log(ff.getRawListing())); + + files = Stream.of(_files) + .filter(ff-> ff.isValid()) + .filter(ff-> ff.isFile()) + .map(ff-> new AndroFTPEntry(ff)) + .toArray(AndroFTPEntry[]::new); + folders = Stream.of(_files) + .filter(ff-> ff.isValid()) + .filter(ff -> ff.isDirectory()) + .map(ff-> new AndroFTPEntry(ff)) + .toArray(AndroFTPEntry[]::new); + + ftp.logout(); + ftp.disconnect(); + + //if (BA.debugMode) raise_log("FTP Logout and Disconnect"); + result = true; + } else raise_log("ListFiles failed, Unable to login to server"); + } catch (IOException e) { + raise_log("ListFiles failed, Exception=" + e.getMessage()); + } + return new Object[] {path, result, files, folders}; + } + + }); + + } else { + raise_log("ListFiles failed, Server not confirmed"); + raise_listcomplete(sender, path, false, null, null); + } + return sender; + } + + // for FTP Debugging + private ProtocolCommandListener pcl = new ProtocolCommandListener() { + @Override + public void protocolCommandSent(ProtocolCommandEvent event) { + if (BA.debugMode) + BA.Log("FTP Command Sent: " + event.getMessage()); + } + + @Override + public void protocolReplyReceived(ProtocolCommandEvent event) { + if (BA.debugMode) + BA.Log("FTP Reply Code: " + event.getReplyCode() + ", Message: " + event.getMessage()); + } + }; + + + + + + + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_log", false, new Object[] {msg}); + } + + private void raise_listcomplete(Object sender, String path, boolean success, AndroFTPEntry[] files, AndroFTPEntry[] folders) { + if (need_listcomplete_event) ba.raiseEventFromDifferentThread(sender, null, 0, eventname+"_listcomplete", false, new Object[] {path, success, files, folders}); + } + + + + private void raise_makedirectorycomplete(Object sender, String path, boolean success) { + if (need_makedirectorycomplete_event) + ba.raiseEventFromDifferentThread(sender, null, 0, eventname + "_makedirectorycomplete", false, + new Object[] { path, success }); + } + + private void raise_deletefilecomplete(Object sender, String path, boolean success) { + if (need_deletefilecomplete_event) + ba.raiseEventFromDifferentThread(sender, null, 0, eventname + "_deletefilecomplete", false, + new Object[] { path, success }); + } + + private void raise_removedirectorycomplete(Object sender, String path, boolean success) { + if (need_removedirectorycomplete_event) + ba.raiseEventFromDifferentThread(sender, null, 0, eventname + "_removedirectorycomplete", false, + new Object[] { path, success }); + } + + private void raise_uploadcomplete(Object sender, String localfile, String ftppath, boolean success) { + if (need_uploadcomplete_event) + ba.raiseEventFromDifferentThread(sender, null, 0, eventname + "_uploadcomplete", false, + new Object[] { localfile, ftppath, success }); + } + + private void raise_downloadcomplete(Object sender, String ftppath, String localfile, boolean success) { + if (need_downloadcomplete_event) + ba.raiseEventFromDifferentThread(sender, null, 0, eventname + "_downloadcomplete", false, + new Object[] { ftppath, localfile, success }); + } + + private void raise_downloadprogress(BA bax, Object sender, String ftppath, String localfile, long totalbytestransfered, + long bytestransfered, long size, double speedKBps) { + if (bax!=null) { + bax.raiseEventFromDifferentThread(sender, null, 0, eventname + "_downloadprogress", false, + new Object[] { ftppath, localfile, totalbytestransfered, bytestransfered, size, speedKBps }); + } + + + } + + private void raise_uploadprogress(BA bax, Object sender, String localfile, String ftppath, + long totalbytestransfered, long bytestransfered, long size) { + if (bax!=null) { + bax.raiseEventFromDifferentThread(sender, null, 0, eventname + "_uploadprogress", false, + new Object[] { localfile, ftppath, totalbytestransfered, bytestransfered, size }); + } + + } + + private void raise_appendprogress(BA bax, Object sender, String localfile, String ftppath, long totalbytestransfered, long bytestransfered, long size) { + if (bax!=null) { + bax.raiseEventFromDifferentThread(sender, null, 0, eventname + "_appendfileprogress", false, + new Object[] { localfile, ftppath, totalbytestransfered, bytestransfered, size }); + } + } + +} diff --git a/src/androgpio/customsocket/AndroFTPEntry.java b/src/androgpio/customsocket/AndroFTPEntry.java new file mode 100644 index 0000000..25bd0f6 --- /dev/null +++ b/src/androgpio/customsocket/AndroFTPEntry.java @@ -0,0 +1,109 @@ +package androgpio.customsocket; +import org.apache.commons.net.ftp.FTPFile; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + +@BA.ShortName("AndroFTPEntry") +public class AndroFTPEntry { + final String name; + final String user; + final String group; + final long size; + final long timestamp; + final boolean isFile; + final boolean isDirectory; + public AndroFTPEntry() { + name = ""; + user = ""; + group = ""; + size = 0; + timestamp = 0; + isFile = false; + isDirectory = false; + } + + public AndroFTPEntry(FTPFile f) { + name = f.getName(); + user = f.getUser(); + group = f.getGroup(); + size = f.getSize(); + //f.getType() // 0 means file, 1 means directory, 2 means symbolic link file, and 3 means unknown + timestamp = f.getTimestamp().getTimeInMillis(); + isFile = f.isFile(); + isDirectory = f.isDirectory(); + } + + /** + * Check if valid AndroFTPEntry + * Name, user, and group must have value + * if isFile=true, then size and timestamp must have value + * @return true if valid + */ + public boolean isValid() { + if (mycodes.valid_string(name)) { + if (mycodes.valid_string(user)) { + if (mycodes.valid_string(group)) { + if (isFile) { + if (size > 0) { + if (timestamp > 0) { + return true; + } + } + } else if (isDirectory) { + return true; + } + + } + } + } + return false; + } + + public String getName() { + return name; + } + + public String getUser() { + return user; + } + + public String getGroup() { + return group; + } + + public long getSize() { + return size; + } + + public long getTimestamp() { + return timestamp; + } + + public String getTimestamp_DDMMYYYY_HHMMSS() { + return mycodes.Tick_To_DDMMYYYY_HHMMSS(timestamp); + } + + public String getTimestamp_DDMMYYYY() { + return mycodes.Tick_To_DDMMYYYY(timestamp); + } + + public String getTimestamp_HHMMSS() { + return mycodes.Tick_To_HHMMSS(timestamp); + } + + public boolean getIsFile() { + return isFile; + } + + public boolean getIsDirectory() { + return isDirectory; + } + + /** + * Print to String + */ + public String toString() { + return "Name: " + name + ", User: " + user + ", Group: " + group + ", Size: " + size + ", Timestamp: " + timestamp + ", isFile: " + isFile + ", isDirectory: " + isDirectory; + } +} diff --git a/src/androgpio/customsocket/JsonArray.java b/src/androgpio/customsocket/JsonArray.java new file mode 100644 index 0000000..816d465 --- /dev/null +++ b/src/androgpio/customsocket/JsonArray.java @@ -0,0 +1,806 @@ +package androgpio.customsocket; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.List; +import anywheresoftware.b4a.objects.streams.File; +@BA.ShortName("JsonArray") +public class JsonArray { + + private JSONArray ja; + + public JsonArray() { + ja = null; + } + + @BA.Hide + public JsonArray(JSONArray value) { + ja = value; + } + + @BA.Hide + /** + * Get Native JSONArray Object + * @return JSONArray object + */ + public JSONArray getObject() { + return ja; + } + + @BA.Hide + /** + * Set Native JSONArray object + * @param value : source value + */ + public void setObject(JSONArray value) { + ja = value; + } + + /** + * Initialize empty JsonArray + */ + public void InitializeEmpty() { + ja = new JSONArray(); + + } + + /** + * Initialize JsonArray from String + * @param value : string with format of Json Array + * @return true if can be initialized + */ + public boolean InitializeFromString(String value) { + if (mycodes.valid_string(value)) { + try { + + ja = new JSONArray(value); + + return true; + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to InitializeFromString, Msg : "+e.getMessage()); + } + } + return false; + } + + /*** + * Initialize JsonArray from a file containing plain string + * Reading result from file, will be used in InitializeFromString function + * @param directory : directory path + * @param filename : filename + * @return true if value can be translated to JsonArray + */ + public boolean InitializeFromFile(String directory, String filename) { + try { + if (File.Exists(directory, filename)) { + String value = File.ReadString(directory, filename); + return InitializeFromString(value); + } + } catch (IOException e) { + if (BA.debugMode) BA.Log("Unable to InitializeFromFile, Msg : "+e.getMessage()); + } + return false; + } + + /** + * Initialize JsonArray from B4X List + * @param value : B4X List + * @return true if value can be translated to JsonArray + */ + public boolean InitializeFromList(List value) { + if (value instanceof List) { + if (value.IsInitialized()) { + if (value.getSize()>0) { + ja = new JSONArray(value.getObject()); + return true; + + } + } + } + return false; + } + + /** + * Initialize JsonArray from array of Object + * @param value : array of object, which can be anything from String, int, long, etc. + * @return true if value can be translated to JsonArray + */ + public boolean InitializeFromObject(Object... value) { + if (value != null) { + if (value.length>0) { + try { + ja = new JSONArray(value); + return true; + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to InitializeFromObject, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Check if JsonArray is initialized + * @return true if initialized + */ + public boolean IsInitialized() { + if (ja != null) { + if (ja instanceof JSONArray) { + return true; + } + } + return false; + } + + /** + * Clear content of JsonArray + */ + public void Clear() { + if (IsInitialized()) { + Object oo; + do { + oo = ja.remove(0); + } while(oo != null); + } + } + + /** + * Check if JsonArray has some value + * @return true if has value + */ + public boolean HasSomeValue() { + if (IsInitialized()) { + + return ja.length()>0; + } + return false; + } + + /** + * Get Size of JsonArray + * @return 0 if empty or not initialized + */ + public int GetSize() { + if (HasSomeValue()) { + return ja.length(); + } + return 0; + } + + /** + * Get JsonArray in string format + * @return String value + */ + public String toString() { + if (ja!=null) { + return mycodes.repair_json(ja.toString()); + } + + return ""; + } + + /** + * Get JsonArray in byte array format + * @return byte array + */ + public byte[] toBytes() { + return toString().getBytes(); + } + + /** + * Get JsonArray in pretty string format + * @param indentvalue how many space to indent string + * @return String value + */ + public String ToStringWithIndent(int indentvalue) { + if (ja!=null) { + + try { + return mycodes.repair_json(ja.toString(indentvalue)) ; + } catch (JSONException e) { + if (BA.debugMode) BA.Log("Exception on ToStringWithIndent, Msg : "+e.getMessage() ); + } + } + return ""; + } + + /** + * Get JsonArray in pretty byte array format + * @param indentvalue how many space to indent string + * @return byte array + */ + public byte[] ToBytesWithIndent(int indentvalue) { + return ToStringWithIndent(indentvalue).getBytes(); + } + + /** + * Write JsonArray to a File + * @param directory : directory path + * @param filename : filename + * @return true if can be written + */ + public boolean WriteToFile(String directory, String filename) { + if (HasSomeValue()) { + try { + Writer ww = new FileWriter(File.Combine(directory, filename)); + ww.write(toString()); + ww.close(); + return true; + } catch (IOException e) { + if (BA.debugMode) BA.Log("Unable to WriteToFile, Msg : "+e.getMessage()); + } + + } + return false; + } + + /** + * Write JsonArray to a file with indentation for pretty string + * @param directory : directory path + * @param filename : filename + * @param indentvalue : how many spaces added for indentation + * @return true if can be written + */ + public boolean WriteToFileWithIndent(String directory, String filename, int indentvalue) { + if (HasSomeValue()) { + try { + Writer ww = new FileWriter(File.Combine(directory, filename)); + ww.write(ToStringWithIndent(indentvalue)); + ww.close(); + return true; + }catch (IOException e) { + if (BA.debugMode) BA.Log("Unable to WriteToFileWithIndent, Msg : "+e.getMessage()); + } + } + return false; + } + + /** + * Get an Object at index + * @param index : zero-based index + * @return null if index not found + */ + public Object Get(int index) { + if (HasSomeValue()) { + if (index>=0 && index=0 && index=0 && index=0 && index=0 && index=0 && index=0 && index=0 && index=0 && index=0 && index0) { + ja.put(value.getObject()); + return true; + } + } + } + } + return false; + } + + @SuppressWarnings("unchecked") + /** + * Get a B4X List at index + * @param index : zero-based index + * @param defaultvalue : if index not found, will return this value + * @return List value + */ + public List GetList(int index, List defaultvalue) { + if (HasSomeValue()) { + if (index>=0 && index) { + java.util.List mm = (java.util.List) xx; + mm.forEach((vv)->{ + result.Add(vv); + }); + } else { + result.Add(xx); + } + + } catch(JSONException e) { + BA.Log("Unable to GetList, Msg : "+e.getMessage()); + } + + } + } + return defaultvalue; + } + + /** + * Convert to B4X List + * @return List + */ + public List toList() { + List result = new List(); + result.Initialize(); + if (HasSomeValue()) { + for (int ii = 0; ii < ja.length(); ii++) { + try { + Object xx = ja.get(ii); + result.Add(xx); + } catch (JSONException e) { + if (BA.debugMode) BA.Log("Failed in toList, Msg : " + e.getMessage()); + } + } + } + return result; + + } + + /** + * Append a boolean value to current JsonArray + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_Boolean(boolean value) { + PutBoolean(value); + return this; + } + + /** + * Append a double value to current JsonArray + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_Double(double value) { + PutDouble(value); + return this; + } + + /** + * Append an int value to current JsonArray + * + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_Int(int value) { + PutInt(value); + return this; + } + + /** + * Append a long value to current JsonArray + * + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_Long(long value) { + PutLong(value); + return this; + } + + /** + * Append a String value to current JsonArray + * + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_String(String value) { + PutString(value); + return this; + } + + /** + * Append a JsonObject value to current JsonArray + * + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_JsonObject(JsonObject value) { + PutJsonObject(value); + return this; + } + + /** + * Append a JsonArray value to current JsonArray + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_JsonArray(JsonArray value) { + PutJsonArray(value); + return this; + } + + /** + * Append a List value to current JsonArray + * + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_List(List value) { + PutList(value); + return this; + } + + /** + * Append a String array to current JsonArray + * + * @param value value to append + * @return current JsonArray + */ + public JsonArray Add_ArrayString(String[] value) { + FromArrayString(value); + return this; + } + + +} diff --git a/src/androgpio/customsocket/JsonObject.java b/src/androgpio/customsocket/JsonObject.java new file mode 100644 index 0000000..3d1353d --- /dev/null +++ b/src/androgpio/customsocket/JsonObject.java @@ -0,0 +1,858 @@ +package androgpio.customsocket; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.List; +import anywheresoftware.b4a.objects.collections.Map; +import anywheresoftware.b4a.objects.collections.Map.MyMap; +import anywheresoftware.b4a.objects.streams.File; + +@BA.ShortName("JsonObject") +/** + * JsonObject is almost like a Map + * it contain key and value pair + * used mainly in SocketIO javascript + * + * Source : https://github.com/stleary/JSON-java + * @author rdkartono + * + */ +public class JsonObject { + private JSONObject jo; + + public JsonObject() { + jo = null; + + } + + @BA.Hide + public JsonObject(JSONObject value) { + jo = value; + } + + + @BA.Hide + /** + * Get Native JSONObject object from org.json java + * @return JSONObject + */ + public JSONObject getObject() { + return jo; + } + + @BA.Hide + /** + * Set Native JSONObject + * @param vv : other value + */ + public void setObject(JSONObject vv) { + jo = vv; + } + + /** + * Initialize empty JsonObject + */ + public void InitializeEmpty() { + jo = new JSONObject(); + } + + /** + * Initialize JsonObject from B4X Map + * @param value : B4X Map + * @return true if value can be translated to JsonObject + */ + public boolean InitializeFromMap(Map value) { + if (value instanceof Map) { + if (value.IsInitialized()) { + if (value.getSize()>0) { + jo = new JSONObject(value.getObject()); + return (jo.length()>0); // kalau empty, return false. kalau gak empty return true + } + } + } + return false; + } + + /** + * Initialize JsonObject from JSON String + * @param value : String + * @return true if value can be translated to JsonObject + */ + public boolean InitializeFromString(String value) { + if (mycodes.valid_string(value)) { + try { + jo = new JSONObject(value); + return (jo.length()>0); + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to Initialize JsonObject from String, Msg : "+e.getMessage()); + } + } + + return false; + } + + /** + * Initialize JsonObject from a file containing plain string + * Reading result from file, will be used in InitializeFromString function + * @param directory : directory path + * @param filename : filename + * @return true if value can be translated to JsonObject + */ + public boolean InitializeFromFile(String directory, String filename) { + try { + if (File.Exists(directory, filename)) { + String value = File.ReadString(directory, filename); + return InitializeFromString(value); + } + } catch (IOException e) { + if (BA.debugMode) BA.Log("InitializeFromFile failed, Msg : "+e.getMessage()); + } + return false; + } + + /*** + * Initialize JsonObject from another JsonObject . + * @param othervalue : other JsonObject as copy source + * @param keys : array of string, containing keys to copy from other JsonObject + * @return true if othervalue can be copied + */ + public boolean InitializeFromOtherJsonObject(JsonObject othervalue, String... keys) { + if (keys!=null) { + if (keys.length>0) { + if (othervalue instanceof JsonObject) { + if (othervalue.HasSomeValue()) { + try { + jo = new JSONObject(othervalue.getObject(), keys); + return true; + } catch (JSONException e) { + if (BA.debugMode) BA.Log("InitializeFromOtherJsonObject failed, msg: "+e.getMessage()); + } + + } + } + } + } + return false; + } + + /** + * Check if JsonObject is initialized + * @return true if inited + */ + public boolean IsInitialized() { + if (jo instanceof JSONObject) { + return true; + } + return false; + } + + /** + * Check if JsonObject has been initialized and has some value + * @return true if has value + */ + public boolean HasSomeValue() { + if (jo instanceof JSONObject) { + return (jo.length()>0); + } + return false; + } + + /** + * Get List of Keys in this JsonObject + * @return B4X List + */ + public List GetListofKeys() { + List result = new List(); + result.Initialize(); + if (HasSomeValue()) { + Iterator iter = jo.keys(); + iter.forEachRemaining(value->{ + if (value instanceof String) { + if (!value.isEmpty()) { + result.Add(value); + } + } + + }); + } + return result; + } + + /** + * Check if this JsonObject contain spesific key + * @param value : String key to search + * @return true if it has + */ + public boolean ContainKey(String value) { + if (mycodes.valid_string(value)) { + if (HasSomeValue()) { + return jo.has(value); + } + } + return false; + } + + /** + * Get JsonObject size + * @return -1 if not initialized yet, 0 if empty, positive numbers if has value + */ + public int Size() { + if (jo instanceof JSONObject) { + return jo.length(); + } + return -1; + } + + /** + * Clear JsonObject contents and keys + * @return true if can be cleared + */ + public boolean Clear() { + if (IsInitialized()) { + while(jo.length()>0) { + jo.remove(jo.keys().next()); + } + return true; + } + return false; + } + + /** + * Remove a content with specific key + * @param key : String key to remove + * @return Object removed from JsonObject, or null if not available or not initialized + */ + public Object Remove(String key) { + if (IsInitialized()) { + if (key instanceof String) { + if (!key.isEmpty()) { + return jo.remove(key); + } + } + + } + return null; + } + + + + /** + * Get JsonObject key-value in string + * @return empty string if JsonObject not initialized, or is empty + */ + public String toString() { + if (jo!=null) { + return mycodes.repair_json( jo.toString()); + } + return ""; + } + + /** + * Getet JsonObject key-value in byte array + * @return empty byte array if JsonObject not initialized or is empty + */ + public byte[] toBytes() { + return toString().getBytes(); + } + + /** + * Get JsonObject key-value in pretty string + * @param indentvalue : how many spaces for indentation to be added + * @return empty string if JsonObject not initialized or is empty + */ + public String ToStringWithIndent(int indentvalue) { + + if (jo!=null) { + try { + return mycodes.repair_json( jo.toString(indentvalue)); + } catch (JSONException e) { + if (BA.debugMode) BA.Log("ToStringWithIndent failed, msg : "+e.getMessage()); + } + } + return ""; + } + + /** + * Get JsonObject key-value in pretty byte array + * @param indentvalue : how many spaces for indentation to be added + * @return empty byte array if JsonObject not initialized or is empty + */ + public byte[] ToBytesWithIndent(int indentvalue) { + return ToStringWithIndent(indentvalue).getBytes(); + } + + /** + * Write JsonObject to a file , in plain string value + * @param directory : directory path + * @param filename : filename + * @return true if write operation succesful + */ + public boolean WriteToFile(String directory, String filename) { + if (HasSomeValue()) { + try { + Writer ww = new FileWriter(File.Combine(directory, filename)); + ww.write(toString()); + ww.close(); + return true; + } catch(IOException e) { + if (BA.debugMode) BA.Log("WriteToFile failed, Msg : "+e.getMessage()); + } + + } + return false; + } + + /** + * Write JsonObject to a file, in plain string value + * @param directory : directory path + * @param filename : filename + * @param indentvalue : how many spaces for indentation to be added + * @return true if write operation succesful + */ + public boolean WriteToFileWithIndent(String directory, String filename, int indentvalue) { + if (HasSomeValue()) { + + try { + Writer ww = new FileWriter(File.Combine(directory, filename)); + ww.write(ToStringWithIndent(indentvalue)); + ww.close(); + return true; + } catch (IOException e) { + if (BA.debugMode) BA.Log("WriteToFileWithIndent failed, Msg : "+e.getMessage()); + } + } + return false; + } + + /** + * Put a boolean value in JsonObject with specific key + * @param key : String key + * @param value : boolean value + * @return true if can be added + */ + public boolean PutBoolean(String key, boolean value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + try { + jo.put(key, value); + return true; + } catch(JSONException | NullPointerException e) { + if (BA.debugMode) BA.Log("Unable to PutBoolean, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Put a double value in JsonObject with specific key + * @param key : String key + * @param value : double value + * @return true if can be added + */ + public boolean PutDouble(String key, double value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + try { + jo.put(key, value); + return true; + } catch(JSONException | NullPointerException e) { + if (BA.debugMode) BA.Log("Unable to PutDouble, Msg : "+e.getMessage()); + } + } + } + return false; + } + + + + /** + * Put an Int value in JsonObject with specific key + * @param key : String key + * @param value : Int value + * @return true if can be added + */ + public boolean PutInt(String key, int value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + try { + jo.put(key, value); + return true; + } catch(JSONException | NullPointerException e) { + if (BA.debugMode) BA.Log("Unable to PutInt, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Put a long value in JsonObject with specific key + * @param key : String key + * @param value : long value + * @return true if can be added + */ + public boolean PutLong(String key, long value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + try { + jo.put(key, value); + return true; + } catch(JSONException | NullPointerException e) { + if (BA.debugMode) BA.Log("Unable to PutLong, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Put an Object value in JsonObject with specific key + * @param key : String key + * @param value : Object value + * @return true if can be added + */ + public boolean PutObject(String key, Object value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + try { + jo.put(key, value); + return true; + } catch(JSONException | NullPointerException e) { + if (BA.debugMode) BA.Log("Unable to PutObject, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Put a String value in JsonObject with specific key + * @param key : String key + * @param value : String value + * @return true if can be added + */ + public boolean PutString(String key, String value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + try { + jo.put(key, value); + return true; + } catch(JSONException | NullPointerException e) { + if (BA.debugMode) BA.Log("Unable to PutString, Msg : "+e.getMessage()); + } + } + } + return false; + } + + /** + * Put a B4X Map value to JsonObject with specific kety + * @param key : String key + * @param value : B4X Map value + * @return true if can be added + */ + public boolean PutMap(String key, Map value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + if (value!=null && value.IsInitialized()) { + MyMap obj = (MyMap) value.getObject(); + if (obj instanceof MyMap) { + try { + + jo.put(key, obj); + return true; + } catch(JSONException | NullPointerException e) { + if (BA.debugMode) BA.Log("Unable to PutMap, Msg : "+e.getMessage()); + } + } + } + } + } + return false; + } + + /** + * Put a B4X List value to JsonObject with specific key + * @param key : String key + * @param value : B4X List value + * @return true if can be added + */ + public boolean PutList(String key, List value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + if (value != null && value.IsInitialized()) { + try { + jo.put(key, value.getObject()); + return true; + } catch (JSONException | NullPointerException e) { + if (BA.debugMode) + BA.Log("Unable to PutList, Msg : " + e.getMessage()); + } + } + } + } + return false; + } + + /** + * Put other JsonObject to this JsonObject with specific key + * @param key : String key + * @param value : other JsonObject + * @return true if can be added + */ + public boolean PutJsonObject(String key, JsonObject value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + if (value != null && value.IsInitialized()) { + try { + jo.put(key, value.getObject()); + return true; + }catch(JSONException | NullPointerException e) { + if (BA.debugMode) BA.Log("Unable to PutJsonObject, Msg : "+e.getMessage()); + } + } + } + } + return false; + } + + /** + * Put a JsonArray value to JsonObject with specific key + * @param key : String key + * @param value : JsonArray value + * @return true if can be added + */ + public boolean PutJsonArray(String key, JsonArray value) { + if (IsInitialized()) { + if (mycodes.valid_string(key)) { + if (value!=null && value.IsInitialized()) { + try { + jo.put(key, value.getObject()); + return true; + } catch (JSONException e) { + if (BA.debugMode) BA.Log("Unable to PutJsonArray , Msg : "+e.getMessage()); + } + } + } + } + return false; + } + + /** + * Check if this JsonObject is similar key-value with other JsonObject + * @param othervalue : other JsonObject to compare + * @return true if similar + */ + @SuppressWarnings("unlikely-arg-type") + public boolean IsSimilar(JsonObject othervalue) { + if (IsInitialized()) { + jo.equals(othervalue); + + } + return false; + } + + /** + * Get a String value matched with the key + * @param key : key linked to value + * @param defaultvalue : if specific key not found, return this value + * @return String value + */ + public String GetStringValue(String key, String defaultvalue) { + if (ContainKey(key)) { + try { + String value = jo.getString(key) ; + return value; + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to GetStringValue, Msg : "+e.getMessage()); + } + + } + return defaultvalue; + } + + /** + * Get a Long value matched with the key + * @param key : key linked to value + * @param defaultvalue : if specific key not found, return this value + * @return long value + */ + public long GetLongValue(String key, long defaultvalue) { + if (ContainKey(key)) { + try { + long value = jo.getLong(key); + return value; + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to GetLongValue, Msg : "+e.getMessage()); + } + + } + return defaultvalue; + } + + public JsonArray GetJsonArray(String key, JsonArray defaultvalue) { + if (ContainKey(key)) { + try { + Object xx = jo.get(key); + if (xx instanceof JsonArray) { + return (JsonArray) xx; + } else if (xx instanceof JSONArray) { + return new JsonArray((JSONArray) xx); + } else { + if (BA.debugMode) + BA.Log("GetJsonArray key : " + key + " is not a JsonArray"); + } + + } catch (JSONException e) { + if (BA.debugMode) + BA.Log("Unable to GetJsonArray, Msg : " + e.getMessage()); + } + + } + return defaultvalue; + } + + /** + * Get a JsonObject value matched with the key + * @param key : key linked to value + * @param defaultvalue : if specific key not found, return this value + * @return JsonObject value + */ + public JsonObject GetJsonObject(String key, JsonObject defaultvalue) { + if (ContainKey(key)) { + try { + Object xx = jo.get(key); + if (xx instanceof JsonObject) { + return (JsonObject) xx; + } else if (xx instanceof JSONObject) { + return new JsonObject((JSONObject) xx); + } else { + if (BA.debugMode) BA.Log("GetJsonObject key : "+key+" is not a JsonObject"); + } + + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to GetJsonObject, Msg : "+e.getMessage()); + } + + } + return defaultvalue; + } + + /** + * Get an int value matched with the key + * @param key : key linked to value + * @param defaultvalue : if specific key not found, return this value + * @return int value + */ + public int GetInt(String key, int defaultvalue) { + if (ContainKey(key)) { + try { + int value = jo.getInt(key); + return value; + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to GetInt, Msg : "+e.getMessage()); + } + } + return defaultvalue; + } + + + + /** + * Get an double value matched with the key + * @param key : key linked to value + * @param defaultvalue : if specific key not found, return this value + * @return double value + */ + public double GetDouble(String key, double defaultvalue) { + if (ContainKey(key)) { + try { + double value = jo.getDouble(key); + return value; + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to GetDouble, Msg : "+e.getMessage()); + } + } + return defaultvalue; + } + + /** + * Get an boolean value matched with the key + * @param key : key linked to value + * @param defaultvalue : if specific key not found, return this value + * @return boolean value + */ + public boolean GetBoolean(String key, boolean defaultvalue) { + if (ContainKey(key)) { + try { + boolean value = jo.getBoolean(key); + return value; + } catch(JSONException e) { + if (BA.debugMode) BA.Log("Unable to GetBoolean, Msg : "+e.getMessage()); + } + } + return defaultvalue; + } + + /** + * Get an Object value matched with the key + * @param key : key linked to value + * @param defaultvalue : if specific key not found, return this value + * @return Object value + */ + public Object GetObject(String key, Object defaultvalue) { + if (ContainKey(key)) { + try { + Object value = jo.get(key); + return value; + } catch(JSONException e) { + BA.Log("Unable to GetObject, Msg : "+e.getMessage()); + } + } + return defaultvalue; + } + + public Map toMap() { + Map m = new Map(); + m.Initialize(); + if (HasSomeValue()) { + + Iterator iter = jo.keys(); + iter.forEachRemaining(key->{ + if (key instanceof String) { + if (!key.isEmpty()) { + try { + m.Put(key, jo.get(key)); + } catch (JSONException e) { + if (BA.debugMode) BA.Log("toMap failed, Msg : "+e.getMessage()); + } + } + } + + }); + } + return m; + } + + /** + * Add a boolean value to JsonObject with specific key + * @param key : String key + * @param value : boolean value to add + * @return current JsonObject + */ + public JsonObject Add_Boolean(String key, boolean value) { + PutBoolean(key, value); + return this; + } + + /** + * Add a double value to JsonObject with specific key + * @param key : String key + * @param value : double value to add + * @return current JsonObject + */ + public JsonObject Add_Double(String key, double value) { + PutDouble(key, value); + return this; + } + + /** + * Add an int value to JsonObject with specific key + * @param key : String key + * @param value : int value to add + * @return current JsonObject + */ + public JsonObject Add_Int(String key, int value) { + PutInt(key, value); + return this; + } + + /** + * Add a long value to JsonObject with specific key + * @param key : String key + * @param value : long value to add + * @return current JsonObject + */ + public JsonObject Add_Long(String key, long value) { + PutLong(key, value); + return this; + } + + /** + * Add an Object value to JsonObject with specific key + * @param key : String key + * @param value : Object value to add + * @return current JsonObject + */ + public JsonObject Add_Object(String key, Object value) { + PutObject(key, value); + return this; + } + + /** + * Add a String value to JsonObject with specific key + * @param key : String key + * @param value : String value to add + * @return current JsonObject + */ + public JsonObject Add_String(String key, String value) { + PutString(key, value); + return this; + } + + /** + * Add a B4X Map value to JsonObject with specific key + * @param key : String key + * @param value : B4X Map value to add + * @return current JsonObject + */ + public JsonObject Add_Map(String key, Map value) { + PutMap(key, value); + return this; + } + + /** + * Add a B4X List value to JsonObject with specific key + * @param key : String key + * @param value : B4X List value to add + * @return current JsonObject + */ + public JsonObject Add_List(String key, List value) { + PutList(key, value); + return this; + } + + /** + * Add another JsonObject to this JsonObject with specific key + * @param key : String key + * @param value : other JsonObject to add + * @return current JsonObject + */ + public JsonObject Add_JsonObject(String key, JsonObject value) { + PutJsonObject(key, value); + return this; + } + + /** + * Add another JsonArray to this JsonObject with specific key + * @param key : String key + * @param value : JsonArray to add + * @return current JsonObject + */ + public JsonObject Add_JsonArray(String key, JsonArray value) { + PutJsonArray(key, value); + return this; + } + + +} diff --git a/src/androgpio/customsocket/NTPClient.java b/src/androgpio/customsocket/NTPClient.java new file mode 100644 index 0000000..a6f8250 --- /dev/null +++ b/src/androgpio/customsocket/NTPClient.java @@ -0,0 +1,132 @@ +package androgpio.customsocket; +import java.io.IOException; +import java.net.InetAddress; +import java.util.concurrent.Callable; + +import org.apache.commons.net.ntp.NTPUDPClient; +import org.apache.commons.net.ntp.TimeInfo; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + +@BA.ShortName("NTPClient") +@BA.Events(values= { + "log(msg as string)", + "ntpresult(host as string, success as boolean, timeinfo as NTPInfo)" +}) +@BA.DependsOn(values = { "commons-net-3.10.0" }) +@BA.Permissions(values = { "android.permission.INTERNET" }) +public class NTPClient { + + private BA ba; + private String eventname; + private Object Me; + private boolean need_log_event = false; + + private NTPUDPClient client; + + /** + * Initialize NTP Client + * Implement Network Time Protocol (NTP) RFC 1305 and Simple Network Time Protocol (SNTP) RFC-2030 + * @param eventname event name + */ + public void Initialize(BA ba, String eventname) { + this.ba = ba; + this.eventname = eventname; + Me = this; + check_events(); + client = new NTPUDPClient(); + } + + private void check_events() { + if (ba!=null) { + if (mycodes.valid_string(eventname)) { + need_log_event = ba.subExists(eventname+"_log"); + } + } + } + + /** + * Get NTP Protocol Version Number that client sets on request packet that is sent to remote host + * Example 3 = NTPv3, 4 = NTPv4, etc + * @return version number + */ + public int getNTPVersion() { + return client.getVersion(); + } + + /** + * Set NTP Protocol Version Number that client sets on request packet that is sent to remote host + * @param version example 3 = NTPv3, 4 = NTPv4, etc + */ + public void setNTPVersion(int version) { + client.setVersion(version); + } + + /** + * Get time information from specified NTP Server, using default Port + * will raise event ntpresult(host as string, success as boolean, timeinfo as NTPInfo) + * if success is false, timeinfo will be null + * @param host NTP Server Host + * @return Object to use in Wait For + */ + public Object GetTime(BA bax, String host) { + Object sender = new Object(); + BA.runAsync(bax, sender, eventname+"_ntpresult", new Object[] {host, false, null}, new Callable() { + + @Override + public Object[] call() { + boolean success = false; + NTPInfo ntpi = null; + try { + TimeInfo info = client.getTime(InetAddress.getByName(host)); + ntpi = new NTPInfo(info); + success = true; + } catch (IOException e) { + raise_log("GetTime exception: "+e.getMessage()); + } + return new Object[] {host, success, ntpi}; + } + + }); + return sender; + } + + /** + * Get time information from specified NTP Server, using specified Port + * Will raise event ntpresult(host as string, success as boolean, timeinfo as NTPInfo) + * @param host NTP Server Host + * @param port NTP Server Port + * @return Object to use in Wait For + */ + public Object GetTimeWithPort(BA bax, String host, int port) { + Object sender = new Object(); + BA.runAsync(bax, sender, eventname + "_ntpresult", new Object[] { host, false, null }, + new Callable() { + + @Override + public Object[] call() { + boolean success = false; + NTPInfo ntpi = null; + try { + TimeInfo info = client.getTime(InetAddress.getByName(host), port); + ntpi = new NTPInfo(info); + success = true; + } catch (IOException e) { + raise_log("GetTimeWithPort exception: " + e.getMessage()); + } + return new Object[] { host, success, ntpi }; + } + + }); + return sender; + } + + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, eventname+"_log", false, new Object[] {msg}); + } + + + + +} diff --git a/src/androgpio/customsocket/NTPInfo.java b/src/androgpio/customsocket/NTPInfo.java new file mode 100644 index 0000000..2a5d77c --- /dev/null +++ b/src/androgpio/customsocket/NTPInfo.java @@ -0,0 +1,74 @@ +package androgpio.customsocket; +import org.apache.commons.net.ntp.TimeInfo; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("NTPInfo") +public class NTPInfo { + // Source : https://commons.apache.org/proper/commons-net/apidocs/org/apache/commons/net/ntp/TimeInfo.html + private final TimeInfo ti; + + public NTPInfo() { + this.ti = null; + } + + public NTPInfo(TimeInfo ti) { + this.ti = ti; + + if (this.ti != null) this.ti.computeDetails(); + } + + /** + * Check if NTPInfo is valid + * @return true if valid + */ + public boolean isValid() { + if (ti!=null) { + if (ti.getOffset()!=null) { + return true; + } + } + return false; + } + + /** + * Returns time at which time message packet was received by local machine + * So it is local machine time, not remote host time + * @return 0 if not valid + */ + public long getReturnTime() { + if (ti!=null) { + return ti.getReturnTime(); + } + return 0; + } + + /** + * Get clock offset needed to adjust local clock to match remote clock + * Value is in milliseconds, compared with local machine Time in milliseconds + * To correct local machine time, add local machine Time (in milliseconds) with this value, and convert to DateTime + * @return 0 if not valid + */ + public long getOffset() { + if (ti != null) { + // return nya Long Object, bukan primitive long, jadi bisa null + Long x = ti.getOffset(); + if (x != null) return x.longValue(); + } + return 0; + } + + /** + * Get round-trip network delay. + * Value in milliseconds + * @return 0 if not valid + */ + public long getDelay() { + if (ti!=null) { + // return nya Long Object, bukan primitive long, jadi bisa null + Long x = ti.getDelay(); + if (x != null) return x.longValue(); + } + return 0; + } +} diff --git a/src/androgpio/customsocket/SocketIoObject.java b/src/androgpio/customsocket/SocketIoObject.java new file mode 100644 index 0000000..4465596 --- /dev/null +++ b/src/androgpio/customsocket/SocketIoObject.java @@ -0,0 +1,364 @@ +package androgpio.customsocket; + + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.json.JSONArray; +import org.json.JSONObject; + + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + + +@BA.ShortName("SocketIoObject") +public class SocketIoObject { + private String _event; + private Object _value; + private Object _reply; + private int _valuecount; + private int _ackindex; + + private AtomicBoolean isnotified = new AtomicBoolean(false); + + public SocketIoObject() { + _event = ""; + _value = null; + _reply = null; + _valuecount = -1; + _ackindex = -1; + } + + @BA.Hide + public void setEvent(String event) { + _event = event; + } + + public String getEvent() { + return _event; + } + + public void setValue(Object value) { + _value = value; + } + + public Object getValue() { + return _value; + } + + public String ValueToString() { + if (_value!=null) { + if (_value instanceof JsonObject) { + return ((JsonObject) _value).toString(); + } else if (_value instanceof JsonArray) { + return ((JsonArray) _value).toString(); + } else if (_value instanceof JSONObject) { + return ((JSONObject) _value).toString(); + } else if (_value instanceof JSONArray) { + return ((JSONArray) _value).toString(); + } else return String.valueOf(_value); + } + return "null"; + } + + public void setReply(Object value) { + _reply = value; + } + + /** + * if NeedReply is true, this method should be called after setting the reply + * value + */ + public void Notify() { + synchronized(isnotified) { + isnotified.set(true); + isnotified.notify(); + } + } + + @BA.Hide + public void Wait(int value) { + // value = 0 , wait forever, other value, wait for value milliseconds + int xx =0; + synchronized (isnotified) { + isnotified.set(false); + while(!isnotified.get()) { + try { + isnotified.wait(value<1 ? 5000: value); + } catch (InterruptedException e) { + xx+=1; + BA.Log("InterruptException ke "+xx ); + } + } + + + + } + } + + public Object getReply() { + return _reply; + } + + public String ReplyToString() { + if (_reply != null) { + if (_reply instanceof JsonObject) { + return ((JsonObject) _reply).toString(); + } else if (_reply instanceof JsonArray) { + return ((JsonArray) _reply).toString(); + } else if (_reply instanceof JSONObject) { + return ((JSONObject) _reply).toString(); + } else if (_reply instanceof JSONArray) { + return ((JSONArray) _reply).toString(); + } else + return String.valueOf(_reply); + } + return "null"; + } + + + + @BA.Hide + public void setValueCount(int value) { + _valuecount = value; + } + + + public int getValueCount() { + return _valuecount; + } + + public boolean HaveValue() { + return _valuecount > 0; + } + + @BA.Hide + public void setAckIndex(int value) { + _ackindex = value; + } + + @BA.Hide + public int getAckIndex() { + return _ackindex; + } + + /** + * Check if a reply is expected + * @return true if a reply is expected + */ + public boolean NeedReply() { + return _ackindex > -1; + } + + /** + * Get Value Object as JsonObject + * @return null if value not a JsonObject + */ + public JsonObject ValueToJsonObject() { + if (_value!=null) { + if (_value instanceof JsonObject) { + return (JsonObject) _value; + + } else if (_value instanceof JSONObject) { + return new JsonObject((JSONObject) _value); + } + } + return null; + } + + /** + * Get Reply Object as JsonObject + * + * @return null if reply not a JsonObject + */ + public JsonObject ReplyToJsonObject() { + if (_reply != null) { + if (_reply instanceof JsonObject) { + return (JsonObject) _reply; + } else if (_reply instanceof JSONObject) { + return new JsonObject((JSONObject) _reply); + } + } + return null; + } + + /** + * Get Value Object as JsonArray + * + * @return null if value not a JsonArray + */ + public JsonArray ValueToJsonArray() { + if (_value!=null) { + if (_value instanceof JsonArray) { + return (JsonArray) _value; + } else if (_value instanceof JSONArray) { + return new JsonArray((JSONArray) _value); + } + } + return null; + } + + /** + * Get Reply Object as JsonArray + * + * @return null if reply not a JsonArray + */ + public JsonArray ReplyToJsonArray() { + if (_reply != null) { + if (_reply instanceof JsonArray) { + return (JsonArray) _reply; + } else if (_reply instanceof JSONArray) { + return new JsonArray((JSONArray) _reply); + } + } + return null; + } + + /** + * Check if Value is a JsonObject + * @return true if value is a JsonObject + */ + public boolean ValueIsJsonObject() { + + return ValueToJsonObject()!=null; + } + + /** + * Check if Reply is a JsonObject + * @return true if reply is a JsonObject + */ + public boolean ReplyIsJsonObject() { + return ReplyToJsonObject()!=null; + + } + + /** + * Check if Value is a JsonArray + * @return true if value is a JsonArray + */ + public boolean ValueIsJsonArray() { + return ValueToJsonArray()!=null; + } + + /** + * Check if Reply is a JsonArray + * @return true if reply is a JsonArray + */ + public boolean ReplyIsJsonArray() { + return ReplyToJsonArray()!=null; + } + + /** + * Check if Value is a String + * @return true if value is a String + */ + public boolean ValueIsString() { + return mycodes.ObjectisString(_value); + } + + /** + * Check if Value is a Boolean + * + * @return true if value is a Boolean + */ + public boolean ValueIsBoolean() { + return mycodes.ObjectisBoolean(_value); + } + + /** + * Check if Value is a Byte + * + * @return true if value is a Byte + */ + public boolean ValueIsByte() { + return mycodes.ObjectisByte(_value); + } + + /** + * Check if Value is a Integer + * + * @return true if value is a Integer + */ + public boolean ValueIsInteger() { + return mycodes.ObjectisInteger(_value); + } + + /** + * Check if Value is a Double + * + * @return true if value is a Double + */ + public boolean ValueIsDouble() { + return mycodes.ObjectisDouble(_value); + } + + /** + * Check if Value is a Float + * + * @return true if value is a Float + */ + public boolean ValueIsFloat() { + return mycodes.ObjectisFloat(_value); + } + + public boolean ValueIsLong() { + return mycodes.ObjectisLong(_value); + } + + + /** + * Check if Reply is a String + * @return true if reply is a String + */ + public boolean ReplyIsString() { + return mycodes.ObjectisString(_reply); + } + + /** + * Check if Reply is a Boolean + * @return true if reply is a Boolean + */ + public boolean ReplyIsBoolean() { + return mycodes.ObjectisBoolean(_reply); + } + + /** + * Check if Reply is a Byte + * @return true if reply is a Byte + */ + public boolean ReplyIsByte() { + return mycodes.ObjectisByte(_reply); + } + + /** + * Check if Reply is a Integer + * @return true if reply is a Integer + */ + public boolean ReplyIsInteger() { + return mycodes.ObjectisInteger(_reply); + } + + /** + * Check if Reply is a Double + * @return true if reply is a Double + */ + public boolean ReplyIsDouble() { + return mycodes.ObjectisDouble(_reply); + } + + /** + * Check if Reply is a Float + * @return true if reply is a Float + */ + public boolean ReplyIsFloat() { + return mycodes.ObjectisFloat(_reply); + } + + /** + * Check if Reply is a Long + * @return true if reply is a Long + */ + public boolean ReplyIsLong() { + return mycodes.ObjectisLong(_reply); + } +} \ No newline at end of file diff --git a/src/androgpio/customsocket/TCPSocket.java b/src/androgpio/customsocket/TCPSocket.java new file mode 100644 index 0000000..a23957a --- /dev/null +++ b/src/androgpio/customsocket/TCPSocket.java @@ -0,0 +1,773 @@ +package androgpio.customsocket; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.keywords.DateTime; +import anywheresoftware.b4a.objects.collections.Map; +import anywheresoftware.b4a.objects.collections.Map.MyMap; +import anywheresoftware.b4a.randomaccessfile.B4XSerializator; + +@BA.ShortName("TCPSocket") +@BA.Events(values= { + "log(msg as string)", + "connected(targetip as string, targetport as int)", + "disconnected(targetip as string, targetport as int, duration as long)", + "newmapdata(value as Map)", + "newbytesdata(value() as byte)", + "newstringdata(value as String)", + "newintsdata(value() as int)", + +}) + + +public class TCPSocket { + private BA bax; + private Object caller; + private Object Me = this; + private String event; + private TcpSocketJavaEvent _javaevent = null; + + private boolean _inited = false; + private boolean need_log_event = false; + private boolean need_connected_event = false; + private boolean need_disconnected_event = false; + private boolean need_newmapdata_event = false; + private boolean need_newbytesdata_event = false; + private boolean need_newstringdata_event = false; + private boolean need_newintsdata_event = false; + + + private Socket _socket; + private ObjectOutputStream _output; + private ObjectInputStream _input; + private B4XSerializator _converter; + + private String _remoteip = ""; + private int _remoteport = 0; + private String _localip = ""; + private int _localport = 0; + + private long _connected_tick = 0; + + private int _txokcount = 0; + private long _txbytescount = 0; + private int _rxokcount = 0; + private long _rxbytescount = 0; + + private Timer onesecondtimer; + private int _rxtimercount; + private int _txtimercount; + + private String _mapkey = ""; + + public TCPSocket() { + // nothing to do here + _converter = new B4XSerializator(); + + } + + @BA.Hide + public TCPSocket(Socket newsocket, Object event) { + this(); + _javaevent = (TcpSocketJavaEvent) event; + Thread tx = new Thread(new tcpsocketrun(newsocket)); + tx.start(); + } + + /** + * Get / Set Identifier Key + * by default, identifier key is RemoteIPAddress:RemotePort + * if identifier key is empty, then TCPSocket has never been connected yet + * User may change IdentifierKey to any String value , and used it for matching Map + * @return key as string + */ + public String getIdentifierKey() { + return _mapkey; + } + + /** + * Get / Set Identifier Key + * by default, identifier key is RemoteIPAddress:RemotePort + * if identifier key is empty, then TCPSocket has never been connected yet + * User may change IdentifierKey to any String value , and used it for matching Map + * @param value as string + */ + public void setIdentifierKey(String value) { + if (value instanceof String) { + if (!value.isEmpty()) { + if (_javaevent instanceof TcpSocketJavaEvent) { + _javaevent.renameidentifier(_mapkey, value); + } + _mapkey = value; + } + } + } + + /** + * Initialize TCPSocket events + * Necessary to call this, so event log, connected, disconnected will be called + * @param callerobject : caller object + * @param eventname : eventname + */ + public void InitializeEvents(BA ba, final Object callerobject, final String eventname) { + + bax = ba; + caller = callerobject; + event = eventname; + if (bax instanceof BA) { + if (caller != null) { + if (event instanceof String) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(event+"_log"); + need_connected_event = bax.subExists(event+"_connected"); + need_disconnected_event = bax.subExists(event+"_disconnected"); + need_newmapdata_event = bax.subExists(event+"_newmapdata"); + need_newbytesdata_event = bax.subExists(event+"_newbytesdata"); + need_newstringdata_event = bax.subExists(event+"_newstringdata"); + need_newintsdata_event = bax.subExists(event+"_newintsdata"); + + _inited = true; + } + } + } + } + + } + + /** + * Check if Events already Initialized for this TCPSocket + * @return true if already initialized + */ + public boolean EventsInitialized() { + return _inited; + } + + /** + * Check if TCPSocket is connected + * @return true if connected + */ + public boolean IsConnected() { + if (_socket instanceof Socket) { + if (!_socket.isClosed()) { + return _socket.isConnected(); + } + } + return false; + } + + /** + * Connect to Target Ip and Target Port + * Will raise Connected(remoteip as string, remoteport as int) event + * if TCPSocket connected, remoteip has ip address, and remoteport != 0; + * @param targetip : target ip + * @param port : target port + */ + public void ConnectTo(String targetip, int port) { + if (targetip instanceof String) { + if (!targetip.isEmpty()) { + if (port>0) { + try { + Socket trysock = new Socket(targetip, port); + Thread tx = new Thread(new tcpsocketrun(trysock)); + tx.start(); + return; + } catch (UnknownHostException e) { + raise_log_error("UnknownHostException on ConnectTo ["+targetip+":"+port+"]",e); + + } catch (IOException e) { + raise_log_error("IOException on ConnectTo ["+targetip+"]:["+port+"]",e); + } + } + } + } + raise_connected("",0); + } + + private class tcpsocketrun implements Runnable{ + private boolean validrun = false; + private ByteBuffer bytebuf ; + public tcpsocketrun(Socket xx) { + // reset + _remoteip = ""; + _remoteport = 0; + _localip = ""; + _localport = 0; + _connected_tick = 0; + _rxbytescount = 0; + _rxokcount = 0; + _txbytescount = 0; + _txokcount = 0; + _txtimercount = 0; + _rxtimercount = 0; + _mapkey = ""; + if (onesecondtimer instanceof Timer) { + onesecondtimer.cancel(); + } + onesecondtimer = null; + + try { + if (_socket instanceof Socket) Disconnect(); + _socket = xx; + ObjectOutputStream _out = new ObjectOutputStream(_socket.getOutputStream()); + ObjectInputStream _in = new ObjectInputStream(_socket.getInputStream()); + bytebuf = ByteBuffer.allocate(1024); + + _output = _out; + _input = _in; + + _remoteip = _socket.getInetAddress() == null ? "" : _socket.getInetAddress().getHostAddress(); + _localip = _socket.getLocalAddress() == null ? "" : _socket.getLocalAddress().getHostAddress(); + _remoteport = _socket.isClosed() ? 0 : _socket.getPort(); + _localport = _socket.getLocalPort(); + _mapkey = _remoteip+":"+String.valueOf(_remoteport); + validrun = true; + + } catch(IOException e) { + raise_log_error("IOException on tcpsocketrun",e); + } + + if (validrun) { + /// shutdownhook taruh di sini, buat auto close ketika socket connected + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + BA.Log("ShutdownHook on TCPSocket identifier="+_mapkey); + Disconnect(); + } + }); + } + + } + + public void run() { + if (validrun) { + raise_log("tcpsocketrun running"); + raise_connected(_socket.getInetAddress().getHostAddress(), _socket.getPort()); + _connected_tick = DateTime.getNow(); + // create new timer + onesecondtimer = new Timer(); + onesecondtimer.scheduleAtFixedRate(checktxrxtimercounter, 1000, 1000); + + while(true) { + // selama _input masih ObjectInputStream, coba looping lagi + // Kalau Disconnect dipanggil, _input akan jadi null + // maka while ini jadi break + + if (_input instanceof ObjectInputStream) { + int readcount = 0; + while(true) { + // coba ambil data di sini , loop terus sampe break + // kondisi terus, kalau readcount = buf.length, artinya bisa ambil data banyak + // mungkin masih ada data lagi setelahnya + // + // kondisi break, kalau readcount < buf.length, artinya buffernya aja gak sampe penuh + // berarti datanya cuma dikit + + byte[] buf = new byte[1024]; + + try { + readcount = _input.read(buf); // ngeblok di sini + } catch (IOException e) { + raise_log_error("IOException from input.read", e); + + // kalau gak online, connection closed + String msg = e.getMessage(); + + // Network disconnect + if (msg.equalsIgnoreCase("connection reset")) { + Disconnect(); // Disconnect di sini nutup _input, _output, dan _socket + break; // keluar dari while(true) dalam + } + break; + } + + if (readcount>0) { // perlu disimpen apa nggak + _rxtimercount = 0; // kalau berhasil terima something, reset jadi zero + if (readcount>bytebuf.remaining()) { + // gak cukup, gedein byte buffer + ByteBuffer newbytebuf = ByteBuffer.allocate(bytebuf.capacity()+readcount); + newbytebuf.put(bytebuf); + bytebuf = newbytebuf; // alokasikan ke sini + + } + + // sampe sini , size cukup + bytebuf.put(buf,0,readcount); + + // kalau readcount = 1024, artinya datanya cukup banyak, mungkin perlu baca lagi + // kalau readcount < 1024, artinya datanya sedikit, cukup sekali baca aja + if (readcount!=buf.length) break; + }; + + } + + if (bytebuf.position()>0) { + bytebuf.flip(); + byte[] totalbytes = new byte[bytebuf.remaining()]; + bytebuf.get(totalbytes); + //printbytes("TotalBytes",totalbytes); + try { + Object rxobject = _converter.ConvertBytesToObject(totalbytes); + if (rxobject instanceof MyMap) { + // MyMap adalah class asli Map Java , diwrapping oleh Erel + Map result = new Map(); // Map ini adalah B4X object + result.setObject((MyMap)rxobject); + raise_newmapdata(result); + } else if (rxobject instanceof String) { + String result = (String) rxobject; + raise_newstringdata(result); + } else if (rxobject instanceof ArrayList) { + ArrayList tempresult = (ArrayList) rxobject; + if (tempresult.size()>0) { + Object _firstobject = tempresult.get(0); + if (_firstobject instanceof Byte) { + byte[] result = new byte[tempresult.size()]; + for(int ii=0;ii0) { + for (int ii=0;ii0) { + try { + return WriteObject(value); + } catch (IOException e) { + raise_log_error("IOException on WriteMap",e); + Disconnect(); + } + } else raise_log("WriteMap canceled, value Size = 0"); + } else raise_log("WriteMap canceled, value is not initialized"); + } else raise_log("WriteMap canceled, value is not Map"); + return false; + } + + /** + * Write bytes array to TCPSocket + * @param value : bytes array + * @return true if can be sent + */ + public boolean WriteBytes(byte[] value) { + if (value instanceof byte[]) { + if (value.length>0) { + try { + ArrayList result = new ArrayList(); + for(int ii=0;ii0) { + try { + ArrayList result = new ArrayList(); + for(int ii=0;ii0) { + long now = DateTime.getNow(); + if ((now - _connected_tick)>0) { + return ((now - _connected_tick) / DateTime.TicksPerSecond); + } + } + return 0; + } + + /** + * Get Date and Time when the socket is connected + * String will formatted as DD-MM-YYYY HH:MM:SS + * @return empty string if not yet connected + */ + public String Connected_DateTime_String() { + if (_connected_tick>0) { + return mycodes.Tick_To_DDMMYYYY_HHMMSS(_connected_tick); + } + return ""; + } + + /** + * Get how many succesful Sending package + * @return zero to 2,147,483,647 + */ + public int getTX_OK_Counter() { + + return _txokcount; + } + + /** + * Get how many succesful Receiving package + * @return zero to 2,147,483,647 + */ + public int getRX_OK_Counter() { + return _rxokcount; + } + + /** + * Get how much bytes has been sucesfully sent + * @return value in bytes, from zero to 9,223,372,036,854,775,807 + */ + public long getTX_Bytes_Counter() { + return _txbytescount; + } + + /** + * Get how much bytes has been sucesfully received + * @return value in bytes, from zero to 9,223,372,036,854,775,807 + */ + public long getRX_Bytes_Counter() { + return _rxbytescount; + } + + /** + * TimerTask to execute, for checking txtimercounter and rxtimercounter + * this TimerTask will execute every 1000 ms + */ + private TimerTask checktxrxtimercounter = new TimerTask() { + + @Override + public void run() { + if (_txtimercount>5) { + raise_log("Not sending more than 5 seconds, sending [PING] to check connection"); + WriteString("[PING]"); // pancingan aja, gak penting hasilnya + } + + if (_rxtimercount>10) { + raise_log("Not receiving more than 10 seconds, connection have problem"); + Disconnect(); + } + } + + }; + + private void Close_ObjectOutputStream() { + if (_output instanceof ObjectOutputStream) { + try { + _output.close(); + raise_log("ObjectOutputStream closed"); + } catch (IOException e) { + raise_log_error("IOException on Close_ObjectOutputStream",e); + } + } + _output = null; + } + + private void Close_ObjectInputStream() { + if (_input instanceof ObjectInputStream) { + try { + _input.close(); + raise_log("ObjectInputStream closed"); + } catch (IOException e) { + raise_log_error("IOException on Close_ObjectInputStream",e); + } + } + _input = null; + } + + private void raise_log_error(String msg, Exception e) { + StringBuffer sbf = new StringBuffer(); + sbf.append(msg); + if (e instanceof Exception) { + if (e.getMessage() instanceof String) { + sbf.append("\r\n"); + sbf.append("Msg : "+e.getMessage()); + } + if (e.getCause() instanceof Throwable) { + sbf.append("\r\n"); + sbf.append(e.getCause()); + } + } + raise_log(sbf.toString()); + } + + private void raise_log(String msg) { + if (need_log_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_connected(String remoteip, int remoteport) { + if (need_connected_event) { + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_connected", false, new Object[] {remoteip, remoteport}); + } + } + + private void raise_disconnected(String remoteip, int remoteport) { + if (need_disconnected_event) { + long tt = 0; + if (_connected_tick > 0) { + tt = (DateTime.getNow() - _connected_tick) / DateTime.TicksPerSecond; + } + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_disconnected", false, new Object[] {remoteip,remoteport, tt / DateTime.TicksPerSecond }); + } + } + + private void raise_newmapdata(Map value) { + if (need_newmapdata_event) { + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newmapdata", false, new Object[] {value}); + } + } + + private void raise_newbytesdata(byte[] value) { + if (need_newbytesdata_event) { + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newbytesdata", false, new Object[] {value}); + } + } + + private void raise_newstringdata(String value) { + if (need_newstringdata_event) { + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newstringdata", false, new Object[] {value}); + } + } + + private void raise_newintsdata(int[] value) { + if (need_newintsdata_event) { + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newintsdata", false, new Object[] {value}); + } + } + +} diff --git a/src/androgpio/customsocket/TCPSocketServer.java b/src/androgpio/customsocket/TCPSocketServer.java new file mode 100644 index 0000000..1922a35 --- /dev/null +++ b/src/androgpio/customsocket/TCPSocketServer.java @@ -0,0 +1,380 @@ +package androgpio.customsocket; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.List; +import anywheresoftware.b4a.objects.collections.Map; + +@BA.ShortName("TCPSocketServer") +@BA.Events(values= { + "log(msg as string)", + "newtcpsocket(value as TCPSocket)", + "listeningstatus(islistening as boolean)", + "connectedclients(value as int, keys as List)" +}) +public class TCPSocketServer implements TcpSocketJavaEvent { + + private BA bax; + private Object caller; + private Object Me = this; + private String event; + private boolean _inited = false; + private boolean need_log_event = false; + private boolean need_newtcpsocket_event = false; + private boolean need_listeningstatus_event = false; + private boolean need_connectedclients_event = false; + private ServerSocket _server; + + private Map _socketmap = new Map(); + + /** + * Initialize TCP Socket Server + * @param callerobject : caller object + * @param eventname : eventname + */ + public void Initialize(BA ba, Object callerobject, String eventname) { + bax = ba; + caller = callerobject; + event = eventname; + if (bax instanceof BA) { + if (caller != null) { + if (event instanceof String) { + if (!event.isEmpty()) { + _inited = true; + need_log_event = bax.subExists(event+"_log"); + need_newtcpsocket_event = bax.subExists(event+"_newtcpsocket"); + need_listeningstatus_event = bax.subExists(event+"_listeningstatus"); + need_connectedclients_event = bax.subExists(event+"_connectedclients"); + } + } + } + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + BA.Log("ShutdownHook on TCPSocketServer at "+getLocalIPAddress()+":"+getLocalPort()); + StopListening(); + Disconnect_All_TCPSocket(); + } + }); + + _socketmap.Initialize(); + } + + /** + * Check if TCPSocketServer is initialized + * @return true if inited + */ + public boolean Initialized() { + return _inited; + } + + /** + * Stop Listening for Connections + */ + public void StopListening() { + if (_server instanceof ServerSocket) { + try { + _server.close(); + } catch (IOException e) { + raise_log_error("IOException on StopListening",e); + } + } + _server = null; + } + + /** + * Get Local IP Address used for listening + * @return empty string if failed + */ + public String getLocalIPAddress() { + if (_server instanceof ServerSocket) { + if (_server.getInetAddress()!=null) { + return _server.getInetAddress().getHostAddress(); + } + } + return ""; + } + + /** + * Get Local Port used for Listening + * @return zero if failed + */ + public int getLocalPort() { + if (_server instanceof ServerSocket) { + if (_server.getLocalPort()>0) { + return _server.getLocalPort(); + } + + } + return 0; + } + + /** + * Start Listening + * @param port : listen port + * @return true if success + */ + public boolean StartListening(int port) { + if (_server instanceof ServerSocket) StopListening(); + try { + ServerSocket newserver = new ServerSocket(port); + Thread tx = new Thread(new tcpsocketserverrun(newserver)); + tx.start(); + return true; + } catch (IOException e) { + raise_log_error("IOException on StartListening at port "+port,e); + } + return false; + } + + /** + * Get how many TCPSockets has been received + * @return value as integer + */ + public int getConnectedTCPSocket() { + return _socketmap.IsInitialized() ? _socketmap.getSize() : 0; + } + + /** + * Get List of IdentifierKey from all connected TCPSockets + * @return List of IdentifierKey, or empty List of none connected + */ + public List getListOfTCPSocketKeys() { + List result = new List(); + result.Initialize(); + if (_socketmap.IsInitialized()) { + if (_socketmap.getSize()>0) { + for(int ii=0;ii<_socketmap.getSize();ii++) { + result.Add((String)_socketmap.GetKeyAt(ii)); + } + } + } + return result; + } + + /** + * Disconnect all TCPSocket in Map + */ + public void Disconnect_All_TCPSocket() { + List result = getListOfTCPSocketKeys(); + if (result.getSize()>0) { + for(int ii=0;ii0) { + for (int ii=0;ii<_socketmap.getSize();ii++) { + String key = (String) _socketmap.GetKeyAt(ii); + if (key.startsWith(ipvalue)) { + Object xx = _socketmap.Get(key); + if (xx instanceof TCPSocket) { + return (TCPSocket)xx; + } + + } + } + } + } + } + return null; + } + + /** + * Disconnect TCPSocket and remove it from Map + * @param identifierkey : identifierkey in string + * @return true if identifierkey can be found + */ + public boolean Disconnect_TCPSocket(String identifierkey) { + if (identifierkey instanceof String) { + if (!identifierkey.isEmpty()) { + if (_socketmap.ContainsKey(identifierkey)) { + Object xx = _socketmap.Get(identifierkey); + if (xx instanceof TCPSocket) { + TCPSocket tcp = (TCPSocket)xx; + tcp.Disconnect(); + tcp = null; + } + xx = null; + _socketmap.Remove(identifierkey); + raise_connectedclients(_socketmap.getSize(), getListOfTCPSocketKeys()); + return true; + } + } + } + return false; + } + + /** + * Rename Map identifier from old_id to new_id, but retain the same TCPSocket object + * @param old_id : old identifier + * @param new_id : new identifier + * @return true if Map contain old_id and succesfully renamed, or false if otherwise + */ + public boolean Rename_Identifier(String old_id, String new_id) { + if (_socketmap instanceof Map) { + if (_socketmap.IsInitialized()) { + if (_socketmap.ContainsKey(old_id)) { + Object xx = _socketmap.Get(old_id); + if (xx instanceof TCPSocket) { + TCPSocket tcp = (TCPSocket) xx; + + _socketmap.Remove(old_id); + _socketmap.Put(new_id, tcp); + if (tcp.getIdentifierKey().equals(new_id)==false) { + tcp.setIdentifierKey(new_id); + } + return true; + } + } + } + } + return false; + } + + private class tcpsocketserverrun implements Runnable{ + private boolean validrun = false; + public tcpsocketserverrun(ServerSocket xx) { + _server = xx; + if (_server instanceof ServerSocket) { + if (!_server.isClosed()) { + validrun = true; + } + } + } + + public void run() { + if (validrun) { + raise_listeningstatus(true); + while(true) { + if (_server instanceof ServerSocket) { + if (!_server.isClosed()) { + try { + Socket newsocket = _server.accept(); + raise_log("New connection from "+newsocket.getInetAddress().getHostAddress()+":"+newsocket.getPort()+" to TCPSocketServer"); + TCPSocket newtcp = new TCPSocket(newsocket, Me); + + raise_newtcpsocket(newtcp); + } catch (IOException e) { + raise_log_error("IOException on server.accept", e); + } + } else break; + } else break; + } + + } + raise_listeningstatus(false); + } + } + + private void raise_log_error(String msg, Exception e) { + StringBuffer sbf = new StringBuffer(); + sbf.append(msg); + if (e instanceof Exception) { + if (e.getMessage() instanceof String) { + sbf.append("\r\n"); + sbf.append("Msg : "+e.getMessage()); + } + if (e.getCause() instanceof Throwable) { + sbf.append("\r\n"); + sbf.append(e.getCause()); + } + } + raise_log(sbf.toString()); + } + + private void raise_newtcpsocket(TCPSocket value) { + if (need_newtcpsocket_event) { + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newtcpsocket", 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_listeningstatus(boolean value) { + if (need_listeningstatus_event) { + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_listeningstatus", false, new Object[] {value}); + } + } + + private void raise_connectedclients(int value, List keys) { + if (need_connectedclients_event) { + bax.raiseEventFromDifferentThread(Me, null, 0, event+"_connectedclients", false, new Object[] {value, keys}); + } + } + + //////////////// Event from TcpSocketJavaEvent ///////////////////////// + @Override + @BA.Hide + public void newidentifierkey(String identifier, TCPSocket Sender) { + if (!_socketmap.IsInitialized()) _socketmap.Initialize(); + if (identifier instanceof String) { + if (!identifier.isEmpty()) { + Object prev_in_map = null; + if (identifier.contains(":")) { + // mengandung ip:port + // coba cek apakah ip nya pernah ada + String ipnya = identifier.substring(0, identifier.indexOf(":")); + prev_in_map = Have_TCPSocket_with_IPAddress(ipnya); + if (prev_in_map instanceof TCPSocket) { + TCPSocket oldsock = (TCPSocket) prev_in_map; + // ada + raise_log("Found another TCPSocket with same IP. ID 1 = "+identifier+", ID 2="+ oldsock.getIdentifierKey()); + _socketmap.Remove(oldsock.getIdentifierKey()); + raise_log("Removing Old TCPSocket with identifier="+oldsock.getIdentifierKey()); + _socketmap.Put(identifier, Sender); + raise_log("Save New TCPSocket with identifer="+Sender.getIdentifierKey()); + } + } else { + prev_in_map = _socketmap.Put(identifier, Sender); + raise_log("Save TCPSocket with identifier="+identifier+" to Map"); + } + + + if (prev_in_map != null) { + if (prev_in_map instanceof TCPSocket) { + TCPSocket prevsock = (TCPSocket) prev_in_map; + prevsock.Disconnect(); + prevsock = null; + raise_log("Disconnect old TCPSocket with the identifier="+identifier); + } + prev_in_map = null; + } + } + } + + + raise_connectedclients(_socketmap.getSize(), getListOfTCPSocketKeys()); + } + + @Override + @BA.Hide + public void disconnected(String identifier) { + Disconnect_TCPSocket(identifier); + } + + @Override + @BA.Hide + public void renameidentifier(String oldvalue, String newvalue) { + Rename_Identifier(oldvalue, newvalue); + + } +} diff --git a/src/androgpio/customsocket/TcpSocketJavaEvent.java b/src/androgpio/customsocket/TcpSocketJavaEvent.java new file mode 100644 index 0000000..fc58278 --- /dev/null +++ b/src/androgpio/customsocket/TcpSocketJavaEvent.java @@ -0,0 +1,8 @@ +package androgpio.customsocket; + +public interface TcpSocketJavaEvent { + public void newidentifierkey(String identifier, TCPSocket Sender); + public void disconnected(String identifier); + public void renameidentifier(String oldvalue, String newvalue); + +} diff --git a/src/androgpio/customsocket/jSocketIOClientV1.java b/src/androgpio/customsocket/jSocketIOClientV1.java new file mode 100644 index 0000000..c5f0911 --- /dev/null +++ b/src/androgpio/customsocket/jSocketIOClientV1.java @@ -0,0 +1,1227 @@ +package androgpio.customsocket; + +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.regex.Pattern; + +import com.google.gson.Gson; + +import androgpio.mycodes; +import io.socket.client.Ack; +import io.socket.client.AckWithTimeout; +import io.socket.client.IO; +import io.socket.client.Manager; +import io.socket.client.Socket; +import io.socket.client.SocketIOException; +import io.socket.emitter.Emitter; +import io.socket.engineio.client.EngineIOException; +import anywheresoftware.b4a.BA; + + + +@BA.ShortName("jSocketIOClientV1") +@BA.Events(values= { + "log(msg as string)", + "connected(socketid as string, remote as string)", + "disconnected(socketid as string, remote as string, reason as string)", + "message(socketid as string, remote as string, value as string)", + "reconnecting(uri as String, attempt as int)", + "reconnected(uri as string, attempt as int)", + "success(replyvalue as Object)", + "success(replyvalue as String)", + "success(replyvalue as byte[])", + "timeout(msg as string)" +}) + +@BA.DependsOn(values= {"socket.io-client-2.1.0","engine.io-client-2.1.0","okhttp-3.12.12","okio-1.15.0","gson-2.10.1"}) +@BA.Permissions(values= {"android.permission.INTERNET"}) + + +/** + * Ini adalah Socket.io client yang inisiatif konek ke Socket.io server + * butuh masukin android:usesCleartextTraffic="true" di AndroidManifest.xml + * Baca : https://socketio.github.io/socket.io-client-java/android.html + * @author rdkartono + * + */ +public class jSocketIOClientV1 { + + private final Object Me = this; + + private BA bax; + private Object caller; + private String event; + private boolean _inited = false; + private boolean need_log_event = false; + private boolean need_connected_event = false; + private boolean need_disconnected_event = false; + private boolean need_message_event = false; + private boolean need_reconnecting_event = false; + private boolean need_reconnected_event = false; + private Socket _client; + + private String _remote; + private String _id; + private Gson gson = new Gson(); + + + /** + * Initialize Events for Socket.IO Client + * This is client side Socket that to be use to connect with SocketIO Server + * @param callerobject : caller object + * @param eventname : event name + */ + public void Initialize(final BA ba, final Object callerobject, final String eventname) { + bax = ba; + caller = callerobject; + event = eventname; + + if (bax instanceof BA) { + if (caller != null) { + if (event instanceof String) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(event+"_log"); + need_connected_event = bax.subExists(event+"_connected"); + need_disconnected_event = bax.subExists(event+"_disconnected"); + need_message_event = bax.subExists(event+"_message"); + need_reconnecting_event = bax.subExists(event+"_reconnecting"); + need_reconnected_event = bax.subExists(event+"_reconnected"); + _inited = true; + } + } + } + } + + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + if (BA.debugMode) BA.Log("ShutdownHook for SocketIOClient"); + Disconnect(); + } + }); + + _id = ""; + _remote = ""; + } + + /** + * Check if Socket.IO Client is initialized + * @return true if inited + */ + public boolean IsInitialized() { + return _inited; + } + + + + /** + * Connect to SocketIO Server + * Will raise connected event or disconnected event + * @param uri : valid uri string, example ws://192.168.31.2:8080 + * @param method : "polling", "websocket" + * @param events : array of string, containing event name that want to be intercepted as soon as possible + * @return true if socket can be created or false if parameters invalid + */ + public boolean ConnectTo(final String uri, String method, String...events ) { + //if (BA.debugMode) raise_log("ConnecTo will using socket.io-client protocol version = "+IO.protocol); + if (!mycodes.valid_string(method)) { + if (BA.debugMode) raise_log("ConnectTo, method is empty, will use polling"); + method = "polling"; + } + if (!_id.isEmpty()) { + if (BA.debugMode) raise_log("Already connected to "+_remote+", will disconnect first"); + Disconnect(); //kalau ID sudah ada, berarti dah connected. Kalau gitu, disconnect dulu + } + + if (uri instanceof String) { + if (!uri.isEmpty()) { + try { + IO.Options opts = new IO.Options(); + // pilihan "polling", "websocket" + opts.transports = new String[] { method }; + + + if (BA.debugMode) { + raise_log("Will connect to "+uri+" with method = "+method); + + } + + Socket newsock = IO.socket(uri, opts); + + Manager manager = newsock.io(); + + manager.on(Manager.EVENT_RECONNECT, (value)->{ + // fired upon a succesful reconnection + int attempt = (int)value[0]; + if (BA.debugMode) raise_log("Reconnected to "+uri+" after attempt number = "+attempt); + raise_reconnected(uri, attempt); + + }); + + manager.on(Manager.EVENT_RECONNECT_ATTEMPT, (value)->{ + // fired upon an attempt to reconnect + int attempt = (int)value[0]; + if (BA.debugMode) raise_log("Reconnect Attempt to "+uri+", attempt number = "+attempt); + raise_reconnecting(uri, attempt); + }); + + manager.on(Manager.EVENT_ERROR, (err)->{ + + EngineIOException error = (EngineIOException)err[0]; + if (BA.debugMode) raise_log("Error : "+String.valueOf(error.getMessage())); + }); + + manager.on(Manager.EVENT_RECONNECT_ERROR, (err)->{ + + SocketIOException error = (SocketIOException)err[0]; + if (BA.debugMode) raise_log("Reconnect Error : "+String.valueOf(error.getMessage())); + }); + + + + newsock.on(Socket.EVENT_CONNECT,(value)->{ + // di sini, gak ada value, tandanya value.length = 0 + + + + // kalau sebelumhya terkoneksi ke tempat lain, disconnect dulu + Disconnect(); + + _id = newsock.id(); + _remote = uri; + _client = newsock; + if (BA.debugMode) raise_log("Socket.io client url="+uri+" connected, id = "+_id); + + raise_connected(_id, _remote); + + // Tambah events + if (events!=null) { + if (events.length>0) { + for(String ev : events) { + if (!ev.isEmpty()) { + Add_Event(ev, BA.debugMode); + } + + } + } + } + }); + newsock.on(Socket.EVENT_CONNECT_ERROR, (value)->{ + // di sini, ada 1 value, tandanya value.length = 1 + // isinya keterangan kenapa gagal connect + + _remote = uri; + String _reason = ""; + if (value!=null) { + + if (value.length>0) { + _reason = String.valueOf(value[0]); + } + + } + + if (BA.debugMode) raise_log("Socket.io client url="+uri+" failed, reason = "+_reason); + + raise_disconnected(_id, _remote,_reason); + // Revisi 06062020, kalau connect error, disconnect saja + Disconnect(); + + }); + newsock.on(Socket.EVENT_DISCONNECT, (value)->{ + // di sini ada 1 value, tandanya value.length = 1 + // isinya keterangan kenapa disconnect + // disconnect artinya sudah pernah connect, beda dengan connect_error + //if (debugmode) printvaluedebug(Socket.EVENT_DISCONNECT, value); + + String _reason = ""; + _remote = uri; + if (value!=null) { + if (value.length>0) { + _reason = String.valueOf(value[0]); + } + } + if (BA.debugMode) raise_log("Socket.io client url="+uri+" disconnected, reason = "+_reason); + + raise_disconnected(_id, _remote,_reason); + // Revisi 06062020, kalau disconnect, langsung disconnect saja + Disconnect(); + + }); + + + + newsock.on("message", (value)->{ + + if (value!=null) { + if (value.length>0) { + String msg = String.valueOf(value[0]); + raise_message(_id, _remote, msg); + } + + } + }); + + + + + + + newsock.connect(); + return true; + } catch (URISyntaxException e) { + raise_log("ConnectTo failed, URISyntaxException for "+uri+", Msg : "+e.getMessage()); + } + + } else raise_log("ConnectTo failed, uri is empty"); + } else raise_log("ConnectTo failed, uri is not String"); + return false; + } + + /** + * Disconnect socket + */ + public void Disconnect() { + + if (_client instanceof Socket) { + Remove_All_Events(); // remove semua event + _client.disconnect(); // disconnect + } + _client = null; + + _remote = ""; + _id = ""; + } + + /** + * Remove all Socket Events + */ + public void Remove_All_Events() { + if (_client instanceof Socket) { + _client.off(); + } + } + + + public boolean IsConnected() { + if (_client != null) { + if (_client instanceof Socket) { + return _client.connected(); + } + } + return false; + } + + + private java.util.Set _debug_ignore = new java.util.HashSet(); + + /** + * Add ignored debug for specified socket.io event + * @param eventname eventname to ignore + */ + public void Add_Ignored_Debug_Event(String eventname) { + _debug_ignore.add(eventname); + } + + private boolean Is_Ignored_Debug(String event) { + return _debug_ignore.contains(event); + } + + + /** + * Add Custom Event to Socket + * If data arrived, will raise event_eventname(value as Object) as Object + * return Object to reply this event + * + * + * Sub event_eventname(value as Object) as Object + * return "reply value" + * End Sub + * + * @param eventname : eventname, lowercase characters and numbers only + * @param debugmode : if true, will give debug output + * @return true if event added + */ + public boolean Add_Event(final String eventname, boolean debugmode) { + if (is_correct_eventname(eventname)) { + if (_client instanceof Socket) { + if (_client.hasListeners(eventname)) { + _client.off(eventname); + raise_log("Add_Event with eventname = "+eventname+" already exist, removing the previous"); + } + + final String eventtoraise = event+"_"+eventname; + final boolean need_event = bax.subExists(eventtoraise); + if (need_event && !Is_Ignored_Debug(eventname)) raise_log("Will raise "+eventtoraise); + + + _client.on(eventname, new Emitter.Listener() { + @Override + public void call(Object... args) { + if (args!=null) { + SocketIoObject sio = new SocketIoObject(); + sio.setEvent(eventname); + sio.setValueCount(ValuesCount(args)); + if (sio.getValueCount()>0) { + if (sio.getValueCount() == 1) + sio.setValue(args[0]); + else { + sio.setValue(Arrays.copyOf(args, sio.getValueCount())); + } + } + sio.setAckIndex(have_Acknowledge(args)); + + if (debugmode && !Is_Ignored_Debug(eventname)) raise_log("Receive Event "+eventname+ (sio.HaveValue() ? " with value = "+sio.ValueToString():"")); + + if (need_event) { + + bax.raiseEventFromDifferentThread(Me, null, 0, eventtoraise, false, new Object[] {sio}); + if ("resync_content".equals(eventname)) { + // resync_content bisa lama, kalau lagi download file, jadi wait 0 = wait forever + //TODO nunggu lama di sini , socket.io server kirim ping, tidak direspon pong oleh client, dianggap disconnected + sio.Wait(0); + } else { + // yang lain , wait 5 detik + sio.Wait(5000); + } + +// if (debugmode && !Is_Ignored_Debug(eventname)) { +// raise_log("Event "+eventname+", Return Data = " + sio.ReplyToString()); +// } + + + if (sio.NeedReply()) { + Ack ack = (Ack) args[sio.getAckIndex()]; + if (sio.getReply()!=null) { + if (debugmode && !Is_Ignored_Debug(eventname)) raise_log("Event "+eventname+", Acknowledge reply = "+sio.ReplyToString()); + if (sio.ReplyIsJsonArray()) { + ack.call(sio.ReplyToJsonArray().getObject()); + } + else if (sio.ReplyIsJsonObject()) { + ack.call(sio.ReplyToJsonObject().getObject()); + } + else { + ack.call(sio.getReply()); + } + } else { + if (debugmode && !Is_Ignored_Debug(eventname)) raise_log("Event "+eventname+", Return Data is null, Acknowledge with empty string"); + ack.call(""); + } + } else if (sio.getReply() != null) { + if (debugmode && !Is_Ignored_Debug(eventname)) raise_log("Event "+eventname+" have Return Data but dont need Acknowledge"); + } + } + } + + } + } + + ); + + return true; + } else raise_log("Add_Event failed, Socket not connected"); + } else raise_log("Add_Event failed, eventname "+eventname+" is invalid. Valid eventname only contains lowercase characters, numbers, and underscore"); + return false; + } + + /** + * Remove specified eventname from socket + * @param eventname : eventname to remove, lowercase characters and numbers only + * @return true if removed + */ + public boolean Remove_Event(String eventname) { + if (is_correct_eventname(eventname)) { + if (_client instanceof Socket) { + if (_client.hasListeners(eventname)) { + _client.off(eventname); + return true; + } else raise_log("Remove_Event failed, Socket dont have event : "+eventname); + } else raise_log("Remove_Event failed, Socket not connected"); + } else raise_log("Remove_Event failed, eventname "+eventname+" is invalid. Valid eventname only contains lowercase characters, numbers, and underscore"); + return false; + } + + /** + * Emit simple String to target socket + * + * @param emitname : event name + * @param value : String to send + * @return true if value can be send + */ + public boolean Emit_String(String emitname, String value) { + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_string(value)) { + _client.emit(emitname, value); + return true; + } + return false; + } + + /** + * Emit simple String to target socket and wait for acknowledge + * @param emitname : event name + * @param value : value to send + * @param ackevent : prefix of acknowledge event + * On success, will raise event ackevent_success(replyvalue as String) + * On timeout, will raise event ackevent_timeout(msg as string) + * @return true if value can be send + */ + public boolean Emit_String_WithAcknowledge(String emitname, String value, String ackevent) { + final boolean need_success = bax.subExists(ackevent+"_success"); + final boolean need_timeout = bax.subExists(ackevent+"_timeout"); + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_string(value) && mycodes.valid_string(ackevent)) { + + _client.emit(emitname, value, new AckWithTimeout(5000) { + + @Override + public void onSuccess(Object... args) { + if (args!=null && args.length>0) { + //TODO cek tipe args dengan log + for (int ii = 0; ii < args.length; ii++) { + raise_log("Emit_String_WithAcknowledge onSuccess, args["+ii+"] = "+args[ii].getClass().getTypeName()); + } + + //String result = mycodes.Combine("\n", args); + String result = gson.toJson(args); + if (need_success) bax.raiseEventFromDifferentThread(Me, null, 0, ackevent+"_success", false, new Object[] {result}); + + } + + } + + @Override + public void onTimeout() { + if (need_timeout) bax.raiseEventFromDifferentThread(Me, null, 0, ackevent+"_timeout", false, new Object[] {"Emitname=["+emitname+"] value=["+value+"] has no reply in 5 seconds"}); + + } + + }); + + //TODO kalau sudah benar, hapus ini +// _client.emit(emitname,value, new Ack() { +// +// @Override +// public void call(Object... args) { +// if (args!=null && args.length>0) { +// //TODO cek tipe args dengan log +// for (int ii = 0; ii < args.length; ii++) { +// raise_log("Emit_String_WithAcknowledge onSuccess, args["+ii+"] = "+args[ii].getClass().getTypeName()); +// } +// +// //String result = mycodes.Combine("\n", args); +// String result = gson.toJson(args); +// if (need_success) bax.raiseEventFromDifferentThread(Me, null, 0, ackevent+"_success", false, new Object[] {result}); +// +// } +// } +// +// }); + + return true; + } + return false; + } + + /** + * Emit byte array to target socket and wait for acknowledge + * @param emitname : event name + * @param value : byte array + * @param ackevent : prefix of acknowledge event + * On success, will raise event ackevent_success(replyvalue as byte[]) + * On timeout, will raise event ackevent_timeout(msg as string) + * @return true if value can be send + */ + public boolean Emit_Bytes_WithAcknowledge(String emitname, byte[] value, String ackevent) { + final boolean need_success = bax.subExists(ackevent + "_success"); + final boolean need_timeout = bax.subExists(ackevent + "_timeout"); + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_bytearray(value) + && mycodes.valid_string(ackevent)) { + + _client.emit(emitname, value, new AckWithTimeout(5000) { + + @Override + public void onSuccess(Object... args) { + if (args != null && args.length>0) { + //TODO cek tipe args dengan log + for (int ii = 0; ii < args.length; ii++) { + raise_log("Emit_Bytes_WithAcknowledge onSuccess, args[" + ii + "] = " + + args[ii].getClass().getTypeName()); + } + + if (args[0]==byte[].class) { + byte[] result = (byte[]) args[0]; + if (need_success) + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { result }); + } else if (args[0]==int[].class) { + int[] result = (int[]) args[0]; + if (need_success) + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { result }); + } else if (args[0]==String.class) { + String result = (String) args[0]; + if (need_success) + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { result }); + } + } + } + + @Override + public void onTimeout() { + if (need_timeout) + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_timeout", false, new Object[] { + "Emitname=[" + emitname + "] value=[" + value + "] has no reply in 5 seconds" }); + + } + + }); + + //TODO kalau sudah benar, hapus ini +// _client.emit(emitname, value, new Ack() { +// +// @Override +// public void call(Object... args) { +// if (args != null && args.length>0) { +// //TODO cek tipe args dengan log +// for (int ii = 0; ii < args.length; ii++) { +// raise_log("Emit_Bytes_WithAcknowledge onSuccess, args[" + ii + "] = " +// + args[ii].getClass().getTypeName()); +// } +// +// if (args[0]==byte[].class) { +// byte[] result = (byte[]) args[0]; +// if (need_success) +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { result }); +// } else if (args[0]==int[].class) { +// int[] result = (int[]) args[0]; +// if (need_success) +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { result }); +// } else if (args[0]==String.class) { +// String result = (String) args[0]; +// if (need_success) +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { result }); +// } +// } +// +// } +// +// }); + + return true; + } + return false; + } + + + /** + * Emit byte array to target socket + * + * @param emitname : event name + * @param value : byte array + * @return true if value can be send + */ + public boolean Emit_Bytes(String emitname, byte[] value) { + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_bytearray(value)) { + _client.emit(emitname, value); + return true; + } + return false; + } + + /** + * Emit Map to target socket + * On Server side, will be received as String in JSON format + * @param emitname : event name + * @param value : Map value to send + * @return true if value can be send + */ + public boolean Emit_Map(String emitname, anywheresoftware.b4a.objects.collections.Map value) { + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_Map(value)) { + //JSONObject json = new JSONObject(value.getObject()); + + _client.emit(emitname, gson.toJson(value.getObject())); + return true; + } + return false; + } + + /** + * Emit Map to target socket and wait for acknowledge + * On Server side, will be received as String in JSON format + * On success, will raise event ackevent_success(replyvalue as Object) + * On timeout, will raise event ackevent_timeout(msg as string) + * @param emitname : event name + * @param value : value to send + * @param ackevent : prefix of acknowledge event + * @return true if value can be send + */ + public boolean Emit_Map_WithAcknowledge(String emitname, anywheresoftware.b4a.objects.collections.Map value, + String ackevent) { + final boolean need_success = bax.subExists(ackevent + "_success"); + final boolean need_timeout = bax.subExists(ackevent + "_timeout"); + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_Map(value) + && mycodes.valid_string(ackevent)) { + + _client.emit(emitname, gson.toJson(value.getObject()), new AckWithTimeout(5000) { + + @Override + public void onSuccess(Object... args) { + if (args != null && args.length>0) { + //TODO cek tipe args dengan log + for (int ii = 0; ii < args.length; ii++) { + raise_log("Emit_Map_WithAcknowledge onSuccess, args[" + ii + "] = " + + args[ii].getClass().getTypeName()); + } + + + + //String rr = mycodes.Combine("\n", args); + String rr = gson.toJson(args); + if (need_success) { + if (mycodes.String_Is_JSONArray(rr)) { + JsonArray jarr = new JsonArray(); + jarr.InitializeFromString(rr); + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { jarr }); + } else if (mycodes.String_Is_JSONObject(rr)) { + JsonObject jobj = new JsonObject(); + jobj.InitializeFromString(rr); + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { jobj }); + } else { + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { rr }); + } + } + + } + } + + @Override + public void onTimeout() { + if (need_timeout) + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_timeout", false, new Object[] { + "Emitname=[" + emitname + "] value=[" + value + "] has no reply in 5 seconds" }); + + } + + }); + + //TODO kalau sudah benar, hapus ini +// _client.emit(emitname, gson.toJson(value.getObject()), new Ack() { +// +// @Override +// public void call(Object... args) { +// if (args != null && args.length>0) { +// //TODO cek tipe args dengan log +// for (int ii = 0; ii < args.length; ii++) { +// raise_log("Emit_Map_WithAcknowledge onSuccess, args[" + ii + "] = " +// + args[ii].getClass().getTypeName()); +// } +// +// +// +// //String rr = mycodes.Combine("\n", args); +// String rr = gson.toJson(args); +// if (need_success) { +// if (mycodes.String_Is_JSONArray(rr)) { +// JsonArray jarr = new JsonArray(); +// jarr.InitializeFromString(rr); +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { jarr }); +// } else if (mycodes.String_Is_JSONObject(rr)) { +// JsonObject jobj = new JsonObject(); +// jobj.InitializeFromString(rr); +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { jobj }); +// } else { +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { rr }); +// } +// } +// +// } +// +// } +// +// }); + + + + return true; + } + return false; + } + + /** + * Emit List to target socket On Server side, will be received as String in JSON + * format + * + * @param emitname : event name + * @param value : List value to send + * @return true if value can be send + */ + public boolean Emit_List(String emitname, anywheresoftware.b4a.objects.collections.List value) { + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_List(value)) { + //JSONArray json = new JSONArray(value.getObject()); + _client.emit(emitname, gson.toJson(value.getObject())); + return true; + } + return false; + } + + /** + * Emit List to target socket and wait for acknowledge + * On Server side, will be received as String in JSON format + * On success, will raise event ackevent_success(replyvalue as Object) + * on timeout, will raise event ackevent_timeout(msg as string) + * @param emitname : event name + * @param value : value to send + * @param ackevent : prefix of acknowledge event + * @return true if value can be send + */ + public boolean Emit_List_WithAcknowledge(String emitname, anywheresoftware.b4a.objects.collections.List value, + String ackevent) { + final boolean need_success = bax.subExists(ackevent + "_success"); + final boolean need_timeout = bax.subExists(ackevent + "_timeout"); + + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_List(value) + && mycodes.valid_string(ackevent)) { + + _client.emit(emitname, gson.toJson(value.getObject()), new AckWithTimeout(5000) { + + @Override + public void onSuccess(Object... args) { + if (args != null && args.length>0) { + // TODO cek tipe args dengan log + for (int ii = 0; ii < args.length; ii++) { + raise_log("Emit_List_WithAcknowledge onSuccess, args[" + ii + "] = " + + args[ii].getClass().getTypeName()); + } + + //String rr = mycodes.Combine("\n", args); + String rr = gson.toJson(args); + if (need_success) { + if (mycodes.String_Is_JSONArray(rr)) { + JsonArray jarr = new JsonArray(); + jarr.InitializeFromString(rr); + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { jarr }); + } else if (mycodes.String_Is_JSONObject(rr)) { + JsonObject jobj = new JsonObject(); + jobj.InitializeFromString(rr); + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { jobj }); + } else { + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { rr }); + } + } + + } + } + + @Override + public void onTimeout() { + if (need_timeout) + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_timeout", false, new Object[] { + "Emitname=[" + emitname + "] value=[" + value + "] has no reply in 5 seconds" }); + + } + + }); + + //TODO kalau sudah benar, hapus ini +// _client.emit(emitname, gson.toJson(value.getObject()), new Ack() { +// +// @Override +// public void call(Object... args) { +// if (args != null && args.length>0) { +// // TODO cek tipe args dengan log +// for (int ii = 0; ii < args.length; ii++) { +// raise_log("Emit_List_WithAcknowledge onSuccess, args[" + ii + "] = " +// + args[ii].getClass().getTypeName()); +// } +// +// //String rr = mycodes.Combine("\n", args); +// String rr = gson.toJson(args); +// if (need_success) { +// if (mycodes.String_Is_JSONArray(rr)) { +// JsonArray jarr = new JsonArray(); +// jarr.InitializeFromString(rr); +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { jarr }); +// } else if (mycodes.String_Is_JSONObject(rr)) { +// JsonObject jobj = new JsonObject(); +// jobj.InitializeFromString(rr); +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { jobj }); +// } else { +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { rr }); +// } +// } +// +// } +// +// } +// +// }); + + + return true; + } + return false; + } + + + /** + * Emit JsonObject to target socket + * On Server side, will be received as String in JSON format + * @param emitname : event name + * @param value : JsonObject + * @return true if value can be send + */ + public boolean Emit_JsonObject(String emitname, JsonObject value) { + if (IsConnected()) { + if (mycodes.valid_string(emitname)) { + if (mycodes.valid_JsonObject(value)) { + //if (BA.debugMode) raise_log("About to emit JsonObject, emitname = "+emitname+", value = "+value.getObject().toString()); + _client.emit(emitname, value.getObject()); + return true; + } else if (BA.debugMode) raise_log("Emit_JsonObject failed, invalid JsonObject value"); + } else if (BA.debugMode) raise_log("Emit_JsonObject failed, invalid emitname"); + } else if (BA.debugMode) raise_log("Emit_JsonObject failed, not connected"); + return false; + } + + /** + * emit JsonObject to target socket and wait for acknowledge + * On Server side, will be received as String in JSON format + * On success, will raise event ackevent_success(replyvalue as Object) + * on timeout, will raise event ackevent_timeout(msg as string) + * @param emitname : event name + * @param value : JsonObject + * @param ackevent : prefix of acknowledge event + * @return true if value can be send + */ + public boolean Emit_JsonObject_WithAcknowledge(String emitname, JsonObject value, String ackevent) { + final boolean need_success = bax.subExists(ackevent + "_success"); + final boolean need_timeout = bax.subExists(ackevent + "_timeout"); + + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_JsonObject(value) + && mycodes.valid_string(ackevent)) { + + _client.emit(emitname, value.getObject(), new AckWithTimeout(5000) { + + @Override + public void onSuccess(Object... args) { + if (args != null && args.length>0) { + // TODO cek tipe args dengan log + for (int ii = 0; ii < args.length; ii++) { + raise_log("Emit_JsonObject_WithAcknowledge onSuccess, args[" + ii + "] = " + + args[ii].getClass().getTypeName()); + } + + //String rr = mycodes.Combine("\n", args); + String rr = gson.toJson(args); + if (need_success) { + if (mycodes.String_Is_JSONArray(rr)) { + JsonArray jarr = new JsonArray(); + jarr.InitializeFromString(rr); + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { jarr }); + } else if (mycodes.String_Is_JSONObject(rr)) { + JsonObject jobj = new JsonObject(); + jobj.InitializeFromString(rr); + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { jobj }); + } else { + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { rr }); + } + } + + } + } + + @Override + public void onTimeout() { + if (need_timeout) + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_timeout", false, new Object[] { + "Emitname=[" + emitname + "] value=[" + value + "] has no reply in 5 seconds" }); + + } + + }); + + //TODO kalau sudah benar, hapus ini +// _client.emit(emitname, value.getObject().toString(), new Ack() { +// +// @Override +// public void call(Object... args) { +// if (args != null && args.length>0) { +// // TODO cek tipe args dengan log +// for (int ii = 0; ii < args.length; ii++) { +// raise_log("Emit_JsonObject_WithAcknowledge onSuccess, args[" + ii + "] = " +// + args[ii].getClass().getTypeName()); +// } +// +// //String rr = mycodes.Combine("\n", args); +// String rr = gson.toJson(args); +// if (need_success) { +// if (mycodes.String_Is_JSONArray(rr)) { +// JsonArray jarr = new JsonArray(); +// jarr.InitializeFromString(rr); +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { jarr }); +// } else if (mycodes.String_Is_JSONObject(rr)) { +// JsonObject jobj = new JsonObject(); +// jobj.InitializeFromString(rr); +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { jobj }); +// } else { +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { rr }); +// } +// } +// +// } +// } +// +// }); + + + return true; + } + return false; + } + + + /** + * Emit JsonArray to target socket + * On Server side, will be received as String in JSON format + * @param emitname : event name + * @param value : JsonArray + * @return true if value can be send + */ + public boolean Emit_JsonArray(String emitname, JsonArray value) { + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_JsonArray(value)) { + _client.emit(emitname, value.getObject()); + return true; + } + return false; + } + + /** + * Emit JsonArray to target socket and wait for acknowledge + * On Server side, will be received as String in JSON format + * On success, will raise event ackevent_success(replyvalue as Object) + * on timeout, will raise event ackevent_timeout(msg as string) + * @param emitname : event name + * @param value : JsonArray + * @param ackevent : prefix of acknowledge event + * @return true if value can be send + */ + public boolean Emit_JsonArray_WithAcknowledge(String emitname, JsonArray value, String ackevent) { + final boolean need_success = bax.subExists(ackevent + "_success"); + final boolean need_timeout = bax.subExists(ackevent + "_timeout"); + if (IsConnected() && mycodes.valid_string(emitname) && mycodes.valid_JsonArray(value) + && mycodes.valid_string(ackevent)) { + + _client.emit(emitname, value.getObject(), new AckWithTimeout(5000) { + + @Override + public void onSuccess(Object... args) { + if (args != null && args.length>0) { + // TODO cek tipe args dengan log + for(int ii = 0; ii < args.length; ii++) { + raise_log("Emit_JsonArray_WithAcknowledge onSuccess, args[" + ii + "] = " + + args[ii].getClass().getTypeName()); + } + + //String rr = mycodes.Combine("\n", args); + String rr = gson.toJson(args); + if (need_success) { + if (mycodes.String_Is_JSONArray(rr)) { + JsonArray jarr = new JsonArray(); + jarr.InitializeFromString(rr); + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { jarr }); + } else if (mycodes.String_Is_JSONObject(rr)) { + JsonObject jobj = new JsonObject(); + jobj.InitializeFromString(rr); + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { jobj }); + } else { + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, + new Object[] { rr }); + } + } + + } + } + + @Override + public void onTimeout() { + if (need_timeout) + bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_timeout", false, new Object[] { + "Emitname=[" + emitname + "] value=[" + value + "] has no reply in 5 seconds" }); + } + + }); + + //TODO kalau sudah benar, hapus ini +// _client.emit(emitname, value.getObject().toString(), new Ack() { +// +// @Override +// public void call(Object... args) { +// if (args != null && args.length>0) { +// // TODO cek tipe args dengan log +// for(int ii = 0; ii < args.length; ii++) { +// raise_log("Emit_JsonArray_WithAcknowledge onSuccess, args[" + ii + "] = " +// + args[ii].getClass().getTypeName()); +// } +// +// //String rr = mycodes.Combine("\n", args); +// String rr = gson.toJson(args); +// if (need_success) { +// if (mycodes.String_Is_JSONArray(rr)) { +// JsonArray jarr = new JsonArray(); +// jarr.InitializeFromString(rr); +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { jarr }); +// } else if (mycodes.String_Is_JSONObject(rr)) { +// JsonObject jobj = new JsonObject(); +// jobj.InitializeFromString(rr); +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { jobj }); +// } else { +// bax.raiseEventFromDifferentThread(Me, null, 0, ackevent + "_success", false, +// new Object[] { rr }); +// } +// } +// +// } +// } +// +// }); + + return true; + } + return false; + } + + /** + * Send simple String to event 'message' + * @param value : String to send + * @return true if can be send + */ + public boolean SendMessage(String value) { + if (IsConnected()) { + if (value instanceof String) { + if (!value.isEmpty()) { + _client.emit(io.socket.engineio.client.Socket.EVENT_MESSAGE, value); + return true; + } else raise_log("SendMessage failed, value is zero length"); + } else raise_log("SendMessage failed, value is not String"); + } else raise_log("SendMessage failed, Socket not connected"); + return false; + } + + private boolean is_correct_eventname(String value) { + if (value instanceof String) { + if (!value.isEmpty()) { + return Pattern.matches("^[a-z0-9_]+$", value);// hanya lowercase dan angka + } + } + return false; + } + + /** + * Get SessionID from socket + * @return empty string if socket not connected + */ + public String getID() { + return _id; + } + + /** + * Get RemoteSocket info, in form of [IP]:[Port] + * @return empty string if socket not connected + */ + public String getRemote() { + return _remote; + } + + + /////////// Events /////////////// + + private void raise_log(String msg) { + if (need_log_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_connected(String id, String remote){ + if (need_connected_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_connected", false, new Object[] {id, remote}); + } + + private void raise_disconnected(String id, String remote, String reason) { + if (need_disconnected_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_disconnected", false, new Object[] {id,remote, reason}); + } + + + private void raise_message(String id, String remote, String value) { + if (need_message_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_message", false, new Object[] {id,remote, value}); + } + + private void raise_reconnecting(String uri, int attempt) { + if (need_reconnecting_event) + bax.raiseEventFromDifferentThread(Me, null, 0, event + "_reconnecting", false, + new Object[] { uri, attempt }); + } + + private void raise_reconnected(String uri, int attempt) { + if (need_reconnected_event) + bax.raiseEventFromDifferentThread(Me, null, 0, event + "_reconnected", false, + new Object[] { uri, attempt }); + } + + /** + * Check if in arguments contain Socket.io.Acknowledge object + * @param objects : object to check + * @return -1 if not found, or index of Acknowledge object + */ + private int have_Acknowledge(Object...objects) { + if (objects!=null) { + int len = objects.length; + if (len>0) { + for(int ii=0;ii0) { + int count = 0; + for (int ii = 0; ii < args.length; ii++) { + Object obj = args[ii]; + if (obj!=null) { + if (obj instanceof Ack) { + break; + } else count++; + } + } + return count; + } + } + return 0; + } +} diff --git a/src/androgpio/customsocket/jUDPSocket.java b/src/androgpio/customsocket/jUDPSocket.java new file mode 100644 index 0000000..8318459 --- /dev/null +++ b/src/androgpio/customsocket/jUDPSocket.java @@ -0,0 +1,329 @@ +package androgpio.customsocket; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; + +//@BA.ShortName("UDPSocket") +@BA.Events(values= { + "log(msg as string)", + "socketstatus(isopen as boolean)", + "newdata(bb() as byte, sourceip as string, sourceport as int)", + "txrxstatistic(txcount as int, txfail as int, rxcount as int, rxfail as int)" +}) + +@Deprecated // Use jUDPSocket2 instead +@BA.Hide +/** + * UDP Socket by me, with more functions + * @author rdkartono + * + */ +public class jUDPSocket { + + private BA ba; + private Object caller; + private String event; + private final Object Me = this; + private boolean need_log_event = false; + private boolean need_socketstatus_event = false; + private boolean need_newdata_event = false; + private boolean need_txrxstatistic_event = false; + private DatagramSocket udp; + private boolean isopened = false; + + private int txfail=0, txcount=0, rxfail=0, rxcount=0; + + /** + * Initialize UDP Socket (made by RDK) + * @param callerobject : caller object + * @param eventname : event name + */ + public void Initialize(BA bax, Object callerobject, String eventname) { + ba = bax; + caller = callerobject; + event = eventname.trim(); + if (ba instanceof BA) { + if (caller != null) { + if (!event.isEmpty()) { + need_log_event = ba.subExists(event+"_log"); + need_socketstatus_event = ba.subExists(event+"_socketstatus"); + need_newdata_event = ba.subExists(event+"_newdata"); + need_txrxstatistic_event = ba.subExists(event+"_txrxstatistic"); + } + } + } + } + + /** + * Check if UDP already opened + * @return true if opened + */ + public boolean getUDPIsOpened() { + return isopened; + } + + /** + * Close UDP Communication + * will raise socketstatus event + */ + public void Close() { + isopened = false; + if (udp instanceof DatagramSocket) { + udp.close(); + } + + udp = null; + raise_socketstatus(false); + } + + /** + * Get IPV4 bytes from ip string + * @param ipstring : ip string to translate + * @return 0.0.0.0 if failed + */ + public byte[] Get_IPV4_Bytes(String ipstring) { + if (!ipstring.isEmpty()) { + try { + InetAddress xx = InetAddress.getByName(ipstring); + return xx.getAddress(); + } catch (UnknownHostException e) { + raise_log("Unable to Get_IPV4_Bytes, UnknownHostException Msg : "+e.getMessage()); + } catch(SecurityException e) { + raise_log("Unable to Get_IPV4_Bytes, SecurityException Msg : "+e.getMessage()); + } + } + return new byte[4]; + } + + /** + * Ping a target IP + * @param ipstring : target IP + * @return true if reachable + */ + public boolean PingTo(String ipstring) { + try { + InetAddress target = InetAddress.getByName(ipstring); + return target.isReachable(1000); + } catch (UnknownHostException e) { + raise_log("Unable to PingTo "+ipstring+", UnknownHostException Msg : "+e.getMessage()); + } catch (IOException e) { + raise_log("Unable to PingTo "+ipstring+", IOException Msg : "+e.getMessage()); + } + return false; + } + + /** + * Get Local MAC address + * @return empty string if failed + */ + public String GetLocalMAC() { + + try { + InetAddress local = InetAddress.getLocalHost(); + NetworkInterface ni = NetworkInterface.getByInetAddress(local); + byte[] result = ni.getHardwareAddress(); + if (result!=null) { + if (result.length>0) { + StringBuilder str = new StringBuilder(); + for(byte xx : result) { + if (str.length()>0) str.append(":"); + int xt = Bit.And(xx, 0xFF); // jadiin positif dulu + if (xt<10) str.append("0"); // kalau di bawah 10, tambahin 0 , biar double digit + str.append(Bit.ToHexString(xt)); + } + return str.toString().toUpperCase(); + } + } + } catch (UnknownHostException e) { + raise_log("Unable to GetLocalMAC, UnknownHostException Msg : "+e.getMessage()); + } catch (SocketException e) { + raise_log("Unable to GetLocalMAC, SocketException Msg : "+e.getMessage()); + } catch(NullPointerException e) { + raise_log("Unable to GetLocalMAC, NullPointerException Msg : "+e.getMessage()); + } + return ""; + } + + /** + * Get Local IP Address + * @return empty string failed + */ + public String GetLocalIP() { + try { + InetAddress local = InetAddress.getLocalHost(); + return local.getHostAddress(); + } catch (UnknownHostException e) { + raise_log("Unable to GetLocalIP, UnknownHostException Msg : "+e.getMessage()); + } + + return ""; + } + + /** + * Get Local Port bounded/opened with UDP Socket + * @return -1 if UDP is not opened yet + */ + public int GetLocalPort() { + if (udp instanceof DatagramSocket) { + return udp.getLocalPort(); + } + return -1; + } + + /** + * Open UDP Socket + * will raise socketstatus event + * @param port : port number to listen + * @param maxpackagesize : max packagesize. Default to 1000 bytes + * @return true if port can be opened + */ + public boolean Open(int port, int maxpackagesize) { + if (maxpackagesize<1) maxpackagesize = 1000; + + if (udp instanceof DatagramSocket) Close(); // kalau sudah pernah open, close dulu + try { + udp = new DatagramSocket(port); + Thread tx = new Thread(new udprun(udp, maxpackagesize)); + tx.start(); + raise_socketstatus(true); + return true; + } catch (SocketException e) { + raise_log("Unable to Open UDP at port "+port+", SocketException Msg : "+e.getMessage()); + raise_socketstatus(false); + } catch(SecurityException e) { + raise_log("Unable to Open UDP at port "+port+", SecurityException Msg : "+e.getMessage()); + raise_socketstatus(false); + } + return false; + } + + private class udprun implements Runnable { + private final int maxudpsize; + private final DatagramSocket udp; + + public udprun(DatagramSocket udpsock, int maxsize) { + udp = udpsock; + maxudpsize = maxsize; + isopened = true; + } + + @Override + public void run() { + + while(isopened) { + if (udp instanceof DatagramSocket) { + byte[] bb = new byte[maxudpsize]; + DatagramPacket xx = new DatagramPacket(bb, maxudpsize); + try { + udp.receive(xx); + // kadang data yang real diterima (getLength), lebih kecil daripada getData + byte[] realdata = Arrays.copyOf(xx.getData(), xx.getLength()); + raise_newdata(realdata, xx.getAddress().getHostAddress(), xx.getPort()); + if (rxcount0) { + if (bb!=null) { + if (bb.length>0) { + if (udp instanceof DatagramSocket) { + try { + InetAddress t_ip = InetAddress.getByName(targetip); + DatagramPacket pp = new DatagramPacket(bb, bb.length, t_ip, targetport); + udp.send(pp); + if (txcount() { + + @Override + public Object[] call() { + boolean success = false; + NetworkInterface nif = null; + try { + if (mycodes.valid_string(localip)) nif = NetworkInterface.getByInetAddress(InetAddress.getByName(localip)); + InetAddress target = InetAddress.getByName(targetip); + success = target.isReachable(nif, 0, waitms < 10 ? 10 : waitms); + } catch (UnknownHostException | SecurityException e) { + // dari InetAddress.getByName + raise_log("PingTo ["+targetip+"] exception : "+e.getMessage()); + } catch (IOException e) { + // dari target.isReachable + raise_log("PingTo ["+targetip+"] exception : "+e.getMessage()); + } + + return new Object[] {targetip, success}; + } + + }); + return sender; + } + + /** + * Get Local IP Addresses in this machine + * @param IPV4Only if true, only return IPV4 address. If false, will return all address + * @return array of ip addresses, in string + */ + public String[] GetLocalIP(final boolean IPV4Only) { + List allip = new ArrayList(); + + try { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while(e.hasMoreElements()) { + NetworkInterface n = e.nextElement(); + Enumeration nn = n.getInetAddresses(); + while(nn.hasMoreElements()) { + InetAddress i = nn.nextElement(); + if (!i.isLoopbackAddress()) { + if (IPV4Only) { + if (i.getAddress().length == 4) { + allip.add(i.getHostAddress()); + } + } else { + allip.add(i.getHostAddress()); + } + } + } + } + } catch (SocketException err) { + raise_log("GetLocalIP exception : "+err.getMessage()); + } + + return allip.toArray(new String[allip.size()]); + } + + /** + * Get all MAC Addresses in this machine + * @return array of MAC addresses, in string + */ + public String[] GetLocalMAC() { + List allmac = new ArrayList(); + + try { + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) { + NetworkInterface n = e.nextElement(); + byte[] mac = n.getHardwareAddress(); + if (mac != null) { + if (mac.length > 0) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mac.length; i++) { + sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : "")); + } + allmac.add(sb.toString()); + } + } + } + } catch (SocketException err) { + raise_log("GetLocalMAC exception : " + err.getMessage()); + } + + return allmac.toArray(new String[allmac.size()]); + } + + /** + * Get Local Port assigned to this UDP Socket + * @return -1 if not available + */ + public int GetLocalPort() { + if (udp!=null) { + return udp.getLocalPort(); + } + return -1; + } + + private void reset_txrxstatistic() { + txcount = 0; + txfail = 0; + rxcount = 0; + rxfail = 0; + } + + /** + * Get Succesfull transmission counter + * @return number of successfull transmission + */ + public int getTX_Sent() { + return txcount; + } + + /** + * Get Failed transmission counter + * @return number of failed transmission + */ + public int getTX_Fail() { + return txfail; + } + + /** + * Get Successfull reception counter + * @return number of successfull reception + */ + public int getRX_Received() { + return rxcount; + } + + /** + * Get Failed reception counter + * @return number of failed reception + */ + public int getRX_Fail() { + return rxfail; + } + + @BA.Hide + /** + * Get DatagramSocket object + * @return DatagramSocket object + */ + public DatagramSocket getDataGramSocket() { + return udp; + } + + /** + * Connect to one remote UDP computer + * will raise event eventname_connectresult(targetip as string, targetport as int, result as boolean) + * on data receiving, will raise event eventname_newdata(bb() as byte, sourceip as string, sourceport as int) + * @param targetip target ip to connect + * @param targetport target port to connect + * @return Object to use in Wait For + */ + public Object ConnectTo(BA ba, final String targetip, final int targetport) { + Object sender = new Object(); + BA.runAsync(ba, sender, event+"_connectresult", new Object[] {targetip, targetport, false}, new Callable() { + + @Override + public Object[] call() { + boolean success = false; + + try { + InetAddress target = InetAddress.getByName(targetip); + DatagramSocket tt = new DatagramSocket(); + tt.connect(target, targetport); + if (tt.isConnected()) { + // Close previous UDP + if (getUDPIsOpened()) { + raise_log("Closing previous UDP Connection"); + Close(); + } + reset_txrxstatistic(); + raise_log("Connected to ["+targetip+":"+targetport+"]"); + raise_txrxstatistic(); + udp = tt; + BA.submitRunnable(new udpreceiver(ba, sender, 1500), null, 0); + success = true; + } else { + tt.close(); + } + } catch(IllegalArgumentException e) { + // dari DatagramSocket.connect + raise_log("ConnectTo ["+targetip+":"+targetport+"] exception : "+e.getMessage()); + } catch (UnknownHostException e) { + // dari InetAddress.getByName + raise_log("ConnectTo ["+targetip+":"+targetport+"] exception : "+e.getMessage()); + } catch (SocketException e) { + // dari DatagramSocket + raise_log("ConnectTo ["+targetip+":"+targetport+"] exception : "+e.getMessage()); + } catch (SecurityException e) { + // dari DatagramSocket.connect + raise_log("ConnectTo ["+targetip+":"+targetport+"] exception : "+e.getMessage()); + } + + return new Object[] {targetip, targetport, success}; + } + + }); + return sender; + } + + /** + * Send data to connected remote + * User must call method ConnectTo first before using this method + * @param bb data to send + * @return true if success + */ + public boolean SendDataToConnectedRemote(byte[] bb) { + if (udp!=null) { + if (!udp.isClosed()) { + if (!udp.isConnected()) { + if (bb!=null) { + if (bb.length>0) { + DatagramPacket dp = new DatagramPacket(bb, bb.length); + try { + udp.send(dp); + txcount++; + raise_txrxstatistic(); + return true; + } catch (IOException e) { + raise_log("SendDataToConnectedRemote failed, IOException Msg : "+e.getMessage()); + } + } else raise_log("SendDataToConnectedRemote failed, data is empty"); + } else raise_log("SendDataToConnectedRemote failed, data is null"); + } else raise_log("SendDataToConnectedRemote failed, UDP is not connected"); + } else raise_log("SendDataToConnectedRemote failed, UDP is closed"); + } else raise_log("SendDataToConnectedRemote failed, UDP is null"); + txfail++; + raise_txrxstatistic(); + return false; + } + + /** + * Send Data to many targets + * will raise event eventname_senddataresult(successtarget() as string, targetport as int, bb() as byte) + * Check at successtarget() to know which target is success + * if failed, successtarget() is null + * @param bb data to send + * @param targetip array of target IP address or host name + * @param targetport target UDP Port + * @return Object to use in Wait For + */ + public Object SendDataToMany(BA ba, final byte[] bb, final String[] targetip, final int targetport) { + Object sender = new Object(); + BA.runAsync(ba, sender, event + "_senddataresult", new Object[] { null, targetport, bb}, + new Callable() { + + @Override + public Object[] call() { + List successtarget = new ArrayList(); + for (String target : targetip) { + if (SendData(bb, target, targetport)) { + successtarget.add(target); + } + } + + return new Object[] { successtarget.size()>0 ? successtarget.toArray(new String[successtarget.size()]) : null, targetport, bb }; + } + + }); + return sender; + } + + /** + * Send Data to one Target + * user must call method Open first before using this method + * @param bb : data to send + * @param targetip : IP address or host name + * @param targetport target UDP Port + * @return true if success + */ + public boolean SendData(byte[] bb, String targetip, int targetport) { + if (mycodes.valid_string(targetip)) { + if (mycodes.valid_portnumber(targetport)) { + if (bb!=null && bb.length>0) { + if (udp!=null) { + if (!udp.isClosed()) { + try { + InetAddress inet = InetAddress.getByName(targetip); + DatagramPacket dp = new DatagramPacket(bb, bb.length, inet, targetport); + udp.send(dp); + txcount++; + raise_txrxstatistic(); + return true; + } catch(SecurityException e) { + // dari InetAddress.getByName + raise_log("SendData to ["+targetip+":"+targetport+"] failed, Exception : "+e.getMessage()); + } catch (UnknownHostException e) { + // dari InetAddress.getByName + raise_log("SendData to ["+targetip+":"+targetport+"] failed, Exception : "+e.getMessage()); + } catch (IOException e) { + // dari udp.send + raise_log("SendData to ["+targetip+":"+targetport+"] failed, Exception : "+e.getMessage()); + } + + } else raise_log("SendData to ["+targetip+":"+targetport+"] failed, UDP is closed"); + } else raise_log("SendData to ["+targetip+":"+targetport+"] failed, UDP is null"); + } else raise_log("SendData to ["+targetip+":"+targetport+"] failed, data is invalid "); + } else raise_log("SendData failed, targetport is invalid"); + } else raise_log("SendData failed, targetip is invalid"); + txfail++; + raise_txrxstatistic(); + return false; + } + + /** + * Open UDP Listening + * on success listening, will raise event eventname_socketstatus(isopen as boolean) + * on data receiving, will raise event eventname_newdata(bb() as byte, sourceip as string, sourceport as int) + * @param localport Local Port to listen + * @param maxpackagesize maximum package size + * @param localip Local IP to bind. Leave it empty/null to bind to any network interface + * @return Object to use in Wait For + */ + public Object Open(BA ba, int localport, final int maxpackagesize, String localip) { + + Object sender = new Object(); + BA.runAsync(ba, sender, event+"_socketstatus", new Object[] {false}, new Callable() { + + @Override + public Object[] call() { + boolean success = false; + InetAddress inet = null; + + try { + DatagramSocket tryudp = null; + if (mycodes.valid_string(localip)) { + inet = InetAddress.getByName(localip); + tryudp = new DatagramSocket(localport, inet); + } else { + tryudp = new DatagramSocket(localport); + } + + if (tryudp!=null) { + success = true; + if (getUDPIsOpened()) { + raise_log("Closing previous UDP Connection"); + Close(); + } + udp = tryudp; + reset_txrxstatistic(); + raise_txrxstatistic(); + raise_log("UDP Opened on port ["+localport+"]"); + BA.submitRunnable(new udpreceiver(ba,sender, maxpackagesize >0 ? maxpackagesize : 1500), null, 0); + } + + } catch(SecurityException e) { + // dari DatagramSocket dan dari InetAddress.getByName + raise_log("Open failed on port ["+localport+"], Exception : "+e.getMessage()); + } catch (SocketException e) { + // dari DatagramSocket + raise_log("Open failed on port ["+localport+"], Exception : "+e.getMessage()); + } catch (UnknownHostException e) { + // dari InetAddress.getByName + raise_log("Open failed on port ["+localport+"], Exception : "+e.getMessage()); + } + + + return new Object[] {success}; + } + + }); + return sender; + } + + + + /** + * Runnable class to receive UDP Data + */ + private class udpreceiver implements Runnable{ + final int maxsize; + final Object msender; + final BA mba; + boolean need_newdata_event = false; + /** + * Initialize UDP Receiver + * @param ba BA Object + * @param sender Sender object to use in Wait For + * @param packagemaxsize maximum package size + */ + public udpreceiver(BA ba, Object sender, int packagemaxsize) { + maxsize = packagemaxsize; + msender = sender; + mba = ba; + if (ba!=null) { + need_newdata_event = ba.subExists(event+"_newdata"); + } + } + @Override + public void run() { + while (getUDPIsOpened()) { + byte[] bb = new byte[maxsize]; + DatagramPacket dp = new DatagramPacket(bb, bb.length); + try { + udp.receive(dp); + rxcount++; + raise_txrxstatistic(); + byte[] data = new byte[dp.getLength()]; + System.arraycopy(dp.getData(), 0, data, 0, dp.getLength()); + String sourceip = dp.getAddress().getHostAddress(); + int sourceport = dp.getPort(); + if (need_newdata_event) mba.raiseEventFromDifferentThread(msender, null, 0, event+"_newdata", false, new Object[] {data, sourceip, sourceport }); + + } catch (IOException e) { + rxfail++; + raise_txrxstatistic(); + raise_log("UDPReceiver failed, IOException Msg : " + e.getMessage()); + } + } + + } + + } + + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_txrxstatistic() { + if (need_txrxstatistic_event) + ba.raiseEventFromDifferentThread(Me, null, 0, event + "_txrxstatistic", false, + new Object[] { txcount, txfail, rxcount, rxfail }); + } + + +} diff --git a/src/androgpio/gps/AdzanCalculator.java b/src/androgpio/gps/AdzanCalculator.java new file mode 100644 index 0000000..03ab557 --- /dev/null +++ b/src/androgpio/gps/AdzanCalculator.java @@ -0,0 +1,72 @@ +package androgpio.gps; + + + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.List; +@BA.ShortName("AdzanCalculator") +public class AdzanCalculator { + + private PrayTime pt; + + /** + * Initialize Adzan Calculator dengan parameter + * time format : 24H + * metode Safii + * + */ + public void Initialize() { + pt = new PrayTime(); + pt.setTimeFormat(pt.getTime24()); + pt.setCalcMethod(pt.getCustom()); // belum tau + pt.setAsrJuristic(pt.getShafii()); // Ashar Juristic --> Safii , dah betul + pt.setAdjustHighLats(pt.getAngleBased()); + int[] offsets = {0, 0, 0, 0, 0, 0, 0}; // {Fajr,Sunrise,Dhuhr,Asr,Sunset,Maghrib,Isha} + pt.tune(offsets); + } + + /** + * Ambil konstanta nama waktu sholat + * @return List + */ + public List GetPrayerNames(){ + List result = new List(); + result.Initialize(); + for(String xx : pt.getTimeNames()) { + result.Add(xx); + } + return result; + } + + /** + * Ambil waktu sholat + * @param year tahun + * @param month bulan + * @param day hari + * @param latitude latitude + * @param longitude longitude + * @param TimeZone timezone + * @return List + */ + public List GetPrayerTime(int year, int month, int day, double latitude, double longitude, double TimeZone){ + // batasi latitude dan longitude untuk 1 digit di belakang koma + List result = new List(); + result.Initialize(); + for(String xx:pt.getDatePrayerTimes(year, month, day, convert_1_desimal(latitude), convert_1_desimal(longitude), TimeZone)) { + result.Add(xx); + } + return result; + } + + + private double convert_1_desimal(double source) { + int xx = (int)(source * 10); + return xx/10.0; + } + + @SuppressWarnings("unused") + private double convert_2_desimal(double source) { + int xx = (int)(source * 100); + return xx / 100.0; + } +} diff --git a/src/androgpio/gps/DataKota.java b/src/androgpio/gps/DataKota.java new file mode 100644 index 0000000..2e1a8a4 --- /dev/null +++ b/src/androgpio/gps/DataKota.java @@ -0,0 +1,39 @@ +package androgpio.gps; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("DataKota") +public class DataKota { + public final String NamaKota; + public final double Latitude; + public final double Longitude; + /** + * Initialize DataKota with empty value + */ + public DataKota() { + this.NamaKota=""; + this.Latitude=0; + this.Longitude=0; + } + + @BA.Hide + /** + * Initialize DataKota with specific value + * @param namakota dalam string + * @param latitude dalam double + * @param longitude dalam double + */ + public DataKota(String namakota, double latitude, double longitude) { + this.NamaKota = namakota; + this.Latitude = latitude; + this.Longitude = longitude; + } + + /** + * Cek apakah ada data + * @return true kalau ada data + */ + public boolean HaveValue() { + return !NamaKota.isEmpty(); + } +} diff --git a/src/androgpio/gps/GpsGPGGA.java b/src/androgpio/gps/GpsGPGGA.java new file mode 100644 index 0000000..8e56048 --- /dev/null +++ b/src/androgpio/gps/GpsGPGGA.java @@ -0,0 +1,163 @@ +package androgpio.gps; + +import java.util.ArrayList; +import java.util.List; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Common; + +@BA.ShortName("GpsGPGGA") +public class GpsGPGGA { + + public String invalidmsg = ""; + public byte UTC_Hour = -1; + public byte UTC_Minute = -1; + public byte UTC_Second = -1; + public double Latitude = 0; + public double Longitude = 0; + public double HDOP = -1; + public boolean PositionFixed = false; + public int Satellites = -1; + public double Altitude = -1; + public double GeoID_Height = -1; + + public GpsGPGGA(String msg) { + String[] partB = msg.split(","); // pisahkan konten berdasar koma + if (partB.length<13) { + invalidmsg = "GPGGA Split is less than 13"; + return; + } + + List err = new ArrayList(); + + // waktu hhmmss.sss + byte[] utc = new byte[] {0,0,0}; + if (mycodes.Gps_UTCTime(partB[1],utc)) { + UTC_Hour = utc[0]; + UTC_Minute = utc[1]; + UTC_Second = utc[2]; + } else { + err.add("GPGGA dont have Time"); + } + + // Latitude + double[] result; + result = new double[] {0}; + if (mycodes.Gps_Latitude_formatchange(partB[2], result)) { + Latitude = result[0]; + } else { + err.add("GPGGA Invalid Latitude : "+partB[2]); + } + + // Latitude polarity, N or S + switch(mycodes.Gps_Char(partB[3])) { + case 'N' : + Latitude = mycodes.SetPositive(Latitude); // hasil harus positif + break; + case 'S' : + Latitude = mycodes.SetNegative(Latitude); // hasil harus negatif + break; + default : + Latitude = 0; // tidak jelas + err.add("GPGGA Invalid Latitude Polarity : "+partB[3]); + break; + } + + // Longitude + result = new double[] {0}; + if (mycodes.Gps_Longitude_formatchange(partB[4], result)) { + Longitude = result[0]; + } else { + err.add("GPGGA Invalid Longitude : "+partB[4]); + } + + // Longitude polarity, W or S + + switch(mycodes.Gps_Char(partB[5])) { + case 'W' : + Longitude = mycodes.SetNegative(Longitude); // hasil harus negatif + break; + case 'E' : + Longitude = mycodes.SetPositive(Longitude); // hasil harus positif + break; + default : + Longitude = 0; // tidak jelas + err.add("GPGGA Invalid Longitude Polarity : "+partB[5]); + break; + } + + // Position Fix, 0 / 1 / 2 + try { + int posfix = Integer.parseInt(partB[6]); + if (posfix<1) PositionFixed = false; else PositionFixed = true; + } catch(NumberFormatException e) { + err.add("GPGGA Invalid PositionFix : "+partB[6]); + } + + // Number of satelites + try { + int satfix = Integer.parseInt(partB[7]); + Satellites = satfix; + } catch(NumberFormatException e) { + err.add("GPGGA Invalid Satelite : "+partB[7]); + + } + + // HDOP di index 8 + try { + double hd = Double.parseDouble(partB[8]); + HDOP = hd; + }catch(NumberFormatException e) { + err.add("GPGGA Invalid HDOP : "+partB[8]); + } + + // Altitude , nilai di 9, M di 10 + try { + double alt = Double.parseDouble(partB[9]); + Altitude = alt; + } catch(NumberFormatException e) { + err.add("GPGGA Invalid Altitude : "+partB[9]); + } + + if (mycodes.Gps_Char(partB[10])!='M') { + err.add("GPGGA Altitude M not found"); + } + + // GeoID_height, nilai di 11, M di 12 + try { + double geoid = Double.parseDouble(partB[11]); + GeoID_Height = geoid; + } catch(NumberFormatException e) { + err.add("GPGGA Invalid Altitude : "+partB[11]); + } + + if (mycodes.Gps_Char(partB[12])!='M') { + err.add("GPGGA GeoID M is not found"); + } + + invalidmsg = mycodes.Gps_ErrList_to_String(err); + } + + public boolean isValid() { + if (invalidmsg=="") return true; else return false; + } + + /** + * Get UTC Time String in format hh:mm:ss + * @return UTC Time String in format hh:mm:ss, or empty string if invalid data + */ + public String UTC_Time_hhmmss() { + if (isValid()) { + StringBuilder str = new StringBuilder(); + str.append(Common.NumberFormat(UTC_Hour, 2, 0)); + str.append(":"); + str.append(Common.NumberFormat(UTC_Minute, 2, 0)); + str.append(":"); + str.append(Common.NumberFormat(UTC_Second,2,0)); + return str.toString(); + } else return ""; + } + + +} diff --git a/src/androgpio/gps/GpsGPGLL.java b/src/androgpio/gps/GpsGPGLL.java new file mode 100644 index 0000000..a2fe206 --- /dev/null +++ b/src/androgpio/gps/GpsGPGLL.java @@ -0,0 +1,121 @@ +package androgpio.gps; + +import java.util.ArrayList; +import java.util.List; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Common; + +@BA.ShortName("GpsGPGLL") +public class GpsGPGLL { + public String invalidmsg = ""; + public double Latitude = -1; + public double Longitude = -1; + public byte UTC_Hour = -1; + public byte UTC_Minute = -1; + public byte UTC_Second = -1; + public boolean PositionFixed = false; + public GpsGPGLL(String msg) { + String[] partB = msg.split(","); + if (partB.length<7) { + invalidmsg = "GPGLL split less than 7"; + return; + } + + List err = new ArrayList(); + + double[] result; + // Latitude + result = new double[] {0}; + if (mycodes.Gps_Latitude_formatchange(partB[1], result)) { + Latitude = result[0]; + } else { + err.add("GPGLL Invalid Latitude : "+partB[1]); + } + + // Latitude polarity, N or S + switch(mycodes.Gps_Char(partB[2])) { + case 'N' : + Latitude = mycodes.SetPositive(Latitude); // hasil harus positif + break; + case 'S' : + Latitude = mycodes.SetNegative(Latitude); // hasil harus negatif + break; + default : + Latitude = 0; // gak jelas + err.add("GPGLL Invalid Latitude Polarity : "+partB[2]); + break; + } + + + // Longitude + result = new double[] {0}; + if (mycodes.Gps_Longitude_formatchange(partB[3], result)) { + Longitude = result[0]; + } else { + err.add("GPGLL Invalid Longitude : "+partB[3]); + } + + // Longitude polarity, W or S + switch(mycodes.Gps_Char(partB[4])) { + case 'W' : + Longitude = mycodes.SetNegative(Longitude); // hasil harus negatif + break; + case 'E' : + Longitude = mycodes.SetPositive(Longitude); // hasil harus positif + break; + default : + Longitude = 0; + err.add("GPGLL Invalid Longitude Polarity : "+partB[4]); + break; + } + + // UTC Time + byte[] utc = new byte[] {0,0,0}; + if (mycodes.Gps_UTCTime(partB[5], utc)) { + UTC_Hour = utc[0]; + UTC_Minute = utc[1]; + UTC_Second = utc[2]; + } else { + err.add("GPGLL dont have Time"); + + } + + // Fixed Status + switch(mycodes.Gps_Char(partB[6])) { + case 'A' : + PositionFixed = true; + break; + case 'V' : + PositionFixed = false; + break; + default : + err.add("GPGLL Invalid PositionFix : "+partB[6]); + break; + } + + invalidmsg = mycodes.Gps_ErrList_to_String(err); + + } + + public boolean isValid() { + if (invalidmsg=="") return true; else return false; + } + + /** + * Get UTC Time String in format hh:mm:ss + * @return UTC Time String in format hh:mm:ss, or empty string if invalid data + */ + public String UTC_Time_hhmmss() { + if (isValid()) { + StringBuilder str = new StringBuilder(); + str.append(Common.NumberFormat(UTC_Hour, 2, 0)); + str.append(":"); + str.append(Common.NumberFormat(UTC_Minute, 2, 0)); + str.append(":"); + str.append(Common.NumberFormat(UTC_Second,2,0)); + return str.toString(); + } else return ""; + } +} diff --git a/src/androgpio/gps/GpsGPGSA.java b/src/androgpio/gps/GpsGPGSA.java new file mode 100644 index 0000000..89ca9fd --- /dev/null +++ b/src/androgpio/gps/GpsGPGSA.java @@ -0,0 +1,98 @@ +package androgpio.gps; + +import java.util.ArrayList; +import java.util.List; + +import anywheresoftware.b4a.BA; +import androgpio.mycodes; + +@BA.ShortName("GpsGPGSA") +public class GpsGPGSA { + public String invalidmsg = ""; + public String Mode = ""; + public String LockMode = ""; + public int[] SatelitteID = new int[] {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; + public double PDOP = -1; + public double HDOP = -1; + public double VDOP = -1; + + public GpsGPGSA(String msg) { + String[] partB = msg.split(","); + if (partB.length<18) { + invalidmsg = "GPGSA Split less than 18"; + return; + } + + List err = new ArrayList(); + switch(mycodes.Gps_Char(partB[1])) { + case 'M' : + Mode = "Manual"; + break; + case 'A' : + Mode = "Automatic"; + break; + default : + Mode = ""; + err.add("GPGSA Mode is invalid : "+partB[1]); + break; + } + + int[] intresult = new int[] {0}; + if (mycodes.Gps_Int(partB[2], intresult)) { + switch(intresult[0]) { + case 1 : + LockMode = "No Fix"; + break; + case 2 : + LockMode = "2D"; + break; + case 3 : + LockMode = "3D"; + break; + default : + LockMode = ""; + err.add("GPGSA LockMode is invalid : "+partB[2]); + break; + } + } else { + err.add("GPGSA Lockmode is invalid : "+partB[2]); + } + + // satelite , 12 unit + for (int jj=0;jj<12;jj++) { + if (mycodes.Gps_Int(partB[3+jj], intresult)) { + SatelitteID[jj] = intresult[0]; + } else SatelitteID[jj] = -1; + } + + double[] doubleresult = new double[] {0}; + + // PDOP = 15 + if (mycodes.Gps_Double(partB[15], doubleresult)) { + PDOP = doubleresult[0]; + } else { + err.add("GPGSA Invalid PDOP : "+partB[15]); + } + + // HDOP = 16 + if (mycodes.Gps_Double(partB[16], doubleresult)) { + HDOP = doubleresult[0]; + } else { + err.add("GPGSA Invalid HDOP : "+partB[16]); + } + + // VDOP = 17 + if (mycodes.Gps_Double(partB[17], doubleresult)) { + VDOP = doubleresult[0]; + } else { + err.add("GPGSA Invalid VDOP : "+partB[17]); + + } + + invalidmsg = mycodes.Gps_ErrList_to_String(err); + } + + public boolean isValid() { + if (invalidmsg=="") return true; else return false; + } +} diff --git a/src/androgpio/gps/GpsGPRMC.java b/src/androgpio/gps/GpsGPRMC.java new file mode 100644 index 0000000..9e7341a --- /dev/null +++ b/src/androgpio/gps/GpsGPRMC.java @@ -0,0 +1,194 @@ +package androgpio.gps; + +import java.util.ArrayList; +import java.util.List; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Common; +import androgpio.mycodes; + +@BA.ShortName("GpsGPRMC") +public class GpsGPRMC { + public String invalidmsg = ""; + public byte UTC_Hour = -1; + public byte UTC_Minute = -1; + public byte UTC_Second = -1; + public byte UTC_Day = -1; + public byte UTC_Month = -1; + public int UTC_Year = -1; + public boolean PositionFix = false; + public double Latitude = 0; + public double Longitude = 0; + + /** + * In Knot + */ + public double Ground_Speed = 0; + + /** + * In Degree + */ + public double Ground_Course = 0; + + /** + * In Degree + */ + public double Magnetic_Variation = 0; + + + public GpsGPRMC(String msg) { + + String[] partB = msg.split(","); + + if (partB.length<12) { + invalidmsg = "GPRMC Split less than 12"; + return; + } + + List err = new ArrayList(); + + byte[] utctime = new byte[] {0,0,0}; + if (mycodes.Gps_UTCTime(partB[1], utctime)) { + UTC_Hour = utctime[0]; + UTC_Minute = utctime[1]; + UTC_Second = utctime[2]; + } else { + err.add("GPRMC dont have Time") ; + } + + switch(mycodes.Gps_Char(partB[2])) { + case 'A' : + PositionFix = true; + break; + case 'V' : + PositionFix = false; + break; + default : + err.add("GPRMC Invalid Position Fix : "+partB[2]); + break; + } + + double[] result; + result = new double[] {0}; + if (mycodes.Gps_Latitude_formatchange(partB[3], result)) { + Latitude = result[0]; + } else { + err.add("GPRMC Invalid Latitude : "+partB[3]); + } + + switch(mycodes.Gps_Char(partB[4])) { + case 'N' : + Latitude = mycodes.SetPositive(Latitude); //hasil harus positif + break; + case 'S' : + Latitude = mycodes.SetNegative(Latitude); // hasil harus negatif; + break; + default : + Latitude = 0; + err.add("GPRMC Invalid Latitude Polarity : "+partB[4]); + break; + } + + result = new double[] {0}; + if (mycodes.Gps_Longitude_formatchange(partB[5], result)) { + Longitude = result[0]; + } else { + err.add("GPRMC Invalid Longitude : "+partB[5]); + } + + switch(mycodes.Gps_Char(partB[6])) { + case 'W' : + Longitude = mycodes.SetNegative(Longitude); // hasil harus negative + break; + case 'E' : + Longitude = mycodes.SetPositive(Longitude); // hasil positive + break; + default : + err.add("GPRMC Invalid Longitude Polarity : "+partB[6]); + break; + } + + try { + double knot = Double.parseDouble(partB[7]); + Ground_Speed = knot; + } catch( NumberFormatException e) { + err.add("GPRMC invalid Ground Speed "+partB[7]); + Ground_Speed = -1; + } + + try { + double course = Double.parseDouble(partB[8]); + Ground_Course = course; + } catch( NumberFormatException e) { + err.add("GPRMC invalid Ground Course "+partB[8]); + Ground_Course =-1; + } + + byte[] utcdate = new byte[] {0,0,0}; + if (mycodes.Gps_UTCDate(partB[9], utcdate)) { + UTC_Day = utcdate[0]; + UTC_Month = utcdate[1]; + UTC_Year = 2000 + utcdate[2]; + } else { + err.add("GPRMC dont have Date"); + } + + try { + double mag = Double.parseDouble(partB[10]); + Magnetic_Variation = mag; + }catch( NumberFormatException e) { + err.add("GPRMC invalid Magnetic Variation "+partB[10]); + } + + switch(mycodes.Gps_Char(partB[11])) { + case 'E' : + Magnetic_Variation = mycodes.SetPositive(Magnetic_Variation); + break; + case 'W' : + Magnetic_Variation = mycodes.SetNegative(Magnetic_Variation); + break; + default : + err.add("GPRMC invalid Magnetic Polarity : "+partB[11]); + break; + } + + invalidmsg = mycodes.Gps_ErrList_to_String(err); + } + + public boolean isValid() { + if (invalidmsg=="") return true; else return false; + } + + /** + * Get UTC Time String in format hh:mm:ss + * @return UTC Time String in format hh:mm:ss, or empty string if invalid data + */ + public String UTC_Time_hhmmss() { + if (PositionFix) { + StringBuilder str = new StringBuilder(); + str.append(Common.NumberFormat(UTC_Hour, 2, 0)); + str.append(":"); + str.append(Common.NumberFormat(UTC_Minute, 2, 0)); + str.append(":"); + str.append(Common.NumberFormat(UTC_Second,2,0)); + return str.toString(); + } else return ""; + } + + /** + * Get UTC Date String in format dd-MM-yyyy + * @return UTC Time String in format dd-MM-yyyy , or empty string if invalid data + */ + public String UTC_Date_ddMMyyyy() { + if (PositionFix) { + StringBuilder str = new StringBuilder(); + str.append(Common.NumberFormat(UTC_Day, 2, 0)); + str.append("-"); + str.append(Common.NumberFormat(UTC_Month, 2, 0)); + str.append("-"); + str.append(UTC_Year); + return str.toString(); + } else return ""; + } + +} diff --git a/src/androgpio/gps/GpsGPVTG.java b/src/androgpio/gps/GpsGPVTG.java new file mode 100644 index 0000000..f2ded91 --- /dev/null +++ b/src/androgpio/gps/GpsGPVTG.java @@ -0,0 +1,83 @@ +package androgpio.gps; + +import java.util.ArrayList; +import java.util.List; + +import anywheresoftware.b4a.BA; +import androgpio.mycodes; + +@BA.ShortName("GpsGPVTG") +public class GpsGPVTG { + public String invalidmsg = ""; + public double Course_Heading = 0; + public double Magnetic_Heading = 0; + public double Horizontal_Knot_Speed = 0; + public double Horizontal_KM_Speed = 0; + + public GpsGPVTG(String msg) { + String partB[] = msg.split(","); + if (partB.length<8) { + invalidmsg = "GPVTG Split less than 8"; + return; + } + + List err = new ArrayList(); + + double[] result = new double[] {0}; + if (mycodes.Gps_Double(partB[1], result)) { + Course_Heading = result[0]; + } else { + Course_Heading = 0; + err.add("GPVTG invalid Course Heading : "+partB[1]); + } + + if (mycodes.Gps_Char(partB[2])!='T') { + Course_Heading = 0; + err.add("GPVTG Course Heading code is not T"); + } + + + if (mycodes.Gps_Double(partB[3], result)) { + Magnetic_Heading = result[0]; + } else { + Magnetic_Heading = 0; + err.add("GPVTG invalid Magnetic Heading : "+partB[3]); + } + + if (mycodes.Gps_Char(partB[4])!='M') { + Magnetic_Heading = 0; + err.add("GPVTG Magnetic Heading code is not M"); + } + + + if (mycodes.Gps_Double(partB[5], result)) { + Horizontal_Knot_Speed = result[0]; + } else { + Horizontal_Knot_Speed = 0; + err.add("GPVTG Invalid Horizontal Knot Speed : "+partB[5]); + } + + if (mycodes.Gps_Char(partB[6])!='N') { + Horizontal_Knot_Speed = 0; + err.add("GPVTG Horizontal Knot Speed code is not N"); + } + + if (mycodes.Gps_Double(partB[7], result)) { + Horizontal_KM_Speed = result[0]; + } else { + Horizontal_KM_Speed = 0; + err.add("GPVTG Invalid Horizontal KM Speed : "+partB[7]); + } + + if (mycodes.Gps_Char(partB[8])!='K') { + Horizontal_KM_Speed = 0; + err.add("GPVTG Horizontal KM Speed code is not K"); + } + + invalidmsg = mycodes.Gps_ErrList_to_String(err); + } + + public boolean isValid() { + if (invalidmsg=="") return true; else return false; + } +} diff --git a/src/androgpio/gps/GpsReceiver.java b/src/androgpio/gps/GpsReceiver.java new file mode 100644 index 0000000..3a83124 --- /dev/null +++ b/src/androgpio/gps/GpsReceiver.java @@ -0,0 +1,489 @@ +package androgpio.gps; + +import java.util.ArrayList; +import java.util.List; + +import com.fazecast.jSerialComm.SerialPort; +import com.fazecast.jSerialComm.SerialPortEvent; +import com.fazecast.jSerialComm.SerialPortInvalidPortException; +import com.fazecast.jSerialComm.SerialPortMessageListener; + +import anywheresoftware.b4a.BA; + +@BA.ShortName("GpsReceiver") +@BA.Events(values= { + "log(msg as string)", + "newgpstime(value as string)", + "newgpsdate(value as string)", + "newgpslatitude(value as double)", + "newgpslongitude(value as double)", + + "rawdata(gpsmsg as string)", + "ggadata(data as GpsGPGGA)", + "glldata(data as GpsGPGLL)", +// "gsadata(data as GpsGPGSA)", +// "vtgdata(data as GpsGPVTG)", + "rmcdata(data as GpsGPRMC)" +}) + +@BA.DependsOn(values = { "jSerialComm-2.9.2" }) + +public class GpsReceiver { + + private BA bax; + private String event = ""; + private Object caller; + private final Object Me = this; + private boolean need_log_event = false; + private boolean need_rawdata_event = false; + private boolean need_ggadata_event = false; + private boolean need_newgpstime_event = false; + private boolean need_newgpsdate_event = false; + private boolean need_newgpslatitude_event = false; + private boolean need_newgpslongitude_event = false; + + private boolean need_glldata_event = false; + //private boolean need_gsadata_event = false; // tidak terlalu penting + private boolean need_rmcdata_event = false; + //private boolean need_vtgdata_event = false; // tidak terlalu penting + + private SerialPort myport; + private boolean initialized = false; + private long rxcount = 0; + + private String last_gps_time = ""; + private String last_gps_date = ""; + private double last_gps_latitude = 0; + private double last_gps_longitude = 0; + private boolean gga_fixed = false; + private boolean gll_fixed = false; + private boolean rmc_fixed = false; + private int last_gps_day = 0; + private int last_gps_month = 0; + private int last_gps_year = 0; + private int last_gps_hour = 0; + private int last_gps_minute = 0; + private int last_gps_second = 0; + + + /** + * Initialize GpsReceiver + * @param eventname : eventname string + */ + public void Initialize(BA ba, Object caller, String eventname ) { + bax = ba; + event = eventname; + this.caller = caller; + + if (bax!=null) { + if (this.caller!=null) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(event+"_log"); + need_rawdata_event = bax.subExists(event+"_rawdata"); + need_ggadata_event = bax.subExists(event+"_ggadata"); + + need_glldata_event = bax.subExists(event+"_glldata"); + //need_gsadata_event = bax.subExists(event+"_gsadata"); + need_rmcdata_event = bax.subExists(event+"_rmcdata"); + //need_vtgdata_event = bax.subExists(event+"_vtgdata"); + + need_newgpstime_event = bax.subExists(event+"_newgpstime"); + need_newgpsdate_event = bax.subExists(event+"_newgpsdate"); + need_newgpslatitude_event = bax.subExists(event+"_newgpslatitude"); + need_newgpslongitude_event = bax.subExists(event+"_newgpslongitude"); + } + } + } + + + if (SerialPort.getVersion()!="") initialized = true; else initialized = false; + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + ClosePort(); + } + }); + } + + + + /** + * Check if GpsReceiver is initialized + * @return true if initialized + */ + public boolean IsInitialized() { + return initialized; + } + + /** + * Get all available port names in this system + * @return List of Serial Port Names + */ + public List GetPortNames(){ + List result = new ArrayList(); + if (initialized) { + for(SerialPort xx:SerialPort.getCommPorts()) { + if (xx != null) { + String portname = xx.getSystemPortName(); + if (portname!="") { + result.add(portname); + } + } + } + } else { + raise_log("GetPortNames failed, SerialPort Library not ready"); + } + return result; + } + + /** + * Close GPS Receiving Port + */ + public void ClosePort() { + if (myport instanceof SerialPort) { + myport.removeDataListener(); + + myport.closePort(); + myport = null; + } + + } + + /** + * How many GPS data received + * @return how many gps data received + */ + public long getRXCounter() { + return rxcount; + } + + public double GetLatitude_rounded(int fractiondigits) { + if (fractiondigits<1) fractiondigits = 1; + if (fractiondigits>4) fractiondigits = 4; + switch(fractiondigits) { + case 1 : + return Math.round(last_gps_latitude * 10.0) / 10.0; + case 2 : + return Math.round(last_gps_latitude * 100.0) / 100.0; + case 3 : + return Math.round(last_gps_latitude * 1000.0) / 1000.0; + default : + return Math.round(last_gps_latitude * 10000.0) / 10000.0; + } + } + + public double GetLongitude_rounded(int fractiondigits) { + if (fractiondigits<1) fractiondigits = 1; + if (fractiondigits>4) fractiondigits = 4; + switch(fractiondigits) { + case 1 : + return Math.round(last_gps_longitude * 10.0) / 10.0; + case 2 : + return Math.round(last_gps_longitude * 100.0) / 100.0; + case 3 : + return Math.round(last_gps_longitude * 1000.0) / 1000.0; + default : + return Math.round(last_gps_longitude * 10000.0) / 10000.0; + } + } + + /** + * Get last GPS Latitude + * @return Latitude value in double + */ + public double getLatitude() { + return last_gps_latitude; + } + + /** + * Get Last GPS Longitude + * @return Longitude value in double + */ + public double getLongitude() { + return last_gps_longitude; + } + + /** + * Get Last GPS Time + * @return time in format hh:mm:ss + */ + public String getTime_hhmmss() { + return last_gps_time; + } + + /** + * Get Last GPS Date + * @return date in format dd-MM-yyyy + */ + public String getDate_ddMMyyyy() { + return last_gps_date; + } + + /** + * Get GPS Fixed status + * @return true if GPS is fixed + */ + public boolean getGPS_Fixed() { + return (gga_fixed || gll_fixed || rmc_fixed); + } + + public int getUTC_Day() { + return last_gps_day; + } + + public int getUTC_Month() { + return last_gps_month; + } + + public int getUTC_Year() { + return last_gps_year; + } + + public int getUTC_Hour() { + return last_gps_hour; + } + + public int getUTC_Minute() { + return last_gps_minute; + } + + public int getUTC_Second() { + return last_gps_second; + } + + private void setUTC_Time(int hour, int minute, int second) { + last_gps_hour = hour; + last_gps_minute = minute; + last_gps_second = second; + } + + private void setUTC_Date(int day, int month, int year) { + last_gps_day = day; + last_gps_month = month; + last_gps_year = year; + } + + /** + * Open Serial Port for Gps Communication + * @param serialname : serialport name + * @param baudrate : default is 9600 + * @return true if success + */ + public boolean OpenPort(String serialname, int baudrate) { + rxcount = 0; + if (baudrate==0) baudrate = 9600; // default baudrate + if (initialized) { + try { + myport = SerialPort.getCommPort(serialname); + if (myport.openPort()) { + myport.setBaudRate(baudrate); + if (myport.addDataListener(new SerialPortMessageListener() { + + @Override + public int getListeningEvents() { + return SerialPort.LISTENING_EVENT_DATA_RECEIVED; + } + + @Override + public void serialEvent(SerialPortEvent arg0) { + if (arg0==null) return; + byte[] bb = arg0.getReceivedData(); + if (bb==null) return; + if (bb.length<1) return; + String msg = new String(bb); + + int indexmulai = msg.indexOf("$GP"); + if (indexmulai<0) return; + + String vv = msg.substring(indexmulai); + if (vv.startsWith("$GPGSV")) return; // ignore GPGSV + raise_rawdata(vv); + process_data(vv); + rxcount+=1; + + } + + @Override + public boolean delimiterIndicatesEndOfMessage() { + // delimiter ada di belakang + return true; + } + + @Override + public byte[] getMessageDelimiter() { + // delimiter adalah CR LF + return new byte[] {(byte) 13, (byte) 10}; + } + + })) + return true; + } + } catch(SerialPortInvalidPortException e) { + raise_exception("Failed to GetCommPort",e); + } + } + + return false; + } + + /** + * Check if SerialPort is opened and ready + * @return true if opened and ready + */ + public boolean PortIsReady() { + if (myport instanceof SerialPort) { + return myport.isOpen(); + } + return false; + } + + private void process_data(String vv) { + String[] gps_split = vv.split("\\*"); + if (gps_split.length!=2) return; + String gpsmsg = gps_split[0]; + + if (gpsmsg.startsWith("$GPGGA")) { + GpsGPGGA datagga = new GpsGPGGA(gpsmsg); + // supaya kalau udah fix, ada UTC Latitude, Longitude, UTC Time , dah bisa raise event + gga_fixed = datagga.PositionFixed; + if (gga_fixed) + { + raise_ggadata(datagga); + String time = datagga.UTC_Time_hhmmss(); + if (last_gps_time.compareTo(time)!=0) { + last_gps_time = time; + setUTC_Time(datagga.UTC_Hour, datagga.UTC_Minute, datagga.UTC_Second); + raise_newgpstime(last_gps_time); + } + if (last_gps_latitude != datagga.Latitude) { + last_gps_latitude = datagga.Latitude; + raise_newgpslatitude(last_gps_latitude); + } + if (last_gps_longitude != datagga.Longitude) { + last_gps_longitude = datagga.Longitude; + raise_newgpslongitude(last_gps_longitude); + } + } + return; + } + + // ini tidak perlu, gak penting + /* + * if (gpsmsg.startsWith("$GPGSA")) { GpsGPGSA datagsa = new GpsGPGSA(gpsmsg); + * if (datagsa.isValid()) { raise_gsadata(datagsa); } else { + * BA.Log(datagsa.invalidmsg); } return; } + */ + + if (gpsmsg.startsWith("$GPGLL")) { + GpsGPGLL datagll = new GpsGPGLL(gpsmsg); + // supaya kalau udah fix, ada UTC Latitude, Longitude, UTC Time, dah bisa raise event + gll_fixed = datagll.PositionFixed; + if (gll_fixed) + { + raise_glldata(datagll); + String time = datagll.UTC_Time_hhmmss(); + if (last_gps_time.compareTo(time)!=0) { + last_gps_time = time; + setUTC_Time(datagll.UTC_Hour, datagll.UTC_Minute, datagll.UTC_Second); + raise_newgpstime(last_gps_time); + } + if (last_gps_latitude != datagll.Latitude) { + last_gps_latitude = datagll.Latitude; + raise_newgpslatitude(last_gps_latitude); + } + if (last_gps_longitude != datagll.Longitude) { + last_gps_longitude = datagll.Longitude; + raise_newgpslongitude(last_gps_longitude); + } + } + return; + } + + if (gpsmsg.startsWith("$GPRMC")) { + GpsGPRMC datarmc = new GpsGPRMC(gpsmsg); + // GPRMC Groud Course, Magnetic Variation, dan Magnetic Polarity sering loss + // jadi tidak pakai isValid, tapi pakai PositionFix aja + rmc_fixed = datarmc.PositionFix; + if (rmc_fixed) + { + raise_rmcdata(datarmc); + String time = datarmc.UTC_Time_hhmmss(); + if (last_gps_time.compareTo(time)!=0) { + last_gps_time = time; + setUTC_Time(datarmc.UTC_Hour, datarmc.UTC_Minute, datarmc.UTC_Second); + raise_newgpstime(last_gps_time); + } + + String date = datarmc.UTC_Date_ddMMyyyy(); + if (last_gps_date.compareTo(date)!=0) { + last_gps_date = date; + setUTC_Date(datarmc.UTC_Day, datarmc.UTC_Month, datarmc.UTC_Year); + raise_newgpsdate(last_gps_date); + } + if (last_gps_latitude != datarmc.Latitude) { + last_gps_latitude = datarmc.Latitude; + raise_newgpslatitude(last_gps_latitude); + } + if (last_gps_longitude != datarmc.Longitude) { + last_gps_longitude = datarmc.Longitude; + raise_newgpslongitude(last_gps_longitude); + } + } + return; + } + + // ini tidak perlu, gak penting + /* + * if (gpsmsg.startsWith("$GPVTG")) { GpsGPVTG datavtg = new GpsGPVTG(gpsmsg); + * if (datavtg.isValid()) { raise_vtgdata(datavtg); } else { + * BA.Log(datavtg.invalidmsg); } return; } + */ + } + + private void raise_exception(String prefix, Exception e) { + raise_log(prefix+", Msg : "+e.getMessage()+", Caused : "+e.getCause()); + } + + private void raise_log(String msg) { + if (need_log_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_rawdata(String vv) { + if (need_rawdata_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_rawdata", false, new Object[] {vv}); + } + + private void raise_ggadata(GpsGPGGA vv) { + if (need_ggadata_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_ggadata", false, new Object[] {vv}); + } + + + + private void raise_glldata(GpsGPGLL vv) { + if (need_glldata_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_glldata", false, new Object[] {vv}); + } + + + + private void raise_rmcdata(GpsGPRMC vv) { + if (need_rmcdata_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_rmcdata", false, new Object[] {vv}); + } + + + + private void raise_newgpstime(String value) { + if (need_newgpstime_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newgpstime", false, new Object[] {value}); + } + + private void raise_newgpsdate(String value) { + if (need_newgpsdate_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newgpsdate", false, new Object[] {value}); + } + + private void raise_newgpslatitude(double value) { + if (need_newgpslatitude_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newgpslatitude", false, new Object[] {value}); + } + + private void raise_newgpslongitude(double value) { + if (need_newgpslongitude_event) bax.raiseEventFromDifferentThread(Me, null, 0, event+"_newgpslongitude", false, new Object[] {value}); + } +} diff --git a/src/androgpio/gps/KotaIndonesia.java b/src/androgpio/gps/KotaIndonesia.java new file mode 100644 index 0000000..cd4ed26 --- /dev/null +++ b/src/androgpio/gps/KotaIndonesia.java @@ -0,0 +1,103 @@ +package androgpio.gps; + +import java.io.BufferedReader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import androgpio.customsocket.JsonArray; +import androgpio.customsocket.JsonObject; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.objects.collections.List; + +@BA.ShortName("KotaIndonesia") +/** + * Daftar Kota dan Kabupaten di Indonesia + * Source : https://github.com/benangmerah/wilayah/blob/master/datasources/daftar-nama-daerah.csv + * Tanggal copy : 5 Agustus 2022 + * @author rdkartono + * + */ +public class KotaIndonesia { + private JsonArray ja; + public KotaIndonesia() { + ja = new JsonArray(); + ja.InitializeEmpty(); + try(InputStream is = KotaIndonesia.class.getResourceAsStream("kotaindonesiaminified.json")) { + BufferedReader bf = new BufferedReader(new InputStreamReader(is)); + String ss; + int ii=0; + do { + ss = bf.readLine(); + if (ss != null) { + // difilter minimal 3 huruf. Gak mungkin JSON string kurang dari 3 huruf + if (ss.length()>3) { + + JsonObject jo = new JsonObject(); + if (jo.InitializeFromString(ss)) { + ja.Put(jo); + ii+=1; + //BA.Log("Reading "+jo.ToString()); + } + } + } + + } while (ss != null); + is.close(); + BA.Log("Finish reading kotaindonesiaminified.json, count = "+ii); + } + catch(IOException|NullPointerException e) { + BA.Log("Exception on constructor KotaIndonesia, Msg : "+e.getMessage()+", Cause : "+e.getCause()); + } + + } + + /** + * Get JsonArray object + * @return JsonArray value + */ + public JsonArray getJsonArray() { + return ja; + } + + /** + * Search city by name + * @param searchtext nama kota, atau sebagian nama kota. Kasih string kosong untuk ambil semua + * @return List berisi object DataKota + */ + public List Search(String searchtext) { + List result = new List(); + result.Initialize(); + + if (ja!=null) { + if (ja.GetSize()>0) { + for(int ii=0;ii timeNames; + private String InvalidTime; // The string used for invalid times + // --------------------- Technical Settings -------------------- + private int numIterations; // number of iterations needed to compute times + // ------------------- Calc Method Parameters -------------------- + private HashMap methodParams; + + /* + * this.methodParams[methodNum] = new Array(fa, ms, mv, is, iv); + * + * fa : fajr angle ms : maghrib selector (0 = angle; 1 = minutes after + * sunset) mv : maghrib parameter value (in angle or minutes) is : isha + * selector (0 = angle; 1 = minutes after maghrib) iv : isha parameter value + * (in angle or minutes) + */ + @SuppressWarnings("unused") + private double[] prayerTimesCurrent; + private int[] offsets; + + public PrayTime() { + // Initialize vars + + this.setCalcMethod(0); + this.setAsrJuristic(0); + this.setDhuhrMinutes(0); + this.setAdjustHighLats(1); + this.setTimeFormat(0); + + // Calculation Methods + this.setJafari(0); // Ithna Ashari + this.setKarachi(1); // University of Islamic Sciences, Karachi + this.setISNA(2); // Islamic Society of North America (ISNA) + this.setMWL(3); // Muslim World League (MWL) + this.setMakkah(4); // Umm al-Qura, Makkah + this.setEgypt(5); // Egyptian General Authority of Survey + this.setTehran(6); // Institute of Geophysics, University of Tehran + this.setCustom(7); // Custom Setting + + // Juristic Methods + this.setShafii(0); // Shafii (standard) + this.setHanafi(1); // Hanafi + + // Adjusting Methods for Higher Latitudes + this.setNone(0); // No adjustment + this.setMidNight(1); // middle of night + this.setOneSeventh(2); // 1/7th of night + this.setAngleBased(3); // angle/60th of night + + // Time Formats + this.setTime24(0); // 24-hour format + this.setTime12(1); // 12-hour format + this.setTime12NS(2); // 12-hour format with no suffix + this.setFloating(3); // floating point number + + // Time Names + timeNames = new ArrayList(); + timeNames.add("Fajr"); + timeNames.add("Sunrise"); + timeNames.add("Dhuhr"); + timeNames.add("Asr"); + timeNames.add("Sunset"); + timeNames.add("Maghrib"); + timeNames.add("Isha"); + + InvalidTime = "-----"; // The string used for invalid times + + // --------------------- Technical Settings -------------------- + + this.setNumIterations(1); // number of iterations needed to compute + // times + + // ------------------- Calc Method Parameters -------------------- + + // Tuning offsets {fajr, sunrise, dhuhr, asr, sunset, maghrib, isha} + offsets = new int[7]; + offsets[0] = 0; + offsets[1] = 0; + offsets[2] = 0; + offsets[3] = 0; + offsets[4] = 0; + offsets[5] = 0; + offsets[6] = 0; + + /* + * + * fa : fajr angle ms : maghrib selector (0 = angle; 1 = minutes after + * sunset) mv : maghrib parameter value (in angle or minutes) is : isha + * selector (0 = angle; 1 = minutes after maghrib) iv : isha parameter + * value (in angle or minutes) + */ + methodParams = new HashMap(); + + // Jafari + double[] Jvalues = {16,0,4,0,14}; + methodParams.put(Integer.valueOf(this.getJafari()), Jvalues); + + // Karachi + double[] Kvalues = {18,1,0,0,18}; + methodParams.put(Integer.valueOf(this.getKarachi()), Kvalues); + + // ISNA + double[] Ivalues = {15,1,0,0,15}; + methodParams.put(Integer.valueOf(this.getISNA()), Ivalues); + + // MWL + double[] MWvalues = {18,1,0,0,17}; + methodParams.put(Integer.valueOf(this.getMWL()), MWvalues); + + // Makkah + double[] MKvalues = {18.5,1,0,1,90}; + methodParams.put(Integer.valueOf(this.getMakkah()), MKvalues); + + // Egypt + double[] Evalues = {19.5,1,0,0,17.5}; + methodParams.put(Integer.valueOf(this.getEgypt()), Evalues); + + // Tehran + double[] Tvalues = {17.7,0,4.5,0,14}; + methodParams.put(Integer.valueOf(this.getTehran()), Tvalues); + + // Custom + double[] Cvalues = {20,0,0,0,18}; // fajar = 20 degree, magrib selector = angle, magrib parameter = 0, isha selector = angle, isha parameter = 18 degree + methodParams.put(Integer.valueOf(this.getCustom()), Cvalues); + + } + + // ---------------------- Trigonometric Functions ----------------------- + // range reduce angle in degrees. + private double fixangle(double a) { + + a = a - (360 * (Math.floor(a / 360.0))); + + a = a < 0 ? (a + 360) : a; + + return a; + } + + // range reduce hours to 0..23 + private double fixhour(double a) { + a = a - 24.0 * Math.floor(a / 24.0); + a = a < 0 ? (a + 24) : a; + return a; + } + + // radian to degree + private double radiansToDegrees(double alpha) { + return ((alpha * 180.0) / Math.PI); + } + + // deree to radian + private double DegreesToRadians(double alpha) { + return ((alpha * Math.PI) / 180.0); + } + + // degree sin + private double dsin(double d) { + return (Math.sin(DegreesToRadians(d))); + } + + // degree cos + private double dcos(double d) { + return (Math.cos(DegreesToRadians(d))); + } + + // degree tan + private double dtan(double d) { + return (Math.tan(DegreesToRadians(d))); + } + + // degree arcsin + private double darcsin(double x) { + double val = Math.asin(x); + return radiansToDegrees(val); + } + + // degree arccos + private double darccos(double x) { + double val = Math.acos(x); + return radiansToDegrees(val); + } + + // degree arctan + @SuppressWarnings("unused") + private double darctan(double x) { + double val = Math.atan(x); + return radiansToDegrees(val); + } + + // degree arctan2 + private double darctan2(double y, double x) { + double val = Math.atan2(y, x); + return radiansToDegrees(val); + } + + // degree arccot + private double darccot(double x) { + double val = Math.atan2(1.0, x); + return radiansToDegrees(val); + } + + // ---------------------- Time-Zone Functions ----------------------- + // compute local time-zone for a specific date + @SuppressWarnings("unused") + private double getTimeZone1() { + TimeZone timez = TimeZone.getDefault(); + double hoursDiff = (timez.getRawOffset() / 1000.0) / 3600; + return hoursDiff; + } + + // compute base time-zone of the system + @SuppressWarnings("unused") + private double getBaseTimeZone() { + TimeZone timez = TimeZone.getDefault(); + double hoursDiff = (timez.getRawOffset() / 1000.0) / 3600; + return hoursDiff; + + } + + // detect daylight saving in a given date + @SuppressWarnings("unused") + private double detectDaylightSaving() { + TimeZone timez = TimeZone.getDefault(); + double hoursDiff = timez.getDSTSavings(); + return hoursDiff; + } + + // ---------------------- Julian Date Functions ----------------------- + // calculate julian date from a calendar date + private double julianDate(int year, int month, int day) { + + if (month <= 2) { + year -= 1; + month += 12; + } + double A = Math.floor(year / 100.0); + + double B = 2 - A + Math.floor(A / 4.0); + + double JD = Math.floor(365.25 * (year + 4716)) + + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5; + + return JD; + } + + // convert a calendar date to julian date (second method) + @SuppressWarnings("unused") + private double calcJD(int year, int month, int day) { + double J1970 = 2440588.0; + @SuppressWarnings("deprecation") + Date date = new Date(year, month-1, day); + + double ms = date.getTime(); // # of milliseconds since midnight Jan 1, + // 1970 + double days = Math.floor(ms / (1000.0 * 60.0 * 60.0 * 24.0)); + return J1970 + days - 0.5; + + } + + // ---------------------- Calculation Functions ----------------------- + // References: + // http://www.ummah.net/astronomy/saltime + // http://aa.usno.navy.mil/faq/docs/SunApprox.html + // compute declination angle of sun and equation of time + private double[] sunPosition(double jd) { + + double D = jd - 2451545; + double g = fixangle(357.529 + 0.98560028 * D); + double q = fixangle(280.459 + 0.98564736 * D); + double L = fixangle(q + (1.915 * dsin(g)) + (0.020 * dsin(2 * g))); + + // double R = 1.00014 - 0.01671 * [self dcos:g] - 0.00014 * [self dcos: + // (2*g)]; + double e = 23.439 - (0.00000036 * D); + double d = darcsin(dsin(e) * dsin(L)); + double RA = (darctan2((dcos(e) * dsin(L)), (dcos(L))))/ 15.0; + RA = fixhour(RA); + double EqT = q/15.0 - RA; + double[] sPosition = new double[2]; + sPosition[0] = d; + sPosition[1] = EqT; + + return sPosition; + } + + // compute equation of time + private double equationOfTime(double jd) { + double eq = sunPosition(jd)[1]; + return eq; + } + + // compute declination angle of sun + private double sunDeclination(double jd) { + double d = sunPosition(jd)[0]; + return d; + } + + // compute mid-day (Dhuhr, Zawal) time + private double computeMidDay(double t) { + double T = equationOfTime(this.getJDate() + t); + double Z = fixhour(12 - T); + return Z; + } + + // compute time for a given angle G + private double computeTime(double G, double t) { + + double D = sunDeclination(this.getJDate() + t); + double Z = computeMidDay(t); + double Beg = -dsin(G) - dsin(D) * dsin(this.getLat()); + double Mid = dcos(D) * dcos(this.getLat()); + double V = darccos(Beg/Mid)/15.0; + + return Z + (G > 90 ? -V : V); + } + + // compute the time of Asr + // Shafii: step=1, Hanafi: step=2 + private double computeAsr(double step, double t) { + double D = sunDeclination(this.getJDate() + t); + double G = -darccot(step + dtan(Math.abs(this.getLat() - D))); + return computeTime(G, t); + } + + // ---------------------- Misc Functions ----------------------- + // compute the difference between two times + private double timeDiff(double time1, double time2) { + return fixhour(time2 - time1); + } + + // -------------------- Interface Functions -------------------- + // return prayer times for a given date + public ArrayList getDatePrayerTimes(int year, int month, int day, + double latitude, double longitude, double tZone) { + this.setLat(latitude); + this.setLng(longitude); + this.setTimeZone(tZone); + this.setJDate(julianDate(year, month, day)); + double lonDiff = longitude / (15.0 * 24.0); + this.setJDate(this.getJDate() - lonDiff); + return computeDayTimes(); + } + + // return prayer times for a given date + public ArrayList getPrayerTimes(Calendar date, double latitude, + double longitude, double tZone) { + + int year = date.get(Calendar.YEAR); + int month = date.get(Calendar.MONTH); + int day = date.get(Calendar.DATE); + + return getDatePrayerTimes(year, month+1, day, latitude, longitude, tZone); + } + + // set custom values for calculation parameters + private void setCustomParams(double[] params) { + + for (int i = 0; i < 5; i++) { + if (params[i] == -1) { + params[i] = methodParams.get(this.getCalcMethod())[i]; + methodParams.put(this.getCustom(), params); + } else { + methodParams.get(this.getCustom())[i] = params[i]; + } + } + this.setCalcMethod(this.getCustom()); + } + + // set the angle for calculating Fajr + public void setFajrAngle(double angle) { + double[] params = {angle, -1, -1, -1, -1}; + setCustomParams(params); + } + + // set the angle for calculating Maghrib + public void setMaghribAngle(double angle) { + double[] params = {-1, 0, angle, -1, -1}; + setCustomParams(params); + + } + + // set the angle for calculating Isha + public void setIshaAngle(double angle) { + double[] params = {-1, -1, -1, 0, angle}; + setCustomParams(params); + + } + + // set the minutes after Sunset for calculating Maghrib + public void setMaghribMinutes(double minutes) { + double[] params = {-1, 1, minutes, -1, -1}; + setCustomParams(params); + + } + + // set the minutes after Maghrib for calculating Isha + public void setIshaMinutes(double minutes) { + double[] params = {-1, -1, -1, 1, minutes}; + setCustomParams(params); + + } + + // convert double hours to 24h format + public String floatToTime24(double time) { + + String result; + + if (Double.isNaN(time)) { + return InvalidTime; + } + + time = fixhour(time + 0.5 / 60.0); // add 0.5 minutes to round + int hours = (int)Math.floor(time); + double minutes = Math.floor((time - hours) * 60.0); + + if ((hours >= 0 && hours <= 9) && (minutes >= 0 && minutes <= 9)) { + result = "0" + hours + ":0" + Math.round(minutes); + } else if ((hours >= 0 && hours <= 9)) { + result = "0" + hours + ":" + Math.round(minutes); + } else if ((minutes >= 0 && minutes <= 9)) { + result = hours + ":0" + Math.round(minutes); + } else { + result = hours + ":" + Math.round(minutes); + } + return result; + } + + // convert double hours to 12h format + public String floatToTime12(double time, boolean noSuffix) { + + if (Double.isNaN(time)) { + return InvalidTime; + } + + time = fixhour(time + 0.5 / 60); // add 0.5 minutes to round + int hours = (int)Math.floor(time); + double minutes = Math.floor((time - hours) * 60); + String suffix, result; + if (hours >= 12) { + suffix = "pm"; + } else { + suffix = "am"; + } + hours = ((((hours+ 12) -1) % (12))+ 1); + /*hours = (hours + 12) - 1; + int hrs = (int) hours % 12; + hrs += 1;*/ + if (noSuffix == false) { + if ((hours >= 0 && hours <= 9) && (minutes >= 0 && minutes <= 9)) { + result = "0" + hours + ":0" + Math.round(minutes) + " " + + suffix; + } else if ((hours >= 0 && hours <= 9)) { + result = "0" + hours + ":" + Math.round(minutes) + " " + suffix; + } else if ((minutes >= 0 && minutes <= 9)) { + result = hours + ":0" + Math.round(minutes) + " " + suffix; + } else { + result = hours + ":" + Math.round(minutes) + " " + suffix; + } + + } else { + if ((hours >= 0 && hours <= 9) && (minutes >= 0 && minutes <= 9)) { + result = "0" + hours + ":0" + Math.round(minutes); + } else if ((hours >= 0 && hours <= 9)) { + result = "0" + hours + ":" + Math.round(minutes); + } else if ((minutes >= 0 && minutes <= 9)) { + result = hours + ":0" + Math.round(minutes); + } else { + result = hours + ":" + Math.round(minutes); + } + } + return result; + + } + + // convert double hours to 12h format with no suffix + public String floatToTime12NS(double time) { + return floatToTime12(time, true); + } + + // ---------------------- Compute Prayer Times ----------------------- + // compute prayer times at given julian date + private double[] computeTimes(double[] times) { + + double[] t = dayPortion(times); + + double Fajr = this.computeTime( + 180 - methodParams.get(this.getCalcMethod())[0], t[0]); + + double Sunrise = this.computeTime(180 - 0.833, t[1]); + + double Dhuhr = this.computeMidDay(t[2]); + double Asr = this.computeAsr(1 + this.getAsrJuristic(), t[3]); + double Sunset = this.computeTime(0.833, t[4]); + + double Maghrib = this.computeTime( + methodParams.get(this.getCalcMethod())[2], t[5]); + double Isha = this.computeTime( + methodParams.get(this.getCalcMethod())[4], t[6]); + + double[] CTimes = {Fajr, Sunrise, Dhuhr, Asr, Sunset, Maghrib, Isha}; + + return CTimes; + + } + + // compute prayer times at given julian date + private ArrayList computeDayTimes() { + double[] times = {5, 6, 12, 13, 18, 18, 18}; // default times + + for (int i = 1; i <= this.getNumIterations(); i++) { + times = computeTimes(times); + } + + times = adjustTimes(times); + times = tuneTimes(times); + + return adjustTimesFormat(times); + } + + // adjust times in a prayer time array + private double[] adjustTimes(double[] times) { + for (int i = 0; i < times.length; i++) { + times[i] += this.getTimeZone() - this.getLng() / 15; + } + + times[2] += this.getDhuhrMinutes() / 60; // Dhuhr + if (methodParams.get(this.getCalcMethod())[1] == 1) // Maghrib + { + times[5] = times[4] + methodParams.get(this.getCalcMethod())[2]/ 60; + } + if (methodParams.get(this.getCalcMethod())[3] == 1) // Isha + { + times[6] = times[5] + methodParams.get(this.getCalcMethod())[4]/ 60; + } + + if (this.getAdjustHighLats() != this.getNone()) { + times = adjustHighLatTimes(times); + } + + return times; + } + + // convert times array to given time format + private ArrayList adjustTimesFormat(double[] times) { + + ArrayList result = new ArrayList(); + + if (this.getTimeFormat() == this.getFloating()) { + for (double time : times) { + result.add(String.valueOf(time)); + } + return result; + } + + for (int i = 0; i < 7; i++) { + if (this.getTimeFormat() == this.getTime12()) { + result.add(floatToTime12(times[i], false)); + } else if (this.getTimeFormat() == this.getTime12NS()) { + result.add(floatToTime12(times[i], true)); + } else { + result.add(floatToTime24(times[i])); + } + } + return result; + } + + // adjust Fajr, Isha and Maghrib for locations in higher latitudes + private double[] adjustHighLatTimes(double[] times) { + double nightTime = timeDiff(times[4], times[1]); // sunset to sunrise + + // Adjust Fajr + double FajrDiff = nightPortion(methodParams.get(this.getCalcMethod())[0]) * nightTime; + + if (Double.isNaN(times[0]) || timeDiff(times[0], times[1]) > FajrDiff) { + times[0] = times[1] - FajrDiff; + } + + // Adjust Isha + double IshaAngle = (methodParams.get(this.getCalcMethod())[3] == 0) ? methodParams.get(this.getCalcMethod())[4] : 18; + double IshaDiff = this.nightPortion(IshaAngle) * nightTime; + if (Double.isNaN(times[6]) || this.timeDiff(times[4], times[6]) > IshaDiff) { + times[6] = times[4] + IshaDiff; + } + + // Adjust Maghrib + double MaghribAngle = (methodParams.get(this.getCalcMethod())[1] == 0) ? methodParams.get(this.getCalcMethod())[2] : 4; + double MaghribDiff = nightPortion(MaghribAngle) * nightTime; + if (Double.isNaN(times[5]) || this.timeDiff(times[4], times[5]) > MaghribDiff) { + times[5] = times[4] + MaghribDiff; + } + + return times; + } + + // the night portion used for adjusting times in higher latitudes + private double nightPortion(double angle) { + double calc = 0; + + if (adjustHighLats == AngleBased) + calc = (angle)/60.0; + else if (adjustHighLats == MidNight) + calc = 0.5; + else if (adjustHighLats == OneSeventh) + calc = 0.14286; + + return calc; + } + + // convert hours to day portions + private double[] dayPortion(double[] times) { + for (int i = 0; i < 7; i++) { + times[i] /= 24; + } + return times; + } + + // Tune timings for adjustments + // Set time offsets + public void tune(int[] offsetTimes) { + + for (int i = 0; i < offsetTimes.length; i++) { // offsetTimes length + // should be 7 in order + // of Fajr, Sunrise, + // Dhuhr, Asr, Sunset, + // Maghrib, Isha + this.offsets[i] = offsetTimes[i]; + } + } + + private double[] tuneTimes(double[] times) { + for (int i = 0; i < times.length; i++) { + times[i] = times[i] + this.offsets[i] / 60.0; + } + + return times; + } + + /** + * @param args + */ + /* + * public static void main(String[] args) { double latitude = -37.823689; double + * longitude = 145.121597; double timezone = 10; // Test Prayer times here + * PrayTime prayers = new PrayTime(); + * + * prayers.setTimeFormat(prayers.Time12); prayers.setCalcMethod(prayers.Jafari); + * prayers.setAsrJuristic(prayers.Shafii); + * prayers.setAdjustHighLats(prayers.AngleBased); int[] offsets = {0, 0, 0, 0, + * 0, 0, 0}; // {Fajr,Sunrise,Dhuhr,Asr,Sunset,Maghrib,Isha} + * prayers.tune(offsets); + * + * Date now = new Date(); Calendar cal = Calendar.getInstance(); + * cal.setTime(now); + * + * ArrayList prayerTimes = prayers.getPrayerTimes(cal, latitude, + * longitude, timezone); ArrayList prayerNames = prayers.getTimeNames(); + * + * for (int i = 0; i < prayerTimes.size(); i++) { + * System.out.println(prayerNames.get(i) + " - " + prayerTimes.get(i)); } + * + * } + */ + + public int getCalcMethod() { + return calcMethod; + } + + public void setCalcMethod(int calcMethod) { + this.calcMethod = calcMethod; + } + + public int getAsrJuristic() { + return asrJuristic; + } + + public void setAsrJuristic(int asrJuristic) { + this.asrJuristic = asrJuristic; + } + + public int getDhuhrMinutes() { + return dhuhrMinutes; + } + + public void setDhuhrMinutes(int dhuhrMinutes) { + this.dhuhrMinutes = dhuhrMinutes; + } + + public int getAdjustHighLats() { + return adjustHighLats; + } + + public void setAdjustHighLats(int adjustHighLats) { + this.adjustHighLats = adjustHighLats; + } + + public int getTimeFormat() { + return timeFormat; + } + + public void setTimeFormat(int timeFormat) { + this.timeFormat = timeFormat; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLng() { + return lng; + } + + public void setLng(double lng) { + this.lng = lng; + } + + public double getTimeZone() { + return timeZone; + } + + public void setTimeZone(double timeZone) { + this.timeZone = timeZone; + } + + public double getJDate() { + return JDate; + } + + public void setJDate(double jDate) { + JDate = jDate; + } + + public int getJafari() { + return Jafari; + } + + private void setJafari(int jafari) { + Jafari = jafari; + } + + public int getKarachi() { + return Karachi; + } + + private void setKarachi(int karachi) { + Karachi = karachi; + } + + public int getISNA() { + return ISNA; + } + + private void setISNA(int iSNA) { + ISNA = iSNA; + } + + public int getMWL() { + return MWL; + } + + private void setMWL(int mWL) { + MWL = mWL; + } + + public int getMakkah() { + return Makkah; + } + + private void setMakkah(int makkah) { + Makkah = makkah; + } + + public int getEgypt() { + return Egypt; + } + + private void setEgypt(int egypt) { + Egypt = egypt; + } + + public int getCustom() { + return Custom; + } + + private void setCustom(int custom) { + Custom = custom; + } + + private int getTehran() { + return Tehran; + } + + private void setTehran(int tehran) { + Tehran = tehran; + } + + public int getShafii() { + return Shafii; + } + + private void setShafii(int shafii) { + Shafii = shafii; + } + + @SuppressWarnings("unused") + private int getHanafi() { + return Hanafi; + } + + private void setHanafi(int hanafi) { + Hanafi = hanafi; + } + + private int getNone() { + return None; + } + + private void setNone(int none) { + None = none; + } + + @SuppressWarnings("unused") + private int getMidNight() { + return MidNight; + } + + private void setMidNight(int midNight) { + MidNight = midNight; + } + + @SuppressWarnings("unused") + private int getOneSeventh() { + return OneSeventh; + } + + private void setOneSeventh(int oneSeventh) { + OneSeventh = oneSeventh; + } + + public int getAngleBased() { + return AngleBased; + } + + private void setAngleBased(int angleBased) { + AngleBased = angleBased; + } + + + public int getTime24() { + return Time24; + } + + private void setTime24(int time24) { + Time24 = time24; + } + + int getTime12() { + return Time12; + } + + private void setTime12(int time12) { + Time12 = time12; + } + + private int getTime12NS() { + return Time12NS; + } + + private void setTime12NS(int time12ns) { + Time12NS = time12ns; + } + + private int getFloating() { + return Floating; + } + + private void setFloating(int floating) { + Floating = floating; + } + + private int getNumIterations() { + return numIterations; + } + + private void setNumIterations(int numIterations) { + this.numIterations = numIterations; + } + + public ArrayList getTimeNames() { + return timeNames; + } +} \ No newline at end of file diff --git a/src/androgpio/gps/kotaindonesiaminified.json b/src/androgpio/gps/kotaindonesiaminified.json new file mode 100644 index 0000000..6d0e26c --- /dev/null +++ b/src/androgpio/gps/kotaindonesiaminified.json @@ -0,0 +1,545 @@ +[ +{"nid":1,"parent_nid":0,"name":"Provinsi Aceh","serial":11,"type":1,"latitude":4.695135,"longitude":96.7493993,"status":1}, +{"nid":2,"parent_nid":0,"name":"Provinsi Sumatera Utara","serial":12,"type":1,"latitude":2.1153547,"longitude":99.5450974,"status":1}, +{"nid":3,"parent_nid":0,"name":"Provinsi Sumatera Barat","serial":13,"type":1,"latitude":-0.7399397,"longitude":100.8000051,"status":1}, +{"nid":4,"parent_nid":0,"name":"Provinsi Riau","serial":14,"type":1,"latitude":0.2933469,"longitude":101.7068294,"status":1}, +{"nid":5,"parent_nid":0,"name":"Provinsi Jambi","serial":15,"type":1,"latitude":-1.4851831,"longitude":102.4380581,"status":1}, +{"nid":6,"parent_nid":0,"name":"Provinsi Sumatera Selatan","serial":16,"type":1,"latitude":-3.3194374,"longitude":103.914399,"status":1}, +{"nid":7,"parent_nid":0,"name":"Provinsi Bengkulu","serial":17,"type":1,"latitude":-3.5778471,"longitude":102.3463875,"status":1}, +{"nid":8,"parent_nid":0,"name":"Provinsi Lampung","serial":18,"type":1,"latitude":-4.5585849,"longitude":105.4068079,"status":1}, +{"nid":9,"parent_nid":0,"name":"Provinsi Kepulauan Bangka Belitung","serial":19,"type":1,"latitude":-2.7410513,"longitude":106.4405872,"status":1}, +{"nid":10,"parent_nid":0,"name":"Provinsi Kepulauan Riau","serial":21,"type":1,"latitude":3.9456514,"longitude":108.1428669,"status":1}, +{"nid":11,"parent_nid":0,"name":"Provinsi DKI Jakarta","serial":31,"type":1,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":12,"parent_nid":0,"name":"Provinsi Jawa Barat","serial":32,"type":1,"latitude":-7.090911,"longitude":107.668887,"status":1}, +{"nid":13,"parent_nid":0,"name":"Provinsi Jawa Tengah","serial":33,"type":1,"latitude":-7.150975,"longitude":110.1402594,"status":1}, +{"nid":14,"parent_nid":0,"name":"Provinsi DI Yogyakarta","serial":34,"type":1,"latitude":-7.8753849,"longitude":110.4262088,"status":1}, +{"nid":15,"parent_nid":0,"name":"Provinsi Jawa Timur","serial":35,"type":1,"latitude":-7.5360639,"longitude":112.2384017,"status":1}, +{"nid":16,"parent_nid":0,"name":"Provinsi Banten","serial":36,"type":1,"latitude":-6.4058172,"longitude":106.0640179,"status":1}, +{"nid":17,"parent_nid":0,"name":"Provinsi Bali","serial":51,"type":1,"latitude":-8.4095178,"longitude":115.188916,"status":1}, +{"nid":18,"parent_nid":0,"name":"Provinsi Nusa Tenggara Barat","serial":52,"type":1,"latitude":-8.6529334,"longitude":117.3616476,"status":1}, +{"nid":19,"parent_nid":0,"name":"Provinsi Nusa Tenggara Timur","serial":53,"type":1,"latitude":-8.6573819,"longitude":121.0793705,"status":1}, +{"nid":20,"parent_nid":0,"name":"Provinsi Kalimantan Barat","serial":61,"type":1,"latitude":-0.2787808,"longitude":111.4752851,"status":1}, +{"nid":21,"parent_nid":0,"name":"Provinsi Kalimantan Tengah","serial":62,"type":1,"latitude":-1.6814878,"longitude":113.3823545,"status":1}, +{"nid":22,"parent_nid":0,"name":"Provinsi Kalimantan Selatan","serial":63,"type":1,"latitude":-3.0926415,"longitude":115.2837585,"status":1}, +{"nid":23,"parent_nid":0,"name":"Provinsi Kalimantan Timur","serial":64,"type":1,"latitude":1.6406296,"longitude":116.419389,"status":1}, +{"nid":24,"parent_nid":0,"name":"Provinsi Sulawesi Utara","serial":71,"type":1,"latitude":0.6246932,"longitude":123.9750018,"status":1}, +{"nid":25,"parent_nid":0,"name":"Provinsi Sulawesi Tengah","serial":72,"type":1,"latitude":-1.4300254,"longitude":121.4456179,"status":1}, +{"nid":26,"parent_nid":0,"name":"Provinsi Sulawesi Selatan","serial":73,"type":1,"latitude":-3.6687994,"longitude":119.9740534,"status":1}, +{"nid":27,"parent_nid":0,"name":"Provinsi Sulawesi Tenggara","serial":74,"type":1,"latitude":-4.14491,"longitude":122.174605,"status":1}, +{"nid":28,"parent_nid":0,"name":"Provinsi Gorontalo","serial":75,"type":1,"latitude":0.6999372,"longitude":122.4467238,"status":1}, +{"nid":29,"parent_nid":0,"name":"Provinsi Sulawesi Barat","serial":76,"type":1,"latitude":-2.8441371,"longitude":119.2320784,"status":1}, +{"nid":30,"parent_nid":0,"name":"Provinsi Maluku","serial":81,"type":1,"latitude":-3.2384616,"longitude":130.1452734,"status":1}, +{"nid":31,"parent_nid":0,"name":"Provinsi Maluku Utara","serial":82,"type":1,"latitude":1.5709993,"longitude":127.8087693,"status":1}, +{"nid":32,"parent_nid":0,"name":"Provinsi Papua Barat","serial":91,"type":1,"latitude":-1.3361154,"longitude":133.1747162,"status":1}, +{"nid":33,"parent_nid":0,"name":"Provinsi Papua","serial":94,"type":1,"latitude":-4.269928,"longitude":138.0803529,"status":1}, +{"nid":562,"parent_nid":74,"name":"Kota Tangerang Selatan","serial":0,"type":2,"latitude":-6.2888889,"longitude":106.7180556,"status":1}, +{"nid":821,"parent_nid":15,"name":"Kabupaten Banyuwangi","serial":1501,"type":2,"latitude":-8.2186111,"longitude":114.3669444,"status":1}, +{"nid":823,"parent_nid":15,"name":"Kabupaten Madiun","serial":1502,"type":2,"latitude":-7.627753,"longitude":111.505483,"status":1}, +{"nid":824,"parent_nid":15,"name":"Kabupaten Ponorogo","serial":1503,"type":2,"latitude":-7.867827,"longitude":111.466003,"status":1}, +{"nid":825,"parent_nid":15,"name":"Kabupaten Magetan","serial":1504,"type":2,"latitude":-7.6493413,"longitude":111.3381593,"status":1}, +{"nid":826,"parent_nid":15,"name":"Kabupaten Pacitan","serial":1505,"type":2,"latitude":-8.204614,"longitude":111.08769,"status":1}, +{"nid":827,"parent_nid":15,"name":"Kabupaten Ngawi","serial":1506,"type":2,"latitude":-7.38993,"longitude":111.46193,"status":1}, +{"nid":828,"parent_nid":15,"name":"Kabupaten Bangkalan","serial":1507,"type":2,"latitude":-7.0306912,"longitude":112.7450068,"status":1}, +{"nid":829,"parent_nid":15,"name":"Kabupaten Kediri","serial":1508,"type":2,"latitude":-7.809356,"longitude":112.032356,"status":1}, +{"nid":830,"parent_nid":15,"name":"Kabupaten Bondowoso","serial":1509,"type":2,"latitude":-7.917704,"longitude":113.813483,"status":1}, +{"nid":831,"parent_nid":15,"name":"Kabupaten Blitar","serial":1510,"type":2,"latitude":-8.1014419,"longitude":112.162762,"status":1}, +{"nid":832,"parent_nid":15,"name":"Kabupaten Trenggalek","serial":1511,"type":2,"latitude":-8.05,"longitude":111.7166667,"status":1}, +{"nid":833,"parent_nid":15,"name":"Kabupaten Tulungagung","serial":1512,"type":2,"latitude":-8.0666667,"longitude":111.9,"status":1}, +{"nid":834,"parent_nid":15,"name":"Kabupaten Nganjuk","serial":1513,"type":2,"latitude":-7.602932,"longitude":111.901808,"status":1}, +{"nid":835,"parent_nid":15,"name":"Kabupaten Situbondo","serial":1514,"type":2,"latitude":-7.702534,"longitude":113.955605,"status":1}, +{"nid":836,"parent_nid":15,"name":"Kabupaten Malang","serial":1515,"type":2,"latitude":-8.0495643,"longitude":112.6884549,"status":1}, +{"nid":837,"parent_nid":15,"name":"Kabupaten Jember","serial":1516,"type":2,"latitude":-8.172357,"longitude":113.700302,"status":1}, +{"nid":838,"parent_nid":15,"name":"Kabupaten Sumenep","serial":1517,"type":2,"latitude":-6.9253999,"longitude":113.9060624,"status":1}, +{"nid":839,"parent_nid":15,"name":"Kabupaten Pasuruan","serial":1518,"type":2,"latitude":-6.8623098,"longitude":108.8001936,"status":1}, +{"nid":840,"parent_nid":15,"name":"Kabupaten Pamekasan","serial":1519,"type":2,"latitude":-7.1666667,"longitude":113.4666667,"status":1}, +{"nid":841,"parent_nid":15,"name":"Kabupaten Probolinggo","serial":1520,"type":2,"latitude":-7.753965,"longitude":113.210675,"status":1}, +{"nid":842,"parent_nid":15,"name":"Kabupaten Lumajang","serial":1521,"type":2,"latitude":-8.137022,"longitude":113.226601,"status":1}, +{"nid":843,"parent_nid":15,"name":"Kabupaten Bojonegoro","serial":1522,"type":2,"latitude":0.882681,"longitude":124.4669566,"status":1}, +{"nid":844,"parent_nid":15,"name":"Kabupaten Tuban","serial":1523,"type":2,"latitude":-8.7493146,"longitude":115.1711298,"status":1}, +{"nid":845,"parent_nid":15,"name":"Kabupaten Lamongan","serial":1524,"type":2,"latitude":-7.406153,"longitude":109.3946794,"status":1}, +{"nid":846,"parent_nid":15,"name":"Kabupaten Sidoarjo","serial":1525,"type":2,"latitude":-7.4530278,"longitude":112.7173389,"status":1}, +{"nid":847,"parent_nid":15,"name":"Kabupaten Sampang","serial":1526,"type":2,"latitude":-7.5782556,"longitude":109.2058436,"status":1}, +{"nid":848,"parent_nid":15,"name":"Kabupaten Mojokerto","serial":1527,"type":2,"latitude":-7.488075,"longitude":112.427027,"status":1}, +{"nid":849,"parent_nid":15,"name":"Kabupaten Gresik","serial":1528,"type":2,"latitude":-7.15665,"longitude":112.6555,"status":1}, +{"nid":850,"parent_nid":15,"name":"Kabupaten Jombang","serial":1529,"type":2,"latitude":-7.5468395,"longitude":112.2264794,"status":1}, +{"nid":851,"parent_nid":15,"name":"Kota Mojokerto","serial":1530,"type":2,"latitude":-7.4722222,"longitude":112.4336111,"status":1}, +{"nid":852,"parent_nid":15,"name":"Kota Surabaya","serial":1531,"type":2,"latitude":-7.289166,"longitude":112.734398,"status":1}, +{"nid":853,"parent_nid":15,"name":"Kota Madiun","serial":1532,"type":2,"latitude":-7.629714,"longitude":111.513702,"status":1}, +{"nid":854,"parent_nid":15,"name":"Kota Blitar","serial":1533,"type":2,"latitude":-8.1,"longitude":112.15,"status":1}, +{"nid":855,"parent_nid":15,"name":"Kota Malang","serial":1534,"type":2,"latitude":-7.981894,"longitude":112.626503,"status":1}, +{"nid":856,"parent_nid":15,"name":"Kota Batu","serial":1535,"type":2,"latitude":-7.8671,"longitude":112.5239,"status":1}, +{"nid":857,"parent_nid":15,"name":"Kota Pasuruan","serial":1536,"type":2,"latitude":-7.644872,"longitude":112.903297,"status":1}, +{"nid":858,"parent_nid":15,"name":"Kota Kediri","serial":1537,"type":2,"latitude":-7.816895,"longitude":112.011398,"status":1}, +{"nid":859,"parent_nid":15,"name":"Kota Probolinggo","serial":1538,"type":2,"latitude":-7.756928,"longitude":113.211502,"status":1}, +{"nid":925,"parent_nid":5,"name":"Kabupaten Batanghari","serial":501,"type":2,"latitude":-1.7083922,"longitude":103.0817903,"status":1}, +{"nid":926,"parent_nid":5,"name":"Kabupaten Bungo","serial":502,"type":2,"latitude":-1.6401338,"longitude":101.8891721,"status":1}, +{"nid":927,"parent_nid":5,"name":"Kabupaten Kerinci","serial":503,"type":2,"latitude":-1.8720467,"longitude":101.4339148,"status":1}, +{"nid":928,"parent_nid":5,"name":"Kabupaten Merangin","serial":504,"type":2,"latitude":-2.1752789,"longitude":101.9804613,"status":1}, +{"nid":929,"parent_nid":5,"name":"Kabupaten Muaro Jambi","serial":505,"type":2,"latitude":-1.596672,"longitude":103.615799,"status":1}, +{"nid":930,"parent_nid":5,"name":"Kabupaten Sarolangun","serial":506,"type":2,"latitude":-2.2654937,"longitude":102.6905326,"status":1}, +{"nid":931,"parent_nid":5,"name":"Kabupaten Tanjung Jabung Barat","serial":507,"type":2,"latitude":-1.2332122,"longitude":103.7984428,"status":1}, +{"nid":932,"parent_nid":5,"name":"Kabupaten Tanjung Jabung Timur","serial":508,"type":2,"latitude":-1.3291599,"longitude":103.89973,"status":1}, +{"nid":933,"parent_nid":5,"name":"Kabupaten Tebo","serial":509,"type":2,"latitude":-1.2592999,"longitude":102.3463875,"status":1}, +{"nid":934,"parent_nid":5,"name":"Kota Jambi","serial":510,"type":2,"latitude":-1.596672,"longitude":103.615799,"status":1}, +{"nid":935,"parent_nid":5,"name":"Kota Sungai Penuh","serial":511,"type":2,"latitude":-2.06314,"longitude":101.387199,"status":1}, +{"nid":1326,"parent_nid":1,"name":"Kabupaten Simeulue","serial":1109,"type":2,"latitude":2.583333,"longitude":96.083333,"status":1}, +{"nid":1327,"parent_nid":1,"name":"Kabupaten Aceh Singkil","serial":1102,"type":2,"latitude":2.3589459,"longitude":97.87216,"status":1}, +{"nid":1328,"parent_nid":1,"name":"Kabupaten Aceh Selatan","serial":1101,"type":2,"latitude":3.3115056,"longitude":97.3516558,"status":1}, +{"nid":1329,"parent_nid":1,"name":"Kabupaten Aceh Tenggara","serial":1102,"type":2,"latitude":3.3088666,"longitude":97.6982272,"status":1}, +{"nid":1330,"parent_nid":1,"name":"Kabupaten Aceh Timur","serial":1103,"type":2,"latitude":5.255443,"longitude":95.9885456,"status":1}, +{"nid":1331,"parent_nid":1,"name":"Kabupaten Aceh Tengah","serial":1104,"type":2,"latitude":4.4482641,"longitude":96.8350999,"status":1}, +{"nid":1332,"parent_nid":1,"name":"Kabupaten Aceh Barat","serial":1105,"type":2,"latitude":4.4542745,"longitude":96.1526985,"status":1}, +{"nid":1333,"parent_nid":1,"name":"Kabupaten Aceh Besar","serial":1106,"type":2,"latitude":5.4529168,"longitude":95.4777811,"status":1}, +{"nid":1334,"parent_nid":1,"name":"Kabupaten Pidie","serial":1107,"type":2,"latitude":5.0742659,"longitude":95.940971,"status":1}, +{"nid":1335,"parent_nid":1,"name":"Kabupaten Bireuen","serial":1111,"type":2,"latitude":5.18254,"longitude":96.89005,"status":1}, +{"nid":1336,"parent_nid":1,"name":"Kabupaten Aceh Utara","serial":1108,"type":2,"latitude":4.9786331,"longitude":97.2221421,"status":1}, +{"nid":1337,"parent_nid":1,"name":"Kabupaten Aceh Barat Daya","serial":1112,"type":2,"latitude":3.0512643,"longitude":97.3368031,"status":1}, +{"nid":1338,"parent_nid":1,"name":"Kabupaten Gayo Lues","serial":1113,"type":2,"latitude":3.955165,"longitude":97.3516558,"status":1}, +{"nid":1339,"parent_nid":1,"name":"Kabupaten Aceh Tamiang","serial":1116,"type":2,"latitude":4.2328871,"longitude":98.0028892,"status":1}, +{"nid":1340,"parent_nid":1,"name":"Kabupaten Nagan Raya","serial":1115,"type":2,"latitude":4.1248406,"longitude":96.4929797,"status":1}, +{"nid":1341,"parent_nid":1,"name":"Kabupaten Aceh Jaya","serial":1114,"type":2,"latitude":4.7873684,"longitude":95.6457951,"status":1}, +{"nid":1342,"parent_nid":1,"name":"Kabupaten Bener Meriah","serial":1117,"type":2,"latitude":4.7748348,"longitude":97.0068393,"status":1}, +{"nid":1343,"parent_nid":1,"name":"Kabupaten Pidie Jaya","serial":1118,"type":2,"latitude":5.1548063,"longitude":96.195132,"status":1}, +{"nid":1344,"parent_nid":1,"name":"Kota Banda Aceh","serial":1171,"type":2,"latitude":5.55,"longitude":95.3166667,"status":1}, +{"nid":1345,"parent_nid":1,"name":"Kota Sabang","serial":1172,"type":2,"latitude":5.8946929,"longitude":95.3192982,"status":1}, +{"nid":1346,"parent_nid":1,"name":"Kota Langsa","serial":1174,"type":2,"latitude":4.48,"longitude":97.9633333,"status":1}, +{"nid":1347,"parent_nid":1,"name":"Kota Lhokseumawe","serial":1173,"type":2,"latitude":5.1880556,"longitude":97.1402778,"status":1}, +{"nid":1348,"parent_nid":1,"name":"Kota Subulussalam","serial":1175,"type":2,"latitude":2.6449927,"longitude":98.0165205,"status":1}, +{"nid":1349,"parent_nid":2,"name":"Kabupaten Nias","serial":1204,"type":2,"latitude":-8.1712591,"longitude":113.7111274,"status":1}, +{"nid":1350,"parent_nid":2,"name":"Kabupaten Mandailing Natal","serial":1213,"type":2,"latitude":0.7432372,"longitude":99.3673084,"status":1}, +{"nid":1351,"parent_nid":2,"name":"Kabupaten Tapanuli Selatan","serial":1203,"type":2,"latitude":1.5774933,"longitude":99.2785583,"status":1}, +{"nid":1352,"parent_nid":2,"name":"Kabupaten Tapanuli Tengah","serial":1201,"type":2,"latitude":1.8493299,"longitude":98.704075,"status":1}, +{"nid":1353,"parent_nid":2,"name":"Kabupaten Tapanuli Utara","serial":1202,"type":2,"latitude":2.0405246,"longitude":99.1013498,"status":1}, +{"nid":1354,"parent_nid":2,"name":"Kabupaten Toba Samosir","serial":1212,"type":2,"latitude":2.3502398,"longitude":99.2785583,"status":1}, +{"nid":1355,"parent_nid":2,"name":"Kabupaten Labuhanbatu","serial":1210,"type":2,"latitude":2.3439863,"longitude":100.1703257,"status":1}, +{"nid":1356,"parent_nid":2,"name":"Kabupaten Asahan","serial":1209,"type":2,"latitude":2.8174722,"longitude":99.634135,"status":1}, +{"nid":1357,"parent_nid":2,"name":"Kabupaten Simalungun","serial":1208,"type":2,"latitude":2.9781612,"longitude":99.2785583,"status":1}, +{"nid":1358,"parent_nid":2,"name":"Kabupaten Dairi","serial":1211,"type":2,"latitude":2.8675801,"longitude":98.265058,"status":1}, +{"nid":1359,"parent_nid":2,"name":"Kabupaten Karo","serial":1206,"type":2,"latitude":3.1052909,"longitude":98.265058,"status":1}, +{"nid":1360,"parent_nid":2,"name":"Kabupaten Deli Serdang","serial":1207,"type":2,"latitude":3.4201802,"longitude":98.704075,"status":1}, +{"nid":1361,"parent_nid":2,"name":"Kabupaten Langkat","serial":1205,"type":2,"latitude":3.8653916,"longitude":98.3088441,"status":1}, +{"nid":1362,"parent_nid":2,"name":"Kabupaten Nias Selatan","serial":1214,"type":2,"latitude":0.7086091,"longitude":97.8286368,"status":1}, +{"nid":1363,"parent_nid":2,"name":"Kabupaten Humbang Hasundutan","serial":1216,"type":2,"latitude":2.1988508,"longitude":98.5721016,"status":1}, +{"nid":1364,"parent_nid":2,"name":"Kabupaten Pakpak Bharat","serial":1215,"type":2,"latitude":2.545786,"longitude":98.299838,"status":1}, +{"nid":1365,"parent_nid":2,"name":"Kabupaten Samosir","serial":1217,"type":2,"latitude":2.5833333,"longitude":98.8166667,"status":1}, +{"nid":1366,"parent_nid":2,"name":"Kabupaten Serdang Bedagai","serial":1218,"type":2,"latitude":3.3371694,"longitude":99.0571089,"status":1}, +{"nid":1367,"parent_nid":2,"name":"Kabupaten Batu Bara","serial":1219,"type":2,"latitude":3.1740979,"longitude":99.5006143,"status":1}, +{"nid":1368,"parent_nid":2,"name":"Kabupaten Padang Lawas Utara","serial":1220,"type":2,"latitude":1.5758644,"longitude":99.634135,"status":1}, +{"nid":1369,"parent_nid":2,"name":"Kabupaten Padang Lawas","serial":1221,"type":2,"latitude":1.1186977,"longitude":99.8124935,"status":1}, +{"nid":1370,"parent_nid":2,"name":"Kota Sibolga","serial":1273,"type":2,"latitude":1.7403745,"longitude":98.7827988,"status":1}, +{"nid":1371,"parent_nid":2,"name":"Kota Tanjung Balai","serial":1274,"type":2,"latitude":2.965122,"longitude":99.800331,"status":1}, +{"nid":1372,"parent_nid":2,"name":"Kota Pematang Siantar","serial":1272,"type":2,"latitude":2.96,"longitude":99.06,"status":1}, +{"nid":1373,"parent_nid":2,"name":"Kota Tebing Tinggi","serial":1276,"type":2,"latitude":3.3856205,"longitude":99.2009815,"status":1}, +{"nid":1374,"parent_nid":2,"name":"Kota Medan","serial":1271,"type":2,"latitude":3.585242,"longitude":98.6755979,"status":1}, +{"nid":1375,"parent_nid":2,"name":"Kota Binjai","serial":1275,"type":2,"latitude":3.594462,"longitude":98.482246,"status":1}, +{"nid":1376,"parent_nid":2,"name":"Kota Padangsidimpuan","serial":1277,"type":2,"latitude":1.380424,"longitude":99.273972,"status":1}, +{"nid":1377,"parent_nid":3,"name":"Kabupaten Kepulauan Mentawai","serial":1309,"type":2,"latitude":-1.426001,"longitude":98.9245343,"status":1}, +{"nid":1378,"parent_nid":3,"name":"Kabupaten Pesisir Selatan","serial":1301,"type":2,"latitude":-1.7223147,"longitude":100.8903099,"status":1}, +{"nid":1379,"parent_nid":3,"name":"Kabupaten Solok","serial":1302,"type":2,"latitude":-0.803027,"longitude":100.644402,"status":1}, +{"nid":1380,"parent_nid":3,"name":"Kabupaten Sijunjung","serial":1303,"type":2,"latitude":-0.6881586,"longitude":100.997658,"status":1}, +{"nid":1381,"parent_nid":3,"name":"Kabupaten Tanah Datar","serial":1304,"type":2,"latitude":-0.4797043,"longitude":100.5746224,"status":1}, +{"nid":1382,"parent_nid":3,"name":"Kabupaten Padang Pariaman","serial":1305,"type":2,"latitude":-0.5546757,"longitude":100.2151578,"status":1}, +{"nid":1383,"parent_nid":3,"name":"Kabupaten Agam","serial":1306,"type":2,"latitude":-0.2209392,"longitude":100.1703257,"status":1}, +{"nid":1384,"parent_nid":3,"name":"Kabupaten Lima Puluh Kota","serial":1307,"type":2,"latitude":3.168216,"longitude":99.4187929,"status":1}, +{"nid":1385,"parent_nid":3,"name":"Kabupaten Pasaman","serial":1308,"type":2,"latitude":0.1288752,"longitude":99.7901781,"status":1}, +{"nid":1386,"parent_nid":3,"name":"Kabupaten Solok Selatan","serial":1311,"type":2,"latitude":-1.4157329,"longitude":101.2523792,"status":1}, +{"nid":1387,"parent_nid":3,"name":"Kabupaten Dharmas Raya","serial":1310,"type":2,"latitude":-1.1120568,"longitude":101.6157773,"status":1}, +{"nid":1388,"parent_nid":3,"name":"Kabupaten Pasaman Barat","serial":1312,"type":2,"latitude":0.2213005,"longitude":99.634135,"status":1}, +{"nid":1389,"parent_nid":3,"name":"Kota Padang","serial":1371,"type":2,"latitude":-0.95,"longitude":100.3530556,"status":1}, +{"nid":1390,"parent_nid":3,"name":"Kota Solok","serial":1372,"type":2,"latitude":-0.803027,"longitude":100.644402,"status":1}, +{"nid":1391,"parent_nid":3,"name":"Kota Sawah Lunto","serial":1373,"type":2,"latitude":-0.6810286,"longitude":100.7763604,"status":1}, +{"nid":1392,"parent_nid":3,"name":"Kota Padang Panjang","serial":1374,"type":2,"latitude":-0.470679,"longitude":100.4059456,"status":1}, +{"nid":1393,"parent_nid":3,"name":"Kota Bukittinggi","serial":1375,"type":2,"latitude":-0.3055556,"longitude":100.3691667,"status":1}, +{"nid":1394,"parent_nid":3,"name":"Kota Payakumbuh","serial":1376,"type":2,"latitude":-0.22887,"longitude":100.632301,"status":1}, +{"nid":1395,"parent_nid":3,"name":"Kota Pariaman","serial":1377,"type":2,"latitude":-0.6264389,"longitude":100.1179574,"status":1}, +{"nid":1396,"parent_nid":4,"name":"Kabupaten Kuantan Singingi","serial":1409,"type":2,"latitude":-0.4411596,"longitude":101.5248055,"status":1}, +{"nid":1397,"parent_nid":4,"name":"Kabupaten Indragiri Hulu","serial":1402,"type":2,"latitude":-0.7361181,"longitude":102.2547919,"status":1}, +{"nid":1398,"parent_nid":4,"name":"Kabupaten Indragiri Hilir","serial":1404,"type":2,"latitude":-0.1456733,"longitude":102.989615,"status":1}, +{"nid":1399,"parent_nid":4,"name":"Kabupaten Pelalawan","serial":1405,"type":2,"latitude":0.441415,"longitude":102.088699,"status":1}, +{"nid":1400,"parent_nid":4,"name":"Kabupaten S I A K","serial":1408,"type":2,"latitude":-0.789275,"longitude":113.921327,"status":1}, +{"nid":1401,"parent_nid":4,"name":"Kabupaten Kampar","serial":1401,"type":2,"latitude":0.146671,"longitude":101.1617356,"status":1}, +{"nid":1402,"parent_nid":4,"name":"Kabupaten Rokan Hulu","serial":1406,"type":2,"latitude":1.0410934,"longitude":100.439656,"status":1}, +{"nid":1403,"parent_nid":4,"name":"Kabupaten Bengkalis","serial":1403,"type":2,"latitude":1.4897222,"longitude":102.0797222,"status":1}, +{"nid":1404,"parent_nid":4,"name":"Kabupaten Rokan Hilir","serial":1407,"type":2,"latitude":1.6463978,"longitude":100.8000051,"status":1}, +{"nid":1405,"parent_nid":4,"name":"Kota Pekanbaru","serial":1471,"type":2,"latitude":0.5333333,"longitude":101.45,"status":1}, +{"nid":1406,"parent_nid":4,"name":"Kota Dumai","serial":1472,"type":2,"latitude":1.665742,"longitude":101.447601,"status":1}, +{"nid":1407,"parent_nid":5,"name":"Kabupaten Kerinci","serial":1501,"type":2,"latitude":-1.697,"longitude":101.264,"status":1}, +{"nid":1408,"parent_nid":5,"name":"Kabupaten Merangin","serial":1502,"type":2,"latitude":-2.1752789,"longitude":101.9804613,"status":1}, +{"nid":1409,"parent_nid":5,"name":"Kabupaten Sarolangun","serial":1503,"type":2,"latitude":-2.2654937,"longitude":102.6905326,"status":1}, +{"nid":1410,"parent_nid":5,"name":"Kabupaten Batang Hari","serial":1504,"type":2,"latitude":-1.7083922,"longitude":103.0817903,"status":1}, +{"nid":1411,"parent_nid":5,"name":"Kabupaten Muaro Jambi","serial":1505,"type":2,"latitude":-1.596672,"longitude":103.615799,"status":1}, +{"nid":1412,"parent_nid":5,"name":"Kabupaten Tanjung Jabung Timur","serial":1507,"type":2,"latitude":-1.3291599,"longitude":103.89973,"status":1}, +{"nid":1413,"parent_nid":5,"name":"Kabupaten Tanjung Jabung Barat","serial":1506,"type":2,"latitude":-1.2332122,"longitude":103.7984428,"status":1}, +{"nid":1414,"parent_nid":5,"name":"Kabupaten Tebo","serial":1509,"type":2,"latitude":-1.2592999,"longitude":102.3463875,"status":1}, +{"nid":1415,"parent_nid":5,"name":"Kabupaten Bungo","serial":1508,"type":2,"latitude":-1.6401338,"longitude":101.8891721,"status":1}, +{"nid":1416,"parent_nid":5,"name":"Kota Jambi","serial":1571,"type":2,"latitude":-1.596672,"longitude":103.615799,"status":1}, +{"nid":1417,"parent_nid":6,"name":"Kabupaten Ogan Komering Ulu","serial":1601,"type":2,"latitude":-4.0283486,"longitude":104.0072348,"status":1}, +{"nid":1418,"parent_nid":6,"name":"Kabupaten Ogan Komering Ilir","serial":1602,"type":2,"latitude":-3.4559744,"longitude":105.2194808,"status":1}, +{"nid":1419,"parent_nid":6,"name":"Kabupaten Muara Enim","serial":1603,"type":2,"latitude":-3.651581,"longitude":103.770798,"status":1}, +{"nid":1420,"parent_nid":6,"name":"Kabupaten Lahat","serial":1604,"type":2,"latitude":-3.7863889,"longitude":103.5427778,"status":1}, +{"nid":1421,"parent_nid":6,"name":"Kabupaten Musi Rawas","serial":1605,"type":2,"latitude":-2.8625305,"longitude":102.989615,"status":1}, +{"nid":1422,"parent_nid":6,"name":"Kabupaten Musi Banyuasin","serial":1606,"type":2,"latitude":-2.5442029,"longitude":103.7289167,"status":1}, +{"nid":1423,"parent_nid":6,"name":"Kabupaten Banyu Asin","serial":1607,"type":2,"latitude":-2.6095639,"longitude":104.7520939,"status":1}, +{"nid":1424,"parent_nid":6,"name":"Kabupaten Ogan Komering Ulu Selatan","serial":1609,"type":2,"latitude":-4.6681951,"longitude":104.0072348,"status":1}, +{"nid":1425,"parent_nid":6,"name":"Kabupaten Ogan Komering Ulu Timur","serial":1608,"type":2,"latitude":-3.8567934,"longitude":104.7520939,"status":1}, +{"nid":1426,"parent_nid":6,"name":"Kabupaten Ogan Ilir","serial":1610,"type":2,"latitude":-3.426544,"longitude":104.6121475,"status":1}, +{"nid":1427,"parent_nid":6,"name":"Kabupaten Empat Lawang","serial":1611,"type":2,"latitude":-3.7286029,"longitude":102.8975098,"status":1}, +{"nid":1428,"parent_nid":6,"name":"Kota Palembang","serial":1671,"type":2,"latitude":-2.9911083,"longitude":104.7567333,"status":1}, +{"nid":1429,"parent_nid":6,"name":"Kota Prabumulih","serial":1674,"type":2,"latitude":-3.440956,"longitude":104.235397,"status":1}, +{"nid":1430,"parent_nid":6,"name":"Kota Pagar Alam","serial":1672,"type":2,"latitude":-4.03767,"longitude":103.265297,"status":1}, +{"nid":1431,"parent_nid":6,"name":"Kota Lubuklinggau","serial":1673,"type":2,"latitude":-3.2966667,"longitude":102.8616667,"status":1}, +{"nid":1432,"parent_nid":7,"name":"Kabupaten Bengkulu Selatan","serial":1701,"type":2,"latitude":-4.3248409,"longitude":103.035694,"status":1}, +{"nid":1433,"parent_nid":7,"name":"Kabupaten Rejang Lebong","serial":1702,"type":2,"latitude":-3.4548154,"longitude":102.6675575,"status":1}, +{"nid":1434,"parent_nid":7,"name":"Kabupaten Bengkulu Utara","serial":1703,"type":2,"latitude":-3.4219555,"longitude":102.1632718,"status":1}, +{"nid":1435,"parent_nid":7,"name":"Kabupaten Kaur","serial":1704,"type":2,"latitude":-4.6792278,"longitude":103.4511768,"status":1}, +{"nid":1436,"parent_nid":7,"name":"Kabupaten Seluma","serial":1705,"type":2,"latitude":-4.0622929,"longitude":102.5642261,"status":1}, +{"nid":1437,"parent_nid":7,"name":"Kabupaten Mukomuko","serial":1706,"type":2,"latitude":-2.5760003,"longitude":101.1169805,"status":1}, +{"nid":1438,"parent_nid":7,"name":"Kabupaten Lebong","serial":1707,"type":2,"latitude":-2.992617,"longitude":104.382202,"status":1}, +{"nid":1439,"parent_nid":7,"name":"Kabupaten Kepahiang","serial":1708,"type":2,"latitude":-3.651431,"longitude":102.578201,"status":1}, +{"nid":1440,"parent_nid":7,"name":"Kota Bengkulu","serial":1771,"type":2,"latitude":-3.7955556,"longitude":102.2591667,"status":1}, +{"nid":1441,"parent_nid":8,"name":"Kabupaten Lampung Barat","serial":1804,"type":2,"latitude":-5.1490396,"longitude":104.1930918,"status":1}, +{"nid":1442,"parent_nid":8,"name":"Kabupaten Tanggamus","serial":1802,"type":2,"latitude":-5.3027489,"longitude":104.5655273,"status":1}, +{"nid":1443,"parent_nid":8,"name":"Kabupaten Lampung Selatan","serial":1801,"type":2,"latitude":-5.5622614,"longitude":105.5474373,"status":1}, +{"nid":1444,"parent_nid":8,"name":"Kabupaten Lampung Timur","serial":1807,"type":2,"latitude":-5.1134995,"longitude":105.6881788,"status":1}, +{"nid":1445,"parent_nid":8,"name":"Kabupaten Lampung Tengah","serial":1802,"type":2,"latitude":-4.8008086,"longitude":105.3131185,"status":1}, +{"nid":1446,"parent_nid":8,"name":"Kabupaten Lampung Utara","serial":1803,"type":2,"latitude":-4.8133905,"longitude":104.7520939,"status":1}, +{"nid":1447,"parent_nid":8,"name":"Kabupaten Way Kanan","serial":1808,"type":2,"latitude":-4.4963689,"longitude":104.5655273,"status":1}, +{"nid":1448,"parent_nid":8,"name":"Kabupaten Tulangbawang","serial":1812,"type":2,"latitude":-4.3176576,"longitude":105.5005483,"status":1}, +{"nid":1449,"parent_nid":8,"name":"Kabupaten Pesawaran","serial":1809,"type":2,"latitude":-5.493245,"longitude":105.0791228,"status":1}, +{"nid":1450,"parent_nid":8,"name":"Kota Bandar Lampung","serial":1871,"type":2,"latitude":-5.45,"longitude":105.2666667,"status":1}, +{"nid":1451,"parent_nid":8,"name":"Kota Metro","serial":1872,"type":2,"latitude":-5.1166667,"longitude":105.3,"status":1}, +{"nid":1452,"parent_nid":9,"name":"Kabupaten Bangka","serial":1901,"type":2,"latitude":-2.2884782,"longitude":106.0640179,"status":1}, +{"nid":1453,"parent_nid":9,"name":"Kabupaten Belitung","serial":1902,"type":2,"latitude":-2.8708938,"longitude":107.9531836,"status":1}, +{"nid":1454,"parent_nid":9,"name":"Kabupaten Bangka Barat","serial":1905,"type":2,"latitude":-2.2884782,"longitude":106.0640179,"status":1}, +{"nid":1455,"parent_nid":9,"name":"Kabupaten Bangka Tengah","serial":1904,"type":2,"latitude":-2.2884782,"longitude":106.0640179,"status":1}, +{"nid":1456,"parent_nid":9,"name":"Kabupaten Bangka Selatan","serial":1903,"type":2,"latitude":-2.2884782,"longitude":106.0640179,"status":1}, +{"nid":1457,"parent_nid":9,"name":"Kabupaten Belitung Timur","serial":1906,"type":2,"latitude":-2.8708938,"longitude":107.9531836,"status":1}, +{"nid":1458,"parent_nid":9,"name":"Kota Pangkal Pinang","serial":1971,"type":2,"latitude":-2.129323,"longitude":106.109596,"status":1}, +{"nid":1459,"parent_nid":10,"name":"Kabupaten Karimun","serial":2102,"type":2,"latitude":1.05,"longitude":103.3666667,"status":1}, +{"nid":1460,"parent_nid":10,"name":"Kabupaten Bintan","serial":2101,"type":2,"latitude":1.0619173,"longitude":104.5189214,"status":1}, +{"nid":1461,"parent_nid":10,"name":"Kabupaten Natuna","serial":2103,"type":2,"latitude":3.9329945,"longitude":108.1812242,"status":1}, +{"nid":1462,"parent_nid":10,"name":"Kabupaten Lingga","serial":2104,"type":2,"latitude":-0.1627686,"longitude":104.6354631,"status":1}, +{"nid":1463,"parent_nid":10,"name":"Kota Batam","serial":2171,"type":2,"latitude":1.0456264,"longitude":104.0304535,"status":1}, +{"nid":1464,"parent_nid":10,"name":"Kota Tanjung Pinang","serial":2172,"type":2,"latitude":0.9179205,"longitude":104.446464,"status":1}, +{"nid":1465,"parent_nid":11,"name":"Kabupaten Kepulauan Seribu","serial":3101,"type":2,"latitude":-5.7985266,"longitude":106.5071982,"status":1}, +{"nid":1466,"parent_nid":11,"name":"Kota Jakarta Selatan","serial":3174,"type":2,"latitude":-6.332973,"longitude":106.807915,"status":1}, +{"nid":1467,"parent_nid":11,"name":"Kota Jakarta Timur","serial":3175,"type":2,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":1468,"parent_nid":11,"name":"Kota Jakarta Pusat","serial":3171,"type":2,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":1469,"parent_nid":11,"name":"Kota Jakarta Barat","serial":3173,"type":2,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":1470,"parent_nid":11,"name":"Kota Jakarta Utara","serial":3172,"type":2,"latitude":-6.211544,"longitude":106.845172,"status":1}, +{"nid":1471,"parent_nid":12,"name":"Kabupaten Bogor","serial":3201,"type":2,"latitude":-6.6,"longitude":106.8,"status":1}, +{"nid":1472,"parent_nid":12,"name":"Kabupaten Sukabumi","serial":3202,"type":2,"latitude":-6.92405,"longitude":106.922203,"status":1}, +{"nid":1473,"parent_nid":12,"name":"Kabupaten Cianjur","serial":3203,"type":2,"latitude":-6.8172531,"longitude":107.1307289,"status":1}, +{"nid":1474,"parent_nid":12,"name":"Kabupaten Bandung","serial":3204,"type":2,"latitude":-6.9147444,"longitude":107.6098111,"status":1}, +{"nid":1475,"parent_nid":12,"name":"Kabupaten Garut","serial":3205,"type":2,"latitude":-7.227906,"longitude":107.908699,"status":1}, +{"nid":1476,"parent_nid":12,"name":"Kabupaten Tasikmalaya","serial":3206,"type":2,"latitude":-7.327954,"longitude":108.214104,"status":1}, +{"nid":1477,"parent_nid":12,"name":"Kabupaten Ciamis","serial":3207,"type":2,"latitude":-7.3333333,"longitude":108.35,"status":1}, +{"nid":1478,"parent_nid":12,"name":"Kabupaten Kuningan","serial":3208,"type":2,"latitude":-6.9833333,"longitude":108.4833333,"status":1}, +{"nid":1479,"parent_nid":12,"name":"Kabupaten Cirebon","serial":3209,"type":2,"latitude":-6.715534,"longitude":108.564003,"status":1}, +{"nid":1480,"parent_nid":12,"name":"Kabupaten Majalengka","serial":3210,"type":2,"latitude":-6.8531026,"longitude":108.2258897,"status":1}, +{"nid":1481,"parent_nid":12,"name":"Kabupaten Sumedang","serial":3211,"type":2,"latitude":0.6095949,"longitude":110.0330554,"status":1}, +{"nid":1482,"parent_nid":12,"name":"Kabupaten Indramayu","serial":3212,"type":2,"latitude":-6.336315,"longitude":108.325104,"status":1}, +{"nid":1483,"parent_nid":12,"name":"Kabupaten Subang","serial":3213,"type":2,"latitude":-6.569361,"longitude":107.752403,"status":1}, +{"nid":1484,"parent_nid":12,"name":"Kabupaten Purwakarta","serial":3214,"type":2,"latitude":-6.5386806,"longitude":107.4499404,"status":1}, +{"nid":1485,"parent_nid":12,"name":"Kabupaten Karawang","serial":3215,"type":2,"latitude":-6.3227303,"longitude":107.3375791,"status":1}, +{"nid":1486,"parent_nid":12,"name":"Kabupaten Bekasi","serial":3216,"type":2,"latitude":-6.2333333,"longitude":107,"status":1}, +{"nid":1487,"parent_nid":12,"name":"Kabupaten Bandung Barat","serial":3217,"type":2,"latitude":-6.8937121,"longitude":107.4321959,"status":1}, +{"nid":1488,"parent_nid":12,"name":"Kota Bogor","serial":3271,"type":2,"latitude":-6.6,"longitude":106.8,"status":1}, +{"nid":1489,"parent_nid":12,"name":"Kota Sukabumi","serial":3272,"type":2,"latitude":-6.92405,"longitude":106.922203,"status":1}, +{"nid":1490,"parent_nid":12,"name":"Kota Bandung","serial":3273,"type":2,"latitude":-6.9147444,"longitude":107.6098111,"status":1}, +{"nid":1491,"parent_nid":12,"name":"Kota Cirebon","serial":3274,"type":2,"latitude":-6.715534,"longitude":108.564003,"status":1}, +{"nid":1492,"parent_nid":12,"name":"Kota Bekasi","serial":3275,"type":2,"latitude":-6.2333333,"longitude":107,"status":1}, +{"nid":1493,"parent_nid":12,"name":"Kota Depok","serial":3276,"type":2,"latitude":-6.39,"longitude":106.83,"status":1}, +{"nid":1494,"parent_nid":12,"name":"Kota Cimahi","serial":3277,"type":2,"latitude":-6.880239,"longitude":107.5355,"status":1}, +{"nid":1495,"parent_nid":12,"name":"Kota Tasikmalaya","serial":3278,"type":2,"latitude":-7.327954,"longitude":108.214104,"status":1}, +{"nid":1496,"parent_nid":12,"name":"Kota Banjar","serial":3279,"type":2,"latitude":-7.3666667,"longitude":108.5333333,"status":1}, +{"nid":1497,"parent_nid":13,"name":"Kabupaten Cilacap","serial":3301,"type":2,"latitude":-7.733333,"longitude":109,"status":1}, +{"nid":1498,"parent_nid":13,"name":"Kabupaten Banyumas","serial":3302,"type":2,"latitude":-7.4832133,"longitude":109.140438,"status":1}, +{"nid":1499,"parent_nid":13,"name":"Kabupaten Purbalingga","serial":3303,"type":2,"latitude":-7.390747,"longitude":109.3638,"status":1}, +{"nid":1500,"parent_nid":13,"name":"Kabupaten Banjarnegara","serial":3304,"type":2,"latitude":-7.402706,"longitude":109.681396,"status":1}, +{"nid":1501,"parent_nid":13,"name":"Kabupaten Kebumen","serial":3305,"type":2,"latitude":-7.678682,"longitude":109.656502,"status":1}, +{"nid":1502,"parent_nid":13,"name":"Kabupaten Purworejo","serial":3306,"type":2,"latitude":-7.709731,"longitude":110.008003,"status":1}, +{"nid":1503,"parent_nid":13,"name":"Kabupaten Wonosobo","serial":3307,"type":2,"latitude":-7.362109,"longitude":109.899399,"status":1}, +{"nid":1504,"parent_nid":13,"name":"Kabupaten Magelang","serial":3308,"type":2,"latitude":-7.481253,"longitude":110.213799,"status":1}, +{"nid":1505,"parent_nid":13,"name":"Kabupaten Boyolali","serial":3309,"type":2,"latitude":-7.523819,"longitude":110.595901,"status":1}, +{"nid":1506,"parent_nid":13,"name":"Kabupaten Klaten","serial":3310,"type":2,"latitude":-7.711687,"longitude":110.595497,"status":1}, +{"nid":1507,"parent_nid":13,"name":"Kabupaten Sukoharjo","serial":3311,"type":2,"latitude":-7.6808818,"longitude":110.8195292,"status":1}, +{"nid":1508,"parent_nid":13,"name":"Kabupaten Wonogiri","serial":3312,"type":2,"latitude":-7.817782,"longitude":110.920601,"status":1}, +{"nid":1509,"parent_nid":13,"name":"Kabupaten Karanganyar","serial":3313,"type":2,"latitude":-7.5961111,"longitude":110.9508333,"status":1}, +{"nid":1510,"parent_nid":13,"name":"Kabupaten Sragen","serial":3314,"type":2,"latitude":-7.430229,"longitude":111.021301,"status":1}, +{"nid":1511,"parent_nid":13,"name":"Kabupaten Grobogan","serial":3315,"type":2,"latitude":-7.0217194,"longitude":110.9625854,"status":1}, +{"nid":1512,"parent_nid":13,"name":"Kabupaten Blora","serial":3316,"type":2,"latitude":-6.95,"longitude":111.4166667,"status":1}, +{"nid":1513,"parent_nid":13,"name":"Kabupaten Rembang","serial":3317,"type":2,"latitude":-6.71124,"longitude":111.345299,"status":1}, +{"nid":1514,"parent_nid":13,"name":"Kabupaten Pati","serial":3318,"type":2,"latitude":-6.751338,"longitude":111.038002,"status":1}, +{"nid":1515,"parent_nid":13,"name":"Kabupaten Kudus","serial":3319,"type":2,"latitude":-6.804087,"longitude":110.838203,"status":1}, +{"nid":1516,"parent_nid":13,"name":"Kabupaten Jepara","serial":3320,"type":2,"latitude":-6.5596059,"longitude":110.6717,"status":1}, +{"nid":1517,"parent_nid":13,"name":"Kabupaten Demak","serial":3321,"type":2,"latitude":-6.888115,"longitude":110.639297,"status":1}, +{"nid":1518,"parent_nid":13,"name":"Kabupaten Semarang","serial":3322,"type":2,"latitude":-6.9666667,"longitude":110.4166667,"status":1}, +{"nid":1519,"parent_nid":13,"name":"Kabupaten Temanggung","serial":3323,"type":2,"latitude":-7.316669,"longitude":110.174797,"status":1}, +{"nid":1520,"parent_nid":13,"name":"Kabupaten Kendal","serial":3324,"type":2,"latitude":-6.919686,"longitude":110.205597,"status":1}, +{"nid":1521,"parent_nid":13,"name":"Kabupaten Batang","serial":3325,"type":2,"latitude":-6.8941111,"longitude":109.7234519,"status":1}, +{"nid":1522,"parent_nid":13,"name":"Kabupaten Pekalongan","serial":3326,"type":2,"latitude":-6.882887,"longitude":109.669998,"status":1}, +{"nid":1523,"parent_nid":13,"name":"Kabupaten Pemalang","serial":3327,"type":2,"latitude":-6.884234,"longitude":109.377998,"status":1}, +{"nid":1524,"parent_nid":13,"name":"Kabupaten Tegal","serial":3328,"type":2,"latitude":-6.8666667,"longitude":109.1333333,"status":1}, +{"nid":1525,"parent_nid":13,"name":"Kabupaten Brebes","serial":3329,"type":2,"latitude":-6.8833333,"longitude":109.05,"status":1}, +{"nid":1526,"parent_nid":13,"name":"Kota Magelang","serial":3371,"type":2,"latitude":-7.481253,"longitude":110.213799,"status":1}, +{"nid":1527,"parent_nid":13,"name":"Kota Surakarta","serial":3372,"type":2,"latitude":-7.5666667,"longitude":110.8166667,"status":1}, +{"nid":1528,"parent_nid":13,"name":"Kota Salatiga","serial":3373,"type":2,"latitude":-7.302328,"longitude":110.4729,"status":1}, +{"nid":1529,"parent_nid":13,"name":"Kota Semarang","serial":3374,"type":2,"latitude":-6.9666667,"longitude":110.4166667,"status":1}, +{"nid":1530,"parent_nid":13,"name":"Kota Pekalongan","serial":3375,"type":2,"latitude":-6.882887,"longitude":109.669998,"status":1}, +{"nid":1531,"parent_nid":13,"name":"Kota Tegal","serial":3376,"type":2,"latitude":-6.8666667,"longitude":109.1333333,"status":1}, +{"nid":1532,"parent_nid":14,"name":"Kabupaten Kulon Progo","serial":3401,"type":2,"latitude":-7.8266798,"longitude":110.1640846,"status":1}, +{"nid":1533,"parent_nid":14,"name":"Kabupaten Bantul","serial":3402,"type":2,"latitude":-7.8846111,"longitude":110.3341111,"status":1}, +{"nid":1534,"parent_nid":14,"name":"Kabupaten Gunung Kidul","serial":3403,"type":2,"latitude":-8.0305091,"longitude":110.6168921,"status":1}, +{"nid":1536,"parent_nid":14,"name":"Kota Yogyakarta","serial":3471,"type":2,"latitude":-7.797224,"longitude":110.368797,"status":1}, +{"nid":1537,"parent_nid":16,"name":"Kabupaten Pandeglang","serial":3601,"type":2,"latitude":-6.314835,"longitude":106.103897,"status":1}, +{"nid":1538,"parent_nid":16,"name":"Kabupaten Lebak","serial":3602,"type":2,"latitude":-6.5643956,"longitude":106.2522143,"status":1}, +{"nid":1539,"parent_nid":16,"name":"Kabupaten Tangerang","serial":3603,"type":2,"latitude":-6.1783056,"longitude":106.6318889,"status":1}, +{"nid":1540,"parent_nid":16,"name":"Kabupaten Serang","serial":3604,"type":2,"latitude":-6.12009,"longitude":106.150299,"status":1}, +{"nid":1541,"parent_nid":16,"name":"Kota Tangerang","serial":3671,"type":2,"latitude":-6.1783056,"longitude":106.6318889,"status":1}, +{"nid":1542,"parent_nid":16,"name":"Kota Cilegon","serial":3672,"type":2,"latitude":-6.0169825,"longitude":106.040506,"status":1}, +{"nid":1543,"parent_nid":16,"name":"Kota Serang","serial":3673,"type":2,"latitude":-6.12009,"longitude":106.150299,"status":1}, +{"nid":1544,"parent_nid":17,"name":"Kabupaten Jembrana","serial":5101,"type":2,"latitude":-8.361852,"longitude":114.6418,"status":1}, +{"nid":1545,"parent_nid":17,"name":"Kabupaten Tabanan","serial":5102,"type":2,"latitude":-8.544516,"longitude":115.119797,"status":1}, +{"nid":1546,"parent_nid":17,"name":"Kabupaten Badung","serial":5103,"type":2,"latitude":-8.5819296,"longitude":115.1770586,"status":1}, +{"nid":1547,"parent_nid":17,"name":"Kabupaten Gianyar","serial":5104,"type":2,"latitude":-8.544185,"longitude":115.3255,"status":1}, +{"nid":1548,"parent_nid":17,"name":"Kabupaten Klungkung","serial":5105,"type":2,"latitude":-8.5389222,"longitude":115.4045111,"status":1}, +{"nid":1549,"parent_nid":17,"name":"Kabupaten Bangli","serial":5106,"type":2,"latitude":-8.454303,"longitude":115.354897,"status":1}, +{"nid":1550,"parent_nid":17,"name":"Kabupaten Karang Asem","serial":5107,"type":2,"latitude":-6.3996057,"longitude":108.0503042,"status":1}, +{"nid":1551,"parent_nid":17,"name":"Kabupaten Buleleng","serial":5108,"type":2,"latitude":-8.113831,"longitude":115.126999,"status":1}, +{"nid":1552,"parent_nid":17,"name":"Kota Denpasar","serial":5171,"type":2,"latitude":-8.65629,"longitude":115.222099,"status":1}, +{"nid":1553,"parent_nid":18,"name":"Kabupaten Lombok Barat","serial":5201,"type":2,"latitude":-8.6464599,"longitude":116.1123078,"status":1}, +{"nid":1554,"parent_nid":18,"name":"Kabupaten Lombok Tengah","serial":5202,"type":2,"latitude":-8.694623,"longitude":116.2777073,"status":1}, +{"nid":1555,"parent_nid":18,"name":"Kabupaten Lombok Timur","serial":5203,"type":2,"latitude":-8.5134471,"longitude":116.5609857,"status":1}, +{"nid":1556,"parent_nid":18,"name":"Kabupaten Sumbawa","serial":5204,"type":2,"latitude":-8.6529334,"longitude":117.3616476,"status":1}, +{"nid":1557,"parent_nid":18,"name":"Kabupaten Dompu","serial":5205,"type":2,"latitude":-8.4966318,"longitude":118.4747173,"status":1}, +{"nid":1558,"parent_nid":18,"name":"Kabupaten Bima","serial":5206,"type":2,"latitude":-8.460566,"longitude":118.727402,"status":1}, +{"nid":1559,"parent_nid":18,"name":"Kabupaten Sumbawa Barat","serial":5207,"type":2,"latitude":-8.9292907,"longitude":116.8910342,"status":1}, +{"nid":1560,"parent_nid":18,"name":"Kota Mataram","serial":5271,"type":2,"latitude":-8.5833333,"longitude":116.1166667,"status":1}, +{"nid":1561,"parent_nid":18,"name":"Kota Bima","serial":5272,"type":2,"latitude":-8.460566,"longitude":118.727402,"status":1}, +{"nid":1562,"parent_nid":19,"name":"Kabupaten Sumba Barat","serial":5312,"type":2,"latitude":-9.6548326,"longitude":119.3947135,"status":1}, +{"nid":1563,"parent_nid":19,"name":"Kabupaten Sumba Timur","serial":5311,"type":2,"latitude":-9.9802103,"longitude":120.3435506,"status":1}, +{"nid":1564,"parent_nid":19,"name":"Kabupaten Kupang","serial":5301,"type":2,"latitude":-10.1833333,"longitude":123.5833333,"status":1}, +{"nid":1565,"parent_nid":19,"name":"Kabupaten Timor Tengah Selatan","serial":5302,"type":2,"latitude":-9.7762816,"longitude":124.4198243,"status":1}, +{"nid":1566,"parent_nid":19,"name":"Kabupaten Timor Tengah Utara","serial":5303,"type":2,"latitude":-9.4522647,"longitude":124.597132,"status":1}, +{"nid":1567,"parent_nid":19,"name":"Kabupaten Belu","serial":5304,"type":2,"latitude":-9.4125796,"longitude":124.9506625,"status":1}, +{"nid":1568,"parent_nid":19,"name":"Kabupaten Alor","serial":5305,"type":2,"latitude":-8.2754027,"longitude":124.7298765,"status":1}, +{"nid":1569,"parent_nid":19,"name":"Kabupaten Lembata","serial":5313,"type":2,"latitude":-8.4719075,"longitude":123.4831906,"status":1}, +{"nid":1570,"parent_nid":19,"name":"Kabupaten Flores Timur","serial":5306,"type":2,"latitude":-8.3130942,"longitude":122.9663018,"status":1}, +{"nid":1571,"parent_nid":19,"name":"Kabupaten Sikka","serial":5307,"type":2,"latitude":-8.6766175,"longitude":122.1291843,"status":1}, +{"nid":1572,"parent_nid":19,"name":"Kabupaten Ende","serial":5308,"type":2,"latitude":-8.854053,"longitude":121.654198,"status":1}, +{"nid":1573,"parent_nid":19,"name":"Kabupaten Ngada","serial":5309,"type":2,"latitude":-8.7430424,"longitude":120.9876321,"status":1}, +{"nid":1574,"parent_nid":19,"name":"Kabupaten Manggarai","serial":5310,"type":2,"latitude":-8.6796987,"longitude":120.3896651,"status":1}, +{"nid":1575,"parent_nid":19,"name":"Kabupaten Rote Ndao","serial":5314,"type":2,"latitude":-10.7386421,"longitude":123.1239049,"status":1}, +{"nid":1576,"parent_nid":19,"name":"Kabupaten Manggarai Barat","serial":5315,"type":2,"latitude":-8.6688149,"longitude":120.0665236,"status":1}, +{"nid":1577,"parent_nid":19,"name":"Kabupaten Sumba Tengah","serial":5317,"type":2,"latitude":-9.4879226,"longitude":119.6962677,"status":1}, +{"nid":1578,"parent_nid":19,"name":"Kabupaten Sumba Barat Daya","serial":5318,"type":2,"latitude":-9.539139,"longitude":119.1390642,"status":1}, +{"nid":1579,"parent_nid":19,"name":"Kabupaten Nagekeo","serial":5316,"type":2,"latitude":-8.6753545,"longitude":121.3084088,"status":1}, +{"nid":1580,"parent_nid":19,"name":"Kabupaten Manggarai Timur","serial":5319,"type":2,"latitude":-8.6206712,"longitude":120.6199895,"status":1}, +{"nid":1581,"parent_nid":19,"name":"Kota Kupang","serial":5371,"type":2,"latitude":-10.1833333,"longitude":123.5833333,"status":1}, +{"nid":1582,"parent_nid":20,"name":"Kabupaten Sambas","serial":6101,"type":2,"latitude":1.361328,"longitude":109.309998,"status":1}, +{"nid":1583,"parent_nid":20,"name":"Kabupaten Bengkayang","serial":6107,"type":2,"latitude":0.8209729,"longitude":109.477699,"status":1}, +{"nid":1584,"parent_nid":20,"name":"Kabupaten Landak","serial":6108,"type":2,"latitude":0.4237287,"longitude":109.7591675,"status":1}, +{"nid":1585,"parent_nid":20,"name":"Kabupaten Pontianak","serial":6102,"type":2,"latitude":-0.022523,"longitude":109.330307,"status":1}, +{"nid":1586,"parent_nid":20,"name":"Kabupaten Sanggau","serial":6103,"type":2,"latitude":0.119275,"longitude":110.597298,"status":1}, +{"nid":1587,"parent_nid":20,"name":"Kabupaten Ketapang","serial":6104,"type":2,"latitude":-1.859098,"longitude":109.971901,"status":1}, +{"nid":1588,"parent_nid":20,"name":"Kabupaten Sintang","serial":6105,"type":2,"latitude":0.080238,"longitude":111.495499,"status":1}, +{"nid":1589,"parent_nid":20,"name":"Kabupaten Kapuas Hulu","serial":6106,"type":2,"latitude":-0.7931004,"longitude":113.9060624,"status":1}, +{"nid":1590,"parent_nid":20,"name":"Kabupaten Sekadau","serial":6109,"type":2,"latitude":0.015637,"longitude":110.888603,"status":1}, +{"nid":1591,"parent_nid":20,"name":"Kabupaten Melawi","serial":6110,"type":2,"latitude":-0.7000681,"longitude":111.6660725,"status":1}, +{"nid":1592,"parent_nid":20,"name":"Kabupaten Kayong Utara","serial":6111,"type":2,"latitude":-0.9225877,"longitude":110.0449662,"status":1}, +{"nid":1593,"parent_nid":20,"name":"Kabupaten Kubu Raya","serial":6112,"type":2,"latitude":-0.3533938,"longitude":109.4735066,"status":1}, +{"nid":1594,"parent_nid":20,"name":"Kota Pontianak","serial":6171,"type":2,"latitude":-0.022523,"longitude":109.330307,"status":1}, +{"nid":1595,"parent_nid":20,"name":"Kota Singkawang","serial":6172,"type":2,"latitude":0.908795,"longitude":108.984596,"status":1}, +{"nid":1596,"parent_nid":21,"name":"Kabupaten Kotawaringin Barat","serial":6201,"type":2,"latitude":-6.1961131,"longitude":106.8630174,"status":1}, +{"nid":1597,"parent_nid":21,"name":"Kabupaten Kotawaringin Timur","serial":6202,"type":2,"latitude":-6.1952992,"longitude":106.8630737,"status":1}, +{"nid":1598,"parent_nid":21,"name":"Kabupaten Kapuas","serial":6203,"type":2,"latitude":-0.0459972,"longitude":110.1313251,"status":1}, +{"nid":1599,"parent_nid":21,"name":"Kabupaten Barito Selatan","serial":6204,"type":2,"latitude":-1.875943,"longitude":114.8092691,"status":1}, +{"nid":1600,"parent_nid":21,"name":"Kabupaten Barito Utara","serial":6205,"type":2,"latitude":-0.9587136,"longitude":115.094045,"status":1}, +{"nid":1601,"parent_nid":21,"name":"Kabupaten Sukamara","serial":6208,"type":2,"latitude":-2.6267517,"longitude":111.2368084,"status":1}, +{"nid":1602,"parent_nid":21,"name":"Kabupaten Lamandau","serial":6209,"type":2,"latitude":-1.9269166,"longitude":111.1891151,"status":1}, +{"nid":1603,"parent_nid":21,"name":"Kabupaten Seruyan","serial":6207,"type":2,"latitude":-3.0123467,"longitude":112.4291464,"status":1}, +{"nid":1604,"parent_nid":21,"name":"Kabupaten Katingan","serial":6206,"type":2,"latitude":-0.9758379,"longitude":112.8105512,"status":1}, +{"nid":1605,"parent_nid":21,"name":"Kabupaten Pulang Pisau","serial":6211,"type":2,"latitude":-2.6849607,"longitude":113.9536466,"status":1}, +{"nid":1606,"parent_nid":21,"name":"Kabupaten Gunung Mas","serial":6210,"type":2,"latitude":-6.7052778,"longitude":106.9913889,"status":1}, +{"nid":1607,"parent_nid":21,"name":"Kabupaten Barito Timur","serial":6213,"type":2,"latitude":-2.0123999,"longitude":115.188916,"status":1}, +{"nid":1608,"parent_nid":21,"name":"Kabupaten Murung Raya","serial":6212,"type":2,"latitude":-0.1362171,"longitude":114.3341432,"status":1}, +{"nid":1609,"parent_nid":21,"name":"Kota Palangka Raya","serial":6271,"type":2,"latitude":-2.21,"longitude":113.92,"status":1}, +{"nid":1610,"parent_nid":22,"name":"Kabupaten Tanah Laut","serial":6301,"type":2,"latitude":-3.7694047,"longitude":114.8092691,"status":1}, +{"nid":1611,"parent_nid":22,"name":"Kabupaten Kota Baru","serial":6302,"type":2,"latitude":-6.332973,"longitude":106.807915,"status":1}, +{"nid":1612,"parent_nid":22,"name":"Kabupaten Banjar","serial":6303,"type":2,"latitude":-7.3666667,"longitude":108.5333333,"status":1}, +{"nid":1613,"parent_nid":22,"name":"Kabupaten Barito Kuala","serial":6304,"type":2,"latitude":-3.0714738,"longitude":114.6667939,"status":1}, +{"nid":1614,"parent_nid":22,"name":"Kabupaten Tapin","serial":6305,"type":2,"latitude":-2.9160746,"longitude":115.0465991,"status":1}, +{"nid":1615,"parent_nid":22,"name":"Kabupaten Hulu Sungai Selatan","serial":6306,"type":2,"latitude":-2.7662681,"longitude":115.2363408,"status":1}, +{"nid":1616,"parent_nid":22,"name":"Kabupaten Hulu Sungai Tengah","serial":6307,"type":2,"latitude":-2.6153162,"longitude":115.5207358,"status":1}, +{"nid":1617,"parent_nid":22,"name":"Kabupaten Hulu Sungai Utara","serial":6308,"type":2,"latitude":-2.4421225,"longitude":115.188916,"status":1}, +{"nid":1618,"parent_nid":22,"name":"Kabupaten Tabalong","serial":6309,"type":2,"latitude":-1.864302,"longitude":115.5681084,"status":1}, +{"nid":1619,"parent_nid":22,"name":"Kabupaten Tanah Bumbu","serial":6310,"type":2,"latitude":-3.4512244,"longitude":115.5681084,"status":1}, +{"nid":1620,"parent_nid":22,"name":"Kabupaten Balangan","serial":6311,"type":2,"latitude":-2.3260425,"longitude":115.6154732,"status":1}, +{"nid":1621,"parent_nid":22,"name":"Kota Banjarmasin","serial":6371,"type":2,"latitude":-3.328499,"longitude":114.589203,"status":1}, +{"nid":1622,"parent_nid":22,"name":"Kota Banjar Baru","serial":6372,"type":2,"latitude":-3.4666667,"longitude":114.75,"status":1}, +{"nid":1623,"parent_nid":23,"name":"Kabupaten Paser","serial":6401,"type":2,"latitude":-1.7175266,"longitude":115.9467997,"status":1}, +{"nid":1624,"parent_nid":23,"name":"Kabupaten Kutai Barat","serial":6407,"type":2,"latitude":0.1353881,"longitude":115.094045,"status":1}, +{"nid":1625,"parent_nid":23,"name":"Kabupaten Kutai Kartanegara","serial":6402,"type":2,"latitude":-0.1336655,"longitude":116.6081653,"status":1}, +{"nid":1626,"parent_nid":23,"name":"Kabupaten Kutai Timur","serial":6408,"type":2,"latitude":0.9433774,"longitude":116.9852422,"status":1}, +{"nid":1627,"parent_nid":23,"name":"Kabupaten Berau","serial":6403,"type":2,"latitude":2.0450883,"longitude":117.3616476,"status":1}, +{"nid":1628,"parent_nid":23,"name":"Kabupaten Malinau","serial":6406,"type":2,"latitude":3.584221,"longitude":116.647797,"status":1}, +{"nid":1629,"parent_nid":23,"name":"Kabupaten Bulungan","serial":6404,"type":2,"latitude":2.9042476,"longitude":116.9852422,"status":1}, +{"nid":1630,"parent_nid":23,"name":"Kabupaten Nunukan","serial":6405,"type":2,"latitude":4.0609227,"longitude":117.666952,"status":1}, +{"nid":1631,"parent_nid":23,"name":"Kabupaten Penajam Paser Utara","serial":6409,"type":2,"latitude":-1.2917094,"longitude":116.5137964,"status":1}, +{"nid":1632,"parent_nid":23,"name":"Kabupaten Tana Tidung","serial":6410,"type":2,"latitude":3.551869,"longitude":117.0794082,"status":1}, +{"nid":1633,"parent_nid":23,"name":"Kota Balikpapan","serial":6471,"type":2,"latitude":-1.2635389,"longitude":116.8278833,"status":1}, +{"nid":1634,"parent_nid":23,"name":"Kota Samarinda","serial":6472,"type":2,"latitude":-0.502183,"longitude":117.153801,"status":1}, +{"nid":1635,"parent_nid":23,"name":"Kota Tarakan","serial":6473,"type":2,"latitude":3.3,"longitude":117.6333333,"status":1}, +{"nid":1636,"parent_nid":23,"name":"Kota Bontang","serial":6474,"type":2,"latitude":0.1333333,"longitude":117.5,"status":1}, +{"nid":1637,"parent_nid":24,"name":"Kabupaten Bolaang Mongondow","serial":7101,"type":2,"latitude":0.6870994,"longitude":124.0641419,"status":1}, +{"nid":1638,"parent_nid":24,"name":"Kabupaten Minahasa","serial":7102,"type":2,"latitude":1,"longitude":124.5833333,"status":1}, +{"nid":1639,"parent_nid":24,"name":"Kabupaten Kepulauan Sangihe","serial":7103,"type":2,"latitude":3.5303212,"longitude":125.5438967,"status":1}, +{"nid":1640,"parent_nid":24,"name":"Kabupaten Kepulauan Talaud","serial":7104,"type":2,"latitude":4.092,"longitude":126.768,"status":1}, +{"nid":1641,"parent_nid":24,"name":"Kabupaten Minahasa Selatan","serial":7105,"type":2,"latitude":1.0946773,"longitude":124.4641848,"status":1}, +{"nid":1642,"parent_nid":24,"name":"Kabupaten Minahasa Utara","serial":7106,"type":2,"latitude":1.5327973,"longitude":124.994751,"status":1}, +{"nid":1643,"parent_nid":24,"name":"Kabupaten Bolaang Mongondow Utara","serial":7108,"type":2,"latitude":0.818691,"longitude":123.5280072,"status":1}, +{"nid":1644,"parent_nid":24,"name":"Kabupaten Siau Tagulandang Biaro","serial":7109,"type":2,"latitude":2.345964,"longitude":125.4124355,"status":1}, +{"nid":1645,"parent_nid":24,"name":"Kabupaten Minahasa Tenggara","serial":7107,"type":2,"latitude":1.0278551,"longitude":124.7298765,"status":1}, +{"nid":1646,"parent_nid":24,"name":"Kota Manado","serial":7171,"type":2,"latitude":1.4917014,"longitude":124.842843,"status":1}, +{"nid":1647,"parent_nid":24,"name":"Kota Bitung","serial":7172,"type":2,"latitude":1.4553529,"longitude":125.204697,"status":1}, +{"nid":1648,"parent_nid":24,"name":"Kota Tomohon","serial":7173,"type":2,"latitude":1.3234131,"longitude":124.8384504,"status":1}, +{"nid":1649,"parent_nid":24,"name":"Kota Kotamobagu","serial":7174,"type":2,"latitude":0.7333333,"longitude":124.3166667,"status":1}, +{"nid":1650,"parent_nid":25,"name":"Kabupaten Banggai Kepulauan","serial":7207,"type":2,"latitude":-1.6408137,"longitude":123.5504076,"status":1}, +{"nid":1651,"parent_nid":25,"name":"Kabupaten Banggai","serial":7201,"type":2,"latitude":-1.6408137,"longitude":123.5504076,"status":1}, +{"nid":1652,"parent_nid":25,"name":"Kabupaten Morowali","serial":7206,"type":2,"latitude":-2.3003072,"longitude":121.5370003,"status":1}, +{"nid":1653,"parent_nid":25,"name":"Kabupaten Poso","serial":7202,"type":2,"latitude":-1.391922,"longitude":120.766998,"status":1}, +{"nid":1654,"parent_nid":25,"name":"Kabupaten Donggala","serial":7203,"type":2,"latitude":-0.4233155,"longitude":119.8352303,"status":1}, +{"nid":1655,"parent_nid":25,"name":"Kabupaten Toli-Toli","serial":7204,"type":2,"latitude":0.8768231,"longitude":120.7579834,"status":1}, +{"nid":1656,"parent_nid":25,"name":"Kabupaten Buol","serial":7205,"type":2,"latitude":0.9695452,"longitude":121.3541631,"status":1}, +{"nid":1657,"parent_nid":25,"name":"Kabupaten Parigi Moutong","serial":7208,"type":2,"latitude":0.5817607,"longitude":120.8039474,"status":1}, +{"nid":1658,"parent_nid":25,"name":"Kabupaten Tojo Una-Una","serial":7209,"type":2,"latitude":-1.098757,"longitude":121.5370003,"status":1}, +{"nid":1659,"parent_nid":25,"name":"Kota Palu","serial":7271,"type":2,"latitude":-0.898583,"longitude":119.850601,"status":1}, +{"nid":1660,"parent_nid":26,"name":"Kabupaten Selayar","serial":7301,"type":2,"latitude":-6,"longitude":120.5,"status":1}, +{"nid":1661,"parent_nid":26,"name":"Kabupaten Bulukumba","serial":7302,"type":2,"latitude":-5.4329368,"longitude":120.2051096,"status":1}, +{"nid":1662,"parent_nid":26,"name":"Kabupaten Bantaeng","serial":7303,"type":2,"latitude":-5.5169316,"longitude":120.0202964,"status":1}, +{"nid":1663,"parent_nid":26,"name":"Kabupaten Jeneponto","serial":7304,"type":2,"latitude":-5.554579,"longitude":119.6730939,"status":1}, +{"nid":1664,"parent_nid":26,"name":"Kabupaten Takalar","serial":7305,"type":2,"latitude":-5.4162493,"longitude":119.4875668,"status":1}, +{"nid":1665,"parent_nid":26,"name":"Kabupaten Gowa","serial":7306,"type":2,"latitude":-5.3102888,"longitude":119.742604,"status":1}, +{"nid":1666,"parent_nid":26,"name":"Kabupaten Sinjai","serial":7307,"type":2,"latitude":-5.2171961,"longitude":120.112735,"status":1}, +{"nid":1667,"parent_nid":26,"name":"Kabupaten Maros","serial":7309,"type":2,"latitude":-4.94695,"longitude":119.578903,"status":1}, +{"nid":1668,"parent_nid":26,"name":"Kabupaten Pangkajene Dan Kepulauan","serial":7310,"type":2,"latitude":-4.805035,"longitude":119.5571677,"status":1}, +{"nid":1669,"parent_nid":26,"name":"Kabupaten Barru","serial":7311,"type":2,"latitude":-4.4172651,"longitude":119.6730939,"status":1}, +{"nid":1670,"parent_nid":26,"name":"Kabupaten Bone","serial":7308,"type":2,"latitude":-2.083333,"longitude":120.216667,"status":1}, +{"nid":1671,"parent_nid":26,"name":"Kabupaten Soppeng","serial":7312,"type":2,"latitude":-4.3518541,"longitude":119.9277947,"status":1}, +{"nid":1672,"parent_nid":26,"name":"Kabupaten Wajo","serial":7313,"type":2,"latitude":-4.022229,"longitude":120.0665236,"status":1}, +{"nid":1673,"parent_nid":26,"name":"Kabupaten Sidenreng Rappang","serial":7314,"type":2,"latitude":-3.7738981,"longitude":120.0202964,"status":1}, +{"nid":1674,"parent_nid":26,"name":"Kabupaten Pinrang","serial":7315,"type":2,"latitude":-3.793071,"longitude":119.6408,"status":1}, +{"nid":1675,"parent_nid":26,"name":"Kabupaten Enrekang","serial":7316,"type":2,"latitude":-3.563128,"longitude":119.7612,"status":1}, +{"nid":1676,"parent_nid":26,"name":"Kabupaten Luwu","serial":7317,"type":2,"latitude":-3.3052214,"longitude":120.2512728,"status":1}, +{"nid":1677,"parent_nid":26,"name":"Kabupaten Tana Toraja","serial":7318,"type":2,"latitude":-3.0753003,"longitude":119.742604,"status":1}, +{"nid":1678,"parent_nid":26,"name":"Kabupaten Luwu Utara","serial":7322,"type":2,"latitude":-2.2690446,"longitude":119.9740534,"status":1}, +{"nid":1679,"parent_nid":26,"name":"Kabupaten Luwu Timur","serial":7324,"type":2,"latitude":-2.5825518,"longitude":121.1710389,"status":1}, +{"nid":1680,"parent_nid":26,"name":"Kota Makassar","serial":7371,"type":2,"latitude":-5.1333333,"longitude":119.4166667,"status":1}, +{"nid":1681,"parent_nid":26,"name":"Kota Pare-Pare","serial":7372,"type":2,"latitude":-4.0166667,"longitude":119.6236111,"status":1}, +{"nid":1682,"parent_nid":26,"name":"Kota Palopo","serial":7373,"type":2,"latitude":-3,"longitude":120.2,"status":1}, +{"nid":1683,"parent_nid":27,"name":"Kabupaten Buton","serial":7404,"type":2,"latitude":-5.3096355,"longitude":122.9888319,"status":1}, +{"nid":1684,"parent_nid":27,"name":"Kabupaten Muna","serial":7403,"type":2,"latitude":-4.901629,"longitude":122.6277455,"status":1}, +{"nid":1685,"parent_nid":27,"name":"Kabupaten Konawe","serial":7402,"type":2,"latitude":-3.9380432,"longitude":122.0837445,"status":1}, +{"nid":1686,"parent_nid":27,"name":"Kabupaten Kolaka","serial":7401,"type":2,"latitude":-4.049665,"longitude":121.593803,"status":1}, +{"nid":1687,"parent_nid":27,"name":"Kabupaten Konawe Selatan","serial":7405,"type":2,"latitude":-4.2027915,"longitude":122.4467238,"status":1}, +{"nid":1688,"parent_nid":27,"name":"Kabupaten Bombana","serial":7406,"type":2,"latitude":-4.6543462,"longitude":121.9017954,"status":1}, +{"nid":1689,"parent_nid":27,"name":"Kabupaten Wakatobi","serial":7407,"type":2,"latitude":-5.3264442,"longitude":123.5951925,"status":1}, +{"nid":1690,"parent_nid":27,"name":"Kabupaten Kolaka Utara","serial":7408,"type":2,"latitude":-3.1347227,"longitude":121.1710389,"status":1}, +{"nid":1691,"parent_nid":27,"name":"Kabupaten Buton Utara","serial":7410,"type":2,"latitude":-4.7023424,"longitude":123.0338767,"status":1}, +{"nid":1692,"parent_nid":27,"name":"Kabupaten Konawe Utara","serial":7409,"type":2,"latitude":-3.3803291,"longitude":122.0837445,"status":1}, +{"nid":1693,"parent_nid":27,"name":"Kota Kendari","serial":7471,"type":2,"latitude":-3.972201,"longitude":122.5149028,"status":1}, +{"nid":1694,"parent_nid":27,"name":"Kota Bau-Bau","serial":7472,"type":2,"latitude":-5.46667,"longitude":122.633,"status":1}, +{"nid":1695,"parent_nid":28,"name":"Kabupaten Boalemo","serial":7502,"type":2,"latitude":0.7013419,"longitude":122.2653887,"status":1}, +{"nid":1696,"parent_nid":28,"name":"Kabupaten Gorontalo","serial":7501,"type":2,"latitude":0.5333333,"longitude":123.0666667,"status":1}, +{"nid":1697,"parent_nid":28,"name":"Kabupaten Pohuwato","serial":7504,"type":2,"latitude":0.7055278,"longitude":121.7195459,"status":1}, +{"nid":1698,"parent_nid":28,"name":"Kabupaten Bone Bolango","serial":7503,"type":2,"latitude":0.5657885,"longitude":123.3486147,"status":1}, +{"nid":1699,"parent_nid":28,"name":"Kabupaten Gorontalo Utara","serial":7505,"type":2,"latitude":0.9252647,"longitude":122.4920088,"status":1}, +{"nid":1700,"parent_nid":28,"name":"Kota Gorontalo","serial":7571,"type":2,"latitude":0.5333333,"longitude":123.0666667,"status":1}, +{"nid":1701,"parent_nid":29,"name":"Kabupaten Majene","serial":7605,"type":2,"latitude":-3.0297251,"longitude":118.9062794,"status":1}, +{"nid":1702,"parent_nid":29,"name":"Kabupaten Polewali Mandar","serial":7604,"type":2,"latitude":-3.3419323,"longitude":119.1390642,"status":1}, +{"nid":1703,"parent_nid":29,"name":"Kabupaten Mamasa","serial":7603,"type":2,"latitude":-2.960135,"longitude":119.368202,"status":1}, +{"nid":1704,"parent_nid":29,"name":"Kabupaten Mamuju","serial":7602,"type":2,"latitude":-2.7293364,"longitude":118.9295737,"status":1}, +{"nid":1705,"parent_nid":29,"name":"Kabupaten Mamuju Utara","serial":7601,"type":2,"latitude":-1.5264542,"longitude":119.5107708,"status":1}, +{"nid":1706,"parent_nid":30,"name":"Kabupaten Maluku Tenggara Barat","serial":8103,"type":2,"latitude":-7.5322642,"longitude":131.3611121,"status":1}, +{"nid":1707,"parent_nid":30,"name":"Kabupaten Maluku Tenggara","serial":8102,"type":2,"latitude":-5.7512455,"longitude":132.7271587,"status":1}, +{"nid":1708,"parent_nid":30,"name":"Kabupaten Maluku Tengah","serial":8101,"type":2,"latitude":-3.0166501,"longitude":129.4864411,"status":1}, +{"nid":1709,"parent_nid":30,"name":"Kabupaten Buru Selatan","serial":8109,"type":2,"latitude":-3.3927754,"longitude":126.7819505,"status":1}, +{"nid":1710,"parent_nid":30,"name":"Kabupaten Kepulauan Aru","serial":8107,"type":2,"latitude":-6.1946502,"longitude":134.5501935,"status":1}, +{"nid":1711,"parent_nid":30,"name":"Kabupaten Seram Bagian Barat","serial":8106,"type":2,"latitude":-3.1271575,"longitude":128.4008357,"status":1}, +{"nid":1712,"parent_nid":30,"name":"Kabupaten Seram Bagian Timur","serial":8105,"type":2,"latitude":-3.4150761,"longitude":130.390488,"status":1}, +{"nid":1713,"parent_nid":30,"name":"Kota Ambon","serial":8171,"type":2,"latitude":-3.65607,"longitude":128.166419,"status":1}, +{"nid":1714,"parent_nid":30,"name":"KotaTual","serial":8172,"type":2,"latitude":-5.640851,"longitude":132.7475093,"status":1}, +{"nid":1715,"parent_nid":31,"name":"Kabupaten Halmahera Barat","serial":8201,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1716,"parent_nid":31,"name":"Kabupaten Halmahera Tengah","serial":8202,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1717,"parent_nid":31,"name":"Kabupaten Kepulauan Sula","serial":8205,"type":2,"latitude":-1.8666667,"longitude":125.3666667,"status":1}, +{"nid":1718,"parent_nid":31,"name":"Kabupaten Halmahera Selatan","serial":8204,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1719,"parent_nid":31,"name":"Kabupaten Halmahera Utara","serial":8203,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1720,"parent_nid":31,"name":"Kabupaten Halmahera Timur","serial":8206,"type":2,"latitude":1.3121235,"longitude":128.4849923,"status":1}, +{"nid":1721,"parent_nid":31,"name":"Kota Ternate","serial":8271,"type":2,"latitude":0.7833333,"longitude":127.3666667,"status":1}, +{"nid":1722,"parent_nid":31,"name":"Kota Tidore Kepulauan","serial":8272,"type":2,"latitude":0.6833333,"longitude":127.4,"status":1}, +{"nid":1723,"parent_nid":32,"name":"Kabupaten Fakfak","serial":9203,"type":2,"latitude":-2.885237,"longitude":132.2658282,"status":1}, +{"nid":1724,"parent_nid":32,"name":"Kabupaten Kaimana","serial":9208,"type":2,"latitude":-3.660925,"longitude":133.774506,"status":1}, +{"nid":1725,"parent_nid":32,"name":"Kabupaten Teluk Wondama","serial":9207,"type":2,"latitude":-2.8551699,"longitude":134.3236557,"status":1}, +{"nid":1726,"parent_nid":32,"name":"Kabupaten Teluk Bintuni","serial":9206,"type":2,"latitude":-1.9056848,"longitude":133.329466,"status":1}, +{"nid":1727,"parent_nid":32,"name":"Kabupaten Manokwari","serial":9202,"type":2,"latitude":-0.8614531,"longitude":134.0620421,"status":1}, +{"nid":1728,"parent_nid":32,"name":"Kabupaten Sorong Selatan","serial":9204,"type":2,"latitude":-0.8666667,"longitude":131.25,"status":1}, +{"nid":1729,"parent_nid":32,"name":"Kota Sorong","serial":9271,"type":2,"latitude":-0.8666667,"longitude":131.25,"status":1}, +{"nid":1730,"parent_nid":32,"name":"Kabupaten Raja Ampat","serial":9205,"type":2,"latitude":-1.0915151,"longitude":130.8778586,"status":1}, +{"nid":1731,"parent_nid":32,"name":"Kabupaten Sorong","serial":9201,"type":2,"latitude":-0.8666667,"longitude":131.25,"status":1}, +{"nid":1732,"parent_nid":33,"name":"Kabupaten Merauke","serial":9101,"type":2,"latitude":-8.4960406,"longitude":140.3945527,"status":1}, +{"nid":1733,"parent_nid":33,"name":"Kabupaten Jayawijaya","serial":9102,"type":2,"latitude":-4.0004481,"longitude":138.7995122,"status":1}, +{"nid":1734,"parent_nid":33,"name":"Kabupaten Jayapura","serial":9103,"type":2,"latitude":-2.533,"longitude":140.717,"status":1}, +{"nid":1735,"parent_nid":33,"name":"Kabupaten Nabire","serial":9104,"type":2,"latitude":-3.5095462,"longitude":135.7520985,"status":1}, +{"nid":1736,"parent_nid":33,"name":"Kabupaten Kepulauan Yapen","serial":9105,"type":2,"latitude":-1.7469359,"longitude":136.1709012,"status":1}, +{"nid":1737,"parent_nid":33,"name":"Kabupaten Biak Numfor","serial":9106,"type":2,"latitude":-1.0381022,"longitude":135.9800848,"status":1}, +{"nid":1738,"parent_nid":33,"name":"Kabupaten Paniai","serial":9108,"type":2,"latitude":-3.7876441,"longitude":136.3624686,"status":1}, +{"nid":1739,"parent_nid":33,"name":"Kabupaten Puncak Jaya","serial":9107,"type":2,"latitude":-4.0836111,"longitude":137.1847222,"status":1}, +{"nid":1740,"parent_nid":33,"name":"Kabupaten Mimika","serial":9109,"type":2,"latitude":-4.4553223,"longitude":137.1362125,"status":1}, +{"nid":1741,"parent_nid":33,"name":"Kabupaten Boven Digoel","serial":9116,"type":2,"latitude":-5.7400018,"longitude":140.3481835,"status":1}, +{"nid":1742,"parent_nid":33,"name":"Kabupaten Mappi","serial":9117,"type":2,"latitude":-7.102232,"longitude":139.396393,"status":1}, +{"nid":1743,"parent_nid":33,"name":"Kabupaten Asmat","serial":9118,"type":2,"latitude":-5.0573958,"longitude":138.3988186,"status":1}, +{"nid":1744,"parent_nid":33,"name":"Kabupaten Yahukimo","serial":9113,"type":2,"latitude":-4.4939717,"longitude":139.5279996,"status":1}, +{"nid":1745,"parent_nid":33,"name":"Kabupaten Pegunungan Bintang","serial":9112,"type":2,"latitude":-4.5589872,"longitude":140.5135589,"status":1}, +{"nid":1746,"parent_nid":33,"name":"Kabupaten Tolikara","serial":9114,"type":2,"latitude":-3.481132,"longitude":138.4787258,"status":1}, +{"nid":1747,"parent_nid":33,"name":"Kabupaten Sarmi","serial":9110,"type":2,"latitude":-1.868727,"longitude":138.743607,"status":1}, +{"nid":1748,"parent_nid":33,"name":"Kabupaten Keerom","serial":9111,"type":2,"latitude":-3.3449536,"longitude":140.7624493,"status":1}, +{"nid":1749,"parent_nid":33,"name":"Kabupaten Waropen","serial":9115,"type":2,"latitude":-2.8435717,"longitude":136.670534,"status":1}, +{"nid":1750,"parent_nid":33,"name":"Kabupaten Supiori","serial":9119,"type":2,"latitude":-0.7295099,"longitude":135.6385125,"status":1}, +{"nid":1751,"parent_nid":33,"name":"Kabupaten Mamberamo Raya","serial":9120,"type":2,"latitude":-2.5331255,"longitude":137.7637565,"status":1}, +{"nid":1752,"parent_nid":33,"name":"Kota Jayapura","serial":9171,"type":2,"latitude":-2.533,"longitude":140.717,"status":1}, +{"nid":1753,"parent_nid":2,"name":"Kabupaten Labuhanbatu Utara","serial":1223,"type":2,"latitude":2.3465638,"longitude":99.8124935,"status":1}, +{"nid":1754,"parent_nid":2,"name":"Kabupaten Labuhanbatu Selatan","serial":1222,"type":2,"latitude":1.8799353,"longitude":100.1703257,"status":1}, +{"nid":1756,"parent_nid":2,"name":"Kabupaten Nias Utara","serial":1224,"type":2,"latitude":1.1255279,"longitude":97.5247243,"status":1}, +{"nid":1757,"parent_nid":2,"name":"Kabupaten Nias Barat","serial":1225,"type":2,"latitude":1.1255279,"longitude":97.5247243,"status":1}, +{"nid":1758,"parent_nid":2,"name":"Kota Gunungsitoli","serial":1278,"type":2,"latitude":1.281964,"longitude":97.61594,"status":1}, +{"nid":1759,"parent_nid":4,"name":"Kabupaten Kepulauan Meranti","serial":1410,"type":2,"latitude":0.9208765,"longitude":102.6675575,"status":1}, +{"nid":1760,"parent_nid":5,"name":"Kota Sungai Penuh","serial":1572,"type":2,"latitude":-2.06314,"longitude":101.387199,"status":1}, +{"nid":1761,"parent_nid":7,"name":"Kabupaten Bengkulu Tengah","serial":1709,"type":2,"latitude":-3.7955556,"longitude":102.2591667,"status":1}, +{"nid":1762,"parent_nid":8,"name":"Kabupaten Tulangbawang Barat","serial":1806,"type":2,"latitude":-4.5256967,"longitude":105.0791228,"status":1}, +{"nid":1763,"parent_nid":8,"name":"Kabupaten Pringsewu","serial":1810,"type":2,"latitude":-5.3539884,"longitude":104.9622498,"status":1}, +{"nid":1764,"parent_nid":8,"name":"Kabupaten Mesuji","serial":1811,"type":2,"latitude":-4.0044783,"longitude":105.3131185,"status":1}, +{"nid":1765,"parent_nid":10,"name":"Kabupaten Lingga","serial":2104,"type":2,"latitude":-0.1627686,"longitude":104.6354631,"status":1}, +{"nid":1766,"parent_nid":10,"name":"Kabupaten Anambas","serial":2105,"type":2,"latitude":3.1055459,"longitude":105.6537231,"status":1}, +{"nid":1767,"parent_nid":14,"name":"Kabupaten Sleman","serial":3404,"type":2,"latitude":-7.716165,"longitude":110.335403,"status":1}, +{"nid":1768,"parent_nid":16,"name":"Kota Tangerang Selatan","serial":3674,"type":2,"latitude":-6.2888889,"longitude":106.7180556,"status":1}, +{"nid":1769,"parent_nid":18,"name":"Kabupaten Lombok Utara","serial":5208,"type":2,"latitude":-8.3739076,"longitude":116.2777073,"status":1}, +{"nid":1770,"parent_nid":19,"name":"Kabupaten Sabu Raijua","serial":5302,"type":2,"latitude":-10.5541116,"longitude":121.8334868,"status":1}, +{"nid":1771,"parent_nid":24,"name":"Kabupaten Bolang Mongondow Timur","serial":7110,"type":2,"latitude":0.7152651,"longitude":124.4641848,"status":1}, +{"nid":1772,"parent_nid":24,"name":"Kabupaten Bolang Mongondow Selatan","serial":7111,"type":2,"latitude":0.4053215,"longitude":123.8411288,"status":1}, +{"nid":1773,"parent_nid":25,"name":"Kabupaten Sigi","serial":7210,"type":2,"latitude":-1.3834127,"longitude":120.0665236,"status":1}, +{"nid":1774,"parent_nid":26,"name":"Kabupaten Toraja Utara","serial":7326,"type":2,"latitude":-2.8621942,"longitude":119.8352303,"status":1}, +{"nid":1775,"parent_nid":30,"name":"Kabupaten Maluku Barat Daya","serial":8108,"type":2,"latitude":-7.7851588,"longitude":126.3498097,"status":1}, +{"nid":1776,"parent_nid":30,"name":"Kabupaten Buru","serial":8104,"type":2,"latitude":-3.3927754,"longitude":126.7819505,"status":1}, +{"nid":1778,"parent_nid":31,"name":"Kabupaten Pulau Morota","serial":8207,"type":2,"latitude":2.3656672,"longitude":128.4008357,"status":1}, +{"nid":1789,"parent_nid":32,"name":"Kabupaten Tambrauw","serial":9209,"type":2,"latitude":-0.781856,"longitude":132.3938375,"status":1}, +{"nid":1790,"parent_nid":32,"name":"Kabupaten Maybat","serial":9210,"type":2,"latitude":3.1472,"longitude":101.6997,"status":1}, +{"nid":1791,"parent_nid":33,"name":"Kabupaten Memberamo Tengah","serial":9121,"type":2,"latitude":-2.3745692,"longitude":138.3190276,"status":1}, +{"nid":1792,"parent_nid":33,"name":"Kabupaten Yalimo","serial":9122,"type":2,"latitude":-3.7852847,"longitude":139.4466005,"status":1}, +{"nid":1793,"parent_nid":33,"name":"Kabupaten Lanny Jaya","serial":9123,"type":2,"latitude":-3.971033,"longitude":138.3190276,"status":1}, +{"nid":1794,"parent_nid":33,"name":"Kabupaten Nduga","serial":9124,"type":2,"latitude":-4.4069496,"longitude":138.2393528,"status":1}, +{"nid":1795,"parent_nid":33,"name":"Kabupaten Puncak","serial":9125,"type":2,"latitude":-6.7125476,"longitude":106.9542425,"status":1}, +{"nid":1796,"parent_nid":33,"name":"Kabupaten Dogiyai","serial":9126,"type":2,"latitude":-4.0193872,"longitude":135.9610446,"status":1}, +{"nid":1797,"parent_nid":33,"name":"Kabupaten Intan Jaya","serial":9127,"type":2,"latitude":-3.5076422,"longitude":136.7478493,"status":1}, +{"nid":1798,"parent_nid":33,"name":"Kabupaten Deiyai","serial":9128,"type":2,"latitude":-4.0974893,"longitude":136.4393054,"status":1} +] \ No newline at end of file diff --git a/src/androgpio/mycodes.java b/src/androgpio/mycodes.java new file mode 100644 index 0000000..96e9b38 --- /dev/null +++ b/src/androgpio/mycodes.java @@ -0,0 +1,1159 @@ +package androgpio; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import androgpio.customsocket.JsonArray; +import androgpio.customsocket.JsonObject; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Bit; +import anywheresoftware.b4a.keywords.DateTime; + + +public class mycodes { + private static final String gpiofolder = "/sys/class/gpio"; + public static final String gpioexportfolder = gpiofolder + "/export"; + public static final String gpiounexportfolder = gpiofolder + "/unexport"; + + public static String gpio_direction_path(int pin) { + return gpio_path(pin)+"/direction"; + } + + public static String gpio_value_path(int pin) { + return gpio_path(pin)+"/value"; + } + + public static String gpio_path(int pin) { + return gpiofolder+"/gpio"+String.valueOf(pin); + } + + public static String path_combine(String...filename) { + if (filename != null && filename.length > 0) { + StringBuilder str = new StringBuilder(); + for (String xx : filename) { + if (str.length() > 0) + str.append("/"); + str.append(xx); + } + return str.toString(); + } + return ""; + } + + /** + * Convert Datetime Tick to Date String, on format DD-MM-YYYY + * @param tick : datetime tick in long + * @return empty string if tick less than 1 + */ + public static String Tick_To_DDMMYYYY(long tick) { + if (tick>0) { + StringBuilder str = new StringBuilder(); + int temp; + temp = DateTime.GetDayOfMonth(tick); + if (temp<10) str.append("0"); + str.append(temp); + str.append("-"); + + temp = DateTime.GetMonth(tick); + if (temp<10) str.append("0"); + str.append(temp); + str.append("-"); + + str.append(DateTime.GetYear(tick)); + return str.toString(); + } + return ""; + } + + /** + * Convert Datetime Tick to Time String, on format HH:MM:SS + * @param tick : datetime tick in long + * @return empty string if tick less than 1 + */ + public static String Tick_To_HHMMSS(long tick) { + if (tick>0) { + StringBuilder str = new StringBuilder(); + int temp; + temp = DateTime.GetHour(tick); + if (temp<10) str.append("0"); + str.append(temp); + str.append(":"); + + temp = DateTime.GetMinute(tick); + if (temp<10) str.append("0"); + str.append(temp); + str.append(":"); + + temp = DateTime.GetSecond(tick); + if (temp<10) str.append("0"); + str.append(temp); + return str.toString(); + } + return ""; + } + + /** + * Convert Datetime Tick to String , on format DD-MM-YYYY HH:MM:SS + * @param tick : datetime tick in long + * @return empty string if tick less than 1 + */ + public static String Tick_To_DDMMYYYY_HHMMSS(long tick) { + if (tick>0) { + return Tick_To_DDMMYYYY(tick) + " "+ Tick_To_HHMMSS(tick); + } else return ""; + + } + + /** + * Check if GPIO exist + * + * @param pinnya : pin number + * @return true if exist + */ + public static boolean GPIO_Exist(int pinnya) { + + File ff = new File(gpiofolder + "/" + GetGPIOName(pinnya)); + if (ff != null) { + if (ff.exists()) + return true; + } + return false; + } + + /** + * Check if file is exists, readable and writable + * @param dir Directory + * @param filename Filename + * @return true if file is exists, readable and writable + */ + public static boolean path_exists(String dir, String filename) { + File ff = new File(dir, filename); + if (ff != null) { + if (ff.isFile()) { + return true; + } + + } + return false; + } + + public static boolean path_readable(String dir, String filename) { + File ff = new File(dir, filename); + if (ff != null) { + if (ff.isFile()) { + if (ff.canRead()) { + return true; + } + } + } + return false; + } + + public static boolean path_writable(String dir, String filename) { + File ff = new File(dir, filename); + if (ff != null) { + if (ff.isFile()) { + if (ff.canWrite()) { + return true; + } + } + } + return false; + } + + /** + * Execute sudo di Android, tapi gak butuh result balik nya + * @param strings + */ + public static boolean sudo(String...strings) { + try{ + Process su = Runtime.getRuntime().exec("su"); + System.out.println("getting process for su"); + DataOutputStream outputStream = new DataOutputStream(su.getOutputStream()); + + for (String s : strings) { + outputStream.writeBytes(s+"\n"); + System.out.println("writing "+s); + outputStream.flush(); + } + + outputStream.writeBytes("exit\n"); + System.out.println("Writing exit"); + outputStream.flush(); + try { + su.waitFor(); + } catch (InterruptedException e) { + System.out.println("sudo error:"+e.getMessage()); + } + outputStream.close(); + return true; + }catch(IOException e){ + System.out.println("sudo error:"+e.getMessage()); + + } + return false; + } + + /** + * Execute command dengan sudo, dan menunggu hasil nya + * @param strings commands + * @return string hasil nya + */ + public static String sudoForResult(String...strings) { + String res = ""; + DataOutputStream outputStream = null; + InputStream response = null; + try{ + Process su = Runtime.getRuntime().exec("su"); + outputStream = new DataOutputStream(su.getOutputStream()); + response = su.getInputStream(); + + for (String s : strings) { + outputStream.writeBytes(s+"\n"); + outputStream.flush(); + } + + outputStream.writeBytes("exit\n"); + outputStream.flush(); + try { + su.waitFor(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + res = readFully(response); + } catch (IOException e){ + e.printStackTrace(); + } finally { + Closer.closeSilently(outputStream, response); + } + return res; + } + + public static String readFully(InputStream is) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = is.read(buffer)) != -1) { + baos.write(buffer, 0, length); + } + return baos.toString("UTF-8"); + } + + public static String FileRead(File f) { + try(FileInputStream fis = new FileInputStream(f)){ + byte[] buf = new byte[fis.available()]; + fis.read(buf); + fis.close(); + return new String(buf); + } catch (IOException ignored) { + + } + return null; + } + + /** + * Write value to file + * + * @param f : file to write + * @param value : value to write + * @return true if success + */ + public static boolean FileWrite(File f, String value) { + return FileWrite(f, value.getBytes()); + } + + + + /** + * Write value to file + * + * @param f : file to write + * @param value : value to write + * @return true if success + */ + public static boolean FileWrite(File f, byte[] value) { + try (FileOutputStream fos = new FileOutputStream(f)) { + fos.write(value); + fos.close(); + return true; + } catch (IOException ignored) { + + } + return false; + } + + /** + * Export GPIO pin + * + * @param pinnya : pin number + * @return true if success + * @throws IOException + * @throws SecurityException + */ + public static boolean GPIO_Export(int pinnya) throws SecurityException, IOException { + File ff = new File(gpioexportfolder); + if (ff != null) { + if (ff.exists()) { + if (ff.canWrite()) { + FileOutputStream fos = new FileOutputStream(ff); + String datatowrite = String.valueOf(pinnya); + fos.write(datatowrite.getBytes()); + fos.close(); + + File fd = new File(gpio_path(pinnya)); + if (fd.exists()) { + return true; + } else BA.Log("GPIO Export failed, "+fd.getAbsolutePath()+" is not exists after export"); + } else BA.Log("GPIO Export failed, "+ff.getAbsolutePath()+" is not writable"); + } else BA.Log("GPIO Export failed, "+ff.getAbsolutePath()+" not exists"); + } + return false; + } + + /** + * Un-Export GPIO pin + * + * @param pinnya : pin number + * @return true if success + * @throws IOException + * @throws SecurityException + */ + public static boolean GPIO_UnExport(int pinnya) throws IOException,SecurityException { + File ff = new File(gpiounexportfolder); + if (ff != null) { + if (ff.exists()) { + if (ff.canWrite()) { + FileOutputStream fos = new FileOutputStream(ff); + String datatowrite = String.valueOf(pinnya); + fos.write(datatowrite.getBytes()); + fos.close(); + return true; + } else BA.Log("GPIO Unexport failed, "+ff.getAbsolutePath()+" is not writable"); + } else BA.Log("GPIO Unexport failed, "+ff.getAbsolutePath()+" not exists"); + } + return false; + } + + /** + * Get GPIO direction + * + * @param pinnya : pin number + * @return empty string if failed, "in" = Input, "out" = Output + * @throws IOException + * @throws SecurityException + */ + public static String GPIO_GetDirection(int pinnya) throws IOException,SecurityException { + File ff = new File(gpio_direction_path(pinnya)); + if (ff != null) { + if (ff.exists()) { + if (ff.canRead()) { + + FileInputStream fis = new FileInputStream(ff); + + byte[] buf = new byte[5]; + int xx = -1; + int ii = 0; + while (true) { + xx = fis.read(); + if (xx == -1) + break; + if (xx == 13) + continue; // skip CR + if (xx == 10) + continue; // skip LF + buf[ii] = (byte) xx; + if (ii < buf.length - 1) + ii += 1; + + } + fis.close(); + return new String(buf); + } else BA.Log("GPIO_GetDirection failed, "+ff.getAbsolutePath()+" is not readable"); + } else BA.Log("GPIO_GetDirection failed, "+ff.getAbsolutePath()+" not exists"); + } + return ""; + } + + /** + * Set GPIO direction + * + * @param pinnya : pin number + * @param asOutput : true for Output pin, and false for Input pin + * @return true if success + * @throws IOException + * @throws SecurityException + */ + public static boolean GPIO_SetDirection(int pinnya, boolean asOutput) throws IOException,SecurityException { + File ff = new File(gpio_direction_path(pinnya)); + if (ff != null) { + if (ff.exists()) { + if (ff.canWrite()) { + FileOutputStream fos = new FileOutputStream(ff); + fos.write(asOutput ? "out".getBytes():"in".getBytes()); + fos.close(); + return true; + } else BA.Log("GPIO_SetDirection failed for "+ff.getAbsolutePath()+" because not writable"); + } else BA.Log("GPIO_SetDirection failed for "+ff.getAbsolutePath()+", because not exists"); + } + return false; + } + + /** + * Get Value from GPIO + * + * @param pinnya : pin number + * @return 0 = low, 1 = high, -1 = unknown / failed + * @throws IOException + * @throws SecurityException + */ + public static int GPIO_GetValue(int pinnya) throws IOException,SecurityException { + int result = -1; + File ff = new File(gpio_value_path(pinnya)); + if (ff != null) { + if (ff.exists()) { + if (ff.canRead()) { + FileInputStream fis = new FileInputStream(ff); + int vv = fis.read(); + fis.close(); + if (vv == '0') { + result = 0; + } else if (vv == '1') { + result = 1; + } + + } else BA.Log("GPIO_GetValue failed, "+ff.getAbsolutePath()+" is not readable"); + } else BA.Log("GPIO_GetValue failed, "+ff.getAbsolutePath()+" not exits"); + } + return result; + } + + /** + * Set Value to GPIO + * + * @param pinnya : pin number + * @param isON : true = high, false = low + * @return true if success + * @throws IOException + * @throws SecurityException + */ + public static boolean GPIO_SetValue(int pinnya, boolean isON) throws IOException,SecurityException { + File ff = new File(gpio_value_path(pinnya)); + if (ff != null) { + if (ff.exists()) { + if (ff.canWrite()) { + FileOutputStream fos = new FileOutputStream(ff); + fos.write(isON ? '1':'0'); + fos.close(); + return true; + } else BA.Log("GPIO_SetValue failed, "+ff.getAbsolutePath()+" is not writable"); + } else BA.Log("GPIO_SetValue failed, "+ff.getAbsolutePath()+" not exists"); + } + return false; + } + + + + /** + * Translate to correct gpio name + * + * @param pinnya : pin number + * @return + */ + private static String GetGPIOName(int pinnya) { + return "gpio" + pinnya; + } + + public static String printbytes(byte[] bb) { + if (bb == null) + return ""; + if (bb.length < 0) + return ""; + StringBuilder result = new StringBuilder(); + for (byte xx : bb) { + if (result.length() > 0) + result.append(" , "); + result.append(Bit.ToHexString(xx & 0xFF)); + } + return result.toString(); + } + + /** + * Change format of GPS Latitude String for ddmm.mmmm to dd.ddddd + * + * @param vv : string in format ddmm.mmmm + * @param result : array double , length 1 , untuk passing hasilnya + * @return false kalau gagal + */ + public static boolean Gps_Latitude_formatchange(String vv, double[] result) { + if (result == null) + return false; + if (result.length < 1) + return false; // gak ada tempatnya + if (vv.length() < 6) + return false; // minimal ddmm.m --> 6 karakter + if (vv.indexOf('.') != 4) + return false; // tidak ada titik nya + + try { + + double dd = Double.parseDouble(vv.substring(0, 2)); // dd + double mm = Double.parseDouble(vv.substring(2)); // mm.mmmm + + result[0] = Gps_Latitude_Round(dd + (mm / 60)); + return true; + } catch (IndexOutOfBoundsException | NumberFormatException | NullPointerException e) { + return false; + } + } + + /** + * Change format of GPS Longitude String from dddmm.mmmm to dd.dddddd + * + * @param vv : String in format dddmm.mmmmm + * @param result : array double, length 1, untuk passing hasil + * @return false kalau gagal + */ + public static boolean Gps_Longitude_formatchange(String vv, double[] result) { + if (result == null) + return false; + if (result.length < 1) + return false; // gak ada tempatnya + if (vv.length() < 7) + return false; // minimal dddmm.m --> 7 karakter + if (vv.indexOf('.') != 5) + return false; // tidak ada titiknya + + try { + double dd = Double.parseDouble(vv.substring(0, 3)); // ddd + double mm = Double.parseDouble(vv.substring(3)); // mm.mmmmm + result[0] = Gps_Longitude_Round(dd + (mm / 60)); + return true; + } catch (IndexOutOfBoundsException | NumberFormatException | NullPointerException e) { + return false; + } + } + + /** + * Convert GPS Time String to array of byte + * + * @param vv : GPS time String (hhmmss.ss or hhmmss) + * @param result : array of byte, length = 3, {hour, minute, second} + * @return true if conversion success + */ + public static boolean Gps_UTCTime(String vv, byte[] result) { + if (result == null) + return false; + if (result.length < 3) + return false; // gak ada tempatnya + if (vv.length() < 6) + return false; // minimal hhmmss + + int indextitik = vv.indexOf("."); // kalau ada desimal second + if (indextitik != -1) { + vv = vv.substring(0, indextitik); // potong aja + } + + try { + result[0] = Byte.parseByte(vv.substring(0, 2)); + result[1] = Byte.parseByte(vv.substring(2, 4)); + result[2] = Byte.parseByte(vv.substring(4)); + return true; + } catch (IndexOutOfBoundsException | NumberFormatException e) { + BA.Log("Error getting GPS_UTCTime, Msg : " + e.getMessage() + ", Caused : " + e.getCause()); + return false; + } + } + + /** + * Convert GPS Date String to array of byte + * + * @param vv : GPS Date String (ddmmyy) + * @param result : array of byte, length = 3, {day, month, year} + * @return true if conversion success + */ + public static boolean Gps_UTCDate(String vv, byte[] result) { + if (result == null) + return false; + if (result.length < 3) + return false; // gak ada tempatnya + if (vv.length() < 6) + return false; // harus ddmmyy + + try { + result[0] = Byte.parseByte(vv.substring(0, 2)); + result[1] = Byte.parseByte(vv.substring(2, 4)); + result[2] = Byte.parseByte(vv.substring(4)); + return true; + } catch (IndexOutOfBoundsException | NumberFormatException e) { + BA.Log("Error getting GPS_UTCDate, Msg : " + e.getMessage() + ", Caused : " + e.getCause()); + return false; + } + } + + /** + * Get Longitude Polarity + * + * @param vv : W = negative, E = positive, other = 0 + * @return 1 / -1 / 0 + */ + public static int Longitude_Polarity(String vv) { + if (vv != null) { + if (vv.length() > 0) { + byte[] xx = vv.getBytes(); + if (xx[0] == 87) // W + return -1; + else if (xx[0] == 69) // E + return 1; + else + return 0; // other + } + } + return 0; // invalid + } + + /** + * Convert GPS String to Double + * + * @param vv : string in format xx.xxxx + * @param result : array of double, length = 1 + * @return true if conversion success + */ + public static boolean Gps_Double(String vv, double[] result) { + if (result == null) + return false; + if (result.length < 1) + return false; + + try { + double xx = Double.parseDouble(vv); + result[0] = xx; + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * Convert GPS String to Integer + * + * @param vv : String in integer + * @param result : array of integer, length = 1 + * @return true if conversion success + */ + public static boolean Gps_Int(String vv, int[] result) { + if (result == null) + return false; + if (result.length < 1) + return false; + try { + int xx = Integer.parseInt(vv); + result[0] = xx; + return true; + } catch (NumberFormatException e) { + return false; + } + + } + + /** + * Get first Char value from a GPS String + * + * @param vv : GPS String + * @return char value + */ + public static char Gps_Char(String vv) { + if (vv == null) + return (char) 0; + if (vv.length() < 1) + return (char) 0; + return vv.charAt(0); + } + + /** + * Force a value become Postive + * + * @param vv : value + * @return positive value from vv + */ + public static double SetPositive(double vv) { + if (vv < 0) + return -vv; + else + return vv; + } + + /** + * Force a value become Negative + * + * @param vv : value + * @return negative value from vv + */ + public static double SetNegative(double vv) { + if (vv > 0) + return -vv; + else + return vv; + } + + /** + * Convert GPS Error List to a String, separated by CRLF + * + * @param err : error list + * @return String + */ + public static String Gps_ErrList_to_String(List err) { + if (err == null) + return ""; + if (err.size() < 1) + return ""; + + StringBuilder str = new StringBuilder(); + + for (String xx : err) { + if (str.length() > 0) + str.append("\r\n"); + str.append(xx.trim()); + } + + return str.toString(); + } + + /** + * Make Latitude fractions to 4 digits + * + * @param vv : Latitude in original + * @return Latitude in dd.dddd + */ + public static double Gps_Latitude_Round(double vv) { + return Math.round(vv * 10000) / 10000.0; + } + + /** + * Make Longitude fractions to 4 digits + * + * @param vv : Longitude in original + * @return Longitude in ddd.dddd + */ + public static double Gps_Longitude_Round(double vv) { + return Math.round(vv * 10000) / 10000.0; + } + + /** + * Make 16bit value from 2 bytes + * @param Hbyte : high byte + * @param Lbyte : low byte + * @return value in 16bit + */ + public static int Make_Int16(byte Hbyte, byte Lbyte) { + int result = Bit.And(Hbyte, 0xFF); + result = result << 8; + result = result + Bit.And(Lbyte, 0xFF); + return result; + } + + /** + * Low byte of a 16bit value + * @param value16bit : value to get + * @return low byte of value16bit + */ + public static byte LowByte(int value16bit) { + return (byte) value16bit; + } + + /** + * low byte of a 16bit value, in unsigned value + * @param value16bit : value to get + * @return low byte of value16bit + */ + public static int LowByte_Unsigned(int value16bit) { + return Bit.And(value16bit, 0xFF); + } + + /** + * High byte of a 16bit value + * @param value16bit value to get + * @return high byte of value16bit + */ + public static byte HighByte(int value16bit) { + return (byte) (value16bit >> 8); + } + + /** + * High byte of a 16bit value, in unsigned value + * @param value16bit : value to get + * @return high byte of value16bit + */ + public static int HighByte_Unsigned(int value16bit) { + int result = Bit.And(value16bit, 0xFF00); + return Bit.ShiftRight(result, 8); + } + + public static boolean valid_string(String value) { + if (value!=null) { + return value.length()>0; + } + return false; + } + + public static List GetInputStreamString(Process pp) { + if (pp!=null) { + InputStream is = pp.getInputStream(); + if (is!=null) { + List result = new ArrayList<>(); + try(BufferedReader reader = new BufferedReader(new InputStreamReader(is))){ + while(true) { + String xx = reader.readLine(); + if (xx==null) break; + if (xx!=null) result.add(xx); + } + reader.close(); + return result; + } catch(Exception e) { + System.out.println("GetInputStreamString exception = "+e.getMessage()); + } + } + } + return new ArrayList(); + } + + public static String ConvertToByteString(String value) { + if (valid_string(value)) { + byte[] valuebyte = value.getBytes(StandardCharsets.UTF_8); + if (valuebyte!=null && valuebyte.length>0) { + StringBuilder str = new StringBuilder(); + str.append("{"); + for(byte xx : valuebyte) { + str.append(xx & 0xFF).append(" "); + } + str.append("}"); + return str.toString(); + } + } + return "{0}"; + } + + public static String GetValueAfterKey(String source, String key) { + if (valid_string(key) && valid_string(source)) { + int i1 = source.indexOf(key); + if (i1>0) { + int i2 = source.indexOf(" ",i1+key.length()); + String result = i2>0 ? source.substring(i1+key.length(), i2) : source.substring(i1+key.length()); + return result; + } + } + return null; + } + + public static int ToInt(String value, int defaultvalue) { + try { + Integer result = Integer.valueOf(value); + return result; + } catch(NumberFormatException e) { + return defaultvalue; + } + } + + public static long ToLong(String value, long defaultvalue) { + try { + Long result = Long.valueOf(value); + return result; + } catch(NumberFormatException e) { + return defaultvalue; + } + } + + public static String Combine(final String combiner, final String...strings) { + if (strings!=null && strings.length>0) { + StringBuilder str = new StringBuilder(); + for(String x : strings) { + if (valid_string(x)) { + if (str.length()>0) str.append(combiner); + str.append(x); + } + } + return str.toString(); + } + return ""; + } + + public static String Combine(String combiner, final Object... values) { + if (values != null && values.length > 0) { + StringBuilder str = new StringBuilder(); + for (Object xx : values) { + if (xx instanceof String && valid_string((String) xx)) { + if (str.length() > 0) + str.append(combiner); + str.append(String.valueOf(xx)); + } + } + return str.toString(); + } + return ""; + } + + + public static void Log(BA ba, String...strings) { + if (strings!=null && strings.length>0) { + StringBuilder str = new StringBuilder(); + for(String xx : strings) { + if (str.length()>0) str.append(" "); + str.append(xx); + } + if (ba!=null) { + BA.Log(str.toString()); + } else { + System.out.println(str.toString()); + } + } + } + + public static InetAddress ToInetAddress(String value) { + try { + return InetAddress.getByName(value); + } catch (UnknownHostException | SecurityException e) { + return null; + } + } + + public static boolean valid_stringarray(String[] value) { + return value != null && value.length > 0; + } + + public static boolean valid_bytearray(byte[] value) { + return value != null && value.length > 0; + } + + public static boolean valid_JsonObject(JsonObject value) { + return value != null && value.HasSomeValue(); + } + + public static boolean valid_JsonArray(JsonArray value) { + return value != null && value.HasSomeValue(); + } + + public static boolean String_Is_JSONObject(String value) { + if (valid_string(value)) { + try { + new org.json.JSONObject(value); + return true; + } catch (org.json.JSONException e) { + + } + } + return false; + } + + + + + + public static boolean String_Is_JSONArray(String value) { + if (valid_string(value)) { + try { + new org.json.JSONArray(value); + return true; + } catch (org.json.JSONException e) { + + } + } + return false; + } + + public static boolean valid_Map(anywheresoftware.b4a.objects.collections.Map value) { + return value != null && value.IsInitialized() && value.getSize() > 0; + } + + public static boolean valid_List(anywheresoftware.b4a.objects.collections.List value) { + return value != null && value.IsInitialized() && value.getSize() > 0; + } + + public static String MaptoJsonString(anywheresoftware.b4a.objects.collections.Map value) { + if (valid_Map(value)) { + JSONObject xx = new JSONObject(value.getObject()); + return xx.toString(); + } + return "{}"; + } + + public static String ListtoJsonString(anywheresoftware.b4a.objects.collections.List value) { + if (valid_List(value)) { + JSONArray xx = new JSONArray(value.getObject()); + return xx.toString(); + } + return "[]"; + } + + public static String ArrayObjecttoJsonString(Object[] value) { + if (value != null && value.length > 0) { + JSONArray xx; + try { + xx = new JSONArray(value); + return xx.toString(); + } catch (JSONException e) { + BA.Log("ArrayObjecttoJsonString error : "+e.getMessage()); + } + + } + return "[]"; + } + + public static String Byte_toHex(byte xx) { + return Bit.ToHexString(Bit.And(xx, 0xFF)); + } + + public static String Int8_toHex(int xx) { + return Bit.ToHexString(Bit.And(xx, 0xFF)); + } + + public static String Int16_ToHex(int xx) { + return Bit.ToHexString(Bit.And(xx, 0xFFFF)); + } + + public static String Int32_ToHex(int xx) { + return Bit.ToHexString(xx); + } + + public static boolean Array_contain_String(String[] array, String value) { + if (array != null && array.length > 0) { + for (String xx : array) { + if (xx.equals(value)) + return true; + } + } + return false; + } + + public static boolean ObjectisString(Object value) { + return value!=null ? String.class.isInstance(value) : false; + } + + public static boolean ObjectisBoolean(Object value) { + return value != null ? Boolean.class.isInstance(value) : false; + } + + public static boolean ObjectisByte(Object value) { + return value != null ? Byte.class.isInstance(value) : false; + } + + public static boolean ObjectisInteger(Object value) { + return value != null ? Integer.class.isInstance(value) : false; + } + + public static boolean ObjectisDouble(Object value) { + return value != null ? Double.class.isInstance(value) : false; + } + + public static boolean ObjectisFloat(Object value) { + return value != null ? Float.class.isInstance(value) : false; + } + + public static boolean ObjectisLong(Object value) { + return value != null ? Long.class.isInstance(value) : false; + } + + public static boolean ObjectisShort(Object value) { + return value != null ? Short.class.isInstance(value) : false; + } + + public static boolean ObjectisCharacter(Object value) { + return value != null ? Character.class.isInstance(value) : false; + } + + public static boolean ObjectisByteArray(Object value) { + return value != null ? byte[].class.isInstance(value) : false; + } + + public static boolean ObjectisIntArray(Object value) { + return value != null ? int[].class.isInstance(value) : false; + } + + public static boolean ObjectisDoubleArray(Object value) { + return value != null ? double[].class.isInstance(value) : false; + } + + public static boolean ObjectisFloatArray(Object value) { + return value != null ? float[].class.isInstance(value) : false; + } + + public static boolean ObjectisLongArray(Object value) { + return value != null ? long[].class.isInstance(value) : false; + } + + public static boolean ObjectisShortArray(Object value) { + return value != null ? short[].class.isInstance(value) : false; + } + + public static boolean ObjectisCharArray(Object value) { + return value != null ? char[].class.isInstance(value) : false; + } + + public static boolean ObjectisBooleanArray(Object value) { + return value != null ? boolean[].class.isInstance(value) : false; + } + + public static boolean ObjectisObjectArray(Object value) { + return value != null ? Object[].class.isInstance(value) : false; + } + + public static boolean ObjectisStringArray(Object value) { + return value != null ? String[].class.isInstance(value) : false; + } + + public static double pembulatan_1desimal(double value) { + int temp = (int) (value * 10); + return temp / 10.0; + } + + public static double pembulatan_2desimal(double value) { + int temp = (int) (value * 100); + return temp / 100.0; + } + + public static float pembulatan_1desimal(float value) { + int temp = (int) (value * 10); + return temp / 10.0f; + } + + public static float pembulatan_2desimal(float value) { + int temp = (int) (value * 100); + return temp / 100.0f; + } + + /** + * Repair Json String + * java will add escape characters, so we need to remove it + * @param value : json string + * @return repaired json string + */ + public static String repair_json(String value) { + if (mycodes.valid_string(value)) { + CharSequence cs1 = "\\/"; + CharSequence cs2 = "/"; + if (value.contains(cs1)) { + value = value.replace(cs1, cs2); + } + return value; + } + return ""; + } + + public static boolean valid_portnumber(int port) { + return (port > 0) && (port < 65536); + } +} diff --git a/src/androgpio/networkrelated/IfConfigResult.java b/src/androgpio/networkrelated/IfConfigResult.java new file mode 100644 index 0000000..f4131e7 --- /dev/null +++ b/src/androgpio/networkrelated/IfConfigResult.java @@ -0,0 +1,120 @@ +package androgpio.networkrelated; + +import java.util.List; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + +@BA.ShortName("IfConfigResult") +public class IfConfigResult { + + private String HWaddr; + private String Driver; + + private String inet_addr; + private String Mask; + private String inet6_addr; + + private boolean isUP; + + private final String HWaddrkey = "HWaddr "; + private final String inetaddrkey = "inet addr:"; + private final String inet6addrkey = "inet6 addr: "; + private final String Driverkey = "Driver "; + private final String Maskkey = "Mask:"; + + + public IfConfigResult(List values) { + for(String xx : values) { + if (contain_Driver(xx)) Driver = mycodes.GetValueAfterKey(xx, Driverkey); + if (contain_HWaddr(xx)) HWaddr = mycodes.GetValueAfterKey(xx, HWaddrkey); + if (contain_inet_addr(xx)) inet_addr = mycodes.GetValueAfterKey(xx, inetaddrkey); + if (contain_inet6_addr(xx)) inet6_addr=mycodes.GetValueAfterKey(xx, inet6addrkey); + if (contain_SubnetMask(xx)) Mask = mycodes.GetValueAfterKey(xx, Maskkey); + if (xx.contains("UP")) isUP = true; + } + } + + public String getInet_addr() { + return inet_addr; + } + + public void setInet_addr(String xx) { + inet_addr = xx; + } + + public void setInet6_addr(String xx) { + inet6_addr = xx; + } + + public String getInet6_addr() { + return inet6_addr; + } + + public boolean isUP() { + return isUP; + } + + public void setUP(boolean xx) { + isUP = xx; + } + + public String getHWaddr() { + return HWaddr; + } + + public String getDriver() { + return Driver; + } + + public String getMask() { + return Mask; + } + + private boolean contain_HWaddr(String xx) { + if (mycodes.valid_string(xx)) { + return xx.contains(HWaddrkey); + } + return false; + } + + private boolean contain_inet_addr(String xx) { + if (mycodes.valid_string(xx)) { + return xx.contains(inetaddrkey); + } + return false; + } + + private boolean contain_inet6_addr(String xx) { + if (mycodes.valid_string(xx)) { + return xx.contains(inet6addrkey); + } + return false; + } + + private boolean contain_Driver(String xx) { + if (mycodes.valid_string(xx)) { + return xx.contains(Driverkey); + } + return false; + } + + private boolean contain_SubnetMask(String xx) { + if (mycodes.valid_string(xx)) { + return xx.contains(Maskkey); + } + return false; + } + + public String toString() { + StringBuilder str = new StringBuilder(); + str.append("MAC = "+HWaddr).append("\r\n"); + str.append("Driver = "+Driver).append("\r\n"); + str.append("Up = "+isUP).append("\r\n"); + str.append("IPV4 = "+inet_addr).append("\r\n"); + str.append("Subnet = "+Mask).append("\r\n"); + str.append("IPV6 = "+inet6_addr).append("\r\n"); + return str.toString(); + } + +} diff --git a/src/androgpio/networkrelated/ethernetmanager.java b/src/androgpio/networkrelated/ethernetmanager.java new file mode 100644 index 0000000..e668761 --- /dev/null +++ b/src/androgpio/networkrelated/ethernetmanager.java @@ -0,0 +1,643 @@ +package androgpio.networkrelated; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.topjohnwu.superuser.Shell; +import com.topjohnwu.superuser.Shell.GetShellCallback; + +import androgpio.mycodes; +import android.Manifest; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.text.TextUtils; +import anywheresoftware.b4a.BA; + + +@BA.ShortName("ethernet_manager") +@BA.Events(values= { + "log(msg as string)", + "linkpropertiesupdated", + "connectionstatus(up as boolean)" +}) +@BA.Permissions(values = { + Manifest.permission.ACCESS_NETWORK_STATE, + }) +@BA.DependsOn(values= { + "core-5.2.1.aar" +}) + +public class ethernetmanager { + private BA ba; + private String event; + private Object Me; + private boolean need_log_event = false; + private boolean need_linkpropertiesupdated_event = false; + private boolean need_connectionstatus_event = false; + private IfConfigResult ifconfigresult; + private String _gateway; + private String devicename; + private String _dhcpserver; + private ConnectivityManager _conman ; + private NetworkRequest netreq; + + private LinkProperties mLinkProp; + private String[] _dns; + + /** + * Initialize Ethernet Manager + * @param eventname eventname + * @param devicename Device Name want to manage + */ + public void Initialize(BA ba, String eventname, String devicename) { + this.ba = ba; + this.event = eventname; + this.devicename = devicename; + + check_events(); + Shell.getShell(new GetShellCallback() { + + @Override + public void onShell(Shell shell) { + mycodes.Log(ba, "Is root : "+isRooted()); + mycodes.Log(ba, "isApproot :"+isAppGrantedRoot()); + mycodes.Log(ba, "Shell alive :"+isShellAlive()); + } + + }); + } + + /** + * Check if Device is Rooted + * @return true if rooted + */ + public boolean isRooted() { + return Shell.getShell().isRoot(); + + } + + /** + * Check if this App is granted Root access + * @return true if if granted + */ + public boolean isAppGrantedRoot() { + return Shell.isAppGrantedRoot(); + } + + /** + * Check if Shell is still alive + * @return true if alive + */ + public boolean isShellAlive() { + return Shell.getShell().isAlive(); + } + + /** + * Get detected DHCP server + * @return null if not available + */ + public String getDHCPServer() { + return _dhcpserver; + } + + /** + * Get devicename for this ethernet manager + * @return devicename + */ + public String getDeviceName() { + return devicename; + } + + /** + * Get IP V4 address + * @return null if not available + */ + public String getIPV4_Address() { + return ifconfigresult!=null ? ifconfigresult.getInet_addr() : null; + } + + /** + * Get Subnet for IP V4 + * @return null if not available + */ + public String getSubnet() { + return ifconfigresult!=null ? ifconfigresult.getMask() : null; + } + + /** + * Get MAC Address + * @return null if not available + */ + public String getMAC_Address() { + return ifconfigresult!=null ? ifconfigresult.getHWaddr() : null; + } + + /** + * Get Gateway for IP V4 + * @return null if not available + */ + public String getGateway() { + return _gateway; + } + + /** + * Get IP V6 address + * @return null if not available + */ + public String getIPV6_Address() { + return ifconfigresult!=null ? ifconfigresult.getInet6_addr() : null; + } + + /** + * Get Detected Driver for this Ethernet + * @return null if not available + */ + public String getDriver() { + return ifconfigresult!=null ? ifconfigresult.getDriver() : null; + } + + + + /** + * Get DNS in one line string + * @return null if not available + */ + public String getDNS() { + if (_dns!=null && _dns.length>0) { + return TextUtils.join(";", _dns); + } + return null; + } + + /** + * Check if Ethernet is UP (enabled) + * @return true if enabled + */ + public boolean getUp() { + return ifconfigresult!=null ? ifconfigresult.isUP() : false; + } + + /** + * Check B4A events + */ + private void check_events() { + if (ba!=null) { + if (event!=null) { + if (event.length()>0) { + need_log_event = ba.subExists(event+"_log"); + need_linkpropertiesupdated_event = ba.subExists(event+"_linkpropertiesupdated"); + need_connectionstatus_event = ba.subExists(event+"_connectionstatus"); + } + } + } + } + + /** + * Turn ON / OFF Ethernet + * @param value True = Up, False = Down + * @return true if success + */ + public boolean SetUp(boolean value) { + final String cmd = mycodes.Combine(" ", "ifconfig", devicename, (value?"up":"down")); + + if (isAppGrantedRoot()) { + mycodes.Log(ba, "SetUp about to execute command "+cmd); + Shell.Result result = Shell.cmd(cmd).exec(); + raise_log_shellresult("SetUp",result.getCode(),result.getOut(), result.getErr()); + return result.isSuccess(); + } else mycodes.Log(ba, "SetUp failed, App is not granted root"); + return false; + } + + /** + * Set DHCP + * Need to trigger SetUp false then SetUp true to refresh network state + * @return true if success + */ + public boolean SetDHCP() { + final String cmd = mycodes.Combine(" ", "ifconfig",devicename,"default"); + if (isAppGrantedRoot()) { + mycodes.Log(ba, "SetDHCP about to execute command "+cmd); + Shell.Result result = Shell.cmd(cmd).exec(); + raise_log_shellresult("SetDHCP",result.getCode(), result.getOut(), result.getErr()); + return result.isSuccess(); + } else mycodes.Log(ba, "SetDHCP failed, App is not granted root"); + return false; + } + + /** + * Set Static + * @param ipaddress IP Address value + * @param subnet Subnet mask + * @param gateway Gateway + * @param dns1 DNS 1 + * @param dns2 DNS 2 + * @return true if all correct + */ + public boolean SetStatic(String ipaddress, String subnet, String gateway, String dns1, String dns2) { + boolean result = true; + result &= SetUp(false); + result &= SetIPV4_Address(ipaddress, subnet); + result &= SetGateway(gateway); + result &= SetDNS(dns1, dns2); + return result; + } + + + + /** + * Set IP V4 address + * @param ipvalue IP address value + * @param netmaskvalue Subnetmask value + * @return true if success + */ + public boolean SetIPV4_Address(String ipvalue, String netmaskvalue) { + InetAddress inetip = mycodes.ToInetAddress(ipvalue); + InetAddress inetmask = mycodes.ToInetAddress(netmaskvalue); + if (isAppGrantedRoot()) { + if (inetip!=null) { + if (inetmask==null) inetmask = mycodes.ToInetAddress("255.255.255.0"); // default + final String cmd = mycodes.Combine(" ", "ifconfig", devicename, inetip.getHostAddress(), "netmask", inetmask.getHostAddress(), "up"); + mycodes.Log(ba, "SetIPV4_Address about to execute command "+cmd); + Shell.Result result = Shell.cmd(cmd).exec(); + raise_log_shellresult("SetIPV4_Address", result.getCode(), result.getOut(), result.getErr()); + return result.isSuccess(); + } else mycodes.Log(ba, "SetIPV4_Address failed, IP Value is invalid"); + } else mycodes.Log(ba, "SetIPV4_Address failed, App is not granted root"); + return false; + } + + + /** + * Set Gateway + * Belum bisa ! + * @param value + * @return true if success + */ + public boolean SetGateway(String value) { + InetAddress inetvalue = mycodes.ToInetAddress(value); + + if (inetvalue!=null) { + + final String cmd = mycodes.Combine(" ", "route add default gw",inetvalue.getHostAddress(),"dev",devicename); + mycodes.Log(ba, "SetGateway about to execute "+cmd); + Shell.Result result = Shell.cmd(cmd).exec(); + raise_log_shellresult("SetGateway", result.getCode(), result.getOut(), result.getErr()); + return result.isSuccess(); + } else mycodes.Log(ba, "SetGateway failed, value is not valid ip address"); + return false; + } + + + + /** + * Set DNS + * Belum bisa ! + * @param value + * @return true if success + */ + public boolean SetDNS(String dns1, String dns2) { + InetAddress inetvalue1 = mycodes.ToInetAddress(dns1); + InetAddress inetvalue2 = mycodes.ToInetAddress(dns2); + + if (inetvalue1!=null || inetvalue2!=null) { + if (mLinkProp!=null) { + List dns = new ArrayList<>(); + dns.add(inetvalue1); + dns.add(inetvalue2); + mLinkProp.setDnsServers(dns); + return true; + } else mycodes.Log(ba, "SetDNS failed, LinkProperties is invalid"); + } else mycodes.Log(ba, "SetDNS failed, dns1 or dns2 is invalid"); + return false; + } + + + /** + * Check / Refresh Ethernet Information + * Call it on Activity_Resume + * @return true if success + */ + public boolean check_device() { + if (mycodes.valid_string(devicename)) { + // dapat MAC, iP4+6, Subnet, status UP + RUNNING + ifconfigresult = ifconfig(); + // dapat gateway + _gateway = getGatewayUsingIP(); + + if (ba!=null) { + _conman = (ConnectivityManager) ba.context.getSystemService(Context.CONNECTIVITY_SERVICE); + netreq = new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .build(); + } + + CheckUsingConnectivityManager(); + + } + return false; + } + + /** + * Network Callback Object used by ConnectivityManager + */ + private NetworkCallback ncb = new NetworkCallback() { + @Override + public void onAvailable(Network network) { + mycodes.Log(ba, devicename+" is available"); + raise_connectionstatus(true); + if (ifconfigresult!=null) ifconfigresult.setUP(true); + + } + + @Override + public void onLost(Network network) { + if (ifconfigresult!=null) ifconfigresult.setUP(false); + mycodes.Log(ba, devicename+" is Lost"); + raise_connectionstatus(false); + mLinkProp = null; + } + + @Override + public void onBlockedStatusChanged(Network network, boolean blocked) { + LinkProperties linkprop = _conman.getLinkProperties(network); + if (linkprop!=null && linkprop.getInterfaceName().equals(devicename)) { + mycodes.Log(ba, devicename, blocked?" is blocked":" is allowed"); + } + } + + @Override + public void onUnavailable() { + mycodes.Log(ba, "Unable to registerNetworkCallback on Ethernet"); + } + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties linkprop) { + if (linkprop!=null && linkprop.getInterfaceName().equals(devicename)) { + mycodes.Log(ba, "Linkproperties changed for ",devicename); + + // DHCP server + _dhcpserver = linkprop.getDhcpServerAddress().getHostAddress(); + mycodes.Log(ba, "DHCP Server :",_dhcpserver); + + // IP Addresses + List ipv4list = new ArrayList<>(); + List ipv6list = new ArrayList<>(); + Categorize_IPAddress(linkprop, ipv4list, ipv6list); + if (ifconfigresult!=null) { + + String ipv4 = ListInetAddressToString(ipv4list); + String ipv6 = ListInetAddressToString(ipv6list); + ifconfigresult.setInet_addr(ipv4); + ifconfigresult.setInet6_addr(ipv6); + mycodes.Log(ba, "IPV4 :",ipv4); + mycodes.Log(ba, "IPV6 :",ipv6); + } + + // DNS + List listdns = linkprop.getDnsServers(); + _dns = null; + if (listdns!=null && listdns.size()>0) { + _dns = new String[listdns.size()]; + for(int ii=0;ii listgateway = new ArrayList<>(); + linkprop.getRoutes().forEach(routeinfo->{ + if (routeinfo.hasGateway()) listgateway.add(routeinfo.getGateway().getHostAddress()); + }); + if (listgateway.size()>0) { + _gateway = TextUtils.join(";", listgateway.toArray(new String[0])); + mycodes.Log(ba, "Gateway :",_gateway); + } else mycodes.Log(ba,"No Gateway available"); + + raise_linkpropertiesupdated(); + mLinkProp = linkprop; + + + } + } + }; + + /** + * Unregister event + * Call it on Activity_Pause + */ + public void unregister_events() { + if (_conman!=null) { + _conman.unregisterNetworkCallback(ncb); + } else mycodes.Log(ba,"ConnectivityManager is null"); + } + + /** + * Getting gateway using ip route + * @param devicename ethernet name + * @return null if not available + */ + private String getGatewayUsingIP() { + final String cmd = "ip route get 8.8.8.8"; + Shell.Result result = Shell.cmd(cmd).exec(); + this.raise_log_shellresult("getGatewayUsingIP", result.getCode(),result.getOut(), result.getErr()); + List r2 = result.getOut(); + if (r2!=null && r2.size()>0) { + Pattern pattern = Pattern.compile("8.8.8.8 via (\\S+) dev (\\S+)"); + + for(String rx : r2) { + Matcher matcher = pattern.matcher(rx); + if (matcher.find()) { + String g1 = matcher.group(1); + String g2 = matcher.group(2); + if (g2.equals(devicename)) { + return g1; + } + } + } + + } + return null; + } + +// @Deprecated +// private List exec_su(String command){ +// mycodes.Log(ba, "Executing su "+command); +// List result = new ArrayList<>(); +// List errors = new ArrayList<>(); +// try { +// Process pp = Runtime.getRuntime().exec("su"); +// DataOutputStream os = new DataOutputStream(pp.getOutputStream()); +// BufferedReader is = new BufferedReader(new InputStreamReader(pp.getInputStream())); +// BufferedReader es = new BufferedReader(new InputStreamReader(pp.getErrorStream())); +// os.writeBytes(command+"\n"); +// os.flush(); +// os.writeBytes("exit\n"); +// os.flush(); +// os.close(); +// +// +// while(true) { +// String xx = is.readLine(); +// if (xx==null) break; +// if (mycodes.valid_string(xx)) result.add(xx); +// } +// is.close(); +// +// while(true) { +// String xx = es.readLine(); +// if (xx==null) break; +// if (mycodes.valid_string(xx)) errors.add(xx); +// } +// es.close(); +// +// int exitcode = pp.waitFor(); +// mycodes.Log(ba, "exitcode = "+exitcode); +// +// if (result.size()>0) +// mycodes.Log(ba, "There is result"); +// else +// mycodes.Log(ba, "No result"); +// if (errors.size()>0) +// mycodes.Log(ba, "there is error"); +// else +// mycodes.Log(ba, "No error"); +// +// return result; +// } catch (IOException | InterruptedException | SecurityException |NullPointerException | IllegalArgumentException e) { +// mycodes.Log(ba, "exec_su exception = "+e.getMessage()); +// } +// return result; +// } + +// @Deprecated +// /** +// * Exec shell command +// * @param command command to execute +// * @return List of String of response +// */ +// private List exec(String command){ +// mycodes.Log(ba, "Executing "+command); +// List result = new ArrayList<>(); +// if (mycodes.valid_string(command)) { +// try { +// Process pp = Runtime.getRuntime().exec(command); +// List ppresult = mycodes.GetInputStreamString(pp); +// if (ppresult!=null && ppresult.size()>0) { +// result.addAll(ppresult); +// } +// } catch (IOException e) { +// raise_log("Exception running command ["+command+"], exception="+e.getMessage()); +// } +// +// } +// return result; +// } + +// @Deprecated +// private List exec(String...commands){ +// +// return exec(TextUtils.join(" ", commands)); +// } + + /** + * Call ifconfig on devicename + * @param devicename ethernet name + * @return null if failed + */ + private IfConfigResult ifconfig() { + if (mycodes.valid_string(devicename)) { + final String cmd = "ifconfig "+devicename; + Shell.Result result = Shell.cmd(cmd).exec(); + this.raise_log_shellresult("ifconfig", result.getCode(), result.getOut(), result.getErr()); + List ppresult = result.getOut(); + if (ppresult.size()>0) { + IfConfigResult ifresult = new IfConfigResult(ppresult); + return ifresult; + + } else raise_log("check_device process result length is zero"); + } else raise_log("check_device devicename is invalid"); + return null; + } + + /** + * Call network monitoring using Connectivity Manager + */ + private void CheckUsingConnectivityManager() { + if (_conman!=null) { + _conman.registerNetworkCallback(netreq, ncb); + } else mycodes.Log(ba,"ConnectivityManager is null"); + } + + private void Categorize_IPAddress(final LinkProperties linkprop, final List ipv4, final List ipv6) { + if (linkprop!=null) { + linkprop.getLinkAddresses().forEach(linkaddress->{ + InetAddress inetaddress = linkaddress.getAddress(); + if (inetaddress instanceof Inet6Address) + { + if (ipv6!=null) { + ipv6.add(inetaddress); + } + } else if (inetaddress instanceof Inet4Address) { + if (ipv4!=null) { + ipv4.add(inetaddress); + } + } + }); + } + } + + + + private String ListInetAddressToString(List inet) { + if (inet!=null && inet.size()>0) { + + StringBuilder str = new StringBuilder(); + for(InetAddress ii : inet) { + if (str.length()>0) str.append(";"); + str.append(ii.getHostAddress()); + } + return str.toString(); + + } + return null; + } + + private void raise_log_shellresult(String functionname, int code, List output, List error) { + mycodes.Log(ba, mycodes.Combine(" ", functionname, "code =",String.valueOf(code))); + if (error!=null && error.size()>0) { + mycodes.Log(ba, mycodes.Combine(" ", functionname, "Error :")); + error.forEach(xx-> mycodes.Log(ba, xx)); + } + if (output!=null && output.size()>0) { + mycodes.Log(ba, mycodes.Combine(" ", functionname,"Output :")); + output.forEach(xx -> mycodes.Log(ba, xx)); + } + } + + /** + * raise event log + * @param msg Message data for log + */ + private void raise_log(String msg) { + if (need_log_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_log", false, new Object[] {msg}); + } + + private void raise_linkpropertiesupdated() { + if (need_linkpropertiesupdated_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_linkpropertiesupdated", false, null); + } + + private void raise_connectionstatus(boolean up) { + if (need_connectionstatus_event) ba.raiseEventFromDifferentThread(Me, null, 0, event+"_connectionstatus", false, new Object[] {up}); + } +} diff --git a/src/androgpio/openweather/OpenWeather.java b/src/androgpio/openweather/OpenWeather.java new file mode 100644 index 0000000..ed6a6e7 --- /dev/null +++ b/src/androgpio/openweather/OpenWeather.java @@ -0,0 +1,227 @@ +package androgpio.openweather; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.net.ssl.HttpsURLConnection; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +import androgpio.mycodes; +import anywheresoftware.b4a.BA; + +@BA.Events(values = { + "log(msg as String)", + "getresult(success as boolean, weather as OpenWeatherData)" +}) + +@BA.ShortName("OpenWeather") +public class OpenWeather { + + // B4X Objects + private BA ba; + private String eventName; + private boolean need_log_event = false; + private boolean need_getresult_event = false; + + private String cityname; + private double latitude; + private double longitude; + private String apiKey; + private URL url; + private Gson gs; + private OpenWeatherEvent javaEvent; + + @BA.Hide + /** + * Java Constructor for OpenWeather + * @param latitude latitude of the location + * @param longitude longitude of the location + * @param apiKey API key for OpenWeather + */ + public OpenWeather(double latitude, double longitude, String apiKey, OpenWeatherEvent event) { + this.latitude = latitude; + this.longitude = longitude; + this.apiKey = apiKey; + this.javaEvent = event; + CreateURL(); + gs = new Gson(); + } + + @BA.Hide + /** + * Java Constructor for OpenWeather + * citiname is used, based on ISO 3166 + * For Indonesia, can be check at https://en.wikipedia.org/wiki/ISO_3166-2:ID + * @param citiname Name of the city, based on ISO 3166 + * @param apiKey API key for OpenWeather + * @param event Event for the object + */ + public OpenWeather(String citiname, String apiKey, OpenWeatherEvent event) { + this.cityname = citiname; + this.apiKey = apiKey; + this.javaEvent = event; + CreateURL(); + gs = new Gson(); + } + + /** + * Initializes the OpenWeather object for B4X + * @param eventname Name of the event + * @param latitude Latitude of the location + * @param longitude Longitude of the location + * @param apiKey API key for OpenWeather + */ + public void Initialize(BA ba, String eventname, double latitude, double longitude, String apiKey) { + this.ba = ba; + this.eventName = eventname.toLowerCase(BA.cul); + this.latitude = latitude; + this.longitude = longitude; + this.apiKey = apiKey; + + if (this.ba!=null) { + if (mycodes.valid_string(eventName)) { + need_log_event = ba.subExists(eventName + "_log"); + need_getresult_event = ba.subExists(eventName + "_getresult"); + } + } + + CreateURL(); + gs = new Gson(); + } + + /** + * Initializes the OpenWeather object for B4X + * City name is used, based on ISO 3166 + * For Indonesia, can be check at https://en.wikipedia.org/wiki/ISO_3166-2:ID + * @param eventname Name of the event + * @param cityname Name of the city, based on ISO 3166 + * @param apiKey API key for OpenWeather + */ + public void Initialize2(BA ba, String eventname, String cityname, String apiKey) { + this.ba = ba; + this.eventName = eventname.toLowerCase(BA.cul); + this.cityname = cityname; + this.apiKey = apiKey; + + if (this.ba != null) { + if (mycodes.valid_string(eventName)) { + need_log_event = ba.subExists(eventName + "_log"); + need_getresult_event = ba.subExists(eventName + "_getresult"); + } + } + + CreateURL(); + gs = new Gson(); + } + + /** + * Create URL for the OpenWeather API + */ + private void CreateURL() { + String xx ; + if (mycodes.valid_string(cityname)) { + xx = "https://api.openweathermap.org/data/2.5/weather?q=" + cityname + "&appid=" + apiKey+"&units=metric"; + } else { + xx = "https://api.openweathermap.org/data/2.5/weather?lat=" + latitude + "&lon=" + longitude + "&appid=" + apiKey+"&units=metric"; + } + try { + url = new URL(xx); + } catch (MalformedURLException e) { + raise_log("Error creating URL: " + e.getMessage()); + } + } + + /** + * Get Latitude + * @return Latitude of the location + */ + public double getLatitude() { + return latitude; + } + + /** + * Get Longitude + * @return Longitude of the location + */ + public double getLongitude() { + return longitude; + } + + /** + * Get API Key + * @return API Key for OpenWeather + */ + public String getApiKey() { + return apiKey; + } + + /** + * Get URL + * @return URL for OpenWeather API + */ + public String getURL() { + if (url != null) { + return url.toString(); + } + return null; + } + + /** + * Get Weather + * if success, will raise the getresult event + */ + public void GetWeather() { + try { + HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.connect(); + + StringBuilder str = new StringBuilder(); + BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream())); + String line; + while ((line = br.readLine()) != null) { + str.append(line); + } + br.close(); + con.disconnect(); + + OpenWeatherData weather = gs.fromJson(str.toString(), OpenWeatherData.class); + //raise_log("Stringbuilder: " + str.toString()); + raise_log("Weather data: " + weather.toString()); + raise_getresult(true, weather); + return; + } catch (IOException e1) { + raise_log("Error opening connection: " + e1.getMessage()); + } catch(JsonSyntaxException e2) { + raise_log("Error parsing JSON: " + e2.getMessage()); + } + raise_getresult(false, null); + } + + + + + private void raise_log(String msg) { + if (need_log_event) { + ba.raiseEventFromDifferentThread(OpenWeather.this, null, 0, eventName+"_log", false, new Object[] {msg}); + } + if (javaEvent!=null) { + javaEvent.log(msg); + } + } + + private void raise_getresult(boolean success, OpenWeatherData result) { + if (need_getresult_event) { + ba.raiseEventFromDifferentThread(OpenWeather.this, null, 0, eventName+"_getresult", false, new Object[] {success, result}); + } + + if (javaEvent!=null) { + javaEvent.GetResult(success, result); + } + } +} diff --git a/src/androgpio/openweather/OpenWeatherData.java b/src/androgpio/openweather/OpenWeatherData.java new file mode 100644 index 0000000..abffdc1 --- /dev/null +++ b/src/androgpio/openweather/OpenWeatherData.java @@ -0,0 +1,39 @@ +package androgpio.openweather; + +import androgpio.openweather.jsondata.*; +import anywheresoftware.b4a.BA; +@BA.ShortName("OpenWeatherData") + +public class OpenWeatherData { + public coord coord; + public weather[] weather; + public String base; + public main main; + public int visibility; + public wind wind; + public clouds clouds; + public rain rain; + public snow snow; + public long dt; + public sys sys; + public int timezone; + public int id; + public String name; + public int cod; + + @Override + public String toString() { + return "OpenWeatherData [coord=" + coord + ", weather=" + getWeatherString() + ", base=" + base + ", main=" + main + + ", visibility=" + visibility + ", wind=" + wind + ", clouds=" + clouds + ", rain=" + rain + ", snow=" + + snow + ", dt=" + dt + ", sys=" + sys + ", timezone=" + timezone + ", id=" + id + ", name=" + name + + ", cod=" + cod + "]"; + } + + private String getWeatherString() { + String weatherString = ""; + for (weather w : weather) { + weatherString += w.toString() + ";"; + } + return weatherString; + } +} diff --git a/src/androgpio/openweather/OpenWeatherEvent.java b/src/androgpio/openweather/OpenWeatherEvent.java new file mode 100644 index 0000000..f9f5c5b --- /dev/null +++ b/src/androgpio/openweather/OpenWeatherEvent.java @@ -0,0 +1,6 @@ +package androgpio.openweather; + +public interface OpenWeatherEvent { + void log(String msg); + void GetResult(boolean success, OpenWeatherData data); +} diff --git a/src/androgpio/openweather/jsondata/clouds.java b/src/androgpio/openweather/jsondata/clouds.java new file mode 100644 index 0000000..2b4152a --- /dev/null +++ b/src/androgpio/openweather/jsondata/clouds.java @@ -0,0 +1,10 @@ +package androgpio.openweather.jsondata; + +public class clouds { + public int all; + + @Override + public String toString() { + return "clouds [all=" + all + "]"; + } +} diff --git a/src/androgpio/openweather/jsondata/coord.java b/src/androgpio/openweather/jsondata/coord.java new file mode 100644 index 0000000..2543a4e --- /dev/null +++ b/src/androgpio/openweather/jsondata/coord.java @@ -0,0 +1,11 @@ +package androgpio.openweather.jsondata; + +public class coord { + public double lat; + public double lon; + + @Override + public String toString() { + return "coord [lat=" + lat + ", lon=" + lon + "]"; + } +} diff --git a/src/androgpio/openweather/jsondata/main.java b/src/androgpio/openweather/jsondata/main.java new file mode 100644 index 0000000..dde75f0 --- /dev/null +++ b/src/androgpio/openweather/jsondata/main.java @@ -0,0 +1,19 @@ +package androgpio.openweather.jsondata; + +public class main { + public double temp; + public double feels_like; + public double temp_min; + public double temp_max; + public int pressure; + public int humidity; + public int sea_level; + public int grnd_level; + + @Override + public String toString() { + return "main [temp=" + temp + ", feels_like=" + feels_like + ", temp_min=" + temp_min + ", temp_max=" + temp_max + + ", pressure=" + pressure + ", humidity=" + humidity + ", sea_level=" + sea_level + ", grnd_level=" + + grnd_level + "]"; + } +} diff --git a/src/androgpio/openweather/jsondata/rain.java b/src/androgpio/openweather/jsondata/rain.java new file mode 100644 index 0000000..8f44181 --- /dev/null +++ b/src/androgpio/openweather/jsondata/rain.java @@ -0,0 +1,11 @@ +package androgpio.openweather.jsondata; + +public class rain { + public double _1h; + public double _3h; + + @Override + public String toString() { + return "rain [_1h=" + _1h + ", _3h=" + _3h + "]"; + } +} diff --git a/src/androgpio/openweather/jsondata/snow.java b/src/androgpio/openweather/jsondata/snow.java new file mode 100644 index 0000000..527a39a --- /dev/null +++ b/src/androgpio/openweather/jsondata/snow.java @@ -0,0 +1,11 @@ +package androgpio.openweather.jsondata; + +public class snow { + public double _1h; + public double _3h; + + @Override + public String toString() { + return "snow [_1h=" + _1h + ", _3h=" + _3h + "]"; + } +} diff --git a/src/androgpio/openweather/jsondata/sys.java b/src/androgpio/openweather/jsondata/sys.java new file mode 100644 index 0000000..aa3ed81 --- /dev/null +++ b/src/androgpio/openweather/jsondata/sys.java @@ -0,0 +1,16 @@ +package androgpio.openweather.jsondata; + +public class sys { + public int type; + public int id; + public String message; + public String country; + public long sunrise; + public long sunset; + + @Override + public String toString() { + return "sys [type=" + type + ", id=" + id + ", message=" + message + ", country=" + country + ", sunrise=" + + sunrise + ", sunset=" + sunset + "]"; + } +} diff --git a/src/androgpio/openweather/jsondata/weather.java b/src/androgpio/openweather/jsondata/weather.java new file mode 100644 index 0000000..9f8f061 --- /dev/null +++ b/src/androgpio/openweather/jsondata/weather.java @@ -0,0 +1,13 @@ +package androgpio.openweather.jsondata; + +public class weather { + public int id; + public String main; + public String description; + public String icon; + + @Override + public String toString() { + return "weather [id=" + id + ", main=" + main + ", description=" + description + ", icon=" + icon + "]"; + } +} diff --git a/src/androgpio/openweather/jsondata/wind.java b/src/androgpio/openweather/jsondata/wind.java new file mode 100644 index 0000000..9355249 --- /dev/null +++ b/src/androgpio/openweather/jsondata/wind.java @@ -0,0 +1,12 @@ +package androgpio.openweather.jsondata; + +public class wind { + public double speed; + public int deg; + public double gust; + + @Override + public String toString() { + return "wind [speed=" + speed + ", deg=" + deg + ", gust=" + gust + "]"; + } +} diff --git a/src/devices/Button.java b/src/devices/Button.java new file mode 100644 index 0000000..4f0d7eb --- /dev/null +++ b/src/devices/Button.java @@ -0,0 +1,219 @@ +package devices; + + + +import java.util.Timer; +import java.util.TimerTask; + +import com.sun.jna.Platform; + +import androgpio.DigitalInput.DigitalInput; +import androgpio.DigitalInput.DigitalInputEvent; +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.DateTime; + + + +@BA.ShortName("HardwareButton") +@BA.Events(values= { + "log(msg as string)", + "shortpressed(tick as long)", + "longpressed(tick as long)", + "currentstate(isON as boolean, tick as long)" +}) + +/** + * Button class + * use class DigitalInput + * Button is always Active Low, so Pin need to be Pull Up using Resistor to VCC + * @author rdkartono + * + */ +public class Button implements DigitalInputEvent { + private DigitalInput di; + private BA bax; + private Object myobject; + private String event; + private int mypinnumber; + + private Timer tt; // scan DigitalInput using timer + private final int timerms = 50; // timer trigger per 50 ms + private int statecounter = 0; // for detecting shortpressed or longpressed + private final int shortpresslimit = 4; // 4 x 50ms = 200ms + private final int longpresslimit = 40; // 40 x 50ms = 2000ms = 2 seconds + + + private boolean initialized = false; + + + private boolean need_log_event = false; + private boolean need_shortpressed_event = false; + private boolean need_longpressed_event = false; + private boolean need_currentstate_event = false; + + public Button() { + + } + + + public boolean Initialize(BA ba, Object caller, String buttonname, int pin_number) { + myobject = caller; + bax = ba; + + event = buttonname.trim(); + mypinnumber = pin_number; + initialized = false; + + if (bax!=null) { + if (caller!=null) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(event+"_log"); + need_shortpressed_event = bax.subExists(event+"_shortpressed"); + need_longpressed_event = bax.subExists(event+"_longpressed"); + need_currentstate_event = bax.subExists(event+"_currentstate"); + } + } + } + + + if (Platform.isAndroid()==false) { + raise_log("Target device is not Android"); + return false; + } + + di = new DigitalInput(); + di.SetJavaEvent(this); // link javaevent to this class + + di.Initialize(bax, caller, event, pin_number); + + if (di.getIsInitialized()) { + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + Release(); + } + }); + + TimerTask task = new TimerTask() { + + @Override + public void run() { + if (di == null) { + cancel(); // stop timer + raise_log("DigitalInput is null, Button Timer stopped"); + return; + } + if (di.getIsInitialized()==false) { + cancel(); // stop timer + raise_log("DigitalInput is not initialized, Button Timer stopped"); + return; + } + int value = di.ReadState(); + + if (value==-1) { + cancel(); // stop timer + raise_log("DigitalInput ReadState failed, Button Timer stopped"); + return; + } + + + if (value==1) { + // high + if (statecounter>=shortpresslimit) { + if (statecounter0) return true; + + } + } + } + } + + return false; + } + + /** + * Read from I2C. Result in int, but actually value will 0 - 255 (byte size) + * @return -1 if failed + */ + public int Read() { + if (i2cbus instanceof I2C_BUS) { + if (i2cbus.IsOpened()) { + if (i2cdev instanceof I2C_Device) { + if (i2cdev.IsOpened()) { + byte[] result = i2cdev.ReadBytes(1); + if (result!=null) { + return Bit.And(result[0], 0xFF); + } + + } + } + } + } + return -1; + } + + private void raise_log(String msg) { + if (need_log_event) { + bax.raiseEventFromDifferentThread(myobject, null, 0, event+"_log", false, new Object[] {msg}); + } + } + + @Override + public void Log(String msg) { + raise_log(msg); + + } +} diff --git a/src/devices/PublicIPChecker.java b/src/devices/PublicIPChecker.java new file mode 100644 index 0000000..2b68ac3 --- /dev/null +++ b/src/devices/PublicIPChecker.java @@ -0,0 +1,115 @@ +package devices; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; + +import anywheresoftware.b4a.BA; +import anywheresoftware.b4a.keywords.Regex; + +@BA.ShortName("PublicIPChecker") +@BA.Events(values= { + "log(msg as string)", + "publicip(success as boolean, testserver as string, result as string)" +}) + + +// https://stackoverflow.com/questions/2939218/getting-the-external-ip-address-in-java + +public class PublicIPChecker { + + private final String[] servers = { + "http://checkip.amazonaws.com/", + "https://ipv4.icanhazip.com/", + "http://myexternalip.com/raw", + "http://ipecho.net/plain", + "http://bot.whatismyipaddress.com" + }; + + private final String regex_validip = "^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$"; + + private BA bax; + private Object caller; + private String event=""; + private final Object Me = this; + private boolean need_log_event = false; + private boolean need_publicip_event = false; + + /** + * Initialize Public IP Checker + * @param callerobject : callback object + * @param eventname : eventname + */ + public void Initialize(BA ba, Object callerobject, String eventname) { + bax = ba; + caller = callerobject; + event = eventname; + if (bax instanceof BA) { + if (caller != null) { + if (!event.isEmpty()) { + need_log_event = bax.subExists(event+"_log"); + need_publicip_event = bax.subExists(event+"_publicip"); + } + } + } + } + + /** + * Get Public IP + * Will raise event publicip(success as boolean, testserver as string, result as string) + */ + public void GetPublicIP() { + Thread tx = new Thread(new Runnable() { + + @Override + public void run() { + boolean success = false; + String currentserver = ""; + String resultip = ""; + for(int ii=0;ii