Rockchip系列之CAN APP测试应用实现(4)

简介: Rockchip系列之CAN APP测试应用实现(4)

Rockchip系列之深度分析CAN接口系列(1)_一歲抬頭的博客-CSDN博客

Rockchip系列之CAN 新增framework系统jni接口访问(2)-CSDN博客

Rockchip系列之CAN 新增framework封装service+manager访问(3)-CSDN博客

Rockchip系列之CAN APP测试应用实现(4)_一歲抬頭的博客-CSDN博客

Rockchip CAN 部分波特率收发不正常解决思路_一歲抬頭的博客-CSDN博客

Android JNI与CAN通信遇到的问题总结_android can通信-CSDN博客

Android 内核关闭CAN 串口设备回显功能_一歲抬頭的博客-CSDN博客

在上一篇博客中,介绍了如何在framework层编写一个服务类CanService,用于提供CAN通信的接口给其他应用程序或模块。还介绍了如何使用CanService类的封装类SystemCan,用于简化CAN通信的操作和配置。

在这篇博客中,将介绍如何在Android平台上开发一个App,用于测试和演示CAN通信的功能。将分别介绍App的界面设计,逻辑实现和使用方法。

这个App比较简单 只要核心接口写完了 应用端写成花都可以 目前实现了以下的功能:

  • 可以选择不同的CAN设备和波特率,打开和关闭CAN总线
  • 可以输入要发送的CAN数据,向CAN设备发送一帧数据
  • 可以接收来自CAN设备的数据,并显示在列表中
  • 可以清除已经接收到的数据

App的界面设计

使用Android Studio来开发的App,首先创建一个新的项目,命名为CanApp,并选择Empty Activity作为模板。然后,在res/layout/activity_main.xml文件中,定义了App的界面布局,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">
 
    <TextView
        android:id="@+id/canTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CAN Interface:"
        android:textSize="18sp" />
 
    <Spinner
        android:id="@+id/canSpinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
 
    <TextView
        android:id="@+id/baudrateTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Baudrate:"
        android:textSize="18sp"
        android:layout_marginTop="16dp" />
 
    <Spinner
        android:id="@+id/baudrateSpinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
 
    <TextView
        android:id="@+id/dataTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Data:"
        android:textSize="18sp"
        android:layout_marginTop="16dp" />
 
    <EditText
        android:id="@+id/dataEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Enter data"
        android:text="1122334455667788"
        android:inputType="text" />
 
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/sendButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send"
            android:layout_margin="8dp" />
 
        <Button
            android:id="@+id/openCanButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Open CAN"
            android:layout_margin="8dp" />
 
        <Button
            android:id="@+id/closeCanButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Close CAN"
            android:layout_margin="8dp" />
 
        <Button
            android:id="@+id/clearButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/openCanButton"
            android:layout_alignStart="@id/openCanButton"
            android:layout_margin="8dp"
            android:text="Clear Received Data" />
    </LinearLayout>
 
    <ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:fillViewport="true">
 
        <ListView
            android:id="@+id/receivedDataListView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:divider="@android:color/darker_gray"
            android:dividerHeight="0.5dp"
            android:padding="8dp"
            android:scrollbarStyle="outsideOverlay"
            android:scrollbars="vertical" />
 
    </ScrollView>
 
</LinearLayout>

这个布局文件定义了一个垂直方向的线性布局。

APP运行效果,如下图所示:

(我写这篇博客的时候 运行环境不在身边 截图以前的视频吧)

App的逻辑实现

