2025年1月9日 星期四

利用Ollama整合OCR的功能

1.執行ollama

ollama pull llama3.2-vision:11b

ollama run llama3.2-vision:11b


2.安裝套件(開啟另一個cmd)

pip install ollama-ocr


3.程式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from ollama_ocr import OCRProcessor

# Initialize OCR processor
ocr = OCRProcessor(model_name='llama3.2-vision:11b')  # You can use any vision model available on Ollama

# Process an image
result = ocr.process_image(
    image_path="img.png",
    format_type="markdown"  # Options: markdown, text, json, structured, key_value
)
print(result)

執行結果:




2025年1月6日 星期一

ESP32-S按下按鈕利用ESP-NOW傳送訊息給另一顆ESP32-S點亮LED

 


傳送端程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import machine
import network
import espnow
import time

# 初始化 Wi-Fi 接口為 STA 模式
sta = network.WLAN(network.STA_IF)
sta.active(True)

# 初始化 ESP-NOW
esp = espnow.ESPNow()
esp.active(True)

# 添加另一顆晶片的 MAC 地址
peer_mac = b'\xb0\xa7\x32\xc0\xc6\xbc'  # 另一顆晶片的 MAC 地址
esp.add_peer(peer_mac)

# 設定 GPIO0 為按鈕
button = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)

while True:
    if button.value() == 0:  # 檢測按鈕是否按下
        print("Button pressed, sending message...")
        esp.send(peer_mac, b"Button pressed!")
        time.sleep(0.5)  # 避免反彈,稍作延遲

接收端程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import machine
import network
import espnow
import time

# 初始化 Wi-Fi 接口為 STA 模式
sta = network.WLAN(network.STA_IF)
sta.active(True)

# 初始化 ESP-NOW
esp = espnow.ESPNow()
esp.active(True)

# 設定 GPIO2 為 LED
led = machine.Pin(2, machine.Pin.OUT)
led.off()  # 初始狀態關閉 LED

def on_recv(peer, msg):
    if msg:
        print("Received message:", msg)
        led.on()  # 點亮 LED
        time.sleep(1)  # 點亮 1 秒
        led.off()  # 關閉 LED

# 保持程式運行
while True:
    # 接收訊息
    if on_recv(*esp.recv()):
        break  # 如果接收到 'end' 訊息,結束循環
    pass

執行結果:
如最上面的影片

傳送端終端機的資訊:
MPY: soft reboot
Button pressed, sending message...
Button pressed, sending message...
Button pressed, sending message...

接收端終端機的資訊:
Received message: b'Button pressed!'
Received message: b'Button pressed!'
Received message: b'Button pressed!'

使用兩顆esp32實現ESP-NOW 1對1的雙向的通訊程式



兩顆程式一樣,只是MAC 位址不同,需注意。 
可以接收和傳送的程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import network
import espnow

# 初始化 Wi-Fi 接口為 STA 模式
sta = network.WLAN(network.STA_IF)
sta.active(True)

# 獲取並顯示 ESP32 的 MAC 地址
mac_address = sta.config('mac')
mac_address_readable = ':'.join(['{:02x}'.format(b) for b in mac_address])
print("MAC Address:", mac_address_readable)

# 初始化 ESP-NOW
esp = espnow.ESPNow()
esp.active(True)

# 添加另一顆晶片的 MAC 地址
peer_mac = b'\xb0\xa7\x32\xc0\xc6\xbc'  # 另一顆晶片的 MAC 地址
esp.add_peer(peer_mac)

# 設置一個函數來處理接收到的訊息
def on_recv(peer, msg):
    if msg:
        print("Received from {}: {}".format(peer, msg))
        if msg == b'end':
            print("Ending communication")
            return True
    return False

# 主要的循環來發送和接收訊息
while True:
    # 傳送訊息
    message = b"Hello from ESP-NOW!"
    esp.send(peer_mac, message)
    print("Message sent:", message)
    
    # 接收訊息
    if on_recv(*esp.recv()):
        break  # 如果接收到 'end' 訊息,結束循環

    # 稍作延遲以避免過多的傳送
    import time
    time.sleep(1)

