2025年6月8日 星期日

尚虎雲產銷平台助水井村智慧減碳節水三生一體

 

國立虎尾科技大學電機資訊學院
113學年度第二學期多媒體應用與實習【創新課程】教學成果


教學目標:

本課程將以先前開發的尚虎雲產銷平台為基礎,著重於前端網站設計及物聯網技術的實踐與應用。課程內容包括強化多媒體應用於產銷平台的功能,提升學生在前端開發、智慧物聯網整合與平台優化方面的能力。本次課程將延續地方場域的實踐,學生將透過實地考察與社區合作,結合所學技能,進一步完善並擴展尚虎雲平台的使用範圍與功能。評量方式涵蓋專案成果展示、技術能力測試及課堂參與,旨在全面提升學生的實務經驗與創新能力。透過此課程,學生將具備更高的實作能力,並能在數位科技及物聯網應用領域展現競爭力。

關鍵詞:多媒體應用、前端網站設計、物聯網、地方場域、尚虎雲產銷平台

場域學習:

  • 114年4月25日 北港高中 第四屆雲林Go科技/國立虎尾科技大學電子工程系吳添全副教授


  • 114年4月25日 水井村 - 生態導覽/水井社區發展協會林文欽總幹事



業師教學:

  • 114年3月14日-「視覺設計的基礎」,盧泰岳品牌主理人/呆尼創意工作室

  • 114年3月21日-「Wireframe(線框圖)」,盧泰岳品牌主理人/呆尼創意工作室


  • 114年3月28日-「網頁基礎架構」,盧泰岳品牌主理人/呆尼創意工作室



  • 114年5月23日-「HUB8735 Ultra」,施朝斌經理/小霸王科技有限公司





  • 114年5月23日-「HUB8735U IOT」,施朝斌經理/小霸王科技有限公司



成果影片:

開源程式碼:


創新教材:

  1. 尚虎雲產銷平台-生產端軟體安裝手冊
  2. 尚虎雲產銷平台-生產端軟體操作手冊
  3. 尚虎雲產銷平台-生產端軟體新增刪除感測器的功能
  4. 尚虎雲產銷平台-銷售端軟體安裝手冊
  5. 善用pythonanywhere開發Django網站
  6. 在Django網站首頁放置樹藝micro:bit的照片
  7. Django Soft UI Dashboard佈署到pythonanywhere
  8. 設計Django網站採用Giftos樣版上傳到pythonanywhere
  9. 在Django網站中採用美食廣場Bootstrap 5 電子商務樣版
  10. 在Django網站中採用美食廣場Bootstrap 5 電子商務樣版(續)
  11. 上傳美食廣場Bootstrap 5 電子商務樣版到pythonanywhere
  12. 下載有機食物樣版實作Django網站
  13. 實作冰淇淋Django網站(一)-首頁
  14. 實作冰淇淋Django網站(二)-關於
  15. 實作冰淇淋Django網站(三)-冰淇淋
  16. 實作冰淇淋Django網站(四)-服務
  17. 實作冰淇淋Django網站(五)-部落格
  18. 實作冰淇淋Django網站(六)-聯絡我們
  19. 實作冰淇淋Django網站(七)-基礎樣版設計
  20. 實作冰淇淋Django網站(八)-輪播迴圈樣版設計
  21. 實作冰淇淋Django網站(九)-建立輪播圖資料庫
  22. 實作冰淇淋Django網站(十)-AI生成輪播圖視域程式
  23. 在pythonanywhere設計Django網站的卡片(Card)功能(新增資料庫)
  24. 在Django中設定和存取Session的內容
  25. 在Django中使用Cookies來計算今天瀏覽次數
  26. 在pythonanywhere設計Django網站的登入功能
  27. 在pythonanywhere設計Django網站的登入功能加上圖形驗證
  28. 用Python/Django/pythonanywhere實作農漁業產品交易行情
  29. 製作水果交易網站(一) -在Django中用pyecharts製作圖表
  30. 尚虎雲產銷平台-銷售端整合市場行情
  31. 尚虎雲產銷平台-銷售端整合商業模式圖
  32. 實作物聯網網站用Django+AJAX+JavaScript整合MQTT
應用範例:

評量工具:




