Android应用实现Iec61850客户端的读取和控制

项目要求读取设备(Iec61850协议)的数据并且实现开关的控制,我主要使用iec61850bean框架来实现。
iec61850bean框架的主页https://www.beanit.com/iec-61850/
项目地址https://github.com/beanit/iec61850bean
Android 作为客户端,要读取的设备装置作为服务端。
在项目/app/build.gradle中添加
implementation 'com.beanit:iec61850bean:1.9.0'
客户端连接:

import com.beanit.iec61850bean.ClientAssociation
import com.beanit.iec61850bean.ClientEventListener
import com.beanit.iec61850bean.ClientSap
import java.net.InetAddress


class Iec61850Client {
    private var IP = ""
    private var PORT = 102
    private var clientAssociation: ClientAssociation? = null
    fun startClient(IP:String,PORT:Int,listener: ClientEventListener?): ClientAssociation?{
        val clientSap = ClientSap()
//            clientSap.setResponseTimeout(2000)
        clientAssociation = clientSap.associate(InetAddress.getByName(IP), PORT, "", listener)
        this.IP = IP
        this.PORT = PORT
        return clientAssociation
    }

    fun disconnect() {
        if (clientAssociation != null && clientAssociation!!.isOpen) {
            clientAssociation!!.close()
        }
    }

    val isConnect: Boolean
        get() = clientAssociation != null && clientAssociation!!.isOpen


    val getIp: String
        get() = IP

    val getPort: Int
        get() = PORT
}

连接至设备服务器:

val iecClient = Iec61850Client()
try {
                    val clientAssociation = iecClient?.startClient(ip, port.toInt(), listener)
                    val serverModel = clientAssociation?.retrieveModel()
                } catch (e: Exception) {
                    callback.onFailure(e)
                    e.printStackTrace()
                    iecClient?.disconnect()
                }

其中clientAssociation?.retrieveModel()用来获取服务器模型。
然后如果想通过上传报告来更新遥测数据,则开启报告

    var measReportRef = "TEMPLATEMEAS/LLN0.urcbAin01"
    private fun enableReporting() {
        try {
            val urcb: Urcb? = serverModel?.getUrcb(measReportRef)
            clientAssociation?.getRcbValues(urcb)
//        iecClient?.clientAssociation?.reserveUrcb(urcb)
            clientAssociation?.enableReporting(urcb)
            clientAssociation?.startGi(urcb)
        } catch (e: Exception) {
            e.printStackTrace()
            println("设置失败" + e.message)
        }
    }

开启报告后你会收到定时发送的报告数据更新:

    override fun newReport(report: Report) {
        println("got a report.${report}")
        try {
            val list = report.values
            for (itemModel in list) {
                val dataRef = itemModel.reference.toString()
                println("dataRef:$dataRef")
                when (report.dataSetRef) {
                    "TEMPLATEMEAS/LLN0.dsAin" -> {
                        when {
                            dataRef.contains(".HzBus.mag.f") || dataRef.contains(".Hz.mag.f") -> {//SIUnit == 33
                                if (itemModel is BdaFloat32) {                              
                                    mydata.f = itemModel.float.toTwoDigitDouble()
                                }
                            }

                            dataRef.contains(".TotPF.mag.f") -> {
                                if (itemModel is BdaFloat32) {
                                    mydata.PF = itemModel.float.toTwoDigitDouble()
                                }
                            }

                            dataRef.contains(".TotW.mag.f") -> {//单位W 1000000
                                if (itemModel is BdaFloat32) {
                                    mydata.activePower = itemModel.float.toTwoDigitDouble() * 1000
                                }
                            }
                        }
                        mydata.time = System.currentTimeMillis()
                    }
                }
            }
            uiHandler.post {
                showYCView()
            }
        } catch (e: ServiceError) {
            e.printStackTrace()
        }
    }

我们也可以通过路径来主动读取:
clientAssociation.getAllDataValues()读取所有路径和数据,调用后所有设备服务器的实时数据都会更新至serverModel变量中。
我们也可以只取我们想要的部分值(通常是读取一些开关量状态值),先定义路径map

 private var dataMapSt = mutableMapOf<String, String>()
 private fun initFCDAMap() {
      dataMapSt.clear()
      dataMapSt["DlqState"] = "TEMPLATECTRL/INDGGIO1.Ind1.stVal"
  }

