ゲームパッドでコマンドを実行 (2017/04/05)
ゲームパッドのボタンを押す組み合わせで Raspberry Pi にコマンドを実行させる仕組みを 2年ほど前に紹介 しましたが、色々問題が見つかったのでアップデートしました。
最近の環境ではゲームパッドを接続してしばらくすると使えなくなる問題、ディスプレイ無しでゲームパッド操作を確認できない問題、掲示板 で yasuo さんに指摘して頂いたコマンドのバックグラウンド実行に伴う問題に対応しました。 Orange Pi Zero でも動作確認しました。
今回も スーファミ風ゲームパッド を使ってテストしました。 700円弱と安いのでおすすめです。
Raspbian を Jessie にアップデートした後ぐらいから、ゲームパッドを接続してしばらくすると使えなくなるような気がしていました。 今回 systemd-udevd の設定を変更して、jsCommand.lua を systemd の service として動作させるように変更しました。また、ボード上のLEDを点滅させるコマンド を少し変更して、 A, B, A, B というボタン操作でACT(緑) と PWR(赤) のLEDを点滅してゲームパッドの動作確認をできるようにしました。
ボード上のLEDの制御のためRaspberry Pi B+ と Pi Zero 用 と Raspberry Pi2 と Pi3用の二種類があります。
Raspberry Pi2 と Pi3用のバージョンは、jscommand2.tar.gz (5,180 byte) には次のファイルが含まれています。すべてテキストファイルです。
$ tar ztvvf jscommand2.tar.gz drwxr-xr-x pi/pi 0 2017-04-03 12:27 jscommand/ -rw-r--r-- pi/pi 1237 2017-04-03 01:30 jscommand/README -rw-r--r-- pi/pi 258 2017-04-03 12:26 jscommand/jscom.service -rw-r--r-- pi/pi 102 2017-04-03 12:25 jscommand/70-jscom.rules -rwxr-xr-x pi/pi 4203 2017-04-03 12:27 jscommand/led.lua -rwxr-xr-x pi/pi 9641 2017-04-02 19:29 jscommand/jsCommand.lua -rwxr-xr-x pi/pi 188 2017-04-03 01:28 jscommand/install.sh
Raspberry Pi B+ と Raspberry Pi Zero 用の jscommand2b.tar.gz (5,184 byte) には次のファイルが含まれています。 Raspberry Pi B+ と Raspberry Pi Zero 用は、GPIO レジスタのベースアドレスが Pi2、Pi3 (0x3F200000)と異なって 0x20200000 であるため、led.lua のベースアドレスのみを変更したものです。
$ tar ztvvf jscommand2b.tar.gz drwxr-xr-x pi/pi 0 2017-04-03 12:29 jscommand/ -rw-r--r-- pi/pi 1237 2017-04-03 01:30 jscommand/README -rw-r--r-- pi/pi 258 2017-04-03 12:26 jscommand/jscom.service -rw-r--r-- pi/pi 102 2017-04-03 12:25 jscommand/70-jscom.rules -rwxr-xr-x pi/pi 4203 2017-04-03 12:29 jscommand/led.lua -rwxr-xr-x pi/pi 9641 2017-04-02 19:29 jscommand/jsCommand.lua -rwxr-xr-x pi/pi 188 2017-04-03 01:28 jscommand/install.sh
インストール方法
まず jscommand2.tar.gz (5KB) をダウンロードして下さい。コマンドラインからは 次のコマンドでダウンロードできます。
$ curl -O https://www.mztn.org/rpi/jscommand2.tar.gz
ダウンロードしたデレクトリに移動 (cd ダウンロードしたデレクトリ) した後、以下のコマンドを実行して下さい。 付属の install.sh は jsCommand.lua を /usr/local/bin にコピーし、/etc/udev/rules.d に 70-jscom.rules をコピーします。 その後、service udev reload を実行して 70-jscom.rules の設定を有効にします。 /usr/local/bin にコピーしていますが、/usr/local/ 以下はシステムの管理範囲外(aptコマンドの対象外)ですから大丈夫です。
$ tar zxf jscommand2.tar.gz $ cd jscommand $ sudo ./install.sh
使い方
USBの ゲームコントローラを接続すると自動的に jsCommand.lua が実行されます。 ディスプレイが接続されている場合は、コマンドの実行結果がシステムコンソールに表示されます。何も接続されていない場合でも 「コナミコマンド」 (上上下下左右左右BA) でシャットダウンさせることができます。リブートは 上上下下左右左右 [Select] [Start] です。
ゲームコントローラのボタンは次の文字で表しています。
| 文字 | 対応するボタン |
|---|---|
| A | [A] ボタン |
| B | [B] ボタン |
| X | [X] ボタン |
| Y | [Y] ボタン |
| L | [L] ボタン |
| R | [R] ボタン |
| S | [START] ボタン |
| E | [SELECT] ボタン |
| u | 十字キーの↑ |
| d | 十字キーの↓ |
| l | 十字キーの← |
| r | 十字キーの→ |
デフォルトで設定されている機能
jsCommand.lua を変更していないデフォルトの設定では、次の表のシステム管理用のコマンドが登録されています。デフォルト設定はボタン数の少ないファミコン用のコントローラでも操作できます。システム管理用のコマンドはシャットダウンとリブートを除いて、システム情報を表示するだけのコマンドです。安心して実行できます。
ボタンを押す間隔が1秒以上離れると押したボタンはクリアされ、また最初からになります。 押したボタンがよくわからなくなったら、1秒待てば最初から入力できます。
| ボタン操作順 | 実行されるコマンド |
|---|---|
| BABABA | この表の表示 |
| ABAB | 基板上の緑と赤のLEDの点滅 |
| ESESESES | jsCommandを終了 |
| uuddlrlrBA | /sbin/shutdown -h now シャットダウン |
| uuddlrlrES | /sbin/reboot リブート |
| SESE | /bin/sync |
| dddd | /bin/cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq 周波数表示 |
| uuuu | /bin/cat /sys/class/thermal/thermal_zone0/temp CPU温度表示 |
| llrr | /sbin/ifconfig -a |
| rrrr | /bin/netstat -a |
| lrlr | /usr/bin/uptime |
| llll | /usr/bin/vmstat -s |
| BBBB | /bin/date -R |
| EEEE | /bin/ps aux |
| SSSS | /usr/bin/pstree -A |
| RRRR | /usr/bin/who -a |
| rrrl | /usr/bin/who -a |
| LLLL | /sbin/lsmod |
| lllr | /sbin/lsmod |
| uldr | /bin/uname -a |
| dudu | /bin/df -kT |
| Eddd | /usr/bin/env |
上に書いたように 「sudo ./install.sh」を実行しておけば、ゲームパッド/ジョイスティックをUSBポートに接続するだけでログインもすることなく、コマンドを実行できるようになります。自動実行されている場合でも「ESESESES」と SELECTボタンとSTARTボタンを交互に4回ずつ押すと実行している jsCommand.lua は終了します。デスクトップが表示 (Xが起動) されている場合はキーボードの [Ctrl] と [Alt] と [F1] を同時に押すとシステムコンソール(黒い画面)が表示されて、jsCommand.lua の実行結果が表示されます。キーボードの [Ctrl] と [Alt] と [F7] を同時に押すとデスクトップ表示に戻ります。
ヘルプ表示
実行可能なコマンドとボタンの操作順を表示するには次のようにコマンドラインで実行して下さい。表示後すぐに終了します。
$ jsCommand.lua -h
「sudo ./install.sh」を実行して/usr/local/binにインストールしていない場合は、jscommandディレクトリに移って、ピリオドとスラッシュを前につけて実行して下さい。
$ cd jscommand $ ./jsCommand.lua -h
システムコンソールに表示
Raspberry Pi 起動時にゲームパッド/ジョイスティックが接続されている場合は、jsCommand.lua は自動で起動するようにしていません。次のコマンドで起動して下さい。コマンドの出力はシステムコンソールに表示されます。
$ sudo jsCommand.lua -c &
標準出力に表示
コマンドラインから起動すると、そのまま jsCommand.lua の出力が表示されます。sudo を付けないで実行すると、シャットダウンとリブートはできません。
$ jsCommand.lua
ボタンの入力テスト
ゲームパッドのボタンの表示と jsCommand.lua の認識するボタンとの対応をチェックするには -d オプションを付けて実行します。 押したボタンが表示されるようになります。
$ jsCommand.lua -d
ボタンを押す間隔が1秒以上離れると押したボタンはクリアされ、また最初からになりますが、そのタイムングも試してみてください。
jun@raspberrypi ~/jscommand $ jsCommand.lua -d
Name: USB,2-axis 8-button gamepad
Ver.: 131328
No. of Axes : 2
No. of Buttons : 8
help : BABABA
AA
AAA
AAAA
X
XXY
XXYY
E
ES
ESES
ESESr
ESESrd
ESESrdul
ESESrdulr
ESESrdulrd
ESESrdulrdES
ESESrdulrdES
ESESrdulrdESX
ESESrdulrdESXR
ESESrdulrdESXRL
B
ES
ESE
ESESE
ESESESE
jun@raspberrypi ~/jscommand $
最後は「ESESESES」と入力していますが、最後の「S」が表示される前に終了しています。
解説
以下は jscommand2.tar.gz に含まれているファイルの解説です。jsCommand.lua をそのまま使用するだけの場合には特に必要ありません。仕組みを知りたい方はどうぞ。
install.sh
インストール用のシェルスクリプトです。jsCommand.lua と led.lua を /usr/local/bin にコピーし、70-jscom.rules を /etc/udev/rules.d にコピーします。 また jscom.service を /etc/systemd/system/ にコピーします。 install.sh は sudo を付けて実行して下さい。
#!/bin/sh cp -a ./jsCommand.lua /usr/local/bin cp -a ./led.lua /usr/local/bin cp -a ./jscom.service /etc/systemd/system/ cp -a ./70-jscom.rules /etc/udev/rules.d/ systemctl daemon-reload
70-jscom.rules
OS が起動している最中にゲームパッド/ジョイスティックをUSBに接続しても jsCommand.lua が自動で実行されるようにする設定ファイルです。 systemd-udevd デーモンが起動する時に読み込みます。 jsCommand.lua が動作していなくても、ゲームパッド/ジョイスティックを接続するだけでシャットダウン等のコマンドを実行できます。
ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="js*",\
TAG+="systemd", ENV{SYSTEMD_WANTS}+="jscom.service"
jscom.service
jsCommand.lua を systemd の service として実行するための設定です。 jsCommand.lua がすでに起動している場合は、ExecStartPre でプロセスを終了させた後に再起動します。 プレフィックスとしてコマンドの前に「-」を付加して、jsCommand.lua プロセスがない場合にも動作を続けるようにしています。 StopWhenUnneeded=yes によって不要な場合には停止させます。
[Unit] Description = jsCommand StopWhenUnneeded=yes [Service] ExecStartPre = -/usr/bin/killall jsCommand.lua ExecStart = /usr/local/bin/jsCommand.lua -c & ExecStop = -/usr/local/bin/killall jsCommand.lua Type = simple [Install] WantedBy=multi-user.target
jsCommand.lua
ゲームコントローラでコマンドを実行するための中心となるプログラムです。Lua という言語のJITコンパイラである luajit-2 用に書かれています。 luajit は Raspbian に最初から入っているため何もインストールする必要はありません。 また、jsCommand.lua は必要なすべての処理を含んでいるため、何にも依存すること無く動作します。 Raspberry Pi に依存していないので Linux なら動作するはずです。掲示板 で yasuo さんに指摘して頂いた変更を加えたので、addCommand で登録するコマンドをバックグラウンドで動作させると停止する不具合を修正しました。
#!/usr/bin/luajit
-- ---------------------------------------------
-- jsCommand.lua 2015/02/28,2017/04/02
-- Copyright (c) 2015-2017 Jun Mizutani,
-- released under the MIT open source license.
-- ---------------------------------------------
local ffi = require("ffi")
local bit = require("bit")
local bnot, bor, band = bit.bnot, bit.bor, bit.band
ffi.cdef unsigned int time; /* event timestamp in milliseconds */
struct js_event {
unsigned int time; /* event timestamp in milliseconds */
short value; /* value */
char type; /* event type */
char number; /* axis/button number */
};
static const int F_SETFL = 4;
int ioctl(int, unsigned long, ...);
int open(const char* filename, int flags);
int read(int fd, void *buf, unsigned int nbytes);
int fcntl(int fd, int cmd, ...);
int close(int fd);
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
]]
local JS_EVENT_BUTTON = 0x1
local JS_EVENT_AXIS = 0x2
local JS_EVENT_INIT = 0x80
local JSIOCGVERSION = 0x80046a01
local JSIOCGAXES = 0x80016a11
local JSIOCGBUTTONS = 0x80016a12
local JSIOCGNAME = 0x80006a13
local EVENT_CLEAR_MSEC = 1000
local devices = {}
local initialized = false
function init()
for i = 0, 7 do
openJoystick(i)
end
if #devices > 0 then
setDeviceInfo()
initialized = true
end
return #devices
end
function open(device)
local O_RDONLY = 0
local O_NONBLOCK = 0x800
local fd = ffi.C.open(device, O_RDONLY + O_NONBLOCK)
return fd
end
function openJoystick(n)
if (n >= 0) and (n <=7) then
local fd = open("/dev/input/js" .. tonumber(n))
if fd >= 0 then
local device = {}
device.num = n
device.fd = fd
table.insert(devices, device)
devices[n + 1].last_time = 0
devices[n + 1].event_list = {}
devices[n + 1].eventStr = {}
return fd
else
return -1
end
else
return -1
end
end
function setDeviceInfo()
local version = ffi.new("int[1]")
local axes = ffi.new("unsigned char[1]")
local buttons = ffi.new("unsigned char[1]")
local name = ffi.new("char[128]")
for i = 1, #devices do
local fd = devices[i].fd
ffi.C.ioctl(fd, JSIOCGVERSION, version)
ffi.C.ioctl(fd, JSIOCGAXES, axes)
ffi.C.ioctl(fd, JSIOCGBUTTONS, buttons)
ffi.C.ioctl(fd, JSIOCGNAME + 128 * 0x10000, name)
devices[i].version = version[0]
devices[i].num_axes = axes[0]
devices[i].num_buttons = buttons[0]
devices[i].name = ffi.string(name)
devices[i].axes = {}
devices[i].buttons = {}
for j = 1, axes[0] do
table.insert(devices[i].axes,
{type = 0, number = 0, value = 0, time = 0})
end
for j = 1, buttons[0] do
table.insert(devices[i].buttons,
{type = 0, number = 0, value = 0, time = 0})
end
end
end
function readOneEvent(device)
if (device < 1) or (device > #devices) then
return nil -- invalid device
end
local fd = devices[device].fd
local js = ffi.new("struct js_event[1]")
local size = ffi.sizeof(js)
local res = ffi.C.read(fd, js, size)
if res == size then
local event = js[0]
return event
else
return nil
end
end
function recordEventStr(device, event)
local js = devices[device]
local n = event.number
if event.type == JS_EVENT_BUTTON then
if n == 0 then table.insert(js.eventStr, "A")
elseif n == 1 then table.insert(js.eventStr, "B")
elseif n == 2 then table.insert(js.eventStr, "X")
elseif n == 3 then table.insert(js.eventStr, "Y")
elseif n == 4 then table.insert(js.eventStr, "L")
elseif n == 5 then table.insert(js.eventStr, "R")
elseif n == 6 then table.insert(js.eventStr, "E")
elseif n == 7 then table.insert(js.eventStr, "S")
end
elseif event.type == JS_EVENT_AXIS then
local v = event.value
if n == 0 then
if v > 0 then table.insert(js.eventStr, "r")
elseif v < 0 then table.insert(js.eventStr, "l")
end
elseif n == 1 then
if v > 0 then table.insert(js.eventStr, "d")
elseif v < 0 then table.insert(js.eventStr, "u")
end
end
end
end
function clearEventStr(device)
devices[device].eventStr = {}
end
function getEventStr(device)
return table.concat(devices[device].eventStr)
end
function readAllEvents(device)
local event
local numEvent = 0
local js = devices[device]
repeat
event = readOneEvent(device)
if event ~= nil then
numEvent = numEvent + 1
if (event.time - js.last_time) > EVENT_CLEAR_MSEC then
clearEventStr(device)
end
local num = event.number + 1
if band(event.type, JS_EVENT_INIT) > 0 then
js.start = event.time;
event.type = band(event.type, bnot(JS_EVENT_INIT))
if event.type == JS_EVENT_BUTTON then
js.buttons[num].value = event.value
js.buttons[num].time = event.time
else
js.axes[num].value = event.value
js.axes[num].time = event.time
end
elseif event.type == JS_EVENT_BUTTON then
local button = js.buttons[num]
if event.value == 0 then
button.longPush = event.time - button.time
button.longRelease = 0
else
button.longPush = 0
button.longRelease = event.time - button.time
recordEventStr(device, event)
end
button.value = event.value
button.time = event.time
else
js.axes[num].value = event.value
js.axes[num].time = event.time
if event.value ~= 0 then
recordEventStr(device, event)
end
end
js.last_time = event.time
end
until event == nil
return numEvent
end
function readAllDevices()
for i = 1, #devices do
readAllEvents(i)
end
end
function exec(command)
local f = io.popen(command .. " 2>&1", "r")
if f ~= nil then
-- Check if the command is running in the background. (by yasuo)
if string.find(command, "&") ~= nil then
return nil -- command is running in the background.
end
local result = f:read('*a') -- read output
f:close()
return result
else
return "execute error"
end
end
function initCommandList()
commandList = { BABABA = "help" }
end
function addCommand(keys, command)
commandList[keys] = command
return #commandList
end
function listCommand(f)
f:write("[A] [B] [X] [Y] [L] [R] [Start] [sElect], ")
f:write("up, down, left, right\n")
for key, command in pairs(commandList) do
f:write(key .. " : " .. command .. "\n")
end
end
function execCommand(numDevice, str, f)
for key, command in pairs(commandList) do
if str == key then
if command == "quit" then os.exit() end
if command == "help" then
listCommand(f)
clearEventStr(numDevice)
else
f:write("\nkey = ".. key .. ", command = " .. command .. "\n")
local result = exec(command)
if result ~= nil then f:write(result) end
clearEventStr(numDevice)
end
end
end
end
function checkCommand(numDevice, f)
local numEvent = readAllEvents(numDevice)
if numEvent > 0 then
str = getEventStr(numDevice)
execCommand(numDevice, str, f)
return str
end
return nil
end
function getNoOfDevices()
return #devices
end
-- device_num: 1..8
function getName(device_num)
return devices[device_num].name
end
function getVersion(device_num)
return devices[device_num].version
end
function getNoOfAxes(device_num)
return devices[device_num].num_axes
end
function getNoOfButtons(device_num)
return devices[device_num].num_buttons
end
function sleep(sec)
ffi.C.poll(nil, 0, sec * 1000)
end
function printf(f, fmt, ...)
f:write(string.format(fmt, ...))
end
function deviceList(f)
local num_device = getNoOfDevices()
for i = 1, num_device do
printf(f, "Name: %s\n", getName(i))
printf(f, "Ver.: %s\n", getVersion(i))
local num_axes = getNoOfAxes(i)
printf(f, " No. of Axes : %d\n", num_axes)
local num_buttons = getNoOfButtons(i)
printf(f, " No. of Buttons : %d\n", num_buttons)
end
printf(f, "\nhelp : BABABA\n\n")
end
-- -------------
-- main
-- -------------
local numOfDevices = init()
local str
local f
if arg[1] == "-d" then DEBUG = true end
if arg[1] == "-c" then CONSOLE = true end
if CONSOLE then
f = io.open("/dev/console", "w")
else
f = io.open("/dev/stdout", "w")
end
initCommandList()
-- A, B, X, Y, L, R, Start, sElect, up, down, left, right
addCommand("ESESESES", "quit")
addCommand("uuddlrlrBA", "/sbin/shutdown -h now")
addCommand("uuddlrlrES", "/sbin/reboot")
addCommand("BBBB", "/bin/date -R")
addCommand("SSSS", "/usr/bin/pstree -A")
addCommand("SESE", "/bin/sync")
addCommand("EEEE", "/bin/ps aux")
addCommand("LLLL", "/sbin/lsmod")
addCommand("lllr", "/sbin/lsmod")
addCommand("RRRR", "/usr/bin/who -a")
addCommand("rrrl", "/usr/bin/who -a")
addCommand("rrrr", "/bin/netstat -a")
addCommand("llrr", "/sbin/ifconfig -a")
addCommand("llll", "/usr/bin/vmstat -s")
addCommand("lrlr", "/usr/bin/uptime")
addCommand("uuuu", "/bin/cat /sys/class/thermal/thermal_zone0/temp")
addCommand("dddd",
"/bin/cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq")
addCommand("dudu", "/bin/df -kT")
addCommand("uldr", "/bin/uname -a")
addCommand("Eddd", "/usr/bin/env")
addCommand("ABAB", "/usr/local/bin/led.lua")
--
if arg[1] == "-h" then
listCommand(f)
os.exit()
end
if numOfDevices > 0 then
deviceList(f)
while (true) do
str = checkCommand(1, f)
if DEBUG and str ~= nil then io.write(str.."\n") end
sleep(0.5)
end
end
led.lua
ACT(緑) と PWR(赤) のLEDを点滅するプログラムです。 Raspberry Pi 専用です。 Raspberry Pi B+ と Raspberry Pi Zero 用は、GPIO レジスタのベースアドレスを 0x20200000 に設定します。 Raspberry Pi2と Pi3 は レジスタのベースアドレスに 0x3F200000 を指定します。
#!/usr/bin/luajit
-- ---------------------------------------------
-- led.lua 2017/04/03
-- Copyright (c) 2015-2017 Jun Mizutani,
-- released under the MIT open source license.
-- ---------------------------------------------
local bit = require("bit")
local ffi = require("ffi")
local C = ffi.C
ffi.cdef int munmap(void *addr, size_t len);
void *mmap(void *addr, size_t len, int prot, int flags, int fd, int offset);
int munmap(void *addr, size_t len);
int open(const char *pathname, int flags, int mode);
typedef unsigned long int nfds_t;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
]]
function sleep(sec)
C.poll(nil, 0, sec * 1000)
end
-- 物理アドレス空間のデバイスファイルをオープン
function mem_open()
local O_RDONLY = 0
local O_WRONLY = 1
local O_RDWR = 2
local fd = C.open("/dev/mem", O_RDWR, 0)
return fd
end
-- メモリをマッピング
function mem_map(fd, addr, length, offset)
local PROT_READ = 1
local PROT_WRITE = 2
local PROT_EXEC = 4
local MAP_SHARED = 1
local MAP_PRIVATE = 2
if fd > 0 then
local p = C.mmap(addr, length, PROT_READ + PROT_WRITE, MAP_SHARED,
fd, offset)
local mem = ffi.cast("int32_t *", p)
if mem > ffi.cast("int32_t *", 0) then
return mem
end
end
return nil
end
-- メモリのマッピングを解除
function mem_unmap(addr, length)
local p = ffi.cast("int32_t *", addr)
return C.munmap(p, length)
end
local gpio
-- 初代 Raspberry Pi 用のベースアドレス
-- local BCM_PEREIFERAL_ADDR = 0x20200000
-- Raspberry Pi2 用のベースアドレス
local BCM_PEREIFERAL_ADDR = 0x3F200000
local GPSET0 = 7
local GPSET1 = 8
local GPCLR0 = 10
local GPCLR1 = 11
local GPLEV0 = 13
local GPLEV1 = 14
-- GPIO のレジスタを gpio 配列に設定
function gpioOpen()
local fd = mem_open()
local mem = mem_map(fd, nil, 256, BCM_PEREIFERAL_ADDR)
if mem == nil then
print("You must run 'sudo luajit gpio.lua'.")
end
gpio = mem
end
-- GPIO のレジスタのマッピングを解除
function gpioClose()
mem_unmap(BCM_PEREIFERAL_ADDR, 256)
end
-- pinNo の GPIO のモードを設定
-- mode: 0/input, 1/output
function gpioSetPinMode(pinNo, mode)
if pinNo > 53 or pinNo < 0 then
print("PinNo is out of range.")
return
elseif mode > 7 then
print("Mode must be [0..7].")
return
else
local reg = math.floor(pinNo / 10)
local shift = (pinNo % 10) * 3
local mask = bit.bnot(bit.lshift(7, shift)) -- ~(7 << shift)
local val = bit.lshift(mode, shift)
local orig = gpio[reg]
local new = bit.bor(bit.band(orig, mask), val)
gpio[reg] = new
end
end
-- pinNo の GPIO のモードを取得
function gpioGetPinMode(pinNo)
if pinNo > 53 or pinNo < 0 then
print("PinNo is out of range.")
return nil
else
local reg = math.floor(pinNo / 10)
local shift = (pinNo % 10) * 3
local mask = bit.lshift(7, shift)
local val = bit.band(gpio[reg], mask)
local mode = bit.rshift(val, shift)
return mode
end
end
-- pinNo の GPIO に 1 を出力
function gpioSet(pinNo)
if pinNo < 32 then
gpio[GPSET0] = bit.lshift(1, pinNo)
elseif pinNo < 54 then
gpio[GPSET1] = bit.lshift(1, pinNo - 32)
end
end
-- pinNo の GPIO に 0 を出力
function gpioClear(pinNo)
if pinNo < 32 then
gpio[GPCLR0] = bit.lshift(1, pinNo)
elseif pinNo < 54 then
gpio[GPCLR1] = bit.lshift(1, pinNo - 32)
end
end
-- pinNo の GPIO を読み出し
function gpioRead(pinNo)
local val
if pinNo < 32 then
val = bit.band(gpio[GPLEV0], bit.lshift(1, pinNo))
elseif pinNo < 54 then
val = bit.band(gpio[GPLEV1], bit.lshift(1, pinNo - 32))
end
if val ~= 0 then
return 1
end
return 0
end
-- PWR(赤)と ACT(緑)を点滅させる
gpioOpen() -- GPIO アクセス準備
gpioSetPinMode(35, 1) -- PWR : 出力モード
gpioSetPinMode(47, 1) -- ACT : 出力モード
for i = 1, 20 do
gpioSet(35) -- PWR を 点灯
gpioSet(47) -- ACT を 点灯
sleep(0.1) -- 0.1秒間待つ
gpioClear(35) -- PWR を 消灯
gpioClear(47) -- ACT を 消灯
sleep(0.1) -- 0.1秒間待つ
end
gpioClose()
動作確認
ゲームパッドの接続と取り外しに伴って、jsCommand.lua が正常に起動/停止されることを確認します。 Raspbian Jessie でカーネルは現時点で最新の以下の環境で確認しました。
機種
Raspberry Pi 2
$ uname -a
Linux raspi_jessie 4.9.20-v7+ #985 SMP Mon Apr 3 10:30:44 BST 2017 armv7l GNU/Linux
Raspberry Pi B+
$ uname -a
Linux raspberrypi 4.1.19+ #858 Tue Mar 15 15:52:03 GMT 2016 armv6l GNU/Linux
起動時の状態
ゲームパッド接続状態で起動
$ systemctl status jscom.service
jscom.service - jsCommand
Loaded: loaded (/etc/systemd/system/jscom.service; disabled)
Active: active (running) since Mon 2017-04-03 11:26:03 JST; 38s ago
Process: 363 ExecStartPre=/usr/bin/killall jsCommand.lua (code=exited, status=1/FAILURE)
Main PID: 437 (jsCommand.lua)
CGroup: /system.slice/jscom.service
└─437 /usr/bin/luajit /usr/local/bin/jsCommand.lua -c &
再接続
ゲームパッドを取りはずした後に状態を確認します。プロセスIDが異っているため、jsCommand.lua が再起動していることが確認できます。
$ systemctl status jscom.service
jscom.service - jsCommand
Loaded: loaded (/etc/systemd/system/jscom.service; disabled)
Active: active (running) since Mon 2017-04-03 11:27:25 JST; 13s ago
Process: 868 ExecStartPre=/usr/bin/killall jsCommand.lua (code=exited, status=1/FAILURE)
Main PID: 871 (jsCommand.lua)
CGroup: /system.slice/jscom.service
└─871 /usr/bin/luajit /usr/local/bin/jsCommand.lua -c &
接続解除
ゲームパッドを取りはずした状態を確認します。 jsCommand.lua のプロセスは動作していません。
$ systemctl status jscom.service
jscom.service - jsCommand
Loaded: loaded (/etc/systemd/system/jscom.service; disabled)
Active: inactive (dead)
再再接続
再度ゲームパッドを接続して確認します。別のプロセスIDで、jsCommand.lua が動作していることが確認できます。
$ systemctl status jscom.service
jscom.service - jsCommand
Loaded: loaded (/etc/systemd/system/jscom.service; disabled)
Active: active (running) since Mon 2017-04-03 11:28:43 JST; 10s ago
Process: 904 ExecStartPre=/usr/bin/killall jsCommand.lua (code=exited, status=1/FAILURE)
Main PID: 907 (jsCommand.lua)
CGroup: /system.slice/jscom.service
└─907 /usr/bin/luajit /usr/local/bin/jsCommand.lua -c &
Orange Pi Zero でも確認
おまけとして Orange Pi Zero でも動作確認してみました。 OS は公式のイメージではなく Armbian (Ubuntu 16.04.1)を使っています。
jun@orangepizero:~$ uname -a
Linux orangepizero 3.4.113-sun8i #28 SMP PREEMPT Thu Feb 2 02:01:28 CET 2017 armv7l armv7l armv7l GNU/Linux
超小型の Orange Pi Zero をサーバとして使っていると、シャットダウンのためにディスプレイやキーボードをつなぐのはバカバカしくなります。ネットワーク越しに ssh で接続するより、ゲームパッドを接続して「上上下下左右左右BA」するほうが楽です。でも、もっと小さいゲームパッドが欲しくなりますね。
LED の点滅コマンドはRaspberry Pi のハードウェアに依存するため動作しません。
jun@orangepizero:~$ tar zxf jscommand2.tar.gz jun@orangepizero:~$ cd jscommand/ jun@orangepizero:~/jscommand$ sudo ./install.sh jun@orangepizero:~/jscommand$ systemctl status jscom.service jscom.service - jsCommand Loaded: loaded (/etc/systemd/system/jscom.service; disabled; vendor preset: e Active: inactive (dead) jun@orangepizero:~/jscommand$ systemctl status jscom.service jscom.service - jsCommand Loaded: loaded (/etc/systemd/system/jscom.service; disabled; vendor preset: e Active: active (running) since Tue 2017-04-04 15:30:31 UTC; 7s ago Process: 5547 ExecStartPre=/usr/bin/killall jsCommand.lua (code=exited, status Main PID: 5549 (jsCommand.lua) CGroup: /system.slice/jscom.service └─5549 /usr/bin/luajit /usr/local/bin/jsCommand.lua -c &
Raspberry Pi にしても、Orange Pi にしても色々な進化が速くて、遊ぶネタは尽きません。 脳もマルチコアにできませんかね (笑)。