1 package jp.osdn.gokigen.gokigenassets.camera.vendor.sony.wrapper.connection
3 import android.content.Context
4 import android.util.Log
5 import jp.osdn.gokigen.gokigenassets.camera.interfaces.ICameraStatusReceiver
6 import jp.osdn.gokigen.gokigenassets.camera.vendor.ICameraControlCoordinator
7 import jp.osdn.gokigen.gokigenassets.camera.vendor.sony.wrapper.ISonyCamera
8 import jp.osdn.gokigen.gokigenassets.camera.vendor.sony.wrapper.SonyCameraDeviceProvider
9 import jp.osdn.gokigen.gokigenassets.constants.IStringResourceConstantConvert
10 import jp.osdn.gokigen.gokigenassets.utils.communication.SimpleHttpClient
11 import jp.osdn.gokigen.gokigenassets.utils.communication.XmlElement
12 import java.net.DatagramPacket
13 import java.net.DatagramSocket
14 import java.net.InetSocketAddress
15 import java.nio.charset.Charset
17 class SonySsdpClient(private val context: Context, private val callback: ISearchResultCallback, private val cameraStatusReceiver: ICameraStatusReceiver, private val cameraCoordinator: ICameraControlCoordinator, private val number : Int, private var sendRepeatCount: Int = 0)
21 private val TAG = SonySsdpClient::class.java.simpleName
22 private const val SEND_TIMES_DEFAULT = 3
23 private const val SEND_WAIT_DURATION_MS = 300
24 private const val SSDP_RECEIVE_TIMEOUT = 4 * 1000 // msec
25 private const val PACKET_BUFFER_SIZE = 4096
26 private const val SSDP_PORT = 1900
27 private const val SSDP_MX = 2
28 private const val SSDP_ADDR = "239.255.255.250"
29 private const val SSDP_ST = "urn:schemas-sony-com:service:ScalarWebAPI:1"
30 private const val SSDP_ST2 = "urn:schemas-upnp-org:service:ContentDirectory:1"
31 private const val SSDP_ST3 = "urn:schemas-upnp-org:device:MediaServer:1"
34 private val ssdpRequest: String
35 private val useSmartphoneTransfer = false
39 this.sendRepeatCount = if (sendRepeatCount > 0) sendRepeatCount else SEND_TIMES_DEFAULT
40 ssdpRequest = "M-SEARCH * HTTP/1.1\r\nHOST: $SSDP_ADDR:$SSDP_PORT\r\nMAN: \"ssdp:discover\"\r\nMX: $SSDP_MX\r\nST: $SSDP_ST\r\n\r\n"
45 val sendData = ssdpRequest.toByteArray()
47 var socket: DatagramSocket? = null
48 var receivePacket: DatagramPacket
49 val packet: DatagramPacket
54 socket = DatagramSocket()
55 socket.reuseAddress = true
56 val iAddress = InetSocketAddress(SSDP_ADDR, SSDP_PORT)
57 packet = DatagramPacket(sendData, sendData.size, iAddress)
60 for (loop in 1..sendRepeatCount)
62 cameraStatusReceiver.onStatusNotify(context.getString(IStringResourceConstantConvert.ID_STRING_CONNECT_CAMERA_SEARCH_REQUEST) + " " + loop)
64 Thread.sleep(SEND_WAIT_DURATION_MS.toLong())
66 Log.v(TAG, " SSDP : SEND")
70 if (socket != null && !socket.isClosed)
77 callback.onErrorFinished(detailString + " : " + e.localizedMessage)
82 val startTime = System.currentTimeMillis()
83 var currentTime = System.currentTimeMillis()
84 val foundDevices: MutableList<String?> = ArrayList()
85 val array = ByteArray(PACKET_BUFFER_SIZE)
89 cameraStatusReceiver.onStatusNotify(context.getString(IStringResourceConstantConvert.ID_STRING_CONNECT_WAIT_REPLY_CAMERA))
90 while (currentTime - startTime < SSDP_RECEIVE_TIMEOUT)
92 receivePacket = DatagramPacket(array, array.size)
93 socket.soTimeout = SSDP_RECEIVE_TIMEOUT
94 socket.receive(receivePacket)
95 val ssdpReplyMessage = String(receivePacket.getData(), 0, receivePacket.length, Charset.forName("UTF-8"))
97 if (ssdpReplyMessage.contains("HTTP/1.1 200"))
99 ddUsn = findParameterValue(ssdpReplyMessage, "USN")
100 Log.v(TAG, "- - - - - - - USN : $ddUsn")
101 cameraStatusReceiver.onStatusNotify(context.getString(IStringResourceConstantConvert.ID_STRING_CONNECT_CAMERA_RECEIVED_REPLY))
102 if ((ddUsn.isNotEmpty())&&(!foundDevices.contains(ddUsn))&&(!cameraCoordinator.isAssignedCameraControl(ddUsn)))
104 val ddLocation = findParameterValue(ssdpReplyMessage, "LOCATION")
105 foundDevices.add(ddUsn)
107 //// Fetch Device Description XML and parse it.
108 if (ddLocation.isNotEmpty())
110 cameraStatusReceiver.onStatusNotify("LOCATION : $ddLocation")
111 val device: ISonyCamera? = searchSonyCameraDevice(ddLocation)
112 if ((device != null)&&(device.hasApiService("camera")))
114 cameraStatusReceiver.onStatusNotify(context.getString(IStringResourceConstantConvert.ID_STRING_CONNECT_CAMERA_FOUND) + " " + device.getFriendlyName())
115 cameraCoordinator.assignCameraControl(number, ddUsn)
116 callback.onDeviceFound(device)
117 // カメラが見つかった場合は breakする
118 Log.v(TAG, " assignCameraControl execution Result: " + cameraCoordinator.isAssignedCameraControl(ddUsn))
123 if (useSmartphoneTransfer && device != null)
125 cameraStatusReceiver.onStatusNotify(context.getString(IStringResourceConstantConvert.ID_STRING_CONNECT_CAMERA_FOUND) + " " + device.getFriendlyName() + " (Smartphone Transfer)")
126 Log.v(TAG, " SMARTPHONE TRANSFER : $ssdpReplyMessage")
127 callback.onDeviceFound(device)
132 cameraStatusReceiver.onStatusNotify(context.getString(IStringResourceConstantConvert.ID_STRING_CAMERA_NOT_FOUND))
138 Log.v(TAG, "Already received. : $ddUsn")
143 Log.v(TAG, " SSDP REPLY MESSAGE (ignored) : $ssdpReplyMessage")
145 currentTime = System.currentTimeMillis()
153 callback.onErrorFinished(detailString + " : " + e.localizedMessage)
160 if (!socket.isClosed())
165 catch (ee: Exception)
170 callback.onFinished()
173 private fun findParameterValue(ssdpMessage: String, paramName: String): String
176 if (!name.endsWith(":"))
180 var start = ssdpMessage.indexOf(name)
181 val end = ssdpMessage.indexOf("\r\n", start)
182 if (start != -1 && end != -1)
187 return ssdpMessage.substring(start, end).trim { it <= ' ' }
197 private fun searchSonyCameraDevice(ddUrl: String): ISonyCamera?
199 val httpClient = SimpleHttpClient()
200 var device: SonyCameraDeviceProvider? = null
204 ddXml = httpClient.httpGet(ddUrl, -1)
205 Log.d(TAG, "fetch () httpGet done. : " + ddXml.length)
206 if (ddXml.length < 2)
209 Log.v(TAG, "NO BODY")
213 catch (e: java.lang.Exception)
220 //Log.v(TAG, "ddXml : " + ddXml);
221 val rootElement = XmlElement.parse(ddXml)
224 if ("root" == rootElement.tagName)
227 val deviceElement = rootElement.findChild("device")
228 val friendlyName = deviceElement.findChild("friendlyName").value
229 val modelName = deviceElement.findChild("modelName").value
230 val udn = deviceElement.findChild("UDN").value
234 val iconListElement = deviceElement.findChild("iconList")
235 val iconElements = iconListElement.findChildren("icon")
236 for (iconElement in iconElements) {
237 // Choose png icon to show Android UI.
238 if ("image/png" == iconElement.findChild("mimetype").value) {
239 val uri = iconElement.findChild("url").value
240 val hostUrl = toSchemeAndHost(ddUrl)
241 iconUrl = hostUrl + uri
244 device = SonyCameraDeviceProvider(ddUrl, friendlyName, modelName, udn, iconUrl)
246 // "av:X_ScalarWebAPI_DeviceInfo"
247 val wApiElement = deviceElement.findChild("X_ScalarWebAPI_DeviceInfo")
248 val wApiServiceListElement = wApiElement.findChild("X_ScalarWebAPI_ServiceList")
249 val wApiServiceElements =
250 wApiServiceListElement.findChildren("X_ScalarWebAPI_Service")
251 for (wApiServiceElement in wApiServiceElements) {
253 wApiServiceElement.findChild("X_ScalarWebAPI_ServiceType").value
255 wApiServiceElement.findChild("X_ScalarWebAPI_ActionList_URL").value
256 device.addApiService(serviceName, actionUrl)
259 } catch (e: java.lang.Exception) {
262 Log.d(TAG, "fetch () parsing XML done.")
263 if (device == null) {
264 Log.v(TAG, "device is null.")
269 private fun toSchemeAndHost(url: String): String {
270 val i = url.indexOf("://") // http:// or https://
274 val j = url.indexOf("/", i + 3)
275 return if (j == -1) {
277 } else url.substring(0, j)
280 private fun toHost(url: String): String
282 val i = url.indexOf("://") // http:// or https://
286 val j = url.indexOf(":", i + 3)
287 return if (j == -1) {
289 } else url.substring(i + 3, j)
296 interface ISearchResultCallback
298 fun onDeviceFound(cameraDevice: ISonyCamera) // デバイスが見つかった!
299 fun onFinished() // 通常の終了をしたとき
300 fun onErrorFinished(reason: String?) // エラーが発生して応答したとき