2025年12月11日 星期四

[OTTO Ninja]OTTO 忍者機器人

 


忍者機器人:Otto Ninja Starter robot
操作介面:RemoteXY
開源程式:OttoNinja


以下文章是由Grok AI生成,提示詞:https://remotexy.com/ 設計理念和創新商模


RemoteXY 的設計理念與創新商業模式

RemoteXY 是一個專門用於微控制器(如 Arduino、ESP8266、ESP32 等)開發的遠端控制平台,允許使用者透過 Android 或 iOS 行動裝置建立圖形化使用者介面(GUI),輕鬆控制硬體裝置。它支援藍牙、Wi-Fi、以太網、USB OTG 及雲端伺服器等多種連接方式,主要針對 IoT(物聯網)、機器人及智慧家居等應用設計。以下根據官方網站、應用程式描述及相關開發資源,分析其設計理念創新商業模式

設計理念(Design Philosophy)

RemoteXY 的核心設計理念強調簡單化(Simplicity)、快速原型開發(Rapid Prototyping)使用者導向的圖形化控制(Graphical User-Centric Control),旨在降低 IoT 開發門檻,讓非專業程式設計師也能輕鬆建立互動介面。其哲學可歸納為以下幾點:

  • 無程式碼圖形化介面建置(No-Code GUI Builder):使用者透過網站編輯器(https://remotexy.com)拖拉元件(如按鈕、滑桿、圖表、聲音播放器等)來設計自訂 GUI,系統自動生成 Arduino IDE 相容的原始碼模板。這體現「民主化開發」(Democratization of Development)的理念,讓初學者或硬體工程師專注於邏輯而非介面程式碼。官方描述:「使用圖形介面編輯器,你可以製作自己的獨特 GUI 並上傳到板子上。」
  • 跨平台與多連接支援(Cross-Platform & Multi-Connectivity):支援多種硬體板卡(如 Arduino UNO/MEGA、STM32、nRF)及通訊模組(如 HC-05 藍牙、W5100 以太網),並整合雲端伺服器實現「全球隨處控制」(Internet from Anywhere)。這反映出「無縫整合」(Seamless Integration)的原則,強調裝置的靈活性與可擴展性,避免傳統開發的複雜佈線與相容性問題。創新點包括「雲端連接測試」,允許裝置從世界任何角落透過網際網路管理。
  • 創新功能導向使用者體驗(Innovative Features for Enhanced UX):如新增「線上圖表」(Online Graph)顯示即時變數變化、「聲音效果播放」(Sound Element)及滑桿自動居中等,這些設計源自使用者反饋,體現迭代式開發(Iterative Design)的理念。平台與 Arduino IDE、FLProg 等工具無縫整合,促進「快速測試與部署」(Rapid Testing & Deployment)。相較競爭對手如 MIT App Inventor,RemoteXY 更專注於硬體控制的即時性與低延遲。

總體而言,RemoteXY 的設計哲學受開源精神啟發(如 Arduino 社群),但更注重「行動端原生控制」(Mobile-Native Control),讓 IoT 從原型到產品化的流程更直觀,適合教育、DIY 愛好者及小型開發團隊。

創新商業模式(Innovative Business Model)

RemoteXY 採用Freemium 模式(免費增值)結合開源生態系統(Open-Source Ecosystem),這是其商業創新的核心,借鏡 Arduino 的「一切免費分享」哲學,但透過專業工具及雲端服務創造付費價值。該模式不僅降低進入障礙,還透過社群貢獻驅動成長。

  • Freemium 結構
    • 免費版(RemoteXY: Arduino control):基本功能免費,包括 GUI 編輯器、行動 App 下載及標準連接(如藍牙、Wi-Fi)。這吸引大量初學者與社群使用者,建立使用者基數。App 在 Google Play 上免費提供,強調「安裝 App 並遵循裝置開發者指示連接」。
    • 付費版(RemoteXY: Arduino control PRO):進階功能需付費購買 App(如 Google Play 上售價約數美元),解鎖雲端伺服器無限連接、更多元件庫及專業支援。這形成「基本免費、進階付費」的漏斗模式,轉換率依賴免費版的黏著度。
  • 開源與社群驅動(Open-Source & Community-Driven):平台提供完整程式庫(Library)給 Arduino IDE 使用,鼓勵開發者分享專案(如機器人控制、智慧家居範例)。這類似 Arduino 的商業模式:「團隊建立了一個基於免費分享的公司,發布所有設計檔案。」 創新在於與 Kickstarter 等眾籌平台合作(如 AirBoard 專案),將 RemoteXY PRO 作為 IoT 開發套件的一部分,透過後援者融資擴大曝光。
  • 擴展收入來源(Revenue Diversification):除了 App 銷售,潛在包括雲端訂閱(無限裝置管理)及合作授權(如與硬體廠商整合)。目標用戶為 DIY 發燒友、教育機構及小型 IoT 企業,模式強調「可擴展性」(Scalability),隨著使用者成長而產生網絡效應(Network Effects)。

此商業模式的創新之處在於低成本獲客 + 高價值轉換:免費工具吸引全球社群,付費解鎖專業功能,預估轉換率高於純開源模式。相較傳統軟體銷售,它更像「SaaS for Hardware」,結合雲端與行動生態。

總結與建議

RemoteXY 的設計理念以「簡單即創新」為本,商業模式則透過 Freemium 實現可持續成長,完美契合 IoT 市場的快速迭代需求。若欲深入應用,建議從官方編輯器起步,建置一個簡單的 LED 控制專案。若有特定功能需求(如整合特定硬體),可參考其部落格更新。 這不僅降低了開發成本,還開啟了無限創意可能。

無裝置程式教育:micro:bit第一堂-剪刀、石頭、布遊戲設計

本課程在虎尾鎮安慶國小試教。網址:https://www.facebook.com/share/v/17mBrum3fb/


 
學生端程



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
def on_received_number(receivedNumber):
    global select, mode
    select = 0
    mode = receivedNumber
radio.on_received_number(on_received_number)

def on_button_pressed_a():
    global mode
    if select == 0:
        if mode == 3:
            basic.show_icon(IconNames.HAPPY)
    else:
        mode = (mode + 1) % 7
        basic.show_number(mode)
input.on_button_pressed(Button.A, on_button_pressed_a)

def on_gesture_shake():
    global step, Number2
    if select == 0:
        if mode == 4:
            step += 1
            basic.show_number(step)
        elif mode == 6:
            Number2 = randint(0, 2)
            if Number2 == 0:
                basic.show_icon(IconNames.SMALL_SQUARE)
            elif Number2 == 1:
                basic.show_icon(IconNames.SQUARE)
            else:
                basic.show_icon(IconNames.SCISSORS)
input.on_gesture(Gesture.SHAKE, on_gesture_shake)

def on_logo_pressed():
    global select
    select = 1
    basic.show_icon(IconNames.ANGRY)
input.on_logo_event(TouchButtonEvent.PRESSED, on_logo_pressed)

def on_received_string(receivedString):
    global name
    name = receivedString
radio.on_received_string(on_received_string)

def on_button_pressed_b():
    global select
    if select == 0:
        if mode == 3:
            basic.show_icon(IconNames.SAD)
    else:
        select = 0
        basic.show_icon(IconNames.YES)
input.on_button_pressed(Button.B, on_button_pressed_b)

Number2 = 0
select = 0
step = 0
name = ""
mode = 0
id2 = 1
mode = 0
name = "Q-Robot AI"
step = 0
select = 0
basic.show_number(id2)
basic.pause(2000)
radio.set_group(212)

def on_forever():
    if select == 0:
        if mode == 0:
            basic.show_number(id2)
        elif mode == 1:
            basic.show_string(name)
        elif mode == 2:
            basic.show_icon(IconNames.HEART)
            basic.pause(500)
            basic.show_icon(IconNames.SMALL_HEART)
            basic.pause(500)
        elif mode == 5:
            if input.light_level() <= 10:
                basic.show_leds("""
                    # # # # #
                    # # # # #
                    # # # # #
                    # # # # #
                    # # # # #
                    """)
            else:
                basic.clear_screen()
basic.forever(on_forever)


2025年11月10日 星期一

[水井USR創新教材]Python一下用爬蟲技術取得環境開放式資料

API使用說明:https://data.moenv.gov.tw/paradigm

範例一:簡易介接

1
2
3
4
5
6
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
r=requests.get('https://data.moenv.gov.tw/api/v2/acidr_p_01?format=json&offset=0&limit=5&api_key=KEY值請申請', verify=False)
print(r.text)

執行結果:
{
    "fields": [
        {
            "id": "siteid",
            "type": "text",
            "info": {
                "label": "測站代碼"
            }
        },
        {
            "id": "sitename",
            "type": "text",
            "info": {
                "label": "測站中文名稱"
            }
        },
<未完>

範例二:使用DataFrame

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import requests
import urllib3
import pandas as pd
import json

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
r=requests.get('https://data.moenv.gov.tw/api/v2/acidr_p_01?format=json&offset=0&limit=5&api_key=申請的KEY值', verify=False)
print(r.text)

data = json.loads(r.text) 
records = data.get("records", [])
df = pd.DataFrame(records)
print(df.head())
df_selected = df[[
    "sitename", "county", "township", "monitor_date",
    "item_name", "result_value", "item_unit"
]]

print(df_selected.head())

執行結果:



  siteid sitename site_ename  ... item_ename result_value item_unit
0     78       馬公     Magong  ...   RAIN_SUM            0        mm
1     77       金門     Kinmen  ...   RAIN_SUM            0        mm
2     75       馬祖      Matsu  ...   RAIN_SUM            0        mm
3     66       冬山   Dongshan  ...   RAIN_SUM            0        mm
4     64       陽明   Yangming  ...   RAIN_SUM            0        mm

[5 rows x 16 columns]
  sitename county township monitor_date item_name result_value item_unit
0       馬公    澎湖縣      馬公市     20211230      雨量累計            0        mm
1       金門    金門縣      金城鎮     20211230      雨量累計            0        mm
2       馬祖    連江縣      南竿鄉     20211230      雨量累計            0        mm
3       冬山    宜蘭縣      冬山鄉     20211230      雨量累計            0        mm
4       陽明    臺北市      北投區     20211230      雨量累計            0        mm

[水井USR創新教材]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
import requests
import urllib3
import json

# 關閉 InsecureRequestWarning
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = 'https://data.moa.gov.tw/Service/OpenData/FromM/AquaticTransData.aspx'
r = requests.get(url, verify=False)

# 檢查 HTTP 狀態
if r.status_code != 200:
    print(f"資料取得失敗,HTTP 狀態碼:{r.status_code}")
else:
    try:
        text = json.loads(r.text)
        for row in text:
            # 有些資料可能沒有「魚貨名稱」或「市場名稱」,需先檢查鍵存在
            if '魚貨名稱' in row and '市場名稱' in row:
                if '吳郭魚' in row['魚貨名稱'] and '斗南' in row['市場名稱']:
                    print('交易日期:' + str(row.get('交易日期', '無資料')))
                    print('品種代碼:' + str(row.get('品種代碼', '無資料')))
                    print('魚貨名稱:' + str(row.get('魚貨名稱', '無資料')))
                    print('市場名稱:' + str(row.get('市場名稱', '無資料')))
                    print('上價:' + str(row.get('上價', '無資料')))
                    print('中價:' + str(row.get('中價', '無資料')))
                    print('下價:' + str(row.get('下價', '無資料')))
                    print('交易量:' + str(row.get('交易量', '無資料')))
                    print('平均價:' + str(row.get('平均價', '無資料')))
                    print('-' * 40)
    except json.JSONDecodeError as e:
        print("JSON 解析錯誤:", e)
    except Exception as e:
        print("發生錯誤:", e)


執行結果: 交易日期:1141109 品種代碼:1011 魚貨名稱:吳郭魚 市場名稱:斗南 上價:76.0 中價:63.6 下價:51.9 交易量:1631.0 平均價:63.8 ---------------------------------------- 交易日期:1141108 品種代碼:1011 魚貨名稱:吳郭魚 市場名稱:斗南 上價:76.7 中價:74.8 下價:72.5 交易量:1751.3 平均價:74.7 ---------------------------------------- 交易日期:1141107 品種代碼:1011 魚貨名稱:吳郭魚 市場名稱:斗南 上價:76.0 中價:66.6 下價:52.0 交易量:1636.3 平均價:65.6 ---------------------------------------- 交易日期:1141106 品種代碼:1011 魚貨名稱:吳郭魚 市場名稱:斗南 上價:76.0 中價:67.2 下價:59.3 交易量:1643.7 平均價:67.4 ---------------------------------------- 交易日期:1141105 品種代碼:1011 魚貨名稱:吳郭魚 市場名稱:斗南 上價:76.0 中價:63.4 下價:56.0 交易量:1593.1 平均價:64.5 ---------------------------------------- 交易日期:1141104 品種代碼:1011 魚貨名稱:吳郭魚 市場名稱:斗南 上價:76.0 中價:70.8 下價:63.6 交易量:1812.5 平均價:70.4 ---------------------------------------- 交易日期:1141102 品種代碼:1011 魚貨名稱:吳郭魚 市場名稱:斗南 上價:76.0 中價:64.0 下價:48.8 交易量:1704.5 平均價:63.4 ---------------------------------------- 交易日期:1141101 品種代碼:1011 魚貨名稱:吳郭魚 市場名稱:斗南 上價:76.0 中價:69.5 下價:58.1 交易量:1790.1 平均價:68.5 ----------------------------------------

[水井USR創新教材]Python一下用水井村故事學會 locals() 和 globals()

以下資訊是由ChatGPT協助生成: 

在程式世界裡,

  • globals() 就像整個「水井村的地圖」🌏,記錄所有村子裡的名字(變數、函式、物件……)

  • locals() 就像「屋子裡的物品清單」🏠,只記錄某個房間(函式內部)的變數。

🏡範例 一、水井村全景地圖:globals()

水井村有許多居民與地標:烏龜、白馬、姻緣花。
這些都是「全域變數」,大家都能看到。

程式:
1
2
3
4
5
6
7
8
# 水井村的全景地圖
village = "水井村"
turtle = "烏龜"
horse = "白馬"
flower = "姻緣花"

print("=== 水井村的全景地圖(globals) ===")
print(globals().keys())  # 查看整個村子的地圖上有誰

執行結果:
=== 水井村的全景地圖(globals) ===
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', 'village', 'turtle', 'horse', 'flower'])

💡說明:
globals() 會回傳一個字典(dict),裡面有所有全域變數的名字與內容。
在這裡,它就像是整座村莊的登記簿。

🕳️ 範例二、進入烏龜的房間:locals()

現在,我們走進烏龜的房間。
裡面有牠自己的小物品(在地變數),這些東西村外的人看不到。

程式:

1
2
3
4
5
6
7
def turtle_room():
    food = "萵苣葉"
    toy = "石頭球"
    print("=== 烏龜房間裡的物品(locals) ===")
    print(locals())  # 查看房間裡的變數有哪些

turtle_room()

執行結果:
=== 烏龜房間裡的物品(locals) ===
{'food': '萵苣葉', 'toy': '石頭球'}

💡說明:
locals() 只會顯示在函式內部定義的變數。
離開房間後,這些變數就不再存在。

🧭 範例三、結合使用:房間內外的界線
程式:
1
2
3
4
5
6
7
8
treasure = "金井水"

def white_horse():
    tool = "竹掃帚"
    print("房間內能看到 globals 嗎?", "treasure" in globals())
    print("房間內能看到 locals 嗎?", "tool" in locals())

white_horse()

執行結果:
房間內能看到 globals 嗎? True
房間內能看到 locals 嗎? True

🧠 意思是:
函式裡面可以看到全村的變數(globals())。
但全村看不到你房間裡的私人物品(locals())。

💬 結論整理
名稱 範圍 比喻 說明
globals() 全域範圍 🌏 水井村地圖 存放所有村莊共用的資源
locals() 區域範圍 🏠 烏龜房間 存放函式內部的私有變數
可視範圍 函式內可看全域,全域看不到區域 🚪 單向門 保護變數,避免混亂

🎯 延伸任務(課堂練習)

請同學完成:
定義一個 love_story() 函式,裡面有 boy = "白馬"、girl = "姻緣花"。
  1. 在函式中列印出 locals()。
  2. 在外部列印出 globals()。
  3. 思考:函式外部能看到 boy 和 girl 嗎?

[水井USR創新教材]Python一下用水井三寶認識 Python 的 os 模組



以下教材是用ChatGPT生成。

在程式的世界裡,os 模組就像是我們在電腦裡旅行時的「導遊」,幫我們找到路、開資料夾、搬東西、檢查有沒有東西存在。

我們來看看三位主角——烏龜、白馬、姻緣花——是怎麼幫助我們理解 os 模組的!

範例一:🐢 烏龜:穩重的導覽員(os.getcwd / os.chdir)

烏龜總是走得慢但很穩,它最擅長「知道自己在哪裡」。

程式碼:

1
2
3
4
5
6
7
8
import os

# 烏龜看看自己現在在哪個資料夾
print("烏龜現在在:", os.getcwd())

# 烏龜想要換到另一個資料夾
os.chdir("C:/Users")  # 換路徑
print("烏龜移動後的位置:", os.getcwd())
執行結果:
烏龜現在在: C:\Users\cheng-min\AppData\Local\Programs\Python\Python313
烏龜移動後的位置: C:\Users

🗣 烏龜的智慧:

「知道當前路徑,才能慢慢走向下一個目標。」

範例二:🐎 白馬:行動派的搬運工(os.listdir / os.mkdir / os.remove)

白馬速度快、效率高,最擅長整理與搬運檔案。
程式碼:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import os

# 白馬看看這裡有哪些東西
print("白馬看到的東西:", os.listdir())

# 白馬替主人開一個新資料夾
os.mkdir("姻緣花資料夾")

# 白馬幫忙確認檔案有沒有存在後再刪掉
if os.path.exists("舊檔案.txt"):
    os.remove("舊檔案.txt")

執行結果:
白馬看到的東西: ['AquaticTransData.docx', 'AquaticTransData.xlsx', 'DLLs', 'Doc', 'grades.xlsx', 'grades_final.xlsx', 'guess.py', 'include', 'Lib', 'libs', 'LICENSE.txt', 'NEWS.txt', 'ollama1.py', 'ollama2.py', 'ollama3.py', 'ollama4.py', 'open.py', 'open2.py', 'python.exe', 'python3.dll', 'python313.dll', 'pythonw.exe', 'Scripts', 'suijing_sanbao', 'tcl', 'template.docx', 'template.py', 'test.py', 'test2.py', 'ttsx.py', 'vcruntime140.dll', 'vcruntime140_1.dll', '__pycache__']

🗣 白馬的智慧:

「行動力就是力量!想整理資料,就用我的蹄子吧~」

範例三:🌸 姻緣花:串起緣分的橋樑(os.path.join / os.path.exists)

姻緣花最會「牽線」——她能把不同的路徑串起來,讓一切有緣相會。

程式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import os

# 姻緣花幫兩段路徑牽在一起
path = os.path.join("C:/Users", "Documents", "水井村")
print("姻緣花牽起的路徑:", path)

# 看看這條緣分路上是否真的存在
if os.path.exists(path):
    print("這條路是真的有緣~")
else:
    print("緣分還沒到,資料夾不存在。")

執行結果:
姻緣花牽起的路徑: C:/Users\Documents\水井村
緣分還沒到,資料夾不存在。

🗣 姻緣花的智慧:

「連結,是一切的開始。」

💡小結論

角色功能對應 os 模組函式
🐢 烏龜了解與改變當前位置os.getcwd()os.chdir()
🐎 白馬整理、建立與刪除資料os.listdir()os.mkdir()os.remove()
🌸 姻緣花串接路徑與檢查存在os.path.join()os.path.exists()

🎯延伸任務(適合課堂練習)

請同學們寫一個「水井三寶自動巡邏程式」:
  1. 讓烏龜顯示目前位置。
  2. 讓白馬建立一個新資料夾 三寶任務。
  3. 讓姻緣花檢查資料夾是否存在並印出結果。