Интернет коров

По большому счету, хочу вам поведовать о моем небольшом DIY-проекте, но начать хотелось бы издалека. Если верить основателю псиохоанализа, то многие наши проблемы родом из детства. В свободное время от школы, с мая и до тех пор, пока не ляжет снег, мне приходилось стеречь коров, было их не очень много, голов до 20 крупнорогатого скота (КРС). Исходя из вышесказанного, идея избавиться от этого интереснейшего занятия или хотя бы как-то облегчить труд не покидает мою голову и по сей день.

Уже будучи студентом мне все равно приходилось иногда этим заниматься. В это время я собрал из подручных средств (катушки зажигания и радиодеталей из старого телевизора) электропастуха. Натянул изгородь из проволоки длиной примерно 3 км, подключил ее к генератору высокого напряжения и все заработало. Скот поначалу просто не понимал, что его бьет током и рвал ограждение, но достаточно быстро привык и потом даже не пытался порвать изгородь. Все работало отлично, казалось, проблема решена. Основным недостатком была необходимость простого обслуживания электропастуха — надо было следить, чтобы никакие ветки или трава не касались проволоки, потому что по ним весь импульс высокого напряжения уходил в землю и КРС током не бил. Несмотря на то, что электропастух очень сильно облегчал труд, никто кроме меня обслуживать его не хотел (а я на тот момент уже в деревне не жил). Не прижилось мое изобретение.

Уже тогда было ясно, что основная проблема — это провода и мысли потекли в сторону беспроводной системы. Миниатюрные приемопередатчики были уже тогда, хотя и не очень дешевые. Надо было решить проблему с определением координат, использовать GPS тогда было просто из области фантастики, сделать определение методом триангуляции было для меня неподъёмной задачей. Шло время, развитие электроники не стояло на месте. И в один прекрасный день, уже не помню где, я прочитал, про ардуино. Так, ради интереса, я решил попробовать, заказал на алиэкспрессе. Поморгал светодиодиком, собрал пидрегулятор температуры.

Я почувствовал силы и решил вернуться к старой идее создания беспроводного «пастуха». Далее буду приводить компоненты моей системы и основные характеристики, которые еще помню.

Arduino pro mini. Была выбрана версия с питанием 3.3 вольта и потребляло примерно 11 мА.

Aroduino pro mini 3.3V

Aroduino pro mini 3.3V

GPS модуль у меня был пятивольтовый, пришлось под него делать отдельное питание. Потребление примерно 5 мА. Точность позиционирования ±2 м. Из неприятного: библиотечка, которая была в Ардуино среде разработки из коробки, не завелась. Пришлоcь читать спецификацию на модуль, настраивать его, править библиотечку. Также этот модуль может работать с ГЛОНАСС. При работе с GPS надо понимать, что есть холодный старт, который занимает около 30 минут, модуль в это время настраивается, калибруется и потребляет большой ток порядка 50 мА.

Беспроводной модуль передачи спектра LoRa SX1278, Ra-01 м. Собственно, когда я наткнулся на этот модуль, понял, что идея осуществима.

С LoRa провозился дольше всего. Прочитал всю спецификацию, надо было досконально разобраться, как устроен и как работает, чтобы оптимизировать потребление энергии, как устроен протокол передачи данных, как логически разделить устройства, какая скорость, как работает шифрование, как защититься от злоумышленников. Напряжение питания 3.3 вольта. В режиме Приема потреблял примерно 12 мА. В спецификации заявлена максимальная дальности приёма 10 км, но это в идеальных условиях и прямой видимости и с хорошей антенной. В городе в условиях прямой видимости и с антенной как на фото связь была до 300 метров. В условиях непрямой видимости пробивает два-три этажа. В условиях сельской местности и прямой видимости связь держал до километра, больше просто не мог протестировать, да и не надо было. В лесу и на пересеченной местности (овраги и поймы рек) дальность 200–300 метров, но и этого для моих задач было достаточно.

