app/src/main/java/com/example/agvcontroller/AGVApplication.java
New file @@ -0,0 +1,33 @@ package com.example.agvcontroller; import android.app.Application; import android.os.Handler; import android.os.Looper; import java.util.ArrayList; import java.util.List; public class AGVApplication extends Application { private static List<String> logList = new ArrayList<>(); @Override public void onCreate() { super.onCreate(); } @Override public void onTerminate() { super.onTerminate(); // 清空日志列表 logList.clear(); } public static List<String> getLogList() { return logList; } public static void addLog(String log) { logList.add(log); } } app/src/main/java/com/example/agvcontroller/LogActivity.java
New file @@ -0,0 +1,39 @@ package com.example.agvcontroller; import static com.example.agvcontroller.utils.ToDBC.toDBC; import android.os.Bundle; import android.widget.TextView; import java.util.List; import androidx.appcompat.app.AppCompatActivity; public class LogActivity extends AppCompatActivity { private TextView logTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_log); logTextView = findViewById(R.id.log_text_view); // 获取传递的日志数据 List<String> logList = getIntent().getStringArrayListExtra("log_list"); // 更新日志显示 updateLogTextView(logList); } private void updateLogTextView(List<String> logList) { StringBuilder logBuilder = new StringBuilder(); for (String log : logList) { logBuilder.append(log).append("\n"); } logTextView.setText(toDBC(logBuilder.toString())); } } app/src/main/java/com/example/agvcontroller/MainActivity.java
@@ -36,6 +36,7 @@ import com.example.agvcontroller.action.HandLift; import com.example.agvcontroller.action.LiftResetAction; import com.example.agvcontroller.action.LoadResetAction; import com.example.agvcontroller.action.LoginReset; import com.example.agvcontroller.action.RotatopnLeftRight; import com.example.agvcontroller.action.SingleSwitchAction; import com.example.agvcontroller.action.SingleSwitchRunAction; @@ -68,6 +69,7 @@ private static final int MAX_RECENT_LOGS = 10; // 最多显示 10 条最新日志 private static TextView agvBattery; private static TextView agvSocket; private static TextView agvNo; private static TextView agvStatus; private static TextView agvPositionId; @@ -77,6 +79,7 @@ private static TextView agvForkExtend; private static TextView agvForkAngle; private static TextView agvError; private Button reLogin; @@ -406,6 +409,7 @@ setContentView(R.layout.activity_main); agvBattery = findViewById(R.id.agv_battery); agvSocket = findViewById(R.id.socket); agvNo = findViewById(R.id.agv_no); agvStatus = findViewById(R.id.agv_status); agvPositionId = findViewById(R.id.agv_position_id); @@ -415,6 +419,8 @@ agvForkExtend = findViewById(R.id.agv_ford); agvForkAngle = findViewById(R.id.agv_ratio); agvError = findViewById(R.id.agv_error); reLogin = findViewById(R.id.relogin_button); @@ -524,6 +530,18 @@ Log.i("message1",clientId); reLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String substring = String.valueOf(new SnowflakeIdWorker().nextId()).substring(0,16); AgvAction agvAction = new AgvAction<>(LoginReset.class) .setAgvNo(AgvNo) .setSerialNo(substring) .setVal(1) .bodySync((action) -> action.setPwd((short) 1)); nettyServerHandler.sendMessageToClient(clientId, agvAction); // 发送消息到客户端 } }); // 前拨杆伸出 frontPaddleExtendBtn.setOnClickListener(new View.OnClickListener() { @Override @@ -1460,6 +1478,7 @@ AgvNo = agvCar.getAgvNo(); agvNo.setText("AGV编号:" + AgvNo); agvBattery.setText("电量:" + agvCar.getBattery() + "%"); agvSocket.setText(agvCar.getStatus() == 1 ? "已连接" : "未连接"); agvStatus.setText("AGV状态:" + agvCar.getStatus()); agvPositionId.setText("ID:" + agvCar.getPositionID()); agvPositionX.setText("(X):" + agvCar.getPositionX()); app/src/main/java/com/example/agvcontroller/action/LoginReset.java
New file @@ -0,0 +1,31 @@ package com.example.agvcontroller.action; import com.example.agvcontroller.protocol.IActionBody; import com.example.agvcontroller.socket.RadixTools; import java.io.Serializable; public class LoginReset implements IActionBody, Serializable { private static final long serialVersionUID = -3250235107705010315L; private Short pwd; @Override public byte[] writeToBytes() { byte[] bytes = RadixTools.shortToByte(pwd); return bytes; } @Override public void readFromBytes(byte[] messageBodyBytes) { } public Short getPwd() { return pwd; } public void setPwd(Short pwd) { this.pwd = pwd; } } app/src/main/java/com/example/agvcontroller/protocol/AGV_03_UP.java
New file @@ -0,0 +1,42 @@ package com.example.agvcontroller.protocol; import com.example.agvcontroller.socket.RadixTools; import java.io.Serializable; public class AGV_03_UP implements IMessageBody, Serializable { private static final long serialVersionUID = -5588066188890649095L; @Override public byte[] writeToBytes() { return new byte[0]; } @Override public void readFromBytes(byte[] bytes) { this.battery = Utils.sliceWithReverse(bytes, 2, 1)[0]; this.error = Utils.sliceWithReverse(bytes, 26, 4)[0]; } @Override public String getSerialNo() { return ""; } private int battery; private int error; public int getBattery() { return battery; } public int getError() { return error; } } app/src/main/java/com/example/agvcontroller/protocol/HandleCmdType.java
@@ -16,6 +16,7 @@ import com.example.agvcontroller.action.HandOutAction; import com.example.agvcontroller.action.LiftResetAction; import com.example.agvcontroller.action.LoadResetAction; import com.example.agvcontroller.action.LoginReset; import com.example.agvcontroller.action.RotatopnLeftRight; import com.example.agvcontroller.action.SingleSwitchAction; import com.example.agvcontroller.action.SingleSwitchRunAction; @@ -60,6 +61,8 @@ BACK_PADDLE(0x8A, "手动控制后拨杆", BackPaddle.class), LOGIN_RESET(0x80, "重新登录", LoginReset.class), ; app/src/main/java/com/example/agvcontroller/socket/NettyServerHandler.java
@@ -46,6 +46,7 @@ private static final String TAG = "NettyServerHandler"; private static ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>(); private Map<String, Runnable> pendingRemovals = new HashMap<>(); AGVCar agvCar; int battery = 0; int status = 0; int agvStatus = 0; @@ -58,6 +59,8 @@ int forkExtend = 0; int forkAngle = 0; int agvError = 0; String agvNo = "--"; String log; private Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -75,14 +78,19 @@ String ip = remoteAddress.getAddress().getHostAddress(); int port = remoteAddress.getPort(); channelMap.put(clientId, ctx.channel()); EventBus.getDefault().post(new AGVCar(clientId,ip,port,"--",0)); Log.d(TAG, "Client connected: " + clientId); agvCar = new AGVCar(clientId, ip, port, agvNo, 1, battery,agvStatus,positionID,positionX,positionY,agvAngle,gyroAngle,forkHeight,forkExtend,forkAngle,agvError); // 取消延迟删除操作 if (pendingRemovals.containsKey(clientId)) { handler.removeCallbacks(pendingRemovals.get(clientId)); pendingRemovals.remove(clientId); } EventBus.getDefault().post(agvCar); // Log.d(TAG, "Client connected: " + clientId); log = formatDate(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " 上行: " + ip + "[tcp]>>>已连接"; Log.d("updown", log); AGVApplication.addLog(log); // // 取消延迟删除操作 // if (pendingRemovals.containsKey(clientId)) { // handler.removeCallbacks(pendingRemovals.get(clientId)); // pendingRemovals.remove(clientId); // } } @@ -92,24 +100,38 @@ InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); String ip = remoteAddress.getAddress().getHostAddress(); int port = remoteAddress.getPort(); channelMap.remove(clientId); // channelMap.remove(clientId); EventBus.getDefault().post(clientId); Log.d(TAG, "Client disconnected: " + clientId); // Log.d(TAG, "Client disconnected: " + clientId); agvCar = new AGVCar(clientId, ip, port, agvNo, 0, battery,agvStatus,positionID,positionX,positionY,agvAngle,gyroAngle,forkHeight,forkExtend,forkAngle,agvError); // 启动延迟删除操作 Runnable removalRunnable = new Runnable() { @Override public void run() { removeItem(clientId); } }; pendingRemovals.put(clientId, removalRunnable); handler.postDelayed(removalRunnable, 20000); // 20秒后执行删除操作 EventBus.getDefault().post(agvCar); // Log.d(TAG, "Client connected: " + clientId); log = formatDate(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " 上行: " + ip + "[tcp]>>>断开连接"; Log.d("updown", log); AGVApplication.addLog(log); // // 启动延迟删除操作 // Runnable removalRunnable = new Runnable() { // @Override // public void run() { // removeItem(clientId); // } // }; // pendingRemovals.put(clientId, removalRunnable); // handler.postDelayed(removalRunnable, 20000); // 20秒后执行删除操作 } private void removeItem(String clientId) { // 原先是要删除后续为了能动态自动连接需要更新 // if (channelMap.remove(clientId) != null) { // Log.d(TAG, "Client removed after 20 seconds: " + clientId); // EventBus.getDefault().post(clientId); // } else { // Log.d(TAG, "Client already reconnected or not found: " + clientId); // } if (channelMap.remove(clientId) != null) { Log.d(TAG, "Client removed after 20 seconds: " + clientId); EventBus.getDefault().post(clientId); @@ -150,11 +172,6 @@ // ack ProtocolType ackType = isNeedAck(pac); final String uniqueNo = pac.getHeader().getUniqueNo(); String agvNo; AGVCar agvCar; String log; label : switch (pac.getHeader().getProtocolType()){ case ACTION_COMPLETE: // 动作完成数据包 AGV_11_UP agv_11_up = (AGV_11_UP) pac.getBody().getMessageBody(); app/src/main/java/com/example/agvcontroller/utils/ToDBC.java
New file @@ -0,0 +1,16 @@ package com.example.agvcontroller.utils; public class ToDBC { public static String toDBC(String input) { char[] chars = input.toCharArray(); for (int i=0;i< chars.length; i++){ if(chars[i]== 12288){ chars[i]=(char)32; continue; } if(chars[i]> 65280 && chars[i]< 65375) chars[i]=(char)(chars[i]- 65248); } return new String(chars); } } app/src/main/res/drawable/new_logo.png
app/src/main/res/layout/activity_log.xml
New file @@ -0,0 +1,27 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".LogActivity"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <TextView android:id="@+id/log_text_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="12sp" android:textColor="#FFFFFF" android:background="#000000" android:focusable="true" android:singleLine="false" android:focusableInTouchMode="true" android:breakStrategy="high_quality" android:hyphenationFrequency="normal" android:scrollbars="vertical" /> </ScrollView> </LinearLayout> app/src/main/res/layout/activity_main.xml
@@ -14,10 +14,13 @@ android:orientation="vertical" android:padding="10dp" android:layout_weight="4"> <LinearLayout <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:layout_weight="2"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> > <TextView @@ -28,14 +31,35 @@ android:textSize="14sp" android:text="电量:100%" android:textColor="#FFFFFF" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/agv_no" android:layout_width="match_parent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="" android:textSize="14sp" android:text="AGV编号:1" android:textColor="#FFFFFF" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="" android:textSize="14sp" android:text=" " android:textColor="#FFFFFF" /> <TextView android:id="@+id/socket" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="" android:textSize="14sp" android:text="--" android:textColor="#FFFFFF" /> </LinearLayout> <TextView android:id="@+id/agv_status" android:layout_width="match_parent" @@ -124,6 +148,23 @@ android:text="AGV故障:导航故障 > 待机状态下丢码" android:textColor="#FFFFFF" /> </LinearLayout> <com.google.android.material.button.MaterialButton android:id="@+id/relogin_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentTop="true" android:layout_marginLeft="20sp" android:layout_centerVertical="true" android:layout_marginRight="10sp" android:backgroundTint="#2196F3" android:minWidth="10dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:text="重新登录" app:cornerRadius="5dp" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" @@ -136,6 +177,7 @@ android:id="@+id/recent_log_text_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical" /> <com.google.android.material.button.MaterialButton android:id="@+id/view_all_logs_button"