Очередная Температурная сигнализация с SMS управлением для дома

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017
Приветствую!
 
Прошлой зимой в деревне два раза вставал автоматический газовый котел,
из-за отключения света на несколько суток, в связи с чем пришлось
делать температурную сигнализацию. В хламе дома нашел древний COM модем,
на нем и собрал.
 
Итак исходные комплектующие: Arduino UNO + БП, модем IRZ MC52iT + БП,
RS232(COM)-TTL конвертер, датчик температуры DS18B20, четырех разрядный
индикатор TM1637, несколько резисторов, проводов и походящая коробка.
 
Принцип работы банален: при температуре меньше 12 градусов отсылается SMS.
Раз в сутки принудительно перезагружается модем, в первых двух разрядах
индикатора отображается уровень сигнала (01...30),во вторых температура.
 
Внешние управляющие команды:
reset - принудительная перезагрузка модема
smson - включение возможности отправки SMS (по умолчанию включено)
smsoff - отключение отправки SMS
balancetele2 - запрос баланса оператора Теле2 - в ответ приходит
  баланс оператора, температура и время работы устройства.
balancemegafon - аналогично для Мегафона
delete - удаление из SIM карты последних 8 SMS сообщейний
gettemp - в ответ приходит сообщение с температурой и временем работы устройства
 
#include <SoftwareSerial.h>

SoftwareSerial gsm(7, 8); // RX, TX

#include <OneWire.h>
#include "TM1637.h"

#define CLK 3
#define DIO 4
#define TPN 2
#define CYCLE_RESET_MODEM 85029 // sec. 85029 sec = 23 h 37m 9s
#define CYCLE_GET_TEMP 169
#define CRYTICAL_TEMP 12
#define AlarmNumber "+7920xxxxxxx"
#define BalanceNumberTele2 "*105#"
#define BalanceNumberMegafon "*100#"

unsigned long timerSec = 0;
unsigned int timerGetTemp = 130;
unsigned int timerResetModem = 50000;
float curr_temp = 0;
int togg_l = 0;
boolean SMSoff = false;
byte HardStatus = 0;
// 0 - OK, 1...30 - Qualyti net, 170 AA - init modem, 255 FF - crytical temp
// 225 E1 - error init modem, 226 E2 - error status modem
// 227 E3 - error send SMS
String val = ""; // get string

OneWire  ds(TPN);
TM1637 tm1637(CLK, DIO);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  tm1637.init();
  tm1637.set(0);
  tm1637.clearDisplay();
  pinMode(LED_BUILTIN, OUTPUT);
  InitModem();
}

void loop() {
  // put your main code here, to run repeatedly:
  // read console
  if (Serial.available()) {
    while (Serial.available()) {
      val += char(Serial.read());
      delay(50);
    }
    MainCommand();
    val = "";
  } else {
    delay(50);
  }
  if (gsm.available()) {
    while (gsm.available()) {
      val += char(gsm.read());
      delay(50);
    }
    Serial.println(val);
    MainCommand();
    val = "";
  } else {
    delay(50);
  }
  // count timers
  unsigned long ctmr = (long(millis() / 1000));
  if ((timerSec > 4294900) && (ctmr < 100)) {
    timerSec = 0;
  }
  if (ctmr > timerSec) {
    timerSec = ctmr;
    digitalWrite(LED_BUILTIN, togg_l); togg_l = !togg_l;
    tm1637.point(togg_l);
    ++timerGetTemp;
    ++timerResetModem;
    if (timerResetModem >= (CYCLE_RESET_MODEM)) {
      timerResetModem = 0;
      timerGetTemp = 0;
      curr_temp = 0;
      ResetModem();
    }
    if (timerGetTemp >= (CYCLE_GET_TEMP)) {
      timerGetTemp = 0;
      curr_temp = GetTemp();
      GetModemStatus();
      Serial.print(curr_temp); Serial.print("°C"); PrintUptime();
      if (curr_temp < CRYTICAL_TEMP)  AlarmTemp();
    }
    ShowStatus();
    ShowTemp();
  } else {
    delay(50);
  }
}

int StrHtoInt(String ins) {
  ins.trim();
  int sl = ins.length();
  if (sl < 1) return 0;
  ins.toUpperCase();
  int sum = 0;
  int pi = 0;
  for (int ii = (sl - 1); ii >= 0; --ii) {
    int ti = int(ins[ii]);
    if (((ti >= 48) && (ti <= 57)) || ((ti >= 65) && (ti <= 70))) {
      int st = 0;
      if ((ti >= 48) && (ti <= 57)) {
        ti -= 48;
      } else {
        ti -= 55;
      }
      if (pi > 0) {
        st = 1 + pow(16, pi);
      } else {
        st = 1;
      }
      sum += (ti * st);
      ++pi;
    }
  }
  return sum;
}

void MainCommand() {
  val.toLowerCase();
  String cmd;
  if ((val.indexOf("+cmgr: ") > -1)) {
    String vt = val.substring((val.indexOf("+cmgr: ") + 7), val.length());
    if ((vt.indexOf("rec unread") > -1)) {
      if ((vt.indexOf(AlarmNumber) > -1)) {
        val = vt.substring((vt.indexOf(AlarmNumber) + 15), vt.length());
        //case command
        SMSCommand();
        //---
        val = "";
      }
    }
  } else {
    //--other command
    if ((val.indexOf("reset") > -1)) {
      timerResetModem = 0;
      timerGetTemp = 0;
      curr_temp = 0;
      ResetModem();
    }
    if ((val.indexOf("smson") > -1)) {
      SMSoff = false;
    }
    if ((val.indexOf("+cusd: 2,") > -1)) {
      String vt = val.substring((val.indexOf("+cusd: 2,") + 10), val.length());
      vt.toUpperCase();
      boolean ft = true;
      for (int i = 0; i <= 3 ; ++i) {
        int ti = int(vt[i]);
        if (!(((ti >= 48) && (ti <= 57)) || ((ti >= 65) && (ti <= 70)))) {
          ft = false;
        }
      }
      if (ft) {
        String vo = "";
        for (int i = 0; i <= vt.length(); i += 4) {
          int tf = StrHtoInt(vt.substring(i, (i + 4)));
          if (tf <= 127) {
            vo += char(tf);
          }
        }
        SendSMS(vo);
      } else {
        if (vt.indexOf('"') > -1) {
          vt = vt.substring(0, vt.indexOf('"'));
        }
        SendSMS(vt);
      }
    }
    if ((val.indexOf("smsoff") > -1)) {
      SMSoff = true;
    }
    if ((val.indexOf("balancetele2") > -1)) {
      // get balance phone
      cmd = "ATD"; cmd += BalanceNumberTele2; cmd += ";";
      gsm.println(cmd);
      delay(500);
    }
    if ((val.indexOf("balancemegafon") > -1)) {
      // get balance phone
      cmd = "ATD"; cmd += BalanceNumberMegafon; cmd += ";";
      gsm.println(cmd);
      delay(500);
    }
    if ((val.indexOf("delete") > -1)) {
      for (int i = 1; i <= 8; ++i) {
        cmd = "AT+CMGD="; cmd +=  String(i);
        gsm.println(cmd);
        delay(500);
      }
    }
    if ((val.indexOf("gettemp") > -1)) {
      SendSMS("");
    }
    if ((val.indexOf("+cmti: ") > -1)) {
      String vt = val.substring((val.indexOf("+cmti: ") + 12), val.length());
      cmd = "AT+CMGR="; cmd += vt;
      gsm.println(cmd);
      delay(500);
    }
    if ((val.indexOf("+csq: ") > -1)) {
      String vt = val.substring((val.indexOf("+csq: ") + 6), val.length());
      if (vt.indexOf(",") > -1) {
        vt = vt.substring(0, (vt.indexOf(",")));
      }
      vt.trim();
      int vl = vt.length();
      if (vl <= 0) {
        HardStatus = 226;
        ShowStatus;
        return;
      }
      if (vl == 1 ) {
        HardStatus = (int(vt[0])) - 48;
      }
      if (vl == 2 ) {
        int d1 = (int(vt[0])) - 48;
        d1 *= 16;
        HardStatus = d1 + (int(vt[1])) - 48;
      }
      ShowStatus();
    }
    //-- end other command
  }
}

void SMSCommand() {
  String cmd;
  if ((val.indexOf("reset") > -1)) {
    timerResetModem = 0;
    timerGetTemp = 0;
    curr_temp = 0;
    ResetModem();
  }
  if ((val.indexOf("smson") > -1)) {
    SMSoff = false;
  }
  if ((val.indexOf("smsoff") > -1)) {
    SMSoff = true;
  }
  if ((val.indexOf("balancetele2") > -1)) {
    // get balance phone
    cmd = "ATD"; cmd += BalanceNumberTele2; cmd += ";";
    gsm.println(cmd);
    delay(500);
  }
  if ((val.indexOf("balancemegafon") > -1)) {
    // get balance phone
    cmd = "ATD"; cmd += BalanceNumberMegafon; cmd += ";";
    gsm.println(cmd);
    delay(500);
  }
  if ((val.indexOf("delete") > -1)) {
    for (int i = 1; i <= 8; ++i) {
      cmd = "AT+CMGD="; cmd +=  String(i);
      gsm.println(cmd);
      delay(500);
    }
  }
  if ((val.indexOf("gettemp") > -1)) {
    SendSMS("");
  }
}

void SendSMS(String text) {
  text += "t";
  text += String(curr_temp);
  text += "u";
  unsigned int days = int(timerSec / 86400);
  text += days;
  text += "d";
  unsigned int hour = int((timerSec - days * 86400) / 3600);
  text += hour;
  text += "h";
  // send sms
  String cmd3 = "AT+CMGF=1";
  gsm.println(cmd3);
  delay(500);
  cmd3 = "AT+CMGS="; cmd3 += AlarmNumber;
  gsm.println(cmd3);
  delay(500);
  gsm.print(text);
  delay(500);
  gsm.print(char(26));
  delay(500);
}

void PrintUptime() {
  Serial.print(" Uptime: ");
  unsigned int days = int(timerSec / 86400);
  Serial.print(days); Serial.print(" day ");
  unsigned int hour = int((timerSec - days * 86400) / 3600);
  Serial.print(hour); Serial.print(" hour ");
  unsigned int min = int((timerSec - days * 86400 - hour * 3600) / 60);
  Serial.print(min); Serial.print(" min ");
  unsigned int sec = int(timerSec % 60);
  Serial.print(sec); Serial.println(" sec ");
}

void ResetModem() {
  tm1637.clearDisplay();
  //reset modem
  gsm.println("AT+CFUN=1,1");
  delay(10000); //wait ready modem
  //init modem
  InitModem();
}

void InitModem() {
  // init modem
  HardStatus = 170; //AA init modem
  ShowStatus();
  delay(1000);
  gsm.begin(9600);
  gsm.println("AT+ILRR=1");
  delay(500);
  gsm.println("AT+IPR=9600");
  delay(500);
  gsm.println("ATE0");
  delay(500);
  gsm.println("AT+CUSD=1");
  delay(500);
  gsm.println("AT+CLIP=1");
  delay(500);
  gsm.println("AT+CMGF=1");
  delay(500);
  gsm.println("AT+CSCS=\"GSM\"");
  delay(500);
  gsm.println("AT+CNMI=2,1");
  delay(500);
  HardStatus = 0;
  ShowStatus();
}

void GetModemStatus() {
  // get modem quality signal and show it
  gsm.println("AT+CSQ");
  delay(500);
  /*gsm.println("AT+COPS?");
    delay(500);
    gsm.println("AT+CREG?");
    delay(500);
    gsm.println("AT+CCLK?");
    delay(500);*/
}

void AlarmTemp() {
  HardStatus = 255; //FF
  ShowStatus();
  delay(1000);
  if (SMSoff) return;
  //send sms
  SendSMS("AlarmTemp ");
}

void ShowStatus() {
  int s1; int s2;
  s1 = int(HardStatus / 16);
  s2 = int(HardStatus % 16);
  tm1637.display(0, s1);
  tm1637.display(1, s2);
}

void ShowTemp() {
  int cti; int s1; int s2;
  cti = abs(curr_temp);
  if (cti > 0) {
    s1 = int(cti / 10);
    s2 = int(cti % 10);
    tm1637.display(2, s1);
    tm1637.display(3, s2);
  } else {
    tm1637.display(2, 15);
    tm1637.display(3, 15);
  }
}

float GetTemp() {
  float ct; byte i; byte present = 0;
  byte type_s; byte data[12]; byte addr[8];
  if ( !ds.search(addr)) {
    ds.reset_search();
    delay(250);
    return;
  }
  switch (addr[0]) {
    case 0x10:
      type_s = 1;
      break;
    case 0x28:
      type_s = 0;
      break;
    case 0x22:
      type_s = 0;
      break;
    default:
      return;
  }
  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // start conversion, with parasite power on at the end
  delay(1050);     // maybe 750ms is enough, maybe not
  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE);         // Read Scratchpad
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
  }
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw < 3; // 9 bit resolution default
    if (data[7] == 0x10) {
      // "count remain" gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    // at lower res, the low bits are undefined, so let's zero them
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    //// default is 12 bit resolution, 750 ms conversion time
  }
  ct = (float)raw / 16.0;
  ct -= 2;
  return ct;
}

 

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

fly245
fly245 аватар
Offline
Зарегистрирован: 25.08.2013

А под какой модуль GSM сделано? 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

используется не GSM модуль, а модем IRZ MC52iT + RS232-TTL конвертор

P.S. Разницы особой не вижу что использовать, при проектировании была только одна сложность - 

долго искал документацию по AT командам на этот модем, настройку, что бы при входящей SMS

сразу в COM порт выходило сообщение о приходе новой SMS.

fly245
fly245 аватар
Offline
Зарегистрирован: 25.08.2013

Спасибо за уточнение.

dmitron1036
Offline
Зарегистрирован: 10.01.2016

Цена модема 150.00 USD ?

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Даже не в курсе про цену - что было то и использовал

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

В яндекс макете только что нашёл за 2900 модем этот

TratorA
Offline
Зарегистрирован: 12.10.2017

кстати... а может и нет... я отказался от SNS уведомлений как основного транспорта сообщений... не гарантирован срок доставки... лучше SNS (или e-mail) использовать как дублирующий, а основной информационный о событии - голосовой звонок.

 

TratorA
Offline
Зарегистрирован: 12.10.2017

кстати... а может и нет... я отказался от SNS уведомлений как основного транспорта сообщений... не гарантирован срок доставки... лучше SNS (или e-mail) использовать как дублирующий, а основной информационный о событии - голосовой звонок.

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

во вы тему древнюю подняли .... :)

В идеале, в общем случае нужен как минимум один резервный канал, но проблема в том что место где стоит мой дом в деревне,

практически не имет не то что 3G а просто нормальной связи, заказал и поставил около окна антену 10 dB ( c AliExpress - дороже чем блин сама сигнализация) штыревую здоровую, добился связи на уровне 8...10 (из 30) с трудом. Позвонить аналогично проблема что входящая что исходящая связь. А СМС вполне нормально туда сюда ходят.

так что в моем случае только SMS.

 

TratorA
Offline
Зарегистрирован: 12.10.2017

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

TratorA
Offline
Зарегистрирован: 12.10.2017

по поводу антенны я бы на вышку направленную поставил. усиление выше.

штырь ненаправленная антена.

x-nikolas
Offline
Зарегистрирован: 30.04.2017

andycat пишет:

Итак исходные комплектующие: Arduino UNO + БП, модем IRZ MC52iT + БП,
RS232(COM)-TTL конвертер

Добрый день!

У меня те же комплектующие, но модем мне, почему-то, не отвечает. Команды от Arduino уходят точно, посылал на COM порт ПК и видел их там. Модем рабочий, изначально был прошит для общения с промышленным контроллером, скорость обмена мне известна. Думаю, вне завиисмости от его прошивки, при правильной скорости порта он должен отвечать хотя бы на "ATI".

Я пробую отсылать библиотекой Serial(), Вы используете MySerial(). Это принципиально? 

Можно ли посмотреть Вашу схему подключения, чтобы сравнить с моей?

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Поменять rx tx провода местами в кабеле соединяющий arduino и ttl конвертер и в кабеле соединяющий конвертер и модем

x-nikolas
Offline
Зарегистрирован: 30.04.2017

andycat пишет:
Поменять rx tx провода местами в кабеле соединяющий arduino и ttl конвертер и в кабеле соединяющий конвертер и модем

Если б это помогало, я бы сюда не писал.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

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

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

И кстати где вы увидели MySerial? Сейчас специально посмотрел - не нашёл в своём скетче

Если вы используете software serial, то у меня объект называется gsm и подключается к 7 и 8 пинам

x-nikolas
Offline
Зарегистрирован: 30.04.2017

andycat пишет:
Мне помогло, модем аналогично прекрасно общался с компьютером, а с ардуино не хотел, попробуйте сначала простейший скетч общения с модемом, перебороть скоростей, пинов и т.д. Модем сбросьте до заводских настроек.

Нет, конечно, Вы правы, это всё правильно (простые операции по замене RX на Tx и т.д.), я имел ввиду, что все простые проверки я уже перепробовал.

На данном этапе через терминальные программы с ПК я нормально опрашиваю модем. К примеру, одна из простых комманд "ATI" в ПК возвращает нормальный ответ, а вот в Ардуину выдаёт  "ERROR". То есть связь нормальная, но что-то пока ещё не так.

x-nikolas
Offline
Зарегистрирован: 30.04.2017

andycat пишет:
И кстати где вы увидели MySerial? Сейчас специально посмотрел - не нашёл в своём скетче Если вы используете software serial, то у меня объект называется gsm и подключается к 7 и 8 пинам

Пардон, я имел ввиду software serial.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

x-nikolas пишет:

andycat пишет:
Мне помогло, модем аналогично прекрасно общался с компьютером, а с ардуино не хотел, попробуйте сначала простейший скетч общения с модемом, перебороть скоростей, пинов и т.д. Модем сбросьте до заводских настроек.

Нет, конечно, Вы правы, это всё правильно (простые операции по замене RX на Tx и т.д.), я имел ввиду, что все простые проверки я уже перепробовал.

На данном этапе через терминальные программы с ПК я нормально опрашиваю модем. К примеру, одна из простых комманд "ATI" в ПК возвращает нормальный ответ, а вот в Ардуину выдаёт  "ERROR". То есть связь нормальная, но что-то пока ещё не так.