LoRa SX1278 433МГц

LoRa SX1278 433МГц

Самая деликатная деталь моего устройства это высоковольтный генератор, который должен бить током животинку. После ряда экспериментов с разными девайсами с алиэкспресса был выбран следующий экземпляр.

Как заявляют производители, выдает он напряжение на выходе 400кВ, напряжение питания 3–6В и ток 2–3А. Вообще, штука страшная, такой, наверное, и убить можно. Эту проблему надо было решать. Решение было следующим: подавать питание на несколько миллисекунд, длиной питающего импульса, очень хорошо регулируется сила удара (все проверял на себе). Поставил плавкий предохранитель на случай, если контроллер подвиснет.

Дальше пришлось освоить egle, нарисовать электрическую принципиальную схему славе и мастер устройств, уж простят меня опытные электронщики, сделал как мог. Немного пояснений. На схеме не изображён GPS модуль, подключаться к питанию и шине (D6, A1) адруинки. 5V1 это питание для модуля lora и charge это контроллер заряда/разряда для литиевого аккумулятора NCR18650B. На схеме есть полевой транзистор, который управляет током питания высоковольтного генератора.

Электрическая принципальная схема slave устройства

Электрическая принципальная схема slave устройства

Трасировка платы славе устройства

Трасировка платы славе устройства

Развел дорожки и лазерноутюжной технологией получил плату на текстолите. Первый блин всегда комом (отзеркалил), а вот вторая попытка была уже удачной.

Далее привожу электрическую принципиальную схему и разводку дорожек для мастер устройства. Тут также gps модуль не отображен, подключался он к пинам D6 и A0 (можно и к другим, настраивается в коде).

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

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

Трасировка платы мастре устройства

Трасировка платы мастре устройства

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

Исходники мастер устройства

Hidden text

#include "SevenSegmentTM1637.h"
#define CLK 8 //pins definitions for TM1637 and can be changed to other ports
#define DIO 7
SevenSegmentTM1637 display(CLK,DIO);

#include 
#include 

int counter = 0;
const int ledPin = 9;
const int b1 = 5;
const int b2 = 4;
const int b3 = 3;
const int b4 = 2;


//GPS
#include 
#include  //подключение необходимых для работы библиотек
 
TinyGPS gps;
SoftwareSerial gpsSerial(6, A0); //номера пинов, к которым подключен модуль (RX, TX)
 
bool newdata = false;
unsigned long start;
long lat, lon;         //координаты полученные по радиоканалу
unsigned long time, date;

long latm = 521xxxxx; //координаты пу
long lonm = 390xxxxx;
long Rp = 3441597;

