feat: 添加 ahrs 和 excavator-bucket-position 模块

This commit is contained in:
2025-05-30 00:41:39 +08:00
parent 74ea2ad62a
commit 784f2324bd
17 changed files with 2303 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
plugins {
kotlin("jvm")
}
repositories {
mavenCentral()
}

View File

@@ -0,0 +1,159 @@
package com.icegps.ahrs
/**
* @author linmiao
* @date 2025/2/6
*/
class AHRS401ACanConfigurationData {
// 波特率枚举
enum class BaudRate(val value: Int) {
BAUD_250K(250),
BAUD_500K(500),
BAUD_1000K(1000);
}
// 输出频率枚举
enum class OutputFrequency(val value: Int) {
FREQ_1HZ(1),
FREQ_10HZ(10),
FREQ_50HZ(50),
FREQ_100HZ(100),
FREQ_200HZ(200);
}
// 横滚俯仰取反枚举
enum class RollPitchInvert(val value: Int) {
NO_INVERT(0x00),
ROLL_INVERT(0x01),
PITCH_INVERT(0x10),
BOTH_INVERT(0x11);
}
// 滤波器截止频率枚举
enum class FilterCutoffFrequency(val value: Int) {
FREQ_10(10),
FREQ_20(20),
FREQ_40(40),
FREQ_47(47);
}
companion object {
// 默认节点ID
private val DEFAULT_NODE_ID = 0x00
val queryBaudRate = intArrayOf(0x20, 0x21, 0x22, 0x23, 0x0A, 0x00, 0x00, 0x00)
val queryVersion = intArrayOf(0x10, 0x11, 0x12, 0x13, 0x00, 0x00, 0x00, 0x00)
val removeResistor = intArrayOf(0x10, 0x11, 0x12, 0x13, 0x01, 0x00, 0x00, 0x00)
val addResistor = intArrayOf(0x10, 0x11, 0x12, 0x13, 0x02, 0x00, 0x00, 0x00)
val queryFrequecy = intArrayOf(0x10, 0x11, 0x12, 0x13, 0x0A, 0xFF, 0x00, 0x00)
val queryRollPitchInvertStatusdata = intArrayOf(0x10, 0x11, 0x12, 0x13, 0x0A, 0xFF, 0x00, 0x00)
val queryFilterCutoffFrequency = intArrayOf(0x20, 0x21, 0x22, 0x0A, 0xFF, 0xFF, 0x00, 0x00)
val queryCoordinateSystem = intArrayOf(0x30, 0x31, 0x32, 0x0A, 0xFF, 0xFF, 0x00, 0x00)
val queryRemoveAttitudeAngle = intArrayOf(0x10, 0x11, 0x12, 0x0A, 0xFF, 0xFF, 0x00, 0x00)
/**
* 设置 CAN 传感器的波特率。
*
* @param baudRate 传入的波特率取值250 (kbps)500 (kbps)1000 (kbps)。
*
*/
fun setBaudRate(baudRate: BaudRate): IntArray {
val data = intArrayOf(0x20, 0x21, 0x22, 0x23, 0x23, 0x00, 0x00, 0x00)
when (baudRate) {
BaudRate.BAUD_250K -> data[4] = 0x01
BaudRate.BAUD_500K -> data[4] = 0x02
BaudRate.BAUD_1000K -> data[4] = 0x03
}
return data
}
/**
* 设置输出频率。
*
* @param frequency 输出频率取值1HZ 10HZ 50HZ 100HZ 200HZ
* 例如,传入 `1` 表示输出频率为 1 Hz。
*/
fun setOutputFrequency(frequency: OutputFrequency): IntArray {
val data = intArrayOf(0x10, 0x11, 0x12, 0x13, 0x0A, 0x00, 0x00, 0x00)
when (frequency) {
OutputFrequency.FREQ_1HZ -> data[4] = 0x01
OutputFrequency.FREQ_10HZ -> data[4] = 0x02
OutputFrequency.FREQ_50HZ -> data[4] = 0x03
OutputFrequency.FREQ_100HZ -> data[4] = 0x04
OutputFrequency.FREQ_200HZ -> data[4] = 0x05
}
return data
}
/**
* 设置横滚俯仰取反命令。
* ID=0x61D, DATA=0x10 0x11 0x12 0x13 XXXX 0xFF ID_H ID_L
* @param rollPitchValue 横滚俯仰取反值:
* - 0x00 不取反
* - 0x01 横滚取反
* - 0x10 俯仰取反
* - 0x11 横滚和俯仰都取反
*/
fun setRollPitchInvert(rollPitchValue: RollPitchInvert): IntArray {
return intArrayOf(0x10, 0x11, 0x12, 0x13, rollPitchValue.value, 0xFF, 0x00, 0x00)
}
/**
* 设置滤波器截止频率命令。
* ID=0x61E, DATA=0x20 0x21 0x22 0x23 XXXX 0xFF ID_H ID_L
* @param frequency 滤波器截止频率取值10 (Hz) 20 (Hz) 40 (Hz) 47 (Hz)
* 例如,传入 `10` 表示截止频率为 10 Hz。
*/
fun setFilterCutoffFrequency(frequency: FilterCutoffFrequency): IntArray {
val commandValue = when (frequency) {
FilterCutoffFrequency.FREQ_10 -> 0x44
FilterCutoffFrequency.FREQ_20 -> 0x66
FilterCutoffFrequency.FREQ_40 -> 0xAA
FilterCutoffFrequency.FREQ_47 -> 0xBB
}
return intArrayOf(0x20, 0x21, 0x22, 0x23, commandValue, 0xFF, 0x00, 0x00)
}
/**
* 设置坐标系命令。
* ID=0x61F, DATA=0x30 0x31 0x32 0x33 XXXX 0xFF ID_H ID_L
* @param coordinate 坐标系统的值,例如:`0x01` 表示某一坐标系统。
*/
fun setCoordinateSystem(coordinate: Int): IntArray {
val data = intArrayOf(0x30, 0x31, 0x32, 0x33, coordinate, 0xFF, 0x00, 0x00)
return data
}
/**
* 设置是否扣除姿态角命令。
* ID=0x620, DATA=0x10 0x11 0x12 0x13 XXXX 0xFF ID_H ID_L
* @param remove 是否扣除姿态角,`true` 表示扣除,`false` 表示不扣除。
*/
fun setRemoveAttitudeAngle(remove: Boolean): IntArray {
val data = intArrayOf(0x10, 0x11, 0x12, 0x13, if (remove) 0x01 else 0x00, 0xFF, 0x00, 0x00)
return data
}
/**
* 设置节点ID命令。
* ID=0x61A, DATA=0x30 0x31 0x32 0x33 0xXX 0xXX 0x00 0x00
* @param highByte 节点 ID 的高字节。
* @param lowByte 节点 ID 的低字节。
* 示例highByte 可以是 0x01, lowByte 可以是 0x00
* @return 返回设置节点 ID 的命令数据
*/
fun setNodeId(highByte: Int, lowByte: Int): IntArray {
if (highByte < 0 || highByte > 0xFF || lowByte < 0 || lowByte > 0xFF) {
throw IllegalArgumentException("Invalid highByte or lowByte value")
}
return intArrayOf(0x30, 0x31, 0x32, 0x33, highByte, lowByte, 0x00, 0x00)
}
}
}