2025年6月7日 星期六

實作物聯網網站用Django+AJAX+JavaScript整合MQTT

 

1.安裝套件

pip install django

pip install paho-mqtt

2.建立mqtttest專案

django-admin startproject mqtttest

3.進入專案

cd mqtttest

4.建立app

python manage.py startapp mqttapp

5.修改mqtttest/mqtttest/settings.py

  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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""
Django settings for mqtttest project.

Generated by 'django-admin startproject' using Django 4.2.9.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-q2ia=(7x3d5_mxao#-sl)+)crp9j%e*732fg4jhid-f&sln5k('

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'mqttapp',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mqtttest.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'mqtttest.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

MQTT_SERVER = 'broker.MQTTGO.io'
MQTT_PORT = 1883
MQTT_KEEPALIVE = 60
MQTT_USER = ''
MQTT_PASSWORD = ''

6.修改mqtttest/mqtttest/urls.py

 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
"""
URL configuration for mqtttest project.

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from mqttapp.views import home, publish_message, mqtt_messages_json

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home),
    path('publish/', publish_message, name='publish'),
    path('api/messages/', mqtt_messages_json, name='mqtt_messages_json'),
]

7.mqtttest/mqttapp/models.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from django.db import models

# Create your models here.

class MQTTMessage(models.Model):
    received_at = models.DateTimeField(auto_now_add=True)  # 自動紀錄訊息接收時間
    topic = models.CharField(max_length=255)
    payload = models.TextField()

    def __str__(self):
        return f"{self.received_at} - {self.topic}"

8.mqtttest/mqttapp/admin.py

1
2
3
4
5
6
7
8
9
from django.contrib import admin
from .models import MQTTMessage

@admin.register(MQTTMessage)
class MQTTMessageAdmin(admin.ModelAdmin):
    list_display = ('received_at', 'topic', 'payload')  # 可自訂顯示欄位
    list_filter = ('topic',)
    search_fields = ('topic', 'payload')
    ordering = ('-received_at',)

9.mqtttest/mqttapp/views.py

 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
from django.shortcuts import render

# Create your views here.
import json
from django.http import JsonResponse
from mqttapp.models import MQTTMessage
from django.views.decorators.csrf import csrf_exempt
import paho.mqtt.client as mqtt

def home(request):
	return render(request, 'index.html')

@csrf_exempt
def publish_message(request):
    if request.method == 'POST':
        try:
            data = json.loads(request.body)
            topic = data.get('topic', 'django/mqtt')
            msg = data.get('msg', 'test')

            # 即時建立連線並發送
            client = mqtt.Client()
            client.connect('broker.MQTTGO.io', 1883, 60)
            rc, mid = client.publish(topic, msg)
            client.disconnect()

            return JsonResponse({'code': rc})
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=400)
    return JsonResponse({'error': 'Only POST allowed'}, status=405)

def mqtt_messages_json(request):
    messages = MQTTMessage.objects.order_by('-received_at')[:20]
    data = [
        {
            'topic': m.topic,
            'payload': m.payload,
            'received_at': m.received_at.strftime('%Y-%m-%d %H:%M:%S')
        }
        for m in messages
    ]
    return JsonResponse(data, safe=False)

10.在mqtttest下建立templates資料夾,並新增mqtttest/templates/index.html

 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
<!DOCTYPE html>
<html>
	<head>
		<title>MQTT 測試</title>
		<script>
			function loadMessages() {
				fetch('/api/messages/')
					.then(response => response.json())
					.then(data => {
						const tbody = document.getElementById('messages');
						tbody.innerHTML = '';
						data.forEach(item => {
							const row = `<tr>
								<td>${item.received_at}</td>
								<td>${item.topic}</td>
								<td>${item.payload}</td>
							</tr>`;
							tbody.innerHTML += row;
						});
					});
				}
			function publishMessage() {
				const topic = document.getElementById('topic').value;
				const msg = document.getElementById('msg').value;

				fetch('/publish/', {
					method: 'POST',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({ topic, msg })
				})
				.then(response => response.json())
				.then(data => {
					alert('已送出 MQTT 訊息!');
				})
				.catch(error => {
					alert('錯誤:' + error);
				});
			}
			setInterval(loadMessages, 3000);
			window.onload = loadMessages;
		</script>
	</head>
	<body>
		<h1>MQTT 測試</h1>
		<h2><a href="https://broker.mqttgo.io/">使用MQTT GO伺服器</a></h2>
		<h2>發送 MQTT 訊息</h2>
		<label>Topic: <input id="topic" value="django/mqtt"></label><br>
		<label>訊息: <input id="msg" value="Hello MQTT!"></label><br>
		<button onclick="publishMessage()">送出</button>
		<h2>最新接收訊息</h2>
		<table border="1">
			<thead>
				<tr><th>時間</th><th>主題</th><th>訊息</th></tr>
			</thead>
			<tbody id="messages"></tbody>
		</table>
	</body>
</html>

12.在mqtttest根目錄下建立mqtt_listener.py

 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 os
import django
import paho.mqtt.client as mqtt

# 初始化 Django 環境
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mqtttest.settings")
django.setup()

from mqttapp.models import MQTTMessage
from django.conf import settings

def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Connected to MQTT broker.")
        client.subscribe("django/mqtt")
    else:
        print("Connection failed with code", rc)

def on_message(client, userdata, msg):
    payload = msg.payload.decode('utf-8')
    print(f"Received message on topic: {msg.topic}, payload: {payload}")
    MQTTMessage.objects.create(topic=msg.topic, payload=payload)

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(settings.MQTT_USER, settings.MQTT_PASSWORD)
client.connect(settings.MQTT_SERVER, settings.MQTT_PORT, settings.MQTT_KEEPALIVE)

client.loop_forever()

13.回到命令列,做資料庫同步,並建立超級使用者
python manage.py makemigrations
python manage.py migrate

14.執行網站
python manage.py runserver



15.再打啟另一個命令列
python mqtt_listener.py



16.啟動MQTT GO,連線並訂閱django/mqtt



17.打開瀏覽器進行測試,如最上圖

2025年6月4日 星期三

利用JinJa2模版語言設計漁產品交易行情Word模版

 


1. template.doc


2.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
import requests
import json
from docxtpl import DocxTemplate

# 取得JSON資料
r = requests.get('https://data.moa.gov.tw/Service/OpenData/FromM/AquaticTransData.aspx')
json_data = json.loads(r.text)

# 整理資料(轉換為適合模板的格式)
data = []
for item in json_data:
    data.append({
        '交易日期': item.get('交易日期'),
        '品種代碼': item.get('品種代碼'),
        '魚貨名稱': item.get('魚貨名稱'),
        '市場名稱': item.get('市場名稱'),
        '上價': str(item.get('上價')),
        '中價': str(item.get('中價')),
        '下價': str(item.get('下價')),
        '交易量': str(item.get('交易量')),
        '平均價': str(item.get('平均價'))
    })

# 套用模板
tpl = DocxTemplate("template.docx")
context = { 'data': data }
tpl.render(context)
tpl.save("AquaticTransData.docx")

print("資料已套用模板並儲存為 AquaticTransData.docx")

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

2025年6月3日 星期二

加入try..except來修復聊天機器人逾時的錯誤

補充執行本土大型語言模型的指令

模型:llama3-taide-lx-8b-chat-alpha1

前一篇文章:利用Ollama結合本土的大型語言模型製作聊天機器人

逾時未輸入語音的錯誤:

Traceback (most recent call last):

  File "C:/Users/cheng-min/AppData/Local/Programs/Python/Python313/ollama4.py", line 51, in <module>

    user_input = recognize_speech()

  File "C:/Users/cheng-min/AppData/Local/Programs/Python/Python313/ollama4.py", line 17, in recognize_speech

    audio = recognizer.listen(source, timeout=5, phrase_time_limit=10)

  File "C:\Users\cheng-min\AppData\Local\Programs\Python\Python313\Lib\site-packages\speech_recognition\__init__.py", line 460, in listen

    for a in result:

  File "C:\Users\cheng-min\AppData\Local\Programs\Python\Python313\Lib\site-packages\speech_recognition\__init__.py", line 490, in _listen

    raise WaitTimeoutError("listening timed out while waiting for phrase to start")

speech_recognition.exceptions.WaitTimeoutError: listening timed out while waiting for phrase to start

修改程式:

 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
import speech_recognition as sr
import pyttsx3
import requests
import ollama

# 初始化文字轉語音引擎
engine = pyttsx3.init()
engine.setProperty('rate', 150)  # 語速調整
engine.setProperty('volume', 0.9)  # 音量調整

# 語音轉文字
def recognize_speech():
    recognizer = sr.Recognizer()
    with sr.Microphone() as source:
        print("請開始說話...")
        try:
            audio = recognizer.listen(source, timeout=5, phrase_time_limit=10)
            text = recognizer.recognize_google(audio, language="zh-TW")
            print(f"辨識結果:{text}")
            return text
        except sr.UnknownValueError:
            print("無法辨識語音,請再試一次。")
            return None
        except sr.RequestError as e:
            print(f"語音服務出現錯誤:{e}")
            return None

# 發送請求到 Ollama API
def get_ollama_response(prompt):
    try:
        # 替換為適用的 ollama.generate 調用方法
        response = ollama.generate(
            model="cwchang/llama3-taide-lx-8b-chat-alpha1:latest",
            prompt=prompt
        )
        return response["response"]
    except Exception as e:
        print(f"Ollama 請求失敗:{e}")
        return "抱歉,目前無法處理您的請求。"

# 文字轉語音
def speak_text(text):
    engine.say(text)
    engine.runAndWait()

# 主程序
if __name__ == "__main__":
    print("啟動語音助手(結束請說退出)...")
    while True:
        try:
            # 語音輸入
            user_input = recognize_speech()
            if user_input:
                # 停止程序的關鍵字
                if "結束" in user_input or "退出" in user_input:
                    print("結束程序。")
                    speak_text("感謝使用,再見!")
                    break
                # 發送給 Ollama 並取得回應
                response = get_ollama_response(user_input)
                print(f"Ollama 回應:{response}")

                # 語音輸出
                speak_text(response)
        except sr.WaitTimeoutError:
             print("未偵測到語音輸入,請再試一次。")

執行結果:
啟動語音助手(結束請說退出)...
請開始說話...
辨識結果:給我一則30個字的童話
Ollama 回應:從前有一座神秘島嶼,住著一群善良的小妖怪,他們幫助旅行者找到心之所向,但代價是他們必須完成一個任務:在月光下散播歡樂與笑聲。於是,小妖怪們在島上舉辦了最歡樂的派對,吸引了眾多旅人參加,在月光下共舞、共享喜悅,成為永恆的傳說。
請開始說話...
辨識結果:結束
結束程序。

水井村USR導覽暨測驗系統

元件:micro:bitSmart AI LensMP3 PlayerIoT擴充板

接線:MP3 Player接在第1支腳、Smart AI Lens接在第19、20支腳
積木式程式:

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
def RightWrong(num: number, ans: str):
    global Number2
    if answer[num] == ans:
        mp3Player.set_tracking(8, mp3Player.repeatList.NO)
        basic.show_icon(IconNames.YES)
    else:
        mp3Player.set_tracking(9, mp3Player.repeatList.NO)
        basic.show_icon(IconNames.NO)
    basic.pause(5000)
    Number2 = (Number2 + 1) % 5
    mp3Player.set_tracking(Number2 + 14, mp3Player.repeatList.NO)
    basic.pause(2000)
    basic.show_icon(IconNames.SQUARE)
Number2 = 0
answer: List[str] = []
basic.show_leds("""
    . # . # .
    # # # # #
    . # . # .
    # # # # #
    . # . # .
    """)
basic.pause(500)
mp3Player.mp3_set_serial(SerialPin.P1)
mp3Player.set_volume(25)
PlanetX_AILens.init_module()
PlanetX_AILens.switchfunc(PlanetX_AILens.FuncList.CARD)
basic.show_leds("""
    . . # # #
    . . . # #
    . . . . #
    # # . . .
    # # . . .
    """)
# 0:介紹、1測驗
mode = 0
answer = ["B", "C", "C", "D", "D"]

def on_forever():
    global mode, Number2
    PlanetX_AILens.camera_image()
    if mode == 0 and PlanetX_AILens.number_card(PlanetX_AILens.numberCards.ZERO):
        mp3Player.set_tracking(1, mp3Player.repeatList.NO)
    elif mode == 0 and PlanetX_AILens.number_card(PlanetX_AILens.numberCards.ONE):
        mp3Player.set_tracking(2, mp3Player.repeatList.NO)
    elif mode == 0 and PlanetX_AILens.number_card(PlanetX_AILens.numberCards.TWO):
        mp3Player.set_tracking(3, mp3Player.repeatList.NO)
    elif mode == 0 and PlanetX_AILens.number_card(PlanetX_AILens.numberCards.THREE):
        mp3Player.set_tracking(4, mp3Player.repeatList.NO)
    elif mode == 0 and PlanetX_AILens.number_card(PlanetX_AILens.numberCards.FOUR):
        mp3Player.set_tracking(5, mp3Player.repeatList.NO)
    elif mode == 0 and PlanetX_AILens.number_card(PlanetX_AILens.numberCards.FIVE):
        mp3Player.set_tracking(7, mp3Player.repeatList.NO)
        mode = 1
        Number2 = 0
        basic.pause(5000)
        mp3Player.set_tracking(14, mp3Player.repeatList.NO)
        basic.show_icon(IconNames.SQUARE)
    elif mode == 1 and PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.A):
        mp3Player.set_tracking(10, mp3Player.repeatList.NO)
        RightWrong(Number2, "A")
    elif mode == 1 and PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.B):
        mp3Player.set_tracking(11, mp3Player.repeatList.NO)
        RightWrong(Number2, "B")
    elif mode == 1 and PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.C):
        mp3Player.set_tracking(12, mp3Player.repeatList.NO)
        RightWrong(Number2, "C")
    elif mode == 1 and PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.D):
        mp3Player.set_tracking(13, mp3Player.repeatList.NO)
        RightWrong(Number2, "D")
    elif mode == 1 and PlanetX_AILens.letter_card(PlanetX_AILens.letterCards.E):
        mp3Player.set_tracking(6, mp3Player.repeatList.NO)
        mode = 0
        basic.show_leds("""
            . . # # #
            . . . # #
            . . . . #
            # # . . .
            # # . . .
            """)
    basic.pause(2500)
basic.forever(on_forever)

2025年6月1日 星期日

Ollama+llama3.2完成智慧成績助教系統

本程式是由小鎮智能黃俊毓提供的程式碼改編而成,並經由ChatGPT協助除錯。

1.在ollama啟動llama3.2。


2.智慧成績助教系統的程式
  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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import ollama
from openpyxl import Workbook, load_workbook
import json
import os

# === Function Calling Schema ===
functions = [
    {
        "type":"function",
        "function": {
            "name":"suggest_pass_adjustment",
            "description": "對於 59 分學生,詢問是否補到 60 分",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "學生姓名(name)"
                    }
                 },
                 "required": ["name"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name":"suggest_remedial_adjustment",
            "description": "對於低於 59 分學生,詢問是否補分,以及新分數",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "學生姓名(name)"
                    },
                    "current_score": {"type": "integer"}
                 },
                 "required": ["name", "current_score"]
            }
        }
    },    
    {
        "type": "function",
        "function": {
            "name":"suggest_edit_for_passed",
            "description": "詢問是否要修改分數大於60分,及格學生的分數",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "學生姓名(name)"
                    },
                    "current_score": {"type": "integer"}
                 },
                 "required": ["name", "current_score"]
            }
        }
    }, 
    {
        "type": "function",
        "function": {
            "name":"suggest_apply_adjustment",
            "description": "將學生分數更新為指定的新分數",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "學生姓名(name)"
                    },
                    "new_score": {"type": "integer"}
                 },
                 "required": ["name", "new_score"]
            }
        }
    }, 
]

# === 初始化成績表(如不存在)===
filename = "grades.xlsx"
if not os.path.exists(filename):
    wb = Workbook()
    ws = wb.active
    ws.title = "成績表"
    ws.append(["姓名", "分數"])
    ws.append(["王小明", 59])
    ws.append(["林小美", 73])
    ws.append(["張大文", 45])
    wb.save(filename)
    print(f"✅ 已建立初始成績檔:{filename}")
else:
    print(f"📥 已載入成績檔:{filename}")

# === GPT 對應的處理函式 ===

# 以下四個函式為 GPT 根據不同情況呼叫的處理邏輯
# 建議學生逐步閱讀與理解:輸入、邏輯、Excel 修改方式

def handle_pass_adjustment(row, name):
    ans = input(f"{name} 的分數是 59,是否補及格至 60?(Y/n):")
    if ans.lower() in ["y", "yes", ""]:
        row[1].value = 60
        print(f"✅ {name} 已補及格(60 分)")
        return f"{name} 補及格至 60 分 建議多加強"
    else:
        print("🟡 未調整")
        return f"{name} 未補及格"

def handle_remedial_adjustment(row, name, score):
    ans = input(f"{name} 的分數是 {score},是否補分?(Y/n):")
    if ans.lower() in ["y", "yes", ""]:
        try:
            new_score = int(input("👉 請輸入補分後的新分數:"))
            row[1].value = new_score
            print(f"✅ 已將 {name} 改為 {new_score} 分")
            return f"{name} 補分為 {new_score} 分 建議多加強"
        except:
            print("⚠️ 格式錯誤,略過")
            return f"{name} 補分輸入錯誤,未調整"
    else:
        print("🟡 未調整")
        return f"{name} 未補分"

def handle_edit_for_passed(row, name, score):
    ans = input(f"{name} 成績為 {score} 分,是否要修改?(輸入新分數或按 Enter 跳過):")
    if ans.strip():
        try:
            new_score = int(ans)
            row[1].value = new_score
            print(f"✅ 已將 {name} 改為 {new_score} 分")
            return f"{name} 改為 {new_score} 分"
        except:
            print("⚠️ 格式錯誤,略過")
            return f"{name} 修改輸入錯誤,未調整"
    else:
        print("✅ 保留原分數")
        return f"{name} 保留原分數 {score} 分"

def handle_apply_adjustment(row, name, new_score):
    row[1].value = new_score
    print(f"✅ GPT 決定將 {name} 的分數改為 {new_score}")
    return f"{name} 的分數改為 {new_score}"

# === 以下請同學補寫 main loop ===
# - 使用 for row in ws.iter_rows(min_row=2): 遍歷每位學生
# - 呼叫 GPT 的 chat.completions.create 傳入使用者輸入與 function schema
# - 根據 GPT 回傳的 function_call.name 判斷要呼叫哪一個處理函式
# - 最後將分數儲存為 grades_final.xlsx

# 提示:請將回傳的 arguments 用 json.loads(...) 解析後取出參數

wb = load_workbook(filename)
ws = wb.active

for row in ws.iter_rows(min_row=2):
    name = row[0].value
    score = row[1].value

    user_input = f"{name} 的分數是 {score}"
    
    response = ollama.chat(
        model="llama3.2",
        messages=[{"role": "user", "content": user_input}],
        tools=functions,
    )

    if hasattr(response, "message") and hasattr(response.message, "tool_calls") and response.message.tool_calls:
        tool_call = response.message.tool_calls[0]
        func_name = tool_call['function']['name']
        args = tool_call['function']['arguments']  

        if func_name == "suggest_pass_adjustment":
            result = handle_pass_adjustment(row, name)
        elif func_name == "suggest_remedial_adjustment":
            result = handle_remedial_adjustment(row, name, args['current_score'])
        elif func_name == "suggest_edit_for_passed":
            result = handle_edit_for_passed(row, name, args['current_score'])
        elif func_name == "suggest_apply_adjustment":
            result = handle_apply_adjustment(row, name, args['new_score'])
        else:
            result = "❌ 無對應函式"

        # 後續回應
        follow_up = ollama.chat(
            model="llama3.2",
            messages=[
                {"role": "user", "content": user_input},
                response.message,
                {"role": "tool", "name": func_name, "content": result}
            ]
        )
        print(f"💬 GPT 回覆:{follow_up.message['content']}")
    else:
        print("🤖 GPT 沒有呼叫任何函式")
# === 儲存檔案 ===
final_name = "grades_final.xlsx"
wb.save(final_name)
print(f"\n🎉 所有成績處理完成,已儲存為 {final_name}")

3.執行結果: