Android Sensor HAL Porting Guide?

0. 개발 환경 및 목표

환경

odroid c2, android 5.1.1, 심박센서

목표

i2c 통신을 하는 심박 센서를 HAL에 추가하여 android sensor service framework에서 호출해 사용할 수 있도록 포팅

코드 포팅을 완료하고 빌드하면 android에서 사용하는 so 라이브러리 파일(shared object)이 생성된다
보통 삼성, LG같은 유명 벤더의 안드로이드 휴대폰 소스코드에는 각각 센서의 소스코드까지 포함되어 있지는 않고 HAL영역은 이런 so파일로 제공된다

물론 거기에 해당 벤더에 맞게 android 소스 전체를 환경설정하는 많은 스크립트 및 설정 파일들이 있겠지만 일단 그런건 무시
그리고 작업을 완료하고나서 가능하면 cts도 돌려볼 생각이다


0. 심박 센서 통신 구조

data
\
processing
\
raw data interrupt
\ /
ioctl() poll()
\ /
/dev/i2c /sys/class/gpio/gpioxxx
\ /
SENSOR

포팅하려는 센서의 linux application과 통신하는 구조이다
별로 좋은 구조는 아니다
driver부터 새로 짜서 다 뜯어고치고 싶은데 우선 포팅이 목표이므로 그냥 진행한다
이제 우리는 linux application이 아닌 Android HAL과 통신하게 된다


1. HAL Interface 문서

https://developer.android.com/guide/topics/sensors/sensors_overview.html#sensors-identify
https://source.android.com/devices/sensors/hal-interface.html
https://source.android.com/compatibility/cts/
모든 것이 깔끔하게 정리되어 나와있다


2. 기존 sensors.cpp 구조 파악

적당히 어떻게 추가할지 흐름을 파악한다
참고 : https://source.android.com/devices/sensors/hal-interface.html

  • get_sensors_list - 새 하드웨어 정의 추가
  • struct sensors_poll_context_t - enum 추가, 새 센서 객체 생성 및 file descriptor, pipe 연결
  • handleToDriver - id에 따른 return 하는 enum값 수정
  • activate - 획득한 handle이 accelerometer인지 심박센서인지 구분하는 코드 추가 및 센서의 enable, wake pipe 구현 -> open 및 init
  • setDelay - handle 구분 코드 추가 및 센서의 setDelay 구현
  • pollEvents - handle 구분 코드 추가 및 센서의 readEvents 구현, pipe 연결 -> ioctl로 i2c read를 넣으면 되겠다


3. 새 센서 객체 파일 추가

hardware/hardkernel/libsensor/HeartRateMonitorSensor.h
hardware/hardkernel/libsensor/HeartRateMonitorSensor.cpp
hardware/hardkernel/libsensor/Android.mk - cpp 파일 추가
만약 객체를 더 생성하거나 그럴 필요가 있으면 더 추가하면 된다


4. get_sensors_list 수정 - sensors.cpp

static struct sensor_t sSensorList[] = {
    {
        .name     = "Odroid-USBIO Accelermeter Sensor", 
        ...
    },
    {
        .name    = "heartrate monitor sensor",
        .vendor = "chocokeki",
        .version = 1,
        .handle = SENSORS_HEARTRATEMONITOR_HANDLE,
        .type = SENSOR_TYPE_HEART_RATE,
        .maxRange = ,
        .resolution = ,
        .power = ,
        .minDelay =  ,
        .fifoReservedEventCount = ,
        .fifoMaxEventCount = ,
        .stringType = SENSOR_STRING_TYPE_HEART_RATE,
        .requiredPermission = SERSOR_PERMISSION_BODY_SENSORS,    //for manifest
        .maxDelay = 0,
        .flags = SENSOR_FLAG_ON_CHANGE_MODE,
    },
}

각 항목에 관해서는 이 링크를 참조할 것. 너무 방대해서 간단한 정리가 불가하다
센서 별 특징이 있으므로 스펙에 맞게 정확히 기록
아래 링크의 sensor_t 를 확인하기 바란다
https://source.android.com/devices/sensors/hal-interface.html


5. open 수정 - sensors.cpp

open이 호출되면 sensors_poll_context_t 객체가 생성된다
sensors_poll_context 객체가 생성될 때 새로운 센서 객체를 같이 생성하도록 코드를 추가하고 poll 환경 설정 정도를 해준다
SensorBase를 통해 상속을 받은 메서드들이 호출되므로 SensorBase의 구현 코드를 참조한다(SensorBase.cpp)
그리고 InputEventCircularReader 객체도 상속을 통해 생성되니 참고하기 바란다(InputEventReader.cpp)


6. 새로운 센서 객체 구현 - HeartRateMonitorSensor.h

A. 자료형

일단 SensorBase로 부터 상속을 받고 sensors.cpp에서 사용하는 method들을 동일하게 선언한다
OdroidSensor.h 참조

#ifndef ANDROID_HRM_SENSOR_H_
#define ANDROID_HRM_SENSOR_H_

#include "SensorBase.h"
#include "InputEventReader.h"
#include "sensor.h"

class HeartRateMonitorSensor : public SensorBase {
public:
    HeartRateMonitorSensor();
    virtual ~HeartRateMonitorSensor();
    virtual int readEvents(sensors_event_t* data, int count);
    ...
};
#endif

펌웨어나 driver만 개발하는 시스템 프로그래밍에 익숙하던 분들이 갑자기 c++ 상속 개념 나오고 virtual 날아다니고 하면 멘붕이 올 수 있으나 금방 적응할 수 있다
class 상속 관련 추천 링크 : http://blog.eairship.kr/175


B. 역할 분담 계획

HeartRateMonitorSensor()
변수 초기화, 장치 open, 연관된 객체들 초기화

~HeartRateMonitorSensor()
enable을 0으로 초기화 - 센서 비활성화

hasPendingEvents()
처리 못하고 pending된 이벤트 있니?

setDelay(int32_t handle, int64_t ns)
보통 sysfs를 통해 sampling frequency를 변경하는데 1.0이하의 HAL에서만 유효 -> deprecated, batch로 변경되었다
실제 구현은 하지 않기로 한다

enable(int32_t handle, int enable)
sysfs 통해 센서 활/비활성화, interrupt 활/비활성화

readEvents(sensors_event_t* data, int count)
생성자를 통해 open한 장치로부터 InputEventCircularReader 를 통해 event data를 읽어 온다
보편적인 linux의 input_event 를 사용하지 않고 /dev/i2c와 통신을 하는 방식으로 구현하기로 했으므로 InputEventCircularReader는 사용하지 않는다

C. Method 구현

HeartRateMonitorSensor()

SensorBase를 통해 상속 받은 객체들을 초기화 한다
readEvents에서 InputEventCircularReader를 사용하기 때문에 객체 초기화를 해준다 i2c dev를 읽어올 수 있도록 장치 open
pending event를 관리할 sensors_event_t 역시 초기화를 해준다
interrupt 설정 및 활성화
마지막으로 enable을 호출하여 센서를 활성화 한다

InputEventCircularReader의 버퍼 크기는 적당히 잡아준다
말 그대로 링이 구현되어 있는 것인데 struct input_event크기로 데이터를 읽어오니 주의 바란다
그런데 input_event라하면 /sys/class/input 여기인데 무조건 이런 형태로 데이터를 넣고 받아야 하는지 의문이다
레퍼런스 어디서 못 보려나

우리는 용감하게 i2c dev를 읽도록 결정하였으므로 과감하게 무시하자

~HeartRateMonitorSensor()

센서가 활성화 중인지 체크하고 enable을 호출하여 센서를 비활성화 한다
얘가 왜 virtual로 선언되어야 하는지 몰랐는데 상속 받은 아이들의 destructor는 virtual가 붙어야 한다고 하더라

hasPendingEvents()

readEvents 에서 data를 읽었는지 여부를 확인

setDelay(int32_t handle, int64_t ns)

sysfs를 통해 센서에 명령을 전달, sampling frequency를 변경
하지만 구현하지 않는다. 신 버전에서 batch로 변경되었으니까

enable(int32_t handle, int enable)

센서 활성화/비활성화 - 보통 sysfs 를 통해 명령을 내린다

readEvents(sensors_events_t* data, int count)

data에 sensors_event_t 형태의 자료를 붙여서 보내면 된다
count는 몇개 읽을까요?고 return값으로는 몇개 이벤트를 읽었는지 보내면 ok

InputEventCircularReader를 사용하는 루틴은 정형화 되어 있으므로
readEvents에서 fill하거나(read), readEvent를 호출하는 구조를 그대로 사용하면 된다

현재 구현하려는 센서가 심박센서라 input_event 모델이 딱히 필요 없기 때문에(사실 귀찮아서)
대충 간략하게 sensors_event_t를 만들어서 보내는 식으로 개발하려 한다


D. Device Driver 구현

이번에는 /dev/i2c와 통신을 하기 때문에 따로 driver를 구현할 필요는 없으나 일반적인 경우에는 InputEventCircularReader가 struct input_event 형태로 데이터를 읽어가므로 read 요청이 오면 해당 형태로 데이터를 보내주도록 한다
그런데 여기에도 함정카드가 있는데 일반적인 기법에 대해서 말이다
퀄컴 보드의 경우 sensors_classdev 라는 linux kernel device driver 영역에 자료형과 api를 만들어서 제공하며 그 규칙을 따라서 제작해야 한다
참고 : Qualcomm Snapdragon Sensors Porting Guide
https://developer.qualcomm.com/qfile/28820/lm80-p0436-9_sensors_porting_guide.pdf

nexus 5x 기준 android kernel source
drivers/sensors/sensors_class.c
drivers/input/misc/akm8963.c - compass sensor
drivers/misc/apds993x.c - ambient light, proximity sensor 등등 좋은 예시가 많다

이런 규칙을 따라 작성하는게 통일성도 유지되고 좋을 것 같다

불행히 일반적인 심박센서가 어떤 자료형으로 어떻게 통신되는지 레퍼런스를 알 길이 없으므로 일단 input_event 형태로 구현하고
driver가 데이터를 넘겨주면 hal에서는 struct heart_rate_event_t형태로 만들어 줘야할 것 같다

hardware/libhardware/include/hardware/sensors.h

typedef struct sensors_event_t {
...
    heart_rate_event_t heart_rate
}sensors_event_t;

typedef struct {
    float bpm;
    int8_t status;
}heart_rate_event_t;

7. Build

모듈 빌드

source build/envsetup.sh
lunch odroidc2-eng-32
mmm hardware/hardkernel/libsensor

안드로이드 풀 빌드는 너무 오래 걸리기 때문에 일부 모듈만 빌드하는 mmm을 사용하도록 한다
참고 : http://www.kaisyu.com/notes/google-android/android-partial-module-build

결과

root@ubuntu: # mmm hardware/hardkernel/libsensor
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=5.1.1
TARGET_PRODUCT=odroidc2
...
target Strip: sensors.odroidc2 (out/target/product/odroidc2/obj/lib/sensors.odroidc2.so)
Install: out/target/product/odroidc2/system/lib/hw/sensors.odroidc2.so
make: Leaving directory

#### make completed successfully (1 seconds) ####

8. Install

백업

adb pull /system/lib/hw/sensors.odroidc2.so .

작업한 결과물이 이상할 수 있으므로 미리 백업한다

적용**

cp odroid/android/out/target/product/odroidc2/system/lib/hw/sensors.odroidc2.so /mnt/2/lib/hw/sensors.odroidc2.so

2번째 파티션에 파일을 교체한다


9. Reboot & Run

재부팅 하고 어플리케이션을 돌려본다
디버그 메세지들이 잘 뜨는지 확인


까먹고 안 썼는데 application 구현 참고

https://developer.android.com/reference/android/hardware/SensorManager.html

https://android.googlesource.com/platform/cts/+/ee43e0b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/HeartRateMonitorTestActivity.java


Posted by 쵸코케키

Sensor Stack


출처 : https://source.android.com/devices/sensors/index.html
이번에는 HAL에서 framework로 어떻게 올라가는지 확인해보려 한다

framework에서 HAL을 어떻게 호출?

이 파일에는 볼게 많다. 실제 휴대폰에 adb로 들어가서 /system/lib/hw 디렉토리에 뭐가 있나 봐보자
hardware/libhardware/hardware.c

int hw_get_module_by_class(const char *class_id, const char *inst,
                            const struct hw_module_t **module)
{
    ...
    return load(classid_, path, module);
}

int hw_get_module(const char *id, const struct hw_module_t **module)
{
    return hw_get_module_by_class(id, NULL, module);
}

#define HAL_MODULE_INFO_SYM                 HMI
#define HAL_MODULE_INFO_SYM_AS_STR         "HMI"
static int load(const char *id,
        const char *path,
        const struct hw_module_t **pHmi)
{
    ...
    void *handle = dlopen(path, RTLD_NOW);

    /* Get the address of the struct hal_module_info */
    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
    struct hw_module_t *hmi = (struct hw_module_t *)dlsym(handle, sym);
    if(hmi->dso)
        hmi->dso = handle;
    *pHmi = hmi;
    ...
}

네 그렇습니다. load라는 녀석을 통해 심볼 호출이 되어 HMI가 읽힙니다
hw_get_module_by_class 같은 함수는 밑의 SensorDevice.cpp에서 호출이 됩니다


hardware/libhardware/include/hardware/sensors.h

static inline int sensors_open_1(const struct hw_module_t* module,
        sensors_poll_device_1_t** device) {
    return module->methods->open(module,
            SENSORS_HARDWARE_POLL, (struct hw_device_t**)device);
}

static inline int sensors_close_1(sensors_poll_device_1_t* device) {
    return device->common.close(&device->common);
}

아주 오래전에 open method가 호출 된다고 했던거 기억하는지요
이 함수는 아래의 class에서 호출 됩니다
잘 기억이 나지 않는다면 1편을 참조하시기 바랍니다



frameworks/native/services/sensorservice/SensorDevice.cpp

SensorDevice::SensorDevice()
    :    mSensorDevice(0),
        mSensorModule(0)
{
    status_t err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,
            (hw_module_t const**)&mSensorModule);            //HMI에서 정의 했던 SENSORS_HARDWARE_MODULE_ID를 여기서 호출

    err = sensors_open_1(&mSensorModule->common, &mSensorDevice);    //HMI open method call & get mSensorDevice
    if (mSensorDevice) {
        sensor_t const* list;
        ssize_t count = mSensorModule->get_sensors_list(mSensorModule, &list);    //HMI call get_sensors_list
        mActivationCount.setCapacity(count);
        Info model;
        for (size_t i=0 ; i<size_t(count) ; i++) {
            mActivationCount.add(list[i].handle, model);
            mSensorDevice->activate(        //아까 &dev->device.common으로 보냈던거 구조체 형변환 기억하나요?
                    reinterpret_cast<struct sensors_poll_device_t *>(mSensorDevice),
                    list[i].handle, 0); 
        }
}

activate method 처럼 HMI에서 정의한 함수들을 호출한다
activate 호출이 되면 pipe로 wake message가 날아간다 그리고 poll에서 pipe를 확인하고 이벤트를 읽는다

기타 HMI에서 정의 되었던 각종 method들 batch, poll, setDelay 등 모두 SensorDevice.cpp 에서 호출한다
SensorDevice.cpp는 google에서 만드니 우리가 건들 부분은 없고 포팅을 위해서는 Sensors.cpp 이하만 구현 해주면 될 것이다
애초에 디렉토리 이름이 frameworks/native/serivces/services/ 이다

