DDA для кошки

    Есть у нас в семье кошка по имени Киса. Молодая, а также трусливая и любопытная одновременно. Единственное, что начисто отбивает у нее всю трусость – это красное лазерное пятнышко от бошевского дальномера. Она готова охотиться за ним безоглядно. Но. У дальномера есть ограничение по времени работы, батареек на него не напасешься, да и влом на длительные игры с кошкой время терять.

    Находил на просторах интернета всякие автоматические кошачьи дразнилки – дорого, да и функционал ограничен. Опять же, нет гарантии, что лучик не попадет на занавески, и они не будут при этом подраны вдрызг.



    Или мы не инженеры-электронщики-ардуинщики?! А самому собрать?

    Сначала взял железо: Arduino Nano, пару сервомашинок простеньких (можно в наборе с Arduino в Мастер-Кит приобрести) и красный полупроводниковый лазер от завалявшейся указки с апертурой пятнышком. ЛУТом в момент сделал платку, чтобы сервы было куда воткнуть, ну и ключ на транзисторе для лазера, чтобы ардуиновский пин не перегружать.

    Принципиальная схема устройства:



    Плата:




    В Arduino Nano втыкается обычный USB-mini из любого блока питания на 5 В. Ну, или в компьютер для заливки скетча.

    Конструкцию хотелось сделать, конечно, как можно проще в изготовлении. Помог 3D-принтер. Вот поистине выручалочка для домашних умельцев! Очень понравилось печатать небольшие детали вместо того, чтобы пилить их напильником. За час MC5 D.R.O.V.A. напечатал четыре детальки для двухкоординатного поворотного устройство. Сам процесс печати настолько завораживает, что час этот пролетел вообще незаметно!





    Собранное поворотное устройство вместе с платой крепим на обрезке крашеной фанеры. Это хозяйство и будем программировать.





    Вот тут самое интересное. Надо заинтересовать кошатину нестандартным движением пятнышка, дабы она не теряла интерес, и обезопасить обои и занавески от когтей.

    Экспериментальным путем после некоторого количества экспериментов были выбраны следующие принципы движения объекта охоты:

    — шевеление лазером происходит не постоянно, а со случайными остановками с выключением пятнышка, при этом животное нервно озирается в его поисках;
    — траектория движения меняется от сеанса к сеансу опять же случайным образом;
    — размах движения тоже меняется в рамках заданной траектории;
    — после перемещения в конец траектории точка замирает, чтобы зверь мог ее затоптать в попытке схватить;
    — пятно не должно попадать на стены и занавески, только на пол!

    Побаловавшись с простейшими движениями типа линия и квадрат от точки к точке попеременными движениями сервомашинок, захотелось реализовать и более сложные траектории. После недолгого гугления остановился на старом добром алгоритме DDA-линии, растеризующим отрезок прямой между двумя точками. То есть, задаем функцию траектории, задаем абсциссу, вычисляем ординату, и перемещаем лазер мелкими последовательными шагами по двум координатам в новую точку. Траектории сделал, какие в голову пришли: веер, синусоида, сектор, квадрат и т.п. Можно и более сложные функции рисовать, если кому охота.

    Под спойлером полный текст работающего на данный момент скетча:

    Скетч
    #include <Servo.h> // библиотека сервомашинок
    #include <Metro.h> // библиотека таймера
    #define laser 7 // лазер включен на этот pin
    #define led 13 // встроенный в Ардуино светодиод

    // экспериментально выбираем безопасную зону перемещения пятна
    int minv = 85; //10; // крайнее нижнее положение пятна
    int maxv = 115; //55; // крайнее верхнее
    int minh = 90; //45; // крайнее левое, смотреть спереди
    int maxh = 145; //120; // крайнее правое, смотреть спереди

    unsigned long DelayBetweenMovements = 1000; // задержка в мс
    unsigned long second = 1000; // одна сек. = 1000 мс

    Servo myservo_ver; // перемещение по вертикали
    Servo myservo_hor; // перемещение по горизонтали

    void setup()
    {
    pinMode(laser, OUTPUT);
    pinMode(led, OUTPUT);
    digitalWrite (led,HIGH);
    ServoOn();
    myservo_hor.write((maxh + minh)/2); // устанавливаем лазер
    myservo_ver.write((maxv + minv)/2); // в среднее положение
    delay(2000); // и пару секунд смотрим

    }

    //-Main----------------------------------------------
    void loop()
    {
    randomSeed(analogRead(0)); // инициализация random случайным значением с порта 0
    int g = random(1,7); // выбираем случайно одно траекторию из семи
    randomSeed(analogRead(0));
    int tg = random(1,3); // выбираем случайно длительность сеанса 30, 60 или 90 сек
    DelayBetweenMovements = second * random(1,5)/2; // выбираем случайно время перемещения между точками траектории
    switch (g){
    case 1: GameRandom(tg*30*second); break;
    case 2: GameFan(tg*30*second); break;
    case 3: GameFan1(tg*30*second); break;
    case 4: GameFan2(tg*30*second); break;
    case 5: GameCorners(tg*30*second); break;
    case 6: GameSinHor(tg*30*second); break;
    case 7: GameSinVer(tg*30*second); break;
    }
    delay(random(10,60)*second);

    }

    //-End Main-----------------------------------------

    // синусоида вертикальная со случайной амплитудой
    void GameSinVer(unsigned long GameTime){
    Metro game_time = Metro(GameTime);
    ServoOn();
    servo_move((maxh+minh)/2,minv,10);
    while (!game_time.check()){
    randomSeed(analogRead(0));
    int i = minv;
    int j = random(1,5);
    for (i; i<maxv+1; i = i+j) {
    servo_move((maxh+minh)/2+(maxh/(j+1))*sin(i),i,10);
    delay(DelayBetweenMovements);
    }
    //digitalWrite (laser,random(2));
    }
    ServoOff();
    }

    // синусоида горизонтальная со случайной амплитудой
    void GameSinHor(unsigned long GameTime){
    Metro game_time = Metro(GameTime);
    ServoOn();
    servo_move((maxv+minv)/2,minh,10);
    while (!game_time.check()){
    randomSeed(analogRead(0));
    int i = minh;
    int j = random(1,5);
    for (i; i<maxh+1; i = i+j) {
    servo_move(i,(maxv+minv)/2+(maxv/(j+1))*sin(i),10);
    delay(DelayBetweenMovements);
    }
    //digitalWrite (laser,random(2));
    }
    ServoOff();
    }

    // квадрат со случайным размером
    void GameCorners(unsigned long GameTime){
    Metro game_time = Metro(GameTime);
    ServoOn();
    servo_move((maxh+minh)/2,minv,10);
    randomSeed(analogRead(0));
    int c = random(1,4);
    while (!game_time.check()){
    int i = random(1,5);
    switch (i){
    case 1:
    servo_move(minh,(minv+1),random(1,4)*10); break;
    case 2:
    servo_move(minh,(maxv+1),random(1,4)*10); break;
    case 3:
    servo_move(maxh,(maxv+1),random(1,4)*10); break;
    case 4:
    servo_move(maxh,(minv+1),random(1,4)*10); break;
    }
    delay(DelayBetweenMovements);
    //digitalWrite (laser,random(2));
    }
    ServoOff();
    }

    // веерные движения со случайным размахом
    void GameFan(unsigned long GameTime){
    Metro game_time = Metro(GameTime);
    ServoOn();
    servo_move((maxh+minh)/2,minv,10);
    while (!game_time.check()){
    randomSeed(analogRead(0));
    servo_move(random(minh,maxh+1),maxv,random(1,4)*10);
    delay(DelayBetweenMovements);
    servo_move((maxh+minh)/2,minv,random(1,4)*10);
    delay(DelayBetweenMovements);
    //digitalWrite (laser,random(2));
    }
    ServoOff();
    }

    // веерные движения
    void GameFan1(unsigned long GameTime){
    Metro game_time = Metro(GameTime);
    ServoOn();
    servo_move((maxh+minh)/2,maxv,10);
    while (!game_time.check()){
    randomSeed(analogRead(0));
    servo_move(random(minh,maxh+1),minv,random(1,4)*10);
    delay(DelayBetweenMovements);
    servo_move((maxh+minh)/2,maxv,random(1,4)*10);
    delay(DelayBetweenMovements);
    //digitalWrite (laser,random(2));
    }
    ServoOff();
    }

    // веерные движения
    void GameFan2(unsigned long GameTime){
    Metro game_time = Metro(GameTime);
    ServoOn();
    servo_move((maxh+minh)/2,(maxv+minv)/2,10);
    while (!game_time.check()){
    randomSeed(analogRead(0));
    servo_move(random(minh,maxh+1),random(minv,maxv+1),random(1,4)*10);
    delay(DelayBetweenMovements);
    servo_move((maxh+minh)/2,(maxv+minv)/2,random(1,4)*10);
    delay(DelayBetweenMovements);
    //digitalWrite (laser,random(2));
    }
    ServoOff();
    }

    // случайные перемещения во всех направлениях
    void GameRandom(unsigned long GameTime){
    Metro game_time = Metro(GameTime);
    ServoOn();
    while (!game_time.check()){
    randomSeed(analogRead(0));
    servo_move(random(minh,maxh+1),random(minv,maxv+1),random(1,4)*10);
    delay(DelayBetweenMovements);
    //digitalWrite (laser,random(2));
    }
    ServoOff();
    }

    void ServoOn(void){ // задействовать сервы и включить лазер
    myservo_ver.attach(9); // серво по вертикали присоединить на цифровой pin 9
    myservo_hor.attach(8); // серво по горизонтали присоединить на цифровой pin 8
    digitalWrite (laser,1); // включаем лазер
    }

    void ServoOff(void){ // отключить сервы и лазер — отдыхаем
    myservo_ver.detach();
    myservo_hor.detach();
    digitalWrite (laser,0);
    }

    // переместить из текущей точки х1, y1 в точку x2, y2 с задержками между шагами delay_ms
    void servo_move(double x2, double y2, int delay_ms)
    {
    double x1 = myservo_hor.read(); // читаем текущее положение серв
    double y1 = myservo_ver.read();

    int iX1 = round(x1); // округляем координаты
    int iY1 = round(y1);
    int iX2 = round(x2);
    int iY2 = round(y2);

    // Длина и высота линии
    int deltaX = abs(iX1 — iX2);
    int deltaY = abs(iY1 — iY2);

    // Считаем минимальное количество итераций, необходимое
    // для отрисовки отрезка. Выбирая максимум из длины и высоты
    // линии, обеспечиваем связность линии
    int length = max(deltaX, deltaY);
    if (length == 0) return;

    // Вычисляем приращения на каждом шаге по осям абсцисс и ординат
    double dX = (x2 — x1) / length;
    double dY = (y2 — y1) / length;

    // Начальные значения
    double x = x1;
    double y = y1;

    // Основной цикл
    length++;
    while (length--)
    {
    x += dX;
    y += dY;
    myservo_hor.write(x);
    myservo_ver.write(y);
    delay(delay_ms);

    }
    }



    Практика.

    Первый лазер вышел из строя через неделю – отломились выводы, хоть и сделаны они из многожильного провода. У второго отформовал выводы спиралью. Помогло. Можно еще клея капнуть из клеевого пистолета на место, откуда выводы выходят из корпуса.

    Не все адаптеры питания подходят. От некоторых Ардуино не заводится или лазер дергается. Видимо, большие пульсации на выходе. Конденсатор лень было впаивать, просто подобрал хороший адаптер, благо их куча валялась.
    Испытано на нескольких кошках. Молодые долго носятся. Постарше – побегают, а потом лежат и смотрят, как пятно шарахается, или на саму машинку пялятся, как она жужжит и шевелится. Лапой машинку не пытались. Но, на всякий случай, придумал кожух из коробки для бумажек.



    При опытах обои, занавески и кошки не пострадали.

    В общем, рекомендую такую штуку всем кошатникам-электронщикам!

    МАСТЕР КИТ

    74,97

    Компания

    Поделиться публикацией
    Комментарии 23
      +5
      Мне думается вместо лазерной указки лучше было бы взять светодиод, либо вообще лампу накаливания умеренной яркости с линзой (можно разобрать и переделать ненужные часы-проектор, к примеру). Луч лазера ведь может попасть животному в глаза, вызвать ожоги сетчатки и при неудачном раскладе через некоторое время кошка вообще может ослепнуть.
        +1
        Вот тоже такая мысль возникла после просмотра видео. Там же видно, что пару раз лучом по глазам мазнули. Да и даже на отраженный луч долго смотреть не рекомендуется без спец. средств.
          0
          Ответ от разраба девайса:
          Интересная мысль насчет светодиода. Только к нему оптическую систему надо прикручивать. Пятно должно получаться маленьким в диапазоне расстояний.
          На большое пятно, непонятно по какой причине, котэ не охотится…
            0
            В самом лучшем случае оптическая система светодиода позволит вам получить изображение собственно кристала светодиода — квадратик с тёмным пятнышком контактной площадки, довольно не маленького размера. Школьная линейная оптика подсказывает, что квадратик будет на столько же крупнее реального кисталла, на сколько пол дальше от оптической системы чем диод. Чёта я сильно сомневаюсь, что кошке такой квадратик будет как-то интересен кошке. даже если ставить 5-градусный диод и относить линзу от него на 5-10 см.
            +1
            Слабый лазер в полмилливатта довольно безопасен. Кот/человек зажмурится задолго до того, как луч нагреет сетчатку. Сфокусированный в точку светодиод не обязательно будет безопаснее.
              0
              Емнип, в обычные указки ставят 1-3 мВт. В бытовые дальномеры аналогично.
              А это уже не очень гут.
              0
              Проще фокусировку лазера расстроить.
              +3
              Игра с кошкой должна доставлять удовольствие как кошке, так и играющему, а эта железная конструкция напрочь лишает удовольствия играющего.

              Ну и кстати, с мелкими котятами так играть может оказаться очень печально — они не умеют остановиться — инстинкт сильнее — и могут элементарно сдохнуть от перенапряжения, так что будьте осторожны.
                0
                + web-камера, сервер и ethernet-шильд облагородят конструкцию. =)
                +5
                Делал такую же почти конструкцию, с лазером для кошки (когда она была котенком), в итоге увлекся больше программированием, сделал из этого устройства тренажер для зрения — на стене лазер рисовал разные фигуры — круг, восмерку и т.д., — глазами водишь за лучиком — делаешь зарядку…
                  +3
                  На видео хорошо видно, что коту неинтересно, плюс он регулярно теряет из виду зайчик. И это неудивительно — система не учитывает реакции животного. Тоже самое и с «лучом по глазам» — человек, играя, всё это учитывал бы.
                    0
                    Можно переписать скетч. Предложите алгоритм движения, плиз.
                      0
                      Я думаю, необходимо ввести зависимость с движениями/перемещениями кота. На самом примитивном уровне. Т.е. камера и отслеживание мест, где меняется освещённость. Соответственно, лучом водить вокруг таких мест — на разном расстоянии (и без чрезмерно резких рывков, чтобы кот не терял зайчик). Некоторая проблема будет увязать координаты пятна кота с изменением положения лазера. Считать придётся, вдобавок выставлять устройство строго перпендикулярно полу.
                        +1
                        без камеры и OpenCv (для определения позиции животного) не обойтись, где то было на просторах интернта, наведение водяного «пистолета» на движущегося животного…

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

                        при игре с кошками определил, что есть несколько программ поведения:
                        1) когда они «охотятся», «зайчик» отдаляется от них и прячется за какой либо выступ,
                        животное крадется, потом когда «цель» попадает в область зрения, прыгает (переходит к пункту 2),
                        здесь нужно быстро уводить соблюдая дистанцию от кошки

                        2) преследование, животное бежит за «зайчиком» — нужно соблюдать дистанцию, давая немного сближаться, можно изменять таректорию (делать круги)

                        3) «зайчик» в области зрения животного, и медленно движется, кошка готовится к пункту 2.
                          0
                          Можно совместить с датчиком температуры типа
                          habrahabr.ru/post/172947

                          Определять местоположение лежащего котэ и «дразнить» его, приближаясь и удаляясь.
                  0
                  А где награда коте за пойманный зайчик?
                    0
                    сама игра и есть награда, кошка целый день ходит просит поиграть :), потом же сама от скуки играет с чем придется…

                    когда с ней начинаешь играть — она мурчит от благодарности!
                      +2
                      Не-не, я серьёзно. Кошке нужно что-то, что она поймала. Вещественное и пощупать. Иначе они обижаются и нервничают. В обычном «поиграть» это та травинка, которая в лапы попала.
                    +5
                    Самый неочевидный минус игры с указкой – хроническая неудовлетворенность и неуверенность в себе. Лазерная игрушка для кошек – это охота в чистом виде, а значит, должна быть и добыча. Если каждый раз охота заканчивается ничем, кошка начинает сомневаться в своей способности добыть пищу, а это неизбежный стресс. Чтобы избежать чувства неудовлетворенности, в конце игры кошка должна получать «мышку». Можно наводить лазер на кусочек лакомства, выключая его, когда охотница ткнет носом находку. Или переводить луч на другую игрушку, которую питомец любит таскать в зубах. Если кошка ценит внимание, лазер переводят к ногам и выключают, одновременно поглаживая и хваля подбежавшего питомца.
                      +1
                      Спасибо за скетч. Делали с ребёнком аналогичное, но вот подвело то, что тригонометрию подзабыл и не смог сделать плавных движений сервами.

                      В вашу конструкцию я бы добавил PIR-датчик. Если кот умаялся бегать — это надо определить и сделать паузу.
                        0
                        Надписи на плату тож можно ЛУТ-ом сделать. Если использовать кальку то и отмачивать не нужно. Будет аккуратнее. А всё остальное — зачот!

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое