浏览代码

充电任务

zhangxin 1 年之前
父节点
当前提交
203aea73b8
共有 23 个文件被更改,包括 1773 次插入3 次删除
  1. 16 0
      base_sql/pallet_sql/jinyinhua_dml_20231025.sql
  2. 8 0
      pom.xml
  3. 5 0
      warewms-ams/pom.xml
  4. 135 0
      warewms-ams/src/main/java/com/warewms/ams/charging/ChargerButtProxy.java
  5. 728 0
      warewms-ams/src/main/java/com/warewms/ams/charging/ChargerButtService.java
  6. 135 0
      warewms-ams/src/main/java/com/warewms/ams/charging/FeedbackTS.java
  7. 5 1
      warewms-ams/src/main/java/com/warewms/ams/common/AmsConstant.java
  8. 1 1
      warewms-ams/src/main/java/com/warewms/ams/ndc/AciService.java
  9. 5 0
      warewms-ams/src/main/java/com/warewms/ams/ndc/controller/AmsTaskController.java
  10. 18 0
      warewms-ams/src/main/java/com/warewms/ams/ndc/dto/ChargeTaskInsertDTO.java
  11. 9 0
      warewms-ams/src/main/java/com/warewms/ams/ndc/service/IAmsTaskService.java
  12. 35 0
      warewms-ams/src/main/java/com/warewms/ams/ndc/service/impl/AmsTaskServiceImpl.java
  13. 86 0
      warewms-ams/src/main/java/com/warewms/ams/ndc/service/impl/SocketBufferServiceImpl.java
  14. 15 0
      warewms-ams/src/main/resources/application-prod.yml
  15. 二进制
      warewms-ams/src/main/resources/lib/RXTXcomm.jar
  16. 二进制
      warewms-ams/src/main/resources/lib/jssc-2.8.0.jar
  17. 二进制
      warewms-ams/src/main/resources/lib/modbus-spring-boot-starter.jar
  18. 二进制
      warewms-ams/src/main/resources/lib/modbus4j-3.0.5.jar
  19. 二进制
      warewms-ams/src/main/resources/lib/rfid-1.0.0.jar
  20. 二进制
      warewms-ams/src/main/resources/lib/rfid-zebra-spring-boot-starter.jar
  21. 22 1
      warewms-ams/src/test/java/warewms/WarehouseTest.java
  22. 87 0
      warewms-hard/pom.xml
  23. 463 0
      warewms-hard/src/main/java/com/warewms/ChargingMachineClient.java

文件差异内容过多而无法显示
+ 16 - 0
base_sql/pallet_sql/jinyinhua_dml_20231025.sql


+ 8 - 0
pom.xml

@@ -191,11 +191,19 @@
                 <version>${warewms.version}</version>
             </dependency>
 
+            <!-- modbus模块-->
+            <dependency>
+                <groupId>com.warewms</groupId>
+                <artifactId>warewms-hard</artifactId>
+                <version>${warewms.version}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 
     <modules>
         <module>warewms-ams</module>
+        <module>warewms-hard</module>
     </modules>
     <packaging>pom</packaging>
 

+ 5 - 0
warewms-ams/pom.xml

@@ -283,6 +283,11 @@
             <artifactId>spring-retry</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.warewms</groupId>
+            <artifactId>warewms-hard</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 135 - 0
warewms-ams/src/main/java/com/warewms/ams/charging/ChargerButtProxy.java

@@ -0,0 +1,135 @@
+package com.warewms.ams.charging;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 充电机对接 代理
+ *
+ * @author JWK
+ * @version 1.0
+ * @date 2022/9/30 22:11
+ */
+@Slf4j
+@Service
+public class ChargerButtProxy {
+
+    @Autowired
+    private ChargerButtService chargerButtService;
+
+    /**
+     * 一辆车对应一把锁
+     */
+    Map<Integer, Lock> lockStartMap = new ConcurrentHashMap<>();
+    Map<Integer, Lock> lockCancelMap = new ConcurrentHashMap<>();
+    Map<Integer, Lock> lockEndMap = new ConcurrentHashMap<>();
+
+    /**
+     * 请求标识
+     */
+    public enum CHARGER_BUTT_REQUEST {
+        START, CANCEL, END
+    }
+
+
+    /**
+     * 充电同步器
+     * 对TS重复消息进行过滤(TS几秒一次的充电信息请求对业务造成干扰)
+     * 保证同一时间只有一个线程处理对应的业务
+     *
+     * @param index
+     * @param chargingSite
+     * @param agvNo
+     */
+    public void theChargingSynchronizer(Integer index, Integer chargingSite, Integer agvNo, CHARGER_BUTT_REQUEST request) {
+        Map<Integer, Lock> lockMap = null;
+        switch (request) {
+            case START:
+                lockMap = lockStartMap;
+                break;
+            case CANCEL:
+                lockMap = lockCancelMap;
+                break;
+            case END:
+                lockMap = lockEndMap;
+                break;
+            default:
+                break;
+        }
+        // 当前的锁
+        Lock currentLock;
+        if (lockMap.get(agvNo) == null) {
+            // 创建新锁
+            Lock lock = new ReentrantLock();
+            lockMap.put(agvNo, lock);
+        }
+        currentLock = lockMap.get(agvNo);
+        try {
+            if (!currentLock.tryLock()) {
+                return;
+            }
+            switch (request) {
+                case START:
+                    theChargingPositionHasBeenReachedProxy(index, chargingSite, agvNo);
+                    break;
+                case CANCEL:
+                    theChargingTaskCanceledArtificiallyProxy(index, chargingSite, agvNo);
+                    break;
+                case END:
+                    theChargingTaskItSOverProxy(index, chargingSite, agvNo);
+                    break;
+                default:
+                    break;
+            }
+            Thread.sleep(500);
+        } catch (Exception e) {
+            log.error("theChargingSynchronizer:", e);
+        } finally {
+            currentLock.unlock();
+        }
+    }
+
+    /**
+     * AGV已经达到充电位置 4001
+     *
+     * @param index        ts任务号
+     * @param chargingSite 充电桩站点号
+     * @param agvNo        车号
+     */
+    public void theChargingPositionHasBeenReachedProxy(Integer index, Integer chargingSite, Integer agvNo) {
+
+        chargerButtService.theChargingPositionHasBeenReached(index, chargingSite, agvNo);
+
+    }
+
+    /**
+     * AGV充电任务被人为取消 4002
+     *
+     * @param index        ts任务号
+     * @param chargingSite 充电桩站点号
+     * @param agvNo        车号
+     */
+    public void theChargingTaskCanceledArtificiallyProxy(Integer index, Integer chargingSite, Integer agvNo) {
+
+        chargerButtService.theChargingTaskCanceledArtificially(index, chargingSite, agvNo);
+
+    }
+
+    /**
+     * AGV充电任务结束 4003
+     *
+     * @param index        ts任务号
+     * @param chargingSite 充电桩站点号
+     * @param agvNo        车号
+     */
+    public void theChargingTaskItSOverProxy(Integer index, Integer chargingSite, Integer agvNo) {
+
+        chargerButtService.theChargingTaskItSOver(index, chargingSite, agvNo);
+    }
+}

+ 728 - 0
warewms-ams/src/main/java/com/warewms/ams/charging/ChargerButtService.java