그 이후로 java랑 어떻게 붙냐면 무지막지하게 긴데 그걸 다 묘사할 수도 없고 대충 써보면
SensorService.cpp -> SensorEventQueue.cpp -> android_hardware_SensorManager.cpp -> SystemSensorManager.java


JNI

java와 c++의 교두보 같은 느낌이다
모든 jni 들은 android_hardware_xxxx.cpp 이런식으로 파일명이 붙는다

framework/base/core/jni/android_hardware_SensorManager.cpp

static JNINativeMethod gSystemSensorManagerMethods[] = { 
    {"nativeClassInit",                                //java
            "()V",                                    //argument, return type
            (void*)nativeClassInit },                 //c++

    {"nativeGetNextSensor",
            "(Landroid/hardware/Sensor;I)I",
            (void*)nativeGetNextSensor },
};

static void
nativeClassInit (JNIEnv *_env, jclass _this)
{
    jclass sensorClass = _env->FindClass("android/hardware/Sensor");
    SensorOffsets& sensorOffsets = gSensorOffsets;
    sensorOffsets.name        = _env->GetFieldID(sensorClass, "mName",      "Ljava/lang/String;");
    sensorOffsets.vendor      = _env->GetFieldID(sensorClass, "mVendor",    "Ljava/lang/String;");
    sensorOffsets.version     = _env->GetFieldID(sensorClass, "mVersion",   "I");
    ...
}


JNINativeMethod 형식으로 이렇게 c++의 함수 이름과 java의 framework api 이름을 매핑시켜 놓는다

int register_android_hardware_SensorManager(JNIEnv *env)
{
    jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager",
            gSystemSensorManagerMethods, NELEM(gSystemSensorManagerMethods));        //아까 위에서 매핑했던 이름

    jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$BaseEventQueue",
            gBaseEventQueueMethods, NELEM(gBaseEventQueueMethods));

    FIND_CLASS(gBaseEventQueueClassInfo.clazz, "android/hardware/SystemSensorManager$BaseEventQueue");
    ...
}

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    ALOGV("Registering %s's %d native methods...", className, numMethods);

    scoped_local_ref<jclass> c(env, findClass(env, className));
    if (c.get() == NULL) {
        char* msg;
        asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
        e->FatalError(msg);
    }

    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
        char* msg;
        asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
        e->FatalError(msg);
    }

    return 0;
}

Android RunTime에 서비스 등록

frameworks/base/core/jni/AndroidRuntime.cpp

#define REG_JNI(name)    { name }
struct RegJNIRec {
    int (*mProc)(JNIEnv*);
};

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    ...
    REG_JNI(register_android_hardware_SensorManager),        //아까 java method와 c++ method를 연결했던 그 함수 포인터
    ...
}

extern "C"
jint Java_LoadClass_registerNatives(JNIEnv* env, jclass clazz) {
    return register_jni_procs(gRegJNI, NELEM(gRegJNI), env);
}

static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        if (array[i].mProc(env) < 0) {
    ...
}    //mProc 즉 RegJNIRec에 등록된 function pointer들이 실행 된다


int AndroidRuntime::startReg(JNIEnv* env)
{
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM. (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc)
    if(register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
    ...
}

부팅하면 ART에 의해 startReg가 호출되고 struct gREGJNI에 들어간 각종 native funtion들은 register_jni_procs에 의해 모두 등록 된다


Java Application

public class MainActivity extends Activity implements SensorEventListener{
    private SensorManager mSensorManager;
    private Sensor mSensorAccelerometer;
    private float[] tGravity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);

        List<Sensor> lSensor = mSensorManager.getSensorList(Sensor.TYPE_ALL);

        for(int i=0; i<lSensor.size(); i++)
        {
            Log.d("test@debug", String.format("vendor: %s, name: %s, type: %s",
                                lSensor.get(i).getVendor(), lSensor.get(i).getName(), lSensor.get(i).getStringType()));
        }

        mSensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(this, mSensorAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        switch(event.sensor.getType()) {
            case Sensor.TYPE_ACCELEROMETER:
            default:
                tGravity = event.values.clone();
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}

Sensor.java - getType, getVendor, getName등의 method들이 정의 되어 있다



실행 - nexus 5x

vendor: Bosch, name: BMI160 accelerometer, type: android.sensor.accelerometer
vendor: Bosch, name: BMI160 gyroscope, type: android.sensor.gyroscope
vendor: Bosch, name: BMM150 magnetometer, type: android.sensor.magnetic_field
...
vendor: Google, name: Double Tap, type: com.google.sensor.double_tap
vendor: Google, name: Device Orientation, type: android.sensor.device_orientation


framework/base/core/java/android/hardware/SensorManager.java

    public boolean registerListener(SensorEventListener listener, Sensor sensor, ...) {
        return registerListenerImpl(listener, sensor, ...);
    }

onResume(), onPause()에서 호출 되는 registerListener method



frameworks/base/core/java/android/hardware/SystemSensorManager.java

    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,...) {
        ...
        Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
        queue = new SensorEventQueue(listener, looper, this);
        queue.addSensor(...)
        mSensorListeners.put(listener, queue);
        ...
    }

    private static abstract class BaseEventQueue {
        private static native int nativeEnableSensor(long eventQ, int handle ...)
        private long nSensorEventQueue;

        BaseEventQueue(Looper looper, SystemSensorManager manager) {
            nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mSractch);
            ...
        }
    }

    static final class SensorEventQueue extends BaseEventQueue {
        public SensorEventQueue(SensorEventListener listener, Looper looper,
            SystemSensorManager manager) {
            super(looper, manager);
            mListener = listener;
            }
    }