然后通过clientAssociation.GetDataValues()来读取

private fun readBrcbRelayDin() {
      lifecycleScope.launch(Dispatchers.IO) {
          for (item in dataMapSt) {
              val dataRef = item.value
               val node = serverModel?.findModelNode(dataRef, Fc.ST)
                if (node != null) {
                    try {
                        clientAssociation?.getDataValues(node as FcModelNode?) // 只获取这一个节点的值
                        if (node is BdaBoolean) {
                            when (item.key) {
                                "DlqState" -> {val dlqState = node.value}
                            }
                        }
                     } catch (e: Exception) {
                        e.printStackTrace()
                       
                    }
                }
           }
       } 
}

以上是读取数据的两种方式。
关于开关量的控制,我在项目中使用的是SBOw控制,即带值选择,然后再控制,但是Iec61850客户端只实现了普通的不带值选择并控制。所以需要我们自己实现带值选择。

    private fun selectWithValue(
        association: ClientAssociation?,
        controlObject: FcModelNode,
        controlValue: Boolean
    ): Boolean {
        var result = false
        try {
            // 获取控制对象中用于携带值的SBOw属性(例如一个BdaVisibleString节点)
            val sbow = controlObject?.getChild("SBOw", Fc.CO) as? ConstructedDataAttribute
            println(sbow)
            sbow?.let {
                (sbow.getChild("ctlVal") as BdaBoolean).value = controlValue
                (sbow.getChild("origin").getChild("orCat") as BdaInt8).value = 3
                (sbow.getChild("origin").getChild("orIdent") as BdaOctetString).value =
                    ConvertUtils.string2Bytes("myClient")
                (sbow.getChild("ctlNum") as BdaInt8U).value = 1.toShort()
                (sbow.getChild("T") as BdaTimestamp).instant = Instant.now()
                association?.setDataValues(sbow)
                result = true
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return result
    }

    private fun operate(
        association: ClientAssociation?,
        controlObject: FcModelNode,
        controlValue: Boolean
    ): Boolean {
        var result = false
        try {
            val oper = controlObject.getChild("Oper", Fc.CO) as? ConstructedDataAttribute
            oper?.let {
                (oper.getChild("ctlVal") as BdaBoolean).value = controlValue
                (oper.getChild("origin").getChild("orCat") as BdaInt8).value = 3
                (oper.getChild("origin").getChild("orIdent") as BdaOctetString).value =
                    ConvertUtils.string2Bytes("myClient")
                (oper.getChild("ctlNum") as BdaInt8U).value = 1.toShort()
                (oper.getChild("T") as BdaTimestamp).instant = Instant.now()
                association?.setDataValues(oper)
                result = true
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return result
    }

以上是我们需要自己实现的带值选择并控制,然后调用上述的方法来控制开关。

//先找到要控制的节点
 val sboNode =
                serverModel?.findModelNode("TEMPLATECTRL/TC1CSWI1.Pos", Fc.CO) as FcModelNode
if (sboNode != null) {
                try {
                    println("dlqSwitch61850dlqSwitch61850$controlValue ")
                    association?.getDataValues(sboNode as FcModelNode?)
                    val selectResult = selectWithValue(association, sboNode, controlValue)
                    if (selectResult) {
                        val operateResult = operate(association, sboNode, controlValue)
                        if (operateResult) {
                            callback.onSuccess(controlValue)
                            operationRecord.result = "操作成功"
                            GreenDaoApi.getGreenDaoApi().addOperationRecord(operationRecord)
                        } else {
                            callback.onFailure(controlValue,TipsException("断路器分合闸失败operateResult$operateResult"))
                            operationRecord.result = "操作失败"
                            GreenDaoApi.getGreenDaoApi().addOperationRecord(operationRecord)
                        }
                    } else {
                        callback.onFailure(controlValue,TipsException("断路器分合闸失败selectResult$selectResult"))
                        operationRecord.result = "操作失败"
                        GreenDaoApi.getGreenDaoApi().addOperationRecord(operationRecord)
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    callback.onFailure(controlValue,TipsException("断路器分合闸失败${e.message}"))
                    operationRecord.result = "操作失败"
                    GreenDaoApi.getGreenDaoApi().addOperationRecord(operationRecord)
                }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容