軟體需求規格檔案(SRS)是軟體開發流程中不可或缺的根本,清晰的SRS能有效減少後續開發階段的歧義與錯誤。本文將探討如何撰寫結構良好、內容完整的SRS,並以實際案例說明如何將抽象的需求轉化為具體的規格描述。從系統架構、功能需求到非功能需求,SRS都必須鉅細靡遺地定義,才能確保開發團隊對專案目標有一致的理解。此外,我們也將探討使用案例分析的應用,如何從使用者角度出發,捕捉系統的行為模式,並將其轉化為可驗證的軟體需求,最終提升軟體開發的效率和品質。

軟體需求規格組織與檔案結構

軟體需求規格(SRS)是軟體開發過程中的關鍵檔案,用於描述軟體的功能、效能、設計限制和屬性。一個組織良好的SRS對於確保專案順利進行至關重要。本篇文章將探討SRS的組織方式、內容結構以及相關的最佳實踐。

SRS的組織方式

面對複雜的系統,SRS的組織方式至關重要。以下是幾種常見的組織方法:

  1. 依系統模式組織:適用於具有多種操作模式的系統,如嵌入式系統的低功耗模式和正常模式。
  2. 依使用者類別組織:針對不同類別的使用者,如初學者、進階使用者和管理員,提供不同的功能和介面。
  3. 依物件類別組織:根據軟體系統中的實體物件進行組織,反映現實世界中的物件型別。
  4. 依功能組織:將需求按照實作的功能進行分類別,特別適用於具有使用者介面的應用程式。
  5. 依輸入刺激組織:針對以處理不同輸入為主要活動的應用程式,按輸入型別進行組織。
  6. 依輸出回應組織:對於產生多種輸出的應用程式,可以按輸出回應進行組織。
  7. 依功能層次組織:一種常見的備用方案,透過功能性分組,如共同輸入、命令輸出、資料函式庫操作和程式中的資料流動。

SRS的內容結構

一份完整的SRS通常包含以下部分:

  1. 簡介:介紹軟體的目的、範圍和定義的術語。
  2. 整體描述:描述軟體的整體架構、功能和使用者介面。
  3. 特定需求:列出詳細的軟體需求,包括功能需求、非功能需求等。
  4. 設計目標:描述可選的功能或效能改進目標,供設計人員參考。
  5. 支援資訊:包括目錄、附錄、詞彙表和索引,以方便讀者理解和使用SRS。

例項分析:游泳池監控系統SRS

本文提供了一個簡化的游泳池監控系統SRS範例,展示瞭如何按照上述結構組織SRS。該範例包括了簡介、整體描述、特定需求等部分,並使用特定的標籤格式來標識需求。

程式碼例項與解析

def calculate_pool_water_level(sensor_data):
    # 計算水位高度
    water_level = sensor_data['water_level']
    return water_level

# 使用範例
sensor_data = {'water_level': 50}
water_level = calculate_pool_water_level(sensor_data)
print(f"目前水位:{water_level} cm")

#### 內容解密:
1. `calculate_pool_water_level`函式用於計算游泳池的水位高度
2. 該函式接收一個包含感測器資料的字典作為輸入並提取水位資料
3. 函式傳回計算後的水位高度值
4. 在使用範例中我們模擬了一個感測器資料並呼叫函式計算水位最後列印出結果

游泳池監控系統(SPM)需求規格檔案

1. 簡介

本檔案旨在定義游泳池監控系統(SPM)的需求規格,確保系統能夠自動維持水池的水位。SPM的需求規格標籤(SRS)將根據需要進行編號,若需新增子需求,將在SRS標籤後附加小數編號(例如POOL_SRS_040.5)。

2. 整體描述

2.1 產品視角

SPM設計假設使用與Arduino相容的單板電腦(SBC),軟體將透過Arduino相容的函式庫與硬體介面。

2.1.1 系統介面

軟體將使用Arduino相容的函式庫與硬體互動。

2.1.2 使用者介面

使用者介面包括一個四行顯示器(每行至少20個字元)、六個按鈕(上、下、左、右、取消/傳回和選擇/確認)以及一個旋轉編碼器。

2.1.3 硬體介面

SBC至少需要提供16個數位輸入、1個類別比輸入、2個數位輸出、少量的非揮發性可寫入記憶體(如EEPROM)以儲存組態值、一個實時時鐘(RTC)以及一個監控軟體操作的看門狗計時器。SPM將連線水池感測器以判斷水位,並透過螺線管介面控制水源的開關。

// 示例程式碼:初始化硬體介面
void initHardware() {
    // 初始化數位輸入
    for (int i = 0; i < 16; i++) {
        pinMode(i, INPUT);
    }
    // 初始化類別比輸入
    pinMode(A0, INPUT);
    // 初始化數位輸出
    pinMode(OUTPUT_PIN_1, OUTPUT);
    pinMode(OUTPUT_PIN_2, OUTPUT);
}

#### 內容解密:

此段程式碼初始化了硬體介面,包括設定數位輸入、類別比輸入和數位輸出的針腳模式。其中,數位輸入用於連線水池感測器,類別比輸入用於讀取類別比感測器的值,而數位輸出則用於控制螺線管閥門。

2.1.4 軟體介面

SPM軟體是自包含的,不提供外部介面,也不需要任何外部軟體介面。

2.1.5 通訊介面

SPM是自包含的,不與外界進行通訊。

2.1.6 記憶體限制

由於SPM執行在與Arduino相容的SBC上,將會有嚴重的記憶體限制,具體取決於所選的型號(例如,Arduino Mega 2560 SBC僅提供8KB的靜態RAM)。

2.1.7 操作

SPM以全天候模式執行,24小時不間斷監控水池水位,並在水位過低時自動開啟水源補水。為避免因感測器故障導致的水池溢水,SPM將限制每日的補水時間(使用者可選)。

2.2 現場適應需求

對於此版本的SPM,現場適應需求很少。系統外部介面僅包括電源和水源(透過螺線管閥門介面)。

2.3 產品功能

SPM使用七個水位感測器來判斷水池水位:三個數位感測器提供低水位指示,三個數位感測器提供高水位指示,以及一個類別比感測器提供水位深度指示。系統採用「二取三」的組態來避免因感測器故障導致的水池溢水或無法正常補水。

// 示例程式碼:讀取感測器資料
int readSensorData() {
    int lowLevelCount = 0;
    for (int i = 0; i < 3; i++) {
        if (digitalRead(LOW_LEVEL_SENSOR_PIN + i) == HIGH) {
            lowLevelCount++;
        }
    }
    if (lowLevelCount >= 2) {
        return LOW_POOL_LEVEL;
    }
    // 其他邏輯...
}

#### 內容解密:

此函式讀取低水位感測器的狀態,並根據「二取三」組態判斷是否為低水位狀態。如果至少兩個感測器指示低水位,則傳回LOW_POOL_LEVEL

2.4 使用者特徵

SPM的使用者分為兩類別:技術人員和最終使用者。技術人員負責安裝和調整SPM,而最終使用者則是日常操作SPM的泳池擁有者。

2.5 約束條件

SPM的設計必須防止意外溢水和過度用水。軟體必須足夠強壯,能夠在感測器故障時停止補水操作。系統應具備故障安全設計,在斷電時自動關閉水閥,並使用看門狗計時器監控軟體執行狀態。

泳池水位監控系統(SPM)需求規格說明

外部介面需求

泳池水位監控系統(SPM)需具備多項外部介面以滿足系統功能需求,包括導航按鈕、旋轉編碼器輸入、水位感測器輸入等。

按鍵輸入介面

  • 系統需提供數字輸入介面供導航按鈕使用,包括向上、向下、向左、向右、取消/傳回及選擇/確認按鈕。
  • 旋轉編碼器需提供四個數字輸入介面以支援正交編碼輸入。

水位感測器介面

  • 系統需支援三個主要、次要及第三水位低感測器的數字輸入。
  • 同時需支援三個主要、次要及第三水位高感測器的數字輸入。
  • 需具備類別比輸入介面(至少8位元解析度)以讀取水位感測器的數值。