looper 없으면 생성하고 SensorEvent Queue 만들고 queue에 센서를 추가한다

    public boolean addSensor(
        ...
        addSensorEvent(sensor);
        if(enableSensor(...))
    )
    private int enableSensor(
        Sensor sensor, ...) {
        return nativeEnableSensor(nSensorEventQueue, ...);
    }
static jint nativeEnableSensor(JNIEnv *env, jclass clazz, ...) {
    sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ));
    return receiver->getSensorEventQueue()->enableSensor(handle, ...);
}


다시 JNI로 돌아가서 확인해보면
frameworks/base/core/jni/android_hardware_SensorManager.cpp

static JNINativeMethod gBaseEventQueueMethods[] = { 
    {"nativeInitBaseEventQueue",
            "(Landroid/hardware/SystemSensorManager$BaseEventQueue;Landroid/os/MessageQueue;[F)J",
            (void*)nativeInitSensorEventQueue },

    {"nativeEnableSensor",
            "(JIIII)I",
            (void*)nativeEnableSensor },

    {"nativeDisableSensor",
            "(JI)I",
            (void*)nativeDisableSensor },

    {"nativeDestroySensorEventQueue",
            "(J)V",
            (void*)nativeDestroySensorEventQueue },

    {"nativeFlushSensor",
            "(J)I",
            (void*)nativeFlushSensor },
}

java application test code
cts/apps/CtsVerifier/src/com/android/cts/verifier/sensors/HeartRateMonitorTestActivity.java


정리

  • SensorManager = onCreate(), (SensorManager)getSystemService(SENSOR_SERVICE); : java sensor application
  • SensorManager.getSensorList - getType, getVendor, getName등 method 정의 : Sensor.java
  • registerListener - onResume() onPause() : SensorManager.java
  • registerListenerImpl - Looper per SensorEvnentListener : SystemSensorManager.java
  • static final class SensorEventQueue extends BaseEventQueue : SystemSensorManager.java
    addSensor가 호출, 부모의 method가 호출된다 - private static abstract class BaseEventQueue
    -> enableSensor -> nativeEnableSensor
  • static jint nativeEnableSensor - framework/base/core/jni/android_hardware_SensorManager.cpp
  • SensorEventQueue::enableSensor - SensorEventQueue.cpp
  • enableDiasble - frameworks/native/services/sernsorservice/SensorService.cpp
    SensorService::enable
  • SensorDevice& dev(SensorDevice::getInstance());
    singleton 기법으로 instatnce 생성
  • SensorDevice.cpp

기타

input keyevent 62
sendevent
sendevent /dev/input/event
getevent
hex 값으로 데이터 가져옴


Posted by 쵸코케키

Android Sensor Service Porting

Android의 linux kernel device driver와 Android OS 가 어떻게 만나는지, 어떻게 연결해야 하는지 알아보자


Sensor Stack


출처 : https://source.android.com/devices/sensors/index.html
가운데 HAL 에 포함된 sensor.h와 sensors.cpp가 핵심이다


Data Structure

hardware/libhardware/include/hardware/sensors.h

/**
 * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
 * and the fields of ths data structure must begin with hw_module_t
 * followed by module specific information
 **/
struct sensors_module_t {
    struct hw_module_t common;
    int (*get_sensors_list)(struct sensors_module_t* module, 
            struct sensor_t const** list);
}

주석에 나와있듯 모든 하드웨어 모듈은 반드시 이 구조체 형식으로 작성되어야 한다
얘가 젤로 중요. 이 형식의 HAL_MODULE_INFO_SYM (HMI)를 선언하는 것으로 시작된다


hardware/libhardware/include/hardware/hardware.h

typedef struct hw_module_t {
    uint32_t tag;
    uint16_t module_api_version;
    uint16_t hal_api_version;
    const char *id;
    const char *name;
    const char *author;
    struct hw_module_methods_t* methods;
    void* dso;
    ...
}hw_module_t;

typedef struct hw_module_methods_t {
    int (*open)(const struct hw_module_t* module, const char* id, 
        struct hw_device_t** device);
}hw_module_methods_t;

typedef struct hw_device_t {
    uint32_t tag;
    uint32_t version;
    struct hw_module_t* module;
    ...
    int (*close)(struct hw_device_t* device);
}hw_device_t;

일단 이런 형식이 있다는 것만 알자
바로 아래 코드에서 이 자료형을 사용해 구조체가 구현된다


Sensors.cpp - Java Framework, HAL

C++ 영역에서 HAL을 어떻게 접근할지, JAVA framework와 어떻게 연결될지 가장 중요한 코드라 할 수 있다
예를 들어 light, proximity sensor의 코드를 보자
device/lge/hammerhead/libsensors/sensors.cpp

