Делаем GPS-трекер на основе Arduino MKRFOX1200

Нам понравилась последнее время плата Arduino MKRFOX1200, так что мы даже опубликовали большой материал про саму плату и урок "Умный физический почтовый ящик, который отправляет e-mail".

Сегодня мы сделаем GPS Tracker на основе Arduino MKR FOX 1200, который отправляет точный GPS-данные через сеть Sigfox.

Это становится еще актуальней для многих стран в связи усилением контроля за любыми ввозимыми техническими устройствами, а особенно связанными с GPS.

Шаг 1. Что нам пригодится

Набор деталей для этого урока не велик:

  • Arduino MKR Fox 1200 × 1
  • Модуль GPS (на выбор, но мы использовали реплику ublox NEO6m (ATGM332D) × 1
  • Транзистор общего назначения NPN (мы использовали BC548) × 1
  • Резистор 1 кОм × 1

Шаг 2. Информация о проекте

Трекер использует GPS-модуль ATGM332, чтобы получить GPS-положение с большей точностью, чем услуги определения местоположения, предоставляемые Sigfox. Затем данные позиции отправляются как «строка» через сеть Sigfox и, наконец, доставляются по электронной почте.

Arduino MKR FOX 1200

Плата похожа на Arduino Zero, которая основана на SAM D21 и включает модуль ATA8520 Sigfox. Это плата с низким энергопотреблением, которая поставляется вместе с платой с бесплатной подпиской на один год в сеть Sigfox (до 140 сообщений в день), а также бесплатным доступом к службе геолокации Spot'it.

Дополнительная информация здесь.

GPS-модуль ATGM332

Этот недорогой маломощный GPS-модуль очень хорошо подходит для Arduino MKR FOX 1200, поскольку он работает только с 2,7 В (номинальный 3,3 В).

Первоначально должен был быть куплен модуль NEO6m2, который имеет режим ожидания, но пришлось использовать NEO6. Фактически это был модуль ATGM332. В результате у него не было режима ожидания, поэтому нужно было использовать транзистор для включения модуля GPS, когда это необходимо, и выключить его, чтобы сэкономить аккумулятор. Наша цель - иметь информацию о местоположении довольно редко, то есть 4 сообщения в час, поскольку Sigfox позволяет только 140 сообщений в день.

Мы используем библиотеку TinyGPS (https://github.com/mikalhart/TinyGPS) для декодирования кадров GPS.

Транзисторный переключатель

Нужно было включить и выключить GPS, когда это необходимо. Модули реле слишком громоздки и мощны, если нужно только переключить нагрузку 3 В и несколько миллиампер. Кроме того, для большинства модулей реле требуется 5 В. Таким образом, транзистор будет лучшим решением. Кроме того, MKR FOX 1200 обеспечивает только 7 мА на пине ввода/вывода.

Подойдет транзистор BC548 NPN. Когда нулевой сигнал подается на базу транзистора, он выключается, действуя как открытый выключатель, и ток коллектора не течет. При положительном сигнале, подаваемом на базу транзистора, он становится «включенным», действующим как замкнутый переключатель, и максимальный ток цепи протекает через устройство.

Шаг 3. Схема соединения

Единственным источником питания являются две 1,5-вольтовых батареи AA, которые питают Arduino MKR FOX 1200. Модуль GPS получает питание от платы Arduino.

Arduino MKR FOX 1200 взаимодействует с модулем GPS, используя второй последовательный порт через контакты 13 и 14, называемые Serial1 в коде. Выход TX-данных модуля GPS подключается к последовательному входу данных (контакт 13) платы Arduino.

Кроме того, плата Arduino использует PIN2 для включения и выключения модуля GPS, как объясняется выше.

Шаг 4. Код проекта

Код нашего проекта вы можете скачать или скопировать ниже:

#include <SigFox.h>
#include <ArduinoLowPower.h>
#include <TinyGPS.h>//incluimos TinyGPS

#define WAITING_TIME 15
#define GPS_PIN 2
#define GPS_INFO_BUFFER_SIZE 128

bool debug = false;

TinyGPS gps;//GPS Object

//GPS data variables
int year;
byte month, day, hour, minute, second, hundredths;
unsigned long chars;
unsigned short sentences, failed_checksum;
char GPS_info_char;
char GPS_info_buffer[GPS_INFO_BUFFER_SIZE];
unsigned int received_char;
bool message_started = false;
int i = 0;

// GPS coordinate structure, 12 bytes size on 32 bits platforms
struct gpscoord {
  float a_latitude;  // 4 bytes
  float a_longitude; // 4 bytes
  float a_altitude;  // 4 bytes
};

float latitude  = 0.0f;
float longitude = 0.0f;
float altitud = 0;


//////////////// Waiting function //////////////////
void Wait(int m, bool s) {
  //m minutes to wait
  //s slow led pulses
  if (debug) {
    Serial.print("Waiting: "); Serial.print(m); Serial.println(" min.");
  }

  digitalWrite(LED_BUILTIN, LOW);

  if (s) {

    int seg = m * 30;
    for (int i = 0; i < seg; i++) {
      digitalWrite(LED_BUILTIN, HIGH); //LED on
      delay(1000);
      digitalWrite(LED_BUILTIN, LOW); //LED off
      delay(1000);
    }

  } else {
    int seg = m * 15;
    for (int i = 0; i < seg; i++) {
      digitalWrite(LED_BUILTIN, HIGH); //LED on
      delay(1000);
      digitalWrite(LED_BUILTIN, LOW); //LED off
      delay(3000);

    }
  }
}

/////////////////// Sigfox Send Data function ////////////////
void SendSigfox(String data) {
  if (debug) {
    Serial.print("Sending: "); Serial.println(data);
    if (data.length() > 12) {
      Serial.println("Message too long, only first 12 bytes will be sent");
    }
  }

  // Remove EOL
  //data.trim();

  // Start the module
  SigFox.begin();
  // Wait at least 30mS after first configuration (100mS before)
  delay(100);
  // Clears all pending interrupts
  SigFox.status();
  delay(1);
  if (debug) SigFox.debug();
  delay(100);

  SigFox.beginPacket();
  SigFox.print(data);


  if (debug) {
    int ret = SigFox.endPacket(true);  // send buffer to SIGFOX network and wait for a response
    if (ret > 0) {
      Serial.println("No transmission");
    } else {
      Serial.println("Transmission ok");
    }

    Serial.println(SigFox.status(SIGFOX));
    Serial.println(SigFox.status(ATMEL));

    if (SigFox.parsePacket()) {
      Serial.println("Response from server:");
      while (SigFox.available()) {
        Serial.print("0x");
        Serial.println(SigFox.read(), HEX);
      }
    } else {
      Serial.println("Could not get any response from the server");
      Serial.println("Check the SigFox coverage in your area");
      Serial.println("If you are indoor, check the 20dB coverage or move near a window");
    }
    Serial.println();
  } else {
    SigFox.endPacket();
  }
  SigFox.end();
}


//////////////////  Convert GPS function  //////////////////
/* Converts GPS float data to Char data */

String ConvertGPSdata(const void* data, uint8_t len) {
  uint8_t* bytes = (uint8_t*)data;
  String cadena ;
  if (debug) {
    Serial.print("Length: "); Serial.println(len);
  }

  for (uint8_t i = len - 1; i < len; --i) {
    if (bytes[i] < 12) {
      cadena.concat(byte(0)); // Not tested
    }
    cadena.concat(char(bytes[i]));
    if (debug) Serial.print(bytes[i], HEX);
  }

  if (debug) {
    Serial.println("");
    Serial.print("String to send: "); Serial.println(cadena);
  }

  return cadena;
}


////////////////////////// Get GPS position function/////////////////////
String GetGPSpositon() {

  int messages_count = 0;
  String pos;

  if (debug) Serial.println("GPS ON");
  digitalWrite(GPS_PIN, HIGH); //Turn GPS on
  Wait(1, false);
  while (messages_count < 5000) {
    while (Serial1.available()) {

      int GPS_info_char = Serial1.read();

      if (GPS_info_char == '$') messages_count ++; // start of message. Counting messages.


      if (debug) {
        if (GPS_info_char == '$') { // start of message
          message_started = true;
          received_char = 0;
        } else if (GPS_info_char == '*') { // end of message
          for (i = 0; i < received_char; i++) {
            Serial.write(GPS_info_buffer[i]); // writes the message to the PC once it has been completely received
          }
          Serial.println();
          message_started = false; // ready for the new message
        } else if (message_started == true) { // the message is already started and I got a new character
          if (received_char <= GPS_INFO_BUFFER_SIZE) { // to avoid buffer overflow
            GPS_info_buffer[received_char] = GPS_info_char;
            received_char++;
          } else { // resets everything (overflow happened)
            message_started = false;
            received_char = 0;
          }
        }
      }

      if (gps.encode(GPS_info_char)) {
        gps.f_get_position(&latitude, &longitude);
        altitud = gps.altitude() / 100;

        // Store coordinates into dedicated structure
        gpscoord coords = {altitud, longitude, latitude};

        gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths);

        if (debug) {
          Serial.println();
          Serial.println();
          Serial.print("Latitud/Longitud: ");
          Serial.print(latitude, 5);
          Serial.print(", ");
          Serial.println(longitude, 5);
          Serial.println();
          Serial.print("Fecha: "); Serial.print(day, DEC); Serial.print("/");
          Serial.print(month, DEC); Serial.print("/"); Serial.print(year);
          Serial.print(" Hora: "); Serial.print(hour, DEC); Serial.print(":");
          Serial.print(minute, DEC); Serial.print(":"); Serial.print(second, DEC);
          Serial.print("."); Serial.println(hundredths, DEC);
          Serial.print("Altitud (metros): "); Serial.println(gps.f_altitude());
          Serial.print("Rumbo (grados): "); Serial.println(gps.f_course());
          Serial.print("Velocidad(kmph): "); Serial.println(gps.f_speed_kmph());
          Serial.print("Satelites: "); Serial.println(gps.satellites());
          Serial.println();

        }

        gps.stats(&chars, &sentences, &failed_checksum);
        if (debug) Serial.println("GPS turned off");
        digitalWrite(GPS_PIN, LOW); //GPS turned off
        pos = ConvertGPSdata(&coords, sizeof(gpscoord)); //Send data
        return pos;

      }

    }
  }
  pos = "No Signal";
}




//////////////////SETUP///////////////////

void setup() {
  if (debug) {
    Serial.begin(9600);
    while (!Serial) {}// wait for serial port to connect. Needed for native USB port only
    Serial.println("Serial Connected");
  }

  //Serial1 pins 13-14 for 3.3V connection to GPS.
  Serial1.begin(9600);
  while (!Serial1) {}
  if (debug) {
    Serial.println("GPS Connected");
  }

  pinMode(GPS_PIN, OUTPUT); //pin de interruptor del GPS

  if (!SigFox.begin()) {
    Serial.println("Shield error or not present!");
    return;
  }

  // Enable debug led and disable automatic deep sleep
  if (debug) {
    SigFox.debug();
  } else {
    SigFox.end(); // Send the module to the deepest sleep
  }


}



//////////////////////LOOP////////////////////////

void loop() {

  String position_data;

  position_data = GetGPSpositon();
  SendSigfox(position_data);

  Wait(WAITING_TIME, false);


}

Шаг 5. Отправка информации GPS через Sigfox

Мы хотел отправить информацию GPS с использованием данных типа float, но когда мы попытались, то всегда получали нулевые значения.

Поиск в Интернете привел на этот проект на GitHub - https://github.com/nicolsc/SmartEverything_SigFox_GPS от Николя Лискони. Он использует AT-команды для отправки любого типа данных и конвертирует 'float' в 'hex'. Тем не менее у Arduino MKR FOX 1200 нет режима AT, и мы не смогли заставить её работать.

Было сделано несколько десятков тестов и, изменив код Николя, был найден способ отправить «строку», которая была проанализирована платформой Sigfox «float: 32», и ее можно было бы использовать напрямую без какого-либо преобразования.

Данные Sigfox ограничены 12 байтами. Данные, которые отправляются в сеть SigFox:

  • Широта, float: 32 типа (float:32type), 4 байта.
  • Долгота, float: 32 типа (float:32type), 4 байта.
  • Высота, float: 32 типа (float:32type), 4 байта.

Шаг 6. Конфигурация обратного вызова Sigfox

Конфигурация пользовательского обратного вызова Sigfox:

lat::float:32lng::float:32 alt::float:32

Вы получите электронное письмо:

Чтобы легко видеть позицию, мы включили URL-адрес в Карты Google, используя полученную информацию:

https://maps.google.com/maps/?q=%7bcustomData#lat},{customData#lng}

И, наконец, результат работы нашего Arduino GPS-трекера:

На этом всё, желаю вам отличных проектов!

Ардуино+