T5 — Thiết kế DeviceFSM - congsinhv/fluxion GitHub Wiki

Thiết Kế DeviceFSM (State Machine Diagram)

Numbering chính thức: Mục 3.4 theo Master TOC

Issue: #24 — Thiết kế DeviceFSM Tuần: 5 | 21/04 – 27/04/2026


3.4.1 State Machine Diagram

stateDiagram-v2
    [*] --> idle : device created

    idle --> registered : register_complete
    idle --> active : enroll_complete
    idle --> released : release

    registered --> enrolled : enroll_start
    registered --> active : enroll_complete
    registered --> released : release

    enrolled --> active : enroll_complete
    enrolled --> released : release

    active --> locked : lock
    active --> active : send_message
    active --> released : release

    locked --> active : unlock
    locked --> locked : lock_message
    locked --> released : release

    released --> [*]

Chú thích:

  • idle: Trạng thái khởi tạo — device record được tạo trong DB
  • registered: Thiết bị đã đăng ký UDID + serial, chờ enrollment
  • enrolled: MDM profile đã upload, chờ certificate validation
  • active: Hoạt động bình thường — hỗ trợ: lock, send_message, release
  • locked: Thiết bị bị khóa — hỗ trợ: unlock, lock_message, release
  • released: Trạng thái kết thúc (terminal) — không có outgoing transitions
  • send_messagelock_messageinternal transitions (không đổi state)

3.4.2 Transition Table

# Trigger Source Destination Guard Condition Action
T1 register_complete idle registered Device có valid UDID + serial INSERT device record, log transition
T2 enroll_start registered enrolled MDM profile uploaded Update state, log transition
T3 enroll_complete idle, registered, enrolled active Device có valid certificate + enrollment complete Update state, notify dashboard via subscription
T4 lock active locked role ∈ {admin, operator} AND action.from_state_id == device.state_id Send APNS lock command, update state, log audit
T5 unlock locked active role ∈ {admin, operator} AND action.from_state_id == device.state_id Send APNS unlock command, update state, log audit
T6 send_message active active (internal) action.from_state_id == device.state_id Send APNS push message, không đổi state, log
T7 lock_message locked locked (internal) action.from_state_id == device.state_id Send message hiển thị trên lock screen, không đổi state, log
T8 release active, locked released role ∈ {admin, operator} Trigger: khách hoàn tất hợp đồng trả góp (graduation). Unenroll MDM profile, máy thành sở hữu 100% khách. KHÔNG wipe data của khách. Log audit

Lưu ý: Transitions không hardcode trong code. Chúng được biểu diễn thông qua quan hệ giữa các bảng state, action, và policy trong database — xem mục 3.4.5.


3.4.3 Guard Conditions

Guard Áp dụng cho Logic
is_not_busy tất cả actions device.assigned_action_id IS NULL — reject nếu device đang có action pending
is_valid_transition tất cả actions action.from_state_id == device.state_id — action chỉ valid từ đúng source state
is_admin_or_operator release jwt.role IN ('admin', 'operator')
is_admin_or_operator lock, unlock, send_message, lock_message jwt.role IN ('admin', 'operator')
has_valid_cert enroll_complete Device certificate validated by SCEP
not_released tất cả (trừ từ released) device.state != 'released'

Invalid transition handling: Nếu trigger không hợp lệ cho state hiện tại (ví dụ: lock khi device đang idle), hệ thống:

  1. action.from_state_id != device.state_id → reject INVALID_TRANSITION
  2. Log attempted transition: {device_id, current_state, attempted_trigger, timestamp}
  3. Return error response qua AppSync

3.4.4 Actions (On-Enter / On-Exit)

On-Enter Actions

State Action Mô tả
registered log_transition Ghi device_events: type=state_change
enrolled log_transition Ghi device_events
active log_transition, notify_dashboard Ghi event + push AppSync subscription
locked log_transition, notify_dashboard Ghi event + push subscription + update action_execution
released log_transition, notify_dashboard, unenroll_mdm Ghi event + unenroll MDM profile (KHÔNG wipe data — graduation khi khách trả góp xong)

Internal Transition Actions