輸出控制介面

  • 系統需提供兩個數字輸出介面以控制水源電磁閥。

功能需求

SPM需具備多項功能以實作自動化水位監控與控制。

日期時間設定

  • 使用者可透過使用者介面設定RTC(實時時鐘)日期與時間。

最大注水時間設定

  • 系統需允許使用者設定最大注水時間,單位為小時與分鐘。
  • 最大注水時間不可超過12小時。

自動檢查水位

  • 系統需每日於指定時間檢查泳池水位,並在必要時進行注水。
  • 使用者可透過介面設定每日檢查水位的時間,預設為凌晨1:00。

注水作業控制

  • 當至少兩個低水位感測器指示水位過低時,系統啟動注水作業。
  • 注水過程中,若累計注水時間超過最大注水時間或至少兩個高水位感測器指示水位過高,則停止注水。
  • 若類別比水位感測器顯示水位在半小時內未上升,系統將關閉水流。

手動模式

  • 系統支援手動注水模式,可由使用者透過介面啟動或關閉。
  • 手動模式下,系統忽略最大注水時間限制及水位感測器的狀態。

系統監控

  • 軟體需定期更新系統監控計時器(watchdog timer),其超時週期應在5至60秒之間。

效能需求

SPM需滿足特定的效能要求以確保系統的穩定運作。

按鍵去抖處理

  • 系統需對所有按鍵輸入進行去抖處理。

旋轉編碼器讀取

  • 系統需能夠準確讀取旋轉編碼器的輸入變化,不得遺失任何輸入變化。

時間準確性

  • 系統需維持最大注水時間及每日檢查水位時間的準確性,誤差不超過一分鐘。

資料儲存需求

SPM需將關鍵資料儲存在非揮發性記憶體中,以確保系統重啟後資料不丟失。

最大注水時間儲存

  • 最大注水時間需儲存在非揮發性記憶體中。

每日檢查時間儲存

  • 每日檢查水位的時間需儲存在非揮發性記憶體中。

系統屬性:可靠性

由於SPM需24/7/365不間斷運作,系統的強健性(robustness)是設計中的關鍵因素。特別是,系統應設計為故障安全(fail-safe),即在軟體或其他故障發生時,能夠自動關閉水閥,防止意外事故的發生。

程式碼實作範例:

#include <stdint.h>
#include <stdbool.h>

// 定義按鍵輸入結構體
typedef struct {
    bool up;
    bool down;
    bool left;
    bool right;
    bool cancel;
    bool enter;
} button_inputs_t;

// 定義旋轉編碼器輸入結構體
typedef struct {
    bool phaseA;
    bool phaseB;
} rotary_encoder_t;

// 定義水位感測器輸入結構體
typedef struct {
    bool lowPrimary;
    bool lowSecondary;
    bool lowTertiary;
    bool highPrimary;
    bool highSecondary;
    bool highTertiary;
    uint8_t analogLevel; // 8-bit 解析度
} water_level_sensors_t;

// 主程式邏輯範例
void main_loop(void) {
    // 讀取按鍵輸入
    button_inputs_t buttons = read_button_inputs();
    
    // 讀取旋轉編碼器狀態
    rotary_encoder_t encoder = read_rotary_encoder();
    
    // 讀取水位感測器狀態
    water_level_sensors_t waterLevel = read_water_level_sensors();
    
    // 更新系統狀態
    update_system_state(buttons, encoder, waterLevel);
    
    // 執行注水控制邏輯
    control_water_fill(waterLevel);
    
    // 更新監控計時器
    update_watchdog();
}

