Очередная Температурная сигнализация с SMS управлением для дома
- Войдите на сайт для отправки комментариев
Чт, 07/09/2017 - 16:07
Приветствую!
Прошлой зимой в деревне два раза вставал автоматический газовый котел,
из-за отключения света на несколько суток, в связи с чем пришлось
делать температурную сигнализацию. В хламе дома нашел древний 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;
}
А под какой модуль GSM сделано?
используется не GSM модуль, а модем IRZ MC52iT + RS232-TTL конвертор
P.S. Разницы особой не вижу что использовать, при проектировании была только одна сложность -
долго искал документацию по AT командам на этот модем, настройку, что бы при входящей SMS
сразу в COM порт выходило сообщение о приходе новой SMS.
Спасибо за уточнение.
Цена модема 150.00 USD ?
Даже не в курсе про цену - что было то и использовал
В яндекс макете только что нашёл за 2900 модем этот
кстати... а может и нет... я отказался от SNS уведомлений как основного транспорта сообщений... не гарантирован срок доставки... лучше SNS (или e-mail) использовать как дублирующий, а основной информационный о событии - голосовой звонок.
кстати... а может и нет... я отказался от SNS уведомлений как основного транспорта сообщений... не гарантирован срок доставки... лучше SNS (или e-mail) использовать как дублирующий, а основной информационный о событии - голосовой звонок.
во вы тему древнюю подняли .... :)
В идеале, в общем случае нужен как минимум один резервный канал, но проблема в том что место где стоит мой дом в деревне,
практически не имет не то что 3G а просто нормальной связи, заказал и поставил около окна антену 10 dB ( c AliExpress - дороже чем блин сама сигнализация) штыревую здоровую, добился связи на уровне 8...10 (из 30) с трудом. Позвонить аналогично проблема что входящая что исходящая связь. А СМС вполне нормально туда сюда ходят.
так что в моем случае только SMS.
понял. я на даче поставил спутниковую двунаправленную связь и отец в тайге также (взяли по акции) у него кроме спутниковой только голубиная связь. тоже возится с ардуинками....
по поводу антенны я бы на вышку направленную поставил. усиление выше.
штырь ненаправленная антена.
Добрый день!
У меня те же комплектующие, но модем мне, почему-то, не отвечает. Команды от Arduino уходят точно, посылал на COM порт ПК и видел их там. Модем рабочий, изначально был прошит для общения с промышленным контроллером, скорость обмена мне известна. Думаю, вне завиисмости от его прошивки, при правильной скорости порта он должен отвечать хотя бы на "ATI".
Я пробую отсылать библиотекой Serial(), Вы используете MySerial(). Это принципиально?
Можно ли посмотреть Вашу схему подключения, чтобы сравнить с моей?
Поменять rx tx провода местами в кабеле соединяющий arduino и ttl конвертер и в кабеле соединяющий конвертер и модем
Если б это помогало, я бы сюда не писал.
Мне помогло, модем аналогично прекрасно общался с компьютером, а с ардуино не хотел, попробуйте сначала простейший скетч общения с модемом, перебороть скоростей, пинов и т.д. Модем сбросьте до заводских настроек.
И кстати где вы увидели MySerial? Сейчас специально посмотрел - не нашёл в своём скетче
Если вы используете software serial, то у меня объект называется gsm и подключается к 7 и 8 пинам
Нет, конечно, Вы правы, это всё правильно (простые операции по замене RX на Tx и т.д.), я имел ввиду, что все простые проверки я уже перепробовал.
На данном этапе через терминальные программы с ПК я нормально опрашиваю модем. К примеру, одна из простых комманд "ATI" в ПК возвращает нормальный ответ, а вот в Ардуину выдаёт "ERROR". То есть связь нормальная, но что-то пока ещё не так.
Пардон, я имел ввиду software serial.
Нет, конечно, Вы правы, это всё правильно (простые операции по замене RX на Tx и т.д.), я имел ввиду, что все простые проверки я уже перепробовал.
На данном этапе через терминальные программы с ПК я нормально опрашиваю модем. К примеру, одна из простых комманд "ATI" в ПК возвращает нормальный ответ, а вот в Ардуину выдаёт "ERROR". То есть связь нормальная, но что-то пока ещё не так.
Т е связь с МК есть? Тупо сбросьте модем до заводских настроек.
Полностью переписал код, избавился от delay, удаляются все СМС после прихода их чтения, добавил watch dog timer, добавил три исполнительных устройства (реле) и команды для их включения/отключения. Добавил управление через DTMF, но не дописал, т.к. модем не поддерживает :(
Команды для входящих СМС:
#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; } } }А можно вопросы по отдельным кускам программы позадавать? хочу подробнее разобраться.
другим тоже надеюсь будет интересно увидеть более подробные обьяснения.
Эх...я то не против, но это ж сколько времени надо объяснять, где столько времени взять, куча текстовых строк + буфер приёма от модема вот и память забита, progmem используйте, тут целая тема есть - все разжеванно
ну хотя бы снабдили строки скетча пояснениями - что выполняется там..
у вас знаний достаточно, вы то понимаете, а большинство я так догадываюсь что нет..
поэтому и откликов на вашу ветку будет просто минимум.. потратили бы пол-часа и добавили пояснения..
Ну во первых за последние несколько месяцев только вам понадобились комментарии к коду)
Во вторых если есть вопросы конкретные - задавайте
Я не блогер, которым нехрен делать вот они и пишут статьи с кучей комментариев, ардуино для меня как и для многих на форуме это просто хобби, и тратить время на комментарии мне лень и некогда
Алгоритм банален - куча команд модема со списком ответов, если ответы не совпадают - проблема
в теме где вы мне писали - там пример качественного комментария. согаситесь - это очень удобно. просто я первый кто спросил, другие просто шли дальше, не останавливаясь...
в теме где вы мне писали - там пример качественного комментария. согаситесь - это очень удобно. просто я первый кто спросил, другие просто шли дальше, не останавливаясь...
Хватит уже воздух сотрясать, есть конкретные вопросы по коду - задавайте, не забыв указать номера строк.
ЗЫ. Если почаще прочитаете форумы по вопросам работы sim800 то увидите что основные вопросы по классической схеме работы с string и delay, много ссылаются на сайт codius (хорошие кстати статьи), а более сложные варианты людям не интересны, так как уровень использования модуля бытовой - поиграться. А для более надёжной работы надо самим мозг включать что тяжело для современных "жертв ЕГЭ"
ок, соберусь, задам вопрос поблочно, мне нравится разбираться в стороннем коде, понимать логику состевления программы и частей программы, сравнивать при этом загрузку процессора и прочее. Поэтому ваш вараинт мне также очень и очень интересен.
Реинкарнация устройства в новом железе, с резервным питанием и более распространенным модемом 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 настройки проекта/пинов/админского телефона/таймеров и т.д.
Код не блокирующий, легко добавить опрос каких либо других датчиков, отправку СМС, получение команд и т.д....
-
В выходные железка поедет на натурные испытания в деревню в зону крайне плохого приема, через пару тройку недель, как засуну в корпус и окончательно оттестирую - напишу.
в собранном устройстве на пин А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(); }- итоговая железка
Добавлен счетчик принудительных перезагрузок модема, отображается на 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 */Обновленный скетч:
- добавлен дополнительный контроль питания на пине 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 / баланс симки (если удалось получить) / время с последнего получения баланса
-
Успешный тест-драйв железки, uptime millis перевалил unsigned long.
P.S. Тут на форуме давно кто то спорил что GSM устарело, все переходят на сообщения в интернете. На днях после урагана в деревне у всех операторов отрубился интернет на несколько суток, так что SMS - наше фсё :)