NTP延迟测量优化

बनाया गया: 18 जनवरी 2025

का उपयोग करके उत्तर दिया गया OpenAI 01 Mini द्वारा OpenAI01

प्रश्न

class NtpClient(serverAddress: String, serverPort: Int) {
companion object {
private const val TAG = "NtpClient"
}

private lateinit var clientSocket: DatagramSocket
private val sendData = ByteArray(Long.SIZE_BYTES * 3)
private val receiveData = ByteArray(Long.SIZE_BYTES * 4)
private lateinit var sendPacket: DatagramPacket
private lateinit var receivePacket: DatagramPacket
private var delay: Long = 0
private var offset: Long = 0
private var currentRequestId = AtomicLong(0)
private val oneWayDelays = mutableListOf<Long>()
private val roundTripDelays = mutableListOf<Long>()

// 新增字段存储最大、最小和平均值
private var maxOneWayDelay: Long = Long.MIN_VALUE
private var minOneWayDelay: Long = Long.MAX_VALUE
private var avgOneWayDelay: Double = 0.0

private var maxRoundTripDelay: Long = Long.MIN_VALUE
private var minRoundTripDelay: Long = Long.MAX_VALUE
private var avgRoundTripDelay: Double = 0.0

private var isFirstMeasurement = true

init {
try {
clientSocket = DatagramSocket()
sendPacket = DatagramPacket(
sendData,
sendData.size,
InetAddress.getByName(serverAddress),
serverPort
)
receivePacket = DatagramPacket(receiveData, receiveData.size)
} catch (e: Exception) {
Log.e(TAG, "Error initializing NtpClient: ${e.message}")
}
}

/**
* 计算客户端与服务器之间的时钟差值(offset)。
* 通过多次测量(多次 singleMeasurement),得到平均的offset和RTT,以此来纠正本地时钟偏差。
* 返回值:Pair(delay, offset)
* delay:这里delay代表通过多次测量得到的平均RTT值,
* offset:通过NTP算法计算得到的时钟偏移量
*/
@RequiresApi(Build.VERSION_CODES.O)
suspend fun calculateTimeDifference(measurements: Int = 5): Pair<Long, Long> =
withContext(Dispatchers.IO) {
var totalOffset = 0L
var totalDelay = 0L
var successfulMeasurements = 0

for (i in 1..measurements) {
val (rtt, singleOffset) = singleMeasurement()
if (rtt != Long.MIN_VALUE && singleOffset != Long.MIN_VALUE) {
totalDelay += rtt
totalOffset += singleOffset
successfulMeasurements++
}
}

if (successfulMeasurements > 0) {
val avgDelay = totalDelay / successfulMeasurements
val avgOffset = totalOffset / successfulMeasurements
delay = avgDelay
offset = avgOffset
return@withContext Pair(delay, offset)
}

return@withContext Pair(Long.MIN_VALUE, Long.MIN_VALUE)
}

/**
* 单次测量,用于计算offset和基本的RTT,但不重点计算和输出单向时延和详细时延信息。
* 这些详细的时延信息留在sendTime函数中进行计算和输出。
*/
@RequiresApi(Build.VERSION_CODES.O)
private suspend fun singleMeasurement(): Pair<Long, Long> = withContext(Dispatchers.IO) {
try {
val requestId = currentRequestId.getAndIncrement()
val t1 = System.currentTimeMillis()

// 发送请求 (t1)
val sendData = ByteArray(Long.SIZE_BYTES * 3)
ByteBuffer.wrap(sendData)
.putLong(requestId)
.putLong(t1)
.putLong(0)

val outPacket =
DatagramPacket(sendData, sendData.size, sendPacket.address, sendPacket.port)
clientSocket.send(outPacket)

// 接收响应并获取时间戳 (t1, t2, t3)
val inData = ByteArray(Long.SIZE_BYTES * 4)
val inPacket = DatagramPacket(inData, inData.size)
clientSocket.receive(inPacket)
val t4 = System.currentTimeMillis() // 客户端接收时间

val buffer = ByteBuffer.wrap(inPacket.data)
val receivedRequestId = buffer.getLong()
val receivedT1 = buffer.getLong()
val t2 = buffer.getLong() // 服务器接收时间
val t3 = buffer.getLong() // 服务器发送时间

if (receivedRequestId != requestId) {
Log.e(TAG, "Mismatched request ID: expected $requestId, got $receivedRequestId")
return@withContext Pair(Long.MIN_VALUE, Long.MIN_VALUE)
}

// 标准NTP公式计算RTT和offset
val rtt = (t4 - t1) - (t3 - t2)
val singleOffset = ((t2 - t1) + (t3 - t4)) / 2

// 这里只打印基本信息,不深入计算单��时延
Log.d(
TAG,
"singleMeasurement request=$receivedRequestId: rtt=$rtt ms, offset=$singleOffset ms"
)

return@withContext Pair(rtt, singleOffset)
} catch (e: Exception) {
Log.e(TAG, "Error in measurement: ${e.message}")
return@withContext Pair(Long.MIN_VALUE, Long.MIN_VALUE)
}
}

/**
* 在获得较稳定的offset(通过calculateTimeDifference和singleMeasurement的多次测量之后),
* 使用sendTime来计算和输出实际的往返时延和单向时延。
*/
@RequiresApi(Build.VERSION_CODES.O)
suspend fun sendTime(): Long = withContext(Dispatchers.IO) {
try {
val requestId = currentRequestId.getAndIncrement()
// 使用纳秒级时间戳以提高精度
val t1 = System.nanoTime() / 1_000_000 // 转换为毫秒

// 发送请求
val sendData = ByteArray(Long.SIZE_BYTES * 3)
ByteBuffer.wrap(sendData)
.putLong(requestId)
.putLong(t1)
.putLong(0)

val outPacket =
DatagramPacket(sendData, sendData.size, sendPacket.address, sendPacket.port)
clientSocket.send(outPacket)

// 接收响应
val inData = ByteArray(Long.SIZE_BYTES * 4)
val inPacket = DatagramPacket(inData, inData.size)
clientSocket.receive(inPacket)
val t4 = System.nanoTime() / 1_000_000 // 转换为毫秒

val buffer = ByteBuffer.wrap(inPacket.data)
val receivedRequestId = buffer.getLong()
val receivedT1 = buffer.getLong()
val t2 = buffer.getLong()
val t3 = buffer.getLong()

// 添加时钟偏移校正
val correctedT2 = t2 + offset
val correctedT3 = t3 + offset

// 使用校正后的时间戳计算延迟
val rtt = (t4 - t1) - (correctedT3 - correctedT2)
val singleOffset = ((correctedT2 - t1) + (correctedT3 - t4)) / 2

if (receivedRequestId != requestId) {
Log.e(TAG, "Mismatched request ID: expected $requestId, got $receivedRequestId")
return@withContext Long.MIN_VALUE
}

// 计算更详细的时延信息
val upstreamDelay = t2 - t1 // 上行时延
val downstreamDelay = t4 - t3 // 下行时延
val serverProcessingTime = t3 - t2
val networkTime = rtt - serverProcessingTime
val estimatedOneWayDelay = networkTime / 2

// 打印详细的延时数据
Log.d(
TAG, """
Request $receivedRequestId timing details:
t1 (client send): $t1
t2 (server receive): $t2
t3 (server send): $t3
t4 (client receive): $t4

Raw delays:
Upstream delay (t2-t1): $upstreamDelay ms
Downstream delay (t4-t3): $downstreamDelay ms
Server processing time: $serverProcessingTime ms

Calculated metrics:
RTT: $rtt ms
Network time (RTT - processing): $networkTime ms
Estimated one-way delay: $estimatedOneWayDelay ms
Time offset: $singleOffset ms
""".trimIndent()
)


// 更新全局offset和delay(这里delay用RTT表示)
offset = singleOffset
delay = rtt

oneWayDelays.add(estimatedOneWayDelay)
roundTripDelays.add(rtt)

// 更新最大、最小和平均值
updateDelayStats(estimatedOneWayDelay, rtt)

return@withContext rtt
} catch (e: Exception) {
Log.e(TAG, "Error sending time: ${e.message}")
Long.MIN_VALUE
}
}


private fun updateDelayStats(oneWayDelay: Long, roundTripDelay: Long) {
if (isFirstMeasurement) {
// 如果是第一次测量,直接设置最大、最小、平均值
maxOneWayDelay = oneWayDelay
minOneWayDelay = oneWayDelay
avgOneWayDelay = oneWayDelay.toDouble()

maxRoundTripDelay = roundTripDelay
minRoundTripDelay = roundTripDelay
avgRoundTripDelay = roundTripDelay.toDouble()

isFirstMeasurement = false
} else {
if (oneWayDelay > maxOneWayDelay) maxOneWayDelay = oneWayDelay
if (oneWayDelay < minOneWayDelay) minOneWayDelay = oneWayDelay
avgOneWayDelay = oneWayDelays.average()

if (roundTripDelay > maxRoundTripDelay) maxRoundTripDelay = roundTripDelay
if (roundTripDelay < minRoundTripDelay) minRoundTripDelay = roundTripDelay
avgRoundTripDelay = roundTripDelays.average()
}
}


fun close() {
if (::clientSocket.isInitialized && !clientSocket.isClosed) {
clientSocket.close()
}
delay = 0L
offset = 0L
}

fun isReadyToSendTime(): Boolean {
return delay != 0L && offset != 0L
}

fun getDelayStatistics(): Map<String, Double> {
return mapOf(
"oneWayMaxDelay" to maxOneWayDelay.toDouble(),
"oneWayMinDelay" to minOneWayDelay.toDouble(),
"oneWayAvgDelay" to avgOneWayDelay,
"roundTripMaxDelay" to maxRoundTripDelay.toDouble(),
"roundTripMinDelay" to minRoundTripDelay.toDouble(),
"roundTripAvgDelay" to avgRoundTripDelay
)
}
}



class NtpServer(private val port: Int) {
companion object {
private const val TAG = "NtpServer"
}

private lateinit var serverSocket: DatagramSocket
private var serverJob: Job? = null
private var timeDifference: Long = 0
private val clientTimestamps: MutableList<Long> = mutableListOf()
private val requestTimestamps = mutableMapOf<String, Long>()

@RequiresApi(Build.VERSION_CODES.O)
fun start() {
serverJob = CoroutineScope(Dispatchers.IO).launch {
try {
serverSocket = DatagramSocket(port)
Log.d(TAG, "NTP Server started on port $port")

while (isActive) {
val receiveData = ByteArray(Long.SIZE_BYTES * 3)
val receivePacket = DatagramPacket(receiveData, receiveData.size)
serverSocket.receive(receivePacket)

val buffer = ByteBuffer.wrap(receivePacket.data)
val requestId = buffer.getLong()
val t1 = buffer.getLong()
buffer.getLong()

val t2 = System.nanoTime() / 1_000_000 // 转换为毫秒

val sendData = ByteArray(Long.SIZE_BYTES * 4)
val sendBuffer = ByteBuffer.wrap(sendData)
sendBuffer.putLong(requestId)
sendBuffer.putLong(t1)
sendBuffer.putLong(t2)
val t3 = System.nanoTime() / 1_000_000 // 转换为毫秒
sendBuffer.putLong(t3)

val sendPacket = DatagramPacket(sendData, sendData.size, receivePacket.address, receivePacket.port)
serverSocket.send(sendPacket)

Log.d(TAG, "Processed request $requestId from ${receivePacket.address.hostAddress}:${receivePacket.port}")
}
} catch (e: SocketException) {
Log.d(TAG, "Socket closed, stopping server")
} catch (e: Exception) {
Log.e(TAG, "Error in server: ${e.message}")
} finally {
if (::serverSocket.isInitialized) {
serverSocket.close()
}
}
}
}

fun stop() {
serverJob?.cancel()
serverJob?.invokeOnCompletion {
if (::serverSocket.isInitialized) {
serverSocket.close()
}
}
serverJob = null
}

fun getClientTimes(): List<Long> {
return clientTimestamps.toList()
}

fun clearClientTimes() {
clientTimestamps.clear()
}

private fun Long.toByteArray(): ByteArray {
val buffer = ByteArray(8)
for (i in 0..7) {
buffer[i] = (this shr (8 * (7 - i))).toByte()
}
return buffer
}
}


class S7ClientFragment : Fragment() {

private lateinit var binding: FragmentS7ClientBinding
private var isConnected: Boolean = false

private lateinit var host: String
private val clientMessages = mutableListOf<String>()

private val metrics = CommunicationMetrics()
private val s7Client = S7Client(metrics)

private lateinit var commandItems: MutableList<S7ClientCommandItem>
private var commandAdapter: S7CommandAdapter? = null
private var ntpClient: NtpClient? = null

data class AddressInfo(
val dbNumber: Int?, val startAddress: Int, val dataType: String, val quantity: Int = 1, val bitOffset: Int = 0
)

private var isLoopTesting: Boolean = false

private lateinit var sharedViewModel: SharedViewModel

override fun onAttach(context: Context) {
super.onAttach(context)
sharedViewModel = ViewModelProvider(requireActivity())[SharedViewModel::class.java]
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = FragmentS7ClientBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

sharedViewModel.currentIpAddress.observe(viewLifecycleOwner) { ipAddress ->
if (ipAddress != null) {
binding.tvLocalIpAddress.text = "本机IP地址: $ipAddress"
} else {
binding.tvLocalIpAddress.text = "本机IP地址: 无法获取"
}
}

binding.btnConnectServerS7.setOnClickListener {
connectToServer()
}

binding.btnDisconnectServerS7.setOnClickListener {
disconnectFromServer()
}

binding.tvClientMessagesS7.movementMethod = ScrollingMovementMethod()

commandItems = mutableListOf(
S7ClientCommandItem(
functionCode = "01", dataPoint = "DB1.DBW0"
)
)
commandAdapter = S7CommandAdapter(commandItems,
onDeleteClick = { position -> handleDeleteCommandClick(position) },
onAddClick = { addNewCommandItem() },
onPointInfoClick = { position ->
showPointInfoDialog(position - 1, commandItems[position - 1])
})

parentFragmentManager.setFragmentResultListener(
"PointInfoDialogFragment", viewLifecycleOwner
) { requestKey, bundle ->
val position = bundle.getInt("position")
val dataItemsList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
bundle.getParcelableArrayList("dataItems", S7ClientDataItem::class.java)
?.toMutableList()
} else {
@Suppress("DEPRECATION") bundle.getParcelableArrayList<S7ClientDataItem>("dataItems")
?.toMutableList()
}

if (position in 0 until commandItems.size && dataItemsList != null) {
commandItems[position].dataItems = dataItemsList
}

commandAdapter?.notifyItemChanged(position + 1)
}

binding.commandRecyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.commandRecyclerView.adapter = commandAdapter

binding.btnShowMetrics.setOnClickListener {
ntpClient?.let { client ->
metrics.delayStatistics = client.getDelayStatistics()
}
val fragment = DataDisplayFragment.newInstance(ProtocolType.S7, true, metrics)
fragment.show(parentFragmentManager, "DataDisplayFragment")
}

binding.btnStartLoopTest.setOnClickListener {
if (isConnected) {
startLoopTest()
} else {
showToast("请先连接到服务器")
}
}

binding.btnStopLoopTest.setOnClickListener {
if (isConnected) {
stopLoopTest()
} else {
showToast("未连接到服务器")
}
}

binding.btnClearMessages.setOnClickListener {
clientMessages.clear()
binding.tvClientMessagesS7.text = "客户端消息"
}

}

private fun showPointInfoDialog(position: Int, item: S7ClientCommandItem) {
val dialog = S7PointInfoDialogFragment.newInstance(position, item.dataPoint, item.dataItems)
dialog.show(parentFragmentManager, "PointInfoDialogFragment")
}

private fun addNewCommandItem() {
val newItem = S7ClientCommandItem(
functionCode = "01",
dataPoint = "DB1.DBW0"
)

commandItems.add(newItem)

commandAdapter?.notifyItemInserted(commandItems.size)
}


private fun handleDeleteCommandClick(position: Int) {
val adjustedPosition = position - 1
if (adjustedPosition >= 0 && adjustedPosition < commandItems.size) {
commandItems.removeAt(adjustedPosition)
commandAdapter?.notifyItemRemoved(position)
}
}


