Эта статья о моем опыте сотрудничества с DeepSeek в разработке некоторых поделок на различных языках программирования. Раньше писал на этих языках, но без помощЭта статья о моем опыте сотрудничества с DeepSeek в разработке некоторых поделок на различных языках программирования. Раньше писал на этих языках, но без помощ

Мой соавтор — DeepSeek

2026/03/14 14:24
28м. чтение
Для обратной связи или замечаний по поводу данного контента, свяжитесь с нами по адресу [email protected]

Эта статья о моем опыте сотрудничества с DeepSeek в разработке некоторых поделок на различных языках программирования. Раньше писал на этих языках, но без помощи ИИ.

Поделка 1:

Решил разработать систему авторизованного доступа в здание с использованием телефона.

Для этого собрал модем , состоящий из 3-х деталей: адаптер USB-UART, модуль SIM800L и корпус.

Получилось вот такое устройство:Внеш

Внешний вид модема в сравнении с банковской картой
Внешний вид модема в сравнении с банковской картой

Программу решил написать на Lua.

Для этого, не в первый раз, обращаюсь к DeepSeek(DS) с просьбой разработать скрипт, который будет выполнять следующие функции:

1) Подключаться через виртуальный com порт к модему;

2) Контролировать подключение к сети оператора связи;

3) Проверять уровень сигнала GSM;

4) При звонках:

4.1), определять номер звонящего;

4.2) записывать номер в файл регистрации и в таблицу;

5) При SMS:

5.1) Принимать SMS содержащие команды и параметры. Формат команды есть в тексте скрипта.

Далее процесс свелся к следующему.

DS пишет скрипт.

Я его запускаю и сообщаю ему результат.

Если есть ошибки, то DS объясняет их и исправляет.

Если у меня есть идея, как изменить или расширить функции кода, то сообщаю об этом DS.

Не всегда DS соглашается с моими идеями. В этом случае он объясняет, почему нет смысла в реализации этой идеи.

Если же нахожу его ошибки и указываю на них, то он соглашается и исправляет.

Когда DS исправляет ошибки, то он начинает улучшать и расширять скрипт. Порою это приводит к тому, что перестают работать те функции, которые работали раньше. В этом случае, предлагаю ему вернуться к предыдущему варианту.

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

Результирующий код:

-- GSM модем контроллер - КОМАНДЫ С БУКВЕННЫМИ ПАРАМЕТРАМИ -- Формат: "КОМАНДА[БУКВА][ЗНАЧЕНИЕ]..." -- Примеры: -- "1" - команда 1 без параметров -- "1t235" - команда 1 с параметром t=235 -- "2r1" - команда 2 с параметром r=1 -- "3t235h67" - команда 3 с параметрами t=235 и h=67 -- "1t235,2r1,3t235h67" - несколько команд через разделитель os.execute("chcp 65001 > nul") local port = nil local buffer = "" -- Таблицы для хранения данных local calls = {} -- входящие звонки local incoming_commands = {} -- входящие команды из SMS local outgoing_messages = {} -- отправленные SMS -- Функция задержки для Windows function sleep(seconds) local start = os.clock() while os.clock() - start < seconds do -- просто ждем end end -- Основная функция отправки команд function sendCommand(cmd, timeout) timeout = timeout or 3 -- Очищаем входной буфер local flush = "" repeat flush = port:read(1) until not flush -- Отправляем команду port:write(cmd .. "\r\n") port:flush() -- Собираем ответ local response = "" local start = os.clock() local lastDataTime = start while os.clock() - start < timeout do -- Пробуем прочитать символ local char = port:read(1) if char then response = response .. char lastDataTime = os.clock() -- Для отладки (можно закомментировать) -- print(string.format("0x%02X (%s)", char:byte(), char)) -- Проверяем окончание ответа if response:find("OK\r\n") or response:find("ERROR\r\n") or response:find("NO CARRIER\r\n") or response:find("RING\r\n") then break end else -- Если нет данных и прошло больше 0.2 сек после последних данных if #response > 0 and os.clock() - lastDataTime > 0.2 then break end end end return response end -- Функция для отправки команды спящему модему function sendToSleepingModem(cmd, timeout) timeout = timeout or 3 -- ШАГ 1: Отправляем пустую строку или AT для пробуждения port:write("\r\n") -- Просто перевод строки для пробуждения port:flush() -- Ждем 300 мс (используем нашу функцию sleep) local wakeStart = os.clock() while os.clock() - wakeStart < 0.3 do -- просто ждем end -- ШАГ 2: Очищаем буфер от мусора пробуждения local garbage = "" local cleanStart = os.clock() while os.clock() - cleanStart < 0.2 do local char = port:read(1) if char then garbage = garbage .. char else break end end if #garbage > 0 then print("Пробуждение мусор:", garbage) end -- ШАГ 3: Отправляем реальную команду return sendCommand(cmd, timeout) end -- Упрощенная версия для вашего случая function wakeAndSend(cmd, timeout) timeout = timeout or 3 -- Отправляем первую команду (она разбудит модем, но ответа не будет) port:write("AT\r\n") port:flush() -- Ждем 500 мс на пробуждение local t = os.clock() while os.clock() - t < 0.5 do end -- Теперь отправляем реальную команду return sendCommand(cmd, timeout) end -- Открытие порта local com = "com4" function openPort() for attempt = 1, 3 do os.execute('mode ' .. com .. ': baud=115200 parity=n data=8 stop=1') local portPath = "\\\\.\\" .. com port = io.open(portPath, "w+b") if port then port:setvbuf("no") print("Порт открыт") return true end print("Попытка " .. attempt .. " не удалась, ждем 2 сек...") sleep(2000) end print("Ошибка: не удалось открыть порт") return false end function sleep(ms) local start = os.clock() while os.clock() - start < ms / 1000 do end end function wrcmd(cmd) port:write(cmd .. "\r\n") port:flush() end -- Отправка команды function sendCommand(cmd, timeout) timeout = timeout or 2 port:write(cmd .. "\r\n");-- port:flush() local response = "" local start = os.clock() while os.clock() - start < timeout do local char = port:read(1) if char then response = response .. char if response:find("OK\r\n") or response:find("ERROR\r\n") then break end end end return response end -- Очистка номера телефона function cleanPhoneNumber(phone) if not phone then return nil end local cleaned = phone:gsub("[^%+%d]", "") if cleaned:match("^8%d%d%d") then cleaned = "+7" .. cleaned:sub(2) end if #cleaned >= 10 and #cleaned <= 15 then return cleaned end return nil end -- Извлечение номера из строки CLIP function extractPhoneFromCLIP(line) local phone = line:match('CLIP: "([^"]+)"') if phone then return cleanPhoneNumber(phone) end return nil end -- ============================================ -- ПАРСИНГ КОМАНД С БУКВЕННЫМИ ПАРАМЕТРАМИ -- ============================================ -- Парсинг одной команды с буквенными параметрами function parseSingleCommand(cmd_str) if not cmd_str or cmd_str == "" then return nil end -- Ищем команду (первые цифры) local cmd_num = cmd_str:match("^(%d+)") if not cmd_num then return nil end local cmd = tonumber(cmd_num) local rest = cmd_str:sub(#cmd_num + 1) local params = {} -- Разбираем буквенные параметры if rest and #rest > 0 then -- Ищем все паттерны "буква + число" for letter, value in rest:gmatch("([a-zA-Z])(%d+)") do params[letter] = tonumber(value) end end return { cmd = cmd, params = params, raw = cmd_str } end -- Разбор SMS с несколькими командами function parseCommands(text) if not text or text == "" then return {} end local commands = {} -- Разделяем по любым не-цифробуквенным символам -- (запятые, пробелы, точки с запятой и т.д.) for part in text:gmatch("[^,%s;:]+") do if part ~= "" then local cmd_data = parseSingleCommand(part) if cmd_data then table.insert(commands, cmd_data) end end end return commands end -- Вывод команды в читаемом виде function dumpCommand(cmd_data) local str = string.format("Команда %d", cmd_data.cmd) if next(cmd_data.params) then local params = {} for letter, value in pairs(cmd_data.params) do table.insert(params, string.format("%s=%d", letter, value)) end str = str .. " [" .. table.concat(params, ", ") .. "]" end return str end -- Обработка нескольких команд function processCommands(phone, commands) print(string.format(" Получено %d команд", #commands)) local responses = {} for idx, cmd_data in ipairs(commands) do print(" " .. dumpCommand(cmd_data)) local response = nil -- Команда 1: запрос статуса if cmd_data.cmd == 1 then response = "101" -- Команда 2: управление реле elseif cmd_data.cmd == 2 then if cmd_data.params['r'] then if cmd_data.params['r'] == 1 then relayOn() response = "102r1" elseif cmd_data.params['r'] == 0 then relayOff() response = "102r0" end end -- Команда 3: данные с датчиков elseif cmd_data.cmd == 3 then local temp = readTemperature() local humidity = readHumidity() local pressure = readPressure() -- Формируем ответ с буквенными параметрами response = "103" if temp then response = response .. "t" .. temp end if humidity then response = response .. "h" .. humidity end if pressure then response = response .. "p" .. pressure end -- Команда 4: установка параметров elseif cmd_data.cmd == 4 then if cmd_data.params['t'] then setTimer(cmd_data.params['t']) end if cmd_data.params['d'] then setDelay(cmd_data.params['d']) end response = "104" if cmd_data.params['t'] then response = response .. "t" .. cmd_data.params['t'] end if cmd_data.params['d'] then response = response .. "d" .. cmd_data.params['d'] end -- Команда 5: мульти-запрос elseif cmd_data.cmd == 5 then local temp = readTemperature() local status = getSystemStatus() response = "105t" .. temp .. "s" .. status end if response then table.insert(responses, response) end end -- Отправляем все ответы одним SMS if #responses > 0 then local answer = table.concat(responses, ",") sendSMS(phone, answer) end end -- SMS ФУНКЦИИ -- Отправка SMS function sendSMS(phone, text) phone = cleanPhoneNumber(phone) if not phone then print("Ошибка: неверный формат номера") return false end print("Отправка SMS на " .. phone .. ": " .. text) sendCommand("AT+CMGF=1", 1) sleep(100) local resp = sendCommand('AT+CMGS="' .. phone .. '"', 2) if resp:find(">") then port:write(text .. "\26") port:flush() sleep(2000) local final_resp = "" local start = os.clock() while os.clock() - start < 5 do local char = port:read(1) if char then final_resp = final_resp .. char if final_resp:find("OK") or final_resp:find("ERROR") then break end end end if final_resp:find("OK") then print("? SMS отправлена") -- Сохраняем в историю local msg = { id = #outgoing_messages + 1, phone = phone, text = text, date = os.date("%Y-%m-%d"), time = os.date("%H:%M:%S"), timestamp = os.time() } table.insert(outgoing_messages, msg) -- Лог local today = os.date("%Y-%m-%d") local f = io.open("sent_sms_" .. today .. ".log", "a") if f then f:write(string.format("[%s] КОМУ: %s\n", os.date("%H:%M:%S"), phone)) f:write(" ТЕКСТ: " .. text .. "\n") f:write(" " .. string.rep("-", 50) .. "\n") f:close() end saveToFile() return true else print("Ошибка отправки: " .. final_resp) return false end else print("Ошибка: модем не ответил символом >") return false end end -- Чтение SMS function readSMS(index) local resp = sendCommand('AT+CMGR=' .. index, 3) local lines = {} for line in resp:gmatch("[^\r\n]+") do table.insert(lines, line) end if #lines < 2 then return nil, nil end local header = lines[1] local phone = header:match('"+([%+%d]+)"') local date = header:match(',,"([^"]+)"') or header:match('","([^"]+)"$') local text = "" for i = 2, #lines do if lines[i] ~= "OK" and lines[i] ~= "" then text = lines[i] break end end return phone, text, date end -- Удаление SMS function deleteSMS(index) sendCommand('AT+CMGD=' .. index, 1) end -- Функция проверки уровня сигнала function getCSQ() -- Отправляем команду несколько раз, пока не получим правильный ответ for i = 1, 3 do local resp = sendCommand("AT+CSQ", 2) -- Проверяем, что это действительно ответ на CSQ if resp:find("+CSQ:") then local _, _, rssi = resp:find("+CSQ: (%d+),") if rssi then print("Уровень сигнала:" .. rssi) return tonumber(rssi) end else -- print("Попытка " .. i .. ": получен мусор, очищаем буфер...") -- Очищаем буфер local start = os.clock() while os.clock() - start < 0.3 do port:read(1) end end end print("Не удалось получить CSQ после 3 попыток") return nil end -- Функция ожидания регистрации в сети function waitForNetwork(maxWaitTime) maxWaitTime = maxWaitTime or 30 -- Ждем до 30 секунд print("Ожидание регистрации в сети (до " .. maxWaitTime .. " сек)...") local startTime = os.clock() while os.clock() - startTime < maxWaitTime do local resp = sendCommand("AT+CREG?", 3) -- Ищем паттерн +CREG: 0,1 (регистрация в домашней сети) -- или +CREG: 0,5 (регистрация в роуминге) if resp:find("+CREG: 0,[15]") then print("Модем зарегистрирован в сети!") -- Узнаем оператора local opResp = sendCommand("AT+COPS?", 3) local _, _, operator = opResp:find('"+COPS: %d+,%d+,"([^"]+)"') if operator then print("Оператор: " .. operator) end return true end -- Проверяем другие статусы if resp:find("+CREG: 0,2") then print(" Поиск сети...") elseif resp:find("+CREG: 0,3") then print(" Регистрация отклонена") elseif resp:find("+CREG: 0,4") then print(" Неизвестно") end sleep(2) -- Проверяем каждые 2 секунды end print("Таймаут ожидания сети") return false end function getSystemStatus() return 1 -- 1 = OK, 0 = Error end function relayOn() print(" Реле ВКЛ") -- Здесь код управления реле end function relayOff() print(" Реле ВЫКЛ") -- Здесь код управления реле end function readTemperature() return 235 -- 23.5°C * 10 end function readHumidity() return 67 -- 67% end function readPressure() return 1013 -- 1013 гПа end function setTimer(seconds) print(" Таймер установлен на " .. seconds .. " сек") end function setDelay(seconds) print(" Задержка установлена на " .. seconds .. " сек") end -- Сохранение звонка function saveCall(phoneNumber) local call = { id = #calls + 1, phone = phoneNumber, date = os.date("%Y-%m-%d"), time = os.date("%H:%M:%S"), timestamp = os.time() } table.insert(calls, call) local today = os.date("%Y-%m-%d") local f = io.open("calls_" .. today .. ".log", "a") if f then f:write(string.format("[%s] ЗВОНОК с %s\n", os.date("%H:%M:%S"), phoneNumber)) f:close() end print(" Звонок сохранен и сброшен") saveToFile() return call end -- Сохранение входящих команд function saveIncomingCommands(phone, raw_text, commands) local record = { id = #incoming_commands + 1, phone = phone, raw = raw_text, commands = commands, date = os.date("%Y-%m-%d"), time = os.date("%H:%M:%S"), timestamp = os.time() } table.insert(incoming_commands, record) local today = os.date("%Y-%m-%d") local f = io.open("commands_" .. today .. ".log", "a") if f then f:write(string.format("[%s] ОТ: %s\n", os.date("%H:%M:%S"), phone)) f:write(" ТЕКСТ: " .. raw_text .. "\n") f:write(" КОМАНДЫ:\n") for _, cmd in ipairs(commands) do f:write(" " .. dumpCommand(cmd) .. "\n") end f:write(" " .. string.rep("-", 50) .. "\n") f:close() end saveToFile() return record end -- Сохранение в файл function saveToFile() local f = io.open("modem_data.lua", "w") if f then f:write("-- Данные модема\n\n") f:write("calls = {\n") for _, call in ipairs(calls) do f:write(string.format(' {id=%d, ph, date="%s", time="%s", timestamp=%d},\n', call.id, call.phone, call.date, call.time, call.timestamp)) end f:write("}\n\n") f:write("incoming_commands = {\n") for _, cmd in ipairs(incoming_commands) do f:write(string.format(' {id=%d, ph, raw="%s", date="%s", time="%s", timestamp=%d},\n', cmd.id, cmd.phone, cmd.raw:gsub('"', '\\"'), cmd.date, cmd.time, cmd.timestamp)) end f:write("}\n\n") f:write("outgoing_messages = {\n") for _, msg in ipairs(outgoing_messages) do local safeText = msg.text:gsub('"', '\\"') f:write(string.format(' {id=%d, ph, text="%s", date="%s", time="%s", timestamp=%d},\n', msg.id, msg.phone, safeText, msg.date, msg.time, msg.timestamp)) end f:write("}\n") f:close() end end -- Загрузка из файла function loadTables() local f = io.open("modem_data.lua", "r") if f then f:close() dofile("modem_data.lua") calls = calls or {} incoming_commands = incoming_commands or {} outgoing_messages = outgoing_messages or {} print(string.format("Загружено: звонков %d, команд %d, исх SMS %d", #calls, #incoming_commands, #outgoing_messages)) else print("Новый файл данных") calls = {} incoming_commands = {} outgoing_messages = {} end end -- ИНИЦИАЛИЗАЦИЯ -- САМОЕ ПРОСТОЕ РЕШЕНИЕ: просто отправляем CSQ несколько раз function getCSQ_simple() -- print("Получение уровня сигнала...") for i = 1, 3 do local resp = sendCommand("AT+CSQ", 2) -- Проверяем, содержит ли ответ "+CSQ:" if resp and resp:find("+CSQ:") then local _, _, rssi = resp:find("+CSQ: (%d+),") if rssi then print("Уровень сигнала: " .. rssi) return tonumber(rssi) end -- else -- Если пришел не CSQ, просто логируем и продолжаем -- print("Попытка " .. i .. ": получили '" .. resp .. "', пробуем снова...") end -- Пауза между попытками local wait = os.clock() while os.clock() - wait < 0.3 do end end print("Не удалось получить уровень сигнала") return nil end function isNetworkReady() local resp = sendCommand("AT+CREG?", 2) -- true если зарегистрирован (0,1 или 0,5) return resp:find("0,1") or resp:find("0,5") ~= nil end function init() if not openPort() then return false end port:write("\r\n") -- Просто перевод строки для пробуждения port:flush() -- Ждем 300 мс (используем нашу функцию sleep) local wakeStart = os.clock() while os.clock() - wakeStart < 0.3 do end -- просто ждем local s=sendCommand("AT",1); -- wrcmd("ATE0"); --sendCommand("ATE0",1) --эхо выкл if s=="" then print("Модем не отвечает") return false end print("Модем отвечает:"..s) wrcmd("ATE0"); --sendCommand("ATE0",1) --эхо выкл waitForNetwork() -- Использование local rssi = getCSQ_simple() if rssi and rssi >= 10 then print("Сигнал достаточный для работы") end wrcmd("AT+IFC=0,0"); sendCommand("AT+IFC=0,0", 1) sendCommand("AT+CMGF=1", 1) -- Текстовый режим для SMS sendCommand("AT+CMGD=1,4", 2) -- print("Очистка памяти SIM...") sendCommand("AT+CLIP=1", 1) --аон вкл print("Настройка определителя номера...") — включает отправку номера вызывающего абонента. Теперь после каждого входящего звонка модем будет автоматически присылать строку с номером . sendCommand("AT+CMEE=1", 1) --— включает развернутые сообщения об ошибках. Полезно для отладки на случай, если что-то пойдет не так. sendCommand("AT+CNMI=2,1", 1) sendCommand("AT+CSCLK=2",1) --авто сон print("Модем готов к командам с буквенными параметрами") return true end -- ============================================ -- ГЛАВНЫЙ ЦИКЛ -- ============================================ function run() print("\n" .. string.rep("=", 60)) print("GSM КОНТРОЛЛЕР - КОМАНДЫ С БУКВЕННЫМИ ПАРАМЕТРАМИ") print("Формат: КОМАНДА[БУКВА][ЗНАЧЕНИЕ]...") print("Примеры:") print(" 1 - команда 1 без параметров") print(" 1t235 - команда 1 с параметром t=235") print(" 2r1 - команда 2 с параметром r=1") print(" 3t235h67 - команда 3 с t=235 и h=67") print(" 1t235,2r1,3t235h67 - несколько команд") print(string.rep("=", 60)) local lastStats = os.clock() local lastPhone = "" local lastPhoneTime = 0 while true do local char = port:read(1) if char then buffer = buffer .. char if char == "\n" then local line = buffer:gsub("\r", ""):gsub("\n", "") buffer = "" -- Обработка звонков local phone = extractPhoneFromCLIP(line) if phone then local currentTime = os.time() if phone == lastPhone and (currentTime - lastPhoneTime) < 21 then -- дубликат else port:write("\r\n") port:flush() -- Просто перевод строки для пробуждения -- Ждем 100 мс (используем нашу функцию sleep) local wakeStart = os.clock() while os.clock() - wakeStart < 0.1 do end -- просто ждем wrcmd("ATH"); print("!!! ВХОДЯЩИЙ ЗВОНОК от " .. phone) saveCall(phone) lastPhone = phone lastPhoneTime = currentTime end end -- Обработка SMS local idx = line:match('CMTI: "[^"]+",(%d+)') if idx then local num = tonumber(idx) print("!!! ВХОДЯЩЕЕ SMS в ячейке " .. num) sleep(200) local phone, text, date = readSMS(num) if phone and text then print(" От: " .. phone) print(" Текст: " .. text) -- Парсим команды local commands = parseCommands(text) if #commands > 0 then print(string.format(" Найдено команд: %d", #commands)) saveIncomingCommands(phone, text, commands) processCommands(phone, commands) else print(" Нет распознанных команд") end deleteSMS(num) end end end else if os.clock() - lastStats > 60 then local s=sendCommand("AT+CSQ",1); --уровень сигнала print("уровень сигнала ",s); print(string.format("Звонков: %d, Команд: %d, Отправлено: %d", #calls, #incoming_commands, #outgoing_messages)) lastStats = os.clock() end sleep(10) end end end -- ЗАПУСК loadTables() if init() then print("\n" .. string.rep("-", 60)) print("ТЕКУЩАЯ СТАТИСТИКА") print(" Звонков: " .. #calls) print(" Команд: " .. #incoming_commands) print(" Отправлено SMS: " .. #outgoing_messages) print(string.rep("-", 60) .. "\n") local ok, err = pcall(run) if not ok then print("Ошибка: " .. tostring(err)) end end if port then port:close() end print("\nПрограмма завершена") print("Всего звонков: " .. #calls) print("Всего команд: " .. #incoming_commands) print("Всего отправлено SMS: " .. #outgoing_messages) saveToFile()

Поделка 2:

Идея в том, чтобы заменить в Поделке 1 ПК на микроконтроллер ESP32-C3. В результате сделать автономное устройство, которое осуществляет авторизованный доступ в здание путем приема звонков с телефона и идентификацию звонящего по белому списку.

При этом программу решил написать на С.

Спросил мнение DS:

178375a96d0cd8d472e43db51964d985.png3d25eccbad0d8f72eae9c77c80ac869b.png

Поделка находится в стадии отладки, поэтому фрагменты кода для примера.

main/gsm.h

#ifndef GSM_H #define GSM_H #include <stdint.h> #include <stdbool.h> // Настройки SIM800L #define GSM_UART_PORT UART_NUM_1 #define GSM_TX_PIN 17 #define GSM_RX_PIN 16 #define GSM_BAUD_RATE 115200 #define GSM_PWR_PIN 4 #define GSM_RST_PIN 5 // Буферы #define GSM_RX_BUF_SIZE 1024 #define GSM_TX_BUF_SIZE 512 #define GSM_CMD_TIMEOUT_MS 5000 #define GSM_SMS_BUFFER_SIZE 256 // Результаты операций typedef enum { GSM_OK = 0, GSM_ERROR, GSM_TIMEOUT, GSM_NO_SIM, GSM_NO_SIGNAL } gsm_status_t; // Структура для SMS typedef struct { char phone[20]; char message[GSM_SMS_BUFFER_SIZE]; char datetime[32]; int index; } sms_data_t; // Инициализация void gsm_init(void); gsm_status_t gsm_power_on(void); gsm_status_t gsm_check_communication(void); // SMS функции gsm_status_t gsm_check_new_sms(int *new_index); gsm_status_t gsm_read_sms(int index, sms_data_t *sms); gsm_status_t gsm_delete_sms(int index); gsm_status_t gsm_send_sms(const char *phone, const char *message); // Утилиты bool gsm_is_authorized(const char *phone); void gsm_parse_command(const char *message, int *cmd, int *params, int *param_count); #endif // GSM_H

main/gsm.c

#include <stdio.h> #include <string.h> #include <stdlib.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/uart.h" #include "driver/gpio.h" #include "esp_log.h" #include "gsm.h" #include "relay.h" #include "storage.h" static const char *TAG = "GSM"; // Авторизованные номера (загружаются из NVS) static char authorized_numbers[5][20] = { "+79093436455", // Ваш номер "", "", "", "" }; // Буфер для ответов static char response_buffer[1024]; // Отправка AT команды и получение ответа static gsm_status_t gsm_send_command(const char *cmd, char *response, int timeout_ms) { uart_write_bytes(GSM_UART_PORT, cmd, strlen(cmd)); uart_write_bytes(GSM_UART_PORT, "\r\n", 2); int len = 0; int total_len = 0; TickType_t start_time = xTaskGetTickCount(); memset(response, 0, sizeof(response_buffer)); while ((xTaskGetTickCount() - start_time) < pdMS_TO_TICKS(timeout_ms)) { len = uart_read_bytes(GSM_UART_PORT, (uint8_t*)response + total_len, sizeof(response_buffer) - total_len - 1, pdMS_TO_TICKS(100)); if (len > 0) { total_len += len; if (strstr(response, "OK\r\n") || strstr(response, "ERROR\r\n")) { break; } } vTaskDelay(pdMS_TO_TICKS(10)); } return (total_len > 0) ? GSM_OK : GSM_TIMEOUT; } // Инициализация UART для GSM void gsm_init(void) { uart_config_t uart_config = { .baud_rate = GSM_BAUD_RATE, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_APB, }; uart_param_config(GSM_UART_PORT, &uart_config); uart_set_pin(GSM_UART_PORT, GSM_TX_PIN, GSM_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); uart_driver_install(GSM_UART_PORT, GSM_RX_BUF_SIZE * 2, GSM_TX_BUF_SIZE * 2, 0, NULL, 0); ESP_LOGI(TAG, "GSM UART initialized"); } // Включение питания модема gsm_status_t gsm_power_on(void) { gpio_set_direction(GSM_PWR_PIN, GPIO_MODE_OUTPUT); gpio_set_direction(GSM_RST_PIN, GPIO_MODE_OUTPUT); // Сигнал PWRKEY для включения SIM800L gpio_set_level(GSM_PWR_PIN, 1); vTaskDelay(pdMS_TO_TICKS(1000)); gpio_set_level(GSM_PWR_PIN, 0); vTaskDelay(pdMS_TO_TICKS(3000)); return GSM_OK; } // Проверка связи с модемом gsm_status_t gsm_check_communication(void) { char response[256]; for (int i = 0; i < 3; i++) { if (gsm_send_command("AT", response, 2000) == GSM_OK) { if (strstr(response, "OK")) { ESP_LOGI(TAG, "GSM modem OK"); return GSM_OK; } } vTaskDelay(pdMS_TO_TICKS(1000)); } ESP_LOGE(TAG, "GSM modem not responding"); return GSM_ERROR; } // Проверка нового SMS gsm_status_t gsm_check_new_sms(int *new_index) { char response[256]; *new_index = -1; if (gsm_send_command("AT+CMGL=\"REC UNREAD\",1", response, 5000) == GSM_OK) { // Ищем +CMGL: index,"REC UNREAD"... char *p = strstr(response, "+CMGL:"); if (p) { p += 6; while (*p == ' ') p++; *new_index = atoi(p); return GSM_OK; } } return GSM_ERROR; } // Чтение SMS gsm_status_t gsm_read_sms(int index, sms_data_t *sms) { char cmd[32]; char response[512]; snprintf(cmd, sizeof(cmd), "AT+CMGR=%d", index); if (gsm_send_command(cmd, response, 5000) != GSM_OK) { return GSM_ERROR; } // Парсинг номера: +CMGR: "REC READ","+79093436455",,"24/03/08,11:32:39+04" char *phone_start = strchr(response, '"'); if (!phone_start) return GSM_ERROR; phone_start = strchr(phone_start + 1, '"'); if (!phone_start) return GSM_ERROR; phone_start++; char *phone_end = strchr(phone_start, '"'); if (!phone_end) return GSM_ERROR; int len = phone_end - phone_start; if (len > 0 && len < (int)sizeof(sms->phone) - 1) { strncpy(sms->phone, phone_start, len); sms->phone[len] = '\0'; } // Ищем текст сообщения (после последней кавычки) char *text_start = strrchr(response, '"'); if (text_start) { text_start = strchr(text_start + 1, '\n'); if (text_start) { text_start++; char *text_end = strstr(text_start, "OK"); if (text_end) { len = text_end - text_start; if (len > 0 && len < (int)sizeof(sms->message) - 1) { strncpy(sms->message, text_start, len); sms->message[len] = '\0'; // Удаляем \r char *p; while ((p = strchr(sms->message, '\r')) != NULL) { *p = ' '; } } } } } sms->index = index; return GSM_OK; } // Удаление SMS gsm_status_t gsm_delete_sms(int index) { char cmd[32]; char response[32]; snprintf(cmd, sizeof(cmd), "AT+CMGD=%d", index); return gsm_send_command(cmd, response, 2000); } // Отправка SMS gsm_status_t gsm_send_sms(const char *phone, const char *message) { char cmd[64]; char response[64]; // Текстовый режим gsm_send_command("AT+CMGF=1", response, 1000); vTaskDelay(pdMS_TO_TICKS(100)); // Подготовка к отправке snprintf(cmd, sizeof(cmd), "AT+CMGS=\"%s\"", phone); if (gsm_send_command(cmd, response, 5000) != GSM_OK) { return GSM_ERROR; } // Отправка текста и Ctrl+Z if (strstr(response, ">")) { uart_write_bytes(GSM_UART_PORT, message, strlen(message)); uart_write_bytes(GSM_UART_PORT, "\x1A", 1); // Ctrl+Z vTaskDelay(pdMS_TO_TICKS(5000)); // Проверка ответа memset(response, 0, sizeof(response)); int len = uart_read_bytes(GSM_UART_PORT, (uint8_t*)response, sizeof(response) - 1, pdMS_TO_TICKS(1000)); if (len > 0 && strstr(response, "OK")) { ESP_LOGI(TAG, "SMS sent to %s", phone); return GSM_OK; } } return GSM_ERROR; } // Проверка авторизации bool gsm_is_authorized(const char *phone) { for (int i = 0; i < 5; i++) { if (authorized_numbers[i][0] == '\0') continue; // Сравниваем номера if (strcmp(phone, authorized_numbers[i]) == 0) { return true; } // Проверка формата 8 вместо +7 if (phone[0] == '8' && authorized_numbers[i][0] == '+') { char phone_with_7[20]; snprintf(phone_with_7, sizeof(phone_with_7), "+7%s", phone + 1); if (strcmp(phone_with_7, authorized_numbers[i]) == 0) { return true; } } } return false; } // Парсинг команды из SMS (формат: "команда параметр") void gsm_parse_command(const char *message, int *cmd, int *params, int *param_count) { *cmd = 0; *param_count = 0; // Парсим первую цифру - команда char *endptr; *cmd = strtol(message, &endptr, 10); // Если есть параметры if (endptr && *endptr != '\0') { char *p = (char*)endptr; while (*p != '\0' && *param_count < 5) { // Пропускаем не-цифры while (*p != '\0' && (*p < '0' || *p > '9')) p++; if (*p == '\0') break; // Читаем параметр params[*param_count] = strtol(p, &p, 10); (*param_count)++; } } }

main/main.c

#include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_log.h" #include "nvs_flash.h" #include "gsm.h" #include "relay.h" #include "wifi_ap.h" #include "web_server.h" #include "storage.h" static const char *TAG = "MAIN"; // Задача обработки GSM событий void gsm_task(void *pvParameters) { int last_sms_index = -1; int check_count = 0; while (1) { // Проверка новых SMS каждые 2 секунды vTaskDelay(pdMS_TO_TICKS(2000)); int new_index; if (gsm_check_new_sms(&new_index) == GSM_OK && new_index != last_sms_index) { if (new_index >= 0) { ESP_LOGI(TAG, "New SMS at index %d", new_index); sms_data_t sms; if (gsm_read_sms(new_index, &sms) == GSM_OK) { ESP_LOGI(TAG, "From: %s", sms.phone); ESP_LOGI(TAG, "Msg: %s", sms.message); // Проверка авторизации if (gsm_is_authorized(sms.phone)) { ESP_LOGI(TAG, "Authorized number, processing command"); // Парсинг команды int cmd, params[5] = {0}, param_count = 0; gsm_parse_command(sms.message, &cmd, params, &param_count); // Обработка команд if (cmd == 1) { // STATUS char status[128]; snprintf(status, sizeof(status), "Relay status: 1=%s 2=%s 3=%s 4=%s", relay_get_state(0) ? "ON" : "OFF", relay_get_state(1) ? "ON" : "OFF", relay_get_state(2) ? "ON" : "OFF", relay_get_state(3) ? "ON" : "OFF"); gsm_send_sms(sms.phone, status); } else if (cmd >= 11 && cmd <= 14) { // RELAY control (11=relay1, 12=relay2, etc) int relay = cmd - 11; if (param_count > 0) { relay_set(relay, params[0] == 1); char response[32]; snprintf(response, sizeof(response), "Relay%d %s", relay + 1, params[0] == 1 ? "ON" : "OFF"); gsm_send_sms(sms.phone, response); } } else if (cmd == 2) { // ALL ON/OFF if (param_count > 0) { relay_all_set(params[0] == 1); gsm_send_sms(sms.phone, params[0] == 1 ? "All ON" : "All OFF"); } } else if (cmd == 3) { // WiFi mode switch if (wifi_get_mode() == WIFI_MODE_AP) { wifi_set_mode(WIFI_MODE_STA); gsm_send_sms(sms.phone, "WiFi mode: Client"); } else { wifi_set_mode(WIFI_MODE_AP); gsm_send_sms(sms.phone, "WiFi mode: AP"); } wifi_ap_stop(); vTaskDelay(pdMS_TO_TICKS(1000)); wifi_ap_start(); } else { gsm_send_sms(sms.phone, "Unknown command"); } } else { ESP_LOGW(TAG, "Unauthorized number: %s", sms.phone); gsm_send_sms(sms.phone, "ERROR: Unauthorized"); } // Удаляем прочитанное SMS gsm_delete_sms(new_index); } last_sms_index = new_index; } } // Периодическая проверка сигнала check_count++; if (check_count >= 30) { // ~1 минута check_count = 0; ESP_LOGI(TAG, "System running: Relays %d%d%d%d", relay_get_state(0), relay_get_state(1), relay_get_state(2), relay_get_state(3)); } } } void app_main(void) { ESP_LOGI(TAG, "GSM Relay Controller starting..."); // Инициализация хранилища storage_init(); // Инициализация реле relay_init(); // Инициализация GSM gsm_init(); // Включение модема if (gsm_power_on() != GSM_OK) { ESP_LOGE(TAG, "Failed to power on GSM"); } // Проверка связи if (gsm_check_communication() != GSM_OK) { ESP_LOGE(TAG, "GSM communication failed"); } // Настройка WiFi AP wifi_ap_init(); wifi_set_mode(WIFI_MODE_AP); wifi_ap_start(); // Запуск веб-сервера web_server_start(); // Создание задачи обработки GSM xTaskCreate(gsm_task, "gsm_task", 4096, NULL, 5, NULL); ESP_LOGI(TAG, "System ready"); }

Поделка 3:

Так как использую программу QUIK для торговли на фондовом рынке, то решил автоматизировать двухфакторную аутентификацию.

Для тех, кто не знает, что это такое, поясняю.

При запуске торгового терминала QUIK, необходимо сначала ввести пароль в соответствующее окно. После этого программа подключится к серверу брокера и запросит у вас код , который получите на телефон(смартфон) от брокера. Необходимо этот код вручную ввести в окно программы.

Так как у меня есть модем, который я сделал в поделки 1, то почему бы не использовать его для автоматизации старта QUIK.

Решил написать скрипт на AutoIt.

Предварительно узнать у DS о возможных вариантах решения.

e7dc38979846f38e6baaf2fe186db88e.png

Ответ DS:

98deffb975776adad4a363f7bfb1fb1b.png

Поделка фактически состоит из двух задач.

Первая задача связана с запуском QUIK и автоматическим вводам пароля и кода в соответствующие окна. При ее решении пароль и код разместил в файлах. Так как задача простая, то на ее решение ушло примерно 20 минут.

Результирующий скрипт:

; ======================================================================= ; Скрипт автоматического ввода пароля и кода для QUIK ; автор nikolz & DS ; Версия: 3.0 (с поддержкой параметров и настроек) ; ======================================================================= ; --- Настройки --- $sQuikPath = "C:\QUIK\info.exe" $sQuikParams = "" ; Сюда добавьте параметры из ярлыка, если есть $sQuikWorkingDir = "C:\QUIK" ; Рабочая папка (обычно там же, где info.exe) $sPasswordFile = @ScriptDir & "\password.txt" $sCodeFile = @ScriptDir & "\code.txt" ; --- Чтение пароля и кода --- Local $sPassword = FileReadLine($sPasswordFile) If @error Then MsgBox(16, "Ошибка", "Не удалось прочитать файл пароля") Exit EndIf Local $sCode = FileReadLine($sCodeFile) If @error Then MsgBox(16, "Ошибка", "Не удалось прочитать файл с кодом") Exit EndIf ; --- Запуск QUIK с рабочей папкой --- ; Это ключевой момент! QUIK должен быть запущен из правильной директории ; чтобы увидеть все сохраненные настройки Run('"' & $sQuikPath & '" ' & $sQuikParams, $sQuikWorkingDir) ; --- Ждем появления окна пароля (с таймаутом) --- ; Окно может появиться не сразу, т.к. сначала грузятся настройки Local $hFirstWindow = 0 Local $iMaxWait = 30 ; ждем до 30 секунд For $i = 1 To $iMaxWait $hFirstWindow = WinGetHandle("[CLASS:#32770]") If $hFirstWindow Then Local $sTitle = WinGetTitle($hFirstWindow) If StringInStr($sTitle, "пароль") Or StringInStr($sTitle, "Идентификация") Then ExitLoop EndIf EndIf Sleep(1000) Next If Not $hFirstWindow Then MsgBox(16, "Ошибка", "Окно пароля не появилось за " & $iMaxWait & " секунд") Exit EndIf ; --- Ввод пароля --- WinActivate($hFirstWindow) Sleep(500) ControlSend($hFirstWindow, "", "[CLASS:Edit; INSTANCE:2]", $sPassword) Sleep(300) ControlSend($hFirstWindow, "", "", "{ENTER}") ; --- Ждем окно для кода --- Sleep(2000) ; Даем время на обработку пароля Local $hSecondWindow = WinWait("[CLASS:QuikGUI]", "", 20) If Not $hSecondWindow Then ; Если не нашли по классу, ищем по заголовку $hSecondWindow = WinWait("Введите код", "", 10) EndIf If $hSecondWindow Then WinActivate($hSecondWindow) Sleep(500) ControlSend($hSecondWindow, "", "[CLASS:Edit; INSTANCE:1]", $sCode) Sleep(300) ControlSend($hSecondWindow, "", "", "{ENTER}") MsgBox(64, "Успех", "Авторизация выполнена!") Else MsgBox(48, "Предупреждение", "Окно кода не найдено, но пароль введен") EndIf Exit