Т е связь с МК есть? Тупо сбросьте модем до заводских настроек.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Полностью переписал код, избавился от delay, удаляются все СМС после прихода их чтения, добавил watch dog timer, добавил три исполнительных устройства (реле) и команды для их включения/отключения. Добавил управление через DTMF, но не дописал, т.к. модем не поддерживает :(

Команды для входящих СМС:

"getuptime", "getbalance", "gettemp", "smson", "smsoff",
                        "relay1on", "relay1off", "relay2on", "relay2off", "relay3on", "relay3off",
                        "gprson", "gprsoff"
 
В проекте отправка температуры на сервер по GPRS для ведения лога.
 
#include <EEPROM.h>
#include <avr/wdt.h>
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((used)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

#define min_temp_on_alarm 14 // minimal temp for start alarm

byte smsON; // 1-alarmsms on 0 off
#define pos_eeprom_gprs_on 200 // value 119 gprs on
byte gprsON; // 1 gprs on 0 off

#define WORK_LEVEL_LED 1      // 1 if HIGH level ON led, else 0
#define WORK_LEVEL_RELAY 1    // 1 if HIGH level ON relay, else 0

char admin_phone[] =  "920*****46"; // always 10 digits !!!

#define relay1_pin 6 // pin relay 1
#define relay2_pin 5 // pin relay 2
#define relay3_pin 9 // pin relay 3

#define ledRed_pin 10 // pin RED led
#define ledGre_pin 11 // pin GREEN led

#if (WORK_LEVEL_LED == 0)
#define LEDREDON digitalWrite(ledRed_pin,LOW)
#define LEDGREON digitalWrite(ledGre_pin,LOW)
#define LEDREDOFF digitalWrite(ledRed_pin,HIGH)
#define LEDGREOFF digitalWrite(ledGre_pin,HIGH)
#else
#define LEDREDON digitalWrite(ledRed_pin,HIGH)
#define LEDGREON digitalWrite(ledGre_pin,HIGH)
#define LEDREDOFF digitalWrite(ledRed_pin,LOW)
#define LEDGREOFF digitalWrite(ledGre_pin,LOW)
#endif

#if (WORK_LEVEL_RELAY == 0)
#define RELAY1ON digitalWrite(relay1_pin,LOW)
#define RELAY2ON digitalWrite(relay2_pin,LOW)
#define RELAY3ON digitalWrite(relay3_pin,LOW)
#define RELAY1OFF digitalWrite(relay1_pin,HIGH)
#define RELAY2OFF digitalWrite(relay2_pin,HIGH)
#define RELAY3OFF digitalWrite(relay3_pin,HIGH)
#else
#define RELAY1ON digitalWrite(relay1_pin,HIGH)
#define RELAY2ON digitalWrite(relay2_pin,HIGH)
#define RELAY3ON digitalWrite(relay3_pin,HIGH)
#define RELAY1OFF digitalWrite(relay1_pin,LOW)
#define RELAY2OFF digitalWrite(relay2_pin,LOW)
#define RELAY3OFF digitalWrite(relay3_pin,LOW)
#endif

#include <SoftwareSerial.h>
SoftwareSerial gsm(7, 8); // RX, TX

#include "TM1637.h"
#define CLK 3 //pins definitions for TM1637 and can be changed to other ports    
#define DIO 4 //pins definitions for TM1637 and can be changed to other ports  
TM1637 tm1637(CLK, DIO); // tm1637 object
boolean displayPoint = true; // on point on display tm1637

#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2 // ds18b20
OneWire oneWire(ONE_WIRE_BUS); // ds18b20
DallasTemperature sensors(&oneWire); // ds18b20
boolean flagReqTemp = true; // start req temp ds18b20

word currentTemp = 9999; // current temp
byte currentQNet = 99; // quality of net gsm modem 1...29, normal if > 8

#define period_get_temp 300243UL // every ~5 min get temp
unsigned long timer_get_temp = 0; // timer get temp
unsigned long timer_sec = 0; // timer every 1 sec
unsigned long current_millis; // main timer
#define period_read_sms 120971UL // read sms every ~2 min
unsigned long timer_read_sms = 0; // timer read sms
unsigned long timer_reg_modem = 0; // timer registration modem in net
#define period_send_sms 27000UL // send sms every 27 sec
unsigned long timer_send_sms = 0; // timer read sms
#define period_get_qnet 180795UL // read quality net every ~3 min
unsigned long timer_get_qnet = 0; // timer read quality net

#define max_size_fixed_resp 8 // in work save 3 response from buf commands
byte resp1[max_size_fixed_resp]; byte resp2[max_size_fixed_resp]; byte resp3[max_size_fixed_resp]; // 3 parts response
byte flresp1, flresp2, flresp3; // found parts of response
#define max_size_sms_buf 24 // max size sms with stop byte and text uptime
byte sms_buf[max_size_sms_buf]; // sms buf
#define max_size_temp_buf 64 // max size temp buf
byte temp_buf[max_size_temp_buf]; // buf for long response
byte sender[13]; // phone number for send sms. +7 and 10 digits + stop byte = always 13 digits!!!
#define max_size_resp_buf 248 // max size response buf from modem, then more than good
byte resp_buf[max_size_resp_buf]; // response buf from modem
byte pos_buf = 0; // current pos response buf
byte modem_step = 100; // current work mode mode in table of command
byte next_step_by_cmd = 102; // next cmd from external command
// static steps:
// 100 - delay 30 sec for registration modem in net
// 101 - not wait response, wait incomming call or other commands
// 102 - no next command from ext cmd
char* commands_at[] = { // record of AT commands
  // AT cmd, response 1, response 2, response 3, type wait response, num next command
  "AT+ILRR=1\r\n", "OK", "", "", "1", "1", // on report of speed local port
  "AT+IPR=9600\r\n", "OK", "", "", "1", "2", // set speed port
  "ATE0\r\n", "OK", "", "", "1", "3", // no echo
  "AT+CLIP=1\r\n", "OK", "", "", "1", "4", // no aon
  "ATS0=0\r\n", "OK", "", "", "1", "5", // no answer by ring
  "ATV1\r\n", "OK", "", "", "1", "6", // get text for error
  "AT+CMEE=2\r\n", "OK", "", "", "1", "7", // get full text error
  "AT+CMGF=1\r\n", "OK", "", "", "1", "8", // text mode sms
  "AT+CREG?\r\n", "+CREG:", ",1", "OK", "1", "27", // true registration on net
  "AT+CSQ\r\n", "CSQ:", ",", "OK", "1", "101", // get quality net
  "AT+CMGL=ALL\r\n", "OK\r\n", "", "", "1", "101", // read all sms
  "AT+CMGD=1\r\n", "OK", "", "", "1", "12", // delete 1 sms
  "AT+CMGD=2\r\n", "OK", "", "", "1", "13", // delete 2 sms
  "AT+CMGD=3\r\n", "OK", "", "", "1", "14", // delete 3 sms
  "AT+CMGD=4\r\n", "OK", "", "", "1", "15", // delete 4 sms
  "AT+CMGD=5\r\n", "OK", "", "", "1", "16", // delete 5 sms
  "AT+CMGD=6\r\n", "OK", "", "", "1", "17", // delete 6 sms
  "AT+CMGD=7\r\n", "OK", "", "", "1", "18", // delete 7 sms
  "AT+CMGD=8\r\n", "OK", "", "", "1", "101", // delete 8 sms
  "AT+CUSD=1,\"*100#\"\r\n", "OK", "+CUSD: ", ",72", "1", "101", // get ussd balance
  "AT+CMGS=", "", "", "", "1", "21", // send sms
  "~sender~", "", "", "", "1", "22", // send sms
  "\r\n", ">", "", "", "1", "23", // send sms
  "~textsms~", "", "", "", "1", "24", // send sms
  "~ctrlz~", "+CMGS:", "OK", "", "1", "101", // send sms
  "ATA\r\n", "NO CARRIER", "", "", "2", "101", // answer on call
  "ATH\r\n", "", "", "", "1", "101", // reset call
  "AT+DDET=1\r\n", "ERROR", "", "", "1", "101" // DTMF on
}; // list at commands for modem = step
byte wait_response = 0; // 0-no wait response, wait ring or other command
// 1-wait fixed response, if time more - reset modem
#define period_fixed_wait 30000UL // max time for type 1 wait response
// 2-wait response data or wait action user, if time more - clear command
#define period_data_wait 60000UL // max time for type 2 wait response
char* ext_commands[] = {"getuptime", "getbalance", "gettemp", "smson", "smsoff",
                        "relay1on", "relay1off", "relay2on", "relay2off", "relay3on", "relay3off",
                        "gprson", "gprsoff"
                       }; // external commands from incoming sms
#define count_ext_commands 13 // count external commands from incoming sms

void setup() {
  // start serial and gsm port
  //Serial.begin(9600);
  gsm.begin(9600);
  // init and start display
  pinMode(CLK, OUTPUT); digitalWrite(CLK, LOW);
  pinMode(DIO, OUTPUT); digitalWrite(DIO, LOW);
  tm1637.set(6); tm1637.init(); tm1637.clearDisplay(); tm1637.point(displayPoint);
  tm1637.display(0, 0xF); tm1637.display(1, 0xF); tm1637.display(2, 0xF); tm1637.display(3, 0xF);
  // Start up the ds18b20
  sensors.begin();
  // off led
  pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW);
  // -
  varInit(); // init start variable
  wdt_enable(WDTO_8S); // start WDT by 8 sec
}

void loop() {
  wdt_reset(); // reset WDT, without this, arduino reset after wdt time
  current_millis = millis(); // main timer
  // get temp
  if ((current_millis - timer_get_temp) >= period_get_temp) {
    if (flagReqTemp) { // request temp on ds18b20
      sensors.requestTemperatures(); // request temp on ds18b20
      flagReqTemp = false; // off request temp on ds18b20
    } else if ((current_millis - timer_get_temp) >= (period_get_temp + 1500UL)) { // read temp from ds18b20 after 1,5 sec after request temp
      timer_get_temp = current_millis; // reset timeg get temp
      flagReqTemp = true; // on request temp on ds18b20 on next start timer
      currentTemp = (sensors.getTempCByIndex(0)) * 100; // read temp from ds18b20
      if (currentTemp < (min_temp_on_alarm * 100)) { // if temp < temp alarm
        if (smsON == 1) sendSMS(admin_phone, "No TEMP!"); // send sms
      } else { // if temp > temp alarm
        smsON = 1; // on alarm sms
      }
    }
  }
  // actions every sec
  if ((current_millis - timer_sec) >= 1000UL) {
    timer_sec = current_millis; // reset timer every sec
    // show q modem and temp
    tm1637.point(displayPoint = !displayPoint); // inversion and show point
    showQNetTemp(); // show digits on tm1637
    // other action every sec
  }
  // read sms
  if (((current_millis - timer_read_sms) >= period_read_sms) && (wait_response == 0) && (modem_step == 101)) {
    timer_read_sms = current_millis; // reset timer read sms
    // set mode read sms
    modem_step = 10;
  }
  // send sms
  if (((current_millis - timer_send_sms) >= period_send_sms) && (wait_response == 0) && (modem_step == 101) && (sender[0] > 0) && (sms_buf[0] > 0)) {
    timer_send_sms = current_millis; // reset timer send sms
    // send sms
    modem_step = 20;
  }
  // read quality net
  if (((current_millis - timer_get_qnet) >= period_get_qnet) && (wait_response == 0) && (modem_step == 101)) {
    timer_get_qnet = current_millis; // reset timer get quality of net
    // read quality net
    modem_step = 9;
  }
  // modem work
  loadDataFromModem(); // load data
  mainLoopModem(); // main actions on modem
}

void varInit() {
  modem_step = 100; // first step = wait modem for registration on net
  wait_response = 0; // no wait response
  sms_buf[0] = 0; // clear sms
  sender[0] = 0; // clear sender
  smsON = 1; // on alarm sms
  next_step_by_cmd = 102; // no next cmd
  if (EEPROM.read(pos_eeprom_gprs_on) == 119) gprsON = 1; else gprsON = 0; // read config gprs on
  // off/init external devices
  pinMode(ledRed_pin, OUTPUT); LEDREDOFF; pinMode(ledGre_pin, OUTPUT); LEDGREOFF;
  pinMode(relay1_pin, OUTPUT); RELAY1OFF; pinMode(relay2_pin, OUTPUT); RELAY2OFF; pinMode(relay3_pin, OUTPUT); RELAY3OFF;
}

void mainLoopModem() { // main action modem
  static byte posTempBuf;
  switch (modem_step) {
    case 100: {
        if ((current_millis - timer_reg_modem) >= 30000UL) { // wait 30sec registration modem
          wait_response = 0; // do not response
          modem_step = 0; // go to init modem mode - first command
        }
        break;
      }
    case 101: {
        if ((next_step_by_cmd != 102) && (next_step_by_cmd < 100)) {
          wait_response = 0;
          modem_step = next_step_by_cmd;
          next_step_by_cmd = 102;
          break;
        }
        if (pos_buf == 0) break; // no response
        // find ring and other commands and execute
        if ((posSubStrFromPos(resp_buf, pos_buf, "+CLIP: ", 0) > 0) && (posSubStrFromPos(resp_buf, pos_buf, admin_phone, 0) > 0)) { // we have ring by admin
          wait_response = 0;
          modem_step = 25; // answer on ring
          break;
        }
        // other find
        break;
      }
    default: {
        switch (wait_response) {
          case 0: {
              sendAtCommand(modem_step); // send command to modem
              break;
            }
          case 1: {
              if ((current_millis - timer_reg_modem) >= period_fixed_wait) { // if end timer wait response
                // we have problem - no correct response from modem
                // reset modem
                // new init modem and start new main loop
                timer_reg_modem = current_millis; // reset registration modem timer
                varInit(); // init - like ferst start device
              } else {
                if (pos_buf == 0) break; // no response
                // action before find response, example long ussd
                switch (modem_step) {
                  case 19: {
                      if (posSubStrFromPos(resp_buf, pos_buf, "+CUSD: ", 0) > 0) { // we have first line ussd balance
                        posTempBuf = 0;
                        while ((resp_buf[posTempBuf] > 0) && (posTempBuf < pos_buf) && (posTempBuf < (max_size_temp_buf - 1))) {
                          temp_buf[posTempBuf] = resp_buf[posTempBuf];
                          ++posTempBuf;
                        }
                        temp_buf[posTempBuf] = 0;
                      } else {
                        if ((posTempBuf > 0) && (posTempBuf < (max_size_temp_buf - 1))) { // we have next lines ussd balance
                          byte i = 0;
                          while ((resp_buf[i] > 0) && (i < pos_buf) && (posTempBuf < (max_size_temp_buf - 1))) {
                            temp_buf[posTempBuf] = resp_buf[i];
                            ++posTempBuf; ++i;
                          }
                          temp_buf[posTempBuf] = 0;
                        }
                      }
                      break;
                    }
                }
                if (findRespParts() == 1) { // need fixed response
                  // good response from modem
                  // other action on one item command
                  switch (modem_step) {
                    case 19: {
                        if (posTempBuf > 0) {
                          byte sbeg = posSubStrFromPos(temp_buf, posTempBuf, "+CUSD: ", 0);
                          if (sbeg > 0) {
                            sbeg = posSubStrFromPos(temp_buf, posTempBuf, ",\"", (sbeg + 7));
                            ++sbeg; // begin string ussd
                            // find digits 003x and points = max 9 char
                            byte charPos = 0;
                            sms_buf[charPos] = 'b'; ++charPos;
                            while (sbeg < posTempBuf) {
                              if (((temp_buf[sbeg] == '0') && (temp_buf[sbeg + 1] == '0') && (temp_buf[sbeg + 2] == '3') && (temp_buf[sbeg + 3] >= '0') && (temp_buf[sbeg + 3] <= '9')) ||
                                  ((temp_buf[sbeg] == '0') && (temp_buf[sbeg + 1] == '0') && (temp_buf[sbeg + 2] == '2') && (temp_buf[sbeg + 3] == 'E')) ||
                                  ((temp_buf[sbeg] == '0') && (temp_buf[sbeg + 1] == '0') && (temp_buf[sbeg + 2] == '2') && (temp_buf[sbeg + 3] == 'C'))) {
                                if (charPos < 9) {
                                  if ((temp_buf[sbeg + 3] >= '0') && (temp_buf[sbeg + 3] <= '9')) {
                                    sms_buf[charPos] = temp_buf[sbeg + 3]; ++charPos;
                                  } else {
                                    if (temp_buf[sbeg + 3] == 'E') {
                                      sms_buf[charPos] = '.'; ++charPos;
                                    } else {
                                      sms_buf[charPos] = ','; ++charPos;
                                    }
                                  }
                                } else {
                                  break;
                                }
                              } else {
                                if (charPos < 9) {
                                  sms_buf[charPos] = ' '; ++charPos;
                                } else {
                                  break;
                                }
                              }
                              sbeg += 4;
                            }
                            sms_buf[charPos] = 0;
                            sendSMS(admin_phone, sms_buf);
                          }
                        }
                        // go to next step
                        wait_response = 0; // do not wait response
                        modem_step = strToByte(commands_at[6 * modem_step + 5]); // to next at command or mode
                        pos_buf = 0; // clear response buf
                        break;
                      }
                    case 24: { // end send sms
                        sender[0] = 0; sms_buf[0] = 0; // clear sms buf
                        // go to next step
                        wait_response = 0; // do not wait response
                        modem_step = strToByte(commands_at[6 * modem_step + 5]); // to next at command or mode
                        pos_buf = 0; // clear response buf
                        break;
                      }
                    case 9: { // exist quality of net
                        int bp = strPos(resp_buf, "CSQ: ");
                        byte i = 0;
                        char qn[4];
                        if (bp >= 0) {
                          bp += 5;
                          while (resp_buf[bp] != ',') {
                            qn[i] = resp_buf[bp];
                            ++bp; ++i;
                          }
                          qn[i] = 0;
                          currentQNet = strToByte(qn);
                        }
                        // go to next step
                        wait_response = 0; // do not wait response
                        modem_step = strToByte(commands_at[6 * modem_step + 5]); // to next at command or mode
                        pos_buf = 0; // clear response buf
                        break;
                      }
                    case 10: {
                        // find command from sms
                        if (posSubStrFromPos(resp_buf, pos_buf, "+CMGL: ", 0) > 0) { // we have sms
                          int sbeg = 0;
                          int spos;
lsms1: if (sbeg < pos_buf) {
                            spos = posSubStrFromPos(resp_buf, pos_buf, "+CMGL: ", sbeg);
                            if (spos > 0) {
                              spos += 7;
                              if (spos < pos_buf) {
                                spos = posSubStrFromPos(resp_buf, pos_buf, admin_phone, spos);
                                if (spos > 0) {
                                  spos += 7;
                                  spos = posSubStrFromPos(resp_buf, pos_buf, "\r\n", spos);
                                  if (spos > 0) {
                                    ++spos; // begin text sms
                                    byte ends = posSubStrFromPos(resp_buf, pos_buf, "\r\n", spos); // end text sms
                                    if (ends > 0) {
                                      for (byte i = 0; i < count_ext_commands; ++i) {
                                        if (posSubStrFromPos(resp_buf, ends, ext_commands[i], spos) > 0) {
                                          execExtCmd(i);
                                        }
                                      }
                                      sbeg = ends;
                                      goto lsms1;
                                    }
                                  }
                                }
                              }
                            }
                          }
                          // go to step delete SMS
                          wait_response = 0; // do not wait response
                          modem_step = 11; // step 11
                          pos_buf = 0; // clear response buf
                        } else {
                          // go to next step
                          wait_response = 0; // do not wait response
                          modem_step = strToByte(commands_at[6 * modem_step + 5]); // to next at command or mode
                          pos_buf = 0; // clear response buf
                        }
                        break;
                      }
                    default: {
                        // go to next step
                        wait_response = 0; // do not wait response
                        modem_step = strToByte(commands_at[6 * modem_step + 5]); // to next at command or mode
                        pos_buf = 0; // clear response buf
                      }
                  }
                }
              }
              break;
            }
          case 2: {
              if ((current_millis - timer_reg_modem) >= period_data_wait) { // if end timer wait response
                // no data or no user action
                // exit from command
                // go to next step
                wait_response = 0; // do not wait response
                modem_step = strToByte(commands_at[6 * modem_step + 5]); // to next at command or mode
              } else {
                if (pos_buf == 0) break; // no response
                // read parts of sms or other commands
                // go to next step after actions
                wait_response = 0; // do not wait response
                modem_step = strToByte(commands_at[6 * modem_step + 5]); // to next at command or mode
                pos_buf = 0; // clear response buf
              }
              break;
            }
        }
      }
  }
  if (pos_buf >= (max_size_resp_buf - 1)) pos_buf = 0; // clear response buf
}

void loadDataFromModem() {
  boolean getData = false;
  // load data from modem to buf - only one line (string)
  if (gsm.available()) {
    while (gsm.available()) {
      if (pos_buf < (max_size_resp_buf - 1)) { // if exist place in response buf
        byte br = gsm.read();
        //if ((br != 0xD) && (br != 0xA) && (br != 0)) {
        resp_buf[pos_buf] = br;  ++pos_buf; getData = true; // save input byte in response buf
        //}
      } else {
        break;
      }
    }
  }
  if (getData) {
    resp_buf[pos_buf] = 0; // stop byte of string
  }
}

byte findRespParts() { // find response parts in response buf
  byte countresp = 0, countgood = 0;
  if (resp1[0] > 0) { // exist response 1
    ++countresp;
    if (strPos(resp_buf, resp1) >= 0) {
      flresp1 = 1; // found response 1
    }
    if (resp2[0] > 0) { // exist response 2
      ++countresp;
      if (strPos(resp_buf, resp2 ) >= 0) {
        flresp2 = 1; // found response 2
      }
      if (resp3[0] > 0) { // exist response 3
        ++countresp;
        if (strPos(resp_buf, resp3) >= 0) {
          flresp3 = 1; // found response 3
        }
      }
    }
  }
  if (flresp1 == 1) {
    ++countgood;
  }
  if (flresp2 == 1) {
    ++countgood;
  }
  if (flresp3 == 1) {
    ++countgood; // calculate good response
  }
  if ((countresp > 0) && (countresp == countgood)) return 1; else return 0;
}

void sendAtCommand(byte numcmd) { // send at command if modem not busy
  // find key worlds
  if (posSubStrFromPos(commands_at[6 * numcmd], strlen(commands_at[6 * numcmd]), "~sender~", 0) > 0) {
    byte i = 0; while (sender[i] > 0) {
      gsm.write(sender[i]);  ++i;
    }
  } else if (posSubStrFromPos(commands_at[6 * numcmd], strlen(commands_at[6 * numcmd]), "~textsms~", 0) > 0) {
    byte i = 0; while (sms_buf[i] > 0) {
      gsm.write(sms_buf[i]);  ++i;
    }
  } else if (posSubStrFromPos(commands_at[6 * numcmd], strlen(commands_at[6 * numcmd]), "~ctrlz~", 0) > 0) {
    gsm.print(char(26));
  } else {
    gsm.print(commands_at[6 * numcmd]); // send cmd from cmd buf by number cmd
  }
  timer_reg_modem = current_millis; // reset wait response timer
  // load response from command to buf
  if (commands_at[6 * numcmd + 1][0] == 0) { // no response
    resp1[0] = 0; resp2[0] = 0; resp3[0] = 0; // no response
    wait_response = 0;
    modem_step = strToByte(commands_at[6 * numcmd + 5]); // go to next step or mode
    pos_buf = 0; // clear response buf
    return; // next loop
  } else {
    byte i = 0; while ((resp1[i] = commands_at[6 * numcmd + 1][i]) > 0) ++i; resp1[i] = 0; // save first response from buf at cmd
    if (commands_at[6 * numcmd + 2][0] == 0) { // no second response
      resp2[0] = 0; resp3[0] = 0; // no response 2 and 3
    } else {
      byte i = 0; while ((resp2[i] = commands_at[6 * numcmd + 2][i]) > 0) ++i; resp2[i] = 0; // save secont response from buf at cmd
      if (commands_at[6 * numcmd + 3][0] == 0) { // no thr response
        resp3[0] = 0; // no response 3
      } else {
        byte i = 0; while ((resp3[i] = commands_at[6 * numcmd + 3][i]) > 0) ++i; resp3[i] = 0; // save thr response from buf at cmd
      }
    }
  }
  wait_response = strToByte(commands_at[6 * numcmd + 4]); // set wait resp from command buf
  flresp1 = 0; flresp2 = 0; flresp3 = 0; // clear result response
  pos_buf = 0; // clear response buf
}

void sendSMS(char *phnum, char *txtsms) { // prepare sender phone number and text for sms
  byte i = 0; // position in sender or text string
  sender[i] = '+'; ++i; sender[i] = '7'; ++i; // add +7 to sender
  while ((sender[i] = phnum[i - 2]) > 0) ++i; // add phone number to sender
  sender[i] = 0; // stop byte
  i = 0; // begin text sms string
  while (((sms_buf[i] = txtsms[i]) > 0) && (i < (max_size_sms_buf - 1))) ++i; // add text to sms buf
  i = addTextUptime(sms_buf, i, max_size_sms_buf); // add uptime text to sms buf
  sms_buf[i] = 0; // stop byte - end string
}

byte addTextUptime(char *text, byte firstpos, byte maxsize) { // add uptime text to input text
  byte days = current_millis / 86400000UL;
  byte hours = (current_millis % 86400000UL) / 3600000UL;
  byte mins = ((current_millis % 86400000UL) % 3600000UL) / 60000UL;
  if (firstpos >= (maxsize - 1)) return firstpos;
  text[firstpos] = 'u'; ++firstpos; if (firstpos >= (maxsize - 1)) return firstpos;
  if (days > 9) {
    text[firstpos] = (days / 10) + '0'; ++firstpos; if (firstpos >= (maxsize - 1)) return firstpos;
  }
  text[firstpos] = (days % 10) + '0'; ++firstpos; if (firstpos >= (maxsize - 1)) return firstpos;
  text[firstpos] = 'd'; ++firstpos; if (firstpos >= (maxsize - 1)) return firstpos;
  if (hours > 9) {
    text[firstpos] = (hours / 10) + '0'; ++firstpos; if (firstpos >= (maxsize - 1)) return firstpos;
  }
  text[firstpos] = (hours % 10) + '0'; ++firstpos; if (firstpos >= (maxsize - 1)) return firstpos;
  text[firstpos] = 'h'; ++firstpos; if (firstpos >= (maxsize - 1)) return firstpos;
  if (mins > 9) {
    text[firstpos] = (mins / 10) + '0'; ++firstpos; if (firstpos >= (maxsize - 1)) return firstpos;
  }
  text[firstpos] = (mins % 10) + '0'; ++firstpos;  if (firstpos >= (maxsize - 1)) return firstpos;
  text[firstpos] = 'm'; ++firstpos;
  return firstpos;
}

void showQNetTemp() { // show digits on tm1637
  byte ssq = constrain(currentQNet, 0, 99); byte sst = constrain(currentTemp / 100, 0, 99);
  tm1637.display(0, ssq / 10); tm1637.display(1, ssq % 10); tm1637.display(2, sst / 10); tm1637.display(3, sst % 10);
}

char* LastPos(char *str1, char *str2) { // find substring in string
  int L1 = strlen(str1);
  int L2 = strlen(str2);
  for (int i = L1 - L2; i >= 0; i--)
  {
    int j = 0;
    for (; j < L2; j++)
      if ((str1[i + j] != str2[j]))
        break;
    if (j == L2)
      return str1 + i;
  }
  return 0;
}

int strPos(char *str11, char *str22) { // find position substring in string
  char*p = LastPos(str11, str22);
  int n = p - str11;
  return n;
}

byte strToByte(char *instr) {
  byte sl = strlen(instr);
  if (sl < 1) return 0;
  byte res = 0, i = 0;
  while (instr[i] > 0) {
    res += ((instr[i] - '0') * pow(10, (sl - i - 1)));
    ++i;
  }
  if (sl > 2) ++res;
  return res;
}

byte posSubStrFromPos(char *inBuf, byte maxPosBuf, char *subStr, byte resPos) {
  if (resPos >= maxPosBuf) return 0;
  while ((resPos < maxPosBuf) && (inBuf[resPos] > 0)) {
    byte isub = 0;
    if (inBuf[resPos] == subStr[isub]) {
      byte sslen = strlen(subStr);
      while (((resPos + isub) < maxPosBuf) && (inBuf[resPos + isub] > 0) && (isub < sslen)) {
        if (subStr[isub] != inBuf[resPos + isub]) break;
        ++isub;
      }
      if (isub >= sslen)   {
        ++resPos;
        return resPos;
      }
    }
    ++resPos;
  }
  return 0;
}

void execExtCmd(byte numCmd) {
  switch (numCmd) {
    case 0: {
        char sout[4];
        byte i = 0;
        sout[i] = 'q'; ++i;
        if (currentQNet > 9) {
          sout[i] = ((currentQNet / 10) + '0'); ++i;
        }
        sout[i] = ((currentQNet % 10) + '0'); ++i;
        sout[i] = 0;
        sendSMS(admin_phone, sout);
        break;
      }
    case 1: {
        next_step_by_cmd = 19;
        break;
      }
    case 2: {
        char sout[7];
        byte i = 0;
        sout[i] = 't'; ++i;
        if (currentTemp > 999) {
          sout[i] = ((currentTemp / 1000) + '0'); ++i;
        }
        sout[i] = (((currentTemp % 1000) / 100) + '0'); ++i;
        sout[i] = '.'; ++i;
        sout[i] = (((currentTemp % 1000) % 100) / 10 + '0'); ++i;
        sout[i] = (((currentTemp % 1000) % 100) % 10 + '0'); ++i;
        sout[i] = 0;
        sendSMS(admin_phone, sout);
        break;
      }
    case 3: {
        smsON = 1;
        break;
      }
    case 4: {
        smsON = 0;
        break;
      }
    case 5: {
        RELAY1ON;
        break;
      }
    case 6: {
        RELAY1OFF;
        break;
      }
    case 7: {
        RELAY2ON;
        break;
      }
    case 8: {
        RELAY2OFF;
        break;
      }
    case 9: {
        RELAY3ON;
        break;
      }
    case 10: {
        RELAY3OFF;
        break;
      }
    case 11: {
        gprsON = 1;
        EEPROM.write(pos_eeprom_gprs_on, 119);
        break;
      }
    case 12: {
        gprsON = 0;
        EEPROM.write(pos_eeprom_gprs_on, 0);
        break;
      }
  }
}

 

 

Short Circuit
Short Circuit аватар
Offline
Зарегистрирован: 17.05.2015

А можно вопросы по отдельным кускам программы позадавать? хочу подробнее разобраться.

другим тоже надеюсь будет интересно увидеть более подробные обьяснения.

 

кетч использует 12078 байт (37%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 1517 байт (74%) динамической памяти, оставляя 531 байт для локальных переменных. Максимум: 2048 байт.
 
ух... 74%  - дофига..  за счет чего так?
andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Эх...я то не против, но это ж сколько времени надо объяснять, где столько времени взять, куча текстовых строк + буфер приёма от модема вот и память забита, progmem используйте, тут целая тема есть - все разжеванно

Short Circuit
Short Circuit аватар
Offline
Зарегистрирован: 17.05.2015

ну хотя бы снабдили строки  скетча пояснениями -  что  выполняется там..

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

поэтому и откликов на вашу ветку будет просто минимум.. потратили бы пол-часа и добавили пояснения..

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Ну во первых за последние несколько месяцев только вам понадобились комментарии к коду)

Во вторых если есть вопросы конкретные - задавайте

Я не блогер, которым нехрен делать вот они и пишут статьи с кучей комментариев, ардуино для меня как и для многих на форуме это просто хобби, и тратить время на комментарии мне лень и некогда

Алгоритм банален - куча команд модема со списком ответов, если ответы не совпадают - проблема

Short Circuit
Short Circuit аватар
Offline
Зарегистрирован: 17.05.2015

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

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Short Circuit пишет:

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

Хватит уже воздух сотрясать, есть конкретные вопросы по коду - задавайте, не забыв указать номера строк.

ЗЫ. Если почаще прочитаете форумы по вопросам работы sim800 то увидите что основные вопросы по классической схеме работы с string и delay, много ссылаются на сайт codius (хорошие кстати статьи), а более сложные варианты людям не интересны, так как уровень использования модуля бытовой - поиграться. А для более надёжной работы надо самим мозг включать что тяжело для современных "жертв ЕГЭ"

Short Circuit
Short Circuit аватар
Offline
Зарегистрирован: 17.05.2015

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

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Реинкарнация устройства в новом железе, с резервным питанием и более распространенным модемом SIM800L
Устройство шлет СМС вида
PowerON CSQ:13 temp:25.0 uptime:0d2h
при включении/отключении питания и при получении команды getdata
При получении команды devreset сбрасывается модем, потом само устройство.

- схема

Повышайку DC-DC-STEP-UP до 5 вольт можно использовать любую.
На схеме не указаны, но ябязательно к установке конденсаторы по питанию на МК и модем, электролит+керамика.

- код

#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include "options.h" // настройки железки
#include "dmfcomm.h" // основные переменные и функции
#include "modinit.h" // инициализация модема
#include "dmfsms.h" // работа с СМС

// инициализация WDT (код из даташита + из форума, хз как работает)
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((used)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

void inCallbackSMS(char * fromSMS, char * textSMS) { // обработчик входящего сообщения
  static char toSendPhone[max_size_abonent_sms];
  if (strcmp(fromSMS, ADMIN_PHONE) == 0) {
    //Serial.println(F("SMS from Admin phone"));
    // find command from sms
    if (findSubstring((char *)"getdata", textSMS)) {
      //Serial.println(F("Input command: getdata"));
      // exec cmd
      strcpy(toSendPhone, (char *)ADMIN_PHONE);
      sendSMS(toSendPhone, prepareStandartSMS());
    } else if (findSubstring((char *)"devreset", textSMS)) {
      //Serial.println(F("Input command: devreset"));
      modemReset();
      delay(10000);
    }
  } else {
    //Serial.print(F("SMS from: ")); Serial.println(fromSMS);
  }
  //Serial.println(textSMS);
}

void processModemActions() { // основная процедуры работы/общение с модемом
  unsigned char br = 0; // переменная куда падает входящий байт из модема
  _Bool flbr = false; // влаг что пришедший байт не равен нулю
  if (modemAvailable()) { // если что то пришло из модема
    br = modemRead(); // читаем пришедший байт
    if (br) { // если он больше нуля
      if (posModemBuf >= maxSizeRespBuf) posModemBuf = 0; respBufModem[posModemBuf] = br; ++posModemBuf; // добавляем пришедший байт в круговой буфер
      if ((br == '\r') || (br == '\n') || ((br >= ' ') && (br < 0x7F))) { // если символ печатный или конец строки
        logWrite(br); // выводим его в лог
      } else { // если не печатный
        logHexPrint(br); logWrite(' '); // выводим его шестнадцатиричное представление в лог
      }
      flbr = true; // ставим флаг что пришедший байт больше нуля
    }
  }
  // main process device with modem // тут начинаем запускать все без исключения модули, работающие с информацией/модемом
  if (currentInitModem != modemStop) { // если модем еще не вышел на связь и не зарегестрировался в сети
    processInitModem(br, flbr); // работаем с модемом
    if (currentInitModem == modemRestart) { // если все плохо и необходимо сбросить модем
      modemReset(); // сбрасываем его
      startDmfDevice(); // заново запускаем всю логику работы устройства
      return; // выходим
    } else if (currentInitModem == modemReady) { // если все хорошо со связью
      clearRespBuf(); // очищаем приемный буфер модема
      currentInitModem = modemStop; // останавливаем инициализацию модема
      // go to next step INIT CIP // переходим в обработку
      currentSms = smsBegin;
    }
  }
  // обработка СМС
  if (currentSms != smsStop) { // если нет коннекта к брокеру - работаем
    processSms(br, flbr); // работаем с модемом
    if (currentSms == smsRestart) { // если все плохо и необходимо сбросить модем
      modemReset(); // сбрасываем его
      startDmfDevice(); // заново запускаем всю логику работы устройства
      return; // выходим
    }
  }
  // end main process
}

void startDmfDevice() { // начальные настройки всего модуля
  Serial.println(F("Start!"));
  currentSms = smsStop; // останавливаем работу с модемом
  currentInitModem = modemBegin; // запускаем модем
}

void setup() {
  MCUSR = 0; // отключение WDT
  wdt_disable(); // отключение WDT
  // put your setup code here, to run once:
  initDmfDevice(); // инициализируем пины
  setCallbackSMS(inCallbackSMS);
  startDmfDevice(); // запускаем железку
  wdt_enable(WDTO_8S); // включение WDT
}

void powerOnOffControl(void) {
  static char toSendProblem[max_size_abonent_sms];
  static byte lastLevelPower = 1;
  static byte startControl = 0;
  static unsigned long timerControl;
  byte currentPowerLevel = getExternalPower();
  if (currentPowerLevel != lastLevelPower) {
    if (currentPowerLevel) Serial.println(F("External power ON")); else Serial.println(F("OFF external power"));
    startControl = 1;
    timerControl = currentMillis;
  } else if (startControl) {
    if ((currentMillis - timerControl) >= period_control_ext_power) {
      startControl = 0;
      if (currentInitModem == modemStop) {
        strcpy(toSendProblem, (char *)ADMIN_PHONE);
        sendSMS(toSendProblem, prepareStandartSMS());
      }
    }
  }
  lastLevelPower = currentPowerLevel;
}

void loop() {
  wdt_reset(); // сброс WDT
  //delay(10000); // test WDT // раскоменнтировать строку для проверки WDT , МК будет перезагружаться каждые 10 секунд
  currentMillis = millis(); // главный таймер
  // put your main code here, to run repeatedly:
  processModemActions(); // работаем с модемом
  // get data from temp sensors
  if (dsCount) readTempFromAllSensors();
  // контроль включения/отключения внешнего питания
  powerOnOffControl();
  // выводим данные на дисплейчик
  showOLEDdata();
}

//dmfcomm.cpp // модуль начального инициализирования устройств/пинов и глобальные переменные/функции
#include <Arduino.h>
#include "dmfcomm.h"
#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include <Wire.h>
#include <OzOLED.h>
#include "modinit.h"
#include "dmfsms.h"

unsigned long currentMillis; // главный таймер

byte getExternalPower(void) { // получение значения на пине внешнего питани
  return digitalRead(pinExtPower);
}

void printAllDataOLED(void) { // очистить дисплей и вывести данные
  OzOled.clearDisplay();
  if (getExternalPower()) OzOled.printString("Power ON"); else OzOled.printString("Power OFF");
  OzOled.setCursorXY(0, 2);
  if (currentInitModem == modemStop) OzOled.printString((const char *)operatorName); else OzOled.printString("Starting device");
  OzOled.setCursorXY(0, 3);
  if (currentInitModem == modemStop) { // have CSQ
    OzOled.printString("CSQ: "); OzOled.printNumber((long)operatorCSQ, 5, 3);
  }
  char tmpStr[10];
  OzOled.setCursorXY(0, 5);
  OzOled.printString("Temp: ");
  if ((dsCount) && (dsTemp != errTemp)) {
    word absTemp = dsTemp;
    if (absTemp >= 2000) {
      OzOled.printString("-");
      absTemp -= 2000;
    }
    tmpStr[0] = 0;
    itoa((absTemp / 10), tmpStr, 10);
    OzOled.printString((const char *)tmpStr);
    OzOled.printString(".");
    tmpStr[0] = 0;
    itoa((absTemp % 10), tmpStr, 10);
    OzOled.printString((const char *)tmpStr);
  } else {
    OzOled.printString("error");
  }
  OzOled.setCursorXY(0, 7);
  OzOled.printString("Uptime: ");
  tmpStr[0] = 0;
  itoa((currentMillis / 86400000UL), tmpStr, 10);
  OzOled.printString((const char *)tmpStr);
  OzOled.printString("d ");
  tmpStr[0] = 0;
  itoa(((currentMillis % 86400000UL) / 3600000UL), tmpStr, 10);
  OzOled.printString((const char *)tmpStr);
  OzOled.printString("h");
}

void showOLEDdata(void) { // отображение данных на OLED 128*64 I2C
  static unsigned long showTimer;
  static byte inShow = 1;
  if (currentInitModem != modemStop) { // инциалимзация модема - дисплей не отключаем, обновляем данные каждые 2 секунды
    if ((currentMillis - showTimer) >= 2000UL) {
      showTimer = currentMillis;
      printAllDataOLED();
    }
  } else { // зажигаем по времени
    if (inShow) {
      if ((currentMillis - showTimer) >= 5000UL) {
        inShow = 0;
        OzOled.clearDisplay();
      }
    } else {
      if (currentSms != smsWait) {
        printAllDataOLED();
        inShow = 1;
        showTimer = currentMillis;
      }
    }
  }
}

#include <SoftwareSerial.h> // стандартная библиотека работы с SoftwareSerial

SoftwareSerial swuart(pinSwUART_RX, pinSwUART_TX); // установка контактов

#define TEMPERATURE_PRECISION 9 // точность получения значения темературы
OneWire dsWire(ds18_pin); // объект 1Ware
DallasTemperature sensors(&dsWire); // объект  датчика температуты
byte dsCount; // количество датчиков температуры
DeviceAddress dsAddres; // адрес датчика
word dsTemp; // прочитанное значение температуры, всегда положительное, последний знак - десятичные доли градуса, если отрицательное значение - от 2000

void readTempFromAllSensors(void) { // получаем температуру со всех датчиков
  static unsigned long timer_get_temp = period_get_temp - 10000UL; // таймер получения температуры
  static byte requestTemp = 1; // запрос температуры датчиков
  // get temper // каждое N время получаем температуру со всех датчиков
  if ((currentMillis - timer_get_temp) >= period_get_temp) {
    if (requestTemp) {
      sensors.requestTemperatures(); requestTemp = 0;
    } else if ((currentMillis - timer_get_temp) >= (period_get_temp + 297UL)) { // for 9 bit PRECISION
      timer_get_temp = currentMillis; requestTemp = 1;
      float tct = sensors.getTempC(dsAddres);
      if ((tct >= 95.0) || (tct <= -35.0)) { // температура не получилась
        dsTemp = errTemp;
      } else {
        if (tct < 0) dsTemp = ((-tct) * 10) + 2000; else dsTemp = (tct * 10);
      }
      Serial.print(F("t = ")); Serial.println(dsTemp, DEC); // печать температур в консоль
    }
  }
}

void printAddress(DeviceAddress deviceAddress) { // function to print a device address DS18B20
  for (uint8_t i = 0; i < 8; i++) {
    // zero pad the address if necessary
    if (deviceAddress[i] < 16) Serial.print('0');
    Serial.print(deviceAddress[i], HEX);
    if (i < 7) Serial.print('-');
  }
  Serial.println(); // новая строка
}

void initFindDefineSensors(void) { // инициализация OneWire DS18B20 поиск и чтение адресов всех датчиков температуры
  dsWire.reset(); // сброс шины
  delay(750); // пауза
  wdt_reset(); // сброс WDT
  sensors.begin(); // инициировать датчики температуры
  delay(750); // пауза
  wdt_reset(); // сброс WDT
  dsCount = sensors.getDeviceCount(); // получить количество датчиков
  Serial.print(F("Found DS18B20 = ")); Serial.println(dsCount, DEC); // вывести количество
  if (dsCount) {
    sensors.getAddress(dsAddres, 0); // получение адреса
    sensors.setResolution(dsAddres, TEMPERATURE_PRECISION); // настраиваем разрешение
    printAddress(dsAddres); // печатаем адрес
  }
  dsTemp = errTemp; // температура еще не получена
}

void initLogModemUART() {
  swuart.begin(9600);
  Serial.begin(9600);
}

void logStrPrint(char * inStr) { // вывод строки в лог
  Serial.print(inStr);
}

void logWrite(byte inCh) { // вывод байта в лог
  Serial.write((char)inCh);
}

void logHexPrint(signed long inHex) { // вывод шестнадцатиричного числа в лог
  logWrite('x');
  Serial.print(inHex, HEX);
}

void logDecPrint(signed long inInt) { // вывод десятичного числа в лог
  Serial.print(inInt, DEC);
}

void modemStrPrint(char * inStr) { // строку в модем
  swuart.print(inStr);
}

void modemWrite(byte inCh) { // байт в модем
  swuart.write((char)inCh);
}

byte modemAvailable() { // есть ли данные в модеме
  return swuart.available();
}

byte modemRead() { // чтение из модема
  return swuart.read();
}

void initDmfDevice() { // инициализация всего устройства
  OzOled.init();  //initialze Oscar OLED display
  OzOled.printString("Init...."); //Print the String
  initLogModemUART();
  digitalWrite(pinResetModem, LOW); pinMode(pinResetModem, INPUT); // инициализация пина сброса модема
  digitalWrite(pinExtPower, LOW); pinMode(pinExtPower, INPUT); // инициализация пина получения питания
  initFindDefineSensors();
  if (!dsCount) { // не нашлись датчики, попробуем все сбросить и заново проинициализировать
    dsWire.reset_search(); initFindDefineSensors();
  }
}

void modemReset(void) { // сброс модема
  pinMode(pinResetModem, OUTPUT); delay(115); pinMode(pinResetModem, INPUT);
}

//dmfcomm.h // модуль начального инициализирования устройств/пинов и глобальные переменные/функции
#ifndef DMFCOMM_H
#define DMFCOMM_H

#include <Arduino.h>
#include "options.h" // модуль пользовательских настроек
#include <DallasTemperature.h>
#include <OneWire.h>

extern byte dsCount; // количество датчиков температуры
extern word dsTemp; // прочитанное значение температуры, всегда положительное, последний знак - десятичные доли градуса, если отрицательное значение - от 2000
void readTempFromAllSensors(void); // получаем температуру со всех датчиков

byte getExternalPower(void); // получение значения на пине внешнего питани

void showOLEDdata(void); // отображение данных на OLED 128*64 I2C

extern unsigned long currentMillis;
void initDmfDevice();
void modemReset(void); // сброс модема
void logStrPrint(char * inStr);
void logWrite(byte inCh);
void logHexPrint(signed long inHex);
void logDecPrint(signed long inInt);
void modemStrPrint(char * inStr);
void modemWrite(byte inCh);
byte modemAvailable();
byte modemRead();

#endif  /* DMFCOMM_H */

// dmfsms.cpp - модуль работы с СМС
#include <Arduino.h>
#include "dmfsms.h"
#include "dmfcomm.h"
#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include "modinit.h" // инициализация модема

enum typeSmsStep currentSms; // переменная - текущий шаг
_Bool waitRespSms; // флаг ожидания ответа на команду
unsigned long timerWorkCmd; // рабочий таймер команд
word balancePhone = 0; // баланс SIM карты телефона, целое число, для получения нужной суммы - поделить на 100

byte flNeedSendSMS;
char * outSmsPhone;
char * outTextSms;

static void (*callbackReadOneSMS)(char *, char *); // внешний обработчик пришедшей СМС

void sendSMS(char * toPhone, char * smsText) { // отправка сообщения
  outSmsPhone = toPhone; outTextSms = smsText;
  flNeedSendSMS = 1;
}

char * prepareStandartSMS(void) { // делает типовую СМСку питание/температуру/аптайм
  static char outTextToSMS[64];
  outTextToSMS[0] = 0;
  if (getExternalPower()) strcpy(outTextToSMS, "PowerON"); else strcpy(outTextToSMS, "PowerOFF"); // 9 char
  char tmpStr[10]; tmpStr[0] = 0;
  itoa(operatorCSQ, tmpStr, 10);
  strcat(outTextToSMS, " CSQ:"); // 6 char
  strcat(outTextToSMS, tmpStr); // 4 char
  strcat(outTextToSMS, " temp:"); // 7 char
  if (dsTemp == errTemp) {
    strcat(outTextToSMS, "Err"); // 4 char
  } else {
    word absTemp = dsTemp;
    if (absTemp >= 2000) {
      absTemp -= 2000;
      strcat(outTextToSMS, "-"); // 2 char
    }
    tmpStr[0] = 0;
    itoa((absTemp / 10), tmpStr, 10);
    strcat(outTextToSMS, tmpStr); // 4 char
    strcat(outTextToSMS, "."); // 2 char
    tmpStr[0] = 0;
    itoa((absTemp % 10), tmpStr, 10);
    strcat(outTextToSMS, tmpStr); // 2 char
  }
  strcat(outTextToSMS, " uptime:"); // 9 char
  tmpStr[0] = 0;
  itoa((currentMillis / 86400000UL), tmpStr, 10);
  strcat(outTextToSMS, tmpStr); // 5 char
  strcat(outTextToSMS, "d"); // 2 char
  tmpStr[0] = 0;
  itoa(((currentMillis % 86400000UL) / 3600000UL), tmpStr, 10);
  strcat(outTextToSMS, tmpStr); // 5 char
  strcat(outTextToSMS, "h"); // 2 char
  return outTextToSMS;
}

byte findSubstring(char * subStr, char * inStr) { // поиск подстроки в строке, используется для поиска команд в СМСке
  byte ssLen = strlen(subStr);
  byte isLen = strlen(inStr);
  if (ssLen <= isLen) {
    byte flGood = 0;
    for (byte startFindPosInStr = 0; startFindPosInStr <= (isLen - ssLen); ++startFindPosInStr) {
      byte countFound = ssLen;
      for (byte posSubStr = 0; posSubStr < ssLen; ++posSubStr) {
        if (toupper(subStr[posSubStr]) == toupper(inStr[posSubStr + startFindPosInStr])) --countFound; else break;
      }
      if (!countFound) {
        flGood = 1; break;
      }
    }
    if (flGood) return 1;
  }
  return 0;
}


void setCallbackSMS(void (*inCallback)(char *, char *)) { // установка обработчика сообщения
  callbackReadOneSMS = inCallback;
}

void parseSmsText(byte newSms, const unsigned char inSms) { // парсинг тескта СМС
  static char abonentNumber[max_size_abonent_sms];
  static char textSMS[max_size_len_read_sms];
  static byte parseStep = 0; // шаг работы
  if (newSms) {
    parseStep = 0; return;
  }
  if (findRespFromBuf((char *)"\r\n", inSms)) { // прилетел конец строки
    if ((abonentNumber[0] != 0) && (parseStep == 5)) { // номер абонента введен
      parseStep = 6; // get text sms
      //Serial.println((char *)abonentNumber);
    } else if ((textSMS[0] != 0) && (parseStep == 6)) { // текст СМС выведен
      parseStep = 0;
      //Serial.println((char *)textSMS);
      wdt_reset(); // сброс WDT
      // exec callback
      callbackReadOneSMS(abonentNumber, textSMS);
    }
  } else { // смотрим побайтно
    switch (parseStep) {
      case 0: {
          memset(abonentNumber, 0, max_size_abonent_sms); memset(textSMS, 0, max_size_len_read_sms); // стерли строки
          if (isdigit(inSms)) ++parseStep; else parseStep = 0;
          break;
        }
      case 1: {
          if (inSms == ',')  ++parseStep; else parseStep = 0;
          break;
        }
      case 2: {
          if (inSms == '\"')  ++parseStep; else parseStep = 0;
          break;
        }
      case 3: {
          if (findRespFromBuf((char *)"\",\"+", inSms)) ++parseStep;
          break;
        }
      case 4: { // phone number
          if (inSms == '\"') {
            ++parseStep;
          } else if (isdigit(inSms)) {
            byte len = strlen((char *)abonentNumber);
            if (len < (max_size_abonent_sms - 1)) {
              abonentNumber[len] = inSms; abonentNumber[len + 1] = 0;
            }
          } else {
            parseStep = 0;
          }
          break;
        }
      case 5: { // dont need data - wait new string
          break;
        }
      case 6: { // text SMS
          byte len = strlen((char *)textSMS);
          if (len < (max_size_len_read_sms - 1)) {
            textSMS[len] = inSms; textSMS[len + 1] = 0;
          }
          break;
        }
      default: {}
    }
  }
}

void smsMODEMcmd(char * inStr, const _Bool showLog) { // отправка модему команды
  modemStrPrint(inStr); // кидаем строку модему
  if (showLog) {
    logStrPrint(inStr); // и если нужно - дублируем в лог
  }
}

void sendATsmsByIdx(byte cmdIdx, const _Bool flClearBuf) { // отправка команды модему из progmem
  char inStr[maxSizeBrokerID];
  const char * addrStroki = pgm_read_word_near((int)(at_list_smsc + cmdIdx));
  strcpy_P(inStr, addrStroki);
  smsMODEMcmd(inStr, true); // отправляем с дублированием в логе
  if (flClearBuf) clearRespBuf(); // если необходимо - чистим буфер
  waitRespSms = true; // ждем ответа на команду
  timerWorkCmd = currentMillis; // обнуляем основной таймер ожидания
}

void processSms(const unsigned char inByte, const _Bool inExist) { // work sms
  static byte stepSms; // рабочий текущий шаг
  static unsigned long timerReadSms; // рабочий таймер запуск чтения всех смс
  static unsigned long timerGetBalance; // рабочий таймер получение баланса телефона
  switch (currentSms) { // работаем по текущему шагу
    case smsBegin: { // самое начало
        timerReadSms = currentMillis; // сбросим таймер
        timerGetBalance = currentMillis; // сбросим таймер
        flNeedSendSMS = 0;
        currentSms = smsWait; // переходим в режим ничего неделания
        break;
      }
    case smsWait: { // ничего не делаем - ждем команды
        if ((currentMillis - timerReadSms) >= period_read_sms) { // пора читать СМС
          timerReadSms = currentMillis; // сбросим таймер
          stepSms = 0; // начать с начала
          currentSms = smsReadAll; // запускаем чтение СМС
          //callbackReadOneSMS = STcallbackReadOneSMS;
        } else if ((currentMillis - timerGetBalance) >= period_get_balance) { // пора получить баланс
          timerGetBalance = currentMillis; // сбросим таймер
          stepSms = 0; // начать с начала
          currentSms = smsGetBalance;
        } else if (findRespFromBuf((char *)"+CUSD:", inByte)) { // USSD пришло
          // тут мы должны перевести в шаг парсить ответ
          balancePhone = 0;
          // но мой Мегафон ничего не выдает, или прошивка модема дурит, поэтому данный код не дописан
        } else if ((outSmsPhone[0] > 0) && (outTextSms[0] > 0) && (flNeedSendSMS)) { // если текст абонента получателя и текст СМС не пустые
          stepSms = 0; // начать с начала
          currentSms = smsSend;
        }
        break;
      }
    case smsSend: {
        switch (stepSms) { // работаем по шагам
          case 0: {
              sendATsmsByIdx(3, true);
              smsMODEMcmd(outSmsPhone, true); // отправляем с дублированием в логе
              smsMODEMcmd((char *)"\"\r", true); // отправляем с дублированием в логе
              ++stepSms;
              break;
            }
          case 1: {
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (inByte == '>') { // ждем ОК на предыдущую команду
                  smsMODEMcmd(outTextSms, true); // отправляем с дублированием в логе
                  modemWrite(0x1A); // отправляем конец строки
                  ++stepSms;
                  flNeedSendSMS = 0; outSmsPhone[0] = 0; outTextSms[0] = 0;
                }
              }
              break;
            }
          case 2: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= 60000UL) { // если не пришел ответ на короткую команду // СМСки должго отправляются, ждем 60 секунд согласно даташита SIM800L
              currentSms = smsRestart; // надо сбросить модем
              return; // выходим
            } else if (inExist) {
              if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    case smsGetBalance : {
        switch (stepSms) { // работаем по шагам
          case 0: {
              sendATsmsByIdx(2, true); ++stepSms;
              break;
            }
          case 1: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    case smsReadAll: { // читаем сообщения
        switch (stepSms) { // работаем по шагам
          case 0: { // шлем модему команду
              sendATsmsByIdx(0, true); ++stepSms;
              break;
            }
          case 1: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  sendATsmsByIdx(1, true); ++stepSms; // удаляем СМС
                } else if (findRespFromBuf((char *)"+CMGL: ", inByte)) { // прилетело начало СМСки
                  parseSmsText(1, 0); // пометим что новое сообщение
                } else {
                  parseSmsText(0, inByte); // парсим все что пришло
                }
              }
              break;
            }
          case 2: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    default: {}
  }
}
// dmfsms.h - модуль работы с СМС
#ifndef DMFSMS_H
#define DMFSMS_H

#include <Arduino.h>
#include "options.h" // модуль пользовательских настроек
#include "modinit.h" // работа с модемом

enum typeSmsStep {smsBegin, smsRestart, smsStop, smsWait, smsReadAll, smsGetBalance, smsSend}; // шаги
extern enum typeSmsStep currentSms; // переменная - текущий шаг

void processSms(const unsigned char inByte, const _Bool inExist); // work sms
void setCallbackSMS(void (*inCallback)(char *, char *)); // установка обработчика сообщения
byte findSubstring(char * subStr, char * inStr); // поиск подстроки в строке, используется для поиска команд в СМСке

extern word balancePhone; // баланс SIM карты телефона, целое число, для получения нужной суммы - поделить на 100

void sendSMS(char * toPhone, char * smsText); // отправка сообщения

char * prepareStandartSMS(void); // делает типовую СМСку питание/температуру/аптайм

const char smsc0[] PROGMEM = "AT+CMGL=\"ALL\"\r";
const char smsc1[] PROGMEM = "AT+CMGDA=\"DEL ALL\"\r";
const char smsc2[] PROGMEM = ussd_balance;
const char smsc3[] PROGMEM = "AT+CMGS=\"+";
// массив всех команд
const char * const at_list_smsc[] PROGMEM = { // text command
  smsc0, smsc1, smsc2, smsc3
};

#endif  /* DMFSMS_H */

//modinit.cpp // модуль включения и инициализации модема
#include <Arduino.h>
#include "modinit.h"
#include "dmfcomm.h"

enum typeModemInitStep currentInitModem; // переменная - текущий шаг
unsigned char stepModem; // рабочий текущий шаг
unsigned long timerModem; // основной таймер
unsigned long readyModem; // таймер ожидания регистрации в сети оператора
_Bool waitRespModem; // флаг ожидания ответа на команду
_Bool goodCREG; // флаг успешной регистрации в сети оператора

unsigned char posModemBuf; // текущая позиция буфера
unsigned char respBufModem[maxSizeRespBuf]; // буфер для обработки входящей информации из модема

unsigned char operatorName[maxSizeOperatorName]; // буфер имя оператора
byte operatorCSQ; // уровень связи подключения к оператору

void processOperatorName(const unsigned char inBt) { // парсинг ответа модема вытаскиваем имя оператора связи
  static unsigned char stepName = 0; // текущий шаг
  static unsigned char currChar = 0; // текущий символ
  switch (stepName) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 101: { // ждем запятую
        if (inBt == ',') ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 102: { // ждем цифру
        if (isdigit((char)inBt)) ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 103: { // ждем запятую
        if (inBt == ',') ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 104: { // ждем кавычки
        if (inBt == '\"') {
          ++stepName; // если да - переходим на другой шаг
          currChar = 0;
        } else {
          stepName = 0;
        }
        break;
      }
    case 105: { // заполняем строку или при приходе кавычки выходим
        if (inBt == '\"') {
          stepName = 0;
        } else if ((currChar < (maxSizeOperatorName - 1)) && (inBt >= 0x20) && (inBt < 0x7f)) {
          operatorName[currChar] = inBt; ++currChar; operatorName[currChar] = 0;
        }
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+COPS: ", inBt)) stepName = 100; // ждем строку +COPS И начинаем пошагово обрабатывать символы
      }
  }
}

void processOperatorCSQ(const unsigned char inBt) { // парсинг ответа модема вытаскиваем качество связи
  static unsigned char stepCSQ = 0; // текущий шаг
  switch (stepCSQ) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) {
          operatorCSQ = inBt - '0';
          ++stepCSQ; // если да - переходим на другой шаг
        } else {
          stepCSQ = 0;
        }
        break;
      }
    case 101: { // ждем или цифру или запятую
        if (isdigit((char)inBt)) {
          operatorCSQ = operatorCSQ * 10 + (inBt - '0');
        }
        stepCSQ = 0;
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+CSQ: ", inBt)) stepCSQ = 100; // ждем строку +CSQ И начинаем пошагово обрабатывать символы
      }
  }
}

_Bool processCREG(const unsigned char inBt) { // парсинг ответа модема на команду AT+CREG
  static unsigned char stepCREG = 0; // текущий шаг
  switch (stepCREG) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) ++stepCREG; else stepCREG = 0; // если да - переходим на другой шаг
        break;
      }
    case 101: { // ждем запятую
        if (inBt == ',') ++stepCREG; else stepCREG = 0; // если да - переходим на другой шаг
        break;
      }
    case 102: { // ждем цифру регистрации в домашней сети или в роуминге
        if ((inBt == '1') || (inBt == '5')) {
          stepCREG = 0; return true; // если да - положительно выходим
        }  else {
          stepCREG = 0;
        }
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+CREG: ", inBt)) stepCREG = 100; // ждем строку +CREG И начинаем пошагово обрабатывать символы
      }
  }
  return false; // иначе выходим с отрицательным результатом
}