執行結果:
MPY: soft reboot
MAC Address: b0:a7:32:c0:c6:70
Message sent: b'Hello from ESP-NOW!'
Received from b'\xb0\xa72\xc0\xc6\xbc': b'Hello from ESP-NOW!'
Message sent: b'Hello from ESP-NOW!'
Received from b'\xb0\xa72\xc0\xc6\xbc': b'Hello from ESP-NOW!'
Message sent: b'Hello from ESP-NOW!'
Received from b'\xb0\xa72\xc0\xc6\xbc': b'Hello from ESP-NOW!'
Message sent: b'Hello from ESP-NOW!'
Received from b'\xb0\xa72\xc0\xc6\xbc': b'Hello from ESP-NOW!'
Message sent: b'Hello from ESP-NOW!'
Received from b'\xb0\xa72\xc0\xc6\xbc': b'Hello from ESP-NOW!'
Message sent: b'Hello from ESP-NOW!'
Received from b'\xb0\xa72\xc0\xc6\xbc': b'Hello from ESP-NOW!'
Message sent: b'Hello from ESP-NOW!'
Received from b'\xb0\xa72\xc0\xc6\xbc': b'Hello from ESP-NOW!'

使用兩顆esp32實現ESP-NOW 1對1的單向的通訊程式





實作環境:利用兩台PC以及兩顆ESP32,分別執行Thonny,

接收端程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import network
import espnow

# A WLAN interface must be active to send()/recv()
sta = network.WLAN(network.WLAN.IF_STA)
sta.active(True)
sta.disconnect()   # Because ESP8266 auto-connects to last Access Point

# Get the MAC address
mac_address = sta.config('mac')

# Convert the MAC address to a readable format
mac_address_readable = ':'.join(['{:02x}'.format(b) for b in mac_address])

print("MAC Address:", mac_address_readable)

e = espnow.ESPNow()
e.active(True)

while True:
    host, msg = e.recv()
    if msg:             # msg == None if timeout in recv()
        print(host, msg)
        if msg == b'end':
            break

僅執行接收程式的執行結果:
MPY: soft reboot
MAC Address: b0:a7:32:c0:c6:70

傳送程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import network
import espnow

# A WLAN interface must be active to send()/recv()
sta = network.WLAN(network.WLAN.IF_STA)  # Or network.WLAN.IF_AP
sta.active(True)
sta.disconnect()      # For ESP8266

e = espnow.ESPNow()
e.active(True)
peer = b'\xb0\xa7\x32\xc0\xc6\x70'   # MAC address of peer's wifi interface
e.add_peer(peer)      # Must add_peer() before send()

e.send(peer, "Starting...")
for i in range(100):
    e.send(peer, str(i), True)
e.send(peer, b'end')

