Skip to content

s881215/EOS-Project

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🌱 Smart Plant Monitoring and Automated Watering System

本專案是一個「智慧盆栽監測 + 自動/手動澆水」系統,整合 感測器資料擷取Linux Driver(Kernel Module)跨網路 TCP 通訊Web 介面遠端控制,並提供完整的歷史紀錄(含自動/手動澆水事件)。

✅ 兩大功能
自動模式:定期讀取感測器;土壤太乾時自動澆水,適中則不動作。
手動模式:確認與 Server 連線狀態、遠端查看目前狀態/歷史紀錄、遠端觸發澆水(觸發前先回報當下土壤濕度並二次確認)。


目錄


系統總覽

本系統主要由 兩台 Raspberry Pi + 使用者裝置 組成,並透過 虛擬網路(如 ZeroTier) 讓使用者可以跨網段直接以瀏覽器存取 Web UI。

  1. Web Server RPi(控制中心 / Web UI + Server)
  • web_server.c:提供簡易 HTTP 網頁按鈕介面(查詢/澆水/歷史紀錄…)
  • client.c:接收網頁端的需求,轉成 TCP 指令送給 server.c
  • server.c(本 repo 為 server1.c):核心 server,同時接收
    • Sensor RPi 上傳的感測資料(記錄到 sensor_log.csv
    • 使用者查詢/控制指令(回覆目前狀態、歷史紀錄、最後澆水時間、手動澆水…)
  1. Sensor RPi(感測與執行端)
  • Kernel drivers:
    • timer_air_temp_driver.c:DHT22 溫溼度讀取(timer + workqueue)
    • timer_soil_sensor.c:ADS1115 I2C ADC 讀取土壤感測電壓(timer polling)
    • pump_driver.c:GPIO 控制水泵/繼電器(char device)
  • User-space tasks:
    • timer_sensor_task.c(簡報:sensor_task.c):定時讀取 /dev/* 並更新 Shared Memory
    • decision_task.c:自動澆水決策 + 接收手動澆水訊號(SIGUSR1)
    • remote_task.c:與 Web Server RPi 通訊,上傳資料、接收澆水命令
  1. 使用者裝置(手機/平板/筆電)
  • 透過瀏覽器連線到 Web Server RPi 的 http://<virtual_ip>:8181/
  • 以按鈕觸發查詢與澆水(澆水前會先顯示土壤濕度並二次確認)

📌 虛擬網路重點:ZeroTier/Remote.It 類工具會替每台設備分配「Virtual IP」,讓不同網段的裝置像在同一個 LAN 中互連(簡報中以 ZeroTier 示意)。

功能特色

  • 自動澆水決策(條件可調整)
  • 手動遠端澆水(二次確認防誤觸)
  • 即時顯示:溫度/濕度/土壤濕度、Pump 狀態
  • 歷史紀錄:最近 N 筆資料、上次澆水時間(含 Auto/Manual)
  • OS 技巧實作
    • Linux Kernel Module:char device、GPIO、I2C、timer/workqueue、mutex
    • User-space:POSIX shared memory、mmap、signals、pthread、select、多 socket 端口
    • Web:手刻 HTTP server、fork/exec、IPC pipe

系統架構

以下為「簡報版」的架構補充:包含 連線拓樸(使用者裝置 ↔ Web Server RPi ↔ Sensor RPi)與 程式/task 架構(Shared Memory + Signal + Driver)。

1) 連線架構(使用者裝置 & RPi 連線環境)

  • 使用者(手機/平板/筆電)透過瀏覽器進入 Web UI
  • Web UI 由 Web Server RPi 提供(web_server.c
  • Web Server RPi 與 Sensor RPi 透過虛擬網路互連(ZeroTier 示意),Sensor RPi 週期上傳感測資料並接收澆水命令

Network Architecture (from slides)

2) Port 規劃(程式預設)

角色 Port 用途
Web Server RPi(server) 55666 接收 client.c 的查詢/控制命令
Web Server RPi(server) 8888 接收 Sensor RPi 上傳資料(struct/binary)
Sensor RPi(remote_task) 8889 接收「澆水觸發」命令
Web Server RPi(web_server) 8181 提供瀏覽器操作介面

⚠️ IP/Port 目前多為硬編碼,跨網路部署時請務必調整(見 設定與客製化)。


3) 程式架構(task 之間的關係)

  • Shared Memory (/plant_shm)sensor_task 定時更新,decision_taskremote_task 直接讀取
  • Signals
    • SIGUSR1:Web 端手動澆水 → Sensor RPi 觸發 decision_task 執行澆水
    • SIGUSR2:自動澆水事件通知(decision_taskremote_task),讓 server 記錄 Auto Watering log
  • Drivers
    • air_temp_driver/soil_sensor_driver 提供最新感測值給 user-space
    • pump_driver 提供最小化 GPIO 控制界面(配合繼電器驅動水泵)

Program / Task Architecture (from slides)


4)(Mermaid)概念版流程圖

下圖是對簡報內容的「文字化」對照,方便在 GitHub 直接閱讀。

flowchart LR
  U["User Devices\nBrowser"] -->|HTTP:8181| WEB["Web Server RPi\nweb_server.c"]
  WEB -->|exec ./client| CLI["client.c"]
  CLI -->|TCP commands| S[(server.c / server1.c)]
  S -->|reply status/history| CLI

  subgraph Sensor["Sensor RPi"]
    DHT["/dev/dht22\nair_temp_driver"]
    SOIL["/dev/soil_sensor\nADS1115 driver"]
    PUMP["/dev/etx_device\npump_driver"]
    ST["sensor_task\n(timer_sensor_task.c)"]
    DEC["decision_task.c"]
    REM["remote_task.c"]
    SHM[(Shared Memory\n/plant_shm)]

    DHT --> ST
    SOIL --> ST
    ST --> SHM
    SHM --> DEC
    SHM --> REM
    DEC -->|write 1/0| PUMP
    REM -->|SIGUSR1 manual| DEC
    DEC -->|SIGUSR2 auto event| REM
  end

  REM -->|sensor data| S
  S -->|manual watering cmd| REM
  S -->|log to CSV| LOG[(sensor_log.csv)]
Loading

硬體與接線

以下接線依照簡報中的實作圖(並對齊程式碼中的 GPIO/I2C 設定)。

Wiring (from slides)

1) DHT22(空氣溫溼度)

  • VCC → Raspberry Pi 3.3V(或依模組需求 5V)
  • GND → Raspberry Pi GND
  • DATAGPIO4(程式:timer_air_temp_driver.c#define DHT_GPIO 4
  • 建議 DATA 加 4.7k~10k 上拉至 VCC(符合 DHT22 典型接法)

2) 土壤濕度(類比感測器 + ADS1115)

  • 土壤感測器(Analog out)→ ADS1115 A0
  • ADS1115 ↔ Raspberry Pi(I2C-1)
    • SDAGPIO2 (SDA1)
    • SCLGPIO3 (SCL1)
    • VDD → 3.3V
    • GND → GND
    • ADDR → GND(對應位址 0x48,程式:I2C_BOARD_INFO("ads1115", 0x48)

簡報圖中 ADDR 有接線(通常接地),這與程式碼使用 0x48 完整對上。

3) 水泵控制(繼電器 / Relay + Pump)

  • Raspberry Pi 透過 GPIO 控制繼電器,繼電器再去切換水泵電源(避免 GPIO 電壓/電流不足)
  • Relay VCC → 5V(依繼電器模組規格)
  • Relay GND → GND
  • Relay INGPIO21(程式:pump_driver.c#define GPIO_21 21
  • 水泵電源與繼電器 COM/NO 端子依你的泵/電源規格接線(注意共地與反灌電流保護)

4) Pin Mapping(建議直接貼在 README 方便查)

模組 Pi 端腳位 程式對應
DHT22 DATA GPIO4 timer_air_temp_driver.c
ADS1115 SDA/SCL GPIO2 / GPIO3 (I2C-1) timer_soil_sensor.c
ADS1115 ADDR GND(0x48) timer_soil_sensor.c
Relay IN(Pump) GPIO21 pump_driver.c

Repository 結構

(以下以本次上傳的檔案命名為主)

.
├── server1.c                  # 中央 server:收感測 + 收 client cmd + 記錄 csv
├── client.c                   # client:發送文字 cmd 到 server1
├── web_server.c               # 簡易 HTTP server:網頁按鈕 -> 呼叫 client -> 回傳結果
│
├── docs/
│   ├── architecture_network.png   # (from slides) 使用者裝置 ↔ Web/Sensor RPi 拓樸
│   ├── architecture_tasks.png     # (from slides) task/driver/IPC 架構
│   └── wiring.png                 # (from slides) 硬體接線圖
├── timer_air_temp_driver.c    # Kernel module:DHT22 driver(timer + workqueue)
├── timer_soil_sensor.c        # Kernel module:ADS1115 soil driver(timer polling)
├── pump_driver.c              # Kernel module:GPIO21 pump char driver
│
├── timer_sensor_task.c        # user daemon:讀 /dev/* -> 寫入 shared memory
├── decision_task.c            # user daemon:自動澆水決策 + manual(SIGUSR1) 澆水
├── remote_task.c              # user daemon:上傳資料到 server + 接收澆水命令(8889)
│
└── sensor_rpi_run_timer.sh    # 一鍵載入模組 + tmux 同時跑三支 user 程式```

---

## 快速開始

> 以下指令示意以 Linux / Raspberry Pi OS 為主。  
> Kernel module 載入/卸載需要 root 權限(`sudo`)。

### A) Web Server RPi / VM 端(server1/server + client + web_server)

> 簡報中此三支程式部署在 **Web Server RPi**(同一台負責 Web UI 與核心 server)。若你改放在 VM/PC 端,流程不變,只需調整 IP/Port。

#### 1) 編譯
```bash
gcc -O2 -Wall -o server1 server1.c
gcc -O2 -Wall -o client client.c
gcc -O2 -Wall -o web_server web_server.c

2) 啟動 server

./server1
# Server listening on ports 55666 (client) and 8888 (sensor)...

3) 啟動 Web UI

./web_server
# 預設監聽 8181

用瀏覽器開啟:

  • http://<web_server_ip>:8181/

B) Sensor RPi 端(Kernel Modules + 任務程式)

1) 編譯 Kernel Modules(示例 Makefile)

在每個 driver 檔案同目錄準備 Makefile(可共用):

obj-m += timer_air_temp_driver.o
obj-m += timer_soil_sensor.o
obj-m += pump_driver.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

編譯:

make

2) 編譯 user-space 程式

gcc -O2 -Wall -pthread -o timer_sensor_task timer_sensor_task.c
gcc -O2 -Wall -pthread -o remote_task remote_task.c
gcc -O2 -Wall -o decision_task decision_task.c

3) 一鍵啟動

chmod +x sensor_rpi_run_timer.sh
./sensor_rpi_run_timer.sh

此 script 會:

  • rmmod/insmod 重新載入三個 .ko
  • 清除 shared memory 與 pid 檔
  • 用 tmux 同時跑:
    • timer_sensor_task(sudo)
    • remote_task
    • decision_task(sudo)

各程式/模組詳細說明

1) server.c / server1.c — 核心 Server(接收感測資料 + 接收控制指令 + 寫入日誌)

角色

  • 同時監聽兩個 port(select() 多工)
    • CLIENT_PORT=55666:接收文字命令(connect/current/history/lastwater/watering)
    • SENSOR_PORT=8888:接收 Sensor RPi 上傳的 SensorData 結構(binary)

核心機制

  • select():同一個 thread 監聽兩個 listening socket
  • latest_data:快取最新一筆感測資料供 current 查詢
  • sensor_log.csv:所有資料落地(含 Auto/Manual Watering 標記)

支援命令(與 client.c 對應)

  • checking......\n → 回覆 Server connection OK.
  • current state of plant...\n → 回覆最新溫/濕/土壤濕度
  • watering......!\n → 記錄 Manual Watering,並轉發到 Sensor RPi: 172.26.213.194:8889
  • historical record...\n → 回覆最近 20 筆紀錄(HISTORY_LINES=20
  • last watered time......\n → 回覆上一次 Auto/Manual Watering 的 timestamp
  • watering check\n → 回覆「目前土壤濕度 + 是否仍要澆水」

⚠️ 注意:程式啟動時會用 "w" 重新建立 sensor_log.csv(會覆蓋舊檔)。若希望保留舊資料,請改為 "a" 或加上日期檔名。


2) client.c — 指令用 TCP Client

角色

  • argv[1] 決定要送的命令字串,連線到固定 SERVER_IP/SERVER_PORT

可用指令

./client connect
./client current
./client watering_check
./client watering_force
./client history
./client lastwater

⚠️ SERVER_IPSERVER_PORT 是硬編碼(預設 172.26.107.26:55666),跨網路部署時務必修改。


3) web_server.c — 輕量 HTTP Server(Web UI)

角色

  • WEB_SERVER_PORT=8181 提供網頁按鈕介面
  • 收到 GET /client?cmd=... 後:
    1. fork()
    2. 子行程 execl("./client", "./client", arg, NULL)
    3. 透過 pipe() 把 client 的 stdout 抓回來
    4. 回傳給瀏覽器(text/plain 或 HTML)

二次確認澆水

  • 首次按「Watering」→ cmd=watering_check
    • 先顯示 The Soil Moisture status is XX %. Still watering?
    • 提供 Yes -> cmd=watering_force / No -> 回首頁
  • cmd=watering_force 會真正觸發 client watering_force → 送出 watering......!\n

OS 技巧

  • fork + exec + pipe + dup2:把子行程輸出當成 Web API 的 response
  • 無需額外 framework,單檔就能跑的 demo 型 Web 控制台

4) timer_air_temp_driver.c — DHT22 Driver(Kernel Module)

做了什麼

  • 以 GPIO bit-banging 讀 DHT22 的 40-bit 資料(含 checksum)
  • 使用 Timer + Workqueue 避免在 Timer callback 中做長時間 busy-wait
  • INTERVAL_SEC=2 秒更新一次快取值

提供的介面

  • 建立 char device:/dev/dht22
  • user-space read() 會得到字串:
    • Temp:[25.3], Hum:[60.4]

OS 技巧

  • timer_list:週期性排程
  • work_struct:把耗時工作丟到 process context
  • mutex:保護快取溫溼度資料,避免 read 與更新互搶

5) timer_soil_sensor.c — ADS1115 Soil Sensor Driver(Kernel Module)

做了什麼

  • 透過 Linux I2C API 與 ADS1115 溝通:
    • 送 config 到 register 0x01
    • 讀 conversion register 0x00
  • 每 1 秒更新一次 latest_voltage_mv
  • read() 直接回傳快取電壓(字串,單位 Volt)

提供的介面

  • 建立 char device:/dev/soil_sensor(device 名稱依 DEVICE_NAME
  • 回傳格式:
    • 2.345\n(表示 2.345V)

⚠️ 你目前的 timer_sensor_task.c 使用 /dev/timer_soil_sensor,若實際節點是 /dev/soil_sensor,請把路徑改一致。


6) timer_sensor_task.c — 感測資料整合(user-space daemon)

做了什麼

  • 讀取:
    • /dev/dht22
    • /dev/soil_sensor(或 /dev/timer_soil_sensor,視你的 device node)
  • 土壤電壓轉濕度百分比(線性換算):
    • 1.333V = 100%3.300V = 0%
  • 以 POSIX SHM 建立共享記憶體:
    • shm_open("/plant_shm")
    • mmap() 讓其他程序(decision/remote)直接讀取最新狀態

OS 技巧

  • POSIX Shared Memory:跨 process 的高效共享
  • mmap:避免使用檔案/pipe 反覆序列化

7) pump_driver.c — Pump GPIO Driver(Kernel Module)

做了什麼

  • 建立 char device:/dev/etx_device
  • write() 接收 '1'/'0' 控制 GPIO21 高/低
  • read() 回傳 GPIO21 目前狀態(0/1)

OS 技巧

  • 完整 char device life-cycle:
    • alloc_chrdev_region / cdev_add
    • class_create / device_create
    • copy_from_user / copy_to_user
  • GPIO API:gpio_request / gpio_set_value / gpio_get_value
  • 也示範 gpio_export() 讓 GPIO 出現在 sysfs(便於 debug)

8) decision_task.c — 自動澆水決策 + 手動澆水(user-space daemon)

資料來源

  • /plant_shm 讀取最新感測資料(mmap 共享記憶體)

自動澆水條件(可改)

soil_moisture < 60
temperature   > 23.0
humidity      < 60.0

執行澆水

  • 呼叫 write_gpio(1) 開啟 Pump → sleep(5)write_gpio(0) 關閉
  • 自動澆水時:
    • shared_data->water_flag = 1
    • kill(remote_pid, SIGUSR2) 通知 remote_task(讓下一次上傳帶上 Auto flag)

手動澆水

  • 收到 SIGUSR1 → 澆水 5 秒

OS 技巧

  • Signal:SIGUSR1(manual)、SIGUSR2(同步 auto flag)
  • IPC:pid 檔(decision_pid.txt/remote_pid.txt)讓 process 互相找到對方 pid
  • 透過 driver node /dev/etx_device 控制硬體

9) remote_task.c — 上傳資料 + 接收遠端澆水命令(user-space daemon)

兩個執行緒

  1. sensor_sender_thread

    • 每 2 秒連線到 Server DATA_PORT=8888
    • 傳送 SensorData struct(binary)
    • 送完把 water_flag 清成 0
  2. watering_receiver_thread

    • 在本機 CMD_PORT=8889 listen
    • 收到 watering......!\nkill(decision_pid, SIGUSR1) 觸發手動澆水

OS 技巧

  • pthread:同時做「週期上傳」與「命令接收」
  • TCP server/client 雙角色
  • Signal handler:SIGUSR2 收到 auto watering 通知時把 water_flag 設 1

10) sensor_rpi_run_timer.sh — 一鍵部署腳本

做的事情:

  • 重新載入三個 kernel module(避免舊狀態殘留)
  • 清除 /dev/shm/plant_shm 與 pid 檔
  • 使用 tmux 同時啟動三支 daemon,方便同時看 log:
    • timer_sensor_task
    • remote_task
    • decision_task

資料格式與紀錄

1) SensorData(Server 端接收的結構)

Server 端 server1.cSensorData

typedef struct {
    float temperature;
    float humidity;
    int   soil_moisture;
    time_t timestep;
    int water_flag;   // 1 表示 Auto Watering 事件
} SensorData;

⚠️ 你的 timer_sensor_task.c 目前 struct 少了 water_flag 欄位。
目前做法「能動」是因為 remote/decision 會自行維護 water_flag,但建議統一 struct 版本(見 能精進的部分)。

2) sensor_log.csv

  • 正常資料:timestamp, ip, T, H, M
  • 自動澆水:同一行末尾加上 "<-- Auto Watering"
  • 手動澆水:寫入 "<-- Manual Watering"(T/H/M 會填 -1)

設定與客製化

IP 與 Port(硬編碼位置)

  • client.c
    • SERVER_IP
    • SERVER_PORT
  • server1.c
    • CLIENT_PORT=55666
    • SENSOR_PORT=8888
    • 轉發澆水到 Sensor RPi 的 IP:172.26.213.194:8889
  • remote_task.c
    • SERVER_IP
    • DATA_PORT=8888
    • CMD_PORT=8889
  • web_server.c
    • WEB_SERVER_PORT=8181

自動澆水條件

  • decision_task.cshould_water()
    你可以改成更貼近植物需求的策略,例如:
  • 加入「澆水冷卻時間」(避免短時間連續澆水)
  • 僅以土壤濕度判斷,或加入光照/季節補償

疑難排解

  • /dev 節點不存在

    • 確認 .ko 是否成功 insmod
    • dmesg | tail -n 50
    • lsmod | grep -E "dht22|soil|pump|timer"
  • permission denied

    • 讀寫 /dev/* 或載入 module 需要 sudo
    • 建議把程式改成 systemd service + udev rule(見加分項)
  • 土壤 device node 名稱不一致

    • timer_soil_sensor.c 建立的是 /dev/soil_sensor
    • timer_sensor_task.c 卻讀 /dev/timer_soil_sensor
    • 請統一其中一邊
  • 歷史紀錄一直被清掉

    • server1.c 每次啟動會用 "w" 重建 sensor_log.csv
    • 改成 "a" 或改成每天一個 log 檔
  • 網路連不上

    • 確認 Server IP/Port 是否正確(跨網路需 NAT/Port Forwarding/ZeroTier/Remote.It 等)
    • 防火牆:ufw status / iptables -L
    • 先用 telnet <ip> <port>nc -vz <ip> <port> 測試

能精進的部分

  • 統一 SensorData struct 定義

    • 建議建立 include/sensordata.h 由所有程式共用
    • 避免 shared memory / network struct 不一致造成 padding/對齊問題
  • 通訊協定改成可擴充格式

    • 目前 sensor 上傳是直接傳 binary struct(受 endian、對齊影響)
    • 可改成 JSON/CBOR/Protobuf 並加上版本欄位
  • Web Server 增加基本安全性

    • 加上 token / 簡易登入,避免同網段任何人都能觸發澆水
    • 澆水加入 Rate limit / Cooldown
  • 改用 systemd 管理 daemon

    • 取代 tmux script,使開機自動啟動、可自動重啟、log 可由 journal 管理
  • 更精準的控制策略

    • Pump 改用 PWM/定量(例如 flow sensor 或計時+校正)
    • 加入「澆水後等待土壤回應時間」再二次判斷
  • Driver 更完整

    • soil driver 可加 ioctl 設定 ADS1115 通道/增益/取樣率
    • dht driver 可加 error counter、並把最後一次成功時間公開

授權與致謝

  • pump_driver.c 參考了 EmbeTronicX 的 GPIO char driver 範例(檔頭標示作者)。
  • 其他程式與 driver 為本專案整合與實作。

About

Embedded Operating System Project in NYCU

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published