├── size.cmd ├── sim800_ups_monitor.bin ├── memory.x ├── d.cmd ├── 2stm.cmd ├── src ├── traits.rs ├── eeprom.rs ├── errors.rs ├── interrupts.rs ├── context.rs ├── megatec.rs ├── esp01.rs ├── sensors.rs ├── main.rs └── sim800l.rs ├── .cargo └── config ├── .gitignore ├── openocd.gdb ├── openocd.cfg ├── README.md ├── Cargo.toml └── ino ├── relay_server.ino └── esp2stm.ino /size.cmd: -------------------------------------------------------------------------------- 1 | cargo size --bin sim800_ups_monitor --release -- -A -------------------------------------------------------------------------------- /sim800_ups_monitor.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lesha108/sim800_ups_monitor/HEAD/sim800_ups_monitor.bin -------------------------------------------------------------------------------- /memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x08000000, LENGTH = 128K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 20K 5 | } 6 | -------------------------------------------------------------------------------- /d.cmd: -------------------------------------------------------------------------------- 1 | arm-none-eabi-gdb.exe -x openocd.gdb C:\Users\USER\rust_proj\sim800_ups_monitor\target\thumbv7m-none-eabi\debug\sim800_ups_monitor -------------------------------------------------------------------------------- /2stm.cmd: -------------------------------------------------------------------------------- 1 | cargo build --release 2 | cargo objcopy --bin sim800_ups_monitor --target thumbv7m-none-eabi --release -- -O binary sim800_ups_monitor.bin 3 | st-flash erase 4 | st-flash write sim800_ups_monitor.bin 0x8000000 5 | rem cargo embed --release --chip STM32F103C8 6 | 7 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::sim800l::Sim800; 3 | use crate::errors::Error; 4 | 5 | /// характеристика указывает, что объект можно взять на контроль 6 | pub trait Observable { 7 | fn check(&mut self, ctx: &mut Context, sim: &mut Sim800) -> Result<(), Error>; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.thumbv7m-none-eabi] 2 | runner = 'arm-none-eabi-gdb' 3 | 4 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 5 | 6 | rustflags = ["-C", "link-arg=-Tlink.x"] 7 | 8 | [build] 9 | target = "thumbv7m-none-eabi" # Cortex-M3 10 | 11 | [unstable] 12 | build-std = ["core"] 13 | build-std-features = ["panic_immediate_abort"] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | # detect unhandled exceptions, hard faults and panics 7 | break DefaultHandler 8 | break HardFault 9 | break rust_begin_unwind 10 | 11 | monitor arm semihosting enable 12 | 13 | load 14 | 15 | # start the process but immediately halt the processor 16 | stepi 17 | -------------------------------------------------------------------------------- /openocd.cfg: -------------------------------------------------------------------------------- 1 | # Sample OpenOCD configuration for the STM32F3DISCOVERY development board 2 | 3 | # Depending on the hardware revision you got you'll have to pick ONE of these 4 | # interfaces. At any time only one interface should be commented out. 5 | 6 | # Revision C (newer revision) 7 | source [find interface/stlink.cfg] 8 | 9 | # Revision A and B (older revisions) 10 | # source [find interface/stlink-v2.cfg] 11 | 12 | source [find target/stm32f1x.cfg] 13 | -------------------------------------------------------------------------------- /src/eeprom.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::errors::Error; 3 | 4 | pub trait Eeprom { 5 | fn load(&mut self, ctx: &mut Context); 6 | fn save(&mut self, ctx: &mut Context) -> Result<(), Error>; 7 | } 8 | 9 | #[derive(Debug, Clone, Copy, PartialEq)] 10 | pub enum EepromAdresses { 11 | V220State = 0x01 , // состояние датчика напряжения 220 в 12 | TempState = 0x02, // состояние датчика температуры DS18B20 13 | Number = 0x04, // телефонный номер отправки SMS 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sim800_ups_monitor 2 | UPS monitoring using SIM800L module and STM32 bluepill board 3 | 4 | new in version 2.5 5 | - Set SIM800L clock using NTP 6 | - I2C LCD1602 support for Temp & Clock display 7 | 8 | new in version 0.2.0 9 | - Error handling Rust-style 10 | - Many modules structure 11 | - State machines using structs 12 | - USB for logging console messages 13 | - SMS commands handling 14 | - SIM balance request using SMS (only for MTS operator) 15 | - interface with ESP01 module for LAN operations via UART. Use two ESP01 modules. One as relay server (relay_server.ino) 16 | Second as STM32-ESP01 interface (esp2stm.ino). Scketches written using Arduino IDE and ESP8266 libs. 17 | 18 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use core::str::Utf8Error; 2 | 3 | #[derive(Debug, PartialEq, Eq)] 4 | pub enum Error { 5 | // invalid UTF-8. 6 | EncodingError, 7 | // что то моё 8 | UPSFailure, 9 | SerialError, 10 | TimerError, 11 | SerialNoData, 12 | FmtError, 13 | CmdFail, 14 | SmsStage1, 15 | SmsStage2, 16 | NoRing, 17 | NotAuthCall, 18 | NoAuthNumbers, 19 | NoInSMS, 20 | EspOffLine, 21 | EepromFail, 22 | NoUCS2, 23 | InvalidUCS2Size, 24 | 25 | } 26 | 27 | impl From for Error { 28 | fn from(_: Utf8Error) -> Self { 29 | Error::EncodingError 30 | } 31 | } 32 | 33 | impl From for Error { 34 | fn from(_: core::fmt::Error) -> Self { 35 | Error::FmtError 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sim800_ups_monitor" 3 | version = "0.2.5" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | # Зависимости для разработки под процессор Cortex-M3 9 | [dependencies] 10 | cortex-m = "0.7.3" 11 | cortex-m-rt = "0.6.15" 12 | cortex-m-semihosting = "0.3.7" 13 | panic-halt = "0.2.0" 14 | nb = "1.0.0" 15 | embedded-hal = "0.2.6" 16 | heapless = "0.7.7" 17 | eeprom24x = "0.4.0" 18 | ds18b20 = "0.1.1" 19 | one-wire-bus = "0.1.1" 20 | usb-device = "0.2.8" 21 | usbd-serial = "0.1.1" 22 | shared-bus = "0.2.2" 23 | hd44780-driver = "0.4.0" 24 | 25 | # Пакет для разработки под отладочные платы stm32f1 26 | [dependencies.stm32f1xx-hal] 27 | version = "0.7.0" 28 | features = ["stm32f103", "stm32-usbd", "rt"] 29 | 30 | # Позволяет использовать `cargo fix`! 31 | [[bin]] 32 | name = "sim800_ups_monitor" 33 | test = false 34 | bench = false 35 | 36 | # Включение оптимизации кода 37 | [profile.release] 38 | codegen-units = 1 # Лучшая оптимизация 39 | debug = true # Нормальные символы, не увеличивающие размер на Flash памяти 40 | lto = true # Лучшая оптимизация 41 | opt-level = "z" 42 | 43 | -------------------------------------------------------------------------------- /src/interrupts.rs: -------------------------------------------------------------------------------- 1 | use core::mem::MaybeUninit; 2 | use core::sync::atomic::{AtomicU32, Ordering}; 3 | use stm32f1xx_hal::pac; 4 | use stm32f1xx_hal::pac::interrupt; 5 | use stm32f1xx_hal::timer::CountDownTimer; 6 | use stm32f1xx_hal::usb::UsbBusType; 7 | use usb_device::{bus::UsbBusAllocator, prelude::*}; 8 | 9 | // переменные и прерывания для работы USB 10 | pub static mut USB_BUS: Option> = None; 11 | pub static mut USB_SERIAL: Option> = None; 12 | pub static mut USB_DEVICE: Option> = None; 13 | 14 | #[interrupt] 15 | fn USB_HP_CAN_TX() { 16 | usb_interrupt(); 17 | } 18 | 19 | #[interrupt] 20 | fn USB_LP_CAN_RX0() { 21 | usb_interrupt(); 22 | } 23 | 24 | fn usb_interrupt() { 25 | cortex_m::interrupt::free(|_| { 26 | let usb_dev = unsafe { USB_DEVICE.as_mut().unwrap() }; 27 | let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; 28 | usb_dev.poll(&mut [serial]); 29 | }); 30 | } 31 | 32 | // Статические переменные для работы с прерываеним таймера 33 | pub static mut SEC_TIMER: MaybeUninit> = 34 | MaybeUninit::uninit(); 35 | pub static SEC_COUNT_REBOOT: AtomicU32 = AtomicU32::new(0); 36 | pub static SEC_COUNT_SENSOR: AtomicU32 = AtomicU32::new(0); 37 | pub static SEC_COUNT_LED: AtomicU32 = AtomicU32::new(0); 38 | pub static SEC_COUNT_ESP: AtomicU32 = AtomicU32::new(0); 39 | pub static SEC_COUNT_BALANCE: AtomicU32 = AtomicU32::new(0); 40 | 41 | // обаботчик прерывания таймера 42 | #[interrupt] 43 | fn TIM2() { 44 | let sec_timer = unsafe { &mut *SEC_TIMER.as_mut_ptr() }; 45 | SEC_COUNT_REBOOT.fetch_add(1, Ordering::Relaxed); 46 | SEC_COUNT_SENSOR.fetch_add(1, Ordering::Relaxed); 47 | SEC_COUNT_LED.fetch_add(1, Ordering::Relaxed); 48 | SEC_COUNT_ESP.fetch_add(1, Ordering::Relaxed); 49 | SEC_COUNT_BALANCE.fetch_add(1, Ordering::Relaxed); 50 | sec_timer.clear_update_interrupt_flag(); 51 | } 52 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use stm32f1xx_hal::prelude::*; 2 | // 3 | use shared_bus::*; 4 | use stm32f1xx_hal::pac; 5 | use stm32f1xx_hal::timer::CountDownTimer; 6 | use stm32f1xx_hal::{delay::Delay, pwm::Channel, rtc::Rtc, watchdog::IndependentWatchdog}; 7 | 8 | use crate::errors::Error; 9 | use crate::sim800l::Sim800; 10 | use crate::traits::Observable; 11 | 12 | /// Определение структуры аппаратного контекста 13 | pub struct Context<'a> { 14 | /// сторожевой таймер 15 | pub watchdog: IndependentWatchdog, 16 | /// функции задержки 17 | pub delay: Delay, 18 | /// таймер отслеживания таймаутов в последовательных портах 19 | pub at_timer: CountDownTimer, 20 | /// часы реального времени 21 | pub rtc: Rtc, 22 | /// heartbeat LED 23 | pub led: stm32f1xx_hal::gpio::gpioc::PC13< 24 | stm32f1xx_hal::gpio::Output, 25 | >, 26 | /// доступ к EEPROM 27 | pub eeprom: eeprom24x::Eeprom24x< 28 | I2cProxy< 29 | 'a, 30 | NullMutex< 31 | stm32f1xx_hal::i2c::BlockingI2c< 32 | pac::I2C1, 33 | ( 34 | stm32f1xx_hal::gpio::gpiob::PB6< 35 | stm32f1xx_hal::gpio::Alternate, 36 | >, 37 | stm32f1xx_hal::gpio::gpiob::PB7< 38 | stm32f1xx_hal::gpio::Alternate, 39 | >, 40 | ), 41 | >, 42 | >, 43 | >, 44 | eeprom24x::page_size::B16, 45 | eeprom24x::addr_size::OneByte, 46 | >, 47 | /// функции пищалки 48 | pub beeper: stm32f1xx_hal::pwm::Pwm< 49 | pac::TIM1, 50 | stm32f1xx_hal::timer::Tim1NoRemap, 51 | stm32f1xx_hal::pwm::C1, 52 | stm32f1xx_hal::gpio::gpioa::PA8< 53 | stm32f1xx_hal::gpio::Alternate, 54 | >, 55 | >, 56 | } 57 | 58 | impl<'a> Context<'a> { 59 | pub fn beep(&mut self) { 60 | self.beeper 61 | .set_duty(Channel::C1, self.beeper.get_max_duty() / 2); 62 | self.watchdog.feed(); 63 | self.delay.delay_ms(500_u16); 64 | self.beeper.set_duty(Channel::C1, 0); 65 | self.watchdog.feed(); 66 | } 67 | 68 | pub fn save_byte(&mut self, address: u32, data: u8) -> Result<(), Error> { 69 | self.eeprom.write_byte(address, data).unwrap(); 70 | self.delay.delay_ms(10_u16); 71 | let read_data = self.eeprom.read_byte(address).unwrap(); 72 | if read_data == data { 73 | return Ok(()); 74 | } else { 75 | return Err(Error::EepromFail); 76 | } 77 | } 78 | 79 | pub fn reset_rtc(&mut self) { 80 | self.rtc.set_time(0); 81 | // Запустить через сутки 82 | self.rtc.set_alarm(24 * 60 * 60); 83 | } 84 | 85 | //fn check(&mut self, control: &mut dyn Observable, sim: &mut Sim800) -> Result<(), Error> { 86 | pub fn check( 87 | &mut self, 88 | control: &mut dyn Observable, 89 | sim: &mut Sim800, 90 | ) -> Result<(), Error> { 91 | control.check(self, sim) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ino/relay_server.ino: -------------------------------------------------------------------------------- 1 | 2 | #include // Подключаем библиотеку ESP8266WiFi 3 | 4 | const char* ssid = "MY_SSID"; // Название Вашей WiFi сети 5 | const char* password = "password";// Пароль от Вашей WiFi сети 6 | IPAddress local_ip(192,168,1,75); // pre-defined IP address values 7 | IPAddress gateway(192,168,1,1); 8 | IPAddress subnet(255,255,255,0); 9 | int value = HIGH; 10 | 11 | #define RELAY 0 // Пин к которому подключено реле 12 | //#define LED 1 // GPIO1/TXD01 - светодиод 13 | WiFiServer server(80); // Указываем порт Web-сервера 14 | 15 | void setup(){ 16 | delay(2200); 17 | Serial.begin(115200); // Скорость передачи 115200 18 | pinMode(RELAY,OUTPUT); // Указываем вывод RELAY как выход 19 | //pinMode(LED,OUTPUT); // Указываем вывод RELAY как выход 20 | digitalWrite(RELAY, HIGH); // Устанавливаем RELAY в HIGH //LOW (0В) 21 | //digitalWrite(LED, HIGH); // Устанавливаем LED в HIGH 22 | Serial.println(); // Печать пустой строки 23 | Serial.print("Connecting to "); // Печать "Подключение к:" 24 | Serial.println(ssid); // Печать "Название Вашей WiFi сети" 25 | 26 | WiFi.config(local_ip, gateway, subnet); 27 | WiFi.begin(ssid, password); // Подключение к WiFi Сети 28 | 29 | while (WiFi.status() != WL_CONNECTED) // Проверка подключения к WiFi сети 30 | { 31 | delay(500); // Пауза 500 мс 32 | Serial.print("."); // Печать "." 33 | switch (WiFi.status()){ 34 | case WL_NO_SSID_AVAIL: { 35 | Serial.println("WiFi not available"); 36 | delay(1500); 37 | continue; 38 | }; 39 | case WL_CONNECT_FAILED: { 40 | Serial.println("WiFi pass failed"); 41 | delay(1500); 42 | continue; 43 | }; 44 | }; 45 | } 46 | Serial.println(""); // Печать пустой строки 47 | Serial.println("WiFi connected"); // Печать "WiFi connected" 48 | 49 | server.begin(); // Запуск сервера 50 | Serial.println("Server started"); // Печать "Server starte" 51 | Serial.print("Use this URL to connect: "); // Печать "Use this URL to connect:" 52 | Serial.print(WiFi.localIP()); // Печать выданого IP адресса 53 | } 54 | 55 | void loop(){ 56 | if (!WiFi.isConnected()) { 57 | ESP.restart(); 58 | }; 59 | 60 | WiFiClient client = server.available(); // Получаем данные, посылаемые клиентом 61 | if (!client) 62 | { 63 | return; 64 | } 65 | Serial.println("new client"); // Отправка "new client" 66 | while(!client.available()) // Пока есть соединение с клиентом 67 | { 68 | delay(1); // пауза 1 мс 69 | } 70 | 71 | String request = client.readStringUntil('\r'); 72 | Serial.println(request); 73 | client.flush(); 74 | 75 | if (request.indexOf("/RELAY=ON") != -1) 76 | { 77 | Serial.println("RELAY=ON"); 78 | digitalWrite(RELAY,LOW); 79 | //digitalWrite(LED,LOW); 80 | value = LOW; 81 | } 82 | if (request.indexOf("/RELAY=OFF") != -1) 83 | { 84 | Serial.println("RELAY=OFF"); 85 | digitalWrite(RELAY,HIGH); 86 | //digitalWrite(LED,HIGH); 87 | value = HIGH; 88 | } 89 | if (request.indexOf("/RELAY=RESET") != -1) 90 | { 91 | Serial.println("RELAY=Resetting..."); 92 | digitalWrite(RELAY,LOW); 93 | //digitalWrite(LED,LOW); 94 | delay(5000); 95 | digitalWrite(RELAY,HIGH); 96 | //digitalWrite(LED,HIGH); 97 | value = HIGH; 98 | } 99 | 100 | client.println("HTTP/1.1 200 OK"); 101 | client.println("Content-Type: text/html"); 102 | client.println(""); 103 | client.println(""); 104 | client.println(""); 105 | client.println("ESP8266 Router RELAY Control"); 106 | client.print("Relay is now: "); 107 | 108 | if(value == HIGH) 109 | { 110 | client.print("OFF"); 111 | } 112 | else 113 | { 114 | client.print("ON"); 115 | } 116 | client.println("

"); 117 | client.println("Turn OFF RELAY
"); 118 | client.println("Turn ON RELAY
"); 119 | client.println("RESET RELAY
"); 120 | 121 | if (request.indexOf("/RELAY=RESET") != -1) 122 | { 123 | client.println("RESET OK
"); 124 | } 125 | 126 | client.println(""); 127 | 128 | delay(1); 129 | Serial.println("Client disconnected"); 130 | Serial.println(""); 131 | } 132 | -------------------------------------------------------------------------------- /src/megatec.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | //-------------------------------------------------------------- 4 | // Функции работы с UPS 5 | //-------------------------------------------------------------- 6 | const UPS_BUF_LEN: usize = 50; // длина буфера приёма статуса UPS 7 | const TIMEOUT_FIRST_CYCLES: u16 = 200; // wait first char for 2 sec 8 | const TIMEOUT_LAST_CYCLES: u16 = 10; // wait finish char for 100 ms 9 | 10 | pub struct Ups { 11 | snd_buf: Vec, 12 | rcv_buf: Vec, 13 | ups_port: UpsPortType, 14 | } 15 | 16 | impl Ups { 17 | pub fn new(port: UpsPortType) -> Self { 18 | Ups { 19 | snd_buf: Vec::new(), 20 | rcv_buf: Vec::new(), 21 | ups_port: port, 22 | } 23 | } 24 | 25 | #[must_use] 26 | pub fn get_ups(&mut self) -> &[u8] { 27 | // должно быть что-то типа 28 | // (218.1 218.1 219.6 000 50.0 2.22 48.0 00000001\r 29 | // вырезаем первый и последний символы 30 | &self.rcv_buf[1..self.rcv_buf.len() - 1] // !!! проблема, если буфер пуст, т.е. не вызвали measure 31 | } 32 | 33 | pub fn measure(&mut self, ctx: &mut Context) -> Result<(), Error> { 34 | // отправляем команду получения статуса UPS 35 | self.snd_buf.clear(); 36 | self.snd_buf.extend_from_slice(b"Q1\r").unwrap(); 37 | for cmd in &self.snd_buf { 38 | block!(self.ups_port.write(*cmd)).ok(); 39 | } 40 | // пробуем получить ответ 41 | self.rcv_buf.clear(); 42 | ctx.at_timer.reset(); // reset timeout counter 43 | let mut got_first_char = false; // признак, что получили что-то из порта 44 | let mut w1_cycles = 0; // циклов задержки ожидания первого символа в ответе 45 | let mut w2_cycles = 0; // циклов задержки ожидания последнего символа в ответе 46 | loop { 47 | let res = self.ups_port.read(); 48 | match res { 49 | Err(nb::Error::Other(_)) => { 50 | write_log(b"UPS e1"); 51 | self.invalidate(); 52 | return Err(Error::SerialError); 53 | } 54 | Err(nb::Error::WouldBlock) => { 55 | // символ не пришёл ещё 56 | let t = ctx.at_timer.wait(); 57 | match t { 58 | Err(nb::Error::Other(_)) => { 59 | write_log(b"UPS e2"); 60 | self.invalidate(); 61 | return Err(Error::TimerError); 62 | } 63 | Err(nb::Error::WouldBlock) => { 64 | // просто ждём ещё таймер 65 | ctx.watchdog.feed(); 66 | continue; 67 | } 68 | Ok(_) => { 69 | // сработал таймер отсчёта таймаута 70 | if got_first_char { 71 | // отрабатываем ожидание последнего символа 72 | if w2_cycles >= TIMEOUT_LAST_CYCLES { 73 | break; // вылет по таймауту 74 | } else { 75 | w2_cycles += 1; 76 | continue; 77 | } 78 | } else { 79 | // отрабатываем ожидание первого символа 80 | if w1_cycles >= TIMEOUT_FIRST_CYCLES { 81 | break; 82 | } else { 83 | w1_cycles += 1; 84 | continue; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | Ok(x) => { 91 | // получили очередной символ от UPS 92 | if self.rcv_buf.len() < UPS_BUF_LEN { 93 | // защита от переполнения буфера 94 | self.rcv_buf.push(x).unwrap(); 95 | } else { 96 | break; 97 | } 98 | got_first_char = true; // после первого символа переходим на ожидание последнего 99 | w2_cycles = 0; 100 | ctx.at_timer.reset(); // timeout timer restart after each byte recieved 101 | continue; 102 | } 103 | } 104 | } 105 | if self.rcv_buf.len() != 47 { 106 | // длина нормального ответа от UPS 107 | write_log(b"UPS ret: "); 108 | write_log(self.rcv_buf.as_slice()); 109 | self.invalidate(); 110 | return Err(Error::UPSFailure); 111 | } 112 | Ok(()) 113 | } 114 | 115 | // стандартное содержимое при ошибке связи с UPS 116 | fn invalidate(&mut self) { 117 | self.rcv_buf.clear(); 118 | self.rcv_buf.extend_from_slice(b"!UPS failure!!").unwrap(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ino/esp2stm.ino: -------------------------------------------------------------------------------- 1 | 2 | #include // Подключаем библиотеку ESP8266WiFi 3 | #include 4 | #include 5 | 6 | const char* ssid = "SSID"; // Название Вашей WiFi сети 7 | const char* password = "password";// Пароль от Вашей WiFi сети 8 | 9 | #define RELAY_IP "192.168.1.75" 10 | 11 | String inputString = ""; // a String to hold incoming data 12 | bool stringComplete = false; // whether the string is complete 13 | 14 | //#define LED 1 // GPIO1/TXD01 - светодиод 15 | 16 | void setup(){ 17 | delay(2200); 18 | Serial.begin(115200); // Скорость передачи 115200 19 | // reserve 200 bytes for the inputString: 20 | inputString.reserve(200); 21 | 22 | //pinMode(LED,OUTPUT); // Указываем вывод RELAY как выход 23 | //digitalWrite(LED, HIGH); // Устанавливаем LED в HIGH 24 | Serial.println(); // Печать пустой строки 25 | Serial.print("Connecting to "); // Печать "Подключение к:" 26 | Serial.println(ssid); // Печать "Название Вашей WiFi сети" 27 | 28 | WiFi.mode(WIFI_STA); 29 | WiFi.begin(ssid, password); // Подключение к WiFi Сети 30 | 31 | while (WiFi.status() != WL_CONNECTED) // Проверка подключения к WiFi сети 32 | { 33 | delay(500); // Пауза 500 мс 34 | Serial.print("."); // Печать "." 35 | switch (WiFi.status()){ 36 | case WL_NO_SSID_AVAIL: { 37 | Serial.println("WiFi not available"); 38 | delay(1500); 39 | continue; 40 | }; 41 | case WL_CONNECT_FAILED: { 42 | Serial.println("WiFi pass failed"); 43 | delay(1500); 44 | continue; 45 | }; 46 | }; 47 | } 48 | Serial.println(""); // Печать пустой строки 49 | Serial.println("WiFi connected"); // Печать "WiFi connected" 50 | 51 | // server.begin(); // Запуск сервера 52 | // Serial.println("Server started"); // Печать "Server starte" 53 | Serial.print("Local IP: "); // Печать "Use this URL to connect:" 54 | Serial.print(WiFi.localIP()); // Печать выданого IP адресса 55 | } 56 | 57 | void loop(){ 58 | if (!WiFi.isConnected()) { 59 | ESP.restart(); 60 | }; 61 | 62 | WiFiClient client; 63 | HTTPClient http; 64 | 65 | if (stringComplete) { 66 | //Serial.println(inputString); 67 | 68 | if (inputString.indexOf("AT") != -1) { // обрабатываем команду AT для проверки связи с модулем 69 | Serial.println("OK"); 70 | } 71 | 72 | if (inputString.indexOf("PING") != -1) { // обрабатываем команду PING для проверки связи с релейным модулем 73 | http.setTimeout(3000); 74 | if (http.begin(client, "http://" RELAY_IP)) { // HTTP 75 | // start connection and send HTTP header 76 | int httpCode = http.GET(); 77 | // httpCode will be negative on error 78 | if (httpCode > 0) { 79 | if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { 80 | Serial.println("OK"); 81 | } else { 82 | Serial.printf("ERROR 2"); 83 | } 84 | } else { 85 | Serial.println("ERROR 1"); 86 | } 87 | http.end(); 88 | } else { 89 | Serial.printf("ERROR 0"); 90 | } 91 | } 92 | 93 | if (inputString.indexOf("RESET") != -1) { // обрабатываем команду RESET 94 | http.setTimeout(15000); // ответ придёт только после ресета 95 | if (http.begin(client, "http://" RELAY_IP "/RELAY=RESET")) { // HTTP 96 | // start connection and send HTTP header 97 | int httpCode = http.GET(); 98 | // httpCode will be negative on error 99 | if (httpCode > 0) { 100 | if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { 101 | String payload = http.getString(); 102 | if (payload.indexOf("RESET OK") != -1) { 103 | Serial.println("OK"); 104 | } else { 105 | Serial.printf("ERROR 2"); 106 | } 107 | } 108 | } else { 109 | Serial.println("ERROR 1"); 110 | } 111 | http.end(); 112 | } else { 113 | Serial.printf("ERROR 0"); 114 | } 115 | } 116 | 117 | // clear the string: 118 | inputString = ""; 119 | stringComplete = false; 120 | } 121 | } 122 | 123 | /* 124 | SerialEvent occurs whenever a new data comes in the hardware serial RX. This 125 | routine is run between each time loop() runs, so using delay inside loop can 126 | delay response. Multiple bytes of data may be available. 127 | */ 128 | void serialEvent() { 129 | while (Serial.available()) { 130 | // get the new byte: 131 | char inChar = (char)Serial.read(); 132 | // add it to the inputString: 133 | inputString += inChar; 134 | // if the incoming character is a newline, set a flag so the main loop can 135 | // do something about it: 136 | if (inChar == '\n') { 137 | stringComplete = true; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/esp01.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use core::marker::PhantomData; 3 | use crate::context::Context; 4 | 5 | //-------------------------------------------------------------- 6 | // Функции работы с ESP01 7 | //-------------------------------------------------------------- 8 | const ESP_BUF_LEN: usize = 50; // длина буфера приёма статуса ESP 9 | 10 | // Объекты возможных состояний машины 11 | #[derive(PartialEq, Eq)] 12 | struct InitialEsp; 13 | #[derive(PartialEq, Eq)] 14 | struct ConnectedEsp; 15 | 16 | // конкретное состояние 17 | #[derive(PartialEq, Eq)] 18 | struct EspState { 19 | state: PhantomData, 20 | } 21 | 22 | // конкретные типы состояний 23 | type ESPI = EspState; 24 | type ESPC = EspState; 25 | 26 | // допустимые методы перехода между состояниями 27 | impl From<&ESPI> for ESPC { 28 | fn from(_val: &ESPI) -> ESPC { 29 | EspState { state: PhantomData } 30 | } 31 | } 32 | impl From<&ESPC> for ESPI { 33 | fn from(_val: &ESPC) -> ESPI { 34 | EspState { state: PhantomData } 35 | } 36 | } 37 | 38 | // обертка допустимых состояний для работы match 39 | #[derive(PartialEq, Eq)] 40 | enum EspStateWrapper { 41 | InitialEsp(ESPI), // не установлена связь с локальным модулем ESP 42 | ConnectedEsp(ESPC), // связь с модулем есть 43 | } 44 | 45 | impl Default for EspStateWrapper { 46 | fn default() -> Self { 47 | EspStateWrapper::InitialEsp(ESPI { state: PhantomData }) 48 | } 49 | } 50 | 51 | pub struct Esp { 52 | state_machine: EspStateWrapper, 53 | rcv_buf: Vec, 54 | esp_port: Esp01PortType, 55 | esp_reset_pin: Esp01PinType, 56 | } 57 | 58 | impl Esp { 59 | pub fn new(port: Esp01PortType, pin: Esp01PinType) -> Self { 60 | Esp { 61 | state_machine: Default::default(), 62 | rcv_buf: Vec::new(), 63 | esp_port: port, 64 | esp_reset_pin: pin, 65 | } 66 | } 67 | 68 | // перезагрузка модуля при проблемах 69 | pub fn reboot(&mut self, ctx: &mut Context) { 70 | self.esp_reset_pin.set_low().ok(); 71 | ctx.beep(); 72 | ctx.delay.delay_ms(1_500_u16); 73 | self.esp_reset_pin.set_high().ok(); 74 | ctx.watchdog.feed(); 75 | } 76 | 77 | // проверка/инициализация связи с модулем ESP 78 | pub fn check_com(&mut self, ctx: &mut Context) -> bool { 79 | if self 80 | .send_cmd_wait_resp_n(ctx, b"AT\n", b"OK", 50, 10, 3) 81 | .is_ok() 82 | { 83 | match &self.state_machine { 84 | EspStateWrapper::InitialEsp(val) => { 85 | self.state_machine = EspStateWrapper::ConnectedEsp(val.into()); 86 | write_log(b"ESP Connected!"); 87 | } 88 | _ => {} 89 | } 90 | return true; 91 | } else { 92 | self.state_machine = Default::default(); 93 | return false; 94 | } 95 | } 96 | 97 | // отправка команды и получение ответа от ESP 98 | fn send_cmd_wait_resp( 99 | &mut self, 100 | ctx: &mut Context, 101 | at_cmd: &[u8], // команда 102 | toc: u16, // timeout for first char 103 | to: u16, 104 | ) -> Result<(), Error> { 105 | // читаем мусор из порта и игнорим все ошибки 106 | loop { 107 | let res = self.esp_port.read(); 108 | match res { 109 | Err(nb::Error::Other(_)) => { 110 | write_log(b"ESP e0"); 111 | break; 112 | } 113 | Err(nb::Error::WouldBlock) => { 114 | // к счастью ничего нет 115 | break; 116 | } 117 | Ok(_) => {} // если что, перезагрузится по сторожевому таймеру 118 | } 119 | } 120 | // отправляем команду 121 | for cmd in at_cmd { 122 | block!(self.esp_port.write(*cmd)).ok(); 123 | } 124 | // пробуем получить ответ 125 | self.rcv_buf.clear(); 126 | ctx.at_timer.reset(); // reset timeout counter 127 | let mut got_first_char = false; // признак, что получили что-то из порта 128 | let mut w1_cycles = 0; // циклов задержки ожидания первого символа в ответе 129 | let mut w2_cycles = 0; // циклов задержки ожидания последнего символа в ответе 130 | loop { 131 | let res = self.esp_port.read(); 132 | match res { 133 | Err(nb::Error::Other(_)) => { 134 | write_log(b"ESP e1"); 135 | return Err(Error::SerialError); 136 | } 137 | Err(nb::Error::WouldBlock) => { 138 | // символ не пришёл ещё 139 | let t = ctx.at_timer.wait(); 140 | match t { 141 | Err(nb::Error::Other(_)) => { 142 | write_log(b"ESP e2"); 143 | return Err(Error::TimerError); 144 | } 145 | Err(nb::Error::WouldBlock) => { 146 | // просто ждём ещё таймер 147 | ctx.watchdog.feed(); 148 | continue; 149 | } 150 | Ok(_) => { 151 | // сработал таймер отсчёта таймаута 152 | if got_first_char { 153 | // отрабатываем ожидание последнего символа 154 | if w2_cycles >= to { 155 | break; // вылет по таймауту 156 | } else { 157 | w2_cycles += 1; 158 | continue; 159 | } 160 | } else { 161 | // отрабатываем ожидание первого символа 162 | if w1_cycles >= toc { 163 | break; 164 | } else { 165 | w1_cycles += 1; 166 | continue; 167 | } 168 | } 169 | } 170 | } 171 | } 172 | Ok(x) => { 173 | // получили очередной символ от ESP 174 | if self.rcv_buf.len() < ESP_BUF_LEN { 175 | // защита от переполнения буфера 176 | self.rcv_buf.push(x).unwrap(); 177 | //write_log(b" ESP resp char:"); 178 | //write_log(&[x]); 179 | } else { 180 | break; 181 | } 182 | got_first_char = true; // после первого символа переходим на ожидание последнего 183 | w2_cycles = 0; 184 | ctx.at_timer.reset(); // timeout timer restart after each byte recieved 185 | continue; 186 | } 187 | } 188 | } 189 | if self.rcv_buf.len() == 0 { 190 | // ничего не смогли получить 191 | write_log(b"ESP nrsp"); 192 | return Err(Error::SerialNoData); 193 | } 194 | Ok(()) 195 | } 196 | 197 | // отправка команды за несколько попыток 198 | fn send_cmd_wait_resp_n( 199 | &mut self, 200 | ctx: &mut Context, 201 | at_cmd: &[u8], 202 | ans: &[u8], 203 | toc: u16, // timeout for first char 204 | to: u16, // timeout after last char 205 | tries: u8, 206 | ) -> Result<(), Error> { 207 | // no of attempts 208 | // checks if reply from ESP contains ans using tries attempts 209 | let mut reply: bool = false; 210 | for _ in 0..tries { 211 | match self.send_cmd_wait_resp(ctx, at_cmd, toc, to) { 212 | Ok(_) => {} 213 | Err(Error::SerialNoData) => continue, 214 | Err(val) => return Err(val), 215 | }; 216 | if buf_contains(&self.rcv_buf, ans) { 217 | reply = true; 218 | break; 219 | } 220 | ctx.delay.delay_ms(500_u16); // delay between attempts 221 | ctx.watchdog.feed(); 222 | } 223 | if reply { 224 | return Ok(()); 225 | } 226 | Err(Error::CmdFail) 227 | } 228 | 229 | // PING удаленного модуля реле 230 | pub fn esp_ping(&mut self, ctx: &mut Context) -> Result<(), Error> { 231 | if self.state_machine == EspStateWrapper::default() { 232 | return Err(Error::EspOffLine); 233 | } 234 | self.send_cmd_wait_resp_n(ctx, b"PING\n", b"OK", 3500, 20, 1) 235 | } 236 | 237 | // RESET удаленного модуля реле 238 | pub fn esp_reset(&mut self, ctx: &mut Context) -> Result<(), Error> { 239 | if self.state_machine == EspStateWrapper::default() { 240 | return Err(Error::EspOffLine); 241 | } 242 | self.send_cmd_wait_resp_n(ctx, b"RESET\n", b"OK", 15000, 50, 1) 243 | } 244 | 245 | // ERROR удаленного модуля реле 246 | pub fn get_error(&mut self) -> &[u8] { 247 | if self.rcv_buf.starts_with(b"ERROR ") && self.rcv_buf.len() > 6 { 248 | return &self.rcv_buf[..7]; 249 | } 250 | b"ERR?" 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/sensors.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use core::marker::PhantomData; 3 | 4 | use crate::traits::Observable; 5 | use crate::context::Context; 6 | use crate::eeprom::{Eeprom, EepromAdresses}; 7 | use crate::sim800l::SmsType::*; 8 | 9 | //-------------------------------------------------------------- 10 | // Функции отслеживания состояний датчиков 220в и температуры 11 | //-------------------------------------------------------------- 12 | 13 | // структура для отслеживания диапазона датчика температуры DS18B20 14 | 15 | const TEMP_LIMIT_HIGH: f32 = 20.0; // приемлемое значение температуры 16 | const TEMP_LIMIT_LOW: f32 = 15.0; // пороговое значение, означающее критическое понижение температуры 17 | 18 | // Объекты возможных состояний машины 19 | struct ColdStartT; 20 | struct MonitoringT; 21 | struct WaitForNormalT; 22 | 23 | // конкретное состояние 24 | struct TempState { 25 | state: PhantomData, 26 | } 27 | 28 | // конкретные типы состояний 29 | type TEMPS = TempState; 30 | type TEMPM = TempState; 31 | type TEMPW = TempState; 32 | 33 | // допустимые методы перехода между состояниями 34 | impl From<&TEMPS> for TEMPM { 35 | fn from(_val: &TEMPS) -> TEMPM { 36 | TempState { state: PhantomData } 37 | } 38 | } 39 | impl From<&TEMPM> for TEMPW { 40 | fn from(_val: &TEMPM) -> TEMPW { 41 | TempState { state: PhantomData } 42 | } 43 | } 44 | impl From<&TEMPW> for TEMPM { 45 | fn from(_val: &TEMPW) -> TEMPM { 46 | TempState { state: PhantomData } 47 | } 48 | } 49 | 50 | // обертка допустимых состояний для работы match 51 | enum TempStateWrapper { 52 | ColdStart(TEMPS), 53 | Monitoring(TEMPM), 54 | WaitForNormal(TEMPW), 55 | } 56 | 57 | impl Default for TempStateWrapper { 58 | fn default() -> Self { 59 | TempStateWrapper::ColdStart(TEMPS { state: PhantomData }) 60 | } 61 | } 62 | 63 | // конвертация в u8 и обратно для сохранения в EEPROM 64 | impl From<&TempStateWrapper> for u8 { 65 | fn from(dr: &TempStateWrapper) -> Self { 66 | match dr { 67 | TempStateWrapper::ColdStart(_) => 0, 68 | TempStateWrapper::Monitoring(_) => 1, 69 | TempStateWrapper::WaitForNormal(_) => 2, 70 | } 71 | } 72 | } 73 | 74 | impl From for TempStateWrapper { 75 | fn from(dr: u8) -> Self { 76 | match dr { 77 | 0 => Default::default(), 78 | 1 => TempStateWrapper::Monitoring(TEMPM { state: PhantomData }), 79 | 2 => TempStateWrapper::WaitForNormal(TEMPW { state: PhantomData }), 80 | _ => Default::default(), 81 | } 82 | } 83 | } 84 | 85 | // методы работы машины состояний 86 | // создание машины 87 | pub struct TempChecker { 88 | temp_checking_machine: TempStateWrapper, 89 | address: u8, 90 | temp: f32, 91 | one_wire_bus: OwBusType, 92 | } 93 | 94 | impl TempChecker { 95 | pub fn new(bus: OwBusType) -> Self { 96 | TempChecker { 97 | temp_checking_machine: Default::default(), 98 | address: EepromAdresses::TempState as u8, 99 | temp: 0.0, 100 | one_wire_bus: bus, 101 | } 102 | } 103 | 104 | #[must_use] 105 | pub fn get_temp(&mut self) -> f32 { 106 | self.temp 107 | } 108 | 109 | // измерение температуры 110 | // ошибка передаётся как отрицательная температура. Её можно будет увидеть в SMS 111 | pub fn measure(&mut self, ctx: &mut Context) { 112 | let w1 = 113 | ds18b20::start_simultaneous_temp_measurement(&mut self.one_wire_bus, &mut ctx.delay); 114 | match w1 { 115 | Err(_) => { 116 | write_log(b"OW e1"); 117 | self.temp = -101.0; 118 | return 119 | } 120 | Ok(_) => { 121 | Resolution::Bits12.delay_for_measurement_time(&mut ctx.delay); 122 | let search_state = None; 123 | let w2 = 124 | self.one_wire_bus 125 | .device_search(search_state.as_ref(), false, &mut ctx.delay); 126 | match w2 { 127 | Err(_) => { 128 | write_log(b"OW e2"); 129 | self.temp = -102.0; 130 | return 131 | } 132 | Ok(None) => { 133 | write_log(b"OW none"); 134 | self.temp = -103.0; 135 | return 136 | } 137 | Ok(Some((device_address, _state))) => { 138 | //search_state = Some(state); // у нас только один датчик, дальше не ищем 139 | if device_address.family_code() == ds18b20::FAMILY_CODE { 140 | let w0: core::result::Result< 141 | ds18b20::Ds18b20, 142 | one_wire_bus::OneWireError, 143 | > = Ds18b20::new(device_address); 144 | match w0 { 145 | Err(_) => { 146 | write_log(b"OW e5"); 147 | self.temp = -104.0; 148 | return 149 | } 150 | Ok(sensor) => { 151 | let w3 = 152 | sensor.read_data(&mut self.one_wire_bus, &mut ctx.delay); 153 | match w3 { 154 | Err(_) => { 155 | write_log(b"OW e4"); 156 | self.temp = -105.0; 157 | return 158 | } 159 | Ok(sensor_data) => { 160 | //writeln!(console, "Device at {:?} is {}C", device_address, sensor_data.temperature); 161 | self.temp = sensor_data.temperature; 162 | return 163 | } 164 | } 165 | } 166 | } 167 | } else { 168 | write_log(b"OW e3"); 169 | self.temp = -106.0; 170 | return 171 | } 172 | } 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | impl Eeprom for TempChecker { 180 | /// восстановление состояния из EEPROM после перезагрузки МК 181 | fn load(&mut self, ctx: &mut Context) { 182 | let address = u32::from(self.address); 183 | let read_data = ctx.eeprom.read_byte(address).unwrap(); 184 | self.temp_checking_machine = read_data.into(); 185 | } 186 | 187 | /// запись состояния в EEPROM 188 | fn save(&mut self, ctx: &mut Context) -> Result<(), Error> { 189 | let address = u32::from(self.address); 190 | let to_save: u8 = (&self.temp_checking_machine).into(); 191 | ctx.save_byte(address, to_save) 192 | } 193 | } 194 | 195 | impl Observable for TempChecker { 196 | fn check(&mut self, ctx: &mut Context, sim: &mut Sim800) -> Result<(), Error> { 197 | match &self.temp_checking_machine { 198 | TempStateWrapper::ColdStart(val) => { 199 | self.temp_checking_machine = TempStateWrapper::Monitoring(val.into()); 200 | self.measure(ctx); 201 | } 202 | TempStateWrapper::Monitoring(val) => { 203 | if self.temp > -99.0 && self.temp < TEMP_LIMIT_LOW { 204 | // температура упала 205 | sim.send_sms(ctx, b"Temp too low", Normal).ok(); 206 | write_log(b"SMS Temp too low"); 207 | ctx.beep(); 208 | 209 | self.temp_checking_machine = TempStateWrapper::WaitForNormal(val.into()); 210 | self.save(ctx).ok(); 211 | } 212 | } 213 | TempStateWrapper::WaitForNormal(val) => { 214 | if self.temp > TEMP_LIMIT_HIGH { 215 | // температура вернулось в норму 216 | sim.send_sms(ctx, b"Temp OK", Normal).ok(); 217 | write_log(b"SMS Temp OK"); 218 | ctx.beep(); 219 | 220 | self.temp_checking_machine = TempStateWrapper::Monitoring(val.into()); 221 | self.save(ctx).ok(); 222 | } 223 | } 224 | } 225 | Ok(()) 226 | } 227 | } 228 | 229 | // константы подбираются экспериментально!!! 230 | const VCC: f32 = 3.3; // напряжение питания МК 231 | const V220_START: f32 = 1.5; // значение ADC, означающее приемлемое напряжение питания 232 | const V220_LIMIT_LOW: f32 = 0.3; // пороговое значение ADC, означающее отсутствие питания 233 | 234 | // Объекты возможных состояний машины 235 | struct ColdStartV; 236 | struct MonitoringV; 237 | struct WaitForNormalV; 238 | 239 | // конкретное состояние 240 | struct V220State { 241 | state: PhantomData, 242 | } 243 | 244 | // конкретные типы состояний 245 | type V220S = V220State; 246 | type V220M = V220State; 247 | type V220W = V220State; 248 | 249 | // допустимые методы перехода между состояниями 250 | impl From<&V220S> for V220M { 251 | fn from(_val: &V220S) -> V220M { 252 | V220State { state: PhantomData } 253 | } 254 | } 255 | impl From<&V220M> for V220W { 256 | fn from(_val: &V220M) -> V220W { 257 | V220State { state: PhantomData } 258 | } 259 | } 260 | impl From<&V220W> for V220M { 261 | fn from(_val: &V220W) -> V220M { 262 | V220State { state: PhantomData } 263 | } 264 | } 265 | 266 | // обертка допустимых состояний для работы match 267 | enum V220StateWrapper { 268 | ColdStart(V220S), 269 | Monitoring(V220M), 270 | WaitForNormal(V220W), 271 | } 272 | 273 | impl Default for V220StateWrapper { 274 | fn default() -> Self { 275 | V220StateWrapper::ColdStart(V220S { state: PhantomData }) 276 | } 277 | } 278 | 279 | // конвертация в u8 и обратно для сохранения в EEPROM 280 | impl From<&V220StateWrapper> for u8 { 281 | fn from(dr: &V220StateWrapper) -> Self { 282 | match dr { 283 | V220StateWrapper::ColdStart(_) => 0, 284 | V220StateWrapper::Monitoring(_) => 1, 285 | V220StateWrapper::WaitForNormal(_) => 2, 286 | } 287 | } 288 | } 289 | 290 | impl From for V220StateWrapper { 291 | fn from(dr: u8) -> Self { 292 | match dr { 293 | 0 => Default::default(), 294 | 1 => V220StateWrapper::Monitoring(V220M { state: PhantomData }), 295 | 2 => V220StateWrapper::WaitForNormal(V220W { state: PhantomData }), 296 | _ => Default::default(), 297 | } 298 | } 299 | } 300 | 301 | // методы работы машины состояний 302 | // создание машины 303 | pub struct V220Checker { 304 | v220_checking_machine: V220StateWrapper, 305 | address: u8, 306 | voltage: f32, 307 | int_temp: i32, 308 | analog_input: AnalogIn0Type, 309 | adc: Adc1Type, 310 | } 311 | 312 | impl V220Checker { 313 | pub fn new(pin: AnalogIn0Type, adc1: Adc1Type) -> Self { 314 | V220Checker { 315 | v220_checking_machine: Default::default(), 316 | address: EepromAdresses::V220State as u8, 317 | voltage: 0.0, 318 | int_temp: 0, 319 | analog_input: pin, 320 | adc: adc1, 321 | } 322 | } 323 | 324 | // измерение напряжения 220 в 325 | pub fn measure(&mut self) { 326 | let mut averaged: u32 = 0; 327 | for _ in 0..8 { 328 | let data: u16 = self.adc.read(&mut self.analog_input).unwrap(); //3.3d = 4095, 3.3/2 = 2036 329 | averaged += u32::from(data); 330 | } 331 | self.voltage = averaged as f32 / 8.0 / 4096.0 * VCC; 332 | } 333 | 334 | #[must_use] 335 | pub fn get_voltage(&mut self) -> f32 { 336 | self.voltage 337 | } 338 | 339 | // измерение температуры чипа 340 | pub fn measure_temp(&mut self) { 341 | self.int_temp = self.adc.read_temp(); 342 | } 343 | 344 | #[must_use] 345 | pub fn get_temp(&mut self) -> i32 { 346 | self.int_temp 347 | } 348 | } 349 | 350 | impl Eeprom for V220Checker { 351 | /// восстановление состояния из EEPROM после перезагрузки МК 352 | fn load(&mut self, ctx: &mut Context) { 353 | let address = u32::from(self.address); 354 | let read_data = ctx.eeprom.read_byte(address).unwrap(); 355 | self.v220_checking_machine = read_data.into(); 356 | } 357 | 358 | /// запись состояния в EEPROM 359 | fn save(&mut self, ctx: &mut Context) -> Result<(), Error> { 360 | let address = u32::from(self.address); 361 | let to_save: u8 = (&self.v220_checking_machine).into(); 362 | ctx.save_byte(address, to_save) 363 | } 364 | } 365 | 366 | impl Observable for V220Checker { 367 | fn check(&mut self, ctx: &mut Context, sim: &mut Sim800) -> Result<(), Error> { 368 | match &self.v220_checking_machine { 369 | V220StateWrapper::ColdStart(val) => { 370 | self.v220_checking_machine = V220StateWrapper::Monitoring(val.into()); 371 | self.measure(); 372 | } 373 | V220StateWrapper::Monitoring(val) => { 374 | if self.voltage < V220_LIMIT_LOW { 375 | // сетевое напряжение пропало 376 | sim.send_sms(ctx, b"220 failed", Normal).ok(); 377 | write_log(b"SMS 220 failed"); 378 | ctx.beep(); 379 | 380 | self.v220_checking_machine = V220StateWrapper::WaitForNormal(val.into()); 381 | self.save(ctx).ok(); 382 | } 383 | } 384 | V220StateWrapper::WaitForNormal(val) => { 385 | if self.voltage > V220_START { 386 | // сетевое напряжение вернулось 387 | sim.send_sms(ctx, b"220 on-line", Normal).ok(); 388 | write_log(b"SMS 220 on-line"); 389 | ctx.beep(); 390 | 391 | self.v220_checking_machine = V220StateWrapper::Monitoring(val.into()); 392 | self.save(ctx).ok(); 393 | } 394 | } 395 | } 396 | Ok(()) 397 | } 398 | } 399 | 400 | // структура для отслеживания напряжения батареи питания МК 401 | 402 | pub struct Battery { 403 | voltage: f32, 404 | analog_input: AnalogIn1Type, 405 | adc: Adc2Type, 406 | } 407 | 408 | impl Battery { 409 | pub fn new(pin: AnalogIn1Type, adc2: Adc2Type) -> Self { 410 | Battery { 411 | voltage: 0.0, 412 | analog_input: pin, 413 | adc: adc2, 414 | } 415 | } 416 | 417 | #[must_use] 418 | pub fn get_voltage(&mut self) -> f32 { 419 | self.voltage 420 | } 421 | 422 | // измерение напряжения батареи 423 | pub fn measure(&mut self) { 424 | let mut averaged: u32 = 0; 425 | for _ in 0..8 { 426 | let data: u16 = self.adc.read(&mut self.analog_input).unwrap(); //3.3d = 4095, 3.3/2 = 2036 427 | averaged += u32::from(data); 428 | } 429 | self.voltage = averaged as f32 / 8.0 / 4096.0 * VCC; 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | mod context; 5 | mod eeprom; 6 | mod errors; 7 | mod esp01; 8 | mod interrupts; 9 | mod megatec; 10 | mod sensors; 11 | mod sim800l; 12 | mod traits; 13 | 14 | use core::fmt::Write; 15 | use core::sync::atomic::Ordering; 16 | use cortex_m_rt::entry; 17 | use ds18b20::*; 18 | use eeprom24x::{Eeprom24x, SlaveAddr}; 19 | use embedded_hal::blocking::delay::DelayMs; 20 | use embedded_hal::digital::v2::OutputPin; 21 | use heapless::*; 22 | use nb::block; 23 | use one_wire_bus::*; 24 | use panic_halt as _; 25 | use stm32f1xx_hal::{ 26 | adc, 27 | delay::Delay, 28 | i2c::{BlockingI2c, DutyCycle, Mode}, 29 | pac, 30 | prelude::*, 31 | pwm::Channel, 32 | rtc::Rtc, 33 | serial::{Config, Serial}, 34 | time::MilliSeconds, 35 | timer::{Event, Timer}, 36 | watchdog::IndependentWatchdog, 37 | }; 38 | //use cortex_m::asm; 39 | //use cortex_m_rt::ExceptionFrame; // Stack frame for exception handling. 40 | //use cortex_m_semihosting::hio; // For displaying messages on the debug console. 41 | 42 | use cortex_m::asm::delay; 43 | use hd44780_driver::*; 44 | use shared_bus::*; 45 | use stm32f1xx_hal::pac::{Interrupt, NVIC}; 46 | use stm32f1xx_hal::usb::{Peripheral, UsbBus}; 47 | use usb_device::prelude::*; 48 | use usbd_serial::{SerialPort, USB_CLASS_CDC}; 49 | 50 | use crate::context::Context; 51 | use crate::eeprom::Eeprom; 52 | use crate::errors::Error; 53 | use crate::esp01::Esp; 54 | use crate::interrupts::*; 55 | use crate::megatec::Ups; 56 | use crate::sensors::{Battery, TempChecker, V220Checker}; 57 | use crate::sim800l::{Sim800, SmsType::*}; 58 | 59 | /// Определение типов данных для сокращёния записи 60 | type AnalogIn1Type = stm32f1xx_hal::gpio::gpiob::PB1; 61 | type Adc2Type = adc::Adc; 62 | type AnalogIn0Type = stm32f1xx_hal::gpio::gpiob::PB0; 63 | type Adc1Type = adc::Adc; 64 | type OwBusType = one_wire_bus::OneWire< 65 | stm32f1xx_hal::gpio::Pxx>, 66 | >; 67 | type Sim800PortType = stm32f1xx_hal::serial::Serial; 68 | type Sim800PinType = 69 | stm32f1xx_hal::gpio::gpioa::PA6>; 70 | type Esp01PortType = stm32f1xx_hal::serial::Serial; 71 | type Esp01PinType = 72 | stm32f1xx_hal::gpio::gpioa::PA7>; 73 | type UpsPortType = stm32f1xx_hal::serial::Serial; 74 | 75 | // Определяем входную функцию 76 | #[entry] 77 | fn main() -> ! { 78 | //Show "Hello, world!" on the debug console, which is shown in OpenOCD 79 | //let mut debug_out = hio::hstdout().unwrap(); 80 | //writeln!(debug_out, "Hello, world!").unwrap(); 81 | // Получаем управление над аппаратными средствами 82 | let cp = cortex_m::Peripherals::take().unwrap(); 83 | let dp = pac::Peripherals::take().unwrap(); 84 | let mut flash = dp.FLASH.constrain(); 85 | let mut rcc = dp.RCC.constrain(); 86 | 87 | let clocks = rcc 88 | .cfgr 89 | .use_hse(8.mhz()) // переключились на кварц 90 | .sysclk(48.mhz()) // должна стать 48 для поддержки USB 91 | .pclk1(24.mhz()) 92 | .adcclk(2.mhz()) // для ADC 93 | .freeze(&mut flash.acr); 94 | // Prepare the alternate function I/O registers 95 | let mut afio = dp.AFIO.constrain(&mut rcc.apb2); 96 | let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); 97 | let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); 98 | let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); 99 | //let channels = dp.DMA1.split(&mut rcc.ahb); 100 | 101 | // Настройка пина встроенного в bluepill светодиода 102 | let led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); 103 | 104 | // Setup ADC 105 | let adc1 = adc::Adc::adc1(dp.ADC1, &mut rcc.apb2, clocks); 106 | let adc2 = adc::Adc::adc2(dp.ADC2, &mut rcc.apb2, clocks); 107 | // Configure pb0, pb1 as an analog input for ADC 108 | let ch0 = gpiob.pb0.into_analog(&mut gpiob.crl); 109 | let ch1 = gpiob.pb1.into_analog(&mut gpiob.crl); 110 | 111 | // Set up the RTC 112 | // Enable writes to the backup domain 113 | let mut pwr = dp.PWR; 114 | let mut backup_domain = rcc.bkp.constrain(dp.BKP, &mut rcc.apb1, &mut pwr); 115 | // Start the RTC 116 | let rtc = Rtc::rtc(dp.RTC, &mut backup_domain); 117 | 118 | // USART1 - ups 119 | let serial1 = { 120 | // Configure pa9 as a push_pull output, this will be the tx pin 121 | let tx = gpioa.pa9.into_alternate_push_pull(&mut gpioa.crh); 122 | let rx = gpioa.pa10; 123 | Serial::usart1( 124 | dp.USART1, 125 | (tx, rx), 126 | &mut afio.mapr, 127 | Config::default().baudrate(2400.bps()), 128 | clocks, 129 | &mut rcc.apb2, 130 | ) 131 | }; 132 | 133 | // USART2 - ESP8266 134 | let serial2 = { 135 | // Configure pa2 as a push_pull output, this will be the tx pin 136 | let tx = gpioa.pa2.into_alternate_push_pull(&mut gpioa.crl); 137 | let rx = gpioa.pa3; 138 | Serial::usart2( 139 | dp.USART2, 140 | (tx, rx), 141 | &mut afio.mapr, 142 | Config::default().baudrate(115_200.bps()), 143 | clocks, 144 | &mut rcc.apb1, 145 | ) 146 | }; 147 | 148 | // USART3 - sim800l 149 | let serial3 = { 150 | // Configure pb10 as a push_pull output, this will be the tx pin 151 | let tx = gpiob.pb10.into_alternate_push_pull(&mut gpiob.crh); 152 | let rx = gpiob.pb11; 153 | Serial::usart3( 154 | dp.USART3, 155 | (tx, rx), 156 | &mut afio.mapr, 157 | Config::default().baudrate(9600.bps()), 158 | clocks, 159 | &mut rcc.apb1, 160 | ) 161 | }; 162 | 163 | // настройка USB serial 164 | let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh); 165 | usb_dp.set_low().ok(); 166 | delay(clocks.sysclk().0 / 100); 167 | 168 | let usb_dm = gpioa.pa11; 169 | let usb_dp = usb_dp.into_floating_input(&mut gpioa.crh); 170 | 171 | let usb = Peripheral { 172 | usb: dp.USB, 173 | pin_dm: usb_dm, 174 | pin_dp: usb_dp, 175 | }; 176 | 177 | // Unsafe to allow access to static variables 178 | unsafe { 179 | let bus = UsbBus::new(usb); 180 | USB_BUS = Some(bus); 181 | USB_SERIAL = Some(SerialPort::new(USB_BUS.as_ref().unwrap())); 182 | let usb_dev = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x16c0, 0x27dd)) 183 | .manufacturer("Fake company") 184 | .product("Serial port") 185 | .serial_number("TEST") 186 | .device_class(USB_CLASS_CDC) 187 | .build(); 188 | USB_DEVICE = Some(usb_dev); 189 | } 190 | 191 | unsafe { 192 | NVIC::unmask(Interrupt::USB_HP_CAN_TX); 193 | NVIC::unmask(Interrupt::USB_LP_CAN_RX0); 194 | } 195 | 196 | // таймер для всяких секундных интервалов 197 | SEC_COUNT_REBOOT.store(0, Ordering::Relaxed); 198 | let sec_timer = unsafe { &mut *SEC_TIMER.as_mut_ptr() }; 199 | *sec_timer = Timer::tim2(dp.TIM2, &clocks, &mut rcc.apb1).start_count_down(1.hz()); 200 | // запускаем прерывание по таймеру 201 | sec_timer.listen(Event::Update); 202 | unsafe { NVIC::unmask(pac::Interrupt::TIM2) }; 203 | 204 | // таймер для отслеживания таймаутов cимволов USART. должен срабатывать через 10 мс 205 | let at_timer = Timer::tim3(dp.TIM3, &clocks, &mut rcc.apb1).start_count_down(100.hz()); 206 | 207 | // Create a delay timer from the RCC clocks 208 | let mut delay = Delay::new(cp.SYST, clocks); 209 | 210 | // ШИМ канал для работы пищалки 211 | let beeper = { 212 | // Configure pa8 as a push_pull output 213 | let pa8 = gpioa.pa8.into_alternate_push_pull(&mut gpioa.crh); 214 | // выдаём 1 кГц на этот пин 215 | let mut pwm = 216 | Timer::tim1(dp.TIM1, &clocks, &mut rcc.apb2).pwm(pa8, &mut afio.mapr, 1.khz()); 217 | pwm.enable(Channel::C1); 218 | pwm.set_duty(Channel::C1, 0); 219 | pwm 220 | }; 221 | 222 | // I2C setup for EEPROM & LCD 223 | let bus_i2c = { 224 | let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl); 225 | let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl); 226 | 227 | let i2c = BlockingI2c::i2c1( 228 | dp.I2C1, 229 | (scl, sda), 230 | &mut afio.mapr, 231 | Mode::Fast { 232 | frequency: 100_000.hz(), 233 | duty_cycle: DutyCycle::Ratio2to1, 234 | }, 235 | clocks, 236 | &mut rcc.apb1, 237 | 1000, 238 | 10, 239 | 1000, 240 | 1000, 241 | ); 242 | BusManagerSimple::new(i2c) 243 | }; 244 | 245 | // Настройка работы чипа EEPROM 246 | let eeprom = { 247 | let address = SlaveAddr::default(); 248 | Eeprom24x::new_24x16(bus_i2c.acquire_i2c(), address) // на модуле arduino 24x32 249 | }; 250 | 251 | const I2C_ADDRESS: u8 = 0x27; 252 | let mut lcd = HD44780::new_i2c(bus_i2c.acquire_i2c(), I2C_ADDRESS, &mut delay).unwrap(); 253 | lcd.reset(&mut delay).ok(); 254 | lcd.clear(&mut delay).ok(); 255 | lcd.set_display_mode( 256 | DisplayMode { 257 | display: Display::On, 258 | cursor_visibility: Cursor::Visible, 259 | cursor_blink: CursorBlink::On, 260 | }, 261 | &mut delay, 262 | ).ok(); 263 | lcd.write_bytes(b"Booting...", &mut delay).ok(); 264 | 265 | // Настройка сторожевого таймера 266 | let mut watchdog = IndependentWatchdog::new(dp.IWDG); 267 | // запускаем с 20 сек интервалом сброса 268 | watchdog.start(MilliSeconds(20_000)); 269 | 270 | // пин связан с контактом reset SIM800. Притягивание к земле перегружает модуль 271 | let mut sim800 = { 272 | let sim_reset_pin = gpioa 273 | .pa6 274 | .into_open_drain_output_with_state(&mut gpioa.crl, stm32f1xx_hal::gpio::State::High); 275 | Sim800::new(serial3, sim_reset_pin) 276 | }; 277 | let mut reboot_count: u8 = 0; // число попыток восстановления связи с SIM800 278 | 279 | // пин связан с контактом reset ESP01. Притягивание к земле перегружает модуль 280 | let mut esp01 = { 281 | let esp_reset_pin = gpioa 282 | .pa7 283 | .into_open_drain_output_with_state(&mut gpioa.crl, stm32f1xx_hal::gpio::State::High); 284 | Esp::new(serial2, esp_reset_pin) 285 | }; 286 | 287 | // создаём переменную с контекстом, которую будем передавать в другие функции 288 | let mut ctx = Context { 289 | watchdog: watchdog, // сторожевой таймер 290 | delay: delay, // функции задержки 291 | at_timer: at_timer, // таймер отслеживания таймаутов в последовательных портах 292 | rtc: rtc, // часы реального времени 293 | eeprom: eeprom, // доступ к EEPROM 294 | beeper: beeper, // функции пищалки 295 | led: led, // heartbeat LED 296 | }; 297 | // Гудок при старте контроллера 298 | ctx.beep(); 299 | 300 | // объект для взаимодействия с UPS 301 | let mut ups = Ups::new(serial1); 302 | if ups.measure(&mut ctx).is_err() { 303 | // первый вызов корректно инициализирует буфер 304 | write_log(b"UPS error"); 305 | } 306 | ctx.watchdog.feed(); 307 | 308 | // объект для взаимодействия с батареей МК 309 | let mut battery = Battery::new(ch1, adc2); 310 | battery.measure(); 311 | ctx.watchdog.feed(); 312 | 313 | // объект контроля 220в 314 | let mut v220control = V220Checker::new(ch0, adc1); 315 | let _ = v220control.load(&mut ctx); 316 | v220control.measure(); 317 | v220control.measure_temp(); 318 | ctx.watchdog.feed(); 319 | 320 | // OneWire DS18b20 line 321 | let one_wire_bus = { 322 | let one_wire_pin = gpiob 323 | .pb12 324 | .into_open_drain_output(&mut gpiob.crh) 325 | .downgrade(); 326 | OneWire::new(one_wire_pin).unwrap() 327 | }; 328 | // объект контроля температуры DS18B20 329 | let mut temp_control = TempChecker::new(one_wire_bus); 330 | let _ = temp_control.load(&mut ctx); 331 | temp_control.measure(&mut ctx); 332 | 333 | // ожидание СМС с балансом счёта сим карты 334 | let mut balance_wait = false; 335 | loop { 336 | // сбрасываем сторожевой таймер 337 | ctx.watchdog.feed(); 338 | // проверяем связь с SIM800L Если нет, то пробуем перегрузить SIM800 339 | // если не помогает перезагрузка, пробуем перегрузить микроконтроллер 340 | let chk = sim800.check_com(&mut ctx); 341 | if !chk { 342 | write_log(b"SIM com failed. SIM800 Reboot"); 343 | sim800.reboot(&mut ctx); 344 | for _ in 0..10 { 345 | ctx.delay.delay_ms(3_000_u16); 346 | ctx.watchdog.feed(); 347 | } 348 | // если не помогает перегрузка модуля, делаем ребут всего контроллера 349 | if reboot_count > 3 { 350 | write_log(b"Self reboot"); 351 | loop {} // должны перегрузиться по сторожевому таймеру 352 | } else { 353 | reboot_count += 1; 354 | } 355 | continue; // зацикливаемся пока не будет связи с SIM800 356 | } else { 357 | reboot_count = 0; 358 | } 359 | 360 | // проверяем регистрацию модуля в сети 361 | let reg_chk = sim800.check_reg(&mut ctx); 362 | if !reg_chk { 363 | write_log(b"No network"); 364 | let cnt = SEC_COUNT_REBOOT.load(Ordering::Relaxed); 365 | if cnt > 30 * 60 { 366 | // полчаса нет регистрации в сети - идем в полную перезагрузку 367 | sim800.reboot(&mut ctx); 368 | write_log(b"Full reboot"); 369 | loop {} // должны перегрузиться по сторожевому таймеру 370 | } else { 371 | ctx.delay.delay_ms(3_000_u16); 372 | continue; // зацикливаемся пока не будет регистрации в сети 373 | } 374 | } else { 375 | SEC_COUNT_REBOOT.store(0, Ordering::Relaxed); // сбрасываем таймер отсутствия регистрации в сети 376 | } 377 | ctx.watchdog.feed(); 378 | 379 | // проверяем связь с ESP Если нет, то пробуем перегрузить ESP 380 | // связи не должно быть, если модуль не может к WiFi подключиться 381 | // проблемы железа не проверяются! 382 | let chk = esp01.check_com(&mut ctx); 383 | if !chk { 384 | let cnt = SEC_COUNT_ESP.load(Ordering::Relaxed); 385 | if cnt > 10 * 60 { 386 | // 10 минут ESP не отвечает 387 | write_log(b"ESP01 com failed. Reboot"); 388 | esp01.reboot(&mut ctx); 389 | SEC_COUNT_ESP.store(0, Ordering::Relaxed); 390 | } 391 | } else { 392 | SEC_COUNT_ESP.store(0, Ordering::Relaxed); // сбрасываем таймер отсутствия связи с ESP 393 | } 394 | ctx.watchdog.feed(); 395 | 396 | // проверяем значения датчиков раз в 30 сек 397 | let cnt2 = SEC_COUNT_SENSOR.load(Ordering::Relaxed); 398 | if cnt2 > 30 { 399 | v220control.measure(); // получить данные о наличии 220в 400 | v220control.measure_temp(); // получить данные о температуре чипа 401 | temp_control.measure(&mut ctx); // тепература DS18B20 402 | if ups.measure(&mut ctx).is_err() { 403 | // получить строку состояния UPS 404 | write_log(b"UPS error"); 405 | } 406 | // получить данные о напряжении батареи МК при напряжении питания 3.3в 407 | battery.measure(); 408 | 409 | SEC_COUNT_SENSOR.store(0, Ordering::Relaxed); // сбрасываем таймер 410 | ctx.watchdog.feed(); 411 | 412 | // обновляем инфо на экране LCD 413 | lcd.reset(&mut ctx.delay).ok(); 414 | lcd.clear(&mut ctx.delay).ok(); 415 | lcd.set_display_mode( 416 | DisplayMode { 417 | display: Display::On, 418 | cursor_visibility: Cursor::Visible, 419 | cursor_blink: CursorBlink::On, 420 | }, 421 | &mut ctx.delay, 422 | ).ok(); 423 | let temp_ext = temp_control.get_temp(); 424 | let mut line: Vec = Vec::new(); 425 | write!(line, "Temp={0:.1}C", temp_ext).unwrap(); 426 | lcd.write_bytes(&line, &mut ctx.delay).ok(); 427 | // переходим на всторую строку 428 | lcd.set_cursor_pos(40, &mut ctx.delay).ok(); 429 | line.clear(); 430 | let datetime = sim800.clk(&mut ctx).unwrap_or_else(|_| { 431 | let mut err: Vec = Vec::new(); 432 | err.extend_from_slice(b"Bad date").unwrap(); 433 | err 434 | }); 435 | lcd.write_bytes(&datetime, &mut ctx.delay).ok(); 436 | } 437 | // проверка диапазонов значений температуры и напряжения 438 | //v220control.check(&mut ctx, &mut sim800); 439 | //temp_control.check(&mut ctx, &mut sim800); 440 | ctx.check(&mut v220control, &mut sim800).ok(); 441 | ctx.check(&mut temp_control, &mut sim800).ok(); 442 | 443 | // проверка необходимости отправки ежедневного статус СМС 444 | let mut need_sms = false; 445 | if ctx.rtc.wait_alarm().is_ok() { 446 | write_log(b"Alarm triggered"); 447 | need_sms = true; 448 | ctx.reset_rtc(); 449 | // пробуем раз в сутки синхронизировать часы через NTP 450 | sim800.sync_clk(&mut ctx).ok(); 451 | sim800.sync_clk_close(&mut ctx).ok(); 452 | } 453 | 454 | // 10 секунд ждем входящего звонка и при этом мигаем светодиодом 455 | if sim800.call_detect(&mut ctx).is_ok() { 456 | write_log(b"Auth call detected!"); 457 | need_sms = true; 458 | ctx.reset_rtc(); 459 | }; 460 | 461 | // обрабатываем команды во входящих SMS 462 | if !balance_wait { 463 | let sms_r = sim800.sms_detect(&mut ctx); 464 | match sms_r { 465 | Err(Error::NotAuthCall) => { 466 | write_log(b"Not auth SMS detected!"); 467 | } 468 | Err(_) => {} 469 | Ok(command) => { 470 | // команда перезагрузки МК по сторожевому таймеру 471 | if command.starts_with(b"Boot") { 472 | write_log(b"Boot command!"); 473 | loop {} 474 | } 475 | // команда отсылки статусного СМС, но 476 | // без сброса суточного таймера как при звонке 477 | if command.starts_with(b"Status") { 478 | write_log(b"Status command!"); 479 | need_sms = true; 480 | } 481 | // команда проверки связи с удаленным реле на ESP-01 482 | if command.starts_with(b"Ping") { 483 | write_log(b"PING command!"); 484 | let ping_result = esp01.esp_ping(&mut ctx); 485 | match ping_result { 486 | Err(_) => { 487 | let error = esp01.get_error(); 488 | sim800.send_sms(&mut ctx, error, Normal).ok(); 489 | write_log(b"PING ERROR:"); 490 | write_log(error); 491 | } 492 | Ok(_) => { 493 | sim800.send_sms(&mut ctx, b"PING OK", Normal).ok(); 494 | write_log(b"PING OK"); 495 | } 496 | } 497 | ctx.beep(); 498 | } 499 | // команда переключения удаленного реле на ESP-01 500 | if command.starts_with(b"Reset") { 501 | write_log(b"RESET command!"); 502 | let reset_result = esp01.esp_reset(&mut ctx); 503 | match reset_result { 504 | Err(_) => { 505 | let error = esp01.get_error(); 506 | sim800.send_sms(&mut ctx, error, Normal).ok(); 507 | write_log(b"RESET ERROR:"); 508 | write_log(error); 509 | } 510 | Ok(_) => { 511 | sim800.send_sms(&mut ctx, b"RESET OK", Normal).ok(); 512 | write_log(b"RESET OK"); 513 | } 514 | } 515 | ctx.beep(); 516 | } 517 | // команда запроса баланса сим карты МТС 518 | if command.starts_with(b"Balance") { 519 | // проверка баланса сим карты МТС - СМС 11 на номер 111 520 | write_log(b"Balance command!"); 521 | sim800.send_sms(&mut ctx, b"11", Balance).ok(); 522 | // сбрасываем таймер ожидания СМС с балансом 523 | SEC_COUNT_SENSOR.store(0, Ordering::Relaxed); 524 | balance_wait = true; 525 | ctx.beep(); 526 | } 527 | // команда синхронизации времени с МТС 528 | if command.starts_with(b"Clk") { 529 | write_log(b"Clk command!"); 530 | let sync_result = sim800.sync_clk(&mut ctx); 531 | sim800.sync_clk_close(&mut ctx).ok(); 532 | match sync_result { 533 | Err(_) => { 534 | sim800.send_sms(&mut ctx, b"Sync failed", Normal).ok(); 535 | write_log(b"Sync failed"); 536 | } 537 | Ok(_) => { 538 | sim800.send_sms(&mut ctx, b"Sync OK", Normal).ok(); 539 | write_log(b"Sync OK"); 540 | } 541 | } 542 | ctx.beep(); 543 | } 544 | } 545 | } 546 | } 547 | ctx.watchdog.feed(); 548 | 549 | // ждем СМС с балансом счёта сим карты после команды Balance 550 | if balance_wait { 551 | balance_wait = false; 552 | let sms_b = sim800.balance_sms_detect(&mut ctx); 553 | match sms_b { 554 | Err(Error::NoInSMS) => { 555 | write_log(b"No balance SMS detected!"); 556 | let bal_cnt = SEC_COUNT_SENSOR.load(Ordering::Relaxed); 557 | // продолжаем ждать 3 минуты СМС с балансом, потом в нормальный режим 558 | if bal_cnt <= 60 * 3 { 559 | balance_wait = true; 560 | } 561 | } 562 | Err(Error::NoUCS2) => { 563 | write_log(b"Balance not in UCS2!"); 564 | } 565 | Err(Error::InvalidUCS2Size) => { 566 | write_log(b"Balance UCS2 invalid!"); 567 | } 568 | Err(_) => {} 569 | Ok(balance) => { 570 | let mut sms_message: Vec = Vec::new(); 571 | write!( 572 | sms_message, 573 | "Balance:{}", 574 | core::str::from_utf8(&balance).unwrap() 575 | ) 576 | .unwrap(); 577 | sim800 578 | .send_sms(&mut ctx, sms_message.as_slice(), Normal) 579 | .ok(); 580 | } 581 | } 582 | } 583 | ctx.watchdog.feed(); 584 | 585 | // отправка статусного сообщения по СМС 586 | if need_sms { 587 | let temp_ext = temp_control.get_temp(); 588 | let temp_int = v220control.get_temp(); 589 | let v220 = v220control.get_voltage(); 590 | let batt = battery.get_voltage(); 591 | let ups_ans = ups.get_ups(); 592 | let datetime = sim800.clk(&mut ctx).unwrap_or_else(|_| { 593 | let mut err: Vec = Vec::new(); 594 | err.extend_from_slice(b"Bad date").unwrap(); 595 | err 596 | }); 597 | 598 | let mut sms_message: Vec = Vec::new(); 599 | writeln!(sms_message, "T1={}C", temp_int).unwrap(); 600 | writeln!(sms_message, "T2={0:.1}C", temp_ext).unwrap(); 601 | writeln!(sms_message, "V220={0:.2}V", v220).unwrap(); 602 | writeln!(sms_message, "V12={0:.2}V", batt).unwrap(); 603 | write!( 604 | sms_message, 605 | "UPS={}\n {}", 606 | core::str::from_utf8(&ups_ans).unwrap(), 607 | core::str::from_utf8(&datetime).unwrap() 608 | ) 609 | .unwrap(); 610 | sim800 611 | .send_sms(&mut ctx, sms_message.as_slice(), Normal) 612 | .ok(); 613 | } 614 | } 615 | } 616 | 617 | // глобальная функция логирования в последовательный порт USB 618 | pub fn write_log(to_write: &[u8]) { 619 | cortex_m::interrupt::free(|_| { 620 | let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; 621 | serial.write(to_write).ok(); 622 | }); 623 | } 624 | 625 | // поиск подстроки в буфере 626 | pub fn buf_contains(buffer: &[u8], pattern: &[u8]) -> bool { 627 | let psize = pattern.len(); 628 | let bsize = buffer.len(); 629 | for i in 0..bsize { 630 | let rlimit = i + psize; 631 | if rlimit > bsize { 632 | break; 633 | } 634 | let sl = &buffer[i..rlimit]; 635 | if sl == pattern { 636 | return true; 637 | } 638 | } 639 | false 640 | } 641 | -------------------------------------------------------------------------------- /src/sim800l.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::context::Context; 3 | use crate::eeprom::EepromAdresses; 4 | use core::marker::PhantomData; 5 | 6 | ///-------------------------------------------------------------- 7 | /// Функции работы с SIM800L 8 | ///-------------------------------------------------------------- 9 | 10 | const SIM800_NUMBER_LEN: usize = 40; // длина буфера телефонного номера SIM800 11 | const SIM800_RCV_BUF_LEN: usize = 1600; // длина буфера приёма данных от SIM800 12 | const DEFAULT_NUM: u8 = 3; // номер ячейки номеров SIM карты с дефолтным номером для SMS 13 | const SMS_COMMAND_LEN: usize = 16; // макисмальная длина команды в пришедем SMS 14 | const SMS_BALANCE_LEN: usize = 20; // макисмальная длина инфо о балансе в пришедем SMS 15 | 16 | // Тип отправляемого СМС 17 | pub enum SmsType { 18 | Normal, 19 | Balance, // Запрос баланса сим карты у оператора МТС 20 | } 21 | 22 | // Объекты возможных состояний машины 23 | struct Initial800; 24 | struct Connected800; 25 | struct Registered800; 26 | 27 | // конкретное состояние 28 | struct SimState { 29 | state: PhantomData, 30 | } 31 | 32 | // конкретные типы состояний 33 | type SIMI = SimState; 34 | type SIMC = SimState; 35 | type SIMR = SimState; 36 | 37 | // допустимые методы перехода между состояниями 38 | impl From<&SIMI> for SIMC { 39 | fn from(_val: &SIMI) -> SIMC { 40 | SimState { state: PhantomData } 41 | } 42 | } 43 | impl From<&SIMR> for SIMC { 44 | fn from(_val: &SIMR) -> SIMC { 45 | SimState { state: PhantomData } 46 | } 47 | } 48 | impl From<&SIMC> for SIMR { 49 | fn from(_val: &SIMC) -> SIMR { 50 | SimState { state: PhantomData } 51 | } 52 | } 53 | 54 | // обертка допустимых состояний для работы match 55 | enum SimStateWrapper { 56 | Initial800(SIMI), // не установлена связь с модулем 57 | Connected800(SIMC), // связь с модулем есть, нет регистрации в сети 58 | Registered800(SIMR), // модуль зарегистрирован в сети 59 | } 60 | 61 | impl Default for SimStateWrapper { 62 | fn default() -> Self { 63 | SimStateWrapper::Initial800(SIMI { state: PhantomData }) 64 | } 65 | } 66 | 67 | pub struct Sim800 { 68 | state_machine: SimStateWrapper, 69 | rcv_buf: Vec, 70 | auth: Vec, 3>, // вектор для 3 телефонных номеров 71 | active_num: u8, // порядковый номер в массиве auth активного телефонного номера 72 | sim800_port: Sim800PortType, 73 | sim_reset_pin: Sim800PinType, 74 | } 75 | 76 | impl Sim800 { 77 | pub fn new(port: Sim800PortType, pin: Sim800PinType) -> Self { 78 | Sim800 { 79 | state_machine: Default::default(), 80 | rcv_buf: Vec::new(), 81 | auth: Vec::new(), // allowed phone numbers 82 | active_num: 0, 83 | sim800_port: port, 84 | sim_reset_pin: pin, 85 | } 86 | } 87 | 88 | // номер, на который будут отправляться СМС 89 | pub fn get_active_num(&mut self) -> &[u8] { 90 | &self.auth[(self.active_num - 1) as usize][..] 91 | } 92 | 93 | // отправка АТ команды и получение ответа от SIM800 94 | fn send_at_cmd_wait_resp( 95 | &mut self, 96 | ctx: &mut Context, 97 | at_cmd: &[u8], // команда 98 | toc: u16, // timeout for first char 99 | to: u16, 100 | ) -> Result<(), Error> { 101 | // читаем мусор из порта и игнорим все ошибки 102 | loop { 103 | let res = self.sim800_port.read(); 104 | match res { 105 | Err(nb::Error::Other(_)) => { 106 | write_log(b"SIM e0"); 107 | break; 108 | } 109 | Err(nb::Error::WouldBlock) => { 110 | // к счастью ничего нет 111 | break; 112 | } 113 | Ok(_) => {} // если что, перезагрузится по сторожевому таймеру 114 | } 115 | } 116 | // отправляем команду 117 | for cmd in at_cmd { 118 | block!(self.sim800_port.write(*cmd)).ok(); 119 | } 120 | // пробуем получить ответ 121 | self.rcv_buf.clear(); 122 | ctx.at_timer.reset(); // reset timeout counter 123 | let mut got_first_char = false; // признак, что получили что-то из порта 124 | let mut w1_cycles = 0; // циклов задержки ожидания первого символа в ответе 125 | let mut w2_cycles = 0; // циклов задержки ожидания последнего символа в ответе 126 | loop { 127 | let res = self.sim800_port.read(); 128 | match res { 129 | Err(nb::Error::Other(_)) => { 130 | write_log(b"SIM e1"); 131 | return Err(Error::SerialError); 132 | } 133 | Err(nb::Error::WouldBlock) => { 134 | // символ не пришёл ещё 135 | let t = ctx.at_timer.wait(); 136 | match t { 137 | Err(nb::Error::Other(_)) => { 138 | write_log(b"SIM e2"); 139 | return Err(Error::TimerError); 140 | } 141 | Err(nb::Error::WouldBlock) => { 142 | // просто ждём ещё таймер 143 | ctx.watchdog.feed(); 144 | continue; 145 | } 146 | Ok(_) => { 147 | // сработал таймер отсчёта таймаута 148 | if got_first_char { 149 | // отрабатываем ожидание последнего символа 150 | if w2_cycles >= to { 151 | break; // вылет по таймауту 152 | } else { 153 | w2_cycles += 1; 154 | continue; 155 | } 156 | } else { 157 | // отрабатываем ожидание первого символа 158 | if w1_cycles >= toc { 159 | break; 160 | } else { 161 | w1_cycles += 1; 162 | continue; 163 | } 164 | } 165 | } 166 | } 167 | } 168 | Ok(x) => { 169 | // получили очередной символ от SIM800 170 | if self.rcv_buf.len() < SIM800_RCV_BUF_LEN { 171 | // защита от переполнения буфера 172 | self.rcv_buf.push(x).unwrap(); 173 | } else { 174 | break; 175 | } 176 | got_first_char = true; // после первого символа переходим на ожидание последнего 177 | w2_cycles = 0; 178 | ctx.at_timer.reset(); // timeout timer restart after each byte recieved 179 | continue; 180 | } 181 | } 182 | } 183 | if self.rcv_buf.len() == 0 { 184 | // ничего не смогли получить 185 | write_log(b"SIM nrsp"); 186 | return Err(Error::SerialNoData); 187 | } 188 | Ok(()) 189 | } 190 | 191 | // отправка команды за несколько попыток 192 | fn send_at_cmd_wait_resp_n( 193 | &mut self, 194 | ctx: &mut Context, 195 | at_cmd: &[u8], 196 | ans: &[u8], 197 | toc: u16, // timeout for first char 198 | to: u16, // timeout after last char 199 | tries: u8, 200 | ) -> Result<(), Error> { 201 | // no of attempts 202 | // checks if reply from SIM800L contains ans using tries attempts 203 | let mut reply: bool = false; 204 | for _ in 0..tries { 205 | match self.send_at_cmd_wait_resp(ctx, at_cmd, toc, to) { 206 | Ok(_) => {} 207 | Err(Error::SerialNoData) => continue, 208 | Err(val) => return Err(val), 209 | }; 210 | if buf_contains(&self.rcv_buf, ans) { 211 | reply = true; 212 | break; 213 | } 214 | ctx.delay.delay_ms(500_u16); // delay between attempts 215 | ctx.watchdog.feed(); 216 | } 217 | if reply { 218 | return Ok(()); 219 | } 220 | Err(Error::CmdFail) 221 | } 222 | 223 | // блокировка приёма звонков 224 | fn gsm_busy(&mut self, ctx: &mut Context, set_to: bool) -> Result<(), Error> { 225 | self.send_at_cmd_wait_resp_n( 226 | ctx, 227 | if set_to { 228 | b"AT+GSMBUSY=1\n" 229 | } else { 230 | b"AT+GSMBUSY=0\n" 231 | }, 232 | b"OK\r", 233 | 100, 234 | 10, 235 | 3, 236 | ) 237 | } 238 | 239 | // команды инициализации до регистрации в сети 240 | fn init_set_0(&mut self, ctx: &mut Context) -> Result<(), Error> { 241 | match &self.state_machine { 242 | SimStateWrapper::Initial800(_) => return Ok(()), 243 | _ => {} 244 | } 245 | // Reset to the factory settings 246 | self.send_at_cmd_wait_resp_n(ctx, b"AT&F\n", b"OK\r", 100, 10, 3)?; 247 | // switch off echo 248 | self.send_at_cmd_wait_resp_n(ctx, b"ATE0\n", b"OK\r", 50, 10, 3)?; 249 | // setup fixed baud rate 9600 250 | self.send_at_cmd_wait_resp_n(ctx, b"AT+IPR=9600\n", b"OK\r", 100, 10, 3) 251 | } 252 | 253 | // executed after module registration in network 254 | fn init_set_1(&mut self, ctx: &mut Context) -> Result<(), Error> { 255 | match &self.state_machine { 256 | SimStateWrapper::Initial800(_) => return Ok(()), 257 | _ => {} 258 | } 259 | // block unsolicited RINGs 260 | self.gsm_busy(ctx, true)?; 261 | // Request calling line identification 262 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CLIP=1\n", b"OK\r", 50, 10, 3)?; 263 | // Mobile Equipment Error Code 264 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CMEE=0\n", b"OK\r", 50, 10, 3)?; 265 | // set the SMS mode to text 266 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CMGF=1\n", b"OK\r", 50, 10, 3)?; 267 | // Disable messages about new SMS from the GSM module 268 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CNMI=2,0\n", b"OK\r", 100, 10, 3)?; 269 | // send AT command to init memory for SMS in the SIM card 270 | // response: 271 | // +CPMS: ,,,,, 272 | self.send_at_cmd_wait_resp_n( 273 | ctx, 274 | b"AT+CPMS=\"SM\",\"SM\",\"SM\"\n", 275 | b"+CPMS:", 276 | 100, 277 | 100, 278 | 3, 279 | )?; 280 | // select phonebook memory storage 281 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CPBS=\"SM\"\n", b"OK\r", 100, 10, 3)?; 282 | // Deactivate GPRS PDP context 283 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CIPSHUT\n", b"SHUT OK", 100, 10, 3) 284 | } 285 | 286 | // проверка/инициализация связи с модулем 287 | pub fn check_com(&mut self, ctx: &mut Context) -> bool { 288 | if self 289 | .send_at_cmd_wait_resp_n(ctx, b"AT\n", b"OK\r", 50, 10, 20) 290 | .is_ok() 291 | { 292 | match &self.state_machine { 293 | SimStateWrapper::Initial800(val) => { 294 | self.state_machine = SimStateWrapper::Connected800(val.into()); 295 | self.init_set_0(ctx).ok(); 296 | write_log(b"SIM800 Connected!"); 297 | } 298 | _ => {} 299 | } 300 | return true; 301 | } else { 302 | self.state_machine = Default::default(); 303 | return false; 304 | } 305 | } 306 | 307 | // перезагрузка модуля при проблемах 308 | pub fn reboot(&mut self, ctx: &mut Context) { 309 | self.sim_reset_pin.set_low().ok(); 310 | ctx.beep(); 311 | ctx.delay.delay_ms(1_500_u16); 312 | self.sim_reset_pin.set_high().ok(); 313 | ctx.watchdog.feed(); 314 | } 315 | 316 | // проверка регистрации в сети 317 | pub fn check_reg(&mut self, ctx: &mut Context) -> bool { 318 | let mut reply: bool = false; 319 | let ans1 = b"+CREG: 0,1"; 320 | let ans2 = b"+CREG: 0,5"; 321 | for _ in 0..10 { 322 | // 10 attempts 323 | if self 324 | .send_at_cmd_wait_resp(ctx, b"AT+CREG?\n", 100, 20) 325 | .is_err() 326 | { 327 | break; // пропала связь с модулем? 328 | } 329 | if buf_contains(&self.rcv_buf, ans1) || buf_contains(&self.rcv_buf, ans2) { 330 | reply = true; 331 | } else { 332 | ctx.watchdog.feed(); 333 | ctx.delay.delay_ms(5000_u16); // delay between attempts 334 | ctx.watchdog.feed(); 335 | write_log(b"Wait for reg..."); 336 | continue; // пробуем еще подождать регистрации 337 | } 338 | match &self.state_machine { 339 | SimStateWrapper::Connected800(val) => { 340 | if reply { 341 | self.state_machine = SimStateWrapper::Registered800(val.into()); 342 | write_log(b"Registered!"); 343 | reply = self.init_set_1(ctx).is_ok(); 344 | if reply { 345 | reply = self.init_auth(ctx).is_ok(); 346 | } 347 | } 348 | } 349 | _ => {} 350 | } 351 | if reply { 352 | // прекращаем попытки 353 | break; 354 | } 355 | } 356 | reply 357 | } 358 | 359 | // Считываение первых 3 номеров с SIM карты. Они будут использоваться для авторизации звонков 360 | fn init_auth(&mut self, ctx: &mut Context) -> Result<(), Error> { 361 | self.active_num = ctx.eeprom.read_byte(EepromAdresses::Number as u32).unwrap(); 362 | if !(self.active_num > 0 && self.active_num < 4) { 363 | // неправильный номер в EEPROM 364 | self.active_num = DEFAULT_NUM; // дефолтное значение 365 | ctx.eeprom 366 | .write_byte(EepromAdresses::Number as u32, self.active_num) 367 | .unwrap(); 368 | ctx.delay.delay_ms(10_u16); 369 | } 370 | 371 | self.auth.clear(); 372 | let mut reply: bool = false; 373 | for j in 1..=3 { 374 | // take first 3 numbers from SIM phonebook 375 | let mut buf: Vec = Vec::new(); 376 | write!(buf, "AT+CPBR={}\n", j).unwrap(); 377 | for _ in 0..3 { 378 | // make 3 attempts 379 | match self.send_at_cmd_wait_resp(ctx, buf.as_slice(), 100, 10) { 380 | Ok(()) => {} 381 | Err(Error::SerialNoData) => continue, 382 | Err(val) => return Err(val), 383 | } 384 | if buf_contains(&self.rcv_buf, b"OK\r") { 385 | if !buf_contains(&self.rcv_buf, b"+CPBR:") { 386 | self.auth 387 | .push(Vec::::from_slice(b"NA").unwrap()) 388 | .unwrap(); 389 | } else { 390 | // parse for phone number 391 | let mut found = false; 392 | let mut number: Vec = Vec::new(); 393 | for sub in &self.rcv_buf { 394 | if sub == &b'"' { 395 | if found { 396 | break; 397 | } 398 | found = true; 399 | continue; 400 | } 401 | if found { 402 | //copy chars from SIM800 reply 403 | number.push(*sub).unwrap(); 404 | } 405 | } 406 | self.auth.push(number).unwrap(); 407 | } 408 | reply = true; 409 | break; 410 | } 411 | ctx.delay.delay_ms(500_u16); // delay between attempts 412 | ctx.watchdog.feed(); 413 | } 414 | } 415 | if reply { 416 | return Ok(()); 417 | } 418 | Err(Error::NoAuthNumbers) 419 | } 420 | 421 | // отправка простого SMS на дефолтный номер 422 | pub fn send_sms(&mut self, ctx: &mut Context, msg: &[u8], typ: SmsType) -> Result<(), Error> { 423 | let mut buf: Vec = Vec::new(); 424 | match typ { 425 | Normal => { 426 | writeln!( 427 | buf, 428 | r#"AT+CMGS="{}""#, 429 | core::str::from_utf8(self.get_active_num())? 430 | )?; 431 | } 432 | // для запроса баланса отправляем СМС на номер МТС 111 433 | Balance => { 434 | writeln!(buf, r#"AT+CMGS="111""#)?; 435 | } 436 | } 437 | self.send_at_cmd_wait_resp(ctx, buf.as_slice(), 100, 10)?; 438 | if buf_contains(&self.rcv_buf, b">") { 439 | buf.clear(); 440 | write!(buf, "{}\u{001a}", core::str::from_utf8(&msg)?)?; 441 | write_log(b"SMS="); 442 | write_log(buf.as_slice()); 443 | self.send_at_cmd_wait_resp(ctx, buf.as_slice(), 700, 100)?; 444 | if buf_contains(&self.rcv_buf, b"+CMGS") { 445 | ctx.delay.delay_ms(1000_u16); // чуть ждём после посылки 446 | return Ok(()); 447 | } 448 | return Err(Error::SmsStage2); 449 | } else { 450 | return Err(Error::SmsStage1); 451 | } 452 | } 453 | 454 | // check number is auth 455 | fn is_auth(&mut self, ctx: &mut Context, number: &[u8]) -> bool { 456 | let mut reply = false; 457 | for (i, nbr) in (&self.auth).into_iter().enumerate() { 458 | if nbr == &number { 459 | //println!("auth num = {}", core::str::from_utf8(&number).unwrap()); 460 | reply = true; 461 | let number_i = i as u8 + 1; 462 | if self.active_num != number_i { 463 | self.active_num = number_i; 464 | let address = u32::from(EepromAdresses::Number as u8); 465 | ctx.save_byte(address, self.active_num).ok(); 466 | } 467 | break; 468 | } 469 | } 470 | reply 471 | } 472 | 473 | // Определение входящего авторизованного звонка 474 | pub fn call_detect(&mut self, ctx: &mut Context) -> Result<(), Error> { 475 | let mut reply = false; 476 | // enable incoming calls and unsolicited RINGs 477 | self.gsm_busy(ctx, false)?; 478 | 479 | // пробуем получить RING 480 | self.rcv_buf.clear(); 481 | ctx.at_timer.reset(); // reset timeout counter 482 | let mut got_first_char = false; // признак, что получили что-то из порта 483 | let mut w1_cycles = 0; // циклов задержки ожидания первого символа в ответе 484 | let mut w2_cycles = 0; // циклов задержки ожидания последнего символа в ответе 485 | let mut led_state = false; 486 | loop { 487 | ctx.watchdog.feed(); 488 | 489 | // мигаем периодически светодиодом - heartbeat 490 | let cnt3 = SEC_COUNT_LED.load(Ordering::Relaxed); 491 | if cnt3 > 1 { 492 | if led_state { 493 | ctx.led.set_high().ok(); 494 | led_state = false; 495 | } else { 496 | ctx.led.set_low().ok(); 497 | led_state = true; 498 | } 499 | SEC_COUNT_LED.store(0, Ordering::Relaxed); // сбрасываем таймер 500 | } 501 | 502 | let res = self.sim800_port.read(); 503 | match res { 504 | Err(nb::Error::Other(_)) => { 505 | write_log(b"RING e1"); 506 | return Err(Error::SerialError); 507 | } 508 | Err(nb::Error::WouldBlock) => { 509 | // символ не пришёл ещё 510 | let t = ctx.at_timer.wait(); 511 | match t { 512 | Err(nb::Error::Other(_)) => { 513 | write_log(b"RING e2"); 514 | return Err(Error::TimerError); 515 | } 516 | Err(nb::Error::WouldBlock) => { 517 | // просто ждём ещё таймер 518 | ctx.watchdog.feed(); 519 | continue; 520 | } 521 | Ok(_) => { 522 | // сработал таймер отсчёта таймаута 523 | if got_first_char { 524 | // отрабатываем ожидание последнего символа 525 | if w2_cycles >= 200 { 526 | break; // вылет по таймауту 527 | } else { 528 | w2_cycles += 1; 529 | continue; 530 | } 531 | } else { 532 | // отрабатываем ожидание первого символа 10 сек 533 | if w1_cycles >= 1000 { 534 | break; 535 | } else { 536 | w1_cycles += 1; 537 | continue; 538 | } 539 | } 540 | } 541 | } 542 | } 543 | Ok(x) => { 544 | // получили очередной символ от SIM800 545 | if self.rcv_buf.len() < SIM800_RCV_BUF_LEN { 546 | // защита от переполнения буфера 547 | self.rcv_buf.push(x).unwrap(); 548 | } else { 549 | break; 550 | } 551 | got_first_char = true; // после первого символа переходим на ожидание последнего 552 | w2_cycles = 0; 553 | ctx.at_timer.reset(); // timeout timer restart after each byte recieved 554 | continue; 555 | } 556 | } 557 | } 558 | if self.rcv_buf.len() == 0 { 559 | // ничего не смогли получить 560 | write_log(b"NO RING"); 561 | self.gsm_busy(ctx, true)?; // block GSM RINGs 562 | return Err(Error::NoRing); 563 | } 564 | 565 | // check RING number in buf 566 | let mut number: Vec = Vec::new(); 567 | if !buf_contains(&self.rcv_buf, b"+CLIP:") { 568 | number.push(b'N').unwrap(); 569 | } else { 570 | // ищем номер вызывающего телефона 571 | let mut found = false; 572 | for sub in &self.rcv_buf { 573 | if sub == &b'"' { 574 | if found { 575 | break; 576 | } 577 | found = true; 578 | continue; 579 | } 580 | if found { 581 | //copy chars from SIM800 reply 582 | if number.len() < SIM800_NUMBER_LEN { 583 | number.push(*sub).unwrap(); 584 | } else { 585 | break; 586 | } 587 | } 588 | } 589 | // звонок с номера 590 | write_log(b"RING: "); 591 | write_log(&number); 592 | } 593 | 594 | // check number is auth 595 | if self.is_auth(ctx, &number) { 596 | reply = true 597 | } 598 | 599 | // hang up call 600 | self.send_at_cmd_wait_resp_n(ctx, b"ATH\n", b"OK\r", 50, 10, 5)?; 601 | // block unsolicited RINGs 602 | self.gsm_busy(ctx, true)?; 603 | if reply { 604 | return Ok(()); 605 | } 606 | Err(Error::NotAuthCall) 607 | } 608 | 609 | // очистка памяти SMS сообщений 610 | fn sms_del(&mut self, ctx: &mut Context) -> Result<(), Error> { 611 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CMGDA=\"DEL ALL\"\n", b"OK\r", 150, 10, 3) 612 | } 613 | 614 | pub fn sms_detect(&mut self, ctx: &mut Context) -> Result, Error> { 615 | /*self.send_at_cmd_wait_resp(ctx, b"AT+CMGL=\"ALL\"\n", 500, 150).unwrap(); 616 | writeln!(ctx.console, "sms list = {}", 617 | core::str::from_utf8(self.rcv_buf.as_slice()).unwrap()).ok();*/ 618 | self.send_at_cmd_wait_resp(ctx, b"AT+CMGR=1\n", 500, 150) 619 | .unwrap(); 620 | // если есть в 1 ячейке SMS, должно прийти что-то типа 621 | // +CMGR: "REC READ","+79850000000","","21/10/29,16:30:00+12"\r\nTest\r\n\r\nOK\r\n 622 | // или ERORR, если в этой ячекйке ничего нет 623 | write_log(b"SMS detected = "); 624 | write_log(self.rcv_buf.as_slice()); 625 | if buf_contains(&self.rcv_buf, b"\r\n+CMGR:") && buf_contains(&self.rcv_buf, b"\r\nOK\r\n") 626 | { 627 | // ищем номер вызывающего телефона 628 | let mut number: Vec = Vec::new(); 629 | let mut found_number = false; 630 | let mut subcount = 1; // ищем с третьих кавычек до 4 кавычек 631 | for sub in &self.rcv_buf { 632 | if sub == &b'"' { 633 | if found_number { 634 | break; 635 | } 636 | if subcount != 3 { 637 | subcount += 1 638 | } else { 639 | found_number = true 640 | } 641 | continue; 642 | } 643 | if found_number { 644 | // копируем символы номера в буфер 645 | if number.len() < SIM800_NUMBER_LEN { 646 | number.push(*sub).unwrap(); 647 | } else { 648 | break; 649 | } 650 | } 651 | } 652 | 653 | // проверяем авторизацию телефона 654 | if !self.is_auth(ctx, &number) { 655 | self.sms_del(ctx)?; 656 | return Err(Error::NotAuthCall); 657 | } 658 | 659 | // ищем в теле сообщения текст команды 660 | let mut found = false; 661 | let mut subcount = 1; 662 | let mut msgpos = 0; // позиция начала тела сообщения 663 | for (i, sub) in (&self.rcv_buf).into_iter().enumerate() { 664 | if sub == &b'"' { 665 | if subcount != 8 { 666 | // ищем с 8ых кавычек 667 | subcount += 1; 668 | continue; 669 | } else { 670 | found = true; 671 | continue; 672 | } 673 | } 674 | if found { 675 | msgpos = i; 676 | break; 677 | } 678 | } 679 | if self.rcv_buf.len() > 2 { 680 | // ограничиваем размер команды SMS_COMMAND_LEN символами 681 | let mut body_cmd: Vec = Vec::new(); 682 | let body_text_src = &self.rcv_buf[(msgpos + 2)..]; // вырезаем первые CRLF 683 | let mut slice_len = body_text_src.len(); 684 | if slice_len > SMS_COMMAND_LEN { 685 | slice_len = SMS_COMMAND_LEN 686 | } 687 | body_cmd 688 | .extend_from_slice(&body_text_src[..slice_len - 1]) 689 | .unwrap(); 690 | self.sms_del(ctx)?; 691 | return Ok(body_cmd); 692 | } 693 | } 694 | Err(Error::NoInSMS) 695 | } 696 | 697 | pub fn balance_sms_detect( 698 | &mut self, 699 | ctx: &mut Context, 700 | ) -> Result, Error> { 701 | fn hex2u16(c: u8) -> u16 { 702 | // from 0 to 9 703 | if (c >= 0x30) && (c <= 0x39) { 704 | return (c - 0x30) as u16; 705 | // from A to F 706 | } else if (c >= 0x41) && (c <= 0x46) { 707 | return (c - 0x41 + 10) as u16; 708 | } else { 709 | return 0; 710 | } 711 | } 712 | 713 | self.send_at_cmd_wait_resp(ctx, b"AT+CMGR=1\n", 500, 150) 714 | .unwrap(); 715 | // если есть в 1 ячейке SMS, должно прийти что-то типа 716 | // +CMGR: "REC UNREAD","111","","22/01/10,20:50:06+12"\r\n04110430043B0430043D0441\r\n\r\nOK\r\n 717 | // или ERORR, если в этой ячекйке ничего нет 718 | // номер отправителя не проверяется! 719 | write_log(b"SMS detected = "); 720 | write_log(self.rcv_buf.as_slice()); 721 | if buf_contains(&self.rcv_buf, b"\r\n+CMGR:") && buf_contains(&self.rcv_buf, b"\r\nOK\r\n") 722 | { 723 | let mut ucs2buf: Vec = Vec::new(); 724 | let mut ucs2_start: usize = 0; 725 | let mut ucs2_stop: usize = 0; 726 | let mut cmgr_found = false; 727 | ucs2buf.clear(); 728 | for (index, chr) in (&self.rcv_buf).iter().enumerate() { 729 | // ищем + как признак начала CMGR 730 | if *chr == b'+' { 731 | cmgr_found = true; 732 | continue; 733 | } 734 | // ищем начало текста смс 735 | if cmgr_found && ucs2_start == 0 && *chr == b'\n' { 736 | ucs2_start = index + 1; 737 | continue; 738 | } 739 | // ищем конец текста смс 740 | if ucs2_start > 0 && *chr == b'\r' { 741 | ucs2_stop = index; 742 | break; 743 | } 744 | } 745 | if !(ucs2_start > 0 && ucs2_stop > 0) { 746 | return Err(Error::NoUCS2); 747 | } 748 | let ucs2str = &self.rcv_buf[ucs2_start..ucs2_stop]; 749 | let ucs2len = ucs2str.len(); 750 | if ucs2len % 4 != 0 { 751 | return Err(Error::InvalidUCS2Size); 752 | } 753 | 754 | let mut idx = 0; 755 | loop { 756 | // восстанавливаем юникодный код символа из UCS2 757 | let code = (hex2u16(ucs2str[idx]) << 12) 758 | + (hex2u16(ucs2str[idx + 1]) << 8) 759 | + (hex2u16(ucs2str[idx + 2]) << 4) 760 | + hex2u16(ucs2str[idx + 3]); 761 | // берем только значимые для баланса счёта ASCII символы 762 | match code { 763 | 0x30..=0x39 | 0x2c..=0x2e => { 764 | if ucs2buf.len() < SMS_BALANCE_LEN { 765 | ucs2buf.push(code as u8).unwrap(); 766 | } else { 767 | break; 768 | } 769 | } 770 | _ => {} 771 | } 772 | // переходим к следующему символу, если он есть 773 | idx += 4; 774 | if idx >= ucs2len { 775 | break; 776 | } 777 | } 778 | self.sms_del(ctx)?; 779 | return Ok(ucs2buf); 780 | } 781 | Err(Error::NoInSMS) 782 | } 783 | 784 | // executed after module registration in network 785 | pub fn sync_clk(&mut self, ctx: &mut Context) -> Result<(), Error> { 786 | // Check whether the GPRS registration is successful 787 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CGREG?\n", b"+CGREG: 0,1", 50, 10, 3)?; 788 | // write_log(b"AT+CGREG is OK"); 789 | ctx.watchdog.feed(); 790 | // Query GPRS attachment status 791 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CGATT?\n", b"+CGATT: 1", 50, 10, 3)?; 792 | // write_log(b"AT+CGATT is OK"); 793 | ctx.watchdog.feed(); 794 | // Set NTP network parameters 795 | self.send_at_cmd_wait_resp_n( 796 | ctx, 797 | b"AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\"\n", 798 | b"OK\r", 799 | 50, 800 | 10, 801 | 3, 802 | )?; 803 | // write_log(b"CONTYPE is OK"); 804 | ctx.watchdog.feed(); 805 | // Set NTP network APN parameters 806 | self.send_at_cmd_wait_resp_n( 807 | ctx, 808 | b"AT+SAPBR=3,1,\"APN\",\"internet.mts.ru\"\n", 809 | b"OK\r", 810 | 50, 811 | 10, 812 | 3, 813 | )?; 814 | // write_log(b"APN is OK"); 815 | self.send_at_cmd_wait_resp_n(ctx, b"AT+SAPBR=3,1,\"USER\",\"mts\"\n", b"OK\r", 50, 10, 3)?; 816 | // write_log(b"USER is OK"); 817 | self.send_at_cmd_wait_resp_n(ctx, b"AT+SAPBR=3,1,\"PWD\",\"mts\"\n", b"OK\r", 50, 10, 3)?; 818 | // write_log(b"PWD is OK"); 819 | // Activate the network scene 820 | self.send_at_cmd_wait_resp_n(ctx, b"AT+SAPBR=1,1\n", b"OK\r", 100, 10, 3)?; 821 | // write_log(b"SAPBR is OK"); 822 | ctx.watchdog.feed(); 823 | // Obtain a local IP address 824 | self.send_at_cmd_wait_resp_n(ctx, b"AT+SAPBR=2,1\n", b"+SAPBR: 1,1", 100, 10, 3)?; 825 | // write_log(b"IP is OK"); 826 | ctx.watchdog.feed(); 827 | // Set up NTP server 828 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CNTP=\"pool.ntp.org\",12\n", b"OK\r", 100, 10, 3)?; 829 | // write_log(b"CNTP is OK"); 830 | // Enable network time synchronization 831 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CNTP\n", b"+CNTP: 1", 1000, 300, 3)?; 832 | // write_log(b"+CNTP: 1 is OK"); 833 | ctx.watchdog.feed(); 834 | self.sync_clk_close(ctx) 835 | } 836 | 837 | pub fn sync_clk_close(&mut self, ctx: &mut Context) -> Result<(), Error> { 838 | // Deactivate the network scene 839 | self.send_at_cmd_wait_resp_n(ctx, b"AT+SAPBR=0,1\n", b"OK\r", 100, 10, 3) 840 | } 841 | 842 | pub fn clk(&mut self, ctx: &mut Context) -> Result, Error> { 843 | // Get local time +CCLK:YY/MM/DD,HH:mm:SS+12 (+CCLK: "YY/MM/DD,HH:mm:SS+32") 844 | self.send_at_cmd_wait_resp_n(ctx, b"AT+CCLK?\n", b"+CCLK:", 100, 10, 3)?; 845 | // write_log(b"+CCLK: is OK"); 846 | // parse for DATETIME 847 | let mut found = false; 848 | let mut subdt: Vec = Vec::new(); 849 | for sub in &self.rcv_buf { 850 | if sub == &b'"' { 851 | if found { 852 | break; 853 | } 854 | found = true; 855 | continue; 856 | } 857 | if found && subdt.len() < 60 { 858 | //copy chars from SIM800 reply 859 | subdt.push(*sub).unwrap(); 860 | } 861 | } 862 | write_log(b"Datetime: "); 863 | write_log(&subdt); 864 | Ok(subdt) 865 | } 866 | } 867 | --------------------------------------------------------------------------------