// Siemens C45 + ESP8266-01s 8 pin
#define PIN_SENSOR 2 // GPIO0 (датчик) pin<-->VCC + pin<-10k->VCC
#define PIN_RELAY A0 // GPIO2 (управление реле) pin<-->VCC
#define BOUNDS_RATE 19200 // для Siemens C45
#define SMS_VALIDATE_PERIOD 0 // Время жизни СМС 0-635040 мин, 0-задано оператором
//[START]Предварительное объявление
void handleInterrupt(); // IRAM_ATTR для ESP8266/32
void handleAlarm();
//[END]Предварительное объявление
String ADMIN_NUM[] = {"79031606514", "79030043821"}; //Эталон номеров автоматизировать получение!!!!????
//[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
volatile struct Flags {
uint8_t alarm : 4; // 3 бит дес 0-15
uint8_t lock : 1;
uint8_t flag3 : 1;
} flags = {0, 0, 0};
//[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CallHandler {
private:
String lastNumber;
unsigned long lastCallTime = 0;
uint8_t callCounter = 0;
const uint16_t TIME_WINDOW = 5000; // 5 сек не более между звонками
struct SmsParsed;
void handleCall(const String& response) {
String number = extractDigits(response);
Serial.println(number);
if (!isAdminNumber(number)) {
rejectCall(); // если номер не из списка Команда отбоя
return;
}
if (number == lastNumber && millis() - lastCallTime < TIME_WINDOW) {
callCounter++;
} else {
callCounter = 1;
lastNumber = number;
}
lastCallTime = millis();
if (callCounter >= ((!flags.lock && digitalRead(PIN_SENSOR)) ? 1 : 3)) { // MAX_CALLS_LOCK : MAX_CALLS_UNLOCK
flags.lock = (callCounter == 1) ? 1 : 0;
if (flags.lock) { // постановка охр при условии целого контура
flags.alarm = 3;
} else {
flags.alarm = 2; // снятие охраны безусловно не проверяем контур
}
rejectCall();
callCounter = 0;
}
}
uint8_t handleSMS(const String& response) {
uint8_t num = extractDigits(response).toInt();
Serial.print("AT+CMGR=");
Serial.println(num); // Запрос чтение SMS с номером
return num;
}
void getSMS(const String& response) {
Serial.println("разбор>>> ");
SmsParsed sms = parseIncomingPdu(response);
Serial.println(sms.senderNumber);
Serial.println(sms.encodedText);
}
void rejectCall() {
Serial.println("ATH"); // Команда отбоя
}
String extractDigits(const String &input) {
String result;
for (uint8_t i = 0; i < input.length(); i++) {
if (isdigit(input[i])) {
result += input[i];
}
}
return result.substring(0, 11);
}
bool isAdminNumber(String num) {
for (String admin : ADMIN_NUM) {
if (num.indexOf(admin) > -1) return true;
}
return false;
}
struct SmsParsed {
String senderNumber;
String encodedText;
};
SmsParsed parseIncomingPdu(String pdu) {
SmsParsed result;
uint8_t index = 0;
index += 2 + (strtol(pdu.substring(index, index + 2).c_str(), nullptr, 16) + 1) * 2; // SMSC
byte sLen = (strtol(pdu.substring(index, index + 2).c_str(), nullptr, 16) + 1) & ~1; // Определяем длину отпраителя и четность
index += 4; // Пропускаем тип номера (TON/NPI)
result.senderNumber += abbaSwap(pdu.substring(index, index + sLen));
result.senderNumber.replace("F", "");
index += sLen + 2 + 2 + 14 + 2; // Пропускаем номер отправителя + PID + DCS + дату + длина текста
result.encodedText += decode7bit(pdu.substring(index));
return result;
}
String abbaSwap(const String& num) { //semi-octets
String result = "";
for (uint8_t i = 0; i < num.length(); i += 2) {
result += String(num[i + 1]) + String(num[i]);
}
return result;
}
String decode7bit(const String& text) {
// C8 32 9B FD 0E 85 42
// 48 65 6C 6C 6F 21 21 21
// "Hello!!!"
int len = text.length();
int byteCount = len / 2;
byte bytes[byteCount];
// Преобразуем HEX-строку в байты
for (int i = 0; i < byteCount; i++) {
bytes[i] = strtol(text.substring(2 * i, 2 * i + 2).c_str(), NULL, 16);
}
String result = "";
byte buffer = 0;
byte availableBits = 0;
for (int i = 0; i < byteCount; i++) {
buffer |= bytes[i] << availableBits;
result += (char)(buffer & 0x7F);
buffer = bytes[i] >> (7 - availableBits);
availableBits++;
if (availableBits == 7) {
result += (char)(buffer & 0x7F);
buffer = 0;
availableBits = 0;
}
}
return result;
}
public:
void processGSM(const String& response) {
static uint8_t CMGR = 0;
if (response.startsWith("+CLIP")) {
handleCall(response);
}
else if (response.startsWith("+CMTI")) {
CMGR = handleSMS(response);
}
else if (response.startsWith("+CMGR")) {
return;
}
else if (CMGR) {
getSMS(response);
Serial.print("AT+CMGD=");
Serial.print(CMGR);
CMGR = 0;
}
}
};
CallHandler callHandler;
//[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
void setup() {
Serial.begin(BOUNDS_RATE);
Serial.println();
Serial.println("### 01-05-2025");
pinMode(PIN_SENSOR, INPUT);
pinMode(PIN_RELAY, OUTPUT);
digitalWrite(PIN_RELAY, HIGH);
attachInterrupt(digitalPinToInterrupt(PIN_SENSOR), handleInterrupt, FALLING);
flags.alarm = 0;
// const char* setupAT[] = {
// "AT",
// "AT&F",
// "ATE0",
// "AT+CPBS=\"SM\"", //место хранения Контактов
// "AT+CLIP=1",
// "AT+CNMI=1,1,0,0,1",
// "AT+CPMS=\"SM\"" // место хранения СМС
// };
// for (uint8_t i = 0; i < sizeof(setupAT) / sizeof(setupAT[0]); i++) {
// Serial.println(setupAT[i]);
// while (1) {
// String response = getSerial();
// if (response.indexOf("OK") >= 0) {
// break;
// }
// }
// }
Serial.println("ГОТОВ");
// digitalWrite(PIN_RELAY, LOW);
// delay(10);
// digitalWrite(PIN_RELAY, HIGH);
flags.alarm = 2;
// Serial.println(codeNum("+79031606514"));
// Serial.println(code7bit("Hello!!!"));
// Serial.println(decode7bit("C8329BFD0E8542"));
// Serial.println(codePDU("+79031606514", "Hello!!!")); // можно без +
// SmsParsed sms = parseIncomingPdu("07919730071111F1040B919730616015F400005250101253922103D96A16");
// Serial.println(sms.senderNumber);
// Serial.println(sms.encodedText);
}
void loop() {
if (Serial.available()) {
String response = Serial.readStringUntil('\n');
callHandler.processGSM(response); // Единственный публичный метод
}
handleAlarm();
}
//[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //Сирена
void handleAlarm() {
static uint32_t alarmTimer = 0;
static bool relayState = false;
static uint8_t alarmStep = 0;
static uint8_t alarmMax = 0;
static uint16_t alarmHigh = 0;
static uint16_t alarmLow = 0;
switch (flags.alarm) {
case 0: return;
case 1: alarmMax = 10; // звук тревога
alarmHigh = 700;
alarmLow = 300;
break;
case 2: alarmMax = 2; // звук снятия с охр 2 коротких
alarmHigh = 300;
alarmLow = 300;
break;
case 3: alarmMax = 1; // звук постановки с охр 1 длинный
alarmHigh = 700;
alarmLow = 0;
break;
case 4: alarmMax = 1; //
alarmHigh = 700;
alarmLow = 0;
break;
default: return;
}
uint32_t now = millis();
// Время переключения:
uint32_t interval = relayState ? alarmHigh : alarmLow;
if (now - alarmTimer >= interval) {
alarmTimer = now;
relayState = !relayState;
digitalWrite(PIN_RELAY, relayState ? LOW : HIGH); // LOW = активное
if (!relayState) {
alarmStep++;
if (alarmStep >= alarmMax) {
flags.alarm = false;
alarmStep = 0;
digitalWrite(PIN_RELAY, HIGH); // Отключаем реле
}
}
}
}
//[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// //[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// // TP-VP Время жизни СМС от 5 мин до 63 недель (0-255) или по кмолчанию оператора
// String getSmsValidatePeriodHex(uint32_t minutes) {
// if (minutes == 0) return "";
// byte vp;
// if (minutes <= 720) {
// vp = ((minutes + 4) / 5) - 1;
// } else if (minutes <= 1440) {
// vp = 144 + (minutes - 720) / 30 - 1 ;
// } else if (minutes <= 43200) {
// vp = 168 + (minutes - 1440) / 1440 - 1;
// } else if (minutes <= 635040) {
// vp = 197 + (minutes - 41760) / 10080;
// } else {
// vp = 0xFF;
// }
// return byteToHex(vp);
// }
// //[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// String codeNum(String num) {
// num.replace("+", "");
// num = !num.length() % 2 ? num : num += "F"; //проверяем на четность и добавляем для четности
// num = abbaSwap(num);
// return num; //+79031606515 => 9730616015F5
// }
// //[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// //[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// // Вспомогательная функция для преобразования байта в HEX формата 00
// String byteToHex(byte b) {
// char hex[3];
// sprintf(hex, "%02X", b);
// return String(hex);
// }
// //[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// // "Hello!!!"
// // 48 65 6C 6C 6F 21 21 21
// // C8 32 9B FD 0E 85 42
// String code7bit(String text) {
// String result = "";
// uint16_t buffer = 0;
// uint8_t availableBits = 0;
// for (int i = 0; i < text.length(); i++) {
// byte septet = (text[i] & 0x80) ? '#' : text[i]; // отсеивает только латиницу и знаки
// buffer |= (septet << availableBits);
// availableBits += 7;
// while (availableBits >= 8) {
// result += byteToHex(buffer & 0xFF);
// buffer >>= 8;
// availableBits -= 8;
// }
// }
// if (availableBits > 0) {
// result += byteToHex(buffer & 0xFF);
// }
// return result;
// }
// //[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// String codePDU(String numSender, String txtMessage) {
// String result = "";
// numSender.replace("+", "");
// result += "000100";
// result += byteToHex(numSender.length()) + "91" + codeNum(numSender); // Длина номера без 91 + еодированный номер
// result += "0000";
// result += byteToHex(txtMessage.length());
// result += code7bit(txtMessage);
// return result;
// }
//[START]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
void handleInterrupt() { // IRAM_ATTR для ESP8266/32
// if (digitalRead(PIN_SENSOR)) return;
static uint32_t lastTime = 0;
static bool firstCall = true;
uint32_t currentTime = millis();
if ( (currentTime - lastTime < 1e3 || flags.alarm) && !firstCall ) return; // выполняем первый и не чаще 1 сек
flags.alarm = flags.lock ? 1 : 0;
firstCall = false;
lastTime = currentTime;
Serial.println("*КОНТУР НАРУШЕН*");
}