static struct sensor_t sSensorList[GLOBAL_SENSORS + LOCAL_SENSORS] = { 
    {   
        .name       = "Light Sensor",
        .vendor     = "Avago Technologies",
        .version    = 1,
        .handle     = SENSORS_LIGHT_HANDLE,
        .type       = SENSOR_TYPE_LIGHT,
        ...
    },  
    {   
        .name       = "Proximity Sensor",
        .vendor     = "Avago Technologies",
        .version    = 1,
        .handle     = SENSORS_PROXIMITY_HANDLE,
        .type       = SENSOR_TYPE_PROXIMITY,
        ...
    },  
};

struct sensors_module_t HAL_MODULE_INFO_SYM = {     //HMI 기억 납니까
    common: {
        tag: HARDWARE_MODULE_TAG,
        version_major: 1,
        version_minor: 0,
        id: SENSORS_HARDWARE_MODULE_ID,
        name: "LGE Sensor module",
        author: "LG Electronics Inc.",
        methods: &sensors_module_methods,    //얘 까먹지 말고 있어봐
        dso: NULL,
        reserved: {0} 
    },  
    get_sensors_list: sensors__get_sensors_list,
};

이 파일은 각기 다양한 센서의 hub 정도 된다
보통 각 vendor는 자사의 다양한 칩을 이런식으로 sensors.cpp 에 적당히 선언해놓고 객체를 생성해서 관리하는 식으로 만든다
각 센서의 세부적인 객체 모양이나 자료형이 다르기 때문에 따로 파일을 분류해서 구현하고 그 들을 sensors.cpp에서 통합해 관리하는 식이다
sensors.cpp는 윗쪽 형님들에게 불려 java framework까지 올라가게 되는데 그 부분은 일단 나중에 보고 먼저 밑으로 내려가자


HAL에서 device driver와 어떻게 통신?

위에 선언 된 HAL_MODULE_INFO_SYM 으로 sensors_motule_methods의 open method 가 불리면
sensors_poll_context_t object가 생성되고 얘는 open method의 마지막 인자에 실려 나간다

static struct hw_module_methods_t sensors_module_methods = {    //제가 아까 걥니다
    open : open_sensors
};

static int open_sensors(const struct hw_module_t* module, const char* id
, struct hw_device_t** device)
{
    sensors_poll_context_t *dev = new sensors_poll_context_t();    //각각의 센서들 객체 생성
    dev->device.common.module    = const_cast<hw_module_t*>(module);
    dev->device.common.close     = poll_close;
    ...
    dev->device.poll             = poll__poll;

    *device = &dev->device.common;        //sensors_open_1 에게 호출 됩니다. 매우 중요
}

sensors_poll_context_t::sensors_poll_context_t() {
    ...
    mSensor[light] = new LightSensor();        //이런식으로 각 센서별 객체를 생성한다
    mPollFds[light].fd = mSensor[light]->getFd();    //그리고 이렇게 poll 관련 설정을 한다
    mPollFds[light].events = POLLIN;
    mPollFds[light].revents = 0;

    mSensor[proximity] = new ProximitySensor();
    ...    
}

이 코드의 흐름이 일반적인 기법으로 보인다
open_sensors (복수형이다!)를 호출할 때 센서 별 각 object들을 쫙 만들고
각각 이벤트 감지용 poll 설정을 하고
pipe로 활성화 메세지가 왔을 때 poll 이벤트를 감지해서 읽는다

*device = &dev->device.common;

이 코드가 중요한데 open의 인자 struct hw_device_t** device로 사용할 수도 있고
sensors_poll_device_1_t 형식으로 사용할 수도 있다. 구조체의 가장 최상단 멤버 주소를 활용한 트릭
&dev->device.common, &dev->device 이 두 주소는 같다
이 부분에 관해서는 너무 길어지니 이 링크를 참조하자

참고 #1 : https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
참고 #2 :


각 센서별 object는 이런식이다
device/lge/hammerhead/libsensors/LightSensor.cpp

#define I2C "/sys/bus/i2c/devices/3-0039"
int LightSensor::enable(int32_t handle, int en)
{
    ...
    strcpy(sysfs, I2C);
    strcat(sysfs, "enable_als_sensor");

    int open(sysfs, O_RDWR);
    ...
    write(fd, ...);
}

sysfs 열어서 write하는 것은 Proximity Sensor 같은 다른 코드 들도 동일
device driver 레벨에서는 하드웨어와 통신하며 sysfs 인터페이스를 제공한다
혹은 이런 예제도 있다 google pixel이 포함된 최신 android source 기준
device/htc/flounder/sensor_hub/linsensors/sensors.cpp
device/htc/flounder/sensor_hub/linsensors/CwMcuSensor.cpp
혹은
device/google/dragon/sensor_hub/sensors.cpp
device/google/dragon/sensor_hub/cros_ec_sensors.cpp


poll은 어떻게 이벤트 읽어오나요

아까 open_sensors에 등록 되었던 poll__poll은 pollEvents를 호출한다

