Giới thiệu
Bài viết này là phần thứ hai trong chuỗi bài viết về việc sử dụng ESP-Now trong Micropython để triển khai mạng master-slave dựa trên ESP32. Phần đầu tiên đã mô tả lớp giao tiếp mạng chính, trong khi phần ba sẽ trình bày cách thực hiện cập nhật OTA đáng tin cậy.
Trong bối cảnh mạng WiFi, băng tần 2.4GHz đang trở nên chật chội. Mặc dù phần lớn lưu lượng truy cập nặng hiện nay chuyển sang băng tần 5GHz, nhưng vẫn còn rất nhiều thiết bị cạnh tranh băng thông, đặc biệt là trong lĩnh vực IoT. Các bộ định tuyến hiện đại giải quyết vấn đề này bằng cách chuyển kênh; chúng theo dõi mạng định kỳ để xem kênh nào ít bận rộn nhất và chuyển sang kênh đó. Điều này có thể xảy ra mỗi giờ hoặc hai giờ một lần, nhưng tôi hy vọng rằng các bộ định tuyến đủ thông minh để nhận ra rằng việc liên tục chuyển kênh sẽ không hiệu quả và có thể gây khó chịu. Mặc dù các hệ điều hành máy tính có thể xử lý việc chuyển kênh một cách mượt mà, nhưng các thiết bị đơn giản hơn không có firmware để làm điều này, để lại cho chúng ta phải tự giải quyết trong các ứng dụng của mình.
Phương pháp đơn giản và được khuyến nghị nhất là hạn chế bộ định tuyến chỉ sử dụng một kênh, nhưng điều này chỉ khả thi nếu bạn có quyền kiểm soát bộ định tuyến. Trong hệ thống master-slave của tôi, tôi không đưa ra giả định như vậy; tôi quyết định đối mặt với vấn đề và tìm ra giải pháp.
Giới thiệu về ESP32-C3
Trước khi tiếp tục, tôi nên đề cập rằng mã này đã được phát triển cho ESP32-C3, là một thiết bị cắt giảm chỉ có một hệ thống radio đơn. Điều này có nghĩa là nó chỉ có thể xử lý một kênh WiFi. Các biến thể khác có hai hệ thống và có thể hoạt động trên hai kênh độc lập. Mã ở đây và trong phần còn lại của chuỗi được xây dựng để xử lý môi trường bị hạn chế hơn. Trong quá trình phát triển, tôi nhận thấy rằng nhiều bài viết được công bố về ESP-Now trên ESP32 không hoạt động trên biến thể C3 vì chúng phụ thuộc vào việc có radio đôi.
Trên thực tế, với các thiết bị radio đôi, có thể không cần chuyển kênh. Toàn bộ hệ thống ESP-Now có thể hoạt động độc lập với các giao diện AP và STA. Tuy nhiên, biến thể C3 chiếm một ngách mà trước đây được lấp đầy bởi ESP8266; chi phí thấp và kích thước rất nhỏ, như được thể hiện bởi các thiết bị như ESP32-C3 Super Mini và ESP01-C3, cho phép thực hiện đầy đủ Micropython để chạy nhiều ứng dụng tiết kiệm chi phí và không gian. Dưới đây là một hình ảnh của ESP32-C3 Super Mini (có ăng-ten) và ESP01-C3.
Mã nguồn
Nếu bạn vẫn ở đây, hãy xem mã dưới đây:
python
import asyncio,machine,time
class Channels():
def __init__(self, espComms):
print('Bắt đầu Channels')
self.espComms = espComms
self.config = espComms.config
self.channels = [1, 6, 11]
self.myMaster = self.config.getMyMaster()
if self.config.isMaster():
self.ssid = self.config.getSSID()
self.password = self.config.getPassword()
asyncio.create_task(self.checkRouterChannel())
self.resetCounter()
def setupSlaveTasks(self):
asyncio.create_task(self.findMyMaster())
asyncio.create_task(self.countMissingMessages())
def resetCounter(self):
print('Đặt lại bộ đếm')
self.idleCount = 0
async def findMyMaster(self):
if await self.ping(): return
self.hopToNextChannel()
asyncio.get_event_loop().stop()
machine.reset()
async def ping(self):
peer = bytes.fromhex(self.myMaster)
self.espComms.espSend(peer, 'ping')
_, msg = self.espComms.e.recv(1000)
print('Phản hồi ping từ', self.myMaster, ':', msg)
if msg:
print('Tìm thấy master trên kênh', self.espComms.channel)
return True
return False
async def countMissingMessages(self):
print('Đếm tin nhắn bị mất')
espComms = self.espComms
ap = espComms.ap
e = espComms.e
self.idleCount = 0
while True:
await asyncio.sleep(1)
self.idleCount += 1
limit = 30
if self.idleCount > limit:
print('Không có tin nhắn trong 30 giây')
# Thử lại kênh hiện tại
if await self.ping():
self.idleCount = 0
continue
self.hopToNextChannel()
channel = self.hopToNextChannel()
asyncio.get_event_loop().stop()
machine.reset()
def hopToNextChannel(self):
index = -1
for n, value in enumerate(self.channels):
if value == self.espComms.channel:
self.espComms.channel = self.channels[(n + 1) % len(self.channels)]
index = n
break
if index == -1: self.espComms.channel = self.channels[0]
self.config.setChannel(self.espComms.channel)
async def checkRouterChannel(self):
print('Kiểm tra kênh router')
while True:
await asyncio.sleep(300)
sta = self.espComms.sta
sta.disconnect()
time.sleep(1)
print('Kết nối lại...', end='')
sta.connect(self.ssid, self.password)
while not sta.isconnected():
time.sleep(1)
print('.', end='')
channel = sta.config('channel')
if channel != self.espComms.channel:
print(' Router đã thay đổi kênh từ', self.espComms.channel, 'sang', channel)
asyncio.get_event_loop().stop()
machine.reset()
print(' Không thay đổi kênh')
self.espComms.restartESPNow()
Trong hệ thống này, lớp ESPComms được mô tả trước đó quản lý tất cả các chức năng mạng ngoại trừ việc chuyển kênh, điều này được thực hiện bởi lớp Channels. Hệ thống cũng sử dụng nặng lớp Config, quản lý dữ liệu hệ thống như SSID và mật khẩu, cách sử dụng chân I/O và v.v. Nó cũng hoạt động như một điểm định tuyến trung tâm cho hầu hết các giao tiếp giữa các mô-đun khác, có các hàm getter và setter chỉ đơn giản chuyển các cuộc gọi đến lớp thích hợp. Channels là một ngoại lệ vì nó gắn bó khá chặt chẽ với ESPComms, vì vậy hầu hết các cuộc gọi hàm trong lớp này đều đi thẳng đến đó.
Các phương thức quan trọng
__init__()
Phương thức này định nghĩa các kênh sẽ được sử dụng. Mặc dù có khoảng 14 kênh khác nhau trong băng tần 2.4GHz, nhưng chúng đủ gần nhau để các kênh liền kề chồng chéo khá nghiêm trọng. Thực tế, chỉ có ba kênh hoàn toàn không chồng chéo: 1, 6 và 11, vì vậy không có gì ngạc nhiên khi hầu hết các bộ định tuyến chuyển đổi giữa ba kênh này. Hàm này định nghĩa những kênh này. Công việc khác của nó là khởi động một công việc bất đồng bộ (checkRouterChannel()) để theo dõi hệ thống và phát hiện khi nào kênh đã thay đổi. Điều này cần thiết khi hệ thống hoạt động như thiết bị master, nếu không việc thay đổi kênh có thể không được phát hiện.
setupSlaveTasks()
Phương thức này thường được gọi sau trong quá trình khởi tạo, sau khi các thứ khác đã ổn định.
resetCounter()
Lớp này liên tục tăng một bộ đếm, và khi nó đạt đến giới hạn đã định trước, hành động phải được thực hiện. Hàm này được gọi từ ESPComms mỗi khi một tin nhắn được nhận, để ngăn chặn hành động được kích hoạt.
findMyMaster()
Khi một thiết bị slave khởi động, nó không có cách nào để biết kênh mà thiết bị master đang sử dụng. Vì vậy, nó gọi hàm này, phát đi một thông điệp 'ping' trên kênh đã được lưu trong lần chạy trước. Nếu nó nhận được phản hồi, nó biết rằng nó đã chọn đúng kênh. Nếu không, nó chuyển sang kênh tiếp theo, lưu lại số kênh mới, và tự khởi động lại. Chỉ khi nhận được phản hồi từ ping, nó mới có thể thoát khỏi vòng lặp vô tận này và bắt đầu hoạt động bình thường.
countMissingMessages()
Hàm này chạy liên tục, tăng bộ đếm và kiểm tra xem nó có vượt quá giới hạn đã đặt không. Nếu có, nó sẽ ping master trước để xem liệu thiết bị vẫn hoạt động và trên cùng một kênh hay không. Có thể có lý do hoàn toàn hợp lệ cho việc thiếu tin nhắn, chẳng hạn như hệ thống đang ở chế độ bảo trì, nơi các hoạt động bình thường bị tạm ngưng. Nếu ping không thành công, thiết bị sẽ khởi động lại và quay lại chu trình findMyMaster() ở trên.
ping()
Hàm này gửi một thông điệp 'ping' đến thiết bị master và chờ phản hồi, trả lại True nếu nhận được phản hồi trong vòng một giây. Để tiết kiệm bạn khỏi việc phải tìm kiếm, hàm espSend() trong ESPComms là:
python
def espSend(self, peer, msg):
if self.addPeer(peer):
try: self.e.send(peer, msg)
except Exception as ex: print('espSend:', ex)
countMissingMessages()
Hàm này liên tục tăng idleCount và kiểm tra xem nó có đạt đến giới hạn đã định không. Nếu có, các tin nhắn đã bị mất, vì vậy nó sẽ chuyển sang kênh tiếp theo và đặt lại.
checkRouterChannel()
Hàm này được gọi định kỳ - chẳng hạn như mỗi 5 phút - bởi thiết bị master để đảm bảo nó vẫn trên cùng một kênh với bộ định tuyến. Để làm điều này, giao diện STA phải được vô hiệu hóa và kết nối lại. Nếu kênh đã thay đổi, cần phải khởi động lại, nếu không ESP-Now có thể được khởi động lại.
Thực tiễn tốt nhất
- Kiểm tra định kỳ: Đảm bảo kiểm tra định kỳ kênh của bộ định tuyến để tối ưu hóa băng thông.
- Giới hạn kênh: Nếu có thể, hạn chế bộ định tuyến sử dụng một kênh duy nhất để tránh phức tạp.
Những cạm bẫy thường gặp
- Quá nhiều kênh: Việc sử dụng quá nhiều kênh có thể dẫn đến việc thiết bị không kết nối được với master.
- Thời gian chờ: Thiết lập thời gian chờ quá thấp có thể dẫn đến việc thiết bị tự khởi động lại không cần thiết.
Mẹo hiệu suất
- Tối ưu hóa mã: Đảm bảo mã của bạn được tối ưu hóa để giảm thiểu thời gian xử lý.
- Giám sát hiệu năng: Sử dụng các công cụ giám sát để theo dõi hiệu suất của mạng.
Xử lý sự cố
- Không nhận được phản hồi: Nếu thiết bị không nhận được phản hồi từ master, hãy kiểm tra lại kênh và tín hiệu.
- Kết nối không ổn định: Nếu kết nối không ổn định, hãy thử sử dụng một kênh khác và khởi động lại thiết bị.
Kết luận
Thông qua bài viết này, bạn đã tìm hiểu về cách triển khai kỹ thuật chuyển kênh trên ESP32-C3 với ESP-Now. Hãy thử áp dụng những kiến thức này vào dự án của bạn và đảm bảo tối ưu hóa hiệu suất mạng của bạn. Nếu bạn có bất kỳ câu hỏi nào, đừng ngần ngại để lại câu hỏi trong phần bình luận!
Câu hỏi thường gặp (FAQ)
ESP-Now là gì?
ESP-Now là một giao thức truyền thông không dây cho phép các thiết bị ESP32 giao tiếp với nhau mà không cần kết nối Internet.
Tại sao cần chuyển kênh?
Việc chuyển kênh giúp tối ưu hóa băng thông và giảm thiểu xung đột giữa các thiết bị trong mạng.
Có cần sử dụng kênh khác nhau không?
Điều này phụ thuộc vào cấu hình mạng của bạn. Nếu bạn gặp vấn đề về tín hiệu, hãy thử sử dụng các kênh khác nhau.
Hãy bắt đầu thực hiện và chia sẻ những trải nghiệm của bạn với cộng đồng phát triển Việt Nam!