View File

@@ -0,0 +1,191 @@
package com.icegps.ahrs
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* @author linmiao
* @date 2025/2/7
*/
object AHRS401ACanDataParser {
private const val TAG = "AHRS401ACanDataParser"
// 用来缓存接收到的数据
private val dataCache = mutableMapOf<String, LongArray>()
private var count = 0
// 新增的查询命令的返回结果
var baudRate: Int? = null
var version: String? = null
var outputFrequency: Int? = null
var rollPitchInvert: String? = null
var filterCutoffFrequency: Int? = null
var coordinateSystem: Int? = null
var attitudeAngle: String? = null
fun parseFrame(frame: AHRS401ACanFrame): AHRS401AParsedCanData? {
try {
val data = AHRS401AParsedCanData()
val strid = frame.id.toString(16)
// Log.d(TAG, "Frame ID: $strid")
// Log.d(TAG, "Frame Len: ${frame.length}")
when (strid) {
"518" -> { // 版本号的回应 (ID=0x518)
val versionBytes = frame.data.sliceArray(0..3) // 版本号
// 将字节拼接为一个十六进制字符串
val versionHex = versionBytes.joinToString("") { String.format("%02X", it) }
// 将十六进制版本号字符串转换为十进制
version = versionHex.toLong(16).toInt().toString()
println("查询命令的返回结果Received version: $version")
}
"519" -> { // 波特率的回应 (ID=0x519)
baudRate = when (frame.data[0]) {
0x01L -> 250
0x02L -> 500
0x03L -> 1000
else -> 0
}
println("查询命令的返回结果Received baud rate response: $baudRate")
}
"51c" -> { // 输出频率的回应 (ID=0x51C)
val frequency = frame.data[0]
outputFrequency = when (frequency) {
0x01L -> 1
0x02L -> 10
0x03L -> 50
0x04L -> 100
0x05L -> 200
else -> 0
}
println("查询命令的返回结果Received output frequency: $outputFrequency")
}
"51d" -> { // 横滚俯仰取反的回应 (ID=0x51D)
val invertStatus = frame.data[0]
rollPitchInvert = when (invertStatus) {
0x00L -> "No invert"
0x01L -> "Roll invert"
0x10L -> "Pitch invert"
0x11L -> "Both invert"
else -> "Unknown"
}
println("查询命令的返回结果Roll/Pitch invert status: $rollPitchInvert")
}
"51e" -> { // 滤波器截止频率的回应 (ID=0x51E)
val cutoffFrequency = frame.data[0]
filterCutoffFrequency = when (cutoffFrequency) {
0x44L -> 10
0x66L -> 20
0xAAL -> 40
0xBBL -> 47
else -> 0
}
println("查询命令的返回结果Received filter cutoff frequency: $filterCutoffFrequency")
}
"51f" -> { // 坐标系的回应 (ID=0x51F)
coordinateSystem = frame.data[0].toInt()
println("查询命令的返回结果Received coordinate system: $coordinateSystem")
}
"520" -> { // 姿态角的回应 (ID=0x520)
val removeAttitudeAngle = frame.data[0]
attitudeAngle = if (removeAttitudeAngle == 0x01L) "Removed" else "Not removed"
println("查询命令的返回结果Attitude angle removal status: $attitudeAngle")
}
else -> {
// 将当前帧缓存起来
dataCache[strid] = frame.data
}
}
// 判断是否收齐了完整的数据65, 66, 67, 68, 69
if ((dataCache.containsKey("65") && dataCache.containsKey("66")) || (dataCache.containsKey("6a") && dataCache.containsKey(
"6b"
))
// && dataCache.containsKey("67") && dataCache.containsKey("68") &&
// dataCache.containsKey("69")
) {
if (dataCache.containsKey("65")) {
// 开始解析数据
// 解析 ROLL & PITCH
data.roll = parseFloat(dataCache["65"]!!, 0)
data.pitch = parseFloat(dataCache["65"]!!, 4)
// Log.e(TAG, "parseFrame: data.roll${data.roll}\n data.pitch${data.pitch}",)
// 解析 YAW & Gx
data.yaw = parseFloat(dataCache["66"]!!, 0)
data.gx = parseFloat(dataCache["66"]!!, 4)
// Log.e(TAG, "parseFrame: data.yaw${data.roll}\n data.gx${data.pitch}",)
} else if (dataCache.containsKey("6a")) {
// 开始解析数据
// 解析 ROLL & PITCH
data.roll = parseFloat(dataCache["6a"]!!, 0)
data.pitch = parseFloat(dataCache["6a"]!!, 4)
// Log.e(TAG, "parseFrame: data.roll${data.roll}\n data.pitch${data.pitch}",)
// 解析 YAW & Gx
data.yaw = parseFloat(dataCache["6b"]!!, 0)
data.gx = parseFloat(dataCache["6b"]!!, 4)
// Log.e(TAG, "parseFrame: data.yaw${data.roll}\n data.gx${data.pitch}",)
}
// 解析 Gy & Gz
// data.gy = parseFloat(dataCache["67"]!!, 0)
// data.gz = parseFloat(dataCache["67"]!!, 4)
// 解析 Ax & Ay
// data.ax = parseFloat(dataCache["68"]!!, 0)
// data.ay = parseFloat(dataCache["68"]!!, 4)
// 解析 Az & TEMP & INDEX
// data.az = parseFloat(dataCache["69"]!!, 0)
// data.temp = parseInt16(dataCache["69"]!!, 4)
// data.index = parseInt16(dataCache["69"]!!, 6)
// count++
// Log.d(TAG, "Received data count: $count")
// 清空缓存
dataCache.clear()
return data
}
// 如果没有收到完整的数据,返回 null
return null
} catch (e: Exception) {
return null
}
}
private fun parseFloat(data: LongArray, offset: Int): Float {
val bytes = ByteArray(4)
for (i in 0..3) {
bytes[i] = data[offset + i].toByte()
}
return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getFloat()
}
private fun parseInt16(data: LongArray, offset: Int): Int {
val bytes = ByteArray(2)
for (i in 0..1) {
bytes[i] = data[offset + i].toByte()
}
return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).short.toInt()
}
}

