延續前一篇文章:在Arduino UNO Q開發板上,使用Needle將文字輸入轉化為動作,接著往下進行更深入的開發。由於Cactus Needle模型只是針對「語言 - 動作」進行處理,並無法應對一般性的對話(如:Where is the capital of France?)。接下來會在Arduino UNO Q開發板外接ASUS UGen300 USB AI 加速器來實作完整的本地端大語言模型之對話應用程序。先看一下成果:

可以看到在使用者想要控制LED點亮或熄滅時Cactus Needle會呼叫tool進行控制,而一般性的對話則會交由本地端大語言模型(這邊用到的llama3.2:1b大語言模型,由hailo-ollama來提供服務)接手處理。
因此會比前一篇文章在開發與執行環境上更為複雜。Arduino UNO Q開發板(4GB/32GB版本)需要運作在SBC模式並搭配周邊設備,硬體組成可參考以下照片所示:

在軟體方面,因為搭配ASUS UGen300 USB AI 加速器軟體所以無法使用Arduino App Lab來進行開發工作;改以命令列方式(在Terminal上操作)進行微控制器(MCU)之編譯與下載,以及Python之環境建立、相關套件安裝與執行應用程序。步驟相對複雜,因此分段進行說明。
Arduino UNO Q開發板的SBC模式就相當於操作一台Linux迷你電腦(概念等同於樹莓派Raspberry Pi),可以在上面編輯與編譯程式。為了開發便利性就先安裝編輯器:
sudo apt install geany
接著建置Python環境,這邊是安裝Miniforge:
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
bash Miniforge3-$(uname)-$(uname -m).sh
安裝ASUS UGen300 USB AI 加速器驅動程式與應用程序,請至官網下載「UGen Utility for UGen300 USB AI Accelerator (Linux-Debian)」後依以下程序安裝(安裝過程若有疑問請參考壓縮檔內的README.txt文件):
unzip ugen-utility_2.4.0-9_arm64_usb.zip
cd ugen-utility_2.4.0-9_arm64_usb
chmod +x ./ugen-utility-install.sh
./ugen-utility-install.sh
安裝完成後請確認一下應用程序與Python環境是否可以運作,確認沒有問題後再往下進行。
使用命令來建立MCU韌體專案:
arduino-cli sketch new McuLedCtrl
修改McuLedCtrl.ino加入以下程式碼:
#include "Arduino_RouterBridge.h"
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // Initialize digital pin LED_BUILTIN as an output.
Bridge.begin(); // Is mandatory calling Bridge.begin() to initialize Bridge communication.
Bridge.provide("set_led_state", set_led_state); // Provide the "set_led_state" function to be called from Python
}
void loop() {
}
void set_led_state(bool state) {
digitalWrite(LED_BUILTIN, state ? LOW : HIGH); // Set the LED state based on the state input parameter
}
修改好後進行編譯與上傳:
arduino-cli compile --fqbn arduino:zephyr:unoq ./McuLedCtrl/
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:zephyr:unoq ./McuLedCtrl/
前面在環境準備時的安裝完成後並沒有hailo-ollama命令可以使用,因此須至Hailo’s Developer Zone中找到相對應的套件(hailo_gen_ai_model_zoo_5.3.0_arm64.deb)並下載,安裝命令如下所示:
sudo dpkg -i hailo_gen_ai_model_zoo_5.3.0_arm64.deb
完成後另開「終端機」執行以下命令以啟動hailo-ollama服務:
hailo-ollama
然後透過以下命令將模型下載至本地:
curl --silent http://localhost:8000/api/pull \
-H 'Content-Type: application/json' \
-d '{ "model": "llama3.2:1b", "stream" : true }'
建立一個虛擬環境以方便開發與應用:
conda create --name ArduinoUnoQ python=3.13
完成後進入該環境:
conda activate ArduinoUnoQ
並安裝以下必要套件:
pip install arduino-router-bridge
pip install cactus-needle==2.0.15
pip install requests
UnoQ.py程式碼如下所示:
from arduino.router_bridge import Bridge
import needle
import requests
import json
bridge = None
# Define your hardware action tools using the @needle.tool decorator
@needle.tool
def set_led(state: str):
"""Turn the device LED on or off.
Args:
state: Must be either 'on' or 'off'.
"""
global bridge
if state.lower() == "on":
bridge.call("set_led_state", True)
print("Hardware action: LED turned ON")
return {"status": "success", "led": "on"}
elif state.lower() == "off":
bridge.call("set_led_state", False)
print("Hardware action: LED turned OFF")
return {"status": "success", "led": "off"}
return {"status": "error", "message": "Invalid state argument"}
def hailo_ollama_chat( question) :
url = "http://localhost:8000/api/chat"
model_name = "llama3.2:1b"
responses = ""
data = {
"model": model_name,
"temperature": 0.7,
"max_length": 2048,
"messages": [
{"role": "user", "content": question}
]
}
try:
res = requests.post(url, json=data, stream=True)
if res.status_code == 200:
for line in res.iter_lines():
if not line:
continue
data = json.loads(line.decode("utf-8"))
content = data.get("message", {}).get("content", "")
if content:
responses += content
if data.get("done"):
break
else:
pass
except Exception as e:
print(e)
return responses
def main():
global bridge
bridge = Bridge()
bridge.connect(timeout=5) # Waits until connected; True if connected, False on timeout
print("Hardware initialize: LED turned OFF")
bridge.call("set_led_state", False)
# Initialize the Needle agent with your tools
agent = needle.Needle(tools=[set_led])
dbgmsg_enable = False
tool_is_call = False
while True :
tool_is_call = False
message = input("Enter a message: ")
if message == 'exit' :
break
# Run a natural language prompt to trigger the action
# response = agent.run(message)
response = agent.complete(message)
if dbgmsg_enable is True :
print('DEBUG : agent.complete() response = {}'.format(response))
if response["type"] == "call" and response["confidence"] >= 0.7:
if len(response["function_calls"]) > 0 and response["function_calls"][0]["name"] == "set_led" :
out = set_led(**response["function_calls"][0]["arguments"])
response = agent.complete(json.dumps(out))
tool_is_call = True
agent.reset()
if tool_is_call is False :
response = hailo_ollama_chat(message)
print(response)
bridge.disconnect()
if __name__ == "__main__":
main()
python UnoQ.py
執行結果如下所示:

參考資訊
iThome鐵人賽