在src/main/java/com/example/canapp/MainActivity.java文件中实现了App的逻辑功能,如下所示:

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener, View.OnClickListener {
    private static final String TAG = "MainActivity";
    private static final int RECEIVE_CAN_DATA = 1;
 
    private Spinner canSpinner;
    private Spinner baudrateSpinner;
    private EditText dataEditText;
    private Button sendButton;
    private Button openCanButton;
    private Button closeCanButton;
    private Button clearButton;
    private ListView receivedDataListView;
    private ReceivedDataAdapter receivedDataAdapter;
    private ScrollView scrollView;
 
    private List<String> canList;
    private List<String> baudrateList;
    private String selectedCan;
    private int selectedBaudrate;
    private boolean isCanOpen = false;
 
    private SystemCan systemCan;
 
    private List<String> receivedDataList;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == RECEIVE_CAN_DATA) {
                Bundle bundle = msg.getData();
                String data = bundle.getString("data");
                String timestamp = getCurrentTimestamp();
                String message = timestamp + " 收到数据: " + data;
                receivedDataList.add(message);
 
                receivedDataAdapter.notifyDataSetChanged();
                scrollToBottom();
            }
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        canSpinner = findViewById(R.id.canSpinner);
        baudrateSpinner = findViewById(R.id.baudrateSpinner);
        dataEditText = findViewById(R.id.dataEditText);
        sendButton = findViewById(R.id.sendButton);
        openCanButton = findViewById(R.id.openCanButton);
        closeCanButton = findViewById(R.id.closeCanButton);
        clearButton = findViewById(R.id.clearButton);
        receivedDataListView = findViewById(R.id.receivedDataListView);
        scrollView = findViewById(R.id.scrollView);
 
        canList = new ArrayList<>();
        canList.add("can1");
        canList.add("can0");
        ArrayAdapter<String> canAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, canList);
        canAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        canSpinner.setAdapter(canAdapter);
        canSpinner.setOnItemSelectedListener(this);
 
        baudrateList = new ArrayList<>();
        baudrateList.add("10000");
        baudrateList.add("20000");
        baudrateList.add("40000");
        baudrateList.add("50000");
        baudrateList.add("100000");
        baudrateList.add("125000");
        baudrateList.add("250000");
        baudrateList.add("500000");
        baudrateList.add("666000");
        baudrateList.add("800000");
        baudrateList.add("1000000");
        ArrayAdapter<String> baudrateAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, baudrateList);
        baudrateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        baudrateSpinner.setAdapter(baudrateAdapter);
        baudrateSpinner.setOnItemSelectedListener(this);
 
        sendButton.setOnClickListener(this);
        openCanButton.setOnClickListener(this);
        closeCanButton.setOnClickListener(this);
        clearButton.setOnClickListener(this);
 
        receivedDataList = new ArrayList<>();
        receivedDataAdapter = new ReceivedDataAdapter(this, receivedDataList);
        receivedDataListView.setAdapter(receivedDataAdapter);
 
        //
        startCanReceiverThread();
    }
 
 
    private void startCanReceiverThread() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
 
                new Thread() {
                    long[] ret = new long[12];
                    @Override
                    public void run() {
                        while (true) {
                            if (isCanOpen) {
                                try {
                                    ret = systemCan.dev_receiveCan(systemCan.fd);
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }
                                long canId = ret[0];
                                long canEff = ret[1];
                                long canRtr = ret[2];
                                long canLen = ret[3];
                                long[] canData = Arrays.copyOfRange(ret, 4, (int) (4 + canLen));
                                String str = "can RX ";
                                str += (canEff == 0) ? "S " : "E ";
                                str += (canRtr == 0) ? "-  " : "R  ";
                                String canIdStr = Long.toHexString(canId);
                                if (canEff == 0) {
                                    for (int i = 0; i < 3 - canIdStr.length(); i++) {
                                        canIdStr = '0' + canIdStr;
                                    }
                                } else {
                                    for (int i = 0; i < 8 - canIdStr.length(); i++) {
                                        canIdStr = '0' + canIdStr;
                                    }
                                }
                                str = str + canIdStr + "   [" + Long.toString(canLen) + "]  ";
                                for (int i = 0; i < canLen; i++) {
                                    String hex = Long.toHexString(canData[i]);
                                    hex = (hex.length() == 1) ? ('0' + hex) : hex;
                                    str = str + ' ' + hex;
                                }
                                str = str.toUpperCase();
                                str += '\n';
                                String finalStr = str;
                                Log.d(TAG, "Received CAN data: " + finalStr);
                                Message message = handler.obtainMessage(RECEIVE_CAN_DATA);
                                Bundle bundle = new Bundle();
                                bundle.putString("data", finalStr);
                                message.setData(bundle);
                                handler.sendMessage(message);
                            }
                        }
                    }
                }.start();
            }
        });
        thread.start();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        closeCan();
    }
 
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        int viewId = parent.getId();
        if (viewId == R.id.canSpinner) {
            selectedCan = canList.get(position);
        } else if (viewId == R.id.baudrateSpinner) {
            String selectedBaudrateStr = baudrateList.get(position);
            selectedBaudrate = Integer.parseInt(selectedBaudrateStr);
        }
    }
 
    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        // Do nothing
    }
 
    @Override
    public void onClick(View v) {
        int viewId = v.getId();
        if (viewId == R.id.sendButton) {
            sendCanData();
        } else if (viewId == R.id.openCanButton) {
            openCan();
        } else if (viewId == R.id.closeCanButton) {
            closeCan();
        } else if (viewId == R.id.clearButton) {
            clearReceivedData();
        }
    }
 
    private void sendCanData() {
        if (!isCanOpen) {
            showToast("请先打开 CAN 总线");
            return;
        }
 
        String data = dataEditText.getText().toString().trim();
        if (data.isEmpty()) {
            showToast("请输入要发送的数据");
            return;
        }
 
        // Convert hex string to byte array
        byte[] dataArray = hexStringToByteArray(data);
        if (dataArray == null) {
            showToast("发送的数据格式不正确");
            return;
        }
 
        try {
            int result = systemCan.dev_sendCan(systemCan.fd, 0, 0, 0, dataArray.length, byteArrayToIntArray(dataArray));
            if (result != 0) {
                showToast("发送数据失败");
            } else {
                showToast("发送数据成功");
            }
        } catch (Exception e) {
            showToast("发送数据出现异常");
            Log.e(TAG, "Error sending CAN data: " + e.getMessage());
        }
    }
 
    private void openCan() {
        if (isCanOpen) {
            showToast("CAN 总线已经打开");
            return;
        }
        try {
            systemCan = new SystemCan(selectedCan, selectedBaudrate);
            int fd = systemCan.dev_openCan(selectedCan);
            if (fd != -1) {
                systemCan.fd = fd;
                showToast("打开 CAN 总线成功");
                openCanButton.setEnabled(false);
                isCanOpen = true;
            } else {
                showToast("打开 CAN 总线失败");
            }
        } catch (Exception e) {
            showToast("打开 CAN 总线出现异常");
            Log.e(TAG, "Error opening CAN: " + e.getMessage());
        }
        // 添加了额外的参数tq(时间量化器),propSeg(传播段时间),phaseSeg1(相位段1时间),phaseSeg2(相位段2时间)和sjw(同步跳转宽度)
        /*runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ShellUtils.CommandResult commandResult = ShellUtils.execCommand("ip link set " + selectedCan + " down && ip link set " + selectedCan + " type can tq 133 prop-seg 6 phase-seg1 6 phase-seg2 2 sjw 1 && ip link set  " + selectedCan + "  up", true);
                //Log.d("SystemCan-test", + commandResult.result + "," + commandResult.successMsg + "," + commandResult.errorMsg);
            }
        });*/
    }
 
    private void closeCan() {
        if (!isCanOpen) {
            showToast("CAN 总线已经关闭");
            openCanButton.setEnabled(true);
 
            return;
        }
 
        try {
            int result = systemCan.dev_closeCan(systemCan.fd);
            if (result != 0) {
                showToast("关闭 CAN 总线失败");
            } else {
                showToast("关闭 CAN 总线成功");
                openCanButton.setEnabled(true);
                isCanOpen = false;
            }
        } catch (Exception e) {
            showToast("关闭 CAN 总线出现异常");
            Log.e(TAG, "Error closing CAN: " + e.getMessage());
        }
    }
 
    private void clearReceivedData() {
        receivedDataList.clear();
        receivedDataAdapter.notifyDataSetChanged();
    }
 
    private String getCurrentTimestamp() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        return sdf.format(new Date());
    }
 
    private void scrollToBottom() {
        scrollView.postDelayed(new Runnable() {
            @Override
            public void run() {
                scrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        }, 100);
    }

在src/main/java/com/example/canapp/ReceivedDataAdapter.java文件中实现了App的can 接收数据DataAdapter功能,如下所示:

public class ReceivedDataAdapter extends ArrayAdapter<String> {
    private Context context;
    private List<String> dataList;
 
    public ReceivedDataAdapter(Context context, List<String> dataList) {
        super(context, 0, dataList);
        this.context = context;
        this.dataList = dataList;
    }
 
    @Override
    public int getCount() {
        return dataList.size();
    }
 
    @Override
    public String getItem(int position) {
        return dataList.get(position);
    }
 
    @Override
    public long getItemId(int position) {
        return position;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
 
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.list_item_received_data, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.timestampTextView = convertView.findViewById(R.id.timestampTextView);
            viewHolder.dataTextView = convertView.findViewById(R.id.dataTextView);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
 
        String data = dataList.get(position);
        String[] parts = data.split("收到数据:");
        String timestamp = parts[0];
        String canData = parts[1];
 
        viewHolder.timestampTextView.setText(timestamp);
        viewHolder.dataTextView.setText(canData);
 
        return convertView;
    }
 
    private static class ViewHolder {
        TextView timestampTextView;
        TextView dataTextView;
    }
}

App的使用方法

可以使用Android Studio来运行的App,并在真机上进行测试。需要先打开CAN总线,然后输入要发送的数据,点击发送按钮,就可以向CAN设备发送一帧数据。还可以在ListView中查看接收到的数据列表,并清除已经接收到的数据。可以通过Spinner来切换不同的CAN设备和波特率,但是需要先关闭当前的CAN总线,再打开新的CAN总线。可以在Logcat中查看App的日志信息,以及发送和接收到的CAN数据。

我希望它能够帮助你和其他人了解和适配CAN通信的功能。如果你有任何问题,欢迎留言 & 支持三连 ~

相关文章
|
29天前
|
机器学习/深度学习 自然语言处理 API
query改写:大模型应用测试离不开的实践
queryrewrite 是一个用于大模型应用测试的 Python 库,专注于查询(query)的改写与验证。它支持多种改写方法,包括大型语言模型(LLM)、词汇表替换和同义词替换,同时提供多种验证方法如 ROUGE-L、BLEU、帕累托最优和LLM语义相似度,以确保改写后的查询在语义上保持一致。该项目特别优化了对中文文本的处理,涵盖分词和相似度计算。用户可通过 pip 安装,并支持扩展不同的 LLM 模型,如 OpenAI、Ollama 等。
292 87
query改写:大模型应用测试离不开的实践
|
29天前
|
JSON 自然语言处理 算法
大模型应用测试必备技能:问题对生成实践
本文介绍了利用LangChain的QAGenerationChain从文本生成问题-答案对(QA pairs)的方法,旨在解决LLM应用开发中测试数据生成的格式不统一、库版本过时、模型输出异常及代码可维护性差等问题。文中提供了完整的代码实现,并对生成结果进行了有效性评估,包括语义相似度检查、关键词匹配和重复性检测,确保生成的QA对质量可靠,适用于知识库测试与评估。
242 86
|
4月前
|
存储 人工智能 测试技术
HarmonyOS Next~HarmonyOS应用测试全流程解析:从一级类目上架到二级类目专项测试
本文深入解析HarmonyOS应用测试全流程,涵盖从一级类目通用测试到二级类目专项测试的技术方案。针对兼容性、性能、安全测试及分布式能力验证等关键环节,提供详细实践指导与代码示例。同时,结合典型案例分析常见问题及优化策略,帮助开发者满足华为严苛的质量标准,顺利上架应用。文章强调测试在开发中的核心地位,助力打造高品质HarmonyOS应用。
204 2
|
2月前
|
Java Shell Maven
【Azure Container App】构建Java应用镜像时候遇无法编译错误:ERROR [build 10/10] RUN ./mvnw.cmd dependency:go-offline -B -Dproduction package
在部署Java应用到Azure Container App时,构建镜像过程中出现错误:“./mvnw.cmd: No such file or directory”。尽管项目根目录包含mvnw和mvnw.cmd文件,但依然报错。问题出现在Dockerfile构建阶段执行`./mvnw dependency:go-offline`命令时,系统提示找不到可执行文件。经过排查,确认是mvnw文件内容异常所致。最终通过重新生成mvnw文件解决该问题,镜像成功构建。
|
4月前
|
安全 测试技术 Linux
Flawnter 5.9.1 (macOS, Linux, Windows) - 应用程序安全测试软件
Flawnter 5.9.1 (macOS, Linux, Windows) - 应用程序安全测试软件
125 2
Flawnter 5.9.1 (macOS, Linux, Windows) - 应用程序安全测试软件
|
2月前
|
存储 移动开发 监控
App Trace功能实战:一键拉起、快速安装与免提写邀请码的应用实践
App Trace系统通过一键拉起、快速安装和免提写邀请码三大功能,显著提升用户转化率、安装成功率和邀请注册率。结合深度技术实现与优化,助力公司用户增长,成为移动端核心基础设施。
|
4月前
|
测试技术 数据库 Python
解释测试中setup和teardown函数的应用。
总结起来,`setup`和 `teardown`函数就像扔宴会的主人,他们保障了宴会的流畅进行。他们是准备环境和清理现场的重要工作人员,他们的工作直接影响着我们的测试效率和质量。我们可以把 `setup`和 `teardown`想象成隐藏在幕后,默默为我们服务的工作者,他们做着我们需要但是往往忽视的工作。所以,下次当你写测试的时候,别忘了给你的 `setup`和 `teardown`留出足够的位置,因为他们的作用可能是你成功的保证。
100 14
|
7月前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
356 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
4月前
|
搜索推荐 API UED
淘宝/天猫获得淘宝app商品详情原数据 API 返回值的应用
该API专注于商品信息整合与展示,提供基础信息抓取、多媒体内容整合等功能,助力实时同步商品数据,构建丰富的详情页。同时支持数据分析与市场洞察,包括销售趋势分析和竞品对比,优化库存与定价策略。此外,动态促销管理和个性化推荐系统可提升营销效果,而实时库存预警和评价数据可视化则显著增强用户体验,为用户决策提供透明依据,全面提升平台竞争力与用户满意度。
|
4月前
|
存储 5G 测试技术
时钟同步测试校验仪的应用介绍
时间同步测试仪是一种高精度、高可靠性的设备,用于测量和评估时间同步系统的性能。它广泛应用于电力系统(如电网调度、继电保护)、通信网络(如5G基站、光传输网络)、铁路交通(如列车运行控制、信号系统)、工业自动化(如生产线、控制系统)以及科学研究(如天文观测、粒子物理实验)等领域。其功能包括高精度时间测量、多信号接口支持、自动测量与分析、数据存储导出及性能评估输出,确保各领域设备间的时间同步精度与稳定性,保障系统高效运行。