#### 內容解密:
1. **程式碼結構說明**:上述程式碼展示瞭如何定義不同的輸入結構體來處理按鍵、旋轉編碼器和水位感測器的輸入,並在主迴圈中讀取這些輸入來更新系統狀態和控制注水作業。
2. **按鍵輸入處理**:`read_button_inputs`函式用於讀取按鍵狀態,需實作去抖動邏輯以確保輸入的準確性。
3. **旋轉編碼器處理**:`read_rotary_encoder`函式用於讀取旋轉編碼器的狀態,需確保能夠正確處理正交編碼訊號,避免遺失任何變化。
4. **水位感測器讀取**:`read_water_level_sensors`函式用於讀取各類別水位感測器的狀態,包括數字和類別比訊號,需確保正確解析感測器的數值。
5. **系統狀態更新與控制邏輯**:`update_system_state`和`control_water_fill`函式根據讀取的輸入狀態更新系統內部狀態並執行相應的控制邏輯,例如啟動或停止注水作業。
6. **監控計時器更新**:`update_watchdog`函式用於定期更新監控計時器,以防止系統超時重啟,需確保更新頻率符合規格要求。

需求檔案製作與使用案例分析

在軟體開發過程中,需求檔案的製作是至關重要的第一步。本章節將探討如何建立需求檔案以及使用案例的分析。

軟體需求規格(SRS)範例分析

以下是一個簡化的軟體需求規格範例,用於監控游泳池的水位和化學性質。

功能性需求

軟體需具備監控水位、pH值、氧化還原電位(ORP)等功能,並能在異常狀況下發出警示。

非功能性需求

可用性

軟體需能持續運作(24/7/365),並至少達到99.99%的正常執行時間。

安全性

由於系統為封閉、斷網、隔離系統,因此無特定安全需求。

可維護性

除專業軟體工程專案的一般預期外,無其他特殊維護需求。

可移植性

軟體預期執行於Arduino級裝置,無其他移植需求。

使用案例的建立

使用案例是根據使用者故事(user story)所建立的一系列情境。以下以Plantation Productions的數位資料擷取和控制(DAQ)系統為例,說明使用案例的建立。

DAQ系統簡介

DAQ系統由多個相互連線的電路板組成,包括類別比/數位輸入輸出板、數位輸出板和單板電腦(SBC)Netburner MOD54415。這些元件使系統設計師能夠讀取各種輸入、計算結果並根據這些輸入做出決策,然後控制外部裝置。

初始化設定

Netburner MOD54415包含一組八個DIP開關,用於初始化各種系統元件。這些DIP開關的功能包括啟用/停用不同埠的命令處理、指定乙太網路連線數量和選擇乙太網路地址等。

使用案例分析

角色

系統使用者是本使用案例中的唯一角色。

觸發條件

所有使用案例均由系統啟動觸發,系統在啟動時讀取DIP開關設定並根據這些設定進行初始化。

情境/事件流程

根據不同的DIP開關設定,系統會執行不同的初始化操作,例如建立任務來處理來自不同埠的輸入命令。

相關需求

相關需求提供了與DAQ系統SRS的交叉參考,使需求與使用案例之間建立明確的對應關係。

程式碼範例與解說

// 初始化DIP開關讀取函式
void initDIPSwitches() {
  // 設定DIP開關的輸入模式
  pinMode(DIP_SWITCH_1, INPUT);
  pinMode(DIP_SWITCH_2, INPUT);
  // ...
}

// 根據DIP開關設定啟用或停用命令處理
void enableCommandProcessing() {
  if (digitalRead(DIP_SWITCH_1) == HIGH) {
    // 啟用RS-232命令處理
    createTask(RS232_TASK);
  }
  if (digitalRead(DIP_SWITCH_2) == HIGH) {
    // 啟用USB命令處理
    createTask(USB_TASK);
  }
  // ...
}

#### 內容解密:
1. **`initDIPSwitches`函式**:設定DIP開關的輸入模式,以讀取其狀態。
2. **`enableCommandProcessing`函式**:根據DIP開關的狀態,決定是否啟用特定埠的命令處理任務。
3. **任務建立**:`createTask`函式用於建立新的任務來處理來自不同埠的輸入命令,這些任務負責讀取字元並將完整的文字行傳送到系統的命令處理器。

## 需求檔案建立與使用案例分析

DAQ系統的軟體需求規格SRS)建立過程中,使用案例(Use Case)扮演著至關重要的角色。以下將詳細闡述如何從使用案例轉換為正式的軟體需求。

