Compare commits
7 Commits
FW_NEWAPP1
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
0ad8de776d | 3 weeks ago |
|
|
e4ac18e12f | 3 weeks ago |
|
|
3f7ae4a680 | 3 weeks ago |
|
|
0afbb377b6 | 3 weeks ago |
|
|
e4c03728b4 | 3 weeks ago |
|
|
7ded020ef3 | 3 weeks ago |
|
|
6aa318858e | 3 weeks ago |
32 changed files with 3951 additions and 1047 deletions
@ -0,0 +1,2 @@ |
|||
#Mon Dec 22 02:41:36 CST 2025 |
|||
java.home=F\:\\Program Files\\Android\\Android Studio\\jbr |
|||
@ -0,0 +1 @@ |
|||
fivewheel |
|||
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="VcsDirectoryMappings"> |
|||
<mapping directory="" vcs="Git" /> |
|||
</component> |
|||
</project> |
|||
@ -1,23 +1,28 @@ |
|||
syntax = "proto3"; |
|||
option java_multiple_files = false; |
|||
option java_package = "com.example.removemarineanimals.models"; |
|||
option java_package = "com.example.fivewheel.models"; |
|||
|
|||
message IV_struct_define{ |
|||
|
|||
// 五轮项目 |
|||
int32 Robot_Move_AutoSpeed= 1; |
|||
int32 Robot_Move_ManualSpeed= 2; |
|||
int32 Robot_CurrentPosition= 3; |
|||
int32 Robot_AngleRoll= 4; |
|||
int32 Robot_Error= 5; |
|||
int32 Robot_DynamometerValue= 6; |
|||
int32 Robot_ForceValue= 7; |
|||
int32 Robot_CurrentState= 8; |
|||
int32 Robot_Current_Left= 9; |
|||
int32 Robot_Current_Right= 10; |
|||
int32 Robot_Error_Left = 11; |
|||
int32 Robot_Error_Right = 12; |
|||
int32 Robot_Compensation_Left = 13; |
|||
int32 Robot_Compensation_Right = 14; |
|||
int32 Robot_RESET = 15; |
|||
// 五轮项目 |
|||
int32 Robot_Move_AutoSpeed= 1; |
|||
int32 Robot_Move_ManualSpeed= 2; |
|||
int32 Robot_CurrentPosition= 3; |
|||
int32 Robot_AngleRoll= 4; |
|||
int32 Robot_Error= 5; |
|||
int32 Robot_DynamometerValue= 6; |
|||
int32 Robot_ForceValue= 7; |
|||
int32 Robot_CurrentState= 8; |
|||
int32 Robot_Current_Left= 9; |
|||
int32 Robot_Current_Right= 10; |
|||
int32 Robot_Error_Left= 11; |
|||
int32 Robot_Error_Right= 12; |
|||
int32 Robot_Compensation_Left= 13; |
|||
int32 Robot_Compensation_Right= 14; |
|||
int32 Robot_RESET = 15; |
|||
int64 TimeStamp=16; |
|||
int32 RobotRestart=17; |
|||
int32 RobotAngle=18; |
|||
int32 SystemError=19; |
|||
}; |
|||
|
|||
@ -1,12 +1,15 @@ |
|||
syntax = "proto3"; |
|||
|
|||
option java_multiple_files = false; |
|||
option java_package = "com.example.removemarineanimals.models"; |
|||
option java_package = "com.example.fivewheel.models"; |
|||
message PV_struct_define{ |
|||
|
|||
int32 Robot_ChgLength= 1; |
|||
double Robot_AutoSpeedBase=2; |
|||
double Robot_ManualSpeedBase=3; |
|||
int32 Robot_LaneChange_Direction = 4; |
|||
int32 Robot_Force = 5; |
|||
int32 Robot_ChgLength= 1; |
|||
double Robot_AutoSpeedBase=2; |
|||
double Robot_ManualSpeedBase=3; |
|||
int32 Robot_LaneChange_Direction = 4; |
|||
int32 Robot_Force = 5; |
|||
int64 TimeStamp=6;//上位机发送下来的时间戳 |
|||
int32 RobotRestartAccepted=7;//上位机接收到单片机发送的Restart信号 |
|||
int32 Robot_AutoWork = 8; |
|||
}; |
|||
|
|||
@ -0,0 +1,57 @@ |
|||
package com.example.fivewheel; |
|||
|
|||
import static com.example.fivewheel.viewmodels.MainViewModel.mainBinding; |
|||
|
|||
import android.content.Context; |
|||
|
|||
import androidx.room.Database; |
|||
import androidx.room.Room; |
|||
import androidx.room.RoomDatabase; |
|||
|
|||
@Database(entities = {RobotLog.class}, version = 1, exportSchema = false) |
|||
public abstract class AppDatabase extends RoomDatabase { |
|||
|
|||
public abstract RobotLogDao robotLogDao(); |
|||
|
|||
private static volatile AppDatabase INSTANCE; |
|||
|
|||
public static AppDatabase getInstance(Context context) { |
|||
if (INSTANCE == null) { |
|||
synchronized (AppDatabase.class) { |
|||
if (INSTANCE == null) { |
|||
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), |
|||
AppDatabase.class, "robot_logs.db") |
|||
.fallbackToDestructiveMigration() // 数据库升级时清空数据,可根据需求改
|
|||
.build(); |
|||
} |
|||
} |
|||
} |
|||
return INSTANCE; |
|||
} |
|||
|
|||
// 在 MainActivity 中添加两个成员变量缓存电机状态
|
|||
private String lastLeftError = "正常"; |
|||
private String lastRightError = "正常"; |
|||
|
|||
// 在 TimerTask 或接收串口数据后调用的方法
|
|||
private void checkMotorErrors() { |
|||
String currentLeftError = mainBinding.tvLeftError.getText().toString().trim(); |
|||
String currentRightError = mainBinding.tvRightError.getText().toString().trim(); |
|||
|
|||
// 左电机状态变化且不正常时保存日志
|
|||
if (!currentLeftError.equals("正常") && !currentLeftError.equals(lastLeftError)) { |
|||
saveMotorError("左电机", currentLeftError); |
|||
} |
|||
lastLeftError = currentLeftError; |
|||
|
|||
// 右电机状态变化且不正常时保存日志
|
|||
if (!currentRightError.equals("正常") && !currentRightError.equals(lastRightError)) { |
|||
saveMotorError("右电机", currentRightError); |
|||
} |
|||
lastRightError = currentRightError; |
|||
} |
|||
|
|||
private void saveMotorError(String 左电机, String currentLeftError) { |
|||
} |
|||
|
|||
} |
|||
@ -1,335 +0,0 @@ |
|||
package com.example.removemarineanimals; |
|||
|
|||
import androidx.appcompat.app.AppCompatActivity; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.databinding.DataBindingUtil; |
|||
import androidx.lifecycle.ViewModelProvider; |
|||
|
|||
import android.app.PendingIntent; |
|||
import android.content.BroadcastReceiver; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.IntentFilter; |
|||
import android.hardware.usb.UsbDevice; |
|||
import android.hardware.usb.UsbDeviceConnection; |
|||
import android.hardware.usb.UsbManager; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
|
|||
import com.example.removemarineanimals.databinding.ActivityMainBinding; |
|||
import com.example.removemarineanimals.services.CustomProber; |
|||
//import com.example.removemarineanimals.services.USBSerialPortHelper; |
|||
import com.example.removemarineanimals.services.VideoHelper; |
|||
import com.example.removemarineanimals.viewmodels.MainViewModel; |
|||
import com.hoho.android.usbserial.driver.UsbSerialDriver; |
|||
import com.hoho.android.usbserial.driver.UsbSerialPort; |
|||
import com.hoho.android.usbserial.driver.UsbSerialProber; |
|||
import com.hoho.android.usbserial.util.SerialInputOutputManager; |
|||
|
|||
import android.os.Bundle; |
|||
import android.os.CountDownTimer; |
|||
import android.os.Handler; |
|||
import android.os.Looper; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.Iterator; |
|||
import java.util.List; |
|||
|
|||
import cn.nodemedia.NodePlayer; |
|||
|
|||
|
|||
public class MainActivity extends AppCompatActivity implements SerialInputOutputManager.Listener { |
|||
|
|||
|
|||
private enum UsbPermission {Unknown, Requested, Granted, Denied} |
|||
|
|||
private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB"; |
|||
private int deviceId = 60000; |
|||
private int deviceId_test = 60000; |
|||
private int portNum; |
|||
private static final int WRITE_WAIT_MILLIS = 500; |
|||
private static final int READ_WAIT_MILLIS = 1000; |
|||
private static String PortNameContians = "SILICON";/**/ |
|||
private int baudRate = 57600; |
|||
private boolean withIoManager = true; |
|||
|
|||
private BroadcastReceiver broadcastReceiver; |
|||
private Handler mainLooper; |
|||
|
|||
|
|||
private SerialInputOutputManager usbIoManager; |
|||
private UsbSerialPort usbSerialPort; |
|||
private UsbPermission usbPermission = UsbPermission.Unknown; |
|||
private boolean connected = false; |
|||
|
|||
|
|||
public void GetControlsReferences() { |
|||
broadcastReceiver = new BroadcastReceiver() { |
|||
@Override |
|||
public void onReceive(Context context, Intent intent) { |
|||
if (INTENT_ACTION_GRANT_USB.equals(intent.getAction())) { |
|||
usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) |
|||
? UsbPermission.Granted : UsbPermission.Denied; |
|||
connect(); |
|||
} |
|||
|
|||
|
|||
} |
|||
}; |
|||
mainLooper = new Handler(Looper.getMainLooper()); |
|||
|
|||
|
|||
_receiveBufferlist = new ArrayList<Byte>(); |
|||
} |
|||
|
|||
|
|||
public static ActivityMainBinding mainBinding;//通过Binding可以获取界面数据 |
|||
// public USBSerialPortHelper serialPortHelper; |
|||
|
|||
@Override |
|||
protected void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
// setContentView(R.layout.activity_main); |
|||
mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); |
|||
MainViewModel vm = new ViewModelProvider(this).get(MainViewModel.class); |
|||
MainViewModel.mainBinding = mainBinding; |
|||
// vm.mainBinding=mainBinding; |
|||
mainBinding.setVm(vm); |
|||
|
|||
|
|||
//nodePlayer0 =new NodePlayer(this); |
|||
//nodePlayer1 =new NodePlayer(this); |
|||
|
|||
VideoHelper.nodePlayerView0 = mainBinding.nodePlayerView0; |
|||
VideoHelper.nodePlayerView1 = mainBinding.nodePlayerView1; |
|||
|
|||
VideoHelper.nodePlayer0 = new NodePlayer(this); |
|||
VideoHelper.nodePlayer1 = new NodePlayer(this); |
|||
|
|||
|
|||
VideoHelper.StatPlayVideo(); |
|||
|
|||
// |
|||
// serialPortHelper=new USBSerialPortHelper(); |
|||
// serialPortHelper.MainActivity=this; |
|||
// serialPortHelper.intialize(); |
|||
|
|||
|
|||
GetControlsReferences(); |
|||
connect(); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
protected void onStart() { |
|||
super.onStart(); |
|||
ContextCompat.registerReceiver(this, broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB), ContextCompat.RECEIVER_NOT_EXPORTED); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onStop() { |
|||
this.unregisterReceiver(broadcastReceiver); |
|||
super.onStop(); |
|||
} |
|||
|
|||
@Override |
|||
public void onResume() { |
|||
super.onResume(); |
|||
if (!connected && (usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted)) { |
|||
//mainLooper.post(this::connect); |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onPause() { |
|||
if (connected) { |
|||
status("串口断开"); |
|||
// _serialPortSwitch.setChecked(false); |
|||
disconnect(); |
|||
} |
|||
super.onPause(); |
|||
} |
|||
|
|||
@Override |
|||
public void onNewData(byte[] data) { |
|||
mainLooper.post(() -> |
|||
{ |
|||
receive(data); |
|||
// receive data |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void onRunError(Exception e) { |
|||
mainLooper.post(() -> |
|||
{ |
|||
status("connection lost: " + e.getMessage()); |
|||
disconnect(); |
|||
}); |
|||
} |
|||
|
|||
private void connect() { |
|||
|
|||
UsbDevice device = null; |
|||
UsbManager usbManager = (UsbManager) this.getSystemService(Context.USB_SERVICE); |
|||
for (UsbDevice v : usbManager.getDeviceList().values()) { |
|||
status(v.getManufacturerName().toUpperCase()); |
|||
if (v.getManufacturerName().toUpperCase().contains(PortNameContians)) { |
|||
device = v; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (device == null) { |
|||
// _serialPortSwitch.setChecked(false); |
|||
|
|||
status("找不到设备"); |
|||
return; |
|||
} |
|||
UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device); |
|||
if (driver == null) { |
|||
driver = CustomProber.getCustomProber().probeDevice(device); |
|||
} |
|||
if (driver == null) { |
|||
// _serialPortSwitch.setChecked(false); |
|||
status("无驱动"); |
|||
return; |
|||
} |
|||
if (driver.getPorts().size() < portNum) //就是0 cp2102 或者同一个驱动,第一个 |
|||
{ |
|||
status("connection failed: not enough ports at device"); |
|||
status("找不到设备"); |
|||
return; |
|||
} |
|||
usbSerialPort = driver.getPorts().get(portNum); |
|||
|
|||
UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); |
|||
if (usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { |
|||
usbPermission = UsbPermission.Requested; |
|||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_MUTABLE : 0; |
|||
Intent intent = new Intent(INTENT_ACTION_GRANT_USB); |
|||
intent.setPackage(this.getPackageName()); |
|||
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(this, 0, intent, flags); |
|||
usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); |
|||
return; |
|||
} |
|||
if (usbConnection == null) { |
|||
if (!usbManager.hasPermission(driver.getDevice())) { |
|||
status("connection failed: permission denied"); |
|||
} else { |
|||
status("connection failed: open failed"); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
try { |
|||
usbSerialPort.open(usbConnection); |
|||
try { |
|||
usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); |
|||
} catch (UnsupportedOperationException e) { |
|||
status("unsupport setparameters"); |
|||
} |
|||
if (withIoManager) { |
|||
usbIoManager = new SerialInputOutputManager(usbSerialPort, this); |
|||
usbIoManager.setReadBufferSize(40960); |
|||
usbIoManager.setReadTimeout(READ_WAIT_MILLIS); |
|||
usbIoManager.start(); |
|||
} |
|||
//status("connected"); |
|||
connected = true; |
|||
// _serialPortSwitch.setChecked(true); |
|||
//switch set true |
|||
|
|||
} catch (Exception e) { |
|||
status("connection failed: " + e.getMessage()); |
|||
disconnect(); |
|||
} |
|||
} |
|||
|
|||
private void disconnect() { |
|||
connected = false; |
|||
|
|||
if (usbIoManager != null) { |
|||
usbIoManager.setListener(null); |
|||
usbIoManager.stop(); |
|||
} |
|||
usbIoManager = null; |
|||
try { |
|||
usbSerialPort.close(); |
|||
} catch (IOException ignored) |
|||
{ |
|||
|
|||
} |
|||
usbSerialPort = null; |
|||
} |
|||
|
|||
List<Byte> _receiveBufferlist; |
|||
|
|||
private byte[] listTobyte(List<Byte> list) { |
|||
if (list == null || list.size() < 0) |
|||
return null; |
|||
byte[] bytes = new byte[list.size()]; |
|||
int i = 0; |
|||
Iterator<Byte> iterator = list.iterator(); |
|||
while (iterator.hasNext()) { |
|||
bytes[i] = iterator.next(); |
|||
i++; |
|||
} |
|||
return bytes; |
|||
} |
|||
|
|||
public int Counter = 1000; |
|||
boolean StartCountDown = false; |
|||
|
|||
private void receive(byte[] data) { |
|||
|
|||
for (int i = 0; i < data.length; i++) { |
|||
_receiveBufferlist.add(data[i]); |
|||
} |
|||
|
|||
//decodeRceive(data); |
|||
if (StartCountDown == false) { |
|||
StartCountDown = true; |
|||
new CountDownTimer(500, 500) { |
|||
public void onTick(long millisUntilFinished) { |
|||
// Used for formatting digit to be in 2 digits only |
|||
|
|||
} |
|||
|
|||
// When the task is over it will print 00:00:00 there |
|||
public void onFinish() { |
|||
decodeRceive(listTobyte(_receiveBufferlist)); |
|||
_receiveBufferlist.clear(); |
|||
StartCountDown = false; |
|||
} |
|||
}.start(); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
private void decodeRceive(byte[] data) { |
|||
try { |
|||
|
|||
} catch ( |
|||
Exception e) { |
|||
//spn.append("exception:{e} "); |
|||
} |
|||
} |
|||
void status(String str) |
|||
{ |
|||
mainBinding.message.setText(str); |
|||
// SpannableStringBuilder spn = new SpannableStringBuilder(str + '\r' + '\n'); |
|||
// |
|||
// // spn.append(getTime()); |
|||
// |
|||
// spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorAccent)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
|||
// receiveText.append(spn); |
|||
// scrollView.fullScroll(ScrollView.FOCUS_DOWN); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,20 @@ |
|||
package com.example.fivewheel; |
|||
|
|||
import androidx.room.ColumnInfo; |
|||
import androidx.room.Entity; |
|||
import androidx.room.PrimaryKey; |
|||
|
|||
@Entity(tableName = "robot_logs") |
|||
public class RobotLog { |
|||
@PrimaryKey(autoGenerate = true) |
|||
public int id; |
|||
|
|||
@ColumnInfo(name = "type") // "error" 或 "param_change"
|
|||
public String type; |
|||
|
|||
@ColumnInfo(name = "message") // 具体信息:报错内容或修改了哪个参数
|
|||
public String message; |
|||
|
|||
@ColumnInfo(name = "time") // 时间戳
|
|||
public long time; |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
package com.example.fivewheel; |
|||
|
|||
import androidx.room.Dao; |
|||
import androidx.room.Insert; |
|||
import androidx.room.Query; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Dao |
|||
public interface RobotLogDao { |
|||
|
|||
@Insert |
|||
void insert(RobotLog log); |
|||
|
|||
|
|||
@Query("SELECT * FROM robot_logs ORDER BY time DESC") |
|||
List<RobotLog> getAllLogs(); |
|||
|
|||
@Query("DELETE FROM robot_logs WHERE time < :timeLimit") |
|||
void deleteLogsBefore(long timeLimit); |
|||
|
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,6 @@ |
|||
package com.example.fivewheel.services; |
|||
|
|||
|
|||
public enum CommunicationMethond { |
|||
Wireless, Wired |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
package com.example.fivewheel.services; |
|||
|
|||
import com.example.fivewheel.models.BspPV; |
|||
import com.example.fivewheel.models.BspIV; |
|||
import com.google.protobuf.InvalidProtocolBufferException; |
|||
|
|||
public class DataExchangeHelper { |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
public static int[] decodedCH = new int[17]; |
|||
static double slope = 1000.0 / (1950 - 1500); |
|||
|
|||
public static int[] getdecodedCH(byte[] receivedData) { |
|||
for (int i = 0; i < 16; i++) { |
|||
decodedCH[i] = ((receivedData[8 + i * 2] & 0xFF) | (receivedData[9 + i * 2] & 0xFF) << 8); |
|||
} |
|||
|
|||
for (int i = 0; i < 16; i++) { |
|||
decodedCH[i] = (int) Math.round(slope * (decodedCH[i] - 1500)); |
|||
} |
|||
return decodedCH; |
|||
} |
|||
|
|||
|
|||
public static byte[] getSendPVBytes(BspPV.PV_struct_define _toSendPV) { |
|||
byte[] byteArray = _toSendPV.toByteArray(); |
|||
byte[] sendbyteArray = new byte[byteArray.length + 4]; |
|||
byte[] sendbyteArray3 = new byte[byteArray.length + 6]; |
|||
if (byteArray.length != 0) { |
|||
System.arraycopy(byteArray, 0, sendbyteArray, 4, byteArray.length); |
|||
} |
|||
sendbyteArray[0] = (byte) 0x55; |
|||
sendbyteArray[1] = (byte) 0x55; |
|||
sendbyteArray[2] = (byte) 0x01; |
|||
sendbyteArray[3] = (byte) 0x01; |
|||
byte[] byteArray2 = ModbusCRC.calculateCRC(sendbyteArray); |
|||
System.arraycopy(sendbyteArray, 0, sendbyteArray3, 0, sendbyteArray.length); |
|||
System.arraycopy(byteArray2, 0, sendbyteArray3, sendbyteArray3.length - 2, 2); |
|||
return sendbyteArray3; |
|||
} |
|||
|
|||
public static BspIV.IV_struct_define getIVByBytes(byte[] data) { |
|||
byte[] crcbytes = new byte[data.length - 2]; |
|||
System.arraycopy(data, 0, crcbytes, 0, data.length - 2); |
|||
byte[] crc = ModbusCRC.calculateCRC(crcbytes); |
|||
// status(bytesToHex(data));
|
|||
if (data[data.length - 2] == (byte) (crc[1] & 0xff) && data[data.length - 1] == (byte) (crc[0] & 0xff)) { |
|||
if ((data[0] == 0x55) && (data[1] == 0x55)) { |
|||
byte[] bytes = new byte[data.length - 4]; |
|||
System.arraycopy(data, 2, bytes, 0, data.length - 4); |
|||
try { |
|||
BspIV.IV_struct_define _toReceiveIV = BspIV.IV_struct_define.parseFrom(bytes); |
|||
return _toReceiveIV; |
|||
} catch (InvalidProtocolBufferException ex) { |
|||
return null; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public static BspIV.IV_struct_define getIVByModbus() { |
|||
|
|||
try { |
|||
//定义100 是IV的数据开头数组个数 建立多少个字节数组
|
|||
byte[] bytes = new byte[ModbusRtuSlaveService.holdingRegisters[100]]; |
|||
int modifyHoldingRegisterNum = (bytes.length + 1) / 2; |
|||
for (int i = 0; i < modifyHoldingRegisterNum; i++) { |
|||
bytes[2 * i] = (byte) (ModbusRtuSlaveService.holdingRegisters[101 + i] >> 8); |
|||
if (2 * i + 1 > bytes.length - 1) { |
|||
break; |
|||
} |
|||
|
|||
bytes[2 * i + 1] = (byte) (ModbusRtuSlaveService.holdingRegisters[101 + i] & 0xff); |
|||
} |
|||
|
|||
BspIV.IV_struct_define _toReceiveIV = BspIV.IV_struct_define.parseFrom(bytes); |
|||
return _toReceiveIV; |
|||
} catch (InvalidProtocolBufferException ex) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
//_toSendPV 换算成bytes
|
|||
public static void setModbusPVValues(BspPV.PV_struct_define _toSendPV) { |
|||
|
|||
byte[] byteArray = _toSendPV.toByteArray(); |
|||
byte[] bytesToSend; |
|||
int modifyHoldingRegisterNum = (byteArray.length + 1) / 2; |
|||
|
|||
if (byteArray.length != 0) { |
|||
if (byteArray.length % 2 != 0) { |
|||
bytesToSend = new byte[byteArray.length + 1]; |
|||
bytesToSend[bytesToSend.length - 1] = 0; |
|||
|
|||
} else { |
|||
bytesToSend = new byte[byteArray.length]; |
|||
} |
|||
System.arraycopy(byteArray, 0, bytesToSend, 0, byteArray.length); |
|||
|
|||
ModbusRtuSlaveService.holdingRegisters[19] = (short) byteArray.length; |
|||
//将数据转为小端发送
|
|||
for (int i = 0; i < modifyHoldingRegisterNum; i++) { |
|||
|
|||
|
|||
ModbusRtuSlaveService.holdingRegisters[20 + i] = (short) ( |
|||
((bytesToSend[2 * i + 1] & 0xFF) << 8) | // 高位字节
|
|||
bytesToSend[2 * i] & 0xFF); // 低位字节
|
|||
|
|||
// (short) ((bytesToSend[2 * i + 1]) << 8 | ((short)bytesToSend[2 * i]));
|
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 判断字符串是否是数字 |
|||
*/ |
|||
public static boolean isNumber(String value) { |
|||
return isInteger(value) || isDouble(value); |
|||
} |
|||
|
|||
/** |
|||
* 判断字符串是否是整数 |
|||
*/ |
|||
public static boolean isInteger(String value) { |
|||
try { |
|||
Integer.parseInt(value); |
|||
return true; |
|||
} catch (NumberFormatException e) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断字符串是否是浮点数 |
|||
*/ |
|||
public static boolean isDouble(String value) { |
|||
try { |
|||
Double.parseDouble(value); |
|||
if (value.contains(".")) |
|||
return true; |
|||
return false; |
|||
} catch (NumberFormatException e) { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
package com.example.fivewheel.services; |
|||
|
|||
|
|||
import com.example.fivewheel.models.BspError; |
|||
|
|||
public class ErrorDeocdeHelper { |
|||
|
|||
//private static Map<String, Integer> flagMap = new HashMap<>();
|
|||
|
|||
// public static void AddMap(String errorName, Integer bitPosition)
|
|||
// {
|
|||
// flagMap.put(errorName,bitPosition);
|
|||
// }
|
|||
private static String ErrorResult; |
|||
private static String addErrorResult; |
|||
public static String ErrorDeocde(int data) |
|||
{ |
|||
ErrorResult=""; |
|||
// 循环遍历 Map
|
|||
// for (Map.Entry<String, Integer> entry : flagMap.entrySet()) {
|
|||
// String key = entry.getKey();
|
|||
// int bitPosiiton = entry.getValue();
|
|||
//
|
|||
// // 使用左移操作生成要检查的标志位
|
|||
// int bitToCheck = 1 << bitPosiiton;
|
|||
//
|
|||
// // 判断该位置是否有对应的标志位
|
|||
// if ((data & bitToCheck) != 0)
|
|||
// {
|
|||
// ErrorResult+=key+":\t 错误! ";
|
|||
// } else
|
|||
// {
|
|||
//
|
|||
// }
|
|||
// }
|
|||
|
|||
|
|||
for (BspError.ComError err : BspError.ComError.values()) { |
|||
|
|||
|
|||
int bitPosiiton = err.ordinal(); |
|||
if (err.toString().equals("UNRECOGNIZED")) continue; |
|||
if (err.toString().equals("TL720D")) |
|||
addErrorResult = " 作业模式已切换至手动"; |
|||
else |
|||
addErrorResult = " "; |
|||
|
|||
// 使用左移操作生成要检查的标志位
|
|||
int bitToCheck = 1 << bitPosiiton; |
|||
|
|||
// 判断该位置是否有对应的标志位
|
|||
if ((data & bitToCheck) != 0) |
|||
{ |
|||
|
|||
ErrorResult+=err.toString()+addErrorResult+"\t"; |
|||
} else |
|||
{ |
|||
|
|||
} |
|||
} |
|||
return ErrorResult; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,517 @@ |
|||
package com.example.fivewheel.services; |
|||
|
|||
import android.util.Log; |
|||
|
|||
import java.util.Arrays; |
|||
import java.util.concurrent.atomic.AtomicBoolean; |
|||
|
|||
public class ModbusRtuSlaveService { |
|||
public static void ModbusRtuSlaveServiceIntialize(USBSerialPortHelper serialPortHelper) { |
|||
_serialPortHelper = serialPortHelper; |
|||
initializeData(); |
|||
|
|||
} |
|||
|
|||
public static final String TAG = "ModbusRtuSlave"; |
|||
public static final int slaveId = 0x40; |
|||
public static Thread readThread; |
|||
public final AtomicBoolean shouldRead = new AtomicBoolean(false); |
|||
public static USBSerialPortHelper _serialPortHelper; |
|||
|
|||
// Modbus 数据存储
|
|||
public static final boolean[] coils = new boolean[65536]; |
|||
public static final boolean[] discreteInputs = new boolean[65536]; |
|||
public static final short[] holdingRegisters = new short[65536]; |
|||
public static final short[] inputRegisters = new short[65536]; |
|||
|
|||
// 数据更新监听器
|
|||
public DataUpdateListener dataUpdateListener; |
|||
|
|||
public interface DataUpdateListener { |
|||
void onCoilUpdated(int address, boolean value); |
|||
|
|||
void onRegisterUpdated(int address, short value); |
|||
|
|||
void onError(String errorMessage); |
|||
} |
|||
|
|||
public static void initializeData() { |
|||
// 初始化示例数据
|
|||
Arrays.fill(holdingRegisters, (short) 0); |
|||
Arrays.fill(inputRegisters, (short) 0); |
|||
Arrays.fill(coils, false); |
|||
Arrays.fill(discreteInputs, false); |
|||
|
|||
// 设置一些默认值 可以处理一些默认值 后面
|
|||
|
|||
|
|||
// 温度
|
|||
holdingRegisters[1] = 60; // 湿度
|
|||
holdingRegisters[2] = 1000; // 压力
|
|||
coils[0] = true; // 运行状态
|
|||
coils[1] = false; // 报警状态
|
|||
|
|||
for (short i = 0; i < 200; i++) { |
|||
holdingRegisters[i] = i; |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
public final byte[] buffer = new byte[256]; |
|||
|
|||
//处理Modbus数据,这个可以在数据接收后调用 返回调用的功能码
|
|||
public static int processModbusRequest(byte[] request, int length) { |
|||
if (length < 4) { |
|||
Log.w(TAG, "Request too short: " + length + " bytes"); |
|||
return 0; |
|||
} |
|||
|
|||
// 提取从站地址
|
|||
byte receivedSlaveId = request[0]; |
|||
if (receivedSlaveId != slaveId && receivedSlaveId != 0) { |
|||
Log.d(TAG, "Request not for this slave. Target: " + receivedSlaveId + ", Our ID: " + slaveId); |
|||
return 0; |
|||
} |
|||
|
|||
Log.d(TAG, "Processing request for slave: " + receivedSlaveId); |
|||
|
|||
// 验证CRC
|
|||
if (!validateCrc(request, length)) { |
|||
Log.w(TAG, "CRC validation failed"); |
|||
return 0; |
|||
} |
|||
|
|||
// 处理Modbus请求
|
|||
byte[] response = handleModbusFrame(request, length); |
|||
if (response != null && response.length > 0) { |
|||
try { |
|||
sendResponse(response); |
|||
return request[1];//返回功能码
|
|||
} catch (Exception e) { |
|||
return 0; |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
public static byte[] handleModbusFrame(byte[] frame, int length) { |
|||
if (length < 4) return null; |
|||
|
|||
byte functionCode = frame[1]; |
|||
byte[] response;// null;
|
|||
|
|||
Log.d(TAG, "Handling function code: 0x" + String.format("%02X", functionCode)); |
|||
|
|||
try { |
|||
switch (functionCode) { |
|||
case 0x01: // Read Coils
|
|||
response = handleReadCoils(frame, length); |
|||
break; |
|||
case 0x02: // Read Discrete Inputs
|
|||
response = handleReadDiscreteInputs(frame, length); |
|||
break; |
|||
case 0x03: // Read Holding Registers
|
|||
response = handleReadHoldingRegisters(frame, length); |
|||
break; |
|||
case 0x04: // Read Input Registers
|
|||
response = handleReadInputRegisters(frame, length); |
|||
break; |
|||
case 0x05: // Write Single Coil
|
|||
response = handleWriteSingleCoil(frame, length); |
|||
break; |
|||
case 0x06: // Write Single Register
|
|||
response = handleWriteSingleRegister(frame, length); |
|||
break; |
|||
case 0x0F: // Write Multiple Coils
|
|||
response = handleWriteMultipleCoils(frame, length); |
|||
break; |
|||
case 0x10: // Write Multiple Registers
|
|||
response = handleWriteMultipleRegisters(frame, length); |
|||
break; |
|||
default: |
|||
Log.w(TAG, "Unsupported function code: 0x" + String.format("%02X", functionCode)); |
|||
response = createExceptionResponse(functionCode, (byte) 0x01); // Illegal function
|
|||
break; |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error handling Modbus request: " + e.getMessage()); |
|||
response = createExceptionResponse(functionCode, (byte) 0x04); // Slave device failure
|
|||
} |
|||
|
|||
return response; |
|||
} |
|||
|
|||
public static byte[] handleWriteMultipleRegisters(byte[] request, int length) { |
|||
int startAddress = ((request[2] & 0xFF) << 8) | (request[3] & 0xFF); |
|||
int quantity = ((request[4] & 0xFF) << 8) | (request[5] & 0xFF); |
|||
int byteCount = request[6] & 0xFF; |
|||
|
|||
Log.d(TAG, "Write Multiple Registers - Start: " + startAddress + ", Quantity: " + quantity); |
|||
|
|||
if (quantity < 1 || quantity > 123) { |
|||
return createExceptionResponse(request[1], (byte) 0x03); |
|||
} |
|||
|
|||
if (startAddress + quantity > holdingRegisters.length) { |
|||
return createExceptionResponse(request[1], (byte) 0x02); |
|||
} |
|||
|
|||
if (byteCount != quantity * 2) { |
|||
return createExceptionResponse(request[1], (byte) 0x03); |
|||
} |
|||
|
|||
// 解析并写入寄存器数据
|
|||
for (int i = 0; i < quantity; i++) { |
|||
int dataIndex = 7 + i * 2; |
|||
short value = (short) (((request[dataIndex] & 0xFF) << 8) | (request[dataIndex + 1] & 0xFF)); |
|||
|
|||
int registerAddress = startAddress + i; |
|||
holdingRegisters[registerAddress] = value; |
|||
|
|||
} |
|||
|
|||
byte[] response = createWriteMultipleResponse(request[1], startAddress, quantity); |
|||
Log.d(TAG, "Wrote " + quantity + " registers from address " + startAddress); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] handleWriteMultipleCoils(byte[] request, int length) { |
|||
int startAddress = ((request[2] & 0xFF) << 8) | (request[3] & 0xFF); |
|||
int quantity = ((request[4] & 0xFF) << 8) | (request[5] & 0xFF); |
|||
int byteCount = request[6] & 0xFF; |
|||
|
|||
Log.d(TAG, "Write Multiple Coils - Start: " + startAddress + ", Quantity: " + quantity); |
|||
|
|||
if (quantity < 1 || quantity > 1968) { |
|||
return createExceptionResponse(request[1], (byte) 0x03); |
|||
} |
|||
|
|||
if (startAddress + quantity > coils.length) { |
|||
return createExceptionResponse(request[1], (byte) 0x02); |
|||
} |
|||
|
|||
int expectedByteCount = (quantity + 7) / 8; |
|||
if (byteCount != expectedByteCount) { |
|||
return createExceptionResponse(request[1], (byte) 0x03); |
|||
} |
|||
|
|||
// 解析并写入线圈数据
|
|||
for (int i = 0; i < quantity; i++) { |
|||
int byteIndex = 7 + (i / 8); |
|||
int bitIndex = i % 8; |
|||
boolean value = ((request[byteIndex] >> bitIndex) & 0x01) == 0x01; |
|||
|
|||
int coilAddress = startAddress + i; |
|||
coils[coilAddress] = value; |
|||
|
|||
} |
|||
|
|||
byte[] response = createWriteMultipleResponse(request[1], startAddress, quantity); |
|||
Log.d(TAG, "Wrote " + quantity + " coils from address " + startAddress); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] createWriteMultipleResponse(byte functionCode, int startAddress, int quantity) { |
|||
byte[] response = new byte[8]; |
|||
|
|||
response[0] = (byte) slaveId; |
|||
response[1] = functionCode; |
|||
response[2] = (byte) (startAddress >> 8); |
|||
response[3] = (byte) (startAddress & 0xFF); |
|||
response[4] = (byte) (quantity >> 8); |
|||
response[5] = (byte) (quantity & 0xFF); |
|||
|
|||
addCrc(response); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] createExceptionResponse(byte functionCode, byte exceptionCode) { |
|||
byte[] response = new byte[5]; |
|||
response[0] = (byte) slaveId; |
|||
response[1] = (byte) (functionCode | 0x80); |
|||
response[2] = exceptionCode; |
|||
addCrc(response); |
|||
|
|||
Log.w(TAG, "Exception response - Function: 0x" + String.format("%02X", functionCode) + |
|||
", Code: " + exceptionCode); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] handleReadDiscreteInputs(byte[] request, int length) { |
|||
int startAddress = ((request[2] & 0xFF) << 8) | (request[3] & 0xFF); |
|||
int quantity = ((request[4] & 0xFF) << 8) | (request[5] & 0xFF); |
|||
|
|||
Log.d(TAG, "Read Discrete Inputs - Start: " + startAddress + ", Quantity: " + quantity); |
|||
|
|||
if (quantity < 1 || quantity > 2000) { |
|||
return createExceptionResponse(request[1], (byte) 0x03); |
|||
} |
|||
|
|||
if (startAddress + quantity > discreteInputs.length) { |
|||
return createExceptionResponse(request[1], (byte) 0x02); |
|||
} |
|||
|
|||
// 读离散输入响应结构:
|
|||
// [slaveId] + [function] + [byteCount] + [data] + [CRC]
|
|||
// 1字节 + 1字节 + 1字节 + N字节 + 2字节
|
|||
|
|||
|
|||
int byteCount = (quantity + 7) / 8; |
|||
byte[] response = new byte[3 + byteCount + 2]; |
|||
response[0] = (byte) slaveId; |
|||
response[1] = request[1]; |
|||
response[2] = (byte) byteCount; |
|||
|
|||
for (int i = 0; i < quantity; i++) { |
|||
if (discreteInputs[startAddress + i]) { |
|||
response[3 + i / 8] |= (1 << (i % 8)); |
|||
} |
|||
} |
|||
|
|||
addCrc(response); |
|||
Log.d(TAG, "Read " + quantity + " discrete inputs from address " + startAddress); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] handleReadInputRegisters(byte[] request, int length) { |
|||
int startAddress = ((request[2] & 0xFF) << 8) | (request[3] & 0xFF); |
|||
int quantity = ((request[4] & 0xFF) << 8) | (request[5] & 0xFF); |
|||
|
|||
Log.d(TAG, "Read Input Registers - Start: " + startAddress + ", Quantity: " + quantity); |
|||
|
|||
if (quantity < 1 || quantity > 125) { |
|||
return createExceptionResponse(request[1], (byte) 0x03); |
|||
} |
|||
|
|||
if (startAddress + quantity > inputRegisters.length) { |
|||
return createExceptionResponse(request[1], (byte) 0x02); |
|||
} |
|||
// 读输入寄存器响应结构:
|
|||
// [slaveId] + [function] + [byteCount] + [data] + [CRC]
|
|||
// 1字节 + 1字节 + 1字节 + (quantity × 2) + 2字节
|
|||
|
|||
byte[] response = new byte[3 + quantity * 2 + 2]; |
|||
response[0] = (byte) slaveId; |
|||
response[1] = request[1]; |
|||
response[2] = (byte) (quantity * 2); |
|||
|
|||
for (int i = 0; i < quantity; i++) { |
|||
short value = inputRegisters[startAddress + i]; |
|||
response[3 + i * 2] = (byte) (value >> 8); |
|||
response[3 + i * 2 + 1] = (byte) value; |
|||
} |
|||
|
|||
addCrc(response); |
|||
Log.d(TAG, "Read " + quantity + " input registers from address " + startAddress); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] handleReadHoldingRegisters(byte[] request, int length) { |
|||
int startAddress = ((request[2] & 0xFF) << 8) | (request[3] & 0xFF); |
|||
int quantity = ((request[4] & 0xFF) << 8) | (request[5] & 0xFF); |
|||
|
|||
Log.d(TAG, "Read Holding Registers - Start: " + startAddress + ", Quantity: " + quantity); |
|||
|
|||
// 验证参数
|
|||
if (quantity < 1 || quantity > 125) { |
|||
Log.w(TAG, "Invalid quantity: " + quantity); |
|||
return createExceptionResponse(request[1], (byte) 0x03); // Illegal data value
|
|||
} |
|||
|
|||
if (startAddress + quantity > holdingRegisters.length) { |
|||
Log.w(TAG, "Address out of range: " + startAddress); |
|||
return createExceptionResponse(request[1], (byte) 0x02); // Illegal data address
|
|||
} |
|||
|
|||
// 读保持寄存器响应数据结构:
|
|||
// [slaveId] + [function] + [byteCount] + [data] + [CRC]
|
|||
// 1字节 + 1字节 + 1字节 + (quantity × 2) + 2字节
|
|||
byte[] response = new byte[1 + 1 + 1 + quantity * 2 + 2]; // slaveId + function + byteCount + data + CRC
|
|||
response[0] = (byte) slaveId; |
|||
response[1] = request[1]; // Function code
|
|||
response[2] = (byte) (quantity * 2); // Byte count
|
|||
|
|||
// 填充寄存器数据
|
|||
for (int i = 0; i < quantity; i++) { |
|||
short value = holdingRegisters[startAddress + i]; |
|||
response[3 + i * 2] = (byte) (value >> 8); |
|||
response[3 + i * 2 + 1] = (byte) value; |
|||
} |
|||
|
|||
// 添加CRC
|
|||
addCrc(response); |
|||
|
|||
Log.d(TAG, "Read " + quantity + " holding registers from address " + startAddress); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] handleWriteSingleRegister(byte[] request, int length) { |
|||
int address = ((request[2] & 0xFF) << 8) | (request[3] & 0xFF); |
|||
short value = (short) (((request[4] & 0xFF) << 8) | (request[5] & 0xFF)); |
|||
|
|||
Log.d(TAG, "Write Single Register - Address: " + address + ", Value: " + value); |
|||
|
|||
if (address >= holdingRegisters.length) { |
|||
Log.w(TAG, "Register address out of range: " + address); |
|||
return createExceptionResponse(request[1], (byte) 0x02); // Illegal data address
|
|||
} |
|||
|
|||
// 更新寄存器值
|
|||
holdingRegisters[address] = value; |
|||
|
|||
|
|||
// 写单个寄存器响应结构:
|
|||
// [slaveId] + [function] + [address] + [value] + [CRC]
|
|||
// 1字节 + 1字节 + 2字节 + 2字节 + 2字节
|
|||
byte[] response = new byte[8]; |
|||
System.arraycopy(request, 0, response, 0, 6); |
|||
response[0] = (byte) slaveId; |
|||
addCrc(response); |
|||
|
|||
|
|||
Log.d(TAG, "Register " + address + " updated to: " + value); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] handleReadCoils(byte[] request, int length) { |
|||
int startAddress = ((request[2] & 0xFF) << 8) | (request[3] & 0xFF); |
|||
int quantity = ((request[4] & 0xFF) << 8) | (request[5] & 0xFF); |
|||
|
|||
Log.d(TAG, "Read Coils - Start: " + startAddress + ", Quantity: " + quantity); |
|||
|
|||
if (quantity < 1 || quantity > 2000) { |
|||
return createExceptionResponse(request[1], (byte) 0x03); |
|||
} |
|||
|
|||
if (startAddress + quantity > coils.length) { |
|||
return createExceptionResponse(request[1], (byte) 0x02); |
|||
} |
|||
|
|||
// 读线圈响应结构:
|
|||
// [slaveId] + [function] + [byteCount] + [data] + [CRC]
|
|||
// 1字节 + 1字节 + 1字节 + N字节 + 2字节
|
|||
|
|||
// 数据字节数计算:N = ceil(quantity / 8)
|
|||
// int byteCount = (quantity + 7) / 8; // 向上取整
|
|||
// int totalBytes = 1 + 1 + 1 + byteCount + 2; // 5 + byteCount
|
|||
|
|||
|
|||
int byteCount = (quantity + 7) / 8; |
|||
byte[] response = new byte[3 + byteCount + 2]; // slaveId + function + byteCount + data + CRC
|
|||
response[0] = (byte) slaveId; |
|||
response[1] = request[1]; |
|||
response[2] = (byte) byteCount; |
|||
|
|||
// 打包线圈状态到字节
|
|||
for (int i = 0; i < quantity; i++) { |
|||
if (coils[startAddress + i]) { |
|||
response[3 + i / 8] |= (1 << (i % 8)); |
|||
} |
|||
} |
|||
|
|||
addCrc(response); |
|||
return response; |
|||
} |
|||
|
|||
public static byte[] handleWriteSingleCoil(byte[] request, int length) { |
|||
int address = ((request[2] & 0xFF) << 8) | (request[3] & 0xFF); |
|||
boolean value = (request[4] & 0xFF) == 0xFF; |
|||
|
|||
Log.d(TAG, "Write Single Coil - Address: " + address + ", Value: " + value); |
|||
|
|||
if (address >= coils.length) { |
|||
return createExceptionResponse(request[1], (byte) 0x02); |
|||
} |
|||
|
|||
coils[address] = value; |
|||
|
|||
|
|||
// 写单个线圈响应结构:
|
|||
// [slaveId] + [function] + [address] + [value] + [CRC]
|
|||
// 1字节 + 1字节 + 2字节 + 2字节 + 2字节 固定 8 字节 byte[] response = new byte[1 + 1 + 2 + 2 + 2]; //
|
|||
|
|||
byte[] response = new byte[8]; |
|||
System.arraycopy(request, 0, response, 0, 6); |
|||
response[0] = (byte) slaveId; |
|||
addCrc(response); |
|||
|
|||
Log.d(TAG, "Coil " + address + " updated to: " + value); |
|||
return response; |
|||
} |
|||
|
|||
public static boolean validateCrc(byte[] data, int length) { |
|||
if (length < 2) return false; |
|||
|
|||
int calculatedCrc = calculateCrc(data, 0, length - 2); |
|||
int receivedCrc = (data[length - 1] & 0xFF) << 8 | (data[length - 2] & 0xFF); |
|||
|
|||
boolean valid = calculatedCrc == receivedCrc; |
|||
if (!valid) { |
|||
Log.w(TAG, "CRC mismatch - Calculated: " + calculatedCrc + ", Received: " + receivedCrc); |
|||
} |
|||
|
|||
return valid; |
|||
} |
|||
|
|||
public static void addCrc(byte[] data) { |
|||
int crc = calculateCrc(data, 0, data.length - 2); |
|||
data[data.length - 2] = (byte) (crc & 0xFF); |
|||
data[data.length - 1] = (byte) ((crc >> 8) & 0xFF); |
|||
} |
|||
|
|||
public static int calculateCrc(byte[] data, int start, int length) { |
|||
int crc = 0xFFFF; |
|||
for (int i = start; i < start + length; i++) { |
|||
crc ^= (data[i] & 0xFF); |
|||
for (int j = 0; j < 8; j++) { |
|||
if ((crc & 0x0001) != 0) { |
|||
crc >>= 1; |
|||
crc ^= 0xA001; |
|||
} else { |
|||
crc >>= 1; |
|||
} |
|||
} |
|||
} |
|||
return crc; |
|||
} |
|||
|
|||
public static void sendResponse(byte[] response) { |
|||
if (_serialPortHelper == null) { |
|||
return; |
|||
} |
|||
_serialPortHelper.SendData(response); |
|||
Log.d(TAG, "Sent response: " + response.length + " bytes"); |
|||
} |
|||
|
|||
// 公共API方法
|
|||
public static void updateHoldingRegister(int address, short value) { |
|||
if (address >= 0 && address < holdingRegisters.length) { |
|||
holdingRegisters[address] = value; |
|||
} |
|||
} |
|||
|
|||
public static short getHoldingRegister(int address) { |
|||
if (address >= 0 && address < holdingRegisters.length) { |
|||
return holdingRegisters[address]; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
public static void updateCoil(int address, boolean state) { |
|||
if (address >= 0 && address < coils.length) { |
|||
coils[address] = state; |
|||
|
|||
} |
|||
} |
|||
|
|||
public static boolean getCoil(int address) { |
|||
if (address >= 0 && address < coils.length) { |
|||
return coils[address]; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,200 @@ |
|||
package com.example.fivewheel.services; |
|||
|
|||
import android.graphics.Color; |
|||
import android.view.Gravity; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import com.example.fivewheel.MainActivity; |
|||
import com.example.fivewheel.models.BspIV; |
|||
import com.google.protobuf.InvalidProtocolBufferException; |
|||
|
|||
public class ReceiivedIVHandler { |
|||
|
|||
private static final int Buttons_Not_Reset = 0; |
|||
private static final int Not_Intialized = 1; |
|||
private static final int Move_Halt = 2; |
|||
private static final int Move_Forward = 3; |
|||
private static final int Move_Backward = 4; |
|||
private static final int Move_TurnLeft = 5; |
|||
private static final int Move_TurnRight = 6; |
|||
private static final int Emergency_Stop = 7; |
|||
private static final int Upper_Computer_TakenOver = 8; |
|||
private static final int Sbus_Not_Online = 9; |
|||
private static final int Android_Down = 10; |
|||
|
|||
private static final int Cruise_Ahead = 11; |
|||
private static final int Cruise_Back = 12; |
|||
|
|||
public static void midToast(String str, int showTime, MainActivity MainActivity) { |
|||
Toast toast = Toast.makeText(MainActivity, str, showTime); |
|||
toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 0, 0); //设置显示位置
|
|||
TextView v = (TextView) toast.getView().findViewById(android.R.id.message); |
|||
v.setTextColor(Color.YELLOW); //设置字体颜色
|
|||
toast.show(); |
|||
} |
|||
|
|||
public static com.example.fivewheel.models.BspIV.IV_struct_define _toReceiveIV = BspIV.IV_struct_define.newBuilder().build(); |
|||
public static BspIV.IV_struct_define _toReceiveIV_Temp = BspIV.IV_struct_define.newBuilder().build(); |
|||
|
|||
public static void HandleIVData(MainActivity MainActivity, byte[] data) { |
|||
try { |
|||
|
|||
if(data.length<5) return; |
|||
if ((data[0] != 0x55) && (data[1] != 0x55)) return;//开头结尾为0x55 表示PV数据
|
|||
byte[] crcbytes = new byte[data.length - 2]; |
|||
System.arraycopy(data, 0, crcbytes, 0, data.length - 2); |
|||
byte[] crc = ModbusCRC.calculateCRC(crcbytes); |
|||
|
|||
|
|||
//这里的校验和C#的校验正好反了
|
|||
if (data[data.length - 2] != (byte) (crc[1] & 0xff)) return; |
|||
if (data[data.length - 1] != (byte) (crc[0] & 0xff)) return; //crc校验
|
|||
// if ((data[0] != 0x55) && (data[1] != 0x55)) return;//开头结尾为0x55 表示PV数据
|
|||
|
|||
byte[] bytes = new byte[data.length - 4]; |
|||
System.arraycopy(data, 2, bytes, 0, data.length - 4); |
|||
|
|||
try { |
|||
_toReceiveIV_Temp = BspIV.IV_struct_define.parseFrom(bytes); |
|||
|
|||
} catch (InvalidProtocolBufferException ex) { |
|||
return; |
|||
} |
|||
HandleIV(MainActivity, _toReceiveIV_Temp); |
|||
} catch ( |
|||
Exception e) { |
|||
|
|||
} |
|||
} |
|||
|
|||
private static int restoreOriginalError(int errorFlag) { |
|||
return Integer.reverseBytes(errorFlag); |
|||
} |
|||
|
|||
public static void HandleIV(MainActivity MainActivity, BspIV.IV_struct_define _toReceiveIV_Temp) { |
|||
if (_toReceiveIV_Temp == null) return; |
|||
|
|||
//若单片机重启,则变量时间戳重置
|
|||
if (_toReceiveIV_Temp.getRobotRestart() == 1) { |
|||
_toReceiveIV = _toReceiveIV.toBuilder().setTimeStamp(0).build(); |
|||
//告知单片机接收到
|
|||
MainActivity._toSendPV = MainActivity._toSendPV.toBuilder().setRobotRestartAccepted(1).build(); |
|||
return; |
|||
} |
|||
MainActivity._toSendPV = MainActivity._toSendPV.toBuilder().setRobotRestartAccepted(0).build(); |
|||
|
|||
|
|||
if (_toReceiveIV.getTimeStamp() > _toReceiveIV_Temp.getTimeStamp()) { |
|||
return; |
|||
|
|||
} |
|||
_toReceiveIV = _toReceiveIV_Temp; |
|||
MainActivity.runOnUiThread(() -> { |
|||
MainActivity.mainBinding.rFAngleRoll.setText(String.valueOf(_toReceiveIV.getRobotAngleRoll() / 100.0)); |
|||
MainActivity.mainBinding.tvRobotError.setText(String.valueOf(_toReceiveIV.getRobotError())); |
|||
MainActivity.mainBinding.tvDynamometer.setText(String.valueOf(_toReceiveIV.getRobotDynamometerValue() / 100.0)); |
|||
MainActivity.mainBinding.tvRobotRightCompensation.setText(String.valueOf(_toReceiveIV.getRobotCompensationRight() / 100.0)); |
|||
MainActivity.mainBinding.tvRobotLeftCompensation.setText(String.valueOf(_toReceiveIV.getRobotCompensationLeft() / 100.0)); |
|||
MainActivity.mainBinding.tvForce.setText(String.valueOf(_toReceiveIV.getRobotForceValue())); |
|||
MainActivity.mainBinding.tvRobotCurrent.setText("L" + String.valueOf(_toReceiveIV.getRobotCurrentLeft() / 1000) |
|||
+ "R" + String.valueOf(_toReceiveIV.getRobotCurrentRight() / 1000)); |
|||
|
|||
int leftError = _toReceiveIV.getRobotErrorLeft(); |
|||
int rightError = _toReceiveIV.getRobotErrorRight(); |
|||
|
|||
// 还原成原始 MCU 的错误值
|
|||
int leftOriginal = restoreOriginalError(leftError); |
|||
int rightOriginal = restoreOriginalError(rightError); |
|||
|
|||
// 左
|
|||
if (leftOriginal != 0) { |
|||
StringBuilder leftBits = new StringBuilder("错误: "); |
|||
for (int i = 0; i < 32; i++) { |
|||
if (((leftOriginal >> i) & 1) == 1) { |
|||
leftBits.append(32 - i).append(" "); |
|||
} |
|||
} |
|||
MainActivity.mainBinding.tvLeftError.setText(leftBits.toString().trim()); |
|||
} else { |
|||
MainActivity.mainBinding.tvLeftError.setText("正常"); |
|||
} |
|||
|
|||
//右
|
|||
if (rightOriginal != 0) { |
|||
StringBuilder rightBits = new StringBuilder("错误: "); |
|||
for (int i = 0; i < 32; i++) { |
|||
if (((rightOriginal >> i) & 1) == 1) { |
|||
rightBits.append(32 - i).append(" "); |
|||
} |
|||
} |
|||
MainActivity.mainBinding.tvRightError.setText(rightBits.toString().trim()); |
|||
} else { |
|||
MainActivity.mainBinding.tvRightError.setText("正常"); |
|||
} |
|||
|
|||
if (_toReceiveIV.getRobotError() != 0 && _toReceiveIV.getRobotCurrentState() != 12) { |
|||
|
|||
} else { |
|||
String m = ""; |
|||
switch (_toReceiveIV.getRobotCurrentState()) { |
|||
case 0: |
|||
m = "停止"; |
|||
break; |
|||
case 1: |
|||
m = "前进"; |
|||
break; |
|||
case 2: |
|||
m = "后退"; |
|||
break; |
|||
case 3: |
|||
m = "左转"; |
|||
break; |
|||
case 4: |
|||
m = "右转"; |
|||
break; |
|||
case 5: |
|||
m = "自动前进"; |
|||
break; |
|||
case 6: |
|||
m = "自动后退"; |
|||
break; |
|||
case 7: |
|||
m = "左换道"; |
|||
break; |
|||
case 8: |
|||
m = "右换道"; |
|||
break; |
|||
case 9: |
|||
m = "上换道"; |
|||
break; |
|||
case 10: |
|||
m = "下换道"; |
|||
break; |
|||
case 11: |
|||
m = "换道完成"; |
|||
break; |
|||
case 12: |
|||
m = "急停"; |
|||
break; |
|||
default: |
|||
throw new IllegalStateException("Unexpected value: " + _toReceiveIV.getRobotCurrentState()); |
|||
} |
|||
MainActivity.mainBinding.tvRobotError.setText(m); |
|||
} |
|||
int errorInt = _toReceiveIV.getSystemError(); |
|||
|
|||
String errorString = ErrorDeocdeHelper.ErrorDeocde(errorInt); |
|||
|
|||
String error_to_Display = "错误:"; |
|||
if (_toReceiveIV.getRobotErrorLeft() != 0) { |
|||
errorString += " \t 电机1错误码:" + String.valueOf(_toReceiveIV.getRobotErrorLeft()); |
|||
} |
|||
if (_toReceiveIV.getRobotErrorRight() != 0) { |
|||
errorString += " \t 电机2错误码:" + String.valueOf(_toReceiveIV.getRobotErrorRight()); |
|||
} |
|||
}); |
|||
|
|||
} |
|||
|
|||
} |
|||
@ -1,6 +1,7 @@ |
|||
cd /d D:\Android_studio_workspace\RemoveMarineAnimals\app\src\main\java |
|||
cd /d D:\Android_studio_workspace\fw_swj\app\src\main\java |
|||
protoc --proto_path=. --java_out=. bsp_IV.proto |
|||
protoc --proto_path=. --java_out=. bsp_PV.proto |
|||
protoc --proto_path=. --java_out=. bsp_Error.proto |
|||
pause |
|||
|
|||
|
|||
|
|||
@ -0,0 +1,25 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
|
|||
<!-- 设置透明背景色 --> |
|||
<!-- <solid android:color="@color/gainsboro" />--> |
|||
<solid android:color="#f2f3f4" /> |
|||
<!-- 设置一个黑色边框 --> |
|||
<!-- <stroke--> |
|||
<!-- android:width="4px"--> |
|||
<!-- android:color="@color/deepskyblue" />--> |
|||
<!-- 设置四个圆角的半径 --> |
|||
<corners |
|||
android:bottomLeftRadius="8dp" |
|||
android:bottomRightRadius="8dp" |
|||
android:topLeftRadius="8dp" |
|||
android:topRightRadius="8dp" /> |
|||
<!-- 设置一下边距,让空间大一点 --> |
|||
<!-- <padding--> |
|||
<!-- android:bottom="5dp"--> |
|||
<!-- android:left="5dp"--> |
|||
<!-- android:right="5dp"--> |
|||
<!-- android:top="5dp" />--> |
|||
|
|||
</shape> |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
|
|||
<!-- 设置透明背景色 --> |
|||
<!-- <solid android:color="@color/white" />--> |
|||
<solid android:color="#d6eaf8" /> |
|||
<!-- 设置一个黑色边框 --> |
|||
<!-- <stroke--> |
|||
<!-- android:width="4px"--> |
|||
<!-- android:color="@color/deepskyblue" />--> |
|||
<!-- 设置四个圆角的半径 --> |
|||
<corners |
|||
android:bottomLeftRadius="8dp" |
|||
android:bottomRightRadius="8dp" |
|||
android:topLeftRadius="8dp" |
|||
android:topRightRadius="8dp" /> |
|||
<!-- 设置一下边距,让空间大一点 --> |
|||
<!-- <padding--> |
|||
<!-- android:bottom="5dp"--> |
|||
<!-- android:left="5dp"--> |
|||
<!-- android:right="5dp"--> |
|||
<!-- android:top="5dp" />--> |
|||
|
|||
</shape> |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
|
|||
<!-- 设置透明背景色 --> |
|||
<solid android:color="#f6ddcc" /> |
|||
<!-- 设置一个黑色边框 --> |
|||
<!-- <stroke--> |
|||
<!-- android:width="4px"--> |
|||
<!-- android:color="@color/" />--> |
|||
<!-- 设置四个圆角的半径 --> |
|||
<corners |
|||
android:bottomLeftRadius="8dp" |
|||
android:bottomRightRadius="8dp" |
|||
android:topLeftRadius="8dp" |
|||
android:topRightRadius="8dp" /> |
|||
<!-- 设置一下边距,让空间大一点 --> |
|||
<!-- <padding--> |
|||
<!-- android:bottom="5dp"--> |
|||
<!-- android:left="5dp"--> |
|||
<!-- android:right="5dp"--> |
|||
<!-- android:top="5dp" />--> |
|||
|
|||
</shape> |
|||
|
|||
@ -0,0 +1,15 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:padding="16dp"> |
|||
|
|||
<TextView |
|||
android:id="@+id/tv_logs_content" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:textSize="14sp" |
|||
android:textColor="#000000" |
|||
android:scrollbars="vertical" /> |
|||
|
|||
</ScrollView> |
|||
Loading…
Reference in new issue