app/build.gradle
@@ -37,6 +37,7 @@ androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation 'org.greenrobot:eventbus:3.2.0' implementation 'redis.clients:jedis:3.0.1' implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'io.netty:netty-all:4.1.68.Final' app/src/main/java/com/example/agvcontroller/MainActivity.java
@@ -23,10 +23,16 @@ import com.example.agvcontroller.utils.SnowflakeIdWorker; import java.net.Socket; import java.sql.Time; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; public class MainActivity extends AppCompatActivity { public static final Map<String, Object> map = new ConcurrentHashMap(); private RecyclerView recyclerView; private ItemAdapter itemAdapter; @@ -45,22 +51,46 @@ private boolean isDowm = false; private boolean isOpen = false; String substring = String.valueOf(new SnowflakeIdWorker().nextId()).substring(0,16); private Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (isDowm) { String substring = String.valueOf(new SnowflakeIdWorker().nextId()).substring(0,16); AgvAction agvAction = new AgvAction<>(ForceSwitchAction.class) .setAgvNo("1") .setSerialNo(substring) .setVal(1) .bodySync((action) -> action.setPwd((short) 21)); nettyServerHandler.sendMessageToClient(clientId, agvAction); // 发送消息到客户端 handler.sendEmptyMessageDelayed(0, 100); boolean result = false; long timestamp = System.currentTimeMillis(); while (System.currentTimeMillis() - timestamp < 5000) { Object o = map.get(substring); if (null != o) { result = true; map.remove(o); break; } } if (result) { // alert ok } else { throw new TimeoutException("超时"); } } if (isOpen) { String substring = String.valueOf(new SnowflakeIdWorker().nextId()).substring(0,16); AgvAction agvAction = new AgvAction<>(ForceSwitchAction.class) .setAgvNo("12") .setSerialNo(substring) @@ -99,7 +129,7 @@ if (vibrator != null && vibrator.hasVibrator()) { vibrator.vibrate(500); } String substring = String.valueOf(new SnowflakeIdWorker().nextId()).substring(0,16); AgvAction agvAction = new AgvAction<>(HandOutAction.class) .setAgvNo("12") .setSerialNo(substring) app/src/main/java/com/example/agvcontroller/action/AGV_11_UP.java
New file @@ -0,0 +1,115 @@ package com.example.agvcontroller.action; import com.example.agvcontroller.protocol.IMessageBody; import com.example.agvcontroller.protocol.Utils; import com.example.agvcontroller.socket.RadixTools; import java.io.Serializable; /** * 动作完成数据包 * * Created by vincent on 2023/3/21 */ public class AGV_11_UP implements IMessageBody, Serializable { private static final long serialVersionUID = 8403019742104020004L; @Override public byte[] writeToBytes() { return new byte[0]; } @Override public void readFromBytes(byte[] bytes) { // try { this.serialNo = new String(Utils.sliceWithReverse(bytes, 0, 16)); // } catch (UnsupportedEncodingException e) { // e.printStackTrace(); // } this.completeCode = Utils.slice(bytes, 16, 1)[0]; this.completeType = AgvCompleteType.query(this.completeCode); this.qrCode = Utils.zeroFill(String.valueOf(RadixTools.bytesToInt(Utils.sliceWithReverse(bytes, 17, 4))), CommonConstant.QR_CODE_LEN); this.direction = RadixTools.byteToShort(Utils.sliceWithReverse(bytes, 21,2)); // this.locCode = new String(Utils.sliceWithReverse(bytes, 23, 16)).replaceAll("\\u0000", ""); // this.boxCode = new String(Utils.sliceWithReverse(bytes, 39, 16)).replaceAll("\\u0000", ""); this.locCode = Utils.zeroFill(String.valueOf(RadixTools.bytesToInt(Utils.sliceWithReverse(bytes, 23, 4))), CommonConstant.QR_CODE_LEN); this.boxCode = Utils.zeroFill(String.valueOf(RadixTools.bytesToInt(Utils.sliceWithReverse(bytes, 39, 4))), CommonConstant.QR_CODE_LEN); } private String serialNo; // 完成动作码 private int completeCode; private AgvCompleteType completeType; // 地面码 - 8位 private String qrCode; // 车头朝向 private short direction; // 货位二维码 private String locCode; public String getSerialNo() { return serialNo; } public void setSerialNo(String serialNo) { this.serialNo = serialNo; } public int getCompleteCode() { return completeCode; } public void setCompleteCode(int completeCode) { this.completeCode = completeCode; } public AgvCompleteType getCompleteType() { return completeType; } public void setCompleteType(AgvCompleteType completeType) { this.completeType = completeType; } public String getQrCode() { return qrCode; } public void setQrCode(String qrCode) { this.qrCode = qrCode; } public short getDirection() { return direction; } public void setDirection(short direction) { this.direction = direction; } public String getLocCode() { return locCode; } public void setLocCode(String locCode) { this.locCode = locCode; } public String getBoxCode() { return boxCode; } public void setBoxCode(String boxCode) { this.boxCode = boxCode; } // 料箱二维码 private String boxCode; } app/src/main/java/com/example/agvcontroller/action/AckMsgBuilder.java
New file @@ -0,0 +1,44 @@ package com.example.agvcontroller.action; import com.example.agvcontroller.protocol.AgvPackage; import com.example.agvcontroller.protocol.ProtocolType; import com.example.agvcontroller.protocol.ProtocolUtils; import java.util.logging.Logger; import io.netty.buffer.ByteBuf; /** * 应答报文建造者 * Created by vincent on 2019-04-04 */ public class AckMsgBuilder { // private static final Logger log = LoggerFactory.getLogger(AckMsgBuilder.class); /** * 成功应答 * @return ByteBuf */ public static AgvPackage ofSuccess(AgvPackage original, ProtocolType ackType) { return ProtocolUtils.installDownProtocol( original.getHeader().getUniqueNo() , ackType ); } /** * 失败应答 * @return ByteBuf */ public static ByteBuf ofFail(ByteBuf sourceBuff) { return sourceBuff; } } app/src/main/java/com/example/agvcontroller/action/AgvCompleteType.java
New file @@ -0,0 +1,53 @@ package com.example.agvcontroller.action; /** * Agv动作完成类型 */ public enum AgvCompleteType { COMMON_COMPLETE(0x01, "初始化自检或被动查询完成"), STRAIGHT_COMPLETE(0x02, "直行路径完成"), TURN_CORNER_COMPLETE(0x03, "转弯完成"), RETURN_ZERO_OF_ROTATE_TELESCOPIC_COMPLETE(0x04, "旋转伸缩归零完成"), TAKE_FROM_STORAGE_COMPLETE(0x05, "从料仓取货完成"), RELEASE_FROM_STORAGE_COMPLETE(0x06, "往料仓放货完成"), TAKE_FROM_SHELVES_COMPLETE(0x07, "从货架取货完成"), RELEASE_FROM_SHELVES_COMPLETE(0x08, "往货架放货完成"), SCAN_COMPLETE(0x09, "扫描货架和料箱完成"), DOCKING_CHARGE_COMPLETE(0x0C, "充电对接完成"), FULL_CHARGE_COMPLETE(0x0D, "充满电"), STOP_RECENT_CODE_COMPLETE(0x0E, "停到最近二维码完成"), ENTIRE_PATH_COMPLETE(0x0F, "整个路径执行完成"), ; private int code; private String desc; AgvCompleteType(int code, String desc) { this.code = code; this.desc = desc; } public static AgvCompleteType query(int completeCode) { for (AgvCompleteType type : AgvCompleteType.values()) { if (completeCode == type.code) { return type; } } return null; } } app/src/main/java/com/example/agvcontroller/action/CommonConstant.java
New file @@ -0,0 +1,10 @@ package com.example.agvcontroller.action; /** * Created by vincent on 2023/3/29 */ public class CommonConstant { public static final int QR_CODE_LEN = 8; } app/src/main/java/com/example/agvcontroller/action/RedisConstant.java
New file @@ -0,0 +1,38 @@ package com.example.agvcontroller.action; /** * Created by vincent on 2023/3/14 */ public class RedisConstant { public static final String AGV_CMD_DOWN_FLAG = "AGV_CMD_DOWN_FLAG"; public static final String AGV_CMD_UP_FLAG = "AGV_CMD_UP_FLAG"; public static final String AGV_ONLINE_FLAG = "AGV_ONLINE_FLAG"; public static final String AGV_COMPLETE_FLAG = "AGV_COMPLETE_FLAG"; public static final String AGV_DATA_FLAG = "AGV_DATA_FLAG"; public static final String AGV_FAULT_REPORT_FLAG = "AGV_FAULT_REPORT_FLAG"; public static final String DIGITAL_AGV_FLAG = "DIGITAL_AGV_FLAG"; public static final int CMD_TIMEOUT_LIMIT = 5000; public static final String AGV_MAP_ASTAR_FLAG = "AGV_MAP_ASTAR_FLAG"; public static final String AGV_MAP_ASTAR_CODE_FLAG = "AGV_MAP_ASTAR_CODE_FLAG"; public static final String AGV_MAP_ASTAR_TURN_FLAG = "AGV_MAP_ASTAR_TURN_FLAG"; public static final String AGV_MAP_ASTAR_CDA_FLAG = "AGV_MAP_ASTAR_CDA_FLAG"; public static final String AGV_MAP_ASTAR_DYNAMIC_FLAG = "AGV_MAP_ASTAR_DYNAMIC_FLAG"; public static final String AGV_MAP_ASTAR_WAVE_FLAG = "AGV_MAP_ASTAR_WAVE_FLAG"; public static final String AGV_TRAFFIC_JAM_FLAG = "AGV_TRAFFIC_JAM_FLAG"; } app/src/main/java/com/example/agvcontroller/met/AbstractInboundHandler.java
New file @@ -0,0 +1,36 @@ package com.example.agvcontroller.met; import com.example.agvcontroller.protocol.AgvPackage; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil; /** * netty handler增强器 * 设计模式: 适配器模式 * Created by vincent on 2019-04-06 */ public abstract class AbstractInboundHandler<T> extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception { @SuppressWarnings("unchecked") T t = (T) obj; if (channelRead0(ctx, t)) { ctx.fireChannelRead(t); } else { // 管道中断,fireChannelRead未执行,需要手动释放堆外内存 if (obj instanceof AgvPackage){ AgvPackage pac = (AgvPackage) obj; if (pac.getSourceBuff().refCnt() > 0) { pac.getSourceBuff().skipBytes(pac.getSourceBuff().readableBytes()); ReferenceCountUtil.release(pac.getSourceBuff()); } } } } protected abstract boolean channelRead0(ChannelHandlerContext ctx, T t) throws Exception; } app/src/main/java/com/example/agvcontroller/protocol/AGV_A1_DOWN.java
@@ -21,6 +21,22 @@ } public byte getAckSign() { return ackSign; } public void setAckSign(byte ackSign) { this.ackSign = ackSign; } public byte[] getTemp() { return temp; } public void setTemp(byte[] temp) { this.temp = temp; } private byte ackSign = 0x11; private byte[] temp = new byte[4]; app/src/main/java/com/example/agvcontroller/socket/NettyServer.java
@@ -35,7 +35,7 @@ .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new NettyServerHandler()).addLast(new ProtocolEncoder()); ch.pipeline().addLast(new ProtocolEncoder()).addLast(new ProtocolDecoder(4096)).addLast(new NettyServerHandler()); } }) // .addLast(new NettyServerHandler()) app/src/main/java/com/example/agvcontroller/socket/NettyServerHandler.java
@@ -1,30 +1,34 @@ package com.example.agvcontroller.socket; import java.nio.charset.StandardCharsets; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import android.util.Log; import com.example.agvcontroller.Item; import com.example.agvcontroller.MainActivity; import com.example.agvcontroller.action.AGV_11_UP; import com.example.agvcontroller.action.AckMsgBuilder; import com.example.agvcontroller.met.AbstractInboundHandler; import com.example.agvcontroller.protocol.AGV_A1_DOWN; import com.example.agvcontroller.protocol.AgvAction; import com.example.agvcontroller.protocol.AgvPackage; import com.example.agvcontroller.protocol.ProtocolType; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; public class NettyServerHandler extends ChannelInboundHandlerAdapter { public class NettyServerHandler extends AbstractInboundHandler<AgvPackage> { private static final String TAG = "NettyServerHandler"; private static ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>(); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { @@ -42,22 +46,53 @@ Log.d(TAG, "Client disconnected: " + clientId); } // @Override // public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // // 处理接收到的消息 // ByteBuf byteBuf = (ByteBuf) msg; // try { // while (byteBuf.isReadable()) { // byte[] bytes = new byte[byteBuf.readableBytes()]; // byteBuf.readBytes(bytes); // String hexString = bytesToHex(bytes); // // 获取agv信息 添加到list中 // Log.d(TAG, "ctx: " + ctx.channel().remoteAddress().toString() ); // Log.d(TAG, "Received: " + hexString); // } // } finally { // byteBuf.release(); // } // } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 处理接收到的消息 ByteBuf byteBuf = (ByteBuf) msg; try { while (byteBuf.isReadable()) { byte[] bytes = new byte[byteBuf.readableBytes()]; byteBuf.readBytes(bytes); String hexString = bytesToHex(bytes); // 获取agv信息 添加到list中 Log.d(TAG, "ctx: " + ctx.channel().remoteAddress().toString() ); Log.d(TAG, "Received: " + hexString); } } finally { byteBuf.release(); protected boolean channelRead0(ChannelHandlerContext ctx, AgvPackage pac) throws Exception { String serialNum = pac.getHeader().getSerialNum(); // ack ProtocolType ackType = isNeedAck(pac); final String uniqueNo = pac.getHeader().getUniqueNo(); label : switch (pac.getHeader().getProtocolType()){ case ACTION_COMPLETE: // 动作完成数据包 AGV_11_UP agv_11_up = (AGV_11_UP) pac.getBody().getMessageBody(); // redis.push(RedisConstant.AGV_COMPLETE_FLAG, AgvProtocol.build(uniqueNo).setMessageBody(agv_11_up)); // 动作完成应答 if (null != ackType) { AgvPackage ackPac = AckMsgBuilder.ofSuccess(pac, ackType); AGV_A1_DOWN agv_a1_down = (AGV_A1_DOWN) ackPac.getBody().getMessageBody(); agv_a1_down.setAckSign((byte) agv_11_up.getCompleteCode()); ctx.writeAndFlush(ackPac); } MainActivity.map.put(serialNum, Boolean.TRUE); break label; } return false; } private String bytesToHex(byte[] bytes) { @@ -109,4 +144,18 @@ ctx.close(); } /** * 服务器是否需要应答 */ public static ProtocolType isNeedAck(AgvPackage pac) { switch (pac.getHeader().getProtocolType()) { case ACTION_COMPLETE: return ProtocolType.ACTION_SUCCESS_ACK; case LOGIN_REPORT: return ProtocolType.LOGIN_ACK; default: return null; } } }