private fun startLoopTest() {
if (isLoopTesting) {
showToast("循环测试已在运行")
return
}

val loopCount = binding.etLoopCount.text.toString().toIntOrNull() ?: 0
val interval = binding.etInterval.text.toString().toIntOrNull() ?: 0

if (loopCount <= 0) {
showToast("循环次数必须大于 0")
return
}

if (interval < 10 || interval > 5000) {
showToast("间隔时间必须在 10 到 5000 毫秒之间")
return
}

isLoopTesting = true
updateLoopTestStatus(-1, 0, "启动中...")
Log.d(
"S7ClientFragment",
"Starting loop test with $loopCount loops and $interval ms interval"
)

lifecycleScope.launch(Dispatchers.IO) {
for (i in 0 until loopCount) {
if (!isLoopTesting) {
Log.d("S7ClientFragment", "Loop test stopped by user")
break
}

Log.d("S7ClientFragment", "Loop iteration: ${i + 1}")

// --- 执行您的 S7 读/写操作 ---
// 循环读取所有命令并执行读/写操作
for (commandItem in commandItems) {
if (!isLoopTesting) {
Log.d("S7ClientFragment", "Loop test stopped by user")
break
}

for (dataItem in commandItem.dataItems) {
if (!isLoopTesting) {
Log.d("S7ClientFragment", "Loop test stopped by user")
break
}

Log.d(
"S7ClientFragment",
"Processing commandItem functionCode: ${commandItem.functionCode}"
)

// 根据操作类型执行读或写操作
if (commandItem.functionCode == "01") {
Log.d(
"S7ClientFragment",
"Performing read operation on ${dataItem.address}"
)
performReadOperation(dataItem)
// val addressInfo = parseAddressFromInput(dataItem)
// s7Client.testReadData(addressInfo?.dbNumber!!, addressInfo.startAddress, addressInfo.bitOffset)
} else if (commandItem.functionCode == "04") {
Log.d(
"S7ClientFragment",
"Performing write operation on ${dataItem.address} with values:"
)
performWriteOperation(dataItem)
// val addressInfo = parseAddressFromInput(dataItem)
// s7Client.testWriteData(addressInfo?.dbNumber!!, addressInfo.startAddress, addressInfo.bitOffset, true)
}

}
}
// S7 读/写操作结束
updateLoopTestStatus(i, loopCount, "运行中...")
Log.d(
"S7ClientFragment",
"Loop iteration ${i + 1} completed, waiting for $interval ms"
)
Thread.sleep(interval.toLong())
}

isLoopTesting = false
updateLoopTestStatus(-1, 0, "已完成")
Log.d("S7ClientFragment", "Loop test finished")
}
}

private suspend fun performReadOperation(dataItem: S7ClientDataItem) {
val dataType = dataItem.type
val quantity = dataItem.quantity

try {
// 使用 parseAddressFromInput 解析地址
val addressInfo = parseAddressFromInput(dataItem)

if (addressInfo != null) {
val dbNumber = addressInfo.dbNumber ?: 0
var startAddress = addressInfo.startAddress
val bitAddress = addressInfo.bitOffset


// 循环读取每个数据
for (i in 0 until quantity) {
Log.d(
"S7ClientFragment",
"Reading data from DB$dbNumber.${addressInfo.dataType}$startAddress, type: $dataType, quantity: 1"
)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && ntpClient?.isReadyToSendTime() == true) {
ntpClient?.sendTime()
}

val data = if (dataType.equals("BOOL", ignoreCase = true)) {
s7Client.readData(
dbNumber,
startAddress,
dataType,
bitAddress,
quantity
)
} else {
s7Client.readData(dbNumber, startAddress, dataType, 0, quantity)
}

if (data != null) {
val value = parseData(data, dataType)
Log.d(
"S7ClientFragment",
"Read data successfully: ${dataItem.address} - $value"
)
activity?.runOnUiThread {
appendClientMessage("读取数据成功: ${dataItem.address} - $value")
}
} else {
Log.w("S7ClientFragment", "Failed to read data from ${dataItem.address}")
activity?.runOnUiThread {
appendClientMessage("读取数据失败: ${dataItem.address}")
}
}
startAddress += if (dataType.equals("BOOL", ignoreCase = true)) {
bitAddress // BOOL 类型递增位地址
} else {
getByteLengthFromDataType(dataType, 1) // 其他类型递增字节地址
}
}
} else {
Log.e("S7ClientFragment", "Invalid address format: ${dataItem.address}")
activity?.runOnUiThread {
appendClientMessage("地址格式错误: ${dataItem.address}")
}
}
} catch (e: Exception) {
Log.e(
"S7ClientFragment",
"Error reading data from ${dataItem.address}: ${e.message}"
)
activity?.runOnUiThread {
appendClientMessage("读取数据时发生异常: ${e.message}")
}
}
}

private suspend fun performWriteOperation(dataItem: S7ClientDataItem) {
val address = dataItem.address
val dataType = dataItem.type
val quantity = dataItem.quantity
val values = dataItem.values

// 检查数据数量和值数量是否一致
if (quantity != values.size) {
Log.e("S7ClientFragment", "数据数量和值数量不一致")
activity?.runOnUiThread { showToast("数据数量和值数量不一致") }
return
}

try {
// 使用 parseAddressFromInput 解析地址
val addressInfo = parseAddressFromInput(dataItem)

if (addressInfo != null) {
val dbNumber = addressInfo.dbNumber ?: 0
var startAddress = addressInfo.startAddress
val bitAddress = addressInfo.bitOffset

// 循环写入每个数据值
for (i in 0 until quantity) {
val currentValue = values[i]

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && ntpClient?.isReadyToSendTime() == true) {
ntpClient?.sendTime()
}

Log.d(
"S7ClientFragment",
"Writing data to DB$dbNumber.${addressInfo.dataType}$startAddress, type: $dataType, quantity: 1, value: $currentValue"
)

// 根据数据类型将 currentValue 转换为 ByteArray
val byteArrayValue = when (dataType.uppercase()) {
"BOOL" -> {
// 布尔类型转换为单个字节,1 表示 true,0 表示 false
byteArrayOf(when (currentValue) {
"1" -> 1
"0" -> 0
else -> throw IllegalArgumentException("Invalid currentValue: $currentValue")
})
}

"BYTE" -> {
// 字节类型直接转换为 ByteArray
byteArrayOf((currentValue.toInt() and 0xFF).toByte())
}

"WORD" -> {
ByteBuffer.allocate(2).putShort(currentValue.toInt().toShort()).array()
}

"DWORD" -> {
val dwordValue = currentValue.toLong() // 确保使用Long来处理大于 2^31 - 1 的值
ByteBuffer.allocate(4)
.put((dwordValue ushr 24).toByte()) // 拆分出高 8 位
.put((dwordValue ushr 16).toByte()) // 拆分出第二高的 8 位
.put((dwordValue ushr 8).toByte()) // 拆分出第三高的 8 位
.put((dwordValue and 0xFF).toByte()) // 拆分出最低的 8 位
.array()
}

"INT" -> {
// 整数类型转换为 2 个字节的 ByteArray
ByteBuffer.allocate(2).putShort(currentValue.toShort()).array()
}

"DINT" -> {
// 双整数类型转换为 4 个字节的 ByteArray
ByteBuffer.allocate(4).putInt(currentValue.toInt()).array()
}

"REAL" -> {
// 浮点数类型转换为 4 个字节的 ByteArray
ByteBuffer.allocate(4).putFloat(currentValue.toFloat()).array()
}

else -> {
// 不支持的数据类型,返回空 ByteArray
byteArrayOf()
}
}
fun ByteArray.toHexString(): String = joinToString("") { "%02x".format(it) }

Log.d("S7ClientFragment", "byteArrayValue: ${byteArrayValue.toHexString()}")

// 调用 writeData 函数,传入 byteArrayValue
val success = s7Client.writeData(
dbNumber,
startAddress,
dataType,
bitAddress,
quantity,
byteArrayValue
)

activity?.runOnUiThread {
if (success) {
Log.d(
"S7ClientFragment",
"Write data successfully: ${dataItem.address} - $currentValue"
)
appendClientMessage("写入数据成功: ${dataItem.address} - $currentValue")
} else {
Log.w(
"S7ClientFragment",
"Failed to write data to ${dataItem.address}"
)
appendClientMessage("写入数据失败: ${dataItem.address} - $currentValue")
}
}

// 更新 startAddress,指向下一个数据地址
startAddress += if (dataType.equals("BOOL", ignoreCase = true)) {
bitAddress // BOOL 类型递增位地址
} else {
getByteLengthFromDataType(dataType, 1) // 其他类型递增字节地址
}
}
} else {
Log.e("S7ClientFragment", "Invalid address format: $address")
activity?.runOnUiThread {
appendClientMessage("地址格式错误: $address")
}
}
} catch (e: Exception) {
Log.e(
"S7ClientFragment",
"Error writing data to ${dataItem.address}: ${e.message}"
)
activity?.runOnUiThread {
appendClientMessage("写入数据时发生异常: ${e.message}")
}
}
}


private fun stopLoopTest() {
isLoopTesting = false
binding.tvLoopTestStatus.text = "状态: 停止"
}

private fun updateLoopTestStatus(currentLoop: Int, totalLoops: Int, status: String) {
activity?.runOnUiThread {
binding.tvLoopTestStatus.text = if (currentLoop >= 0) {
"状态: 运行中 (${currentLoop + 1}/$totalLoops) - $status"
} else {
"状态: $status"
}
}
}


private fun parseData(data: ByteArray, dataType: String): String {
return when (dataType) {
"BOOL" -> ((data[0].toInt() shr 0) and 1).toString()
"BYTE" -> (data[0].toInt() and 0xFF).toString()
"WORD" -> ByteBuffer.wrap(data).short.toInt().and(0xFFFF).toString()
"DWORD" -> ByteBuffer.wrap(data).int.toLong().and(0xFFFFFFFF).toString()
"INT" -> ByteBuffer.wrap(data).short.toString()
"DINT" -> ByteBuffer.wrap(data).int.toString()
"REAL" -> Float.fromBits(ByteBuffer.wrap(data).int).toString() // 解析 REAL 类型数据
"STRING" -> {
val nullTerminatorIndex = data.indexOf(0)
val endIndex = if (nullTerminatorIndex != -1) nullTerminatorIndex else data.size
String(data, 0, endIndex, Charsets.UTF_8)
}

else -> throw IllegalArgumentException("Unsupported data type: $dataType")
}
}


private fun connectToServer() {
val ip = binding.etClientIpS7.text.toString()
val portString = binding.etClientPortS7.text.toString()
val rackString = binding.etClientRackS7.text.toString()
val slotString = binding.etClientSlotS7.text.toString()

if (ip.isEmpty()) {
showToast("请输入有效的IP地址")
return
}

if (portString.isEmpty()) {
showToast("请输入端口号")
return
}

val port = portString.toIntOrNull()
if (port == null) {
showToast("端口号必须是数字")
return
}

val rack = rackString.toIntOrNull() ?: 0
val slot = slotString.toIntOrNull() ?: 0

lifecycleScope.launch(Dispatchers.IO) {
try {
ntpClient = NtpClient(ip, port)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ntpClient?.calculateTimeDifference()
}

} catch (e: Exception) {
ntpClient = null
Log.e("ModbusClientFragment", "Error calculating time difference: ${e.message}")
}
}

lifecycleScope.launch {

// 连接到 S7 服务器
host = ip
isConnected = s7Client.connectToServer(ip, rack, slot, port)
if (isConnected) {
updateConnectionStatus(true)
appendClientMessage(formatS7Message("01"))
showToast("连接成功")
} else {
updateConnectionStatus(false)
appendClientMessage("无法连接到服务器: $ip:$port")
showToast("连接失败")
}
}
}


private fun disconnectFromServer() {
ntpClient?.close()
if (isConnected) {
s7Client.disconnectFromServer()
updateConnectionStatus(false)
appendClientMessage(formatS7Message("02"))
isConnected = false
showToast("断开连接成功")
} else {
showToast("已经断开连接")
}
}


private fun updateConnectionStatus(isConnected: Boolean) {
binding.tvConnectionStatus.text = "连接状态: ${if (isConnected) "已连接" else "未连接"}"
}

private fun updateClientMessages() {
binding.tvClientMessagesS7.text = clientMessages.joinToString(separator = "\n")
}

private fun showToast(message: String) {
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}

private fun appendClientMessage(message: String) {
val currentDateTime = Utils.getCurrentDateTime()
clientMessages.add("$currentDateTime: $message")
updateClientMessages()
}

private fun formatS7Message(
command: String, address: Int? = null, data: String? = null
): String {
val header = "03 00"
val protocolId = "02 F0 80"
val messageType = when (command) {
"01" -> "32 01 00 00 00 00 01" // Connect
"02" -> "32 01 00 00 00 00 02" // Disconnect
"03" -> "32 01 00 00 00 00 03" // Read
"04" -> "32 01 00 00 00 00 04" // Write
else -> "32 01 00 00 00 00 00"
}
val paramFieldLength = "00 04"
val dataFieldLength = data?.let { "%04X".format(it.length) } ?: "00 00"
val addressHex = address?.let { "%04X".format(it) } ?: "00 00"
val dataPart =
data?.let { it.toByteArray(Charsets.UTF_8).joinToString(" ") { "%02X".format(it) } }
?: ""

val messageFields = "$header ${
calculateMessageLength(
header,
protocolId,
messageType,
paramFieldLength,
dataFieldLength,
addressHex,
dataPart
)
} $protocolId $messageType $paramFieldLength $dataFieldLength $addressHex $dataPart"

return "[S7] Msg:$messageFields"
}

private fun calculateMessageLength(vararg fields: String): String {
val totalLength = fields.sumOf { it.replace(" ", "").length / 2 }
return "%04X".format(totalLength)
}

private fun parseAddressFromInput(dataItem: S7ClientDataItem): AddressInfo? {
val address = dataItem.address
val dataType = dataItem.type
val dbRegex = """DB(\d+)\.(\d+)(?:\.(\d+))?(?:\[(\d+)])?""".toRegex()

return when {
dbRegex.matches(address) -> {
val match = dbRegex.find(address)
val dbNumber = match?.groupValues?.get(1)?.toIntOrNull()
val startAddress = match?.groupValues?.get(2)?.toIntOrNull() ?: 0
val bitAddress = match?.groupValues?.get(3)?.toIntOrNull() ?: 0

if (dbNumber != null) {
AddressInfo(dbNumber, startAddress, dataType, dataItem.quantity, bitAddress )
} else {
activity?.runOnUiThread { showToast("地址格式错误: $address") }
null
}
}

else -> {
activity?.runOnUiThread { showToast("地址格式错误: $address") }
null
}
}
}

private fun getByteLengthFromDataType(dataType: String, length: Int): Int {
return when (dataType.uppercase()) {
"BOOL" -> (length + 7) / 8 // 向上取整到最接近的字节
"BYTE", "CHAR" -> length
"WORD", "INT" -> length * 2
"DWORD", "DINT", "REAL" -> length * 4
"STRING" -> length * 2 + 2 // 添加两个额外的字节
else -> throw IllegalArgumentException("Unsupported data type: $dataType")
}
}
}


class S7ServerFragment : Fragment() {
private lateinit var binding: FragmentS7ServerBinding

private var isServerRunning = false
private val serverMessages = mutableListOf<String>()
private val dataItems = mutableListOf<S7DataBlockItem>()
private var adapter: S7DataBlockListAdapter? = null

private val metrics = CommunicationMetrics()
private val s7Server = S7Server(metrics)
private lateinit var sharedViewModel: SharedViewModel
private var ntpServer: NtpServer? = null

override fun onAttach(context: Context) {
super.onAttach(context)
sharedViewModel = ViewModelProvider(requireActivity())[SharedViewModel::class.java]
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentS7ServerBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

binding.btnStartServerS7.setOnClickListener {
if (!isServerRunning) {
val port = binding.etServerPortS7.text.toString().toIntOrNull() ?: 1025
val rack = binding.etServerRackS7.text.toString().toIntOrNull() ?: 0
val slot = binding.etServerSlotS7.text.toString().toIntOrNull() ?: 0
lifecycleScope.launch(Dispatchers.IO) {
ntpServer = NtpServer(port)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ntpServer?.start()
}
}
startServer(port, rack, slot)

} else {
showToast("服务器已在运行")
}
}

binding.btnStopServerS7.setOnClickListener {
if (isServerRunning) {
stopServer()
ntpServer?.stop()
} else {
showToast("服务器未运行")
}
}

binding.btnShowMetrics.setOnClickListener {
val clientTimes = ntpServer?.getClientTimes()
s7Server.getLatestMetrics(metrics, clientTimes)
ntpServer?.clearClientTimes()
val fragment = DataDisplayFragment.newInstance(ProtocolType.S7, false, metrics)
fragment.show(parentFragmentManager, "DataDisplayFragment")
}

sharedViewModel.currentIpAddress.observe(viewLifecycleOwner) { ipAddress ->
if (ipAddress != null) {
binding.tvLocalIpAddress.text = "本机IP地址: $ipAddress"
} else {
binding.tvLocalIpAddress.text = "本机IP地址: 无法获取"
}
}

binding.btnAddDataBlock.setOnClickListener {
if (isServerRunning) {
addDataBlock()
} else {
showToast("服务器未运行,无法添加数据块")
}
}

// binding.enableTimestampSwitch.setOnCheckedChangeListener { _, isChecked ->
// s7Server.setEnableTimestamp(isChecked)
// val currentDateTime = Utils.getCurrentDateTime()
// serverMessages.add("$currentDateTime: 时间戳功能已${if (isChecked) "启用" else "禁用"}")
// updateServerMessages()
// }

adapter = S7DataBlockListAdapter(
dataItems,
onDeleteClick = { position ->
if (isServerRunning) { // 检查服务器运行状态
handleDeleteClick(position)
} else {
showToast("服务器未运行,无法删除数据块")
}
})
binding.dataBlockRecyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.dataBlockRecyclerView.adapter = adapter


binding.btnClearMessages.setOnClickListener {
serverMessages.clear()
binding.tvServerMessagesS7.text = "服务端消息"
}

}

private fun showToast(message: String) {
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}

