Một số mẹo cho việc phát triển ứng dụng hệ thống nhúng

Trong quá trình phát triển ứng dụng cho hệ thống nhúng, do sự hạn chế của thiết bị, đôi khi có những việc lắt nhắt làm tốn mớ thời gian. Sau đây mình liệt kê một số mẹo để đi tắt, giúp tiết kiệm thời gian cho công việc. Các hướng dẫn này chỉ dành cho hệ điều hành Linux (như Ubuntu).

1. Chia sẻ Internet từ laptop cho máy tính nhúng

Đây là tình huống mà bạn đang phát triển ứng dụng cho máy tính mini (NUC, Raspberry Pi, Beagle Bone...) và chủ yếu dùng mạng dây (chưa setup wifi hoặc máy đó không có card wifi). Đôi lúc bạn cần phải xách nó đi đâu đó (để trình diễn demo, để cài đặt lại chẳng hạn) mà chỗ đó không có router để cấp mạng, bạn có thể chia sẻ Internet từ laptop (laptop đang kết nối Internet qua wifi) cho nó. Để cho ngắn gọn, mình sẽ gọi máy tính nhúng là RPi trong bài này.

Nguyên lý của việc này là biến laptop của bạn thành một thiết bị router mạng đơn sơ, tạo một mạng con với RPi, do laptop của bạn quản lý.

Với Ubuntu 20.04+ thì việc này rất đơn giản. Sau khi dùng dây LAN nối giữa RPi với laptop, bạn chỉ việc mở phần mềm tên là "Settings", tìm đến mục "Network", bấm vào nút "chỉnh sửa" (có hình bánh răng) cho mạng dây (Wired):

Network Settings

Ở màn hình kế tiếp, chọn tab "IPv4" rồi chọn "Shared to other computers".

share network

Sau khi lưu lại cấu hình này, bấm vào tab "Details", bạn sẽ thấy địa chỉ IP của laptop trong mạng con mới tạo này. Thông thường là 10.42.0.1. Tuy nhiên, đó chỉ là địa chỉ của laptop, còn địa chỉ của RPi là gì, làm sao truy cập đến nó? Có các cách sau đây:

a) Dùng mDNS

Cách này áp dụng khi cả laptop và RPi của bạn đều đã cài đặt phần mềm avahi-daemon và bạn nhớ tên hostname của RPi. Khi đó địa chỉ của RPi sẽ có dạng [hostname].local, ví dụ raspberrypi.local. Thử ping vào địa chỉ đó, bạn sẽ thấy RPi trả lời:

$ ping raspberrypi.local
PING raspberrypi.local (10.42.0.95) 56(84) bytes of data.
64 bytes from 10.42.0.95 (10.42.0.95): icmp_seq=1 ttl=64 time=1.05 ms
64 bytes from 10.42.0.95 (10.42.0.95): icmp_seq=2 ttl=64 time=0.844 ms
64 bytes from 10.42.0.95 (10.42.0.95): icmp_seq=3 ttl=64 time=1.21 ms

Với mDNS thì thật ra bạn không cần dùng đến địa IP nữa, có thể dùng địa chỉ "*.local" này để truy cập bằng bất cứ phương thức nào, như SSH, FTP, HTTP:

ssh

b) Dùng nmap để quét

Nếu lỡ quên cài avahi-daemon cho RPi thì ta dùng nmap để quét trong mạng con:

$ sudo nmap -sn 10.42.0.0/24
[sudo] password for quan: 
Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-06 12:15 +07
Nmap scan report for 10.42.0.95
Host is up (0.0011s latency).
MAC Address: B8:27:EB:D1:AB:91 (Raspberry Pi Foundation)
Nmap scan report for Fossa (10.42.0.1)
Host is up.
Nmap done: 256 IP addresses (2 hosts up) scanned in 3.89 seconds

Địa chỉ của mạng con được xác định bằng cách thay số cuối của địa chỉ laptop bằng số 0 rồi gắn thêm /24 vào, ví dụ 10.42.0.1 -> 10.42.0.0/24.

nmap không được cài sẵn trong Ubuntu, bạn phải cài bằng cách:

sudo apt install nmap

c) Xem trong danh sách địa chỉ IP đã cấp

Tìm trong thư mục /var/lib/NetworkManager/ (cần quyền sudo), coi có file nào có tên dạng dnsmasq-[ifname].leases, ví dụ, vì card mạng của tôi có tên enp2s0 nên sẽ có file dnsmasq-enp2s0.leases. Mở nội dung file đó sẽ thấy địa chỉ nào được cấp cho RPi:

$ sudo cat /var/lib/NetworkManager/dnsmasq-enp2s0.leases
1596695111 b8:27:eb:d1:ab:91 10.42.0.95 raspberrypi 01:b8:27:eb:d1:ab:91

Giải thích thêm, NetworkManager là chương trình quản lý mạng phổ biến trên hệ điều hành Linux dành cho desktop (và smartphone) (còn có các chương trình khác dành cho bối cảnh sử dụng khác). Khi dùng nó để tạo mạng con, nó sẽ gọi thêm một chương trình tên là dnsmasq lên để đảm nhiệm chức năng DHCP server và DNS server cho mạng con đó.

Với một số phiên bản Ubuntu cũ hơn (khoảng 18.10 đến 19.10), bạn sẽ không thấy lựa chọn "Shared to other computers". Đó chỉ là thiếu sót của giao diện "Settings", bạn vẫn có thể cấu hình chia sẻ mạng trong một ứng dụng khác, tên là "Advanced Network Configuration". Ứng dụng này có thể tìm thấy trong màn hình Dash (ấn phím Super/Windows để mở). Với Ubuntu 19.04 và 19.10 thì ứng dụng này bị giấu khỏi Dash, bạn phải mở bằng dòng lệnh:

nm-connection-editor

Sau khi mở "Advanced Network Configuration", bạn có thể chọn để sửa một cấu hình mạng dây đang có, tương tự với chương trình "Settings" phía trên.

nm-connection-editor

Mẹo nhỏ:

Để đưa "Advanced Network Configuration" trở lại màn hình Dash thì sửa file /usr/share/applications/nm-connection-editor.desktop và xóa dòng "NotShowIn=GNOME;" đi.

[Desktop Entry]
Name=Advanced Network Configuration
Comment=Manage and change your network connection settings
Icon=preferences-system-network
Exec=nm-connection-editor
Terminal=false
StartupNotify=true
Type=Application
X-GNOME-Bugzilla-Bugzilla=GNOME
X-GNOME-Bugzilla-Product=NetworkManager
X-GNOME-Bugzilla-Component=nm-connection-editor
Categories=GNOME;GTK;Settings;X-GNOME-NetworkSettings;X-GNOME-Utilities;
NotShowIn=GNOME;  # <~~ Xóa dòng này, hoặc comment lại.
X-Ubuntu-Gettext-Domain=nm-applet

2. Tạo cổng serial ảo

Tình huống này có liên quan đến bài Áp dụng quy trình hiện đại khi làm phần mềm cho hệ thống nhúng. Đó là khi mình cần cho phần mềm server giao tiếp với một thiết bị kết nối qua cổng serial (bên Windows gọi là cổng COM). Như đã kể, trong khi phát triển ứng dụng IoT thì mình luôn viết phần mềm mô phỏng để thay mặt thiết bị, khi thiết bị chưa được chế tạo xong, chưa được viết code nhúng. Cả phần mềm server lẫn phần mềm mô phỏng cùng chạy trên PC mà lại cần giao tiếp qua cổng serial thì phải làm sao? Đó là lúc cần phải tạo một cổng serial ảo để phần mềm server nắm một đầu, phần mềm mô phỏng nắm một đầu. Phần mềm này ghi dữ liệu vào đầu này thì tại đầu kia, phần mềm kia sẽ đọc được.

Để tạo cổng serial ảo thì mình dùng socat. Cách tạo như sau:

Mở một tab của Terminal, gõ lệnh:

socat -d -d PTY,link=writer PTY,link=reader

Khi đó, socat sẽ tạo ra hai file, writerreader. Hai tên này thích đặt sao cũng được, không nhất thiết phải là writerreader.

Trong lúc vẫn giữ socat chạy, mở một tab khác của Terminal lên, chạy phần mềm mô phỏng, truyền tên của một trong hai file kia vào, giống với cách bạn truyền tên cổng serial thật vào, ví dụ:

./device-emulator writer

Và ở một Terminal khác thì bạn cấu hình cho phần mềm server truy cập vào file còn lại, thay cho cổng serial thật.

Lưu ý là tên file đặt là gì không quan trọng, nên tại đầu "reader" bạn vẫn có thể ghi, để đầu "writer" đọc được.

Bạn cũng có thể bỏ tham số "link" khỏi socat, ví dụ:

socat -d -d PTY PTY

Nhìn vào thông báo của socat để lấy tên file tương ứng với cổng serial ảo, ví dụ:

2017/10/06 17:09:49 socat[6828] N PTY is /dev/pts/5
2017/10/06 17:09:49 socat[6828] N PTY is /dev/pts/6
2017/10/06 17:09:49 socat[6828] N starting data transfer loop with FDs [5,5] and [7,7]

Tuy nhiên, đây là cách rất không được khuyến khích, vì mỗi lần tắt socat mở lại, socat sẽ phải tạo file mới với con số mới (để tránh đụng với các file pseudo-terminal sinh ra mỗi khi bạn mở một tab Terminal mới), ứng dụng của bạn sẽ phải cấu hình lại, nếu không sẽ vô tình ghi vào file pts của một Terminal nào đó.

Sau đây là minh họa bằng hình động:

Với cổng serial ảo tạo bởi socat, bạn có thể truy cập, theo dõi bằng hầu hết các phần mềm giao tiếp serial, ngoại trừ các phần mềm viết bằng Qt như CuteCom. Cụ thể, nếu cần phần mềm có giao diện đồ họa thì nên dùng GtkTerm:

gtkterm

Nếu thấy thoải mái với dòng lệnh thì bạn nên dùng thư viện PySerial của Python, khởi chạy công cụ dòng lệnh của nó bằng cách:

$ python3 -m serial.tools.miniterm /tên/port

PySerial cũng là thư viện đảm nhiệm tính năng truy cập serial trong các bộ công cụ lập trình nhúng sau:

  • IDF của Espressif (tương tác với ESP32/8266)
  • PlatformIO (tương tác với tất cả các loại board mà PlatformIO hỗ trợ).

(và tất nhiên, cùng với các thư viện Python khác, đã đóng góp phần rất lớn trong các sản phẩm của AgriConnect).

Trên đây mình đã kể xong hai mẹo. Không biết có tầm thường không, nhưng với mình thì nó đã tiết kiệm rất nhiều thời gian làm việc, và từ đó cũng thấy Linux có nhiều đồ chơi hay thật.