int sensors_poll_context_t::pollEvents(sensors_event_t *data, int count)
{
    do {
        for(등록된 센서수) {
            if(mPollFds[i].revents & (POLLIN | POLLPRI)) {    //남아있는 poll 이벤트가 있는지 확인
                ...
                nb = sensor->readEvents(data, count);        //있다면 이벤트를 읽어온다
                if(nb < count) {
                    // no more data for this sensor
                    mPollFds[i].revents = 0;
                }
                count -= nb;
                nbEvents += nb;
                data += nb;
            }

           if (count) {        //요청한 수량 만큼 아직 이벤트를 못 읽어왔다면 다시 poll 체크
                do {                                                    //-1 -> no timeout, 0 -> return immediately
                    n = poll(mPollFds, numFds, nbEvents ? 0 : -1);        //그동안 받은 데이터가 없다면 무한 기다림
                } while (n < 0 && errno == EINTR);
                if (n < 0) {
                    ALOGE("poll() failed (%s)", strerror(errno));
                    return -errno;
                }   
                if (mPollFds[wake].revents & (POLLIN | POLLPRI)) {        //activate method를 통해 pipe로 wake up! 메시지
                    char msg;
                    int result = read(mPollFds[wake].fd, &msg, 1); 
                    ALOGE_IF(result < 0, "error reading from wake pipe (%s)", strerror(errno));
                    ALOGE_IF(msg != WAKE_MESSAGE, "unknown message on wake queue (0x%02x)", int(msg));
                    mPollFds[wake].revents = 0;
                }이거 왜 있는 코드인지 모르겠다
            }  
        }
    }while(n && count);

    return nbEvents;
}

poll이 데이터가 들어오는지 보고 있다가 들어오면 readEvents를 호출하는데 얘는 각 센서의 구현 파일에 정의되어 있다
참고 : http://www.joinc.co.kr/w/Site/Network_Programing/Documents/Poll

예를 들면 device/lge/hammerhead/libsensors/LightSensor.cpp

int LightSensor::readEvents(sensors_event_t* data, int count)
{
    ...
    ssize_t n = mInputReader.fill(data_fd);                //큐에 데이터 넣어달라고 요청
    input_event const* event;

    while(count && mInputReader.readEvent(&event)) {    //data가 담긴 포인터를 받아온다
        event를 가지고 처리
    }
}

놀랍게도 또! 객체를 호출해서 데이터가 들어왔나 확인을 요청하고 읽어온다
LightSensor중에도 invensense의 여러 모델이 있어서 이렇게 객체를 다시 생성해서 나누어놓았다

hardware/invensense/60xx/libsensors_iio/InputEventReader.cpp

ssize_t InputEventCircularReader::fill(int fd)
{
    ...
    const ssize_t nread = read(fd, mHead, mFreeSpace * sizeof(input_event));
    ...
    numEvnetsRead = nread / sizeof(input_event);
    ...
    return numEventsRead;
}

이 내부의 구현은 보통 ring buffer를 통해 데이터를 읽는 식으로 구현되어 있다
상세하게 들어가지는 않으려 한다
대충 /dev/iio:device 같은 장치를 open 해서 ring buffer에 값을 쌓아주는 식으로 비슷하게 구현되어 있다

sysfs 쪽은 센서의 하드웨어적인 제어만 하고 dev 쪽은 데이터 r/w에 관여하는 것 같다

참고 코드
device/htc/flounder/sensor_hub/linsensor/InputEventReader.cpp
device/htc/flounder/sensor_hub/libsensors/CwMcuSensor.cpp
open 후 read를 호출하면 device driver에서 특정 자료형 형태로 데이터를 보낸다


정리

만약 lge나 samsung 같은 대형 벤더에 속해 센서 서비스를 안드로이드에 제공할 수 있도록 포팅해야 한다면 sensors.cpp 만 개발한다고 되는 것이 아니라 device/vendor name/device name/ 에 있는 Android.mk 같은 수많은 mk파일들과 쉘스크립트 등등 파일들을 같이 세팅해야 할 것이고(물론 거기서 일하는 분들이 여기서 이걸 보고 있지는 않겠다만)
그게 아니라 일부 센서만 포팅해야 하는 경우라면 이미 구현되어 있는 복잡한 sensors.cpp 및 기타 환경 설정을 그대로 사용하고 거기에 추가로 자신이 포팅하고자 하는 센서 코드만 개발해서 추가하면 될 것 같다

예를 들면 device/htc/flounder/sensor_hub/libsensors/ 디렉토리를 보면
Android.mk 파일이 있는데 우리가 그간 봐왔던 반가운 makefile 스타일의 환경 설정이 있다

...
LOCAL_MODULE := sensors.$(TARGET_BOOTLOADER_BOARD_NAME)
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_OWNER := htc 
LOCAL_SRC_FILES :=                  \   
                   sensors.cpp      \   
                   SensorBase.cpp   \   
                   CwMcuSensor.cpp  \
                   InputEventReader.cpp
LOCAL_SHARED_LIBRARIES := liblog libcutils libdl
...

이런식으로 여기에 적당한 센서 객체를 구현해서 집어 넣으면 될 것 같다
예를 들면 DetectHRM.cpp 심박 감지 센서 객체를 구현하고
sensors.cpp에서 이 객체를 생성하는 스타일로 만들면 될 것 이다(아마도)

이제 다음페이지에서 sensors.cpp에서 윗쪽으로 어떻게 연결되는지 살펴보자!!


기타 잡코드 예제

hardware/qcom/display/msm8226/liblight/lights.c
device driver가 sysfs의 class에 각종 인터페이스를 등록, 접근 제어할 수 있도록 함
android framework에서는 sysfs를 open 해서 write하는 식으로 제어

char const*const RED_LED_FILE = "/sys/class/leds/red/brightness";
...