View File

@@ -0,0 +1,22 @@
package com.icegps.ahrs
import java.util.*
/**
* @author linmiao
* @date 2025/2/7
*/
class AHRS401ACanFrame(revdata: LongArray) {
val id: Long = revdata[0]
val eff: Long = revdata[1] // 扩展帧标志
val rtr: Long = revdata[2] // 远程帧标志
val length: Int = revdata[3].toInt()
val data: LongArray = Arrays.copyOfRange(revdata, 4, 4 + length)
init {
require(revdata.size >= 4) { "revdata must contain at least 4 elements" }
val length = revdata[3].toInt()
require(length >= 0 && length <= (revdata.size - 4)) { "Invalid data length or insufficient revdata size" }
}
}

View File

@@ -0,0 +1,234 @@
package com.icegps.ahrs
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong
/**
* @author linmiao
* @date 2025/2/7
*/
class AHRS401ACanManager(val canDevice: android_socketcan, private val interfaceName: String) {
private val TAG = "AHRS401ACanManager"
// private val canDevice = android_socketcan()
private val listeners = mutableListOf<OnCanDataListener>()
private val isRunning = AtomicBoolean(false)
private var receiveThread: Thread? = null
// 记录上次发送配置命令的时间
private val lastConfigCommandTime = AtomicLong(0)
interface OnCanDataListener {
fun onCanDataReceived(data: AHRS401AParsedCanData)
}
init {
canDevice.fd = canDevice.socketcanOpen(interfaceName)
println("初始化: $interfaceName")
}
fun addListener(listener: OnCanDataListener) {
if (!listeners.contains(listener)) {
listeners.add(listener)
}
}
fun removeListener(listener: OnCanDataListener) {
listeners.remove(listener)
}
fun startListening() {
if (isRunning.get()) return
isRunning.set(true)
receiveThread = Thread {
var ret = LongArray(12)
while (isRunning.get()) {
ret = canDevice.socketcanRead(canDevice.fd)
val can0id = ret[0]
val can0eff = ret[1]
val can0rtr = ret[2]
val can0len = ret[3]
// 确保 can0len 不会超出 ret 数组的大小
val length = can0len.toInt()
val toIndex = 4 + length
val can0data = if (toIndex <= ret.size) {
ret.copyOfRange(4, toIndex)
} else {
println("Invalid can0len: $can0len, ret size: ${ret.size}")
LongArray(0) // 或者根据需要处理错误情况
}
// 打印日志
// println("CAN ID: ${java.lang.Long.toHexString(can0id)}, Eff: $can0eff, RTR: $can0rtr, Length: $can0len, Data: ${Arrays.toString(can0data)}")
// 创建 CanFrame 对象并处理数据
if (can0data.isNotEmpty()) {
val frame = AHRS401ACanFrame(ret)
val parsedData = AHRS401ACanDataParser.parseFrame(frame)
// 如果解析到了完整的数据,则通知监听者
if (parsedData != null) {
notifyListeners(parsedData)
}
}
}
}
receiveThread?.start()
}
fun stopListening() {
isRunning.set(false)
receiveThread?.let {
try {
it.join()
} catch (e: InterruptedException) {
println("Error stopping receive thread")
}
}
}
private fun notifyListeners(data: AHRS401AParsedCanData) {
listeners.forEach { it.onCanDataReceived(data) }
}
fun sendCanData(id: Long, data: IntArray, isConfigCommand: Boolean) {
val currentTime = System.currentTimeMillis()
// 如果是配置命令,检查时间间隔
if (isConfigCommand) {
val lastTime = lastConfigCommandTime.get()
if (currentTime - lastTime < 3000) {
println("配置命令发送太频繁,等待 3 秒再发送")
return
}
lastConfigCommandTime.set(currentTime)
}
// 发送数据
if (canDevice.fd >= 0) {
println("Sending data: ID=${java.lang.Long.toHexString(id)}, Data=${Arrays.toString(data)}")
canDevice.socketcanWrite(canDevice.fd, id, 0, 0, data.size, data)
} else {
println("CAN device is not initialized or opened")
}
}
fun queryVersion() {
sendCanData(0x618, AHRS401ACanConfigurationData.queryVersion, false)
}
fun queryBaudRate() {
sendCanData(0x619, AHRS401ACanConfigurationData.queryBaudRate, false)
}
fun setBaudRate(baudRate: AHRS401ACanConfigurationData.BaudRate) {
sendCanData(0x619, AHRS401ACanConfigurationData.setBaudRate(baudRate), true)
}
fun addTerminalResistor() {
sendCanData(0x61B, AHRS401ACanConfigurationData.addResistor, true)
}
fun removeTerminalResistor() {
sendCanData(0x61B, AHRS401ACanConfigurationData.removeResistor, true)
}
fun queryOutputFrequency() {
sendCanData(0x61C, AHRS401ACanConfigurationData.queryFrequecy, false)
}
fun setOutputFrequency(frequency: AHRS401ACanConfigurationData.OutputFrequency) {
sendCanData(0x61C, AHRS401ACanConfigurationData.setOutputFrequency(frequency), true)
}
fun queryRollPitchInvertStatus() {
sendCanData(0x61D, AHRS401ACanConfigurationData.queryRollPitchInvertStatusdata, false)
}
fun setRollPitchInvert(rollPitchValue: AHRS401ACanConfigurationData.RollPitchInvert) {
sendCanData(0x61D, AHRS401ACanConfigurationData.setRollPitchInvert(rollPitchValue), true)
}
fun queryFilterCutoffFrequency() {
sendCanData(0x61E, AHRS401ACanConfigurationData.queryFilterCutoffFrequency, false)
}
fun setFilterCutoffFrequency(frequency: AHRS401ACanConfigurationData.FilterCutoffFrequency) {
sendCanData(0x61E, AHRS401ACanConfigurationData.setFilterCutoffFrequency(frequency), true)
}
fun queryCoordinateSystem() {
sendCanData(0x61F, AHRS401ACanConfigurationData.queryCoordinateSystem, false)
}
fun setCoordinateSystem(coordinate: Int) {
sendCanData(0x61F, AHRS401ACanConfigurationData.setCoordinateSystem(coordinate), true)
}
fun queryRemoveAttitudeAngle() {
sendCanData(0x620, AHRS401ACanConfigurationData.queryRemoveAttitudeAngle, false)
}
fun setRemoveAttitudeAngle(remove: Boolean) {
sendCanData(0x620, AHRS401ACanConfigurationData.setRemoveAttitudeAngle(remove), true)
}
fun configCanShell(baudRate: String): Boolean {
val isPermission = ShellUtils.checkRootPermission()
println("result的结果: $isPermission")
if (isPermission) {
try {
Thread.sleep(10000)
} catch (e: InterruptedException) {
println("Sleep interrupted")
}
val ret = ShellUtils.execCommand("ip link set down can0", true)
println("result: ${ret.result}")
val baudRateCommand = "ip link set can0 type can bitrate $baudRate sample-point 0.875 restart-ms 100"
val baudRateResult = ShellUtils.execCommand(baudRateCommand, true)
if (baudRateResult.result == 0) {
println("can0 set BaudRate [$baudRate] Success!")
}
val upCommand = "ip link set up can0 mtu 16"
val upResult = ShellUtils.execCommand(upCommand, true)
if (upResult.result == 0) {
println("can0 up Success!")
}
val txQueueLenCommand = "ifconfig can0 txqueuelen 1000"
val txQueueLenResult = ShellUtils.execCommand(txQueueLenCommand, true)
if (txQueueLenResult.result == 0) {
println("can0 txqueuelen Success!")
}
val downCan1Command = "ip link set down can1"
val downCan1Result = ShellUtils.execCommand(downCan1Command, true)
println("result can1: ${downCan1Result.result}")
val baudRateCan1Command = "ip link set can1 type can bitrate $baudRate sample-point 0.875 restart-ms 100"
val baudRateCan1Result = ShellUtils.execCommand(baudRateCan1Command, true)
if (baudRateCan1Result.result == 0) {
println("can1 set BaudRate [$baudRate] Success!")
}
val upCan1Command = "ip link set up can1 mtu 16"
val upCan1Result = ShellUtils.execCommand(upCan1Command, true)
if (upCan1Result.result == 0) {
println("can1 up Success!")
}
val txQueueLenCan1Command = "ifconfig can1 txqueuelen 1000"
val txQueueLenCan1Result = ShellUtils.execCommand(txQueueLenCan1Command, true)
if (txQueueLenCan1Result.result == 0) {
println("can1 txqueuelen Success!")
}
} else {
println("shell root is [$isPermission] failed!")
}
return isPermission
}
}

