plyer.compass が使い物にならない件

ちょっとイケてない。

kivy がなんなのかとか plyer がなんなのかについては、おいおいでまとめたりするかもしんない。今回は唐突に plyer は「あなたは知ってる」ていで書いとく。

タイトルの通りで、plyer.compass がちょっとマズい。

「コンパス」という機能名で欲しいのは文字通りコンパスであろう。つまり東西南北を知りたいわけだ。けど plyer.compass 単体では「何も出来ない」。

要は azimuth, roll, pitch を得たいわけなんだけれども、android API を使ってこれを得る方法は例えばこことかこことかこことか、まぁ色々みつかる。一応公式の説明はこれ。(stackexchange でのやりとりも参考になりそう。ちゃんと読めてないけど。)

これらサイトをみるに、方法は2つ:

  • (非推奨)Sensor.TYPE_ORIENTATION を使う
  • Sensor.TYPE_MAGNETIC_FIELD と Sensor.TYPE_ACCELEROMETER を組み合わせて計算する

そうなんだけれども、plyer.compass の実装はこのどちらでもない:

plyer/plyer/platforms/android/compass.py
 1 '''
 2 Android Compass
 3 ---------------------
 4 '''
 5 
 6 from plyer.facades import Compass
 7 from jnius import PythonJavaClass, java_method, autoclass, cast
 8 from plyer.platforms.android import activity
 9 
10 Context = autoclass('android.content.Context')
11 Sensor = autoclass('android.hardware.Sensor')
12 SensorManager = autoclass('android.hardware.SensorManager')
13 
14 
15 class MagneticFieldSensorListener(PythonJavaClass):
16     __javainterfaces__ = ['android/hardware/SensorEventListener']
17 
18     def __init__(self):
19         super(MagneticFieldSensorListener, self).__init__()
20         self.SensorManager = cast('android.hardware.SensorManager',
21                     activity.getSystemService(Context.SENSOR_SERVICE))
22         self.sensor = self.SensorManager.getDefaultSensor(
23                 Sensor.TYPE_MAGNETIC_FIELD)
24 
25         self.values = [None, None, None]
26 
27     def enable(self):
28         self.SensorManager.registerListener(self, self.sensor,
29                     SensorManager.SENSOR_DELAY_NORMAL)
30 
31     def disable(self):
32         self.SensorManager.unregisterListener(self, self.sensor)
33 
34     @java_method('(Landroid/hardware/SensorEvent;)V')
35     def onSensorChanged(self, event):
36         self.values = event.values[:3]
37 
38     @java_method('(Landroid/hardware/Sensor;I)V')
39     def onAccuracyChanged(self, sensor, accuracy):
40         # Maybe, do something in future?
41         pass
42 
43 
44 class AndroidCompass(Compass):
45     def __init__(self):
46         super(AndroidCompass, self).__init__()
47         self.bState = False
48 
49     def _enable(self):
50         if (not self.bState):
51             self.listener = MagneticFieldSensorListener()
52             self.listener.enable()
53             self.bState = True
54 
55     def _disable(self):
56         if (self.bState):
57             self.bState = False
58             self.listener.disable()
59             del self.listener
60 
61     def _get_orientation(self):
62         if (self.bState):
63             return tuple(self.listener.values)
64         else:
65             return (None, None, None)
66 
67     def __del__(self):
68         if(self.bState):
69             self._disable()
70         super(self.__class__, self).__del__()
71 
72 
73 def instance():
74     return AndroidCompass()

これ、「3軸の磁力をマイクロテスラで取得」してるだけ。無論これだけが欲しいなんてことはありえない。というか「コンパス」という名前から想像出来る機能ではないわな。よくわかんないんだけど、まさにこの指摘をした人がいるんだけど、なんの議論もコメントもなくそのまんま(何もせずに)クローズされちゃってる。なんか作者に誤解かなにかがあるんじゃないのかなぁ…?

この plyer.compass クラスの何がいけてないかって。結局「全書き換え」しか術がないことね。欲しいものが欲しければ。

「Sensor.TYPE_MAGNETIC_FIELD と Sensor.TYPE_ACCELEROMETER を組み合わせて計算する」の方はともかくとして、まずは TYPE_ORIENTATION を使う方法でやるなら、結局丸々書き換えて:

my_compass.py
 1 '''
 2 Android Compass
 3 ---------------------
 4 '''
 5 
 6 from plyer.facades import Compass
 7 from jnius import PythonJavaClass, java_method, autoclass, cast
 8 from plyer.platforms.android import activity
 9 
10 Context = autoclass('android.content.Context')
11 Sensor = autoclass('android.hardware.Sensor')
12 SensorManager = autoclass('android.hardware.SensorManager')
13 
14 
15 class MagneticFieldSensorListener(PythonJavaClass):
16     __javainterfaces__ = ['android/hardware/SensorEventListener']
17 
18     def __init__(self):
19         super(MagneticFieldSensorListener, self).__init__()
20         self.SensorManager = cast('android.hardware.SensorManager',
21                     activity.getSystemService(Context.SENSOR_SERVICE))
22         #self.sensor = self.SensorManager.getDefaultSensor(
23         #        Sensor.TYPE_MAGNETIC_FIELD)
24         self.sensor = self.SensorManager.getDefaultSensor(
25                 Sensor.TYPE_ORIENTATION)
26 
27         self.values = [None, None, None]
28 
29     def enable(self):
30         self.SensorManager.registerListener(self, self.sensor,
31                     SensorManager.SENSOR_DELAY_NORMAL)
32 
33     def disable(self):
34         self.SensorManager.unregisterListener(self, self.sensor)
35 
36     @java_method('(Landroid/hardware/SensorEvent;)V')
37     def onSensorChanged(self, event):
38         #self.values = event.values[:3]
39         self.values = event.values
40 
41     @java_method('(Landroid/hardware/Sensor;I)V')
42     def onAccuracyChanged(self, sensor, accuracy):
43         # Maybe, do something in future?
44         pass
45 
46 
47 class AndroidCompass(Compass):
48     def __init__(self):
49         super(AndroidCompass, self).__init__()
50         self.bState = False
51 
52     def _enable(self):
53         if (not self.bState):
54             self.listener = MagneticFieldSensorListener()
55             self.listener.enable()
56             self.bState = True
57 
58     def _disable(self):
59         if (self.bState):
60             self.bState = False
61             self.listener.disable()
62             del self.listener
63 
64     def _get_orientation(self):
65         if (self.bState):
66             return tuple(self.listener.values)
67         else:
68             return (None, None, None)
69 
70     def __del__(self):
71         if(self.bState):
72             self._disable()
73         super(self.__class__, self).__del__()

とするしかない、と。

まぁ… plyer.compass の出来はともかくとして、kivy, jnius, plyer 全体としては素晴らしいもんなわけでね。多少間違ってても自力でどうにか出来るのは jnius, plyer のおかげなわけで。