void clearRespBuf(void) { // очистка приемного буфера
  memset(respBufModem, 0 , maxSizeRespBuf); // чистим
  posModemBuf = 0; // обнуляем текущую позицию
}

void sendMODEMcmd(char * inStr, const _Bool showLog) { // отправка модему команды
  modemStrPrint(inStr); // кидаем строку модему
  if (showLog) {
    logStrPrint(inStr); // и если нужно - дублируем в лог
  }
}

void sendATcmdByIdx(byte cmdIdx, const _Bool flClearBuf) { // отправка команды модему из progmem
  char inStr[maxSizeBrokerID];
  const char * addrStroki = pgm_read_word_near((int)(at_list_cmd + cmdIdx));
  strcpy_P(inStr, addrStroki);
  sendMODEMcmd(inStr, true); // отправляем с дублированием в логе
  if (flClearBuf) clearRespBuf(); // если необходимо - чистим буфер
  waitRespModem = true; // ждем ответа на команду
  timerModem = currentMillis; // обнуляем основной таймер ожидания
}

void processInitModem(const unsigned char inByte, const _Bool inExist) { // инициалдизация модема
  switch (currentInitModem) { // работаем по текущему шагу
    case modemBegin: { // самое начало
        currentInitModem = modemFirst; // переходим на след шаг
        stepModem = 0; // обнуляем внутренний шаг
        clearRespBuf(); // чистим буфер приема
        timerModem = currentMillis; // обнуляем таймер
        waitRespModem = 0; // не ждем ответ - первый старт
        break;
      }
    case modemFirst: { // начальная инициализация - работает всегда
        if (!waitRespModem) {
          if (((currentMillis - timerModem) >= periodWaitModemBegin) && (!stepModem)) { // ждем начальное время - даем модему прочухаться
            sendATcmdByIdx(0, true); // проверка связи с модемом AT / сброс настроек на default - ATZ
          }
        } else {
          if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
            currentInitModem = modemRestart; // надо сбросить модем
            return; // выходим
          }
          switch (stepModem) {
            case 0: {
                if (inExist) if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                    sendATcmdByIdx(1, true); ++stepModem; // отключение ЭХО, т е модем в обратку не дублирует команды
                  }
                break;
              }
            case 1: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(2, true); ++stepModem; // развернуты подробный ответ от модема
                  }
                break;
              }
            case 2: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(3, true); ++stepModem; // вывод подробных описаний ошибок
                  }
                break;
              }
            case 3: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(4, true); ++stepModem; // закрыть все интернет сессии
                  }
                break;
              }
            case 4: {
                if (inExist) if (findOKfromBuf(inByte)) {
                    sendATcmdByIdx(5, true); ++stepModem; // отключение АОН
                  }
                break;
              }
            case 5: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(6, true); ++stepModem; // модем не берет трубку при входящем звонке
                  }
                break;
              }
            case 6: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(7, true); ++stepModem; // обычный режим вывода СМСе
                  }
                break;
              }
            case 7: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(8, true); ++stepModem; // отключение работы энергосберегающего режима через пин DTR
                    ++stepModem; // пропустим следующую команду // по умолчанию и так в normal mode модем работает
                  }
                break;
              }
            case 8: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(9, true); ++stepModem; // моносокет - единомоментно работаем с одним подключением
                  }
                break;
              }
            case 9: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(10, true); ++stepModem; // не добавлять заголовок при приеме данных
                  }
                break;
              }
            case 10: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(11, true); ++stepModem; // автоматический вывод принятых данных
                  }
                break;
              }
            case 11: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(12, true); ++stepModem; // не показывать данные отправителя при приеме данных
                  }
                break;
              }
            case 12: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    currentInitModem = modemStartReg; // переходим в режим регистрации модема у сотового оператора - основная логика
                  }
                break;
              }
            default: {}
          }
        }
        break;
      }
    case modemStartReg: { // начало регистрации в сети оператора
        stepModem = 0; // начинаем с начала
        currentInitModem = modemReg; // следующий шаг - проверка зарегестрировался ли модем и СИМ карта
        timerModem = currentMillis + periodWaitRegistrationModem; // обнуляем таймер
        readyModem = currentMillis; // обнуляем таймер
        break;
      }
    case modemReg: { // проверяем зарегестрировалась ли симка
        if ((currentMillis - readyModem) >= periodReadyModem) { // если за время отведенное не зарегестрировались
          currentInitModem = modemRestart; // перезагружаемся
          return; // выходим
        }
        switch (stepModem) { // вработаем последовательно по шагам
          case 0: {
              if ((currentMillis - timerModem) >= periodWaitRegistrationModem) { // ждем время
                sendATcmdByIdx(14, true); ++stepModem; // проверяем зарегестрировался ли модем в сети
                goodCREG = false; // сбрасываем флаг что зарегестрирован
              }
              break;
            }
          case 1: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) { // если пришел байт
                if (processCREG(inByte)) goodCREG = true; // парсим строку - если зарегестрировались - ставим флаг
                if (findOKfromBuf( inByte)) { // ищем ответ ОК
                  if (goodCREG) { // если успешно зарегестрировались
                    ++stepModem; // переходим на след шаг
                  } else { // если так и не смогли зарегестрироваться
                    stepModem = 0; timerModem = currentMillis; // через время periodWaitRegistrationModem опять отправляем команду для проверки зарегестрировалась ли СИМ карта
                  }
                }
              }
              break;
            }
          case 2: {
              sendATcmdByIdx(15, true); ++stepModem; // получить режим подключения к оператору и имя оператора
              operatorName[0] = 0; // очищаем имя оператора
              break;
            }
          case 3: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) {
                processOperatorName(inByte); // парсим ответ
                if (findOKfromBuf( inByte)) {
                  sendATcmdByIdx(16, true); // запрос качества связи
                  ++stepModem; operatorCSQ = 0;
                  //logStrPrint((char *)operatorName); logStrPrint((char *)"\r\n");
                }
              }
              break;
            }
          case 4: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) {
                processOperatorCSQ(inByte); // парсим ответ
                if (findOKfromBuf( inByte)) {
                  //logDecPrint(operatorCSQ); logStrPrint((char *)"\r\n");
                  sendATcmdByIdx(17, true); ++stepModem; // командный режим передачи данных
                  //++stepModem; // пропустим удаление СМС - модем не всегда при включении успевает инициализироваться :( и происходит перезагрузка
                }
              }
              break;
            }
          case 5: {
              if (inExist) if (findOKfromBuf( inByte)) {
                  sendATcmdByIdx(18, true); ++stepModem; // удаление всех SMS на SIM карте
                }
              break;
            }
          case 6: {
              if (inExist) if (findOKfromBuf( inByte)) {
                  currentInitModem = modemReady; // закончили инициализацию модема
                }
              break;
            }
          default: {}
        }
        break;
      }
    default: {}
  }
}