다양한 command는 hw_module_methods_t의 id 인자를 통해 구분


Posted by 쵸코케키

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#type_signatures


Type Signatures

The JNI uses the Java VM’s representation of type signatures. The following table shows these type signatures.

Java VM Type Signatures
Type SignatureJava Type
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
L fully-qualified-class ;fully-qualified-class
[ typetype[]
( arg-types ) ret-typemethod type




frameworks/base/core/jni/* 참조

Posted by 쵸코케키
java class definition

package com.forge.blacksmith;

public class ironworks {     enum qctest { FAIL, PASS };     class weapon {     class knife {     public int price;      public byte serialnumber;      }     class sword {     private qctest result;      public void setQcPass() { result = qctest.PASS; }      public void setQcFail() { result = qctest.FAIL; }     public qctest getQcResult() { return result; }      }      public knife[] poisoned;      public sword[] twohanded;     }

}



jni code

jint Java_com_forge_blacksmith_ironworks_WeaponTest( JNIEnv* env, jobject thiz, jobject weapon ) { jclass jclsWeapon = (*env)->GetObjectClass(env, weapon); if(!jclsWeapon || (*env)->ExceptionCheck(env)) {

__android_log_print(ANDROID_LOG_INFO, "jni@debug", "%s, %d", __func__, __LINE__); return JNI_ERR; } jfieldID jfidPoisoned = (*env)->GetFieldID(env, jclsWeapon, "poisoned", 

"[com/forge/blacksmith/ironworks$weapon"); if(!jfidPoisoned) { __android_log_print(ANDROID_LOG_INFO, "jni@debug", "check your Full Qualified Class Name"); return -EFAULT; }

// 이후부터 null 검사 생략

jobjectArray jobjarrPoisoned = (*env)->GetObjectField(env, weapon, jfidPoisoned); //class weapon jobject jobjPoisoned = (*env)->GetObjectArrayElement(env, jobjarrPoisoned, 0); //knife[0] poisoned jclass jclsPoisoned = (*env)->GetObjectClass(env, jobjPoisoned); // jfieldID jfidPrice = (*env)->GetFieldID(env, jclsPoisoned, "price", "I"); (*env)->SetIntField(env, jobjPoisoned, jfidPrice, 800); jfieldID jfidTwohanded = (*env)->GetFieldID(env, jclsWeapon, "twohanded", 

"[Lcom/forge/blacksmith/ironworks$weapon;");

/* 

버그인지 안드로이드 에뮬레이터에서는 FQCN을 아래로 적어도 정상 동작했다

"[com/forge/blacksmith/ironworks$weapon");

*/ jobjectArray jobjarrTwohanded = (*env)->GetObjectField(env, weapon, jfidTwohanded); jobject jobjTwohanded = (*env)->GetObjectArrayElement(env, jobjarrTwohanded, 0); jclass jclsTwohanded = (*env)->GetObjectClass(env, jobjTwohanded); jmethodID jmthdTwohanded = (*env)->GetMethodID(env, jclsTwohanded, "setQcPass", "()V"); (*env)->CallVoidMethod(env, jobjTwohanded, jmthdTwohanded); }


java code
package com.forge.blacksmith;
        
weapon box = new weapon();
box.poisoned = new weapon.knife[2];
box.twohanded = new weapon.sword[3];

box.poisoned[0].price = 500;
box.twohanded[2].setQcFail();

이후 jni 함수 호출, 인자는 box 객체로 넘기면 끝



jni를 본의 아니게 하는 중

android studio 에서 이것저것 해보는데 재미있는거 같다


member 변수의 private, public는 적당히 바꿔서 사용하면 될듯(c 입장에서는 상관없다)

java의 enum은 c의 enum과 너무 다르기 때문에 Set자료형Field 함수를 사용해서 기록하는걸 안하는게 낫다

왜냐면 java는 enum이 진짜 enum이라서 아래와 같은 code가 작동하지 않는다

그래서 method call하는게 정답


enum test { A, B };

if( test.A == 0 }  -> 불가능


단순 객체 가져오는 code는 많은데 java 중첩 클래스를 c로 가져오는 것에 대해 설명이 없어서 끄적여봄

맞게 한건지는 모르겠다 -.-;;


기타 ibm developerworks의 jni 문서링크

꽤 이런저런 자세한 내용이 많아 읽어봄직하다

http://www.ibm.com/developerworks/library/j-jni/


Posted by 쵸코케키

android studio 1.5 를 사용하다보니 ndk쪽은 아직 experimental이라 그런지 마이너버전 업데이트마다 홱홱 바뀐다

구글에서 검색했던 정보들도 다 무용지물인 경우가 많았다

external tools로 javah하는 것도 잘안되는거 같고(command를 직접 치면 되긴 하더만)


여튼간에 1.4까지만 해도 ldLibs += ["log"] 이런식으로 썼던 것 같은데 1.5부터는 


build.gradle file에서

android.ndk {

ldLibs.add("log")

}


이렇게 바뀌었단다


jre버전 바꾸거나 android studio를 껐다켜거나 파일을 다시 다운 받을 필요가 없다



출처 

http://tools.android.com/tech-docs/new-build-system/gradle-experimental


Posted by 쵸코케키
이전버튼 1 이전버튼

블로그 이미지
chocokeki
쵸코케키

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함