Fiddle은 1.9.x에서 Ruby의 표준 라이브러리에 추가된 잘 알려지지 않은 모듈입니다. Ruby의 C 라이브러리와 직접 상호 작용할 수 있습니다. Ruby 인터프리터가 실행되는 동안 검사하고 변경할 수도 있습니다.
한 언어로 작성된 코드가 다른 언어로 작성된 메소드를 호출할 수 있도록 하는 인기 있는 C 라이브러리인 libffi를 래핑하여 작동합니다. 들어 본 적이 없다면 "ffi"는 "외부 기능 인터페이스"를 의미합니다. 그리고 C에만 국한되지 않습니다. Fiddle을 배우면 Rust 및 이를 지원하는 다른 언어로 작성된 라이브러리를 사용할 수 있습니다.
바이올린을 살펴보자. 간단한 예제로 시작한 다음 직렬 포트를 통해 Arduino에 액세스하는 것으로 끝낼 것입니다. C. 그렇게 많이 알 필요는 없습니다. 약속합니다. :)
간단한 예
평범한 Ruby에서는 호출하기 전에 항상 메서드를 정의해야 합니다. 바이올린도 마찬가지입니다. C 함수를 직접 호출할 수는 없습니다. 대신 C 함수에 대한 래퍼를 만든 다음 래퍼를 호출해야 합니다.
아래 예에서는 C의 로그 함수를 래핑하고 있습니다. 기본적으로 Ruby의 Math.log
를 복제하고 있습니다. .
require 'fiddle'
# We're going to "open" a library, so we have to tell Fiddle where
# it's located on disk. This example works on OSX Yosemite.
libm = Fiddle.dlopen('/usr/lib/libSystem.dylib')
# Create a wrapper for the c function "log".
log = Fiddle::Function.new(
libm['log'], # Get the function from the math library we opened
[Fiddle::TYPE_DOUBLE], # It has one argument, a double, which is similar to ruby's Float
Fiddle::TYPE_DOUBLE # It returns a double
)
# call the c function via our wrapper
puts log.call(3.14159)
예쁘게 만들기
이전 예제는 작동하지만 장황합니다. 100개의 함수를 래핑해야 한다면 일이 얼마나 복잡해질지 상상할 수 있을 것입니다. 이것이 Fiddle이 멋진 DSL을 제공하는 이유입니다. Fiddle::Importer
를 통해 노출됩니다. 믹스인.
이 믹스인을 사용하면 외부 기능으로 가득 찬 모듈을 만드는 작업이 단축됩니다. 아래 예에서는 여러 대수 메서드가 포함된 모듈을 만들고 있습니다.
require 'fiddle'
require 'fiddle/import'
module Logs
extend Fiddle::Importer
dlload '/usr/lib/libSystem.dylib'
extern 'double log(double)'
extern 'double log10(double)'
extern 'double log2(double)'
end
# We can call the external functions as if they were ruby methods!
puts Logs.log(10) # 2.302585092994046
puts Logs.log10(10) # 1.0
puts Logs.log2(10) # 3.321928094887362
직렬 포트 제어
좋아, 마침내 몇 년 동안 구입하려고 생각했던 Arduino 중 하나를 구입했습니다. 직렬 포트를 사용하여 초당 한 번씩 "hello world"라는 텍스트를 컴퓨터에 보냅니다.
이제 Ruby를 사용하여 해당 데이터를 읽을 수 있다면 정말 좋을 것입니다. 그리고 실제로 Ruby의 표준 IO 방식을 사용하여 직렬 포트에서 읽을 수 있습니다. 그러나 이러한 IO 방법을 사용하면 하드 코어 하드웨어 해커가 되는 경우 필요한 세분성 수준으로 직렬 입력을 구성할 수 없습니다.
다행히 C 표준 라이브러리는 직렬 포트로 작업하는 데 필요한 모든 기능을 제공합니다. 그리고 Fiddle을 사용하여 Ruby에서 이러한 C 함수에 액세스할 수 있습니다.
물론, 이미 당신을 위해 이것을 하는 보석이 있습니다. 이 코드를 작업할 때 rubyserial gem에서 약간의 영감을 얻었습니다. 하지만 우리의 목표는 이 작업을 스스로 수행하는 방법을 배우는 것입니다.
termios.h 검사
C에서 .h로 끝나는 파일 이름은 헤더 파일입니다. 여기에는 라이브러리가 타사 코드(저희 코드)에서 사용할 수 있도록 하는 모든 함수 및 상수 목록이 포함되어 있습니다. C 라이브러리를 래핑하는 첫 번째 단계는 이 파일을 찾아 열고 둘러보는 것입니다.
우리가 사용하는 라이브러리를 termio라고 합니다. OSX Yosemite에서 헤더 파일은 /usr/include/sys/termios.h에 있습니다. Linux의 다른 위치에 있을 것입니다.
다음은 termios.h의 모습입니다. 명확성을 위해 상당히 압축했습니다.
typedef unsigned long tcflag_t;
typedef unsigned char cc_t;
typedef unsigned long speed_t;
struct termios {
tcflag_t c_iflag; /* input flags */
tcflag_t c_oflag; /* output flags */
tcflag_t c_cflag; /* control flags */
tcflag_t c_lflag; /* local flags */
cc_t c_cc[NCCS]; /* control chars */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
int tcgetattr(int, struct termios *);
int tcsetattr(int, int, const struct termios *);
int tcflush(int, int);
이 코드에서 주의해야 할 세 가지 중요한 사항이 있습니다. 먼저 몇 가지 typedef가 있습니다. 그런 다음 연결에 대한 구성 정보를 보유하는 데 사용되는 데이터 구조가 있습니다. 마지막으로 사용할 방법이 있습니다.
fiddle의 가져오기 도구를 사용하여 이 섹션을 그대로 Ruby 코드에 복사할 수 있습니다. 하나씩 해결해 봅시다.
유형 별칭 및 typedef
C에서는 데이터 유형에 대한 별칭을 만드는 것이 매우 일반적입니다. 예를 들어, termios.h 파일은 단지 긴 정수인 speed_t라는 새로운 유형을 생성합니다. Fiddle 임포터는 typealias 기능을 통해 이를 처리할 수 있습니다.
# equivalent to `typedef unsigned long tcflag_t;`
typealias "tcflag_t", "unsigned long"
구조체
C에는 클래스나 모듈이 없습니다. 여러 변수를 함께 그룹화하려면 구조체를 사용합니다. Fiddle은 루비에서 구조체로 작업하기 위한 좋은 메커니즘을 제공합니다.
첫 번째 단계는 Fiddle 가져오기 도구에서 구조체를 정의하는 것입니다. 보시다시피 헤더 파일에서 거의 복사하여 붙여넣을 수 있습니다.
Termios = struct [
'tcflag_t c_iflag',
'tcflag_t c_oflag',
'tcflag_t c_cflag',
'tcflag_t c_lflag',
'cc_t c_cc[20]',
'speed_t c_ispeed',
'speed_t c_ospeed',
]
이제 malloc 메서드를 사용하여 구조체의 "인스턴스"를 만들 수 있습니다. 일반적인 Ruby 클래스 인스턴스와 거의 동일한 방식으로 값을 설정하고 검색할 수 있습니다.
s = Termios.malloc
s.c_iflag = 12345
모두 합치기
방금 typedef 및 struct에 대해 배운 내용을 함수로 이미 시연한 내용과 결합하면 termios 라이브러리의 작업 래퍼를 구성할 수 있습니다.
래퍼에는 직렬 포트를 구성하는 데 필요한 메서드만 포함되어 있습니다. 나머지는 평범한 오래된 루비를 사용하겠습니다.
require 'fiddle'
require 'fiddle/import'
# Everything in this module was pretty much copied directly from
# termios.h. In Yosemite it's at /usr/include/sys/termios.h
module Serial
extend Fiddle::Importer
dlload '/usr/lib/libSystem.dylib'
# Type definitions
typealias "tcflag_t", "unsigned long"
typealias "speed_t", "unsigned long"
typealias "cc_t", "char"
# A structure which will hold configuratin data. Instantiate like
# so: `Serial::Termios.malloc()`
Termios = struct [
'tcflag_t c_iflag',
'tcflag_t c_oflag',
'tcflag_t c_cflag',
'tcflag_t c_lflag',
'cc_t c_cc[20]',
'speed_t c_ispeed',
'speed_t c_ospeed',
]
# Functions for working with a serial device
extern 'int tcgetattr(int, struct termios*)' # get the config for a serial device
extern 'int tcsetattr(int, int, struct termios*)' # set the config for a serial device
extern 'int tcflush(int, int)' # flush all buffers in the device
end
라이브러리 사용
아래 예에서는 Ruby에서 직렬 장치를 연 다음 파일 설명자를 가져옵니다. 우리는 termios 기능을 사용하여 읽기용으로 장치를 구성합니다. 그런 다음 읽습니다.
나는 여기에 몇 가지 마법의 숫자를 사용하고 있습니다. 이것은 arduino와 통신하는 데 필요한 많은 비트 구성 옵션을 결합한 결과입니다. 매직 넘버의 출처가 궁금하시면 이 블로그 게시물을 확인하세요.
file = open("/dev/cu.wchusbserial1450", 'r')
fd = file.to_i
# Create a new instance of our config structure
config = Serial::Termios.malloc
# Load the default config options into our struct
Serial.tcgetattr(fd, config);
# Set config options important to the arduino.
# I'm sorry for the magic numbers.
config.c_ispeed = 9600;
config.c_ospeed = 9600;
config.c_cflag = 51968;
config.c_iflag = 0;
config.c_oflag = 0;
# wait for 12 characters to come in before read returns.
config.c_cc[17] = 12;
# no minimum time to wait before read returns
config.c_cc[16] = 0;
# Save our configuration
Serial.tcsetattr(fd, 0, config);
# Wait 1 second for the arduino to reboot
sleep(1)
# Remove any existing serial input waiting to be read
Serial.tcflush(fd, 1)
buffer = file.read(12)
puts "#{ buffer.size } bytes: #{ buffer }"
file.close
작동 중인 직렬 리더기
이제 Hello World를 지속적으로 인쇄하는 프로그램으로 arduino를 로드하면 내 루비 스크립트를 사용하여 읽을 수 있습니다.
이 arduino 프로그램은 "hello world"를 직렬에 계속해서 기록합니다.
다음은 직렬 모니터를 실행할 때의 모습입니다.