_Bool findOKfromBuf(const unsigned char inByte) { // поиск OK\r\n в циклическом буфере
  const char inStr[] = "OK\r\n"; // искомая строка
  byte slen = 4; // длина искомой строки
  if (inByte == inStr[slen - 1]) { // если последний символ совпадает - продолжаем искать всю строку
    byte abpos = posModemBuf; // абсолютная позиция в буфере поиска-1  = последнему символу искомой строки
    if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // ищем с предпоследнего символа
    for ( byte i = 0; i < (slen - 1); ++i) { // цикл по оставшимся символам
      if (inStr[slen - 1 - i] != respBufModem[abpos]) return false; // если не сопадабт символы - выходим
      if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // уменьшаем счетчики
    }
  } else {
    return false;
  } return true;
}

_Bool findRespFromBuf(char * inStr, const unsigned char inByte) { // поиск строки в циклическом буфере
  byte slen = strlen((const char *)inStr); // длина искомой строки
  if (inByte == inStr[slen - 1]) { // если последний символ совпадает - продолжаем искать всю строку
    byte abpos = posModemBuf; // абсолютная позиция в буфере поиска-1  = последнему символу искомой строки
    if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // ищем с предпоследнего символа
    for ( byte i = 0; i < (slen - 1); ++i) { // цикл по оставшимся символам
      if (inStr[slen - 1 - i] != respBufModem[abpos]) return false; // если не сопадабт символы - выходим
      if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // уменьшаем счетчики
    }
  } else {
    return false;
  } return true;
}

//modinit.h // модуль включения и инициализации модема
#ifndef MODINIT_H
#define MODINIT_H

#include "options.h" // модуль пользовательских настроек
#include "dmfcomm.h" // модуль начального инициализирования устройств/пинов и глобальные переменные/функции

enum typeModemInitStep {modemBegin, modemFirst, modemStartReg, modemReg, modemRestart, modemStop, modemReady}; // шаги инициализации модема
extern enum typeModemInitStep currentInitModem; // переменная - текущий шаг

void clearRespBuf(void); // очистка приемного буфера
void processInitModem(const unsigned char inByte, const _Bool inExist); // инициализация модема

extern unsigned char posModemBuf; // текущая позиция буфера
extern unsigned char respBufModem[maxSizeRespBuf]; // буфер для обработки входящей информации из модема
extern unsigned char operatorName[maxSizeOperatorName]; // буфер имя оператора
extern byte operatorCSQ; // уровень связи подключения к оператору

_Bool findRespFromBuf(char * inStr, const unsigned char inByte); // поиск строки в циклическом буфере
_Bool findOKfromBuf(const unsigned char inByte); // поиск OK\r\n в циклическом буфере

const char cmd0[] PROGMEM = "ATZ\r";
const char cmd1[] PROGMEM = "ATE0\r";
const char cmd2[] PROGMEM = "ATV1\r";
const char cmd3[] PROGMEM = "AT+CMEE=2\r";
const char cmd4[] PROGMEM = "AT+CIPSHUT\r";
const char cmd5[] PROGMEM = "AT+CLIP=0\r";
const char cmd6[] PROGMEM = "ATS0=0\r";
const char cmd7[] PROGMEM = "AT+CMGF=1\r";
const char cmd8[] PROGMEM = "AT+CSCLK=0\r";
const char cmd9[] PROGMEM = "AT+CIPMUX=0\r";
const char cmd10[] PROGMEM = "AT+CIPHEAD=0\r";
const char cmd11[] PROGMEM = "AT+CIPRXGET=0\r";
const char cmd12[] PROGMEM = "AT+CIPSRIP=0\r";
const char cmd13[] PROGMEM = "AT+CSPN?\r";
const char cmd14[] PROGMEM = "AT+CREG?\r";
const char cmd15[] PROGMEM = "AT+COPS?\r";
const char cmd16[] PROGMEM = "AT+CSQ\r";
const char cmd17[] PROGMEM = "AT+CIPMODE=0\r";
const char cmd18[] PROGMEM = "AT+CMGDA=\"DEL ALL\"\r";
// массив всех команд
const char * const at_list_cmd[] PROGMEM = { // text command
  cmd0, cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, cmd8, cmd9,
  cmd10, cmd11, cmd12, cmd13, cmd14, cmd15, cmd16, cmd17, cmd18
};

#endif  /* MODINIT_H */

//options.h // пользовательские настройки железки
#ifndef OPTIONS_H
#define OPTIONS_H

#include <Arduino.h>

#define ADMIN_PHONE "79009698478" // номер администратора

#define ussd_balance "AT+CUSD=1,\"*100#\"\r" // строка запроса баланса телефона // команда зависит от оператора, погоды на луне, настроения БС и т д......Гарантии что баланс придет в нормальном виде никакой.
#define period_get_balance 864007979UL // получение баланса телефона раз в 10 суток

#define errTemp ((word)2100) // значение температуры ошибка, т е еще не получено
static constexpr byte ds18_pin = A2; // пин куда подключен DS18B20
#define period_get_temp 64963UL // получение температуры каждые 65 сек

static constexpr byte pinExtPower  = A3; // пин где определяем наличие внешнего питания
#define period_control_ext_power 8000UL // время в течении которого контролируется изменение питание и после которого шлется СМС

#define period_read_sms 15967UL // чтение всех СМС каждые 16 сек
#define max_size_abonent_sms 16 // размер буфера номера абонента
#define max_size_len_read_sms 64 // размер буфера текста СМС прием

// настройки работы модема
static constexpr byte pinResetModem  = A1; // пин сброса модема SIM800L
#define periodWaitModemBegin 10000UL // время ожидания после включения, прежде чем начнем работать с модемом // модем прочухивается
#define periodWaitShortCmdModem 7000UL // время ожидания ответа на короткие команды, обычно ждем OK, и если нет - сброс модема // на команде CIPSHUT закрытия всех сессий интернета при старте может не хватить, тогда или увеличить или пропустить эту команду при старте
#define periodWaitRegistrationModem 5000UL // каждый этот период спрашивается зарегестрировался ли модем
#define periodReadyModem 300000UL // общее время, которое дается на регистрацию в сети, иначе перезагрузка модема
#define maxSizeRespBuf ((byte)64) // максимальный размер входящего буфера для данных из модема - кольцевой буфер FIFO, по идее должен быть чуть больше максимальной лексемы
#define maxSizeOperatorName ((byte)16) // максимальная длина поля имени оператора связи
#define maxSizeBrokerID ((byte)21) // максимальный размер отправляемой команды модему

// настройка SoftWare Serial
static constexpr byte pinSwUART_TX = 3; // RX вход устройства, принимающего/отображающее данные/лог
static constexpr byte pinSwUART_RX = 2; // TX выход устройства, от которого получать данные

#endif  /* OPTIONS_H */

/*Скетч использует 16612 байт (54%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 1079 байт (52%) динамической памяти, оставляя 969 байт для локальных переменных. Максимум: 2048 байт.
*/

проект из 8 файлов, разбито по функциональному назначению, часть запуска инициализации модема взято с более сложного крепного проекта, поэтому какие то части кода могут показаться странными и/или лишними. В последнем файле options.h настройки проекта/пинов/админского телефона/таймеров и т.д.

Код не блокирующий, легко добавить опрос каких либо других датчиков, отправку СМС, получение команд и т.д....

-

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

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

в собранном устройстве на пин А3 контроля внешнего питания каждые ~44 секунды прилетал непонятный импульс, в итоге часто слались СМС, исправленная функция, типа антидребезга на несколько секунд:

void powerOnOffControl(void) {
  static char toSendProblem[max_size_abonent_sms];
  static byte lastLevelPower = 1;
  static byte levelRealPower = lastLevelPower;
  static byte startControl = 0;
  static unsigned long timerControl;
  byte currentPowerLevel = getExternalPower();
  if (currentPowerLevel != lastLevelPower) {
    //if (currentPowerLevel) Serial.println(F("External power ON")); else Serial.println(F("OFF external power"));
    startControl = 1;
    timerControl = currentMillis;
  } else if (startControl) {
    if ((currentMillis - timerControl) >= period_control_ext_power) {
      startControl = 0;
      if (levelRealPower != currentPowerLevel) {
        levelRealPower = currentPowerLevel;
        if (currentInitModem == modemStop) {
          //Serial.println(F("send power SMS"));
          strcpy(toSendProblem, (char *)ADMIN_PHONE);
          sendSMS(toSendProblem, prepareStandartSMS());
        }
      }
    }
  }
  lastLevelPower = currentPowerLevel;
}

- целиковый код одного файла:

#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include "options.h" // настройки железки
#include "dmfcomm.h" // основные переменные и функции
#include "modinit.h" // инициализация модема
#include "dmfsms.h" // работа с СМС

