하나 만들어보고 싶은게 생겼습니다.
Shift Lighting Indicator의 줄임말인 SLI는 게임 속 차량 정보와 랩 타임 등의 여러 데이터를 가독성있게 출력해주는 일종의 계기판입니다.
SLI가 있으면 게임 속 렌더링 되는 화면 일부를 없애서 시야를 넓히고 레이싱에 더 몰입할 수도 있죠.
특히, 헬멧 시점으로 레이싱 할 때 가려지지 않아서 좋고, 더욱 현실감을 더해줍니다.
기본 시야에서는 이렇게 보이지가 않아요........ 물론 VR을 사서 밑을 쳐다보면 보이겠죠?^^
아 SLI가 기본 장착된 스티어링 휠도 있습니다!!
맙소사... 가격이....... 너무합니다 ㅠㅠ 모션기어 사의 SLI 모듈도 13만원이 넘습니다 ㅠㅠ
그래서 직접 만들어보기로 합니다.
우선은 Project Cars 2 게임에서 제공하는 데이터를 받아서 모듈로 전송이 필요하겠죠?
이렇게 게임 옵션에 들어가면 어떤 방식을 사용할지 설정할 수 있습니다.
1. 직접 개발한 PC 소프트웨어에서 공유 메모리를 열어 데이터 가공하고 모듈로 다시 전송하는 방법
2. 모듈에서 직접 데이터를 받아 출력하는 방법
이 두 가지 방법에서 크게 달라지지 않습니다.
2번 같은 경우는 데이터를 직접 처리해므로 컴퓨팅이 필요하겠지요.
하지만 라즈베리 파이는 정말 물건입니다. 이 쪼그만한게 별 걸 다하지요.
그래서 라즈베리에서 UDP 데이터를 직접 받고 직접 LED를 컨트롤하도록 개발하고자 합니다.
지금 필요한 준비물은.
라즈베리 파이와 이더넷을 micro usb로 바꿔주는 어댑터 입니다.
이더넷을 usb로 바꿔주는 제품을 참 많을텐데, 라즈베리에서 사용하기 가장 적절한 제품은 USB 허브와 이더넷이 합쳐진 게 아닐까요?
https://www.adafruit.com/product/2992
이 제품으로 PC와 라즈베리를 같은 네트워크에 묶어주면 UDP 패킷을 받을 수 있습니다.
와이어샤크로 패킷을 찍어본 결과, 브로드캐스트(255.255.255.255) 패킷이기 때문에 네트워크로 연결된 모든 장비에서 데이터를 받을 수 있습니다.
그러면 이제 Project Cars 2 데이터를 라즈베리에서 받을 수 있도록 코딩해봅시다!
공식 홈페이지에서 UDP 패킷 구조가 담겨있는 헤더 파일과 패킷을 받을 수 있는 Windows console 응용프로그램 샘플소스를 제공하지만,
그 샘플을 gcc 등의 Windows가 아닌 환경에서 컴파일하려고 했다가는 엄청난 에러를 만나겠죠 :)
라즈베리파이에서 돌아가는 소스를 올려드립니다.
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <wiringPi.h>
#include "SMS_UDP_Definitions.hpp"
int sockfd_udp_cli;
void intHandler(int dummy)
{
close(sockfd_udp_cli);
exit(0);
}
int main(int argc, char** argv)
{
signal(SIGINT, intHandler);
if( wiringPiSetup() < 0 )
{
return 1;
}
puts("\nLocoField SLI\n");
char udp_recvbuff[SMS_UDP_MAX_PACKETSIZE];
PacketBase packetHeader;
memset(&packetHeader, 0, sizeof(packetHeader));
sockfd_udp_cli = socket(AF_INET, SOCK_DGRAM, 0);
if( sockfd_udp_cli < 0 )
{
puts("ERROR: create socket.\n");
return 1;
}
//int flags = fcntl(sockfd_udp_cli, F_GETFL, 0);
//fcntl(sockfd_udp_cli, F_SETFL, flags | O_NONBLOCK);
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
setsockopt(sockfd_udp_cli, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
sockaddr_in receiver;
receiver.sin_family = AF_INET;
receiver.sin_addr.s_addr = INADDR_ANY;
receiver.sin_port = htons(SMS_UDP_PORT);
int retval = bind(sockfd_udp_cli, (sockaddr*) &receiver, sizeof(receiver));
if( retval < 0 )
{
puts("ERROR: bind socket.\n");
return 1;
}
sTelemetryData data1;
memset(&data1, 0, sizeof(data1));
while( 1 )
{
retval = recvfrom(sockfd_udp_cli, (char*)&udp_recvbuff, sizeof(udp_recvbuff), 0, NULL, 0);
if( retval > 0 )
{
memcpy(&packetHeader, udp_recvbuff, sizeof(PacketBase));
switch( packetHeader.mPacketType )
{
case eCarPhysics:
{
memcpy(&data1, udp_recvbuff, sizeof(data1));
}
default:
continue;
}
}
}
intHandler(0);
return 0;
}
|
cs |
프로젝트의 종속성은 WiringPi 뿐이네요.
사실 이 프로젝트에서는 전혀 필요가 없어요. 라즈베리에서 GPIO 다룰 일이 많기 때문에 습관적으로 넣는 코드인거죠.
코드 자체는 정말 간단합니다.
signal 함수로 Ctrl+C로 종료할 때 소켓을 닫구요. 나중에 LED를 사용하게 되면 LED 끄는 코드를 넣으면 됩니다.
패킷은 UDP이므로 소켓은 데이터그램으로 만들고, 타임아웃을 설정합니다.
사실 논블록 소켓을 만들고 싶었는데 잘 되지는 않았네요...
그리고 recvfrom 함수로 데이터를 받았을 때 Project Cars 2에서 제공하는 구조체에 여러 값들을 복사해줍니다.
제가 소켓에 타임아웃을 설정했으니 받을 데이터가 없으면(게임에서 레이싱 상태가 아니라면)
조건문에서 계속 검사하며 while 문에서 계속 돌고 있겠지요.
라즈베리에서 바로 사용하실 수 있게 프로젝트 파일(CMakeLists.txt, MakeFile)도 올려드려요.
질문은 언제든지 환영입니다!
참고 웹페이지:
[1] https://www.projectcarsgame.com/project-cars-2-api/
'코딩 > Raspberry PI' 카테고리의 다른 글
Project Cars 2 SLI 완성! (0) | 2018.05.16 |
---|---|
Raspberry Pi에서 MAX7219 제어하기 (0) | 2018.03.16 |