private fun handleDeleteClick(position: Int) {
val adjustedPosition = position - 1
val itemCount = adapter?.itemCount
Log.d("S7ServerFragment", "itemCount: $itemCount")

if (adjustedPosition >= 0 && adjustedPosition < dataItems.size) {
val dataBlockItem = dataItems[adjustedPosition]
val blockNumber = dataBlockItem.blockNumber

val success = s7Server.unregisterDataBlock(blockNumber)

val currentDateTime = Utils.getCurrentDateTime()
if (success) {
dataItems.removeAt(adjustedPosition)
adapter?.notifyItemRemoved(position)
Toast.makeText(context, "数据块 DB$blockNumber 反注册成功", Toast.LENGTH_SHORT)
.show()
serverMessages.add("$currentDateTime: 数据块 DB$blockNumber 反注册成功")
} else {
Toast.makeText(context, "数据块 DB$blockNumber 反注册失败", Toast.LENGTH_SHORT)
.show()
serverMessages.add("$currentDateTime: 数据块 DB$blockNumber 反注册失败")
}

updateServerMessages()
} else {
Log.e("S7ServerFragment", "无效的 position: $position")
}
}

private fun addDataBlock() {
val blockNumber = binding.etDataBlockNumber.text.toString().toIntOrNull()
val startAddress = binding.etDataBlockStartAddress.text.toString().toIntOrNull()
val length = binding.etDataBlockLength.text.toString().toIntOrNull()
val dataType = binding.spinnerDataType.selectedItem.toString()

if (blockNumber != null && startAddress != null && length != null) {
val byteLength = getByteLengthFromDataType(dataType, length)

val result = s7Server.registerDataBlock(blockNumber, startAddress, byteLength)
val currentDateTime = Utils.getCurrentDateTime()
if (result) {
val newDataBlock = S7DataBlockItem(blockNumber, startAddress, length, dataType, "")
dataItems.add(newDataBlock)
adapter?.notifyItemInserted(dataItems.size)
serverMessages.add("$currentDateTime: 数据块注册成功 (DB$blockNumber, 起始地址: $startAddress, 长度: $length, 数据类型: $dataType)")
Toast.makeText(context, "数据块注册成功", Toast.LENGTH_SHORT).show()
} else {
serverMessages.add("$currentDateTime: 数据块注册失败 (DB$blockNumber, 起始地址: $startAddress, 长度: $length, 数据类型: $dataType)")
Toast.makeText(context, "数据块注册失败", Toast.LENGTH_SHORT).show()
}
updateServerMessages()
} else {
Toast.makeText(context, "请确保所有字段都已填写", Toast.LENGTH_SHORT).show()
}
}


private fun getByteLengthFromDataType(dataType: String, length: Int): Int {
return when (dataType.uppercase()) {
"BOOL" -> (length + 7) / 8 // 向上取整到最接近的字节
"BYTE", "CHAR" -> length
"WORD", "INT" -> length * 2
"DWORD", "DINT", "REAL" -> length * 4
"STRING" -> length * 2 + 2 // 添加两个额外的字节
else -> throw IllegalArgumentException("Unsupported data type: $dataType")
}
}


private fun startServer(port: Int, rack: Int, slot: Int) {
if (s7Server.startServer(port, rack, slot)) {
isServerRunning = true
updateConnectionStatus(true)
Toast.makeText(requireContext(), "服务器已在端口$port 上启动", Toast.LENGTH_SHORT)
.show()
val currentDateTime = Utils.getCurrentDateTime()
serverMessages.add("$currentDateTime: 服务器已在端口$port 上启动")
updateServerMessages()

} else {
Toast.makeText(requireContext(), "无法在端口$port 上启动服务器", Toast.LENGTH_SHORT)
.show()
}
}

private fun stopServer() {
if (isServerRunning) {
s7Server.stopServer()
isServerRunning = false
updateConnectionStatus(false)
Toast.makeText(context, "服务器已停止", Toast.LENGTH_SHORT).show()

val currentDateTime = Utils.getCurrentDateTime()
serverMessages.add("$currentDateTime: 服务器已停止")
updateServerMessages()
} else {
Toast.makeText(context, "服务器未运行", Toast.LENGTH_SHORT).show()
}
}

private fun updateServerMessages() {
binding.tvServerMessagesS7.text = serverMessages.joinToString(separator = "\n")
}

private fun updateConnectionStatus(isRunning: Boolean) {
binding.tvConnectionStatus.text = "服务器状态: ${if (isRunning) "运行中" else "未运行"}"
}

override fun onDestroyView() {
super.onDestroyView()
ntpServer?.stop()
if (isServerRunning) {
try {
s7Server.stopServer()
} catch (e: Exception) {
Log.e("S7ServerFragment", "Error stopping server", e)
}
}
}
}


#include <jni.h>
#include "snap7/examples/cpp/snap7.h"
#include <cstdio>
#include <android/log.h>
#include <string>
#include <vector>
#include <deque>
#include <sys/time.h>
#include <mutex>
#include <algorithm>
#include <utility>
#include <limits>
#include <chrono>
#include <mutex>
#include <unordered_map>
#include <deque>
#include <random>
#include <cctype>
#include <iostream>
#include <iomanip>

static TS7Client *Client = nullptr;

#define LOG_TAG "s7-client"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// 使用 ConcurrentHashMap 保证线程安全
std::unordered_map<std::string, std::pair<std::atomic<int>, std::atomic<int>>> testItemResults;