// инициализация WDT (код из даташита + из форума, хз как работает)
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((used)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

void inCallbackSMS(char * fromSMS, char * textSMS) { // обработчик входящего сообщения
  static char toSendPhone[max_size_abonent_sms];
  if (strcmp(fromSMS, ADMIN_PHONE) == 0) {
    //Serial.println(F("SMS from Admin phone"));
    // find command from sms
    if (findSubstring((char *)"getdata", textSMS)) {
      //Serial.println(F("Input command: getdata"));
      // exec cmd
      strcpy(toSendPhone, (char *)ADMIN_PHONE);
      sendSMS(toSendPhone, prepareStandartSMS());
    } else if (findSubstring((char *)"devreset", textSMS)) {
      //Serial.println(F("Input command: devreset"));
      modemReset();
      delay(10000);
    }
  } else {
    //Serial.print(F("SMS from: ")); Serial.println(fromSMS);
  }
  //Serial.println(textSMS);
}

void processModemActions() { // основная процедуры работы/общение с модемом
  unsigned char br = 0; // переменная куда падает входящий байт из модема
  _Bool flbr = false; // влаг что пришедший байт не равен нулю
  if (modemAvailable()) { // если что то пришло из модема
    br = modemRead(); // читаем пришедший байт
    if (br) { // если он больше нуля
      if (posModemBuf >= maxSizeRespBuf) posModemBuf = 0; respBufModem[posModemBuf] = br; ++posModemBuf; // добавляем пришедший байт в круговой буфер
      if ((br == '\r') || (br == '\n') || ((br >= ' ') && (br < 0x7F))) { // если символ печатный или конец строки
        logWrite(br); // выводим его в лог
      } else { // если не печатный
        logHexPrint(br); logWrite(' '); // выводим его шестнадцатиричное представление в лог
      }
      flbr = true; // ставим флаг что пришедший байт больше нуля
    }
  }
  // main process device with modem // тут начинаем запускать все без исключения модули, работающие с информацией/модемом
  if (currentInitModem != modemStop) { // если модем еще не вышел на связь и не зарегестрировался в сети
    processInitModem(br, flbr); // работаем с модемом
    if (currentInitModem == modemRestart) { // если все плохо и необходимо сбросить модем
      modemReset(); // сбрасываем его
      startDmfDevice(); // заново запускаем всю логику работы устройства
      return; // выходим
    } else if (currentInitModem == modemReady) { // если все хорошо со связью
      clearRespBuf(); // очищаем приемный буфер модема
      currentInitModem = modemStop; // останавливаем инициализацию модема
      // go to next step INIT CIP // переходим в обработку
      currentSms = smsBegin;
    }
  }
  // обработка СМС
  if (currentSms != smsStop) { // если нет коннекта к брокеру - работаем
    processSms(br, flbr); // работаем с модемом
    if (currentSms == smsRestart) { // если все плохо и необходимо сбросить модем
      modemReset(); // сбрасываем его
      startDmfDevice(); // заново запускаем всю логику работы устройства
      return; // выходим
    }
  }
  // end main process
}

void startDmfDevice() { // начальные настройки всего модуля
  Serial.println(F("Start!"));
  currentSms = smsStop; // останавливаем работу с модемом
  currentInitModem = modemBegin; // запускаем модем
}

void setup() {
  MCUSR = 0; // отключение WDT
  wdt_disable(); // отключение WDT
  // put your setup code here, to run once:
  initDmfDevice(); // инициализируем пины
  setCallbackSMS(inCallbackSMS);
  startDmfDevice(); // запускаем железку
  wdt_enable(WDTO_8S); // включение WDT
}

void powerOnOffControl(void) {
  static char toSendProblem[max_size_abonent_sms];
  static byte lastLevelPower = 1;
  static byte levelRealPower = lastLevelPower;
  static byte startControl = 0;
  static unsigned long timerControl;
  byte currentPowerLevel = getExternalPower();
  if (currentPowerLevel != lastLevelPower) {
    //if (currentPowerLevel) Serial.println(F("External power ON")); else Serial.println(F("OFF external power"));
    startControl = 1;
    timerControl = currentMillis;
  } else if (startControl) {
    if ((currentMillis - timerControl) >= period_control_ext_power) {
      startControl = 0;
      if (levelRealPower != currentPowerLevel) {
        levelRealPower = currentPowerLevel;
        if (currentInitModem == modemStop) {
          //Serial.println(F("send power SMS"));
          strcpy(toSendProblem, (char *)ADMIN_PHONE);
          sendSMS(toSendProblem, prepareStandartSMS());
        }
      }
    }
  }
  lastLevelPower = currentPowerLevel;
}

void loop() {
  wdt_reset(); // сброс WDT
  //delay(10000); // test WDT // раскоменнтировать строку для проверки WDT , МК будет перезагружаться каждые 10 секунд
  currentMillis = millis(); // главный таймер
  // put your main code here, to run repeatedly:
  processModemActions(); // работаем с модемом
  // get data from temp sensors
  if (dsCount) readTempFromAllSensors();
  // контроль включения/отключения внешнего питания
  powerOnOffControl();
  // выводим данные на дисплейчик
  showOLEDdata();
}

- итоговая железка

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Добавлен счетчик принудительных перезагрузок модема, отображается на OLED перед именем оператора.
Добавлено получение баланса раз в 10 суток SIM карты через USSD запрос *100#, на МТС работает корректно, на Мегафоне модем не может выдать такой длинный ответ оператора (там еще куча рекламы в сообщении судя по всему) и поэтому не работает. Баланс присылается в стандартной ответной SMS по запросу getdata или при отключении/включении внешнего питания.

#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include "options.h" // настройки железки
#include "dmfcomm.h" // основные переменные и функции
#include "modinit.h" // инициализация модема
#include "dmfsms.h" // работа с СМС

// инициализация WDT (код из даташита + из форума, хз как работает)
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((used)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

void inCallbackSMS(char * fromSMS, char * textSMS) { // обработчик входящего сообщения
  static char toSendPhone[max_size_abonent_sms];
  if (strcmp(fromSMS, ADMIN_PHONE) == 0) {
    //Serial.println(F("SMS from Admin phone"));
    // find command from sms
    if (findSubstring((char *)"getdata", textSMS)) {
      //Serial.println(F("Input command: getdata"));
      // exec cmd
      strcpy(toSendPhone, (char *)ADMIN_PHONE);
      sendSMS(toSendPhone, prepareStandartSMS());
    } else if (findSubstring((char *)"devreset", textSMS)) {
      //Serial.println(F("Input command: devreset"));
      modemReset();
      delay(10000);
    }
  } else {
    //Serial.print(F("SMS from: ")); Serial.println(fromSMS);
  }
  //Serial.println(textSMS);
}

void processModemActions() { // основная процедуры работы/общение с модемом
  unsigned char br = 0; // переменная куда падает входящий байт из модема
  _Bool flbr = false; // влаг что пришедший байт не равен нулю
  if (modemAvailable()) { // если что то пришло из модема
    br = modemRead(); // читаем пришедший байт
    if (br) { // если он больше нуля
      if (posModemBuf >= maxSizeRespBuf) posModemBuf = 0; respBufModem[posModemBuf] = br; ++posModemBuf; // добавляем пришедший байт в круговой буфер
      if ((br == '\r') || (br == '\n') || ((br >= ' ') && (br < 0x7F))) { // если символ печатный или конец строки
        logWrite(br); // выводим его в лог
      } else { // если не печатный
        logHexPrint(br); logWrite(' '); // выводим его шестнадцатиричное представление в лог
      }
      flbr = true; // ставим флаг что пришедший байт больше нуля
    }
  }
  // main process device with modem // тут начинаем запускать все без исключения модули, работающие с информацией/модемом
  if (currentInitModem != modemStop) { // если модем еще не вышел на связь и не зарегестрировался в сети
    processInitModem(br, flbr); // работаем с модемом
    if (currentInitModem == modemRestart) { // если все плохо и необходимо сбросить модем
      modemReset(); // сбрасываем его
      startDmfDevice(); // заново запускаем всю логику работы устройства
      return; // выходим
    } else if (currentInitModem == modemReady) { // если все хорошо со связью
      clearRespBuf(); // очищаем приемный буфер модема
      currentInitModem = modemStop; // останавливаем инициализацию модема
      // go to next step INIT CIP // переходим в обработку
      currentSms = smsBegin;
    }
  }
  // обработка СМС
  if (currentSms != smsStop) { // если нет коннекта к брокеру - работаем
    processSms(br, flbr); // работаем с модемом
    if (currentSms == smsRestart) { // если все плохо и необходимо сбросить модем
      modemReset(); // сбрасываем его
      startDmfDevice(); // заново запускаем всю логику работы устройства
      return; // выходим
    }
  }
  // end main process
}

void startDmfDevice() { // начальные настройки всего модуля
  Serial.println(F("Start!"));
  currentSms = smsStop; // останавливаем работу с модемом
  currentInitModem = modemBegin; // запускаем модем
}

void setup() {
  MCUSR = 0; // отключение WDT
  wdt_disable(); // отключение WDT
  // put your setup code here, to run once:
  initDmfDevice(); // инициализируем пины
  setCallbackSMS(inCallbackSMS);
  startDmfDevice(); // запускаем железку
  wdt_enable(WDTO_8S); // включение WDT
}

void powerOnOffControl(void) {
  static char toSendProblem[max_size_abonent_sms];
  static byte lastLevelPower = 1;
  static byte levelRealPower = lastLevelPower;
  static byte startControl = 0;
  static unsigned long timerControl;
  byte currentPowerLevel = getExternalPower();
  if (currentPowerLevel != lastLevelPower) {
    //if (currentPowerLevel) Serial.println(F("External power ON")); else Serial.println(F("OFF external power"));
    startControl = 1;
    timerControl = currentMillis;
  } else if (startControl) {
    if ((currentMillis - timerControl) >= period_control_ext_power) {
      startControl = 0;
      if (levelRealPower != currentPowerLevel) {
        levelRealPower = currentPowerLevel;
        if (currentInitModem == modemStop) {
          //Serial.println(F("send power SMS"));
          strcpy(toSendProblem, (char *)ADMIN_PHONE);
          sendSMS(toSendProblem, prepareStandartSMS());
        }
      }
    }
  }
  lastLevelPower = currentPowerLevel;
}

void loop() {
  wdt_reset(); // сброс WDT
  //delay(10000); // test WDT // раскоменнтировать строку для проверки WDT , МК будет перезагружаться каждые 10 секунд
  currentMillis = millis(); // главный таймер
  // put your main code here, to run repeatedly:
  processModemActions(); // работаем с модемом
  // get data from temp sensors
  if (dsCount) readTempFromAllSensors();
  // контроль включения/отключения внешнего питания
  powerOnOffControl();
  // выводим данные на дисплейчик
  showOLEDdata();
}

//dmfcomm.h // модуль начального инициализирования устройств/пинов и глобальные переменные/функции
#ifndef DMFCOMM_H
#define DMFCOMM_H

#include <Arduino.h>
#include "options.h" // модуль пользовательских настроек
#include "DallasTemperature.h"
#include <OneWire.h>

extern byte dsCount; // количество датчиков температуры
extern word dsTemp; // прочитанное значение температуры, всегда положительное, последний знак - десятичные доли градуса, если отрицательное значение - от 2000
void readTempFromAllSensors(void); // получаем температуру со всех датчиков

byte getExternalPower(void); // получение значения на пине внешнего питани

void showOLEDdata(void); // отображение данных на OLED 128*64 I2C

extern unsigned long currentMillis;
void initDmfDevice();
void modemReset(void); // сброс модема
void logStrPrint(char * inStr);
void logWrite(byte inCh);
void logHexPrint(signed long inHex);
void logDecPrint(signed long inInt);
void modemStrPrint(char * inStr);
void modemWrite(byte inCh);
byte modemAvailable();
byte modemRead();

#endif  /* DMFCOMM_H */
//dmfcomm.cpp // модуль начального инициализирования устройств/пинов и глобальные переменные/функции
#include <Arduino.h>
#include "dmfcomm.h"
#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include <Wire.h>
#include "OzOLED.h"
#include "modinit.h"
#include "dmfsms.h"

unsigned long currentMillis; // главный таймер

unsigned char countResetModem; // счетчик принудительных перезагрузок модема и соотвественно перезапуска программы

byte getExternalPower(void) { // получение значения на пине внешнего питани
  return digitalRead(pinExtPower);
}

void printAllDataOLED(void) { // очистить дисплей и вывести данные
  OzOled.clearDisplay();
  if (getExternalPower()) OzOled.printString("Power ON"); else OzOled.printString("Power OFF");
  OzOled.setCursorXY(0, 2);
  char tmpStr[20]; tmpStr[0] = 0;
  if (currentInitModem == modemStop) {
    itoa(countResetModem, tmpStr, 10);
    strcat(tmpStr, (char *)" ");
    strcat(tmpStr, (char *)operatorName);
    OzOled.printString((const char *) tmpStr);
  } else {
    OzOled.printString("Starting device");
  }
  OzOled.setCursorXY(0, 3);
  if (currentInitModem == modemStop) { // have CSQ
    OzOled.printString("CSQ: "); OzOled.printNumber((long)operatorCSQ, 5, 3);
  }
  OzOled.setCursorXY(0, 5);
  OzOled.printString("Temp: ");
  if ((dsCount) && (dsTemp != errTemp)) {
    word absTemp = dsTemp;
    if (absTemp >= 2000) {
      OzOled.printString("-");
      absTemp -= 2000;
    }
    tmpStr[0] = 0;
    itoa((absTemp / 10), tmpStr, 10);
    OzOled.printString((const char *)tmpStr);
    OzOled.printString(".");
    tmpStr[0] = 0;
    itoa((absTemp % 10), tmpStr, 10);
    OzOled.printString((const char *)tmpStr);
  } else {
    OzOled.printString("error");
  }
  OzOled.setCursorXY(0, 7);
  OzOled.printString("Uptime: ");
  tmpStr[0] = 0;
  itoa((currentMillis / 86400000UL), tmpStr, 10);
  OzOled.printString((const char *)tmpStr);
  OzOled.printString("d ");
  tmpStr[0] = 0;
  itoa(((currentMillis % 86400000UL) / 3600000UL), tmpStr, 10);
  OzOled.printString((const char *)tmpStr);
  OzOled.printString("h");
}

void showOLEDdata(void) { // отображение данных на OLED 128*64 I2C
  static unsigned long showTimer;
  static byte inShow = 1;
  if (currentInitModem != modemStop) { // инциалимзация модема - дисплей не отключаем, обновляем данные каждые 2 секунды
    if ((currentMillis - showTimer) >= 2000UL) {
      showTimer = currentMillis;
      printAllDataOLED();
    }
  } else { // зажигаем по времени
    if (inShow) {
      if ((currentMillis - showTimer) >= 5000UL) {
        inShow = 0;
        OzOled.clearDisplay();
      }
    } else {
      if (currentSms != smsWait) {
        printAllDataOLED();
        inShow = 1;
        showTimer = currentMillis;
      }
    }
  }
}

#include <SoftwareSerial.h> // стандартная библиотека работы с SoftwareSerial

SoftwareSerial swuart(pinSwUART_RX, pinSwUART_TX); // установка контактов

#define TEMPERATURE_PRECISION 9 // точность получения значения темературы
OneWire dsWire(ds18_pin); // объект 1Ware
DallasTemperature sensors(&dsWire); // объект  датчика температуты
byte dsCount; // количество датчиков температуры
DeviceAddress dsAddres; // адрес датчика
word dsTemp; // прочитанное значение температуры, всегда положительное, последний знак - десятичные доли градуса, если отрицательное значение - от 2000

void readTempFromAllSensors(void) { // получаем температуру со всех датчиков
  static unsigned long timer_get_temp = period_get_temp - 10000UL; // таймер получения температуры
  static byte requestTemp = 1; // запрос температуры датчиков
  // get temper // каждое N время получаем температуру со всех датчиков
  if ((currentMillis - timer_get_temp) >= period_get_temp) {
    if (requestTemp) {
      sensors.requestTemperatures(); requestTemp = 0;
    } else if ((currentMillis - timer_get_temp) >= (period_get_temp + 297UL)) { // for 9 bit PRECISION
      timer_get_temp = currentMillis; requestTemp = 1;
      float tct = sensors.getTempC(dsAddres);
      if ((tct >= 95.0) || (tct <= -35.0)) { // температура не получилась
        dsTemp = errTemp;
      } else {
        if (tct < 0) dsTemp = ((-tct) * 10) + 2000; else dsTemp = (tct * 10);
      }
      Serial.print(F("t = ")); Serial.println(dsTemp, DEC); // печать температур в консоль
    }
  }
}

void printAddress(DeviceAddress deviceAddress) { // function to print a device address DS18B20
  for (uint8_t i = 0; i < 8; i++) {
    // zero pad the address if necessary
    if (deviceAddress[i] < 16) Serial.print('0');
    Serial.print(deviceAddress[i], HEX);
    if (i < 7) Serial.print('-');
  }
  Serial.println(); // новая строка
}

void initFindDefineSensors(void) { // инициализация OneWire DS18B20 поиск и чтение адресов всех датчиков температуры
  dsWire.reset(); // сброс шины
  delay(750); // пауза
  wdt_reset(); // сброс WDT
  sensors.begin(); // инициировать датчики температуры
  delay(750); // пауза
  wdt_reset(); // сброс WDT
  dsCount = sensors.getDeviceCount(); // получить количество датчиков
  Serial.print(F("Found DS18B20 = ")); Serial.println(dsCount, DEC); // вывести количество
  if (dsCount) {
    sensors.getAddress(dsAddres, 0); // получение адреса
    sensors.setResolution(dsAddres, TEMPERATURE_PRECISION); // настраиваем разрешение
    printAddress(dsAddres); // печатаем адрес
  }
  dsTemp = errTemp; // температура еще не получена
}

void initLogModemUART() {
  swuart.begin(9600);
  Serial.begin(9600);
}

void logStrPrint(char * inStr) { // вывод строки в лог
  Serial.print(inStr);
}

void logWrite(byte inCh) { // вывод байта в лог
  Serial.write((char)inCh);
}

void logHexPrint(signed long inHex) { // вывод шестнадцатиричного числа в лог
  logWrite('x');
  Serial.print(inHex, HEX);
}

void logDecPrint(signed long inInt) { // вывод десятичного числа в лог
  Serial.print(inInt, DEC);
}

void modemStrPrint(char * inStr) { // строку в модем
  swuart.print(inStr);
}

void modemWrite(byte inCh) { // байт в модем
  swuart.write((char)inCh);
}

byte modemAvailable() { // есть ли данные в модеме
  return swuart.available();
}

byte modemRead() { // чтение из модема
  return swuart.read();
}

void initDmfDevice() { // инициализация всего устройства
  OzOled.init();  //initialze Oscar OLED display
  OzOled.printString("Init...."); //Print the String
  countResetModem = 0; // обнулим счетчик перезагрузок модема
  initLogModemUART();
  digitalWrite(pinResetModem, LOW); pinMode(pinResetModem, INPUT); // инициализация пина сброса модема
  digitalWrite(pinExtPower, LOW); pinMode(pinExtPower, INPUT); // инициализация пина получения питания
  initFindDefineSensors();
  if (!dsCount) { // не нашлись датчики, попробуем все сбросить и заново проинициализировать
    dsWire.reset_search(); initFindDefineSensors();
  }
}

void modemReset(void) { // сброс модема
  ++countResetModem; // увеличим счетчик перезагрузок
  pinMode(pinResetModem, OUTPUT); delay(115); pinMode(pinResetModem, INPUT);
}

// dmfsms.h - модуль работы с СМС
#ifndef DMFSMS_H
#define DMFSMS_H

#include <Arduino.h>
#include "options.h" // модуль пользовательских настроек
#include "modinit.h" // работа с модемом

enum typeSmsStep {smsBegin, smsRestart, smsStop, smsWait, smsReadAll, smsGetBalance, smsSend, smsParseBalance}; // шаги
extern enum typeSmsStep currentSms; // переменная - текущий шаг

void processSms(const unsigned char inByte, const _Bool inExist); // work sms
void setCallbackSMS(void (*inCallback)(char *, char *)); // установка обработчика сообщения
byte findSubstring(char * subStr, char * inStr); // поиск подстроки в строке, используется для поиска команд в СМСке

extern word balancePhone; // баланс SIM карты телефона, целое число, для получения нужной суммы - поделить на 100

void sendSMS(char * toPhone, char * smsText); // отправка сообщения

char * prepareStandartSMS(void); // делает типовую СМСку питание/температуру/аптайм

const char smsc0[] PROGMEM = "AT+CMGL=\"ALL\"\r";
const char smsc1[] PROGMEM = "AT+CMGDA=\"DEL ALL\"\r";
const char smsc2[] PROGMEM = ussd_balance;
const char smsc3[] PROGMEM = "AT+CMGS=\"+";
// массив всех команд
const char * const at_list_smsc[] PROGMEM = { // text command
  smsc0, smsc1, smsc2, smsc3
};

#endif  /* DMFSMS_H */

// dmfsms.cpp - модуль работы с СМС
#include <Arduino.h>
#include "dmfsms.h"
#include "dmfcomm.h"
#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include "modinit.h" // инициализация модема

enum typeSmsStep currentSms; // переменная - текущий шаг
_Bool waitRespSms; // флаг ожидания ответа на команду
unsigned long timerWorkCmd; // рабочий таймер команд
word balancePhone = 0; // баланс SIM карты телефона, целое число, для получения нужной суммы - поделить на 100

byte flNeedSendSMS;
char * outSmsPhone;
char * outTextSms;

static void (*callbackReadOneSMS)(char *, char *); // внешний обработчик пришедшей СМС

void sendSMS(char * toPhone, char * smsText) { // отправка сообщения
  outSmsPhone = toPhone; outTextSms = smsText;
  flNeedSendSMS = 1;
}

char * prepareStandartSMS(void) { // делает типовую СМСку питание/температуру/аптайм
  static char outTextToSMS[128];
  outTextToSMS[0] = 0;
  if (getExternalPower()) strcpy(outTextToSMS, "PowerON"); else strcpy(outTextToSMS, "PowerOFF"); // 9 char
  char tmpStr[10]; tmpStr[0] = 0;
  itoa(operatorCSQ, tmpStr, 10);
  strcat(outTextToSMS, " CSQ:"); // 6 char
  strcat(outTextToSMS, tmpStr); // 4 char
  strcat(outTextToSMS, " temp:"); // 7 char
  if (dsTemp == errTemp) {
    strcat(outTextToSMS, "Err"); // 4 char
  } else {
    word absTemp = dsTemp;
    if (absTemp >= 2000) {
      absTemp -= 2000;
      strcat(outTextToSMS, "-"); // 2 char
    }
    tmpStr[0] = 0;
    itoa((absTemp / 10), tmpStr, 10);
    strcat(outTextToSMS, tmpStr); // 4 char
    strcat(outTextToSMS, "."); // 2 char
    tmpStr[0] = 0;
    itoa((absTemp % 10), tmpStr, 10);
    strcat(outTextToSMS, tmpStr); // 2 char
  }
  strcat(outTextToSMS, " uptime:"); // 9 char
  tmpStr[0] = 0;
  itoa((currentMillis / 86400000UL), tmpStr, 10);
  strcat(outTextToSMS, tmpStr); // 5 char
  strcat(outTextToSMS, "d"); // 2 char
  tmpStr[0] = 0;
  itoa(((currentMillis % 86400000UL) / 3600000UL), tmpStr, 10);
  strcat(outTextToSMS, tmpStr); // 5 char
  strcat(outTextToSMS, "h"); // 2 char
  if (balancePhone) {
    strcat(outTextToSMS, " balance:");
    tmpStr[0] = 0;
    itoa((balancePhone / 100), tmpStr, 10);
    strcat(outTextToSMS, tmpStr);
    strcat(outTextToSMS, ".");
    tmpStr[0] = 0;
    itoa((balancePhone % 100), tmpStr, 10);
    strcat(outTextToSMS, tmpStr);
  }
  return outTextToSMS;
}

byte findSubstring(char * subStr, char * inStr) { // поиск подстроки в строке, используется для поиска команд в СМСке
  byte ssLen = strlen(subStr);
  byte isLen = strlen(inStr);
  if (ssLen <= isLen) {
    byte flGood = 0;
    for (byte startFindPosInStr = 0; startFindPosInStr <= (isLen - ssLen); ++startFindPosInStr) {
      byte countFound = ssLen;
      for (byte posSubStr = 0; posSubStr < ssLen; ++posSubStr) {
        if (toupper(subStr[posSubStr]) == toupper(inStr[posSubStr + startFindPosInStr])) --countFound; else break;
      }
      if (!countFound) {
        flGood = 1; break;
      }
    }
    if (flGood) return 1;
  }
  return 0;
}


void setCallbackSMS(void (*inCallback)(char *, char *)) { // установка обработчика сообщения
  callbackReadOneSMS = inCallback;
}

void parseSmsText(byte newSms, const unsigned char inSms) { // парсинг тескта СМС
  static char abonentNumber[max_size_abonent_sms];
  static char textSMS[max_size_len_read_sms];
  static byte parseStep = 0; // шаг работы
  if (newSms) {
    parseStep = 0; return;
  }
  if (findRespFromBuf((char *)"\r\n", inSms)) { // прилетел конец строки
    if ((abonentNumber[0] != 0) && (parseStep == 5)) { // номер абонента введен
      parseStep = 6; // get text sms
      //Serial.println((char *)abonentNumber);
    } else if ((textSMS[0] != 0) && (parseStep == 6)) { // текст СМС выведен
      parseStep = 0;
      //Serial.println((char *)textSMS);
      wdt_reset(); // сброс WDT
      // exec callback
      callbackReadOneSMS(abonentNumber, textSMS);
    }
  } else { // смотрим побайтно
    switch (parseStep) {
      case 0: {
          memset(abonentNumber, 0, max_size_abonent_sms); memset(textSMS, 0, max_size_len_read_sms); // стерли строки
          if (isdigit(inSms)) ++parseStep; else parseStep = 0;
          break;
        }
      case 1: {
          if (inSms == ',')  ++parseStep; else parseStep = 0;
          break;
        }
      case 2: {
          if (inSms == '\"')  ++parseStep; else parseStep = 0;
          break;
        }
      case 3: {
          if (findRespFromBuf((char *)"\",\"+", inSms)) ++parseStep;
          break;
        }
      case 4: { // phone number
          if (inSms == '\"') {
            ++parseStep;
          } else if (isdigit(inSms)) {
            byte len = strlen((char *)abonentNumber);
            if (len < (max_size_abonent_sms - 1)) {
              abonentNumber[len] = inSms; abonentNumber[len + 1] = 0;
            }
          } else {
            parseStep = 0;
          }
          break;
        }
      case 5: { // dont need data - wait new string
          break;
        }
      case 6: { // text SMS
          byte len = strlen((char *)textSMS);
          if (len < (max_size_len_read_sms - 1)) {
            textSMS[len] = inSms; textSMS[len + 1] = 0;
          }
          break;
        }
      default: {}
    }
  }
}

void smsMODEMcmd(char * inStr, const _Bool showLog) { // отправка модему команды
  modemStrPrint(inStr); // кидаем строку модему
  if (showLog) {
    logStrPrint(inStr); // и если нужно - дублируем в лог
  }
}

void sendATsmsByIdx(byte cmdIdx, const _Bool flClearBuf) { // отправка команды модему из progmem
  char inStr[maxSizeBrokerID];
  const char * addrStroki = pgm_read_word_near((int)(at_list_smsc + cmdIdx));
  strcpy_P(inStr, addrStroki);
  smsMODEMcmd(inStr, true); // отправляем с дублированием в логе
  if (flClearBuf) clearRespBuf(); // если необходимо - чистим буфер
  waitRespSms = true; // ждем ответа на команду
  timerWorkCmd = currentMillis; // обнуляем основной таймер ожидания
}

void parseUssdText(byte newUssd, const unsigned char inUssd) { // парсинг тескта баланса
  static char localBalance[10];
  static char localChar[4];
  static unsigned char localPosChar = 0;
  static unsigned char posLocal = 0;
  if (newUssd) {
    memset(localBalance, 0, 10);
    posLocal = 0;
    localPosChar = 0;
    return;
  }
  if ((isdigit(inUssd)) || ((inUssd >= 'A') && (inUssd <= 'F'))) {
    localChar[localPosChar] = inUssd;
    ++localPosChar;
    if (localPosChar >= 4) { // good char
      if ((localChar[0] == '0') && (localChar[1] == '0') && (localChar[2] == '3') && (isdigit(localChar[3]))) {
        localBalance[posLocal] = localChar[3];
        ++posLocal;
      }
      localPosChar = 0;
    }
  } else if ((inUssd == '"') and (posLocal)) { // end line
    balancePhone = atoi(localBalance);
    //Serial.print("bal="); Serial.println(balancePhone, DEC);
    memset(localBalance, 0, 10);
    posLocal = 0;
    localPosChar = 0;
  }
}

void processSms(const unsigned char inByte, const _Bool inExist) { // work sms
  static byte stepSms; // рабочий текущий шаг
  static unsigned long timerReadSms; // рабочий таймер запуск чтения всех смс
  static unsigned long timerGetBalance; // рабочий таймер получение баланса телефона
  switch (currentSms) { // работаем по текущему шагу
    case smsParseBalance: {
        if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
          currentSms = smsRestart; // надо сбросить модем
          return; // выходим
        } else if (inExist) {
          if (findRespFromBuf((char *)"\", 72\r\n", inByte)) { // ждем ОК на предыдущую команду
            currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
          } else {
            parseUssdText(0, inByte);
          }
        }
        break;
      }
    case smsBegin: { // самое начало
        timerReadSms = currentMillis; // сбросим таймер
        timerGetBalance = currentMillis; // сбросим таймер
        flNeedSendSMS = 0;
        currentSms = smsWait; // переходим в режим ничего неделания
        break;
      }
    case smsWait: { // ничего не делаем - ждем команды
        if ((currentMillis - timerReadSms) >= period_read_sms) { // пора читать СМС
          timerReadSms = currentMillis; // сбросим таймер
          stepSms = 0; // начать с начала
          currentSms = smsReadAll; // запускаем чтение СМС
        } else if ((currentMillis - timerGetBalance) >= period_get_balance) { // пора получить баланс
          timerGetBalance = currentMillis; // сбросим таймер
          stepSms = 0; // начать с начала
          currentSms = smsGetBalance;
        } else if (findRespFromBuf((char *)"+CUSD: 0, \"", inByte)) { // USSD пришло
          // тут мы должны перевести в шаг парсить ответ
          balancePhone = 0;
          // но мой Мегафон ничего не выдает, или прошивка модема дурит, поэтому данный код не дописан // для МТС работает
          parseUssdText(1, 0); // сьросим обрабьтку
          stepSms = 0; // начать с начала
          currentSms = smsParseBalance; // парсчим ответы
          timerWorkCmd = currentMillis; // сбросим таймер
        } else if ((outSmsPhone[0] > 0) && (outTextSms[0] > 0) && (flNeedSendSMS)) { // если текст абонента получателя и текст СМС не пустые
          stepSms = 0; // начать с начала
          currentSms = smsSend;
        }
        break;
      }
    case smsSend: {
        switch (stepSms) { // работаем по шагам
          case 0: {
              sendATsmsByIdx(3, true);
              smsMODEMcmd(outSmsPhone, true); // отправляем с дублированием в логе
              smsMODEMcmd((char *)"\"\r", true); // отправляем с дублированием в логе
              ++stepSms;
              break;
            }
          case 1: {
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (inByte == '>') { // ждем ОК на предыдущую команду
                  smsMODEMcmd(outTextSms, true); // отправляем с дублированием в логе
                  modemWrite(0x1A); // отправляем конец строки
                  ++stepSms;
                  flNeedSendSMS = 0; outSmsPhone[0] = 0; outTextSms[0] = 0;
                }
              }
              break;
            }
          case 2: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= 60000UL) { // если не пришел ответ на короткую команду // СМСки должго отправляются, ждем 60 секунд согласно даташита SIM800L
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    case smsGetBalance : {
        switch (stepSms) { // работаем по шагам
          case 0: {
              sendATsmsByIdx(2, true); ++stepSms;
              break;
            }
          case 1: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    case smsReadAll: { // читаем сообщения
        switch (stepSms) { // работаем по шагам
          case 0: { // шлем модему команду
              sendATsmsByIdx(0, true); ++stepSms;
              break;
            }
          case 1: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  sendATsmsByIdx(1, true); ++stepSms; // удаляем СМС
                } else if (findRespFromBuf((char *)"+CMGL: ", inByte)) { // прилетело начало СМСки
                  parseSmsText(1, 0); // пометим что новое сообщение
                } else {
                  parseSmsText(0, inByte); // парсим все что пришло
                }
              }
              break;
            }
          case 2: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    default: {}
  }
}

//modinit.h // модуль включения и инициализации модема
#ifndef MODINIT_H
#define MODINIT_H

#include "options.h" // модуль пользовательских настроек
#include "dmfcomm.h" // модуль начального инициализирования устройств/пинов и глобальные переменные/функции

enum typeModemInitStep {modemBegin, modemFirst, modemStartReg, modemReg, modemRestart, modemStop, modemReady}; // шаги инициализации модема
extern enum typeModemInitStep currentInitModem; // переменная - текущий шаг

void clearRespBuf(void); // очистка приемного буфера
void processInitModem(const unsigned char inByte, const _Bool inExist); // инициализация модема

extern unsigned char posModemBuf; // текущая позиция буфера
extern unsigned char respBufModem[maxSizeRespBuf]; // буфер для обработки входящей информации из модема
extern unsigned char operatorName[maxSizeOperatorName]; // буфер имя оператора
extern byte operatorCSQ; // уровень связи подключения к оператору

_Bool findRespFromBuf(char * inStr, const unsigned char inByte); // поиск строки в циклическом буфере
_Bool findOKfromBuf(const unsigned char inByte); // поиск OK\r\n в циклическом буфере

const char cmd0[] PROGMEM = "ATZ\r";
const char cmd1[] PROGMEM = "ATE0\r";
const char cmd2[] PROGMEM = "ATV1\r";
const char cmd3[] PROGMEM = "AT+CMEE=2\r";
const char cmd4[] PROGMEM = "AT+CIPSHUT\r";
const char cmd5[] PROGMEM = "AT+CLIP=0\r";
const char cmd6[] PROGMEM = "ATS0=0\r";
const char cmd7[] PROGMEM = "AT+CMGF=1\r";
const char cmd8[] PROGMEM = "AT+CSCLK=0\r";
const char cmd9[] PROGMEM = "AT+CIPMUX=0\r";
const char cmd10[] PROGMEM = "AT+CIPHEAD=0\r";
const char cmd11[] PROGMEM = "AT+CIPRXGET=0\r";
const char cmd12[] PROGMEM = "AT+CIPSRIP=0\r";
const char cmd13[] PROGMEM = "AT+CSPN?\r";
const char cmd14[] PROGMEM = "AT+CREG?\r";
const char cmd15[] PROGMEM = "AT+COPS?\r";
const char cmd16[] PROGMEM = "AT+CSQ\r";
const char cmd17[] PROGMEM = "AT+CIPMODE=0\r";
const char cmd18[] PROGMEM = "AT+CMGDA=\"DEL ALL\"\r";
// массив всех команд
const char * const at_list_cmd[] PROGMEM = { // text command
  cmd0, cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, cmd8, cmd9,
  cmd10, cmd11, cmd12, cmd13, cmd14, cmd15, cmd16, cmd17, cmd18
};

#endif  /* MODINIT_H */

//modinit.cpp // модуль включения и инициализации модема
#include <Arduino.h>
#include "modinit.h"
#include "dmfcomm.h"

enum typeModemInitStep currentInitModem; // переменная - текущий шаг
unsigned char stepModem; // рабочий текущий шаг
unsigned long timerModem; // основной таймер
unsigned long readyModem; // таймер ожидания регистрации в сети оператора
_Bool waitRespModem; // флаг ожидания ответа на команду
_Bool goodCREG; // флаг успешной регистрации в сети оператора

unsigned char posModemBuf; // текущая позиция буфера
unsigned char respBufModem[maxSizeRespBuf]; // буфер для обработки входящей информации из модема

unsigned char operatorName[maxSizeOperatorName]; // буфер имя оператора
byte operatorCSQ; // уровень связи подключения к оператору

void processOperatorName(const unsigned char inBt) { // парсинг ответа модема вытаскиваем имя оператора связи
  static unsigned char stepName = 0; // текущий шаг
  static unsigned char currChar = 0; // текущий символ
  switch (stepName) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 101: { // ждем запятую
        if (inBt == ',') ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 102: { // ждем цифру
        if (isdigit((char)inBt)) ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 103: { // ждем запятую
        if (inBt == ',') ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 104: { // ждем кавычки
        if (inBt == '\"') {
          ++stepName; // если да - переходим на другой шаг
          currChar = 0;
        } else {
          stepName = 0;
        }
        break;
      }
    case 105: { // заполняем строку или при приходе кавычки выходим
        if (inBt == '\"') {
          stepName = 0;
        } else if ((currChar < (maxSizeOperatorName - 1)) && (inBt >= 0x20) && (inBt < 0x7f)) {
          operatorName[currChar] = inBt; ++currChar; operatorName[currChar] = 0;
        }
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+COPS: ", inBt)) stepName = 100; // ждем строку +COPS И начинаем пошагово обрабатывать символы
      }
  }
}

void processOperatorCSQ(const unsigned char inBt) { // парсинг ответа модема вытаскиваем качество связи
  static unsigned char stepCSQ = 0; // текущий шаг
  switch (stepCSQ) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) {
          operatorCSQ = inBt - '0';
          ++stepCSQ; // если да - переходим на другой шаг
        } else {
          stepCSQ = 0;
        }
        break;
      }
    case 101: { // ждем или цифру или запятую
        if (isdigit((char)inBt)) {
          operatorCSQ = operatorCSQ * 10 + (inBt - '0');
        }
        stepCSQ = 0;
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+CSQ: ", inBt)) stepCSQ = 100; // ждем строку +CSQ И начинаем пошагово обрабатывать символы
      }
  }
}

_Bool processCREG(const unsigned char inBt) { // парсинг ответа модема на команду AT+CREG
  static unsigned char stepCREG = 0; // текущий шаг
  switch (stepCREG) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) ++stepCREG; else stepCREG = 0; // если да - переходим на другой шаг
        break;
      }
    case 101: { // ждем запятую
        if (inBt == ',') ++stepCREG; else stepCREG = 0; // если да - переходим на другой шаг
        break;
      }
    case 102: { // ждем цифру регистрации в домашней сети или в роуминге
        if ((inBt == '1') || (inBt == '5')) {
          stepCREG = 0; return true; // если да - положительно выходим
        }  else {
          stepCREG = 0;
        }
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+CREG: ", inBt)) stepCREG = 100; // ждем строку +CREG И начинаем пошагово обрабатывать символы
      }
  }
  return false; // иначе выходим с отрицательным результатом
}

