MicroPython ESP8266: OSError -40 с Telegram API и urequests
Почему urequests на MicroPython ESP8266 выдает OSError -40 при запросах к Telegram API? Разбор причин (SNI, длина URL) и проверенные решения для Telegram бота.
Почему библиотека urequests в MicroPython на ESP8266 выдает ошибку OSError: -40 при GET-запросе к API Telegram, но корректно работает с API OpenWeatherMap? В чем заключается разница в обработке этих запросов и как исправить проблему с Telegram API?
Контекст и код:
import urequests
# Запрос к Telegram API вызывает OSError: -40
url = f"https://api.telegram.org/{token}/sendMessage?chat_id={chat_id}&text=txt1"
# Запрос к OpenWeatherMap API работает корректно
# url = f"https://api.openweathermap.org/data/2.5/weather?lat=42.98&lon=47.47&appid={token}"
response = urequests.get(url)
content = response.text
print(content)
Ошибка OSError: -40 при GET к Telegram API вызвана ограничениями SSL/ TLS-стека в MicroPython на ESP8266 (отсутствие SNI и особенности ussl/usocket) и дополнительными проблемами с очень длинным URL (токен + query). Запрос к OpenWeatherMap проходит, потому что тот endpoint не требует SNI/специфичной обработки TLS в той же конфигурации; простое и надёжное решение — переводить вызов Telegram на POST (urequests.post) или перейти на прошивку/платформу с поддержкой SNI. Ниже — разбор причин, варианты исправления и проверенные примеры кода.
Содержание
- Почему urequests в MicroPython на ESP8266 выдаёт OSError: -40
- В чём разница между Telegram API и OpenWeatherMap для MicroPython/SSL
- Как исправить проблему с Telegram API на ESP8266 в MicroPython
- Рабочие примеры кода: POST и TLS-обход (ESP8266 / ESP32)
- Чек‑лист отладки и быстрые проверки
- Источники
- Заключение
Почему urequests в MicroPython на ESP8266 выдаёт OSError: -40
urequests в MicroPython поверх простых модулей использует usocket и ussl. На ESP8266 реализация TLS/SSL урезана: часто отсутствует поддержка SNI (Server Name Indication) и некоторые опции SSL-контекста. Если сервер требует SNI, TLS‑handshake может быть разорван со стороны сервера прямо во время handshake — в MicroPython это проявляется как OSError: -40 (Connection reset by peer). Об этом подробно описано в статье с тестами и примерами для ESP8266/ESP32: https://techtutorialsx.com/2017/06/11/esp32-esp8266-micropython-http-get-requests/ и в обсуждениях багов MicroPython: https://github.com/micropython/micropython/issues/16561.
Ключевые моменты:
- SNI — это TLS‑расширение, которое сообщает серверу имя хоста ещё на этапе ClientHello, и многие хосты (виртуальные хосты, CDN, балансировщики) используют его, чтобы вернуть корректный сертификат. Без SNI сервер может закрыть соединение.
- OSError: -40 сигнализирует, что сервер закрыл соединение на уровне TCP/TLS во время handshake, поэтому HTTP‑ответа вы не увидите.
- urequests/ussl на ESP8266 часто не поддерживают передачу имени хоста для SNI, в то время как другие сервисы (например, OpenWeatherMap) могут принимать соединение и без SNI.
В чём разница между Telegram API и OpenWeatherMap для MicroPython/SSL
Почему один API работает, а другой — нет?
- Telegram API (api.telegram.org) в практических тестах требовал SNI/особой TLS‑обработки в части серверной конфигурации. Если в момент TLS‑handshake не передано имя хоста, сервер может отвергнуть соединение — результат: OSError: -40. Это замечено в практических статьях с примерами для ESP8266: https://techtutorialsx.com/2017/06/11/esp32-esp8266-micropython-http-get-requests/.
- OpenWeatherMap в ряде случаев пропускает соединение без SNI (или его конфигурация позволяет успешно завершить handshake даже при ограниченной реализации ussl), поэтому тот же код GET на том же модуле работает.
Дополнительный фактор — длина URL: Telegram-метод sendMessage при GET формирует очень длинный URL (токен ≈45 символов + query string). На ESP8266 сочетание длинного запроса и ограничений стеков (буферов/реализаций TLS) в некоторых тестах приводило к сбросу соединения; перевод на POST убирает длинную query-строку и решает проблему с практической стороны (подробности: https://techtutorialsx.com/2017/06/18/esp32-esp8266-micropython-http-post-requests/).
Как исправить проблему с Telegram API на ESP8266 в MicroPython
Перечень вариантов по убыванию практичности:
-
Перейти на POST (самое простое и надёжное)
- Используйте
urequests.postи передавайтеchat_idиtextв теле запроса. Это устраняет длинную GET‑query и в практике на ESP8266 решает проблему OSError: -40. Рабочий пример и объяснение в статье: https://techtutorialsx.com/2017/06/18/esp32-esp8266-micropython-http-post-requests/.
- Используйте
-
Обновить прошивку / использовать плату с лучшей поддержкой TLS (ESP32)
- На ESP32 поддержка SNI/более полного SSL чаще доступна; если вы можете перейти на ESP32 или поставить сборку MicroPython с расширенным
ussl, это решит проблему TLS без изменений в коде. См. заметки и примеры для ESP32: https://randomnerdtutorials.com/esp32-https-requests/.
- На ESP32 поддержка SNI/более полного SSL чаще доступна; если вы можете перейти на ESP32 или поставить сборку MicroPython с расширенным
-
Попытаться задать SNI вручную (только для платформ/сборок, где это поддерживается)
- В некоторых сборках есть
ussl.wrap_socket(..., server_hostname=...)илиSSLContextсset_server_hostname. На стандартном ESP8266 MicroPython эта опция часто недоступна. Если у вас есть такая возможность — используйте её, но будьте готовы к тому, что на большинстве ESP8266 это не сработает. Примерная идея показана в руководствах по TLS для ESP32: https://randomnerdtutorials.com/esp32-https-requests/.
- В некоторых сборках есть
-
Использовать прокси / внешний сервер
- Если изменить устройство нельзя, можно отправлять запросы к своему лёгкому HTTPS‑прокси (на VPS/Heroku), который уже имеет нормальный TLS‑стек, и тот проксирует запросы к api.telegram.org.
-
Проверить URL и формат запроса
- Убедитесь, что URL корректен: путь в Telegram должен быть вида
https://api.telegram.org/bot<token>/sendMessage(обратите внимание на префиксbot). Неправильный путь даст HTTP‑ошибку, но не OSError — всё равно стоит проверить ещё на этапе отладки.
- Убедитесь, что URL корректен: путь в Telegram должен быть вида
-
Системные рекомендации
- Всегда закрывайте ответы:
resp.close()— это освобождает память на ограниченных контроллерах. - Снижайте нагрузку на heap: избегайте больших строк/несколько больших объектов одновременно.
- Всегда закрывайте ответы:
Рабочие примеры кода: POST и TLS-обход (ESP8266 / ESP32)
- Рекомендуемый способ (POST) — обычно решает проблему на ESP8266:
import urequests
url = "https://api.telegram.org/bot{}/sendMessage".format(token)
payload = {'chat_id': chat_id, 'text': 'Hello from ESP8266'}
try:
resp = urequests.post(url, data=payload)
print(resp.status_code)
print(resp.text)
resp.close()
except OSError as e:
print("OSError:", e)
Если хотите отправлять JSON:
import ujson, urequests
url = "https://api.telegram.org/bot{}/sendMessage".format(token)
data = ujson.dumps({'chat_id': chat_id, 'text': 'Hi'})
headers = {'Content-Type': 'application/json'}
resp = urequests.post(url, data=data, headers=headers)
print(resp.status_code)
print(resp.text)
resp.close()
- Попытка задать SNI вручную (может работать на ESP32/специфичных сборках; на большинстве ESP8266 — нет):
import usocket, ussl
addr = usocket.getaddrinfo("api.telegram.org", 443)[0][-1]
s = usocket.socket()
s.connect(addr)
try:
# Многие сборки MicroPython на ESP8266 НЕ поддерживают server_hostname
ssl_sock = ussl.wrap_socket(s, server_hostname="api.telegram.org")
req = "GET /bot{}/sendMessage?chat_id={}&text=hi HTTP/1.1\r\nHost: api.telegram.org\r\nConnection: close\r\n\r\n".format(token, chat_id)
ssl_sock.write(req.encode())
data = ssl_sock.read(4096)
print(data)
ssl_sock.close()
except Exception as ex:
print("SSL wrap failed:", ex)
s.close()
- На ESP32 с поддержкой SSLContext (возможность варьируется в зависимости от сборки):
# Встроенная поддержка SSLContext в MicroPython на ESP32 может отличаться от CPython.
# Если доступно, можно создавать контекст и указывать имя сервера — это даёт SNI.
# Также urequests в некоторых реализациях принимает параметр ssl_context.
Эти варианты и ограничения обсуждаются в статьях и примерах: https://techtutorialsx.com/2017/06/18/esp32-esp8266-micropython-http-post-requests/ и https://randomnerdtutorials.com/esp32-https-requests/.
Чек‑лист отладки и быстрые проверки
- Убедитесь, что общий HTTPS работает:
- Попробуйте
urequests.getк OpenWeatherMap (как в вашем примере) — если работает, сеть и базовая TLS связность есть.
- Попробуйте
- Проверьте правильность URL Telegram:
- Формат должен быть
https://api.telegram.org/bot<token>/sendMessage.
- Формат должен быть
- Попробуйте POST (см. пример выше). Если POST работает — проблема в сочетании GET+TLS/серверной конфигурации на стороне Telegram при вашей сборке.
- Обновите MicroPython до последней стабильной/nightly сборки для вашей платы — возможно улучшена поддержка ussl.
- Попробуйте на ESP32 (если есть) — там поддержка SNI/SSL полнофункциональнее.
- Если POST и обновление не помогают — используйте простой прокси или внешний сервер для отправки сообщений.
- Проверьте, что бот добавлен в чат/группу и что
chat_idв правильном формате (для групп часто нужен ведущий-): обсуждение формата chat_id — https://community.make.com/t/telegram-bot-chatid-value-must-not-be-empty-and-400-bad-request-chat-not-found/68872. - Всегда закрывайте
resp.close()и минимизируйте копирование больших строк, чтобы не исчерпать RAM.
Источники
- Статья о проблемах с GET и SNI на ESP32/ESP8266: https://techtutorialsx.com/2017/06/11/esp32-esp8266-micropython-http-get-requests/
- Решение через POST и описание работы urequests/usocket/ussl: https://techtutorialsx.com/2017/06/18/esp32-esp8266-micropython-http-post-requests/
- Обсуждение chat_id и ошибок Telegram (формат/доступ бота): https://community.make.com/t/telegram-bot-chatid-value-must-not-be-empty-and-400-bad-request-chat-not-found/68872
- Технические заметки о проблемах сетевого/SSL стека MicroPython: https://github.com/micropython/micropython/issues/16561
- Примеры и советы по HTTPS для ESP32 (альтернативный подход с server_hostname): https://randomnerdtutorials.com/esp32-https-requests/
Заключение
Вкратце: OSError: -40 в MicroPython на ESP8266 при GET к Telegram вызван ограничениями TLS/SSL (отсутствие SNI и связанные с этим handshake‑сбросы) и усугубляется длинной GET‑строкой с токеном. OpenWeatherMap в вашей конфигурации не вызывает такие требования, поэтому работает. Самый простой и проверенный способ — перейти на urequests.post (отправлять chat_id и text в теле), либо обновить платформу/прошивку или использовать ESP32/сервер‑прокси для полноценной поддержки SNI.