// 获取当前时间戳的函数 (单位: 毫秒)
long long getCurrentTimestamp() {
struct timeval tv{};
gettimeofday(&tv, nullptr);
return (long long) tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

// 用于存储指标数据的结构体
struct Metrics {
int sentCommands;
int receivedCommands;
int sentPackets;
int receivedPackets;
int sentBytes;
int receivedBytes;
double sentRate;
double receivedRate;
int connectionAttemptCount;
int connectionSuccessCount;
long long connectionTotalDelay;
int commandAttemptCount;
int commandSuccessCount;
int commandTotalRetryCount;
long long lastTimestamp; // 上一次接收字节的时间戳
int lastIndex; // 上一次接收字节的时间戳
};

// 全局变量,用于存储最新的指标数据
Metrics metrics = {0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0};

// 存储每秒发送的数据包个数
std::vector<int> sentPacketsPerSecond(300, 0);
std::mutex sentPacketsPerSecondMutex;

// 存储每秒接收的数据包个数
std::vector<int> receivedPacketsPerSecond(300, 0);
std::mutex receivedPacketsPerSecondMutex;

void updateTestItemResult(const std::string &testItemName, bool isSuccess) {
auto it = testItemResults.find(testItemName);
if (it == testItemResults.end()) {
// 如果测试项不存在,则创建新的条目
testItemResults[testItemName] = std::make_pair(0, 0); // 初始化为0
it = testItemResults.find(testItemName);
}

if (isSuccess) {
it->second.first++; // 成功次数加 1
} else {
it->second.second++; // 失败次数加 1
}
}

void printDataToSend(byte *data, int size, const char *dataTypeStr) {
if (strcmp(dataTypeStr, "BOOL") == 0) {
LOGD("Data: %d", *data);
} else if (strcmp(dataTypeStr, "BYTE") == 0) {
for (int i = 0; i < size; i++) {
LOGD("Byte %d: 0x%02x", i, data[i]);
}
} else if (strcmp(dataTypeStr, "WORD") == 0) {
for (int i = 0; i < size / 2; i++) {
LOGD("Word %d: 0x%04x", i, ((uint16_t *) data)[i]);
}
} else if (strcmp(dataTypeStr, "DWORD") == 0 || strcmp(dataTypeStr, "DINT") == 0) {
for (int i = 0; i < size / 4; i++) {
LOGD("DWORD %d: 0x%08x", i, ((uint32_t *) data)[i]);
}
} else if (strcmp(dataTypeStr, "INT") == 0) {
for (int i = 0; i < size / 2; i++) {
LOGD("INT %d: %d", i, ((int16_t *) data)[i]);
}
} else if (strcmp(dataTypeStr, "REAL") == 0) {
for (int i = 0; i < size / 4; i++) {
LOGD("REAL %d: %f", i, ((float *) data)[i]);
}
} else if (strcmp(dataTypeStr, "STRING") == 0) {
LOGD("String: %s", data + 2); // 跳过前两个字节的长度信息
}
}

// 获取当前秒数在列表中的索引
int getCurrentSecondIndex() {
long long currentSecond = getCurrentTimestamp() / 1000;
return static_cast<int>(currentSecond % 300);
}


void printByteArray(const void *data, int size) {
const unsigned char *bytes = reinterpret_cast<const unsigned char *>(data);
for (int i = 0; i < size; ++i) {
LOGD("Byte %d: 0x%02x", i, bytes[i]);
}
}

void updateMetrics(JNIEnv *env, jobject thiz, int sentBytes, int receivedBytes,
long long roundTripDelay) {
// 在函数开头创建局部引用框架
env->PushLocalFrame(10);

// 更新指标数据
metrics.sentCommands++;
metrics.receivedCommands++;
metrics.sentPackets++;
metrics.receivedPackets++;
metrics.sentBytes += sentBytes;
metrics.receivedBytes += receivedBytes;


LOGD("updateMetrics - metrics.sentCommands: %d", metrics.sentCommands);
LOGD("updateMetrics - metrics.receivedCommands: %d",
metrics.receivedCommands);
LOGD("updateMetrics - metrics.sentPackets: %d", metrics.sentPackets);
LOGD("updateMetrics - metrics.receivedPackets: %d",
metrics.receivedPackets);
LOGD("updateMetrics - metrics.sentBytes: %d", metrics.sentBytes);
LOGD("updateMetrics - metrics.receivedBytes: %d", metrics.receivedBytes);

LOGD("updateMetrics - metrics.sentRate: %f", metrics.sentRate);
LOGD("updateMetrics - metrics.receivedRate: %f", metrics.receivedRate);
LOGD("updateMetrics - roundTripDelay: %lld", roundTripDelay);


// 更新 CommunicationMetrics 对象
jclass s7ClientClass = env->GetObjectClass(thiz);
jfieldID metricsFieldId = env->GetFieldID(
s7ClientClass, "metrics", "Lcom/dotouch/industrial/bean/CommunicationMetrics;");
jobject metricsObject = env->GetObjectField(thiz, metricsFieldId);

// 获取 CommunicationMetrics 类
jclass metricsClass = env->GetObjectClass(metricsObject);

// 获取 AtomicInteger 和 AtomicLong 的 set 方法 ID
jclass atomicIntegerClass = env->FindClass(
"java/util/concurrent/atomic/AtomicInteger");
jmethodID atomicIntegerSetMethodId = env->GetMethodID(atomicIntegerClass, "set",
"(I)V");

jclass atomicLongClass = env->FindClass("java/util/concurrent/atomic/AtomicLong");
jmethodID atomicLongSetMethodId = env->GetMethodID(atomicLongClass, "set", "(J)V");

// 获取 AtomicDouble 的 set 方法 ID
jclass atomicDoubleClass = env->FindClass("com/dotouch/industrial/bean/AtomicDouble");
jmethodID setDoubleMethodId = env->GetMethodID(atomicDoubleClass, "set", "(D)V");

// --- 获取字段 ID ---
jfieldID sentCommandsField = env->GetFieldID(metricsClass, "sentCommands",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jfieldID sentPacketsField = env->GetFieldID(metricsClass, "sentPackets",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jfieldID sentBytesField = env->GetFieldID(metricsClass, "sentBytes",
"Ljava/util/concurrent/atomic/AtomicLong;");
jfieldID receivedCommandsField = env->GetFieldID(
metricsClass, "receivedCommands", "Ljava/util/concurrent/atomic/AtomicInteger;");
jfieldID receivedPacketsField = env->GetFieldID(
metricsClass, "receivedPackets", "Ljava/util/concurrent/atomic/AtomicInteger;");
jfieldID receivedBytesField = env->GetFieldID(
metricsClass, "receivedBytes", "Ljava/util/concurrent/atomic/AtomicLong;");

jfieldID deviceRegistrationSuccessRateFieldId = env->GetFieldID(
metricsClass, "deviceRegistrationSuccessRate",
"Lcom/dotouch/industrial/bean/AtomicDouble;");
jfieldID deviceRegistrationAverageDelayFieldId = env->GetFieldID(
metricsClass, "deviceRegistrationAverageDelay",
"Lcom/dotouch/industrial/bean/AtomicDouble;");
jfieldID controlCommandSuccessRateFieldId = env->GetFieldID(
metricsClass, "controlCommandSuccessRate",
"Lcom/dotouch/industrial/bean/AtomicDouble;");
jfieldID controlCommandRetryCountFieldId = env->GetFieldID(
metricsClass, "controlCommandRetryCount",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jfieldID testItemResultsField = env->GetFieldID(metricsClass, "testItemResults",
"Ljava/util/concurrent/ConcurrentHashMap;");

// 获取 Integer 类和构造函数 ID (用于 sentRateHistory 和 receivedRateHistory)
jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID integerConstructorId = env->GetMethodID(integerClass, "<init>", "(I)V");


// --- 更新 sentRate 和 receivedRate 的值 ---
jfieldID sentRateFieldId = env->GetFieldID(metricsClass, "sentRate",
"Lcom/dotouch/industrial/bean/AtomicDouble;");
jfieldID receivedRateFieldId = env->GetFieldID(metricsClass, "receivedRate",
"Lcom/dotouch/industrial/bean/AtomicDouble;");

jobject sentRateObject = env->GetObjectField(metricsObject, sentRateFieldId);
jobject receivedRateObject = env->GetObjectField(metricsObject, receivedRateFieldId);

env->CallVoidMethod(sentRateObject, setDoubleMethodId, metrics.sentRate);
env->CallVoidMethod(receivedRateObject, setDoubleMethodId, metrics.receivedRate);


// --- 更新 sentRateHistory 和 receivedRateHistory ---
jfieldID sentRateHistoryFieldId = env->GetFieldID(metricsClass, "sentRateHistory",
"Ljava/util/List;");
jfieldID receivedRateHistoryFieldId = env->GetFieldID(metricsClass, "receivedRateHistory",
"Ljava/util/List;");

jobject sentRateHistoryObject = env->GetObjectField(metricsObject, sentRateHistoryFieldId);
jobject receivedRateHistoryObject = env->GetObjectField(metricsObject,
receivedRateHistoryFieldId);

// 获取 List 的 add 方法 ID
jclass listClass = env->FindClass("java/util/List");
jmethodID addMethodId = env->GetMethodID(listClass, "add", "(Ljava/lang/Object;)Z");
jmethodID clearMethodId = env->GetMethodID(listClass, "clear", "()V");

env->CallVoidMethod(sentRateHistoryObject, clearMethodId);
env->CallVoidMethod(receivedRateHistoryObject, clearMethodId);

std::lock_guard<std::mutex> lock1(sentPacketsPerSecondMutex);
for (int i = 0; i < sentPacketsPerSecond.size(); ++i) {
// LOGD("sentPacketsPerSecond[%d]: %d", i, sentPacketsPerSecond[i]);
int packetsCount = sentPacketsPerSecond[i];

// 创建 Integer 对象
jobject integerObject = env->NewObject(integerClass, integerConstructorId, packetsCount);

// 将 Integer 对象添加到 List
env->CallBooleanMethod(sentRateHistoryObject, addMethodId, integerObject);

// 删除局部引用,防止内存泄漏
env->DeleteLocalRef(integerObject);
}

std::lock_guard<std::mutex> lock2(receivedPacketsPerSecondMutex);
for (int i = 0; i < receivedPacketsPerSecond.size(); ++i) {
// LOGD("receivedPacketsPerSecond[%d]: %d", i, receivedPacketsPerSecond[i]);
int packetsCount = receivedPacketsPerSecond[i];

// 创建 Integer 对象
jobject integerObject = env->NewObject(integerClass, integerConstructorId, packetsCount);

// 将 Integer 对象添加到 List
env->CallBooleanMethod(receivedRateHistoryObject, addMethodId, integerObject);

// 删除局部引用,防止内存泄漏
env->DeleteLocalRef(integerObject);
}

// --- 更新 AtomicLong 和 AtomicInteger 字段的值 ---
jobject sentCommandsObject = env->GetObjectField(metricsObject, sentCommandsField);
env->CallVoidMethod(sentCommandsObject, atomicIntegerSetMethodId,
metrics.sentCommands);

jobject sentPacketsObject = env->GetObjectField(metricsObject, sentPacketsField);
env->CallVoidMethod(sentPacketsObject, atomicIntegerSetMethodId,
metrics.sentPackets);

jobject sentBytesObject = env->GetObjectField(metricsObject, sentBytesField);
env->CallVoidMethod(sentBytesObject, atomicLongSetMethodId,
(jlong) metrics.sentBytes);

jobject receivedCommandsObject = env->GetObjectField(metricsObject,
receivedCommandsField);
env->CallVoidMethod(receivedCommandsObject, atomicIntegerSetMethodId,
metrics.receivedCommands);

jobject receivedPacketsObject = env->GetObjectField(metricsObject,
receivedPacketsField);
env->CallVoidMethod(receivedPacketsObject, atomicIntegerSetMethodId,
metrics.receivedPackets);

jobject receivedBytesObject = env->GetObjectField(metricsObject, receivedBytesField);
env->CallVoidMethod(receivedBytesObject, atomicLongSetMethodId,
(jlong) metrics.receivedBytes);


jobject controlCommandRetryCountObject = env->GetObjectField(
metricsObject, controlCommandRetryCountFieldId);
env->CallVoidMethod(controlCommandRetryCountObject, atomicIntegerSetMethodId,
metrics.commandTotalRetryCount);


jobject deviceRegistrationSuccessRateObject = env->GetObjectField(
metricsObject, deviceRegistrationSuccessRateFieldId);
double deviceRegistrationSuccessRate = ((double) metrics.connectionSuccessCount /
(double) metrics.connectionAttemptCount) * 100;
env->CallVoidMethod(deviceRegistrationSuccessRateObject, setDoubleMethodId,
deviceRegistrationSuccessRate);

jobject deviceRegistrationAverageDelayObject = env->GetObjectField(
metricsObject, deviceRegistrationAverageDelayFieldId);
double deviceRegistrationAverageDelay = (double) metrics.connectionTotalDelay /
(double) metrics.connectionSuccessCount;
env->CallVoidMethod(deviceRegistrationAverageDelayObject, setDoubleMethodId,
deviceRegistrationAverageDelay);

jobject controlCommandSuccessRateObject = env->GetObjectField(
metricsObject, controlCommandSuccessRateFieldId);
double controlCommandSuccessRate = ((double) metrics.commandSuccessCount /
(double) metrics.commandAttemptCount) * 100;
env->CallVoidMethod(controlCommandSuccessRateObject, setDoubleMethodId,
controlCommandSuccessRate);

// --- 更新 testItemResults (ConcurrentHashMap) ---
jobject testItemResultsObject = env->GetObjectField(metricsObject,
testItemResultsField);

// 获取 ConcurrentHashMap 的 put 方法 ID
jclass concurrentHashMapClass = env->FindClass(
"java/util/concurrent/ConcurrentHashMap");
jmethodID putMethodId = env->GetMethodID(concurrentHashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

// 获取 AtomicInteger 的构造函数 ID
jclass atomicIntegerClass2 = env->FindClass(
"java/util/concurrent/atomic/AtomicInteger");
jmethodID atomicIntegerConstructorId = env->GetMethodID(atomicIntegerClass2, "<init>",
"(I)V");

// 获取 Pair 类和构造函数 ID
jclass pairClass = env->FindClass("kotlin/Pair");
jmethodID pairConstructorId = env->GetMethodID(pairClass, "<init>",
"(Ljava/lang/Object;Ljava/lang/Object;)V");

for (const auto &pair: testItemResults) {
const std::string &testItemName = pair.first;
int successCount = pair.second.first;
int failureCount = pair.second.second;

// 创建 Java 字符串对象
jstring testItemNameObject = env->NewStringUTF(testItemName.c_str());

// 创建 Java AtomicInteger 对象
jobject successCountObject = env->NewObject(atomicIntegerClass2,
atomicIntegerConstructorId,
successCount);
jobject failureCountObject = env->NewObject(atomicIntegerClass2,
atomicIntegerConstructorId,
failureCount);

// 创建 Java Pair 对象
jobject pairObject = env->NewObject(pairClass, pairConstructorId,
successCountObject, failureCountObject);

// 将测试项结果添加到 Java 的 ConcurrentHashMap 中
env->CallObjectMethod(testItemResultsObject, putMethodId, testItemNameObject,
pairObject);

// 删除局部引用,防止内存泄漏
env->DeleteLocalRef(testItemNameObject);
env->DeleteLocalRef(successCountObject);
env->DeleteLocalRef(failureCountObject);
env->DeleteLocalRef(pairObject);
}


// 在函数结尾释放局部引用框架
env->PopLocalFrame(nullptr);
}


extern "C" JNIEXPORT jboolean JNICALL
Java_com_dotouch_industrial_s7_S7Client_connectToServer(JNIEnv *env, jobject obj, jstring jip,
jint rack, jint slot, jint port) {
const char *ip = env->GetStringUTFChars(jip, nullptr);

metrics.connectionAttemptCount++;

// 如果已经存在客户端实例,先断开连接并删除
if (Client != nullptr) {
Client->Disconnect();
delete Client;
Client = nullptr; // 避免悬挂指针
LOGD("Disconnected from previous connection.");
}

// 创建新的客户端实例
Client = new TS7Client();

// 设置连接参数
int timeout = 5000;
int result = Client->SetParam(p_i32_PingTimeout, &timeout);
if (result != 0) {
LOGE("Failed to set PingTimeout parameter. Error code: %d", result);
delete Client;
Client = nullptr;
env->ReleaseStringUTFChars(jip, ip);
return JNI_FALSE;
}

// 设置连接类型(如果需要)
result = Client->SetConnectionType(CONNTYPE_BASIC);
if (result != 0) {
LOGE("Failed to set connection type. Error code: %d", result);
delete Client;
Client = nullptr;
env->ReleaseStringUTFChars(jip, ip);
return JNI_FALSE;
}

// 设置端口(如果支持)
result = Client->SetParam(p_u16_RemotePort, &port);
if (result != 0) {
LOGE("Failed to set remote port. Error code: %d", result);
delete Client;
Client = nullptr;
env->ReleaseStringUTFChars(jip, ip);
return JNI_FALSE;
}

// 记录连接开始时间
long long startTime = getCurrentTimestamp();
// 连接到服务器
result = Client->ConnectTo(ip, rack, slot);
// 记录连接结束时间
long long endTime = getCurrentTimestamp();
env->ReleaseStringUTFChars(jip, ip);

if (result == 0) {
// 连接成功
metrics.connectionSuccessCount++;
metrics.connectionTotalDelay += endTime - startTime;
LOGD("Successfully connected to PLC: %s, rack: %d, slot: %d", ip, rack, slot);
return JNI_TRUE;
} else {

// 连接失败
LOGE("Failed to connect to PLC: %s, rack: %d, slot: %d, Error code: %d", ip, rack, slot,
result);
delete Client;
Client = nullptr;
return JNI_FALSE;
}
}


extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_dotouch_industrial_s7_S7Client_readData(JNIEnv *env, jobject obj, jint dbNumber,
jint startAddress, jstring dataType,
jint bitAddress,
jint quantity) {
LOGD("readData called");
// 获取 S7Client 对象
if (Client == nullptr) {
LOGE("Not connected to PLC. Call connectToServer first.");
return nullptr;
}

metrics.commandAttemptCount++;

long long startTime = getCurrentTimestamp();

// 将 jstring 转换为 C 字符串
const char *cDataType = env->GetStringUTFChars(dataType, 0);

// 计算单个数据的大小
int dataSize;
if (strcmp(cDataType, "BOOL") == 0) {
dataSize = 1; // 对于 BOOL,dataSize 始终为 1 (表示按位读取)
} else if (strcmp(cDataType, "BYTE") == 0) {
dataSize = 1;
} else if (strcmp(cDataType, "WORD") == 0 || strcmp(cDataType, "INT") == 0) {
dataSize = 2;
} else if (strcmp(cDataType, "DWORD") == 0 || strcmp(cDataType, "DINT") == 0 ||
strcmp(cDataType, "REAL") == 0) {
dataSize = 4;
} else {
LOGE("Unsupported data type: %s", cDataType);
env->ReleaseStringUTFChars(dataType, cDataType);
return nullptr;
}

// 更新 s7receivedPacketsPerSecond
{
std::lock_guard<std::mutex> lock(receivedPacketsPerSecondMutex);
long long currentTimestamp = getCurrentTimestamp();
if (metrics.lastTimestamp == 0) {
// 第一次接收数据,重置计数为 1,currentSecondIndex 为 0
receivedPacketsPerSecond[0] = 1;
metrics.lastTimestamp = currentTimestamp;
LOGD("Received first data, resetting s7receivedPacketsPerSecond");
} else {
long long elapsedTimeSinceLastData = currentTimestamp - metrics.lastTimestamp;
LOGD("elapsedTimeSinceLastData: %lld ms", elapsedTimeSinceLastData);
if (elapsedTimeSinceLastData < 1000) {
// 仍在同一秒内,使用 lastTimestamp 计算索引
receivedPacketsPerSecond[metrics.lastIndex]++;
LOGD("Received data, updating s7receivedPacketsPerSecond[%d]", metrics.lastIndex);
} else {
// 经过了至少 1 秒,使用 elapsedTimeSinceLastData 计算索引
int currentSecondIndex =
(elapsedTimeSinceLastData / 1000) % 300 + metrics.lastIndex;
receivedPacketsPerSecond[currentSecondIndex] = 1; // 重置计数为 1
metrics.lastIndex = currentSecondIndex;
metrics.lastTimestamp = currentTimestamp;
LOGD("Received data, resetting s7receivedPacketsPerSecond[%d]", currentSecondIndex);
}
}
}

int result;
int retryCount = 0;

if (strcmp(cDataType, "BOOL") == 0) {
// 处理 BOOL 类型数据
if (quantity == 1) {
// 读取单个 BOOL 值
int byteAddress = startAddress;
int bitOffset = bitAddress;

uint8_t buffer[1]; // 分配一个字节的缓冲区

do {
result = Client->DBRead(dbNumber, byteAddress, 1, buffer);
if (result == 0) {
metrics.commandSuccessCount++;
updateTestItemResult("读取操作", true);
break;
} else {
retryCount++;
metrics.commandTotalRetryCount++;
updateTestItemResult("读取操作", false);
LOGE("Error reading data (attempt %d): %s", retryCount,
CliErrorText(result).c_str());
usleep(100000);
}
} while (retryCount < 3);

if (result != 0) {
env->ReleaseStringUTFChars(dataType, cDataType);
return nullptr;
}

uint8_t bitValue = (buffer[0] >> bitOffset) & 0x01;
LOGD("Read bit value: %d", bitValue);

jbyteArray jbyteArrayResult = env->NewByteArray(1);
jbyte boolValue = bitValue;
env->SetByteArrayRegion(jbyteArrayResult, 0, 1, &boolValue);

// 计算往返延迟
long long endTime = getCurrentTimestamp();
long long roundTripDelay = endTime - startTime;

// 计算接收速率,避免除以 0 的错误
if (roundTripDelay > 0) {
metrics.receivedRate = (double) 1 / ((double) roundTripDelay / 1000.0); // bytes/s
} else {
metrics.receivedRate = 1; // 或其他默认值
}

updateMetrics(env, obj, 0, 1, roundTripDelay);
env->ReleaseStringUTFChars(dataType, cDataType);
return jbyteArrayResult;
} else {
// 读取多个连续的 BOOL 值
int byteAddress = startAddress;
int bitOffset = bitAddress;
int totalSize = (quantity + bitOffset + 7) / 8;

uint8_t *readBuffer = (uint8_t *)malloc(totalSize);
if (readBuffer == nullptr) {
LOGE("Failed to allocate memory for read buffer");
env->ReleaseStringUTFChars(dataType, cDataType);
return nullptr;
}

do {
result = Client->DBRead(dbNumber, byteAddress, totalSize, readBuffer);
if (result == 0) {
metrics.commandSuccessCount++;
updateTestItemResult("读取操作", true);
break;
} else {
retryCount++;
metrics.commandTotalRetryCount++;
updateTestItemResult("读取操作", false);
LOGE("Error reading data (attempt %d): %s", retryCount,
CliErrorText(result).c_str());
usleep(100000);
}
} while (retryCount < 3);

if (result != 0) {
free(readBuffer);
env->ReleaseStringUTFChars(dataType, cDataType);
return nullptr;
}

jbyteArray jbyteArrayResult = env->NewByteArray(quantity);
jbyte *boolValues = (jbyte *)malloc(quantity);
if (boolValues == nullptr) {
LOGE("Failed to allocate memory for bool values");
free(readBuffer);
env->ReleaseStringUTFChars(dataType, cDataType);
return nullptr;
}

for (int i = 0; i < quantity; i++) {
int currentByte = (bitOffset + i) / 8;
int currentBit = (bitOffset + i) % 8;
boolValues[i] = (readBuffer[currentByte] >> currentBit) & 0x01;
}

env->SetByteArrayRegion(jbyteArrayResult, 0, quantity, boolValues);

// 计算往返延迟
long long endTime = getCurrentTimestamp();
long long roundTripDelay = endTime - startTime;

// 计算接收速率,避免除以 0 的错误
if (roundTripDelay > 0) {
metrics.receivedRate =
(double) totalSize / ((double) roundTripDelay / 1000.0); // bytes/s
} else {
metrics.receivedRate = totalSize; // 或其他默认值
}

free(boolValues);
free(readBuffer);
updateMetrics(env, obj, 0, totalSize, roundTripDelay);
env->ReleaseStringUTFChars(dataType, cDataType);
return jbyteArrayResult;
}
} else {
// 处理其他数据类型 (BYTE, WORD, DWORD, 等)
int totalSize = quantity * dataSize;

void *readBuffer = malloc(totalSize);
if (readBuffer == nullptr) {
LOGE("Failed to allocate memory for read buffer");
env->ReleaseStringUTFChars(dataType, cDataType);
return nullptr;
}

do {
result = Client->DBRead(dbNumber, startAddress, totalSize, readBuffer);
if (result == 0) {
metrics.commandSuccessCount++;
updateTestItemResult("读取操作", true);
break;
} else {
retryCount++;
metrics.commandTotalRetryCount++;
updateTestItemResult("读取操作", false);
LOGE("Error reading data (attempt %d): %s", retryCount,
CliErrorText(result).c_str());
usleep(100000);
}
} while (retryCount < 3);

env->ReleaseStringUTFChars(dataType, cDataType);

if (result != 0) {
free(readBuffer);
return nullptr;
}

jbyteArray jbyteArrayResult = env->NewByteArray(totalSize);
env->SetByteArrayRegion(jbyteArrayResult, 0, totalSize, (jbyte *)readBuffer);

// 计算往返延迟
long long endTime = getCurrentTimestamp();
long long roundTripDelay = endTime - startTime;

// 计算接收速率,避免除以 0 的错误
if (roundTripDelay > 0) {
metrics.receivedRate =
(double) totalSize / ((double) roundTripDelay / 1000.0); // bytes/s
} else {
metrics.receivedRate = totalSize; // 或其他默认值
}

free(readBuffer);
updateMetrics(env, obj, 0, totalSize, roundTripDelay);
return jbyteArrayResult;
}
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_dotouch_industrial_s7_S7Client_writeData(JNIEnv *env, jobject obj, jint dbNumber,
jint startAddress, jstring dataType,
jint bitAddress, jint quantity,
jbyteArray value) {

LOGD("writeData called");

// 获取 S7Client 对象
if (Client == nullptr) {
return JNI_FALSE;
}

metrics.commandAttemptCount++;

// 将 jstring 转换为 C 字符串
const char *cDataType = env->GetStringUTFChars(dataType, 0);

// 获取 jbyteArray 的长度和内容
jsize valueLength = env->GetArrayLength(value);
jbyte *cValue = env->GetByteArrayElements(value, 0);

// 数据类型信息结构体
struct DataTypeInfo {
int size;
// 可以添加更多字段,例如数据转换函数指针
};

// 数据类型映射表
std::map<std::string, DataTypeInfo> dataTypeMap = {
{"BOOL", {1}},
{"BYTE", {1}},
{"WORD", {2}},
{"INT", {2}},
{"DWORD", {4}},
{"DINT", {4}},
{"REAL", {4}}
};

// 查找数据类型信息
auto it = dataTypeMap.find(cDataType);
if (it == dataTypeMap.end()) {
LOGE("Unsupported data type: %s", cDataType);
env->ReleaseStringUTFChars(dataType, cDataType);
env->ReleaseByteArrayElements(value, cValue, 0);
return JNI_FALSE;
}

int dataSize = it->second.size;
int totalSize = quantity * dataSize;

// 获取当前时间戳 (用于计算延迟)
long long startTime = getCurrentTimestamp();

// 写入数据 (最多重试 3 次)
int result;
int retryCount = 0;
void *writeBuffer = malloc(totalSize); // 在这里分配缓冲区

if (writeBuffer == nullptr) {
LOGE("Failed to allocate memory for write buffer");
env->ReleaseStringUTFChars(dataType, cDataType);
env->ReleaseByteArrayElements(value, cValue, 0);
return JNI_FALSE;
}

do {
if (strcmp(cDataType, "BOOL") == 0) {
// BOOL 类型数据写入
int byteOffset = startAddress;
int bitOffset = bitAddress;

// 读取原字节数据
uint8_t tempBuffer[1];
result = Client->DBRead(dbNumber, byteOffset, 1, tempBuffer);
if (result != 0) {
LOGE("Error reading byte before write: %s", CliErrorText(result).c_str());
break; // 读取错误时退出循环
}

// 修改目标位
if (cValue[0] == 1) {
tempBuffer[0] |= (1 << bitOffset);
} else {
tempBuffer[0] &= ~(1 << bitOffset);
}

memcpy(writeBuffer, tempBuffer, 1); // 将修改后的字节复制到 writeBuffer
result = Client->DBWrite(dbNumber, byteOffset, 1, writeBuffer);
} else {
// 其他类型数据写入
memcpy(writeBuffer, cValue, totalSize); // 将数据复制到 writeBuffer
result = Client->DBWrite(dbNumber, startAddress, totalSize, writeBuffer);
}

if (result == 0) {
metrics.commandSuccessCount++;
break;
} else {
retryCount++;
metrics.commandTotalRetryCount++;
LOGE("Error writing data (attempt %d): %s", retryCount, CliErrorText(result).c_str());
usleep(100000);
}
} while (retryCount < 3);

// 释放资源
env->ReleaseStringUTFChars(dataType, cDataType);
env->ReleaseByteArrayElements(value, cValue, 0);
free(writeBuffer);

// 检查写入结果
if (result != 0) {
updateTestItemResult("写入操作", false);
LOGE("Error writing data: %s", CliErrorText(result).c_str());
return JNI_FALSE;
} else {
updateTestItemResult("写入操作", true);
}

// 计算往返延迟
long long endTime = getCurrentTimestamp();
long long roundTripDelay = endTime - startTime;

// 计算发送速率,避免除以 0 的错误
if (roundTripDelay > 0) {
metrics.sentRate = (double) totalSize / ((double) roundTripDelay / 1000.0); // bytes/s
} else {
metrics.sentRate = 0.0; // 或其他默认值
}

// 更新 s7sentPacketsPerSecond
{
std::lock_guard<std::mutex> lock(sentPacketsPerSecondMutex);
long long currentTimestamp = getCurrentTimestamp();
if (metrics.lastTimestamp == 0) {
// 第一次发送数据,重置计数为 1,currentSecondIndex 为 0
sentPacketsPerSecond[0] = 1;
metrics.lastTimestamp = currentTimestamp;
LOGD("Sent first data, resetting s7sentPacketsPerSecond");
} else {
long long elapsedTimeSinceLastData = currentTimestamp - metrics.lastTimestamp;
LOGD("elapsedTimeSinceLastData: %lld ms", elapsedTimeSinceLastData);
if (elapsedTimeSinceLastData < 1000) {
// 仍在同一秒内,使用 lastSentBytesTimestamp 计算索引
sentPacketsPerSecond[metrics.lastIndex]++;
LOGD("Sent data, updating s7sentPacketsPerSecond[%d]", metrics.lastIndex);
} else {
// 经过了至少 1 秒,使用 elapsedTimeSinceLastData 计算索引
int currentSecondIndex =
(elapsedTimeSinceLastData / 1000) % 300 + metrics.lastIndex;
sentPacketsPerSecond[currentSecondIndex] = 1; // 重置计数为 1
metrics.lastIndex = currentSecondIndex;
metrics.lastTimestamp = currentTimestamp;
LOGD("Sent data, resetting s7sentPacketsPerSecond[%d]", currentSecondIndex);
}
}
}

updateMetrics(env, obj, totalSize, 0, roundTripDelay);

return JNI_TRUE;
}


extern "C" JNIEXPORT jboolean JNICALL
Java_com_dotouch_industrial_s7_S7Client_testReadData(JNIEnv *env, jobject obj, jint dbNumber,
jint byteOffset, jint bitOffset) {
// 检查bitOffset是否在有效范围内(0到7)
if (bitOffset < 0 || bitOffset > 7) {
LOGE("Invalid bitOffset: %d", bitOffset);
return JNI_FALSE; // 返回错误
}

uint8_t buffer[1]; // 用于存储读取的一个字节的数据

// 调用Client的DBRead函数,读取一个字节的数据
int result = Client->DBRead(dbNumber, byteOffset, 1, buffer);

// 检查读取结果
if (result != 0) {
LOGE("Error read data: %s", CliErrorText(result).c_str());
return JNI_FALSE; // 如果读取失败,返回false
}

// 提取特定位上的值
uint8_t bitValue = (buffer[0] >> bitOffset) & 0x01;
LOGD("Read bit value: %d", bitValue);

// 根据位的值返回 JNI_TRUE 或 JNI_FALSE
return bitValue == 1 ? JNI_TRUE : JNI_FALSE;
}


extern "C" JNIEXPORT jint JNICALL
Java_com_dotouch_industrial_s7_S7Client_testWriteData(JNIEnv *env, jobject obj, jint dbNumber,
jint byteOffset, jint bitOffset,
jboolean value) {
// 检查bitOffset是否在有效范围内(0到7)
if (bitOffset < 0 || bitOffset > 7) {
LOGE("Invalid bitOffset: %d", bitOffset);
return -1; // 返回错误码,表示bitOffset无效
}

uint8_t buffer[1]; // 用于读取现有的字节数据

// 调用Client的DBRead函数,读取一个字节的数据
int result = Client->DBRead(dbNumber, byteOffset, 1, buffer);

// 检查读取结果
if (result != 0) {
LOGE("Error reading byte before write: %s", CliErrorText(result).c_str());
return result; // 如果读取失败,返回错误码
}

// 根据value值修改buffer中对应位的数据
if (value == JNI_TRUE) {
buffer[0] |= (1 << bitOffset); // 将对应位设置为1
} else {
buffer[0] &= ~(1 << bitOffset); // 将对应位设置为0
}

// 调用Client的DBWrite函数,写入修改后的字节数据
result = Client->DBWrite(dbNumber, byteOffset, 1, buffer);

// 检查写入结果
if (result != 0) {
LOGE("Error writing data: %s", CliErrorText(result).c_str());
return result; // 写入失败,返回错误码
}

LOGD("Write data success: byte value = %d", buffer[0]); // 打印写入后的字节值
return 0; // 返回0表示写入成功
}



extern "C" JNIEXPORT jboolean JNICALL
Java_com_dotouch_industrial_s7_S7Client_disconnectFromServer(
JNIEnv *env,
jobject /* this */) {
if (Client != nullptr) {
int result = Client->Disconnect();
delete Client;
Client = nullptr;

if (result == 0) {
// 断开连接成功
LOGD("Successfully disconnected from PLC.");
return JNI_TRUE;
} else {
// 断开连接失败
LOGE("Failed to disconnect from PLC. Error code: %d", result);
return JNI_FALSE;
}
} else {
// 客户端不存在,视为已断开连接
LOGD("No active connection to disconnect.");
return JNI_TRUE;
}
}


#include <jni.h>
#include "snap7/examples/cpp/snap7.h"
#include <cstdio>
#include <android/log.h>
#include <map>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <sys/time.h>
#include <deque>
#include <chrono>
#include <algorithm>
#include <utility>
#include <random>
#include <thread>
#include <iostream>
#include <queue>

// 定义日志标签
#define LOG_TAG "s7-server"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

static S7Object server = 0U;

struct DataBlockInfo {
int areaCode;
word dbNumber;
word startAddress;
word length;
void *dataBuffer;
};

static std::queue<long long> requestTimestamps;
static std::mutex requestTimestampsMutex;

// 使用 std::mutex 保护共享资源
std::mutex dataBlockMutex;

// 使用 std::unordered_map 存储已注册的数据块
std::unordered_map<int, DataBlockInfo> registeredDataBlocks;

// 使用原子变量保证线程安全
std::atomic<int> nextDataBlockId{0};


// 获取当前时间戳的函数 (单位: 毫秒)
long long getCurrentTimestamp() {
struct timeval tv{};
gettimeofday(&tv, nullptr);
return (long long) tv.tv_sec * 1000 + tv.tv_usec / 1000;
}


void printReceivedData(uint8_t *data, int size) {
LOGD("Received data (Size: %d):", size);
for (int i = 0; i < size; ++i) {
LOGD(" Byte %d: 0x%02x", i, data[i]);
}

// 根据数据类型解析并打印数据 (你需要根据实际情况修改这部分代码)
// 例如,如果客户端写入的是一个 INT 类型的数据:
if (size >= 2) {
int intValue = (data[1] << 8) | data[0]; // 假设是大端字节序
LOGD(" INT Value: %d", intValue);
}
if (size >= 4) {
float floatValue;
memcpy(&floatValue, data, sizeof(float)); // 假设是大端字节序
LOGD(" REAL Value: %f", floatValue);
}
}

void printByteArray(void* data, int size, int wordLen) {
if (wordLen == S7WLBit) { // BOOL 类型
byte* bytes = (byte*)data;
for (int i = 0; i < size; i++) {
for (int j = 0; j < 8; j++) {
LOGD("Bit %d.%d: %d", i, j, (bytes[i] >> j) & 1);
}
}
} else if (wordLen == S7WLByte) { // BYTE 类型
byte* bytes = (byte*)data;
for (int i = 0; i < size; i++) {
LOGD("Byte %d: %d", i, bytes[i]);
}
} else if (wordLen == S7WLWord) { // WORD 类型
short* shorts = (short*)data;
for (int i = 0; i < size / 2; i++) {
LOGD("Word %d: %d", i, shorts[i]);
}
} else if (wordLen == S7WLDWord) { // DWORD 类型
int* ints = (int*)data;
for (int i = 0; i < size / 4; i++) {
LOGD("DWord %d: %d", i, ints[i]);
}
} else if (wordLen == S7WLReal) { // REAL 类型
float* floats = (float*)data;
for (int i = 0; i < size / 4; i++) {
LOGD("Real %d: %f", i, floats[i]);
}
} else if (wordLen == S7WLCounter) {
// 处理 S7WLCounter 类型的代码 (如果需要)
} else if (wordLen == S7WLTimer) {
// 处理 S7WLTimer 类型的代码 (如果需要)
} else { // 其他类型,检查是否是以空字符结尾的字符串
byte* bytes = (byte*)data;
bool isString = false;
for (int i = 0; i < size; i++) {
if (bytes[i] == '\0') {
isString = true;
break;
}
}

if (isString) { // 是字符串
char* str = (char*)data;
LOGD("String: %s", str);
} else { // 不是字符串,以十六进制打印
for (int i = 0; i < size; i++) {
LOGD("Byte %d: 0x%02X", i, bytes[i]);
}
}
}
}

// 用于存储指标数据的结构体
struct Metrics {
int receivedCommands; // 接收到的命令数量
int receivedPackets; // 接收到的数据包数量
int receivedBytes;
double receivedRate;
int lastIndex; // 上一次接收字节的时间戳
long long lastReceivedBytesTimestamp; // 上一次接收字节的时间戳
long long lastReceivedBytes; // 上一次接收的字节数
};

// 全局变量,用于存储最新的指标数据
Metrics metrics = {0, 0, 0, 0, 0, 0, 0};

// 存储每秒发送的数据包个数
std::vector<int> sentPacketsPerSecond(300, 0);
std::mutex sentPacketsPerSecondMutex;

// 存储每秒接收的数据包个数
std::vector<int> receivedPacketsPerSecond(300, 0);
std::mutex receivedPacketsPerSecondMutex;

// 存储最近一段时间内接收到的字节数和对应的时间戳
std::deque<std::pair<long long, int>> receivedBytesWindow;
std::mutex receivedBytesWindowMutex;

// 获取当前秒数在列表中的索引
int getCurrentSecondIndex() {
long long currentSecond = getCurrentTimestamp() / 1000;
return currentSecond % 300;
}

// 将时间戳转换为中国标准时间字符串
std::string convertTimestampToCST(long long timestamp) {
// 将时间戳转换为 time_t
auto time = static_cast<time_t>(timestamp / 1000); // 毫秒转换为秒

// 使用 gmtime 将 time_t 转换为 tm 结构体 (UTC 时间)
tm *timeinfo = gmtime(&time);

// 使用 mktime 将 tm 结构体转换为 time_t (仍然是 UTC 时间)
time_t utcTime = mktime(timeinfo);

// 使用 localtime 将 time_t 转换为 tm 结构体 (本地时间,考虑了时区)
tm *localTimeinfo = localtime(&utcTime);

// 使用 strftime 将 tm 结构体格式化为字符串
char buffer[80];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localTimeinfo);

return {buffer};
}

// 获取网络时间戳 (JNI 调用 Java)
jlong getNetworkTimestampFromJava(JNIEnv *env) {
// 获取 Utils 类
jclass utilsClass = env->FindClass("com/dotouch/industrial/utils/Utils");
if (utilsClass == nullptr) {
LOGE("Error: Could not find Utils class.");
return -1;
}

// 获取 Utils 类的构造函数
jmethodID constructor = env->GetMethodID(utilsClass, "<init>", "()V");
if (constructor == nullptr) {
LOGE("Error: Could not find Utils constructor.");
return -1;
}

// 创建 Utils 类的实例
jobject utilsObject = env->NewObject(utilsClass, constructor);
if (utilsObject == nullptr) {
LOGE("Error: Could not create Utils object.");
return -1;
}

// 获取 getNetworkTimestamp 方法
jmethodID getNetworkTimestampMethod = env->GetMethodID(utilsClass, "getNetworkTimestamp",
"()Ljava/lang/Long;");
if (getNetworkTimestampMethod == nullptr) {
LOGE("Error: Could not find getNetworkTimestamp method.");
return -1;
}

// 调用 getNetworkTimestamp 方法
jobject networkTimestampObject = env->CallObjectMethod(utilsObject, getNetworkTimestampMethod);

// 检查返回值是否为空
if (networkTimestampObject == nullptr) {
LOGE("Error: getNetworkTimestamp returned null.");
return -1;
}

// 获取 Long 对象的值
jmethodID longValueMethod = env->GetMethodID(env->FindClass("java/lang/Long"), "longValue",
"()J");
if (longValueMethod == nullptr) {
LOGE("Error: Could not find longValue method.");
return -1;
}
jlong networkTimestamp = env->CallLongMethod(networkTimestampObject, longValueMethod);

return networkTimestamp;
}

JavaVM *vm = nullptr;
pthread_key_t g_env_key;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
::vm = vm;
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}

// 初始化 TLS 键
if (pthread_key_create(&g_env_key, nullptr) != 0) {
LOGE("Error creating TLS key");
return -1; // 或抛出异常
}

return JNI_VERSION_1_6;
}

jlong getCurrentTimestampInternal(JNIEnv *env) {
jclass systemClass = env->FindClass("java/lang/System");
jmethodID currentTimeMillisMethod = env->GetStaticMethodID(systemClass, "currentTimeMillis",
"()J");
return env->CallStaticLongMethod(systemClass, currentTimeMillisMethod);
}

// 服务器事件回调函数
void S7API ServerEventCallback(void *usrPtr, PSrvEvent PEvent, int Size) {
LOGD("Event Code: 0x%08X", PEvent->EvtCode);
switch (PEvent->EvtCode) {
case evcClientAdded:
LOGD("Client connected!");
break;
case evcClientDisconnected:
LOGD("Client disconnected!");
break;
}
}


int S7API RWAreaCallback(void *usrPtr, int Sender, int Operation, PS7Tag PTag, void *pUsrData) {
std::lock_guard<std::mutex> lock1(dataBlockMutex); // 假设你使用了 dataBlockMutex 来保护数据访问
// 更新与数据块注册无关的指标数据
metrics.receivedCommands++;
metrics.receivedPackets++;
metrics.receivedBytes += PTag->Size;

// 计算接收速率
long long currentTimestamp = getCurrentTimestamp();
if (metrics.lastReceivedBytesTimestamp > 0) {
long long timeDiff = currentTimestamp - metrics.lastReceivedBytesTimestamp; // 时间差,单位毫秒
long long bytesDiff = metrics.receivedBytes - metrics.lastReceivedBytes; // 字节差
if (timeDiff > 0) {
metrics.receivedRate = (double)bytesDiff / ((double)timeDiff / 1000.0); // bytes/s
} else {
metrics.receivedRate = 0; // 或其他默认值
}
}

// 更新 receivedPacketsPerSecond
{
std::lock_guard<std::mutex> lock2(receivedPacketsPerSecondMutex);
LOGD("Received data, updating lastReceivedBytesTimestamp %lld", metrics.lastReceivedBytesTimestamp);
if (metrics.lastReceivedBytesTimestamp == 0) {
// 第一次接收数据,重置计数为 1,currentSecondIndex 为 0
receivedPacketsPerSecond[0] = 1;
metrics.lastReceivedBytesTimestamp = currentTimestamp;
LOGD("Received first data, resetting receivedPacketsPerSecond");
} else {
long long elapsedTimeSinceLastData = currentTimestamp - metrics.lastReceivedBytesTimestamp;
LOGD("elapsedTimeSinceLastData: %lld ms", elapsedTimeSinceLastData);
if (elapsedTimeSinceLastData < 1000) {
// 仍在同一秒内,使用 lastReceivedBytesTimestamp 计算索引
receivedPacketsPerSecond[metrics.lastIndex]++;
LOGD("Received data, updating receivedPacketsPerSecond[%d]", metrics.lastIndex);
} else {
// 经过了至少 1 秒,使用 elapsedTimeSinceLastData 计算索引
int currentSecondIndex = (elapsedTimeSinceLastData / 1000) % 300 + metrics.lastIndex;
receivedPacketsPerSecond[currentSecondIndex] = 1; // 重置计数为 1
metrics.lastIndex = currentSecondIndex;
metrics.lastReceivedBytesTimestamp = currentTimestamp;
LOGD("Received data, resetting receivedPacketsPerSecond[%d]", currentSecondIndex);
}
}
}

metrics.lastReceivedBytes = metrics.receivedBytes;


// 将时间戳添加到队列
{
std::lock_guard<std::mutex> lock3(requestTimestampsMutex);
requestTimestamps.push(currentTimestamp);
}
LOGI("Receive Timestamp: %lld", currentTimestamp);

if (Operation == OperationRead) { // 读取操作
// 处理读取的数据 (例如,将数据从服务器端复制到 PTag->Data)
if (registeredDataBlocks.count(PTag->DBNumber)) {
DataBlockInfo& dbInfo = registeredDataBlocks[PTag->DBNumber];

// 检查读取范围是否越界 (使用原始 PTag->Start 和 PTag->Size)
if (PTag->Start + PTag->Size <= dbInfo.length) {

// 将 dbInfo.dataBuffer 转换成 byte* 指针
memcpy(pUsrData, (byte*)dbInfo.dataBuffer + PTag->Start, PTag->Size);
} else {
LOGE("Read area out of range: DB[%d], Start[%d], Size[%d]", PTag->DBNumber, PTag->Start, PTag->Size);
return evrErrOutOfRange; // 使用已有的常量
}
} else {
LOGE("DB not found: %d", PTag->DBNumber);
return evrErrAreaNotFound; // 使用已有的常量
}
} else if (Operation == OperationWrite) { // 写入操作
// 处理写入的数据 (例如,将 PTag->Data 中的数据复制到服务器端)
if (registeredDataBlocks.count(PTag->DBNumber)) {
DataBlockInfo& dbInfo = registeredDataBlocks[PTag->DBNumber];

// if (enableTimestamp) { // 处理时间戳
// // 1. 检查数据大小是否足够包含时间戳
// if (PTag->Size < sizeof(long long)) {
// LOGE("Data size too small for timestamp");
// return -1;
// }
//
// // 2. 获取客户端发送的时间戳
// long long recvTimestamp = *(long long*)pUsrData;
//
// LOGD("Received timestamp: %lld", recvTimestamp);
//
// // 3. 跳过时间戳,指向实际数据
// pUsrData = (byte*)pUsrData + sizeof(long long);
// PTag->Size -= sizeof(long long);
//
// // 4. 计算单向时延
// long long currentTime = getCurrentTimestamp();
// LOGD("Current timestamp: %lld", currentTime);
// double oneWayDelay = currentTime - recvTimestamp;
//
// // 计算接收速率,避免除以 0 的错误
// if (oneWayDelay > 0) {
// metrics.receiveTotalDelay += oneWayDelay;
// metrics.receiveMaxDelay = std::max(metrics.receiveMaxDelay, oneWayDelay);
// metrics.receiveMinDelay = (metrics.receiveCount == 1) ? oneWayDelay : std::min(metrics.receiveMinDelay, oneWayDelay);
//
// LOGD("Round Trip Delay: %f ms", oneWayDelay);
// metrics.receivedRate = (double)PTag->Size / ((double)oneWayDelay / 1000.0); // bytes/s
// } else {
// LOGE("Invalid timestamp received oneWayDelay: %f ms", oneWayDelay);
// metrics.receivedRate = PTag->Size; // 或其他默认值
// }
// }

// 检查写入范围是否越界 (使用更新后的 PTag->Size)
if (PTag->Start + PTag->Size <= dbInfo.length) {
// 将 dbInfo.dataBuffer 转换成 byte* 指针
memcpy((byte*)dbInfo.dataBuffer + PTag->Start, pUsrData, PTag->Size);

// 打印写入的值
LOGD("Write data (DB: %d, Start: %d, Size: %d):", PTag->DBNumber, PTag->Start, PTag->Size);
printByteArray(pUsrData, PTag->Size, PTag->WordLen);

} else {
LOGE("Write area out of range: DB[%d], Start[%d], Size[%d]", PTag->DBNumber, PTag->Start, PTag->Size);
return evrErrOutOfRange; // 使用已有的常量
}
} else {
LOGE("DB not found: %d", PTag->DBNumber);
return evrErrAreaNotFound; // 使用已有的常量
}
}

return 0; // 其他操作,返回 0 表示操作成功
}


extern "C" JNIEXPORT jboolean JNICALL
Java_com_dotouch_industrial_s7_S7Server_startServer(
JNIEnv *env,
jobject thiz,
jint port,
jint rack,
jint slot) {

auto *Server = new TS7Server();

Server->SetEventsMask(0x0FFFF);

int Result = Server->SetParam(p_u16_LocalPort, &port);
if (Result != 0) {
LOGE("Failed to set server port: Error code %d", Result);
delete Server;
return JNI_FALSE;
}
LOGD("Server port set to %d", port);

Result = Server->Start();

if (Result == 0) {
server = reinterpret_cast<S7Object>(Server);

auto rackByte = static_cast<uint8_t>(rack);
auto slotByte = static_cast<uint8_t>(slot);
int success;

success = Server->RegisterArea(srvAreaPE, 0, &rackByte, sizeof(uint8_t));
if (success != 0) {
LOGE("Failed to register PE area for rack: Error code %d", Result);
delete Server;
return JNI_FALSE;
} else {
LOGD("Successfully registered PE area for rack: %d", rack);
}

success = Server->RegisterArea(srvAreaPA, 0, &slotByte, sizeof(uint8_t));
if (success != 0) {
LOGE("Failed to register PA area for slot: Error code %d", Result);
delete Server;
return JNI_FALSE;
} else {
LOGD("Successfully registered PA area for slot: %d", slot);
}

// // 注册 DB10
// int DBNumber = 1; // DB10 的编号
// int startAddress = 0; // 起始地址
// int byteLength = 1024; // 假设 DB10 的大小为 1024 字节,请根据实际情况修改
//
// DataBlockInfo dataBlockInfo{};
// dataBlockInfo.areaCode = srvAreaDB;
// dataBlockInfo.dbNumber = DBNumber;
// dataBlockInfo.startAddress = startAddress;
//// dataBlockInfo.length = byteLength + sizeof(long long); // 添加额外的空间用于存储时间戳
// dataBlockInfo.length = byteLength; // 添加额外的空间用于存储时间戳
//
// // 分配内存用于数据缓冲区
// dataBlockInfo.dataBuffer = calloc(dataBlockInfo.length, 1);
// if (dataBlockInfo.dataBuffer == nullptr) {
// LOGE("Failed to register DB10: Memory allocation failed.");
// delete Server;
// return JNI_FALSE;
// }
//
// // 使用 SNAP7 服务器注册数据块
// success = Server->RegisterArea(dataBlockInfo.areaCode,
// dataBlockInfo.dbNumber,
// dataBlockInfo.dataBuffer,
// dataBlockInfo.length);
//
// if (success == 0) {
// // 在映射中注册数据块,使用 dbNumber 作为键值
// registeredDataBlocks[DBNumber] = dataBlockInfo; // 假设 registeredDataBlocks 是一个全局的 std::map<int, DataBlockInfo>
//
// LOGD("Successfully registered DB10 (Area: %d, DB: %d, Start: %d, Length: %d)",
// dataBlockInfo.areaCode, dataBlockInfo.dbNumber, dataBlockInfo.startAddress,
// dataBlockInfo.length);
// } else {
// // 释放内存
// free(dataBlockInfo.dataBuffer);
//
// // 使用 Cli_ErrorText 获取更详细的错误信息
// char errorText[1024];
// Cli_ErrorText(success, errorText, sizeof(errorText));
//
// LOGE("Failed to register DB10: Srv_RegisterArea returned error code %d (%s).", success,
// errorText);
// delete Server;
// return JNI_FALSE;
// }

// 设置读写区域回调函数
Server->SetRWAreaCallback(RWAreaCallback, nullptr);

// 设置服务器事件回调函数
Server->SetEventsCallback(ServerEventCallback, nullptr);

Server->SetCpuStatus(S7CpuStatusRun);
LOGD("S7 server started successfully on port %d (Rack: %d, Slot: %d)", port, rack, slot);
return JNI_TRUE;
} else {
LOGE("Failed to start S7 server: Error code %d", Result);
delete Server;
return JNI_FALSE;
}
}

void printDataBlockInfo(const DataBlockInfo &dataBlockInfo) {
LOGD("Data Block Information:");
LOGD(" Area Code: %d", dataBlockInfo.areaCode);
LOGD(" DB Number: %d", dataBlockInfo.dbNumber);
LOGD(" Start Address: %d", dataBlockInfo.startAddress);
LOGD(" Length: %d", dataBlockInfo.length);
LOGD(" Data Buffer Address: %p", dataBlockInfo.dataBuffer);
// 以下代码需要根据实际数据类型进行调整
if (dataBlockInfo.areaCode == srvAreaDB) {
// 打印 DB 数据
LOGD(" Data (Hex):");
for (int i = 0; i < dataBlockInfo.length; i++) {
LOGD(" %02X", ((uint8_t *) dataBlockInfo.dataBuffer)[i]);
}
}
}

extern "C" JNIEXPORT void JNICALL
Java_com_dotouch_industrial_s7_S7Server_stopServer(JNIEnv *env, jobject obj) {
if (server != 0U) {
auto *Server = reinterpret_cast<TS7Server *>(server);

Server->SetEventsCallback(nullptr, nullptr);
Server->SetRWAreaCallback(nullptr, nullptr);

// 先停止服务器
Server->Stop();

// 清理已注册的数据块
{
std::lock_guard<std::mutex> lock(dataBlockMutex);
for (auto &pair : registeredDataBlocks) {
if (pair.second.dataBuffer != nullptr) {
free(pair.second.dataBuffer);
}
}
registeredDataBlocks.clear();
}

// 删除服务器对象
delete Server;
server = 0U;

LOGD("S7 server stopped successfully.");
} else {
LOGW("S7 server is not running.");
}
}


extern "C" JNIEXPORT jboolean JNICALL
Java_com_dotouch_industrial_s7_S7Server_registerDataBlock(JNIEnv *env, jobject thiz,
jint dbNumber, jint startAddress,
jint byteLength) {
LOGD("Server pointer in registerDataBlock: %u", server);
if (server == 0U) {
LOGE("Failed to register data block: Server is not running.");
return JNI_FALSE;
}

DataBlockInfo dataBlockInfo{};
dataBlockInfo.areaCode = srvAreaDB;
dataBlockInfo.dbNumber = dbNumber;
dataBlockInfo.startAddress = startAddress;
dataBlockInfo.length = byteLength;

// 分配内存用于数据缓冲区
dataBlockInfo.dataBuffer = calloc(dataBlockInfo.length, 1);
if (dataBlockInfo.dataBuffer == nullptr) {
LOGE("Failed to register data block: Memory allocation failed.");
return JNI_FALSE;
}

// 打印所有参数值
LOGD("Srv_RegisterArea parameters:");
LOGD(" Server handle: %u", server);
LOGD(" Area Code: %d", dataBlockInfo.areaCode);
LOGD(" DB Number: %d", dataBlockInfo.dbNumber);
LOGD(" Start Address: %d", dataBlockInfo.startAddress);
LOGD(" Length: %d", dataBlockInfo.length);
LOGD(" Data Buffer Address: %p", dataBlockInfo.dataBuffer);

// 使用 SNAP7 服务器注册数据块
auto *Server = reinterpret_cast<TS7Server *>(server);
int result = Server->RegisterArea(dataBlockInfo.areaCode,
dataBlockInfo.dbNumber,
dataBlockInfo.dataBuffer,
dataBlockInfo.length);

if (result == 0) {
// 在映射中注册数据块, 使用 dbNumber 作为键值
registeredDataBlocks[dbNumber] = dataBlockInfo;

LOGD("Data block registered successfully (Area: %d, DB: %d, Start: %d, Length: %d)",
dataBlockInfo.areaCode, dataBlockInfo.dbNumber, dataBlockInfo.startAddress,
dataBlockInfo.length);
return JNI_TRUE;
} else {
// 释放内存
free(dataBlockInfo.dataBuffer);

// 使用 Cli_ErrorText 获取更详细的错误信息
char errorText[1024];
Cli_ErrorText(result, errorText, sizeof(errorText));

LOGE("Failed to register data block: Srv_RegisterArea returned error code %d (%s).", result,
errorText);
return JNI_FALSE;
}
}


extern "C" JNIEXPORT jboolean JNICALL
Java_com_dotouch_industrial_s7_S7Server_unregisterDataBlock(JNIEnv *env, jobject thiz,
jint blockNumber) {
std::lock_guard<std::mutex> lock(dataBlockMutex); // 使用互斥锁保护 registeredDataBlocks

// 查找要反注册的数据块
for (auto it = registeredDataBlocks.begin(); it != registeredDataBlocks.end(); ++it) {
if (it->second.dbNumber == blockNumber) {
// 找到匹配的数据块

DataBlockInfo &dataBlockInfo = it->second;

// 打印所有参数值
LOGD("Srv_UnregisterArea parameters:");
LOGD(" Server handle: %u", server);
LOGD(" Area Code: %d", dataBlockInfo.areaCode);
LOGD(" DB Number: %d", dataBlockInfo.dbNumber);

// 使用 SNAP7 服务器反注册数据块
auto *Server = reinterpret_cast<TS7Server *>(server);
int result = Server->UnregisterArea(dataBlockInfo.areaCode, dataBlockInfo.dbNumber);

if (result == 0) {
// 反注册成功
// 释放内存
free(dataBlockInfo.dataBuffer);

// 从 registeredDataBlocks 中移除数据块
registeredDataBlocks.erase(it);

LOGD("Data block unregistered successfully (DB Number: %d)", blockNumber);
return JNI_TRUE;
} else {
// 反注册失败
// 使用 Cli_ErrorText 获取更详细的错误信息
char errorText[1024];
Cli_ErrorText(result, errorText, sizeof(errorText));

LOGE("Failed to unregister data block: Srv_UnregisterArea returned error code %d (%s).",
result, errorText);
return JNI_FALSE;
}
}
}

// 没有找到匹配的数据块
LOGW("Data block not found for unregistration (DB Number: %d)", blockNumber);
return JNI_FALSE;
}


// 获取最新的指标数据
extern "C"
JNIEXPORT void JNICALL
Java_com_dotouch_industrial_s7_S7Server_getLatestMetrics(JNIEnv *env, jobject thiz, jobject metricsObject,
jobject clientTimes) {
// 获取必要的 Java 类和方法 ID
jclass metricsClass = env->FindClass("com/dotouch/industrial/bean/CommunicationMetrics");
if (metricsClass == nullptr) {
LOGE("Failed to find class: com/dotouch/industrial/bean/CommunicationMetrics");
return;
}
jfieldID receivedCommandsField = env->GetFieldID(metricsClass, "receivedCommands",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jfieldID receivedPacketsField = env->GetFieldID(metricsClass, "receivedPackets",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jfieldID receivedBytesField = env->GetFieldID(metricsClass, "receivedBytes",
"Ljava/util/concurrent/atomic/AtomicLong;");
jfieldID receivedRateField = env->GetFieldID(metricsClass, "receivedRate",
"Lcom/dotouch/industrial/bean/AtomicDouble;");
jfieldID receivedRateHistoryField = env->GetFieldID(metricsClass, "receivedRateHistory",
"Ljava/util/List;");

jclass atomicIntegerClass = env->FindClass("java/util/concurrent/atomic/AtomicInteger");
jmethodID setIntMethodId = env->GetMethodID(atomicIntegerClass, "set", "(I)V");
// 获取 AtomicInteger 构造函数 ID
jmethodID atomicIntegerConstructorId = env->GetMethodID(atomicIntegerClass, "<init>", "(I)V");
jclass atomicLongClass = env->FindClass("java/util/concurrent/atomic/AtomicLong");
jmethodID setLongMethodId = env->GetMethodID(atomicLongClass, "set", "(J)V");
jclass atomicDoubleClass = env->FindClass("com/dotouch/industrial/bean/AtomicDouble");
jmethodID setDoubleMethodId = env->GetMethodID(atomicDoubleClass, "set", "(D)V");
jmethodID getDoubleMethodId = env->GetMethodID(atomicDoubleClass, "get", "()D");

jclass listClass = env->FindClass("java/util/List");
jmethodID addMethodId = env->GetMethodID(listClass, "add", "(Ljava/lang/Object;)Z");
jmethodID sizeMethodId = env->GetMethodID(listClass, "size", "()I");
jmethodID getMethodId = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
jmethodID clearMethodId = env->GetMethodID(listClass, "clear", "()V");

jclass concurrentHashMapClass = env->FindClass("java/util/concurrent/ConcurrentHashMap");
jmethodID putMethodId = env->GetMethodID(concurrentHashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID integerConstructorId = env->GetMethodID(integerClass, "<init>", "(I)V");
jclass pairClass = env->FindClass("kotlin/Pair");
jmethodID pairConstructorId = env->GetMethodID(pairClass, "<init>",
"(Ljava/lang/Object;Ljava/lang/Object;)V");

if (receivedCommandsField == nullptr || receivedPacketsField == nullptr ||
receivedBytesField == nullptr || receivedRateField == nullptr ||
receivedRateHistoryField == nullptr || setIntMethodId == nullptr ||
setLongMethodId == nullptr ||
setDoubleMethodId == nullptr || getDoubleMethodId == nullptr ||
addMethodId == nullptr || putMethodId == nullptr ||
integerConstructorId == nullptr || pairConstructorId == nullptr ||
atomicIntegerConstructorId == nullptr) { // 检查 AtomicInteger 构造函数 ID
LOGE("Failed to get field or method ID from CommunicationMetrics");
return;
}

// --- 获取 S7Metrics 数据 ---
int receivedCommands = metrics.receivedCommands;
int receivedPackets = metrics.receivedPackets;
long long receivedBytes = metrics.receivedBytes;
double receivedRate = metrics.receivedRate;

jint listSize = 0;
if (clientTimes != nullptr) {
listSize = env->CallIntMethod(clientTimes, sizeMethodId);

// 打印 clientTimes 的值
LOGD("clientTimes values:");
for (jint i = 0; i < listSize; i++) {
jobject longObject = env->CallObjectMethod(clientTimes, getMethodId, i);
jlong clientTime = env->CallLongMethod(longObject, env->GetMethodID(env->FindClass("java/lang/Long"), "longValue", "()J"));
LOGD(" [%d] %lld", i, clientTime);
env->DeleteLocalRef(longObject);
}
} else {
LOGD("clientTimes is null, skipping delay calculation.");
}


// 打印 requestTimestamps 的值
LOGD("requestTimestamps values:");
std::vector<long long> requestTimestampsVector;
{
std::lock_guard<std::mutex> lock(requestTimestampsMutex);
std::queue<long long> tempQueue = requestTimestamps; // 复制队列以避免死锁
while (!tempQueue.empty()) {
requestTimestampsVector.push_back(tempQueue.front());
tempQueue.pop();
}
}
for (size_t i = 0; i < requestTimestampsVector.size(); i++) {
LOGD(" [%zu] %lld", i, requestTimestampsVector[i]);
}

// 从后向前遍历并计算单向时延
int requestTimestampsSize = requestTimestampsVector.size();

if (listSize > 0){
for (int i = requestTimestampsSize - 1; i >= 0; i--) {
long long receiveTimestamp = requestTimestampsVector[i];

// 获取对应的 clientTime
int clientTimeIndex = listSize - (requestTimestampsSize - i);
if (clientTimeIndex < 0) {
LOGE("Not enough elements in clientTimes!");
break;
}

jobject longObject = env->CallObjectMethod(clientTimes, getMethodId, clientTimeIndex);
jlong clientTime = env->CallLongMethod(longObject, env->GetMethodID(env->FindClass("java/lang/Long"), "longValue", "()J"));

// 计算差值并打印
long long diff = receiveTimestamp - clientTime;
LOGD("clientTime[%d]: %lld, receiveTimestamp[%d]: %lld, diff: %lld", clientTimeIndex, clientTime, i, receiveTimestamp, diff);


env->DeleteLocalRef(longObject); // 释放局部引用
}
}

// 清空 requestTimestamps 队列
{
std::lock_guard<std::mutex> lock(requestTimestampsMutex);
std::queue<long long> empty;
std::swap(requestTimestamps, empty);
}

LOGD("receivedCommands: %d", receivedCommands);
LOGD("receivedPackets: %d", receivedPackets);
LOGD("receivedBytes: %lld", receivedBytes);
LOGD("receivedRate: %.2f", receivedRate);


// receivedCommands
jobject receivedCommandsObject = env->GetObjectField(metricsObject, receivedCommandsField);
env->CallVoidMethod(receivedCommandsObject, setIntMethodId, receivedCommands);

// receivedPackets
jobject receivedPacketsObject = env->GetObjectField(metricsObject, receivedPacketsField);
env->CallVoidMethod(receivedPacketsObject, setIntMethodId, receivedPackets);

// receivedBytes
jobject receivedBytesObject = env->GetObjectField(metricsObject, receivedBytesField);
env->CallVoidMethod(receivedBytesObject, setLongMethodId, receivedBytes);

// receivedRate
jobject receivedRateObject = env->GetObjectField(metricsObject, receivedRateField);
env->CallVoidMethod(receivedRateObject, setDoubleMethodId, receivedRate);


// --- 接收速率历史数据 ---
jobject receivedRateHistoryObject = env->GetObjectField(metricsObject,
receivedRateHistoryField);

env->CallVoidMethod(receivedRateHistoryObject, clearMethodId);

// 锁定 receivedPacketsPerSecondMutex
std::lock_guard<std::mutex> lock(receivedPacketsPerSecondMutex);
for (int i = 0; i < receivedPacketsPerSecond.size(); ++i) {
LOGD("receivedPacketsPerSecond[%d]: %d", i, receivedPacketsPerSecond[i]);
int packetsCount = receivedPacketsPerSecond[i];

// 创建 Integer 对象
jobject integerObject = env->NewObject(integerClass, integerConstructorId, packetsCount);

// 将 Integer 对象添加到 List
env->CallBooleanMethod(receivedRateHistoryObject, addMethodId, integerObject);

// 删除局部引用,防止内存泄漏
env->DeleteLocalRef(integerObject);
}

LOGD("receivedPacketsPerSecond数组: %zu", receivedPacketsPerSecond.size());

}


class DataDisplayFragment : DialogFragment() {


companion object {
fun newInstance(
protocol: ProtocolType,
isClient: Boolean,
metrics: CommunicationMetrics
): DataDisplayFragment {
val fragment = DataDisplayFragment()
val args = Bundle()
args.putSerializable("protocol", protocol)
args.putBoolean("isClient", isClient)
args.putSerializable("metrics", metrics)
fragment.arguments = args
return fragment
}
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireActivity())
val inflater = requireActivity().layoutInflater
val view = inflater.inflate(R.layout.fragment_data_display, null)

// 获取传递的参数
val protocol = arguments?.getSerializable("protocol") as ProtocolType?
val isClient = arguments?.getBoolean("isClient") != false
val metrics = arguments?.getSerializable("metrics") as CommunicationMetrics?

// 获取指标数据并展示
if (metrics != null) {
updateMetricsView(metrics, view)
configureChart(metrics, view, requireContext())

val recyclerView = view.findViewById<RecyclerView>(R.id.rv_test_items)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = TestItemAdapter(metrics.testItemResults)
} else {
Toast.makeText(
context,
"No metrics found for this protocol and client state.",
Toast.LENGTH_SHORT
).show()
}

// 设置弹窗视图
builder.setView(view)

// 创建对话框
val dialog = builder.create()

// 设置对话框居中显示
dialog.window?.setGravity(Gravity.CENTER)

// 添加关闭按钮
view.findViewById<Button>(R.id.btn_close).setOnClickListener {
dismiss()
}

// 添加导出按钮
view.findViewById<Button>(R.id.btn_export_report).setOnClickListener {
exportMetricsToCsv(metrics ?: CommunicationMetrics(), requireContext())
}

return dialog
}

private fun updateMetricsView(metrics: CommunicationMetrics, view: View) {
view.findViewById<TextView>(R.id.tv_sent_commands).text =
"发送命令个数:${metrics.sentCommands}"
view.findViewById<TextView>(R.id.tv_sent_packets).text = "发送包数:${metrics.sentPackets}"
view.findViewById<TextView>(R.id.tv_sent_bytes).text =
"发送字节数:${metrics.sentBytes} bytes"
view.findViewById<TextView>(R.id.tv_sent_rate).text =
"发送速率:${String.format("%.2f", metrics.sentRate.get())} B/s"

view.findViewById<TextView>(R.id.tv_received_commands).text =
"接收命令个数:${metrics.receivedCommands}"
view.findViewById<TextView>(R.id.tv_received_packets).text =
"接收包数:${metrics.receivedPackets}"
view.findViewById<TextView>(R.id.tv_received_bytes).text =
"接收字节数:${metrics.receivedBytes} bytes"
view.findViewById<TextView>(R.id.tv_received_rate).text =
"接收速率:${String.format("%.2f", metrics.receivedRate.get())} B/s"

val delayStats = metrics.delayStatistics
if (delayStats != null) {
// 单向时延
view.findViewById<TextView>(R.id.tv_send_max_delay).text =
"单向最大时延:${String.format("%.2f", delayStats["oneWayMaxDelay"])} ms"
view.findViewById<TextView>(R.id.tv_send_min_delay).text =
"单向最小时延:${String.format("%.2f", delayStats["oneWayMinDelay"])} ms"
view.findViewById<TextView>(R.id.tv_send_avg_delay).text =
"单向平均时延:${String.format("%.2f", delayStats["oneWayAvgDelay"])} ms"

// 往返时延
view.findViewById<TextView>(R.id.tv_receive_max_delay).text =
"往返最大时延:${String.format("%.2f", delayStats["roundTripMaxDelay"])} ms"
view.findViewById<TextView>(R.id.tv_receive_min_delay).text =
"往返最小时延:${String.format("%.2f", delayStats["roundTripMinDelay"])} ms"
view.findViewById<TextView>(R.id.tv_receive_avg_delay).text =
"往返平均时延:${String.format("%.2f", delayStats["roundTripAvgDelay"])} ms"
}

view.findViewById<TextView>(R.id.tv_device_registration_success_rate).text =
"设备注册成功率:${String.format("%.2f", metrics.deviceRegistrationSuccessRate.get())}%"
view.findViewById<TextView>(R.id.tv_device_registration_average_delay).text =
"设备注册平均延时:${String.format("%.2f", metrics.deviceRegistrationAverageDelay.get())} ms"
view.findViewById<TextView>(R.id.tv_control_command_success_rate).text =
"控制指令成功率:${String.format("%.2f", metrics.controlCommandSuccessRate.get())}%"
view.findViewById<TextView>(R.id.tv_control_command_retry_count).text =
"控制指令重传次数:${metrics.controlCommandRetryCount}"
}


// // 函数用于生成模拟数据
// fun generateMockData(size: Int): List<Entry> {
// val entries = mutableListOf<Entry>()
// for (i in 0 until size) {
// val xValue = i.toFloat()
// // 生成一个基于正弦波形但加入随机偏差的Y值
// val yValue = sin(xValue / 10f) + Random.nextFloat() * 100
// entries.add(Entry(xValue, yValue))
// }
// return entries
// }

// 配置图表的函数,现在接受 metrics 参数
private fun configureChart(metrics: CommunicationMetrics, view: View, context: Context) {
val lineChart = view.findViewById<LineChart>(R.id.chart)

// 获取发送速率和接收速率历史数据
val sentRateEntries = metrics.sentRateHistory.mapIndexed { index, value ->
Entry(
index.toFloat(),
value.toFloat()
)
}
val receivedRateEntries = metrics.receivedRateHistory.mapIndexed { index, value ->
Entry(
index.toFloat(),
value.toFloat()
)
}
Log.d("ChartData", "Sent Rate History: ${metrics.sentRateHistory}")
Log.d("ChartData", "Received Rate History: ${metrics.receivedRateHistory}")

// 创建数据集
val sentRateDataSet = LineDataSet(sentRateEntries, "发送速率").apply {
color = Color.BLUE
valueTextColor = Color.WHITE
valueTextSize = 10f
mode = LineDataSet.Mode.CUBIC_BEZIER
setCircleColor(Color.BLUE)
lineWidth = 2f // 增加线条宽度
enableDashedLine(10f, 5f, 0f) // 使用虚线
}
val receivedRateDataSet = LineDataSet(receivedRateEntries, "接收速率").apply {
color = Color.GREEN
valueTextColor = Color.WHITE
valueTextSize = 10f
mode = LineDataSet.Mode.CUBIC_BEZIER
setCircleColor(Color.GREEN)
}

// 将数据集添加到图表数据中
val lineData = LineData(sentRateDataSet, receivedRateDataSet)
lineChart.data = lineData

// 设置图表样式
lineChart.xAxis.apply {
valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return "${value.toInt()} s"
}
}
position = XAxis.XAxisPosition.BOTTOM
axisMinimum = 0f
axisMaximum = 300f
granularity = 1f // 每秒一个标签
setLabelCount(5, true) // 保证有足够的标签
isGranularityEnabled = true
}

lineChart.axisLeft.apply {
textColor = Color.WHITE
axisMinimum = 0f
granularity = 1f // 设置Y轴刻度间隔为1,确保显示整数
valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return value.toInt().toString()
}
}
}

lineChart.axisRight.isEnabled = false
lineChart.legend.textColor = Color.WHITE
lineChart.setBackgroundColor(ContextCompat.getColor(context, android.R.color.darker_gray))

// 启用缩放和拖动
lineChart.setTouchEnabled(true)
lineChart.isDragEnabled = true
lineChart.setScaleEnabled(true)
lineChart.setPinchZoom(false) // 禁用双指缩放
lineChart.setDrawGridBackground(false)

// 设置初始缩放比例
lineChart.zoomToCenter(1f, 1f)

// 设置最大和最小可见范围
lineChart.setVisibleXRangeMaximum(300f) // 设置最大可见范围
lineChart.setVisibleXRangeMinimum(1f) // 设置最小可见范围为1秒

// 强制刷新标签
lineChart.xAxis.setLabelCount(6, true)

lineChart.invalidate() // 刷新图表
}


private fun exportMetricsToCsv(metrics: CommunicationMetrics, context: Context) {
val metricsDir = File(context.getExternalFilesDir(null), "metrics")
if (!metricsDir.exists()) {
metricsDir.mkdirs()
}

val currentDateTime = getCurrentDateTime()
val fileName = "$currentDateTime.csv"

try {
// 在 FileOutputStream 中使用 metricsDir 和 fileName
FileOutputStream(File(metricsDir, fileName)).use { outputStream ->
// 写入 CSV 文件头
outputStream.write("发送命令个数,发送包数,发送字节数,发送速率,单向最大时延,单向最小时延,单向平均时延,接收命令个数,接收包数,接收字节数,接收速率,往返最大时延,往返最小时延,往返平均时延,设备注册成功率,设备注册平均延时,控制指令成功率,控制指令重传次数,测试项结果\n".toByteArray())

// 创建主要指标数据的字符串
val mainMetricsLine =
"${metrics.sentCommands},${metrics.sentPackets},${metrics.sentBytes},${metrics.sentRate},${metrics.delayStatistics?.get("oneWayMaxDelay")},${metrics.delayStatistics?.get("oneWayMinDelay")},${metrics.delayStatistics?.get("oneWayAvgDelay")},${metrics.receivedCommands},${metrics.receivedPackets},${metrics.receivedBytes},${metrics.receivedRate},${metrics.delayStatistics?.get("roundTripMaxDelay")},${metrics.delayStatistics?.get("roundTripMinDelay")},${metrics.delayStatistics?.get("roundTripAvgDelay")},${metrics.deviceRegistrationSuccessRate},${metrics.deviceRegistrationAverageDelay},${metrics.controlCommandSuccessRate},${metrics.controlCommandRetryCount},"

// 将测试项结果拼接成字符串,使用分号分隔每个测试项
val testItemResultsString = metrics.testItemResults.entries.joinToString(",") {
"${it.key}:成功${it.value.first}失败${it.value.second}"
}

// 将所有数据拼接成一行
val fullLine = mainMetricsLine + testItemResultsString + "\n" // 添加换行符表示一行结束

// 一次性写入所有数据
outputStream.write(fullLine.toByteArray())
}

// 使用 ACTION_SEND Intent 分享文件
val file = File(metricsDir, fileName)
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/csv"
val fileUri = FileProvider.getUriForFile(
context,
"com.dotouch.industrial.fileprovider",
file
)
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
intent.putExtra(Intent.EXTRA_SUBJECT, "通信指标数据")
intent.putExtra(Intent.EXTRA_TEXT, "请查收附件中的通信指标数据。")
startActivity(Intent.createChooser(intent, "选择邮件应用"))

Toast.makeText(context, "指标数据已保存到: ${file.absolutePath}", Toast.LENGTH_LONG)
.show()
} catch (e: Exception) {
Toast.makeText(context, "保存指标数据失败: ${e.message}", Toast.LENGTH_SHORT).show()
}
}

private fun getCurrentDateTime(): String {
val formattedDateTime = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val dateTime = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")
dateTime.format(formatter)
} else {
val dateTime = Calendar.getInstance()
val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault())
simpleDateFormat.format(dateTime.time)
}
return formattedDateTime
}
}