void clearRespBuf(void) { // очистка приемного буфера
  memset(respBufModem, 0 , maxSizeRespBuf); // чистим
  posModemBuf = 0; // обнуляем текущую позицию
}

void sendMODEMcmd(char * inStr, const _Bool showLog) { // отправка модему команды
  modemStrPrint(inStr); // кидаем строку модему
  if (showLog) {
    logStrPrint(inStr); // и если нужно - дублируем в лог
  }
}

void sendATcmdByIdx(byte cmdIdx, const _Bool flClearBuf) { // отправка команды модему из progmem
  char inStr[maxSizeBrokerID];
  const char * addrStroki = pgm_read_word_near((int)(at_list_cmd + cmdIdx));
  strcpy_P(inStr, addrStroki);
  sendMODEMcmd(inStr, true); // отправляем с дублированием в логе
  if (flClearBuf) clearRespBuf(); // если необходимо - чистим буфер
  waitRespModem = true; // ждем ответа на команду
  timerModem = currentMillis; // обнуляем основной таймер ожидания
}

void processInitModem(const unsigned char inByte, const _Bool inExist) { // инициалдизация модема
  switch (currentInitModem) { // работаем по текущему шагу
    case modemBegin: { // самое начало
        currentInitModem = modemFirst; // переходим на след шаг
        stepModem = 0; // обнуляем внутренний шаг
        clearRespBuf(); // чистим буфер приема
        timerModem = currentMillis; // обнуляем таймер
        waitRespModem = 0; // не ждем ответ - первый старт
        break;
      }
    case modemFirst: { // начальная инициализация - работает всегда
        if (!waitRespModem) {
          if (((currentMillis - timerModem) >= periodWaitModemBegin) && (!stepModem)) { // ждем начальное время - даем модему прочухаться
            sendATcmdByIdx(0, true); // проверка связи с модемом AT / сброс настроек на default - ATZ
          }
        } else {
          if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
            currentInitModem = modemRestart; // надо сбросить модем
            return; // выходим
          }
          switch (stepModem) {
            case 0: {
                if (inExist) if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                    sendATcmdByIdx(1, true); ++stepModem; // отключение ЭХО, т е модем в обратку не дублирует команды
                  }
                break;
              }
            case 1: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(2, true); ++stepModem; // развернуты подробный ответ от модема
                  }
                break;
              }
            case 2: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(3, true); ++stepModem; // вывод подробных описаний ошибок
                  }
                break;
              }
            case 3: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(4, true); ++stepModem; // закрыть все интернет сессии
                  }
                break;
              }
            case 4: {
                if (inExist) if (findOKfromBuf(inByte)) {
                    sendATcmdByIdx(5, true); ++stepModem; // отключение АОН
                  }
                break;
              }
            case 5: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(6, true); ++stepModem; // модем не берет трубку при входящем звонке
                  }
                break;
              }
            case 6: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(7, true); ++stepModem; // обычный режим вывода СМСе
                  }
                break;
              }
            case 7: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(8, true); ++stepModem; // отключение работы энергосберегающего режима через пин DTR
                    ++stepModem; // пропустим следующую команду // по умолчанию и так в normal mode модем работает
                  }
                break;
              }
            case 8: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(9, true); ++stepModem; // моносокет - единомоментно работаем с одним подключением
                  }
                break;
              }
            case 9: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(10, true); ++stepModem; // не добавлять заголовок при приеме данных
                  }
                break;
              }
            case 10: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(11, true); ++stepModem; // автоматический вывод принятых данных
                  }
                break;
              }
            case 11: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(12, true); ++stepModem; // не показывать данные отправителя при приеме данных
                  }
                break;
              }
            case 12: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    currentInitModem = modemStartReg; // переходим в режим регистрации модема у сотового оператора - основная логика
                  }
                break;
              }
            default: {}
          }
        }
        break;
      }
    case modemStartReg: { // начало регистрации в сети оператора
        stepModem = 0; // начинаем с начала
        currentInitModem = modemReg; // следующий шаг - проверка зарегестрировался ли модем и СИМ карта
        timerModem = currentMillis + periodWaitRegistrationModem; // обнуляем таймер
        readyModem = currentMillis; // обнуляем таймер
        break;
      }
    case modemReg: { // проверяем зарегестрировалась ли симка
        if ((currentMillis - readyModem) >= periodReadyModem) { // если за время отведенное не зарегестрировались
          currentInitModem = modemRestart; // перезагружаемся
          return; // выходим
        }
        switch (stepModem) { // вработаем последовательно по шагам
          case 0: {
              if ((currentMillis - timerModem) >= periodWaitRegistrationModem) { // ждем время
                sendATcmdByIdx(14, true); ++stepModem; // проверяем зарегестрировался ли модем в сети
                goodCREG = false; // сбрасываем флаг что зарегестрирован
              }
              break;
            }
          case 1: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) { // если пришел байт
                if (processCREG(inByte)) goodCREG = true; // парсим строку - если зарегестрировались - ставим флаг
                if (findOKfromBuf( inByte)) { // ищем ответ ОК
                  if (goodCREG) { // если успешно зарегестрировались
                    ++stepModem; // переходим на след шаг
                  } else { // если так и не смогли зарегестрироваться
                    stepModem = 0; timerModem = currentMillis; // через время periodWaitRegistrationModem опять отправляем команду для проверки зарегестрировалась ли СИМ карта
                  }
                }
              }
              break;
            }
          case 2: {
              sendATcmdByIdx(15, true); ++stepModem; // получить режим подключения к оператору и имя оператора
              operatorName[0] = 0; // очищаем имя оператора
              break;
            }
          case 3: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) {
                processOperatorName(inByte); // парсим ответ
                if (findOKfromBuf( inByte)) {
                  sendATcmdByIdx(16, true); // запрос качества связи
                  ++stepModem; operatorCSQ = 0;
                  //logStrPrint((char *)operatorName); logStrPrint((char *)"\r\n");
                }
              }
              break;
            }
          case 4: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) {
                processOperatorCSQ(inByte); // парсим ответ
                if (findOKfromBuf( inByte)) {
                  //logDecPrint(operatorCSQ); logStrPrint((char *)"\r\n");
                  sendATcmdByIdx(17, true); ++stepModem; // командный режим передачи данных
                  //++stepModem; // пропустим удаление СМС - модем не всегда при включении успевает инициализироваться :( и происходит перезагрузка
                }
              }
              break;
            }
          case 5: {
              if (inExist) if (findOKfromBuf( inByte)) {
                  sendATcmdByIdx(18, true); ++stepModem; // удаление всех SMS на SIM карте
                }
              break;
            }
          case 6: {
              if (inExist) if (findOKfromBuf( inByte)) {
                  currentInitModem = modemReady; // закончили инициализацию модема
                }
              break;
            }
          default: {}
        }
        break;
      }
    default: {}
  }
}

_Bool findOKfromBuf(const unsigned char inByte) { // поиск OK\r\n в циклическом буфере
  const char inStr[] = "OK\r\n"; // искомая строка
  byte slen = 4; // длина искомой строки
  if (inByte == inStr[slen - 1]) { // если последний символ совпадает - продолжаем искать всю строку
    byte abpos = posModemBuf; // абсолютная позиция в буфере поиска-1  = последнему символу искомой строки
    if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // ищем с предпоследнего символа
    for ( byte i = 0; i < (slen - 1); ++i) { // цикл по оставшимся символам
      if (inStr[slen - 1 - i] != respBufModem[abpos]) return false; // если не сопадабт символы - выходим
      if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // уменьшаем счетчики
    }
  } else {
    return false;
  } return true;
}

_Bool findRespFromBuf(char * inStr, const unsigned char inByte) { // поиск строки в циклическом буфере
  byte slen = strlen((const char *)inStr); // длина искомой строки
  if (inByte == inStr[slen - 1]) { // если последний символ совпадает - продолжаем искать всю строку
    byte abpos = posModemBuf; // абсолютная позиция в буфере поиска-1  = последнему символу искомой строки
    if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // ищем с предпоследнего символа
    for ( byte i = 0; i < (slen - 1); ++i) { // цикл по оставшимся символам
      if (inStr[slen - 1 - i] != respBufModem[abpos]) return false; // если не сопадабт символы - выходим
      if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // уменьшаем счетчики
    }
  } else {
    return false;
  } return true;
}

//options.h // пользовательские настройки железки
#ifndef OPTIONS_H
#define OPTIONS_H

#include <Arduino.h>

#define ADMIN_PHONE "79009698478" // номер администратора

#define ussd_balance "AT+CUSD=1,\"*100#\"\r" // строка запроса баланса телефона // команда зависит от оператора, погоды на луне, настроения БС и т д......Гарантии что баланс придет в нормальном виде никакой.
#define period_get_balance 864007979UL // получение баланса телефона раз в 10 суток

#define errTemp ((word)2100) // значение температуры ошибка, т е еще не получено
static constexpr byte ds18_pin = A2; // пин куда подключен DS18B20
#define period_get_temp 64963UL // получение температуры каждые 65 сек

static constexpr byte pinExtPower  = A3; // пин где определяем наличие внешнего питания
#define period_control_ext_power 4000UL // время в течении которого контролируется изменение питание и после которого шлется СМС

#define period_read_sms 15967UL // чтение всех СМС каждые 16 сек
#define max_size_abonent_sms 16 // размер буфера номера абонента
#define max_size_len_read_sms 64 // размер буфера текста СМС прием

// настройки работы модема
static constexpr byte pinResetModem  = A1; // пин сброса модема SIM800L
#define periodWaitModemBegin 10000UL // время ожидания после включения, прежде чем начнем работать с модемом // модем прочухивается
#define periodWaitShortCmdModem 7000UL // время ожидания ответа на короткие команды, обычно ждем OK, и если нет - сброс модема // на команде CIPSHUT закрытия всех сессий интернета при старте может не хватить, тогда или увеличить или пропустить эту команду при старте
#define periodWaitRegistrationModem 5000UL // каждый этот период спрашивается зарегестрировался ли модем
#define periodReadyModem 300000UL // общее время, которое дается на регистрацию в сети, иначе перезагрузка модема
#define maxSizeRespBuf ((byte)64) // максимальный размер входящего буфера для данных из модема - кольцевой буфер FIFO, по идее должен быть чуть больше максимальной лексемы
#define maxSizeOperatorName ((byte)16) // максимальная длина поля имени оператора связи
#define maxSizeBrokerID ((byte)21) // максимальный размер отправляемой команды модему

// настройка SoftWare Serial
static constexpr byte pinSwUART_TX = 3; // RX вход устройства, принимающего/отображающее данные/лог
static constexpr byte pinSwUART_RX = 2; // TX выход устройства, от которого получать данные

#endif  /* OPTIONS_H */


 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Обновленный скетч:
- добавлен дополнительный контроль питания на пине A0 
- увеличено время ожидания получения СМС команды по даташиту до 20 секунд

#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include "options.h" // настройки железки
#include "dmfcomm.h" // основные переменные и функции
#include "modinit.h" // инициализация модема
#include "dmfsms.h" // работа с СМС