### 使用案例與需求之間的關係

使用案例描述了系統與外部實體之間的互動行為,而這些互動行為正是軟體需求的來源。透過分析使用案例,可以提取出系統需要滿足的功能性需求。

### 啟用/停用偵錯模式使用案例分析

#### 目標
啟用或停用DAQ系統的偵錯輸出功能

#### 前置條件
系統已完成啟動。

#### 結束條件
偵錯模式根據設定被啟用或停用。

#### 事件流程

1. 系統初始化過程中讀取DIP開關的狀態
2. 儲存DIP開關8的狀態(開 = 偵錯模式啟用,關 = 偵錯模式停用)。
3. DIP開關8為開且DIP開關2USB模式)為關時,啟用偵錯模式。
4. 啟動`maintPrintf`任務。

#### 相關需求

- DAQ_SRS_721_001: PPDAQ偵錯模式啟用
- DAQ_SRS_721_002: PPDAQ偵錯模式停用

### 從使用案例建立DAQ軟體需求

將非正式的使用案例轉換為正式的軟體需求,需要提取使用案例中的關鍵資訊、補充缺失的細節,並將結果結構化為需求規範。

#### 示例:啟用/停用偵錯模式

初看之下,「啟用/停用偵錯模式」的使用案例似乎只產生一個需求:

當Netburner DIP開關8設為ON且USB(DIP開關2)未啟用時,PPDAQ軟體應運作於特殊偵錯模式;當開關8設為OFF或DIP開關2啟用時,則運作於非偵錯模式。

然而,這實際上包含了兩個獨立的需求:
1. 當Netburner DIP開關8設為ON且USB(DIP開關2)未啟用時,PPDAQ軟體應運作於特殊偵錯模式。
2. 當開關8設為OFF或DIP開關2啟用時,PPDAQ軟體應運作於非偵錯模式。

這兩個需求由分號分隔,而非單純由「和」或「或」連線詞決定。正確地拆分和表述這些需求對於確保軟體功能的正確實作至關重要。

### 程式碼實作與邏輯解說

#### 程式碼範例
```c
void readDIPSwithes() {
    // 讀取DIP開關狀態
    uint8_t dipSwitchState = getDIPSwitchState();
    
    // 儲存DIP開關8的狀態
    bool debugModeEnabled = (dipSwitchState & (1 << 7)) != 0; // DIP開關8
    
    // 檢查USB模式是否啟用
    bool usbModeEnabled = (dipSwitchState & (1 << 1)) != 0; // DIP開關2
    
    // 根據DIP開關狀態決定是否啟用偵錯模式
    if (debugModeEnabled && !usbModeEnabled) {
        enableDebugMode();
    } else {
        disableDebugMode();
    }
}

void enableDebugMode() {
    // 啟動maintPrintf任務
    startTask(maintPrintfTask, DEBUG_MODE_PRIORITY);
}

void disableDebugMode() {
    // 停用maintPrintf任務
    stopTask(maintPrintfTask);
}

內容解密:

  1. readDIPSwithes函式:負責讀取DIP開關的狀態,並根據DIP開關8和2的狀態決定是否啟用偵錯模式。

    • 首先,呼叫getDIPSwitchState()函式取得DIP開關的當前狀態。
    • 然後,檢查DIP開關8的狀態以確定偵錯模式是否應該被啟用。
    • 再者,檢查DIP開關2的狀態以判斷USB模式是否啟用。
    • 最後,根據這兩個條件決定是否呼叫enableDebugMode()disableDebugMode()
  2. enableDebugMode函式:負責啟動maintPrintf任務以啟用偵錯模式。

    • 呼叫startTask函式,將maintPrintfTask任務以特定優先順級(DEBUG_MODE_PRIORITY)啟動。
  3. disableDebugMode函式:負責停用maintPrintf任務以停用偵錯模式。

    • 呼叫stopTask函式,停止正在執行的maintPrintfTask任務。

透過這種方式,可以清晰地理解程式碼如何根據DIP開關的狀態控制偵錯模式的啟用與停用,從而滿足相關的軟體需求。