Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Магия с сенсором освещения

Помимо сенсора внезапного движения многие новые «Макбуки» оборудованы сенсором освещённости, он едва заметно располагается рядом с видеокамерой.

Мне интересно стало попробовать поработать и с ним тоже. На этот раз цель была — научиться эффектным жестом блокировать свой ноут. Конечно, ложных срабатываний масса — всё-таки этот сенсор крайне примитивен, он видит только освещённость, но в качестве фокуса годится:

Фокус

По-моему, довольно эффектно смотрится — блокировка экрана магическим пассом.

Код на Си получился чуть более ста строк и на этот раз я распознаю шаблон поведения, он задаётся в подстановке «+vvv000-^^^», что означает «сначала идёт положительное отклонение от среднего, потом в последовательности не менее трёх, отклонение должно уменьшаться, потом не менее трёх раз должно встретиться нулевое отклонение от среднего, потом отрицательное отклонение, потом отклонение должно увеличиваться».

При сканировании чисел с датчика я всегда рассматриваю десять значений от которых считаю среднее и каждое значение рассматриваю как отклонение от этого среднего, собственно, вот код:

// Магические пассы, улавливаемые через датчик освещённости
// Written by Evgeny Stepanischev Jun 2013
// gcc lightlock.c -framework IOKit -framework ApplicationServices -O3 -o lightlock

#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
#include <unistd.h>

#define WINDOWSIZE 10
#define GESTURES "+vvv000-^^^"
#define UPDATEINTERVAL .01

const kGetSensorReadingID = 0;
int makeReaction(uint64_t current);
static io_connect_t port = 0;

void macLock () {
    CGSCreateLoginSession(NULL);
}

void updateTimerCallBack(CFRunLoopTimerRef timer, void *info) {
    IOItemCount osize = 2;
    uint64_t values[osize];

    kern_return_t ret = IOConnectCallMethod(port, kGetSensorReadingID,
    NULL, 0, NULL, 0, values, &osize, NULL, 0);

    if (ret == KERN_SUCCESS) {
        if (makeReaction(values[0])) {
            macLock();
        }
    }
    else if (ret != kIOReturnBusy) {
      mach_error("IOConnectCallMethod: ", ret);
      exit(-3);
    }   
}

int main() {
    io_service_t service = IOServiceGetMatchingService(
        kIOMasterPortDefault, IOServiceMatching("AppleLMUController")
    );

    if (service == kIOReturnNoDevice) {
        fprintf(stderr, "Cannot find Ambient Light Sensor\n");
        exit(-1);
    }

    kern_return_t ret = IOServiceOpen(service, mach_task_self(), 0, &port);
    IOObjectRelease(service);

    if (ret != KERN_SUCCESS) {
        mach_error("IOServiceOpen:", ret);
        exit(-2);
    }

    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(
        kCFAllocatorDefault,
        CFAbsoluteTimeGetCurrent() + UPDATEINTERVAL,
        UPDATEINTERVAL, 0, 0, updateTimerCallBack, NULL
    );
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
    CFRunLoopRun();

    exit(0);
}

int checkMask(char mask, const int64_t diff, const int64_t prevdiff) {
    switch (mask) {
        case '+': return diff > 0;
        case '-': return diff < 0;
        case '0': return diff == 0;
        case 'v': return prevdiff > diff;
        case '^': return diff > prevdiff;
    }

    return 0;
}

int makeReaction(uint64_t current) {
    static uint64_t window[WINDOWSIZE];
    static uint64_t prevdiff = 0;
    static gesturepos = 0;

    uint64_t average = current;
    int i, notzerocnt = 1;

    for (i = WINDOWSIZE-1; i > 0; i--) {
        if (window[i] = window[i-1]) {
            average += window[i];
            notzerocnt++;
        }
    }

    window[0] = current;
    average /= notzerocnt;

    int64_t diff = average - current;
    if (checkMask(GESTURES[gesturepos], diff, prevdiff)) {
        gesturepos++;
    } else {
        if (!gesturepos || !checkMask(GESTURES[gesturepos-1], diff, prevdiff)) {
            gesturepos = 0;
        }
    }

    prevdiff = diff;
    return gesturepos == sizeof GESTURES - 1;
}

В функции main, в конце стоит магический код, который нужен для того, чтобы вызываемые мною внутренние шестерёнки «Мака» благополучно крутились, иначе функция CGSCreateLoginSession будет ждать когда ей перепадёт процессорного времени.

Ну и традиционно, если кто-то что-то понимает в Си, поругайте код. Заранее спасибо!

Ctrl →Malbolge
6 комментариев
Анонимус 2013

Лучше бы автоматическую подстройку яркости сделал. Или она там и так есть?

bagir 2013

Используй вебкамеру и opencv. Тогда будет выше точность. Можно,кстати, совместить датчик и камеру.
Другое дело, что я не знаю насколько это оправданно и не накладно по ресурсам держать камеру постоянно включенной.

Еще читал не давно, что заморачиваются с уровнями сигналов стандартов 802.11 и судя по статье точность определения жестов достаточно высокая.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для bagir:

Используй вебкамеру и opencv. Тогда будет выше точность. Можно,кстати, совместить датчик и камеру.
Другое дело, что я не знаю насколько это оправданно и не накладно по ресурсам держать камеру постоянно включенной.

Ирек, у меня нет нет цели решить какую-то утилитарную задачу, мне этого на работе хватает. Мне интересно сделать что-то необычное.

Еще читал недавно, что заморачиваются с уровнями сигналов стандартов 802.11 и судя по статье точность определения жестов достаточно высокая.

Насколько я помню ту статью, там можно только определить где стоят люди, это всё.

bagir 2013

Определять где и сколько людей стоят уже давно можно. Думаю даже в домашних условиях можно в короткие сроки разработать нечто подобное.

А вот с жестами куда интереснее. Как работает — не знаю.

http://habrahabr.ru/post/182212/

Евгений Степанищев (bolknote.ru) 2013

Комментарий для bagir:

Не факт, что мой ноут имеет всё необходимое, чтобы это анализировать.