@@ -0,0 +1,728 @@
+package com.warewms.ams.charging;
+
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.warewms.ChargingMachineClient;
+import com.warewms.ams.ndc.domain.AmsTask;
+import com.warewms.ams.ndc.service.IAmsTaskService;
+import com.warewms.common.base.domain.AjaxResult;
+import com.warewms.common.utils.redis.RedisCache;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import static com.warewms.ChargingMachineClient.CHARGER_ADDRESS_MEANING.*;
+import static com.warewms.ChargingMachineClient.CHARGER_STATUS.*;
+import static com.warewms.ams.charging.ChargerButtService.CHARGER_ASSEMBLY_STATUS.*;
+import static com.warewms.ams.charging.FeedbackTS.FEEDBACK_TS_STATUS.*;
+
+/**
+ * 充电机对接
+ *
+ * @author JWK
+ * @version 1.0
+ * @date 2022/9/30 22:16
+ */
+@Slf4j
+@Service
+public class ChargerButtService {
+
+    @Autowired
+    private IAmsTaskService amsTaskService;
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private FeedbackTS feedbackTS;
+    @Autowired(required = false)
+    private ChargingMachineClient chargingMachineClient;
+
+    // 创建一个线程池
+    private ExecutorService executorService = new ThreadPoolExecutor(2, 20
+            , 60L, TimeUnit.SECONDS
+            , new SynchronousQueue<Runnable>());
+
+    /**
+     * KEY前缀
+     */
+    public static final String KEY_PREFIX = "charging:agvNo:";
+
+    /**
+     * 充电桩站点号
+     */
+    public static final String KEY_CHARGING_SITE = "chargingSite";
+
+    /**
+     * TS充电任务号
+     */
+    public static final String KEY_AGV_INDEX = "index";
+
+    /**
+     * WcsTask任务号
+     */
+    public static final String KEY_TASK_NO = "taskNo";
+
+    /**
+     * AGV充电状态
+     */
+    public static final String KEY_AGV_STS = "status";
+
+    /**
+     * AGV充电状态
+     */
+    public enum AGV_CHARGING_STS {
+        READY,
+        START,
+        STOP;
+    }
+
+    /**
+     * 充电桩编号和从节点关系
+     */
+    public enum CHARGING_SLAVEID_MAPPING {
+        /***
+         * A
+         */
+        A(1004, 1),
+        /***
+         * B
+         */
+        B(2, 1),
+        /***
+         * 空
+         */
+        C(3, 1),
+        /***
+         * 无
+         */
+        D(4, 1);
+        private Integer chargerNo;
+        private Integer slaveId;
+
+        CHARGING_SLAVEID_MAPPING(Integer chargerNo, Integer slaveId) {
+            this.chargerNo = chargerNo;
+            this.slaveId = slaveId;
+        }
+
+        public Integer getChargerNo() {
+            return chargerNo;
+        }
+
+        public Integer getSlaveId() {
+            return slaveId;
+        }
+
+        /**
+         * 根据值获得枚举类型 switch
+         *
+         * @param chargerNo
+         * @return
+         */
+        public static CHARGING_SLAVEID_MAPPING getByChargerNo(Integer chargerNo) {
+            for (CHARGING_SLAVEID_MAPPING code : values()) {
+                if (code.getChargerNo().equals(chargerNo)) {
+                    return code;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * 根据充电机编号拿到从节点ID
+         *
+         * @param chargerNo
+         * @return
+         */
+        public static Integer getSlaveIdByChargerNo(Integer chargerNo) {
+            CHARGING_SLAVEID_MAPPING mapping = getByChargerNo(chargerNo);
+            if (mapping == null) {
+                return null;
+            }
+            return mapping.getChargerNo();
+        }
+
+    }
+
+    /**
+     * AGV已经达到充电位置:
+     * 1.确认几号充电机,查询并且判断充电机状态(确认信号:待机、在线、红外到位 > 发送启动指令 > 确认信号压紧)
+     * 2.反馈TS是否能正常充电:通过给参数1改值来反馈(1表示让AGV退出充电点并重进,2表示充电机故障,3表示正常充电,4表示结束充电)
+     * 3.给充电机发送开始放电信号
+     * 4.整个充电过程需要每隔几秒查询充电机状态,根据充电机状态调度充电机
+     * 5.查询到充电机状态为完成(确认信号:无压紧、无充电、归位)
+     * 6.发送TS指令4(结束充电)
+     * 故障处理:
+     * 1.发送TS指令(充电机故障)
+     * 2.发送停止充电机指令
+     */
+    public void theChargingPositionHasBeenReached(Integer index, Integer chargingSite, Integer agvNo) {
+        if (chargingMachineClient == null) {
+            log.error("充电机通讯开关关闭!");
+            return;
+        }
+        String taskNo = "";
+        // 充电机号
+        Integer chargerNo = chargingSite;
+        boolean xx = !(chargerNo == 1004);
+        // 从Redis拿到充电信息
+        Map<String, Object> agvInfoMap = redisCache.getCacheMap(KEY_PREFIX + agvNo);
+        if (agvInfoMap == null) {
+            return;
+        }
+        // 如果已经开始充电 不可重复请求
+        if (agvInfoMap.get(KEY_AGV_STS) != null
+                && agvInfoMap.get(KEY_AGV_STS).toString().equals(AGV_CHARGING_STS.START.toString())) {
+            // 反馈TS正常充电
+            this.feedbackTSLog(taskNo, index, NORMAL, agvNo + "AGV开始充电!", xx, chargingSite);
+            log.error("充电机已经开始充电,不可重复请求!车号:{},充电机号:{}", agvNo, chargingSite);
+            return;
+        }
+        // 如果是TS创建的充电任务 是没有对应的WcsTask任务 需要创建 并且设置到Redis
+        // 不管是不是wms下发的充电任务,为了方便我们都重新生成一条wcstask任务
+        if (agvInfoMap.get(KEY_TASK_NO) == null || StringUtils.isEmpty(agvInfoMap.get(KEY_TASK_NO).toString())) {
+            AjaxResult ajaxResult = amsTaskService.addChargingTask(agvNo, chargingSite, index, "charge task insert from CWay");
+            if (!ajaxResult.isSuccess()) {
+                log.error(ajaxResult.getMsg());
+                return;
+            }
+            taskNo = (String) ajaxResult.get("data");
+            agvInfoMap.put(KEY_TASK_NO, taskNo);
+            agvInfoMap.put(KEY_AGV_STS, AGV_CHARGING_STS.READY.toString());
+            redisCache.setCacheMap(KEY_PREFIX + agvNo, agvInfoMap);
+        } else {
+            taskNo = agvInfoMap.get(KEY_TASK_NO).toString();
+        }
+
+        // 从节点ID 充电机根据不同的ip地址区分,从节点地址默认都是1
+        Integer slaveId = CHARGING_SLAVEID_MAPPING.getSlaveIdByChargerNo(chargingSite);
+        // 确认是否故障
+        boolean isFault = this.confirmChargerStatus(IS_FAULT, new Date(), true, xx, chargingSite);
+        if (isFault) {
+            // 反馈TS充电机故障
+            this.feedbackTSLog(taskNo, index, FAULT, agvNo + ",充电机故障!", xx, chargingSite);
+            return;
+        }
+        // 确认信号:在线,待机
+        boolean isOnline = this.confirmChargerStatus(IS_ONLINE, new Date(), true, xx, chargingSite);
+        if (!isOnline) {
+            // 反馈TS充电机故障
+            this.feedbackTSLog(taskNo, index, FAULT, agvNo + ",充电机离线!", xx, chargingSite);
+            return;
+        }
+        // 确认信号:红外到位
+        boolean isReady = this.confirmChargerStatus(IS_READY, new Date(), true, xx, chargingSite);
+        if (!isReady) {
+            // 反馈TS退出重入
+            this.feedbackTSLog(taskNo, index, RE_ENTRY, agvNo + ",AGV退出充电点并重进!", xx, chargingSite);
+            return;
+        }
+        // 充电机启动指令
+        boolean isStart = this.operation(START, xx, chargingSite);
+        if (!isStart) {
+            // 反馈TS充电机故障
+            feedbackTSLog(taskNo, index, FAULT, agvNo + ",充电机启动失败!", xx, chargingSite);
+        }
+        // 确认信号:压紧
+        boolean isCompacting = this.confirmChargerStatus(IS_COMPACTING, new Date(), true, xx, chargingSite);
+        if (!isCompacting) {
+            // 反馈TS充电机故障 不能让AGV退出重进因为会有危险
+            this.feedbackTSLog(taskNo, index, RE_ENTRY, agvNo + ",充电机压紧失败!", xx, chargingSite);
+        }
+        // 反馈TS正常充电
+        this.feedbackTSLog(taskNo, index, NORMAL, agvNo + "AGV开始充电!", xx, chargingSite);
+        // 充电机进行放电(不需要收到TS反馈,直接放电)
+        boolean isDischarge = this.operation(DISCHARGE, xx, chargingSite);
+        if (!isDischarge) {
+            // 反馈TS充电机故障
+            this.feedbackTSLog(taskNo, index, FAULT, agvNo + ",充电机放电失败!", xx, chargingSite);
+        }
+
+        // 设置Redis充电状态为开始
+        Map<String, Object> agvInfoMapU = redisCache.getCacheMap(KEY_PREFIX + agvNo);
+        if (agvInfoMapU != null) {
+            agvInfoMapU.put(KEY_AGV_STS, AGV_CHARGING_STS.START.toString());
+            redisCache.setCacheMap(KEY_PREFIX + agvNo, agvInfoMapU);
+        }
+
+        // 充电期间循环充电机状态
+//        executorService.execute(new CyclicJudgmentOfChargerStatus(wcsTaskService, amsTaskService,
+//                this, feedbackTS, chargingMachineClient, index, chargingSite, slaveId, agvNo, taskNo, xx));
+    }
+
+    /**
+     * AGV充电任务被人为取消
+     * 1.通知充电机结束充电,收回电推杆
+     * 2.通过给参数1反馈一个4(表示结束充电),如果有先前收到过4003消息的标识,此操作可以省略
+     * 3.下发一个TS101的任务,需要给两个参数,参数0给车号,参数1给一个0
+     */
+    public void theChargingTaskCanceledArtificially(Integer index, Integer chargingSite, Integer agvNo) {
+        if (chargingMachineClient == null) {
+            log.error("充电机通讯开关关闭!");
+            return;
+        }
+        // 没有充电桩号没办法取消对应的充电机
+        if (chargingSite == null) {
+            log.error("没有充电桩号没办法取消对应的充电机!");
+            return;
+        }
+        String taskNo = "";
+        // 充电机号
+        Integer chargerNo = chargingSite;
+        boolean xx = !(chargerNo == 1004);
+        // 从节点ID
+        Integer slaveId = CHARGING_SLAVEID_MAPPING.getSlaveIdByChargerNo(chargingSite);
+        // 从Redis拿到充电信息
+        Map<String, Object> agvInfoMap = redisCache.getCacheMap(KEY_PREFIX + agvNo);
+        // 拿到对应WcsTask任务号
+        if (agvInfoMap != null && agvInfoMap.get(KEY_TASK_NO) != null
+                && StringUtils.isNotEmpty(agvInfoMap.get(KEY_TASK_NO).toString())) {
+            taskNo = agvInfoMap.get(KEY_TASK_NO).toString();
+        }
+
+        // 停止充电机
+        boolean isStop = this.operation(STOP, xx, chargingSite);
+        if (!isStop) {
+            // 反馈TS充电机故障
+            feedbackTSLog(taskNo, index, FAULT, agvNo + ",充电机关闭失败!", xx, chargingSite);
+        }
+        // 确认信号:无压紧、无充电、归位
+        boolean isEnd = confirmChargerStatus(IS_END, new Date(), true, xx, chargingSite);
+        if (!isEnd) {
+            // 反馈TS充电机故障
+            this.feedbackTSLog(taskNo, index, FAULT, agvNo + ",确认信号:无压紧、无充电、归位,失败!", xx, chargingSite);
+        }
+        // 判断先前是否收到过4003消息的标识
+//        if (wcsTask != null) {
+//            boolean con = wcsTask.getExt4().equals("1") ? true : false;
+//            if (!con) {
+                // 反馈TS结束充电
+                this.feedbackTSLog(taskNo, index, AGV_STOP, agvNo + ",AGV结束充电!", xx, chargingSite);
+//            }
+//        }
+        // 下发一个TS101的任务
+        feedbackTS.feedbackTS101(taskNo, index, agvNo);
+
+        // 设置Redis充电状态为停止
+        Map<String, Object> agvInfoMapU = redisCache.getCacheMap(KEY_PREFIX + agvNo);
+        if (agvInfoMapU != null) {
+            agvInfoMapU.put(KEY_AGV_STS, AGV_CHARGING_STS.STOP.toString());
+            redisCache.setCacheMap(KEY_PREFIX + agvNo, agvInfoMapU);
+        }
+
+    }
+
+    /**
+     * AGV充电任务结束
+     * 车辆电量到达充电任务的电量,但小于98%,且车辆当前无其他需执行任务时,充电任务结束,
+     * 给你发一个4003的消息,此时你这边不需要做任何操作,因为充电任务结束,但车因为无其他任务,所以仍在原地充电。
+     * 直到车体通过event给你发了一个4002的消息,此时直接下发一个ts101的任务即可。
+     * 1.修改充电对应任务Wcstask已经收到4003消息标识
+     */
+    public void theChargingTaskItSOver(Integer index, Integer chargingSite, Integer agvNo) {
+        String taskNo = "";
+        // 从redis拿到充电信息
+        Map<String, Object> agvInfoMap = redisCache.getCacheMap(KEY_PREFIX + agvNo);
+        // 拿到对应WcsTask任务号
+        if (agvInfoMap != null && agvInfoMap.get(KEY_TASK_NO) != null
+                && StringUtils.isNotEmpty(agvInfoMap.get(KEY_TASK_NO).toString())) {
+            taskNo = agvInfoMap.get(KEY_TASK_NO).toString();
+            amsTaskService.update(new AmsTask(), Wrappers.<AmsTask>lambdaUpdate().set(AmsTask::getExt3, "1").eq(AmsTask::getTaskNo, taskNo));
+        }
+    }
+
+    /**
+     * 充电机操作
+     *
+     * @return
+     * @see com.warewms.ChargingMachineClient.CHARGER_ADDRESS_MEANING
+     */
+    private boolean operation(ChargingMachineClient.CHARGER_ADDRESS_MEANING op, boolean xx, Integer chargingSite) {
+        boolean con = false;
+        con = chargingMachineClient.operation(op, xx, chargingSite);
+        // 如果失败 再重复三次
+        if (!con) {
+            for (int i = 0; i < 2; i++) {
+                con = chargingMachineClient.operation(op, xx, chargingSite);
+                if (con) {
+                    break;
+                }
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return con;
+    }
+
+
+    /**
+     * 反馈TS:通过给参数address改值value来反馈
+     * 并且打印日志
+     *
+     * @param taskNo
+     * @param index
+     * @param tsStatus
+     * @param remarks
+     */
+    private void feedbackTSLog(String taskNo, Integer index
+            , FeedbackTS.FEEDBACK_TS_STATUS tsStatus, String remarks, boolean xx, Integer chargingSite) {
+        // 打印日志
+        if (StringUtils.isNotEmpty(remarks)) {
+            Map<String, Boolean> statusNameMapping = chargingMachineClient.getStatusNameMapping(xx, chargingSite);
+            if (statusNameMapping != null) {
+                log.error(remarks + JSONUtil.toJsonStr(statusNameMapping));
+            }
+        }
+        // 反馈TS
+        feedbackTS.feedbackTS(taskNo, index, tsStatus);
+    }
+
+    /**
+     * 充电期间循环充电机状态 每隔3秒
+     */
+    @Data
+    public class CyclicJudgmentOfChargerStatus implements Runnable {
+        private IAmsTaskService amsTaskService;
+        private ChargerButtService chargerButtService;
+        private FeedbackTS feedbackTS;
+        private ChargingMachineClient chargingMachineClient;
+        private Integer index;
+        private Integer chargingSite;
+        private Integer slaveId;
+        private Integer agvNo;
+        private String taskNo;
+        private boolean xx;
+
+        public CyclicJudgmentOfChargerStatus(IAmsTaskService amsTaskService
+                , ChargerButtService chargerButtService, FeedbackTS feedbackTS, ChargingMachineClient chargingMachineClient
+                , Integer index, Integer chargingSite, Integer slaveId, Integer agvNo, String taskNo, boolean xx) {
+            this.amsTaskService = amsTaskService;
+            this.chargerButtService = chargerButtService;
+            this.feedbackTS = feedbackTS;
+            this.chargingMachineClient = chargingMachineClient;
+            this.index = index;
+            this.chargingSite = chargingSite;
+            this.slaveId = slaveId;
+            this.agvNo = agvNo;
+            this.taskNo = taskNo;
+            this.xx = xx;
+        }
+
+        @Override
+        public void run() {
+            // 开始时间
+            Date startTime = new Date();
+            for (; ; ) {
+                // 超过5小时 退出
+                if (DateUtil.between(startTime, new Date(), DateUnit.HOUR) > 5) {
+                    break;
+                }
+                // 充电任务已经结束(Redis拿到充电状态) 退出
+                Map<String, Object> agvInfoMap = redisCache.getCacheMap(KEY_PREFIX + agvNo);
+                if (agvInfoMap != null) {
+                    String chargingStatus = (String) agvInfoMap.get(KEY_AGV_STS);
+                    if (chargingStatus.equals(AGV_CHARGING_STS.STOP.toString())) {
+                        break;
+                    }
+                }
+                // 拿到amsTask
+                AmsTask amsTask = amsTaskService.getAmsTaskByTaskNo(taskNo);
+
+                // 查询充电机状态是否为完成(确认信号:无压紧、无充电、归位),充电机充电完成会自动停止放电 退回伸缩杆、归位
+                boolean isEnd = chargerButtService.confirmChargerStatus(IS_END, new Date(), false, xx, chargingSite);
+                if (isEnd) {
+                    // 判断先前是否收到过4003消息的标识
+                    if (amsTask != null) {
+                        boolean con = amsTask.getExt3().equals("1") ? true : false;
+                        if (!con) {
+                            // 反馈TS结束充电
+                            chargerButtService.feedbackTSLog(taskNo, index, AGV_STOP, agvNo + "AGV充电结束!", xx, chargingSite);
+                        }
+                    }
+                    // 下发一个TS101的任务
+                    feedbackTS.feedbackTS101(taskNo, index, agvNo);
+                    // 设置Redis充电状态为停止
+                    Map<String, Object> agvInfoMapU = redisCache.getCacheMap(KEY_PREFIX + agvNo);
+                    if (agvInfoMapU != null) {
+                        agvInfoMapU.put(KEY_AGV_STS, AGV_CHARGING_STS.STOP.toString());
+                        redisCache.setCacheMap(KEY_PREFIX + agvNo, agvInfoMapU);
+                    }
+                    break;
+                }
+                // 查询充电机是否故障 关闭充电机 停止充电
+                boolean isFault = chargerButtService.confirmChargerStatus(IS_FAULT, new Date(), false, xx, chargingSite);
+                if (isFault) {
+                    chargerButtService.operation(STOP, xx, chargingSite);
+                    // 反馈TS充电机故障
+                    chargerButtService.feedbackTSLog(taskNo, index, FAULT, agvNo + ",充电机故障!", xx, chargingSite);
+                    // 设置Redis充电状态为停止
+                    Map<String, Object> agvInfoMapU = redisCache.getCacheMap(KEY_PREFIX + agvNo);
+                    if (agvInfoMapU != null) {
+                        agvInfoMapU.put(KEY_AGV_STS, AGV_CHARGING_STS.STOP.toString());
+                        redisCache.setCacheMap(KEY_PREFIX + agvNo, agvInfoMapU);
+                    }
+                    break;
+                }
+                try {
+                    Thread.sleep(3000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 充电机组合状态
+     */
+    public enum CHARGER_ASSEMBLY_STATUS {
+        /***
+         * 确认信号:待机、在线
+         */
+        IS_ONLINE,
+        /***
+         * 确认信号:红外到位
+         */
+        IS_READY,
+        /***
+         * 确认信号:是否故障
+         */
+        IS_FAULT,
+        /***
+         * 确认信号:压紧
+         */
+        IS_COMPACTING,
+        /***
+         * 确认信号:无压紧、无充电、归位
+         */
+        IS_END;
+    }
+
+    /**
+     * 确认充电机状态 递归
+     * 再给充电机发送指令之后,充电机需要一定操作之后才能达到某种状态
+     * 比如发送充电指令之后,充电机从伸出推杆到给压紧信号需要一定的时间
+     * 所以需要重复轮询判断充电机的状态
+     *
+     * @param status
+     * @param startTime   开始时间
+     * @param isRecursion 是否递归
+     * @return
+     */
+    private boolean confirmChargerStatus(CHARGER_ASSEMBLY_STATUS status, Date startTime
+            , boolean isRecursion, boolean xx, Integer chargingSite) {
+        boolean con = false;
+        DateUnit unit = DateUnit.SECOND;
+        int timeout = 1; // 超时时间
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        switch (status) {
+            case IS_ONLINE:
+                // 确认信号:待机、在线
+                con = confirmChargerStatusIsOnline(xx, chargingSite);
+                timeout = 10;
+                break;
+            case IS_READY:
+                // 确认信号:红外到位
+                con = confirmChargerStatusIsReady(xx, chargingSite);
+                timeout = 10;
+                break;
+            case IS_FAULT:
+                // 确认是否故障
+                con = confirmChargerStatusIsFault(xx, chargingSite);
+                if (!con) {
+                    // 如果无故障 不需要重复检查
+                    timeout = 1;
+                }
+                break;
+            case IS_COMPACTING:
+                // 确认信号:压紧
+                con = confirmChargerStatusIsCompacting(xx, chargingSite);
+                timeout = 20;
+                break;
+            case IS_END:
+                // 确认信号:无压紧、无充电、归位
+                con = confirmChargerStatusIsEnd(xx, chargingSite);
+                timeout = 20;
+                break;
+            default:
+                break;
+        }
+        if (!con && isRecursion) {
+            if (DateUtil.between(startTime, new Date(), unit) > timeout) {
+                return con;
+            }
+            // 递归
+            confirmChargerStatus(status, startTime, isRecursion, xx, chargingSite);
+        }
+        return con;
+    }
+
+    /**
+     * 充电机状态
+     * 确认信号:待机、在线
+     *
+     * @return true 正常
+     */
+    private boolean confirmChargerStatusIsOnline(boolean xx, Integer chargingSite) {
+        boolean[] status = chargingMachineClient.getStatus(xx, chargingSite);
+        if (status == null) {
+            // 再去拿一次
+            status = chargingMachineClient.getStatus(xx, chargingSite);
+            if (status == null) {
+                return false;
+            }
+        }
+        // 待机
+        boolean bit_04_0 = BIT_04_0.isTrue(status);
+        // 在线
+//        boolean bit_04_5 = BIT_04_5.isTrue(status);
+
+
+        // 所有的是true 返回true
+        boolean result = Stream.of(bit_04_0/*, bit_04_5*/).allMatch(t -> t == true);
+
+        return result;
+    }
+
+    /**
+     * 充电机状态
+     * 确认信号:红外到位
+     *
+     * @return true 正常
+     */
+    private boolean confirmChargerStatusIsReady(boolean xx, Integer chargingSite) {
+        boolean[] status = chargingMachineClient.getStatus(xx, chargingSite);
+        if (status == null) {
+            // 再去拿一次
+            status = chargingMachineClient.getStatus(xx, chargingSite);
+            if (status == null) {
+                return false;
+            }
+        }
+        // 红外到位
+        boolean bit_06_7 = BIT_06_7.isTrue(status);
+
+
+        // 所有的是true 返回true
+        boolean result = Stream.of(bit_06_7).allMatch(t -> t == true);
+
+        return result;
+    }
+
+    /**
+     * 充电机状态
+     * 确认是否故障
+     *
+     * @return true 有故障 false无故障
+     */
+    private boolean confirmChargerStatusIsFault(boolean xx, Integer chargingSite) {
+        boolean[] status = chargingMachineClient.getStatus(xx, chargingSite);
+        if (status == null) {
+            // 再去拿一次
+            status = chargingMachineClient.getStatus(xx, chargingSite);
+            if (status == null) {
+                return false;
+            }
+        }
+        // 故障
+        boolean bit_04_1 = BIT_04_1.isTrue(status);
+
+        // 总故障
+        boolean bit_05_0 = BIT_05_0.isTrue(status);
+        // 过流
+        boolean bit_05_1 = BIT_05_1.isTrue(status);
+        // 过压
+        boolean bit_05_2 = BIT_05_2.isTrue(status);
+        // 短路
+        boolean bit_05_3 = BIT_05_3.isTrue(status);
+        // 电池未接
+//        boolean bit_05_4 = BIT_05_4.isTrue(status);
+        // 电池反接
+        boolean bit_05_5 = BIT_05_5.isTrue(status);
+        // 模块通信故障
+        boolean bit_05_6 = BIT_05_6.isTrue(status);
+        // Can通信超时
+//        boolean bit_05_7 = BIT_05_7.isTrue(status);
+
+        // 伸缩故障
+        boolean bit_06_5 = BIT_06_5.isTrue(status);
+
+        // 如果任何一个是true 返回true
+        boolean result = Stream.of(bit_04_1
+                , bit_05_0, bit_05_1, bit_05_2, bit_05_3, /*bit_05_4,*/ bit_05_5, bit_05_6/*, bit_05_7*/
+                , bit_06_5).anyMatch(t -> t == true);
+
+        return result;
+    }
+
+    /**
+     * 充电机状态
+     * 确认信号:压紧
+     *
+     * @return true 已压紧
+     */
+    private boolean confirmChargerStatusIsCompacting(boolean xx, Integer chargingSite) {
+        boolean[] status = chargingMachineClient.getStatus(xx, chargingSite);
+        if (status == null) {
+            // 再去拿一次
+            status = chargingMachineClient.getStatus(xx, chargingSite);
+            if (status == null) {
+                return false;
+            }
+        }
+        // 压紧
+        boolean bit_06_6 = BIT_06_6.isTrue(status);
+
+        // 所有的是true 返回true
+        boolean result = Stream.of(bit_06_6).allMatch(t -> t == true);
+
+        return result;
+    }
+
+    /**
+     * 充电机状态
+     * 确认信号:无压紧、无充电、归位
+     *
+     * @return true 正常
+     */
+    private boolean confirmChargerStatusIsEnd(boolean xx, Integer chargingSite) {
+        boolean[] status = chargingMachineClient.getStatus(xx, chargingSite);
+        if (status == null) {
+            // 再去拿一次
+            status = chargingMachineClient.getStatus(xx, chargingSite);
+            if (status == null) {
+                return false;
+            }
+        }
+        // 压紧
+        boolean bit_06_6 = BIT_06_6.isTrue(status);
+        // 进行
+        boolean bit_04_2 = BIT_04_2.isTrue(status);
+        // 归位 取反
+        boolean bit_06_0 = !BIT_06_0.isTrue(status);
+
+
+        // 所有的是false 返回true
+        boolean result = Stream.of(bit_06_6, bit_04_2, bit_06_0).allMatch(t -> t == false);
+
+        return result;
+    }
+
+}

+ 135 - 0
warewms-ams/src/main/java/com/warewms/ams/charging/FeedbackTS.java

@@ -0,0 +1,135 @@
+package com.warewms.ams.charging;
+
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.warewms.ams.common.AmsConstant;
+import com.warewms.ams.ndc.domain.AmsTask;
+import com.warewms.ams.ndc.service.IAmsTaskService;
+import com.warewms.ams.ndc.service.impl.AmsTaskServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author JWK
+ * @version 1.0
+ * @date 2022/10/1 11:22
+ */
+@Service
+public class FeedbackTS {
+
+    @Autowired
+    private IAmsTaskService amsTaskService;
+
+
+    /**
+     * 反馈TS状态
+     */
+    public enum FEEDBACK_TS_STATUS {
+        /***
+         * 让AGV退出充电点并重进
+         */
+        RE_ENTRY("01", "0001"),
+        /***
+         * 充电机故障
+         */
+        FAULT("01", "0002"),
+        /***
+         * 正常充电
+         */
+        NORMAL("01", "0003"),
+        /***
+         * 结束充电
+         */
+        AGV_STOP("01", "0004");
+
+        private String address;
+        private String value;
+
+        FEEDBACK_TS_STATUS(String address, String value) {
+            this.address = address;
+            this.value = value;
+        }
+
+        public String getAddress() {
+            return address;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * 反馈TS:通过给参数address改值value来反馈
+     *
+     * @param taskNo   WcsTask任务号,可以为空
+     * @param index    TS任务号
+     * @param tsStatus
+     */
+    public void feedbackTS(String taskNo, Integer index, FEEDBACK_TS_STATUS tsStatus) {
+        AmsTask addTaskForm = new AmsTask();
+        addTaskForm.setTaskNo(IdWorker.getIdStr());
+        addTaskForm.setIkey((long) AmsTaskServiceImpl.genIKey(addTaskForm.getTaskNo()));
+        addTaskForm.setBusinessType(AmsConstant.TASK_BUSINESS_TYPE.TASK_m.getValue());
+        addTaskForm.setStFrom(998);
+        addTaskForm.setStTo(999);
+        addTaskForm.setPriority(99);
+        addTaskForm.setRemark(taskNo);
+        addTaskForm.setExt1(index.toString());
+        // 把第address位变为value
+        addTaskForm.setExt2(tsStatus.getAddress());
+        addTaskForm.setExt3(tsStatus.getValue());
+
+        // 如果ams_task已经有此任务的待接收m消息,则不生成新的m消息
+        AmsTask amsTaskQuery = new AmsTask();
+        amsTaskQuery.setAciAccept(0);
+        amsTaskQuery.setIsDelete(0);
+        amsTaskQuery.setRemark(taskNo);
+        amsTaskQuery.setBusinessType(AmsConstant.TASK_BUSINESS_TYPE.TASK_m.getValue());
+        amsTaskQuery.setExt1(addTaskForm.getExt1());
+        amsTaskQuery.setExt2(addTaskForm.getExt2());
+        amsTaskQuery.setExt3(addTaskForm.getExt3());
+        List<AmsTask> undoneList = amsTaskService.selectAmsTaskList(amsTaskQuery);
+        if (undoneList.size() > 0) {
+            return;
+        }
+        amsTaskService.insertAmsTask(addTaskForm);
+    }
+
+    /**
+     * 反馈TS
+     *
+     * @param taskNo 任务号 可以为空
+     * @param index TS任务号 可以为空
+     * @param agvNo
+     */
+    public void feedbackTS101(String taskNo, Integer index, Integer agvNo) {
+        AmsTask addTaskForm = new AmsTask();
+        addTaskForm.setTaskNo(IdWorker.getIdStr());
+        addTaskForm.setIkey((long) AmsTaskServiceImpl.genIKey(addTaskForm.getTaskNo()));
+        addTaskForm.setBusinessType(AmsConstant.TASK_BUSINESS_TYPE.TASK_101.getValue());
+        addTaskForm.setExt1(index.toString());
+        addTaskForm.setStFrom(998);
+        addTaskForm.setStTo(999);
+        addTaskForm.setPriority(99);
+        addTaskForm.setRemark(taskNo);
+        // 固定写法
+        addTaskForm.setExt2(agvNo.toString());
+        addTaskForm.setExt3("0004");
+
+        // 如果ams_task已经有此任务的待接收消息,则不生成新的消息
+        AmsTask amsTaskQuery = new AmsTask();
+        amsTaskQuery.setAciAccept(0);
+        amsTaskQuery.setIsDelete(0);
+        amsTaskQuery.setBusinessType(AmsConstant.TASK_BUSINESS_TYPE.TASK_101.getValue());
+        amsTaskQuery.setExt2(addTaskForm.getExt2());
+        amsTaskQuery.setExt3(addTaskForm.getExt3());
+        List<AmsTask> undoneList = amsTaskService.selectAmsTaskList(amsTaskQuery);
+        if (undoneList.size() > 0) {
+            return;
+        }
+        amsTaskService.insertAmsTask(addTaskForm);
+    }
+
+}

+ 5 - 1
warewms-ams/src/main/java/com/warewms/ams/common/AmsConstant.java

@@ -165,7 +165,11 @@ public class AmsConstant {
         /**
          * 暂停
          */
-        TASK_03("03");
+        TASK_03("03"),
+        /**
+         * 结束充电机
+         */
+        TASK_101("101");
 
         private String value;
 

+ 1 - 1
warewms-ams/src/main/java/com/warewms/ams/ndc/AciService.java

@@ -111,7 +111,7 @@ public class AciService {
                 taskBean.setAciAccept(0);
                 taskBean.setBusinessType(AmsConstant.TASK_BUSINESS_TYPE.TASK_POWER.getValue());
                 taskBean = amsTaskService.selectAmsTaskByModel(taskBean);
-                s = "87CD0008000C000100710008658100010001%04X";
+                s = "87CD000800080001007100046301%04X";
                 if (taskBean != null) {
 
                     s = String.format(s, Integer.parseInt(taskBean.getDeviceName()));

+ 5 - 0
warewms-ams/src/main/java/com/warewms/ams/ndc/controller/AmsTaskController.java

@@ -54,6 +54,11 @@ public class AmsTaskController extends BaseController {
         return AjaxResult.success("任务生成成功", Maps.newHashMap("taskNo", taskNo));
     }
 
+    @PostMapping(value = "/addChargingTask")
+    public AjaxResult addChargingTask(@RequestBody @Valid ChargeTaskInsertDTO chargeTaskInsertDTO) {
+        return AjaxResult.success("充电任务生成成功, 任务号为:", amsTaskService.addChargingTask(chargeTaskInsertDTO));
+    }
+
     @GetMapping("{id}")
     @ApiOperation("信息")
     public AjaxResult get(@PathVariable("id") Long id){

+ 18 - 0
warewms-ams/src/main/java/com/warewms/ams/ndc/dto/ChargeTaskInsertDTO.java

@@ -0,0 +1,18 @@
+package com.warewms.ams.ndc.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+public class ChargeTaskInsertDTO implements Serializable {
+
+    @ApiModelProperty("车号")
+    @NotNull(message = "车号不能为空!")
+    private Integer agvNo;
+
+    @ApiModelProperty("充电站点")
+    private Integer chargingSite;
+}

+ 9 - 0
warewms-ams/src/main/java/com/warewms/ams/ndc/service/IAmsTaskService.java

@@ -2,6 +2,7 @@ package com.warewms.ams.ndc.service;
 
 import com.warewms.ams.ndc.domain.AmsTask;
 import com.warewms.ams.ndc.dto.*;
+import com.warewms.common.base.domain.AjaxResult;
 import com.warewms.framework.service.CrudService;
 
 import java.util.List;
@@ -13,6 +14,8 @@ import java.util.List;
  * @date 2022-08-08
  */
 public interface IAmsTaskService extends CrudService<AmsTask, AmsTaskDTO> {
+
+
     /**
      * 查询ams任务列表
      *
@@ -90,4 +93,10 @@ public interface IAmsTaskService extends CrudService<AmsTask, AmsTaskDTO> {
     AmsTask getAmsTaskByAciIndex(Integer aciIndex);
 
     void completeAmsTaskToWms(Long id);
+
+    AjaxResult addChargingTask(ChargeTaskInsertDTO chargeTaskInsertDTO);
+
+    AjaxResult addChargingTask(Integer agvNo, Integer chargingSite, Integer index, String remark);
+
+    AmsTask getAmsTaskByTaskNo(String taskNo);
 }

+ 35 - 0
warewms-ams/src/main/java/com/warewms/ams/ndc/service/impl/AmsTaskServiceImpl.java

@@ -18,6 +18,7 @@ import com.warewms.ams.ndc.dto.*;
 import com.warewms.ams.ndc.mapper.AmsTaskMapper;
 import com.warewms.ams.ndc.service.IAmsTaskService;
 import com.warewms.ams.ndc.service.IBaseLocationInfoService;
+import com.warewms.common.base.domain.AjaxResult;
 import com.warewms.common.exception.base.BaseException;
 import com.warewms.common.utils.ConvertUtils;
 import com.warewms.common.utils.DateUtils;
@@ -360,6 +361,40 @@ public class AmsTaskServiceImpl extends CrudServiceImpl<AmsTaskMapper, AmsTask,
         amsStateFeedbackToWms(AmsConstant.TASK_STS.TASK2.getValue(), amsTaskDTO.getId().toString(), amsTaskDTO.getTaskNo(), null);
     }
 
+    @Override
+    public AjaxResult addChargingTask(ChargeTaskInsertDTO chargeTaskInsertDTO) {
+        log.info("AMS is execute addChargingTask , agvNo is {}",
+                chargeTaskInsertDTO.getAgvNo());
+        return addChargingTask(chargeTaskInsertDTO.getAgvNo(), null
+                , null, "charge task insert from AMS");
+    }
+
+    @Override
+    public AjaxResult addChargingTask(Integer agvNo, Integer chargingSite, Integer index, String remark) {
+        AmsTask amsTask = new AmsTask();
+        amsTask.setTaskNo(IdWorker.getIdStr());
+        amsTask.setBusinessType(AmsConstant.TASK_BUSINESS_TYPE.TASK_POWER.getValue());
+        amsTask.setIsDelete(0);
+        amsTask.setAciAccept(0);
+        amsTask.setIkey((long) genIKey(amsTask.getTaskNo()));
+        amsTask.setPriority(Integer.parseInt(BigDecimal.ONE.toString()));
+        // 充电任务直接传参数
+        amsTask.setStFrom(Integer.parseInt(AmsConstant.LOC_SORTATION_CACHE.toString()));
+        amsTask.setStTo(Integer.parseInt(AmsConstant.LOC_MIDDLE_CACHE.toString()));
+        amsTask.setDeviceName(agvNo.toString());
+        amsTask.setExt1(ObjectUtil.isNotNull(chargingSite) ? chargingSite.toString() : null);
+        amsTask.setExt2(ObjectUtil.isNotNull(index) ? index.toString() : null);
+        amsTask.setRemark(remark);
+        amsTask.setAddtime(DateUtils.getNowDate());
+        insertAmsTask(amsTask);
+        return AjaxResult.success("", amsTask.getTaskNo());
+    }
+
+    @Override
+    public AmsTask getAmsTaskByTaskNo(String taskNo){
+        return getAmsTaskList(Lists.newArrayList(taskNo), null, null).stream().findFirst().orElseGet(() -> null);
+    }
+
     public List<AmsTask> getAmsTaskList(List<String> businessNoList, List<String> idList, Integer aciIndex){
         List<AmsTask> amsTaskList = amsTaskMapper.selectList(Wrappers.<AmsTask>lambdaQuery()
                 .in(CollectionUtil.isNotEmpty(businessNoList), AmsTask::getTaskNo, businessNoList)

+ 86 - 0
warewms-ams/src/main/java/com/warewms/ams/ndc/service/impl/SocketBufferServiceImpl.java

@@ -2,6 +2,7 @@ package com.warewms.ams.ndc.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
+import com.warewms.ams.charging.ChargerButtProxy;
 import com.warewms.ams.common.AmsConstant;
 import com.warewms.ams.ndc.common.Aci;
 import com.warewms.ams.ndc.common.ByteUtil;
@@ -12,18 +13,23 @@ import com.warewms.ams.ndc.service.IAmsNdcEventService;
 import com.warewms.ams.ndc.service.IAmsTaskService;
 import com.warewms.ams.ndc.service.SocketBufferService;
 import com.warewms.ams.ndc.service.StatusUpdateService;
+import com.warewms.common.utils.redis.RedisCache;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
+import static com.warewms.ams.charging.ChargerButtProxy.CHARGER_BUTT_REQUEST.*;
+import static com.warewms.ams.charging.ChargerButtService.*;
 /**
  * Created by IntelliJ IDEA.
  * User: andy.qu
@@ -40,6 +46,11 @@ public class SocketBufferServiceImpl implements SocketBufferService {
     private IAmsTaskService amsTaskService;
     @Autowired
     private StatusUpdateService statusUpdateService;
+    @Autowired
+    private ChargerButtProxy chargerButtProxy;
+
+    @Autowired
+    private RedisCache redisCache;
 
     // 创建一个线程池
     private static ExecutorService executorService;
@@ -298,6 +309,81 @@ public class SocketBufferServiceImpl implements SocketBufferService {
                     task139 = amsTaskService.selectAmsTaskByModel(task139);
                     // agvUnloadingConfirmService.agvError(event,task139);
                     break;
+                case 200://NDC eCode = 4001 | 5秒一次 | 4001消息:充电站点 + INDEX + 车号
+                    log.info("充电消息4001:AGV已经达到充电位置!" + "-" + JSON.toJSONString(event));
+                    Integer chargingSite200 = Integer.parseInt(event.getLp1());// 充电站点
+                    Integer index200 = Integer.parseInt(event.getLp2());// index
+                    Integer agvNo200 = Integer.parseInt(event.getLp3());// 车号
+                    log.info("充电消息4001:AGV已经达到充电位置!车号:" + agvNo200 + ",充电桩:" + chargingSite200 + ",index:" + index200);
+
+                    // 存放当前充电车号和index,充电桩号对应关系
+                    Map<String, Object> agvParamsMap = redisCache.getCacheMap(KEY_PREFIX + agvNo200);
+                    if (agvParamsMap == null || agvParamsMap.get(KEY_AGV_INDEX) == null || agvParamsMap.get(KEY_CHARGING_SITE) == null
+                            || !agvParamsMap.get(KEY_AGV_INDEX).equals(index200)
+                            || !agvParamsMap.get(KEY_CHARGING_SITE).equals(chargingSite200)) {
+                        Map<String, Object> chargingMap = new HashMap<>();
+                        chargingMap.put(KEY_CHARGING_SITE, chargingSite200);
+                        chargingMap.put(KEY_AGV_INDEX, index200);
+                        redisCache.setCacheMap(KEY_PREFIX + agvNo200, chargingMap);
+                    }
+
+               /*
+                  这里为什么没有用index号查询出对应的任务:
+                  1.因为如果是车主动下发的充电任务wms系统是不存在对应的任务号,
+                  2.而index在TS系统里是循环使用的,所以很大概率会把错误的任务号查询出来。
+               */
+                    executorService.execute(() -> {
+                        chargerButtProxy.theChargingSynchronizer(index200, chargingSite200, agvNo200, START);
+                    });
+                    break;
+                case 201://NDC eCode = 4002 | 5秒一次 | 4002消息:两种情况,第一种充电机报错情况下的4002消息——0+0+0+车号,第二种正常充电情况下的4002消息——0+0+1+车号;
+                    log.info("充电消息4002:AGV充电任务被人为取消!" + "-" + JSON.toJSONString(event));
+                    Integer agvNo201 = Integer.parseInt(event.getLp4()); // 车号
+                    Map<String, Object> agvParamsMap01 = redisCache.getCacheMap(KEY_PREFIX + agvNo201);
+                    if (agvParamsMap01.size() == 0) {
+                        log.error("充电消息4002:根据车号获取充电信息为空!");
+                    }
+                    Integer chargingSite201 = (Integer) agvParamsMap01.get(KEY_CHARGING_SITE);// 充电站点
+                    Integer index201 = (Integer) agvParamsMap01.get(KEY_AGV_INDEX);// index
+                    log.info("充电消息4002:AGV充电任务被人为取消!车号:" + agvNo201 + ",充电桩:" + chargingSite201 + ",index:" + index201);
+                    // 因为车体主动取消的任务是没有index号和充电桩站点号的,所以这里只能处理根据车号处理业务。
+                    executorService.execute(() -> {
+                        chargerButtProxy.theChargingSynchronizer(index201, chargingSite201, agvNo201, CANCEL);
+                    });
+                    break;
+                case 202://NDC eCode = 4003 | 4003消息:车号
+                    log.info("充电消息4003:AGV充电任务结束!" + "-" + JSON.toJSONString(event));
+                    Integer agvNo202 = Integer.parseInt(event.getLp1()); // 车号
+                    Map<String, Object> agvParamsMap02 = redisCache.getCacheMap(KEY_PREFIX + agvNo202);
+                    if (agvParamsMap02.size() == 0) {
+                        log.error("充电消息4003:根据车号获取充电信息为空!");
+                    }
+                    Integer chargingSite202 = (Integer) agvParamsMap02.get(KEY_CHARGING_SITE);// 充电站点
+                    Integer index202 = (Integer) agvParamsMap02.get(KEY_AGV_INDEX);// index
+                    log.info("充电消息4003:AGV充电任务结束!车号:" + agvNo202 + ",充电桩:" + chargingSite202 + ",index:" + index202);
+                    // 因为车体主动取消的任务是没有index号和充电桩站点号的,所以这里只能处理根据车号处理业务。
+                    executorService.execute(() -> {
+                        chargerButtProxy.theChargingSynchronizer(index202, chargingSite202, agvNo202, END);
+                    });
+                    break;
+                case 203://NDC eCode = 702 | 4003消息:车号
+                    Integer agvNo = Integer.parseInt(event.getLp1()); // 车号
+                    if (ObjectUtil.isNull(agvNo)) {
+                        log.error("SocketBufferServiceImpl - handleMsgEvent event 702 agvNo is null");
+                        return;
+                    }
+                    Integer battery = Integer.parseInt(event.getLp2()); // 电量
+                    if (ObjectUtil.isNull(battery)) {
+                        log.error("SocketBufferServiceImpl - handleMsgEvent event 702 battery is null");
+                        return;
+                    }
+                    Integer x = Integer.parseInt(event.getLp3()); // x坐标
+                    Integer y = Integer.parseInt(event.getLp4()); // y坐标
+                    if (ObjectUtil.isNull(x) || ObjectUtil.isNull(y)) {
+                        log.error("SocketBufferServiceImpl - handleMsgEvent event 702 coordinate is null");
+                        return;
+                    }
+                    break;
                 default:
                     log.info("----event:" + eventId);
                     break;

+ 15 - 0
warewms-ams/src/main/resources/application-prod.yml

@@ -89,3 +89,18 @@ testtag:
     aciservice: true
     gsService: false
 
+# modbus-tcp
+modbus:
+    tcp-master:
+        first: # 新乡充电机
+            open: true
+            host: 192.168.2.154
+            port: 8899
+        second: # 1号泰坦充电机
+            open: true
+            host: 192.168.2.155
+            port: 8899
+        third: # 2号泰坦充电机
+            open: true
+            host: 192.168.2.156
+            port: 8899

二进制
warewms-ams/src/main/resources/lib/RXTXcomm.jar


二进制
warewms-ams/src/main/resources/lib/jssc-2.8.0.jar


二进制
warewms-ams/src/main/resources/lib/modbus-spring-boot-starter.jar


二进制
warewms-ams/src/main/resources/lib/modbus4j-3.0.5.jar


二进制
warewms-ams/src/main/resources/lib/rfid-1.0.0.jar


二进制
warewms-ams/src/main/resources/lib/rfid-zebra-spring-boot-starter.jar


+ 22 - 1
warewms-ams/src/test/java/warewms/WarehouseTest.java

@@ -2,13 +2,17 @@ package warewms;
 
 import com.warewms.WareWmsApplication;
 import com.warewms.ams.ndc.service.IAmsTaskService;
+import lombok.extern.slf4j.Slf4j;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
 import org.springframework.test.context.junit4.SpringRunner;
 import org.springframework.web.client.RestTemplate;
 
+import javax.annotation.Resource;
+
 ;
 
 /**
@@ -18,6 +22,7 @@ import org.springframework.web.client.RestTemplate;
  */
 @RunWith(SpringRunner.class)
 @SpringBootTest(classes = WareWmsApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Slf4j
 public class WarehouseTest {
 
     @Autowired
@@ -26,10 +31,26 @@ public class WarehouseTest {
     @Autowired
     private IAmsTaskService amsTaskService;
 
-    private final String WMS_URL = "http://localhost:8008/base/locationInfo/save";
+    private final String WMS_URL = "localhost:8008/base/locationInfo/save";
     @Test
     public void test() {
+//        Map<String, Object> feignClientBeanMap = applicationContext.getBeansWithAnnotation(FeignClient.class);
+//        Assert.isTrue(CollectionUtil.isNotEmpty(feignClientBeanMap), "Annotation FeignClient is not configuration in the project");
+//        Map<String, Object> feignClientAnnotationMap = feignClientBeanMap.values().stream()
+//                .collect(Collectors.toMap(item -> item.getClass().getSimpleName(), Object::getClass));
+//        Map<String, Object> feignClientAnnotationMap3 = feignClientBeanMap.entrySet().stream()
+//                .collect(Collectors.toMap(item -> item.getValue().getClass().getSimpleName(), item -> item.getValue().getClass()));
+//        System.out.println(feignClientAnnotationMap);
+//
+//        System.out.println(feignClientAnnotationMap3);
         amsTaskService.ndcAmsTaskInsert("JHSWMS000005122_19", "01", 20000, "7014");
     }
 
+    @Resource
+    private ApplicationContext applicationContext;
+    @Test
+    public void testClassInvokeMethod(){
+
+    }
+
 }

+ 87 - 0
warewms-hard/pom.xml

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>warewms</artifactId>
+        <groupId>com.warewms</groupId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>warewms-hard</artifactId>
+    <version>1.0.0</version>
+    <description>硬件对接</description>
+
+    <properties>
+        <local.lib.basedir>${pom.basedir}/../warewms-ams/src/main/resources/lib</local.lib.basedir>
+    </properties>
+
+    <dependencies>
+
+        <!-- SpringBoot Web容器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+
+        <!--自定义starter组件-->
+        <dependency>
+            <groupId>com.jwk</groupId>
+            <artifactId>modbus-spring-boot-starter</artifactId>
+            <version>1.0</version>
+            <scope>system</scope>
+            <systemPath>${local.lib.basedir}/modbus-spring-boot-starter.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.jwk</groupId>
+            <artifactId>rfid-zebra-spring-boot-starter</artifactId>
+            <version>1.0</version>
+            <scope>system</scope>
+            <systemPath>${local.lib.basedir}/rfid-zebra-spring-boot-starter.jar</systemPath>
+        </dependency>
+
+        <!-- rfid-zebra-只能跑在window上-->
+        <dependency>
+            <groupId>com.jwk</groupId>
+            <artifactId>rfid</artifactId>
+            <version>1.0.0</version>
+            <scope>system</scope>
+            <systemPath>${local.lib.basedir}/rfid-1.0.0.jar</systemPath>
+        </dependency>
+
+        <!--modbus4j start-->
+        <dependency>
+            <groupId>com.jwk</groupId>
+            <artifactId>RXTXcomm</artifactId>
+            <version>3.0.5</version>
+            <scope>system</scope>
+            <systemPath>${local.lib.basedir}/RXTXcomm.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.jwk</groupId>
+            <artifactId>modbus4j</artifactId>
+            <version>3.0.5</version>
+            <scope>system</scope>
+            <systemPath>${local.lib.basedir}/modbus4j-3.0.5.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.jwk</groupId>
+            <artifactId>jssc</artifactId>
+            <version>2.8.0</version>
+            <scope>system</scope>
+            <systemPath>${local.lib.basedir}/jssc-2.8.0.jar</systemPath>
+        </dependency>
+        <!--modbus4j end-->
+    </dependencies>
+</project>

+ 463 - 0
warewms-hard/src/main/java/com/warewms/ChargingMachineClient.java

@@ -0,0 +1,463 @@
+package com.warewms;
+
+import cn.hutool.core.comparator.CompareUtil;
+import com.jwk.spring.boot.autoconfigure.ModbusTcpMasterTemplate;
+import com.jwk.spring.boot.modbus4j.ModbusMasterUtil;
+import com.serotonin.modbus4j.msg.ReadResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static com.warewms.ChargingMachineClient.CHARGER_ADDRESS_MEANING.*;
+
+
+/**
+ * 充电机
+ *
+ * @author JWK
+ * @version 1.0
+ * @date 2022/9/9 15:03
+ */
+@Slf4j
+@Component
+public class ChargingMachineClient {
+
+    /**
+     * 充电机(三向车)
+     */
+    @Autowired(required = false)
+    @Qualifier("modbusTcpMasterTemplateFirst")
+    private ModbusTcpMasterTemplate modbusTcpMasterTemplateFirst;
+
+    /**
+     * 充电机(三向车)
+     */
+    @Autowired(required = false)
+    @Qualifier("modbusTcpMasterTemplateSecond")
+    private ModbusTcpMasterTemplate modbusTcpMasterTemplateSecond;
+
+    /**
+     * 充电机(三向车)
+     */
+    @Autowired(required = false)
+    @Qualifier("modbusTcpMasterTemplateThird")
+    private ModbusTcpMasterTemplate modbusTcpMasterTemplateThird;
+
+    private Map<String, ModbusTcpMasterTemplate> tcpMasterTemplateMap;
+
+    @PostConstruct
+    public void initMap() {
+        tcpMasterTemplateMap = new HashMap<>();
+        tcpMasterTemplateMap.put(CHARGING_SITE.FIRST.name(), modbusTcpMasterTemplateFirst);
+        tcpMasterTemplateMap.put(CHARGING_SITE.SECOND.name(), modbusTcpMasterTemplateSecond);
+        tcpMasterTemplateMap.put(CHARGING_SITE.THIRD.name(), modbusTcpMasterTemplateThird);
+    }
+
+    public ModbusMasterUtil getModbusMasterUtil(Integer chargingSite) {
+        return tcpMasterTemplateMap.get(CHARGING_SITE.getNameByValue(chargingSite)).getModbusMasterUtil();
+    }
+
+    /**
+     * 自动门编号
+     */
+    public enum CHARGING_SITE {
+        /***
+         * 靠墙门
+         */
+        FIRST(7050),
+        /***
+         * 靠室内
+         */
+        SECOND(7051),
+        /***
+         * 靠墙门(第二层)
+         */
+        THIRD(7052);
+
+        private Integer value;
+
+        CHARGING_SITE(Integer value) {
+            this.value = value;
+        }
+
+        public Integer getValue() {
+            return value;
+        }
+
+        public static String getNameByValue(Integer value) {
+            return Arrays.stream(values()).filter(item -> CompareUtil.compare(item.getValue(), value) == 0)
+                    .map(CHARGING_SITE::name).findFirst().orElseGet(() -> null);
+        }
+
+        /**
+         * 根据值获得枚举类型 switch
+         *
+         * @param value
+         * @return
+         */
+        public static CHARGING_SITE getByValue(Integer value) {
+            for (CHARGING_SITE code : values()) {
+                if (code.getValue().equals(value)) {
+                    return code;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * 充电机操作
+     *
+     * @param operation
+     * @return
+     */
+    public boolean operation(CHARGER_ADDRESS_MEANING operation, boolean con, Integer chargingSite) {
+        switch (operation) {
+            // 启动
+            case START:
+                return start(chargingSite);
+            // 停止
+            case STOP:
+                return stop(chargingSite);
+            // 放电
+            case DISCHARGE:
+                return discharge(chargingSite);
+            default:
+                break;
+        }
+        return false;
+    }
+
+    /**
+     * 获取状态类型和状态对应关系表
+     *
+     * @return
+     */
+    public Map<CHARGER_STATUS, Boolean> getStatusMapping(boolean con, Integer chargingSite) {
+        return ChargingMachineClient.CHARGER_STATUS.getMapping(getStatus(con, chargingSite));
+    }
+
+    /**
+     * 获取状态描述和状态对应关系表
+     *
+     * @return
+     */
+    public Map<String, Boolean> getStatusNameMapping(boolean con, Integer chargingSite) {
+        return ChargingMachineClient.CHARGER_STATUS.getNameMapping(getStatus(con, chargingSite));
+    }
+
+    /**
+     * 获取充电机状态
+     * 第4个字节(状态代码) 第5个字节(故障代码) 第6个字节(各种状态,有些功能需要硬件支持)
+     * 返回3个字节 24位
+     * 假设3个字节为:00000110 00000101 00000100
+     * 则最终返回的数组:[0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+     */
+    public boolean[] getStatus(boolean con, Integer chargingSite) {
+        ModbusMasterUtil modbusMasterUtil = getModbusMasterUtil(chargingSite);
+        ReadResponse read = modbusMasterUtil.readCoils(1, CHARGER_STATUS.getOffset(), CHARGER_STATUS.getCommand());
+        if (read == null || read.getBooleanData() == null) {
+            log.error("获取充电机状态为空!");
+            return null;
+        }
+        if (read.getBooleanData().length != 24) {
+            log.error("获取充电机状态错误,不足24位:" + Arrays.toString(read.getBooleanData()));
+            return null;
+        }
+        return read.getBooleanData();
+    }
+
+    /**
+     * 启动充电机
+     *
+     * @return
+     */
+    private boolean start(Integer chargingSite) {
+        ModbusMasterUtil modbusMasterUtil = getModbusMasterUtil(chargingSite);
+        return modbusMasterUtil.writeCoil(1, START.getOffset(), START.getCommand());
+    }
+
+    /**
+     * 停止充电机
+     *
+     * @return
+     */
+    private boolean stop(Integer chargingSite) {
+        ModbusMasterUtil modbusMasterUtil = getModbusMasterUtil(chargingSite);
+        return modbusMasterUtil.writeCoil(1, STOP.getOffset(), STOP.getCommand());
+    }
+
+    /**
+     * 充电机放电
+     *
+     * @return
+     */
+    private boolean discharge(Integer chargingSite) {
+        ModbusMasterUtil modbusMasterUtil = getModbusMasterUtil(chargingSite);
+        return modbusMasterUtil.writeCoil(1, DISCHARGE.getOffset(), DISCHARGE.getCommand());
+    }
+
+    /**
+     * 功能码
+     */
+    public enum FUNCTION_CODE {
+        /***
+         * 读线圈寄存器
+         */
+        READ_01H(0x01),
+        /***
+         * 读保持寄存器
+         */
+        READ_03H(0x03),
+        /***
+         * 写单个线圈寄存器
+         */
+        WRITE_05H(0x05),
+        /***
+         * 写单个保持寄存器
+         */
+        WRITE_06H(0x06);
+
+        private Integer value;
+
+        FUNCTION_CODE(Integer value) {
+            this.value = value;
+        }
+
+        public Integer getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * 充电机ModBus地址码
+     */
+    public enum CHARGER_ADDRESS_MEANING {
+        /***
+         * 启动充电机
+         */
+        START(FUNCTION_CODE.WRITE_05H, 0x0007, 0, 0x0007, 0XFF00),
+        /***
+         * 停止充电机
+         */
+        STOP(FUNCTION_CODE.WRITE_05H, 0x0007, 0, 0x0007, 0X0000),
+        /**
+         * 充电机放电
+         */
+        DISCHARGE(FUNCTION_CODE.WRITE_05H, 0x0008, 0, 0x0008, 0xFF00),
+        /**
+         * 充电机状态
+         */
+        CHARGER_STATUS(FUNCTION_CODE.READ_01H, 0x0040, 24, 0x0040, 0X0018),
+        /**
+         * 读取电压(/100)
+         */
+        READ_VOLTAGE(FUNCTION_CODE.READ_03H, 0x0065, 1, 0x0065, 0X0001),
+        /**
+         * 读取电流(/100)
+         */
+        READ_CURRENT(FUNCTION_CODE.READ_03H, 0x0066, 1, 0x0066, 0X0001),
+        /**
+         * 读取当前容量(/100)
+         */
+        READ_CAPACITY(FUNCTION_CODE.READ_03H, 0x0067, 1, 0x0067, 0X0001),
+        /**
+         * 时
+         */
+        HOURS(FUNCTION_CODE.READ_03H, 0x0068, 1, 0x0068, 0X0001),
+        /**
+         * 钟
+         */
+        MINUTES(FUNCTION_CODE.READ_03H, 0x0069, 1, 0x0069, 0X0001),
+        /**
+         * 秒
+         */
+        SECONDS(FUNCTION_CODE.READ_03H, 0x006A, 1, 0x006A, 0X0001),
+        /**
+         * 设置电压29.00V
+         */
+        SET_VOLTAGE(FUNCTION_CODE.WRITE_06H, 0x0090, 0, 0x0090, 0X0B54),
+        /**
+         * 设置电流29.00A
+         */
+        SET_CURRENT(FUNCTION_CODE.WRITE_06H, 0x0091, 0, 0x0091, 0X0B54),
+        /**
+         * 离线
+         */
+        SET_OFFLINE(FUNCTION_CODE.WRITE_06H, 0x0092, 0, 0x0092, 0X000),
+        /**
+         * 自动
+         */
+        SET_AUTO(FUNCTION_CODE.WRITE_06H, 0x0092, 0, 0x0092, 0X001),
+        /**
+         * 在线
+         */
+        SET_ONLINE(FUNCTION_CODE.WRITE_06H, 0x0092, 0, 0x0092, 0X002);
+
+        /**
+         * 功能码
+         */
+        private FUNCTION_CODE functionCode;
+
+        /**
+         * 读取和写入的开始偏移量
+         */
+        private int offset;
+
+        /**
+         * 长度 10进制
+         */
+        private int length;
+
+        /**
+         * PLC地址
+         */
+        private int address;
+
+        /**
+         * 命令
+         */
+        private int command;
+
+        CHARGER_ADDRESS_MEANING(FUNCTION_CODE functionCode, int offset, int length, int address, int command) {
+            this.functionCode = functionCode;
+            this.offset = offset;
+            this.length = length;
+            this.address = address;
+            this.command = command;
+        }
+
+        public FUNCTION_CODE getFunctionCode() {
+            return functionCode;
+        }
+
+        public int getOffset() {
+            return offset;
+        }
+
+        public int getLength() {
+            return length;
+        }
+
+        public int getAddress() {
+            return address;
+        }
+
+        public int getCommand() {
+            return command;
+        }
+    }
+
+    /**
+     * 充电机状态:
+     * 第4个字节(状态代码) 第5个字节(故障代码) 第6个字节(各种状态,有些功能需要硬件支持)
+     * 假设:第4个字节[00000110] 第5个字节[00000101] 第6个[00000100]
+     * 放在同一个数组中表示:[0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+     */
+    public enum CHARGER_STATUS {
+
+        // 4bit
+        BIT_04_0("待机", 0),
+        BIT_04_1("故障", 1),
+        BIT_04_2("进行", 2),
+        BIT_04_3("完成", 3),
+        BIT_04_4("离线", 4),
+        BIT_04_5("在线", 5),
+        BIT_04_6("空", 6),
+        BIT_04_7("空", 7),
+
+        // 5bit
+        BIT_05_0("总故障", 8),
+        BIT_05_1("过流", 9),
+        BIT_05_2("过压", 10),
+        BIT_05_3("短路", 11),
+        BIT_05_4("电池未接", 12),
+        BIT_05_5("电池反接", 13),
+        BIT_05_6("模块通信故障", 14),
+        BIT_05_7("Can通信超时", 15),
+
+        // 6bit
+        BIT_06_0("归位", 16),
+        BIT_06_1("正在伸出", 17),
+        BIT_06_2("正在退回", 18),
+        BIT_06_3("伸出充电", 19),
+        BIT_06_4("伸出到底", 20),
+        BIT_06_5("伸缩故障", 21),
+        BIT_06_6("压紧", 22),
+        BIT_06_7("红外到位", 23);
+
+        /**
+         * 判断某位是否为true
+         *
+         * @param booleans
+         * @return
+         */
+        public boolean isTrue(boolean[] booleans) {
+            return booleans[this.getIndex()];
+        }
+
+        /**
+         * 获取枚举类型和状态对应关系表
+         *
+         * @param booleans
+         * @return
+         */
+        public static Map<CHARGER_STATUS, Boolean> getMapping(boolean[] booleans) {
+            if (booleans == null) {
+                return null;
+            }
+            Map<CHARGER_STATUS, Boolean> map = new LinkedHashMap<>();
+            for (CHARGER_STATUS value : values()) {
+                map.put(value, value.isTrue(booleans));
+            }
+            return map;
+        }
+
+        /**
+         * 获取描述和状态对应关系表
+         *
+         * @param booleans
+         * @return
+         */
+        public static Map<String, Boolean> getNameMapping(boolean[] booleans) {
+            if (booleans == null) {
+                return null;
+            }
+            Map<String, Boolean> map = new LinkedHashMap<>();
+            Map<CHARGER_STATUS, Boolean> mapping = getMapping(booleans);
+            mapping.forEach((k, v) -> map.put(k.getDescription(), v));
+            mapping.remove(BIT_04_6.getDescription());
+            return map;
+        }
+
+        /**
+         * 状态描述
+         */
+        private String description;
+        /**
+         * 在数组中下标
+         */
+        private int index;
+
+        CHARGER_STATUS(String description, int index) {
+            this.description = description;
+            this.index = index;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public int getIndex() {
+            return index;
+        }
+    }
+
+}