ゲームパッドでコマンドを実行 (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 にしても色々な進化が速くて、遊ぶネタは尽きません。 脳もマルチコアにできませんかね (笑)。