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.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(
102 IStringResourceConstantConvert.ID_STRING_CONNECT_CAMERA_RECEIVED_REPLY))
103 if ((ddUsn.isNotEmpty())&&(!foundDevices.contains(ddUsn))&&(!cameraCoordinator.isAssignedCameraControl(ddUsn)))
105 val ddLocation = findParameterValue(ssdpReplyMessage, "LOCATION")
106 foundDevices.add(ddUsn)
108 //// Fetch Device Description XML and parse it.
109 if (ddLocation.isNotEmpty())
111 cameraStatusReceiver.onStatusNotify("LOCATION : $ddLocation")
112 val device: ISonyCamera? = searchSonyCameraDevice(ddLocation)
113 if ((device != null)&&(device.hasApiService("camera")))
115 cameraStatusReceiver.onStatusNotify(context.getString(
116 IStringResourceConstantConvert.ID_STRING_CONNECT_CAMERA_FOUND) + " " + device.getFriendlyName())
117 cameraCoordinator.assignCameraControl(number, ddUsn)
118 callback.onDeviceFound(device)
119 // カメラが見つかった場合は breakする
120 Log.v(TAG, " assignCameraControl execution Result: " + cameraCoordinator.isAssignedCameraControl(ddUsn))
125 if (useSmartphoneTransfer && device != null)
127 cameraStatusReceiver.onStatusNotify(context.getString(
128 IStringResourceConstantConvert.ID_STRING_CONNECT_CAMERA_FOUND) + " " + device.getFriendlyName() + " (Smartphone Transfer)")
129 Log.v(TAG, " SMARTPHONE TRANSFER : $ssdpReplyMessage")
130 callback.onDeviceFound(device)
135 cameraStatusReceiver.onStatusNotify(context.getString(
136 IStringResourceConstantConvert.ID_STRING_CAMERA_NOT_FOUND))
142 Log.v(TAG, "Already received. : $ddUsn")
147 Log.v(TAG, " SSDP REPLY MESSAGE (ignored) : $ssdpReplyMessage")
149 currentTime = System.currentTimeMillis()
157 callback.onErrorFinished(detailString + " : " + e.localizedMessage)
164 if (!socket.isClosed())
169 catch (ee: Exception)
174 callback.onFinished()
177 private fun findParameterValue(ssdpMessage: String, paramName: String): String
180 if (!name.endsWith(":"))
184 var start = ssdpMessage.indexOf(name)
185 val end = ssdpMessage.indexOf("\r\n", start)
186 if (start != -1 && end != -1)
191 return ssdpMessage.substring(start, end).trim { it <= ' ' }
201 private fun searchSonyCameraDevice(ddUrl: String): ISonyCamera?
203 val httpClient = SimpleHttpClient()
204 var device: SonyCameraDeviceProvider? = null
208 ddXml = httpClient.httpGet(ddUrl, -1)
209 Log.d(TAG, "fetch () httpGet done. : " + ddXml.length)
210 if (ddXml.length < 2)
213 Log.v(TAG, "NO BODY")
217 catch (e: java.lang.Exception)
224 //Log.v(TAG, "ddXml : " + ddXml);
225 val rootElement = XmlElement.parse(ddXml)
228 if ("root" == rootElement.tagName)
231 val deviceElement = rootElement.findChild("device")
232 val friendlyName = deviceElement.findChild("friendlyName").value
233 val modelName = deviceElement.findChild("modelName").value
234 val udn = deviceElement.findChild("UDN").value
238 val iconListElement = deviceElement.findChild("iconList")
239 val iconElements = iconListElement.findChildren("icon")
240 for (iconElement in iconElements) {
241 // Choose png icon to show Android UI.
242 if ("image/png" == iconElement.findChild("mimetype").value) {
243 val uri = iconElement.findChild("url").value
244 val hostUrl = toSchemeAndHost(ddUrl)
245 iconUrl = hostUrl + uri
248 device = SonyCameraDeviceProvider(ddUrl, friendlyName, modelName, udn, iconUrl)
250 // "av:X_ScalarWebAPI_DeviceInfo"
251 val wApiElement = deviceElement.findChild("X_ScalarWebAPI_DeviceInfo")
252 val wApiServiceListElement = wApiElement.findChild("X_ScalarWebAPI_ServiceList")
253 val wApiServiceElements =
254 wApiServiceListElement.findChildren("X_ScalarWebAPI_Service")
255 for (wApiServiceElement in wApiServiceElements) {
257 wApiServiceElement.findChild("X_ScalarWebAPI_ServiceType").value
259 wApiServiceElement.findChild("X_ScalarWebAPI_ActionList_URL").value
260 device.addApiService(serviceName, actionUrl)
263 } catch (e: java.lang.Exception) {
266 Log.d(TAG, "fetch () parsing XML done.")
267 if (device == null) {
268 Log.v(TAG, "device is null.")
273 private fun toSchemeAndHost(url: String): String {
274 val i = url.indexOf("://") // http:// or https://
278 val j = url.indexOf("/", i + 3)
279 return if (j == -1) {
281 } else url.substring(0, j)
284 private fun toHost(url: String): String
286 val i = url.indexOf("://") // http:// or https://
290 val j = url.indexOf(":", i + 3)
291 return if (j == -1) {
293 } else url.substring(i + 3, j)
300 interface ISearchResultCallback
302 fun onDeviceFound(cameraDevice: ISonyCamera) // デバイスが見つかった!
303 fun onFinished() // 通常の終了をしたとき
304 fun onErrorFinished(reason: String?) // エラーが発生して応答したとき