ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 한 컴퓨터로 2대의 아두이노 제어하기(서보모터 동시에 동작)
    Arduino/Web Socket 2021. 1. 12. 21:05

     

    아래의 프로젝트를 그대로 가지고 와서 진행할 것이다.

    designatedroom87.tistory.com/369?category=917476

     

    RFID의 값에 따라 LED의 On/Off 제어하기

    구현할 내용을 좀 더 자세히 기술하면 아두이노로부터 값(RFID)을 자바로 받아서 자바에서 서버로 전송한 후에 다시 아두이노로 데이터를 전송하는 것이다. 아두이노로부터 값(RFID)을 자바로 보

    designatedroom87.tistory.com

     

    여기서 진행할 내용은 한 대의 컴퓨터에 2개의 아두이노를 연결할 것이다.

    각 아두이노는 서보 모터와 RFID만 연결하자.

    그리고 한쪽 RFID에 카드가 읽히면 두 대의 아두이노에 연결된 서보모터가 동작하도록 하는 것이다.

    여기서 웹 소켓의 가치가 나타난다.

    즉, 아두이노에서 읽은 데이터가 서버로 전송되면, 세션에 접속해있는 모든 아두이노로 데이터를 전송하면 된다.

     

     

    하드웨어부터 구성해보자.

    아래의 사진에는 키패드까지 같인 연결되어있는 것을 볼 수 있는데, 이는 무시하자.

    서보 모터의 출력핀은 아두이노의 D8번핀에 연결한다.

     

     

     

     

    2대의 아두이노에 모두 하드웨어 연결이 끝났으면, 2대의 아두이노에 모두 아래의 소스 파일을 업로드 한다.

    업로드 시에 반드시 포트번호를 기억하도록 하자.

    반드시 아두이노 2대에 모두 업로드해야 한다.

    그래야 같은 동작을 한다.

    아두이노 소스파일

    더보기
    //  흰 카드     97   A8 B6 4E
    //  파란색 카드  C7  99  FF  32
    #include <Servo.h>
    #include <MFRC522.h>
    #include <SPI.h>
    
    #define   SS_PIN    10  //  칩셋핀(데이터 핀)
    #define   RST_PIN   9   //  리셋핀
    #define   MOTOR_PIN 8   //  서보모터 데이터 핀
    
    Servo servo;
    MFRC522 mfrc522(SS_PIN, RST_PIN);
    
    void setup() {
      Serial.begin(9600);
    
      SPI.begin();            // Init SPI bus
    
      servo.attach(MOTOR_PIN);    // 서보모터
      servo.write(20);             // 서보모터 값 초기화( 20 )
    
      mfrc522.PCD_Init();     // Init mfrc522
    }
    
    void loop() {
     SendRFID();  //  RFID 카드 읽기
     ReadData();
    }
    
    void ReadData()
    {
      if ( Serial.available() )
      {
        byte servoState = Serial.read();
    
        //  읽은 데이터가 '1'이면 서보모터를 회전시킨다.
        if ( servoState == '1' ) 
        {
          servo.write(160);
          delay(1000);
          servo.write(20);
        }
      }
    }
    
    void SendRFID()
    {
       //  카드가 읽혀지지 않은 상태(찍히지 않으면)이면 리턴
      if (!mfrc522.PICC_IsNewCardPresent())   return;
      if (!mfrc522.PICC_ReadCardSerial())     return;
    
      ShowOtherPrintHex( mfrc522.uid.uidByte, 4 );
      
      mfrc522.PICC_HaltA();
      mfrc522.PCD_StopCrypto1(); 
    }
    
    byte GetDecimalToHex(int decimal)
    {
      const int diffHex = 'A' - 10;   
      const int diffDecimal = '0';   
      byte ret;
      
      if ( decimal > 9 )  ret = diffHex + decimal;
      else                ret = diffDecimal + decimal;
    
      return ret;
    } 
    
    void ShowOtherPrintHex(byte* buffer, byte bufferSize)
    {
      byte l_buf[8] = { 0, };
      int l_bufLen = 0;       
      
      const int l_HEX = 16;
    
      for (byte i = 0; i < bufferSize; i++)
      {
        int frontNum = buffer[i] / l_HEX;
        int rearNum = buffer[i] % l_HEX;
    
        l_buf[l_bufLen++] = GetDecimalToHex(frontNum);
        l_buf[l_bufLen++] = GetDecimalToHex(rearNum);
      }
      Serial.write(l_buf, 8);
    }

     

     

     

     

    WebSocketRFID 프로젝트의 src폴더의 serial 패키지와 websocket 패키지의 파일들에 수정을 할 것이다.

    아래에서 차근차근 알아보자.

    Main.java

    아래의 Main클래스는 실행 파일이다. 그리고 아두이노를 2대 가동시킬 것이므로 

    Serial 클래스를 하나 더 생성한다. 반드시 포트번호를 확인하자.

     

    Main.java의 전체 내용

    더보기
    package serial;
    
    public class Main {
    	public static void main(String[] args) 
    	{
    		try {
    			(new Serial()).connect("COM3");
    			(new Serial()).connect("COM4");
    		}
    		catch(Exception e) {e.printStackTrace();}
    	}
    }

     

    Serial.java

    더보기
    package serial;
    import java.io.InputStream;
    import java.io.OutputStream;
    import gnu.io.CommPort;
    import gnu.io.CommPortIdentifier;
    import gnu.io.SerialPort;
    
    public class Serial 
    {
    	void connect(String port) 
    	{
    		CommPort commPort = null;
    		SerialPort serialPort = null;
    		
    		try 
    		{
    			CommPortIdentifier com = CommPortIdentifier.getPortIdentifier(port);
    			
    			//	com포트를 확인하는 작업
    			if (com.isCurrentlyOwned())
    				System.out.println("Error : "+port +"포트를 사용중입니다.");
    			
    			//	포트가 열려있으면
    			else
    			{
    				commPort = com.open(this.getClass().getName(),1000);
    				
    				//	획득한 포트를 객체가 사용할 수 있는지 여부 확인
    				if (commPort instanceof SerialPort)	//	commPort가 SerialPort로 사용할 수 있는지 확인
    				{
    					serialPort = (SerialPort)commPort;
    					
    					//	정상적으로 포트를 사용할 수 있을 경우
    					//	포트에 필요한 정보를 입력해 준다.
    					serialPort.setSerialPortParams(
    							9600,						//	바운드레이트 
    							SerialPort.DATABITS_8, 
    							SerialPort.STOPBITS_1, 
    							SerialPort.PARITY_NONE);	//	오류제어 비트
    				}
    				System.out.println("comport성공");
    				
    				InputStream in = serialPort.getInputStream();
    				OutputStream out = serialPort.getOutputStream();
    				
    				(new Thread(new SerialRead(in, out))).start();
    				(new Thread(new SerialWebsocket(out))).start();
    			}
    		}	//	end try
    		catch(Exception e) {}
    	}
    }

     

    SerialRead.java

    더보기
    package serial;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    public class SerialRead implements Runnable
    {
    	InputStream in;
    	private OutputStream out;
    	
    	boolean dataReceiveOk = false;
    	boolean dataReceiveComplete = false;
    	String receivedData = "";
    	
    	public SerialRead(InputStream in, OutputStream out)
    	{
    		this.in = in;
    		this.out = out;
    	}
    	
    	@Override
    	public void run() 
    	{
    		byte[] buffer = new byte[1024];
    		int len = -1;
    		
    		try {
    			String s = "";
    			//	buffer에 저장하고나서, 그 길이를 반환한다.
    			while ((len = this.in.read(buffer)) > -1)
    			{
    				s = new String( buffer, 0, len );
    				
    				if ( s.length() == 0 && this.dataReceiveOk == true )
    					this.dataReceiveComplete = true;
    				
    				if ( s.length() > 0 && this.dataReceiveComplete == false ) 
    				{
    					receivedData += s;
    					this.dataReceiveOk = true;
    				}
    				else if ( this.dataReceiveComplete )
    				{
    					System.out.println("receivedData : " +this.receivedData);
    					
    					//	읽은 데이터가 카드정보이면, 카드 정보를 웹 서버에 전송
    					if ( ProcessRFID.IsMatchedRFID(this.receivedData) )
    					{
    						System.out.println("카드 정보 입니다.");
    						(new ProcessRFID(in, out)).SendReceivedDataToWebSocket(receivedData);
    					}
    					
    					//	정보 초기화
    					this.receivedData = "";
    					this.dataReceiveComplete = false;
    					this.dataReceiveOk = false;
    				}
    			}
    		} 
    		catch (IOException e) {e.printStackTrace();}
    	}
    }

     

    SerialWebsocket.java

    더보기
    package serial;
    import java.io.OutputStream;
    import java.net.URI;
    import java.net.http.HttpClient;
    import java.net.http.WebSocket;
    import java.util.concurrent.CountDownLatch;
    
    public class SerialWebsocket implements Runnable 
    {
    	OutputStream out;
    	
    	//	Constructor
    	public SerialWebsocket(OutputStream out) {this.out = out;}
    	
    	@Override
    	public void run() {
    		try {
    			CountDownLatch latch = new CountDownLatch(10);	//	데이터를 10번만 받겠다는 의미
    			URI uri = URI.create("ws://localhost:9090/websocket");
    			WebsocketClientArduino listener = new WebsocketClientArduino(latch, out);
    			
    			WebSocket ws = HttpClient.newHttpClient().newWebSocketBuilder().buildAsync(
    					uri, listener).join();
    			
    			ws.sendText("java app sendmessage", false);
    			
    			latch.await();
    	
    		}catch(Exception e) { e.printStackTrace();	}
    	}
    }

     

    ProcessRFID.java

    더보기
    package serial;
    
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.URI;
    import java.net.http.HttpClient;
    import java.net.http.WebSocket;
    import java.util.concurrent.CountDownLatch;
    
    public class ProcessRFID 
    {
    	private InputStream in;
    	private OutputStream out;
    	private CountDownLatch latch;
    	private WebSocket ws;
    								
    	//									흰색 카드		파란 카드, 	
    	public static String[] RFIDs = { "97A8B64E", "C799FF32"}; 
    	
    	public ProcessRFID(InputStream in, OutputStream out) 
    	{
    		this.in = in;
    		this.out = out;
    		latch = new CountDownLatch(10);	//	데이터를 10번만 받겠다는 의미
    		URI uri = URI.create("ws://localhost:9090/websocket"); 
    		//	WebsocketClientArduino 클래스의 onText함수가 호출이 된다.
    		WebsocketClientArduino listener = new WebsocketClientArduino( this.latch, this.out );
    		
    		ws = HttpClient.newHttpClient().newWebSocketBuilder().buildAsync(
    				uri, listener).join();
    	}
    	
    	//	파란 카드이면 true를, 흰색 카드이면 false를 리턴
    	public static boolean IsMatchedBlueCard(String receivedData)
    	{
    		if ( RFIDs[0].equals(receivedData) )
    			return false;
    		else
    			return true;
    	}
    	
    	public static boolean IsMatchedRFID(String receivedData)
    	{
    		boolean ret = false;
    		
    		for ( String s : RFIDs)
    		{
    			if ( s.equals(receivedData))
    			{
    				ret = true;
    				break;
    			}
    		}
    		return ret;
    	}
    	
    	private String GetCardInfo(String receivedData)
    	{
    		String ret = "";
    		
    		if (receivedData.equals(RFIDs[0]))
    			ret = "흰색카드";
    		else if (receivedData.equals(RFIDs[1]))
    			ret = "파란카드";
    		else
    			ret = "정의되지않음";
    		
    		return ret;
    	}
    	
    	public void SendReceivedDataToWebSocket(String receivedData)
    	{
            System.out.println("카드 유형 : " +this.GetCardInfo(receivedData));
            
            ws.sendText(receivedData, true);
    	}
    }

     

    WebsocketClientArduino.java

    더보기
    package serial;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.http.WebSocket;
    import java.net.http.WebSocket.Listener;
    import java.util.concurrent.CompletionStage;
    import java.util.concurrent.CountDownLatch;
    
    //	자바에서 지원하는 기본적인 소켓을 만든 것이다.
    //	WebSocket.Listener를 통해, 웹 소켓에 접속할 것이다.
    public class WebsocketClientArduino implements WebSocket.Listener
    {
    	//	웹 소켓에서 전송되는 데이터 수를 정하는 변수
    	//	만약, 3번만 데이터를 수신하고 싶으면 3으로 설정
    	//	웹 소켓이 닫히게 된다. onClose함수가 자동으로 실행된다.
    	private final CountDownLatch latch;	//	몇번을 받을 것인가?
    	private OutputStream out;
    	
    	//	사용할 횟수만큼 매개변수로 전달하면 된다.
    	public WebsocketClientArduino( CountDownLatch latch, OutputStream out) {
    		this.latch = latch;
    		this.out = out;
    	}
    	
    	@Override
    	//	서버에 접속했을 때, 호출된다.
    	public void onOpen(WebSocket webSocket) {
    		System.out.println("arduino websocket open");
    		Listener.super.onOpen(webSocket);
    	}
    	
    	@Override
    	public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
    		System.out.println("arduino websocket close");
    		return Listener.super.onClose(webSocket, statusCode, reason);
    	}
    	
    	@Override
    	public void onError(WebSocket webSocket, Throwable error) {
    		System.out.println("arduino websocket error");
    		Listener.super.onError(webSocket, error);
    	}
    	
    	@Override
    	//	메시지(텍스트)가 도착했을 때, 호출되는 함수
    	public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
    		System.out.println( "arduino recv message : " +data );
    		this.latch.countDown();		
    		
    		byte rfid = '0';
    		
    		//	카드 정보이면 '1'을 전송
    		if ( ProcessRFID.IsMatchedRFID(data.toString()) )		rfid = '1';
    		else       												rfid = '0';
    		
    		try {	this.out.write(rfid);	} 
    		catch (IOException e) {e.printStackTrace();}
    
    		return Listener.super.onText(webSocket, data, last);
    	}
    }

     

     

     

     

     

     

    websocket 패키지를 보자.

    webcontroller.java

    더보기
    package websocket;
    
    import java.io.IOException;
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    @WebServlet("/websocketcomm")
    public class webcontroller extends HttpServlet
    {
    	private static final long serialVersionUID = 1L;
    
    	protected void service(HttpServletRequest request, HttpServletResponse response) 
    			throws ServletException, IOException 
    	{
    				HttpSession session = request.getSession();
    				//페이지를 전송하기 전에 url을 분석하고 비교해서 실행
    				String url = request.getRequestURI();
    				//System.out.println("url : "+url);
    				String contextPath = request.getContextPath();
    				//System.out.println("contextpath : "+contextPath);
    				String path = url.substring(contextPath.length());
    				//System.out.println("path : "+path);
    				//마지막 '/'를 기준으로 값 추출
    				String lastPath = url.substring(url.lastIndexOf('/')+1);
    				System.out.println("subPath : "+ lastPath);
    				//최종 주소
    				String command = lastPath;
    				
    				if(command.equals("led")) 
    				{	
    					RequestDispatcher dispatcher = 
    							request.getRequestDispatcher("/WEB-INF/arduinoled.jsp");
    					dispatcher.forward(request, response);
    				}
    				else 
    				{
    					RequestDispatcher dispatcher = 
    							request.getRequestDispatcher("/WEB-INF/arduinoled.jsp");
    					dispatcher.forward(request, response);
    				}
    	}
    }

     

     

    WSocket 클래스의 메소드에 변경 사항이 있다. 한 번 알아보자.

    handleMessage 메소드를 보자.

    아두이노 2대가 연결이 되면, 세션의 개수는 2개가 된다.

    여기서, 각 RFID에 카드가 읽히면 2대 모두에 서보 모터가 움직이도록 데이터를 전송해줘야 한다.

    그렇기 때문에 현재 접속해 있는 세션을 탐색해서 읽은 데이터를 전송한다.

    그리고 기존의 sendAll 메소드는 비어있는 상태로 둔다.

     

    아래는 sendAll 메소드의 내용이다.

     

    WSocket.java 의 전체 내용

    더보기
    package websocket;
    
    import java.util.Collections;
    import java.util.Set;
    
    import javax.websocket.OnClose;
    import javax.websocket.OnError;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.RemoteEndpoint.Basic;
    import javax.websocket.Session;
    import javax.websocket.server.ServerEndpoint;
    
    @ServerEndpoint("/websocket")
    public class WSocket 
    {
    	private static final Set<Session> sessions = Collections.synchronizedSet(
    			new java.util.HashSet<Session>()
    			);
    	
    	//	onopen 이벤트가 호출 되면 실행되는 함수
    	@OnOpen
    	public void handleOpen(Session session)
    	{
    		System.out.println("클라이언트가 접속했습니다.");
    		System.out.println("WSocket 클래스의 handleOpen 메소드 호출 => " +"session id : " +session.getId());
    		sessions.add(session);
    		System.out.println("세션 개수 : "+sessions.size());
    	}
    	
    	//	onclose 이벤트가 호출 되면 실행되는 함수
    	@OnClose
    	public void handleClose(Session session)
    	{
    		System.out.println(session.getId() + "클라이언트가 연결을 해제했습니다.");
    		
    		//	세션을 닫는다.
    		sessions.remove(session);
    		System.out.println("세션 개수  : "+sessions.size());
    	}
    	
    	//	onerror 이벤트가 호출 되면 실행되는 함수
    	@OnError
    	public void handleError(Throwable t){t.printStackTrace();}
    	
    	//	onmessage 이벤트가 호출 되면 실행되는 함수
    	@OnMessage
    	public void handleMessage(String message, Session session)
    	{
    		System.out.println("현재 handleMessage메소드에 들어온 session id : " +session.getId()); 
    		//	서버가 받는다.
    		System.out.println("WSocket 클래스의 handleMessage 메소드 호출 => " +"클라이언트가 보내온 메시지 : " +message);
    		
    		try {
    			int i = 0;
    			//this.sendAll(session, message);
    			for (Session s : WSocket.sessions) {
    				System.out.println(++i +"번째 : " +s.getId());
    				//session.getBasicRemote().sendText(message);
    			
    				if ( !s.getId().equals( session.getId() ) )
    				{
    					s.getBasicRemote().sendText(message);
    				}
    				
    			}
    		}catch(Exception e) {e.printStackTrace();}
    		
    		try {
    			//final Basic basic = session.getBasicRemote();
    			//basic.sendText(message);
    			session.close();
    			
    		}catch(Exception e) {}
    	}
    	
    	public void sendAll(Session session, String message)
    	{
    		System.out.println("WSocket 클래스의 sendAll 메소드 호출 => " + session.getId() + " : " +message);
    		
    		/*
    		try {
    			int i = 0;
    		
    			//	웹 소켓에 연결되어 있는 모든 아이디를 찾는다.	
    			for (Session s : WSocket.sessions) {
    				System.out.println(++i +"번째 : " +s.getId());
    				session.getBasicRemote().sendText(message);
    			
    				if (!s.getId().equals(session.getId()))
    				{
    					session.getBasicRemote().sendText(message);
    				}
    				
    			}
    		
    		}catch(Exception e) {e.printStackTrace();}
    		*/
    	}
    }

     

    그리고, 서버를 먼저 실행시킨 후에, Main.java를 실행시키자.

     

     

    프로젝트 폴더

    WebSocketRFID.war
    0.18MB

     

    흰색카드를 대면 아래와 같이 두 아두이노에 데이터가 전송된다.

    아래는 파란카드를 대면 아래와 같이 두 아두이노에 데이터가 전송된다.

     

    영상 

     

    댓글

Designed by Tistory.