// инициализация WDT (код из даташита + из форума, хз как работает)
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((used)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

void inCallbackSMS(char * fromSMS, char * textSMS) { // обработчик входящего сообщения
  static char toSendPhone[max_size_abonent_sms];
  if (strcmp(fromSMS, ADMIN_PHONE) == 0) {
    //Serial.println(F("SMS from Admin phone"));
    // find command from sms
    if (findSubstring((char *)"getdata", textSMS)) {
      //Serial.println(F("Input command: getdata"));
      // exec cmd
      strcpy(toSendPhone, (char *)ADMIN_PHONE);
      sendSMS(toSendPhone, prepareStandartSMS());
    } else if (findSubstring((char *)"devreset", textSMS)) {
      //Serial.println(F("Input command: devreset"));
      modemReset();
      delay(10000);
    }
  } else {
    //Serial.print(F("SMS from: ")); Serial.println(fromSMS);
  }
  //Serial.println(textSMS);
}

void processModemActions() { // основная процедуры работы/общение с модемом
  unsigned char br = 0; // переменная куда падает входящий байт из модема
  _Bool flbr = false; // влаг что пришедший байт не равен нулю
  if (modemAvailable()) { // если что то пришло из модема
    br = modemRead(); // читаем пришедший байт
    if (br) { // если он больше нуля
      if (posModemBuf >= maxSizeRespBuf) posModemBuf = 0; respBufModem[posModemBuf] = br; ++posModemBuf; // добавляем пришедший байт в круговой буфер
      if ((br == '\r') || (br == '\n') || ((br >= ' ') && (br < 0x7F))) { // если символ печатный или конец строки
        logWrite(br); // выводим его в лог
      } else { // если не печатный
        logHexPrint(br); logWrite(' '); // выводим его шестнадцатиричное представление в лог
      }
      flbr = true; // ставим флаг что пришедший байт больше нуля
    }
  }
  // main process device with modem // тут начинаем запускать все без исключения модули, работающие с информацией/модемом
  if (currentInitModem != modemStop) { // если модем еще не вышел на связь и не зарегестрировался в сети
    processInitModem(br, flbr); // работаем с модемом
    if (currentInitModem == modemRestart) { // если все плохо и необходимо сбросить модем
      modemReset(); // сбрасываем его
      startDmfDevice(); // заново запускаем всю логику работы устройства
      return; // выходим
    } else if (currentInitModem == modemReady) { // если все хорошо со связью
      clearRespBuf(); // очищаем приемный буфер модема
      currentInitModem = modemStop; // останавливаем инициализацию модема
      // go to next step INIT CIP // переходим в обработку
      currentSms = smsBegin;
    }
  }
  // обработка СМС
  if (currentSms != smsStop) { // если нет коннекта к брокеру - работаем
    processSms(br, flbr); // работаем с модемом
    if (currentSms == smsRestart) { // если все плохо и необходимо сбросить модем
      modemReset(); // сбрасываем его
      startDmfDevice(); // заново запускаем всю логику работы устройства
      return; // выходим
    }
  }
  // end main process
}

void startDmfDevice() { // начальные настройки всего модуля
  Serial.println(F("Start!"));
  currentSms = smsStop; // останавливаем работу с модемом
  currentInitModem = modemBegin; // запускаем модем
}

void setup() {
  MCUSR = 0; // отключение WDT
  wdt_disable(); // отключение WDT
  // put your setup code here, to run once:
  initDmfDevice(); // инициализируем пины
  setCallbackSMS(inCallbackSMS);
  startDmfDevice(); // запускаем железку
  wdt_enable(WDTO_8S); // включение WDT
}

void powerOnOffControl(void) {
  static byte flagSendSMS = 0;
  static char toSendProblem[max_size_abonent_sms];
  static byte lastLevelPower = 1;
  static byte levelRealPower = lastLevelPower;
  static byte startControl = 0;
  static unsigned long timerControl;
  byte currentPowerLevel = getExternalPower();
  if (currentPowerLevel != lastLevelPower) {
    //if (currentPowerLevel) Serial.println(F("External power ON")); else Serial.println(F("OFF external power"));
    startControl = 1;
    timerControl = currentMillis;
  } else if (startControl) {
    if ((currentMillis - timerControl) >= period_control_ext_power) {
      startControl = 0;
      if (levelRealPower != currentPowerLevel) {
        levelRealPower = currentPowerLevel;
        if (currentInitModem == modemStop) {
          //Serial.println(F("send power SMS"));
          flagSendSMS = 1;
        }
      }
    }
  }
  lastLevelPower = currentPowerLevel;
  // control additional power
  static byte lastLevelAdd = 1;
  static byte levelRealAdd = lastLevelAdd;
  static byte addControl = 0;
  static unsigned long addTimer;
  byte currentAddLevel = getAdditionalPower();
  if (currentAddLevel != lastLevelAdd) {
    addControl = 1;
    addTimer = currentMillis;
  } else if (addControl) {
    if ((currentMillis - addTimer) >= period_control_ext_power) {
      addControl = 0;
      if (levelRealAdd != currentAddLevel) {
        levelRealAdd = currentAddLevel;
        if (currentInitModem == modemStop) {
          flagSendSMS = 1;
        }
      }
    }
  }
  lastLevelAdd = currentAddLevel;
  // send SMS ?
  if (flagSendSMS) {
     flagSendSMS = 0;
     Serial.println(F("send power SMS"));
     strcpy(toSendProblem, (char *)ADMIN_PHONE);
     sendSMS(toSendProblem, prepareStandartSMS());
  }
}

void loop() {
  wdt_reset(); // сброс WDT
  //delay(10000); // test WDT // раскоменнтировать строку для проверки WDT , МК будет перезагружаться каждые 10 секунд
  currentMillis = millis(); // главный таймер
  // put your main code here, to run repeatedly:
  processModemActions(); // работаем с модемом
  // get data from temp sensors
  if (dsCount) readTempFromAllSensors();
  // контроль включения/отключения внешнего питания
  powerOnOffControl();
  // выводим данные на дисплейчик
  showOLEDdata();
}

//dmfcomm.h // модуль начального инициализирования устройств/пинов и глобальные переменные/функции
#ifndef DMFCOMM_H
#define DMFCOMM_H

#include <Arduino.h>
#include "options.h" // модуль пользовательских настроек
#include "DallasTemperature.h"
#include <OneWire.h>

static constexpr byte max_count_reset_error = 20; // максимальное количество видов ошибок
extern unsigned long lastTimeGetBalance; // время от последнего запроса баланса

extern byte dsCount; // количество датчиков температуры
extern word dsTemp; // прочитанное значение температуры, всегда положительное, последний знак - десятичные доли градуса, если отрицательное значение - от 2000
void readTempFromAllSensors(void); // получаем температуру со всех датчиков

byte getExternalPower(void); // получение значения на пине внешнего питани
byte getAdditionalPower(void); // получение значения на пине внешнего питани

void showOLEDdata(void); // отображение данных на OLED 128*64 I2C

extern unsigned long currentMillis;
void initDmfDevice();
void modemReset(void); // сброс модема
void logStrPrint(char * inStr);
void logWrite(byte inCh);
void logHexPrint(signed long inHex);
void logDecPrint(signed long inInt);
void modemStrPrint(char * inStr);
void modemWrite(byte inCh);
byte modemAvailable();
byte modemRead();

#endif  /* DMFCOMM_H */

//dmfcomm.cpp // модуль начального инициализирования устройств/пинов и глобальные переменные/функции
#include <Arduino.h>
#include "dmfcomm.h"
#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include <Wire.h>
#include "OzOLED.h"
#include "modinit.h"
#include "dmfsms.h"

unsigned long lastTimeGetBalance; // время от последнего запроса баланса

unsigned long currentMillis; // главный таймер

byte getExternalPower(void) { // получение значения на пине внешнего питани
  return digitalRead(pinExtPower);
}

byte getAdditionalPower(void) { // получение значения на пине внешнего питани
  return digitalRead(pinAddPower);
}

void printAllDataOLED(void) { // очистить дисплей и вывести данные
  OzOled.clearDisplay();
  if (getExternalPower()) OzOled.printString("Power ON"); else OzOled.printString("Power OFF");
  OzOled.setCursorXY(0, 1);
  if (getAdditionalPower()) OzOled.printString("Ext.power ON"); else OzOled.printString("Ext.power OFF");
  OzOled.setCursorXY(0, 2);
  char tmpStr[20]; tmpStr[0] = 0;
  if (currentInitModem == modemStop) {
    strcat(tmpStr, (char *)operatorName);
    OzOled.printString((const char *) tmpStr);
  } else {
    OzOled.printString("Starting device");
  }
  OzOled.setCursorXY(0, 3);
  if (currentInitModem == modemStop) { // have CSQ
    OzOled.printString("CSQ: "); OzOled.printNumber((long)operatorCSQ, 5, 3);
  }
  OzOled.setCursorXY(0, 5);
  OzOled.printString("Temp: ");
  if ((dsCount) && (dsTemp != errTemp)) {
    word absTemp = dsTemp;
    if (absTemp >= 2000) {
      OzOled.printString("-");
      absTemp -= 2000;
    }
    tmpStr[0] = 0;
    itoa((absTemp / 10), tmpStr, 10);
    OzOled.printString((const char *)tmpStr);
    OzOled.printString(".");
    tmpStr[0] = 0;
    itoa((absTemp % 10), tmpStr, 10);
    OzOled.printString((const char *)tmpStr);
  } else {
    OzOled.printString("error");
  }
  OzOled.setCursorXY(0, 7);
  OzOled.printString("Uptime: ");
  tmpStr[0] = 0;
  itoa((currentMillis / 86400000UL), tmpStr, 10);
  OzOled.printString((const char *)tmpStr);
  OzOled.printString("d ");
  tmpStr[0] = 0;
  itoa(((currentMillis % 86400000UL) / 3600000UL), tmpStr, 10);
  OzOled.printString((const char *)tmpStr);
  OzOled.printString("h");
}

void showOLEDdata(void) { // отображение данных на OLED 128*64 I2C
  static unsigned long showTimer, showData;
  static byte inShow = 1;
  if (currentInitModem != modemStop) { // инциалимзация модема - дисплей не отключаем, обновляем данные каждые 2 секунды
    if ((currentMillis - showTimer) >= 2000UL) {
      showTimer = currentMillis;
      printAllDataOLED();
    }
  } else { // зажигаем по времени
    if (inShow) {
      if ((currentMillis - showTimer) >= 5000UL) {
        inShow = 0;
        OzOled.clearDisplay();
      }
    } else {
      if ((currentSms == smsWait)  && ((currentMillis - showData) >= period_read_sms)) {
        printAllDataOLED();
        inShow = 1;
        showTimer = currentMillis;
        showData = currentMillis;
      }
    }
  }
}

#include <SoftwareSerial.h> // стандартная библиотека работы с SoftwareSerial

SoftwareSerial swuart(pinSwUART_RX, pinSwUART_TX); // установка контактов

#define TEMPERATURE_PRECISION 9 // точность получения значения темературы
OneWire dsWire(ds18_pin); // объект 1Ware
DallasTemperature sensors(&dsWire); // объект  датчика температуты
byte dsCount; // количество датчиков температуры
DeviceAddress dsAddres; // адрес датчика
word dsTemp; // прочитанное значение температуры, всегда положительное, последний знак - десятичные доли градуса, если отрицательное значение - от 2000

void readTempFromAllSensors(void) { // получаем температуру со всех датчиков
  static unsigned long timer_get_temp = period_get_temp - 10000UL; // таймер получения температуры
  static byte requestTemp = 1; // запрос температуры датчиков
  // get temper // каждое N время получаем температуру со всех датчиков
  if ((currentMillis - timer_get_temp) >= period_get_temp) {
    if (requestTemp) {
      sensors.requestTemperatures(); requestTemp = 0;
    } else if ((currentMillis - timer_get_temp) >= (period_get_temp + 297UL)) { // for 9 bit PRECISION
      timer_get_temp = currentMillis; requestTemp = 1;
      float tct = sensors.getTempC(dsAddres);
      if ((tct >= 95.0) || (tct <= -35.0)) { // температура не получилась
        dsTemp = errTemp;
      } else {
        if (tct < 0) dsTemp = ((-tct) * 10) + 2000; else dsTemp = (tct * 10);
      }
      //Serial.print(F("t = ")); Serial.println(dsTemp, DEC); // печать температур в консоль
    }
  }
}

void printAddress(DeviceAddress deviceAddress) { // function to print a device address DS18B20
  for (uint8_t i = 0; i < 8; i++) {
    // zero pad the address if necessary
    if (deviceAddress[i] < 16) Serial.print('0');
    Serial.print(deviceAddress[i], HEX);
    if (i < 7) Serial.print('-');
  }
  Serial.println(); // новая строка
}

void initFindDefineSensors(void) { // инициализация OneWire DS18B20 поиск и чтение адресов всех датчиков температуры
  dsWire.reset(); // сброс шины
  delay(750); // пауза
  wdt_reset(); // сброс WDT
  sensors.begin(); // инициировать датчики температуры
  delay(750); // пауза
  wdt_reset(); // сброс WDT
  dsCount = sensors.getDeviceCount(); // получить количество датчиков
  Serial.print(F("Found DS18B20 = ")); Serial.println(dsCount, DEC); // вывести количество
  if (dsCount) {
    sensors.getAddress(dsAddres, 0); // получение адреса
    sensors.setResolution(dsAddres, TEMPERATURE_PRECISION); // настраиваем разрешение
    printAddress(dsAddres); // печатаем адрес
  }
  dsTemp = errTemp; // температура еще не получена
}

void initLogModemUART() {
  swuart.begin(9600);
  Serial.begin(9600);
}

void logStrPrint(char * inStr) { // вывод строки в лог
  Serial.print(inStr);
}

void logWrite(byte inCh) { // вывод байта в лог
  Serial.write((char)inCh);
}

void logHexPrint(signed long inHex) { // вывод шестнадцатиричного числа в лог
  logWrite('x');
  Serial.print(inHex, HEX);
}

void logDecPrint(signed long inInt) { // вывод десятичного числа в лог
  Serial.print(inInt, DEC);
}

void modemStrPrint(char * inStr) { // строку в модем
  swuart.print(inStr);
}

void modemWrite(byte inCh) { // байт в модем
  swuart.write((char)inCh);
}

byte modemAvailable() { // есть ли данные в модеме
  return swuart.available();
}

byte modemRead() { // чтение из модема
  return swuart.read();
}

void initDmfDevice() { // инициализация всего устройства
  OzOled.init();  //initialze Oscar OLED display
  OzOled.printString("Init...."); //Print the String
  initLogModemUART();
  digitalWrite(pinResetModem, LOW); pinMode(pinResetModem, INPUT); // инициализация пина сброса модема
  digitalWrite(pinExtPower, LOW); pinMode(pinExtPower, INPUT); // инициализация пина получения питания
  digitalWrite(pinAddPower, LOW); pinMode(pinAddPower, INPUT); // инициализация пина получения питания
  initFindDefineSensors();
  if (!dsCount) { // не нашлись датчики, попробуем все сбросить и заново проинициализировать
    dsWire.reset_search(); initFindDefineSensors();
  }
}

void modemReset(void) { // сброс модема
  pinMode(pinResetModem, OUTPUT); delay(115); pinMode(pinResetModem, INPUT);
}

// dmfsms.h - модуль работы с СМС
#ifndef DMFSMS_H
#define DMFSMS_H

#include <Arduino.h>
#include "options.h" // модуль пользовательских настроек
#include "modinit.h" // работа с модемом

enum typeSmsStep {smsBegin, smsRestart, smsStop, smsWait, smsReadAll, smsGetBalance, smsSend, smsParseBalance}; // шаги
extern enum typeSmsStep currentSms; // переменная - текущий шаг

void processSms(const unsigned char inByte, const _Bool inExist); // work sms
void setCallbackSMS(void (*inCallback)(char *, char *)); // установка обработчика сообщения
byte findSubstring(char * subStr, char * inStr); // поиск подстроки в строке, используется для поиска команд в СМСке

extern word balancePhone; // баланс SIM карты телефона, целое число, для получения нужной суммы - поделить на 100

void sendSMS(char * toPhone, char * smsText); // отправка сообщения

char * prepareStandartSMS(void); // делает типовую СМСку питание/температуру/аптайм

const char smsc0[] PROGMEM = "AT+CMGL=\"ALL\"\r";
const char smsc1[] PROGMEM = "AT+CMGDA=\"DEL ALL\"\r";
const char smsc2[] PROGMEM = ussd_balance;
const char smsc3[] PROGMEM = "AT+CMGS=\"+";
// массив всех команд
const char * const at_list_smsc[] PROGMEM = { // text command
  smsc0, smsc1, smsc2, smsc3
};

#endif  /* DMFSMS_H */

// dmfsms.cpp - модуль работы с СМС
#include <Arduino.h>
#include "dmfsms.h"
#include "dmfcomm.h"
#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include "modinit.h" // инициализация модема

enum typeSmsStep currentSms; // переменная - текущий шаг
_Bool waitRespSms; // флаг ожидания ответа на команду
unsigned long timerWorkCmd; // рабочий таймер команд
word balancePhone = 0; // баланс SIM карты телефона, целое число, для получения нужной суммы - поделить на 100
byte stepSms; // рабочий текущий шаг

byte flNeedSendSMS;
char * outSmsPhone;
char * outTextSms;

static void (*callbackReadOneSMS)(char *, char *); // внешний обработчик пришедшей СМС

void sendSMS(char * toPhone, char * smsText) { // отправка сообщения
  outSmsPhone = toPhone; outTextSms = smsText;
  flNeedSendSMS = 1;
}

char * prepareStandartSMS(void) { // делает типовую СМСку питание/температуру/аптайм
  static char outTextToSMS[250];
  outTextToSMS[0] = 0;
  if (getExternalPower()) strcpy(outTextToSMS, "PowON"); else strcpy(outTextToSMS, "PowOFF"); // 9 char
  if (getAdditionalPower()) strcat(outTextToSMS, " InON"); else strcat(outTextToSMS, " InOFF"); // 9 char
  char tmpStr[10]; tmpStr[0] = 0;
  itoa(operatorCSQ, tmpStr, 10);
  strcat(outTextToSMS, " C:"); // 6 char
  strcat(outTextToSMS, tmpStr); // 4 char
  strcat(outTextToSMS, " t:"); // 7 char
  if (dsTemp == errTemp) {
    strcat(outTextToSMS, "Err"); // 4 char
  } else {
    word absTemp = dsTemp;
    if (absTemp >= 2000) {
      absTemp -= 2000;
      strcat(outTextToSMS, "-"); // 2 char
    }
    tmpStr[0] = 0;
    itoa((absTemp / 10), tmpStr, 10);
    strcat(outTextToSMS, tmpStr); // 4 char
    strcat(outTextToSMS, "."); // 2 char
    tmpStr[0] = 0;
    itoa((absTemp % 10), tmpStr, 10);
    strcat(outTextToSMS, tmpStr); // 2 char
  }
  strcat(outTextToSMS, " u:"); // 9 char
  tmpStr[0] = 0;
  itoa((currentMillis / 86400000UL), tmpStr, 10);
  strcat(outTextToSMS, tmpStr); // 5 char
  strcat(outTextToSMS, "d"); // 2 char
  tmpStr[0] = 0;
  itoa(((currentMillis % 86400000UL) / 3600000UL), tmpStr, 10);
  strcat(outTextToSMS, tmpStr); // 5 char
  strcat(outTextToSMS, "h"); // 2 char
  if (balancePhone) {
    strcat(outTextToSMS, " b:");
    tmpStr[0] = 0;
    itoa((balancePhone / 100), tmpStr, 10);
    strcat(outTextToSMS, tmpStr);
    strcat(outTextToSMS, ".");
    tmpStr[0] = 0;
    itoa((balancePhone % 100), tmpStr, 10);
    strcat(outTextToSMS, tmpStr);
  }
  strcat(outTextToSMS, " lb:");
  tmpStr[0] = 0;
  itoa(((currentMillis - lastTimeGetBalance) / 3600000UL), tmpStr, 10);
  strcat(outTextToSMS, tmpStr);
  strcat(outTextToSMS, "h");
  return outTextToSMS;
}

byte findSubstring(char * subStr, char * inStr) { // поиск подстроки в строке, используется для поиска команд в СМСке
  byte ssLen = strlen(subStr);
  byte isLen = strlen(inStr);
  if (ssLen <= isLen) {
    byte flGood = 0;
    for (byte startFindPosInStr = 0; startFindPosInStr <= (isLen - ssLen); ++startFindPosInStr) {
      byte countFound = ssLen;
      for (byte posSubStr = 0; posSubStr < ssLen; ++posSubStr) {
        if (toupper(subStr[posSubStr]) == toupper(inStr[posSubStr + startFindPosInStr])) --countFound; else break;
      }
      if (!countFound) {
        flGood = 1; break;
      }
    }
    if (flGood) return 1;
  }
  return 0;
}


void setCallbackSMS(void (*inCallback)(char *, char *)) { // установка обработчика сообщения
  callbackReadOneSMS = inCallback;
}

void parseSmsText(byte newSms, const unsigned char inSms) { // парсинг тескта СМС
  static char abonentNumber[max_size_abonent_sms];
  static char textSMS[max_size_len_read_sms];
  static byte parseStep = 0; // шаг работы
  if (newSms) {
    parseStep = 0; return;
  }
  if (findRespFromBuf((char *)"\r\n", inSms)) { // прилетел конец строки
    if ((abonentNumber[0] != 0) && (parseStep == 5)) { // номер абонента введен
      parseStep = 6; // get text sms
      //Serial.println((char *)abonentNumber);
    } else if ((textSMS[0] != 0) && (parseStep == 6)) { // текст СМС выведен
      parseStep = 0;
      //Serial.println((char *)textSMS);
      wdt_reset(); // сброс WDT
      // exec callback
      callbackReadOneSMS(abonentNumber, textSMS);
    }
  } else { // смотрим побайтно
    switch (parseStep) {
      case 0: {
          memset(abonentNumber, 0, max_size_abonent_sms); memset(textSMS, 0, max_size_len_read_sms); // стерли строки
          if (isdigit(inSms)) ++parseStep; else parseStep = 0;
          break;
        }
      case 1: {
          if (inSms == ',')  ++parseStep; else parseStep = 0;
          break;
        }
      case 2: {
          if (inSms == '\"')  ++parseStep; else parseStep = 0;
          break;
        }
      case 3: {
          if (findRespFromBuf((char *)"\",\"+", inSms)) ++parseStep;
          break;
        }
      case 4: { // phone number
          if (inSms == '\"') {
            ++parseStep;
          } else if (isdigit(inSms)) {
            byte len = strlen((char *)abonentNumber);
            if (len < (max_size_abonent_sms - 1)) {
              abonentNumber[len] = inSms; abonentNumber[len + 1] = 0;
            }
          } else {
            parseStep = 0;
          }
          break;
        }
      case 5: { // dont need data - wait new string
          break;
        }
      case 6: { // text SMS
          byte len = strlen((char *)textSMS);
          if (len < (max_size_len_read_sms - 1)) {
            textSMS[len] = inSms; textSMS[len + 1] = 0;
          }
          break;
        }
      default: {}
    }
  }
}

void smsMODEMcmd(char * inStr, const _Bool showLog) { // отправка модему команды
  modemStrPrint(inStr); // кидаем строку модему
  if (showLog) {
    logStrPrint(inStr); // и если нужно - дублируем в лог
  }
}

void sendATsmsByIdx(byte cmdIdx, const _Bool flClearBuf) { // отправка команды модему из progmem
  char inStr[maxSizeBrokerID];
  const char * addrStroki = pgm_read_word_near((int)(at_list_smsc + cmdIdx));
  strcpy_P(inStr, addrStroki);
  smsMODEMcmd(inStr, true); // отправляем с дублированием в логе
  if (flClearBuf) clearRespBuf(); // если необходимо - чистим буфер
  waitRespSms = true; // ждем ответа на команду
  timerWorkCmd = currentMillis; // обнуляем основной таймер ожидания
}

void parseUssdText(byte newUssd, const unsigned char inUssd) { // парсинг тескта баланса
  static char localBalance[10];
  static char localChar[4];
  static unsigned char localPosChar = 0;
  static unsigned char posLocal = 0;
  if (newUssd) {
    memset(localBalance, 0, 10);
    posLocal = 0;
    localPosChar = 0;
    return;
  }
  if ((isdigit(inUssd)) || ((inUssd >= 'A') && (inUssd <= 'F'))) {
    localChar[localPosChar] = inUssd;
    ++localPosChar;
    if (localPosChar >= 4) { // good char
      if ((localChar[0] == '0') && (localChar[1] == '0') && (localChar[2] == '3') && (isdigit(localChar[3]))) {
        localBalance[posLocal] = localChar[3];
        ++posLocal;
      }
      localPosChar = 0;
    }
  } else if ((inUssd == '"') and (posLocal)) { // end line
    balancePhone = atoi(localBalance);
    //Serial.print("bal="); Serial.println(balancePhone, DEC);
    memset(localBalance, 0, 10);
    posLocal = 0;
    localPosChar = 0;
  }
}

void processSms(const unsigned char inByte, const _Bool inExist) { // work sms
  static unsigned long timerReadSms; // рабочий таймер запуск чтения всех смс
  static unsigned long timerGetBalance = period_get_balance; // рабочий таймер получение баланса телефона // запросить при старте
  switch (currentSms) { // работаем по текущему шагу
    case smsParseBalance: {
        if ((currentMillis - timerWorkCmd) >= (periodWaitShortCmdModem * 4)) { // если не пришел ответ на короткую команду // иногда не полностью приходит ответ
          //currentSms = smsRestart; // надо сбросить модем
          currentSms = smsWait; // возвращаемся в ожидание команд // иногда не полностью приходит ответ USSD
          return; // выходим
        } else if (inExist) {
          if (findRespFromBuf((char *)"\", 72\r\n", inByte)) { // ждем ОК на предыдущую команду
            currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
          } else {
            parseUssdText(0, inByte);
          }
        }
        break;
      }
    case smsBegin: { // самое начало
        timerReadSms = currentMillis; // сбросим таймер
        //timerGetBalance = currentMillis + period_get_balance; // сбросим таймер
        flNeedSendSMS = 0;
        currentSms = smsWait; // переходим в режим ничего неделания
        break;
      }
    case smsWait: { // ничего не делаем - ждем команды
        if ((currentMillis - timerReadSms) >= period_read_sms) { // пора читать СМС
          timerReadSms = currentMillis; // сбросим таймер
          stepSms = 0; // начать с начала
          currentSms = smsReadAll; // запускаем чтение СМС
        } else if ((currentMillis - timerGetBalance) >= period_get_balance) { // пора получить баланс
          timerGetBalance = currentMillis; // сбросим таймер
          stepSms = 0; // начать с начала
          currentSms = smsGetBalance;
          lastTimeGetBalance = currentMillis;
        } else if (findRespFromBuf((char *)"+CUSD: 0, \"", inByte)) { // USSD пришло
          // тут мы должны перевести в шаг парсить ответ
          balancePhone = 0;
          // но мой Мегафон ничего не выдает, или прошивка модема дурит, поэтому данный код не дописан // для МТС работает
          parseUssdText(1, 0); // сьросим обрабьтку
          stepSms = 0; // начать с начала
          currentSms = smsParseBalance; // парсчим ответы
          timerWorkCmd = currentMillis; // сбросим таймер
        } else if ((outSmsPhone[0] > 0) && (outTextSms[0] > 0) && (flNeedSendSMS)) { // если текст абонента получателя и текст СМС не пустые
          stepSms = 0; // начать с начала
          currentSms = smsSend;
        }
        break;
      }
    case smsSend: {
        switch (stepSms) { // работаем по шагам
          case 0: {
              sendATsmsByIdx(3, true);
              smsMODEMcmd(outSmsPhone, true); // отправляем с дублированием в логе
              smsMODEMcmd((char *)"\"\r", true); // отправляем с дублированием в логе
              ++stepSms;
              break;
            }
          case 1: {
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (inByte == '>') { // ждем ОК на предыдущую команду
                  smsMODEMcmd(outTextSms, true); // отправляем с дублированием в логе
                  modemWrite(0x1A); // отправляем конец строки
                  ++stepSms;
                  flNeedSendSMS = 0; outSmsPhone[0] = 0; outTextSms[0] = 0;
                }
              }
              break;
            }
          case 2: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= 60000UL) { // если не пришел ответ на короткую команду // СМСки должго отправляются, ждем 60 секунд согласно даташита SIM800L
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    case smsGetBalance : {
        switch (stepSms) { // работаем по шагам
          case 0: {
              sendATsmsByIdx(2, true); ++stepSms;
              break;
            }
          case 1: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    case smsReadAll: { // читаем сообщения
        switch (stepSms) { // работаем по шагам
          case 0: { // шлем модему команду
              sendATsmsByIdx(0, true); ++stepSms;
              break;
            }
          case 1: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= 20000UL) { // если не пришел ответ на короткую команду // 20 sec by datasheet
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  sendATsmsByIdx(1, true); ++stepSms; // удаляем СМС
                } else if (findRespFromBuf((char *)"+CMGL: ", inByte)) { // прилетело начало СМСки
                  parseSmsText(1, 0); // пометим что новое сообщение
                } else {
                  parseSmsText(0, inByte); // парсим все что пришло
                }
              }
              break;
            }
          case 2: { // ждем ответ
              if ((currentMillis - timerWorkCmd) >= 25000UL) { // если не пришел ответ на короткую команду // 25 sec by datasheet
                currentSms = smsRestart; // надо сбросить модем
                return; // выходим
              } else if (inExist) {
                if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                  currentSms = smsWait; // закончили читать СМС - возвращаемся в ожидание команд
                }
              }
              break;
            }
          default: {}
        }
        break;
      }
    default: {}
  }
}

