OSDN Git Service

Sonyのマルチデバイス制御。
[gokigen/mangle.git] / app / src / main / java / jp / osdn / gokigen / gokigenassets / camera / vendor / sony / wrapper / connection / SonySsdpClient.kt
1 package jp.osdn.gokigen.gokigenassets.camera.vendor.sony.wrapper.connection
2
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.ICameraConstantConvert
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
16
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)
18 {
19     companion object
20     {
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"
32     }
33
34     private val ssdpRequest: String
35     private val useSmartphoneTransfer = false
36
37     init
38     {
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"
41     }
42
43     fun search()
44     {
45         val sendData = ssdpRequest.toByteArray()
46         val detailString = ""
47         var socket: DatagramSocket? = null
48         var receivePacket: DatagramPacket
49         val packet: DatagramPacket
50
51         //  要求の送信
52         try
53         {
54             socket = DatagramSocket()
55             socket.reuseAddress = true
56             val iAddress = InetSocketAddress(SSDP_ADDR, SSDP_PORT)
57             packet = DatagramPacket(sendData, sendData.size, iAddress)
58
59             // 要求を繰り返し送信する
60             for (loop in 1..sendRepeatCount)
61             {
62                 cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_CAMERA_SEARCH_REQUEST) + " " + loop)
63                 socket.send(packet)
64                 Thread.sleep(SEND_WAIT_DURATION_MS.toLong())
65             }
66             Log.v(TAG, " SSDP : SEND")
67         }
68         catch (e: Exception)
69         {
70             if (socket != null && !socket.isClosed)
71             {
72                 socket.close()
73             }
74             e.printStackTrace()
75
76             // エラー応答する
77             callback.onErrorFinished(detailString + " : " + e.localizedMessage)
78             return
79         }
80
81         // 応答の受信
82         val startTime = System.currentTimeMillis()
83         var currentTime = System.currentTimeMillis()
84         val foundDevices: MutableList<String?> = ArrayList()
85         val array = ByteArray(PACKET_BUFFER_SIZE)
86
87         try
88         {
89             cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_WAIT_REPLY_CAMERA))
90             while (currentTime - startTime < SSDP_RECEIVE_TIMEOUT)
91             {
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"))
96                 var ddUsn: String
97                 if (ssdpReplyMessage.contains("HTTP/1.1 200"))
98                 {
99                     ddUsn = findParameterValue(ssdpReplyMessage, "USN")
100                     Log.v(TAG, "- - - - - - - USN : $ddUsn")
101                     cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_CAMERA_RECEIVED_REPLY))
102                     if ((ddUsn.isNotEmpty())&&(!foundDevices.contains(ddUsn))&&(!cameraCoordinator.isAssignedCameraControl(ddUsn)))
103                     {
104                         val ddLocation = findParameterValue(ssdpReplyMessage, "LOCATION")
105                         foundDevices.add(ddUsn)
106
107                         //// Fetch Device Description XML and parse it.
108                         if (ddLocation.isNotEmpty())
109                         {
110                             cameraStatusReceiver.onStatusNotify("LOCATION : $ddLocation")
111                             val device: ISonyCamera? = searchSonyCameraDevice(ddLocation)
112                             if ((device != null)&&(device.hasApiService("camera")))
113                             {
114                                 cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.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))
119                                 break
120                             }
121                             else
122                             {
123                                 if (useSmartphoneTransfer && device != null)
124                                 {
125                                     cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CONNECT_CAMERA_FOUND) + " " + device.getFriendlyName() + " (Smartphone Transfer)")
126                                     Log.v(TAG, " SMARTPHONE TRANSFER : $ssdpReplyMessage")
127                                     callback.onDeviceFound(device)
128                                     break
129                                 }
130
131                                 // カメラが見つからない...
132                                 cameraStatusReceiver.onStatusNotify(context.getString(ICameraConstantConvert.ID_STRING_CAMERA_NOT_FOUND))
133                             }
134                         }
135                     }
136                     else
137                     {
138                         Log.v(TAG, "Already received. : $ddUsn")
139                     }
140                 }
141                 else
142                 {
143                     Log.v(TAG, " SSDP REPLY MESSAGE (ignored) : $ssdpReplyMessage")
144                 }
145                 currentTime = System.currentTimeMillis()
146             }
147         }
148         catch (e: Exception)
149         {
150             e.printStackTrace()
151
152             // エラー応答する
153             callback.onErrorFinished(detailString + " : " + e.localizedMessage)
154             return
155         }
156         finally
157         {
158             try
159             {
160                 if (!socket.isClosed())
161                 {
162                     socket.close()
163                 }
164             }
165             catch (ee: Exception)
166             {
167                 ee.printStackTrace()
168             }
169         }
170         callback.onFinished()
171     }
172
173     private fun findParameterValue(ssdpMessage: String, paramName: String): String
174     {
175         var name = paramName
176         if (!name.endsWith(":"))
177         {
178             name = "$name:"
179         }
180         var start = ssdpMessage.indexOf(name)
181         val end = ssdpMessage.indexOf("\r\n", start)
182         if (start != -1 && end != -1)
183         {
184             start += name.length
185             try
186             {
187                 return ssdpMessage.substring(start, end).trim { it <= ' ' }
188             }
189             catch (e: Exception)
190             {
191                 e.printStackTrace()
192             }
193         }
194         return ("")
195     }
196
197     private fun searchSonyCameraDevice(ddUrl: String): ISonyCamera?
198     {
199         val httpClient = SimpleHttpClient()
200         var device: SonyCameraDeviceProvider? = null
201         val ddXml: String
202         try
203         {
204             ddXml = httpClient.httpGet(ddUrl, -1)
205             Log.d(TAG, "fetch () httpGet done. : " + ddXml.length)
206             if (ddXml.length < 2)
207             {
208                 // 内容がないときは...終了する
209                 Log.v(TAG, "NO BODY")
210                 return (null)
211             }
212         }
213         catch (e: java.lang.Exception)
214         {
215             e.printStackTrace()
216             return (null)
217         }
218         try
219         {
220             //Log.v(TAG, "ddXml : " + ddXml);
221             val rootElement = XmlElement.parse(ddXml)
222
223             // "root"
224             if ("root" == rootElement.tagName)
225             {
226                 // "device"
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
231
232                 // "iconList"
233                 var iconUrl = ""
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
242                     }
243                 }
244                 device = SonyCameraDeviceProvider(ddUrl, friendlyName, modelName, udn, iconUrl)
245
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) {
252                     val serviceName =
253                         wApiServiceElement.findChild("X_ScalarWebAPI_ServiceType").value
254                     val actionUrl =
255                         wApiServiceElement.findChild("X_ScalarWebAPI_ActionList_URL").value
256                     device.addApiService(serviceName, actionUrl)
257                 }
258             }
259         } catch (e: java.lang.Exception) {
260             e.printStackTrace()
261         }
262         Log.d(TAG, "fetch () parsing XML done.")
263         if (device == null) {
264             Log.v(TAG, "device is null.")
265         }
266         return device
267     }
268
269     private fun toSchemeAndHost(url: String): String {
270         val i = url.indexOf("://") // http:// or https://
271         if (i == -1) {
272             return ""
273         }
274         val j = url.indexOf("/", i + 3)
275         return if (j == -1) {
276             ""
277         } else url.substring(0, j)
278     }
279
280     private fun toHost(url: String): String
281     {
282         val i = url.indexOf("://") // http:// or https://
283         if (i == -1) {
284             return ""
285         }
286         val j = url.indexOf(":", i + 3)
287         return if (j == -1) {
288             ""
289         } else url.substring(i + 3, j)
290     }
291
292     /**
293      * 検索結果のコールバック
294      *
295      */
296     interface ISearchResultCallback
297     {
298         fun onDeviceFound(cameraDevice: ISonyCamera) // デバイスが見つかった!
299         fun onFinished() // 通常の終了をしたとき
300         fun onErrorFinished(reason: String?) // エラーが発生して応答したとき
301     }
302 }