Distributed project iot-device-data device data monitoring

Keywords: Session socket Spring kafka

Last time, when the device sends data, we need to see what the data sent by the device is on the management page, so now we can complete the device data monitoring module.

iot-device-data

Create iot-device-data module, because this module is also a subscription board service, so it also consumes Mapping data in kakfa and introduces the corresponding redis, kakfa module.

Logic diagram

web socket

Data is real-time interaction with pages, and web socket is needed.

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>

There are two ways for spring boot to use web sockets, Server Endpoint Exporter and Text web socket Handler, while Server Endpoint Exporter, because webSocket bean s are not managed by spring containers, has the problem that injection can't be used, although it can be solved, but it's a bit uncomfortable; Text web socket Handler is completely implemented by spring, so there will be no injection problem. Therefore, the author uses TextWebSocket Handler.

web socket handler

[@Component](https://my.oschina.net/u/3907912)
public class DeviceDateHandler extends TextWebSocketHandler {

    @Autowired
    private DeviceDateService deviceDateService;

    /**
     * Open session
     * [@param](https://my.oschina.net/u/2303379) session
     */

    [@Override](https://my.oschina.net/u/1162528)
    public void afterConnectionEstablished(WebSocketSession session) 
	throws Exception {
        deviceDateService.onOpen(session);
    }

    /**
     * Closing session
     * [@param](https://my.oschina.net/u/2303379) session
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, 
	CloseStatus status) throws Exception {
        deviceDateService.onClose(session);
    }

    /**
     * exception handling
     * @param session
     * @param
     */
    @Override
    public void handleTransportError(WebSocketSession session, 
	Throwable exception) throws Exception {
        deviceDateService.onError(session,exception);
    }
}

web socket config

@Configuration
@EnableWebSocket //Open web socket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private DeviceDateHandler deviceDateHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry 
	registry) {
        registry.addHandler(deviceDateHandler, "/ws/device").
		setAllowedOrigins("*"); //Cross domain allowed
    }
}

DeviceDateService web socket establishes login

When the web socket establishes the login, it needs to send the device's IMEI and id number together. We verify the data in onOpen() method. Only through the link of verification, can we complete the login.

@Service
public class DeviceDateService {

    @Autowired
    private BaseRedisUtil baseRedisUtil;

    private static final String IEMI = "imei";
    private static final String DID = "did";

    /**
     * Open session
     * @param session
     */
    public void onOpen(WebSocketSession session) {
        Map<String,String> map = getMap(session);
        RedisDeviceVO vo = baseRedisUtil.get(map.get(IEMI));
        if (vo == null){
            throw new RuntimeException("Equipment not registered");
        }
        if (vo.getId().equals(map.get(DID))){
            throw new RuntimeException("Error in device number");
        }
        WebSocketUtil.put(map.get(DID),session);
    }

    /**8
     * Closing session
     * @param session
     */
    public void onClose(WebSocketSession session) throws IOException {
        Map<String,String> map = getMap(session);
        WebSocketUtil.remove(map.get(DID),session);
        session.close();
    }

    /**
     * exception handling
     * @param session
     * @param error
     */
    public void onError(WebSocketSession session, Throwable error) 
	throws IOException {
        if (error instanceof RuntimeException){
            session.sendMessage(new TextMessage(error.getMessage()));
        }else {
            session.sendMessage(new TextMessage("System exception"));
        }
        onClose(session);
    }

    private Map<String,String> getMap(WebSocketSession session){
        String query = session.getUri().getQuery();
        if (StringUtils.isEmpty(query)){
            throw new RuntimeException("Parameter error");
        }
        String[] param = query.split("&");
        if (param.length != 2){
            throw new RuntimeException("Parameter error");
        }
        Map<String,String> map = new HashMap<>();
        map.put(IEMI,param[0]);
        map.put(DID,param[1]);
        return map;
    }
}

web socket link cache

For links that have been successfully logged in, session needs to be cached, and a device may have several web socket s monitored at the same time, so session is saved in one-to-many form here. Session is a concurrent operation where different threads are created and destroyed at the same time, so concurrent collections are used for processing.

public class WebSocketUtil {

    private static final Map<String, CopyOnWriteArrayList<WebSocketSession>>
	SESSION_MAP = new HashMap<>();


    public static void put(String did,WebSocketSession session){
        CopyOnWriteArrayList<WebSocketSession> list = SESSION_MAP.get(did);
        if (list == null){
            list = new CopyOnWriteArrayList();
            SESSION_MAP.put(did,list);
        }
        list.add(session);
    }

    public static void remove(String did,WebSocketSession session){
        CopyOnWriteArrayList<WebSocketSession> list = SESSION_MAP.get(did);
        if (CollectionUtils.isEmpty(list)){
            return;
        }

        int index = -1;
        Iterator<WebSocketSession> it = list.iterator();
        while (it.hasNext()){
            index++;
            WebSocketSession se = it.next();
            if (se == session){
                list.remove(index);
                return;
            }
        }
        if (CollectionUtils.isEmpty(list)){
            SESSION_MAP.remove(did);
        }
    }

    public static boolean isEmpty(){
        return SESSION_MAP.size() > 0 ? false : true;
    }

    public static CopyOnWriteArrayList<WebSocketSession>
	findSocketConnect(String did){
        return SESSION_MAP.get(did);
    }
}

kafka monitoring

Subscribe to kakfa's downstream data (mapping data), determine if there is a device link, and if so, send the data to the corresponding session.

@Component
public class DeviceListener {

    @KafkaListener(topics = DOWN_TOPIC)
    public void listener(String msg){
        System.out.println(msg);
        if (WebSocketUtil.isEmpty()){
            return;
        }
        KafkaDownVO vo = JSONObject.parseObject(msg,KafkaDownVO.class);

        List<WebSocketSession> socket = WebSocketUtil.findSocketConnect(
		String.valueOf(vo.getDeviceId()));
        if (CollectionUtils.isEmpty(socket)){
            return;
        }
        TextMessage textMessage = new TextMessage(msg);
        socket.forEach(session -> {
            try {
                session.sendMessage(textMessage);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

application.properties

#Access port
server.port=8082
#Project path
server.servlet.context-path=/device-data
#entry name
spring.application.name=device-data

spring.kafka.consumer.group-id=websocket-device-data

Start the project, create a web socket link, and verify that the data is pushed to the page.

The author uses the web socket test tool now. http://www.blue-zero.com/WebSocket/

Concluding remarks

iot-pt, we have achieved the whole data upstream, from the device injection, device sending data, Mapping device data, pgsql persistent data, web socket push real-time data, there is a complete process, so the next use of spring cloud to build a distributed.

https://gitee.com/distant/iot-pt.git

Posted by tommychi on Fri, 17 May 2019 09:19:30 -0700