//modinit.h // модуль включения и инициализации модема
#ifndef MODINIT_H
#define MODINIT_H

#include "options.h" // модуль пользовательских настроек
#include "dmfcomm.h" // модуль начального инициализирования устройств/пинов и глобальные переменные/функции

byte getModeModem();
byte getStepModem();

enum typeModemInitStep {modemBegin, modemFirst, modemStartReg, modemReg, modemRestart, modemStop, modemReady}; // шаги инициализации модема
extern enum typeModemInitStep currentInitModem; // переменная - текущий шаг

void clearRespBuf(void); // очистка приемного буфера
void processInitModem(const unsigned char inByte, const _Bool inExist); // инициализация модема

extern unsigned char posModemBuf; // текущая позиция буфера
extern unsigned char respBufModem[maxSizeRespBuf]; // буфер для обработки входящей информации из модема
extern unsigned char operatorName[maxSizeOperatorName]; // буфер имя оператора
extern byte operatorCSQ; // уровень связи подключения к оператору

_Bool findRespFromBuf(char * inStr, const unsigned char inByte); // поиск строки в циклическом буфере
_Bool findOKfromBuf(const unsigned char inByte); // поиск OK\r\n в циклическом буфере

const char cmd0[] PROGMEM = "ATZ\r";
const char cmd1[] PROGMEM = "ATE0\r";
const char cmd2[] PROGMEM = "ATV1\r";
const char cmd3[] PROGMEM = "AT+CMEE=2\r";
const char cmd4[] PROGMEM = "AT+CIPSHUT\r";
const char cmd5[] PROGMEM = "AT+CLIP=0\r";
const char cmd6[] PROGMEM = "ATS0=0\r";
const char cmd7[] PROGMEM = "AT+CMGF=1\r";
const char cmd8[] PROGMEM = "AT+CSCLK=0\r";
const char cmd9[] PROGMEM = "AT+CIPMUX=0\r";
const char cmd10[] PROGMEM = "AT+CIPHEAD=0\r";
const char cmd11[] PROGMEM = "AT+CIPRXGET=0\r";
const char cmd12[] PROGMEM = "AT+CIPSRIP=0\r";
const char cmd13[] PROGMEM = "AT+CSPN?\r";
const char cmd14[] PROGMEM = "AT+CREG?\r";
const char cmd15[] PROGMEM = "AT+COPS?\r";
const char cmd16[] PROGMEM = "AT+CSQ\r";
const char cmd17[] PROGMEM = "AT+CIPMODE=0\r";
const char cmd18[] PROGMEM = "AT+CMGDA=\"DEL ALL\"\r";
// массив всех команд
const char * const at_list_cmd[] PROGMEM = { // text command
  cmd0, cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, cmd8, cmd9,
  cmd10, cmd11, cmd12, cmd13, cmd14, cmd15, cmd16, cmd17, cmd18
};

#endif  /* MODINIT_H */

//modinit.cpp // модуль включения и инициализации модема
#include <Arduino.h>
#include "modinit.h"
#include "dmfcomm.h"

enum typeModemInitStep currentInitModem; // переменная - текущий шаг
unsigned char stepModem; // рабочий текущий шаг
unsigned long timerModem; // основной таймер
unsigned long readyModem; // таймер ожидания регистрации в сети оператора
_Bool waitRespModem; // флаг ожидания ответа на команду
_Bool goodCREG; // флаг успешной регистрации в сети оператора
byte lastModeModem;

byte getModeModem() {
  return lastModeModem;
}

byte getStepModem() {
  return stepModem;
}

unsigned char posModemBuf; // текущая позиция буфера
unsigned char respBufModem[maxSizeRespBuf]; // буфер для обработки входящей информации из модема

unsigned char operatorName[maxSizeOperatorName]; // буфер имя оператора
byte operatorCSQ; // уровень связи подключения к оператору

void processOperatorName(const unsigned char inBt) { // парсинг ответа модема вытаскиваем имя оператора связи
  static unsigned char stepName = 0; // текущий шаг
  static unsigned char currChar = 0; // текущий символ
  switch (stepName) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 101: { // ждем запятую
        if (inBt == ',') ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 102: { // ждем цифру
        if (isdigit((char)inBt)) ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 103: { // ждем запятую
        if (inBt == ',') ++stepName; else stepName = 0; // если да - переходим на другой шаг
        break;
      }
    case 104: { // ждем кавычки
        if (inBt == '\"') {
          ++stepName; // если да - переходим на другой шаг
          currChar = 0;
        } else {
          stepName = 0;
        }
        break;
      }
    case 105: { // заполняем строку или при приходе кавычки выходим
        if (inBt == '\"') {
          stepName = 0;
        } else if ((currChar < (maxSizeOperatorName - 1)) && (inBt >= 0x20) && (inBt < 0x7f)) {
          operatorName[currChar] = inBt; ++currChar; operatorName[currChar] = 0;
        }
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+COPS: ", inBt)) stepName = 100; // ждем строку +COPS И начинаем пошагово обрабатывать символы
      }
  }
}

void processOperatorCSQ(const unsigned char inBt) { // парсинг ответа модема вытаскиваем качество связи
  static unsigned char stepCSQ = 0; // текущий шаг
  switch (stepCSQ) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) {
          operatorCSQ = inBt - '0';
          ++stepCSQ; // если да - переходим на другой шаг
        } else {
          stepCSQ = 0;
        }
        break;
      }
    case 101: { // ждем или цифру или запятую
        if (isdigit((char)inBt)) {
          operatorCSQ = operatorCSQ * 10 + (inBt - '0');
        }
        stepCSQ = 0;
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+CSQ: ", inBt)) stepCSQ = 100; // ждем строку +CSQ И начинаем пошагово обрабатывать символы
      }
  }
}

_Bool processCREG(const unsigned char inBt) { // парсинг ответа модема на команду AT+CREG
  static unsigned char stepCREG = 0; // текущий шаг
  switch (stepCREG) {
    case 100: { // ждем цифру
        if (isdigit((char)inBt)) ++stepCREG; else stepCREG = 0; // если да - переходим на другой шаг
        break;
      }
    case 101: { // ждем запятую
        if (inBt == ',') ++stepCREG; else stepCREG = 0; // если да - переходим на другой шаг
        break;
      }
    case 102: { // ждем цифру регистрации в домашней сети или в роуминге
        if ((inBt == '1') || (inBt == '5')) {
          stepCREG = 0; return true; // если да - положительно выходим
        }  else {
          stepCREG = 0;
        }
        break;
      }
    default: {
        if (findRespFromBuf((char *) "+CREG: ", inBt)) stepCREG = 100; // ждем строку +CREG И начинаем пошагово обрабатывать символы
      }
  }
  return false; // иначе выходим с отрицательным результатом
}

void clearRespBuf(void) { // очистка приемного буфера
  memset(respBufModem, 0 , maxSizeRespBuf); // чистим
  posModemBuf = 0; // обнуляем текущую позицию
}

void sendMODEMcmd(char * inStr, const _Bool showLog) { // отправка модему команды
  modemStrPrint(inStr); // кидаем строку модему
  if (showLog) {
    logStrPrint(inStr); // и если нужно - дублируем в лог
  }
}

void sendATcmdByIdx(byte cmdIdx, const _Bool flClearBuf) { // отправка команды модему из progmem
  char inStr[maxSizeBrokerID];
  const char * addrStroki = pgm_read_word_near((int)(at_list_cmd + cmdIdx));
  strcpy_P(inStr, addrStroki);
  sendMODEMcmd(inStr, true); // отправляем с дублированием в логе
  if (flClearBuf) clearRespBuf(); // если необходимо - чистим буфер
  waitRespModem = true; // ждем ответа на команду
  timerModem = currentMillis; // обнуляем основной таймер ожидания
}

void processInitModem(const unsigned char inByte, const _Bool inExist) { // инициалдизация модема
  switch (currentInitModem) { // работаем по текущему шагу
    case modemBegin: { // самое начало
        currentInitModem = modemFirst; // переходим на след шаг
        stepModem = 0; // обнуляем внутренний шаг
        clearRespBuf(); // чистим буфер приема
        timerModem = currentMillis; // обнуляем таймер
        waitRespModem = 0; // не ждем ответ - первый старт
        lastModeModem = modemStop;
        break;
      }
    case modemFirst: { // начальная инициализация - работает всегда
        if (!waitRespModem) {
          if (((currentMillis - timerModem) >= periodWaitModemBegin) && (!stepModem)) { // ждем начальное время - даем модему прочухаться
            sendATcmdByIdx(0, true); // проверка связи с модемом AT / сброс настроек на default - ATZ
          }
        } else {
          if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если не пришел ответ на короткую команду
            lastModeModem = currentInitModem;
            currentInitModem = modemRestart; // надо сбросить модем
            return; // выходим
          }
          switch (stepModem) {
            case 0: {
                if (inExist) if (findOKfromBuf(inByte)) { // ждем ОК на предыдущую команду
                    sendATcmdByIdx(1, true); ++stepModem; // отключение ЭХО, т е модем в обратку не дублирует команды
                  }
                break;
              }
            case 1: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(2, true); ++stepModem; // развернуты подробный ответ от модема
                  }
                break;
              }
            case 2: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(3, true); ++stepModem; // вывод подробных описаний ошибок
                  }
                break;
              }
            case 3: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(4, true); ++stepModem; // закрыть все интернет сессии
                  }
                break;
              }
            case 4: {
                if (inExist) if (findOKfromBuf(inByte)) {
                    sendATcmdByIdx(5, true); ++stepModem; // отключение АОН
                  }
                break;
              }
            case 5: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(6, true); ++stepModem; // модем не берет трубку при входящем звонке
                  }
                break;
              }
            case 6: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(7, true); ++stepModem; // обычный режим вывода СМСе
                  }
                break;
              }
            case 7: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(8, true); ++stepModem; // отключение работы энергосберегающего режима через пин DTR
                    ++stepModem; // пропустим следующую команду // по умолчанию и так в normal mode модем работает
                  }
                break;
              }
            case 8: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(9, true); ++stepModem; // моносокет - единомоментно работаем с одним подключением
                  }
                break;
              }
            case 9: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(10, true); ++stepModem; // не добавлять заголовок при приеме данных
                  }
                break;
              }
            case 10: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(11, true); ++stepModem; // автоматический вывод принятых данных
                  }
                break;
              }
            case 11: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    sendATcmdByIdx(12, true); ++stepModem; // не показывать данные отправителя при приеме данных
                  }
                break;
              }
            case 12: {
                if (inExist) if (findOKfromBuf( inByte)) {
                    currentInitModem = modemStartReg; // переходим в режим регистрации модема у сотового оператора - основная логика
                  }
                break;
              }
            default: {}
          }
        }
        break;
      }
    case modemStartReg: { // начало регистрации в сети оператора
        stepModem = 0; // начинаем с начала
        currentInitModem = modemReg; // следующий шаг - проверка зарегестрировался ли модем и СИМ карта
        timerModem = currentMillis + periodWaitRegistrationModem; // обнуляем таймер
        readyModem = currentMillis; // обнуляем таймер
        break;
      }
    case modemReg: { // проверяем зарегестрировалась ли симка
        if ((currentMillis - readyModem) >= periodReadyModem) { // если за время отведенное не зарегестрировались
          lastModeModem = currentInitModem;
          currentInitModem = modemRestart; // перезагружаемся
          return; // выходим
        }
        switch (stepModem) { // вработаем последовательно по шагам
          case 0: {
              if ((currentMillis - timerModem) >= periodWaitRegistrationModem) { // ждем время
                sendATcmdByIdx(14, true); ++stepModem; // проверяем зарегестрировался ли модем в сети
                goodCREG = false; // сбрасываем флаг что зарегестрирован
              }
              break;
            }
          case 1: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                lastModeModem = currentInitModem;
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) { // если пришел байт
                if (processCREG(inByte)) goodCREG = true; // парсим строку - если зарегестрировались - ставим флаг
                if (findOKfromBuf( inByte)) { // ищем ответ ОК
                  if (goodCREG) { // если успешно зарегестрировались
                    ++stepModem; // переходим на след шаг
                  } else { // если так и не смогли зарегестрироваться
                    stepModem = 0; timerModem = currentMillis; // через время periodWaitRegistrationModem опять отправляем команду для проверки зарегестрировалась ли СИМ карта
                  }
                }
              }
              break;
            }
          case 2: {
              sendATcmdByIdx(15, true); ++stepModem; // получить режим подключения к оператору и имя оператора
              operatorName[0] = 0; // очищаем имя оператора
              break;
            }
          case 3: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                lastModeModem = currentInitModem;
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) {
                processOperatorName(inByte); // парсим ответ
                if (findOKfromBuf( inByte)) {
                  sendATcmdByIdx(16, true); // запрос качества связи
                  ++stepModem; operatorCSQ = 0;
                  //logStrPrint((char *)operatorName); logStrPrint((char *)"\r\n");
                }
              }
              break;
            }
          case 4: {
              if ((currentMillis - timerModem) >= periodWaitShortCmdModem) { // если нет ответа за период на короткую команду
                lastModeModem = currentInitModem;
                currentInitModem = modemRestart; // сброс
                return; // выходим
              }
              if (inExist) {
                processOperatorCSQ(inByte); // парсим ответ
                if (findOKfromBuf( inByte)) {
                  //logDecPrint(operatorCSQ); logStrPrint((char *)"\r\n");
                  sendATcmdByIdx(17, true); ++stepModem; // командный режим передачи данных
                  //++stepModem; // пропустим удаление СМС - модем не всегда при включении успевает инициализироваться :( и происходит перезагрузка
                }
              }
              break;
            }
          case 5: {
              if (inExist) if (findOKfromBuf( inByte)) {
                  sendATcmdByIdx(18, true); ++stepModem; // удаление всех SMS на SIM карте
                }
              break;
            }
          case 6: {
              if (inExist) if (findOKfromBuf( inByte)) {
                  currentInitModem = modemReady; // закончили инициализацию модема
                }
              break;
            }
          default: {}
        }
        break;
      }
    default: {}
  }
}

_Bool findOKfromBuf(const unsigned char inByte) { // поиск OK\r\n в циклическом буфере
  const char inStr[] = "OK\r\n"; // искомая строка
  byte slen = 4; // длина искомой строки
  if (inByte == inStr[slen - 1]) { // если последний символ совпадает - продолжаем искать всю строку
    byte abpos = posModemBuf; // абсолютная позиция в буфере поиска-1  = последнему символу искомой строки
    if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // ищем с предпоследнего символа
    for ( byte i = 0; i < (slen - 1); ++i) { // цикл по оставшимся символам
      if (inStr[slen - 1 - i] != respBufModem[abpos]) return false; // если не сопадабт символы - выходим
      if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // уменьшаем счетчики
    }
  } else {
    return false;
  } return true;
}

_Bool findRespFromBuf(char * inStr, const unsigned char inByte) { // поиск строки в циклическом буфере
  byte slen = strlen((const char *)inStr); // длина искомой строки
  if (inByte == inStr[slen - 1]) { // если последний символ совпадает - продолжаем искать всю строку
    byte abpos = posModemBuf; // абсолютная позиция в буфере поиска-1  = последнему символу искомой строки
    if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // ищем с предпоследнего символа
    for ( byte i = 0; i < (slen - 1); ++i) { // цикл по оставшимся символам
      if (inStr[slen - 1 - i] != respBufModem[abpos]) return false; // если не сопадабт символы - выходим
      if (!abpos) abpos = maxSizeRespBuf - 1; else --abpos; // уменьшаем счетчики
    }
  } else {
    return false;
  } return true;
}

//options.h // пользовательские настройки железки
#ifndef OPTIONS_H
#define OPTIONS_H

#include <Arduino.h>

#define ADMIN_PHONE "79206319946" // номер администратора

#define ussd_balance "AT+CUSD=1,\"*100#\"\r" // строка запроса баланса телефона // команда зависит от оператора, погоды на луне, настроения БС и т д......Гарантии что баланс придет в нормальном виде никакой.
//#define period_get_balance 864007979UL // получение баланса телефона раз в 10 суток
//#define period_get_balance 86400797UL // получение баланса телефона раз в сутки
#define period_get_balance 86400797UL*2 // получение баланса телефона раз в 2 суток
//#define period_get_balance 86400797UL/3 // получение баланса телефона тест

#define errTemp ((word)2100) // значение температуры ошибка, т е еще не получено
static constexpr byte ds18_pin = A2; // пин куда подключен DS18B20
#define period_get_temp 64963UL // получение температуры каждые 65 сек

static constexpr byte pinExtPower  = A3; // пин где определяем наличие внешнего питания
#define period_control_ext_power 4000UL // время в течении которого контролируется изменение питание и после которого шлется СМС

static constexpr byte pinAddPower  = A0; // пин где определяем наличие дополнительного питания

#define period_read_sms 15967UL // чтение всех СМС каждые 16 сек
#define max_size_abonent_sms 16 // размер буфера номера абонента
#define max_size_len_read_sms 64 // размер буфера текста СМС прием

// настройки работы модема
static constexpr byte pinResetModem  = A1; // пин сброса модема SIM800L
#define periodWaitModemBegin 10000UL // время ожидания после включения, прежде чем начнем работать с модемом // модем прочухивается
#define periodWaitShortCmdModem 7000UL // время ожидания ответа на короткие команды, обычно ждем OK, и если нет - сброс модема // на команде CIPSHUT закрытия всех сессий интернета при старте может не хватить, тогда или увеличить или пропустить эту команду при старте
#define periodWaitRegistrationModem 5000UL // каждый этот период спрашивается зарегестрировался ли модем
#define periodReadyModem 300000UL // общее время, которое дается на регистрацию в сети, иначе перезагрузка модема
#define maxSizeRespBuf ((byte)64) // максимальный размер входящего буфера для данных из модема - кольцевой буфер FIFO, по идее должен быть чуть больше максимальной лексемы
#define maxSizeOperatorName ((byte)16) // максимальная длина поля имени оператора связи
#define maxSizeBrokerID ((byte)21) // максимальный размер отправляемой команды модему

// настройка SoftWare Serial
static constexpr byte pinSwUART_TX = 3; // RX вход устройства, принимающего/отображающее данные/лог
static constexpr byte pinSwUART_RX = 2; // TX выход устройства, от которого получать данные

#endif  /* OPTIONS_H */

- пример СМС, питание основное/питание дополнительное/уровень сигнала/ uptime / баланс симки (если удалось получить) / время с последнего получения баланса

PowON InOFF C:12 t:24.0 u:0d0h lb:0h

-

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Успешный тест-драйв железки, uptime millis перевалил unsigned long.
P.S. Тут на форуме давно кто то спорил что GSM устарело, все переходят на сообщения в интернете. На днях после урагана в деревне у всех операторов отрубился интернет на несколько суток, так что SMS - наше фсё :)