Raspberry Piを使用してXbox 360からPSPゲームパッドに接続します

...またはペンギンがどのようにアメリカと日本と友達になったかについての物語。



ですから、古代では、人々はソニーのプレイステーションでプレイし、すべてに満足していました。 しかし、進歩は止まりませんでした。 超小型回路の集積度が向上しました。 エンジニアリングの考え方は常に新しいフォームファクターを探し、マーケティングの考え方は新しい市場を探していました。 そのため、2005年には、ポータブルゲームシステムであるSony Playstation Portableが日本国外で販売されました。 彼女のゲームライン(他のコンソールと同様)は、PSPアイアンゲーム用に特別にコンパイルされました。 しかし、彼女は、組み込みのエミュレータを介して元のPlayStationからゲームを実行するのに十分な計算能力も備えていました。 おそらく、重要な役割は、PlayStationとPSPの両方に同じアーキテクチャ(MIPS)のプロセッサが搭載されているという事実によって果たされました。 しかし、このシステムで最も注目すべきことは、発売された年にすぐに、PSP SDKライブラリネットワークにリークしたことです。 その結果、発売からほぼ10年後には、膨大な数のゲームライブラリと機能性の高いホームブリューがあります。 また、今ではロシアの最大の都市ではなく、完全に機能するPSP(最も機能的な変更の)を3000ルーブルで購入できます。 これらすべてにより、現在、非常に魅力的な予算のゲーミングシステムであり、巨大な設置ベースを備えています。 最も機能的な変更には、テレビに接続するためのコンポーネント出力があります。 ただし、PSPを長期間ゲームパッドとして使用するという点では、TVコネクタの位置が適切ではありません。 さらに、長時間の使用では、充電器からの2番目のワイヤを接続する必要があります。 そして、そのようなキメラの使いやすさはゼロになる傾向があります。 この問題を比較的安価でかつオタクで解決する方法-これについては、この記事で説明します。 また、PSP用のUSBクライアントドライバーのプログラミング、PSPにフック関数をインストールする方法、LinuxでUSBデバイスとAPI経由のジョイスティックを使用する方法についても簡単に触れます。 始めました。

ポータブルコンソールをテレビに接続するという考えは新しいものではありません


しかし、始める前に、1つの興味深い事実について説明します。 PSPはほぼ10年前にリリースされて以来、次世代のソニーのポータブルコンソール、つまりSony Playstation Vitaが現在関連しています。 そして事実は、日本ではポータブルコンソールの固定バージョンがあったということです。 Sony PlayStation Vita TV。
PS Vita TV


ゲームパッドとして、Playstation 3の通常のDualshock 3を使用します。USBまたはBluetooth接続をサポートしています。 Vita TVは、Vitaと同様に、Vita、PSP、および元のPlaystationのゲームをプレイできます。 したがって、「固定ポータブル」コンソールのアイデアは非常に一貫性があり、興味深いものです。

友達をゲームパッドとPSPにするにはどうすればよいですか?


次に、外部ゲームパッドをPSPに接続する方法について質問がありました。 PSPにはUSBコネクタがあり、愛好家はPSP 、接続されたコンピューターのフォルダーからゲームを実行するか、ゲームと一緒に画像全体をこのUSB接続されたコンピューターのウィンドウに転送 するように教えました。 しかし、 判明したように 、PSPのUSBはクライアントにしかなれません。 また、公式のアクセサリ(カメラなど)でもホストモードで動作します(ちなみに、Androidで周辺機器を操作する場合は、スマートフォンをクライアントモードに切り替えることをお勧めします)。 つまり ゲームパッドをPSPに直接接続することは役に立ちません。 したがって、何らかの中間デバイスが必要です。 地元の電気店では、さまざまな程度の急勾配のボードをデバッグするのに1から1万ルーブルかかります。 これらはマイクロコントローラですが、USBホストについては別に考える必要があります。 それから、Raspberry Piが目を引きました。
ラズベリーパイ


このマシンには必要なものがすべて揃っています-2つのUSBポートと完全なLinux。 それほど大きくないロシアの都市では、古いモデル(512 MBのメモリとイーサネット)は、無料のプライベート掲示板で1,500ルーブルかかります。 価格は、最も安価なコントローラーデバッグボードに見合ったものであり、機能はもはや例ではありません。 また、「英国製」。

研究開始


USBケーブルをPSPに接続するだけで、USBフラッシュドライブとして表示されます。 彼女には外部管理に関するコマンドを受け入れる必要があります。 つまり USBを介して情報を受信し、PSP自体のコントロールの押下をシミュレートするコードのいくつかの種類がPSPでスピンする必要があります。 ライセンスされたゲームに加えていくつかのコードを実行する能力は、海賊版ファームウェアでのみ可能です。 技術的には、海賊版ファームウェアは、メモリカードにある公式プログラムのふりをするプログラムであり、起動すると、動作中のファームウェアコードをRAM内の修正されたコードに置き換えます。これにより、PSPメモリカードの.isoファイルからゲームを実行できます。 したがって、ファームウェアは、次のPSPの再起動までスムーズに実行されます。 しかし、私たちにとって重要なのはこれではなく、プラグインをサポートしているという事実です。 プラグインは、特定の形式でリンクされたオブジェクトファイルであり、メインメニュー、PSPゲーム、または元のプレイステーションのゲームの起動と並行して、別々のストリームで開始されます。 元のPSPファームウェアの最新バージョンはすでに6.60です。 ファームウェアの以前のバージョン用にシャープ化されたプラグインは、最新のファームウェアでは動作しない場合があります。 これはこの場合に起こりました。 USB経由でPSPからPSPで発生するすべてのWindowsベースのPCビデオにU​​SB経由で転送し、PSP内のPCに接続されたゲームパッドからUSB経由でデータを受信できるプラグイン。ファームウェア6.60で動作するのは半分だけです。 ゲームパッドからPSPへのデータは到達しましたが、PSPコントロールのコントロールのシミュレーションは機能しませんでした。 ファームウェア6.60のコントロールで何らかの形で機能するプラグインを探し始めました。 そしてそれを見つけました。 別のプラグインは、PSPアナログスティックを操作するために使用され、最新のファームウェアで動作します。 すべてのPSPソースは、 この homebrew SDKでコンパイルされています。

PSPプラグインのソースコードの変更。 フック


変更するプラグインプロジェクトの基礎として、USBクライアントの作業コードが既に含まれているものを選択しました。 しかし、通常のデバッグと一般的に居心地の良い雰囲気のために、printf()が必要でした。 PSPで。 選択したプラグインにはありませんでした。 しかし、PSPコントロールイベントをインターセプトするための作業コードを取得したいプラグインでは、次のフレームをフレームバッファーに描画する機能をインターセプトし、必要なデバッグラインをフレームに追加することで実装されました。 レンダリング自体のキャプチャ機能(フック)は、次のように実装されます。

#define GET_JUMP_TARGET_(x) (0x80000000 | (((x) & 0x03FFFFFF) << 2)) int (*g_setframebuf)(int unk, void* addr, int width, int psm, int sync); int setframebuf_hook_func(int unk, void* addr, int width, int psm, int sync) { if(g_info == 1) { dbgprint( debugmsg, addr, psm ); if (!g_info) DEBUG_RESET() } return g_setframebuf(unk, addr, width, psm, sync); } int hook_function(unsigned int* jump, void* hook, unsigned int* result) { unsigned int target; unsigned int func; int inst; target = GET_JUMP_TARGET_(*jump); while (((inst = _lw(target+4)) & ~0x03FFFFFF) != 0x0C000000) // search next JAL instruction target += 4; if((inst & ~0x03FFFFFF) != 0x0C000000) { printf("invalid!\n"); return 1; } *result = GET_JUMP_TARGET_(inst); func = (unsigned int) hook; func = (func & 0x0FFFFFFF) >> 2; _sw(0x0C000000 | func, target+4); return 0; } int module_start( SceSize args, void *argp ) { //... hook_function( (unsigned int*) sceDisplaySetFrameBuf, setframebuf_hook_func, (unsigned int*)&g_setframebuf ); //... } 

hook_function()を呼び出した後、PSPオペレーティングシステムは、その内部カーネル関数を呼び出すときに、sceDisplaySetFrameBuf()が実際にsetframebuf_hook_func()を呼び出します。 元のsceDisplaySetFrameBuf()を呼び出すには、g_setframebuf()を呼び出す必要があります。 たとえばフックのトピックに興味がある人は、 ここでより詳細に見つけることができます。

PSPプラグインのソースコードの変更。 管理。