void setup() {
  
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  pinMode(b1, INPUT);
  digitalWrite(b1, HIGH);
  pinMode(b2, INPUT);
  digitalWrite(b2, HIGH);
  pinMode(b3, INPUT);
  digitalWrite(b3, HIGH);
  pinMode(b4, INPUT);
  digitalWrite(b4, HIGH);
  
  Serial.begin(9600);
  while (!Serial);

  Serial.println("LoRa Sender");
  if (!LoRa.begin(433E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
  LoRa.setSpreadingFactor(12);
  LoRa.setSignalBandwidth(125E3);

  //Gps
  gpsSerial.begin(9600); // установка скорости обмена с приемником
  //Serial.begin(9600);
  //Serial.println("Waiting data of GPS...");

  //display
  display.begin();            // initializes the display
  display.setBacklight(100);  // set the brightness to 100 %
}

void loop() {
  digitalWrite(ledPin, LOW);
  counter = 0;
  
  int nb = 0;
  delay(10);
  
  while(digitalRead(b1)==LOW){
    nb = 1;  
    count();
  }
  while(digitalRead(b2)==LOW){
    nb = 2;  
    count();  
  }
  while(digitalRead(b3)==LOW){
    nb = 3;  
    count();
  }
  while(digitalRead(b4)==LOW){
    nb = 4;  
    count();  
  }

  if(nb==1){
    if(counter < 30){
      ping("sh01");
    } else {
      ping("pi01"); 
    }
  }

  if(nb==2){
    if(counter < 30){
      ping("sh02");
    } else {
      ping("pi02");
    }
  }

  if(nb==3){
    if(counter < 30){
      ping("sh03");
    } else {
      ping("pi03");
    } 
  }

  if(nb==4){
    if(counter < 30){
      ping("sh04");
    } else {
      ping("pi04");
    } 
  }

  //try to parse packet
  int packetSize = LoRa.parsePacket();
  
  if (packetSize) {
    //Serial.print("packet size ");
    //Serial.println(packetSize);
    byte coords[16];
    int i = 0;
    while (LoRa.available()) {
      coords[i] = (byte)LoRa.read() - 48;
      i++;
    }
    parseCoords(coords);
    showCoords();
  } 

  //Gps
  if (millis() - start > 1000) //установка задержки в одну секунду между обновлением данных
  {
    newdata = readgps();
  
    if (newdata)
    {
      start = millis();
   
      gps.get_position(&latm, &lonm);
      //gps.get_datetime(&date, &time);
      //Serial.print("Lat: "); Serial.print(lat);
      //Serial.print(" Long: "); Serial.print(lon);
      //Serial.print(" Date: "); Serial.print(date);
      //Serial.print(" Time: "); Serial.println(time);
      //Serial.print(" Number satellites "); Serial.print(gps.satellites());
      //Serial.print(" Hdop "); Serial.println(gps.hdop());
      //Serial.print(" X = " ); Serial.print(getX(lat));
      //Serial.print(" Y = " ); Serial.println(getY(lon));
    }
  }

  delay(200);
  
}

void ping(const char* num){
  display.clear();
  flash(500);
  //Serial.print("Sending packet: ");
  //Serial.println(counter);

  LoRa.beginPacket();
  LoRa.print(num);
  LoRa.endPacket();
  delay(10);
}

void flash(int mlsek){
  digitalWrite(ledPin, HIGH);
  delay(mlsek);
  digitalWrite(ledPin, LOW);
}

void count(){
  counter++;
  if(counter > 30){
    flash(200);  
  } 
  delay(100); 
  return;  
}

void parseCoords(const byte* coords){
  lat = 0;
  lon = 0;
  for(int i = 0; i < 8; i++){
    lat += (coords[i]) * pow(10, 7-i);
    lon += (coords[i + 8]) * pow(10, 7-i);
  }
  //Serial.println(lat);
  //Serial.println(lon);
}

void disp(int angle, int dist){
    uint8_t rawBytes[4] = {48,49,50,51}; 
    rawBytes[0] = (uint8_t)(angle/10 + 48);
    rawBytes[1] = (uint8_t)(angle%10 +48);
    rawBytes[2] = (uint8_t)(dist/10 + 48);
    rawBytes[3] = (uint8_t)(dist%10 + 48);
    display.write(rawBytes, 4);  
      
    delay(10);
}

void showCoords(){
  float x = (latm - lat)*3.14/180*6;
  float y = -(lonm - lon)*3.14/180000000*Rp; 
  float dist = pow(pow(x, 2) + pow(y, 2), 0.5);
  float angle = acos(x/dist);
  if(y < 0){
    angle = 6.28 - angle;  
  }
  angle = 6.28 - angle;
  // переходим к часам
  byte h = (byte)round(angle/6.28*12);
  long d = (long)round(dist/10);

  //Serial.println(x);
  //Serial.println(y);
  
  if (d > 99) d = 99;
  disp(h, d);
}

// проверка наличия данных
bool readgps()
{
  bool out = false;
  for (int i = 1; i < 40; i++){
    while (gpsSerial.available())
    {
      char b = gpsSerial.read();
      //Serial.print(b);
      if(gps.encode(b)) out = true;
    }
  delay(5);
  }
  return out;
}

//float getX(long t){
//  return (latm - t)*3.14/180*6;
//}

//float getY(long n){
//  return -(lonm - n)*3.14/180000000*Rp;  
//}

Исходники для славе устройства

Hidden text

//Lora
#include 
#include 

#define ledPin 7
#define putPin 8
#define HDOP 200
#define TSH 20
#define POINTS 16

//GPS
#include 
#include  //подключение необходимых для работы библиотек
 
TinyGPS gps;
SoftwareSerial gpsSerial(2, 3); //номера пинов, к которым подключен модуль (RX, TX)
 
bool newdata = false;
unsigned long start;
long lat, lon;
unsigned long time, date;

long latm = 521xxxxx; 
long lonm = 390xxxxx;
float hm = 250;
//long Rp = 3441597;
//                   1     3          4          5          6          
//              1          3          4          5          6a         
float mlat[] = {52.1xxxxx, 52.1xxxxx, 52.1xxxxx, 52.1xxxxx, 52.1xxxxx};
float mlon[] = {39.0xxxxx, 39.0xxxxx, 39.0xxxxx, 39.0xxxxx, 39.0xxxxx};


void setup() {
  
  //Lora
  pinMode(ledPin, OUTPUT);
  pinMode(putPin, OUTPUT); 
  digitalWrite(putPin,LOW);
  
  //Serial.begin(9600);
  //while (!Serial);

  //Serial.println("LoRa Receiver");

  if (!LoRa.begin(433E6)) {
    //Serial.println("Starting LoRa failed!");
    while (1);
  }
  LoRa.setSpreadingFactor(12);
  LoRa.setSignalBandwidth(125E3);

  //Gps
  gpsSerial.begin(9600); // установка скорости обмена с приемником
  Serial.begin(9600);
  //Serial.println("Waiting data of GPS...");
}

void loop() {
  //Lora
  digitalWrite(putPin,LOW);
  delay(200);
  
  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    //Serial.print("Received packet");

    // read packet
    char mes[5];
    byte i = 0;
    while(LoRa.available() && i < 4){
      mes[i] = (char)LoRa.read();
      i++;
    }
    mes[4] = '\0';
    //Serial.print(mes);

    //на всякий случай читаем все оставшиеся
    while (LoRa.available()) {
      LoRa.read();
      //Serial.print((char)LoRa.read());
    }

    // print RSSI of packet
    //Serial.print("' with RSSI ");
    //Serial.println(LoRa.packetRssi());
    if(stringEquals(mes, "sh03")){
      shock();
    }
    if(stringEquals(mes, "pi03")){
      // send packet
      delay(100);
      LoRa.beginPacket();
      LoRa.print(latm);
      LoRa.print(lonm);
      LoRa.endPacket();

      digitalWrite(ledPin, HIGH);
      delay(100);
      digitalWrite(ledPin,LOW);
      delay(200);
      digitalWrite(ledPin, HIGH);
      delay(100);
      digitalWrite(ledPin,LOW);
      delay(200);
      digitalWrite(ledPin, HIGH);
      delay(100);
      digitalWrite(ledPin,LOW);
      delay(100);
      
    } 
  }
  
  //Gps
  if (millis() - start > 2000) //установка задержки в одну секунду между обновлением данных
  {
    newdata = readgps();
    if (newdata)
    {
      start = millis();
   
      gps.get_position(&lat, &lon);
      //gps.get_datetime(&date, &time);
      //Serial.print("Lat: "); Serial.print(lat);
      //Serial.print(" Long: "); Serial.print(lon);
      //Serial.print(" Date: "); Serial.print(date);
      //Serial.print(" Time: "); Serial.println(time);
      //Serial.print(" Number satellites "); Serial.print(gps.satellites());
      //Serial.print(" Hdop "); Serial.println(gps.hdop());
      //Serial.print(" X = " ); Serial.print(getX(lat));
      //Serial.print(" Y = " ); Serial.println(getY(lon));

      hm = hm*0.75 + gps.hdop()*0.25;
      latm = latm*0.75 + lat*0.25;
      lonm = lonm*0.75 + lon*0.25;

      //Serial.print("Latm: "); Serial.print(latm);
      //Serial.print(" Longm: "); Serial.println(lonm);
      
      if(pnpoly(POINTS, mlat, mlon, latm/1000000.0, lonm/1000000.0) && hm < HDOP){
        shock();
      }
    }
  }
}



// проверка наличия данных
bool readgps()
{
  bool out = false;
  for (int i = 1; i < 40; i++){
    while (gpsSerial.available())
    {
      char b = gpsSerial.read();
      //Serial.print(b);
      if(gps.encode(b)) out = true;
    }
  delay(5);
  }
  return out;
}

//float getX(long t){
//  return (latm - t)*3.14/180*6;
//}
//
//float getY(long n){
//  return (lonm - n)*3.14/180000000*Rp;  
//}


bool pnpoly(int npol, float * xp, float * yp, float x, float y)
  {
    bool c = true;
    for (int i = 0, j = npol - 1; i < npol; j = i++) 
    {
      if ((((yp[i]<=y) && (y (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i]))
          c = !c;
    }
    return c;
  }

void shock(){
      digitalWrite(putPin, HIGH);
      delay(TSH);
      digitalWrite(putPin,LOW);
      
      digitalWrite(ledPin, HIGH);
      delay(100);
      digitalWrite(ledPin,LOW);
      delay(100);
}

boolean stringEquals(const char* one, const char* two)
{  
  int i=0;
  while (one[i]==two[i]){
    i++;
    if (one[i] == '\0') return true;
  }
  return false;
}

В массивах mlat b mlon находятся точки периметра, за который коровы не должны выходить, младшие значения в чипах заменил xxxxx, чтобы не выдавать своего местоположения. Память адруинки не очень большая, но точек 20 по-моему влазило в память устройства.

Мастер устройство

Мастер устройство

На мастер устройстве есть цифровой дисплей. При кратковременном нажатии отправлялся запрос на получение координат и на дисплее отображалось положение славе устройства относительно мастера. Первые два символа отвечали за направление как на часах от 0 до 12, а вторые два символа отображали расстояние в десятках метров. При долговременном нажатии на кнопку (более двух секунд) отправлялась команда на удар током.

Славе устройство собирал в корпусе с алиэкспреса, а мастер устройсво оставил без корпуса.

80a57c9474b499b24aab3e9a1752b20c.png

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

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

Собственно, такое решение показало существенные недостатки. Коровы — животные достаточно сильные и оторвать и погнуть могут, что угодно. Слева черный батарейный отсек долго не продержался, антенна была погнута, провода сверху отрывали. После этого был выбран корпус побольше и все было помещено в него, смотрелось не очень элегантно, но было надежнее.

Испытания

Хочется начать с удачных моментов. На удивление устройство работало, позиционирование было достаточно точным и, самое главное, не было ложных срабатываний. Животныe достаточно быстро поняли границы, в которых им дозволено пастись, и, самое главное, что когда при выходе за периметр их било током, то они бежали не в разные стороны, а в сторону дома и их переставало бить.

Оказалось, что животные очень хорошо понимают команды человека, если их подкреплять ударами тока. По команде «домой» животные достаточно быстро начинали перемещаться в сторону дома. Без этого ошейника была большая проблема выгнать их из воды или высокой крапивы, где они любили скрываться от пастуха.

К сожалению, было много и недостатков. В основном они сводились к механической прочности. Периодически животные что-нибудь отрывали. Так же, как говорил уже ранее, была неудачно выбрана конструкция электродов, параллельно вдоль всего ошейника. Надо было сделать два электрода, расположенных диаметрально противоположно.

Так уж сложилось, что довести до ума данный девайс не удалось, закочилась эпоха разведения КРС. Вот, делюсь с читателями, а вдруг кому пригодится мой опыт.

© Habrahabr.ru