執行結果(接收端):
MPY: soft reboot
MAC Address: b0:a7:32:c0:c6:70
b'\xb0\xa72\xc0\xc6\xbc' b'Starting...'
b'\xb0\xa72\xc0\xc6\xbc' b'0'
b'\xb0\xa72\xc0\xc6\xbc' b'1'
b'\xb0\xa72\xc0\xc6\xbc' b'2'
b'\xb0\xa72\xc0\xc6\xbc' b'3'
b'\xb0\xa72\xc0\xc6\xbc' b'4'
b'\xb0\xa72\xc0\xc6\xbc' b'5'
b'\xb0\xa72\xc0\xc6\xbc' b'6'
b'\xb0\xa72\xc0\xc6\xbc' b'7'
b'\xb0\xa72\xc0\xc6\xbc' b'8'
b'\xb0\xa72\xc0\xc6\xbc' b'9'
b'\xb0\xa72\xc0\xc6\xbc' b'10'
b'\xb0\xa72\xc0\xc6\xbc' b'11'
b'\xb0\xa72\xc0\xc6\xbc' b'12'
b'\xb0\xa72\xc0\xc6\xbc' b'13'
b'\xb0\xa72\xc0\xc6\xbc' b'14'
b'\xb0\xa72\xc0\xc6\xbc' b'15'
b'\xb0\xa72\xc0\xc6\xbc' b'16'
b'\xb0\xa72\xc0\xc6\xbc' b'17'
b'\xb0\xa72\xc0\xc6\xbc' b'18'
b'\xb0\xa72\xc0\xc6\xbc' b'19'
b'\xb0\xa72\xc0\xc6\xbc' b'20'
b'\xb0\xa72\xc0\xc6\xbc' b'21'
b'\xb0\xa72\xc0\xc6\xbc' b'22'
b'\xb0\xa72\xc0\xc6\xbc' b'23'
b'\xb0\xa72\xc0\xc6\xbc' b'24'
b'\xb0\xa72\xc0\xc6\xbc' b'25'
b'\xb0\xa72\xc0\xc6\xbc' b'26'
b'\xb0\xa72\xc0\xc6\xbc' b'27'
b'\xb0\xa72\xc0\xc6\xbc' b'28'
b'\xb0\xa72\xc0\xc6\xbc' b'29'
b'\xb0\xa72\xc0\xc6\xbc' b'30'
b'\xb0\xa72\xc0\xc6\xbc' b'31'
b'\xb0\xa72\xc0\xc6\xbc' b'32'
b'\xb0\xa72\xc0\xc6\xbc' b'33'
b'\xb0\xa72\xc0\xc6\xbc' b'34'
b'\xb0\xa72\xc0\xc6\xbc' b'35'
b'\xb0\xa72\xc0\xc6\xbc' b'36'
b'\xb0\xa72\xc0\xc6\xbc' b'37'
b'\xb0\xa72\xc0\xc6\xbc' b'38'
b'\xb0\xa72\xc0\xc6\xbc' b'39'
b'\xb0\xa72\xc0\xc6\xbc' b'40'
b'\xb0\xa72\xc0\xc6\xbc' b'41'
b'\xb0\xa72\xc0\xc6\xbc' b'42'
b'\xb0\xa72\xc0\xc6\xbc' b'43'
b'\xb0\xa72\xc0\xc6\xbc' b'44'
b'\xb0\xa72\xc0\xc6\xbc' b'45'
b'\xb0\xa72\xc0\xc6\xbc' b'46'
b'\xb0\xa72\xc0\xc6\xbc' b'47'
b'\xb0\xa72\xc0\xc6\xbc' b'48'
b'\xb0\xa72\xc0\xc6\xbc' b'49'
b'\xb0\xa72\xc0\xc6\xbc' b'50'
b'\xb0\xa72\xc0\xc6\xbc' b'51'
b'\xb0\xa72\xc0\xc6\xbc' b'52'
b'\xb0\xa72\xc0\xc6\xbc' b'53'
b'\xb0\xa72\xc0\xc6\xbc' b'54'
b'\xb0\xa72\xc0\xc6\xbc' b'55'
b'\xb0\xa72\xc0\xc6\xbc' b'56'
b'\xb0\xa72\xc0\xc6\xbc' b'57'
b'\xb0\xa72\xc0\xc6\xbc' b'58'
b'\xb0\xa72\xc0\xc6\xbc' b'59'
b'\xb0\xa72\xc0\xc6\xbc' b'60'
b'\xb0\xa72\xc0\xc6\xbc' b'61'
b'\xb0\xa72\xc0\xc6\xbc' b'62'
b'\xb0\xa72\xc0\xc6\xbc' b'63'
b'\xb0\xa72\xc0\xc6\xbc' b'64'
b'\xb0\xa72\xc0\xc6\xbc' b'65'
b'\xb0\xa72\xc0\xc6\xbc' b'66'
b'\xb0\xa72\xc0\xc6\xbc' b'67'
b'\xb0\xa72\xc0\xc6\xbc' b'68'
b'\xb0\xa72\xc0\xc6\xbc' b'69'
b'\xb0\xa72\xc0\xc6\xbc' b'70'
b'\xb0\xa72\xc0\xc6\xbc' b'71'
b'\xb0\xa72\xc0\xc6\xbc' b'72'
b'\xb0\xa72\xc0\xc6\xbc' b'73'
b'\xb0\xa72\xc0\xc6\xbc' b'74'
b'\xb0\xa72\xc0\xc6\xbc' b'75'
b'\xb0\xa72\xc0\xc6\xbc' b'76'
b'\xb0\xa72\xc0\xc6\xbc' b'77'
b'\xb0\xa72\xc0\xc6\xbc' b'78'
b'\xb0\xa72\xc0\xc6\xbc' b'79'
b'\xb0\xa72\xc0\xc6\xbc' b'80'
b'\xb0\xa72\xc0\xc6\xbc' b'81'
b'\xb0\xa72\xc0\xc6\xbc' b'82'
b'\xb0\xa72\xc0\xc6\xbc' b'83'
b'\xb0\xa72\xc0\xc6\xbc' b'84'
b'\xb0\xa72\xc0\xc6\xbc' b'85'
b'\xb0\xa72\xc0\xc6\xbc' b'86'
b'\xb0\xa72\xc0\xc6\xbc' b'87'
b'\xb0\xa72\xc0\xc6\xbc' b'88'
b'\xb0\xa72\xc0\xc6\xbc' b'89'
b'\xb0\xa72\xc0\xc6\xbc' b'90'
b'\xb0\xa72\xc0\xc6\xbc' b'91'
b'\xb0\xa72\xc0\xc6\xbc' b'92'
b'\xb0\xa72\xc0\xc6\xbc' b'93'
b'\xb0\xa72\xc0\xc6\xbc' b'94'
b'\xb0\xa72\xc0\xc6\xbc' b'95'
b'\xb0\xa72\xc0\xc6\xbc' b'96'
b'\xb0\xa72\xc0\xc6\xbc' b'97'
b'\xb0\xa72\xc0\xc6\xbc' b'98'
b'\xb0\xa72\xc0\xc6\xbc' b'99'
b'\xb0\xa72\xc0\xc6\xbc' b'end'

2025年1月1日 星期三

IoT MQTT Panel來察看micro:bit和電腦通訊情形


實作上圖的控制面板

前兩篇文章:micro:bit和電腦的串列通訊程式micro:bit和電腦的串列通訊程式加上MQTT

MQTT Broker:broker.MQTTGO.io

IoT MQTT Panel:AndriodiOS

IoT MQTT Panel的連線設定:

新增一個溫度面板:


三個按鈕面板:







micro:bit和電腦的串列通訊程式加上MQTT

 前一篇文章:micro:bit和電腦的串列通訊程式


電腦執行的Python程式:


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
import serial
import random
import paho.mqtt.client as mqtt
import time

# MQTT 設定
BROKER = "broker.mqttgo.io"
PORT = 1883
TOPIC_PUBLISH = "temperature"
TOPIC_SUBSCRIBE = "messages"
MQTT_CLIENT_ID = "microbit_client"
USERNAME = ""  # 如果需要帳號,填入使用者名稱
PASSWORD = ""  # 如果需要密碼,填入密碼
message = ""

def on_connect(client, userdata, flags, rc):
    print(f"MQTT 連線狀態: {rc}")
    if rc == 0:
        print("成功連線到 MQTT Broker")
        client.subscribe(TOPIC_SUBSCRIBE)
    else:
        print("無法連線到 MQTT Broker")

def on_message(client, userdata, msg):
    global message
    print(f"接收到來自 {msg.topic} 的訊息: {msg.payload.decode('utf-8')}")
    # 處理收到的訊息
    payload = msg.payload.decode('utf-8')
    if payload in ["heart", "OK", "Error"]:
        message = payload
        print(f"處理訂閱訊息: {payload}")
    else:
        print("收到未知訊息")

def main():
    # 配置串列埠參數
    global message
    port = 'COM4'  # 替換為您的串列埠名稱
    baudrate = 115200  # 串列埠速率

    # 初始化 MQTT 客戶端
    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, MQTT_CLIENT_ID)
    client.username_pw_set(USERNAME, PASSWORD)
    client.on_connect = on_connect
    client.on_message = on_message

    # 嘗試連線到 MQTT Broker
    try:
        client.connect(BROKER, PORT, 60)
        client.loop_start()

        # 開啟串列埠
        ser = serial.Serial(port, baudrate, timeout=1)
        print(f"已開啟串列埠: {ser.port}, 波特率: {ser.baudrate}")

        buffer = ""  # 初始化資料緩衝區

        while True:
            # 處理串列埠的輸入
            if ser.in_waiting > 0:
                data = ser.read(ser.in_waiting).decode('latin-1', errors='ignore').strip()
                buffer += data
                print(f"目前緩衝區資料: {buffer}")

                while '|' in buffer:
                    line, buffer = buffer.split('|', 1)  # 提取一行資料
                    line = line.strip()
                    print(f"處理資料: {line}")

                    if line.startswith("T="):
                        try:
                            # 發佈溫度值到 MQTT Broker
                            temperature = int(line.split("=")[1])
                            print(f"當前溫度: {temperature} 度")
                            client.publish(TOPIC_PUBLISH, temperature)
                        except ValueError:
                            print("無法解析溫度值")

            # 模擬隨機傳送訊息給 micro:bit
            #message = random.choice(["heart", "OK", "Error"])
            ser.write((message + "\n").encode('utf-8'))
            print(f"已傳送: {message}")

            time.sleep(1)

    except serial.SerialException as e:
        print(f"串列埠錯誤: {e}")

    except KeyboardInterrupt:
        print("程序中止")

    finally:
        # 清理資源
        if 'ser' in locals() and ser.is_open:
            ser.close()
            print("串列埠已關閉")

        client.loop_stop()
        client.disconnect()
        print("已斷開 MQTT 連線")

if __name__ == "__main__":
    main()

執行結果:
目前緩衝區資料: T=2|
處理資料: T=2
當前溫度: 2 度
已傳送: OK
已傳送: OK
目前緩衝區資料: T=21|
處理資料: T=21
當前溫度: 21 度
已傳送: OK
目前緩衝區資料: T=21
已傳送: OK
目前緩衝區資料: T=21T=21|
處理資料: T=21T=21
無法解析溫度值
已傳送: OK
目前緩衝區資料: T=21|
處理資料: T=21
當前溫度: 21 度
已傳送: OK

連上MQTTGO的執行結果如最上圖

micro:bit和電腦的串列通訊程式


功能:把micro:bit的溫度值經由串列埠傳給電腦,電腦端隨機產生字串傳給micro:bit,改變micro:bit的圖案。

micro:bit 積木程式:



micro:bit端的Python程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
MyString = ""
serial.redirect(SerialPin.USB_TX, SerialPin.USB_RX, BaudRate.BAUD_RATE115200)

def on_forever():
    global MyString
    MyString = convert_to_text(input.temperature())
    serial.write_string("T=" + MyString + "|")
    MyString = convert_to_text(input.light_level())
    if MyString == "heart":
        basic.show_icon(IconNames.HEART)
    elif MyString == "OK":
        basic.show_icon(IconNames.YES)
    elif MyString == "Error":
        basic.show_icon(IconNames.NO)
    basic.pause(500)
basic.forever(on_forever)

電腦端:
先裝serial套件
pip install pyserial


注意,要觀察當micro:bit連線插到電腦USB埠時,會用那一個通訊埠來和電腦溝通。
Python程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import serial
import random


def main():
    # 配置串列埠參數
    port = 'COM4'  # 替換為您的串列埠名稱(例如:'/dev/ttyUSB0' 或 'COM3')
    baudrate = 115200  # 串列埠速率
    messages = ["heart", "OK", "Error"]

    try:
        # 開啟串列埠
        ser = serial.Serial(port, baudrate, timeout=1)
        print(f"已開啟串列埠: {ser.port}, 波特率: {ser.baudrate}")

        buffer = ""  # 初始化資料緩衝區

        while True:
            if ser.in_waiting > 0:
                # 嘗試用指定編碼解碼資料
                data = ser.read(ser.in_waiting).decode('latin-1', errors='ignore').strip()
                buffer += data  # 將新資料串接到緩衝區
                print(f"目前緩衝區資料: {buffer.strip()}")

                # 嘗試處理緩衝區中的每一行資料
                while '|' in buffer:
                    line, buffer = buffer.split('|', 1)  # 提取第一行,更新緩衝區
                    line = line.strip()  # 去除行首尾空白
                    print(f"處理資料: {line}")

                    # 檢查是否為溫度資料
                    if line.startswith("T="):
                        try:
                            # 提取溫度值
                            temperature = int(line.split("=")[1])
                            print(f"當前溫度: {temperature} 度")
                        except ValueError:
                            print("無法解析溫度值")

                            
                    # 偵測退出指令
                    if line.lower() == "exit":
                        print("收到 'exit',結束程序")
                        return
                message = random.choice(messages)
                ser.write((message + "\n").encode('utf-8'))
                print(f"已傳送: {message}")

    except serial.SerialException as e:
        print(f"串列埠錯誤: {e}")

    finally:
        # 關閉串列埠
        if 'ser' in locals() and ser.is_open:
            ser.close()
            print("串列埠已關閉")

if __name__ == "__main__":
    main()

執行結果:
目前緩衝區資料: T=21|
處理資料: T=21
當前溫度: 21 度
已傳送: Error
目前緩衝區資料: T=21|
處理資料: T=21
當前溫度: 21 度
已傳送: OK
目前緩衝區資料: T=21|
處理資料: T=21
當前溫度: 21 度
已傳送: heart