目前的单向时延、往返时延的计算方式不应该是通过自定义Ntp的请求来实现的,而是应该在s7的c++代码中的读写功能中进行统计,然后在DataDisplayFragment中进行展示,理解我的需求吗?如何做,给我详细的修改后的代码

विचार

整合延迟计算

正在将NTP客户端和服务器部分移除,正在计划将延迟和RTT基于S7 C++代码中的读写操作计算,并将这些指标传递给DataDisplayFragment进行展示。

记录和计算

在C++ S7Client代码的readData和writeData函数中,记录操作前后的时间戳,计算RTT和单向延迟,并通过JNI传递给Java/Kotlin进行展示。

移除NTP调用

在S7ClientFragment中移除NTP客户端调用,确保延迟计算依赖于读写操作,而不是NTP。这确实涉及了一些优化。

移除NTP依赖

注意到在S7ClientFragment中,NtpClient涉及延迟计算的重要性。下一步是调整C++ S7Client代码,通过执行时间计算RTT和单向延迟。

扩展urc调度功能

更新urc调度功能、调整时延计算,满足分别上行和下行的最大延迟需求,确保设备的实时性和稳定性。

调整和更新

将RTT和单向延迟添加到Java的CommunicationMetrics类,并通过JNI从C++代码更新这些值。移除S7ClientFragment中的NTP客户端使用。

