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_messagevàlock_messagelà internal 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àpolicytrong 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:
action.from_state_id != device.state_id→ rejectINVALID_TRANSITION- Log attempted transition:
{device_id, current_state, attempted_trigger, timestamp} - 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_id→policy.state_id= destination state (state mới sau transition)- Không cần bảng transitions riêng — quan hệ
action → policy → stateIS 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-handlernhậnACTION_COMPLETEDevent → cập nhậtdevice.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:
- 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).
- 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).
- 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. - 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)

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.