View File

@@ -0,0 +1,30 @@
package com.icegps.ahrs
/**
* @author linmiao
* @date 2025/2/7
*/
class AHRS401AParsedCanData {
var roll: Float = 0f
var pitch: Float = 0f
var yaw: Float = 0f
var gx: Float = 0f
var gy: Float = 0f
var gz: Float = 0f
var ax: Float = 0f
var ay: Float = 0f
var az: Float = 0f
var temp: Int = 0
var index: Int = 0
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
override fun toString(): String {
return super.toString()
}
}

View File

@@ -0,0 +1,93 @@
package com.icegps.ahrs
import java.io.BufferedReader
import java.io.DataOutputStream
import java.io.IOException
import java.io.InputStreamReader
/**
* Shell 工具类
*
* @author lm
* @date 2024/12/26
*/
object ShellUtils {
private const val COMMAND_SU = "/system/xbin/su"
private const val COMMAND_SH = "sh"
private const val COMMAND_EXIT = "exit\n"
private const val COMMAND_LINE_END = "\n"
fun checkRootPermission(): Boolean {
return execCommand("echo root", true, false).result == 0
}
fun execCommand(command: String, isRoot: Boolean = false, isNeedResultMsg: Boolean = true): CommandResult {
return execCommand(arrayOf(command), isRoot, isNeedResultMsg)
}
fun execCommand(commands: List<String>?, isRoot: Boolean = false, isNeedResultMsg: Boolean = true): CommandResult {
return execCommand(commands?.toTypedArray(), isRoot, isNeedResultMsg)
}
fun execCommand(commands: Array<String>?, isRoot: Boolean, isNeedResultMsg: Boolean): CommandResult {
var result = -1
if (commands.isNullOrEmpty()) {
return CommandResult(result, null, null)
}
var process: Process? = null
var successResult: BufferedReader? = null
var errorResult: BufferedReader? = null
var successMsg: StringBuilder? = null
var errorMsg: StringBuilder? = null
var os: DataOutputStream? = null
try {
process = Runtime.getRuntime().exec(if (isRoot) COMMAND_SU else COMMAND_SH)
os = DataOutputStream(process.outputStream)
for (command in commands) {
if (command.isEmpty()) continue
os.write(command.toByteArray())
os.writeBytes(COMMAND_LINE_END)
os.flush()
}
os.writeBytes(COMMAND_EXIT)
os.flush()
result = process.waitFor()
if (isNeedResultMsg) {
successMsg = StringBuilder()
errorMsg = StringBuilder()
successResult = BufferedReader(InputStreamReader(process.inputStream))
errorResult = BufferedReader(InputStreamReader(process.errorStream))
var s: String?
while (successResult.readLine().also { s = it } != null) {
successMsg.append(s)
}
while (errorResult.readLine().also { s = it } != null) {
errorMsg.append(s)
}
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
os?.close()
successResult?.close()
errorResult?.close()
} catch (e: IOException) {
e.printStackTrace()
}
process?.destroy()
}
return CommandResult(result, successMsg?.toString(), errorMsg?.toString())
}
data class CommandResult(
val result: Int,
val successMsg: String?,
val errorMsg: String?
)
}

View File

@@ -0,0 +1,16 @@
package com.icegps.ahrs;
/**
* @author linmiao
* @date 2025/1/9
*/
public class android_socketcan {
static {
System.loadLibrary("android_socketcan");
}
public int fd;
public native int socketcanOpen(String canx); //return fd
public native int socketcanClose(int fd); //return 0 is success
public native int socketcanWrite(int fd, long canid, long eff, long rtr, int len, int[] data);
public native long[] socketcanRead(int fd);
}