FusionManager.kt 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package com.doverfuelingsolutions.issp.fusion
  2. import androidx.lifecycle.Lifecycle
  3. import androidx.lifecycle.LifecycleObserver
  4. import androidx.lifecycle.OnLifecycleEvent
  5. import com.doverfuelingsolutions.issp.R
  6. import com.doverfuelingsolutions.issp.api.FuelInfoApi
  7. import com.doverfuelingsolutions.issp.api.dto.DFSResult
  8. import com.doverfuelingsolutions.issp.api.entity.PumpInfo
  9. import com.doverfuelingsolutions.issp.data.GlobalData
  10. import com.doverfuelingsolutions.issp.fusion.callback.OnFusionEvent
  11. import com.doverfuelingsolutions.issp.utils.StringUtil
  12. import com.doverfuelingsolutions.issp.utils.thread.ThreadUtil
  13. import com.doverfuelingsolutions.issp.utils.ValidateUtil
  14. import com.doverfuelingsolutions.issp.utils.log.DFSLog
  15. import com.doverfuelingsolutions.issp.utils.sp.SPKeys
  16. import com.doverfuelingsolutions.issp.utils.sp.SPUtil
  17. import com.wayne.www.waynelib.fdc.FdcClient
  18. import com.wayne.www.waynelib.fdc.OnFdcClientStateChangedListener
  19. import com.wayne.www.waynelib.fdc.OnFdcMessageReceivedListener
  20. import com.wayne.www.waynelib.fdc.OnFdcServiceResponseReceivedListener
  21. import com.wayne.www.waynelib.fdc.message.*
  22. import kotlinx.coroutines.*
  23. import kotlin.coroutines.resume
  24. import kotlin.coroutines.suspendCoroutine
  25. object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
  26. OnFdcServiceResponseReceivedListener,
  27. OnFdcMessageReceivedListener {
  28. var stateFusion: FdcClient.FdcClientState = FdcClient.FdcClientState.Stopped
  29. private const val timeoutMax = 4000
  30. var firstLink = false
  31. private set
  32. private var isLogin = false
  33. val pumpList = arrayListOf<PumpInfo>()
  34. private val coroutineIO = CoroutineScope(Dispatchers.IO)
  35. var onFusionEvent: OnFusionEvent? = null
  36. override fun onFdcClientStateChanged(sender: FdcClient?, state: FdcClient.FdcClientState?) {
  37. DFSLog.i("Fusion: state = ${state?.name}")
  38. if (sender == null || state == null) return
  39. stateFusion = state
  40. when (state) {
  41. FdcClient.FdcClientState.Connected -> {
  42. firstLink = true
  43. loginFetchInfo()
  44. }
  45. // 中途断网重连
  46. FdcClient.FdcClientState.Connecting -> onFusionEvent?.onFusionReconnect()
  47. FdcClient.FdcClientState.MyAddReConnect -> {
  48. if (!firstLink) {
  49. onFusionEvent?.onFusionInit(FusionError.WrongAddress, StringUtil.get(R.string.wrong_fusion_address))
  50. } else {
  51. onFusionEvent?.onFusionReconnect()
  52. }
  53. }
  54. else -> {}
  55. }
  56. }
  57. override fun onServiceResponseReceived(sender: FdcClient?, serviceResponse: ServiceResponse?) {
  58. if (sender == null || serviceResponse == null) return
  59. // DFSLog.v("onServiceResponseReceived")
  60. }
  61. override fun onFdcMessageReceived(sender: FdcClient?, fdcMessage: FdcMessage?) {
  62. if (sender == null || fdcMessage == null) return
  63. // DFSLog.v("onFdcMessageReceived")
  64. }
  65. @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
  66. fun initialize() {
  67. FdcClient.getDefault().addOnFdcClientStateChangedListeners(this)
  68. FdcClient.getDefault().addOnFdcServiceResponseReceivedListeners(this)
  69. FdcClient.getDefault().addOnFdcMessageReceivedListeners(this)
  70. FdcClient.getDefault().start(
  71. SPUtil.getString(SPKeys.MIDDLE_IP),
  72. SPUtil.getString(SPKeys.MIDDLE_PORT).toInt(),
  73. GlobalData.serialNumber.get(),
  74. SPUtil.getString(SPKeys.MIDDLE_WORKSTATION_ID).toInt(),
  75. null
  76. )
  77. coroutineIO.launch {
  78. delay(12000)
  79. if (stateFusion == FdcClient.FdcClientState.Stopped) {
  80. onFusionEvent?.onFusionInit(FusionError.Timeout, StringUtil.get(R.string.connect_timeout))
  81. }
  82. }
  83. }
  84. @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
  85. fun close() {
  86. val manager = this
  87. coroutineIO.launch {
  88. logout()
  89. FdcClient.getDefault().removeOnFdcClientStateChangedListeners(manager)
  90. FdcClient.getDefault().removeOnFdcServiceResponseReceivedListeners(manager)
  91. FdcClient.getDefault().removeOnFdcMessageReceivedListeners(manager)
  92. FdcClient.getDefault().stop()
  93. firstLink = false
  94. }
  95. }
  96. fun restart() {
  97. coroutineIO.launch {
  98. close()
  99. delay(timeoutMax.toLong() + 1000L)
  100. initialize()
  101. }
  102. }
  103. /**
  104. * 初始化流程中的登录+获取油品信息
  105. */
  106. fun loginFetchInfo() {
  107. coroutineIO.launch {
  108. if (!isLogin) {
  109. val success = if (login()) true else {
  110. DFSLog.d("Fusion: retry login after logout")
  111. logout()
  112. delay(7000)
  113. login()
  114. }
  115. if (!success) {
  116. isLogin = false
  117. onFusionEvent?.onFusionInit(FusionError.Login, StringUtil.get(R.string.login_fail))
  118. return@launch
  119. } else {
  120. isLogin = true
  121. }
  122. }
  123. val resultPumpList = requestPumpInfo()
  124. if (resultPumpList.success && resultPumpList.data != null) {
  125. if (pumpList.isNotEmpty()) pumpList.clear()
  126. pumpList.addAll(resultPumpList.data)
  127. onFusionEvent?.onFusionInit(FusionError.Success)
  128. } else {
  129. onFusionEvent?.onFusionInit(FusionError.GetNozzleInfo, StringUtil.get(R.string.fail_get_nozzle))
  130. }
  131. }
  132. }
  133. /**
  134. * 获取所有未结算加油订单
  135. * @param pumpId -1表示全部FP,其他代表某个FP
  136. */
  137. suspend fun getAllUnsettledOrder(pumpId: Int = -1) = suspendCoroutine<DFSResult<List<DeviceClass>>> {
  138. ThreadUtil.io {
  139. FdcClient.getDefault().sendGetAvailableFuelSaleTrxs(pumpId, { _, response ->
  140. coroutineIO.launch {
  141. if (response == null || response !is ServiceResponseGetAvailableFuelSaleTrxs) {
  142. it.resume(DFSResult.fail(R.string.fail_get_order))
  143. } else if (response.singleFdcData == null || response.singleDeviceClass == null) {
  144. it.resume(DFSResult.success(emptyList()))
  145. } else {
  146. DFSLog.v("uncleared orders of pump $pumpId", response.singleFdcData.deviceClasses)
  147. val unsettledList = response.singleFdcData.deviceClasses.filter { !it.isClear }
  148. val resultDetailList = unsettledList
  149. .map { dc -> async { getOrderDetail(dc) } }
  150. .map { deferred -> deferred.await() }
  151. .filter { it.success }
  152. .mapNotNull {
  153. it.data?.productName =
  154. pumpList.find { pump -> pump.pumpId == it.data?.pumpNo }?.oilName
  155. ?: ""
  156. it.data
  157. }
  158. it.resume(DFSResult.success(resultDetailList))
  159. }
  160. }
  161. }, 4000)
  162. }
  163. }
  164. /**
  165. * 挂起函数:获取订单详情
  166. */
  167. private suspend fun getOrderDetail(deviceClass: DeviceClass) = suspendCoroutine<DFSResult<DeviceClass>> {
  168. getOrderDetailAsync(deviceClass) { asyncResult ->
  169. it.resume(asyncResult)
  170. }
  171. }
  172. /**
  173. * 获取订单详情
  174. */
  175. private fun getOrderDetailAsync(dcRaw: DeviceClass, callback: (result: DFSResult<DeviceClass>) -> Unit) {
  176. ThreadUtil.io {
  177. FdcClient.getDefault().sendGetFuelSalesTrxDetailsRequestAsync(dcRaw.pumpNo, dcRaw.transactionSeqNo, dcRaw.releaseTokenAttribute, { _, response ->
  178. if (response == null || response !is ServiceResponseGetFuelSalesTrxDetails || response.fdcData.isEmpty() || response.fdcData[0].deviceClasses.isEmpty()) {
  179. callback(DFSResult.fail(R.string.fail_get_order_detail))
  180. } else {
  181. val dc = response.fdcData[0].deviceClasses[0]
  182. // 迷惑的数据需要 hack 一下
  183. dc.releaseTokenAttribute = dcRaw.releaseTokenAttribute
  184. val correctNozzle = pumpList.find {
  185. it.pumpId == dc.pumpNo
  186. }?.nozzleList?.find {
  187. it.logicId == dc.nozzleNo
  188. }
  189. correctNozzle?.let {
  190. dc.nozzleNo = it.physicalId
  191. dc.logicNo = it.logicId
  192. }
  193. callback(DFSResult.success(dc))
  194. }
  195. }, 3000)
  196. }
  197. }
  198. /**
  199. * 消单
  200. * @desc 返回的 dc.isLock 不会发生改变 FIXME
  201. */
  202. suspend fun clearOrder(dc: DeviceClass): DFSResult<Boolean> {
  203. return if (dc.isClear) {
  204. DFSResult.success(true)
  205. } else if (dc.releaseTokenAttribute == null || !ValidateUtil.isPositiveInt(dc.releaseTokenAttribute)) {
  206. DFSResult.fail(R.string.wrong_order_info)
  207. } else {
  208. clearOrder(dc.pumpNo, dc.transactionSeqNo, dc.releaseTokenAttribute.toInt())
  209. }
  210. }
  211. suspend fun clearOrder(pumpId: Int, transactionNo: String, token: Int,) = suspendCoroutine<DFSResult<Boolean>> {
  212. ThreadUtil.io {
  213. FdcClient.getDefault().sendClearFuelSaleTrxRequestAsync(pumpId, transactionNo, token, { _, response ->
  214. if (response != null && response.singleDeviceClass != null && response.overallResult.equals("Success", true)) {
  215. DFSLog.i("clear order[$pumpId-$transactionNo-$token] succeed")
  216. it.resume(DFSResult.success(true))
  217. } else {
  218. DFSLog.e("FusionManager.clearOrder failed", response)
  219. it.resume(DFSResult.fail(R.string.fail_lock_order))
  220. }
  221. },4000)
  222. }
  223. }
  224. /**
  225. * 锁定/解锁订单
  226. */
  227. suspend fun lockOrder(dc: DeviceClass, lock: Boolean): DFSResult<Boolean> {
  228. return if (dc.isLock == lock) {
  229. DFSResult.success(true)
  230. } else if (dc.releaseTokenAttribute == null || !ValidateUtil.isPositiveInt(dc.releaseTokenAttribute)) {
  231. DFSResult.fail(R.string.wrong_order_info)
  232. } else {
  233. lockOrder(dc.pumpNo, dc.transactionSeqNo, dc.releaseTokenAttribute.toInt(), lock)
  234. }
  235. }
  236. suspend fun lockOrder(pumpId: Int, transactionNo: String, token: Int, lock: Boolean) = suspendCoroutine<DFSResult<Boolean>> {
  237. ThreadUtil.io {
  238. val callback = OnFdcServiceResponseReceivedListener { _, response ->
  239. if (response != null && response.singleDeviceClass != null && response.overallResult.equals("Success", true)) {
  240. it.resume(DFSResult.success(true))
  241. } else {
  242. it.resume(DFSResult.fail(response?.overallResult ?: StringUtil.get(R.string.fail_operate)))
  243. }
  244. }
  245. if (lock) {
  246. FdcClient.getDefault().sendLockFuelSaleTrxRequestAsync(pumpId, transactionNo, token, callback, 4000)
  247. } else {
  248. FdcClient.getDefault().sendUnLockFuelSaleTrxRequestAsync(pumpId, transactionNo, token, callback, 4000)
  249. }
  250. }
  251. }
  252. suspend fun requestPumpInfo() = FuelInfoApi.requestPumpInfo()
  253. private suspend fun login() = suspendCoroutine<Boolean> {
  254. val port = SPUtil.getString(SPKeys.MIDDLE_PORT).toInt()
  255. FdcClient.getDefault().sendLogonRequestAsync(port, port, "00.07", { _, response ->
  256. if (response != null && response is ServiceResponseLogOn) {
  257. val status = response.singleFdcData.fdcStatus
  258. DFSLog.v("Fusion: try login response = $status")
  259. if (status.equals(FusionConstants.CODE_SUCCESS, true)) {
  260. DFSLog.d("Fusion: login succeeded")
  261. it.resume(true)
  262. } else {
  263. DFSLog.d("Fusion: login failed")
  264. it.resume(false)
  265. }
  266. }
  267. }, timeoutMax)
  268. }
  269. private suspend fun logout() = suspendCoroutine<Boolean> {
  270. FdcClient.getDefault().sendLogOffRequestAsync({ _, response ->
  271. val status = response?.singleFdcData?.fdcStatus
  272. DFSLog.v("Fusion: try logout response = $status")
  273. if (status != null && status.equals(FusionConstants.CODE_SUCCESS, true)) {
  274. DFSLog.d("Fusion: logout succeeded")
  275. it.resume(true)
  276. } else {
  277. DFSLog.d("Fusion: logout failed")
  278. it.resume(false)
  279. }
  280. }, timeoutMax)
  281. }
  282. }