Вторая задача - прием кода из SMS от брокера

Решение этой задачи находится в стадии отладки, поэтому скрипт лишь для примера:

AutoIt-скрипт для работы с модемом

; ======================================================================= ; Модуль получения кода с GSM-модема (AutoIt версия) ; Версия: 1.0 ; Основан на ATCmd.au3 UDF ; ======================================================================= #include <ATCmd.au3> #include <FileConstants.au3> #include <MsgBoxConstants.au3> #include <StringConstants.au3> ; --- Параметры командной строки --- ; Формат: script.exe "ComPort=COM3;BaudRate=9600;SenderNumber=+79...;Timeout=60" "output.txt" If $CmdLine[0] < 2 Then ConsoleWrite("Использование: " & @ScriptName & ' "параметры" "выходной_файл"' & @CRLF) Exit 1 EndIf Local $sSettings = $CmdLine[1] Local $sOutputFile = $CmdLine[2] Local $sLogFile = @ScriptDir & "\modem_autoit.log" ; --- Очищаем лог при запуске --- FileDelete($sLogFile) LogWrite("=== Запуск AutoIt-модуля модема ===") ; --- Парсинг параметров --- Local $aSettings = StringSplit($sSettings, ";") Local $sComPort = "COM3" Local $iBaudRate = 9600 Local $sSenderNumber = "" Local $iTimeout = 60 Local $sSimPIN = "" For $i = 1 To $aSettings[0] Local $aPair = StringSplit($aSettings[$i], "=") If $aPair[0] >= 2 Then Switch $aPair[1] Case "ComPort" $sComPort = $aPair[2] Case "BaudRate" $iBaudRate = Number($aPair[2]) Case "SenderNumber" $sSenderNumber = $aPair[2] Case "Timeout" $iTimeout = Number($aPair[2]) Case "SimPIN" $sSimPIN = $aPair[2] EndSwitch EndIf Next LogWrite("Порт: " & $sComPort) LogWrite("Скорость: " & $iBaudRate) LogWrite("Номер отправителя: " & $sSenderNumber) LogWrite("Таймаут: " & $iTimeout & " сек") ; --- Главная функция получения кода --- Local $sCode = GetCodeFromModem() Local $iResult = 1 ; 0 - успех, 1 - ошибка If $sCode <> "" Then ; Сохраняем код в выходной файл Local $hFile = FileOpen($sOutputFile, $FO_OVERWRITE) If $hFile <> -1 Then FileWrite($hFile, $sCode) FileClose($hFile) LogWrite("Код сохранен в файл: " & $sCode) $iResult = 0 Else LogWrite("ОШИБКА: Не удалось создать выходной файл") EndIf Else LogWrite("ОШИБКА: Код не получен") EndIf LogWrite("=== Завершение работы ===") Exit $iResult ; ============================================================================== ; ФУНКЦИИ ; ============================================================================== ; #FUNCTION# =================================================================== ; Name ..........: GetCodeFromModem ; Description ...: Получает код подтверждения из SMS с модема ; Syntax ........: GetCodeFromModem() ; Return values .: Успех - строка с кодом, Неудача - пустая строка ; ============================================================================== Func GetCodeFromModem() Local $sCode = "" Local $hModem = 0 Local $iMaxAttempts = Int($iTimeout / 2) ; Проверяем каждые 2 секунды LogWrite("Инициализация модема на порту " & $sComPort) ; Открываем соединение с модемом $hModem = _ATCmd_Open($sComPort, $iBaudRate) If @error Then LogWrite("ОШИБКА: Не удалось открыть COM-порт " & $sComPort) Return "" EndIf LogWrite("Соединение с модемом установлено") ; Проверяем наличие SIM-карты If Not _ATCmd_IsSIMInserted($hModem) Then LogWrite("ОШИБКА: SIM-карта не обнаружена") _ATCmd_Close($hModem) Return "" EndIf LogWrite("SIM-карта обнаружена") ; Проверяем, требуется ли ввод PIN-кода If _ATCmd_IsPINRequired($hModem) Then LogWrite("Требуется PIN-код SIM-карты") If $sSimPIN = "" Then LogWrite("ОШИБКА: PIN не указан в настройках") _ATCmd_Close($hModem) Return "" EndIf _ATCmd_SetPIN($hModem, $sSimPIN) Sleep(2000) LogWrite("PIN введен") EndIf ; Проверяем регистрацию в сети If Not _ATCmd_WaitForNetwork($hModem, 30) Then LogWrite("ОШИБКА: Модем не зарегистрирован в сети") _ATCmd_Close($hModem) Return "" EndIf LogWrite("Модем зарегистрирован в сети") ; Получаем уровень сигнала для информации Local $iCSQ = _ATCmd_GetCSQ($hModem) If $iCSQ <> -1 Then LogWrite("Уровень сигнала: " & $iCSQ) EndIf ; Настраиваем текстовый режим для SMS _ATCmd_SMS_SetTextMode($hModem) ; --- Цикл ожидания SMS --- LogWrite("Ожидание SMS с кодом от " & $sSenderNumber & "...") For $i = 1 To $iMaxAttempts Sleep(2000) LogWrite("Проверка SMS (попытка " & $i & " из " & $iMaxAttempts & ")") ; Получаем список всех SMS Local $aSMSList = _ATCmd_SMS_ListMessages($hModem, "ALL") If Not @error And IsArray($aSMSList) Then For $j = 0 To UBound($aSMSList) - 1 ; Формат: [0] - индекс, [1] - статус, [2] - отправитель, [3] - дата, [4] - текст Local $sSender = $aSMSList[$j][2] Local $sText = $aSMSList[$j][4] Local $sIndex = $aSMSList[$j][0] LogWrite(" Найдено SMS от: " & $sSender) ; Проверяем отправителя (ищем номер брокера) If StringInStr($sSender, $sSenderNumber) Then LogWrite(" Совпадение отправителя!") $sCode = ExtractCodeFromText($sText) If $sCode <> "" Then LogWrite(" Код найден: " & $sCode) ; Удаляем прочитанное SMS _ATCmd_SMS_DeleteMessage($hModem, $sIndex) LogWrite(" SMS удалено") ExitLoop 2 Else LogWrite(" Внимание: код не найден в тексте: " & $sText) EndIf EndIf Next Else LogWrite(" Новых SMS не найдено") EndIf Next _ATCmd_Close($hModem) LogWrite("Соединение с модемом закрыто") Return $sCode EndFunc ; #FUNCTION# =================================================================== ; Name ..........: ExtractCodeFromText ; Description ...: Извлекает цифровой код из текста SMS ; Syntax ........: ExtractCodeFromText($sText) ; Parameters ....: $sText - текст SMS ; Return values .: строка с кодом или пустая строка ; ============================================================================== Func ExtractCodeFromText($sText) ; Ищем последовательность из 4-8 цифр Local $aMatches = StringRegExp($sText, "(\d{4,8})", $STR_REGEXPARRAYMATCH) If Not @error And UBound($aMatches) > 0 Then Local $sCode = $aMatches[0] ; Дополнительная проверка: код обычно не начинается с 0 If StringLeft($sCode, 1) <> "0" Then Return $sCode EndIf EndIf ; Альтернативный поиск - если код в формате "код: 123456" или "code: 123456" $aMatches = StringRegExp($sText, "(?i)(?:код|code|pass|пароль)[:\s]*(\d{4,8})", $STR_REGEXPARRAYMATCH) If Not @error And UBound($aMatches) > 0 Then Return $aMatches[0] EndIf Return "" EndFunc ; #FUNCTION# =================================================================== ; Name ..........: LogWrite ; Description ...: Записывает сообщение в лог-файл ; ============================================================================== Func LogWrite($sMessage) FileWriteLine($sLogFile, @HOUR & ":" & @MIN & ":" & @SEC & " - " & $sMessage) EndFunc

Источник

Возможности рынка
Логотип Ucan fix life in1day
Ucan fix life in1day Курс (1)
$0.0004362
$0.0004362$0.0004362
+6.44%
USD
График цены Ucan fix life in1day (1) в реальном времени
Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.