关于MSDK的一些问题
Completedclass VirtualStickFragment : DJIFragment() {
private val basicAircraftControlVM: BasicAircraftControlVM by activityViewModels()
private val virtualStickVM: VirtualStickVM by activityViewModels()
private val simulatorVM: SimulatorVM by activityViewModels()
private val deviation: Double = 0.02
private lateinit var logUtils: LogUtils
private lateinit var logUtilsSend: LogUtilsSend
private lateinit var lineChart: LineChart
private lateinit var binding: FragVirtualStickPageBinding
var rollT = 0
var yawT = 0
var pitchT = 0
var rollTime = 0.0
var yawTime = 0.0
var pitchTime = 0.0
var number = 0
var pi = 0.0
var max = 0.0
var taskManager : TaskManager?= null
val entries1 = ArrayList<Entry>()
val entries2 = ArrayList<Entry>()
val entries3 = ArrayList<Entry>()
val entries10 = ArrayList<Entry>()
val entries20 = ArrayList<Entry>()
val entries30 = ArrayList<Entry>()
var flag = true
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.frag_virtual_stick_page, container, false)
lineChart = view.findViewById(R.id.line_chart)
// initChart()
return view
// return inflater.inflate(R.layout.frag_virtual_stick_page, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
widget_horizontal_situation_indicator.setSimpleModeEnable(false)
initBtnClickListener()
initStickListener()
virtualStickVM.listenRCStick()
virtualStickVM.currentSpeedLevel.observe(viewLifecycleOwner) {
updateVirtualStickInfo()
}
virtualStickVM.useRcStick.observe(viewLifecycleOwner) {
updateVirtualStickInfo()
}
virtualStickVM.currentVirtualStickStateInfo.observe(viewLifecycleOwner) {
updateVirtualStickInfo()
}
virtualStickVM.stickValue.observe(viewLifecycleOwner) {
updateVirtualStickInfo()
}
virtualStickVM.virtualStickAdvancedParam.observe(viewLifecycleOwner) {
updateVirtualStickInfo()
}
simulatorVM.simulatorStateSb.observe(viewLifecycleOwner) {
simulator_state_info_tv.text = it
}
virtualStickVM.virtualStickAdvancedParam.value?.let { param ->
param.yawControlMode = YawControlMode.ANGLE
// 更新 ViewModel 中的值
virtualStickVM.virtualStickAdvancedParam.postValue(param)
virtualStickVM.sendVirtualStickAdvancedParam(param)
}
logUtils = LogUtils(requireContext())
logUtilsSend = LogUtilsSend(requireContext())
var logUtilsPosition = LogUtilsPosition(requireContext())
initChart()
taskManager = TaskManager()
KeyManager.getInstance().listen(createKey(FlightControllerKey.KeyAircraftLocation),this
) { _, newValue -> logUtilsPosition.log(newValue.toString())
}
}
override fun onDestroy() {
super.onDestroy()
taskManager?.destroy()
}
private fun initChart(){
entries1.add(Entry(0f, 0f, null))
entries2.add(Entry(0f, 0f, null))
entries3.add(Entry(0f, 0f, null))
entries10.add(Entry(0f, 0f, null))
entries20.add(Entry(0f, 0f, null))
entries30.add(Entry(0f, 0f, null))
val dataSet1 = getDataSet(entries1, "pitch")
val dataSet2 = getDataSet(entries2, "roll")
val dataSet3 = getDataSet(entries3, "yaw")
// 创建数据对象
val data = LineData(dataSet1,dataSet2,dataSet3)
// 设置数据
lineChart.data = data
// 配置图表描述
lineChart.description.isEnabled = false
}
private fun getDataSet(entries: List<Entry>, label: String): LineDataSet {
return LineDataSet(entries, label).apply {
var color = Color.BLACK
when (label) {
"pitch" -> color = Color.RED
"roll" -> color = Color.GREEN
"yaw" -> color = Color.BLUE
}
setColor(color) // 设置折线颜色
setLineWidth(2.5f) // 设置线条宽度
setCircleColor(color) // 设置圆点颜色
setCircleRadius(4f) // 设置圆点半径
setMode(LineDataSet.Mode.CUBIC_BEZIER) // 设置线型为圆滑曲线
valueTextSize = 9f
setDrawValues(false)
}
}
private fun getDataSet0(entries: List<Entry>, label: String): LineDataSet {
return LineDataSet(entries, label).apply {
var color = Color.BLACK
when (label) {
"pitchSet" -> color = Color.RED
"rollSet" -> color = Color.GREEN
"yawSet" -> color = Color.BLUE
}
setColor(color) // 设置折线颜色
setLineWidth(1.5f) // 设置线条宽度
setDrawCircles(false)
setCircleColor(Color.TRANSPARENT) // 可选:将圆点颜色设为透明(如果仍然想保留绘制圆点的代码,但希望它们不可见)
setCircleRadius(0f)
setMode(LineDataSet.Mode.STEPPED)
valueTextSize = 9f
setDrawValues(false)
}
}
private fun initBtnClickListener() {
btn_enable_virtual_stick.setOnClickListener {
virtualStickVM.enableVirtualStick(object : CommonCallbacks.CompletionCallback {
override fun onSuccess() {
ToastUtils.showToast("enableVirtualStick success.")
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("enableVirtualStick error,$error")
}
})
}
btn_disable_virtual_stick.setOnClickListener {
virtualStickVM.disableVirtualStick(object : CommonCallbacks.CompletionCallback {
override fun onSuccess() {
ToastUtils.showToast("disableVirtualStick success.")
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("disableVirtualStick error,${error})")
}
})
}
btn_set_virtual_stick_speed_level.setOnClickListener {
val speedLevels = doubleArrayOf(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0)
initPopupNumberPicker(Helper.makeList(speedLevels)) {
virtualStickVM.setSpeedLevel(speedLevels[indexChosen[0]])
resetIndex()
}
}
btn_take_off.setOnClickListener {
basicAircraftControlVM.startTakeOff(object :
CommonCallbacks.CompletionCallbackWithParam<EmptyMsg> {
override fun onSuccess(t: EmptyMsg?) {
ToastUtils.showToast("start takeOff onSuccess.")
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("start takeOff onFailure,$error")
}
})
}
btn_landing.setOnClickListener {
basicAircraftControlVM.startLanding(object :
CommonCallbacks.CompletionCallbackWithParam<EmptyMsg> {
override fun onSuccess(t: EmptyMsg?) {
ToastUtils.showToast("start landing onSuccess.")
}
override fun onFailure(error: IDJIError) {
ToastUtils.showToast("start landing onFailure,$error")
}
})
}
btn_use_rc_stick.setOnClickListener {
virtualStickVM.useRcStick.value = virtualStickVM.useRcStick.value != true
if (virtualStickVM.useRcStick.value == true) {
ToastUtils.showToast(
"After it is turned on," +
"the joystick value of the RC will be used as the left/ right stick value"
)
}
}
btn_set_virtual_stick_advanced_param.setOnClickListener {
KeyValueDialogUtil.showInputDialog(
activity, "Set Virtual Stick Advanced Param",
JsonUtil.toJson(virtualStickVM.virtualStickAdvancedParam.value), "", false
) {
it?.apply {
val param = JsonUtil.toBean(this, VirtualStickFlightControlParam::class.java)
if (param == null) {
ToastUtils.showToast("Value Parse Error")
return@showInputDialog
}
virtualStickVM.virtualStickAdvancedParam.postValue(param)
}
}
}
btn_send_virtual_stick_advanced_param.setOnClickListener {
virtualStickVM.virtualStickAdvancedParam.value?.let {
virtualStickVM.sendVirtualStickAdvancedParam(it)
}
}
btn_enable_virtual_stick_advanced_mode.setOnClickListener {
virtualStickVM.enableVirtualStickAdvancedMode()
}
btn_disable_virtual_stick_advanced_mode.setOnClickListener {
virtualStickVM.disableVirtualStickAdvancedMode()
}
val handler = Handler(Looper.getMainLooper()) // 确保Handler在UI线程
fun calculateSineWave(A: Double, omega: Double, x: Double, phi: Double): Double {
// 计算ωx + φ
val argument = omega * x + phi
// 计算A * sin(ωx + φ)
val result = A * Math.sin(argument)
//保留2位小数
// val nf = NumberFormat.getNumberInstance()
// nf.maximumFractionDigits = 2
// nf.roundingMode = RoundingMode.UP
// return nf.format(result).toDouble()
return result
}
fun sendPitch(pitchMax: Double,tNumber: Int,tPi:Double){
var times = -1
virtualStickVM.virtualStickAdvancedParam.value?.let { param ->
var i = 0.0
val task1 = Runnable {
//需要执行的任务
param.pitch = calculateSineWave(pitchMax,tPi * Math.PI,i,0.0)
param.roll = 0.0
param.yaw = 0.0
virtualStickVM.sendVirtualStickAdvancedParam(param)
virtualStickVM.virtualStickAdvancedParam.postValue(param)
i+=0.1
if(param.pitch * calculateSineWave(pitchMax,tPi * Math.PI,i,0.0) < 0){
times++
}
if(param.pitch == 0.0) times++
if(times==tNumber*2){
taskManager?.pauseTask1()
}
}
taskManager?.startTask1(task1, 0, 100, TimeUnit.MILLISECONDS)
}
}
fun sendRoll(rollMax: Double,tNumber: Int,tPi:Double){
flag = false
var times = -1
virtualStickVM.virtualStickAdvancedParam.value?.let { param ->
var i = 0.0
val task2 = Runnable {
//需要执行的任务
param.pitch = 0.0
param.roll = calculateSineWave(rollMax,tPi * Math.PI,i,0.0)
param.yaw = 0.0
virtualStickVM.sendVirtualStickAdvancedParam(param)
virtualStickVM.virtualStickAdvancedParam.postValue(param)
i+=0.1
if(param.roll * calculateSineWave(rollMax,tPi * Math.PI,i,0.0) < 0){
times++
}
if(param.roll == 0.0) times++
if(times==tNumber*2){
flag = true
taskManager?.pauseTask2()
}
}
taskManager?.startTask2(task2, 0, 100, TimeUnit.MILLISECONDS)
}
}
fun sendYaw(yawMax: Double,tNumber: Int,tPi:Double){
flag = false
var times = -1
virtualStickVM.virtualStickAdvancedParam.value?.let { param ->
var i = 0.0
val task3 = Runnable {
//需要执行的任务
param.pitch = 0.0
param.roll = 0.0
param.yaw = calculateSineWave(yawMax,tPi * Math.PI,i,0.0)
virtualStickVM.sendVirtualStickAdvancedParam(param)
virtualStickVM.virtualStickAdvancedParam.postValue(param)
i+=0.1
if(param.yaw * calculateSineWave(yawMax,tPi * Math.PI,i,0.0) < 0){
times++
}
if(param.yaw == 0.0) times++
if(times==tNumber*2){
flag = true
taskManager?.pauseTask3()
}
}
taskManager?.startTask3(task3, 0, 100, TimeUnit.MILLISECONDS)
}
}
fun sendThrottle(throttleTime: Double,throttlePitch: Double){
val task = object : TimerTask() {
override fun run() {
// 在这里定义任务的行为
flag = true
taskManager?.pauseTask0()
}
}
val timer = Timer()
timer.schedule(task, throttleTime.toLong() * 1000)
virtualStickVM.virtualStickAdvancedParam.value?.let { param ->
try {
val task0 = Runnable {
//需要执行的任务
param.pitch = throttlePitch
param.roll = 0.0
param.yaw = 0.0
virtualStickVM.sendVirtualStickAdvancedParam(param)
virtualStickVM.virtualStickAdvancedParam.postValue(param)
}
taskManager?.startTask0(task0, 0, 100, TimeUnit.MILLISECONDS)
} catch (e: NumberFormatException) {
// 处理数字格式异常
Log.e("VirtualStick", "Invalid number format", e)
ToastUtils.showToast("请补全参数", Toast.LENGTH_SHORT)
}
}
}
fun sendTurn(turnTime: Double,turnYaw: Double){
val task = object : TimerTask() {
override fun run() {
// 在这里定义任务的行为
flag = true
taskManager?.pauseTask0()
}
}
val timer = Timer()
timer.schedule(task, turnTime.toLong() * 1000)
virtualStickVM.virtualStickAdvancedParam.value?.let { param ->
try {
val task0 = Runnable {
//需要执行的任务
param.pitch = 0.0
param.roll = 0.0
param.yaw = turnYaw
virtualStickVM.sendVirtualStickAdvancedParam(param)
virtualStickVM.virtualStickAdvancedParam.postValue(param)
}
taskManager?.startTask0(task0, 0, 100, TimeUnit.MILLISECONDS)
} catch (e: NumberFormatException) {
// 处理数字格式异常
Log.e("VirtualStick", "Invalid number format", e)
ToastUtils.showToast("请补全参数", Toast.LENGTH_SHORT)
}
}
}
fun read(){
val filename = fileName.text.toString().trim()
if (filename.isEmpty()) {
ToastUtils.showToast("请输入文件名", Toast.LENGTH_SHORT)
}else{
val file = FileUtils(requireContext(),filename)
fom_info_tv.text = file.readAll()
}
}
btn_save_pitch.setOnClickListener {
val filename = fileName.text.toString().trim()
if (filename.isEmpty()) {
ToastUtils.showToast("请输入文件名", Toast.LENGTH_SHORT)
}else{
val file = FileUtils(requireContext(),filename)
try {
number = nPitch.text.toString().toInt()
pi = sPitch.text.toString().toDouble()
max = mPitch.text.toString().toDouble()
}catch (e: NumberFormatException) {
ToastUtils.showToast("请补全参数", Toast.LENGTH_SHORT)
}
val message = "俯仰 ${number} 组 周期 ${pi} π 最大值 ${max}"
file.writer(message,false)
read()
}
}
btn_save_roll.setOnClickListener {
val filename = fileName.text.toString().trim()
if (filename.isEmpty()) {
ToastUtils.showToast("请输入文件名", Toast.LENGTH_SHORT)
}else{
val file = FileUtils(requireContext(),filename)
try {
number = nRoll.text.toString().toInt()
pi = sRoll.text.toString().toDouble()
max = mRoll.text.toString().toDouble()
}catch (e: NumberFormatException) {
ToastUtils.showToast("请补全参数", Toast.LENGTH_SHORT)
}
val message = "横滚 ${number} 组 周期 ${pi} π 最大值 ${max}"
file.writer(message,false)
read()
}
}
btn_save_yaw.setOnClickListener {
val filename = fileName.text.toString().trim()
if (filename.isEmpty()) {
ToastUtils.showToast("请输入文件名", Toast.LENGTH_SHORT)
}else{
val file = FileUtils(requireContext(),filename)
try {
number = nYaw.text.toString().toInt()
pi = sYaw.text.toString().toDouble()
max = mYaw.text.toString().toDouble()
}catch (e: NumberFormatException) {
ToastUtils.showToast("请补全参数", Toast.LENGTH_SHORT)
}
val message = "偏航 ${number} 组 周期 ${pi} π 最大值 ${max}"
file.writer(message,false)
read()
}
}
btn_save_throttle.setOnClickListener {
val filename = fileName.text.toString().trim()
if (filename.isEmpty()) {
ToastUtils.showToast("请输入文件名", Toast.LENGTH_SHORT)
}else{
val file = FileUtils(requireContext(),filename)
try {
val time = sThrottle.text.toString().toDouble()
val throttle = throttlePitch.text.toString().toDouble()
val message = "平飞 ${time} 秒 角度 ${throttle}"
file.writer(message,false)
read()
}catch (e: NumberFormatException) {
ToastUtils.showToast("请补全参数", Toast.LENGTH_SHORT)
}
}
}
btn_save_turn.setOnClickListener {
val filename = fileName.text.toString().trim()
if (filename.isEmpty()) {
ToastUtils.showToast("请输入文件名", Toast.LENGTH_SHORT)
}else{
val file = FileUtils(requireContext(),filename)
try {
val turn = turn.text.toString().toDouble()
val message = "转弯 1.0 秒 角度 $turn"
file.writer(message,false)
read()
}catch (e: NumberFormatException) {
ToastUtils.showToast("请补全参数", Toast.LENGTH_SHORT)
}
}
}
btn_read.setOnClickListener {
read()
}
btn_fom.setOnClickListener {
// 获取当前文本
val currentText = fom_info_tv.text.toString()
// 按行拆分文本(假设文本中使用换行符 "\n" 分隔行)
val lines = currentText.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if(lines.size>0){
val param = lines[0].split(" ")
if (param[0].equals("俯仰")){
sendPitch(param[7].toDouble(),param[1].toInt(),param[4].toDouble())
}else if (param[0].equals("横滚")){
sendRoll(param[7].toDouble(),param[1].toInt(),param[4].toDouble())
}else if (param[0].equals("偏航")){
sendYaw(param[7].toDouble(),param[1].toInt(),param[4].toDouble())
}else if (param[0].equals("平飞")){
sendThrottle(param[1].toDouble(),param[4].toDouble())
}else if (param[0].equals("转弯")){
sendTurn(param[1].toDouble(),param[4].toDouble())
}
// 检查是否有多行文本
if (lines.size > 1) {
// 移除第一行并重新组合文本
val newText = lines.sliceArray(1 until lines.size).joinToString("\n")
// 设置新的文本
fom_info_tv.text = newText
} else {
// 如果只有一行,则清空文本或显示提示信息
fom_info_tv.text = "" // 或者 textView.text = "没有更多行可以移除"
}
}else{
ToastUtils.showToast("请重新读取", Toast.LENGTH_SHORT)
}
}
btn_listenPose.setOnClickListener {
KeyManager.getInstance().listen(createKey(FlightControllerKey.KeyAircraftAttitude),this
) { _, newValue -> logUtils.log(newValue.toString())
// 将新数据点添加到LineDataSet中
val newXValue = entries1.size + 1f // 假设新X值是前一个条目的大小加1
if (newValue != null) {
entries1.add(Entry(newXValue, newValue.pitch.toFloat(), null))
} // getNewYValue 是一个假设的函数,用于生成新的Y值
if (newValue != null) {
entries2.add(Entry(newXValue, newValue.roll.toFloat(), null))
} // 示例:为entries2添加稍微不同的Y值
if (newValue != null) {
entries3.add(Entry(newXValue, newValue.yaw.toFloat(), null))
} // 示例:为entries3添加稍微不同的Y值
virtualStickVM.virtualStickAdvancedParam.value?.let { param ->
logUtilsSend.log(param.toString())
entries10.add(Entry(newXValue, param.pitch.toFloat(), null))
entries20.add(Entry(newXValue, param.roll.toFloat(), null))
entries30.add(Entry(newXValue, param.yaw.toFloat(), null))
}
// 更新数据集
val dataSet1 = getDataSet(entries1, "pitch")
val dataSet2 = getDataSet(entries2, "roll")
val dataSet3 = getDataSet(entries3, "yaw")
val dataSet10 = getDataSet0(entries10, "pitchSet")
val dataSet20 = getDataSet0(entries20, "rollSet")
val dataSet30 = getDataSet0(entries30, "yawSet")
// 创建一个新的LineData对象并设置给图表
val lineData = LineData(dataSet1, dataSet2, dataSet3,dataSet10, dataSet20, dataSet30)
lineChart.data = lineData
// 通知图表更新(这里我们直接调用invalidate())
lineChart.invalidate() // 这将导致图表重绘,并显示所有LineDataSet中的最新数据
lineChart.moveViewToX(newXValue)
}
}
private fun initStickListener() {
left_stick_view.setJoystickListener(object : OnScreenJoystickListener {
override fun onTouch(joystick: OnScreenJoystick?, pX: Float, pY: Float) {
var leftPx = 0F
var leftPy = 0F
if (abs(pX) >= deviation) {
leftPx = pX
}
if (abs(pY) >= deviation) {
leftPy = pY
}
virtualStickVM.setLeftPosition(
(leftPx * Stick.MAX_STICK_POSITION_ABS).toInt(),
(leftPy * Stick.MAX_STICK_POSITION_ABS).toInt()
)
}
})
right_stick_view.setJoystickListener(object : OnScreenJoystickListener {
override fun onTouch(joystick: OnScreenJoystick?, pX: Float, pY: Float) {
var rightPx = 0F
var rightPy = 0F
if (abs(pX) >= deviation) {
rightPx = pX
}
if (abs(pY) >= deviation) {
rightPy = pY
}
virtualStickVM.setRightPosition(
(rightPx * Stick.MAX_STICK_POSITION_ABS).toInt(),
(rightPy * Stick.MAX_STICK_POSITION_ABS).toInt()
)
}
})
}
private fun updateVirtualStickInfo() {
val builder = StringBuilder()
builder.append("Speed level:").append(virtualStickVM.currentSpeedLevel.value)
builder.append("\n")
builder.append("Use rc stick as virtual stick:").append(virtualStickVM.useRcStick.value)
builder.append("\n")
builder.append("Is virtual stick enable:").append(virtualStickVM.currentVirtualStickStateInfo.value?.state?.isVirtualStickEnable)
builder.append("\n")
builder.append("Current control permission owner:").append(virtualStickVM.currentVirtualStickStateInfo.value?.state?.currentFlightControlAuthorityOwner)
builder.append("\n")
builder.append("Change reason:").append(virtualStickVM.currentVirtualStickStateInfo.value?.reason)
builder.append("\n")
builder.append("Rc stick value:").append(virtualStickVM.stickValue.value?.toString())
builder.append("\n")
builder.append("Is virtual stick advanced mode enable:").append(virtualStickVM.currentVirtualStickStateInfo.value?.state?.isVirtualStickAdvancedModeEnabled)
builder.append("\n")
builder.append("Virtual stick advanced mode param:").append(virtualStickVM.virtualStickAdvancedParam.value?.toJson())
builder.append("\n")
mainHandler.post {
virtual_stick_info_tv.text = builder.toString()
}
}
}
你好,我现在实现的是利用虚拟摇杆实现无人机四边FOM飞行,就是在一个正方形路径中做俯仰横滚偏航等动作,现在我遇到的问题是:1.代码中可以看到我有设置转弯90°,但是转弯90°以后,我再次点俯仰横滚动作时,飞机没有按照转弯后的方向继续做,而是回到第一条边的方向,自动转回初始方向了。2.转弯90°,也就是偏航90°,怎样设置成参照机头方向转弯,目前我只能第一个弯转90°,第二个弯180°没第三个就无法解决了,我在遥控器的虚拟摇杆高高级模式坐标系设置的是BODY模式。3.我想要实现每0.1秒回传一次姿态数据,数据包括无人机发送的数据和无人机响应的数据,怎样可以卡着0.1s回传这些数据呢?
麻烦您帮忙解答,谢谢。
-
1.代码中可以看到我有设置转弯90°,但是转弯90°以后,我再次点俯仰横滚动作时,飞机没有按照转弯后的方向继续做,而是回到第一条边的方向,自动转回初始方向了。 -->您在控制俯仰和横滚的时候传入的参数设置Yaw值,那么飞机将会按照设置参数转动到对应的角度。您可以把Yaw值设置成飞机的当前角度、 2.转弯90°,也就是偏航90°,怎样设置成参照机头方向转弯,目前我只能第一个弯转90°,第二个弯180°没第三个就无法解决了,我在遥控器的虚拟摇杆高高级模式坐标系设置的是BODY模式。 -->第三个角度飞机朝向的是-90度。 3.我想要实现每0.1秒回传一次姿态数据,数据包括无人机发送的数据和无人机响应的数据,怎样可以卡着0.1s回传这些数据呢? -->你使用的是什么无人机? 还有一个问题是怎样回传无人机的高度值呢? -->使用KeyAircraftLocation3D去获取飞机的位置和相对高度:https://developer.dji.com/cn/api-reference-v5/android-api/Components/IKeyManager/Key_FlightController_FlightControllerKey.html#key_flightcontroller_aircraftlocation3d_inline
Please sign in to leave a comment.
Comments
4 comments