一つ前の投稿の前半を繰り返しておく:
「コンパス」という機能名で欲しいのは文字通りコンパスであろう。つまり東西南北を知りたいわけだ。けど plyer.compass 単体では「何も出来ない」。
要は azimuth, roll, pitch を得たいわけなんだけれども、android API を使ってこれを得る方法は例えばこことかこことかこことか、まぁ色々みつかる。一応公式の説明はこれ。(stackexchange でのやりとりも参考になりそう。ちゃんと読めてないけど。)
これらサイトをみるに、方法は2つ:
- (非推奨)Sensor.TYPE_ORIENTATION を使う
- Sensor.TYPE_MAGNETIC_FIELD と Sensor.TYPE_ACCELEROMETER を組み合わせて計算する
そうなんだけれども、plyer.compass の実装はこのどちらでもない:
前回の Sensor.TYPE_ORIENTATION でも動くし、ワタシのデバイスでは特に問題はなさそうにもみえるんだけれども、確かにドキュメントではっきり非推奨と書かれているし、公式の説明があるので、欲しいものを「javaで」書くのは簡単なわけだが、これを Pyjnius を通して書きたい、ってわけだ。
デバッグ環境を整えてないので無駄に苦労したのはナイショである。なんであれ、出来上がってしまえばまったくもって難しくはなくて、これだけ:
1 '''
2 Android Compass
3 ---------------------
4 '''
5
6 import math
7 from copy import deepcopy
8 from plyer.facades import Compass
9 from jnius import PythonJavaClass, java_method, autoclass, cast
10 from plyer.platforms.android import activity
11
12 Context = autoclass('android.content.Context')
13 Sensor = autoclass('android.hardware.Sensor')
14 SensorManager = autoclass('android.hardware.SensorManager')
15
16
17 class _SensorListener(PythonJavaClass):
18 __javainterfaces__ = ['android/hardware/SensorEventListener']
19
20 def __init__(self, sensor_manager, sensor_type):
21 super(_SensorListener, self).__init__()
22 self.SensorManager = sensor_manager
23 self.sensor = self.SensorManager.getDefaultSensor(sensor_type)
24
25 self.values = 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 = deepcopy(event.values)
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.SensorManager = cast('android.hardware.SensorManager',
48 activity.getSystemService(Context.SENSOR_SERVICE))
49 self.bState = False
50
51 def _enable(self):
52 if not self.bState:
53 self.listener_a = _SensorListener(
54 self.SensorManager,
55 Sensor.TYPE_ACCELEROMETER)
56 self.listener_a.enable()
57 #
58 self.listener_m = _SensorListener(
59 self.SensorManager,
60 Sensor.TYPE_MAGNETIC_FIELD)
61 self.listener_m.enable()
62 #
63 self.bState = True
64
65 def _disable(self):
66 if self.bState:
67 self.bState = False
68 #
69 self.listener_m.disable()
70 del self.listener_m
71 #
72 self.listener_a.disable()
73 del self.listener_a
74
75 def _get_orientation(self):
76 if self.bState and \
77 self.listener_a.values and \
78 self.listener_m.values:
79 R = [0., 0., 0., 0., 0., 0., 0., 0., 0.]
80 values = [0., 0., 0.]
81 self.SensorManager.getRotationMatrix(
82 R, None,
83 self.listener_a.values,
84 self.listener_m.values)
85 self.SensorManager.getOrientation(R, values)
86 # convert to degrees for compatibility with TYPE_ORIENTATION
87 return tuple(math.degrees(v) for v in values)
88 else:
89 return (None, None, None)
90
91 def __del__(self):
92 if self.bState:
93 self._disable()
94 super(self.__class__, self).__del__()
元の実装とはまぁ結構違うことは違うが、まぁわかるでしょ。
さて。この「my_compass.py」。ワタシのサイトでは一度も説明してない「kivy」用である。そして、これもまた説明してない「kivyLauncher」を android 機に入れて使うと、「正規の Google Play アプリのように java (や NDK を使った C/C++) で書いてビルドして apk としてパッケージング」せずとも特定のフォルダに python スクリプトを置くだけで kivy アプリが動かせるので、つまり「今すぐにあなたの android 機で使える」。
kivy やら kivyLauncher やら、あるいは「android における python」全般についてはおいおい整理してお伝えしようかとは思うが、kivyLauncher の利用そのもの自体はとっても簡単である:
- kivyLauncher をあなたの android スマホ(やタブレット)に Google Play からインストール
-
特定の場所(多分
/sdcard/kivy
、おそらく実体として/storage/emulated/0/kivy
とか)に特定の形式のアプリケーションフォルダとして配置- 当たり前だが書くのは kivy アプリケーション
/sdcard/kivy
の直下のフォルダに突っ込む/sdcard/kivy
の直下のフォルダには必ず main.py と android.txt 必要- ↑これを満たせば kivyLauncher がアプリケーションであることを認識する
- main.py が文字通りアプリケーションのメイン
- android.txt の形式は後掲
つーわけで、my_compass.py を使った最もシンプルなコンパス:
1 title=Graphical Compass Demo
2 author=hhsprings
3 orientation=all
1 # -*- coding: utf-8 -*-
2 # コード全体としては
3 # http://stackoverflow.com/questions/18923321/making-a-clock-in-kivy
4 # を参考にした。
5 from kivy.app import App
6 from kivy.uix.widget import Widget
7 from kivy.graphics import Color, Line
8 from kivy.uix.floatlayout import FloatLayout
9 from math import cos, sin, pi, radians
10 from kivy.clock import Clock
11 from kivy.lang import Builder
12
13 import datetime
14
15 #------------------------------------------------------
16 #from plyer import compass # これがダメだったわけですよ
17 from my_compass import AndroidCompass
18 compass = AndroidCompass()
19 #------------------------------------------------------
20
21 # kv は kv ファイルとして外に書くことも出来るがここでは内部で。
22 kv = '''
23 #:import math math
24
25 <MyClockWidget>:
26 face: face
27 ticks: ticks
28 FloatLayout:
29 id: face
30 size_hint: None, None
31 pos_hint: {"center_x": 0.5, "center_y": 0.5}
32 size: 0.9 * min(root.size), 0.9 * min(root.size)
33 canvas:
34 Color:
35 rgb: 0.1, 0.1, 0.1
36 Ellipse:
37 size: self.size
38 pos: self.pos
39 Ticks:
40 id: ticks
41 r: min(root.size) * 0.9 / 2
42 '''
43 Builder.load_string(kv)
44
45 class MyClockWidget(FloatLayout):
46 pass
47
48 class Ticks(Widget):
49 def __init__(self, **kwargs):
50 super(Ticks, self).__init__(**kwargs)
51 compass.enable()
52 Clock.schedule_interval(self.update_clock, 1/5.)
53
54 def update_clock(self, df):
55 magnorth = compass.orientation[0]
56 if magnorth is None:
57 return
58
59 # clockwise from north
60 magnorth = radians(360 - compass.orientation[0])
61
62 self.canvas.clear()
63 with self.canvas:
64 Color(0.7, 0.2, 0.2)
65 Line(points=[
66 self.center_x, self.center_y,
67 self.center_x + 0.8 * self.r * sin(magnorth),
68 self.center_y + 0.8 * self.r * cos(magnorth)
69 ], width=3, cap="round")
70
71 class MyClockApp(App):
72 def build(self):
73 clock = MyClockWidget()
74 return clock
75
76 if __name__ == '__main__':
77 MyClockApp().run()
以上 3つのファイルを例えば /sdcard/kivy/compass_g
(/storage/emulated/0/kivy/compass_g
) に置くと、kivyLauncher に「Graphical Compass Demo」が現れるはず。起動するとこんなシンプルなコンパスが:
シンプル過ぎてキャプチャする価値もない…って?
ちなみに…。作ろうとしてる本当にワタシが欲しいコンパスは「ただのコンパス」ではなくて、日出日没の情報がくっついたもの。というかもう作ってあるんだけどね。そっちの画面はだいたいこんな:
赤線が「Magnetic North」(磁北)、青線が「True North」(真北)、黄色が日の出の方位と時刻、紫色が日没の方位と時刻ね。てなわけでほんとに練習兼ねた「自分さえ良ければいい」ってもんであって、こんなん誰が嬉しいんだ、とも思うけれど、少しは整理出来たらコードはお披露目します、そのうち。「何か作りたい」人は嬉しいでしょ、きっと。