ThinkraceUtil.java 11.8 KB
package com.ruoyi.system.protocol;

import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.global.IDeviceException;
import com.ruoyi.common.global.ResultCode;
import com.ruoyi.system.protocol.adapter.ICommandAdapter;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class ThinkraceUtil {
    // 命令过期时间
    private static final Integer COMMAND_TTL = 300;

    private static final String COMMAND_SN = "command:sn";
    private static final String COMMAND_SEND = "command:send:";
    public static final Integer COMMAND_LABEL_SURE = 1;
    public static final Integer COMMAND_LABEL_INIT = 0;
    private static final Long SN_LENGTH = 1000000L;
    private static RedisCache REDIS_CACHE;
    /**
     * 指令列表
     */
    private static final Map<String, ICommandAdapter> REGEX_MAP = new HashMap<>();

    /**
     * 时区
     */
    public static String UTC_8 = "8";
    /**
     * 命令结束符
     */
    public static String END_SYMBOL = "#";
    /**
     * 设备号
     */
    public static String IMEI = "IMEI";

    public static String generatorSn() {
        Long commandSn;
        // 防止出现线程安全 重复覆盖值的情况
        synchronized (ThinkraceUtil.class) {
            // 指令流水号是6位的 不足补0
            commandSn = REDIS_CACHE.increment(COMMAND_SN);
            if (commandSn == null || commandSn > SN_LENGTH) {
                commandSn = 0L;
                REDIS_CACHE.setCacheObject(COMMAND_SN, commandSn.intValue());
            }
        }
        String commandSnStr = commandSn.toString();
        int snLen = (SN_LENGTH.toString().length() - 1) - commandSnStr.length();
        StringBuilder sb = new StringBuilder();
        //长度不足6位补零
        for (int i = 0; i < snLen; i++) {
            sb.append("0");
        }
        return sb + commandSnStr;
    }

    public static String commandLabelInit(String imei, String sn) {
        // 存入redis处理会有网络io的消耗  所以redis和服务器最好是在同一台服务器 原则是30s 增加容错
        String key = getCommandKey(imei, sn);
        REDIS_CACHE.expire(key, COMMAND_LABEL_INIT, COMMAND_TTL, TimeUnit.SECONDS);
        return key;
    }

    private static String getCommandKey(String imei, String sn) {
        return COMMAND_SEND + imei + ":" + sn;
    }

    public static Integer getCommandSn(String key) {
        return REDIS_CACHE.getCacheObject(key);
    }


    public static class REQ {
        /**
         * 登陆包
         */
        public static final String LOGIN_REQ = "IWAP00";
        public static final String GPS_REQ = "IWAP01";
        public static final String MORE_GPS_REQ = "IWAP02";
        public static final String KEEPLIVE_REQ = "IWAP03";
        /**
         * 电量报警
         */
        public static final String POWER_REQ = "IWAP04";
        /**
         * 这个可能在文档内是被划线了的可能没有启用过 语音传输没有在设备找到按钮
         */
        @Deprecated
        public static final String SPEECH_REQ = "IWAP05";
        /**
         * 语音上行
         */
        public static final String SPEECH_UP_REQ = "IWAP07";
        /**
         * 设备报警与地址回复包
         */
        public static final String ALARM_REQ = "IWAP10";
        public static final String IMAGE_REQ = "IWAP42";
        public static final String HEART_REQ = "IWAP49";
        public static final String TEMPERATURE_50_REQ = "IWAP50";
        public static final String QUERY_BLUETOOTH_REQ = "IWAP51";
        public static final String CDMA_GPS_REQ = "IWAP91";
        public static final String CDMA_WIFI_GPS_REQ = "IWAP92";
        public static final String BLUETOOTH_REQ = "IWAPBL";
        public static final String HEART_BLOOD_REQ = "IWAPHT";
        public static final String HEALTH_REQ = "IWAPJK";
        public static final String TEMPERATURE_REQ = "IWAPTP";
        public static final String VERSION_REQ = "IWAPVR";
        public static final String WEARING_REQ = "IWAPWR";
        /**
         * 12	上传心率、血压、血氧、血糖
         */
        public static final String HEART_RATE_BLOOD_PRESSURE_OXYGEN_SUGAR_REQ = "IWAPHP";
        public static final String BASE_STATION_TIMING_GPS_REQ = "IWAPTM";
        /**
         * 14	同步天气
         */
        public static final String WEATHER_REQ = "IWAPWT";
        /**
         * 确认已读文字
         */
        public static final String SURE_TEXT_REQ = "IWAP94";
        public static final String ECG_DATA_REQ = "IWAPHD";
        /**
         * 设备响应
         */
        public static final String CONTACT_REQ = "IWAP14";
        public static final String SOS_REQ = "IWAP12";


        public static final String UPLOAD_GPS_TIME_REQ = "IWAP15";

    }

    public static class RES {
        /**
         * 登陆包
         */
        public static final String LOGIN_RES = "IWBP00";
        /**
         * 定位
         */
        public static final String GPS_RES = "IWBP01";
        /**
         * 多基站GPS定位
         */
        public static final String MORE_GPS_RES = "IWBP02";
        /**
         * 心跳
         */
        public static final String KEEPLIVE_RES = "IWBP03";
        public static final String HEART_RES = "IWBP49";
        public static final String TEMPERATURE_50_RES = "IWBP50";
        public static final String QUERY_BLUETOOTH_RES = "IWBP51";
        public static final String CDMA_GPS_RES = "IWBP91";

        public static final String CDMA_WIFI_GPS_RES = "IWBP92";
        /**
         * 电源报警
         */
        public static final String POWER_RES = "IWBP04";
        /**
         * 设备报警与地址回复包
         */
        public static final String ALARM_RES = "IWBP10";
        public static final String IMAGE_RES = "IWBP42";
        public static final String BLUETOOTH_RES = "IWBPBL";
        public static final String HEART_BLOOD_RES = "IWBPHT";
        public static final String HEALTH_RES = "IWBPJK";
        public static final String TEMPERATURE_RES = "IWBPTP";
        public static final String HEART_RATE_BLOOD_PRESSURE_OXYGEN_SUGAR_RES = "IWBPHP";
        /**
         * 基站校时并获取经纬度协议
         */
        public static final String BASE_STATION_TIMING_GPS_RES = "IWBPTM";
        ;
        /**
         * 确认已读文字
         */
        public static final String SURE_TEXT_RES = "IWBP94";
        public static final String ECG_DATA_RES = "IWBPHD";
        @Deprecated
        public static final String SPEECH_RES = "IWBP05";
        public static final String SPEECH_UP_RES = "IWBP07";
        public static final String WEATHER_RES = "IWBPWT";
    }

    /**
     * 下发指令集
     */
    public static class ACTIVE {
        public static final String CONTACT_ACTIVE = "IWBP14";
        public static final String UPLOAD_GPS_TIME_ACTIVE = "IWBP15";
        public static final String SOS_ACTIVE = "IWBP12";
    }


    public static ICommandAdapter getAdapter(String key) {
        return REGEX_MAP.get(key);
    }

    public static void addAdapter(String key, ICommandAdapter adapter) {
        REGEX_MAP.put(key, adapter);
    }

    public static void removeDevice(String imei) {
        DeviceManager.removeDevice(imei);
    }

    public static boolean hasDevice(Channel channel) {
        return DeviceManager.hasDevice(channel);
    }

    public static void sendCommand(String imei, String command) {
        DeviceManager.sendCommand(imei, command);
    }

    public static boolean askCommand(String txt, String imei, String command, String commandSn, ICommandAdapter adapter) {
        return DeviceManager.askCommand(txt, imei, command, commandSn, adapter);
    }

    public static void setDevice(String imei, ChannelHandlerContext ctx) {
        DeviceManager.onlineDevice(imei, ctx);
    }


    /**
     * 管理设备
     *
     * @author 20412
     */
    protected static class DeviceManager {

        private static final Logger log = LoggerFactory.getLogger(DeviceManager.class);
        private static final ConcurrentHashMap<String, Channel> DEVICE_MAP = new ConcurrentHashMap<>();

        /**
         * 设备上线
         *
         * @param imei 设备号
         * @param ctx  当前连接的处理器上下文
         */
        public static void onlineDevice(String imei, ChannelHandlerContext ctx) {
            if (DEVICE_MAP.containsKey(imei)) {
                // 关闭之前的连接
                Channel channel = DEVICE_MAP.get(imei);
                channel.close();
            }
            // 为当前连接设置id
            Channel channel = ctx.channel();
            channel.attr(AttributeKey.valueOf(ThinkraceUtil.IMEI)).set(imei);
            // 继续传递事件给下一个 ChannelHandler
            ctx.fireChannelActive();
            init(imei, channel);
            log.info("设备上线:设备号为:{}", imei);
        }

        /**
         * 移除设备 以及资源管理器
         *
         * @param imei 设备号
         */
        public static void removeDevice(String imei) {
            if (DEVICE_MAP.containsKey(imei)) {
                clean(imei);
                log.info("设备离线,设备号为:{}", imei);
            }
        }

        /**
         * 向设备发送指令
         *
         * @param imei    设备号
         * @param command 指令
         * @return obj      处理结果
         */
        public static void sendCommand(String imei, String command) {
            log.info("服务器发起:{}", command);
            Channel channel = DEVICE_MAP.get(imei);
            if (Objects.isNull(channel)) {
                throw new IDeviceException(ResultCode.CODE_207, "设备" + imei + "不在线");
            }
            channel.writeAndFlush(Unpooled.copiedBuffer(command, CharsetUtil.UTF_8));
        }

        /**
         * 判断信道内是否有设备注册
         *
         * @param channel 通道
         * @return result 如果存在返回 --true 如果不存在返回 --false
         */
        public static boolean hasDevice(Channel channel) {
            AttributeKey<String> key = AttributeKey.valueOf(ThinkraceUtil.IMEI);
            //netty移除了这个map的remove方法,这里的判断谨慎一点
            return (channel.hasAttr(key) || channel.attr(key).get() != null);
        }


        // 设备上线 创建资源管理器
        private static void init(String imei, Channel channel) {
            // 设备上线
            DEVICE_MAP.put(imei, channel);
        }

        // 离线删除创建的资源管理器
        private static void clean(String imei) {
            DEVICE_MAP.remove(imei);
        }

        /**
         * 指令响应
         *
         * @param imei      设备号 可以为 -- null
         * @param command   指令
         * @param commandSn 指令流水号
         * @param adapter   适配器  -- 调用适配器的更新方法
         * @return obj 处理结果
         */
        public static boolean askCommand(String txt, String imei, String command, String commandSn, ICommandAdapter adapter) {
            log.info("{}:设备:{},命令流水号:{},响应命令:{}", txt, imei, commandSn, command);
            // 通过事件通知 另外单独一个类通知
            String key = getCommandKey(imei, commandSn);
            REDIS_CACHE.expire(key, COMMAND_LABEL_SURE, COMMAND_TTL, TimeUnit.SECONDS);
            return true;
        }
    }


    public static void initRedisThinkraceCommandParam(RedisCache redisCache) {
        REDIS_CACHE = redisCache;
        REDIS_CACHE.setNx(COMMAND_SN, 0);
    }

}