更新C++代码

先在C++代码中定位readData和writeData的实现,再将RTT和单向延迟添加到Java的CommunicationMetrics类中,并通过JNI进行更新。

细读实现细节

正在研究readData和writeData实现,计算RTT和单向延迟,并通过JNI更新Java的CommunicationMetrics。当前的updateMetrics函数仅处理传送和接收字节及回程延迟。

锁定目标

准备修改updateMetrics和CommunicationMetrics类,添加单向延迟和RTT字段,同时更新Java/Kotlin代码以展示这些值,并去除S7ClientFragment中的NTP客户端。

调整参数和添加字段

正在修改C++代码的updateMetrics函数,加入单向延迟参数。然后,在Java的CommunicationMetrics类中添加字段用于存储单向延迟和RTT。

调整函数和字段

正在调整C++代码的updateMetrics函数,增加单向延迟参数,并在Java的CommunicationMetrics类中新增字段来存储这些值。

设置和更新

在CommunicationMetrics类中添加单向延迟和回程延迟字段,并初始化这些字段。然后,在C++代码的updateMetrics函数中设置这些字段,以便在DataDisplayFragment中展示。

调整与更新

在CommunicationMetrics类中增加单向延迟和回程延迟字段,并确保持久化以及实时更新。然后调整C++代码的updateMetrics函数,利用JNI设置这些新字段。