Trigger Action Mô tả
send_message dispatch_apns_push, log_event Gửi APNS notification + log vào device_events
lock_message dispatch_apns_lock_msg, log_event Gửi message tới lock screen + log

3.4.5 FSM Implementation — Implicit DB-Driven

Theo wiki nghiên cứu (T3 — FSM), Fluxion dùng DB-driven FSM — transitions được biểu diễn qua quan hệ giữa 3 bảng: state, policy, action. Không có FSM class hay transition table riêng.

FSM = DB Schema

┌──────────┐     from_state_id     ┌──────────┐
│  state   │◄─────────────────────│  action   │
│          │                       │          │
│ id       │     apply_policy_id   │ id       │
│ name     │       ┌──────────┐   │ name     │
└──────────┘       │  policy   │◄──│ from_... │
      ▲            │          │   │ apply_.. │
      │            │ id       │   │ config   │
      │            │ name     │   └──────────┘
      │ state_id   │ state_id─┼───────┘
      └────────────│ color    │
                   └──────────┘

Cách đọc:

  • action.from_state_id = source state (device phải đang ở state này)
  • action.apply_policy_idpolicy.state_id = destination state (state mới sau transition)
  • Không cần bảng transitions riêng — quan hệ action → policy → state IS the transition

DB Schema