次に、管理関数sceCtrlReadBufferPositive()、sceCtrlPeekBufferPositive()、sceCtrlReadBufferNegative()、およびsceCtrlPeekBufferNegative()で変更するワークフックをプロジェクトに追加し、同じJoySensから取得します。 それらの内部の入力データが、PCホストに接続されたゲームパッドの状態についてPSPに最後に送信されたデータであることを確認しました。 必要なすべてのバイナリとソースコードを含むアーカイブを次に示します。 プログラムのPC部分を開始する前に、USBドライバーをインストールする必要があります。 まず、PSPでプラグインを実行する必要があります(Yandexでグーグルでプラグインを実行する方法を見つけることができます)。 次に、PSPを再起動し、PCに接続します。 PSPタイプBデバイスが検出されるはずです。次に、 ドライバーをダウンロードします。 ウィザード(bin \ inf-wizard.exe)を使用してドライバーをインストールし、PSP Type Bデバイスをポイントして、最後にドライバーをインストールするよう指示します。

最小PSPバージョンの準備


すべて問題ありませんが、ネットワークにはバージョン0.19 RemoteJoyLiteのソースコードのみがあります。 また、一部のゲームでは正常に動作しません(たとえば、 K-On!は非常に遅く、 Dungeon Siegeにグラフィックアーティファクトが表示されます)。 バージョン0.20では、これは修正されたと言われていますが、このバージョンのソースコードは公開されていません。 したがって、ゲームパッドの状態に関する最小限の情報のみを送信し、PSPパーツのソースのサイズを最小化するために、USBを介して送信されるデータのプロトコルを変更することが決定されました。 PSPからPCに転送されたすべてのデータはプロトコルから削除され、1つの構造のみがPCからPSPに転送されました。その結果、ブレーキとアーティファクトは忘れ去られました。

 #define USBDATA_PATTERN 0x1234ABFE struct { unsigned int Pattern; unsigned int ButtonData; unsigned int AnalogX; unsigned int AnalogY; } PSPUsbData; 

PSP自体のスティックからのアナログデータは、各軸のシングルバイトの符号なし整数として提示され(127が中心)、プロトコルの4バイトが割り当てられます。これは、構造のパッキングとアライメントの問題さえ考えないためです。 RemoteJoyLite自体では、データは次のようにパックされます。

 struct HostFsCmd { uint32_t magic; uint32_t command; uint32_t extralen; } __attribute__((packed)); 

そして、対応する問題について考えることさえしないように(結局、RemoteJoyLite自体はMinGW GCCによってコンパイルされ、次のステップはMicrosoft Visual Studioでプロジェクトを作成し、__ attribute __((packed))を知らない切り捨てられたプロトコルを解決することです)、すべてのアライメントとパッケージ構造を削除しましたそれらを32ビット表現にします。 その結果、 このアーカイブには、PSPとWindowsの両方の部分である、削除されたプロジェクトのソースコードとバイナリが含まれています。 Windowsアプリケーションは、Microsoft Visual Studio 2010のC ++のmfcプロジェクトとして設計されています。Windowsプログラマーにとって、カーソル制御キーストロークの実装方法を確認すると役立ちます(mfcアプリケーションでは、アプリケーション全体のウィンドウメッセージフィルターにある場合にのみ機能し、ダイアログのキーストロークではなく)、また、別のストリームからでもprintf()がフォーム上で正しく機能するためです。 PSPパートでは、USB、control、printf()のみが残されている、接続された軽量ソースを研究することは興味深いでしょう。 たとえば、USBデータ受信は非同期で、次のように実装されます。 USBホストに接続するとき、USBバスの内部アドレスを取得する正しい手順(USBの説明を参照)の後、UsbAttach()呼び出しが行われます。これは、この呼び出しが初期化中に登録されたドライバー構造に記述されているためです:

 #define RJLITE_DRIVERNAME "RJLiteDriver" #define RJLITE_DRIVERPID (0x1C9) struct UsbDriver UsbDriver = { RJLITE_DRIVERNAME, 4, UsbEndpoint, &UsbInterface, &UsbData[0].devdesc[0], &UsbData[0].config, &UsbData[1].devdesc[0], &UsbData[1].config, &StringDescriptor, UsbRequest, UsbUnknown, UsbAttach, UsbDetach, 0, UsbStartFunc, UsbStopFunc, NULL }; int module_start( SceSize args, void *argp ) { //... sceUsbbdRegister(&UsbDriver); if((sceUsbStart(PSP_USBBUS_DRIVERNAME, 0, 0) == 0) && (sceUsbStart(RJLITE_DRIVERNAME, 0, 0) == 0) && //... { //... } //... } 

ここで、module_start()は、プラグインが海賊版ファームウェアで起動するときに別のスレッドで呼び出される関数です。 また、ドライバーを起動すると、int型のフラグ変数(つまり32フラグ)が作成され、PSPオペレーティングシステム内のオブジェクトの一意の識別子を介してアクセスされ、補助ストリームが起動されます。

 static SceUID UsbMainEventFlag = -1; static int UsbStartFunc( int size, void *p ) { //... UsbMainEventFlag = sceKernelCreateEventFlag( "USBMainEvent", 0x200, 0, NULL ); //... UsbMainThreadID = sceKernelCreateThread( "USBMainThread", UsbMainThread, 10, 0x10000, 0, NULL ); //... sceKernelStartThread( UsbMainThreadID, 0, NULL ); //... } 

したがって、UsbAttach()と呼ばれると、UsbMainEventFlagオブジェクトのフラグ変数にフラグUSB_EVENT_ATTACHが設定されます。

 static int UsbAttach(int speed, void *arg2, void *arg3) { sceKernelSetEventFlag( UsbMainEventFlag, USB_EVENT_ATTACH); return 0; } 

同時に、UsbStartFunc()を呼び出すときに以前に作成されたUsbMainThread()スレッドでは、次のように表示されます。

 static int UsbMainThread(SceSize size, void *argp) { int ret; u32 result; while(1) { ret = sceKernelWaitEventFlag(UsbMainEventFlag, USB_EVENT_ATTACH | USB_EVENT_ASYNC, PSP_EVENT_WAITOR | PSP_EVENT_WAITCLEAR, &result, NULL); if(ret < 0) { sceKernelExitDeleteThread(0); } if(result&USB_EVENT_ASYNC) { usb_async_events++;//nyashkoshkko: debug SetUsbAyncReq(&PSPUsbData, sizeof(PSPUsbData)); } if(result&USB_EVENT_ATTACH) { usb_attach_events++;//nyashkoshkko: debug SetUsbAyncReq(&PSPUsbData, sizeof(PSPUsbData)); } } return 0; } 

つまり、無限ループ内のスレッドは、USB_EVENT_ATTACHフラグまたはUSB_EVENT_ASYNCフラグがUsbMainEventFlagオブジェクトのフラグ変数に設定されるのを待機します。 USBホストとの接続に成功すると、USB_EVENT_ASYNCフラグがリセットされ、USB_EVENT_ATTACHフラグが設定されます。これにより、このスレッドはUSB_EVENT_ASYNCフラグをリセットしながら、USB経由でデータパケットを受信する非同期要求を実行します。

 static int SetUsbAyncReq( void *data, int size ) { //... UsbAsyncReq.data = data; UsbAsyncReq.size = size; UsbAsyncReq.func = UsbAsyncReqDone; sceKernelClearEventFlag( UsbMainEventFlag, ~USB_EVENT_ASYNC ); return( sceUsbbdReqRecv( &UsbAsyncReq ) ); } 

このリクエストでは、コールバックはUsbAsyncReqDone()関数の呼び出しをセットアップします。

 static int UsbAsyncReqDone( struct UsbdDeviceReq *req, int arg2, int arg3 ) { sceKernelSetEventFlag( UsbMainEventFlag, USB_EVENT_ASYNC ); return( 0 ); } 

この関数は、ご覧のとおり、USBホストからのデータパケットの受信が完了すると(PSP USBコントローラーからの割り込みによってPSPオペレーティングシステムカーネルによって処理されます)、UsbMainEventFlagオブジェクトのフラグ変数にフラグUSB_EVENT_ASYNCを設定します。 その上で、無限ループが新しい非同期データ要求をセットアップします。 このようなイベントメカニズムを使用すると、sceKernelWaitEventFlag()を呼び出すと、必要なイベントが発生するまでストリームにタイムスライスが割り当てられないため、データレディフラグを無限にポーリングするためにCPU時間を浪費することはできません-これはPSPオペレーティングシステム内のスレッドスケジューラーによって保証されます基本原則は、マルチタスクオペレーティングシステムで機能します。

Raspberry PiでLinux用USBを使用するためのサービスを作成する


これで、PSPパーツが完成しました。 LinuxでRaspberry Piがオンになったときに自動的に開始されるアプリケーション、またはむしろサービスを開発するときです。 一般に、Raspberry Pi用のLinuxディストリビューションがいくつかあります。 しかし、私はFedoraに落ち着きました。 彼はRed Hatにルーツを持っています。RedHatで私は仕事をするのは簡単な仕事で、彼のパッケージ配布RPMに慣れました。 Fedora Remix 18をインストールし、必要に応じてネットワークをセットアップした直後(私の場合、ホームネットワークのDHCPサーバーが正しく機能しないため、ネットワークアドレスとゲートウェイを手動で設定する必要がありました)、マウスを接続してクリックする右上隅のネットワーク接続アイコン、SSHサーバーはボックスから直接動作します。 ただし、SMBサーバーをすばやく構成することはできなかったため(smbpasswdの問題)、ソースは深夜の司令官を介してSSH経由でリモートで作成および編集されました。 最初に始めたのは、PSPへの接続です。 これを行うには、LinuxでUSBと対話する方法を学ぶ必要がありました。 この点で、ちょっと不愉快な話がありました。そのため、やはりLinuxの魅力全体が私の目の前で崩れています。 事実は、コンパイルのためにライブラリとヘッダーをインストールしようとすると

 > yum install libusb1-devel 

パッケージマネージャーは戦いをしており、期限切れだと言いました。libusbxを使用してください。 OKチーム

 > yum install libusbx-devel 

必要なファイルをダウンロードしました。 しかし、実際には、api呼び出しのlibusbxはlibusb1と互換性がありません。libusb1は、Windowsバージョンのlibusbと同じです。これは、元のRemoteJoyLiteソースで使用され、通常はWindowsで正常に動作します。 でも大丈夫。 usbが整理されたら、次にLinuxからゲームパッドにアクセスします。 私は自由に使えるXbox 360ゲームパッドをWindowsで使い、驚くほど、Raspberry PiのFedora Remix 18で箱から出して、デバイス/ dev / input / js0を作成しました 。 これにより、通常のxpadドライバーが機能しました。 代替のxboxdrvドライバーがあります-より柔軟に構成できます。 しかし、私たちには十分なフルタイムがあります。
ところで、アンドロイドはまったく同じです。
xpadドライバー 、AndroidのLinuxカーネルの一部です。

 #define DRIVER_DESC "X-Box pad driver" 

同様に、デバイス/ dev / input / js0が作成されます:

 MODULE_SUPPORTED_DEVICE("input/js"); 

たとえば、Androidで入力デバイスのリストを取得する方法を検討してください。 推奨されるAPIは、getDeviceIds()を呼び出すことでこれを行うように指示します。

  /** * Gets the ids of all input devices in the system. * @return The input device ids. */ public static int[] getDeviceIds() { return InputManager.getInstance().getInputDeviceIds(); } 

getInputDeviceIds()

  private final IInputManager mIm; //... private SparseArray<InputDevice> mInputDevices; //... /** * Gets the ids of all input devices in the system. * @return The input device ids. */ public int[] getInputDeviceIds() { synchronized (mInputDevicesLock) { populateInputDevicesLocked(); final int count = mInputDevices.size(); final int[] ids = new int[count]; for (int i = 0; i < count; i++) { ids[i] = mInputDevices.keyAt(i); } return ids; } } //... private void populateInputDevicesLocked() { if (mInputDevicesChangedListener == null) { final InputDevicesChangedListener listener = new InputDevicesChangedListener(); try { mIm.registerInputDevicesChangedListener(listener); } catch (RemoteException ex) { throw new RuntimeException( "Could not get register input device changed listener", ex); } mInputDevicesChangedListener = listener; } if (mInputDevices == null) { final int[] ids; try { ids = mIm.getInputDeviceIds(); } catch (RemoteException ex) { throw new RuntimeException("Could not get input device ids.", ex); } mInputDevices = new SparseArray<InputDevice>(); for (int i = 0; i < ids.length; i++) { mInputDevices.put(ids[i], null); } } } 

mIm.getInputDeviceIds()

 interface IInputManager { // Gets input device information. InputDevice getInputDevice(int deviceId); int[] getInputDeviceIds(); //... 

これがInputManagerサービスの出番です。

 import android.view.InputDevice; //... /* * Wraps the C++ InputManager and provides its callbacks. */ public class InputManagerService extends IInputManager.Stub implements Watchdog.Monitor, DisplayManagerService.InputManagerFuncs { static final String TAG = "InputManager"; static final boolean DEBUG = false; private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; //... private InputDevice[] mInputDevices = new InputDevice[0]; //... /** * Gets the ids of all input devices in the system. * @return The input device ids. */ @Override // Binder call public int[] getInputDeviceIds() { synchronized (mInputDevicesLock) { final int count = mInputDevices.length; int[] ids = new int[count]; for (int i = 0; i < count; i++) { ids[i] = mInputDevices[i].getId(); } return ids; } } 

getId()は、私たちがいた場所に既に連れて行ってくれます(アンドロイドは素晴らしいです):

 public final class InputDevice implements Parcelable { private final int mId; //... /** * Gets the input device id. * <p> * Each input device receives a unique id when it is first configured * by the system. The input device id may change when the system is restarted or if the * input device is disconnected, reconnected or reconfigured at any time. * If you require a stable identifier for a device that persists across * boots and reconfigurations, use {@link #getDescriptor()}. * </p> * * @return The input device id. */ public int getId() { return mId; } //... // Called by native code. private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, int productId, String descriptor, boolean isExternal, int sources, int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasButtonUnderPad) { mId = id; mGeneration = generation; mControllerNumber = controllerNumber; mName = name; mVendorId = vendorId; mProductId = productId; mDescriptor = descriptor; mIsExternal = isExternal; mSources = sources; mKeyboardType = keyboardType; mKeyCharacterMap = keyCharacterMap; mHasVibrator = hasVibrator; mHasButtonUnderPad = hasButtonUnderPad; } private InputDevice(Parcel in) { mId = in.readInt(); mGeneration = in.readInt(); mControllerNumber = in.readInt(); mName = in.readString(); mVendorId = in.readInt(); mProductId = in.readInt(); mDescriptor = in.readString(); mIsExternal = in.readInt() != 0; mSources = in.readInt(); mKeyboardType = in.readInt(); mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in); mHasVibrator = in.readInt() != 0; mHasButtonUnderPad = in.readInt() != 0; for (;;) { int axis = in.readInt(); if (axis < 0) { break; } addMotionRange(axis, in.readInt(), in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat()); } } 

Androidのネイティブ部分では、InputDevice()のインスタンスがここに作成されますtoasterからの迅速な応答のおかげ):

 jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& deviceInfo) { //... ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(), deviceInfo.getControllerNumber(), nameObj.get(), static_cast<int32_t>(ident.vendor), static_cast<int32_t>(ident.product), descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(), deviceInfo.getKeyboardType(), kcmObj.get(), deviceInfo.hasVibrator(), deviceInfo.hasButtonUnderPad())); 

この関数はここで呼び出されます

 void NativeInputManager::notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) { JNIEnv* env = jniEnv(); //... jobject inputDeviceObj = android_view_InputDevice_create(env, inputDevices.itemAt(i)); 

notifyInputDevicesChanged()関数の呼び出しは、すでによく知られているInputManagerサービスのコールバックによって再度決定されます。

  // Native callback. private void notifyInputDevicesChanged(InputDevice[] inputDevices) { synchronized (mInputDevicesLock) { if (!mInputDevicesChangedPending) { mInputDevicesChangedPending = true; mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED, mInputDevices).sendToTarget(); } mInputDevices = inputDevices; } } 

コールバック自体の呼び出しはInputReaderで開始されます

 void InputReader::loopOnce() { //... size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); //... // Send out a message that the describes the changed input devices. if (inputDevicesChanged) { mPolicy->notifyInputDevicesChanged(inputDevices); } 

また、ここでは、入力デバイスからのイベントがEventHubInterfaceクラスによって受信されることがわかります。

  sp<EventHubInterface> mEventHub; 

そして、最後に、このクラスEventHub.cppの実装では、Raspberry Piの通常のLinux Fedoraのように、/ dev / inputからデバイスを開いて操作します。

  static const char *DEVICE_PATH = "/dev/input"; //... char devname[PATH_MAX]; char *filename; //... strcpy(devname, DEVICE_PATH); filename = devname + strlen(devname); *filename++ = '/'; //... strcpy(filename, event->name); //... openDeviceLocked(devname); //... status_t EventHub::openDeviceLocked(const char *devicePath) { char buffer[80]; ALOGV("Opening device: %s", devicePath); int fd = open(devicePath, O_RDWR | O_CLOEXEC); if(fd < 0) { ALOGE("could not open %s, %s\n", devicePath, strerror(errno)); return -1; } 

一般に、Androidのこの入力システム全体については、ソースコード自体簡単に説明されています

 /* * The input manager is the core of the system event processing. * * The input manager uses two threads. * * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events, * applies policy, and posts messages to a queue managed by the DispatcherThread. * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the * queue and asynchronously dispatches them to applications. * * By design, the InputReaderThread class and InputDispatcherThread class do not share any * internal state. Moreover, all communication is done one way from the InputReaderThread * into the InputDispatcherThread and never the reverse. Both classes may interact with the * InputDispatchPolicy, however. * * The InputManager class never makes any calls into Java itself. Instead, the * InputDispatchPolicy is responsible for performing all external interactions with the * system, including calling DVM services. */ class InputManagerInterface : public virtual RefBase { 

したがって、サービスの最終ソースコードは次の形式になります。
 #include <stdio.h> #include <stdlib.h> #include <termios.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <libusb-1.0/libusb.h> #define SONY_VENDOR_ID 0x054C #define PSP_B_PRODUCT_ID 0x01C9 #define UP 0x00000010 #define DOWN 0x00000040 #define LEFT 0x00000080 #define RIGHT 0x00000020 #define B_X 0x00004000 #define B_O 0x00002000 #define B_KVADRAT 0x00008000 #define B_TREUGOLNIK 0x00001000 #define B_L 0x00000100 #define B_R 0x00000200 #define B_SELECT 0x00000001 #define B_START 0x00000008 #define B_NOTE 0x00800000 struct { unsigned int Pattern; unsigned int Btn; unsigned int X; unsigned int Y; } PS = {0x1234ABFE, 0, 127, 127}; struct js_event { unsigned int time; short value; unsigned char type; unsigned char number; }; int is_usbdevblock(libusb_device *dev) { struct libusb_device_descriptor desc; int r = libusb_get_device_descriptor(dev, &desc); if((desc.idVendor == SONY_VENDOR_ID) && (desc.idProduct == PSP_B_PRODUCT_ID)) { return 1; } return 0; } int main(int argc, char** argv) { unsigned int real_x = 0, real_y = 0; int x, y; int fd = 0; while(1) { libusb_device **list; libusb_device *found = NULL; libusb_context *ctx = NULL; int attached = 0; libusb_init(&ctx); libusb_set_debug(ctx, 3); ssize_t cnt = libusb_get_device_list(ctx, &list); ssize_t i = 0; int err = 0; if(cnt < 0) { return -1; } for(i = 0; i < cnt; i++) { libusb_device *device = list[i]; if(is_usbdevblock(device)) { found = device; break; } } if(found) { libusb_device_handle *handle; err = libusb_open(found, &handle); if (err) { return -1; } if (libusb_kernel_driver_active(handle, 0)) { libusb_detach_kernel_driver(handle, 0); attached = 1; } err = libusb_claim_interface(handle, 0); if (err) { return -1; } if(fd == 0) { fd = open("/dev/input/js0", O_RDONLY); } if(fd < 0) { goto clean; } int nEndpoint = 0x01; int nTimeout = 500; //in milliseconds int BytesWritten = 0; int ret; struct js_event e; int t; while(1) { read(fd, &e, sizeof(struct js_event)); e.type &= ~0x80; t = 0; //transfer = 0; if(e.type == 1) { if(e.value == 1) { if(e.number == 0) {PS.Btn |= B_X; t = 1;} if(e.number == 1) {PS.Btn |= B_O; t = 1;} if(e.number == 2) {PS.Btn |= B_KVADRAT; t = 1;} if(e.number == 3) {PS.Btn |= B_TREUGOLNIK; t = 1;} if(e.number == 4) {PS.Btn |= B_L; t = 1;} if(e.number == 5) {PS.Btn |= B_R; t = 1;} if(e.number == 6) {PS.Btn |= B_SELECT; t = 1;} if(e.number == 7) {PS.Btn |= B_START; t = 1;} if(e.number == 8) {PS.Btn |= B_NOTE; t = 1;}//XBOX_HOME //if(e.number == 9) PS.Btn |= ;//L_STICK_PRESS //if(e.number == 10)PS.Btn |= ;//R_STICK_PRESS } if(e.value == 0) { if(e.number == 0) {PS.Btn &= ~B_X; t = 1;} if(e.number == 1) {PS.Btn &= ~B_O; t = 1;} if(e.number == 2) {PS.Btn &= ~B_KVADRAT; t = 1;} if(e.number == 3) {PS.Btn &= ~B_TREUGOLNIK; t = 1;} if(e.number == 4) {PS.Btn &= ~B_L; t = 1;} if(e.number == 5) {PS.Btn &= ~B_R; t = 1;} if(e.number == 6) {PS.Btn &= ~B_SELECT; t = 1;} if(e.number == 7) {PS.Btn &= ~B_START; t = 1;} if(e.number == 8) {PS.Btn &= ~B_NOTE; t = 1;} } } if(e.type == 2) { if(e.number == 6) { if(e.value == -32767) {PS.Btn |= LEFT; t = 1;} if(e.value == 32767) {PS.Btn |= RIGHT; t = 1;} if(e.value == 0) {PS.Btn &= ~(LEFT | RIGHT); t = 1;} } if(e.number == 7) { if(e.value == -32767) {PS.Btn |= UP; t = 1;} if(e.value == 32767) {PS.Btn |= DOWN; t = 1;} if(e.value == 0) {PS.Btn &= ~(UP | DOWN); t = 1;} } if(e.number == 0) { if(real_x != ((e.value + 32767) / 256)) {real_x = ((e.value + 32767) / 256); t = 1;} } if(e.number == 1) { if(real_y != ((e.value + 32767) / 256)) {real_y = ((e.value + 32767) / 256); t = 1;} } } if(t == 1) { #define KOEF 1.4 //[-128..0..127] x = real_x - 128; y = real_y - 128; x = x * (1. + ((abs(x) * (KOEF-1.))/(127./KOEF))); if(x > 127) x = 127; if(x < -128) x = -128; y = y * (1. + ((abs(y) * (KOEF-1.))/(127./KOEF))); if(y > 127) y = 127; if(y < -128) y = -128; PS.X = 128 + x; PS.Y = 128 + y; ret = libusb_bulk_transfer(handle, nEndpoint, (unsigned char *)&PS, sizeof(PS), &BytesWritten, nTimeout); if(ret < 0) { break; } } } clean: if(fd) { close(fd); fd = 0; } if(attached == 1) { libusb_attach_kernel_driver(handle, 0); } libusb_close(handle); } libusb_free_device_list(list, 1); libusb_exit(ctx); sleep(1); } return 0; } 

これはサービスバイナリへのリンクです。 また、m.shコンパイルスクリプトの内容も次のとおりです。

 gcc xbox2psp.c -o xbox2psp.o -I/usr/local -L/usr/local -lusb-1.0 

ソース自体によると、私は2つの点に注意したいと思います。 まず、Xboxアナログスティックの精度は16ビットですが、PSPスティックの精度は8ビットです。 この点で、Xboxコントローラーからのソースデータを変更せずに、8ビットに縮小された軸の値を変更するためにパッケージを送信します。 第二に、PSPでは、対角線の値はフルスケールに対応します(つまり、スケールに関する丸いPSPスティックは正方形です)、そしてXboxでは、そうであるように、スケールの半分:


したがって、線形に増加する(Xboxコントローラーの中心軸からの偏差が大きくなるほど)係数は最大1.4で導入されました(ただし、後で判明したように、角度値を決定し、角度が対角線に近づくほど係数は大きくなります)。 これらの値を使用すると、Xboxゲームパッドは違和感なく感じることができますが、純粋に技術的には感度は失礼でした。 Doom 0.05では制御が便利で、ダンジョンシージでは(スティックのたわみ力に応じて)3つの移動速度すべてが機能し、PSP自体のように感じます。 問題に直面したとき、最初に単純な係数(1.5と1.4の両方)がテストされ、偏差に応じて線形に増加せず、指定されたゲームに急激な不快感がありました-プレイすることは不可能でした。

Fedora Remix 18 for Raspberry Piのスタートアップに独自のサービスを追加する


Linuxで自動ロードするプログラムの追加に関する表面的なグーグルでは、主にinit rcスクリプトの変更に関する推奨事項が提供されています。 しかし、この場合、別の方法で行う必要があります。

1.まず、サービスxbox2psp.oを/ usr / local / binにコピーし、その起動権限を設定する必要があります(3ビットすべて)。

2.次に、次の内容で/lib/systemd/system/xbox2psp.serviceファイルを作成します。
 [Unit] Description=xbox2psp After=syslog.target network.target [Service] Type=simple ExecStart=/usr/local/bin/xbox2psp.o [Install] WantedBy=multi-user.target 

3. / etc / systemd / system /フォルダーに移動し、コマンドでリンクを作成します
 > ln -s /lib/systemd/system/xbox2psp.service xbox2psp.service 

4.起動デーモン構成を再ロードします。
 > systemctl daemon-reload 

5.新しいサービスの自動起動を有効にする
 > systemctl enable xbox2psp.service 

6.必要に応じて、コマンドを使用してすぐにサービスを開始できます
 > systemctl start xbox2psp.service 

その結果、Xbox 360コントローラーを使用してPSPを制御する便利な機会が得られ、必要に応じて、このプロジェクトを変更して、たとえばBluetooth経由でDualshock 3に接続できます。

Source: https://habr.com/ru/post/J213513/


All Articles