调整更新机制

正在修改updateMetrics函数,采用JNI设置单向延迟和回程延迟字段。确保通过JNI动态更新CommunicationMetrics类的这些字段,以保持其实时性和准确性。

确保同步和线程安全

调用updateMetrics时,添加单向延迟和回程延迟参数,处理好同步和线程安全。这就像在保证数据一致性的同时更新客户端系统。

更新表结构

正在调用updateMetrics函数,确保传递单向延迟和回程延迟参数,专门处理同步和线程安全,来保持数据的一致性。

修改更新函数

正在调整updateMetrics函数,加入单向延迟和回程延迟参数,并确保在Java的CommunicationMetrics和DataDisplayFragment中显示这些新字段。

思考,持续 25 秒

उत्तर

Understood. To shift the calculation of single-way delay and round-trip delay (RTT) from the custom NTP implementation to being based directly on the S7 read/write operations within your C++ code, you'll need to make several modifications across both your C++ and Kotlin/Java codebases. Here's a comprehensive guide to achieve this:

1. Modify the C++ JNI Code to Calculate Delays

A. Update the updateMetrics Function

Current Function Signature:

void updateMetrics(JNIEnv *env, jobject thiz, int sentBytes, int receivedBytes, long long roundTripDelay)

Modified Function Signature:

void updateMetrics(JNIEnv *env, jobject thiz, int sentBytes, int receivedBytes, long long roundTripDelay, double singleWayDelay)

Modified Function Implementation:

void updateMetrics(JNIEnv *env, jobject thiz, int sentBytes, int receivedBytes, long long roundTripDelay, double singleWayDelay) { // Existing metric updates... // Get the CommunicationMetrics object jclass s7ClientClass = env->GetObjectClass(thiz); jfieldID metricsFieldId = env->GetFieldID(s7ClientClass, "metrics", "Lcom/dotouch/industrial/bean/CommunicationMetrics;"); jobject metricsObject = env->GetObjectField(thiz, metricsFieldId); // Get the CommunicationMetrics class jclass metricsClass = env->GetObjectClass(metricsObject); // Get AtomicDouble class and its set method jclass atomicDoubleClass = env->FindClass("com/dotouch/industrial/bean/AtomicDouble"); jmethodID setDoubleMethodId = env->GetMethodID(atomicDoubleClass, "set", "(D)V"); if (setDoubleMethodId == nullptr) { LOGE("Failed to get set method for AtomicDouble."); return; } // Update singleWayDelay jfieldID singleWayDelayFieldId = env->GetFieldID(metricsClass, "singleWayDelay", "Lcom/dotouch/industrial/bean/AtomicDouble;"); if (singleWayDelayFieldId == nullptr) { LOGE("Failed to get field ID for singleWayDelay."); return; } jobject singleWayDelayObject = env->GetObjectField(metricsObject, singleWayDelayFieldId); env->CallVoidMethod(singleWayDelayObject, setDoubleMethodId, singleWayDelay); // Update roundTripDelay jfieldID roundTripDelayFieldId = env->GetFieldID(metricsClass, "roundTripDelay", "Lcom/dotouch/industrial/bean/AtomicDouble;"); if (roundTripDelayFieldId == nullptr) { LOGE("Failed to get field ID for roundTripDelay."); return; } jobject roundTripDelayObject = env->GetObjectField(metricsObject, roundTripDelayFieldId); env->CallVoidMethod(roundTripDelayObject, setDoubleMethodId, static_cast<double>(roundTripDelay)); }

B. Modify the readData and writeData JNI Functions

For readData:

Before Modification:

updateMetrics(env, obj, 0, 1, roundTripDelay);

After Modification:

double singleWayDelay = static_cast<double>(roundTripDelay) / 2.0; updateMetrics(env, obj, 0, 1, roundTripDelay, singleWayDelay);

For writeData:

Before Modification:

updateMetrics(env, obj, totalSize, 0, roundTripDelay);

After Modification:

double singleWayDelay = static_cast<double>(roundTripDelay) / 2.0; updateMetrics(env, obj, totalSize, 0, roundTripDelay, singleWayDelay);

Complete Example for readData:

extern "C" JNIEXPORT jbyteArray JNICALL Java_com_dotouch_industrial_s7_S7Client_readData(JNIEnv *env, jobject obj, jint dbNumber, jint startAddress, jstring dataType, jint bitAddress, jint quantity) { // Existing code... // Calculate RTT and single-way delay long long endTime = getCurrentTimestamp(); long long roundTripDelay = endTime - startTime; double singleWayDelay = static_cast<double>(roundTripDelay) / 2.0; // Update metrics with the new delay information updateMetrics(env, obj, 0, 1, roundTripDelay, singleWayDelay); // Existing code... }

Complete Example for writeData:

extern "C" JNIEXPORT jboolean JNICALL Java_com_dotouch_industrial_s7_S7Client_writeData(JNIEnv *env, jobject obj, jint dbNumber, jint startAddress, jstring dataType, jint bitAddress, jint quantity, jbyteArray value) { // Existing code... // Calculate RTT and single-way delay long long endTime = getCurrentTimestamp(); long long roundTripDelay = endTime - startTime; double singleWayDelay = static_cast<double>(roundTripDelay) / 2.0; // Update metrics with the new delay information updateMetrics(env, obj, totalSize, 0, roundTripDelay, singleWayDelay); // Existing code... }

2. Update the Java/Kotlin CommunicationMetrics Class

Ensure that your CommunicationMetrics class includes fields for singleWayDelay and roundTripDelay. Here's how you can modify it:

// Assuming this is your existing CommunicationMetrics class import java.io.Serializable import java.util.concurrent.atomic.AtomicDouble import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong data class CommunicationMetrics( val sentCommands: AtomicInteger = AtomicInteger(0), val sentPackets: AtomicInteger = AtomicInteger(0), val sentBytes: AtomicLong = AtomicLong(0), val sentRate: AtomicDouble = AtomicDouble(0.0), val receivedCommands: AtomicInteger = AtomicInteger(0), val receivedPackets: AtomicInteger = AtomicInteger(0), val receivedBytes: AtomicLong = AtomicLong(0), val receivedRate: AtomicDouble = AtomicDouble(0.0), val singleWayDelay: AtomicDouble = AtomicDouble(0.0), val roundTripDelay: AtomicDouble = AtomicDouble(0.0), val testItemResults: MutableMap<String, Pair<Int, Int>> = mutableMapOf() ) : Serializable

Note: Ensure that you have an AtomicDouble class implemented, as Java doesn't provide one by default. Here's a simple implementation:

import java.util.concurrent.atomic.AtomicReference class AtomicDouble(initialValue: Double) : Serializable { private val value = AtomicReference(initialValue) fun set(newValue: Double) { value.set(newValue) } fun get(): Double = value.get() }

3. Update the DataDisplayFragment to Show Delays

Modify your DataDisplayFragment to include views that display the singleWayDelay and roundTripDelay.

A. Update the Layout XML

Add TextView elements to your fragment_data_display.xml (or corresponding layout file) to display the delays:

<!-- Example additions to your layout --> <TextView android:id="@+id/tv_single_way_delay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="单向时延:0.0 ms" /> <TextView android:id="@+id/tv_round_trip_delay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="往返时延:0.0 ms" />

B. Update DataDisplayFragment.kt

Modify the updateMetricsView function to display the new delay metrics:

private fun updateMetricsView(metrics: CommunicationMetrics, view: View) { // Existing metric updates... // Single-way Delay view.findViewById<TextView>(R.id.tv_single_way_delay).text = "单向时延:${String.format("%.2f", metrics.singleWayDelay.get())} ms" // Round-trip Delay view.findViewById<TextView>(R.id.tv_round_trip_delay).text = "往返时延:${String.format("%.2f", metrics.roundTripDelay.get())} ms" // Existing metric updates... }

4. Remove NTP Client Usage from S7ClientFragment

Since delays are now being calculated based on read/write operations, you should remove or disable the NTP client to prevent redundant or conflicting delay calculations.

A. Remove NTP Client Fields and Initialization

In your S7ClientFragment, remove the following:

  • The ntpClient field:

    // Remove or comment out private var ntpClient: NtpClient? = null
  • The initialization and usage of NtpClient in connectToServer:

    lifecycleScope.launch(Dispatchers.IO) { try { ntpClient = NtpClient(ip, port) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ntpClient?.calculateTimeDifference() } } catch (e: Exception) { ntpClient = null Log.e("ModbusClientFragment", "Error calculating time difference: ${e.message}") } }
  • Remove calls to ntpClient?.sendTime() in performReadOperation and performWriteOperation.

B. Remove NTP-Related UI Elements and Logic

If there are any UI elements or additional logic related to NTP (like showing delay statistics from NTP), remove or disable them to clean up the interface.

5. Ensure Thread Safety and Consistency

Make sure that the C++ side correctly manages thread safety when updating metrics, especially since JNI calls can be made from different threads.

6. Final Code Snippets for Clarity

A. Modified C++ updateMetrics Function

void updateMetrics(JNIEnv *env, jobject thiz, int sentBytes, int receivedBytes, long long roundTripDelay, double singleWayDelay) { // Existing metric updates... // Get the CommunicationMetrics object jclass s7ClientClass = env->GetObjectClass(thiz); jfieldID metricsFieldId = env->GetFieldID(s7ClientClass, "metrics", "Lcom/dotouch/industrial/bean/CommunicationMetrics;"); jobject metricsObject = env->GetObjectField(thiz, metricsFieldId); // Get the CommunicationMetrics class jclass metricsClass = env->GetObjectClass(metricsObject); // Get AtomicDouble class and its set method jclass atomicDoubleClass = env->FindClass("com/dotouch/industrial/bean/AtomicDouble"); jmethodID setDoubleMethodId = env->GetMethodID(atomicDoubleClass, "set", "(D)V"); if (setDoubleMethodId == nullptr) { LOGE("Failed to get set method for AtomicDouble."); return; } // Update singleWayDelay jfieldID singleWayDelayFieldId = env->GetFieldID(metricsClass, "singleWayDelay", "Lcom/dotouch/industrial/bean/AtomicDouble;"); if (singleWayDelayFieldId == nullptr) { LOGE("Failed to get field ID for singleWayDelay."); return; } jobject singleWayDelayObject = env->GetObjectField(metricsObject, singleWayDelayFieldId); env->CallVoidMethod(singleWayDelayObject, setDoubleMethodId, singleWayDelay); // Update roundTripDelay jfieldID roundTripDelayFieldId = env->GetFieldID(metricsClass, "roundTripDelay", "Lcom/dotouch/industrial/bean/AtomicDouble;"); if (roundTripDelayFieldId == nullptr) { LOGE("Failed to get field ID for roundTripDelay."); return; } jobject roundTripDelayObject = env->GetObjectField(metricsObject, roundTripDelayFieldId); env->CallVoidMethod(roundTripDelayObject, setDoubleMethodId, static_cast<double>(roundTripDelay)); // Existing code to update other metrics... }

B. Modified Java CommunicationMetrics Class

import java.io.Serializable import java.util.concurrent.atomic.AtomicDouble import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong data class CommunicationMetrics( val sentCommands: AtomicInteger = AtomicInteger(0), val sentPackets: AtomicInteger = AtomicInteger(0), val sentBytes: AtomicLong = AtomicLong(0), val sentRate: AtomicDouble = AtomicDouble(0.0), val receivedCommands: AtomicInteger = AtomicInteger(0), val receivedPackets: AtomicInteger = AtomicInteger(0), val receivedBytes: AtomicLong = AtomicLong(0), val receivedRate: AtomicDouble = AtomicDouble(0.0), val singleWayDelay: AtomicDouble = AtomicDouble(0.0), val roundTripDelay: AtomicDouble = AtomicDouble(0.0), val testItemResults: MutableMap<String, Pair<Int, Int>> = mutableMapOf() ) : Serializable

C. Modified DataDisplayFragment

class DataDisplayFragment : DialogFragment() { companion object { fun newInstance( protocol: ProtocolType, isClient: Boolean, metrics: CommunicationMetrics ): DataDisplayFragment { val fragment = DataDisplayFragment() val args = Bundle() args.putSerializable("protocol", protocol) args.putBoolean("isClient", isClient) args.putSerializable("metrics", metrics) fragment.arguments = args return fragment } } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(requireActivity()) val inflater = requireActivity().layoutInflater val view = inflater.inflate(R.layout.fragment_data_display, null) // Get arguments val protocol = arguments?.getSerializable("protocol") as ProtocolType? val isClient = arguments?.getBoolean("isClient") ?: false val metrics = arguments?.getSerializable("metrics") as CommunicationMetrics? // Display metrics if (metrics != null) { updateMetricsView(metrics, view) configureChart(metrics, view, requireContext()) val recyclerView = view.findViewById<RecyclerView>(R.id.rv_test_items) recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.adapter = TestItemAdapter(metrics.testItemResults) } else { Toast.makeText( context, "No metrics found for this protocol and client state.", Toast.LENGTH_SHORT ).show() } // Set dialog view builder.setView(view) // Create dialog val dialog = builder.create() // Center the dialog dialog.window?.setGravity(Gravity.CENTER) // Close button view.findViewById<Button>(R.id.btn_close).setOnClickListener { dismiss() } // Export button view.findViewById<Button>(R.id.btn_export_report).setOnClickListener { exportMetricsToCsv(metrics ?: CommunicationMetrics(), requireContext()) } return dialog } private fun updateMetricsView(metrics: CommunicationMetrics, view: View) { view.findViewById<TextView>(R.id.tv_sent_commands).text = "发送命令个数:${metrics.sentCommands}" view.findViewById<TextView>(R.id.tv_sent_packets).text = "发送包数:${metrics.sentPackets}" view.findViewById<TextView>(R.id.tv_sent_bytes).text = "发送字节数:${metrics.sentBytes} bytes" view.findViewById<TextView>(R.id.tv_sent_rate).text = "发送速率:${String.format("%.2f", metrics.sentRate.get())} B/s" view.findViewById<TextView>(R.id.tv_received_commands).text = "接收命令个数:${metrics.receivedCommands}" view.findViewById<TextView>(R.id.tv_received_packets).text = "接收包数:${metrics.receivedPackets}" view.findViewById<TextView>(R.id.tv_received_bytes).text = "接收字节数:${metrics.receivedBytes} bytes" view.findViewById<TextView>(R.id.tv_received_rate).text = "接收速率:${String.format("%.2f", metrics.receivedRate.get())} B/s" // Single-way and Round-trip Delays view.findViewById<TextView>(R.id.tv_single_way_delay).text = "单向时延:${String.format("%.2f", metrics.singleWayDelay.get())} ms" view.findViewById<TextView>(R.id.tv_round_trip_delay).text = "往返时延:${String.format("%.2f", metrics.roundTripDelay.get())} ms" // Existing metric updates... // Delay Statistics val delayStats = metrics.delayStatistics if (delayStats != null) { // Existing delay stats updates... } // Other metrics... } // Existing configureChart and other functions... }

6. Remove NTP Client from S7ClientFragment

Since delays are now handled within the S7 read/write operations, you should remove the NTP client to prevent any unnecessary computations or conflicts.

A. Remove NTP Client Initialization and Usage

Remove the NtpClient field:

// Remove or comment out this line private var ntpClient: NtpClient? = null

Remove NTP Client Initialization in connectToServer:

// Remove or comment out the entire lifecycleScope.launch block related to NTP lifecycleScope.launch(Dispatchers.IO) { try { ntpClient = NtpClient(ip, port) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ntpClient?.calculateTimeDifference() } } catch (e: Exception) { ntpClient = null Log.e("ModbusClientFragment", "Error calculating time difference: ${e.message}") } }

Remove Calls to ntpClient?.sendTime():

// In performReadOperation and performWriteOperation, remove or comment out the following lines if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && ntpClient?.isReadyToSendTime() == true) { ntpClient?.sendTime() }

Remove NTP Statistics and Metrics Display:

  • Ensure that any UI elements or logic related to displaying NTP-based delays are removed from your layout files and DataDisplayFragment.

7. Ensure Proper Data Passing Between C++ and Kotlin/Java

Your C++ code now calculates singleWayDelay and roundTripDelay during each read/write operation and updates the CommunicationMetrics object via JNI. Ensure that these metrics are correctly passed and displayed in your Kotlin/Java code.

8. Testing and Validation

After making these changes, thoroughly test your application to ensure:

  • Delays are being correctly calculated and displayed.
  • Removing the NTP client does not introduce any unexpected behavior.
  • All metrics are being accurately tracked and updated.
  • Thread safety is maintained, especially when multiple read/write operations occur concurrently.

9. Additional Recommendations

  • Logging: Continue using comprehensive logging in both C++ and Kotlin/Java to trace and debug the delay calculations.

  • Error Handling: Ensure that any potential errors in JNI calls or metric updates are gracefully handled to prevent crashes or inconsistent states.

  • Performance: Monitor the performance impact of the added delay calculations, especially if read/write operations are frequent.

By following these steps, you should be able to transition the delay calculations from a separate NTP mechanism to being directly integrated within your S7 read/write operations, providing more accurate and context-specific timing metrics.

इस प्रश्नोत्तर को साझा करें