-- Các trạng thái hợp lệ
CREATE TABLE services (
    id SMALLINT PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,       -- Postpaid, Supply Chain
    is_enabled BOOL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE states (
    id SMALLINT PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,       -- Idle, Registered, Enrolled, Active, Locked, Released
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Policy: quy tắc áp dụng cho device (mỗi policy gắn với 1 state)
CREATE TABLE policies (
    id SMALLINT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,             -- Idle, Registered, Enrolled, Active, Locked, Released
    service_type_id SMALLINT REFERENCES services(id),               -- loại dịch vụ (Postpaid, etc.)
    state_id SMALLINT REFERENCES states(id), -- state mà policy này đại diện
    color VARCHAR(6),                       -- hex color
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Action: hành động kích hoạt transition
CREATE TABLE actions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100) NOT NULL,             -- "Lock", "Unlock", "Register", "Send Message", "Lock Message", "Release".
    action_type_id SMALLINT NOT NULL,
    from_state_id SMALLINT REFERENCES states(id),       -- source state (NULL = wildcard)
    service_type_id SMALLINT REFERENCES services(id),
    apply_policy_id SMALLINT REFERENCES policies(id),   -- destination policy → state
    configuration JSONB,                     -- tham số cho action (message text, etc.)
    ext_fields JSONB,                        -- metadata mở rộng
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Milestone: lịch sử thực thi (device history)
CREATE TABLE milestones (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    device_id UUID NOT NULL REFERENCES devices(id),
    assigned_action_id UUID REFERENCES actions(id),
    policy_id SMALLINT REFERENCES policies(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    ext_fields JSONB
);

-- Seed states
INSERT INTO services (id, name, is_enabled) VALUES
    (1, 'Inventory', True),
    (2, 'Supply Chain', False),
    (3, 'Postpaid', True);

INSERT INTO states (id, name) VALUES
    (1, 'Idle'),
    (2, 'Registered'),
    (3, 'Enrolled'),
    (4, 'Active'),
    (5, 'Locked'),
    (6, 'Released');

-- Seed policies (mỗi policy → 1 state)
INSERT INTO policies (id, name, state_id, service_type_id) VALUES
    (1, 'Idle', 1, 1),
    (2, 'Registered', 2, 3),
    (3, 'Enrolled', 3, 3),
    (4, 'Active', 4, 3),
    (5, 'Locked', 5, 3),
    (6, 'Released', 6, 3);

-- Seed actions (transitions implicit qua from_state_id → apply_policy_id)
INSERT INTO actions (name, action_type_id, from_state_id, apply_policy_id, service_type_id) VALUES
    ('Upload', 1, NULL, 1, NULL),
    ('Register', 2, 1, 2, 1),
    ('Checkin', 3, 2, 3, 3),
    ('Activate', 4, 3, 4, 3),
    ('Lock', 5, 4, 5, 3),
    ('Unlock', 6, 5, 4, 3),
    ('Send Message', 7, 4, 4, 3),
    ('Lock Message', 8, 5, 5, 3),
    -- Release: wildcard — từ bất kỳ state nào (idle=1, registered=2, enrolled=3, active=4, locked=5)
    ('Release', 9, 1, 6, 3),
    ('Release', 9, 2, 6, 3),
    ('Release', 9, 3, 6, 3),
    ('Release', 9, 4, 6, 3),
    ('Release', 9, 5, 6, 3),
    ('Deregister', 10, 4, 1, 3),
    ('Deregister', 10, 5, 1, 3);

FSM Validation Logic (trong action-resolver)

def validate_action(device, action_id):
    # Guard 1: Device đang pending?
    if device.assigned_action_id is not None:
        raise DevicePendingError(device.id)

    # Query action từ DB
    action = db.query(f"SELECT * FROM {schema}.actions ta WHERE ta.id = :action_id", {'action_id': action_id})

    # Guard 2: Transition hợp lệ?
    if action.from_state_id != device.state_id:
        raise InvalidTransitionError(device.state_id, action.name)

    return action  # Valid — proceed to pipeline

Lợi ích so với transition table riêng

Lợi ích Mô tả
Simpler schema 3 tables thay vì 5 (không cần device_states + device_transitions)
Policy-centric Device state gắn với policy — dễ mở rộng policy-based management
Single source of truth action table vừa define transition vừa define command type
Configurable Thêm/sửa actions bằng INSERT/UPDATE, không cần deploy lại code
Auditable milestones table ghi lịch sử mọi state change

3.4.6 Action Execution Flow (Command Pipeline)

3-Layer Flow

BE (action-resolver)  → validate + enqueue
BE (action-trigger)   → INSERT action_executions, publish command → SNS
OEM (apple-process-action)     → cache command + APNS silent push
OEM (apple-process-action)     → serve command to device, receive result, publish event → SNS
BE (checkin-handler)  → UPDATE action_executions + devices + INSERT milestones

Xem sequence diagram chi tiết tại T5 — Command Pipeline mục 3.27.

Step-by-Step Breakdown

Step Layer Component Actions DB Changes
1 BE action-resolver Validate busy + transition SELECT only
2 BE action-trigger Create execution, publish command INSERT action_executions, UPDATE devices.assigned_action_id
3 OEM apple-process-action Cache command + send APNS — (cache only, no DB)
4 OEM apple-process-action Device polls → serve command — (read cache, no DB)
5 OEM apple-process-action Device result → publish event — (SNS only, no DB)
6 BE checkin-handler Process ACTION_COMPLETED event UPDATE action_executions, devices, INSERT milestones

Hai FSM Liên Kết

action_executions FSM:  ACTION_PENDING ──→ ACTION_SENT ──→ ACTION_COMPLETED
                                                                ↓ (trigger)
Device state:           policy_A (active)  ──────────────→  policy_B (locked)
                        via device.current_policy_id = action.apply_policy_id
  • action_executions FSM: theo dõi trạng thái lệnh (3 states: PENDING→SENT→COMPLETED, + FAILED)
  • Device state: theo dõi trạng thái thiết bị (implicit qua current_policy_id → policy.state_id)
  • Khi checkin-handler nhận ACTION_COMPLETED event → cập nhật device.current_policy_id

Checkin Events (OEM → BE)

Event Trigger checkin-handler Action
DEVICE_TOKEN_UPDATE Device TokenUpdate check-in UPSERT device_tokens
DEVICE_RELEASED Device CheckOut UPDATE devices → Released policy
ACTION_COMPLETED Device Status=Acknowledged UPDATE action_executions + devices.current_policy_id + INSERT milestones
ACTION_FAILED Device Status=Error UPDATE action_executions → FAILED, clear assigned_action_id

Error Handling

Scenario Layer Component Action
Device đang busy BE action-resolver Reject: assigned_action_id IS NOT NULL
Invalid transition BE action-resolver Reject: action.from_state_id != device.state_id
APNS failure OEM apple-process-action Publish ACTION_FAILED event → BE
Device timeout BE timeout-handler Periodic check → mark stale as ACTION_FAILED
Device báo lỗi OEM apple-process-action Publish ACTION_FAILED event → BE

Nguyên tắc: OEM không update DB. Mọi DB changes đi qua checkin-handler (BE).


Tại Sao Chọn Flat FSM Thay Vì Harel Statechart

Mặc dù nghiên cứu lý thuyết (xem T3 — FSM và Harel Statechart) đã khảo sát Harel Statechart với các tính năng nâng cao (hierarchical states, parallel regions, history states), Fluxion chọn triển khai Flat FSM với guard conditions vì các lý do sau:

  1. Quy mô đủ nhỏ: DeviceFSM chỉ có 6 trạng thái — không cần phân cấp (hierarchy) hay vùng song song (parallel regions).
  2. Nguyên tắc KISS: Flat FSM đơn giản hơn đáng kể về mặt triển khai và bảo trì. Harel Statechart phù hợp cho hệ thống phức tạp hơn (>20 states, nested behaviors).
  3. Guard conditions đủ mạnh: Các điều kiện is_not_busy, is_valid_transition, is_admin đáp ứng toàn bộ logic kiểm soát chuyển trạng thái mà không cần cơ chế hierarchy.
  4. DB-driven thay thế complexity: Thay vì dùng statechart engine, DeviceFSM được biểu diễn hoàn toàn qua quan hệ giữa 3 bảng (states, policies, actions) trong PostgreSQL — linh hoạt hơn hardcoded transitions và dễ mở rộng qua dữ liệu thay vì code.

Biểu Đồ Lớp — DeviceFSM (Class Diagram)

Biểu đồ lớp DeviceFSM

Hình 3.4.1: Biểu đồ lớp mô tả quan hệ giữa các thực thể trong DeviceFSM. Xanh dương = bảng cấu hình (Config), xanh lá = bảng lõi (Core), vàng = bảng giao dịch (Transactional), tím = kiểu liệt kê (Enum).


Kết Luận

Thiết kế DeviceFSM của Fluxion thể hiện cách tiếp cận DB-driven Flat FSM — một giải pháp thực dụng cân bằng giữa đủ biểu đạt (expressiveness) và đủ đơn giản (simplicity) cho phạm vi đồ án. Sáu trạng thái (Idle → Registered → Enrolled → Active ⇄ Locked → Released) phủ toàn bộ vòng đời thiết bị iOS trong môi trường MDM doanh nghiệp. Tám transitions với guard conditions rõ ràng (is_not_busy, is_valid_transition, is_admin_or_operator) đảm bảo tính nhất quán trạng thái mà không cần FSM library hay statechart engine riêng biệt.

Điểm khác biệt cốt lõi là FSM không tồn tại như một class hay transition table độc lập — thay vào đó, toàn bộ logic chuyển trạng thái được mã hóa trong quan hệ giữa 3 bảng PostgreSQL (states, policies, actions). Cách tiếp cận này cho phép thêm/sửa transitions bằng SQL INSERT/UPDATE mà không cần deploy lại code, đồng thời bảng milestones cung cấp audit trail đầy đủ cho mọi state change.

Quyết định chọn Flat FSM thay vì Harel Statechart phản ánh nguyên tắc KISS: với 6 trạng thái và không có nested behaviors, overhead của hierarchical statechart là không cần thiết. Guard conditions kết hợp với DB-driven approach cung cấp đủ sức mạnh để xử lý toàn bộ business logic của Fluxion MDM, bao gồm cả các internal transitions (send_message, lock_message) và wildcard transition (release từ bất kỳ state nào).

Tài Liệu Tham Khảo

[1] Harel, D. Statecharts: A Visual Formalism for Complex Systems. Science of Computer Programming, 8(3), 231–274. 1987.

[2] Hopcroft, J., Motwani, R., Ullman, J. Introduction to Automata Theory, Languages, and Computation, 3rd ed. Pearson, 2006.

[3] Samek, M. Practical UML Statecharts in C/C++, 2nd ed. Newnes, 2008.

[4] pytransitions. transitions — A lightweight state machine library for Python. GitHub, 2024.